Skip to content
On this page

开发案例

1、toto cli

三方库

  • clap: 命令行解析
  • dirs: 获取用户目录

1.1、安装依赖

shell
cargo add clap
cargo add dirs

1.2、cli.rs

js
use clap::{Parser, Subcommand};

#[derive(Parser)]
#[clap(version, about)]
#[clap(propagate_version = true)]
pub struct Cli {
    #[clap(subcommand)]
    pub command: Commands,
}

#[derive(Debug, Subcommand)]
pub enum Commands {
    #[clap(about = "Show odo info.")]
    Info,

    #[clap(about = "Add a todo item.")]
    Add {
        #[clap(help = "The item content to add.")]
        content: Option<String>,
    },

    #[clap(about = "Remove a todo item.")]
    #[clap(visible_aliases = & ["rm"])]
    Remove {
        #[clap(help = "The item id to remove.")]
        id: Option<String>,
    },

    #[clap(about = "List all the todo items.")]
    #[clap(visible_aliases = & ["ls", "ll", "la"])]
    List,
}

1.3、commands.rs

js
use std::io;
use crate::database::{Database, Record};

pub fn info() -> Result<(), io::Error> {
    println!("Todo is a simple todo list manager.");
    Ok(())
}

pub fn add(db: &mut Database, content: Option<String>) -> Result<(), io::Error> {
    if let Some(content) = content {
        let records = db.read_records();
        db.add_record(&Record {
            id: records.len() as u32 + 1,
            content: content.clone(),
        })?;
        println!("📝 Item added: {}", content);
        Ok(())
    } else {
        eprintln!("You need to specify the content of the todo item.");
        std::process::exit(1);
    }
}

pub fn remove(db: &mut Database, id: Option<String>) -> Result<(), io::Error> {
    if id.is_none() {
        println!("You need to specify the id of the todo item.");
        std::process::exit(1);
    }
    println!("Removing a todo item: {}", id.clone().unwrap());
    db.remove_record(id.unwrap().parse::<u32>().unwrap())?;
    println!(" ❌ Item removed!\n");
    Ok(())
}

pub fn list(db: &mut Database) -> Result<(), io::Error> {
    let records = db.read_records();
    if records.is_empty() {
        eprintln!("No records. You can add one with `todo add [content]`");
        std::process::exit(1);
    }
    for record in records {
        println!(" ⬜️ {}: {}", record.id, record.content);
    }
    Ok(())
}

1.4、database.rs

js
use std::fs::{File, OpenOptions};
use std::io::{self, BufRead, BufReader, Seek, Write};
use crate::utils::{check_db_file, get_db_file_path};

pub struct Record {
    pub id: u32,
    pub content: String,
}

pub struct Database {
    file: File,
}

// 解析记录
pub fn parse_record_line(line: &str) -> Record {
    let fields: Vec<&str> = line.split(',').collect();
    // 处理空行的情况
    if fields.len() == 1 {
        return Record {
            id: 0,
            content: "".to_string(),
        };
    }
    let content = fields[1..].join(",");
    Record {
        id: fields[0].parse::<u32>().unwrap(),
        content,
    }
}

impl Database {
    pub fn open() -> Database {
        // 先检查 db 文件是否存在,不存在就创建
        check_db_file().unwrap();
        // 获取db文件路径
        let db_file = get_db_file_path();
        let file = OpenOptions::new()
            .create(true)
            .read(true)
            .write(true)
            .open(db_file)
            .unwrap();
        Database { file }
    }

    pub fn add_record(&mut self, record: &Record) -> Result<(), io::Error> {
        let line = format!("{},{}", record.id, record.content);
        writeln!(self.file, "{}", line)
    }

    pub fn read_records(&mut self) -> Vec<Record> {
        let reader = BufReader::new(&self.file);
        reader
            .lines()
            .map_while(Result::ok)
            .filter(|line| !line.is_empty())
            .map(|line| parse_record_line(&line))
            .collect()
    }

