use std::path::PathBuf;
use clap::{ArgAction, Parser, ValueHint};
#[derive(Debug, Parser)]
#[command(about, version, args_override_self = true, disable_help_flag = true)]
pub struct Cli {
#[arg(value_name = "FILE", default_value = ".", value_hint = ValueHint::AnyPath)]
pub inputs: Vec<PathBuf>,
#[arg(short, long, overrides_with = "almost_all")]
pub all: bool,
#[arg(short = 'A', long)]
pub almost_all: bool,
#[arg(long, value_name = "MODE", value_parser = ["always", "auto", "never"])]
pub color: Option<String>,
#[arg(long, value_name = "MODE", value_parser = ["always", "auto", "never"])]
pub icon: Option<String>,
#[arg(long, value_name = "THEME", value_parser = ["fancy", "unicode"])]
pub icon_theme: Option<String>,
#[arg(short = 'F', long = "classify")]
pub indicators: bool,
#[arg(short, long)]
pub long: bool,
#[arg(long)]
pub ignore_config: bool,
#[arg(long, value_name = "PATH")]
pub config_file: Option<PathBuf>,
#[arg(short = '1', long)]
pub oneline: bool,
#[arg(short = 'R', long, conflicts_with = "tree")]
pub recursive: bool,
#[arg(short, long)]
human_readable: bool,
#[arg(long)]
pub tree: bool,
#[arg(long, value_name = "NUM")]
pub depth: Option<usize>,
#[arg(short, long, conflicts_with_all = ["depth", "recursive"])]
pub directory_only: bool,
#[arg(long, value_name = "MODE", value_parser = ["rwx", "octal", "attributes", "disable"])]
pub permission: Option<String>,
#[arg(long, value_name = "MODE", value_parser = ["default", "short", "bytes"])]
pub size: Option<String>,
#[arg(long)]
pub total_size: bool,
#[arg(long, value_parser = validate_date_argument)]
pub date: Option<String>,
#[arg(short = 't', long)]
pub timesort: bool,
#[arg(short = 'S', long)]
pub sizesort: bool,
#[arg(short = 'X', long)]
pub extensionsort: bool,
#[arg(short = 'G', long)]
pub gitsort: bool,
#[arg(short = 'v', long)]
pub versionsort: bool,
#[arg(
long,
value_name = "TYPE",
value_parser = ["size", "time", "version", "extension", "git", "none"],
overrides_with_all = ["timesort", "sizesort", "extensionsort", "versionsort", "gitsort", "no_sort"]
)]
pub sort: Option<String>,
#[arg(short = 'U', long, overrides_with_all = ["timesort", "sizesort", "extensionsort", "versionsort", "gitsort", "sort"])]
pub no_sort: bool,
#[arg(short, long)]
pub reverse: bool,
#[arg(long, value_name = "MODE", value_parser = ["none", "first", "last"])]
pub group_dirs: Option<String>,
#[arg(long)]
pub group_directories_first: bool,
#[arg(
long,
value_delimiter = ',',
value_parser = ["permission", "user", "group", "context", "size", "date", "name", "inode", "links", "git"],
)]
pub blocks: Vec<String>,
#[arg(long)]
pub classic: bool,
#[arg(long)]
pub no_symlink: bool,
#[arg(short = 'I', long, value_name = "PATTERN")]
pub ignore_glob: Vec<String>,
#[arg(short, long)]
pub inode: bool,
#[arg(short, long)]
pub git: bool,
#[arg(short = 'L', long)]
pub dereference: bool,
#[arg(short = 'Z', long)]
pub context: bool,
#[arg(long, value_name = "MODE", value_parser = ["always", "auto", "never"])]
pub hyperlink: Option<String>,
#[arg(long)]
pub header: bool,
#[arg(long, value_name = "NUM")]
pub truncate_owner_after: Option<usize>,
#[arg(long, value_name = "STR")]
pub truncate_owner_marker: Option<String>,
#[arg(long, hide = !cfg!(windows))]
pub system_protected: bool,
#[arg(short = 'N', long)]
pub literal: bool,
#[arg(long, action = ArgAction::Help)]
help: (),
}
fn validate_date_argument(arg: &str) -> Result<String, String> {
if arg.starts_with('+') {
validate_time_format(arg)
} else if arg == "date" || arg == "relative" || arg == "locale" {
Result::Ok(arg.to_owned())
} else {
Result::Err("possible values: date, locale, relative, +date-time-format".to_owned())
}
}
pub fn validate_time_format(formatter: &str) -> Result<String, String> {
let mut chars = formatter.chars();
loop {
match chars.next() {
Some('%') => match chars.next() {
Some('.') => match chars.next() {
Some('f') => (),
Some(n @ ('3' | '6' | '9')) => match chars.next() {
Some('f') => (),
Some(c) => return Err(format!("invalid format specifier: %.{n}{c}")),
None => return Err("missing format specifier".to_owned()),
},
Some(c) => return Err(format!("invalid format specifier: %.{c}")),
None => return Err("missing format specifier".to_owned()),
},
Some(n @ (':' | '#')) => match chars.next() {
Some('z') => (),
Some(c) => return Err(format!("invalid format specifier: %{n}{c}")),
None => return Err("missing format specifier".to_owned()),
},
Some(n @ ('-' | '_' | '0')) => match chars.next() {
Some(
'C' | 'd' | 'e' | 'f' | 'G' | 'g' | 'H' | 'I' | 'j' | 'k' | 'l' | 'M' | 'm'
| 'S' | 's' | 'U' | 'u' | 'V' | 'W' | 'w' | 'Y' | 'y',
) => (),
Some(c) => return Err(format!("invalid format specifier: %{n}{c}")),
None => return Err("missing format specifier".to_owned()),
},
Some(
'A' | 'a' | 'B' | 'b' | 'C' | 'c' | 'D' | 'd' | 'e' | 'F' | 'f' | 'G' | 'g'
| 'H' | 'h' | 'I' | 'j' | 'k' | 'l' | 'M' | 'm' | 'n' | 'P' | 'p' | 'R' | 'r'
| 'S' | 's' | 'T' | 't' | 'U' | 'u' | 'V' | 'v' | 'W' | 'w' | 'X' | 'x' | 'Y'
| 'y' | 'Z' | 'z' | '+' | '%',
) => (),
Some(n @ ('3' | '6' | '9')) => match chars.next() {
Some('f') => (),
Some(c) => return Err(format!("invalid format specifier: %{n}{c}")),
None => return Err("missing format specifier".to_owned()),
},
Some(c) => return Err(format!("invalid format specifier: %{c}")),
None => return Err("missing format specifier".to_owned()),
},
None => break,
_ => continue,
}
}
Ok(formatter.to_owned())
}
struct LabelFilter<Filter: Fn(&'static str) -> bool, const C: usize>([&'static str; C], Filter);
impl<Filter: Fn(&'static str) -> bool, const C: usize> From<LabelFilter<Filter, C>>
for clap::builder::ValueParser
{
fn from(label_filter: LabelFilter<Filter, C>) -> Self {
let filter = label_filter.1;
let values = label_filter.0.into_iter().filter(|x| filter(x));
let inner = clap::builder::PossibleValuesParser::from(values);
Self::from(inner)
}
}