facet_args/
format.rs

1use crate::arg::{ArgType, extract_subspan};
2use crate::deserialize::{
3    DeserError, DeserErrorKind, Expectation, Format, NextData, NextResult, Outcome, Raw, Scalar,
4    Span, Spanned,
5};
6use crate::fields::*;
7use crate::parse::parse_scalar;
8use crate::results::*;
9use alloc::borrow::Cow;
10use core::fmt;
11use facet_core::Facet;
12
13/// Command-line argument format for Facet deserialization
14pub struct Cli;
15
16impl fmt::Display for Cli {
17    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
18        write!(f, "Cli")
19    }
20}
21
22/// Parse command line arguments into a Facet-compatible type
23pub fn from_slice<'input, 'facet, T: Facet<'facet>>(
24    args: &'input [&'input str],
25) -> Result<T, DeserError<'input>>
26where
27    'input: 'facet,
28{
29    crate::deserialize::deserialize(args, Cli)
30}
31
32/// Parse command line arguments provided by std::env::args() into a Facet-compatible type
33pub fn from_std_args<'input, 'facet, T: Facet<'facet>>() -> Result<T, DeserError<'input>>
34where
35    'input: 'facet,
36{
37    let args = std::env::args().skip(1).collect::<Vec<String>>();
38    let args_str: Vec<&'static str> = args
39        .into_iter()
40        .map(|s| Box::leak(s.into_boxed_str()) as &str)
41        .collect();
42
43    from_slice(Box::leak(args_str.into_boxed_slice()))
44}
45
46impl Format for Cli {
47    type Input<'input> = [&'input str];
48    type SpanType = Raw;
49
50    fn next<'input, 'facet>(
51        &mut self,
52        nd: NextData<'input, 'facet, Self::SpanType, Self::Input<'input>>,
53        expectation: Expectation,
54    ) -> NextResult<
55        'input,
56        'facet,
57        Spanned<Outcome<'input>, Self::SpanType>,
58        Spanned<DeserErrorKind, Self::SpanType>,
59        Self::SpanType,
60        Self::Input<'input>,
61    > {
62        let arg_idx = nd.start();
63        let shape = nd.wip.shape();
64        let args = nd.input();
65        let subspans = nd.substack().get();
66        let has_subspans = !subspans.is_empty();
67
68        let stay_put = Span::new(arg_idx, 0);
69        let step_forth = Span::new(arg_idx, 1);
70
71        let span = match expectation {
72            Expectation::Value => stay_put,
73            Expectation::ObjectKeyOrObjectClose
74            | Expectation::ObjectVal
75            | Expectation::ListItemOrListClose => step_forth,
76        };
77
78        let result = match expectation {
79            // Top-level value
80            Expectation::Value => {
81                // Check if it's a struct type
82                wrap_outcome_result(validate_struct_type(shape), Outcome::ObjectStarted, span)
83            }
84
85            // Object key (or finished)
86            Expectation::ObjectKeyOrObjectClose => {
87                /* Check if we have more arguments */
88                if arg_idx < args.len() {
89                    let arg = args[arg_idx];
90
91                    // Check if we need to resegment an arg with '='
92                    if arg.starts_with("-") && arg.contains('=') && !has_subspans {
93                        // This is an argument with '=' that needs resegmentation
94                        if let Some(key_value_subspans) = create_key_value_subspans(arg) {
95                            return (nd, wrap_resegmented_result(key_value_subspans, stay_put));
96                        }
97                    }
98
99                    // Regular argument or subspan processing
100                    let effective_arg = if has_subspans {
101                        extract_subspan(&subspans[0], arg)
102                    } else {
103                        arg
104                    };
105
106                    // Parse the argument type
107                    match ArgType::parse(effective_arg) {
108                        ArgType::LongFlag(key) => {
109                            // Validate field exists
110                            wrap_string_result(
111                                validate_field(&key, shape, &nd.wip).map(|_| key),
112                                if has_subspans { stay_put } else { span },
113                            )
114                        }
115                        ArgType::ShortFlag(key) => {
116                            // Convert short argument to field name via shape
117                            wrap_field_result(
118                                find_field_by_short_flag(key, shape),
119                                if has_subspans { stay_put } else { span },
120                            )
121                        }
122                        ArgType::Positional => {
123                            // Handle positional argument
124                            wrap_field_result(find_positional_field(shape, &nd.wip), stay_put)
125                        }
126                        ArgType::None => {
127                            // Handle empty argument (shouldn't happen normally)
128                            let err = create_unknown_field_error("empty argument", shape);
129                            Err(Spanned { node: err, span })
130                        }
131                    }
132                } else {
133                    // EOF: inject implicit-false-if-absent bool flags, if there are any
134                    handle_unset_bool_field_error(find_unset_bool_field(shape, &nd.wip), span)
135                }
136            }
137
138            // Value for the current key
139            Expectation::ObjectVal => {
140                // Determine what to do based on the type and available arguments
141                if shape.is_type::<bool>() {
142                    // Handle boolean values (true if we have an arg, false if EOF)
143                    let has_arg = arg_idx < args.len();
144                    wrap_result(handle_bool_value(has_arg), Outcome::Scalar, stay_put)
145                } else {
146                    // For non-boolean types, check if we have subspans
147                    let result = if has_subspans && arg_idx < args.len() {
148                        let arg = args[arg_idx];
149                        let subspan = &subspans[1];
150                        let arg_type: ArgType = (subspan, arg).into();
151
152                        // If this isn't a flag type (neither ShortFlag nor LongFlag), use it as a value
153                        match arg_type {
154                            ArgType::ShortFlag(_) | ArgType::LongFlag(_) => {
155                                // It's a flag, not a value - continue to validation
156                                None
157                            }
158                            _ => {
159                                // Extract the actual substring to use
160                                let part = extract_subspan(subspan, arg);
161                                Some(Ok(parse_scalar(part, span)))
162                            }
163                        }
164                    } else {
165                        None
166                    };
167
168                    // Use the result from above if available, otherwise fall back to regular validation
169                    result.unwrap_or_else(|| {
170                        // No usable subspans, fall back to regular validation
171                        match validate_value_available(arg_idx, args) {
172                            Ok(arg) => Ok(parse_scalar(arg, span)),
173                            Err(err) => Err(Spanned {
174                                node: err,
175                                span: Span::new(arg_idx.saturating_sub(1), 0),
176                            }),
177                        }
178                    })
179                }
180            }
181
182            // List items
183            Expectation::ListItemOrListClose => {
184                // End the list if we're out of arguments, or if it's a new flag
185                if is_list_ended(arg_idx, args) {
186                    // End the list
187                    Ok(Spanned {
188                        node: Outcome::ListEnded,
189                        span,
190                    })
191                } else {
192                    // Process the next item
193                    Ok(Spanned {
194                        node: Outcome::Scalar(Scalar::String(Cow::Borrowed(args[arg_idx]))),
195                        span: step_forth,
196                    })
197                }
198            }
199        };
200
201        (nd, result)
202    }
203
204    fn skip<'input, 'facet>(
205        &mut self,
206        nd: NextData<'input, 'facet, Self::SpanType, Self::Input<'input>>,
207    ) -> NextResult<
208        'input,
209        'facet,
210        Span<Self::SpanType>,
211        Spanned<DeserErrorKind, Self::SpanType>,
212        Self::SpanType,
213        Self::Input<'input>,
214    > {
215        let arg_idx = nd.start();
216        let args = nd.input();
217        let span = Span::new(arg_idx, 1);
218
219        let result = if arg_idx < args.len() {
220            // Simply skip one position
221            Ok(span)
222        } else {
223            // No argument to skip
224            Err(Spanned {
225                node: DeserErrorKind::UnexpectedEof {
226                    wanted: "argument to skip",
227                },
228                span,
229            })
230        };
231
232        (nd, result)
233    }
234}