    pub fn remove_record(&mut self, id: u32) -> Result<(), io::Error> {
        let reader = BufReader::new(&self.file);
        let mut lines = reader.lines().enumerate();
        // 根据id找出对应的行
        let line = lines.find(|(_, line)| {
            let record = parse_record_line(line.as_ref().unwrap());
            record.id == id
        });
        match line {
            Some((i, _)) => {
                // 过滤掉对应的行,这里使用的对应 api 可以查看 Rust 标准库
                let new_contents = lines
                    .filter(|(j, _)| *j != i)
                    .map(|(_, line)| line.unwrap())
                    .collect::<Vec<_>>()
                    .join("\n");
                // 这里使用了 std::io::Seek,需要导入
                self.file.seek(std::io::SeekFrom::Start(0)).unwrap();
                self.file.write_all(new_contents.as_bytes()).unwrap();
                self.file.set_len(new_contents.len() as u64).unwrap();
                // println!(" ❌ Item removed!\n");
                Ok(())
            }
            None => Err(io::Error::new(
              io::ErrorKind::Other,
              format!("No such record: {}", id),
          )),
        }
    }
}

1.5、utils.rs

js
use std::{path, fs, io};
use dirs::home_dir;

pub const DB_FILE: &str = ".todo_db";

// 获取db文件路径
pub fn get_db_file_path() -> path::PathBuf {
  home_dir().map(|it| it.join(DB_FILE)).unwrap_or_default()
}

// 检查db文件是否存在
pub fn db_exists() -> bool {
  let dir = get_db_file_path();
  fs::metadata(&dir).is_ok()
}

// 创建db
pub fn create_db_file() -> io::Result<()> {
  let dir = get_db_file_path();
  fs::File::create(dir)?;
  Ok(())
}

// 检查db文件是否存在,不存在就创建
pub fn check_db_file() -> io::Result<()> {
  if !db_exists() {
    create_db_file()?;
  }
  Ok(())
}

1.6、main.rs

js
mod cli;
mod commands;
mod database;
mod utils;

use clap::Parser;
use cli::{Cli, Commands};
use database::Database;

// cargo run add -h
fn main() {
    let args = Cli::parse();
    let mut db = Database::open();

    // 匹配命令
    let result = match args.command {
        Commands::Info => commands::info(),
        Commands::Add { content } => commands::add(&mut db, content),
        Commands::Remove { id } => commands::remove(&mut db, id),
        Commands::List => commands::list(&mut db),
    };

    // 统一处理错误
    if let Err(err) = result {
        eprintln!("\x1b[31merror:\x1b[39m {}", err);
        std::process::exit(1);
    }
}

1.7、安装使用

安装

shell
cargo install

使用

shell
todo-list add -h

2、grep from file

2.1、lib.rs

js
use std::{env, error, fs};

#[derive(Debug)]
pub struct Config {
  pub query: String, // 查询关键词
  pub filename: String, // 文件名
  pub case_sensitive: bool // 是否区分大小写,默认区分
}

impl Config {
    pub fn new(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();
        Ok(Config { query, filename, case_sensitive })
    }
}

pub fn run(config: Config) -> Result<(), Box<dyn error::Error>> {
    let contents = fs::read_to_string(config.filename)?;
    let result;
    if config.case_sensitive {
        result = search(&config.query, &contents)
    } else {
        result = search_case_insensitive(&config.query, &contents)
    }
    for line in result {
        println!("{}", line);
    }
    Ok(())
}

// 区分大小写
pub fn search<'a>(query: &'a str, contents: &'a str) -> Vec<&'a str> {
    let mut results = Vec::new();
    for line in contents.lines() {
        if line.contains(query) {
            results.push(line);
        }
    }
    results
}

// 忽略大小写
pub fn search_case_insensitive<'a>(query: &'a 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) {
          results.push(line);
      }
  }
  results
}

2.2、main.rs

js
use std::{env, process};
use grep_cli::{Config, run};

fn main() {
    let args: Vec<String> = env::args().collect();
    let config = Config::new(&args).unwrap_or_else(|err| {
        eprintln!("Problem parsing arguments: {}", err);
        process::exit(1);
    });
    if let Err(e) = run(config) {
      eprintln!("Application error: {}", e);
      process::exit(1);
    }
}

3、CaCher(缓存, 有问题)

js
struct CaCher<T,>
where
    T: Fn(u32) -> u32,
{
    cb: T,
    value: Option<u32>,
}

impl<T> CaCher<T>
where
    T: Fn(u32) -> u32,
{
    fn new(cb: T) -> Self {
        Self {
            cb,
            value: None,
        }
    }

    fn value(&mut self, arg: u32) -> u32 {
        match self.value {
            Some(v) => v,
            None => {
                let v = (self.cb)(arg);
                self.value = Some(v);
                v
            }
        }
    }
}

