viva_camctl/lib.rs
1//! `viva-camctl` — the GenICam diagnostic CLI, as a library.
2//!
3//! The command-line surface lives in [`cli`] and is driven by [`run`], so there
4//! is exactly one implementation of it. The `viva-camctl` binary calls [`run`],
5//! and so does the `viva-camctl` console script that `pip install viva-genicam`
6//! installs — the Python users we most often ask for a `viva-camctl report` are
7//! the ones least able to build it from source, and a second entry point would
8//! be a second thing to keep in step.
9
10pub mod cli;
11pub mod cmd_bench;
12pub mod cmd_chunks;
13pub mod cmd_events;
14pub mod cmd_get;
15pub mod cmd_list;
16pub mod cmd_report;
17pub mod cmd_set;
18pub mod cmd_set_ip;
19pub mod cmd_stream;
20pub mod cmd_usb;
21pub mod cmd_xml;
22pub mod common;
23
24use std::ffi::OsString;
25
26use clap::Parser;
27
28/// Exit code for a command that ran but failed, matching `ExitCode::FAILURE`.
29const FAILURE: u8 = 1;
30
31/// Parse `args`, run the selected command to completion, and return the exit
32/// code the process should use.
33///
34/// `args` is a complete argv, program name included. Diagnostics go to stderr in
35/// the same form the binary has always produced — `anyhow`'s `Debug` rendering,
36/// so an error's source chain survives. The code is clap's own for a usage error
37/// or for `--help`, and 1 for a command that ran and failed.
38///
39/// A plain `u8` rather than [`ExitCode`](std::process::ExitCode) because
40/// `ExitCode` cannot be inspected, and the Python entry point has to hand the
41/// number back to the interpreter rather than exit the process itself.
42///
43/// Builds its own multi-thread tokio runtime rather than requiring one, so it
44/// works from a plain `fn main` and from a Python interpreter that has none.
45pub fn run<I, T>(args: I) -> u8
46where
47 I: IntoIterator<Item = T>,
48 T: Into<OsString> + Clone,
49{
50 let cli = match cli::Cli::try_parse_from(args) {
51 Ok(cli) => cli,
52 Err(err) => {
53 // Covers `--help` and `--version` too, which clap reports as errors
54 // carrying an exit code of 0.
55 let _ = err.print();
56 return u8::try_from(err.exit_code()).unwrap_or(FAILURE);
57 }
58 };
59
60 let runtime = match tokio::runtime::Builder::new_multi_thread()
61 .enable_all()
62 .build()
63 {
64 Ok(runtime) => runtime,
65 Err(err) => {
66 eprintln!("Error: could not start the async runtime: {err}");
67 return FAILURE;
68 }
69 };
70
71 match runtime.block_on(cli::dispatch(cli)) {
72 Ok(()) => 0,
73 Err(err) => {
74 eprintln!("Error: {err:?}");
75 FAILURE
76 }
77 }
78}