use std::path::{Component, Path, PathBuf};
use super::declaration::{Verb, WrappedCommand};
use super::error::WrappedError;
use super::parse::{Call, Item};
use super::PathCheck;
pub(crate) fn check(
declaration: &WrappedCommand,
verb: &Verb,
call: &Call,
) -> Vec<WrappedError> {
let scope = declaration.scope_of(verb);
let mut errors = Vec::new();
for use_ in &call.flags {
let Some(flag) = verb.flags.get(use_.flag_index) else {
continue;
};
if !use_.value_known {
continue;
}
let Some(value) = use_.value.as_deref() else {
continue;
};
if flag.int && value.parse::<i64>().is_err() {
errors.push(WrappedError::NotAnInteger {
command: declaration.name.clone(),
flag: flag.written_name(),
value: value.to_string(),
});
}
if !flag.choices.is_empty() && !flag.choices.iter().any(|c| c == value) {
errors.push(WrappedError::NotInChoices {
command: declaration.name.clone(),
flag: flag.written_name(),
value: value.to_string(),
choices: flag.choices.clone(),
});
}
}
if call.uncertain {
return errors;
}
for (flag_index, flag) in verb.flags.iter().enumerate() {
if flag.required && !call.flags.iter().any(|u| u.flag_index == flag_index) {
errors.push(WrappedError::MissingRequiredFlag {
command: declaration.name.clone(),
scope: scope.clone(),
flag: flag.written_name(),
});
}
}
for (slot, positional) in verb.positionals.iter().enumerate() {
if positional.required && !call.items.iter().any(|item| filled(item, slot)) {
errors.push(WrappedError::MissingRequiredPositional {
command: declaration.name.clone(),
scope: scope.clone(),
positional: positional.name.clone(),
});
}
}
errors
}
fn filled(item: &Item, slot: usize) -> bool {
matches!(item, Item::Positional { slot: filled, .. } if *filled == slot)
}
pub(crate) fn path_checks(
verb: &Verb,
call: &Call,
item_argv_index: &[usize],
) -> Vec<PathCheck> {
let mut checks = Vec::new();
for (item_index, item) in call.items.iter().enumerate() {
let Item::Positional { slot, value, known } = item else {
continue;
};
if !known {
continue;
}
let Some(positional) = verb.positionals.get(*slot) else {
continue;
};
let Some(root) = &positional.path_under else {
continue;
};
let Some(argv_index) = item_argv_index.get(item_index) else {
continue;
};
checks.push(PathCheck {
positional: positional.name.clone(),
value: value.clone(),
root: root.clone(),
argv_index: *argv_index,
resolved: false,
});
}
checks
}
pub(crate) fn path_is_under(candidate: &Path, root: &Path) -> bool {
let mut candidate_components = candidate.components();
for root_component in root.components() {
match candidate_components.next() {
Some(component) if component == root_component => {}
_ => return false,
}
}
true
}
pub(crate) fn resolve_under(
value: &str,
real_cwd: &Path,
root: &Path,
) -> Result<PathBuf, WrappedError> {
let Ok(canonical_root) = std::fs::canonicalize(root) else {
return Err(WrappedError::PathRootUnresolvable {
command: String::new(),
positional: String::new(),
root: root.to_path_buf(),
});
};
let joined = if Path::new(value).is_absolute() {
PathBuf::from(value)
} else {
real_cwd.join(value)
};
let canonical = canonicalize_existing_prefix(&joined);
if path_is_under(&canonical, &canonical_root) {
Ok(canonical)
} else {
Err(WrappedError::PathOutsideRoot {
command: String::new(),
positional: String::new(),
root: root.to_path_buf(),
value: value.to_string(),
})
}
}
fn canonicalize_existing_prefix(path: &Path) -> PathBuf {
let mut existing = path.to_path_buf();
let mut remainder: Vec<Component<'_>> = Vec::new();
let components: Vec<Component<'_>> = path.components().collect();
let mut keep = components.len();
let canonical = loop {
if let Ok(canonical) = std::fs::canonicalize(&existing) {
break canonical;
}
if keep == 0 {
break PathBuf::new();
}
keep -= 1;
remainder.push(components[keep]);
existing = components[..keep].iter().collect();
};
let mut resolved = canonical;
for component in remainder.iter().rev() {
match component {
Component::CurDir => {}
Component::ParentDir => {
resolved.pop();
}
other => resolved.push(other.as_os_str()),
}
}
resolved
}