spvirit_tools/spvirit_client/
cli.rs1use 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
9pub 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 pub fields: String,
40}
41
42impl CommonClientArgs {
43 pub fn new() -> Self {
44 Self {
45 timeout_secs: 5,
46 server: String::new(),
47 search_addr: String::new(),
48 bind_addr: String::new(),
49 name_server: String::new(),
50 udp_port: 5076,
51 tcp_port: 5075,
52 debug: false,
53 no_broadcast: false,
54 authnz_user: String::new(),
55 authnz_host: String::new(),
56 fields: String::new(),
57 }
58 }
59
60 pub fn add_to_parser<'a, 'b>(&'a mut self, ap: &'b mut ArgumentParser<'a>) {
65 ap.refer(&mut self.timeout_secs).add_option(
66 &["-w", "--timeout"],
67 Store,
68 "Timeout in seconds",
69 );
70 ap.refer(&mut self.server)
71 .add_option(&["--server"], Store, "Server address (ip:port)");
72 ap.refer(&mut self.search_addr).add_option(
73 &["--search-addr"],
74 Store,
75 "Search target IP (default EPICS_PVA_ADDR_LIST/auto broadcast)",
76 );
77 ap.refer(&mut self.bind_addr).add_option(
78 &["--bind-addr"],
79 Store,
80 "Local bind IP for UDP search",
81 );
82 ap.refer(&mut self.name_server).add_option(
83 &["--name-server"],
84 Store,
85 "PVA name server address (host:port, repeatable via EPICS_PVA_NAME_SERVERS)",
86 );
87 ap.refer(&mut self.udp_port)
88 .add_option(&["--udp-port"], Store, "UDP search port");
89 ap.refer(&mut self.tcp_port)
90 .add_option(&["--tcp-port"], Store, "TCP server default port");
91 ap.refer(&mut self.debug)
92 .add_option(&["-d", "--debug"], StoreTrue, "Enable debug logging");
93 ap.refer(&mut self.no_broadcast).add_option(
94 &["--no-broadcast"],
95 StoreTrue,
96 "Disable UDP broadcast/multicast search (also set via EPICS_PVA_AUTO_ADDR_LIST=NO)",
97 );
98 ap.refer(&mut self.authnz_user).add_option(
99 &["--authnz-user"],
100 Store,
101 "AuthNZ user override (takes precedence over env)",
102 );
103 ap.refer(&mut self.authnz_host).add_option(
104 &["--authnz-host"],
105 Store,
106 "AuthNZ host override (takes precedence over env)",
107 );
108 ap.refer(&mut self.fields).add_option(
109 &["-F", "--fields"],
110 Store,
111 "Comma-separated dotted field paths to request (e.g. value,alarm.severity). Empty = all fields.",
112 );
113 }
114
115 pub fn init_tracing(&self) {
117 let max_level = if self.debug {
118 tracing::Level::DEBUG
119 } else {
120 tracing::Level::INFO
121 };
122 tracing_subscriber::fmt().with_max_level(max_level).init();
123 }
124
125 pub fn into_pv_get_options(
127 self,
128 pv_name: String,
129 ) -> Result<PvGetOptions, Box<dyn std::error::Error>> {
130 let mut opts = PvGetOptions::new(pv_name);
131 opts.timeout = Duration::from_secs(self.timeout_secs);
132 opts.udp_port = self.udp_port;
133 opts.tcp_port = self.tcp_port;
134 opts.debug = self.debug;
135 opts.no_broadcast = self.no_broadcast || !is_auto_addr_list_enabled();
136
137 if !self.server.is_empty() {
138 let addr: SocketAddr = self.server.parse()?;
139 opts.server_addr = Some(addr);
140 }
141 if !self.search_addr.is_empty() {
142 opts.search_addr = Some(self.search_addr.parse()?);
143 }
144 if !self.bind_addr.is_empty() {
145 opts.bind_addr = Some(self.bind_addr.parse()?);
146 }
147
148 let mut ns = parse_name_servers(&self.name_server);
149 if let Ok(env) = std::env::var("EPICS_PVA_NAME_SERVERS") {
150 ns.extend(parse_name_servers(&env));
151 }
152 opts.name_servers = ns;
153
154 if !self.authnz_user.is_empty() {
155 opts.authnz_user = Some(self.authnz_user);
156 }
157 if !self.authnz_host.is_empty() {
158 opts.authnz_host = Some(self.authnz_host);
159 }
160
161 Ok(opts)
162 }
163
164 pub fn fields_list(&self) -> Vec<String> {
166 parse_fields_arg(&self.fields)
167 }
168}
169
170pub fn parse_fields_arg(raw: &str) -> Vec<String> {
173 raw.split(',')
174 .map(|s| s.trim().to_string())
175 .filter(|s| !s.is_empty())
176 .collect()
177}