Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 5 additions & 22 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,25 +32,10 @@ jobs:

steps:
- name: Checkout repository
uses: actions/checkout@v3
uses: actions/checkout@v4

- name: Install Rust toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
target: ${{ matrix.target }}
profile: minimal
override: true

- name: Install Rust library source
if: matrix.target == 'x86_64-unknown-linux-gnu'
uses: actions-rs/toolchain@v1
with:
toolchain: stable
target: ${{ matrix.target }}
profile: minimal
override: true
components: rust-src
run: rustup toolchain install stable --target ${{ matrix.target }} --profile minimal

- name: Build
run: cargo build --verbose --target ${{ matrix.target }}
Expand All @@ -59,15 +44,13 @@ jobs:
run: cargo test --verbose --target ${{ matrix.target }}

lint:
name: Formatter

needs: build-test
name: Lint & Format

runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v3
uses: actions/checkout@v4

- name: Install Rust
run: |
Expand All @@ -79,5 +62,5 @@ jobs:
- name: Check formatting
run: cargo fmt --all -- --check

- name: Check code for possible improvements
- name: Run clippy
run: cargo clippy -- -D warnings
72 changes: 34 additions & 38 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,52 +28,48 @@ rust_search = "2.0.0"
```rust
use rust_search::SearchBuilder;

fn main(){
let search: Vec<String> = SearchBuilder::default()
.location("~/path/to/directory")
.search_input("what to search")
.more_locations(vec!["/anotherPath/to/search", "/keepAddingIfYouWant/"])
.limit(1000) // results to return
.ext("extension")
.strict()
.depth(1)
.ignore_case()
.hidden()
.build()
.collect();

for path in search {
println!("{}", path);
}
let search: Vec<String> = SearchBuilder::default()
.location("~/path/to/directory")
.search_input("what to search")
.more_locations(vec!["/anotherPath/to/search", "/keepAddingIfYouWant/"])
.limit(1000) // results to return
.ext("extension")
.strict()
.depth(1)
.ignore_case()
.hidden()
.build()
.collect();

for path in search {
println!("{}", path);
}
```

- Sort the output by similarity with the input

```rust
use rust_search::{SearchBuilder, similarity_sort};
fn main() {
let search_input = "fly";
let mut search: Vec<String> = SearchBuilder::default()
.location("~/Desktop/")
.search_input(search_input)
.depth(1)
.ignore_case()
.build()
.collect();

similarity_sort(&mut search, &search_input);
for path in search {
println!("{:?}", path);
}
}

use rust_search::{SearchBuilder, similarity_sort};

let search_input = "fly";
let mut search: Vec<String> = SearchBuilder::default()
.location("~/Desktop/")
.search_input(search_input)
.depth(1)
.ignore_case()
.build()
.collect();

similarity_sort(&mut search, &search_input);
for path in search {
println!("{:?}", path);
}
```
> search **without** similarity sort
`["afly.txt", "bfly.txt", "flyer.txt", "fly.txt"]`
> `["afly.txt", "bfly.txt", "flyer.txt", "fly.txt"]`

> search **with** similarity sort
`["fly.txt", "flyer.txt", "afly.txt", "bfly.txt",]`
> `["fly.txt", "flyer.txt", "afly.txt", "bfly.txt",]`

- To get all the files with a specific extension in a directory, use:

Expand All @@ -98,7 +94,7 @@ let files: Vec<String> = SearchBuilder::default()
.build()
.collect();
```
To filter files by date_created, date_modified, file_size and/or custom_filter, use:
To filter files by `date_created`, `date_modified`, `file_size` and/or `custom_filter`, use:

```rust
use rust_search::{FileSize, FilterExt, SearchBuilder};
Expand All @@ -121,7 +117,7 @@ let search: Vec<String> = SearchBuilder::default()

## ⚙️ Benchmarks

The difference in sample size is due to the fact that fd and glob are different tools and have different use cases. fd is a command line tool that searches for files and directories. glob is a library that can be used to search for files and directories. The benchmark is done on a MacBook Air M2, 16 GB Unified memory.
The difference in sample size is due to the fact that fd and glob are different tools and have different use cases. fd is a command line tool that searches for files and directories. glob is a library that can be used to search for files and directories. The benchmark is done on a `MacBook` Air M2, 16 GB Unified memory.

