Skip to main content

appcore_args/
parsed.rs

1// =============================================================================
2//        #######
3//     ###       ###     F: parsed.rs
4//    ##   ## ##   ##    P: AppCore-Runtime
5//         ## ##
6//                       C: 2026/10/05 00:00:00 by dnettoRaw
7//    ##   ## ##   ##    U: 2026/10/05 00:00:00 by dnettoRaw
8//      ###########      S: 1.0.2
9// =============================================================================
10
11//! Parsed UTF-8 command-line results.
12
13#[derive(Clone, Debug, PartialEq, Eq)]
14pub struct ParsedCli {
15    pub(crate) command_path: Vec<String>,
16    pub(crate) options: Vec<ParsedOption>,
17    pub(crate) positionals: Vec<String>,
18    pub(crate) passthrough: Vec<String>,
19}
20
21#[derive(Clone, PartialEq, Eq)]
22pub struct ParsedOption {
23    pub(crate) name: String,
24    pub(crate) value: Option<String>,
25}
26
27impl ParsedCli {
28    pub fn command_path(&self) -> &[String] {
29        &self.command_path
30    }
31    pub fn options(&self) -> &[ParsedOption] {
32        &self.options
33    }
34    pub fn positionals(&self) -> &[String] {
35        &self.positionals
36    }
37    pub fn passthrough(&self) -> &[String] {
38        &self.passthrough
39    }
40    pub fn has_flag(&self, name: &str) -> bool {
41        self.options.iter().any(|option| option.name == name)
42    }
43    pub fn option_value(&self, name: &str) -> Option<&str> {
44        self.options
45            .iter()
46            .rev()
47            .find(|option| option.name == name)
48            .and_then(ParsedOption::value)
49    }
50    pub fn option_values<'b>(&'b self, name: &'b str) -> impl Iterator<Item = &'b str> + 'b {
51        self.options
52            .iter()
53            .filter(move |option| option.name == name)
54            .filter_map(ParsedOption::value)
55    }
56    /// Returns a path from a textual value. Use `parse_os` for native non-UTF-8 paths.
57    pub fn option_path(&self, name: &str) -> Option<std::path::PathBuf> {
58        self.option_value(name).map(std::path::PathBuf::from)
59    }
60    /// Parses a numeric socket address without DNS resolution.
61    pub fn option_socket_addr(
62        &self,
63        name: &str,
64    ) -> Result<Option<std::net::SocketAddr>, crate::CliError> {
65        self.option_value(name)
66            .map(|v| {
67                v.parse().map_err(|_| {
68                    crate::CliError::new(
69                        crate::CliErrorKind::InvalidValue,
70                        format!("invalid socket address for --{name}"),
71                    )
72                })
73            })
74            .transpose()
75    }
76    pub fn option_occurrences(&self, name: &str) -> usize {
77        self.options
78            .iter()
79            .filter(|option| option.name == name)
80            .count()
81    }
82}
83
84impl ParsedOption {
85    pub fn name(&self) -> &str {
86        &self.name
87    }
88    pub fn value(&self) -> Option<&str> {
89        self.value.as_deref()
90    }
91}
92
93impl std::fmt::Debug for ParsedOption {
94    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
95        f.debug_struct("ParsedOption")
96            .field("name", &self.name)
97            .field("value", &"[REDACTED]")
98            .finish()
99    }
100}