batgrep 0.1.0

A library with very simple but useful functions to grep text.
Documentation
//! # batgrep
//! `batgrep` is is a CLI tool I build by reading The Rust Book. Rust is so
//! amazing.

// pub use self::pub_mods::deeper_pub_stuff
// thats how one shows all the pub stuff - lvl1 and deeper - so that pub api
// easily accessible. These are reexports

use std::env;
use std::error::Error;
use std::fs;

/// `ConfigArgs` struct will hold all the configuration variables such as
/// query(s) to be searched, filepath(s) of file(s) to be searched and 
/// other flags for different functionalities.
pub struct ConfigArgs {
    pub query: String,
    pub file_path: String,
    pub ignore_case: bool,
}

impl ConfigArgs {
    pub fn build(mut args: impl Iterator<Item = String>) -> Result<Self, &'static str> {
        args.next();
        Ok(ConfigArgs {
            query: args.next().ok_or("query not found")?,
            file_path: args.next().ok_or("file path not found")?,
            ignore_case: env::var("IGNORE_CASE").is_ok(),
        })
    }
}

/// `run()` will be responsible for running different types of search functions
/// depending on the types of flags passed when `batgrep` is called. It will
/// call suitable functions and print matched lines to `stdout` if any match
/// is found.
pub fn run(config: &ConfigArgs) -> Result<(), Box<dyn Error>> {
    let contents = fs::read_to_string(&config.file_path)?;

    if config.ignore_case {
        for lines in search_i(&config.query, &contents) {
            println!("{lines}");
        }
    } else {
        for lines in search(&config.query, &contents) {
            println!("{lines}");
        }
    }

    Ok(())
}

fn search<'a>(query: &str, content: &'a str) -> Vec<&'a str> {
    content
        .lines()
        .filter(|&line| line.contains(query))
        .collect()
}

fn search_i<'a>(query: &str, content: &'a str) -> Vec<&'a str> {
    // whats the problem in query.to_lowercase().as_str();

    // The problem is that to_lowercase() returns a String which is an owned
    // data type. We dont store this in any variable and try to reference it
    // in as_str(). Because we dont store it in any variable its lifetime ends
    // immediately and there is nothing left to reference.
    let query = query.to_lowercase();
    content
        .lines()
        .filter(|&line| line.to_lowercase().contains(&query))
        .collect()
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn case_sensitive_search() {
        let needle = "more";
        let haystack = "\
Once more into the fray.
This time even MORE deep.
Into the last good fight I'll ever know.
Live and die on this day.
Live and die on this day.";

        assert_eq!(vec!["Once more into the fray."], search(needle, haystack));
    }

    #[test]
    fn case_insensitive_search() {
        let needle = "lIvE";
        let haystack = "\
Once more into the fray.
Into the last good fight I'll ever know.
Live and die on this day.
Live and die on this day.
I am alive.";

        assert_eq!(
            vec![
                "Live and die on this day.",
                "Live and die on this day.",
                "I am alive."
            ],
            search_i(needle, haystack)
        );
    }
}