use std::sync::Arc;
use crate::{
CompareTypes, Flag, Record, ShellError, Signature, Span, Value, engine::Argument,
ir::DataSlice, shell_error::generic::GenericError,
};
#[inline]
pub fn flag_type_accepts_nothing(flag: &Flag) -> bool {
flag.type_accepts_nothing()
}
pub fn find_signature_flag<'a>(
signature: &'a Signature,
long: &[u8],
short: &[u8],
) -> Option<&'a Flag> {
signature.named.iter().find(|flag| {
(!long.is_empty() && flag.long.as_bytes() == long)
|| (!short.is_empty()
&& flag.short.is_some_and(|c| {
let mut buf = [0u8; 4];
c.encode_utf8(&mut buf).as_bytes() == short
}))
})
}
pub fn data_from_name_and_short(name: &str, short: &str) -> (Arc<[u8]>, DataSlice, DataSlice) {
let data: Vec<u8> = name.bytes().chain(short.bytes()).collect();
let data: Arc<[u8]> = data.into();
#[allow(clippy::cast_possible_truncation)]
let name_len = name.len() as u32;
#[allow(clippy::cast_possible_truncation)]
let short_len = short.len() as u32;
let name = DataSlice {
start: 0,
len: name_len,
};
let short = DataSlice {
start: name_len,
len: short_len,
};
(data, name, short)
}
pub fn expand_flag_record(
signature: &Signature,
record: Record,
spread_span: Span,
) -> Result<Vec<Argument>, ShellError> {
let mut out = Vec::with_capacity(record.len());
for (key, val) in record {
let Some(flag) = signature.get_long_flag(&key) else {
return Err(ShellError::Generic(GenericError::new(
format!("Unknown flag `{key}` in spread record"),
format!("`{key}` is not a named argument of this command"),
spread_span,
)));
};
let short = flag
.short
.map(|c| {
let mut buf = [0u8; 4];
c.encode_utf8(&mut buf).to_string()
})
.unwrap_or_default();
let (data, name_slice, short_slice) = data_from_name_and_short(&flag.long, &short);
if flag.arg.is_none() {
match val {
Value::Bool { val: true, .. } => {
out.push(Argument::Flag {
data,
name: name_slice,
short: short_slice,
span: spread_span,
});
}
Value::Bool { val: false, .. } | Value::Nothing { .. } => {}
other => {
return Err(ShellError::CantConvert {
to_type: "bool".into(),
from_type: other.get_type().to_string(),
span: other.span(),
help: Some(format!(
"spread field `{key}` is a switch; use true/false or omit/null"
)),
});
}
}
} else if val.is_nothing() && !flag_type_accepts_nothing(&flag) {
} else {
if !val.is_nothing()
&& let Some(shape) = &flag.arg
{
let expected = shape.to_type();
if !val.is_assignable_to(&expected) {
return Err(ShellError::CantConvert {
to_type: expected.to_string(),
from_type: val.get_type().to_string(),
span: val.span(),
help: Some(format!(
"spread field `{key}` does not match the type of `--{key}`"
)),
});
}
}
out.push(Argument::Named {
data,
name: name_slice,
short: short_slice,
span: spread_span,
val,
ast: None,
});
}
}
Ok(out)
}
pub fn can_rest_spread(signature: &Signature) -> bool {
signature.rest_positional.is_some() || signature.allows_unknown_args
}
pub fn list_spread_before_required_error(spread_span: Span) -> ShellError {
ShellError::Generic(GenericError::new(
"Cannot spread a list before required positional arguments are provided",
"List spreads fill rest arguments. Provide required positionals first, or use a record to spread named flags, e.g. ...{flag: value}",
spread_span,
))
}
pub fn normalize_engine_arguments(
signature: &Signature,
args: Vec<Argument>,
) -> Result<Vec<Argument>, ShellError> {
let mut expanded = Vec::with_capacity(args.len());
for arg in args {
match arg {
Argument::Named {
data,
name,
short,
span,
val: Value::Nothing { .. },
ast,
} => {
let accepts = find_signature_flag(signature, &data[name], &data[short])
.is_some_and(flag_type_accepts_nothing);
if accepts {
expanded.push(Argument::Named {
data,
name,
short,
span,
val: Value::nothing(span),
ast,
});
}
}
Argument::Spread {
vals,
span: spread_span,
ast,
} => match vals {
Value::Record { val, .. } => {
expanded.extend(expand_flag_record(
signature,
val.into_owned(),
spread_span,
)?);
}
Value::List { .. } => {
if !can_rest_spread(signature) {
return Err(ShellError::Generic(GenericError::new(
"Cannot spread a list into this command",
"This command has no ...rest parameter to receive a list spread. Use a record to spread named flags, e.g. ...{flag: value}",
spread_span,
)));
}
expanded.push(Argument::Spread {
vals,
span: spread_span,
ast,
});
}
Value::Nothing { .. } | Value::Error { .. } => {
expanded.push(Argument::Spread {
vals,
span: spread_span,
ast,
});
}
other => {
return Err(ShellError::CannotSpreadAsList { span: other.span() });
}
},
other => expanded.push(other),
}
}
Ok(expanded)
}