1use std::sync::Arc;
8
9use crate::{
10 CompareTypes, Flag, Record, ShellError, Signature, Span, Value, engine::Argument,
11 ir::DataSlice, shell_error::generic::GenericError,
12};
13
14#[inline]
16pub fn flag_type_accepts_nothing(flag: &Flag) -> bool {
17 flag.type_accepts_nothing()
18}
19
20pub fn find_signature_flag<'a>(
21 signature: &'a Signature,
22 long: &[u8],
23 short: &[u8],
24) -> Option<&'a Flag> {
25 signature.named.iter().find(|flag| {
26 (!long.is_empty() && flag.long.as_bytes() == long)
27 || (!short.is_empty()
28 && flag.short.is_some_and(|c| {
29 let mut buf = [0u8; 4];
30 c.encode_utf8(&mut buf).as_bytes() == short
31 }))
32 })
33}
34
35pub fn data_from_name_and_short(name: &str, short: &str) -> (Arc<[u8]>, DataSlice, DataSlice) {
36 let data: Vec<u8> = name.bytes().chain(short.bytes()).collect();
37 let data: Arc<[u8]> = data.into();
38 #[allow(clippy::cast_possible_truncation)]
40 let name_len = name.len() as u32;
41 #[allow(clippy::cast_possible_truncation)]
42 let short_len = short.len() as u32;
43 let name = DataSlice {
44 start: 0,
45 len: name_len,
46 };
47 let short = DataSlice {
48 start: name_len,
49 len: short_len,
50 };
51 (data, name, short)
52}
53
54pub fn expand_flag_record(
60 signature: &Signature,
61 record: Record,
62 spread_span: Span,
63) -> Result<Vec<Argument>, ShellError> {
64 let mut out = Vec::with_capacity(record.len());
65 for (key, val) in record {
66 let Some(flag) = signature.get_long_flag(&key) else {
67 return Err(ShellError::Generic(GenericError::new(
68 format!("Unknown flag `{key}` in spread record"),
69 format!("`{key}` is not a named argument of this command"),
70 spread_span,
71 )));
72 };
73
74 let short = flag
75 .short
76 .map(|c| {
77 let mut buf = [0u8; 4];
78 c.encode_utf8(&mut buf).to_string()
79 })
80 .unwrap_or_default();
81 let (data, name_slice, short_slice) = data_from_name_and_short(&flag.long, &short);
82
83 if flag.arg.is_none() {
84 match val {
86 Value::Bool { val: true, .. } => {
87 out.push(Argument::Flag {
88 data,
89 name: name_slice,
90 short: short_slice,
91 span: spread_span,
92 });
93 }
94 Value::Bool { val: false, .. } | Value::Nothing { .. } => {}
95 other => {
96 return Err(ShellError::CantConvert {
97 to_type: "bool".into(),
98 from_type: other.get_type().to_string(),
99 span: other.span(),
100 help: Some(format!(
101 "spread field `{key}` is a switch; use true/false or omit/null"
102 )),
103 });
104 }
105 }
106 } else if val.is_nothing() && !flag_type_accepts_nothing(&flag) {
107 } else {
109 if !val.is_nothing()
110 && let Some(shape) = &flag.arg
111 {
112 let expected = shape.to_type();
113 if !val.is_assignable_to(&expected) {
114 return Err(ShellError::CantConvert {
115 to_type: expected.to_string(),
116 from_type: val.get_type().to_string(),
117 span: val.span(),
118 help: Some(format!(
119 "spread field `{key}` does not match the type of `--{key}`"
120 )),
121 });
122 }
123 }
124 out.push(Argument::Named {
125 data,
126 name: name_slice,
127 short: short_slice,
128 span: spread_span,
129 val,
130 ast: None,
131 });
132 }
133 }
134 Ok(out)
135}
136
137pub fn can_rest_spread(signature: &Signature) -> bool {
139 signature.rest_positional.is_some() || signature.allows_unknown_args
140}
141
142pub fn list_spread_before_required_error(spread_span: Span) -> ShellError {
144 ShellError::Generic(GenericError::new(
145 "Cannot spread a list before required positional arguments are provided",
146 "List spreads fill rest arguments. Provide required positionals first, or use a record to spread named flags, e.g. ...{flag: value}",
147 spread_span,
148 ))
149}
150
151pub fn normalize_engine_arguments(
156 signature: &Signature,
157 args: Vec<Argument>,
158) -> Result<Vec<Argument>, ShellError> {
159 let mut expanded = Vec::with_capacity(args.len());
160
161 for arg in args {
162 match arg {
163 Argument::Named {
164 data,
165 name,
166 short,
167 span,
168 val: Value::Nothing { .. },
169 ast,
170 } => {
171 let accepts = find_signature_flag(signature, &data[name], &data[short])
172 .is_some_and(flag_type_accepts_nothing);
173 if accepts {
174 expanded.push(Argument::Named {
175 data,
176 name,
177 short,
178 span,
179 val: Value::nothing(span),
180 ast,
181 });
182 }
183 }
185 Argument::Spread {
186 vals,
187 span: spread_span,
188 ast,
189 } => match vals {
190 Value::Record { val, .. } => {
191 expanded.extend(expand_flag_record(
192 signature,
193 val.into_owned(),
194 spread_span,
195 )?);
196 }
197 Value::List { .. } => {
198 if !can_rest_spread(signature) {
199 return Err(ShellError::Generic(GenericError::new(
200 "Cannot spread a list into this command",
201 "This command has no ...rest parameter to receive a list spread. Use a record to spread named flags, e.g. ...{flag: value}",
202 spread_span,
203 )));
204 }
205 expanded.push(Argument::Spread {
206 vals,
207 span: spread_span,
208 ast,
209 });
210 }
211 Value::Nothing { .. } | Value::Error { .. } => {
212 expanded.push(Argument::Spread {
213 vals,
214 span: spread_span,
215 ast,
216 });
217 }
218 other => {
219 return Err(ShellError::CannotSpreadAsList { span: other.span() });
220 }
221 },
222 other => expanded.push(other),
223 }
224 }
225
226 Ok(expanded)
227}