【RUST学习】命令行程序(项目实例)

接受命令行参数

主要:实现在特定文件中搜索特定的内容

use std::env;
//args函数会返回一个迭代器,迭代器会产生一系列的值
//我们可以调用collect方法把这些值转化成一个集合
fn main() {
    let args :Vec<String> = env::args().collect();


    let query =&args[1];
    let filename = &args[2];

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

加入读取文件

use std::env;
//args函数会返回一个迭代器,迭代器会产生一系列的值
//我们可以调用collect方法把这些值转化成一个集合
use std::fs;
//处理跟文件相关的一些事物
fn main() {
    let args :Vec<String> = env::args().collect();


    let query =&args[1];
    let filename = &args[2];

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

    let contents = fs::read_to_string(filename).expect("Something wrong in reading txt");
    println!("with the text:\n{}",contents);
}

但是这样一股脑的将功能全部放在main函数里会导致可读性变差,可维护性变弱,因此rust社区发行了二进制关注点分离的指导性原则。

1、将程序拆分为main.rs和lib.rs时,将业务逻辑放进lib.rs。
2、当命令行解析逻辑较少时,将他放在main.rs也可以。
3、当命令行解析逻辑变复杂时,需要将他从main.rs提取到lib.rs。

改善模块化

我们尝试将解析参数的代码解剖出来

use std::env;
//args函数会返回一个迭代器,迭代器会产生一系列的值
//我们可以调用collect方法把这些值转化成一个集合
use std::fs;
//处理跟文件相关的一些事物
fn main() {
    let args :Vec<String> = env::args().collect();
    let (query, filename) = parse_config(&args);

    let contents = fs::read_to_string(filename).expect("Something wrong in reading txt");
    println!("with the text:\n{}",contents);
}

fn parse_config(args: &[String]) ->(&str, &str) {//通过这个函数来解析参数
    let query = &args[1];
    let filename = &args[2];
    (query,filename)
}

这段代码的可读性还不够强,那么我们继续做改进

use std::env;
//args函数会返回一个迭代器,迭代器会产生一系列的值
//我们可以调用collect方法把这些值转化成一个集合
use std::fs;
//处理跟文件相关的一些事物
fn main() {
    let args :Vec<String> = env::args().collect();//读取了一个字符串,args的所有权归main函数
    let config = Config::new(&args);

    let contents = fs::read_to_string(config.filename).expect("Something wrong in reading txt");
    println!("with the text:\n{}",contents);
}

struct Config{
    query:String,
    filename:String,
}

impl Config {
    fn new(args: &[String]) ->Config { //传了一个切片进去,但是目前并没有所有权
        let query = args[1].clone();
        let filename = args[2].clone();
        Config{//这里是需要arg中某些切片的所有权的,所以我们采用克隆的方法获得原切片的副本
            query,
            filename,
        }
    }
}

错误处理

代码总是会发生错误,这里我们对可能发生的错误进行处理。

use std::env;
//args函数会返回一个迭代器,迭代器会产生一系列的值
//我们可以调用collect方法把这些值转化成一个集合
use std::fs;
//处理跟文件相关的一些事物
use std::process;

fn main() {
    let args :Vec<String> = env::args().collect();//读取了一个字符串,args的所有权归main函数
    let config = Config::new(&args).unwrap_or_else(|err|{
        //如果发生了错误,会屏蔽掉之前的一些代码然后打印err中的内容
        println!("problem parsing arguments: {}", err);
        process::exit(1);
    });

    let contents = fs::read_to_string(config.filename).expect("Something wrong in reading txt");
    println!("with the text:\n{}",contents);
}

struct Config{
    query:String,
    filename:String,
}

impl Config {
    fn new(args: &[String]) ->Result<Config, &'static str> { 
        //传了一个切片进去,但是目前并没有所有权
        //如果出现错误就返回一个err变体
        if args.len() < 3{
            return Err("not enough arguments")
        }
        let query = args[1].clone();
        let filename = args[2].clone();
        Ok(        Config{//这里是需要arg中某些切片的所有权的,所以我们采用克隆的方法获得原切片的副本
            query,
            filename,
        })
    }
}

