From 282fbc584554bc7db4d06e603cd05961ee2f060f Mon Sep 17 00:00:00 2001 From: Canop Date: Wed, 15 Jun 2022 07:47:48 +0200 Subject: use clap derive for arguments --- CHANGELOG.md | 6 +- Cargo.lock | 55 ++++++++-- Cargo.toml | 6 +- build.rs | 7 +- resources/default-conf.hjson | 15 +++ src/app/app.rs | 9 +- src/app/app_context.rs | 80 ++++++++++++-- src/cli/app_launch_args.rs | 22 ---- src/cli/args.rs | 200 +++++++++++++++++++++++++++++++++++ src/cli/clap_args.rs | 235 ----------------------------------------- src/cli/install_launch_args.rs | 19 ++-- src/cli/mod.rs | 110 ++++--------------- src/print.rs | 83 ++++----------- src/shell_install/mod.rs | 82 ++++++-------- src/tree/tree_options.rs | 55 +++++----- src/verb/external_execution.rs | 11 -- 16 files changed, 455 insertions(+), 540 deletions(-) delete mode 100644 src/cli/app_launch_args.rs create mode 100644 src/cli/args.rs delete mode 100644 src/cli/clap_args.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a5517f..70ba8a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,9 @@ -### next +### v1.13.2 - ??? + - advice to hit alt-i and|or alt-h when no file is visible - Fix #556 -- update many dependencies - examples on search modes in help screen - Fix #559 +- list of syntactic themes in default conf +- the --file-export-path launch argument which was deprecated since broot 1.6 has been removed (redirect the output of broot instead) ### v1.13.1 - 2022-05-30 diff --git a/Cargo.lock b/Cargo.lock index c19b8c0..290c657 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -255,14 +255,16 @@ dependencies = [ [[package]] name = "clap" -version = "3.1.18" +version = "3.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2dbdf4bdacb33466e854ce889eee8dfd5729abf7ccd7664d0a2d60cd384440b" +checksum = "a836566fa5f52f7ddf909a8a2f9029b9f78ca584cd95cf7e87f8073110f4c5c9" dependencies = [ "atty", "bitflags", + "clap_derive", "clap_lex", "indexmap", + "lazy_static", "strsim", "termcolor", "textwrap", @@ -270,18 +272,31 @@ dependencies = [ [[package]] name = "clap_complete" -version = "3.1.4" +version = "3.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da92e6facd8d73c22745a5d3cbb59bdf8e46e3235c923e516527d8e81eec14a4" +checksum = "0f6ebaab5f25e4f0312dfa07cb30a755204b96e6531457c2cfdecfdf5f2adf40" dependencies = [ "clap", ] +[[package]] +name = "clap_derive" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "986fd75d1dfd2c34eb8c9275ae38ad87ea9478c9b79e87f1801f7d866dfb1e37" +dependencies = [ + "heck 0.4.0", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "clap_lex" -version = "0.2.0" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a37c35f1112dad5e6e0b1adaff798507497a18fceeb30cceb3bae7d1427b9213" +checksum = "5538cd660450ebeb4234cfecf8f2284b844ffc4c50531e66d584ad5b91293613" dependencies = [ "os_str_bytes", ] @@ -645,9 +660,9 @@ dependencies = [ [[package]] name = "flume" -version = "0.10.12" +version = "0.10.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843c03199d0c0ca54bc1ea90ac0d507274c28abcc4f691ae8b4eaa375087c76a" +checksum = "1ceeb589a3157cac0ab8cc585feb749bd2cea5cb55a6ee802ad72d9fd38303da" dependencies = [ "futures-core", "futures-sink", @@ -1370,6 +1385,30 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + [[package]] name = "proc-macro-hack" version = "0.5.19" diff --git a/Cargo.toml b/Cargo.toml index 281408a..8609489 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,7 +26,7 @@ base64 = "0.13" bet = "1.0" char_reader = "0.1" chrono = "0.4" -clap = "3.1" +clap = { version = "3.2.1", features = ["derive"] } cli-log = "2.0" crokey = "0.4.1" crossbeam = "0.8" @@ -74,8 +74,8 @@ users = "0.11" is_executable = "1.0.1" [build-dependencies] -clap = "3.1" -clap_complete = "3.1" +clap = { version = "3.2.1", features = ["derive"] } +clap_complete = "3.2.1" [profile.dev] debug = false diff --git a/build.rs b/build.rs index 173302e..5ea3386 100644 --- a/build.rs +++ b/build.rs @@ -2,6 +2,7 @@ // It builds shell completion scripts. use { + clap::CommandFactory, clap_complete::{Generator, Shell}, std::{ env, @@ -9,14 +10,14 @@ use { }, }; -include!("src/cli/clap_args.rs"); +include!("src/cli/args.rs"); fn write_completions_file>(generator: G, out_dir: P) { - let mut app = clap_app(); + let mut args = Args::command(); for name in &["broot", "br"] { clap_complete::generate_to( generator, - &mut app, + &mut args, name.to_string(), &out_dir, ).expect("clap complete generation failed"); diff --git a/resources/default-conf.hjson b/resources/default-conf.hjson index dc5bb6f..33fe8ed 100644 --- a/resources/default-conf.hjson +++ b/resources/default-conf.hjson @@ -356,4 +356,19 @@ # Change this if you sometimes want to have more than 2 panels # open # max_panels_count: 2 + + + ############################################################### + # Syntax Theme + # + # If you want to choose the them used for preview, uncomment + # one of the following lines: + # + # syntax_theme: base16-ocean.light + # syntax_theme: base16-ocean.dark + # syntax_theme: base16-mocha.dark + # syntax_theme: base16-mocha.light + # syntax_theme: InspiredGitHub + # syntax_theme: "Solarized (dark)" + # syntax_theme: "Solarized (light)" } diff --git a/src/app/app.rs b/src/app/app.rs index 73bbc6d..7f75c11 100644 --- a/src/app/app.rs +++ b/src/app/app.rs @@ -2,6 +2,7 @@ use { super::*, crate::{ browser::BrowserState, + cli::TriBool, command::{Command, Sequence}, conf::Conf, display::{Areas, Screen, W}, @@ -80,8 +81,8 @@ impl App { PanelId::from(0), Box::new( BrowserState::new( - con.launch_args.root.clone(), - con.launch_args.tree_options.clone(), + con.initial_root.clone(), + con.initial_tree_options.clone(), screen, con, &Dam::unlimited(), @@ -663,10 +664,10 @@ impl App { let event_source = EventSource::new()?; let rx_events = event_source.receiver(); let mut dam = Dam::from(rx_events); - let skin = AppSkin::new(conf, con.launch_args.color == Some(false)); + let skin = AppSkin::new(conf, con.launch_args.color == TriBool::No); let mut app_state = AppState { stage: Stage::default(), - root: con.launch_args.root.clone(), + root: con.initial_root.clone(), other_panel_path: None, }; diff --git a/src/app/app_context.rs b/src/app/app_context.rs index 518bafa..d3424e3 100644 --- a/src/app/app_context.rs +++ b/src/app/app_context.rs @@ -1,19 +1,21 @@ use { super::*, crate::{ - cli::AppLaunchArgs, + cli::{Args, TriBool}, conf::Conf, - errors::ConfError, + errors::*, file_sum, icon::*, pattern::SearchModeMap, path::SpecialPath, skin::ExtColorMap, + tree::TreeOptions, verb::VerbStore, }, std::{ convert::{TryFrom, TryInto}, - path::PathBuf, + io, + path::{Path, PathBuf}, }, }; @@ -22,12 +24,18 @@ use { /// life of the App pub struct AppContext { + /// The initial tree root + pub initial_root: PathBuf, + + /// Initial tree options + pub initial_tree_options: TreeOptions, + /// where's the config file we're using /// This vec can't be empty pub config_paths: Vec, /// all the arguments specified at launch - pub launch_args: AppLaunchArgs, + pub launch_args: Args, /// the verbs in use (builtins and configured ones) pub verb_store: VerbStore, @@ -82,10 +90,10 @@ pub struct AppContext { impl AppContext { pub fn from( - launch_args: AppLaunchArgs, + launch_args: Args, verb_store: VerbStore, config: &Conf, - ) -> Result { + ) -> Result { let config_paths = config.files.clone(); let standard_status = StandardStatus::new(&verb_store); let true_colors = if let Some(value) = config.true_colors { @@ -105,11 +113,12 @@ impl AppContext { .map(|map| map.try_into()) .transpose()? .unwrap_or_default(); - let ext_colors = ExtColorMap::try_from(&config.ext_colors)?; + let ext_colors = ExtColorMap::try_from(&config.ext_colors) + .map_err(ConfError::from)?; let file_sum_threads_count = config.file_sum_threads_count .unwrap_or(file_sum::DEFAULT_THREAD_COUNT); if file_sum_threads_count < 1 || file_sum_threads_count > 50 { - return Err(ConfError::InvalidThreadsCount{ count: file_sum_threads_count }); + return Err(ConfError::InvalidThreadsCount{ count: file_sum_threads_count }.into()); } let max_panels_count = config.max_panels_count .unwrap_or(2) @@ -122,7 +131,21 @@ impl AppContext { let max_staged_count = config.max_staged_count .unwrap_or(10_000) .clamp(10, 100_000); + let initial_root = get_root_path(&launch_args)?; + + // tree options are built from the default_flags + // found in the config file(s) (if any) then overriden + // by the cli args + let mut initial_tree_options = TreeOptions::default(); + initial_tree_options.apply_config(config)?; + initial_tree_options.apply_launch_args(&launch_args); + if launch_args.color == TriBool::No { + initial_tree_options.show_selection_mark = true; + } + Ok(Self { + initial_root, + initial_tree_options, config_paths, launch_args, verb_store, @@ -165,3 +188,44 @@ fn are_true_colors_available() -> bool { true } } + +fn get_root_path(cli_args: &Args) -> Result { + let mut root = cli_args + .root + .as_ref() + .map_or(std::env::current_dir()?, PathBuf::from); + if !root.exists() { + return Err(TreeBuildError::FileNotFound { + path: format!("{:?}", &root), + }.into()); + } + if !root.is_dir() { + // we try to open the parent directory if the passed file isn't one + if let Some(parent) = root.parent() { + info!("Passed path isn't a directory => opening parent instead"); + root = parent.to_path_buf(); + } else { + // let's give up + return Err(TreeBuildError::NotADirectory { + path: format!("{:?}", &root), + }.into()); + } + } + Ok(canonicalize_root(&root)?) +} + +#[cfg(not(windows))] +fn canonicalize_root(root: &Path) -> io::Result { + root.canonicalize() +} + +#[cfg(windows)] +fn canonicalize_root(root: &Path) -> io::Result { + Ok(if root.is_relative() { + env::current_dir()?.join(root) + } else { + root.to_path_buf() + }) +} + + diff --git a/src/cli/app_launch_args.rs b/src/cli/app_launch_args.rs deleted file mode 100644 index 03b25ae..0000000 --- a/src/cli/app_launch_args.rs +++ /dev/null @@ -1,22 +0,0 @@ -use { - crate::{ - tree::TreeOptions, - }, - std::{ - path::PathBuf, - }, -}; - - -/// the parsed program launch arguments which are kept for the -/// life of the program -pub struct AppLaunchArgs { - pub root: PathBuf, // what should be the initial root - pub file_export_path: Option, // where to write the produced path (if required with --out) - deprecated - pub cmd_export_path: Option, // where to write the produced command (if required with --outcmd) - pub tree_options: TreeOptions, // initial tree options - pub commands: Option, // commands passed as cli argument, still unparsed - pub height: Option, // an optional height to replace the screen's one - pub color: Option, // whether to display colors and styles - pub listen: Option, // if some, broot will start in serve mode on this socket -} diff --git a/src/cli/args.rs b/src/cli/args.rs new file mode 100644 index 0000000..9ad810e --- /dev/null +++ b/src/cli/args.rs @@ -0,0 +1,200 @@ +use { + std::{ + path::PathBuf, + str::FromStr, + }, +}; + +#[derive(Debug, clap::Parser)] +/// A tree explorer and a customizable launcher +/// +/// Complete documentation lives at https://dystroy.org/broot" +#[clap(author, version, about)] +pub struct Args { + + /// Show the last modified date of files and directories" + #[clap(short, long, action)] + pub dates: bool, + + /// Don't show the last modified date" + #[clap(short='D', long, action)] + pub no_dates: bool, + + #[clap(short='f', long, action)] + /// Only show folders + pub only_folders: bool, + + /// Show folders and files alike + #[clap(short='F', long, action)] + pub no_only_folders: bool, + + /// Show filesystem info on top + #[clap(long, action)] + pub show_root_fs: bool, + + /// Show git statuses on files and stats on repo + #[clap(short='g', long, action)] + pub show_git_info: bool, + + /// Don't show git statuses on files and stats on repo + #[clap(short='G', long, action)] + pub no_show_git_info: bool, + + #[clap(long, action)] + /// Only show files having an interesting git status, including hidden ones + pub git_status: bool, + + #[clap(short='h', long, action)] + /// Show hidden files + pub hidden: bool, + + #[clap(short='H', long, action)] + /// Don't show hidden files + pub no_hidden: bool, + + #[clap(short='i', long, action)] + /// Show git ignored files + pub git_ignored: bool, + + #[clap(short='I', long, action)] + /// Don't show git ignored files + pub no_git_ignored: bool, + + #[clap(short='p', long, action)] + /// Show permissions + pub permissions: bool, + + #[clap(short='P', long, action)] + /// Don't show permissions + pub no_permissions: bool, + + #[clap(short='s', long, action)] + /// Show the size of files and directories + pub sizes: bool, + + #[clap(short='S', long, action)] + /// Don't show sizes + pub no_sizes: bool, + + #[clap(long, action)] + /// Sort by count (only show one level of the tree) + pub sort_by_count: bool, + + #[clap(long, action)] + /// Sort by date (only show one level of the tree) + pub sort_by_date: bool, + + #[clap(long, action)] + /// Sort by size (only show one level of the tree) + pub sort_by_size: bool, + + /// Sort by size, show ignored and hidden files + #[clap(short, long, action)] + pub whale_spotting: bool, + + /// Don't sort + #[clap(long, action)] + pub no_sort: bool, + + /// Trim the root too and don't show a scrollbar + #[clap(short='t', long, action)] + pub trim_root: bool, + + /// Don't trim the root level, show a scrollbar + #[clap(short='T', long, action)] + pub no_trim_root: bool, + + /// Where to write the produced cmd (if any) + #[clap(long, value_parser)] + pub cmd_export_path: Option, + + /// Semicolon separated commands to execute + #[clap(short, long, value_parser)] + pub commands: Option, + + /// Whether to have styles and colors (auto is default and usually OK) + #[clap(long, arg_enum, value_parser, default_value="auto")] + pub color: TriBool, + + /// Semicolon separated paths to specific config files"), + #[clap(long, value_parser)] + pub conf: Option, + + /// Height (if you don't want to fill the screen or for file export) + #[clap(long, value_parser)] + pub height: Option, + + /// Install or reinstall the br shell function + #[clap(long, action)] + pub install: bool, + + /// Where to write the produced cmd (if any) + #[clap(long, value_parser)] + pub set_install_state: Option, + + /// Print to stdout the br function for a given shell + #[clap(long, value_parser)] + pub print_shell_function: Option, + + /// A socket to listen to for commands + #[cfg(unix)] + #[clap(long, value_parser)] + pub listen: Option, + + /// Ask for the current root of the remote broot + #[cfg(unix)] + #[clap(long, action)] + pub get_root: bool, + + /// A socket that broot sends commands to before quitting + #[cfg(unix)] + #[clap(long, value_parser)] + pub send: Option, + + /// Root Directory + #[clap(value_parser, value_name="FILE")] + pub root: Option, +} + +/// This is an Option but I didn't find any way to configure +/// clap to parse an Option as I want +#[derive(Debug, Clone, Copy, PartialEq, Eq, clap::ArgEnum)] +pub enum TriBool { + Auto, + Yes, + No, +} +impl TriBool { + pub fn unwrap_or_else(self, f: F) -> bool + where + F: FnOnce() -> bool + { + match self { + Self::Auto => f(), + Self::Yes => true, + Self::No => false, + } + } +} + +#[derive(Debug, Clone, Copy, clap::ValueEnum)] +pub enum ShellInstallState { + Undefined, // before any install, this is the initial state + Refused, + Installed, +} +impl FromStr for ShellInstallState { + type Err = String; + fn from_str(state: &str) -> Result { + match state { + "undefined" => Ok(Self::Undefined), + "refused" => Ok(Self::Refused), + "installed" => Ok(Self::Installed), + _ => Err( + // not supposed to happen because claps check the values + format!("unexpected install state: {:?}", state) + ), + } + } +} + diff --git a/src/cli/clap_args.rs b/src/cli/clap_args.rs deleted file mode 100644 index 0ac9524..0000000 --- a/src/cli/clap_args.rs +++ /dev/null @@ -1,235 +0,0 @@ -/// this module generate the clap App, which defines -/// launch arguments - -/// Declare the possible CLI arguments -pub fn clap_app() -> clap::Command<'static> { - let app = clap::Command::new("broot") - .version(env!("CARGO_PKG_VERSION")) - .author("dystroy ") - .about( - "A tree explorer and a customizable launcher\n\ - Complete documentation lives at https://dystroy.org/broot" - ) - .arg(clap::Arg::new("ROOT").help("sets the root directory")) - // tree flags - .arg( - clap::Arg::new("dates") - .short('d') - .long("dates") - .help("Show the last modified date of files and directories"), - ) - .arg( - clap::Arg::new("no-dates") - .short('D') - .long("no-dates") - .help("Don't show last modified date"), - ) - .arg( - clap::Arg::new("only-folders") - .short('f') - .long("only-folders") - .help("Only show folders"), - ) - .arg( - clap::Arg::new("no-only-folders") - .short('F') - .long("no-only-folders") - .help("Show folders and files alike"), - ) - .arg( - clap::Arg::new("show-root-fs") - .long("show-root-fs") - .help("Show filesystem info on top"), - ) - .arg( - clap::Arg::new("show-git-info") - .short('g') - .long("show-git-info") - .help("Show git statuses on files and stats on repo"), - ) - .arg( - clap::Arg::new("no-show-git-info") - .short('G') - .long("no-show-git-info") - .help("Don't show git statuses on files"), - ) - .arg( - clap::Arg::new("git-status") - .long("git-status") - .help("Only show files having an interesting git status, including hidden ones"), - ) - .arg( - clap::Arg::new("hidden") - .short('h') - .long("hidden") - .help("Show hidden files"), - ) - .arg( - clap::Arg::new("no-hidden") - .short('H') - .long("no-hidden") - .help("Don't show hidden files"), - ) - .arg( - clap::Arg::new("show-gitignored") - .short('i') - .long("show-gitignored") - .help("Show files which should be ignored according to git"), - ) - .arg( - clap::Arg::new("no-show-gitignored") - .short('I') - .long("no-show-gitignored") - .help("Don't show gitignored files"), - ) - .arg( - clap::Arg::new("permissions") - .short('p') - .long("permissions") - .help("Show permissions, with owner and group"), - ) - .arg( - clap::Arg::new("no-permissions") - .short('P') - .long("no-permissions") - .help("Don't show permissions"), - ) - .arg( - clap::Arg::new("sizes") - .short('s') - .long("sizes") - .help("Show the size of files and directories"), - ) - .arg( - clap::Arg::new("no-sizes") - .short('S') - .long("no-sizes") - .help("Don't show sizes"), - ) - .arg( - clap::Arg::new("sort-by-count") - .long("sort-by-count") - .help("Sort by count (only show one level of the tree)"), - ) - .arg( - clap::Arg::new("sort-by-date") - .long("sort-by-date") - .help("Sort by date (only show one level of the tree)"), - ) - .arg( - clap::Arg::new("sort-by-size") - .long("sort-by-size") - .help("Sort by size (only show one level of the tree)"), - ) - .arg( - clap::Arg::new("whale-spotting") - .short('w') - .long("whale-spotting") - .help("Sort by size, show ignored and hidden files"), - ) - .arg( - clap::Arg::new("no-sort") - .long("no-sort") - .help("Don't sort"), - ) - .arg( - clap::Arg::new("trim-root") - .short('t') - .long("trim-root") - .help("Trim the root too and don't show a scrollbar"), - ) - .arg( - clap::Arg::new("no-trim-root") - .short('T') - .long("no-trim-root") - .help("Don't trim the root level, show a scrollbar"), - ) - // other options - .arg( - clap::Arg::new("cmd-export-path") - .long("outcmd") - .takes_value(true) - .help("Where to write the produced cmd (if any)"), - ) - .arg( - clap::Arg::new("commands") - .short('c') - .long("cmd") - .takes_value(true) - .help("Semicolon separated commands to execute"), - ) - .arg( - clap::Arg::new("color") - .long("color") - .takes_value(true) - .possible_values(&["yes", "no", "auto"]) - .default_value("auto") - .help("Whether to have styles and colors (auto is default and usually OK)"), - ) - .arg( - clap::Arg::new("conf") - .long("conf") - .takes_value(true) - .help("Semicolon separated paths to specific config files"), - ) - .arg( - clap::Arg::new("height") - .long("height") - .help("Height (if you don't want to fill the screen or for file export)") - .takes_value(true), - ) - .arg( - clap::Arg::new("file-export-path") // deprecated since broot 1.6 - .short('o') - .long("out") - .takes_value(true) - .hide(true) - .help("Where to write the produced path (if any)"), - ) - .arg( - clap::Arg::new("install") - .long("install") - .help("Install or reinstall the br shell function"), - ) - .arg( - clap::Arg::new("set-install-state") - .long("set-install-state") - .takes_value(true) - .value_name("state") - .possible_values(&["undefined", "refused", "installed"]) - .help("Set the installation state (for use in install script)"), - ) - .arg( - clap::Arg::new("print-shell-function") - .long("print-shell-function") - .takes_value(true) - .value_name("shell") - .help("Print to stdout the br function for a given shell"), - ) - .setting(clap::AppSettings::DeriveDisplayOrder); - #[cfg(unix)] - let app = app - .arg( - clap::Arg::new("listen") - .long("listen") - .takes_value(true) - .help("Listen for commands") - ) - .arg( - clap::Arg::new("get-root") - .long("get-root") - .help("Ask for the current root of the remote broot") - ) - .arg( - clap::Arg::new("send") - .long("send") - .takes_value(true) - .help("send commands to a remote broot then quits") - ); - app -} - -#[test] -fn verify_app() { - clap_app().debug_assert(); -} diff --git a/src/cli/install_launch_args.rs b/src/cli/install_launch_args.rs index c688e12..e5b1d01 100644 --- a/src/cli/install_launch_args.rs +++ b/src/cli/install_launch_args.rs @@ -3,12 +3,10 @@ use { crate::{ errors::ProgramError, - shell_install::ShellInstallState, + cli::{Args, ShellInstallState}, }, - clap::{self, ArgMatches}, std::{ env, - str::FromStr, }, }; @@ -21,7 +19,7 @@ pub struct InstallLaunchArgs { pub print_shell_function: Option, // shell function to print on stdout } impl InstallLaunchArgs { - pub fn from(cli_args: &ArgMatches) -> Result { + pub fn from(args: &Args) -> Result { let mut install = None; if let Ok(s) = env::var("BR_INSTALL") { if s == "yes" { @@ -33,18 +31,13 @@ impl InstallLaunchArgs { } } // the cli arguments may override the env var value - if cli_args.is_present("install") { + if args.install { install = Some(true); - } else if cli_args.value_of("cmd-export-path").is_some() { + } else if args.cmd_export_path.is_some() { install = Some(false); } - let print_shell_function = cli_args - .value_of("print-shell-function") - .map(str::to_string); - let set_install_state = cli_args - .value_of("set-install-state") - .map(ShellInstallState::from_str) - .transpose()?; + let print_shell_function = args.print_shell_function.clone(); + let set_install_state = args.set_install_state; Ok(Self { install, set_install_state, diff --git a/src/cli/mod.rs b/src/cli/mod.rs index 29c5152..310a7ed 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -1,12 +1,13 @@ //! this module manages reading and translating //! the arguments passed on launch of the application. -pub mod clap_args; -mod app_launch_args; +//mod app_launch_args; +mod args; mod install_launch_args; pub use { - app_launch_args::*, + //app_launch_args::*, + args::*, install_launch_args::*, }; @@ -15,13 +16,12 @@ use { app::{App, AppContext}, conf::Conf, display, - errors::{ProgramError, TreeBuildError}, + errors::ProgramError, launchable::Launchable, - shell_install::ShellInstall, - tree::TreeOptions, + shell_install::{ShellInstall, write_state}, verb::VerbStore, }, - clap::{self, ArgMatches}, + clap::Parser, crossterm::{ self, cursor, @@ -30,65 +30,25 @@ use { QueueableCommand, }, std::{ - env, io::{self, Write}, - path::{Path, PathBuf}, + path::PathBuf, }, }; -#[cfg(not(windows))] -fn canonicalize_root(root: &Path) -> io::Result { - root.canonicalize() -} - -#[cfg(windows)] -fn canonicalize_root(root: &Path) -> io::Result { - Ok(if root.is_relative() { - env::current_dir()?.join(root) - } else { - root.to_path_buf() - }) -} - -fn get_root_path(cli_args: &ArgMatches) -> Result { - let mut root = cli_args - .value_of("ROOT") - .map_or(env::current_dir()?, PathBuf::from); - if !root.exists() { - return Err(TreeBuildError::FileNotFound { - path: format!("{:?}", &root), - }.into()); - } - if !root.is_dir() { - // we try to open the parent directory if the passed file isn't one - if let Some(parent) = root.parent() { - info!("Passed path isn't a directory => opening parent instead"); - root = parent.to_path_buf(); - } else { - // let's give up - return Err(TreeBuildError::NotADirectory { - path: format!("{:?}", &root), - }.into()); - } - } - Ok(canonicalize_root(&root)?) -} - /// run the application, and maybe return a launchable /// which must be run after broot pub fn run() -> Result, ProgramError> { - let clap_app = clap_args::clap_app(); // parse the launch arguments we got from cli - let cli_matches = clap_app.get_matches(); + let args = Args::parse(); // read the install related arguments - let install_args = InstallLaunchArgs::from(&cli_matches)?; + let install_args = InstallLaunchArgs::from(&args)?; // execute installation things required by launch args let mut must_quit = false; if let Some(state) = install_args.set_install_state { - state.write_file()?; + write_state(state)?; must_quit = true; } if let Some(shell) = &install_args.print_shell_function { @@ -100,8 +60,8 @@ pub fn run() -> Result, ProgramError> { } // read the list of specific config files - let specific_conf: Option> = cli_matches - .value_of("conf") + let specific_conf: Option> = args.conf + .as_ref() .map(|s| s.split(';').map(PathBuf::from).collect()); // if we don't run on a specific config file, we check the @@ -128,64 +88,32 @@ pub fn run() -> Result, ProgramError> { }; debug!("config: {:#?}", &config); - // tree options are built from the default_flags - // found in the config file(s) (if any) then overriden - // by the cli args - let mut tree_options = TreeOptions::default(); - tree_options.apply_config(&config)?; - tree_options.apply_launch_args(&cli_matches); // verb store is completed from the config file(s) let verb_store = VerbStore::new(&mut config)?; - // reading the other arguments - let file_export_path = cli_matches.value_of("file-export-path").map(str::to_string); - let cmd_export_path = cli_matches.value_of("cmd-export-path").map(str::to_string); - let commands = cli_matches.value_of("commands").map(str::to_string); - let height = cli_matches.value_of("height").and_then(|s| s.parse().ok()); - let color = match cli_matches.value_of("color") { - Some("yes") => Some(true), - Some("no") => Some(false), - _ => None, - }; - - let root = get_root_path(&cli_matches)?; + let context = AppContext::from(args, verb_store, &config)?; #[cfg(unix)] - if let Some(server_name) = cli_matches.value_of("send") { + if let Some(server_name) = &context.launch_args.send { use crate::{ command::Sequence, net::{Client, Message}, }; let client = Client::new(server_name); - if let Some(seq) = &commands { + if let Some(seq) = &context.launch_args.commands { let message = Message::Sequence(Sequence::new_local(seq.to_string())); client.send(&message)?; - } else if !cli_matches.is_present("get-root") { - let message = Message::Command(format!(":focus {}", root.to_string_lossy())); + } else if !context.launch_args.get_root { + let message = Message::Command(format!(":focus {}", context.initial_root.to_string_lossy())); client.send(&message)?; }; - if cli_matches.is_present("get-root") { + if context.launch_args.get_root { client.send(&Message::GetRoot)?; } return Ok(None); } - let mut launch_args = AppLaunchArgs { - root, - file_export_path, - cmd_export_path, - tree_options, - commands, - height, - color, - listen: cli_matches.value_of("listen").map(str::to_string), - }; - if color == Some(false) { - launch_args.tree_options.show_selection_mark = true; - } - - let context = AppContext::from(launch_args, verb_store, &config)?; let mut w = display::writer(); let app = App::new(&context)?; w.queue(EnterAlternateScreen)?; diff --git a/src/print.rs b/src/print.rs index 90d38ef..d7c4af7 100644 --- a/src/print.rs +++ b/src/print.rs @@ -3,37 +3,26 @@ use { crate::{ app::*, - display::{DisplayableTree, Screen}, + display::Screen, errors::ProgramError, launchable::Launchable, - skin::{ExtColorMap, PanelSkin, StyleMap}, + skin::{PanelSkin, StyleMap}, tree::Tree, }, crossterm::tty::IsTty, pathdiff, std::{ - fs::OpenOptions, - io::{self, Write, stdout}, + io::{self, stdout}, path::Path, }, }; -fn print_string(string: String, con: &AppContext) -> io::Result { +fn print_string(string: String, _con: &AppContext) -> io::Result { Ok( - if let Some(ref output_path) = con.launch_args.file_export_path { - // an output path was provided, we write to it - let f = OpenOptions::new() - .create(true) - .append(true) - .open(output_path)?; - writeln!(&f, "{}", string)?; - CmdResult::Quit - } else { - // no output path provided. We write on stdout, but we must - // do it after app closing to have the desired stdout (it may - // be the normal terminal or a file, or other output) - CmdResult::from(Launchable::printer(string)) - } + // We write on stdout, but we must do it after app closing + // to have the desired stdout (it may be the normal terminal + // or a file, or other output) + CmdResult::from(Launchable::printer(string)) ) } @@ -54,7 +43,7 @@ pub fn print_paths(sel_info: &SelInfo, con: &AppContext) -> io::Result io::Result { - let relative_path = match pathdiff::diff_paths(path, &con.launch_args.root) { + let relative_path = match pathdiff::diff_paths(path, &con.initial_root) { None => { return Err(io::Error::new( io::ErrorKind::Other, @@ -88,53 +77,23 @@ pub fn print_relative_paths(sel_info: &SelInfo, con: &AppContext) -> io::Result< print_string(string, con) } -fn print_tree_to_file( - tree: &Tree, - screen: Screen, - file_path: &str, - ext_colors: &ExtColorMap, -) -> Result { - let no_style_skin = StyleMap::no_term(); - let dp = DisplayableTree::out_of_app( - tree, - &no_style_skin, - ext_colors, - screen.width, - (tree.lines.len() as u16).min(screen.height), - ); - let mut f = OpenOptions::new() - .create(true) - .append(true) - .open(file_path)?; - dp.write_on(&mut f)?; - Ok(CmdResult::Quit) -} - pub fn print_tree( tree: &Tree, screen: Screen, panel_skin: &PanelSkin, con: &AppContext, ) -> Result { - if let Some(ref output_path) = con.launch_args.file_export_path { - // an output path was provided, we write to it - print_tree_to_file(tree, screen, output_path, &con.ext_colors) + // We write on stdout, but we must do it after app closing to have the normal terminal + let show_color = con.launch_args.color.unwrap_or_else(|| stdout().is_tty()); + let styles = if show_color { + panel_skin.styles.clone() } else { - // no output path provided. We write on stdout, but we must - // do it after app closing to have the normal terminal - let show_color = con.launch_args - .color - .unwrap_or_else(|| stdout().is_tty()); - let styles = if show_color { - panel_skin.styles.clone() - } else { - StyleMap::no_term() - }; - Ok(CmdResult::from(Launchable::tree_printer( - tree, - screen, - styles, - con.ext_colors.clone(), - ))) - } + StyleMap::no_term() + }; + Ok(CmdResult::from(Launchable::tree_printer( + tree, + screen, + styles, + con.ext_colors.clone(), + ))) } diff --git a/src/shell_install/mod.rs b/src/shell_install/mod.rs index e6a54b8..183a316 100644 --- a/src/shell_install/mod.rs +++ b/src/shell_install/mod.rs @@ -1,9 +1,13 @@ use { - crate::{cli, conf, errors::ProgramError, skin}, + crate::{ + cli::{self, ShellInstallState}, + conf, + errors::ProgramError, + skin, + }, std::{ fs, io, os, path::{Path, PathBuf}, - str::FromStr, }, termimad::{mad_print_inline, MadSkin}, }; @@ -57,56 +61,34 @@ pub struct ShellInstall { done: bool, // true if the installation was just made } -#[derive(Debug, Clone, Copy)] -pub enum ShellInstallState { - Undefined, // before any install, this is the initial state - Refused, - Installed, -} -impl FromStr for ShellInstallState { - type Err = ProgramError; - fn from_str(state: &str) -> Result { - match state { - "undefined" => Ok(Self::Undefined), - "refused" => Ok(Self::Refused), - "installed" => Ok(Self::Installed), - _ => Err(ProgramError::InternalError { - // not supposed to happen because claps check the values - details: format!("unexpected install state: {:?}", state), - }), - } +/// write either the "installed" or the "refused" file, or remove +/// those files. +/// +/// This is useful in installation +/// or test scripts when we don't want the user to be prompted +/// to install the function, or in case something doesn't properly +/// work in shell detections +pub fn write_state(state: ShellInstallState) -> Result<(), ProgramError> { + let refused_path = get_refused_path(); + let installed_path = get_installed_path(); + if installed_path.exists() { + fs::remove_file(&installed_path)?; } -} -impl ShellInstallState { - /// write either the "installed" or the "refused" file, or remove - /// those files. - /// - /// This is useful in installation - /// or test scripts when we don't want the user to be prompted - /// to install the function, or in case something doesn't properly - /// work in shell detections - pub fn write_file(self) -> Result<(), ProgramError> { - let refused_path = get_refused_path(); - let installed_path = get_installed_path(); - if installed_path.exists() { - fs::remove_file(&installed_path)?; - } - if refused_path.exists() { - fs::remove_file(&refused_path)?; + if refused_path.exists() { + fs::remove_file(&refused_path)?; + } + match state { + ShellInstallState::Refused => { + fs::create_dir_all(refused_path.parent().unwrap())?; + fs::write(&refused_path, REFUSED_FILE_CONTENT)?; } - match self { - Self::Refused => { - fs::create_dir_all(refused_path.parent().unwrap())?; - fs::write(&refused_path, REFUSED_FILE_CONTENT)?; - } - Self::Installed => { - fs::create_dir_all(installed_path.parent().unwrap())?; - fs::write(&installed_path, INSTALLED_FILE_CONTENT)?; - } - _ => {} + ShellInstallState::Installed => { + fs::create_dir_all(installed_path.parent().unwrap())?; + fs::write(&installed_path, INSTALLED_FILE_CONTENT)?; } - Ok(()) + _ => {} } + Ok(()) } fn get_refused_path() -> PathBuf { @@ -165,7 +147,7 @@ impl ShellInstall { // even if the installation isn't really complete (for example // when no bash file was found), we don't want to ask the user // again, we'll assume it's done - ShellInstallState::Installed.write_file()?; + write_state(ShellInstallState::Installed)?; } debug!("Starting install"); bash::install(self)?; @@ -203,7 +185,7 @@ impl ShellInstall { debug!("proceed: {:?}", proceed); self.authorization = Some(proceed); if !proceed { - ShellInstallState::Refused.write_file()?; + write_state(ShellInstallState::Refused)?; self.skin.print_text(MD_INSTALL_CANCELLED); } Ok(proceed) diff --git a/src/tree/tree_options.rs b/src/tree/tree_options.rs index f1f5353..52f9b6c 100644 --- a/src/tree/tree_options.rs +++ b/src/tree/tree_options.rs @@ -1,13 +1,13 @@ use { super::Sort, crate::{ - cli::clap_args, + cli::Args, conf::Conf, display::{Cols, DEFAULT_COLS}, errors::ConfError, pattern::*, }, - clap::ArgMatches, + clap::Parser, std::convert::TryFrom, }; @@ -79,9 +79,8 @@ impl TreeOptions { /// change tree options according to configuration pub fn apply_config(&mut self, config: &Conf) -> Result<(), ConfError> { if let Some(default_flags) = &config.default_flags { - let clap_app = clap_args::clap_app().no_binary_name(true); let flags_args = format!("-{}", default_flags); - let conf_matches = clap_app.get_matches_from(vec![&flags_args]); + let conf_matches = Args::parse_from(vec![&flags_args]); self.apply_launch_args(&conf_matches); } if let Some(b) = &config.show_selection_mark { @@ -99,75 +98,75 @@ impl TreeOptions { Ok(()) } /// change tree options according to broot launch arguments - pub fn apply_launch_args(&mut self, cli_args: &ArgMatches) { - if cli_args.is_present("sizes") { + pub fn apply_launch_args(&mut self, cli_args: &Args) { + if cli_args.sizes { self.show_sizes = true; self.show_root_fs = true; - } else if cli_args.is_present("no-sizes") { + } else if cli_args.no_sizes { self.show_sizes = false; } - if cli_args.is_present("whale-spotting") { + if cli_args.whale_spotting { self.show_hidden = true; self.respect_git_ignore = false; self.sort = Sort::Size; self.show_sizes = true; self.show_root_fs = true; } - if cli_args.is_present("only-folders") { + if cli_args.only_folders { self.only_folders = true; - } else if cli_args.is_present("no-only-folders") { + } else if cli_args.no_only_folders { self.only_folders = false; } - if cli_args.is_present("git-status") { + if cli_args.git_status { self.filter_by_git_status = true; self.show_hidden = true; } - if cli_args.is_present("hidden") { + if cli_args.hidden { self.show_hidden = true; - } else if cli_args.is_present("no-hidden") { + } else if cli_args.no_hidden { self.show_hidden = false; } - if cli_args.is_present("dates") { + if cli_args.dates { self.show_dates = true; - } else if cli_args.is_present("no-dates") { + } else if cli_args.no_dates { self.show_dates = false; } - if cli_args.is_present("permissions") { + if cli_args.permissions { self.show_permissions = true; - } else if cli_args.is_present("no-permissions") { + } else if cli_args.no_permissions { self.show_permissions = false; } - if cli_args.is_present("show-root-fs") { + if cli_args.show_root_fs { self.show_root_fs = true; } - if cli_args.is_present("show-gitignored") { + if cli_args.git_ignored { self.respect_git_ignore = false; - } else if cli_args.is_present("no-show-gitignored") { + } else if cli_args.no_git_ignored { self.respect_git_ignore = true; } - if cli_args.is_present("show-git-info") { + if cli_args.show_git_info { self.show_git_file_info = true; - } else if cli_args.is_present("no-show-git-info") { + } else if cli_args.no_show_git_info { self.show_git_file_info = false; } - if cli_args.is_present("sort-by-count") { + if cli_args.sort_by_count { self.sort = Sort::Count; self.show_counts = true; } - if cli_args.is_present("sort-by-date") { + if cli_args.sort_by_date { self.sort = Sort::Date; self.show_dates = true; } - if cli_args.is_present("sort-by-size") { + if cli_args.sort_by_size { self.sort = Sort::Size; self.show_sizes = true; } - if cli_args.is_present("no-sort") { + if cli_args.no_sort { self.sort = Sort::None; } - if cli_args.is_present("trim-root") { + if cli_args.trim_root { self.trim_root = true; - } else if cli_args.is_present("no-trim-root") { + } else if cli_args.no_trim_root { self.trim_root = false; } } diff --git a/src/verb/external_execution.rs b/src/verb/external_execution.rs index af4a245..cd2ab5c 100644 --- a/src/verb/external_execution.rs +++ b/src/verb/external_execution.rs @@ -116,17 +116,6 @@ impl ExternalExecution { let f = OpenOptions::new().append(true).open(export_path)?; writeln!(&f, "{}", builder.shell_exec_string(&self.exec_pattern))?; Ok(CmdResult::Quit) - } else if let Some(ref export_path) = con.launch_args.file_export_path { - if let Some(sel) = builder.sel_info.one_sel() { - // old version of the br function: only the file is exported - // in the passed file - let f = OpenOptions::new().append(true).open(export_path)?; - writeln!(&f, "{}", sel.path.to_string_lossy())?; - Ok(CmdResult::Quit) - } else { - // should not happen - Ok(CmdResult::error("no selection")) - } } else { Ok(CmdResult::error( "this verb needs broot to be launched as `br`. Try `broot --install` if necessary." -- cgit v1.2.3