Rust练手项目1--minigrep

6 阅读2分钟

minigrep

官方教程断断续续看了10章,写个小demo练习下。目前感觉还行,继续看完再来重构一个版本。

目录结构

image.png

cargo.toml

[package]
name = "minigrep"
version = "0.0.1"
edition = "2024"

[dependencies]

grepp.txt

I'm nobody! Who are you?
Are you nobody, too?
Then there's a pair of us - don't tell!
They'd banish us, you know.

How dreary to be somebody!
How public, like a frog
To tell your name the livelong day
To an admiring bog!

main.rs

use std::env::args;

fn main() {
    let args: Vec<String> = args().collect();
    // 去掉第一个参数
    let params = &args[1..].to_vec();
    minigrep::grep(params);
}

lib.rs

use std::io::{Error};
use std::io::ErrorKind::InvalidInput;
use std::process;
use crate::common::status::Status;
pub mod common;

pub fn grep(params:&Vec<String>){
    check_params(&params);
    use common::content::Content;
    let result =  Content::parse(&params).search();
    if !result.is_empty(){
        println!("{:?}", result);
    }
}

/// 校验参数
fn check_params(params:&Vec<String>){
    // 检验长度
    let mut  status = check_params_len(params.len()).unwrap_or_else(out_err);
    if status.eq(&Status::Stop){
        process::exit(0);
    }
    // 检验合法
    status = check_params_invalid(params).unwrap_or_else(out_err);
    if status.eq(&Status::Stop){
        process::exit(0);
    }
}
fn check_params_len(len:usize) -> Result<Status,Error>{
    if len > 0 {
        return Ok(Status::Continue )
    }
    Err(Error::new(InvalidInput,"未指定有效参数 --help 可查看支持的参数!"))
}

fn check_params_invalid(params:&Vec<String>) -> Result<Status, Error>{
    use common::commands::Commands;
    let commands = Commands::values();
    for param in params {
        for command in &commands {
         if param.to_lowercase().starts_with(command.as_str()) {
             let display = command.display();
             if display.is_empty() {
                 return Ok(Status::Continue)
             }else {
                 println!("{}", display);
                 return Ok(Status::Stop )
             }
            }
        }
    }
    Ok(Status::Continue)
}

fn out_err(error:Error) -> Status{
    eprintln!("参数错误:{}", error);
    process::exit(1);
}

common/mod.rs

pub mod content;
pub mod commands;
pub mod status;

common/commands.rs

pub enum Commands {
    Help,
    Version,
    FilePath,
    LowerCase,
    Keyword,
}
impl Commands {
    pub fn as_str(&self) -> &str {
        use Commands::*;
        match *self {
            Help => "--help",
            Version => "--version",
            FilePath => "--filepath",
            LowerCase => "--lowercase",
            Keyword => "--keyword",
        }
    }
    pub fn values() -> Vec<Commands> {
        use Commands::*;
        vec![Help, Version, FilePath, LowerCase, Keyword]
    }

    pub fn display(&self) -> &str{
        use Commands::*;
        match *self {
            Help => "--help \t 帮助 \n \
            --version \t 版本 \n \
            --filepath=xxx.txt \t 文件路径 \n \
            --lowercase=false \t 是否忽略大小写 \n \
            --keyword=key \t 搜索关键词 \n",
            Version => "0.0.1",
            _ => "",
        }
    }
}

common/content.rs

use std::{fs, process};

pub struct Content{
    file_path: String, // 文件路径
    search_keyword:String, //搜索关键字
    context: String, // 文件内容
    lowercase:bool,//忽略大小写
}

impl Content {
   pub fn parse(params:&Vec<String>)->Content{
       let mut content = Content {
           file_path: "".to_string(),
           search_keyword: "".to_string(),
           context: "".to_string(),
           lowercase: true
       };
       for param in params {
           if param.to_lowercase().starts_with("--filepath="){
               content.file_path = param.replace("--filepath=", "");
           }
           if param.to_lowercase().starts_with("--keyword="){
               content.search_keyword = param.replace("--keyword=", "");
           }
           if param.to_lowercase().starts_with("--lowercase="){
               content.lowercase = param.replace("--lowercase=", "").parse().unwrap_or_default();
           }
       }
       if content.search_keyword.is_empty() {
           eprintln!("No search keyword specified");
           process::exit(1);
       }
       if !content.file_path.is_empty() {
           let result = fs::read_to_string(&content.file_path)
               .unwrap_or_else(|err| {
                   eprintln!("Error reading file: {}", err);
                   process::exit(1)
               });
           content.context = result.trim().to_string();
       }
       if content.context.is_empty() {
           eprintln!("No context specified");
           process::exit(1);
       }
       content
    }

    pub fn search(&self) -> Vec<String> {
        let mut resut = vec![];
        if self.context.is_empty() {
            vec![]
        }else {
            let mut key = self.search_keyword.clone();
            for line in self.context.lines() {
                if !line.is_empty() {
                    let mut context = line.to_string();
                    if self.lowercase{
                        context = line.to_string().to_lowercase();
                        key = key.to_lowercase();
                    }
                    if context.contains(&key) {
                        resut.push(context);
                    }
                }
            }
            resut
        }
    }
}

common/status.rs

#[derive(PartialEq)]
pub enum Status{
    Stop,
    Continue,
}

输出

image.png

image.png

image.png

image.png

image.png

image.png