MySQL, Oracle, Linux, 软件架构及大数据技术知识分享平台

网站首页 > 精选文章 / 正文

使用Rust开发命令行程序之项目实战

2025-01-29 15:57 huorong 精选文章 4 ℃ 0 评论

前言

在前面的几篇文章里我们简单的介绍如何使用Rust标准库和第三方库来编写命令行工具,在本篇文章中,我们将从零开始构建一个命令行 Todo 管理器,允许用户添加、查看、标记完成以及删除待办事项。本项目将使用 clap 作为命令行参数解析工具,并结合标准库完成核心逻辑。

项目目标

需求分析

1.管理任务列表

支持以下操作:

  • 添加任务
  • 删除任务
  • 列出任务
  • 标记任务为完成

2.支持持久化到文件

  • 数据将保存到本地文件,以 JSON 格式存储,后续可以扩展为 TOML 或 YAML 格式。

3.美观的命令行界面

  • 使用颜色和表格格式化输出,提高用户体验。

4.测试与优化

  • 包括单元测试和集成测试,确保项目稳定性。

创建并初始化项目结构

使用cargo new创建一个项目,具体如下所示:

carog new cli_demo

在Cargo.toml文件中添加项目依赖,具体如下所示:

[package]
name = "cli_demo"
version = "0.1.0"
edition = "2021"

[dependencies]
clap = { version = "4.5.23", features = ["derive"] }
colored = "2.2.0"
serde = { version = "1.0.217", features = ["derive"] }
serde_json = "1.0.134"
tabwriter = "1.4.0"

在src目录下分别创建ui.rs,storage.rs和todo.rs文件,项目目录结构如下所示:

?  cli_demo git:(master) ? tree . -I target
.
├── Cargo.lock
├── Cargo.toml
├── src
│   ├── main.rs
│   ├── storage.rs
│   ├── todo.rs
│   └── ui.rs
└── tasks.json

2 directories, 7 files

实现具体代码逻辑

打开并编辑todo.rs文件,这里是项目的核心逻辑,具体实现如下所示:

use serde::{Deserialize, Serialize};
use crate::storage;
use crate::ui;

#[derive(Debug, Deserialize, Serialize)]
pub struct Task {
    pub id: usize,
    pub description: String,
    pub done: bool,
}

pub struct TodoManager {
    pub tasks: Vec,
    file_path: String,
}

impl TodoManager {
    pub fn new(file_path: &str) -> Self {
        let tasks = storage::load_tasks(file_path).unwrap_or_default();
        Self {
            tasks,
            file_path: file_path.to_string(),
        }
    }

    pub fn save(&self) {
        storage::save_tasks(&self.file_path, &self.tasks);
    }

    pub fn add(&mut self, description: String) {
        let id = self.tasks.len() + 1;
        self.tasks.push(Task{
            id,
            description,
            done: false,
        });
        self.save();
        println!("Added task with id {}", id);
    }

    pub fn list_tasks(&self) {
        ui::print_tasks(&self.tasks);
    }

    pub fn mark_task(&mut self, id: usize) {
        if let Some(task) = self.tasks.iter_mut().find(|t| t.id == id) {
            task.done = true;
            self.save();
            println!("Marking task");
        } else {
            println!("Task with id {} not found", id);
        }
    }

    pub fn delete(&mut self, id: usize) {
        if self.tasks.iter().any(|t| t.id == id) {
            self.tasks.retain(|t| t.id != id);
            self.save();
            println!("Removed task");
        } else {
            println!("Task with id {} not found", id);
        }
    }
}

打开并编辑storage.rs文件,这里是项目实现持久化的地方,具体实现如下所示:

use crate::todo::Task;
use std::io::Error;

pub fn load_tasks(file_path: &str) -> Result, Error> {
    let data = std::fs::read_to_string(file_path).unwrap_or_else(|_err| "[]".to_string());
    let tasks: Vec = serde_json::from_str(&data).unwrap_or_default();
    Ok(tasks)
}

pub fn save_tasks(file_path: &str, tasks: &Vec) {
    let data = serde_json::to_string_pretty(tasks).expect("failed to serialize tasks");
    std::fs::write(file_path, data).expect("failed to write data to file");
}

打开并编辑ui.rs文件,这里的逻辑主要负责美化输出,具体实现如下所示:

use std::io::Write;
use colored::Colorize;
use tabwriter::TabWriter;
use crate::todo::Task;

pub fn print_tasks(tasks: &[Task]) {
    if tasks.is_empty() {
        println!("{}", " No tasks selected".yellow());
        return;
    }

    let mut tw = TabWriter::new(Vec::new());
    writeln!(&mut tw, "ID\tDescription\tStatus").unwrap();
    for task in tasks {
        let status = if task.done {
            "Done".green()
        } else {
            "Pending".red()
        };
        writeln!(&mut tw, "{}\t{}\t{}", task.id, task.description, status).unwrap();
    }
    tw.flush().unwrap();
    println!("{}", String::from_utf8(tw.into_inner().unwrap()).unwrap());
}

