1#![warn(missing_docs)]
2#![warn(clippy::std_instead_of_core)]
3#![warn(clippy::std_instead_of_alloc)]
4#![forbid(unsafe_code)]
5#![doc = include_str!("../README.md")]
6
7extern crate alloc;
8use alloc::borrow::Cow;
9
10mod error;
11
12use error::{ArgsError, ArgsErrorKind};
13use facet_core::{Def, Facet, FieldAttribute};
14use facet_reflect::{ReflectError, Wip};
15
16fn parse_field<'facet>(wip: Wip<'facet>, value: &'facet str) -> Result<Wip<'facet>, ArgsError> {
17 let shape = wip.shape();
18 match shape.def {
19 Def::Scalar(_) => {
20 if shape.is_type::<String>() {
21 wip.put(value.to_string())
22 } else if shape.is_type::<&str>() {
23 wip.put(value)
24 } else if shape.is_type::<bool>() {
25 log::trace!("Boolean field detected, setting to true");
26 wip.put(value.to_lowercase() == "true")
27 } else {
28 wip.parse(value)
29 }
30 }
31 _def => {
32 return Err(ArgsError::new(ArgsErrorKind::GenericReflect(
33 ReflectError::OperationFailed {
34 shape,
35 operation: "parsing field",
36 },
37 )));
38 }
39 }
40 .map_err(|e| ArgsError::new(ArgsErrorKind::GenericReflect(e)))?
41 .pop()
42 .map_err(|e| ArgsError {
43 kind: ArgsErrorKind::GenericReflect(e),
44 })
45}
46
47fn kebab_to_snake(input: &str) -> Cow<str> {
48 if !input.contains('-') {
51 return Cow::Borrowed(input);
52 }
53 Cow::Owned(input.replace('-', "_"))
54}
55
56pub fn from_slice<'input, 'facet, T>(s: &[&'input str]) -> Result<T, ArgsError>
58where
59 T: Facet<'facet>,
60 'input: 'facet,
61{
62 log::trace!("Entering from_slice function");
63 let mut s = s;
64 let mut wip =
65 Wip::alloc::<T>().map_err(|e| ArgsError::new(ArgsErrorKind::GenericReflect(e)))?;
66 log::trace!("Allocated Poke for type T");
67 let Def::Struct(sd) = wip.shape().def else {
68 return Err(ArgsError::new(ArgsErrorKind::GenericArgsError(
69 "Expected struct defintion".to_string(),
70 )));
71 };
72
73 while let Some(token) = s.first() {
74 log::trace!("Processing token: {}", token);
75 s = &s[1..];
76
77 if let Some(key) = token.strip_prefix("--") {
78 let key = kebab_to_snake(key);
79 let field_index = match wip.field_index(&key) {
80 Some(index) => index,
81 None => {
82 return Err(ArgsError::new(ArgsErrorKind::GenericArgsError(format!(
83 "Unknown argument `{key}`",
84 ))));
85 }
86 };
87 log::trace!("Found named argument: {}", key);
88
89 let field = wip
90 .field(field_index)
91 .expect("field_index should be a valid field bound");
92
93 if field.shape().is_type::<bool>() {
94 wip = parse_field(field, "true")?;
96 } else {
97 let value = s
98 .first()
99 .ok_or(ArgsError::new(ArgsErrorKind::GenericArgsError(format!(
100 "expected value after argument `{key}`"
101 ))))?;
102 log::trace!("Field value: {}", value);
103 s = &s[1..];
104 wip = parse_field(field, value)?;
105 }
106 } else if let Some(key) = token.strip_prefix("-") {
107 log::trace!("Found short named argument: {}", key);
108 for (field_index, f) in sd.fields.iter().enumerate() {
109 if f.attributes
110 .iter()
111 .any(|a| matches!(a, FieldAttribute::Arbitrary(a) if a.contains("short") && a.contains(key))
112 )
113 {
114 log::trace!("Found field matching short_code: {} for field {}", key, f.name);
115 let field = wip.field(field_index).expect("field_index is in bounds");
116 if field.shape().is_type::<bool>() {
117 wip = parse_field(field, "true")?;
118 } else {
119 let value = s
120 .first()
121 .ok_or(ArgsError::new(ArgsErrorKind::GenericArgsError(format!(
122 "expected value after argument `{key}`"
123 ))))?;
124 log::trace!("Field value: {}", value);
125 s = &s[1..];
126 wip = parse_field(field, value)?;
127 }
128 break;
129 }
130 }
131 } else {
132 log::trace!("Encountered positional argument: {}", token);
133 for (field_index, f) in sd.fields.iter().enumerate() {
134 if f.attributes
135 .iter()
136 .any(|a| matches!(a, FieldAttribute::Arbitrary(a) if a.contains("positional")))
137 {
138 if wip
139 .is_field_set(field_index)
140 .expect("field_index is in bounds")
141 {
142 continue;
143 }
144 let field = wip.field(field_index).expect("field_index is in bounds");
145 wip = parse_field(field, token)?;
146 break;
147 }
148 }
149 }
150 }
151
152 for (field_index, f) in sd.fields.iter().enumerate() {
156 if f.shape().is_type::<bool>() && !wip.is_field_set(field_index).expect("in bounds") {
157 let field = wip.field(field_index).expect("field_index is in bounds");
158 wip = parse_field(field, "false")?;
159 }
160 }
161
162 let heap_vale = wip
163 .build()
164 .map_err(|e| ArgsError::new(ArgsErrorKind::GenericReflect(e)))?;
165 let result = heap_vale
166 .materialize()
167 .map_err(|e| ArgsError::new(ArgsErrorKind::GenericReflect(e)))?;
168 Ok(result)
169}