fn main() {
  let mut cacher = CaCher::new(|num| {
    num + 1
  });
  let result = cacher.value(1);
  let result2 = cacher.value(2);
  println!("{}-{}", result, result2);
}

4、博客发布

js
pub struct Post {
    state: Option<Box<dyn State>>,
    content: String,
}

impl Post {
    pub fn new() -> Post {
        Post {
            state: Some(Box::new(Draft {})),
            content: String::new(),
        }
    }
    pub fn add_text(&mut self, text: &str) {
        self.content.push_str(text);
    }
    pub fn content(&self) -> &str {
        self.state.as_ref().unwrap().content(self)
    }
    pub fn request_review(&mut self) {
        if let Some(s) = self.state.take() {
            self.state = Some(s.request_review())
        }
    }
    pub fn approve(&mut self) {
        if let Some(s) = self.state.take() {
            self.state = Some(s.approve())
        }
    }
}

trait State {
    fn request_review(self: Box<Self>) -> Box<dyn State>;
    fn approve(self: Box<Self>) -> Box<dyn State>;
    fn content<'a>(&self, post: &'a Post) -> &'a str {
        ""
    }
}

struct Draft {}

impl State for Draft {
    fn request_review(self: Box<Self>) -> Box<dyn State> {
        Box::new(PendingReview {})
    }
    fn approve(self: Box<Self>) -> Box<dyn State> {
        self
    }
}

struct PendingReview {}

impl State for PendingReview {
    fn request_review(self: Box<Self>) -> Box<dyn State> {
        self
    }
    fn approve(self: Box<Self>) -> Box<dyn State> {
        Box::new(Published {})
    }
}

struct Published {}

impl State for Published {
    fn request_review(self: Box<Self>) -> Box<dyn State> {
        self
    }
    fn approve(self: Box<Self>) -> Box<dyn State> {
        self
    }
    fn content<'a>(&self, post: &'a Post) -> &'a str {
        &post.content
    }
}

pub fn generate_post() -> Post {
    Post::new()
}

#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    fn post() {
        let mut post = generate_post();
        post.add_text("I ate a salad for lunch today");
        post.request_review();
        post.approve();
        assert_eq!("I ate a salad for lunch today", post.content());
    }
}

5、进度条

js
use std::io::{stdout, Result, Write};
use std::time::Duration;
use std::thread::sleep;

fn main() -> Result<()> {
    const BAR_LAB: &str = "-\\|/";
    for per in 0..101 {
            print!("\r {} \u{1b}[42m{}\u{1b}[0m [ {}% ] ", 
            BAR_LAB.chars().nth(per % 4).unwrap(), 
            " ".repeat(per / 2), 
            per
        );
        stdout().flush()?;
        sleep(Duration::from_micros(60_000));
    }
    println!();
    Ok(())
}

6、转json

6.1、字符串转json

js
use serde_json::Value;

fn main() {
    let json = r#"
    {
      "article": "how to work with json in Rust",
      "author": "tdep",
      "paragraph": [
        {
          "name": "untyped"
        },
        {
          "name": "strongly typed"
        },
        {
          "name": "writing json"
        }
      ]
    }
    "#;
    let parsed: Value = serde_json::from_str(json).unwrap();
    println!("{}-{}", parsed, parsed["article"]);
}

6.2、字符串转结构体

js
use serde::{Serialize, Deserialize};

#[derive(Serialize, Deserialize, Debug)]
struct Paragraph {
    name: String,
}

#[derive(Serialize, Deserialize, Debug)]
struct Article {
    article: String,
    author: String,
    paragraph: Vec<Paragraph>,
}

fn main() {
    let json = r#"
    {
      "article": "how to work with json in Rust",
      "author": "tdep",
      "paragraph": [
        {
          "name": "untyped"
        },
        {
          "name": "strongly typed"
        },
        {
          "name": "writing json"
        }
      ]
    }
    "#;
    let parsed: Article = serde_json::from_str(json).unwrap();
    println!("{:?}", parsed);
}

6.3、结构体转字符串

js
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize, Debug)]
struct Paragraph {
    name: String,
}

