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_execute;
15pub mod cmd_get;
16pub mod cmd_list;
17pub mod cmd_report;
18pub mod cmd_set;
19pub mod cmd_set_ip;
20pub mod cmd_stream;
21pub mod cmd_usb;
22pub mod cmd_xml;
23pub mod common;
24
25use std::ffi::OsString;
26
27use clap::Parser;
28
29/// Exit code for a command that ran but failed, matching `ExitCode::FAILURE`.
30const FAILURE: u8 = 1;
31
32/// Parse `args`, run the selected command to completion, and return the exit
33/// code the process should use.
34///
35/// `args` is a complete argv, program name included. Diagnostics go to stderr in
36/// the same form the binary has always produced — `anyhow`'s `Debug` rendering,
37/// so an error's source chain survives. The code is clap's own for a usage error
38/// or for `--help`, and 1 for a command that ran and failed.
39///
40/// A plain `u8` rather than [`ExitCode`](std::process::ExitCode) because
41/// `ExitCode` cannot be inspected, and the Python entry point has to hand the
42/// number back to the interpreter rather than exit the process itself.
43///
44/// Builds its own multi-thread tokio runtime rather than requiring one, so it
45/// works from a plain `fn main` and from a Python interpreter that has none.
46pub fn run<I, T>(args: I) -> u8
47where
48 I: IntoIterator<Item = T>,
49 T: Into<OsString> + Clone,
50{
51 let cli = match cli::Cli::try_parse_from(args) {
52 Ok(cli) => cli,
53 Err(err) => {
54 // Covers `--help` and `--version` too, which clap reports as errors
55 // carrying an exit code of 0.
56 let _ = err.print();
57 return u8::try_from(err.exit_code()).unwrap_or(FAILURE);
58 }
59 };
60
61 let runtime = match tokio::runtime::Builder::new_multi_thread()
62 .enable_all()
63 .build()
64 {
65 Ok(runtime) => runtime,
66 Err(err) => {
67 eprintln!("Error: could not start the async runtime: {err}");
68 return FAILURE;
69 }
70 };
71
72 match runtime.block_on(cli::dispatch(cli)) {
73 Ok(()) => 0,
74 Err(err) => {
75 eprintln!("Error: {err:?}");
76 FAILURE
77 }
78 }
79}