将业务逻辑移动至lib.rs

main.rs中代码如下:

use minigrep::Config;
use std::env;
//args函数会返回一个迭代器,迭代器会产生一系列的值
//我们可以调用collect方法把这些值转化成一个集合
use std::process;


fn main() {
    let args :Vec<String> = env::args().collect();//读取了一个字符串,args的所有权归main函数
    let config = Config::new(&args).unwrap_or_else(|err|{
        //如果发生了错误,会屏蔽掉之前的一些代码然后打印err中的内容
        println!("problem parsing arguments: {}", err);
        process::exit(1);
    });
    if let Err(e) = minigrep::run(config){//如果返回了一个错误
        println!("Application error: {}",e);
        process::exit(1);
    }

}

lib.rs中的代码如下

use std::error::Error;
use std::fs;
//处理跟文件相关的一些事物

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

pub fn run(config:Config) ->Result<(),Box<dyn Error>>{
    let contents = fs::read_to_string(config.filename)?;//在发生错误时不会产生panic而是把错误交给函数的调用者来进行处理
    println!("with the text:\n{}",contents);
    Ok(())
}

impl Config {
   pub fn new(args: &[String]) ->Result<Config, &'static str> { 
        //传了一个切片进去,但是目前并没有所有权
        //如果出现错误就返回一个err变体
        if args.len() < 3{
            return Err("not enough arguments")
        }
        let query = args[1].clone();
        let filename = args[2].clone();
        Ok(        Config{//这里是需要arg中某些切片的所有权的,所以我们采用克隆的方法获得原切片的副本
            query,
            filename,
        })
    }
}

使用TDD编写库功能

编写一个会失败的测试,运行该测试,确保他按照预期的原因失败
编写或修改刚好足够的代码,让新测试通过
重构刚刚添加或修改的代码,确保测试会通过
返回步骤1,继续

main.rs函数不做修改

use minigrep::Config;
use std::env;
//args函数会返回一个迭代器,迭代器会产生一系列的值
//我们可以调用collect方法把这些值转化成一个集合
use std::process;


fn main() {
    let args :Vec<String> = env::args().collect();//读取了一个字符串,args的所有权归main函数
    let config = Config::new(&args).unwrap_or_else(|err|{
        //如果发生了错误,会屏蔽掉之前的一些代码然后打印err中的内容
        println!("problem parsing arguments: {}", err);
        process::exit(1);
    });
    if let Err(e) = minigrep::run(config){//如果返回了一个错误
        println!("Application error: {}",e);
        process::exit(1);
    }

}

lib.rs

use std::error::Error;
use std::fs;
//处理跟文件相关的一些事物

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

pub fn run(config:Config) ->Result<(),Box<dyn Error>>{
    let contents = fs::read_to_string(config.filename)?;//在发生错误时不会产生panic而是把错误交给函数的调用者来进行处理
    for line in search(&config.query, &contents){
        println!("{}",line);
    }
    println!("with the text:\n{}",contents);
    Ok(())
}

impl Config {
   pub fn new(args: &[String]) ->Result<Config, &'static str> { 
        //传了一个切片进去,但是目前并没有所有权
        //如果出现错误就返回一个err变体
        if args.len() < 3{
            return Err("not enough arguments")
        }
        let query = args[1].clone();
        let filename = args[2].clone();
        Ok(        Config{//这里是需要arg中某些切片的所有权的,所以我们采用克隆的方法获得原切片的副本
            query,
            filename,
        })
    }
}

pub fn search<'a>(query: &str,contents:&'a str) ->Vec<&'a str> {
    let mut results = Vec::new();
    for line in contents.lines(){
        if line.contains(query) {//lines.contains()返回的是一个bool类型
            results.push(line);
        }
    }
    results
}

#[cfg(test)]   //测试函数
mod tests{
    use super::*;
    
    #[test]
    fn one_result() {
        let query = "duct";
        let contents = "\
Rust:
safe,fase,productive.
pick three.";

        assert_eq!(vec!["safe,fase,productive."],search(query,contents ))
    }
}

