1use crate::{
2 arg::ArgType,
3 error::{ArgsError, ArgsErrorKind, ArgsErrorWithInput, get_variants_from_shape},
4 help::{HelpConfig, generate_help_for_shape},
5 is_counted_field, is_supported_counted_type,
6 span::Span,
7};
8use alloc::collections::BTreeMap;
9use facet_core::{Def, EnumType, Facet, Field, Shape, StructKind, Type, UserType, Variant};
10use facet_reflect::{HeapValue, Partial};
11use heck::{ToKebabCase, ToSnakeCase};
12
13fn is_help_flag(arg: &str) -> bool {
15 matches!(arg, "-h" | "--help" | "-help" | "/?")
16}
17
18pub fn from_std_args<T: Facet<'static>>() -> Result<T, ArgsErrorWithInput> {
20 let args = std::env::args().skip(1).collect::<Vec<String>>();
21 let args_str: Vec<&str> = args.iter().map(|s| s.as_str()).collect();
22 from_slice(&args_str[..])
23}
24
25pub fn from_slice<'input, T: Facet<'static>>(
27 args: &'input [&'input str],
28) -> Result<T, ArgsErrorWithInput> {
29 from_slice_with_config(args, &HelpConfig::default())
30}
31
32pub fn from_slice_with_config<'input, T: Facet<'static>>(
34 args: &'input [&'input str],
35 help_config: &HelpConfig,
36) -> Result<T, ArgsErrorWithInput> {
37 if let Some(first_arg) = args.first()
39 && is_help_flag(first_arg)
40 {
41 let help_text = generate_help_for_shape(T::SHAPE, help_config);
42 let span = Span::new(0, first_arg.len());
43 return Err(ArgsErrorWithInput {
44 inner: ArgsError::new(ArgsErrorKind::HelpRequested { help_text }, span),
45 flattened_args: args.join(" "),
46 });
47 }
48
49 let mut cx = Context::new(args, T::SHAPE);
50 let hv = cx.work_add_input()?;
51
52 Ok(hv.materialize::<T>().unwrap())
54}
55
56struct Context<'input> {
57 shape: &'static Shape,
59
60 args: &'input [&'input str],
62
63 index: usize,
65
66 positional_only: bool,
68
69 arg_indices: Vec<usize>,
71
72 flattened_args: String,
74
75 counted_stack: Vec<BTreeMap<usize, u64>>,
78}
79
80impl<'input> Context<'input> {
81 fn new(args: &'input [&'input str], shape: &'static Shape) -> Self {
82 let mut arg_indices = vec![];
83 let mut flattened_args = String::new();
84
85 for arg in args {
86 arg_indices.push(flattened_args.len());
87 flattened_args.push_str(arg);
88 flattened_args.push(' ');
89 }
90 tracing::trace!("flattened args: {flattened_args:?}");
91 tracing::trace!("arg_indices: {arg_indices:?}");
92
93 Self {
94 shape,
95 args,
96 index: 0,
97 positional_only: false,
98 arg_indices,
99 flattened_args,
100 counted_stack: Vec::new(),
101 }
102 }
103
104 fn push_counted_scope(&mut self) {
105 self.counted_stack.push(BTreeMap::new());
106 }
107
108 fn pop_and_apply_counted_fields(
109 &mut self,
110 mut p: Partial<'static>,
111 ) -> Result<Partial<'static>, ArgsErrorKind> {
112 let counts = self.counted_stack.pop().unwrap_or_default();
113 for (field_index, count) in counts {
114 p = p.begin_nth_field(field_index)?;
115 p = p.parse_from_str(&count.to_string())?;
116 p = p.end()?;
117 }
118 Ok(p)
119 }
120
121 fn increment_counted(&mut self, field_index: usize) {
122 if let Some(counts) = self.counted_stack.last_mut() {
123 let count = counts.entry(field_index).or_insert(0);
124 *count = count.saturating_add(1);
125 }
126 }
127
128 fn try_handle_counted_long_flag(&mut self, field: &'static Field, field_index: usize) -> bool {
129 if is_counted_field(field) && is_supported_counted_type(field.shape()) {
130 self.increment_counted(field_index);
131 self.index += 1;
132 true
133 } else {
134 false
135 }
136 }
137
138 fn fields(&self, p: &Partial<'static>) -> Result<&'static [Field], ArgsErrorKind> {
140 let shape = p.shape();
141 match &shape.ty {
142 Type::User(UserType::Struct(struct_type)) => Ok(struct_type.fields),
143 _ => Err(ArgsErrorKind::NoFields { shape }),
144 }
145 }
146
147 fn handle_field(
150 &mut self,
151 p: Partial<'static>,
152 field_index: usize,
153 value: Option<SplitToken<'input>>,
154 ) -> Result<Partial<'static>, ArgsErrorKind> {
155 tracing::trace!("Handling field at index {field_index}");
156
157 let mut p = p.begin_nth_field(field_index)?;
158
159 tracing::trace!("After begin_field, shape is {}", p.shape());
160 if p.shape().is_shape(bool::SHAPE) {
161 let bool_value = if let Some(value) = value {
163 match value.s.to_lowercase().as_str() {
165 "true" | "yes" | "1" | "on" => true,
166 "false" | "no" | "0" | "off" => false,
167 "" => true, other => {
169 tracing::warn!("Unknown boolean value '{other}', treating as true");
170 true
171 }
172 }
173 } else {
174 true
176 };
177 tracing::trace!("Flag is boolean, setting it to {bool_value}");
178 p = p.set(bool_value)?;
179
180 self.index += 1;
181 } else {
182 tracing::trace!("Flag isn't boolean, expecting a {} value", p.shape());
183
184 if let Some(value) = value {
185 p = self.handle_value(p, value.s)?;
186 } else {
187 if self.index + 1 >= self.args.len() {
188 return Err(ArgsErrorKind::ExpectedValueGotEof { shape: p.shape() });
189 }
190 let value = self.args[self.index + 1];
191
192 self.index += 1;
193 p = self.handle_value(p, value)?;
194 }
195
196 self.index += 1;
197 }
198
199 p = p.end()?;
200
201 Ok(p)
202 }
203
204 fn handle_value(
205 &mut self,
206 p: Partial<'static>,
207 value: &'input str,
208 ) -> Result<Partial<'static>, ArgsErrorKind> {
209 if let Type::User(UserType::Enum(_)) = p.shape().ty {
212 return Err(ArgsErrorKind::ReflectError(
215 facet_reflect::ReflectError::OperationFailed {
216 shape: p.shape(),
217 operation: "Subcommands must be provided as positional arguments, not as flag values. Use the subcommand name directly instead of --flag <value>.",
218 },
219 ));
220 }
221
222 let p = match p.shape().def {
223 Def::List(_) => {
224 let mut p = p.init_list()?;
226 p = p.begin_list_item()?;
227 p = p.parse_from_str(value)?;
228 p.end()?
229 }
230 Def::Option(_) => {
231 let mut p = p.begin_some()?;
233 p = p.parse_from_str(value)?;
234 p.end()?
235 }
236 _ => {
237 p.parse_from_str(value)?
239 }
240 };
241
242 Ok(p)
243 }
244
245 fn work_add_input(&mut self) -> Result<HeapValue<'static>, ArgsErrorWithInput> {
246 self.work().map_err(|e| ArgsErrorWithInput {
247 inner: e,
248 flattened_args: self.flattened_args.clone(),
249 })
250 }
251
252 fn work(&mut self) -> Result<HeapValue<'static>, ArgsError> {
254 self.work_inner().map_err(|kind| {
255 let span = kind.precise_span().unwrap_or_else(|| {
257 if self.index >= self.args.len() {
258 Span::new(self.flattened_args.len(), 0)
259 } else {
260 let arg = self.args[self.index];
261 let index = self.arg_indices[self.index];
262 Span::new(index, arg.len())
263 }
264 });
265 ArgsError::new(kind, span)
266 })
267 }
268
269 #[allow(unsafe_code)]
270 fn work_inner(&mut self) -> Result<HeapValue<'static>, ArgsErrorKind> {
271 let p = unsafe { Partial::alloc_shape(self.shape) }?;
274
275 match self.shape.ty {
278 Type::User(UserType::Struct(_)) => self.parse_struct(p),
279 Type::User(UserType::Enum(_)) => {
280 Err(ArgsErrorKind::ReflectError(
282 facet_reflect::ReflectError::OperationFailed {
283 shape: self.shape,
284 operation: "Top-level enums must be wrapped in a struct with #[facet(args::subcommand)] attribute to be used as subcommands.",
285 },
286 ))
287 }
288 _ => Err(ArgsErrorKind::NoFields { shape: self.shape }),
289 }
290 }
291
292 fn parse_struct(
294 &mut self,
295 mut p: Partial<'static>,
296 ) -> Result<HeapValue<'static>, ArgsErrorKind> {
297 self.push_counted_scope();
298
299 while self.args.len() > self.index {
300 let arg = self.args[self.index];
301 let arg_span = Span::new(self.arg_indices[self.index], arg.len());
302 let at = if self.positional_only {
303 ArgType::Positional
304 } else {
305 ArgType::parse(arg)
306 };
307 tracing::trace!("Parsed {at:?}");
308
309 match at {
310 ArgType::DoubleDash => {
311 self.positional_only = true;
312 self.index += 1;
313 }
314 ArgType::LongFlag(flag) => {
315 if flag.starts_with('-') {
317 let fields = self.fields(&p)?;
318 return Err(ArgsErrorKind::UnknownLongFlag {
319 flag: flag.to_string(),
320 fields,
321 });
322 }
323
324 let flag_span = Span::new(arg_span.start + 2, arg_span.len - 2);
325 match split(flag, flag_span) {
326 Some(tokens) => {
327 let mut tokens = tokens.into_iter();
329 let Some(key) = tokens.next() else {
330 unreachable!()
331 };
332 let Some(value) = tokens.next() else {
333 unreachable!()
334 };
335
336 let flag = key.s;
337 tracing::trace!("Looking up long flag {flag}");
338 let fields = self.fields(&p)?;
339 let Some(field_index) =
340 find_field_index_by_effective_name(fields, flag)
341 else {
342 return Err(ArgsErrorKind::UnknownLongFlag {
343 flag: flag.to_string(),
344 fields,
345 });
346 };
347 p = self.handle_field(p, field_index, Some(value))?;
348 }
349 None => {
350 tracing::trace!("Looking up long flag {flag}");
351 let fields = self.fields(&p)?;
352 let Some(field_index) =
353 find_field_index_by_effective_name(fields, flag)
354 else {
355 return Err(ArgsErrorKind::UnknownLongFlag {
356 flag: flag.to_string(),
357 fields,
358 });
359 };
360 if !self.try_handle_counted_long_flag(&fields[field_index], field_index)
361 {
362 p = self.handle_field(p, field_index, None)?;
363 }
364 }
365 }
366 }
367 ArgType::ShortFlag(flag) => {
368 let flag_span = Span::new(arg_span.start + 1, arg_span.len - 1);
369 match split(flag, flag_span) {
370 Some(tokens) => {
371 let mut tokens = tokens.into_iter();
373 let Some(key) = tokens.next() else {
374 unreachable!()
375 };
376 let Some(value) = tokens.next() else {
377 unreachable!()
378 };
379
380 let short_char = key.s;
381 tracing::trace!("Looking up short flag {short_char}");
382 let fields = self.fields(&p)?;
383 let Some(field_index) =
384 find_field_index_with_short_char(fields, short_char)
385 else {
386 return Err(ArgsErrorKind::UnknownShortFlag {
387 flag: short_char.to_string(),
388 fields,
389 precise_span: None, });
391 };
392 p = self.handle_field(p, field_index, Some(value))?;
393 }
394 None => {
395 let fields = self.fields(&p)?;
397 p = self.process_short_flag(p, flag, flag_span, fields)?;
398 }
399 }
400 }
401 ArgType::Positional => {
402 let fields = self.fields(&p)?;
403
404 if let Some((field_index, field)) = find_subcommand_field(fields)
406 && !p.is_field_set(field_index)?
407 {
408 p = self.handle_subcommand_field(p, field_index, field)?;
409 continue;
410 }
411
412 let mut chosen_field_index: Option<usize> = None;
414
415 for (field_index, field) in fields.iter().enumerate() {
416 let is_positional = field.has_attr(Some("args"), "positional");
417 if !is_positional {
418 continue;
419 }
420
421 if matches!(field.shape().def, Def::List(_list_def)) {
424 } else if p.is_field_set(field_index)? {
426 continue;
428 }
429
430 tracing::trace!("found field, it's not a list {field:?}");
431 chosen_field_index = Some(field_index);
432 break;
433 }
434
435 let Some(chosen_field_index) = chosen_field_index else {
436 return Err(ArgsErrorKind::UnexpectedPositionalArgument { fields });
437 };
438
439 p = p.begin_nth_field(chosen_field_index)?;
440
441 let value = self.args[self.index];
442
443 if let Type::User(UserType::Enum(_)) = fields[chosen_field_index].shape().ty
445 && !fields[chosen_field_index].has_attr(Some("args"), "subcommand")
446 {
447 return Err(ArgsErrorKind::EnumWithoutSubcommandAttribute {
448 field: &fields[chosen_field_index],
449 });
450 }
451
452 p = self.handle_value(p, value)?;
453
454 p = p.end()?;
455 self.index += 1;
456 }
457 ArgType::None => todo!(),
458 }
459 }
460
461 p = self.pop_and_apply_counted_fields(p)?;
462 p = self.finalize_struct(p)?;
463
464 Ok(p.build()?)
465 }
466
467 fn parse_variant_fields(
469 &mut self,
470 mut p: Partial<'static>,
471 variant: &'static Variant,
472 ) -> Result<Partial<'static>, ArgsErrorKind> {
473 let fields = variant.data.fields;
474
475 if variant.data.kind == StructKind::TupleStruct && fields.len() == 1 {
477 let inner_shape = fields[0].shape();
478 if let Type::User(UserType::Struct(struct_type)) = inner_shape.ty {
479 p = p.begin_nth_field(0)?;
480 p = self.parse_fields_loop(p, struct_type.fields)?;
481 p = self.pop_and_apply_counted_fields(p)?;
482 p = self.finalize_variant_fields(p, struct_type.fields)?;
483 p = p.end()?;
484 return Ok(p);
485 }
486 }
487
488 self.push_counted_scope();
489
490 while self.args.len() > self.index {
491 let arg = self.args[self.index];
492 let arg_span = Span::new(self.arg_indices[self.index], arg.len());
493 let at = if self.positional_only {
494 ArgType::Positional
495 } else {
496 ArgType::parse(arg)
497 };
498 tracing::trace!("Parsing variant field, arg: {at:?}");
499
500 match at {
501 ArgType::DoubleDash => {
502 self.positional_only = true;
503 self.index += 1;
504 }
505 ArgType::LongFlag(flag) => {
506 if flag.starts_with('-') {
508 return Err(ArgsErrorKind::UnknownLongFlag {
509 flag: flag.to_string(),
510 fields,
511 });
512 }
513
514 let flag_span = Span::new(arg_span.start + 2, arg_span.len - 2);
515 match split(flag, flag_span) {
516 Some(tokens) => {
517 let mut tokens = tokens.into_iter();
518 let key = tokens.next().unwrap();
519 let value = tokens.next().unwrap();
520
521 let flag = key.s;
522 tracing::trace!("Looking up long flag {flag} in variant");
523 let Some(field_index) =
524 find_field_index_by_effective_name(fields, flag)
525 else {
526 return Err(ArgsErrorKind::UnknownLongFlag {
527 flag: flag.to_string(),
528 fields,
529 });
530 };
531 p = self.handle_field(p, field_index, Some(value))?;
532 }
533 None => {
534 tracing::trace!("Looking up long flag {flag} in variant");
535 let Some(field_index) =
536 find_field_index_by_effective_name(fields, flag)
537 else {
538 return Err(ArgsErrorKind::UnknownLongFlag {
539 flag: flag.to_string(),
540 fields,
541 });
542 };
543 if !self.try_handle_counted_long_flag(&fields[field_index], field_index)
544 {
545 p = self.handle_field(p, field_index, None)?;
546 }
547 }
548 }
549 }
550 ArgType::ShortFlag(flag) => {
551 let flag_span = Span::new(arg_span.start + 1, arg_span.len - 1);
552 match split(flag, flag_span) {
553 Some(tokens) => {
554 let mut tokens = tokens.into_iter();
555 let key = tokens.next().unwrap();
556 let value = tokens.next().unwrap();
557
558 let short_char = key.s;
559 tracing::trace!("Looking up short flag {short_char} in variant");
560 let Some(field_index) =
561 find_field_index_with_short_char(fields, short_char)
562 else {
563 return Err(ArgsErrorKind::UnknownShortFlag {
564 flag: short_char.to_string(),
565 fields,
566 precise_span: None, });
568 };
569 p = self.handle_field(p, field_index, Some(value))?;
570 }
571 None => {
572 p = self.process_short_flag(p, flag, flag_span, fields)?;
574 }
575 }
576 }
577 ArgType::Positional => {
578 if let Some((field_index, field)) = find_subcommand_field(fields)
580 && !p.is_field_set(field_index)?
581 {
582 p = self.handle_subcommand_field(p, field_index, field)?;
583 continue;
584 }
585
586 let mut chosen_field_index: Option<usize> = None;
588
589 for (field_index, field) in fields.iter().enumerate() {
590 let is_positional = field.has_attr(Some("args"), "positional");
591 if !is_positional {
592 continue;
593 }
594
595 if matches!(field.shape().def, Def::List(_)) {
596 } else if p.is_field_set(field_index)? {
598 continue;
599 }
600
601 chosen_field_index = Some(field_index);
602 break;
603 }
604
605 let Some(chosen_field_index) = chosen_field_index else {
606 return Err(ArgsErrorKind::UnexpectedPositionalArgument { fields });
607 };
608
609 p = p.begin_nth_field(chosen_field_index)?;
610 let value = self.args[self.index];
611
612 if let Type::User(UserType::Enum(_)) = fields[chosen_field_index].shape().ty
614 && !fields[chosen_field_index].has_attr(Some("args"), "subcommand")
615 {
616 return Err(ArgsErrorKind::EnumWithoutSubcommandAttribute {
617 field: &fields[chosen_field_index],
618 });
619 }
620
621 p = self.handle_value(p, value)?;
622 p = p.end()?;
623 self.index += 1;
624 }
625 ArgType::None => todo!(),
626 }
627 }
628
629 p = self.pop_and_apply_counted_fields(p)?;
631 p = self.finalize_variant_fields(p, fields)?;
632
633 Ok(p)
634 }
635
636 fn handle_subcommand_field(
638 &mut self,
639 p: Partial<'static>,
640 field_index: usize,
641 field: &'static Field,
642 ) -> Result<Partial<'static>, ArgsErrorKind> {
643 let field_shape = field.shape();
644 tracing::trace!(
645 "Handling subcommand field: {} with shape {}",
646 field.name,
647 field_shape
648 );
649
650 let mut p = p.begin_nth_field(field_index)?;
651
652 let (is_optional, _enum_shape, enum_type) = if let Def::Option(option_def) = field_shape.def
655 {
656 let inner_shape = option_def.t;
658 if let Type::User(UserType::Enum(enum_type)) = inner_shape.ty {
659 (true, inner_shape, enum_type)
660 } else {
661 return Err(ArgsErrorKind::NoFields { shape: field_shape });
662 }
663 } else if let Type::User(UserType::Enum(enum_type)) = field_shape.ty {
664 (false, field_shape, enum_type)
666 } else {
667 return Err(ArgsErrorKind::NoFields { shape: field_shape });
668 };
669
670 let subcommand_name = self.args[self.index];
672 tracing::trace!("Looking for subcommand variant: {subcommand_name}");
673
674 let variant = match find_variant_by_name(enum_type, subcommand_name) {
676 Ok(v) => v,
677 Err(e) => {
678 if is_optional {
679 return Err(e);
684 } else {
685 return Err(e);
686 }
687 }
688 };
689
690 self.index += 1;
691
692 if self.index < self.args.len() && is_help_flag(self.args[self.index]) {
694 let help_text = crate::help::generate_subcommand_help(
696 variant,
697 "command", &HelpConfig::default(),
699 );
700 return Err(ArgsErrorKind::HelpRequested { help_text });
701 }
702
703 if is_optional {
704 p = p.begin_some()?;
706 }
707
708 p = p.select_variant_named(variant.effective_name())?;
710
711 p = self.parse_variant_fields(p, variant)?;
713
714 if is_optional {
715 p = p.end()?; }
717
718 p = p.end()?; Ok(p)
721 }
722
723 fn parse_fields_loop(
725 &mut self,
726 mut p: Partial<'static>,
727 fields: &'static [Field],
728 ) -> Result<Partial<'static>, ArgsErrorKind> {
729 self.push_counted_scope();
730
731 while self.args.len() > self.index {
732 let arg = self.args[self.index];
733 let arg_span = Span::new(self.arg_indices[self.index], arg.len());
734 let at = if self.positional_only {
735 ArgType::Positional
736 } else {
737 ArgType::parse(arg)
738 };
739 tracing::trace!("Parsing flattened struct field, arg: {at:?}");
740
741 match at {
742 ArgType::DoubleDash => {
743 self.positional_only = true;
744 self.index += 1;
745 }
746 ArgType::LongFlag(flag) => {
747 if flag.starts_with('-') {
748 return Err(ArgsErrorKind::UnknownLongFlag {
749 flag: flag.to_string(),
750 fields,
751 });
752 }
753
754 let flag_span = Span::new(arg_span.start + 2, arg_span.len - 2);
755 match split(flag, flag_span) {
756 Some(tokens) => {
757 let mut tokens = tokens.into_iter();
758 let key = tokens.next().unwrap();
759 let value = tokens.next().unwrap();
760
761 let flag = key.s;
762 let Some(field_index) =
763 find_field_index_by_effective_name(fields, flag)
764 else {
765 return Err(ArgsErrorKind::UnknownLongFlag {
766 flag: flag.to_string(),
767 fields,
768 });
769 };
770 p = self.handle_field(p, field_index, Some(value))?;
771 }
772 None => {
773 let Some(field_index) =
774 find_field_index_by_effective_name(fields, flag)
775 else {
776 return Err(ArgsErrorKind::UnknownLongFlag {
777 flag: flag.to_string(),
778 fields,
779 });
780 };
781 if !self.try_handle_counted_long_flag(&fields[field_index], field_index)
782 {
783 p = self.handle_field(p, field_index, None)?;
784 }
785 }
786 }
787 }
788 ArgType::ShortFlag(flag) => {
789 let flag_span = Span::new(arg_span.start + 1, arg_span.len - 1);
790 match split(flag, flag_span) {
791 Some(tokens) => {
792 let mut tokens = tokens.into_iter();
793 let key = tokens.next().unwrap();
794 let value = tokens.next().unwrap();
795
796 let short_char = key.s;
797 let Some(field_index) =
798 find_field_index_with_short_char(fields, short_char)
799 else {
800 return Err(ArgsErrorKind::UnknownShortFlag {
801 flag: short_char.to_string(),
802 fields,
803 precise_span: None,
804 });
805 };
806 p = self.handle_field(p, field_index, Some(value))?;
807 }
808 None => {
809 p = self.process_short_flag(p, flag, flag_span, fields)?;
810 }
811 }
812 }
813 ArgType::Positional => {
814 let mut chosen_field_index: Option<usize> = None;
816 for (field_index, field) in fields.iter().enumerate() {
817 let is_positional = field.has_attr(Some("args"), "positional");
818 if !is_positional {
819 continue;
820 }
821
822 if matches!(field.shape().def, Def::List(_)) {
825 } else if p.is_field_set(field_index)? {
827 continue;
828 }
829
830 chosen_field_index = Some(field_index);
831 break;
832 }
833
834 if let Some(field_index) = chosen_field_index {
835 let value = SplitToken {
836 s: arg,
837 span: arg_span,
838 };
839 p = self.handle_field(p, field_index, Some(value))?;
840 } else {
841 return Err(ArgsErrorKind::UnexpectedPositionalArgument { fields });
842 }
843 }
844 ArgType::None => todo!(),
845 }
846 }
847 Ok(p)
848 }
849
850 fn finalize_struct(&self, mut p: Partial<'static>) -> Result<Partial<'static>, ArgsErrorKind> {
852 let fields = self.fields(&p)?;
853 for (field_index, field) in fields.iter().enumerate() {
854 if p.is_field_set(field_index)? {
855 continue;
856 }
857
858 if field.has_attr(Some("args"), "subcommand") {
860 let field_shape = field.shape();
861 if let Def::Option(_) = field_shape.def {
862 p = p.set_nth_field_to_default(field_index)?;
865 continue;
866 } else {
867 return Err(ArgsErrorKind::MissingSubcommand {
869 variants: get_variants_from_shape(field_shape),
870 });
871 }
872 }
873
874 if is_counted_field(field) && is_supported_counted_type(field.shape()) {
875 p = p.begin_nth_field(field_index)?;
877 p = p.parse_from_str("0")?;
878 p = p.end()?;
879 } else if field.has_default() {
880 tracing::trace!("Setting #{field_index} field to default: {field:?}");
881 p = p.set_nth_field_to_default(field_index)?;
882 } else if field.shape().is_shape(bool::SHAPE) {
883 p = p.set_nth_field(field_index, false)?;
885 } else if let Def::Option(_) = field.shape().def {
886 p = p.set_nth_field_to_default(field_index)?;
888 } else {
889 return Err(ArgsErrorKind::MissingArgument { field });
890 }
891 }
892 Ok(p)
893 }
894
895 fn finalize_variant_fields(
897 &self,
898 mut p: Partial<'static>,
899 fields: &'static [Field],
900 ) -> Result<Partial<'static>, ArgsErrorKind> {
901 for (field_index, field) in fields.iter().enumerate() {
902 if p.is_field_set(field_index)? {
903 continue;
904 }
905
906 if field.has_attr(Some("args"), "subcommand") {
908 let field_shape = field.shape();
909 if let Def::Option(_) = field_shape.def {
910 p = p.set_nth_field_to_default(field_index)?;
912 continue;
913 } else {
914 return Err(ArgsErrorKind::MissingSubcommand {
916 variants: get_variants_from_shape(field_shape),
917 });
918 }
919 }
920
921 if is_counted_field(field) && is_supported_counted_type(field.shape()) {
922 p = p.begin_nth_field(field_index)?;
924 p = p.parse_from_str("0")?;
925 p = p.end()?;
926 } else if field.has_default() {
927 tracing::trace!("Setting variant field #{field_index} to default: {field:?}");
928 p = p.set_nth_field_to_default(field_index)?;
929 } else if field.shape().is_shape(bool::SHAPE) {
930 p = p.set_nth_field(field_index, false)?;
931 } else if let Def::Option(_) = field.shape().def {
932 p = p.set_nth_field_to_default(field_index)?;
934 } else {
935 return Err(ArgsErrorKind::MissingArgument { field });
936 }
937 }
938 Ok(p)
939 }
940}
941
942fn find_variant_by_name(
944 enum_type: EnumType,
945 name: &str,
946) -> Result<&'static Variant, ArgsErrorKind> {
947 tracing::trace!(
948 "find_variant_by_name: looking for '{}' among variants: {:?}",
949 name,
950 enum_type
951 .variants
952 .iter()
953 .map(|v| v.name)
954 .collect::<Vec<_>>()
955 );
956
957 for variant in enum_type.variants {
959 if let Some(attr) = variant.get_builtin_attr("rename")
960 && let Some(rename) = attr.get_as::<&str>()
961 && *rename == name
962 {
963 return Ok(variant);
964 }
965 }
966
967 for variant in enum_type.variants {
969 let kebab_name = variant.name.to_kebab_case();
970 tracing::trace!(
971 " checking variant '{}' -> kebab '{}' against '{}'",
972 variant.name,
973 kebab_name,
974 name
975 );
976 if kebab_name == name {
977 return Ok(variant);
978 }
979 }
980
981 for variant in enum_type.variants {
983 if variant.name == name {
984 return Ok(variant);
985 }
986 }
987
988 Err(ArgsErrorKind::UnknownSubcommand {
989 provided: name.to_string(),
990 variants: enum_type.variants,
991 })
992}
993
994fn find_subcommand_field(fields: &'static [Field]) -> Option<(usize, &'static Field)> {
996 fields
997 .iter()
998 .enumerate()
999 .find(|(_, f)| f.has_attr(Some("args"), "subcommand"))
1000}
1001
1002#[derive(Debug, PartialEq)]
1004struct SplitToken<'input> {
1005 s: &'input str,
1006 span: Span,
1007}
1008
1009fn split<'input>(input: &'input str, span: Span) -> Option<Vec<SplitToken<'input>>> {
1013 let equals_index = input.find('=')?;
1014
1015 let l = &input[0..equals_index];
1016 let l_span = Span::new(span.start, l.len());
1017
1018 let r = &input[equals_index + 1..];
1019 let r_span = Span::new(equals_index + 1, r.len());
1020
1021 Some(vec![
1022 SplitToken { s: l, span: l_span },
1023 SplitToken { s: r, span: r_span },
1024 ])
1025}
1026
1027#[test]
1028fn test_split() {
1029 assert_eq!(split("ababa", Span::new(5, 5)), None);
1030 assert_eq!(
1031 split("foo=bar", Span::new(0, 7)),
1032 Some(vec![
1033 SplitToken {
1034 s: "foo",
1035 span: Span::new(0, 3)
1036 },
1037 SplitToken {
1038 s: "bar",
1039 span: Span::new(4, 3)
1040 },
1041 ])
1042 );
1043 assert_eq!(
1044 split("foo=", Span::new(0, 4)),
1045 Some(vec![
1046 SplitToken {
1047 s: "foo",
1048 span: Span::new(0, 3)
1049 },
1050 SplitToken {
1051 s: "",
1052 span: Span::new(4, 0)
1053 },
1054 ])
1055 );
1056 assert_eq!(
1057 split("=bar", Span::new(0, 4)),
1058 Some(vec![
1059 SplitToken {
1060 s: "",
1061 span: Span::new(0, 0)
1062 },
1063 SplitToken {
1064 s: "bar",
1065 span: Span::new(1, 3)
1066 },
1067 ])
1068 );
1069}
1070
1071impl<'input> Context<'input> {
1072 fn process_short_flag(
1082 &mut self,
1083 mut p: Partial<'static>,
1084 flag: &'input str,
1085 flag_span: Span,
1086 fields: &'static [Field],
1087 ) -> Result<Partial<'static>, ArgsErrorKind> {
1088 let first_char = flag.chars().next().unwrap();
1090 let first_char_str = &flag[..first_char.len_utf8()];
1091 let rest = &flag[first_char.len_utf8()..];
1092
1093 tracing::trace!("Looking up short flag '{first_char}' (rest: '{rest}')");
1094
1095 let Some(field_index) = find_field_index_with_short_char(fields, first_char_str) else {
1097 let char_span = Span::new(flag_span.start, first_char.len_utf8());
1099 return Err(ArgsErrorKind::UnknownShortFlag {
1100 flag: first_char_str.to_string(),
1101 fields,
1102 precise_span: Some(char_span),
1103 });
1104 };
1105
1106 let field = &fields[field_index];
1107 let field_shape = field.shape();
1108
1109 let is_bool = field_shape.is_shape(bool::SHAPE);
1111 let is_bool_list = if let facet_core::Def::List(list_def) = field_shape.def {
1112 list_def.t.is_shape(bool::SHAPE)
1113 } else {
1114 false
1115 };
1116 let is_counted = is_counted_field(field) && is_supported_counted_type(field_shape);
1117
1118 if rest.is_empty() {
1119 if is_counted {
1121 self.increment_counted(field_index);
1122 self.index += 1;
1123 } else if is_bool || is_bool_list {
1124 p = p.begin_nth_field(field_index)?;
1126
1127 if is_bool_list {
1128 p = p.init_list()?;
1130 p = p.begin_list_item()?;
1131 p = p.set(true)?;
1132 p = p.end()?; } else {
1134 p = p.set(true)?;
1136 }
1137
1138 p = p.end()?; self.index += 1; } else {
1141 p = self.handle_field(p, field_index, None)?;
1143 }
1144 } else if is_counted {
1145 self.increment_counted(field_index);
1147 let rest_span = Span::new(flag_span.start + first_char.len_utf8(), rest.len());
1148 p = self.process_short_flag(p, rest, rest_span, fields)?;
1149 } else if is_bool || is_bool_list {
1150 p = p.begin_nth_field(field_index)?;
1154
1155 if is_bool_list {
1156 p = p.init_list()?;
1158 p = p.begin_list_item()?;
1159 p = p.set(true)?;
1160 p = p.end()?; } else {
1162 p = p.set(true)?;
1164 }
1165
1166 p = p.end()?; let rest_span = Span::new(flag_span.start + first_char.len_utf8(), rest.len());
1170 p = self.process_short_flag(p, rest, rest_span, fields)?;
1171 } else {
1173 let value_span = Span::new(flag_span.start + first_char.len_utf8(), rest.len());
1175 p = self.handle_field(
1176 p,
1177 field_index,
1178 Some(SplitToken {
1179 s: rest,
1180 span: value_span,
1181 }),
1182 )?;
1183 }
1184
1185 Ok(p)
1186 }
1187}
1188
1189fn find_field_index_with_short_char(fields: &'static [Field], short: &str) -> Option<usize> {
1193 let short_char = short.chars().next()?;
1194 fields.iter().position(|f| {
1195 if let Some(ext) = f.get_attr(Some("args"), "short") {
1196 if let Some(crate::Attr::Short(opt_char)) = ext.get_as::<crate::Attr>() {
1198 match opt_char {
1199 Some(c) => *c == short_char,
1200 None => {
1201 f.effective_name().starts_with(short_char)
1204 }
1205 }
1206 } else {
1207 false
1208 }
1209 } else {
1210 false
1211 }
1212 })
1213}
1214
1215fn find_field_index_by_effective_name(fields: &'static [Field], flag: &str) -> Option<usize> {
1218 let snek = flag.to_snake_case();
1219 fields
1220 .iter()
1221 .position(|f| f.effective_name().to_snake_case() == snek)
1222}