打开并编辑main.rs文件,作为入口文件主要负责命令行参数的创建和主要逻辑,这里我选用clap宏特性来创建和解析命令行,具体实现如下所示:

mod todo;
mod ui;
mod storage;


#[derive(Debug, Parser)]
#[command(name="todo manager", version="0.0.1", about="manage your tasks effortlessly!")]
struct Cli {
    #[command(subcommand)]
    commands: Commands
}

#[derive(Debug, Subcommand)]
enum Commands {
    Add {
        #[arg(short, long)]
        description: String,
    },
    List,
    Done {
        #[arg(short, long)]
        id: usize
    },
    Delete {
        #[arg(short, long)]
        id: usize
    },
}

fn main() {
    let args = Cli::parse();

    let mut manager = TodoManager::new("tasks.json");
    match args.commands {
        Commands::Add { description } => manager.add(description),
        Commands::List => manager.list_tasks(),
        Commands::Done { id } => manager.mark_task(id),
        Commands::Delete { id } => manager.delete(id),
    }
}

单元测试我这里做得比较简单,在 src/todo.rs 中添加如下代码:

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

    #[test]
    fn test_add() {
        let mut manager = TodoManager::new("test.json");
        manager.add(String::from("test"));
        assert_eq!(manager.tasks.len(), 1);
    }

    #[test]
    fn test_mar_done() {
        let mut manager = TodoManager::new("test.json");
        manager.add(String::from("test"));
        manager.mark_task(1);
        assert!(manager.tasks[0].done);
    }
}

编译并运行,结果如下所示:

?  cli_demo git:(master) ? cargo run -- add --description "learn rust"
   Compiling cli_demo v0.1.0 (/Users/Alen/Workspaces/Rust/cli_demo)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.63s
     Running `target/debug/cli_demo add --description 'learn rust'`
Added task with id 1

?  cli_demo git:(master) ? cargo run -- add --description "learn rust-lang"
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.09s
     Running `target/debug/cli_demo add --description 'learn rust-lang'`
Added task with id 2

?  cli_demo git:(master) ? cargo run -- add --description "learn rust-lang-web"
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.02s
     Running `target/debug/cli_demo add --description 'learn rust-lang-web'`
Added task with id 3

?  cli_demo git:(master) ? cargo run -- list
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.08s
     Running `target/debug/cli_demo list`
ID  Description          Status
1   learn rust           Pending
2   learn rust-lang      Pending
3   learn rust-lang-web  Pending

?  cli_demo git:(master) ? cargo run -- done --id 1
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.05s
     Running `target/debug/cli_demo done --id 1`
Marking task

?  cli_demo git:(master) ? cargo run -- list
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.04s
     Running `target/debug/cli_demo list`
ID  Description          Status
1   learn rust           Done
2   learn rust-l      Pending
3   learn rust-lang-web  Pending

?  cli_demo git:(master) ? cargo run -- delete --id 1
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.05s
     Running `target/debug/cli_demo delete --id 1`
Removed task

?  cli_demo git:(master) ? cargo run -- list
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.02s
     Running `target/debug/cli_demo list`
ID  Description          Status
2   learn rust-lang      Pending
3   learn rust-lang-web  Pending

项目总结

学到的关键知识点

1.命令行工具开发

  • 学会了使用 clap 库定义命令行参数和子命令,构建功能强大的 CLI 工具。

2.数据持久化

  • 使用 serde 和 serde_json 库完成数据的序列化和反序列化,实现了任务的本地存储和加载。

3.美观的输出

  • 通过 colored 和 tabwriter 库,学会了如何增强命令行工具的用户体验。

4.代码组织与模块化

  • 理解了如何分离核心逻辑、存储逻辑和用户界面模块,提升代码的可维护性。

5.测试与优化

  • 掌握了基础的单元测试,确保项目功能正常。

项目可扩展方向

1.支持多种数据格式

  • 增加对 TOML、YAML 等格式的支持,让用户可以选择自己的数据存储方式。

2.支持多用户

  • 增加多用户管理功能,为每个用户维护独立的任务列表。

3.任务分类与标签

  • 为任务添加分类和标签功能,实现更高效的任务管理。

4.云同步功能

  • 通过 API 与云服务集成,实现任务的实时同步。

5.多语言支持

  • 提供国际化支持,让更多用户可以方便地使用工具。

总结

通过本章,我们完成了一个功能完整的 Todo 管理器,学会了以下技能:

  • 使用 clap 构建复杂命令行工具。
  • 使用 serde 和 serde_json 管理数据的序列化和反序列化。
  • 使用标准库进行文件操作。

本项目为实际应用场景提供了一个良好的示例,今后可以扩展更多功能,如支持分类、搜索或网络同步等。希望通过这个系列的文章能够帮助到你吧,感谢收到,欢迎点赞,转发让更多的rust看到!!!

Tags:yaml 格式化

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言