Skip to main content

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}