Skip to main content

spvirit_tools/spvirit_client/
cli.rs

1use std::net::SocketAddr;
2use std::time::Duration;
3
4use argparse::{ArgumentParser, Store, StoreTrue};
5
6use super::search::{is_auto_addr_list_enabled, parse_name_servers};
7use super::types::PvGetOptions;
8
9/// Common CLI arguments shared across all PVA client tools.
10///
11/// Usage:
12/// ```ignore
13/// let mut common = CommonClientArgs::new();
14/// // optionally adjust defaults before parsing:
15/// // common.timeout_secs = 3;
16/// {
17///     let mut ap = ArgumentParser::new();
18///     common.add_to_parser(&mut ap);
19///     // add tool-specific args here
20///     ap.parse_args_or_exit();
21/// }
22/// common.init_tracing();
23/// let opts = common.into_pv_get_options("myPV".to_string())?;
24/// ```
25pub struct CommonClientArgs {
26    pub timeout_secs: u64,
27    pub server: String,
28    pub search_addr: String,
29    pub bind_addr: String,
30    pub name_server: String,
31    pub udp_port: u16,
32    pub tcp_port: u16,
33    pub debug: bool,
34    pub no_broadcast: bool,
35    pub authnz_user: String,
36    pub authnz_host: String,
37}
38
39impl CommonClientArgs {
40    pub fn new() -> Self {
41        Self {
42            timeout_secs: 5,
43            server: String::new(),
44            search_addr: String::new(),
45            bind_addr: String::new(),
46            name_server: String::new(),
47            udp_port: 5076,
48            tcp_port: 5075,
49            debug: false,
50            no_broadcast: false,
51            authnz_user: String::new(),
52            authnz_host: String::new(),
53        }
54    }
55
56    /// Register common flags on the given parser.
57    ///
58    /// Short flags `-w` and `-d` are included so that tools like `pvlist`
59    /// that historically accepted them keep working.
60    pub fn add_to_parser<'a, 'b>(&'a mut self, ap: &'b mut ArgumentParser<'a>) {
61        ap.refer(&mut self.timeout_secs)
62            .add_option(&["-w", "--timeout"], Store, "Timeout in seconds");
63        ap.refer(&mut self.server)
64            .add_option(&["--server"], Store, "Server address (ip:port)");
65        ap.refer(&mut self.search_addr).add_option(
66            &["--search-addr"],
67            Store,
68            "Search target IP (default EPICS_PVA_ADDR_LIST/auto broadcast)",
69        );
70        ap.refer(&mut self.bind_addr).add_option(
71            &["--bind-addr"],
72            Store,
73            "Local bind IP for UDP search",
74        );
75        ap.refer(&mut self.name_server).add_option(
76            &["--name-server"],
77            Store,
78            "PVA name server address (host:port, repeatable via EPICS_PVA_NAME_SERVERS)",
79        );
80        ap.refer(&mut self.udp_port)
81            .add_option(&["--udp-port"], Store, "UDP search port");
82        ap.refer(&mut self.tcp_port)
83            .add_option(&["--tcp-port"], Store, "TCP server default port");
84        ap.refer(&mut self.debug)
85            .add_option(&["-d", "--debug"], StoreTrue, "Enable debug logging");
86        ap.refer(&mut self.no_broadcast).add_option(
87            &["--no-broadcast"],
88            StoreTrue,
89            "Disable UDP broadcast/multicast search (also set via EPICS_PVA_AUTO_ADDR_LIST=NO)",
90        );
91        ap.refer(&mut self.authnz_user).add_option(
92            &["--authnz-user"],
93            Store,
94            "AuthNZ user override (takes precedence over env)",
95        );
96        ap.refer(&mut self.authnz_host).add_option(
97            &["--authnz-host"],
98            Store,
99            "AuthNZ host override (takes precedence over env)",
100        );
101    }
102
103    /// Initialise the `tracing_subscriber` based on `--debug`.
104    pub fn init_tracing(&self) {
105        let max_level = if self.debug {
106            tracing::Level::DEBUG
107        } else {
108            tracing::Level::INFO
109        };
110        tracing_subscriber::fmt().with_max_level(max_level).init();
111    }
112
113    /// Convert the parsed CLI strings into a ready-to-use `PvGetOptions`.
114    pub fn into_pv_get_options(
115        self,
116        pv_name: String,
117    ) -> Result<PvGetOptions, Box<dyn std::error::Error>> {
118        let mut opts = PvGetOptions::new(pv_name);
119        opts.timeout = Duration::from_secs(self.timeout_secs);
120        opts.udp_port = self.udp_port;
121        opts.tcp_port = self.tcp_port;
122        opts.debug = self.debug;
123        opts.no_broadcast = self.no_broadcast || !is_auto_addr_list_enabled();
124
125        if !self.server.is_empty() {
126            let addr: SocketAddr = self.server.parse()?;
127            opts.server_addr = Some(addr);
128        }
129        if !self.search_addr.is_empty() {
130            opts.search_addr = Some(self.search_addr.parse()?);
131        }
132        if !self.bind_addr.is_empty() {
133            opts.bind_addr = Some(self.bind_addr.parse()?);
134        }
135
136        let mut ns = parse_name_servers(&self.name_server);
137        if let Ok(env) = std::env::var("EPICS_PVA_NAME_SERVERS") {
138            ns.extend(parse_name_servers(&env));
139        }
140        opts.name_servers = ns;
141
142        if !self.authnz_user.is_empty() {
143            opts.authnz_user = Some(self.authnz_user);
144        }
145        if !self.authnz_host.is_empty() {
146            opts.authnz_host = Some(self.authnz_host);
147        }
148
149        Ok(opts)
150    }
151}