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 #[allow(unsafe_code)]
229 fn work_inner(&mut self) -> Result<HeapValue<'static>, ArgsErrorKind> {
230 let p = unsafe { Partial::alloc_shape(self.shape) }?;
233
234 match self.shape.ty {
237 Type::User(UserType::Struct(_)) => self.parse_struct(p),
238 Type::User(UserType::Enum(_)) => {
239 Err(ArgsErrorKind::ReflectError(
241 facet_reflect::ReflectError::OperationFailed {
242 shape: self.shape,
243 operation: "Top-level enums must be wrapped in a struct with #[facet(args::subcommand)] attribute to be used as subcommands.",
244 },
245 ))
246 }
247 _ => Err(ArgsErrorKind::NoFields { shape: self.shape }),
248 }
249 }
250
251 fn parse_struct(
253 &mut self,
254 mut p: Partial<'static>,
255 ) -> Result<HeapValue<'static>, ArgsErrorKind> {
256 while self.args.len() > self.index {
257 let arg = self.args[self.index];
258 let arg_span = Span::new(self.arg_indices[self.index], arg.len());
259 let at = if self.positional_only {
260 ArgType::Positional
261 } else {
262 ArgType::parse(arg)
263 };
264 tracing::trace!("Parsed {at:?}");
265
266 match at {
267 ArgType::DoubleDash => {
268 self.positional_only = true;
269 self.index += 1;
270 }
271 ArgType::LongFlag(flag) => {
272 if flag.starts_with('-') {
274 let fields = self.fields(&p)?;
275 return Err(ArgsErrorKind::UnknownLongFlag {
276 flag: flag.to_string(),
277 fields,
278 });
279 }
280
281 let flag_span = Span::new(arg_span.start + 2, arg_span.len - 2);
282 match split(flag, flag_span) {
283 Some(tokens) => {
284 let mut tokens = tokens.into_iter();
286 let Some(key) = tokens.next() else {
287 unreachable!()
288 };
289 let Some(value) = tokens.next() else {
290 unreachable!()
291 };
292
293 let flag = key.s;
294 let snek = key.s.to_snake_case();
295 tracing::trace!("Looking up long flag {flag} (field name: {snek})");
296 let fields = self.fields(&p)?;
297 let Some(field_index) = p.field_index(&snek) else {
298 return Err(ArgsErrorKind::UnknownLongFlag {
299 flag: flag.to_string(),
300 fields,
301 });
302 };
303 p = self.handle_field(p, field_index, Some(value))?;
304 }
305 None => {
306 let snek = flag.to_snake_case();
307 tracing::trace!("Looking up long flag {flag} (field name: {snek})");
308 let fields = self.fields(&p)?;
309 let Some(field_index) = p.field_index(&snek) else {
310 return Err(ArgsErrorKind::UnknownLongFlag {
311 flag: flag.to_string(),
312 fields,
313 });
314 };
315 p = self.handle_field(p, field_index, None)?;
316 }
317 }
318 }
319 ArgType::ShortFlag(flag) => {
320 let flag_span = Span::new(arg_span.start + 1, arg_span.len - 1);
321 match split(flag, flag_span) {
322 Some(tokens) => {
323 let mut tokens = tokens.into_iter();
325 let Some(key) = tokens.next() else {
326 unreachable!()
327 };
328 let Some(value) = tokens.next() else {
329 unreachable!()
330 };
331
332 let short_char = key.s;
333 tracing::trace!("Looking up short flag {short_char}");
334 let fields = self.fields(&p)?;
335 let Some(field_index) =
336 find_field_index_with_short_char(fields, short_char)
337 else {
338 return Err(ArgsErrorKind::UnknownShortFlag {
339 flag: short_char.to_string(),
340 fields,
341 precise_span: None, });
343 };
344 p = self.handle_field(p, field_index, Some(value))?;
345 }
346 None => {
347 let fields = self.fields(&p)?;
349 p = self.process_short_flag(p, flag, flag_span, fields)?;
350 }
351 }
352 }
353 ArgType::Positional => {
354 let fields = self.fields(&p)?;
355
356 if let Some((field_index, field)) = find_subcommand_field(fields)
358 && !p.is_field_set(field_index)?
359 {
360 p = self.handle_subcommand_field(p, field_index, field)?;
361 continue;
362 }
363
364 let mut chosen_field_index: Option<usize> = None;
366
367 for (field_index, field) in fields.iter().enumerate() {
368 let is_positional = field.has_attr(Some("args"), "positional");
369 if !is_positional {
370 continue;
371 }
372
373 if matches!(field.shape().def, Def::List(_list_def)) {
376 } else if p.is_field_set(field_index)? {
378 continue;
380 }
381
382 tracing::trace!("found field, it's not a list {field:?}");
383 chosen_field_index = Some(field_index);
384 break;
385 }
386
387 let Some(chosen_field_index) = chosen_field_index else {
388 return Err(ArgsErrorKind::UnexpectedPositionalArgument { fields });
389 };
390
391 p = p.begin_nth_field(chosen_field_index)?;
392
393 let value = self.args[self.index];
394
395 if let Type::User(UserType::Enum(_)) = fields[chosen_field_index].shape().ty
397 && !fields[chosen_field_index].has_attr(Some("args"), "subcommand")
398 {
399 return Err(ArgsErrorKind::EnumWithoutSubcommandAttribute {
400 field: &fields[chosen_field_index],
401 });
402 }
403
404 p = self.handle_value(p, value)?;
405
406 p = p.end()?;
407 self.index += 1;
408 }
409 ArgType::None => todo!(),
410 }
411 }
412
413 p = self.finalize_struct(p)?;
415
416 Ok(p.build()?)
417 }
418
419 fn parse_variant_fields(
421 &mut self,
422 mut p: Partial<'static>,
423 variant: &'static Variant,
424 ) -> Result<Partial<'static>, ArgsErrorKind> {
425 let fields = variant.data.fields;
426
427 if variant.data.kind == StructKind::TupleStruct && fields.len() == 1 {
431 let inner_shape = fields[0].shape();
432 if let Type::User(UserType::Struct(struct_type)) = inner_shape.ty {
433 p = p.begin_nth_field(0)?;
436 p = self.parse_fields_loop(p, struct_type.fields)?;
439 p = self.finalize_variant_fields(p, struct_type.fields)?;
440 p = p.end()?;
441 return Ok(p);
442 }
443 }
444
445 while self.args.len() > self.index {
446 let arg = self.args[self.index];
447 let arg_span = Span::new(self.arg_indices[self.index], arg.len());
448 let at = if self.positional_only {
449 ArgType::Positional
450 } else {
451 ArgType::parse(arg)
452 };
453 tracing::trace!("Parsing variant field, arg: {at:?}");
454
455 match at {
456 ArgType::DoubleDash => {
457 self.positional_only = true;
458 self.index += 1;
459 }
460 ArgType::LongFlag(flag) => {
461 if flag.starts_with('-') {
463 return Err(ArgsErrorKind::UnknownLongFlag {
464 flag: flag.to_string(),
465 fields,
466 });
467 }
468
469 let flag_span = Span::new(arg_span.start + 2, arg_span.len - 2);
470 match split(flag, flag_span) {
471 Some(tokens) => {
472 let mut tokens = tokens.into_iter();
473 let key = tokens.next().unwrap();
474 let value = tokens.next().unwrap();
475
476 let snek = key.s.to_snake_case();
477 tracing::trace!(
478 "Looking up long flag {flag} in variant (field name: {snek})"
479 );
480 let Some(field_index) = fields.iter().position(|f| f.name == snek)
481 else {
482 return Err(ArgsErrorKind::UnknownLongFlag {
483 flag: flag.to_string(),
484 fields,
485 });
486 };
487 p = self.handle_field(p, field_index, Some(value))?;
488 }
489 None => {
490 let snek = flag.to_snake_case();
491 tracing::trace!(
492 "Looking up long flag {flag} in variant (field name: {snek})"
493 );
494 let Some(field_index) = fields.iter().position(|f| f.name == snek)
495 else {
496 return Err(ArgsErrorKind::UnknownLongFlag {
497 flag: flag.to_string(),
498 fields,
499 });
500 };
501 p = self.handle_field(p, field_index, None)?;
502 }
503 }
504 }
505 ArgType::ShortFlag(flag) => {
506 let flag_span = Span::new(arg_span.start + 1, arg_span.len - 1);
507 match split(flag, flag_span) {
508 Some(tokens) => {
509 let mut tokens = tokens.into_iter();
510 let key = tokens.next().unwrap();
511 let value = tokens.next().unwrap();
512
513 let short_char = key.s;
514 tracing::trace!("Looking up short flag {short_char} in variant");
515 let Some(field_index) =
516 find_field_index_with_short_char(fields, short_char)
517 else {
518 return Err(ArgsErrorKind::UnknownShortFlag {
519 flag: short_char.to_string(),
520 fields,
521 precise_span: None, });
523 };
524 p = self.handle_field(p, field_index, Some(value))?;
525 }
526 None => {
527 p = self.process_short_flag(p, flag, flag_span, fields)?;
529 }
530 }
531 }
532 ArgType::Positional => {
533 if let Some((field_index, field)) = find_subcommand_field(fields)
535 && !p.is_field_set(field_index)?
536 {
537 p = self.handle_subcommand_field(p, field_index, field)?;
538 continue;
539 }
540
541 let mut chosen_field_index: Option<usize> = None;
543
544 for (field_index, field) in fields.iter().enumerate() {
545 let is_positional = field.has_attr(Some("args"), "positional");
546 if !is_positional {
547 continue;
548 }
549
550 if matches!(field.shape().def, Def::List(_)) {
551 } else if p.is_field_set(field_index)? {
553 continue;
554 }
555
556 chosen_field_index = Some(field_index);
557 break;
558 }
559
560 let Some(chosen_field_index) = chosen_field_index else {
561 return Err(ArgsErrorKind::UnexpectedPositionalArgument { fields });
562 };
563
564 p = p.begin_nth_field(chosen_field_index)?;
565 let value = self.args[self.index];
566
567 if let Type::User(UserType::Enum(_)) = fields[chosen_field_index].shape().ty
569 && !fields[chosen_field_index].has_attr(Some("args"), "subcommand")
570 {
571 return Err(ArgsErrorKind::EnumWithoutSubcommandAttribute {
572 field: &fields[chosen_field_index],
573 });
574 }
575
576 p = self.handle_value(p, value)?;
577 p = p.end()?;
578 self.index += 1;
579 }
580 ArgType::None => todo!(),
581 }
582 }
583
584 p = self.finalize_variant_fields(p, fields)?;
586
587 Ok(p)
588 }
589
590 fn handle_subcommand_field(
592 &mut self,
593 p: Partial<'static>,
594 field_index: usize,
595 field: &'static Field,
596 ) -> Result<Partial<'static>, ArgsErrorKind> {
597 let field_shape = field.shape();
598 tracing::trace!(
599 "Handling subcommand field: {} with shape {}",
600 field.name,
601 field_shape
602 );
603
604 let mut p = p.begin_nth_field(field_index)?;
605
606 let (is_optional, _enum_shape, enum_type) = if let Def::Option(option_def) = field_shape.def
609 {
610 let inner_shape = option_def.t;
612 if let Type::User(UserType::Enum(enum_type)) = inner_shape.ty {
613 (true, inner_shape, enum_type)
614 } else {
615 return Err(ArgsErrorKind::NoFields { shape: field_shape });
616 }
617 } else if let Type::User(UserType::Enum(enum_type)) = field_shape.ty {
618 (false, field_shape, enum_type)
620 } else {
621 return Err(ArgsErrorKind::NoFields { shape: field_shape });
622 };
623
624 let subcommand_name = self.args[self.index];
626 tracing::trace!("Looking for subcommand variant: {subcommand_name}");
627
628 let variant = match find_variant_by_name(enum_type, subcommand_name) {
630 Ok(v) => v,
631 Err(e) => {
632 if is_optional {
633 return Err(e);
638 } else {
639 return Err(e);
640 }
641 }
642 };
643
644 self.index += 1;
645
646 if self.index < self.args.len() && is_help_flag(self.args[self.index]) {
648 let help_text = crate::help::generate_subcommand_help(
650 variant,
651 "command", &HelpConfig::default(),
653 );
654 return Err(ArgsErrorKind::HelpRequested { help_text });
655 }
656
657 if is_optional {
658 p = p.begin_some()?;
660 }
661
662 p = p.select_variant_named(variant.name)?;
664
665 p = self.parse_variant_fields(p, variant)?;
667
668 if is_optional {
669 p = p.end()?; }
671
672 p = p.end()?; Ok(p)
675 }
676
677 fn parse_fields_loop(
679 &mut self,
680 mut p: Partial<'static>,
681 fields: &'static [Field],
682 ) -> Result<Partial<'static>, ArgsErrorKind> {
683 while self.args.len() > self.index {
684 let arg = self.args[self.index];
685 let arg_span = Span::new(self.arg_indices[self.index], arg.len());
686 let at = if self.positional_only {
687 ArgType::Positional
688 } else {
689 ArgType::parse(arg)
690 };
691 tracing::trace!("Parsing flattened struct field, arg: {at:?}");
692
693 match at {
694 ArgType::DoubleDash => {
695 self.positional_only = true;
696 self.index += 1;
697 }
698 ArgType::LongFlag(flag) => {
699 if flag.starts_with('-') {
700 return Err(ArgsErrorKind::UnknownLongFlag {
701 flag: flag.to_string(),
702 fields,
703 });
704 }
705
706 let flag_span = Span::new(arg_span.start + 2, arg_span.len - 2);
707 match split(flag, flag_span) {
708 Some(tokens) => {
709 let mut tokens = tokens.into_iter();
710 let key = tokens.next().unwrap();
711 let value = tokens.next().unwrap();
712
713 let snek = key.s.to_snake_case();
714 let Some(field_index) = fields.iter().position(|f| f.name == snek)
715 else {
716 return Err(ArgsErrorKind::UnknownLongFlag {
717 flag: flag.to_string(),
718 fields,
719 });
720 };
721 p = self.handle_field(p, field_index, Some(value))?;
722 }
723 None => {
724 let snek = flag.to_snake_case();
725 let Some(field_index) = fields.iter().position(|f| f.name == snek)
726 else {
727 return Err(ArgsErrorKind::UnknownLongFlag {
728 flag: flag.to_string(),
729 fields,
730 });
731 };
732 p = self.handle_field(p, field_index, None)?;
733 }
734 }
735 }
736 ArgType::ShortFlag(flag) => {
737 let flag_span = Span::new(arg_span.start + 1, arg_span.len - 1);
738 match split(flag, flag_span) {
739 Some(tokens) => {
740 let mut tokens = tokens.into_iter();
741 let key = tokens.next().unwrap();
742 let value = tokens.next().unwrap();
743
744 let short_char = key.s;
745 let Some(field_index) =
746 find_field_index_with_short_char(fields, short_char)
747 else {
748 return Err(ArgsErrorKind::UnknownShortFlag {
749 flag: short_char.to_string(),
750 fields,
751 precise_span: None,
752 });
753 };
754 p = self.handle_field(p, field_index, Some(value))?;
755 }
756 None => {
757 p = self.process_short_flag(p, flag, flag_span, fields)?;
758 }
759 }
760 }
761 ArgType::Positional => {
762 let mut chosen_field_index: Option<usize> = None;
764 for (field_index, field) in fields.iter().enumerate() {
765 let is_positional = field.has_attr(Some("args"), "positional");
766 if !is_positional {
767 continue;
768 }
769
770 if matches!(field.shape().def, Def::List(_)) {
773 } else if p.is_field_set(field_index)? {
775 continue;
776 }
777
778 chosen_field_index = Some(field_index);
779 break;
780 }
781
782 if let Some(field_index) = chosen_field_index {
783 let value = SplitToken {
784 s: arg,
785 span: arg_span,
786 };
787 p = self.handle_field(p, field_index, Some(value))?;
788 } else {
789 return Err(ArgsErrorKind::UnexpectedPositionalArgument { fields });
790 }
791 }
792 ArgType::None => todo!(),
793 }
794 }
795 Ok(p)
796 }
797
798 fn finalize_struct(&self, mut p: Partial<'static>) -> Result<Partial<'static>, ArgsErrorKind> {
800 let fields = self.fields(&p)?;
801 for (field_index, field) in fields.iter().enumerate() {
802 if p.is_field_set(field_index)? {
803 continue;
804 }
805
806 if field.has_attr(Some("args"), "subcommand") {
808 let field_shape = field.shape();
809 if let Def::Option(_) = field_shape.def {
810 p = p.set_nth_field_to_default(field_index)?;
813 continue;
814 } else {
815 return Err(ArgsErrorKind::MissingSubcommand {
817 variants: get_variants_from_shape(field_shape),
818 });
819 }
820 }
821
822 if field.has_default() {
823 tracing::trace!("Setting #{field_index} field to default: {field:?}");
824 p = p.set_nth_field_to_default(field_index)?;
825 } else if field.shape().is_shape(bool::SHAPE) {
826 p = p.set_nth_field(field_index, false)?;
828 } else if let Def::Option(_) = field.shape().def {
829 p = p.set_nth_field_to_default(field_index)?;
831 } else {
832 return Err(ArgsErrorKind::MissingArgument { field });
833 }
834 }
835 Ok(p)
836 }
837
838 fn finalize_variant_fields(
840 &self,
841 mut p: Partial<'static>,
842 fields: &'static [Field],
843 ) -> Result<Partial<'static>, ArgsErrorKind> {
844 for (field_index, field) in fields.iter().enumerate() {
845 if p.is_field_set(field_index)? {
846 continue;
847 }
848
849 if field.has_attr(Some("args"), "subcommand") {
851 let field_shape = field.shape();
852 if let Def::Option(_) = field_shape.def {
853 p = p.set_nth_field_to_default(field_index)?;
855 continue;
856 } else {
857 return Err(ArgsErrorKind::MissingSubcommand {
859 variants: get_variants_from_shape(field_shape),
860 });
861 }
862 }
863
864 if field.has_default() {
865 tracing::trace!("Setting variant field #{field_index} to default: {field:?}");
866 p = p.set_nth_field_to_default(field_index)?;
867 } else if field.shape().is_shape(bool::SHAPE) {
868 p = p.set_nth_field(field_index, false)?;
869 } else if let Def::Option(_) = field.shape().def {
870 p = p.set_nth_field_to_default(field_index)?;
872 } else {
873 return Err(ArgsErrorKind::MissingArgument { field });
874 }
875 }
876 Ok(p)
877 }
878}
879
880fn find_variant_by_name(
882 enum_type: EnumType,
883 name: &str,
884) -> Result<&'static Variant, ArgsErrorKind> {
885 tracing::trace!(
886 "find_variant_by_name: looking for '{}' among variants: {:?}",
887 name,
888 enum_type
889 .variants
890 .iter()
891 .map(|v| v.name)
892 .collect::<Vec<_>>()
893 );
894
895 for variant in enum_type.variants {
897 if let Some(attr) = variant.get_builtin_attr("rename")
898 && let Some(rename) = attr.get_as::<&str>()
899 && *rename == name
900 {
901 return Ok(variant);
902 }
903 }
904
905 for variant in enum_type.variants {
907 let kebab_name = variant.name.to_kebab_case();
908 tracing::trace!(
909 " checking variant '{}' -> kebab '{}' against '{}'",
910 variant.name,
911 kebab_name,
912 name
913 );
914 if kebab_name == name {
915 return Ok(variant);
916 }
917 }
918
919 for variant in enum_type.variants {
921 if variant.name == name {
922 return Ok(variant);
923 }
924 }
925
926 Err(ArgsErrorKind::UnknownSubcommand {
927 provided: name.to_string(),
928 variants: enum_type.variants,
929 })
930}
931
932fn find_subcommand_field(fields: &'static [Field]) -> Option<(usize, &'static Field)> {
934 fields
935 .iter()
936 .enumerate()
937 .find(|(_, f)| f.has_attr(Some("args"), "subcommand"))
938}
939
940#[derive(Debug, PartialEq)]
942struct SplitToken<'input> {
943 s: &'input str,
944 span: Span,
945}
946
947fn split<'input>(input: &'input str, span: Span) -> Option<Vec<SplitToken<'input>>> {
951 let equals_index = input.find('=')?;
952
953 let l = &input[0..equals_index];
954 let l_span = Span::new(span.start, l.len());
955
956 let r = &input[equals_index + 1..];
957 let r_span = Span::new(equals_index + 1, r.len());
958
959 Some(vec![
960 SplitToken { s: l, span: l_span },
961 SplitToken { s: r, span: r_span },
962 ])
963}
964
965#[test]
966fn test_split() {
967 assert_eq!(split("ababa", Span::new(5, 5)), None);
968 assert_eq!(
969 split("foo=bar", Span::new(0, 7)),
970 Some(vec![
971 SplitToken {
972 s: "foo",
973 span: Span::new(0, 3)
974 },
975 SplitToken {
976 s: "bar",
977 span: Span::new(4, 3)
978 },
979 ])
980 );
981 assert_eq!(
982 split("foo=", Span::new(0, 4)),
983 Some(vec![
984 SplitToken {
985 s: "foo",
986 span: Span::new(0, 3)
987 },
988 SplitToken {
989 s: "",
990 span: Span::new(4, 0)
991 },
992 ])
993 );
994 assert_eq!(
995 split("=bar", Span::new(0, 4)),
996 Some(vec![
997 SplitToken {
998 s: "",
999 span: Span::new(0, 0)
1000 },
1001 SplitToken {
1002 s: "bar",
1003 span: Span::new(1, 3)
1004 },
1005 ])
1006 );
1007}
1008
1009impl<'input> Context<'input> {
1010 fn process_short_flag(
1020 &mut self,
1021 mut p: Partial<'static>,
1022 flag: &'input str,
1023 flag_span: Span,
1024 fields: &'static [Field],
1025 ) -> Result<Partial<'static>, ArgsErrorKind> {
1026 let first_char = flag.chars().next().unwrap();
1028 let first_char_str = &flag[..first_char.len_utf8()];
1029 let rest = &flag[first_char.len_utf8()..];
1030
1031 tracing::trace!("Looking up short flag '{first_char}' (rest: '{rest}')");
1032
1033 let Some(field_index) = find_field_index_with_short_char(fields, first_char_str) else {
1035 let char_span = Span::new(flag_span.start, first_char.len_utf8());
1037 return Err(ArgsErrorKind::UnknownShortFlag {
1038 flag: first_char_str.to_string(),
1039 fields,
1040 precise_span: Some(char_span),
1041 });
1042 };
1043
1044 let field = &fields[field_index];
1045 let field_shape = field.shape();
1046
1047 let is_bool = field_shape.is_shape(bool::SHAPE);
1049 let is_bool_list = if let facet_core::Def::List(list_def) = field_shape.def {
1050 list_def.t.is_shape(bool::SHAPE)
1051 } else {
1052 false
1053 };
1054
1055 if rest.is_empty() {
1056 if is_bool || is_bool_list {
1058 p = p.begin_nth_field(field_index)?;
1060
1061 if is_bool_list {
1062 p = p.begin_list()?;
1064 p = p.begin_list_item()?;
1065 p = p.set(true)?;
1066 p = p.end()?; } else {
1068 p = p.set(true)?;
1070 }
1071
1072 p = p.end()?; self.index += 1; } else {
1075 p = self.handle_field(p, field_index, None)?;
1077 }
1078 } else if is_bool || is_bool_list {
1079 p = p.begin_nth_field(field_index)?;
1083
1084 if is_bool_list {
1085 p = p.begin_list()?;
1087 p = p.begin_list_item()?;
1088 p = p.set(true)?;
1089 p = p.end()?; } else {
1091 p = p.set(true)?;
1093 }
1094
1095 p = p.end()?; let rest_span = Span::new(flag_span.start + first_char.len_utf8(), rest.len());
1099 p = self.process_short_flag(p, rest, rest_span, fields)?;
1100 } else {
1102 let value_span = Span::new(flag_span.start + first_char.len_utf8(), rest.len());
1104 p = self.handle_field(
1105 p,
1106 field_index,
1107 Some(SplitToken {
1108 s: rest,
1109 span: value_span,
1110 }),
1111 )?;
1112 }
1113
1114 Ok(p)
1115 }
1116}
1117
1118fn find_field_index_with_short_char(fields: &'static [Field], short: &str) -> Option<usize> {
1122 let short_char = short.chars().next()?;
1123 fields.iter().position(|f| {
1124 if let Some(ext) = f.get_attr(Some("args"), "short") {
1125 if let Some(crate::Attr::Short(opt_char)) = ext.get_as::<crate::Attr>() {
1127 match opt_char {
1128 Some(c) => *c == short_char,
1129 None => {
1130 f.name.starts_with(short_char)
1132 }
1133 }
1134 } else {
1135 false
1136 }
1137 } else {
1138 false
1139 }
1140 })
1141}