Skip to content

Commit

Permalink
feat(cli): improve the cli and add more options (#16)
Browse files Browse the repository at this point in the history
* feat(cli): improve the cli and add more options

* feat(cli): enhancing the cli

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* chore(ci): fix some bugs

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
  • Loading branch information
jeertmans and pre-commit-ci[bot] authored Mar 12, 2023
1 parent de0b3c1 commit a2c12cd
Show file tree
Hide file tree
Showing 7 changed files with 394 additions and 62 deletions.
52 changes: 39 additions & 13 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ path = "src/bin.rs"
crossbeam-channel = "0.5"
globset = "0.4"
ignore = "0.4"
num_cpus = "1.15.0"
regex = "1"

[package]
Expand All @@ -18,7 +19,7 @@ license = "MIT"
name = "filesfinder"
readme = "README.md"
repository = "https://github.com/jeertmans/filesfinder"
rust-version = "1.58.1"
rust-version = "1.63.0"
version = "0.3.8"

[[test]]
Expand Down
19 changes: 6 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
<p align="center">
<img src="https://raw.githubusercontent.com/jeertmans/filesfinder/main/static/logo.svg" width="200" height="200"> </img>
</p>

# FilesFinder

> Find files matching patterns while respecting `.gitignore`
Expand All @@ -9,16 +13,15 @@
3. [Examples](#examples)
4. [GitHub Action](#github-action)
5. [Contributing](#contributing)
- [Future features](#future-features)

## About

FilesFinder (FF) is a command-line tool that aims to search for files within a given repository.
As such, it respects your `.gitignore` files and exclude the same files from the output.

FF is a fast and simpler-to-use alternative to other tools such as `find` from [Findutils](https://www.gnu.org/software/findutils/manual/html_mono/find.html).
FF is a **faster** and **simpler-to-use** alternative to other tools such as `find` from [Findutils](https://www.gnu.org/software/findutils/manual/html_mono/find.html).

> **NOTE:** FF is not necessarily faster than `find` (or else), but speed is plays an important in its development and you can be sure that opting to `ff` will not decrease performance by much.
> **NOTE:** FF is generally faster than `find` (or else), mainly because it uses parallel processing. If you find a scenario in which FF is slower than `find` or any other tool, please report it to me :-)
## Installation

Expand Down Expand Up @@ -127,13 +130,3 @@ A major application to `FF` is to be used within repositories. Therefore, you ca
## Contributing
Contributions are more than welcome!
### Future features
- [ ] Benchmark the tool against alternatives
- [ ] Provide other flags for case
- [ ] Allow to match fullname or basename
- [x] Add tests for CI
- [x] Create a GitHub action
- [ ] Publish pre-built binaries and use them for GitHub action
- [ ] ...
129 changes: 108 additions & 21 deletions src/bin.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use globset::GlobBuilder;
use regex::bytes::RegexSetBuilder;
use std::io::{self, Write};
use std::path::Path;
use std::path::{Path, PathBuf};

#[macro_export]
macro_rules! path_as_bytes {
Expand Down Expand Up @@ -32,6 +32,8 @@ OPTIONS:
-r, -R
Parse pattern as a regular expression.
Note that expressions are unanchored by default.
Use '^' or '\\A' to denote start, and '$' or '\\z' for the end.
-i, -I
Matching files will be included in the output.
Expand All @@ -40,15 +42,34 @@ OPTIONS:
-e, -E
Matching files will be excluded from the output.
-j <JOBS>
Number of threads to use.
Setting this to zero will choose the number of threads automatically.
[default: num_cpus]
--dir <PATH>
Files will be searched in the directory specified by the PATH.
Multiple occurences are allowed.
[default: '.']
--max-depth <DEPTH>
Maximum depth to recurse.
[default: None]
--follow-links
Allow to follow symbolic links.
--show-hidden
Allow to show hidden files.
--no-gitignore
Ignore gitignore files.
ignore .gitignore files.
--no-ignore
ignore .ignore files.
--no-strip-prefix
Do not strip './' prefix, same as what GNU find does.
-h, --help
Print help information.
Expand Down Expand Up @@ -154,14 +175,24 @@ impl<'source> MatcherBuilder<'source> {

fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut args = std::env::args().skip(1);

// Matcher options
let mut default_kind = MatcherKind::Glob;
let mut default_include = true;
let mut include: Vec<String> = vec![];
let mut exclude: Vec<String> = vec![];
let mut last_arg_seen = false;
let mut directory = ".".to_string();
let mut ignore_hidden = true;
let mut strip_prefix: bool = true;

// Walker options
let mut directories: Vec<String> = vec![];
let mut follow_links = false;
let mut use_gitignore = true;
let mut use_ignore = true;
let mut ignore_hidden = true;
let mut max_depth: Option<usize> = None;
let mut threads: Option<usize> = None;

let mut last_arg_seen = false;

while !last_arg_seen {
let mut matcher = MatcherBuilder::new(default_kind.clone());
Expand All @@ -173,16 +204,29 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
match option {
"dir" => {
if let Some(path) = args.next() {
directory = path;
directories.push(path);
} else {
eprintln!(
"--dir option is missing a <PATH>. Print help with '--help'."
"Error: --dir option is missing a <PATH>. Print help with '--help'."
);
std::process::exit(1);
}
}
"follow-links" => follow_links = true,
"show-hidden" => ignore_hidden = false,
"no-gitignore" => use_gitignore = false,
"no-ignore" => use_ignore = false,
"no-strip-prefix" => strip_prefix = false,
"max-depth" => {
if let Some(depth) = args.next() {
max_depth = depth.parse().ok();
} else {
eprintln!(
"Error: --max-depth option is missing a <DEPTH>. Print help with '--help'."
);
std::process::exit(1);
}
}
"help" => {
print_help();
std::process::exit(0);
Expand Down Expand Up @@ -223,6 +267,16 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
default_include = false;
}
}
'j' => {
if let Some(jobs) = args.next() {
threads = jobs.parse().ok();
} else {
eprintln!(
"error: -j option is missing a <JOBS>. Print help with '--help'."
);
std::process::exit(1);
}
}
'h' => {
print_help();
std::process::exit(0);
Expand Down Expand Up @@ -263,30 +317,63 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
let include = RegexSetBuilder::new(include).build()?;
let exclude = RegexSetBuilder::new(exclude).build()?;

let (tx, rx) = crossbeam_channel::unbounded::<ignore::DirEntry>();
let (tx, rx) = crossbeam_channel::unbounded::<PathBuf>();

let walker = ignore::WalkBuilder::new(directory.as_str())
.hidden(ignore_hidden)
let mut directories = directories.iter().map(|s| s.as_str());

let mut walk_builder = ignore::WalkBuilder::new(directories.next().unwrap_or("."));

walk_builder
.follow_links(follow_links)
.git_ignore(use_gitignore)
.build_parallel();
.hidden(ignore_hidden)
.ignore(use_ignore)
.max_depth(max_depth)
.threads(threads.unwrap_or(num_cpus::get()));

for directory in directories {
walk_builder.add(directory);
}

let walker = walk_builder.build_parallel();

let mut stdout = io::BufWriter::new(io::stdout());

let stdout_thread = std::thread::spawn(move || {
let mut stdout = io::BufWriter::new(io::stdout());
for de in rx.iter().filter(|de| {
let path = de.path();
let strl = path.to_string_lossy();
let utf8 = strl.as_bytes();
path.is_file() && include.is_match(utf8) && !exclude.is_match(utf8)
}) {
write_path(&mut stdout, de.path());
for path_buf in rx {
write_path(&mut stdout, path_buf.as_path());
}
});

walker.run(|| {
let tx = tx.clone();
let include = &include;
let exclude = &exclude;

Box::new(move |result| {
tx.send(result.unwrap()).unwrap();
ignore::WalkState::Continue
let de = match result {
Ok(de) => de,
Err(_) => return ignore::WalkState::Continue,
};

let mut path = de.path();

if strip_prefix {
if let Ok(p) = path.strip_prefix("./") {
path = p;
}
}

let strl = path.to_string_lossy();
let utf8 = strl.as_bytes();
if path.is_file() && include.is_match(utf8) && !exclude.is_match(utf8) {
match tx.send(path.to_path_buf()) {
Ok(_) => ignore::WalkState::Continue,
Err(_) => ignore::WalkState::Quit,
}
} else {
ignore::WalkState::Continue
}
})
});

Expand Down
Loading

0 comments on commit a2c12cd

Please sign in to comment.