第17章 - 实战项目:待办事项 CLI
嗨,朋友!我是长安。
这一章我们要做一个实用的命令行工具——待办事项管理器(Todo CLI)。
这个项目会用到:文件操作、结构体、错误处理、JSON 序列化等知识。
🎯 项目目标
制作一个命令行待办事项工具,支持:
- ✅ 添加任务
- ✅ 列出所有任务
- ✅ 完成任务
- ✅ 删除任务
- ✅ 数据持久化(保存到文件)
🚀 创建项目
cargo new todo_cli
cd todo_cli
📦 添加依赖
编辑 Cargo.toml:
[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
serde:序列化/反序列化库serde_json:JSON 格式支持
💻 完整代码
创建 src/main.rs:
use serde::{Deserialize, Serialize};
use std::env;
use std::fs;
use std::io::{self, Write};
#[derive(Debug, Serialize, Deserialize, Clone)]
struct Task {
id: usize,
title: String,
completed: bool,
}
struct TodoList {
tasks: Vec<Task>,
file_path: String,
}
impl TodoList {
fn new(file_path: &str) -> Self {
let tasks = Self::load_from_file(file_path);
TodoList {
tasks,
file_path: file_path.to_string(),
}
}
fn load_from_file(file_path: &str) -> Vec<Task> {
match fs::read_to_string(file_path) {
Ok(content) => serde_json::from_str(&content).unwrap_or_else(|_| Vec::new()),
Err(_) => Vec::new(),
}
}
fn save_to_file(&self) {
let json = serde_json::to_string_pretty(&self.tasks).unwrap();
fs::write(&self.file_path, json).expect("无法保存文件");
}
fn add_task(&mut self, title: String) {
let id = if self.tasks.is_empty() {
1
} else {
self.tasks.iter().map(|t| t.id).max().unwrap() + 1
};
let task = Task {
id,
title,
completed: false,
};
self.tasks.push(task);
self.save_to_file();
println!("✅ 任务已添加!ID: {}", id);
}
fn list_tasks(&self) {
if self.tasks.is_empty() {
println!("📝 暂无任务");
return;
}
println!("\n📋 待办事项列表:");
println!("{:-<50}", "");
for task in &self.tasks {
let status = if task.completed { "✓" } else { " " };
println!("[{}] {} - {}", status, task.id, task.title);
}
println!("{:-<50}\n", "");
}
fn complete_task(&mut self, id: usize) {
if let Some(task) = self.tasks.iter_mut().find(|t| t.id == id) {
task.completed = true;
self.save_to_file();
println!("🎉 任务 {} 已完成!", id);
} else {
println!("❌ 找不到 ID 为 {} 的任务", id);
}
}
fn delete_task(&mut self, id: usize) {
let len_before = self.tasks.len();
self.tasks.retain(|t| t.id != id);
if self.tasks.len() < len_before {
self.save_to_file();
println!("🗑️ 任务 {} 已删除", id);
} else {
println!("❌ 找不到 ID 为 {} 的任务", id);
}
}
}
fn print_help() {
println!("\n📖 待办事项管理器");
println!("用法:");
println!(" todo add <任务内容> - 添加新任务");
println!(" todo list - 列出所有任务");
println!(" todo done <ID> - 完成任务");
println!(" todo delete <ID> - 删除任务");
println!(" todo help - 显示帮助\n");
}
fn main() {
let args: Vec<String> = env::args().collect();
if args.len() < 2 {
print_help();
return;
}
let mut todo_list = TodoList::new("tasks.json");
match args[1].as_str() {
"add" => {
if args.len() < 3 {
println!("❌ 请提供任务内容");
println!("示例: todo add 买菜");
return;
}
let title = args[2..].join(" ");
todo_list.add_task(title);
}
"list" => {
todo_list.list_tasks();
}
"done" => {
if args.len() < 3 {
println!("❌ 请提供任务 ID");
println!("示例: todo done 1");
return;
}
match args[2].parse::<usize>() {
Ok(id) => todo_list.complete_task(id),
Err(_) => println!("❌ 无效的 ID"),
}
}
"delete" => {
if args.len() < 3 {
println!("❌ 请提供任务 ID");
println!("示例: todo delete 1");
return;
}
match args[2].parse::<usize>() {
Ok(id) => todo_list.delete_task(id),
Err(_) => println!("❌ 无效的 ID"),
}
}
"help" => {
print_help();
}
_ => {
println!("❌ 未知命令: {}", args[1]);
print_help();
}
}
}
📖 代码详解
1. 任务结构体
#[derive(Debug, Serialize, Deserialize, Clone)]
struct Task {
id: usize,
title: String,
completed: bool,
}
Serialize和Deserialize:支持 JSON 序列化id:任务唯一标识completed:完成状态
2. 文件操作
fn load_from_file(file_path: &str) -> Vec<Task> {
match fs::read_to_string(file_path) {
Ok(content) => serde_json::from_str(&content).unwrap_or_else(|_| Vec::new()),
Err(_) => Vec::new(),
}
}
fn save_to_file(&self) {
let json = serde_json::to_string_pretty(&self.tasks).unwrap();
fs::write(&self.file_path, json).expect("无法保存文件");
}
- 读取 JSON 文件并解析为任务列表
- 将任务列表序列化为 JSON 并保存
3. 命令解析
let args: Vec<String> = env::args().collect();
match args[1].as_str() {
"add" => { /* ... */ },
"list" => { /* ... */ },
// ...
}
使用 env::args() 获取命令行参数。
🏃 使用示例
构建项目
cargo build --release
运行命令
# 添加任务
cargo run -- add 学习 Rust
cargo run -- add 写代码
cargo run -- add 锻炼身体
# 列出任务
cargo run -- list
# 完成任务
cargo run -- done 1
# 删除任务
cargo run -- delete 2
# 查看帮助
cargo run -- help
输出示例
$ cargo run -- add 学习 Rust
✅ 任务已添加!ID: 1
$ cargo run -- add 写代码
✅ 任务已添加!ID: 2
$ cargo run -- list
📋 待办事项列表:
--------------------------------------------------
[ ] 1 - 学习 Rust
[ ] 2 - 写代码
--------------------------------------------------
$ cargo run -- done 1
🎉 任务 1 已完成!
$ cargo run -- list
📋 待办事项列表:
--------------------------------------------------
[✓] 1 - 学习 Rust
[ ] 2 - 写代码
--------------------------------------------------
📁 数据文件
任务保存在 tasks.json 文件中:
[
{
"id": 1,
"title": "学习 Rust",
"completed": true
},
{
"id": 2,
"title": "写代码",
"completed": false
}
]
🎨 改进建议
1. 添加优先级
#[derive(Debug, Serialize, Deserialize, Clone)]
enum Priority {
Low,
Medium,
High,
}
struct Task {
id: usize,
title: String,
completed: bool,
priority: Priority, // 新增
}
2. 添加截止日期
use chrono::{NaiveDate, Local};
struct Task {
id: usize,
title: String,
completed: bool,
due_date: Option<NaiveDate>, // 新增
}
3. 搜索功能
fn search_tasks(&self, keyword: &str) {
let results: Vec<&Task> = self.tasks
.iter()
.filter(|t| t.title.contains(keyword))
.collect();
for task in results {
println!("[{}] {} - {}", task.id, task.title, task.completed);
}
}
4. 统计功能
fn show_stats(&self) {
let total = self.tasks.len();
let completed = self.tasks.iter().filter(|t| t.completed).count();
let pending = total - completed;
println!("📊 统计信息:");
println!(" 总任务:{}", total);
println!(" 已完成:{}", completed);
println!(" 待完成:{}", pending);
}
💡 学到的知识点
通过这个项目,我们学会了:
- ✅ 结构体和实现(
struct和impl) - ✅ 文件 I/O 操作
- ✅ JSON 序列化/反序列化
- ✅ 命令行参数处理
- ✅ 向量操作(
Vec) - ✅ 模式匹配
- ✅ 错误处理
💪 挑战任务
- 添加任务分类功能(工作、生活、学习等)
- 支持任务编辑功能
- 添加任务导出功能(导出为 CSV 或 Markdown)
- 制作交互式 UI(使用
crossterm或tui-rs) - 添加任务提醒功能
任务分类示例
#[derive(Debug, Serialize, Deserialize, Clone)]
enum Category {
Work,
Personal,
Study,
}
struct Task {
id: usize,
title: String,
completed: bool,
category: Category,
}
// 按分类列出
fn list_by_category(&self, category: Category) {
let tasks: Vec<&Task> = self.tasks
.iter()
.filter(|t| matches!(t.category, category))
.collect();
// 打印任务...
}
🚀 下一步
完成这个项目后,你可以:
- 发布到 crates.io
- 制作安装脚本
- 添加更多功能
