facet_args/format.rs
1use alloc::borrow::Cow;
2use alloc::string::ToString;
3use core::fmt;
4use facet_core::{Facet, FieldAttribute, Type, UserType};
5use facet_deserialize::{
6 DeserError, DeserErrorKind, Expectation, Format, NextData, NextResult, Outcome, Raw, Scalar,
7 Span, Spanned,
8};
9
10/// Command-line argument format for Facet deserialization
11pub struct Cli;
12
13impl fmt::Display for Cli {
14 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
15 write!(f, "Cli")
16 }
17}
18
19impl Cli {
20 /// Helper function to convert kebab-case to snake_case
21 fn kebab_to_snake(input: &str) -> Cow<str> {
22 if !input.contains('-') {
23 return Cow::Borrowed(input);
24 }
25 Cow::Owned(input.replace('-', "_"))
26 }
27}
28
29/// Parse command line arguments into a Facet-compatible type
30pub fn from_slice<'input, 'facet, 'shape, T: Facet<'facet>>(
31 args: &'input [&'input str],
32) -> Result<T, DeserError<'input, 'shape>>
33where
34 'input: 'facet + 'shape,
35{
36 facet_deserialize::deserialize(args, Cli)
37}
38
39impl Format for Cli {
40 type Input<'input> = [&'input str];
41 type SpanType = Raw;
42
43 fn source(&self) -> &'static str {
44 "args"
45 }
46
47 fn next<'input, 'facet, 'shape>(
48 &mut self,
49 nd: NextData<'input, 'facet, 'shape, Self::SpanType, Self::Input<'input>>,
50 expectation: Expectation,
51 ) -> NextResult<
52 'input,
53 'facet,
54 'shape,
55 Spanned<Outcome<'input>, Self::SpanType>,
56 Spanned<DeserErrorKind<'shape>, Self::SpanType>,
57 Self::SpanType,
58 Self::Input<'input>,
59 >
60 where
61 'shape: 'input,
62 {
63 let arg_idx = nd.start();
64 let shape = nd.wip.shape();
65 let args = nd.input();
66
67 match expectation {
68 // Top-level value
69 Expectation::Value => {
70 // Check if it's a struct type
71 if !matches!(shape.ty, Type::User(UserType::Struct(_))) {
72 return (
73 nd,
74 Err(Spanned {
75 node: DeserErrorKind::UnsupportedType {
76 got: shape,
77 wanted: "struct",
78 },
79 span: Span::new(arg_idx, 0),
80 }),
81 );
82 }
83 // For CLI args, we always start with an object (struct)
84 (
85 nd,
86 Ok(Spanned {
87 node: Outcome::ObjectStarted,
88 span: Span::new(arg_idx, 0),
89 }),
90 )
91 }
92
93 // Object key (or finished)
94 Expectation::ObjectKeyOrObjectClose => {
95 /* Check if we have more arguments */
96 if arg_idx < args.len() {
97 let arg = args[arg_idx];
98 let span = Span::new(arg_idx, 1);
99
100 // Named long argument?
101 if let Some(key) = arg.strip_prefix("--") {
102 let key = Self::kebab_to_snake(key);
103
104 // Check if the field exists in the struct
105 if let Type::User(UserType::Struct(_)) = shape.ty {
106 if nd.wip.field_index(&key).is_none() {
107 return (
108 nd,
109 Err(Spanned {
110 node: DeserErrorKind::UnknownField {
111 field_name: key.to_string(),
112 shape,
113 },
114 span: Span::new(arg_idx, 0),
115 }),
116 );
117 }
118 }
119 return (
120 nd,
121 Ok(Spanned {
122 node: Outcome::Scalar(Scalar::String(key)),
123 span,
124 }),
125 );
126 }
127
128 // Short flag?
129 if let Some(key) = arg.strip_prefix('-') {
130 // Convert short argument to field name via shape
131 if let Type::User(UserType::Struct(st)) = shape.ty {
132 for field in st.fields.iter() {
133 for attr in field.attributes {
134 if let FieldAttribute::Arbitrary(a) = attr {
135 // Don't require specifying a short key for a single-char key
136 if a.contains("short")
137 && (a.contains(key)
138 || (key.len() == 1 && field.name == key))
139 {
140 return (
141 nd,
142 Ok(Spanned {
143 node: Outcome::Scalar(Scalar::String(
144 Cow::Borrowed(field.name),
145 )),
146 span,
147 }),
148 );
149 }
150 }
151 }
152 }
153 }
154 return (
155 nd,
156 Err(Spanned {
157 node: DeserErrorKind::UnknownField {
158 field_name: key.to_string(),
159 shape,
160 },
161 span: Span::new(arg_idx, 0),
162 }),
163 );
164 }
165
166 // positional argument
167 if let Type::User(UserType::Struct(st)) = &shape.ty {
168 for (idx, field) in st.fields.iter().enumerate() {
169 for attr in field.attributes.iter() {
170 if let FieldAttribute::Arbitrary(a) = attr {
171 if a.contains("positional") {
172 // Check if this field is already set
173 let is_set = nd.wip.is_field_set(idx).unwrap_or(false);
174
175 if !is_set {
176 // Use this positional field
177 return (
178 nd,
179 Ok(Spanned {
180 node: Outcome::Scalar(Scalar::String(
181 Cow::Borrowed(field.name),
182 )),
183 span: Span::new(arg_idx, 0),
184 }),
185 );
186 }
187 }
188 }
189 }
190 }
191 }
192
193 // If no positional field was found
194 return (
195 nd,
196 Err(Spanned {
197 node: DeserErrorKind::UnknownField {
198 field_name: "positional argument".to_string(),
199 shape,
200 },
201 span: Span::new(arg_idx, 0),
202 }),
203 );
204 }
205
206 // EOF: inject implicit-false-if-absent bool flags, if there are any
207 if let Type::User(UserType::Struct(st)) = &shape.ty {
208 for (idx, field) in st.fields.iter().enumerate() {
209 if !nd.wip.is_field_set(idx).unwrap_or(false)
210 && field.shape().is_type::<bool>()
211 {
212 return (
213 nd,
214 Ok(Spanned {
215 node: Outcome::Scalar(Scalar::String(Cow::Borrowed(
216 field.name,
217 ))),
218 span: Span::new(arg_idx, 0),
219 }),
220 );
221 }
222 }
223 }
224
225 // Real end of object
226 (
227 nd,
228 Ok(Spanned {
229 node: Outcome::ObjectEnded,
230 span: Span::new(arg_idx, 0),
231 }),
232 )
233 }
234
235 // Value for the current key
236 Expectation::ObjectVal => {
237 // Synthetic implicit-false
238 if arg_idx >= args.len() && shape.is_type::<bool>() {
239 return (
240 nd,
241 Ok(Spanned {
242 node: Outcome::Scalar(Scalar::Bool(false)),
243 span: Span::new(arg_idx, 0),
244 }),
245 );
246 }
247
248 // Explicit boolean true
249 if shape.is_type::<bool>() {
250 // For boolean fields, we don't need an explicit value
251 return (
252 nd,
253 Ok(Spanned {
254 node: Outcome::Scalar(Scalar::Bool(true)),
255 span: Span::new(arg_idx, 0),
256 }),
257 );
258 }
259
260 // For other types, get the next arg as the value.
261 // Need another CLI token:
262 if arg_idx >= args.len() {
263 return (
264 nd,
265 Err(Spanned {
266 node: DeserErrorKind::MissingValue {
267 expected: "argument value",
268 field: args[arg_idx.saturating_sub(1)].to_string(),
269 },
270 span: Span::new(arg_idx.saturating_sub(1), 0),
271 }),
272 );
273 }
274
275 let arg = args[arg_idx];
276 let span = Span::new(arg_idx, 1);
277
278 // Skip this value if it starts with - (it's probably another flag)
279 if arg.starts_with('-') {
280 // This means we're missing a value for the previous argument
281 return (
282 nd,
283 Err(Spanned {
284 node: DeserErrorKind::MissingValue {
285 expected: "argument value",
286 field: args[arg_idx.saturating_sub(1)].to_string(),
287 },
288 span: Span::new(arg_idx.saturating_sub(1), 0),
289 }),
290 );
291 }
292
293 // Try to parse as appropriate type
294 // Handle numeric types
295 if let Ok(v) = arg.parse::<u64>() {
296 return (
297 nd,
298 Ok(Spanned {
299 node: Outcome::Scalar(Scalar::U64(v)),
300 span,
301 }),
302 );
303 }
304 if let Ok(v) = arg.parse::<i64>() {
305 return (
306 nd,
307 Ok(Spanned {
308 node: Outcome::Scalar(Scalar::I64(v)),
309 span,
310 }),
311 );
312 }
313 if let Ok(v) = arg.parse::<f64>() {
314 return (
315 nd,
316 Ok(Spanned {
317 node: Outcome::Scalar(Scalar::F64(v)),
318 span,
319 }),
320 );
321 }
322
323 // Default to string type
324 (
325 nd,
326 Ok(Spanned {
327 node: Outcome::Scalar(Scalar::String(Cow::Borrowed(arg))),
328 span,
329 }),
330 )
331 }
332
333 // List items
334 Expectation::ListItemOrListClose => {
335 // End the list if we're out of arguments, or if it's a new flag
336 if arg_idx >= args.len() || args[arg_idx].starts_with('-') {
337 return (
338 nd,
339 Ok(Spanned {
340 node: Outcome::ListEnded,
341 span: Span::new(arg_idx, 0),
342 }),
343 );
344 }
345
346 // Process the next item in the list
347 (
348 nd,
349 Ok(Spanned {
350 node: Outcome::Scalar(Scalar::String(Cow::Borrowed(args[arg_idx]))),
351 span: Span::new(arg_idx, 1),
352 }),
353 )
354 }
355 }
356 }
357
358 fn skip<'input, 'facet, 'shape>(
359 &mut self,
360 nd: NextData<'input, 'facet, 'shape, Self::SpanType, Self::Input<'input>>,
361 ) -> NextResult<
362 'input,
363 'facet,
364 'shape,
365 Span<Self::SpanType>,
366 Spanned<DeserErrorKind<'shape>, Self::SpanType>,
367 Self::SpanType,
368 Self::Input<'input>,
369 >
370 where
371 'shape: 'input,
372 {
373 let arg_idx = nd.start();
374 let args = nd.input();
375
376 if arg_idx < args.len() {
377 // Simply skip one position
378 (nd, Ok(Span::new(arg_idx, 1)))
379 } else {
380 // No argument to skip
381 (
382 nd,
383 Err(Spanned {
384 node: DeserErrorKind::UnexpectedEof {
385 wanted: "argument to skip",
386 },
387 span: Span::new(arg_idx, 1),
388 }),
389 )
390 }
391 }
392}