Rust6.2 An I/O Project: Building a Command Line Program (mini_grep)

Rust学习笔记

Rust编程语言入门教程课程笔记

参考教材: The Rust Programming Language (by Steve Klabnik and Carol Nichols, with contributions from the Rust Community)

Lecture 12: An I/O Project: Building a Command Line Program

project: minigrep

src/main.rs

//grep: globally search a regular expression and print

use std::env;//command line arguments
use std::process;//exit

use minigrep::Config;//Config struct
use minigrep::run;//run function


//Separation of Concerns for Binary Projects
//Splitting code into a main.rs and a lib.rs is a good default choice when starting a binary project.

//1. Split your program into a main.rs and a lib.rs and move your program’s logic to lib.rs.
//2. As long as your command line parsing logic is small, it can remain in main.rs.
//3. When the command line parsing logic starts getting complicated, extract it from main.rs and move it to lib.rs.

fn main() {
    let args: Vec<String> = env::args().collect();//collect command line arguments
    // println!("{:?}", args);//print command line arguments //[./target/debug/minigrep, xxxx, yyyy]

    // let query = &args[1];//query string 
    // let filename = &args[2];//filename
    
    //let (query, filename) = parse_config(&args[1..]);//parse command line arguments

    // let config = parse_config(&args);//parse command line arguments

    // let config = Config::new(&args);//parse command line arguments

    let config = Config::build(&args).unwrap_or_else(|err| {
        // println!("Problem parsing arguments: {}", err);
        eprintln!("Problem parsing arguments: {}", err);//error handling: print to stderr
        process::exit(1);
    });//parse command line arguments

    // println!("Searching for {}", query);
    // println!("In file {}", filename);

    // let contents = fs::read_to_string(config.filename)
    //     .expect("Something went wrong reading the file");//read file

    // println!("With text:\n{}", contents);

    if let Err(e) = run(config){
        // println!("Application error: {}", e);
        eprintln!("Application error: {}", e);//error handling: print to stderr
        process::exit(1);
    }
}

lib.rs

use std::fs;//file system
use std::error::Error;//error handling
use std::env;//environment variables

// fn parse_config(args: &[String]) -> (&str, &str) {
//     let query = &args[1];//query string 
//     let filename = &args[2];//filename

//     (query, filename)
// }

pub fn run(config: Config) -> Result<(), Box<dyn Error>>{
    let contents = fs::read_to_string(config.filename)?;
    //.expect("Something went wrong reading the file");//read file
    //println!("With text:\n{}", contents);
    let results = if config.case_sensitive {//if case sensitive
        search(&config.query, &contents)//search case sensitive
    } else {
        search_case_insensitive(&config.query, &contents)//search case insensitive
    };
    // for line in search(&config.query, &contents) {//iterate over each line
    //     println!("{}", line);//print line
    // }
    for line in results {//iterate over each line
        println!("{}", line);//print line
    }
    Ok(())
}

pub struct Config {
    query: String,
    filename: String,
    case_sensitive: bool,
}


// fn parse_config(args: &[String]) -> Config {
//     let query = args[1].clone();//query string 
//     let filename = args[2].clone();//filename

//     Config { query, filename }
// }

impl Config {
    // fn new(args: &[String]) -> Config {
    //     if args.len() < 3 {
    //         panic!("not enough arguments");
    //     }
    //     let query = args[1].clone();//query string 
    //     let filename = args[2].clone();//filename

    //     Config { query, filename }
    // }

    pub fn build(args: &[String]) -> Result<Config, &'static str> {
        if args.len() < 3 {
            return Err("not enough arguments");
        }

        let query = args[1].clone();
        let filename = args[2].clone();
        let case_sensitive = env::var("CASE_INSENSITIVE").is_err();//case sensitive
        Ok(Config { query, filename, case_sensitive })
    }
}

pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {//<'a> lifetime annotation
    // let mut results = Vec::new();//mutable vector

    // for line in contents.lines() {//iterate over each line
    //     if line.contains(query) {//if line contains query
    //         results.push(line);//add line to results
    //     }
    // }
    // results//return results

    contents.lines()//iterate over each line
        .filter(|line| line.contains(query))//if line contains query
        .collect()//collect into vector
}

pub fn search_case_insensitive<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {//<'a> lifetime annotation
    let mut results = Vec::new();//mutable vector
    let query = query.to_lowercase();//convert query to lowercase
    for line in contents.lines() {//iterate over each line
        if line.to_lowercase().contains(&query) {//if line contains query
            results.push(line);//add line to results
        }
    }
    results//return results
}

//TDD: Test-Driven Development 
//Writing a Failing Test and Seeing It Pass
//1. Write a test that fails and run it to make sure it fails for the reason you expect.
//2. Write or modify just enough code to make the new test pass.
//3. Refactor the code you just added or changed and make sure the tests continue to pass.
//4. Repeat from step 1!

#[cfg(test)]
mod tests {
    use super::*;//import outer scope

    #[test]
    fn one_result() {
        let query = "duct";
        let contents = "\
Rust:
safe, fast, productive.
Pick three.";
        assert_eq!(
            vec!["safe, fast, productive."],
            search(query, contents)
        );
    }

    #[test]
    fn case_sensitive() {
        let query = "duct";
        let contents = "\
Rust:
safe, fast, productive.
Pick three.
Duct tape.";
        assert_eq!(
            vec!["safe, fast, productive."],
            search(query, contents)
        );
    }

    #[test]
    fn case_insensitive() {
        let query = "rUsT";
        let contents = "\
Rust:
safe, fast, productive.
Pick three.
Trust me.";
        assert_eq!(
            vec!["Rust:", "Trust me."],
            search_case_insensitive(query, contents)
        );
    }
}

你可能感兴趣的:(笔记,rust)