1use crate::{
2 arg::ArgType,
3 error::{ArgsError, ArgsErrorKind, ArgsErrorWithInput, get_variants_from_shape},
4 help::{HelpConfig, generate_help_for_shape},
5 span::Span,
6};
7use facet_core::{Def, EnumType, Facet, Field, Shape, StructKind, Type, UserType, Variant};
8use facet_reflect::{HeapValue, Partial};
9use heck::{ToKebabCase, ToSnakeCase};
10
11fn is_help_flag(arg: &str) -> bool {
13 matches!(arg, "-h" | "--help" | "-help" | "/?")
14}
15
16pub fn from_std_args<T: Facet<'static>>() -> Result<T, ArgsErrorWithInput> {
18 let args = std::env::args().skip(1).collect::<Vec<String>>();
19 let args_str: Vec<&str> = args.iter().map(|s| s.as_str()).collect();
20 from_slice(&args_str[..])
21}
22
23pub fn from_slice<'input, T: Facet<'static>>(
25 args: &'input [&'input str],
26) -> Result<T, ArgsErrorWithInput> {
27 from_slice_with_config(args, &HelpConfig::default())
28}
29
30pub fn from_slice_with_config<'input, T: Facet<'static>>(
32 args: &'input [&'input str],
33 help_config: &HelpConfig,
34) -> Result<T, ArgsErrorWithInput> {
35 if let Some(first_arg) = args.first()
37 && is_help_flag(first_arg)
38 {
39 let help_text = generate_help_for_shape(T::SHAPE, help_config);
40 let span = Span::new(0, first_arg.len());
41 return Err(ArgsErrorWithInput {
42 inner: ArgsError::new(ArgsErrorKind::HelpRequested { help_text }, span),
43 flattened_args: args.join(" "),
44 });
45 }
46
47 let mut cx = Context::new(args, T::SHAPE);
48 let hv = cx.work_add_input()?;
49
50 Ok(hv.materialize::<T>().unwrap())
52}
53
54struct Context<'input> {
55 shape: &'static Shape,
57
58 args: &'input [&'input str],
60
61 index: usize,
63
64 positional_only: bool,
66
67 arg_indices: Vec<usize>,
69
70 flattened_args: String,
72}
73
74impl<'input> Context<'input> {
75 fn new(args: &'input [&'input str], shape: &'static Shape) -> Self {
76 let mut arg_indices = vec![];
77 let mut flattened_args = String::new();
78
79 for arg in args {
80 arg_indices.push(flattened_args.len());
81 flattened_args.push_str(arg);
82 flattened_args.push(' ');
83 }
84 tracing::trace!("flattened args: {flattened_args:?}");
85 tracing::trace!("arg_indices: {arg_indices:?}");
86
87 Self {
88 shape,
89 args,
90 index: 0,
91 positional_only: false,
92 arg_indices,
93 flattened_args,
94 }
95 }
96
97 fn fields(&self, p: &Partial<'static>) -> Result<&'static [Field], ArgsErrorKind> {
99 let shape = p.shape();
100 match &shape.ty {
101 Type::User(UserType::Struct(struct_type)) => Ok(struct_type.fields),
102 _ => Err(ArgsErrorKind::NoFields { shape }),
103 }
104 }
105
106 fn handle_field(
109 &mut self,
110 p: Partial<'static>,
111 field_index: usize,
112 value: Option<SplitToken<'input>>,
113 ) -> Result<Partial<'static>, ArgsErrorKind> {
114 tracing::trace!("Handling field at index {field_index}");
115
116 let mut p = p.begin_nth_field(field_index)?;
117
118 tracing::trace!("After begin_field, shape is {}", p.shape());
119 if p.shape().is_shape(bool::SHAPE) {
120 let bool_value = if let Some(value) = value {
122 match value.s.to_lowercase().as_str() {
124 "true" | "yes" | "1" | "on" => true,
125 "false" | "no" | "0" | "off" => false,
126 "" => true, other => {
128 tracing::warn!("Unknown boolean value '{other}', treating as true");
129 true
130 }
131 }
132 } else {
133 true
135 };
136 tracing::trace!("Flag is boolean, setting it to {bool_value}");
137 p = p.set(bool_value)?;
138
139 self.index += 1;
140 } else {
141 tracing::trace!("Flag isn't boolean, expecting a {} value", p.shape());
142
143 if let Some(value) = value {
144 p = self.handle_value(p, value.s)?;
145 } else {
146 if self.index + 1 >= self.args.len() {
147 return Err(ArgsErrorKind::ExpectedValueGotEof { shape: p.shape() });
148 }
149 let value = self.args[self.index + 1];
150
151 self.index += 1;
152 p = self.handle_value(p, value)?;
153 }
154
155 self.index += 1;
156 }
157
158 p = p.end()?;
159
160 Ok(p)
161 }
162
163 fn handle_value(
164 &mut self,
165 p: Partial<'static>,
166 value: &'input str,
167 ) -> Result<Partial<'static>, ArgsErrorKind> {
168 if let Type::User(UserType::Enum(_)) = p.shape().ty {
171 return Err(ArgsErrorKind::ReflectError(
174 facet_reflect::ReflectError::OperationFailed {
175 shape: p.shape(),
176 operation: "Subcommands must be provided as positional arguments, not as flag values. Use the subcommand name directly instead of --flag <value>.",
177 },
178 ));
179 }
180
181 let p = match p.shape().def {
182 Def::List(_) => {
183 let mut p = p.begin_list()?;
185 p = p.begin_list_item()?;
186 p = p.parse_from_str(value)?;
187 p.end()?
188 }
189 Def::Option(_) => {
190 let mut p = p.begin_some()?;
192 p = p.parse_from_str(value)?;
193 p.end()?
194 }
195 _ => {
196 p.parse_from_str(value)?
198 }
199 };
200
201 Ok(p)
202 }
203
204 fn work_add_input(&mut self) -> Result<HeapValue<'static>, ArgsErrorWithInput> {
205 self.work().map_err(|e| ArgsErrorWithInput {
206 inner: e,
207 flattened_args: self.flattened_args.clone(),
208 })
209 }
210
211 fn work(&mut self) -> Result<HeapValue<'static>, ArgsError> {
213 self.work_inner().map_err(|kind| {
214 let span = kind.precise_span().unwrap_or_else(|| {
216 if self.index >= self.args.len() {
217 Span::new(self.flattened_args.len(), 0)
218 } else {
219 let arg = self.args[self.index];
220 let index = self.arg_indices[self.index];
221 Span::new(index, arg.len())
222 }
223 });
224 ArgsError::new(kind, span)
225 })
226 }
227
228 fn work_inner(&mut self) -> Result<HeapValue<'static>, ArgsErrorKind> {
229 let p = Partial::alloc_shape(self.shape)?;
230
231 match self.shape.ty {
234 Type::User(UserType::Struct(_)) => self.parse_struct(p),
235 Type::User(UserType::Enum(_)) => {
236 Err(ArgsErrorKind::ReflectError(
238 facet_reflect::ReflectError::OperationFailed {
239 shape: self.shape,
240 operation: "Top-level enums must be wrapped in a struct with #[facet(args::subcommand)] attribute to be used as subcommands.",
241 },
242 ))
243 }
244 _ => Err(ArgsErrorKind::NoFields { shape: self.shape }),
245 }
246 }
247
248 fn parse_struct(
250 &mut self,
251 mut p: Partial<'static>,
252 ) -> Result<HeapValue<'static>, ArgsErrorKind> {
253 while self.args.len() > self.index {
254 let arg = self.args[self.index];
255 let arg_span = Span::new(self.arg_indices[self.index], arg.len());
256 let at = if self.positional_only {
257 ArgType::Positional
258 } else {
259 ArgType::parse(arg)
260 };
261 tracing::trace!("Parsed {at:?}");
262
263 match at {
264 ArgType::DoubleDash => {
265 self.positional_only = true;
266 self.index += 1;
267 }
268 ArgType::LongFlag(flag) => {
269 if flag.starts_with('-') {
271 let fields = self.fields(&p)?;
272 return Err(ArgsErrorKind::UnknownLongFlag {
273 flag: flag.to_string(),
274 fields,
275 });
276 }
277
278 let flag_span = Span::new(arg_span.start + 2, arg_span.len - 2);
279 match split(flag, flag_span) {
280 Some(tokens) => {
281 let mut tokens = tokens.into_iter();
283 let Some(key) = tokens.next() else {
284 unreachable!()
285 };
286 let Some(value) = tokens.next() else {
287 unreachable!()
288 };
289
290 let flag = key.s;
291 let snek = key.s.to_snake_case();
292 tracing::trace!("Looking up long flag {flag} (field name: {snek})");
293 let fields = self.fields(&p)?;
294 let Some(field_index) = p.field_index(&snek) else {
295 return Err(ArgsErrorKind::UnknownLongFlag {
296 flag: flag.to_string(),
297 fields,
298 });
299 };
300 p = self.handle_field(p, field_index, Some(value))?;
301 }
302 None => {
303 let snek = flag.to_snake_case();
304 tracing::trace!("Looking up long flag {flag} (field name: {snek})");
305 let fields = self.fields(&p)?;
306 let Some(field_index) = p.field_index(&snek) else {
307 return Err(ArgsErrorKind::UnknownLongFlag {
308 flag: flag.to_string(),
309 fields,
310 });
311 };
312 p = self.handle_field(p, field_index, None)?;
313 }
314 }
315 }
316 ArgType::ShortFlag(flag) => {
317 let flag_span = Span::new(arg_span.start + 1, arg_span.len - 1);
318 match split(flag, flag_span) {
319 Some(tokens) => {
320 let mut tokens = tokens.into_iter();
322 let Some(key) = tokens.next() else {
323 unreachable!()
324 };
325 let Some(value) = tokens.next() else {
326 unreachable!()
327 };
328
329 let short_char = key.s;
330 tracing::trace!("Looking up short flag {short_char}");
331 let fields = self.fields(&p)?;
332 let Some(field_index) =
333 find_field_index_with_short_char(fields, short_char)
334 else {
335 return Err(ArgsErrorKind::UnknownShortFlag {
336 flag: short_char.to_string(),
337 fields,
338 precise_span: None, });
340 };
341 p = self.handle_field(p, field_index, Some(value))?;
342 }
343 None => {
344 let fields = self.fields(&p)?;
346 p = self.process_short_flag(p, flag, flag_span, fields)?;
347 }
348 }
349 }
350 ArgType::Positional => {
351 let fields = self.fields(&p)?;
352
353 if let Some((field_index, field)) = find_subcommand_field(fields)
355 && !p.is_field_set(field_index)?
356 {
357 p = self.handle_subcommand_field(p, field_index, field)?;
358 continue;
359 }
360
361 let mut chosen_field_index: Option<usize> = None;
363
364 for (field_index, field) in fields.iter().enumerate() {
365 let is_positional = field.has_attr(Some("args"), "positional");
366 if !is_positional {
367 continue;
368 }
369
370 if matches!(field.shape().def, Def::List(_list_def)) {
373 } else if p.is_field_set(field_index)? {
375 continue;
377 }
378
379 tracing::trace!("found field, it's not a list {field:?}");
380 chosen_field_index = Some(field_index);
381 break;
382 }
383
384 let Some(chosen_field_index) = chosen_field_index else {
385 return Err(ArgsErrorKind::UnexpectedPositionalArgument { fields });
386 };
387
388 p = p.begin_nth_field(chosen_field_index)?;
389
390 let value = self.args[self.index];
391
392 if let Type::User(UserType::Enum(_)) = fields[chosen_field_index].shape().ty
394 && !fields[chosen_field_index].has_attr(Some("args"), "subcommand")
395 {
396 return Err(ArgsErrorKind::EnumWithoutSubcommandAttribute {
397 field: &fields[chosen_field_index],
398 });
399 }
400
401 p = self.handle_value(p, value)?;
402
403 p = p.end()?;
404 self.index += 1;
405 }
406 ArgType::None => todo!(),
407 }
408 }
409
410 p = self.finalize_struct(p)?;
412
413 Ok(p.build()?)
414 }
415
416 fn parse_variant_fields(
418 &mut self,
419 mut p: Partial<'static>,
420 variant: &'static Variant,
421 ) -> Result<Partial<'static>, ArgsErrorKind> {
422 let fields = variant.data.fields;
423
424 if variant.data.kind == StructKind::TupleStruct && fields.len() == 1 {
428 let inner_shape = fields[0].shape();
429 if let Type::User(UserType::Struct(struct_type)) = inner_shape.ty {
430 p = p.begin_nth_field(0)?;
433 p = self.parse_fields_loop(p, struct_type.fields)?;
436 p = self.finalize_variant_fields(p, struct_type.fields)?;
437 p = p.end()?;
438 return Ok(p);
439 }
440 }
441
442 while self.args.len() > self.index {
443 let arg = self.args[self.index];
444 let arg_span = Span::new(self.arg_indices[self.index], arg.len());
445 let at = if self.positional_only {
446 ArgType::Positional
447 } else {
448 ArgType::parse(arg)
449 };
450 tracing::trace!("Parsing variant field, arg: {at:?}");
451
452 match at {
453 ArgType::DoubleDash => {
454 self.positional_only = true;
455 self.index += 1;
456 }
457 ArgType::LongFlag(flag) => {
458 if flag.starts_with('-') {
460 return Err(ArgsErrorKind::UnknownLongFlag {
461 flag: flag.to_string(),
462 fields,
463 });
464 }
465
466 let flag_span = Span::new(arg_span.start + 2, arg_span.len - 2);
467 match split(flag, flag_span) {
468 Some(tokens) => {
469 let mut tokens = tokens.into_iter();
470 let key = tokens.next().unwrap();
471 let value = tokens.next().unwrap();
472
473 let snek = key.s.to_snake_case();
474 tracing::trace!(
475 "Looking up long flag {flag} in variant (field name: {snek})"
476 );
477 let Some(field_index) = fields.iter().position(|f| f.name == snek)
478 else {
479 return Err(ArgsErrorKind::UnknownLongFlag {
480 flag: flag.to_string(),
481 fields,
482 });
483 };
484 p = self.handle_field(p, field_index, Some(value))?;
485 }
486 None => {
487 let snek = flag.to_snake_case();
488 tracing::trace!(
489 "Looking up long flag {flag} in variant (field name: {snek})"
490 );
491 let Some(field_index) = fields.iter().position(|f| f.name == snek)
492 else {
493 return Err(ArgsErrorKind::UnknownLongFlag {
494 flag: flag.to_string(),
495 fields,
496 });
497 };
498 p = self.handle_field(p, field_index, None)?;
499 }
500 }
501 }
502 ArgType::ShortFlag(flag) => {
503 let flag_span = Span::new(arg_span.start + 1, arg_span.len - 1);
504 match split(flag, flag_span) {
505 Some(tokens) => {
506 let mut tokens = tokens.into_iter();
507 let key = tokens.next().unwrap();
508 let value = tokens.next().unwrap();
509
510 let short_char = key.s;
511 tracing::trace!("Looking up short flag {short_char} in variant");
512 let Some(field_index) =
513 find_field_index_with_short_char(fields, short_char)
514 else {
515 return Err(ArgsErrorKind::UnknownShortFlag {
516 flag: short_char.to_string(),
517 fields,
518 precise_span: None, });
520 };
521 p = self.handle_field(p, field_index, Some(value))?;
522 }
523 None => {
524 p = self.process_short_flag(p, flag, flag_span, fields)?;
526 }
527 }
528 }
529 ArgType::Positional => {
530 if let Some((field_index, field)) = find_subcommand_field(fields)
532 && !p.is_field_set(field_index)?
533 {
534 p = self.handle_subcommand_field(p, field_index, field)?;
535 continue;
536 }
537
538 let mut chosen_field_index: Option<usize> = None;
540
541 for (field_index, field) in fields.iter().enumerate() {
542 let is_positional = field.has_attr(Some("args"), "positional");
543 if !is_positional {
544 continue;
545 }
546
547 if matches!(field.shape().def, Def::List(_)) {
548 } else if p.is_field_set(field_index)? {
550 continue;
551 }
552
553 chosen_field_index = Some(field_index);
554 break;
555 }
556
557 let Some(chosen_field_index) = chosen_field_index else {
558 return Err(ArgsErrorKind::UnexpectedPositionalArgument { fields });
559 };
560
561 p = p.begin_nth_field(chosen_field_index)?;
562 let value = self.args[self.index];
563
564 if let Type::User(UserType::Enum(_)) = fields[chosen_field_index].shape().ty
566 && !fields[chosen_field_index].has_attr(Some("args"), "subcommand")
567 {
568 return Err(ArgsErrorKind::EnumWithoutSubcommandAttribute {
569 field: &fields[chosen_field_index],
570 });
571 }
572
573 p = self.handle_value(p, value)?;
574 p = p.end()?;
575 self.index += 1;
576 }
577 ArgType::None => todo!(),
578 }
579 }
580
581 p = self.finalize_variant_fields(p, fields)?;
583
584 Ok(p)
585 }
586
587 fn handle_subcommand_field(
589 &mut self,
590 p: Partial<'static>,
591 field_index: usize,
592 field: &'static Field,
593 ) -> Result<Partial<'static>, ArgsErrorKind> {
594 let field_shape = field.shape();
595 tracing::trace!(
596 "Handling subcommand field: {} with shape {}",
597 field.name,
598 field_shape
599 );
600
601 let mut p = p.begin_nth_field(field_index)?;
602
603 let (is_optional, _enum_shape, enum_type) = if let Def::Option(option_def) = field_shape.def
606 {
607 let inner_shape = option_def.t;
609 if let Type::User(UserType::Enum(enum_type)) = inner_shape.ty {
610 (true, inner_shape, enum_type)
611 } else {
612 return Err(ArgsErrorKind::NoFields { shape: field_shape });
613 }
614 } else if let Type::User(UserType::Enum(enum_type)) = field_shape.ty {
615 (false, field_shape, enum_type)
617 } else {
618 return Err(ArgsErrorKind::NoFields { shape: field_shape });
619 };
620
621 let subcommand_name = self.args[self.index];
623 tracing::trace!("Looking for subcommand variant: {subcommand_name}");
624
625 let variant = match find_variant_by_name(enum_type, subcommand_name) {
627 Ok(v) => v,
628 Err(e) => {
629 if is_optional {
630 return Err(e);
635 } else {
636 return Err(e);
637 }
638 }
639 };
640
641 self.index += 1;
642
643 if self.index < self.args.len() && is_help_flag(self.args[self.index]) {
645 let help_text = crate::help::generate_subcommand_help(
647 variant,
648 "command", &HelpConfig::default(),
650 );
651 return Err(ArgsErrorKind::HelpRequested { help_text });
652 }
653
654 if is_optional {
655 p = p.begin_some()?;
657 }
658
659 p = p.select_variant_named(variant.name)?;
661
662 p = self.parse_variant_fields(p, variant)?;
664
665 if is_optional {
666 p = p.end()?; }
668
669 p = p.end()?; Ok(p)
672 }
673
674 fn parse_fields_loop(
676 &mut self,
677 mut p: Partial<'static>,
678 fields: &'static [Field],
679 ) -> Result<Partial<'static>, ArgsErrorKind> {
680 while self.args.len() > self.index {
681 let arg = self.args[self.index];
682 let arg_span = Span::new(self.arg_indices[self.index], arg.len());
683 let at = if self.positional_only {
684 ArgType::Positional
685 } else {
686 ArgType::parse(arg)
687 };
688 tracing::trace!("Parsing flattened struct field, arg: {at:?}");
689
690 match at {
691 ArgType::DoubleDash => {
692 self.positional_only = true;
693 self.index += 1;
694 }
695 ArgType::LongFlag(flag) => {
696 if flag.starts_with('-') {
697 return Err(ArgsErrorKind::UnknownLongFlag {
698 flag: flag.to_string(),
699 fields,
700 });
701 }
702
703 let flag_span = Span::new(arg_span.start + 2, arg_span.len - 2);
704 match split(flag, flag_span) {
705 Some(tokens) => {
706 let mut tokens = tokens.into_iter();
707 let key = tokens.next().unwrap();
708 let value = tokens.next().unwrap();
709
710 let snek = key.s.to_snake_case();
711 let Some(field_index) = fields.iter().position(|f| f.name == snek)
712 else {
713 return Err(ArgsErrorKind::UnknownLongFlag {
714 flag: flag.to_string(),
715 fields,
716 });
717 };
718 p = self.handle_field(p, field_index, Some(value))?;
719 }
720 None => {
721 let snek = flag.to_snake_case();
722 let Some(field_index) = fields.iter().position(|f| f.name == snek)
723 else {
724 return Err(ArgsErrorKind::UnknownLongFlag {
725 flag: flag.to_string(),
726 fields,
727 });
728 };
729 p = self.handle_field(p, field_index, None)?;
730 }
731 }
732 }
733 ArgType::ShortFlag(flag) => {
734 let flag_span = Span::new(arg_span.start + 1, arg_span.len - 1);
735 match split(flag, flag_span) {
736 Some(tokens) => {
737 let mut tokens = tokens.into_iter();
738 let key = tokens.next().unwrap();
739 let value = tokens.next().unwrap();
740
741 let short_char = key.s;
742 let Some(field_index) =
743 find_field_index_with_short_char(fields, short_char)
744 else {
745 return Err(ArgsErrorKind::UnknownShortFlag {
746 flag: short_char.to_string(),
747 fields,
748 precise_span: None,
749 });
750 };
751 p = self.handle_field(p, field_index, Some(value))?;
752 }
753 None => {
754 p = self.process_short_flag(p, flag, flag_span, fields)?;
755 }
756 }
757 }
758 ArgType::Positional => {
759 let mut chosen_field_index: Option<usize> = None;
761 for (field_index, field) in fields.iter().enumerate() {
762 let is_positional = field.has_attr(Some("args"), "positional");
763 if !is_positional {
764 continue;
765 }
766 if p.is_field_set(field_index)? {
767 continue;
768 }
769 chosen_field_index = Some(field_index);
770 break;
771 }
772
773 if let Some(field_index) = chosen_field_index {
774 let value = SplitToken {
775 s: arg,
776 span: arg_span,
777 };
778 p = self.handle_field(p, field_index, Some(value))?;
779 } else {
780 return Err(ArgsErrorKind::UnexpectedPositionalArgument { fields });
781 }
782 }
783 ArgType::None => todo!(),
784 }
785 }
786 Ok(p)
787 }
788
789 fn finalize_struct(&self, mut p: Partial<'static>) -> Result<Partial<'static>, ArgsErrorKind> {
791 let fields = self.fields(&p)?;
792 for (field_index, field) in fields.iter().enumerate() {
793 if p.is_field_set(field_index)? {
794 continue;
795 }
796
797 if field.has_attr(Some("args"), "subcommand") {
799 let field_shape = field.shape();
800 if let Def::Option(_) = field_shape.def {
801 p = p.set_nth_field_to_default(field_index)?;
804 continue;
805 } else {
806 return Err(ArgsErrorKind::MissingSubcommand {
808 variants: get_variants_from_shape(field_shape),
809 });
810 }
811 }
812
813 if field.has_default() {
814 tracing::trace!("Setting #{field_index} field to default: {field:?}");
815 p = p.set_nth_field_to_default(field_index)?;
816 } else if field.shape().is_shape(bool::SHAPE) {
817 p = p.set_nth_field(field_index, false)?;
819 } else {
820 return Err(ArgsErrorKind::MissingArgument { field });
821 }
822 }
823 Ok(p)
824 }
825
826 fn finalize_variant_fields(
828 &self,
829 mut p: Partial<'static>,
830 fields: &'static [Field],
831 ) -> Result<Partial<'static>, ArgsErrorKind> {
832 for (field_index, field) in fields.iter().enumerate() {
833 if p.is_field_set(field_index)? {
834 continue;
835 }
836
837 if field.has_attr(Some("args"), "subcommand") {
839 let field_shape = field.shape();
840 if let Def::Option(_) = field_shape.def {
841 p = p.set_nth_field_to_default(field_index)?;
843 continue;
844 } else {
845 return Err(ArgsErrorKind::MissingSubcommand {
847 variants: get_variants_from_shape(field_shape),
848 });
849 }
850 }
851
852 if field.has_default() {
853 tracing::trace!("Setting variant field #{field_index} to default: {field:?}");
854 p = p.set_nth_field_to_default(field_index)?;
855 } else if field.shape().is_shape(bool::SHAPE) {
856 p = p.set_nth_field(field_index, false)?;
857 } else {
858 return Err(ArgsErrorKind::MissingArgument { field });
859 }
860 }
861 Ok(p)
862 }
863}
864
865fn find_variant_by_name(
867 enum_type: EnumType,
868 name: &str,
869) -> Result<&'static Variant, ArgsErrorKind> {
870 tracing::trace!(
871 "find_variant_by_name: looking for '{}' among variants: {:?}",
872 name,
873 enum_type
874 .variants
875 .iter()
876 .map(|v| v.name)
877 .collect::<Vec<_>>()
878 );
879
880 for variant in enum_type.variants {
882 if let Some(attr) = variant.get_builtin_attr("rename")
883 && let Some(rename) = attr.get_as::<&str>()
884 && *rename == name
885 {
886 return Ok(variant);
887 }
888 }
889
890 for variant in enum_type.variants {
892 let kebab_name = variant.name.to_kebab_case();
893 tracing::trace!(
894 " checking variant '{}' -> kebab '{}' against '{}'",
895 variant.name,
896 kebab_name,
897 name
898 );
899 if kebab_name == name {
900 return Ok(variant);
901 }
902 }
903
904 for variant in enum_type.variants {
906 if variant.name == name {
907 return Ok(variant);
908 }
909 }
910
911 Err(ArgsErrorKind::UnknownSubcommand {
912 provided: name.to_string(),
913 variants: enum_type.variants,
914 })
915}
916
917fn find_subcommand_field(fields: &'static [Field]) -> Option<(usize, &'static Field)> {
919 fields
920 .iter()
921 .enumerate()
922 .find(|(_, f)| f.has_attr(Some("args"), "subcommand"))
923}
924
925#[derive(Debug, PartialEq)]
927struct SplitToken<'input> {
928 s: &'input str,
929 span: Span,
930}
931
932fn split<'input>(input: &'input str, span: Span) -> Option<Vec<SplitToken<'input>>> {
936 let equals_index = input.find('=')?;
937
938 let l = &input[0..equals_index];
939 let l_span = Span::new(span.start, l.len());
940
941 let r = &input[equals_index + 1..];
942 let r_span = Span::new(equals_index + 1, r.len());
943
944 Some(vec![
945 SplitToken { s: l, span: l_span },
946 SplitToken { s: r, span: r_span },
947 ])
948}
949
950#[test]
951fn test_split() {
952 assert_eq!(split("ababa", Span::new(5, 5)), None);
953 assert_eq!(
954 split("foo=bar", Span::new(0, 7)),
955 Some(vec![
956 SplitToken {
957 s: "foo",
958 span: Span::new(0, 3)
959 },
960 SplitToken {
961 s: "bar",
962 span: Span::new(4, 3)
963 },
964 ])
965 );
966 assert_eq!(
967 split("foo=", Span::new(0, 4)),
968 Some(vec![
969 SplitToken {
970 s: "foo",
971 span: Span::new(0, 3)
972 },
973 SplitToken {
974 s: "",
975 span: Span::new(4, 0)
976 },
977 ])
978 );
979 assert_eq!(
980 split("=bar", Span::new(0, 4)),
981 Some(vec![
982 SplitToken {
983 s: "",
984 span: Span::new(0, 0)
985 },
986 SplitToken {
987 s: "bar",
988 span: Span::new(1, 3)
989 },
990 ])
991 );
992}
993
994impl<'input> Context<'input> {
995 fn process_short_flag(
1005 &mut self,
1006 mut p: Partial<'static>,
1007 flag: &'input str,
1008 flag_span: Span,
1009 fields: &'static [Field],
1010 ) -> Result<Partial<'static>, ArgsErrorKind> {
1011 let first_char = flag.chars().next().unwrap();
1013 let first_char_str = &flag[..first_char.len_utf8()];
1014 let rest = &flag[first_char.len_utf8()..];
1015
1016 tracing::trace!("Looking up short flag '{first_char}' (rest: '{rest}')");
1017
1018 let Some(field_index) = find_field_index_with_short_char(fields, first_char_str) else {
1020 let char_span = Span::new(flag_span.start, first_char.len_utf8());
1022 return Err(ArgsErrorKind::UnknownShortFlag {
1023 flag: first_char_str.to_string(),
1024 fields,
1025 precise_span: Some(char_span),
1026 });
1027 };
1028
1029 let field = &fields[field_index];
1030 let field_shape = field.shape();
1031
1032 let is_bool = field_shape.is_shape(bool::SHAPE);
1034 let is_bool_list = if let facet_core::Def::List(list_def) = field_shape.def {
1035 list_def.t.is_shape(bool::SHAPE)
1036 } else {
1037 false
1038 };
1039
1040 if rest.is_empty() {
1041 if is_bool || is_bool_list {
1043 p = p.begin_nth_field(field_index)?;
1045
1046 if is_bool_list {
1047 p = p.begin_list()?;
1049 p = p.begin_list_item()?;
1050 p = p.set(true)?;
1051 p = p.end()?; } else {
1053 p = p.set(true)?;
1055 }
1056
1057 p = p.end()?; self.index += 1; } else {
1060 p = self.handle_field(p, field_index, None)?;
1062 }
1063 } else if is_bool || is_bool_list {
1064 p = p.begin_nth_field(field_index)?;
1068
1069 if is_bool_list {
1070 p = p.begin_list()?;
1072 p = p.begin_list_item()?;
1073 p = p.set(true)?;
1074 p = p.end()?; } else {
1076 p = p.set(true)?;
1078 }
1079
1080 p = p.end()?; let rest_span = Span::new(flag_span.start + first_char.len_utf8(), rest.len());
1084 p = self.process_short_flag(p, rest, rest_span, fields)?;
1085 } else {
1087 let value_span = Span::new(flag_span.start + first_char.len_utf8(), rest.len());
1089 p = self.handle_field(
1090 p,
1091 field_index,
1092 Some(SplitToken {
1093 s: rest,
1094 span: value_span,
1095 }),
1096 )?;
1097 }
1098
1099 Ok(p)
1100 }
1101}
1102
1103fn find_field_index_with_short_char(fields: &'static [Field], short: &str) -> Option<usize> {
1107 let short_char = short.chars().next()?;
1108 fields.iter().position(|f| {
1109 if let Some(ext) = f.get_attr(Some("args"), "short") {
1110 if let Some(crate::Attr::Short(opt_char)) = ext.get_as::<crate::Attr>() {
1112 match opt_char {
1113 Some(c) => *c == short_char,
1114 None => {
1115 f.name.starts_with(short_char)
1117 }
1118 }
1119 } else {
1120 false
1121 }
1122 } else {
1123 false
1124 }
1125 })
1126}