Repository: ParthJadhav/Rust_Search (language: rust)

Issue:
[Feature Request]: Make `Search` struct easier 
**Breaking changes**

I think add `search` instance function to `Search` struct will make it easier. This will make it easier to do something like this

```rust
use rust_search::Search;

fn main() {
    let mains = Search::default().input("main").search();
    let readmes = Search::default().input("readme")
    .ignore_case() // will ignore ascii case
    .extension("md")
    .search();
}
```


Discussion / hints:
If you don't mind adding this, I'm ready to add it 🚀 
Go ahead @TheAwiteb 🚀 I'm out for a while

Fix patch:
diff --git a/README.md b/README.md
index f774d20..9a84758 100644
--- a/README.md
+++ b/README.md
@@ -15,15 +15,25 @@ Add `rust_search = "0.1.4"` in Cargo.toml.
 rust_search = "0.1.4"
 ```
 
-Then, use it in your code:
+## Examples
+
+Genral use
 
 ```rust
-use rust_search::Search;
+use rust_search::SearchBuilder;
 
 fn main(){
-    let depth = 1;
-
-    let search = Search::new("/path/to/directory", Some("fileName"), Some(".fileExtension"), Some(depth));
+    let search: Vec<String> = SearchBuilder::default()
+        .location("/path/to/search")
+        .search_input("what to search")
+        .more_locations(vec!["/anotherPath/to/search", "/keepAddingIfYouWant/"])
+        .ext(".extension")
+        .strict()
+        .depth(1)
+        .ignore_case()
+        .hidden()
+        .build()
+        .collect();
 
     for path in search {
         println!("{}", path);
@@ -34,25 +44,38 @@ fn main(){
 To get all the files with a specific extension in a directory, use:
 
 ```rust
-use rust_search::Search;
+use rust_search::SearchBuilder;
 
-Search::new("/path/to/directory", None, Some(".fileExtension"), Some(1));
+let files: Vec<String> = SearchBuilder::default()
+    .location("/path/to/directory")
+    .ext("file_extension")
+    .build()
+    .collect();
 ```
 
 To get all the files in a directory, use:
 
 ```rust
-use rust_search::Search;
+use rust_search::SearchBuilder;
 
-Search::new("/path/to/directory", None, None, Some(1));
+let files: Vec<String> = SearchBuilder::default()
+    .location("/path/to/directory")
+    .depth(1)
+    .build()
+    .collect();
 ```
 
+👉 For more examples, please refer to the [Documentation](https://docs.rs/rust_search/latest/rust_search/)
+
 ## ⚙️ 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.
 
 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.
+
 ### - Rust vs Glob
+
 The benchmark was done on a directories containing 300K files.
 
 | Command / Library | Mean [s] | Min [s] | Max [s] | Relative |
@@ -61,7 +84,9 @@ The benchmark was done on a directories containing 300K files.
 | `glob` | 22.728 ± 0.023 | 22.690 | 22.746 | 17.25 ± 0.03 |
 
 ---
+
 ### - Rust vs FD
+
 The benchmark was done on a directories containing 45K files.
 
 | Command / Library | Mean [ms] | Min [ms] | Max [ms] | Relative |
@@ -72,6 +97,7 @@ The benchmark was done on a directories containing 45K files.
 ---
 
 ### Results:-
+
 ```diff
 + Rust_Search is 17.25 times faster than Glob.
 
@@ -80,7 +106,7 @@ The benchmark was done on a directories containing 45K files.
 
 ## 🔵 Discord server & Linkedin
 
-Click the button below to join the discord server or Linkedin 
+Click the button below to join the discord server or Linkedin
 
 <a href="https://discord.gg/hqDPyNb9m3" target="_blank"><img src="https://user-images.githubusercontent.com/42001064/126635148-9a736436-5a6d-4298-8d8e-acda11aec74c.png" alt="Join Discord Server" width="180px" ></a>
 <a href="https://www.linkedin.com/in/parthjadhav04" target="_blank"><img src="https://img.shields.io/badge/Linkedin-blue?style=flat-square&logo=linkedin" alt="Connect on Linkedin" width="180px" height="58"></a>
@@ -93,3 +119,25 @@ Any contributions would be greatly valued as this library is still in its early
 - Benchmarks
 - Implementation guidlines
 - Code Improvement
+
+If you want to contribute to this project, please follow the steps below:
+
+1. Fork the project
+2. Clone the forked repository
+3. Create a feature branch
+4. Make changes to the code
+5. Commit the changes
+6. Push the changes to the forked repository
+7. Create a pull request
+8. Wait for the pull request to be reviewed and merged (if approved)
+
+## License
+
+This project is licensed under the terms of the MIT license.
+
+## Discord server & Linkedin
+
+Click the button below to join the discord server or Linkedin
+
+<a href="https://discord.gg/hqDPyNb9m3" target="_blank"><img src="https://user-images.githubusercontent.com/42001064/126635148-9a736436-5a6d-4298-8d8e-acda11aec74c.png" alt="Join Discord Server" width="180px" ></a>
+<a href="https://www.linkedin.com/in/parthjadhav04" target="_blank"><img src="https://img.shields.io/badge/Linkedin-blue?style=flat-square&logo=linkedin" alt="Connect on Linkedin" width="180px" height="58"></a>
diff --git a/src/builder.rs b/src/builder.rs
new file mode 100644
index 0000000..c52d4d9
--- /dev/null
+++ b/src/builder.rs
@@ -0,0 +1,192 @@
+use std::path::{Path, PathBuf};
+
+use crate::Search;
+
+/// Builder for a [`Search`] instance, allowing for more complex searches.
+pub struct SearchBuilder {
+    /// The location to search in, defaults to the current directory.
+    search_location: PathBuf,
+    /// Vector of additional locations to search in.
+    more_locations: Option<Vec<PathBuf>>,
+    /// The search input, defaults to search for every word.
+    search_input: Option<String>,
+    /// The file extension to search for, defaults to any file extension.
+    file_ext: Option<String>,
+    /// The depth to search to, defaults to no limit.
+    depth: Option<usize>,
+    /// When set to true, Searches for exact match, defaults to false.
+    strict: bool,
+    /// Set search option to be case insensitive, defaults to false.
+    ignore_case: bool,
+    /// Search for hidden files, defaults to false.
+    hidden: bool,
+}
+
+impl SearchBuilder {
+    /// Build a new [`Search`] instance.
+    #[allow(deprecated)]
+    pub fn build(&self) -> Search {
+        Search::new(
+            &self.search_location,
+            self.more_locations.clone(),
+            self.search_input.as_deref(),
+            self.file_ext.as_deref(),
+            self.depth,
+            self.strict,
+            self.ignore_case,
+            self.hidden,
+        )
+    }
+
+    /// Set the search location to search in.
+    /// ### Arguments
+    /// * `location` - The location to search in.
+    /// ### Examples
+    /// ```rust
+    /// use rust_search::SearchBuilder;
+    ///
+    /// let search: Vec<String> = SearchBuilder::default()
+    /// .location("src")
+    /// .build()
+    /// .collect();
+    /// ```
+    pub fn location(mut self, location: impl AsRef<Path>) -> Self {
+        self.search_location = location.as_ref().to_path_buf();
+        self
+    }
+
+    /// Set the search input.
+    /// ### Arguments
+    /// * `input` - The search input.
+    /// ### Examples
+    /// ```rust
+    /// use rust_search::SearchBuilder;
+    ///
+    /// let search: Vec<String> = SearchBuilder::default()
+    /// .search_input("Search")
+    /// .build()
+    /// .collect();
+    /// ```
+    pub fn search_input(mut self, input: impl Into<String>) -> Self {
+        self.search_input = Some(input.into());
+        self
+    }
+
+    /// Set the file extension to search for.
+    /// ### Arguments
+    /// * `ext` - The file extension to search for.
+    /// ### Examples
+    /// ```rust
+    /// use rust_search::SearchBuilder;
+    ///
+    /// let search: Vec<String> = SearchBuilder::default()
+    /// .ext(".rs")
+    /// .build()
+    /// .collect();
+    /// ```
+    pub fn ext(mut self, ext: impl Into<String>) -> Self {
+        self.file_ext = Some(ext.into());
+        self
+    }
+
+    /// Set the depth to search to.
+    /// ### Arguments
+    /// * `depth` - The depth to search to.
+    /// ### Examples
+    /// ```rust
+    /// use rust_search::SearchBuilder;
+    ///
+    /// let search: Vec<String> = SearchBuilder::default()
+    /// .depth(1)
+    /// .build()
+    /// .collect();
+    /// ```
+    pub fn depth(mut self, depth: usize) -> Self {
+        self.depth = Some(depth);
+        self
+    }
+
+    /// Searches for exact match
+    /// ### Examples
+    /// ```rust
+    /// use rust_search::SearchBuilder;
+    ///
+    /// let search: Vec<String> = SearchBuilder::default()
+    /// .search_input("name")
+    /// .strict()
+    /// .build()
+    /// .collect();
+    /// ```
+    pub fn strict(mut self) -> Self {
+        self.strict = true;
+        self
+    }
+
+    /// Set search option to be case insensitive.
+    /// ### Examples
+    /// ```rust
+    /// use rust_search::SearchBuilder;
+    ///
+    /// let search: Vec<String> = SearchBuilder::default()
+    /// .search_input("name")
+    /// .ignore_case()
+    /// .build()
+    /// .collect();
+    /// ```
+    pub fn ignore_case(mut self) -> Self {
+        self.ignore_case = true;
+        self
+    }
+
+    /// Searches for hidden files
+    /// ### Examples
+    /// ```rust
+    /// use rust_search::SearchBuilder;
+    ///
+    /// let search: Vec<String> = SearchBuilder::default()
+    /// .with_hidden()
+    /// .build()
+    /// .collect();
+    /// ```
+    pub fn hidden(mut self) -> Self {
+        self.hidden = true;
+        self
+    }
+
+    /// Add extra locations to search in.
+    /// ### Arguments
+    /// * `more_locations` - Vec<> of locations to search in.
+    /// ### Examples
+    /// ```rust
+    /// use rust_search::SearchBuilder;
+    ///
+    /// let search: Vec<String> = SearchBuilder::default()
+    /// .more_locations(vec!["/Users/username/b/", "/Users/username/c/"])
+    /// .build()
+    /// .collect();
+    /// ```
+    pub fn more_locations(mut self, more_locations: Vec<impl AsRef<Path>>) -> Self {
+        self.more_locations = Some(
+            more_locations
+                .into_iter()
+                .map(|x| x.as_ref().to_path_buf())
+                .collect(),
+        );
+        self
+    }
+}
+
+impl Default for SearchBuilder {
+    fn default() -> Self {
+        Self {
+            search_location: std::env::current_dir().expect("Failed to get current directory"),
+            more_locations: None,
+            search_input: None,
+            file_ext: None,
+            depth: None,
+            strict: false,
+            ignore_case: false,
+            hidden: false,
+        }
+    }
+}
diff --git a/src/lib.rs b/src/lib.rs
index ea765c6..fc04646 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -2,111 +2,9 @@
 // Use the readme as the crate documentation
 #![doc = include_str!("../README.md")]
 
-use std::{
-    cmp,
-    path::Path,
-    sync::mpsc::{self, Sender},
-};
+mod builder;
+mod search;
+mod utils;
 
-use ignore::{WalkBuilder, WalkState};
-use regex::Regex;
-
-/// A struct that holds the receiver for the search results
-///
-/// Can be iterated on to get the next element in the search results
-///
-/// # Examples
-///
-/// ## Iterate on the results
-///
-/// ```
-/// use rust_search::Search;
-///
-/// let search = Search::new("src", None, Some(".rs"), Some(1));
-///
-/// for path in search {
-///    println!("{:?}", path);
-/// }
-/// ```
-///
-/// ## Collect results into a vector
-///
-/// ```
-/// use rust_search::Search;
-///
-/// let search = Search::new("src", None, Some(".rs"), Some(1));
-///
-/// let paths_vec: Vec<String> = search.collect();
-/// ```
-pub struct Search {
-    rx: mpsc::Receiver<String>,
-}
-
-impl Iterator for Search {
-    type Item = String;
-
-    fn next(&mut self) -> Option<Self::Item> {
-        match self.rx.recv() {
-            Ok(v) => Some(v),
-            Err(_) => None,
-        }
-    }
-}
-
-impl Search {
-    /// Create a new instance of the searcher
-    pub fn new(
-        search_location: impl AsRef<Path>,
-        search_input: Option<&str>,
-        file_type: Option<&str>,
-        depth: Option<usize>,
-    ) -> Self {
-        let regex_search_input = build_regex_search_input(search_input, file_type);
-
-        let walker = WalkBuilder::new(search_location)
-            .hidden(true)
-            .git_ignore(true)
-            .max_depth(depth)
-            .threads(cmp::min(12, num_cpus::get()))
-            .build_parallel();
-
-        let (tx, rx) = mpsc::channel::<String>();
-
-        walker.run(|| {
-            let tx: Sender<String> = tx.clone();
-            let reg_exp: Regex = regex_search_input.clone();
-
-            Box::new(move |path_entry| {
-                if let Ok(entry) = path_entry {
-                    let path: String = entry.path().display().to_string();
-
-                    if reg_exp.is_match(&path) {
-                        return match tx.send(path) {
-                            Ok(_) => WalkState::Continue,
-                            Err(_) => WalkState::Quit,
-                        };
-                    }
-                }
-
-                WalkState::Continue
-            })
-        });
-
-        Self { rx }
-    }
-}
-
-impl Default for Search {
-    /// Effectively just creates a [`WalkBuilder`] over the current diretory
-    fn default() -> Self {
-        Self::new(std::env::current_dir().unwrap(), None, None, None)
-    }
-}
-
-fn build_regex_search_input(search_input: Option<&str>, file_type: Option<&str>) -> Regex {
-    let file_type = file_type.unwrap_or(".*");
-    let search_input = search_input.unwrap_or(r"\w+\");
-
-    let formatted_search_input = format!(r#"{}{}$"#, search_input, file_type);
-    Regex::new(&formatted_search_input).unwrap()
-}
+pub use builder::SearchBuilder;
+pub use search::Search;
diff --git a/src/search.rs b/src/search.rs
new file mode 100644
index 0000000..4392ce6
--- /dev/null
+++ b/src/search.rs
@@ -0,0 +1,118 @@
+use std::{
+    cmp,
+    path::Path,
+    sync::mpsc::{self, Sender},
+};
+
+use crate::{utils, SearchBuilder};
+use ignore::{WalkBuilder, WalkState};
+use regex::Regex;
+
+/// A struct that holds the receiver for the search results
+///
+/// Can be iterated on to get the next element in the search results
+///
+/// # Examples
+///
+/// ## Iterate on the results
+///
+/// ```
+/// use rust_search::Search;
+///
+/// let search = Search::new("src", None, Some(".rs"), Some(1));
+///
+/// for path in search {
+///    println!("{:?}", path);
+/// }
+/// ```
+///
+/// ## Collect results into a vector
+///
+/// ```
+/// use rust_search::Search;
+///
+/// let search = Search::new("src", None, Some(".rs"), Some(1));
+///
+/// let paths_vec: Vec<String> = search.collect();
+/// ```
+pub struct Search {
+    rx: mpsc::Receiver<String>,
+}
+
+impl Iterator for Search {
+    type Item = String;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        match self.rx.recv() {
+            Ok(v) => Some(v),
+            Err(_) => None,
+        }
+    }
+}
+
+impl Search {
+    /// Search for files in a given arguments
+    /// ### Arguments
+    /// * `search_location` - The location to search in
+    /// * `search_input` - The search input, defaults to any word
+    /// * `file_ext` - The file extension to search for, defaults to any file extension
+    /// * `depth` - The depth to search to, defaults to no limit
+    #[allow(clippy::too_many_arguments)]
+    pub fn new(
+        search_location: impl AsRef<Path>,
+        more_locations: Option<Vec<impl AsRef<Path>>>,
+        search_input: Option<&str>,
+        file_ext: Option<&str>,
+        depth: Option<usize>,
+        strict: bool,
+        ignore_case: bool,
+        with_hidden: bool,
+    ) -> Self {
+        let regex_search_input =
+            utils::build_regex_search_input(search_input, file_ext, strict, ignore_case);
+
+        let mut walker = WalkBuilder::new(search_location);
+
+        walker
+            .hidden(!with_hidden)
+            .git_ignore(true)
+            .max_depth(depth)
+            .threads(cmp::min(12, num_cpus::get()));
+
+        if let Some(locations) = more_locations {
+            for location in locations {
+                walker.add(location);
+            }
+        }
+
+        let (tx, rx) = mpsc::channel::<String>();
+        walker.build_parallel().run(|| {
+            let tx: Sender<String> = tx.clone();
+            let reg_exp: Regex = regex_search_input.clone();
+
+            Box::new(move |path_entry| {
+                if let Ok(entry) = path_entry {
+                    let path: String = entry.path().display().to_string();
+
+                    if reg_exp.is_match(&path) {
+                        return match tx.send(path) {
+                            Ok(_) => WalkState::Continue,
+                            Err(_) => WalkState::Quit,
+                        };
+                    }
+                }
+
+                WalkState::Continue
+            })
+        });
+
+        Self { rx }
+    }
+}
+
+impl Default for Search {
+    /// Effectively just creates a [`WalkBuilder`] over the current directory
+    fn default() -> Self {
+        SearchBuilder::default().build()
+    }
+}
diff --git a/src/utils.rs b/src/utils.rs
new file mode 100644
index 0000000..13b458a
--- /dev/null
+++ b/src/utils.rs
@@ -0,0 +1,26 @@
+use regex::Regex;
+
+pub(crate) fn build_regex_search_input(
+    search_input: Option<&str>,
+    file_ext: Option<&str>,
+    strict: bool,
+    ignore_case: bool,
+) -> Regex {
+    let file_type = file_ext.unwrap_or(".*");
+    let search_input = search_input.unwrap_or(r"\w+\");
+    const FUZZY_SEARCH: &str = r".*\";
+    let mut formatted_search_input;
+    if strict {
+        formatted_search_input = format!(r#"{}{}$"#, search_input, file_type);
+    } else {
+        formatted_search_input = format!(r#"{}{}{}$"#, search_input, FUZZY_SEARCH, file_type);
+    }
+    if ignore_case {
+        formatted_search_input = set_case_insensitive(&formatted_search_input);
+    }
+    Regex::new(&formatted_search_input).unwrap()
+}
+
+fn set_case_insensitive(formatted_search_input: &str) -> String {
+    "(?i)".to_owned() + formatted_search_input
+}


Test patch: