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).add_option(
62            &["-w", "--timeout"],
63            Store,
64            "Timeout in seconds",
65        );
66        ap.refer(&mut self.server)
67            .add_option(&["--server"], Store, "Server address (ip:port)");
68        ap.refer(&mut self.search_addr).add_option(
69            &["--search-addr"],
70            Store,
71            "Search target IP (default EPICS_PVA_ADDR_LIST/auto broadcast)",
72        );
73        ap.refer(&mut self.bind_addr).add_option(
74            &["--bind-addr"],
75            Store,
76            "Local bind IP for UDP search",
77        );
78        ap.refer(&mut self.name_server).add_option(
79            &["--name-server"],
80            Store,
81            "PVA name server address (host:port, repeatable via EPICS_PVA_NAME_SERVERS)",
82        );
83        ap.refer(&mut self.udp_port)
84            .add_option(&["--udp-port"], Store, "UDP search port");
85        ap.refer(&mut self.tcp_port)
86            .add_option(&["--tcp-port"], Store, "TCP server default port");
87        ap.refer(&mut self.debug)
88            .add_option(&["-d", "--debug"], StoreTrue, "Enable debug logging");
89        ap.refer(&mut self.no_broadcast).add_option(
90            &["--no-broadcast"],
91            StoreTrue,
92            "Disable UDP broadcast/multicast search (also set via EPICS_PVA_AUTO_ADDR_LIST=NO)",
93        );
94        ap.refer(&mut self.authnz_user).add_option(
95            &["--authnz-user"],
96            Store,
97            "AuthNZ user override (takes precedence over env)",
98        );
99        ap.refer(&mut self.authnz_host).add_option(
100            &["--authnz-host"],
101            Store,
102            "AuthNZ host override (takes precedence over env)",
103        );
104    }
105
106    /// Initialise the `tracing_subscriber` based on `--debug`.
107    pub fn init_tracing(&self) {
108        let max_level = if self.debug {
109            tracing::Level::DEBUG
110        } else {
111            tracing::Level::INFO
112        };
113        tracing_subscriber::fmt().with_max_level(max_level).init();
114    }
115
116    /// Convert the parsed CLI strings into a ready-to-use `PvGetOptions`.
117    pub fn into_pv_get_options(
118        self,
119        pv_name: String,
120    ) -> Result<PvGetOptions, Box<dyn std::error::Error>> {
121        let mut opts = PvGetOptions::new(pv_name);
122        opts.timeout = Duration::from_secs(self.timeout_secs);
123        opts.udp_port = self.udp_port;
124        opts.tcp_port = self.tcp_port;
125        opts.debug = self.debug;
126        opts.no_broadcast = self.no_broadcast || !is_auto_addr_list_enabled();
127
128        if !self.server.is_empty() {
129            let addr: SocketAddr = self.server.parse()?;
130            opts.server_addr = Some(addr);
131        }
132        if !self.search_addr.is_empty() {
133            opts.search_addr = Some(self.search_addr.parse()?);
134        }
135        if !self.bind_addr.is_empty() {
136            opts.bind_addr = Some(self.bind_addr.parse()?);
137        }
138
139        let mut ns = parse_name_servers(&self.name_server);
140        if let Ok(env) = std::env::var("EPICS_PVA_NAME_SERVERS") {
141            ns.extend(parse_name_servers(&env));
142        }
143        opts.name_servers = ns;
144
145        if !self.authnz_user.is_empty() {
146            opts.authnz_user = Some(self.authnz_user);
147        }
148        if !self.authnz_host.is_empty() {
149            opts.authnz_host = Some(self.authnz_host);
150        }
151
152        Ok(opts)
153    }
154}