diff options
Diffstat (limited to 'src/cli')
-rw-r--r-- | src/cli/app_launch_args.rs | 22 | ||||
-rw-r--r-- | src/cli/args.rs | 200 | ||||
-rw-r--r-- | src/cli/clap_args.rs | 235 | ||||
-rw-r--r-- | src/cli/install_launch_args.rs | 19 | ||||
-rw-r--r-- | src/cli/mod.rs | 110 |
5 files changed, 225 insertions, 361 deletions
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<String>, // where to write the produced path (if required with --out) - deprecated - pub cmd_export_path: Option<String>, // where to write the produced command (if required with --outcmd) - pub tree_options: TreeOptions, // initial tree options - pub commands: Option<String>, // commands passed as cli argument, still unparsed - pub height: Option<u16>, // an optional height to replace the screen's one - pub color: Option<bool>, // whether to display colors and styles - pub listen: Option<String>, // 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<PathBuf>, + + /// Semicolon separated commands to execute + #[clap(short, long, value_parser)] + pub commands: Option<String>, + + /// 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<String>, + + /// Height (if you don't want to fill the screen or for file export) + #[clap(long, value_parser)] + pub height: Option<u16>, + + /// 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<ShellInstallState>, + + /// Print to stdout the br function for a given shell + #[clap(long, value_parser)] + pub print_shell_function: Option<String>, + + /// A socket to listen to for commands + #[cfg(unix)] + #[clap(long, value_parser)] + pub listen: Option<String>, + + /// 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<String>, + + /// Root Directory + #[clap(value_parser, value_name="FILE")] + pub root: Option<PathBuf>, +} + +/// This is an Option<bool> but I didn't find any way to configure +/// clap to parse an Option<T> as I want +#[derive(Debug, Clone, Copy, PartialEq, Eq, clap::ArgEnum)] +pub enum TriBool { + Auto, + Yes, + No, +} +impl TriBool { + pub fn unwrap_or_else<F>(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<Self, Self::Err> { + 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 <denys.seguret@gmail.com>") - .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<String>, // shell function to print on stdout } impl InstallLaunchArgs { - pub fn from(cli_args: &ArgMatches) -> Result<Self, ProgramError> { + pub fn from(args: &Args) -> Result<Self, ProgramError> { 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<PathBuf> { - root.canonicalize() -} - -#[cfg(windows)] -fn canonicalize_root(root: &Path) -> io::Result<PathBuf> { - Ok(if root.is_relative() { - env::current_dir()?.join(root) - } else { - root.to_path_buf() - }) -} - -fn get_root_path(cli_args: &ArgMatches) -> Result<PathBuf, ProgramError> { - 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<Option<Launchable>, 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<Option<Launchable>, ProgramError> { } // read the list of specific config files - let specific_conf: Option<Vec<PathBuf>> = cli_matches - .value_of("conf") + let specific_conf: Option<Vec<PathBuf>> = 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<Option<Launchable>, 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)?; |