feat: add hidden flag to traverse hidden files (#250)

Add the `hidden` and `no-hidden` flag that allows
configuring the traversal of hidden files.

Examples:

```
treefmt --hidden
```
will traverse hidden files.

```
treefmt --hidden --no-hidden
```
will not traverse hidden files, same as the default (`treefmt`).

```
treefmt --hidden --no-hidden --hidden
```
will traverse hidden files.
This commit is contained in:
a-kenji 2023-10-15 08:06:41 +00:00 committed by GitHub
parent 5943babf13
commit 67d3c553be
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 159 additions and 14 deletions

View File

@ -1 +1 @@
too-many-arguments-threshold = 9
too-many-arguments-threshold = 10

View File

@ -64,6 +64,14 @@ treefmt [FLAGS] [OPTIONS] [--] [paths]...
> Only apply selected formatters. Defaults to all formatters.
`-H, --hidden`
> Also traverse hidden files (files that start with a .). This behaviour can be overridden with the `--no-hidden` flag.
`--no-hidden`
> Override the `--hidden` flag. Don't traverse hidden files.
`--tree-root <tree-root>`
> Set the path to the tree root directory where treefmt will look for the files to format. Defaults to the folder holding the `treefmt.toml` file. Its mostly useful in combination with `--config-file` to specify the project root which wont coincide with the directory holding `treefmt.toml`.

View File

@ -9,6 +9,7 @@ pub fn format_cmd(
work_dir: &Path,
config_file: &Path,
paths: &[PathBuf],
hidden: bool,
no_cache: bool,
clear_cache: bool,
fail_on_change: bool,
@ -55,6 +56,7 @@ pub fn format_cmd(
&cache_dir,
config_file,
&paths,
hidden,
no_cache,
clear_cache,
fail_on_change,

View File

@ -23,7 +23,7 @@ use std::{
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
pub struct Cli {
/// Create a new treefmt.toml
/// Create a new treefmt.toml.
#[arg(short, long, default_value_t = false)]
pub init: bool,
@ -39,6 +39,15 @@ pub struct Cli {
#[arg(short, long, default_value_t = false)]
pub clear_cache: bool,
#[arg(long = "hidden", short = 'H')]
/// Include hidden files while traversing the tree.
/// Override with the --no-hidden flag.
pub hidden: bool,
/// Overrides the --hidden flag.
/// Don't include hidden files while traversing the tree.
#[arg(long, overrides_with = "hidden", hide = true)]
no_hidden: bool,
/// Exit with error if any changes were made. Useful for CI.
#[arg(
long,
@ -52,7 +61,7 @@ pub struct Cli {
#[arg(long, default_value_t = false)]
pub allow_missing_formatter: bool,
/// Log verbosity is based off the number of v used
/// Log verbosity is based off the number of v used.
#[clap(flatten)]
pub verbose: Verbosity<InfoLevel>,
@ -158,6 +167,7 @@ pub fn run_cli(cli: &Cli) -> anyhow::Result<()> {
&cli.work_dir,
config_file,
&cli.paths,
cli.hidden,
cli.no_cache,
cli.clear_cache,
cli.fail_on_change,

View File

@ -29,6 +29,7 @@ pub fn run_treefmt(
cache_dir: &Path,
treefmt_toml: &Path,
paths: &[PathBuf],
hidden: bool,
no_cache: bool,
clear_cache: bool,
fail_on_change: bool,
@ -89,7 +90,7 @@ pub fn run_treefmt(
cache.update_formatters(formatters.clone());
}
let walker = build_walker(paths);
let walker = build_walker(paths, hidden);
let matches = collect_matches_from_walker(walker, &formatters, &mut stats);
stats.timed_debug("tree walk");
@ -322,7 +323,7 @@ fn collect_matches_from_walker(
}
/// Configure and build the tree walker
fn build_walker(paths: Vec<PathBuf>) -> Walk {
fn build_walker(paths: Vec<PathBuf>, hidden: bool) -> Walk {
// For some reason the WalkBuilder must start with one path, but can add more paths later.
// unwrap: we checked before that there is at least one path in the vector
let mut builder = WalkBuilder::new(paths.first().unwrap());
@ -330,6 +331,7 @@ fn build_walker(paths: Vec<PathBuf>) -> Walk {
for path in paths[1..].iter() {
builder.add(path);
}
builder.hidden(!hidden);
// TODO: builder has a lot of interesting options.
// TODO: use build_parallel with a Visitor.
// See https://docs.rs/ignore/0.4.17/ignore/struct.WalkParallel.html#method.visit
@ -981,7 +983,7 @@ mod tests {
let formatters = load_formatters(root, tree_root, false, &None, &mut stats).unwrap();
let walker = build_walker(vec![tree_root.to_path_buf()]);
let walker = build_walker(vec![tree_root.to_path_buf()], false);
let _matches = collect_matches_from_walker(walker, &formatters, &mut stats);
assert_eq!(stats.traversed_files, 3);
@ -1030,13 +1032,62 @@ mod tests {
let formatters = load_formatters(root, tree_root, false, &None, &mut stats).unwrap();
let walker = build_walker(vec![tree_root.to_path_buf()]);
let walker = build_walker(vec![tree_root.to_path_buf()], false);
let _matches = collect_matches_from_walker(walker, &formatters, &mut stats);
assert_eq!(stats.traversed_files, 15);
assert_eq!(stats.matched_files, 9);
}
#[test]
fn test_walker_some_matches_walk_hidden() {
let tmpdir = utils::tmp_mkdir();
let black = tmpdir.path().join("black");
let nixpkgs_fmt = tmpdir.path().join("nixpkgs-fmt");
let elm_fmt = tmpdir.path().join("elm-fmt");
utils::write_binary_file(&black, " ");
utils::write_binary_file(&nixpkgs_fmt, " ");
utils::write_binary_file(&elm_fmt, " ");
let tree_root = tmpdir.path();
let files = vec!["test", "test1", "test3", ".test4"];
for file in files {
utils::write_file(tree_root.join(format!("{file}.py")), " ");
utils::write_file(tree_root.join(format!("{file}.nix")), " ");
utils::write_file(tree_root.join(format!("{file}.elm")), " ");
utils::write_file(tree_root.join(file), " ");
}
let config = format!(
"
[formatter.python]
command = {black:?}
includes = [\"*.py\"]
[formatter.nix]
command = {nixpkgs_fmt:?}
includes = [\"*.nix\"]
[formatter.elm]
command = {elm_fmt:?}
options = [\"--yes\"]
includes = [\"*.elm\"]
"
);
let root = from_string(&config).unwrap();
let mut stats = Statistics::init();
let formatters = load_formatters(root, tree_root, false, &None, &mut stats).unwrap();
let walker = build_walker(vec![tree_root.to_path_buf()], true);
let _matches = collect_matches_from_walker(walker, &formatters, &mut stats);
assert_eq!(stats.traversed_files, 19);
assert_eq!(stats.matched_files, 12);
}
#[test]
fn test_walker_some_matches_specific_include() {
let tmpdir = utils::tmp_mkdir();
@ -1080,7 +1131,7 @@ mod tests {
let formatters = load_formatters(root, tree_root, false, &None, &mut stats).unwrap();
let walker = build_walker(vec![tree_root.to_path_buf()]);
let walker = build_walker(vec![tree_root.to_path_buf()], false);
let _matches = collect_matches_from_walker(walker, &formatters, &mut stats);
assert_eq!(stats.traversed_files, 15);
@ -1130,7 +1181,7 @@ mod tests {
let formatters = load_formatters(root, tree_root, false, &None, &mut stats).unwrap();
let walker = build_walker(vec![tree_root.to_path_buf()]);
let walker = build_walker(vec![tree_root.to_path_buf()], false);
let matches = collect_matches_from_walker(walker, &formatters, &mut stats);
assert_eq!(stats.traversed_files, 12);
@ -1213,7 +1264,7 @@ mod tests {
let formatters = load_formatters(root, tree_root, false, &None, &mut stats).unwrap();
let walker = build_walker(vec![tree_root.to_path_buf()]);
let walker = build_walker(vec![tree_root.to_path_buf()], false);
let matches = collect_matches_from_walker(walker, &formatters, &mut stats);
assert_eq!(stats.traversed_files, 18);
@ -1285,7 +1336,7 @@ mod tests {
let formatters = load_formatters(root, tree_root, false, &None, &mut stats).unwrap();
let walker = build_walker(vec![tree_root.to_path_buf()]);
let walker = build_walker(vec![tree_root.to_path_buf()], false);
let matches = collect_matches_from_walker(walker, &formatters, &mut stats);
assert_eq!(stats.traversed_files, 7);
@ -1347,7 +1398,7 @@ mod tests {
let formatters = load_formatters(root, tree_root, false, &None, &mut stats).unwrap();
let walker = build_walker(vec![tree_root.to_path_buf()]);
let walker = build_walker(vec![tree_root.to_path_buf()], false);
let matches = collect_matches_from_walker(walker, &formatters, &mut stats);
assert_eq!(stats.traversed_files, 7);
@ -1417,7 +1468,7 @@ mod tests {
let formatters = load_formatters(root, tree_root, false, &None, &mut stats).unwrap();
let walker = build_walker(vec![tree_root.to_path_buf()]);
let walker = build_walker(vec![tree_root.to_path_buf()], false);
let matches = collect_matches_from_walker(walker, &formatters, &mut stats);
assert_eq!(stats.traversed_files, 8);
@ -1486,7 +1537,7 @@ mod tests {
let formatters = load_formatters(root, tree_root, false, &None, &mut stats).unwrap();
let walker = build_walker(vec![tree_root.to_path_buf()]);
let walker = build_walker(vec![tree_root.to_path_buf()], false);
let matches = collect_matches_from_walker(walker, &formatters, &mut stats);
assert_eq!(stats.traversed_files, 7);
@ -1505,4 +1556,78 @@ mod tests {
assert_eq!(python_matches, expected_python_matches);
assert!(nix_matches);
}
#[test]
fn test_walker_some_matches_exclude_gitignore_hidden() {
let tmpdir = utils::tmp_mkdir();
let black = tmpdir.path().join("black");
let nixpkgs_fmt = tmpdir.path().join("nixpkgs-fmt");
utils::write_binary_file(&black, " ");
utils::write_binary_file(&nixpkgs_fmt, " ");
let tree_root = tmpdir.path();
let git_dir = tree_root.join(".git");
utils::Git::new(tmpdir.path().to_path_buf())
.git_ignore("test1.nix\n.git/*.nix")
.exclude("result\n.direnv")
.create();
let files = vec!["test", "test1", ".test4"];
for file in files {
utils::write_file(tree_root.join(format!("{file}.py")), " ");
utils::write_file(tree_root.join(format!("{file}.nix")), " ");
utils::write_file(tree_root.join(format!("{file}.py")), " ");
utils::write_file(tree_root.join(format!("{file}.nix")), " ");
utils::write_file(git_dir.join(file), " ");
utils::write_file(tree_root.join(file), " ");
}
utils::write_file(tree_root.join("result"), " ");
utils::write_file(tree_root.join(".direnv"), " ");
let config = format!(
"
[formatter.python]
command = {black:?}
includes = [\"*.py\", \"test\"]
excludes = [\"test.py\" ]
[formatter.nix]
command = {nixpkgs_fmt:?}
includes = [\"*.nix\"]
excludes = [\"test.nix\"]
"
);
let root = from_string(&config).unwrap();
let mut stats = Statistics::init();
let formatters = load_formatters(root, tree_root, false, &None, &mut stats).unwrap();
let walker = build_walker(vec![tree_root.to_path_buf()], true);
let matches = collect_matches_from_walker(walker, &formatters, &mut stats);
assert_eq!(stats.traversed_files, 15);
assert_eq!(stats.matched_files, 5);
let python_matches: Vec<PathBuf> = matches
.get(&FormatterName::new("python"))
.unwrap()
.keys()
.cloned()
.collect();
let nix_matches: Vec<PathBuf> = matches
.get(&FormatterName::new("nix"))
.unwrap()
.keys()
.cloned()
.collect();
let expected_nix_matches: Vec<PathBuf> =
[".test4.nix"].iter().map(|p| tree_root.join(p)).collect();
let expected_python_matches: Vec<PathBuf> = [".git/test", ".test4.py", "test", "test1.py"]
.iter()
.map(|p| tree_root.join(p))
.collect();
assert_eq!(python_matches, expected_python_matches);
assert_eq!(nix_matches, expected_nix_matches);
}
}