#[derive(Serialize, Deserialize, Debug)]
struct Article {
    article: String,
    author: String,
    paragraph: Vec<Paragraph>,
}

fn main() {
    let article: Article = Article {
        article: String::from("article name"),
        author: String::from("author name"),
        paragraph: vec![
            Paragraph {
                name: String::from("paragraph 1")
            },
            Paragraph {
                name: String::from("paragraph 2")
            }
        ],
    };
    let json = serde_json::to_string(&article).unwrap();
    println!("{}", json);
}

7、泛型

7.1、最大值泛型

js
fn largest_number(arr: &[i32]) -> i32 {
    let mut largest = arr[0];
    for index in 1..arr.len() {
        if arr[index] > largest {
            largest = arr[index];
        }
    }
    largest
}

fn largest_char(arr: &[char]) -> char {
    let mut largest = arr[0];
    for index in 1..arr.len() {
        if arr[index] > largest {
            largest = arr[index];
        }
    }
    largest
}

fn largest<T: std::cmp::PartialOrd + Copy>(arr: &[T]) -> T {
    let mut largest = arr[0];
    for index in 1..arr.len() {
        if arr[index] > largest {
            largest = arr[index];
        }
    }
    largest
}

7.2、冒泡

8、端口嗅探器

js
use std::{env, net::{IpAddr, TcpStream}, str::FromStr, process, thread, sync::mpsc::Sender, io::{self, Write}};

const MAX_PORT: u16 = 65535;

#[derive(Debug)]
struct Arguments {
  flag: String,
  ip_addr: String,
  threads: u16
}

impl Arguments {
    fn new(args: &[String]) -> Result<Arguments, &'static str> {
        if args.len() < 2 {
            return Err("not enough arguments");
        } else if args.len() > 4 {
            return Err("too many arguments");
        }
        let flag = args[1].clone();
        println!("flag: {}", flag);
        if let Ok(ip_addr) = IpAddr::from_str(&flag) {
            return Ok(Arguments {flag, ip_addr: ip_addr.to_string(), threads: 4});
        } else {
            // let flag = args[1].clone();
            if flag.contains("-h") || flag.contains("-help") && args.len() == 2 {
                println!("Usage: -j to select how many threads you want");
                println!("Usage: -h or -help to show this help message");
                return Err("help");
            } else if flag.contains("-h") || flag.contains("-help") {
                return Err("too many arguments");
            } else if flag.contains("-j") {
                let ip_addr = match IpAddr::from_str(&args[3]) {
                    Ok(ip) => ip,
                    Err(_) => return Err("invalid IP address")
                };
                let threads = match args[2].parse::<u16>() {
                    Ok(t) => t,
                    Err(_) => return Err("invalid thread number")
                };
                return Ok(Arguments {flag, ip_addr: ip_addr.to_string(), threads});
            } else {
                return Err("invalid argument");
            }
        }
    }
}

fn main() {
    let args: Vec<String> = env::args().collect();
    let program = args[0].clone();
    let arguments = Arguments::new(&args).unwrap_or_else(|err| {
        if err.contains("-h")  || err.contains("-help") {
            print!("{}", err);
            process::exit(0)
        } else {
            println!("{} Problem parsing arguments: {}", program, err);
            process::exit(0)
        }
    });
    println!("{:#?}", arguments);

    let num_threads = arguments.threads;
    let (tx, rx) = std::sync::mpsc::channel::<u16>();
    for i in 0..num_threads {
        let tx = tx.clone();
        let addr = arguments.ip_addr.clone();
        thread::spawn(move || {
          scan(tx, i, addr, num_threads);
        });
    }

    let mut out = vec![];
    drop(tx);
    for p in rx {
        out.push(p);
    }
    println!();
    out.sort();
    for v in out {
        println!("{} is open", v);
    }
}

fn scan(tx: Sender<u16>, start_port: u16, addr: String, num_threads: u16) {
    let mut port: u16 = start_port + 1;
    loop {
        match TcpStream::connect(format!("{}:{}", addr, port)) {
            Ok(_) => {
                println!("{}:{}", addr, port);
                io::stdout().flush().unwrap();
                tx.send(port).unwrap();
            },
            Err(_) => {}
        }
        if (MAX_PORT - port) <= num_threads {
            break;
        }
        port += num_threads;
    }
}

Released under the MIT License.