Draft: Add FD_CONFIG_PATH environment variable

Summary: Add FD_CONFIG_PATH environment variable which points to a file with config options that are prepended to any `fd` command.
This has the same behavior as `RG_CONFIG_PATH` from ripgrep

Test Plan: `cargo test`

TODO:
- add flag `--no-config` to not respect FD_CONFIG_PATH, `.fdignore`, or global
- add documentation
fd ignore
- make parsing config file blazingly fast
- if FD_CONFIG_PATH not set, or file does not exist, just use `args_os()`
directly
- fix `test_invalid_cwd`
This commit is contained in:
Max 👨🏽‍💻 Coplan 2023-01-13 13:52:34 -08:00
parent 08c0d427bf
commit a2130bb68d
3 changed files with 73 additions and 2 deletions

View File

@ -18,7 +18,7 @@ use std::time;
use anyhow::{anyhow, bail, Context, Result};
use atty::Stream;
use clap::{CommandFactory, Parser};
use clap::{CommandFactory, FromArgMatches};
use globset::GlobBuilder;
use lscolors::LsColors;
use regex::bytes::{Regex, RegexBuilder, RegexSetBuilder};
@ -67,7 +67,7 @@ fn main() {
}
fn run() -> Result<ExitCode> {
let opts = Opts::parse();
let opts = parse_opts();
#[cfg(feature = "completions")]
if let Some(shell) = opts.gen_completions()? {
@ -105,6 +105,40 @@ fn run() -> Result<ExitCode> {
walk::scan(&search_paths, Arc::new(regexps), Arc::new(config))
}
fn parse_opts() -> Opts {
let mut cli_args = std::env::args_os(); // [/usr/bin/fd, --hidden, --etc]
let mut args: Vec<std::ffi::OsString> = args_from_config_file(); // [--no-require-git]
// TODO(vegerot): If `args` is empty we can just skip the next two lines and not pay the cost
// of moving things around
args.insert(0, cli_args.next().unwrap()); // [/usr/bin/fd, --no-require-git]
// this way, CLI arguments override config-file arguments
args.extend(cli_args.skip(1)); // [/usr/bin/fd, --no-require-git, --hidden, --etc]
let mut matches = Opts::command().get_matches_from(args);
let res =
Opts::from_arg_matches_mut(&mut matches).map_err(|err| err.format(&mut Opts::command()));
match res {
Ok(s) => s,
Err(e) => e.exit(),
}
}
fn args_from_config_file() -> Vec<std::ffi::OsString> {
let config_path = match std::env::var_os("FD_CONFIG_PATH") {
Some(path) => path,
None => return vec![],
};
// TODO(vegerot): 🚀
match std::fs::read_to_string(config_path) {
Ok(config) => config.split_whitespace().map(|conf| conf.into()).collect(),
Err(_) => vec![],
}
}
#[cfg(feature = "completions")]
#[cold]
fn print_completions(shell: clap_complete::Shell) -> Result<ExitCode> {

View File

@ -161,6 +161,14 @@ impl TestEnv {
}
}
pub fn set_environment_variable(&self, key: &str, value: &str) {
env::set_var(key, value);
}
pub fn remove_environment_variable(&self, key: &str) {
env::remove_var(key);
}
/// Create a broken symlink at the given path in the temp_dir.
pub fn create_broken_symlink<P: AsRef<Path>>(
&mut self,

View File

@ -2500,3 +2500,32 @@ fn test_invalid_cwd() {
panic!("{:?}", output);
}
}
/// Read config file from FD_CONFIG_PATH environment variable if present
#[test]
#[cfg(unix)]
fn test_fd_config_path() {
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
te.set_environment_variable("FD_CONFIG_PATH", "fd.conf");
fs::File::create(te.test_root().join("fd.conf"))
.unwrap()
// test flags separated by spaces and newlines
.write_all(
b"--hidden --ignore-case
--no-ignore-vcs
--maxdepth=2",
)
.unwrap();
te.assert_output(
&["foo"],
".hidden.foo
a.foo
gitignored.foo
one/b.foo",
);
te.remove_environment_variable("FD_CONFIG_PATH");
}