use clap::{Arg, ArgAction, Command};
use clap::builder::PossibleValue;
use clap::builder::styling;
use super::flags;
const STYLES: styling::Styles = styling::Styles::styled()
.header(styling::AnsiColor::Yellow.on_default().bold())
.usage(styling::AnsiColor::Yellow.on_default().bold())
.literal(styling::AnsiColor::Cyan.on_default().bold())
.placeholder(styling::AnsiColor::Green.on_default())
.error(styling::AnsiColor::Red.on_default().bold());
fn sort_values() -> Vec<PossibleValue> {
vec![
PossibleValue::new("name"),
PossibleValue::new("Name"),
PossibleValue::new("size"),
PossibleValue::new("extension"),
PossibleValue::new("Extension"),
PossibleValue::new("modified"),
PossibleValue::new("changed"),
PossibleValue::new("accessed"),
PossibleValue::new("created"),
PossibleValue::new("type"),
PossibleValue::new("none"),
PossibleValue::new("inode"),
PossibleValue::new("ext").hide(true),
PossibleValue::new("Ext").hide(true),
PossibleValue::new("date").hide(true),
PossibleValue::new("time").hide(true),
PossibleValue::new("mod").hide(true),
PossibleValue::new("newest").hide(true),
PossibleValue::new("new").hide(true),
PossibleValue::new("age").hide(true),
PossibleValue::new("old").hide(true),
PossibleValue::new("oldest").hide(true),
PossibleValue::new(".name").hide(true),
PossibleValue::new(".Name").hide(true),
PossibleValue::new("ch").hide(true),
PossibleValue::new("acc").hide(true),
PossibleValue::new("cr").hide(true),
]
}
pub struct MatchedFlags {
matches: clap::ArgMatches,
}
impl MatchedFlags {
pub fn new(matches: clap::ArgMatches) -> Self {
Self { matches }
}
pub fn has(&self, flag: &str) -> bool {
if self.matches.try_get_one::<bool>(flag).ok().flatten().copied().unwrap_or(false) {
return true;
}
self.matches.try_get_one::<u8>(flag).ok().flatten().copied().unwrap_or(0) > 0
}
pub fn count(&self, flag: &str) -> u8 {
self.matches.get_count(flag)
}
pub fn get(&self, flag: &str) -> Option<&str> {
self.matches.get_one::<String>(flag).map(std::string::String::as_str)
}
pub fn get_usize(&self, flag: &str) -> Option<usize> {
self.matches.get_one::<usize>(flag).copied()
}
pub fn is_present(&self, flag: &str) -> bool {
self.matches.contains_id(flag) && self.matches.value_source(flag) == Some(clap::parser::ValueSource::CommandLine)
}
}
pub fn build_command() -> Command {
Command::new("lx")
.version(env!("CARGO_PKG_VERSION"))
.about(env!("CARGO_PKG_DESCRIPTION"))
.styles(STYLES)
.max_term_width(80)
.disable_help_flag(true)
.disable_version_flag(true)
.args_override_self(true)
.help_template("{name} {version}\n{about-section}\n\
{usage-heading}\n{tab}{usage}\n\n\
{all-args}{after-help}")
.after_help("\
Environment variables:\n \
COLUMNS Override terminal width (characters)\n \
LX_GRID_ROWS Minimum rows before grid-details view activates\n \
LX_ICON_SPACING Spaces between icon and file name\n \
NO_COLOR Disable colours (overridden by --colour)\n \
LS_COLORS File-type colour scheme\n \
LX_COLORS Extended colour scheme (UI elements and metadata)\n \
TIME_STYLE Default timestamp style (overridden by --time-style)")
.arg(Arg::new(flags::ONE_LINE)
.short('1').long("oneline")
.help("Display one entry per line")
.help_heading("Display")
.action(ArgAction::Count)
.overrides_with_all([flags::LONG, flags::GRID]))
.arg(Arg::new(flags::LONG)
.short('l').long("long")
.help("Long view — repeat for more detail: -ll, -lll")
.help_heading("Display")
.action(ArgAction::Count)
.overrides_with(flags::ONE_LINE))
.arg(Arg::new(flags::GRID)
.short('G').long("grid")
.help("Display entries as a grid (default)")
.help_heading("Display")
.action(ArgAction::Count)
.overrides_with(flags::ONE_LINE))
.arg(Arg::new(flags::ACROSS)
.short('x').long("across")
.help("Sort the grid across, rather than downwards")
.help_heading("Display")
.action(ArgAction::Count))
.arg(Arg::new(flags::RECURSE)
.short('R').long("recurse")
.help("Recurse into directories")
.help_heading("Display")
.action(ArgAction::Count))
.arg(Arg::new(flags::TREE)
.short('T').long("tree")
.help("Recurse into directories as a tree")
.help_heading("Display")
.action(ArgAction::Count))
.arg(Arg::new(flags::LEVEL)
.short('L').long("level")
.help("Limit the depth of recursion")
.help_heading("Display")
.action(ArgAction::Set)
.value_name("DEPTH")
.value_parser(clap::value_parser!(usize)))
.arg(Arg::new(flags::CLASSIFY)
.long("classify")
.help("Display file kind indicators [always, auto, never]")
.help_heading("Display")
.action(ArgAction::Set)
.value_name("WHEN")
.hide_possible_values(true)
.value_parser([
PossibleValue::new("always"),
PossibleValue::new("auto"),
PossibleValue::new("never"),
])
.num_args(0..=1)
.require_equals(true)
.default_missing_value("auto"))
.arg(Arg::new(flags::ALL)
.short('a').long("all")
.help("Show hidden and dot files (-aa for . and ..)")
.help_heading("Filtering")
.action(ArgAction::Count))
.arg(Arg::new(flags::LIST_DIRS)
.short('d').long("list-dirs")
.help("List directories as regular files")
.help_heading("Filtering")
.action(ArgAction::Count))
.arg(Arg::new(flags::ONLY_DIRS)
.short('D').long("only-dirs")
.help("List only directories, not files")
.help_heading("Filtering")
.action(ArgAction::Count))
.arg(Arg::new(flags::ONLY_FILES)
.short('f').long("only-files")
.help("List only regular files, not directories")
.help_heading("Filtering")
.action(ArgAction::Count))
.arg(Arg::new(flags::IGNORE_GLOB)
.short('I').long("ignore")
.visible_alias("ignore-glob")
.help("Glob patterns (pipe-separated) of files to hide")
.help_heading("Filtering")
.action(ArgAction::Set)
.value_name("GLOB"))
.arg(Arg::new(flags::SYMLINKS)
.long("symlinks")
.help("How to handle symlinks [show, hide, follow]")
.help_heading("Filtering")
.action(ArgAction::Set)
.value_name("MODE")
.hide_possible_values(true)
.value_parser([
PossibleValue::new("show"),
PossibleValue::new("hide"),
PossibleValue::new("follow"),
]))
.arg(Arg::new(flags::PRUNE)
.short('P').long("prune")
.visible_alias("prune-glob")
.help("Glob patterns of directories to show but not recurse")
.help_heading("Filtering")
.action(ArgAction::Set)
.value_name("GLOB"))
.arg(Arg::new(flags::REVERSE)
.short('r').long("reverse")
.help("Reverse the sort order")
.help_heading("Filtering")
.action(ArgAction::Count))
.arg(Arg::new(flags::SORT)
.short('s').long("sort")
.help("Sort field\n[name, Name, size, extension, Extension,\
modified, changed, accessed, created, type, none, inode]")
.help_heading("Filtering")
.action(ArgAction::Set)
.value_name("FIELD")
.hide_possible_values(true)
.value_parser(sort_values()))
.arg(Arg::new(flags::GROUP_DIRS)
.long("group-dirs")
.help("Group directories before or after other files\n[first, last, none]")
.help_heading("Filtering")
.action(ArgAction::Set)
.value_name("WHEN")
.hide_possible_values(true)
.overrides_with_all([flags::DIRS_FIRST, flags::DIRS_LAST])
.value_parser([
PossibleValue::new("first"),
PossibleValue::new("last"),
PossibleValue::new("none"),
]))
.arg(Arg::new(flags::DIRS_FIRST)
.short('F')
.long("dirs-first")
.alias("group-directories-first")
.help("Directories first [short for --group-dirs=first]")
.help_heading("Filtering")
.action(ArgAction::SetTrue)
.overrides_with_all([flags::GROUP_DIRS, flags::DIRS_LAST]))
.arg(Arg::new(flags::DIRS_LAST)
.short('J')
.long("dirs-last")
.alias("group-directories-last")
.help("Directories last [short for --group-dirs=last]")
.help_heading("Filtering")
.action(ArgAction::SetTrue)
.overrides_with_all([flags::GROUP_DIRS, flags::DIRS_FIRST]))
.arg(Arg::new(flags::BINARY)
.short('b').long("binary")
.help("File sizes with binary prefixes (KiB, MiB)")
.help_heading("Long view")
.action(ArgAction::Count)
.overrides_with(flags::BYTES))
.arg(Arg::new(flags::BYTES)
.short('B').long("bytes")
.help("File sizes in bytes, without prefixes")
.help_heading("Long view")
.action(ArgAction::Count)
.overrides_with(flags::BINARY))
.arg(Arg::new(flags::GROUP)
.short('g').long("group")
.help("Show the group column")
.help_heading("Long view")
.action(ArgAction::Count))
.arg(Arg::new(flags::NUMERIC)
.short('n').long("numeric")
.help("Numeric user and group IDs")
.help_heading("Long view")
.action(ArgAction::Count))
.arg(Arg::new(flags::HEADER)
.short('h').long("header")
.help("Add a header row")
.help_heading("Long view")
.action(ArgAction::Count))
.arg(Arg::new(flags::INODE)
.short('i').long("inode")
.help("Show inode numbers")
.help_heading("Long view")
.action(ArgAction::Count))
.arg(Arg::new(flags::LINKS)
.short('H').long("links")
.help("Show hard link counts")
.help_heading("Long view")
.action(ArgAction::Count))
.arg(Arg::new(flags::BLOCKS)
.short('S').long("blocks")
.help("Show file system block counts")
.help_heading("Long view")
.action(ArgAction::Count))
.arg(Arg::new(flags::OCTAL)
.short('o').long("octal")
.alias("octal-permissions")
.help("Show permissions in octal format\n[aliases: --octal-permissions]")
.help_heading("Long view")
.action(ArgAction::Count))
.arg(Arg::new(flags::TOTAL_SIZE)
.short('Z').long("total-size")
.help("Show directory content sizes (recursive)")
.help_heading("Long view")
.action(ArgAction::Count))
.arg(Arg::new(flags::EXTENDED)
.short('@').long("extended")
.help("Show extended attributes and sizes")
.help_heading("Long view")
.action(ArgAction::Count))
.arg(Arg::new(flags::PERSONALITY)
.short('p').long("personality")
.help("Apply a named personality (columns + flags) 🌟\n[ll, lll, la, tree, ...]")
.help_heading("Personalities & Formats")
.action(ArgAction::Set)
.value_name("NAME"))
.arg(Arg::new(flags::COLUMNS)
.long("columns")
.help("Explicit column list (comma-separated)")
.help_heading("Personalities & Formats")
.action(ArgAction::Set)
.value_name("COLS"))
.arg(Arg::new(flags::FORMAT)
.long("format")
.help("Named column format [long, long2, long3, ...]")
.help_heading("Personalities & Formats")
.action(ArgAction::Set)
.value_name("NAME")
.hide_possible_values(true)
.value_parser({
let names = crate::options::view::format_names();
names.into_iter()
.map(|s| { let leaked: &'static str = Box::leak(s.into_boxed_str()); PossibleValue::new(leaked) })
.collect::<Vec<_>>()
}))
.arg(Arg::new(flags::MODIFIED)
.short('m').long("modified")
.help("Use the modified timestamp")
.help_heading("Timestamps")
.action(ArgAction::Count))
.arg(Arg::new(flags::CHANGED)
.short('c').long("changed")
.help("Use the changed timestamp")
.help_heading("Timestamps")
.action(ArgAction::Count))
.arg(Arg::new(flags::ACCESSED)
.short('u').long("accessed")
.help("Use the accessed timestamp")
.help_heading("Timestamps")
.action(ArgAction::Count))
.arg(Arg::new(flags::CREATED)
.short('U').long("created")
.help("Use the created timestamp")
.help_heading("Timestamps")
.action(ArgAction::Count))
.arg(Arg::new(flags::TIME)
.short('t').long("time")
.help("Which timestamp field to display\n[modified, changed, accessed, created]")
.help_heading("Timestamps")
.action(ArgAction::Set)
.value_name("FIELD")
.hide_possible_values(true)
.value_parser([
PossibleValue::new("modified"),
PossibleValue::new("changed"),
PossibleValue::new("accessed"),
PossibleValue::new("created"),
PossibleValue::new("mod").hide(true),
PossibleValue::new("ch").hide(true),
PossibleValue::new("acc").hide(true),
PossibleValue::new("cr").hide(true),
])
.conflicts_with_all([
flags::MODIFIED,
flags::CHANGED,
flags::ACCESSED,
flags::CREATED,
]))
.arg(Arg::new(flags::TIME_STYLE)
.long("time-style")
.help("How to format timestamps\n[default, iso, long-iso, full-iso,\nrelative, +FORMAT]")
.help_heading("Timestamps")
.action(ArgAction::Set)
.value_name("STYLE"))
.arg(Arg::new(flags::SHOW_PERMISSIONS)
.long("permissions")
.help("Show the permissions field")
.help_heading("Column visibility")
.action(ArgAction::Count)
.overrides_with(flags::NO_PERMISSIONS))
.arg(Arg::new(flags::NO_PERMISSIONS)
.long("no-permissions")
.help("Suppress the permissions field")
.help_heading("Column visibility")
.action(ArgAction::Count)
.overrides_with(flags::SHOW_PERMISSIONS))
.arg(Arg::new(flags::SHOW_FILESIZE)
.long("filesize")
.help("Show the file size field")
.help_heading("Column visibility")
.action(ArgAction::Count)
.overrides_with(flags::NO_FILESIZE))
.arg(Arg::new(flags::NO_FILESIZE)
.long("no-filesize")
.help("Suppress the file size field")
.help_heading("Column visibility")
.action(ArgAction::Count)
.overrides_with(flags::SHOW_FILESIZE))
.arg(Arg::new(flags::SHOW_USER)
.long("user")
.help("Show the user field")
.help_heading("Column visibility")
.action(ArgAction::Count)
.overrides_with(flags::NO_USER))
.arg(Arg::new(flags::NO_USER)
.long("no-user")
.help("Suppress the user field")
.help_heading("Column visibility")
.action(ArgAction::Count)
.overrides_with(flags::SHOW_USER))
.arg(Arg::new(flags::NO_TIME)
.long("no-time")
.help("Suppress the time field")
.help_heading("Column visibility")
.action(ArgAction::Count))
.arg(Arg::new(flags::NO_ICONS)
.long("no-icons")
.help("Suppress icons (alias for --icons=never)")
.help_heading("Column visibility")
.action(ArgAction::Count))
.arg(Arg::new(flags::NO_INODE)
.long("no-inode")
.help("Suppress the inode field")
.help_heading("Column visibility")
.action(ArgAction::Count))
.arg(Arg::new(flags::NO_GROUP)
.long("no-group")
.help("Suppress the group field")
.help_heading("Column visibility")
.action(ArgAction::Count))
.arg(Arg::new(flags::NO_LINKS)
.long("no-links")
.help("Suppress the hard links field")
.help_heading("Column visibility")
.action(ArgAction::Count))
.arg(Arg::new(flags::NO_BLOCKS)
.long("no-blocks")
.help("Suppress the blocks field")
.help_heading("Column visibility")
.action(ArgAction::Count))
.arg(Arg::new(flags::VCS)
.long("vcs")
.help("VCS backend [auto, git, jj, none]")
.help_heading("VCS")
.action(ArgAction::Set)
.value_name("BACKEND")
.hide_possible_values(true)
.value_parser([
PossibleValue::new("auto"),
PossibleValue::new("git"),
PossibleValue::new("jj"),
PossibleValue::new("none"),
]))
.arg(Arg::new(flags::VCS_STATUS)
.long("vcs-status")
.help("Show per-file VCS status column")
.help_heading("VCS")
.action(ArgAction::Count))
.arg(Arg::new(flags::VCS_IGNORE)
.long("vcs-ignore")
.help("Hide VCS-ignored files and metadata directories")
.help_heading("VCS")
.action(ArgAction::Count))
.arg(Arg::new(flags::VCS_REPOS)
.long("vcs-repos")
.help("Show per-directory VCS repo indicator")
.help_heading("VCS")
.action(ArgAction::Count))
.arg(Arg::new(flags::COLOR)
.long("colour").visible_alias("color")
.help("When to use terminal colours\n[always, auto, never]")
.help_heading("Appearance")
.action(ArgAction::Set)
.value_name("WHEN")
.hide_possible_values(true)
.value_parser([
PossibleValue::new("always"),
PossibleValue::new("auto"),
PossibleValue::new("never"),
PossibleValue::new("automatic").hide(true),
]))
.arg(Arg::new(flags::COLOR_SCALE)
.long("colour-scale").visible_alias("color-scale")
.help("Colour file sizes on a scale\n[16, 256, none]")
.help_heading("Appearance")
.action(ArgAction::Set)
.value_name("MODE")
.hide_possible_values(true)
.value_parser([
PossibleValue::new("16"),
PossibleValue::new("256"),
PossibleValue::new("none"),
])
.num_args(0..=1)
.require_equals(true)
.default_missing_value("16"))
.arg(Arg::new(flags::ICONS)
.long("icons")
.help("Display icons next to file names\n[always, auto, never]")
.help_heading("Appearance")
.action(ArgAction::Set)
.value_name("WHEN")
.hide_possible_values(true)
.value_parser([
PossibleValue::new("always"),
PossibleValue::new("auto"),
PossibleValue::new("never"),
])
.num_args(0..=1)
.require_equals(true)
.default_missing_value("auto"))
.arg(Arg::new("theme")
.long("theme")
.help("Use a named colour theme")
.help_heading("Appearance")
.action(ArgAction::Set)
.value_name("NAME"))
.arg(Arg::new("hyperlink")
.long("hyperlink")
.help("File names as clickable hyperlinks\n[always, auto, never]")
.help_heading("Appearance")
.action(ArgAction::Set)
.value_name("WHEN")
.hide_possible_values(true)
.default_missing_value("always")
.require_equals(true)
.num_args(0..=1)
.value_parser(["always", "auto", "never"]))
.arg(Arg::new("quotes")
.long("quotes")
.help("Quote file names containing spaces\n[always, auto, never]")
.help_heading("Appearance")
.action(ArgAction::Set)
.value_name("WHEN")
.hide_possible_values(true)
.default_missing_value("always")
.require_equals(true)
.num_args(0..=1)
.value_parser(["always", "auto", "never"]))
.arg(Arg::new("width")
.long("width")
.short('w')
.help("Override terminal width")
.help_heading("Appearance")
.action(ArgAction::Set)
.value_name("COLS")
.value_parser(clap::value_parser!(usize)))
.arg(Arg::new("absolute")
.short('A')
.long("absolute")
.help("Show absolute file paths")
.help_heading("Appearance")
.action(ArgAction::SetTrue))
.arg(Arg::new("show-config")
.long("show-config")
.help("Show the active configuration and exit")
.help_heading("Configuration")
.action(ArgAction::SetTrue))
.arg(Arg::new("dump-class")
.long("dump-class")
.help("Dump class definitions as TOML")
.help_heading("Configuration")
.value_name("NAME")
.default_missing_value("")
.num_args(0..=1)
.require_equals(true))
.arg(Arg::new("dump-format")
.long("dump-format")
.help("Dump format definitions as TOML")
.help_heading("Configuration")
.value_name("NAME")
.default_missing_value("")
.num_args(0..=1)
.require_equals(true))
.arg(Arg::new("dump-personality")
.long("dump-personality")
.help("Dump personality definitions as TOML")
.help_heading("Configuration")
.value_name("NAME")
.default_missing_value("")
.num_args(0..=1)
.require_equals(true))
.arg(Arg::new("dump-theme")
.long("dump-theme")
.help("Dump theme definitions as TOML")
.help_heading("Configuration")
.value_name("NAME")
.default_missing_value("")
.num_args(0..=1)
.require_equals(true))
.arg(Arg::new("dump-style")
.long("dump-style")
.help("Dump style definitions as TOML")
.help_heading("Configuration")
.value_name("NAME")
.default_missing_value("")
.num_args(0..=1)
.require_equals(true))
.arg(Arg::new("init-config")
.long("init-config")
.help("Generate a default config file")
.help_heading("Configuration")
.action(ArgAction::SetTrue))
.arg(Arg::new("upgrade-config")
.long("upgrade-config")
.help("Upgrade a legacy config file")
.help_heading("Configuration")
.action(ArgAction::SetTrue))
.arg(Arg::new("completions")
.long("completions")
.help("Generate shell completions\n[bash, zsh, fish, elvish, powershell]")
.help_heading("Shell completions")
.action(ArgAction::Set)
.value_name("SHELL")
.hide_possible_values(true)
.value_parser(clap::value_parser!(clap_complete::Shell)))
.arg(Arg::new("help")
.short('?').long("help")
.help("Print help information")
.action(ArgAction::HelpShort))
.arg(Arg::new("version")
.short('v').long("version")
.help("Print version information")
.action(ArgAction::Version))
.arg(Arg::new("FILE")
.action(ArgAction::Append)
.value_parser(clap::value_parser!(std::ffi::OsString)))
}