使用环境变量

用一个环境变量来控制搜索时是否需要将大小写进行区分

CASE_INSENSITIVE=1 : 无法将“CASE_INSENSITIVE=1”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。请检查名称的拼写,如果包括路径,请确保路径正确,然后再试一次。

main.rs

use minigrep::Config;
use std::env;
//args函数会返回一个迭代器,迭代器会产生一系列的值
//我们可以调用collect方法把这些值转化成一个集合
use std::process;


fn main() {
    let args :Vec<String> = env::args().collect();//读取了一个字符串,args的所有权归main函数
    let config = Config::new(&args).unwrap_or_else(|err|{
        //如果发生了错误,会屏蔽掉之前的一些代码然后打印err中的内容
        println!("problem parsing arguments: {}", err);
        process::exit(1);
    });
    if let Err(e) = minigrep::run(config){//如果返回了一个错误
        println!("Application error: {}",e);
        process::exit(1);
    }

}

lib.rs

use std::error::Error;
use std::fs;
//处理跟文件相关的一些事物
use std::env;

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

pub fn run(config:Config) ->Result<(),Box<dyn Error>>{
    let contents = fs::read_to_string(config.filename)?;//在发生错误时不会产生panic而是把错误交给函数的调用者来进行处理
    let results = if config.case_sensitive{
        search(&config.query, &contents)
    }else {
        search_case_insensitive(&config.query, &contents)
    };
    for line in results{
        println!("{}",line);
    }
    println!("with the text:\n{}",contents);
    Ok(())
}

impl Config {
   pub fn new(args: &[String]) ->Result<Config, &'static str> { 
        //传了一个切片进去,但是目前并没有所有权
        //如果出现错误就返回一个err变体
        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();
        Ok(        Config{//这里是需要arg中某些切片的所有权的,所以我们采用克隆的方法获得原切片的副本
            query,
            filename,
            case_sensitive
        })
    }
}

pub fn search<'a>(query: &str,contents:&'a str) ->Vec<&'a str> {
    let mut results = Vec::new();
    for line in contents.lines(){
        if line.contains(query) {//lines.contains()返回的是一个bool类型
            results.push(line);
        }
    }
    results
}

pub fn search_case_insensitive<'a>(query: &str,contents:&'a str) ->Vec<&'a str> {
    let mut results = Vec::new();
    let query = query.to_lowercase();

    for line in contents.lines(){
        if line.to_lowercase().contains(&query) {//lines.contains()返回的是一个bool类型
            results.push(line);
        }
    }
    results
}

#[cfg(test)]   //测试函数
mod tests{
    use super::*;
    
    #[test]
    fn case_sensitive() {
        let query = "duct";
        let contents = "\
Rust:
safe,fase,productive.
pick three.
Duct tape.";

        assert_eq!(vec!["safe,fase,productive."],search(query,contents ))
    }

    #[test]
    fn case_insensitive(){
        let query = "rUsT";
        let contents = "\
Rust:
safe,fase,productive.
pick three.
Trust me.";

        assert_eq!(vec!["Rust:","Trust me."],search_case_insensitive(query, contents) )
    }
}

将错误信息输出到标准错误

标准错误vs标准输出
标准输出:stdout
—println!
标准错误:stderr
—eprintln!

main.rs进行修改

use minigrep::Config;
use std::env;
//args函数会返回一个迭代器,迭代器会产生一系列的值
//我们可以调用collect方法把这些值转化成一个集合
use std::process;


fn main() {
    let args :Vec<String> = env::args().collect();//读取了一个字符串,args的所有权归main函数
    let config = Config::new(&args).unwrap_or_else(|err|{
        //如果发生了错误,会屏蔽掉之前的一些代码然后打印err中的内容
        eprintln!("problem parsing arguments: {}", err);
        process::exit(1);
    });
    if let Err(e) = minigrep::run(config){//如果返回了一个错误
        eprintln!("Application error: {}",e);
        process::exit(1);
    }

}

cmd输入

cargo run to poem.txt > output.txt

会将输出的内容存放到output.txt文件里

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值