#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ColorWhen {
#[default]
Auto,
Never,
Always,
}
#[derive(Debug, Clone)]
pub enum PreferTarget {
IndexBased { name: String, index: usize },
PathBased { name: String, path: String },
PathOnly { path: String },
}
#[derive(Debug, Clone)]
pub enum DeleteTarget {
Index(usize),
Path(String),
}
#[derive(Debug, Clone)]
pub enum ApplyTarget {
CurrentShell,
Shell(String),
}
#[derive(Debug, Clone)]
pub enum PathEdit {
Move { from: usize, to: usize },
Swap { first: usize, second: usize },
}
#[derive(Debug, Clone)]
pub enum HistoryAction {
Undo(usize),
Redo(usize),
}
#[allow(clippy::struct_excessive_bools)]
#[derive(Debug, Default, Clone)]
pub struct Args {
pub names: Vec<String>,
pub all: bool,
pub full: bool,
pub follow_symlinks: bool,
pub print0: bool,
pub quiet: bool,
pub silent: bool,
pub one: bool,
pub show_nonexec: bool,
pub path_override: Option<String>,
pub color: ColorWhen,
pub stat: bool,
pub no_index: bool,
pub swap_fuzzy: bool,
pub path_edit: Option<PathEdit>,
pub prefer_target: Option<PreferTarget>,
pub clean: bool,
pub delete_targets: Vec<DeleteTarget>,
pub apply_target: Option<ApplyTarget>,
pub no_protect: bool,
pub diff: bool,
pub diff_full: bool,
pub init_shell: Option<String>,
pub reset: bool,
pub history_action: Option<HistoryAction>,
pub save_profile: Option<String>,
pub load_profile: Option<String>,
pub remove_profile: Option<String>,
}
impl Args {
pub fn parse_color(color: &str) -> Result<ColorWhen, String> {
match color {
"auto" => Ok(ColorWhen::Auto),
"never" => Ok(ColorWhen::Never),
"always" => Ok(ColorWhen::Always),
other => Err(format!(
"Invalid color option: {other}. Expected auto, never, or always"
)),
}
}
}
pub fn parse_prefer_arguments(tokens: Vec<String>) -> Result<PreferTarget, String> {
if tokens.is_empty() {
return Err("prefer requires at least one argument".to_string());
}
if tokens.len() == 1 {
let path = tokens.into_iter().next().unwrap();
return Ok(PreferTarget::PathOnly { path });
}
let mut iter = tokens.into_iter();
let name = iter.next().unwrap();
let remaining: Vec<String> = iter.collect();
let target_raw = remaining.join(" ");
if remaining.len() == 1 {
let candidate = &remaining[0];
if let Ok(index) = candidate.parse::<usize>()
&& !looks_like_path(candidate)
{
return Ok(PreferTarget::IndexBased { name, index });
}
}
Ok(PreferTarget::PathBased {
name,
path: target_raw,
})
}
pub fn parse_delete_arguments(tokens: Vec<String>) -> Result<Vec<DeleteTarget>, String> {
if tokens.is_empty() {
return Err("delete requires at least one target".to_string());
}
if tokens
.iter()
.all(|t| t.parse::<usize>().ok().is_some_and(|_| !looks_like_path(t)))
{
let indices = tokens
.into_iter()
.map(|t| DeleteTarget::Index(t.parse::<usize>().unwrap()))
.collect();
return Ok(indices);
}
Ok(tokens
.into_iter()
.map(|token| parse_delete_target(&token))
.collect())
}
fn parse_delete_target(target_str: &str) -> DeleteTarget {
if let Ok(index) = target_str.parse::<usize>()
&& !looks_like_path(target_str)
{
return DeleteTarget::Index(index);
}
DeleteTarget::Path(target_str.to_string())
}
fn looks_like_path(s: &str) -> bool {
s.contains('/') || s.starts_with('~') || s.starts_with('.') || s.contains('\\')
}
pub fn parse_add_arguments(tokens: Vec<String>) -> Result<Vec<String>, String> {
if tokens.is_empty() {
return Err("add requires at least one path".to_string());
}
Ok(tokens)
}