summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorCanop <cano.petrole@gmail.com>2020-02-04 22:25:19 +0100
committerCanop <cano.petrole@gmail.com>2020-02-04 22:25:19 +0100
commitaff659b73a54532ea3dd195d4edc33fc8056f5dd (patch)
tree8c5049e470f9d9c6adf6d5c66ba70f3c9dfddcdf
parent5e43b91284b6ad1c30ec764de7885c55e6dd6ef5 (diff)
default_args in conf.toml and new launch flags
New flags are mostly here to let you negate on command line the flags you've set in conf.toml. For example `-D` negates `-d`.
-rw-r--r--CHANGELOG.md4
-rw-r--r--Cargo.toml26
-rw-r--r--src/clap.rs69
-rw-r--r--src/cli.rs170
-rw-r--r--src/conf.rs28
-rw-r--r--src/main.rs46
-rw-r--r--src/shell_install/mod.rs3
-rw-r--r--src/tree_options.rs45
-rw-r--r--website/docs/index.md2
9 files changed, 266 insertions, 127 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 32d1af1..307eb77 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,8 +1,10 @@
### master
-New major feature: git related features
+Major change: git related features
- `:show_git_file_info` compute git repo statistics and file statuses. Statistics are computed in background and cached.
- `:git_diff` verb launching `git diff {file}`
- `:git_status` filter files to show only the ones which are relevant for `git status` (warning: slow on big repositories)
+Major change: rewamped launch
+Several new launch flags have been added, mostly doing the opposite of previous ones (eg `-S` negates `-s`)and a new entry in the conf.toml lets you define default flags (which can be overriden by the ones you pass on the command line)
Minor changes:
- on refresh or after command, if the previously selected path can't be selected (missing file, probably) then the previous index will be kept if possible
diff --git a/Cargo.toml b/Cargo.toml
index 43406a2..b3886c8 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -13,25 +13,25 @@ readme = "README.md"
build = "build.rs"
[dependencies]
-chrono = "0.4"
-regex = "1.3"
-lazy_static = "1.4"
-directories = "2.0"
-toml = "0.5"
-custom_error = "1.6"
-log = "0.4"
-simplelog = "0.7"
clap = "2.33"
-glob = "0.3"
+chrono = "0.4"
crossbeam = "0.7"
-open = "1.3.2"
crossterm = "0.15.0"
-umask = "0.1.7"
-minimad = "0.6.3"
-termimad = "0.8.12"
+custom_error = "1.6"
+directories = "2.0"
+glob = "0.3"
id-arena = "2.2.1"
lazy-regex = "0.1"
+lazy_static = "1.4"
+log = "0.4"
+minimad = "0.6.3"
+open = "1.3.2"
pathdiff = "0.1.0"
+regex = "1.3"
+simplelog = "0.7"
+termimad = "0.8.12"
+toml = "0.5"
+umask = "0.1.7"
[dependencies.git2]
version = "0.11"
diff --git a/src/clap.rs b/src/clap.rs
index 19109a8..9b5b271 100644
--- a/src/clap.rs
+++ b/src/clap.rs
@@ -16,91 +16,130 @@ pub fn clap_app() -> clap::App<'static, 'static> {
clap::Arg::with_name("cmd_export_path")
.long("outcmd")
.takes_value(true)
- .help("where to write the produced cmd (if any)"),
+ .help("Where to write the produced cmd (if any)"),
)
.arg(
clap::Arg::with_name("commands")
.short("c")
.long("cmd")
.takes_value(true)
- .help("semicolon separated commands to execute (experimental)"),
+ .help("Semicolon separated commands to execute (experimental)"),
)
.arg(
clap::Arg::with_name("conf")
.long("conf")
.takes_value(true)
- .help("semicolon separated paths to specific config files"),
+ .help("Semicolon separated paths to specific config files"),
)
.arg(
clap::Arg::with_name("dates")
.short("d")
.long("dates")
- .help("show the last modified date of files and directories"),
+ .help("Show the last modified date of files and directories"),
+ )
+ .arg(
+ clap::Arg::with_name("no-dates")
+ .short("D")
+ .long("no-dates")
+ .help("Don't show last modified date"),
)
.arg(
clap::Arg::with_name("file_export_path")
.short("o")
.long("out")
.takes_value(true)
- .help("where to write the produced path (if any)"),
+ .help("Where to write the produced path (if any)"),
)
.arg(
clap::Arg::with_name("show-gitignored")
.short("i")
.long("show-gitignored")
- .help("show files which should be ignored according to git"),
+ .help("Show files which should be ignored according to git"),
+ )
+ .arg(
+ clap::Arg::with_name("no-show-gitignored")
+ .short("I")
+ .long("no-show-gitignored")
+ .help("Don't show gitignored files"),
)
.arg(
clap::Arg::with_name("height")
.long("height")
- .help("height (if you don't want to fill the screen or for file export)")
+ .help("Height (if you don't want to fill the screen or for file export)")
.takes_value(true),
)
.arg(
clap::Arg::with_name("hidden")
.short("h")
.long("hidden")
- .help("show hidden files"),
+ .help("Show hidden files"),
+ )
+ .arg(
+ clap::Arg::with_name("no-hidden")
+ .short("H")
+ .long("no-hidden")
+ .help("Show hidden files"),
)
.arg(
clap::Arg::with_name("install")
.long("install")
- .help("install or reinstall the br shell function"),
+ .help("Install or reinstall the br shell function"),
)
.arg(
clap::Arg::with_name("no-style")
.long("no-style")
- .help("whether to remove all style and colors"),
+ .help("Whether to remove all style and colors"),
)
.arg(
clap::Arg::with_name("only-folders")
.short("f")
.long("only-folders")
- .help("only show folders"),
+ .help("Only show folders"),
+ )
+ .arg(
+ clap::Arg::with_name("no-only-folders")
+ .short("F")
+ .long("no-only-folders")
+ .help("Show folders and files alike"),
)
.arg(
clap::Arg::with_name("permissions")
.short("p")
.long("permissions")
- .help("show permissions, with owner and group"),
+ .help("Show permissions, with owner and group"),
+ )
+ .arg(
+ clap::Arg::with_name("no-permissions")
+ .short("P")
+ .long("no-permissions")
+ .help("Don't show permissions"),
)
.arg(
clap::Arg::with_name("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)"),
+ .help("Set the installation state (for use in install script)"),
)
.arg(
clap::Arg::with_name("print-shell-function")
.long("print-shell-function")
.takes_value(true)
- .help("print to stdout the br function for a given shell"),
+ .value_name("SHELL")
+ .help("Print to stdout the br function for a given shell"),
)
.arg(
clap::Arg::with_name("sizes")
.short("s")
.long("sizes")
- .help("show the size of files and directories"),
+ .help("Show the size of files and directories"),
+ )
+ .arg(
+ clap::Arg::with_name("no-sizes")
+ .short("S")
+ .long("no-sizes")
+ .help("Don't show sizes"),
)
+ .setting(clap::AppSettings::DeriveDisplayOrder)
}
diff --git a/src/cli.rs b/src/cli.rs
index fb021c0..c1e9be0 100644
--- a/src/cli.rs
+++ b/src/cli.rs
@@ -2,10 +2,23 @@
/// the arguments passed on launch of the application.
use {
+ clap::{
+ self,
+ ArgMatches,
+ },
crate::{
+ app::App,
+ app_context::AppContext,
+ conf::Conf,
errors::{ProgramError, TreeBuildError},
- shell_install::ShellInstallState,
+ external::Launchable,
+ shell_install::{
+ ShellInstall,
+ ShellInstallState,
+ },
+ skin,
tree_options::TreeOptions,
+ verb_store::VerbStore,
},
std::{
env,
@@ -14,19 +27,41 @@ use {
},
};
-/// the parsed program launch arguments
+// launch arguments related to installation
+// (not used by the application after the first step)
+struct InstallLaunchArgs {
+ install: bool, // installation is required
+ set_install_state: Option<ShellInstallState>, // the state to set
+ print_shell_function: Option<String>, // shell function to print on stdout
+}
+impl InstallLaunchArgs {
+ fn from(cli_args: &ArgMatches<'_>) -> Result<Self, ProgramError> {
+ let install = cli_args.is_present("install");
+ 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()?;
+ Ok(Self {
+ install,
+ set_install_state,
+ print_shell_function,
+ })
+ }
+}
+
+/// 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)
pub cmd_export_path: Option<String>, // where to write the produced command (if required with --outcmd)
- pub print_shell_function: Option<String>, // shell function to print on stdout
- pub set_install_state: Option<ShellInstallState>, // the state to set
pub tree_options: TreeOptions, // initial tree options
pub commands: Option<String>, // commands passed as cli argument, still unparsed
- pub install: bool, // installation is required
pub height: Option<u16>, // an optional height to replace the screen's one
pub no_style: bool, // whether to remove all styles (including colors)
- pub specific_conf: Option<Vec<PathBuf>>,
}
#[cfg(not(windows))]
@@ -43,9 +78,7 @@ fn canonicalize_root(root: &Path) -> io::Result<PathBuf> {
})
}
-/// return the parsed launch arguments
-pub fn read_launch_args() -> Result<AppLaunchArgs, ProgramError> {
- let cli_args = crate::clap::clap_app().get_matches();
+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);
@@ -66,49 +99,104 @@ pub fn read_launch_args() -> Result<AppLaunchArgs, ProgramError> {
})?;
}
}
+ Ok(canonicalize_root(&root)?)
+}
- let root = 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 = crate::clap::clap_app();
- let mut tree_options = TreeOptions::default();
- tree_options.show_sizes = cli_args.is_present("sizes");
- tree_options.only_folders = cli_args.is_present("only-folders");
- tree_options.show_hidden = cli_args.is_present("hidden");
- tree_options.show_dates = cli_args.is_present("dates");
- tree_options.show_permissions = cli_args.is_present("permissions");
- tree_options.respect_git_ignore = !cli_args.is_present("show_git_ignored");
- let install = cli_args.is_present("install");
- let file_export_path = cli_args.value_of("file_export_path").map(str::to_string);
- let cmd_export_path = cli_args.value_of("cmd_export_path").map(str::to_string);
- let commands = cli_args.value_of("commands").map(str::to_string);
- let no_style = cli_args.is_present("no-style");
- let height = cli_args.value_of("height").and_then(|s| s.parse().ok());
- 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()?;
- if tree_options.show_sizes {
- // by default, if we're asked to show the size, we show all files
- tree_options.show_hidden = true;
- tree_options.respect_git_ignore = false;
+ // parse the launch arguments we got from cli
+ let cli_matches = clap_app.get_matches();
+
+ // read the install related arguments
+ let install_args = InstallLaunchArgs::from(&cli_matches)?;
+
+ // execute installation things required by launch args
+ let mut must_quit = false;
+ if let Some(state) = install_args.set_install_state {
+ state.write_file()?;
+ must_quit = true;
+ }
+ if let Some(shell) = &install_args.print_shell_function {
+ ShellInstall::print(shell)?;
+ must_quit = true;
+ }
+ if must_quit {
+ return Ok(None);
}
- let specific_conf = cli_args.value_of("conf")
+
+ // read the list of specific config files
+ let specific_conf: Option<Vec<PathBuf>> = cli_matches.value_of("conf")
.map(|s| s.split(';').map(PathBuf::from).collect());
- Ok(AppLaunchArgs {
+
+ // if we don't run on a specific config file, we check the
+ // configuration
+ if specific_conf.is_none() {
+ let mut shell_install = ShellInstall::new(install_args.install);
+ shell_install.check()?;
+ if shell_install.should_quit {
+ return Ok(None);
+ }
+ }
+
+ // read the configuration file(s): either the standard one
+ // or the ones required by the launch args
+ let config = match &specific_conf {
+ Some(conf_paths) => {
+ let mut conf = Conf::default();
+ for path in conf_paths {
+ conf.read_file(path)?;
+ }
+ conf
+ }
+ _ => {
+ Conf::from_default_location()?
+ }
+ };
+
+ // 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();
+ if !config.default_flags.is_empty() {
+ debug!("Applying default flags {:?} from conf", &config.default_flags);
+ let clap_app = crate::clap::clap_app()
+ .setting(clap::AppSettings::NoBinaryName);
+ let flags_args = format!("-{}", &config.default_flags);
+ let conf_matches = clap_app.get_matches_from(vec![&flags_args]);
+ tree_options.apply(&conf_matches);
+ debug!("modified tree options: {:?}", &tree_options);
+ }
+ tree_options.apply(&cli_matches);
+
+ // verb store is completed from the config file(s)
+ let mut verb_store = VerbStore::new();
+ verb_store.init(&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 no_style = cli_matches.is_present("no-style");
+ let height = cli_matches.value_of("height").and_then(|s| s.parse().ok());
+
+ let root = get_root_path(&cli_matches)?;
+
+ let launch_args = AppLaunchArgs {
root,
file_export_path,
cmd_export_path,
- print_shell_function,
- set_install_state,
tree_options,
commands,
- install,
height,
no_style,
- specific_conf,
- })
+ };
+
+ let context = AppContext::from(launch_args, verb_store);
+ let skin = skin::Skin::create(config.skin);
+ App::new().run(crate::io::writer(), &context, skin)
}
/// wait for user input, return `true` if she
diff --git a/src/conf.rs b/src/conf.rs
index 77f0590..276304f 100644
--- a/src/conf.rs
+++ b/src/conf.rs
@@ -22,6 +22,7 @@ use {
#[derive(Default)]
pub struct Conf {
+ pub default_flags: String, // the flags to apply before cli ones
pub verbs: Vec<VerbConf>,
pub skin: HashMap<String, CompoundStyle>,
}
@@ -100,6 +101,10 @@ impl Conf {
pub fn read_file(&mut self, filepath: &Path) -> Result<(), ConfError> {
let data = fs::read_to_string(filepath)?;
let root: Value = data.parse::<Value>()?;
+ // reading default flags
+ if let Some(s) = string_field(&root, "default_flags") {
+ self.default_flags.push_str(&s);
+ }
// reading verbs
if let Some(Value::Array(verbs_value)) = &root.get("verbs") {
for verb_value in verbs_value.iter() {
@@ -165,15 +170,22 @@ impl Conf {
}
const DEFAULT_CONF_FILE: &str = r#"
-# This configuration file lets you define new commands
-# or change the shortcut or triggering keys of built-in verbs.
-# You can change the colors of broot too.
-#
-# Configuration documentation is available at https://dystroy.org/broot
+###############################################################
+# This configuration file lets you
+# - define new commands
+# - change the shortcut or triggering keys of built-in verbs
+# - change the colors
+# - set default values for flags
#
+# Configuration documentation is available at
+# https://dystroy.org/broot
+###############################################################
+
+###############################################################
+# default flags
-#####################
-# user defined verbs:
+###############################################################
+# verbs and shortcuts
# If $EDITOR isn't set on your computer, you should either set it using
# something similar to
@@ -211,7 +223,7 @@ name = "view"
invocation = "view"
execution = "$PAGER {file}"
-#####################
+###############################################################
# Skin
# If you want to change the colors of broot,
diff --git a/src/main.rs b/src/main.rs
index 7e93e8f..c51db7b 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -3,8 +3,7 @@ extern crate log;
use {
broot::{
- app::App, app_context::AppContext, cli, conf::Conf, errors::ProgramError,
- external::Launchable, io, shell_install::ShellInstall, skin, verb_store::VerbStore,
+ cli,
},
log::LevelFilter,
simplelog,
@@ -38,49 +37,10 @@ fn configure_log() {
}
}
-/// run the application, and maybe return a launchable
-/// which must be run after broot
-fn run() -> Result<Option<Launchable>, ProgramError> {
- configure_log();
- let launch_args = cli::read_launch_args()?;
- let mut must_quit = false;
- if let Some(state) = launch_args.set_install_state {
- state.write_file()?;
- must_quit = true;
- }
- if let Some(shell) = &launch_args.print_shell_function {
- ShellInstall::print(shell)?;
- must_quit = true;
- }
- if must_quit {
- return Ok(None);
- }
- let mut verb_store = VerbStore::new();
- let config = match &launch_args.specific_conf {
- Some(conf_paths) => {
- let mut conf = Conf::default();
- for path in conf_paths {
- conf.read_file(path)?;
- }
- conf
- }
- _ => {
- let mut shell_install = ShellInstall::new(&launch_args);
- shell_install.check()?;
- if shell_install.should_quit {
- return Ok(None);
- }
- Conf::from_default_location()?
- }
- };
- verb_store.init(&config);
- let context = AppContext::from(launch_args, verb_store);
- let skin = skin::Skin::create(config.skin);
- App::new().run(io::writer(), &context, skin)
-}
fn main() {
- match run() {
+ configure_log();
+ match cli::run() {
Ok(Some(launchable)) => {
if let Err(e) = launchable.execute() {
warn!("Failed to launch {:?}", &launchable);
diff --git a/src/shell_install/mod.rs b/src/shell_install/mod.rs
index 0613c94..2eda9d0 100644
--- a/src/shell_install/mod.rs
+++ b/src/shell_install/mod.rs
@@ -110,8 +110,7 @@ fn get_installed_path() -> PathBuf {
impl ShellInstall {
- pub fn new(launch_args: &cli::AppLaunchArgs) -> Self {
- let force_install = launch_args.install;
+ pub fn new(force_install: bool) -> Self {
Self {
force_install,
skin: mad_skin::make_cli_mad_skin(),
diff --git a/src/tree_options.rs b/src/tree_options.rs
index 9b9d65c..4459dcf 100644
--- a/src/tree_options.rs
+++ b/src/tree_options.rs
@@ -1,4 +1,7 @@
use {
+ clap::{
+ ArgMatches,
+ },
crate::{
patterns::Pattern,
},
@@ -19,20 +22,56 @@ pub struct TreeOptions {
}
impl TreeOptions {
- pub fn without_pattern(&self) -> TreeOptions {
+ pub fn without_pattern(&self) -> Self {
TreeOptions {
show_hidden: self.show_hidden,
only_folders: self.only_folders,
show_sizes: self.show_sizes,
show_dates: self.show_dates,
- show_git_file_info: self.show_git_file_info,
- trim_root: self.trim_root,
show_permissions: self.show_permissions,
respect_git_ignore: self.respect_git_ignore,
filter_by_git_status: self.filter_by_git_status,
+ show_git_file_info: self.show_git_file_info,
+ trim_root: self.trim_root,
pattern: Pattern::None,
}
}
+ pub fn apply(&mut self, cli_args: &ArgMatches<'_>) {
+ if cli_args.is_present("sizes") {
+ self.show_sizes = true;
+ // by default, if we're asked to show the size, we show
+ // all files. This may be overriden by other settings
+ self.show_hidden = true;
+ self.respect_git_ignore = false;
+ } else if cli_args.is_present("no-sizes") {
+ self.show_sizes = false;
+ }
+ if cli_args.is_present("only-folders") {
+ self.only_folders = true;
+ } else if cli_args.is_present("no-only-folders") {
+ self.only_folders = false;
+ }
+ if cli_args.is_present("hidden") {
+ self.show_hidden = true;
+ } else if cli_args.is_present("no-hidden") {
+ self.show_hidden = false;
+ }
+ if cli_args.is_present("dates") {
+ self.show_dates = true;
+ } else if cli_args.is_present("no-dates") {
+ self.show_dates = false;
+ }
+ if cli_args.is_present("permissions") {
+ self.show_permissions = true;
+ } else if cli_args.is_present("no-permissions") {
+ self.show_permissions = false;
+ }
+ if cli_args.is_present("show_git_ignored") {
+ self.respect_git_ignore = false;
+ } else if cli_args.is_present("no-show_git_ignored") {
+ self.respect_git_ignore = true;
+ }
+ }
}
impl Default for TreeOptions {
diff --git a/website/docs/index.md b/website/docs/index.md
index aa5a142..16c7dea 100644
--- a/website/docs/index.md
+++ b/website/docs/index.md
@@ -84,7 +84,7 @@ And you keep all broot tools, like filtering or the ability to delete or open fi
Sizes are computed in the background, you don't have to wait for them when you navigate.
-### check git statuses:
+# check git statuses:
![size](img/20200203-git.png)