Benchmarks are done using [hyperfine](https://github.com/sharkdp/hyperfine),
Benchmarks files are available in the [benchmarks](https://drive.google.com/drive/folders/1ug6ojNixS5jAe6Lh6M0o2d3tku73zQ9w?usp=sharing) drive folder.
Expand Down
7 changes: 5 additions & 2 deletions src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,10 @@ impl SearchBuilder {
pub fn ext(mut self, ext: impl Into<String>) -> Self {
let ext: String = ext.into();
// Remove the dot if it's there.
self.file_ext = Some(ext.strip_prefix('.').map_or(ext.clone(), str::to_owned));
self.file_ext = Some(
ext.strip_prefix('.')
.map_or_else(|| ext.clone(), str::to_owned),
);
self
}

Expand Down Expand Up @@ -201,7 +204,7 @@ impl SearchBuilder {
/// use rust_search::SearchBuilder;
///
/// let search: Vec<String> = SearchBuilder::default()
/// .with_hidden()
/// .hidden()
/// .build()
/// .collect();
/// ```
Expand Down
14 changes: 9 additions & 5 deletions src/filter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,12 +57,11 @@ fn convert(b: f64, pow: u32) -> u64 {
(b * 1024_u64.pow(pow) as f64) as u64
}

#[allow(clippy::from_over_into)]
impl Into<u64> for FileSize {
fn into(self) -> u64 {
impl From<FileSize> for u64 {
fn from(size: FileSize) -> Self {
use self::FileSize::{Byte, Gigabyte, Kilobyte, Megabyte, Terabyte};

match self {
match size {
Byte(b) => b,
Kilobyte(b) => convert(b, 1),
Megabyte(b) => convert(b, 2),
Expand Down Expand Up @@ -94,7 +93,12 @@ pub trait FilterExt {
fn file_size_greater(self, size: FileSize) -> Self;
/// custom filter that exposes the [`DirEntry`] directly
/// ```rust
/// builder.custom_filter(|dir| dir.metadata().unwrap().is_file())
/// use rust_search::{SearchBuilder, FilterExt};
///
/// let search: Vec<String> = SearchBuilder::default()
/// .custom_filter(|dir| dir.metadata().unwrap().is_file())
/// .build()
/// .collect();
/// ```
fn custom_filter(self, f: FilterFn) -> Self;
}
Expand Down
43 changes: 26 additions & 17 deletions src/search.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
use std::{
cmp,
path::Path,
sync::mpsc::{self, Sender},
sync::{
atomic::{AtomicUsize, Ordering},
mpsc::{self, Sender},
Arc,
},
};

use crate::{filter::FilterType, utils, SearchBuilder};
use ignore::{WalkBuilder, WalkState};
use regex::Regex;

/// A struct that holds the receiver for the search results
///
Expand All @@ -17,9 +20,13 @@ use regex::Regex;
/// ## Iterate on the results
///
/// ```
/// use rust_search::Search;
/// use rust_search::SearchBuilder;
///
/// let search = Search::new("src", None, Some(".rs"), Some(1));
/// let search = SearchBuilder::default()
/// .location("src")
/// .ext("rs")
/// .depth(1)
/// .build();
///
/// for path in search {
/// println!("{:?}", path);
Expand All @@ -29,11 +36,14 @@ use regex::Regex;
/// ## Collect results into a vector
///
/// ```
/// use rust_search::Search;
/// use rust_search::SearchBuilder;
///
/// let search = Search::new("src", None, Some(".rs"), Some(1));
///
/// let paths_vec: Vec<String> = search.collect();
/// let paths_vec: Vec<String> = SearchBuilder::default()
/// .location("src")
/// .ext("rs")
/// .depth(1)
/// .build()
/// .collect();
/// ```
pub struct Search {
rx: Box<dyn Iterator<Item = String>>,
Expand Down Expand Up @@ -94,10 +104,13 @@ impl Search {
}

let (tx, rx) = mpsc::channel::<String>();
let reg_exp = Arc::new(regex_search_input);
let counter = Arc::new(AtomicUsize::new(0));

walker.build_parallel().run(|| {
let tx: Sender<String> = tx.clone();
let reg_exp: Regex = regex_search_input.clone();
let mut counter = 0;
let reg_exp = Arc::clone(&reg_exp);
let counter = Arc::clone(&counter);

Box::new(move |path_entry| {
if let Ok(entry) = path_entry {
Expand All @@ -106,17 +119,13 @@ impl Search {
// Lossy means that if the file name is not valid UTF-8
// it will be replaced with �.
// Will return the file name with extension.
let file_name = file_name.to_string_lossy().to_string();
let file_name = file_name.to_string_lossy();
if reg_exp.is_match(&file_name) {
// Continue searching if the send was successful
// and there is no limit or the limit has not been reached
if tx.send(path.display().to_string()).is_ok()
&& (limit.is_none() || counter < limit.unwrap())
if limit.is_none_or(|l| counter.fetch_add(1, Ordering::Relaxed) < l)
&& tx.send(path.display().to_string()).is_ok()
{
counter += 1;
return WalkState::Continue;
}

return WalkState::Quit;
}
}
Expand Down
Loading