今天我們要整合本週學到的所有概念:結構體、列舉、模式匹配和錯誤處理,建立一個功能完整的命令列待辦事項管理工具。
我們將建立一個名為 todo-cli
的命令列工具,以下是 小J PM 提出的相關需求:
# 新增任務
./todo-cli add "學習 Rust" --priority high --due "2024-01-15"
# 列出所有任務
./todo-cli list
# 完成任務
./todo-cli complete 1
# 刪除任務
./todo-cli delete 2
# 列出高優先順序的任務
./todo-cli list --status pending --priority high
先建立專案結構:
day14/
├── Cargo.toml
└── src/
├── main.rs
├── lib.rs
├── task.rs
├── storage.rs
├── cli.rs
└── error.rs
[package]
name = "todo-cli"
version = "0.1.0"
edition = "2021"
[[bin]]
name = "todo-cli"
path = "src/main.rs"
[dependencies]
clap = { version = "4.0", features = ["derive"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
chrono = { version = "0.4", features = ["serde"] }
colored = "2.0"
anyhow = "1.0"
thiserror = "1.0"
use thiserror::Error;
#[derive(Error, Debug)]
pub enum TodoError {
#[error("任務 ID {id} 不存在")]
TaskNotFound { id: u32 },
#[error("任務標題不能為空")]
EmptyTitle,
#[error("無效的優先順序: {priority}")]
InvalidPriority { priority: String },
#[error("無效的日期格式: {date}")]
InvalidDate { date: String },
#[error("任務 '{title}' 已經存在")]
DuplicateTask { title: String },
#[error("任務已經是 {status} 狀態")]
TaskAlreadyInStatus { status: String },
#[error("IO 錯誤: {0}")]
Io(#[from] std::io::Error),
#[error("JSON 序列化錯誤: {0}")]
Json(#[from] serde_json::Error),
#[error("日期解析錯誤: {0}")]
DateParse(#[from] chrono::ParseError),
}
pub type Result<T> = std::result::Result<T, TodoError>;
use crate::error::{Result, TodoError};
use chrono::{DateTime, Local, NaiveDate};
use serde::{Deserialize, Serialize};
use std::fmt;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum TaskStatus {
Pending,
InProgress,
Completed,
Cancelled,
}
impl fmt::Display for TaskStatus {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
TaskStatus::Pending => write!(f, "待辦"),
TaskStatus::InProgress => write!(f, "進行中"),
TaskStatus::Completed => write!(f, "已完成"),
TaskStatus::Cancelled => write!(f, "已取消"),
}
}
}
impl TaskStatus {
pub fn from_str(s: &str) -> Result<Self> {
match s.to_lowercase().as_str() {
"pending" | "待辦" | "p" => Ok(TaskStatus::Pending),
"progress" | "進行中" | "i" => Ok(TaskStatus::InProgress),
"completed" | "已完成" | "done" | "c" => Ok(TaskStatus::Completed),
"cancelled" | "已取消" | "cancel" | "x" => Ok(TaskStatus::Cancelled),
_ => Err(TodoError::InvalidPriority {
priority: s.to_string()
}),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub enum Priority {
Low = 1,
Medium = 2,
High = 3,
Urgent = 4,
}
impl fmt::Display for Priority {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Priority::Low => write!(f, "低"),
Priority::Medium => write!(f, "中"),
Priority::High => write!(f, "高"),
Priority::Urgent => write!(f, "緊急"),
}
}
}
impl Priority {
pub fn from_str(s: &str) -> Result<Self> {
match s.to_lowercase().as_str() {
"low" | "l" | "1" | "低" => Ok(Priority::Low),
"medium" | "med" | "m" | "2" | "中" => Ok(Priority::Medium),
"high" | "h" | "3" | "高" => Ok(Priority::High),
"urgent" | "u" | "4" | "緊急" => Ok(Priority::Urgent),
_ => Err(TodoError::InvalidPriority {
priority: s.to_string()
}),
}
}
pub fn color(&self) -> &'static str {
match self {
Priority::Low => "green",
Priority::Medium => "yellow",
Priority::High => "red",
Priority::Urgent => "magenta",
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Task {
pub id: u32,
pub title: String,
pub description: Option<String>,
pub status: TaskStatus,
pub priority: Priority,
pub created_at: DateTime<Local>,
pub updated_at: DateTime<Local>,
pub due_date: Option<NaiveDate>,
pub tags: Vec<String>,
}
impl Task {
pub fn new(id: u32, title: String) -> Result<Self> {
if title.trim().is_empty() {
return Err(TodoError::EmptyTitle);
}
let now = Local::now();
Ok(Task {
id,
title: title.trim().to_string(),
description: None,
status: TaskStatus::Pending,
priority: Priority::Medium,
created_at: now,
updated_at: now,
due_date: None,
tags: Vec::new(),
})
}
pub fn with_description(mut self, description: String) -> Self {
self.description = Some(description);
self.updated_at = Local::now();
self
}
pub fn with_priority(mut self, priority: Priority) -> Self {
self.priority = priority;
self.updated_at = Local::now();
self
}
pub fn with_due_date(mut self, due_date: NaiveDate) -> Self {
self.due_date = Some(due_date);
self.updated_at = Local::now();
self
}
pub fn with_tags(mut self, tags: Vec<String>) -> Self {
self.tags = tags;
self.updated_at = Local::now();
self
}
pub fn set_status(&mut self, status: TaskStatus) -> Result<()> {
if self.status == status {
return Err(TodoError::TaskAlreadyInStatus {
status: status.to_string(),
});
}
self.status = status;
self.updated_at = Local::now();
Ok(())
}
pub fn set_priority(&mut self, priority: Priority) {
self.priority = priority;
self.updated_at = Local::now();
}
pub fn add_tag(&mut self, tag: String) {
if !self.tags.contains(&tag) {
self.tags.push(tag);
self.updated_at = Local::now();
}
}
pub fn remove_tag(&mut self, tag: &str) -> bool {
if let Some(pos) = self.tags.iter().position(|t| t == tag) {
self.tags.remove(pos);
self.updated_at = Local::now();
true
} else {
false
}
}
pub fn is_overdue(&self) -> bool {
if let Some(due_date) = self.due_date {
Local::now().date_naive() > due_date && self.status != TaskStatus::Completed
} else {
false
}
}
pub fn days_until_due(&self) -> Option<i64> {
self.due_date.map(|due_date| {
(due_date - Local::now().date_naive()).num_days()
})
}
pub fn matches_filter(&self, status: Option<&TaskStatus>, priority: Option<&Priority>, tag: Option<&str>) -> bool {
let status_matches = status.map_or(true, |s| &self.status == s);
let priority_matches = priority.map_or(true, |p| &self.priority == p);
let tag_matches = tag.map_or(true, |t| self.tags.iter().any(|tag| tag.contains(t)));
status_matches && priority_matches && tag_matches
}
}
impl fmt::Display for Task {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let status_symbol = match self.status {
TaskStatus::Pending => "○",
TaskStatus::InProgress => "◐",
TaskStatus::Completed => "●",
TaskStatus::Cancelled => "✗",
};
write!(f, "{} [{}] {}", status_symbol, self.id, self.title)?;
if let Some(due_date) = self.due_date {
let days_until = self.days_until_due().unwrap_or(0);
if days_until < 0 {
write!(f, " (逾期 {} 天)", -days_until)?;
} else if days_until <= 3 {
write!(f, " ({}天內到期)", days_until)?;
}
}
if !self.tags.is_empty() {
write!(f, " [{}]", self.tags.join(", "))?;
}
Ok(())
}
}
use crate::error::{Result, TodoError};
use crate::task::Task;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fs;
use std::path::{Path, PathBuf};
#[derive(Debug, Serialize, Deserialize)]
pub struct TodoData {
pub tasks: HashMap<u32, Task>,
pub next_id: u32,
}
impl TodoData {
pub fn new() -> Self {
TodoData {
tasks: HashMap::new(),
next_id: 1,
}
}
}
pub struct Storage {
file_path: PathBuf,
}
impl Storage {
pub fn new<P: AsRef<Path>>(file_path: P) -> Self {
Storage {
file_path: file_path.as_ref().to_path_buf(),
}
}
pub fn load(&self) -> Result<TodoData> {
if !self.file_path.exists() {
return Ok(TodoData::new());
}
let content = fs::read_to_string(&self.file_path)?;
if content.trim().is_empty() {
return Ok(TodoData::new());
}
let data: TodoData = serde_json::from_str(&content)?;
Ok(data)
}
pub fn save(&self, data: &TodoData) -> Result<()> {
// 確保父目錄存在
if let Some(parent) = self.file_path.parent() {
fs::create_dir_all(parent)?;
}
let content = serde_json::to_string_pretty(data)?;
fs::write(&self.file_path, content)?;
Ok(())
}
pub fn get_default_path() -> PathBuf {
dirs::home_dir()
.unwrap_or_else(|| PathBuf::from("."))
.join(".todo-cli")
.join("tasks.json")
}
}
pub struct TodoStore {
storage: Storage,
data: TodoData,
}
impl TodoStore {
pub fn new(storage_path: Option<PathBuf>) -> Result<Self> {
let storage = Storage::new(
storage_path.unwrap_or_else(Storage::get_default_path)
);
let data = storage.load()?;
Ok(TodoStore { storage, data })
}
pub fn add_task(&mut self, title: String) -> Result<u32> {
// 檢查是否有重複標題
for task in self.data.tasks.values() {
if task.title == title {
return Err(TodoError::DuplicateTask { title });
}
}
let task = Task::new(self.data.next_id, title)?;
let id = task.id;
self.data.tasks.insert(id, task);
self.data.next_id += 1;
self.save()?;
Ok(id)
}
pub fn get_task(&self, id: u32) -> Result<&Task> {
self.data.tasks.get(&id).ok_or(TodoError::TaskNotFound { id })
}
pub fn get_task_mut(&mut self, id: u32) -> Result<&mut Task> {
self.data.tasks.get_mut(&id).ok_or(TodoError::TaskNotFound { id })
}
pub fn update_task<F>(&mut self, id: u32, update_fn: F) -> Result<()>
where
F: FnOnce(&mut Task) -> Result<()>,
{
let task = self.get_task_mut(id)?;
update_fn(task)?;
self.save()?;
Ok(())
}
pub fn delete_task(&mut self, id: u32) -> Result<Task> {
let task = self.data.tasks.remove(&id).ok_or(TodoError::TaskNotFound { id })?;
self.save()?;
Ok(task)
}
pub fn list_tasks(&self) -> Vec<&Task> {
let mut tasks: Vec<_> = self.data.tasks.values().collect();
tasks.sort_by(|a, b| {
// 先按狀態排序(未完成的在前)
match (&a.status, &b.status) {
(crate::task::TaskStatus::Completed, crate::task::TaskStatus::Completed) |
(crate::task::TaskStatus::Cancelled, crate::task::TaskStatus::Cancelled) => {},
(crate::task::TaskStatus::Completed | crate::task::TaskStatus::Cancelled, _) => return std::cmp::Ordering::Greater,
(_, crate::task::TaskStatus::Completed | crate::task::TaskStatus::Cancelled) => return std::cmp::Ordering::Less,
_ => {},
}
// 再按優先順序排序(高優先順序在前)
b.priority.cmp(&a.priority)
.then_with(|| a.created_at.cmp(&b.created_at))
});
tasks
}
pub fn list_filtered_tasks(
&self,
status: Option<crate::task::TaskStatus>,
priority: Option<crate::task::Priority>,
tag: Option<String>,
) -> Vec<&Task> {
self.data.tasks.values()
.filter(|task| {
task.matches_filter(
status.as_ref(),
priority.as_ref(),
tag.as_deref(),
)
})
.collect()
}
pub fn get_statistics(&self) -> TaskStatistics {
let mut stats = TaskStatistics::default();
for task in self.data.tasks.values() {
stats.total += 1;
match task.status {
crate::task::TaskStatus::Pending => stats.pending += 1,
crate::task::TaskStatus::InProgress => stats.in_progress += 1,
crate::task::TaskStatus::Completed => stats.completed += 1,
crate::task::TaskStatus::Cancelled => stats.cancelled += 1,
}
if task.is_overdue() {
stats.overdue += 1;
}
}
stats
}
fn save(&self) -> Result<()> {
self.storage.save(&self.data)
}
}
#[derive(Debug, Default)]
pub struct TaskStatistics {
pub total: usize,
pub pending: usize,
pub in_progress: usize,
pub completed: usize,
pub cancelled: usize,
pub overdue: usize,
}
impl std::fmt::Display for TaskStatistics {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
writeln!(f, "任務統計:")?;
writeln!(f, " 總計: {}", self.total)?;
writeln!(f, " 待辦: {}", self.pending)?;
writeln!(f, " 進行中: {}", self.in_progress)?;
writeln!(f, " 已完成: {}", self.completed)?;
writeln!(f, " 已取消: {}", self.cancelled)?;
if self.overdue > 0 {
writeln!(f, " ⚠️ 逾期: {}", self.overdue)?;
}
Ok(())
}
}
// 添加 dirs crate 到 Cargo.toml
// [dependencies]
// dirs = "5.0"
use crate::task::{Priority, TaskStatus};
use chrono::NaiveDate;
use clap::{Parser, Subcommand};
#[derive(Parser)]
#[command(name = "todo-cli")]
#[command(about = "一個簡單而強大的待辦事項管理工具")]
#[command(version)]
pub struct Cli {
#[command(subcommand)]
pub command: Commands,
/// 指定資料檔案路徑
#[arg(long, global = true)]
pub file: Option<std::path::PathBuf>,
}
#[derive(Subcommand)]
pub enum Commands {
/// 新增任務
Add {
/// 任務標題
title: String,
/// 任務描述
#[arg(short, long)]
description: Option<String>,
/// 優先順序 (low, medium, high, urgent)
#[arg(short, long)]
priority: Option<String>,
/// 到期日 (YYYY-MM-DD)
#[arg(short = 'd', long)]
due: Option<String>,
/// 標籤 (可多次使用)
#[arg(short, long)]
tag: Vec<String>,
},
/// 列出任務
List {
/// 按狀態篩選
#[arg(short, long)]
status: Option<String>,
/// 按優先順序篩選
#[arg(short, long)]
priority: Option<String>,
/// 按標籤篩選
#[arg(short, long)]
tag: Option<String>,
/// 只顯示逾期任務
#[arg(long)]
overdue: bool,
},
/// 完成任務
Complete {
/// 任務 ID
id: u32,
},
/// 開始任務
Start {
/// 任務 ID
id: u32,
},
/// 取消任務
Cancel {
/// 任務 ID
id: u32,
},
/// 刪除任務
Delete {
/// 任務 ID
id: u32,
/// 強制刪除(不需確認)
#[arg(short, long)]
force: bool,
},
/// 編輯任務
Edit {
/// 任務 ID
id: u32,
/// 新標題
#[arg(short, long)]
title: Option<String>,
/// 新描述
#[arg(short = 'D', long)]
description: Option<String>,
/// 新優先順序
#[arg(short, long)]
priority: Option<String>,
/// 新到期日
#[arg(short = 'd', long)]
due: Option<String>,
},
/// 為任務添加標籤
Tag {
/// 任務 ID
id: u32,
/// 要添加的標籤
#[arg(short, long)]
add: Vec<String>,
/// 要移除的標籤
#[arg(short, long)]
remove: Vec<String>,
},
/// 顯示統計資訊
Stats,
/// 顯示任務詳情
Show {
/// 任務 ID
id: u32,
},
}
impl Commands {
pub fn parse_priority(priority_str: &str) -> crate::error::Result<Priority> {
Priority::from_str(priority_str)
}
pub fn parse_status(status_str: &str) -> crate::error::Result<TaskStatus> {
TaskStatus::from_str(status_str)
}
pub fn parse_date(date_str: &str) -> crate::error::Result<NaiveDate> {
NaiveDate::parse_from_str(date_str, "%Y-%m-%d")
.map_err(|_| crate::error::TodoError::InvalidDate {
date: date_str.to_string(),
})
}
}
pub mod cli;
pub mod error;
pub mod storage;
pub mod task;
use crate::cli::{Cli, Commands};
use crate::error::Result;
use crate::storage::TodoStore;
use crate::task::{Priority, TaskStatus};
use colored::*;
use std::io::{self, Write};
pub fn run(cli: Cli) -> Result<()> {
let mut store = TodoStore::new(cli.file)?;
match cli.command {
Commands::Add { title, description, priority, due, tag } => {
let id = store.add_task(title.clone())?;
// 設定可選屬性
if let Some(desc) = description {
store.update_task(id, |task| {
task.description = Some(desc);
Ok(())
})?;
}
if let Some(priority_str) = priority {
let priority = Commands::parse_priority(&priority_str)?;
store.update_task(id, |task| {
task.set_priority(priority);
Ok(())
})?;
}
if let Some(due_str) = due {
let due_date = Commands::parse_date(&due_str)?;
store.update_task(id, |task| {
task.due_date = Some(due_date);
Ok(())
})?;
}
for tag_name in tag {
store.update_task(id, |task| {
task.add_tag(tag_name);
Ok(())
})?;
}
println!("{} 任務已新增: {} (ID: {})",
"✓".green(), title.bold(), id.to_string().cyan());
},
Commands::List { status, priority, tag, overdue } => {
let status_filter = if let Some(s) = status {
Some(Commands::parse_status(&s)?)
} else {
None
};
let priority_filter = if let Some(p) = priority {
Some(Commands::parse_priority(&p)?)
} else {
None
};
let mut tasks = store.list_filtered_tasks(status_filter, priority_filter, tag);
if overdue {
tasks.retain(|task| task.is_overdue());
}
if tasks.is_empty() {
println!("{}", "沒有符合條件的任務".yellow());
return Ok(());
}
print_task_list(&tasks);
},
Commands::Complete { id } => {
store.update_task(id, |task| {
task.set_status(TaskStatus::Completed)
})?;
let task = store.get_task(id)?;
println!("{} 任務已完成: {}", "✓".green(), task.title.bold());
},
Commands::Start { id } => {
store.update_task(id, |task| {
task.set_status(TaskStatus::InProgress)
})?;
let task = store.get_task(id)?;
println!("{} 任務已開始: {}", "▶".blue(), task.title.bold());
},
Commands::Cancel { id } => {
store.update_task(id, |task| {
task.set_status(TaskStatus::Cancelled)
})?;
let task = store.get_task(id)?;
println!("{} 任務已取消: {}", "✗".red(), task.title.bold());
},
Commands::Delete { id, force } => {
let task = store.get_task(id)?;
if !force {
print!("確定要刪除任務 '{}' 嗎? (y/N): ", task.title);
io::stdout().flush().unwrap();
let mut input = String::new();
io::stdin().read_line(&mut input).unwrap();
if !input.trim().to_lowercase().starts_with('y') {
println!("取消刪除");
return Ok(());
}
}
let deleted_task = store.delete_task(id)?;
println!("{} 任務已刪除: {}", "🗑".yellow(), deleted_task.title.bold());
},
Commands::Edit { id, title, description, priority, due } => {
store.update_task(id, |task| {
if let Some(new_title) = title {
if new_title.trim().is_empty() {
return Err(crate::error::TodoError::EmptyTitle);
}
task.title = new_title.trim().to_string();
}
if let Some(new_desc) = description {
task.description = Some(new_desc);
}
if let Some(priority_str) = priority {
let new_priority = Commands::parse_priority(&priority_str)?;
task.set_priority(new_priority);
}
if let Some(due_str) = due {
let due_date = Commands::parse_date(&due_str)?;
task.due_date = Some(due_date);
}
Ok(())
})?;
let task = store.get_task(id)?;
println!("{} 任務已更新: {}", "✓".green(), task.title.bold());
},
Commands::Tag { id, add, remove } => {
store.update_task(id, |task| {
for tag in add {
task.add_tag(tag);
}
for tag in remove {
task.remove_tag(&tag);
}
Ok(())
})?;
let task = store.get_task(id)?;
println!("{} 標籤已更新: {}", "🏷".yellow(), task.title.bold());
},
Commands::Stats => {
let stats = store.get_statistics();
println!("{}", stats);
},
Commands::Show { id } => {
let task = store.get_task(id)?;
print_task_detail(task);
},
}
Ok(())
}
fn print_task_list(tasks: &[&crate::task::Task]) {
println!("{} 任務列表 {}", "=".repeat(10), "=".repeat(10));
for task in tasks {
let status_color = match task.status {
TaskStatus::Pending => "white",
TaskStatus::InProgress => "blue",
TaskStatus::Completed => "green",
TaskStatus::Cancelled => "red",
};
let priority_indicator = match task.priority {
Priority::Low => "●".green(),
Priority::Medium => "●".yellow(),
Priority::High => "●".red(),
Priority::Urgent => "●".purple(),
};
print!("{} ", priority_indicator);
if task.is_overdue() {
print!("{} ", "⚠️ ".red());
}
println!("{}", task.to_string().color(status_color));
}
}
fn print_task_detail(task: &crate::task::Task) {
println!("{}", "任務詳情".bold().underline());
println!("ID: {}", task.id.to_string().cyan());
println!("標題: {}", task.title.bold());
if let Some(desc) = &task.description {
println!("描述: {}", desc);
}
let status_color = match task.status {
TaskStatus::Pending => "white",
TaskStatus::InProgress => "blue",
TaskStatus::Completed => "green",
TaskStatus::Cancelled => "red",
};
println!("狀態: {}", task.status.to_string().color(status_color));
println!("優先順序: {}", task.priority.to_string().color(task.priority.color()));
if let Some(due_date) = task.due_date {
let days_until = task.days_until_due().unwrap_or(0);
if task.is_overdue() {
println!("到期日: {} {}", due_date.to_string().red(), format!("(逾期 {} 天)", -days_until).red());
} else if days_until <= 3 {
println!("到期日: {} {}", due_date.to_string().yellow(), format!("({}天內到期)", days_until).yellow());
} else {
println!("到期日: {}", due_date);
}
}
if !task.tags.is_empty() {
println!("標籤: {}", task.tags.join(", ").cyan());
}
println!("建立時間: {}", task.created_at.format("%Y-%m-%d %H:%M:%S"));
println!("更新時間: {}", task.updated_at.format("%Y-%m-%d %H:%M:%S"));
}
use clap::Parser;
use todo_cli::cli::Cli;
use todo_cli::run;
fn main() {
let cli = Cli::parse();
if let Err(e) = run(cli) {
eprintln!("錯誤: {}", e);
std::process::exit(1);
}
}
[package]
name = "todo-cli"
version = "0.1.0"
edition = "2021"
[[bin]]
name = "todo-cli"
path = "src/main.rs"
[dependencies]
clap = { version = "4.0", features = ["derive"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
chrono = { version = "0.4", features = ["serde"] }
colored = "2.0"
anyhow = "1.0"
thiserror = "1.0"
dirs = "5.0"
建置專案:
cd day14
cargo build --release
測試基本功能:
# 新增任務
./target/release/todo-cli add "學習 Rust" --priority high --due "2024-01-15"
# 列出所有任務
./target/release/todo-cli list
# 開始任務
./target/release/todo-cli start 1
# 完成任務
./target/release/todo-cli complete 1
# 顯示統計
./target/release/todo-cli stats
// 在 Commands 枚舉中添加
Export {
/// 匯出檔案路徑
#[arg(short, long)]
output: std::path::PathBuf,
/// 匯出格式 (json, csv)
#[arg(short, long, default_value = "json")]
format: String,
},
Import {
/// 匯入檔案路徑
#[arg(short, long)]
input: std::path::PathBuf,
/// 匯入格式 (json, csv)
#[arg(short, long, default_value = "json")]
format: String,
},
Search {
/// 搜尋關鍵字
query: String,
/// 在標題中搜尋
#[arg(long)]
title: bool,
/// 在描述中搜尋
#[arg(long)]
description: bool,
/// 在標籤中搜尋
#[arg(long)]
tags: bool,
},
# 基本用法
todo-cli add "完成 Rust 專案" --priority urgent --due "2024-01-20"
todo-cli add "買菜" --tag 生活 --tag 購物
todo-cli add "讀書" --description "閱讀 Rust 程式設計語言" --priority medium
# 列出不同狀態的任務
todo-cli list --status pending
todo-cli list --priority high
todo-cli list --overdue
# 管理任務
todo-cli start 1
todo-cli complete 1
todo-cli edit 2 --title "買菜和做飯" --priority high
# 標籤管理
todo-cli tag 2 --add 緊急 --remove 購物
# 查看詳情和統計
todo-cli show 1
todo-cli stats
# 刪除任務
todo-cli delete 3 --force
為 CLI 工具添加以下功能:
// 1. 添加任務模板功能
todo-cli template create "每日站會" --description "參加團隊每日站會" --priority medium --tag 工作
// 2. 添加任務重複功能
todo-cli repeat 1 --interval daily --count 7
// 3. 添加任務依賴關係
todo-cli depend 2 --requires 1 // 任務2依賴任務1完成
實作以下分析功能:
// 1. 生產力報告
todo-cli report --period week // 週報
todo-cli report --period month // 月報
// 2. 時間追蹤
// 為任務添加時間追蹤功能
todo-cli time start 1 // 開始計時
todo-cli time stop 1 // 停止計時
todo-cli time report // 時間報告
// 3. 任務建議
todo-cli suggest // 根據歷史數據建議下一個任務
// 1. 與日曆整合
todo-cli calendar sync // 同步到系統日曆
// 2. 提醒功能
todo-cli remind 1 --before "1 hour" // 任務提醒
// 3. 團隊協作
todo-cli share 1 --user "teammate@example.com" // 分享任務
todo-cli sync --server "http://todo-server.com" // 同步到服務器
第三部分將更加注重實用性和效能,我們會學習如何高效地處理和轉換資料,並建立更複雜的應用程式!