use std::ffi::OsString;
use crate::fs::dir_action::DirAction;
use crate::fs::filter::{FileFilter, VcsIgnore};
use crate::output::{View, Mode, details, grid_details};
use crate::theme::Options as ThemeOptions;
mod dir_action;
mod file_name;
mod filter;
pub(crate) mod flags;
mod theme;
mod view;
mod error;
pub use self::error::{OptionsError, NumberSource};
pub mod parser;
use self::parser::MatchedFlags;
pub mod vars;
pub use self::vars::Vars;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum VcsBackend {
Auto,
Git,
Jj,
None,
}
#[derive(Debug)]
pub struct Options {
pub dir_action: DirAction,
pub filter: FileFilter,
pub view: View,
pub theme: ThemeOptions,
pub vcs_backend: VcsBackend,
pub count: bool,
}
impl Options {
#[allow(unused_results)]
pub fn parse<V>(args: &[OsString], vars: &V) -> OptionsResult
where V: Vars,
{
let mut clap_args = vec![OsString::from("lx")];
clap_args.extend_from_slice(args);
let cmd = parser::build_command();
match cmd.try_get_matches_from(&clap_args) {
Err(e) => {
use clap::error::ErrorKind;
match e.kind() {
ErrorKind::DisplayHelp | ErrorKind::DisplayVersion => {
OptionsResult::HelpOrVersion(e)
}
_ => {
OptionsResult::InvalidOptionsClap(e)
}
}
}
Ok(clap_matches) => {
if let Some(shell) = clap_matches.get_one::<clap_complete::Shell>("completions") {
return OptionsResult::Completions(*shell);
}
if clap_matches.get_flag("show-config") {
return OptionsResult::ShowConfig;
}
if clap_matches.contains_id("dump-class")
&& clap_matches.value_source("dump-class") == Some(clap::parser::ValueSource::CommandLine)
{
let name = clap_matches.get_one::<String>("dump-class")
.cloned()
.unwrap_or_default();
return OptionsResult::DumpClass(name);
}
if clap_matches.contains_id("dump-format")
&& clap_matches.value_source("dump-format") == Some(clap::parser::ValueSource::CommandLine)
{
let name = clap_matches.get_one::<String>("dump-format")
.cloned()
.unwrap_or_default();
return OptionsResult::DumpFormat(name);
}
if clap_matches.contains_id("dump-personality")
&& clap_matches.value_source("dump-personality") == Some(clap::parser::ValueSource::CommandLine)
{
let name = clap_matches.get_one::<String>("dump-personality")
.cloned()
.unwrap_or_default();
return OptionsResult::DumpPersonality(name);
}
if clap_matches.contains_id("dump-theme")
&& clap_matches.value_source("dump-theme") == Some(clap::parser::ValueSource::CommandLine)
{
let name = clap_matches.get_one::<String>("dump-theme")
.cloned()
.unwrap_or_default();
return OptionsResult::DumpTheme(name);
}
if clap_matches.contains_id("dump-style")
&& clap_matches.value_source("dump-style") == Some(clap::parser::ValueSource::CommandLine)
{
let name = clap_matches.get_one::<String>("dump-style")
.cloned()
.unwrap_or_default();
return OptionsResult::DumpStyle(name);
}
if clap_matches.get_flag("init-config") {
return OptionsResult::InitConfig;
}
if clap_matches.get_flag("upgrade-config") {
return OptionsResult::UpgradeConfig;
}
let frees = clap_matches.get_many::<OsString>("FILE")
.map(|vals| vals.cloned().collect())
.unwrap_or_default();
let flags = MatchedFlags::new(clap_matches);
match Self::deduce(&flags, vars) {
Ok(options) => OptionsResult::Ok(options, frees),
Err(oe) => OptionsResult::InvalidOptions(oe),
}
}
}
}
pub fn should_scan_for_vcs(&self) -> bool {
if self.filter.vcs_ignore == VcsIgnore::CheckAndIgnore {
return true;
}
use crate::output::table::Column;
match self.view.mode {
Mode::Details(details::Options { table: Some(ref table), .. }) |
Mode::GridDetails(grid_details::Options { details: details::Options { table: Some(ref table), .. }, .. }) => {
table.columns.contains(&Column::VcsStatus)
}
_ => false,
}
}
fn deduce<V: Vars>(matches: &MatchedFlags, vars: &V) -> Result<Self, OptionsError> {
let wants_git = matches.has(flags::VCS_STATUS) || matches.has(flags::VCS_IGNORE)
|| matches.get(flags::VCS).is_some_and(|v| v != "none");
if cfg!(not(feature = "git")) && wants_git {
return Err(OptionsError::Unsupported(String::from(
"VCS options can't be used because the `git` feature was disabled in this build of lx"
)));
}
if cfg!(not(feature = "jj")) && matches.get(flags::VCS) == Some("jj") {
return Err(OptionsError::Unsupported(String::from(
"--vcs=jj can't be used because the `jj` feature was disabled in this build of lx"
)));
}
let vcs_backend = match matches.get(flags::VCS) {
Some("git") => VcsBackend::Git,
Some("jj") => VcsBackend::Jj,
Some("none") => VcsBackend::None,
_ => VcsBackend::Auto,
};
let view = View::deduce(matches, vars)?;
let dir_action = DirAction::deduce(matches, matches!(view.mode, Mode::Details(_)))?;
let filter = FileFilter::deduce(matches)?;
let theme = ThemeOptions::deduce(matches, vars)?;
let count = matches.has(flags::COUNT) && !matches.has(flags::NO_COUNT);
Ok(Self { dir_action, filter, view, theme, vcs_backend, count })
}
}
#[derive(Debug)]
pub enum OptionsResult {
Ok(Options, Vec<OsString>),
InvalidOptions(OptionsError),
HelpOrVersion(clap::Error),
InvalidOptionsClap(clap::Error),
Completions(clap_complete::Shell),
ShowConfig,
DumpClass(String),
DumpFormat(String),
DumpPersonality(String),
DumpTheme(String),
DumpStyle(String),
InitConfig,
UpgradeConfig,
}
#[cfg(test)]
pub mod test {
use crate::options::parser::MatchedFlags;
use std::ffi::OsString;
pub fn parse_for_test<T, F>(inputs: &[&str], get: F) -> Vec<T>
where F: Fn(&MatchedFlags) -> T
{
let args: Vec<OsString> = std::iter::once(OsString::from("lx"))
.chain(inputs.iter().map(|s| OsString::from(s)))
.collect();
let cmd = crate::options::parser::build_command();
let clap_matches = cmd.try_get_matches_from(&args)
.expect("Clap parse error in test");
let mf = MatchedFlags::new(clap_matches);
vec![get(&mf)]
}
}