A CLI (Command-Line Interface) To-Do List App is a software application that allows users to manage their daily tasks directly from the terminal or command prompt. Unlike graphical user interface (GUI) applications, all interactions occur via text commands, making it fast, efficient, and often scriptable. This type of application is popular among developers, system administrators, and anyone who prefers a keyboard-driven workflow.
Key functionalities typically found in a CLI To-Do List App include:
1. Adding Tasks: Users can add new tasks with a descriptive text.
2. Listing Tasks: Displays all current tasks, usually with an identifier, their description, and their completion status.
3. Completing Tasks: Allows users to mark an existing task as finished.
4. Deleting Tasks: Provides a way to remove tasks from the list.
5. Persistence: Tasks are saved to a file (e.g., a simple text file, JSON, CSV, or a database) so they are retained even after the application closes and reopened.
Implementing a CLI To-Do List App in Rust involves several core components:
* Command-Line Argument Parsing: Interpreting the commands and arguments provided by the user (e.g., `todo add "Buy groceries"`, `todo list`). Rust's `std::env::args` can be used for basic parsing, or more robust crates like `clap` can handle complex argument structures.
* Data Structures: Defining a struct (e.g., `Task`) to represent each individual to-do item, typically including fields like `id` (a unique identifier), `description` (the task's text), and `completed` (a boolean indicating its status).
* File I/O: Using Rust's `std::fs` module to read tasks from and write tasks to a persistent storage file. A common approach for simplicity is to store tasks line-by-line or in a simple CSV-like format.
* Application Logic: Implementing functions that encapsulate the core operations: `add_task`, `list_tasks`, `complete_task`, and `delete_task`. These functions manipulate a collection of `Task` objects and handle file operations.
* Error Handling: Gracefully managing potential issues such as invalid command syntax, non-existent task IDs, or file access errors.
The example code provided will demonstrate a basic CLI To-Do List App using `std::env::args` for argument parsing and a simple line-separated text file for task persistence, where each line represents a task.
Example Code
```rust
use std::env;
use std::fs;
use std::io::{self, Write};
const TASKS_FILE: &str = "tasks.txt";
#[derive(Debug, Clone)]
struct Task {
id: u32,
description: String,
completed: bool,
}
impl Task {
fn new(id: u32, description: String) -> Self {
Task { id, description, completed: false }
}
fn to_string_for_file(&self) -> String {
format!("{} {} {}", self.id, self.description, self.completed)
}
fn from_string_for_file(line: &str) -> Option<Self> {
let parts: Vec<&str> = line.split('\t').collect();
if parts.len() == 3 {
if let (Ok(id), Ok(completed)) = (parts[0].parse::<u32>(), parts[2].parse::<bool>()) {
return Some(Task {
id,
description: parts[1].to_string(),
completed,
});
}
}
None
}
}
// Loads tasks from the file
fn load_tasks() -> io::Result<Vec<Task>> {
if !fs::metadata(TASKS_FILE).is_ok() {
return Ok(Vec::new()); // File does not exist, return empty list
}
let contents = fs::read_to_string(TASKS_FILE)?;
let tasks: Vec<Task> = contents
.lines()
.filter_map(Task::from_string_for_file)
.collect();
Ok(tasks)
}
// Saves tasks to the file
fn save_tasks(tasks: &[Task]) -> io::Result<()> {
let contents: Vec<String> = tasks.iter().map(|t| t.to_string_for_file()).collect();
fs::write(TASKS_FILE, contents.join("\n"))?
Ok(())
}
// Adds a new task
fn add_task(tasks: &mut Vec<Task>, description: String) {
let next_id = tasks.iter().map(|t| t.id).max().unwrap_or(0) + 1;
let new_task = Task::new(next_id, description);
tasks.push(new_task);
println!("Task added: #{}", next_id);
}
// Lists all tasks
fn list_tasks(tasks: &[Task]) {
if tasks.is_empty() {
println!("No tasks found.");
return;
}
println!("--- Your To-Do List ---");
for task in tasks {
let status = if task.completed { "[✓]" } else { "[ ]" };
println!("{} #{}: {}", status, task.id, task.description);
}
println!("-----------------------");
}
// Marks a task as complete
fn complete_task(tasks: &mut Vec<Task>, task_id: u32) {
if let Some(task) = tasks.iter_mut().find(|t| t.id == task_id) {
if !task.completed {
task.completed = true;
println!("Task #{}: '{}' marked as complete.", task_id, task.description);
} else {
println!("Task #{}: '{}' was already complete.", task_id, task.description);
}
} else {
eprintln!("Error: Task with ID {} not found.", task_id);
}
}
// Deletes a task
fn delete_task(tasks: &mut Vec<Task>, task_id: u32) {
let initial_len = tasks.len();
tasks.retain(|t| t.id != task_id);
if tasks.len() < initial_len {
println!("Task #{} deleted.", task_id);
} else {
eprintln!("Error: Task with ID {} not found.", task_id);
}
}
fn main() -> io::Result<()> {
let args: Vec<String> = env::args().collect();
if args.len() < 2 {
eprintln!("Usage:\n todo list\n todo add <description>\n todo complete <id>\n todo delete <id>");
return Ok(());
}
let mut tasks = load_tasks()?;
let command = &args[1];
match command.as_str() {
"list" => list_tasks(&tasks),
"add" => {
if args.len() < 3 {
eprintln!("Usage: todo add <description>");
} else {
let description = args[2..].join(" "); // Join all parts of description
add_task(&mut tasks, description);
}
},
"complete" => {
if args.len() != 3 {
eprintln!("Usage: todo complete <id>");
} else if let Ok(id) = args[2].parse::<u32>() {
complete_task(&mut tasks, id);
} else {
eprintln!("Error: Invalid ID provided for complete command.");
}
},
"delete" => {
if args.len() != 3 {
eprintln!("Usage: todo delete <id>");
} else if let Ok(id) = args[2].parse::<u32>() {
delete_task(&mut tasks, id);
} else {
eprintln!("Error: Invalid ID provided for delete command.");
}
},
_ => eprintln!("Unknown command: {}\nUsage:\n todo list\n todo add <description>\n todo complete <id>\n todo delete <id>", command),
}
save_tasks(&tasks)?; // Save changes after command execution
Ok(())
}
```
To run this code:
1. Save the code as `main.rs`.
2. Open your terminal or command prompt.
3. Navigate to the directory where you saved `main.rs`.
4. Compile the Rust code: `rustc main.rs`
5. Run the executable (on Linux/macOS: `./main`, on Windows: `.\main.exe` or `main`)
Example Usage:
* `./main add "Buy groceries for dinner"`
* `./main add "Call mom about weekend plans"`
* `./main list`
* `./main complete 1` (assuming "Buy groceries for dinner" got ID 1)
* `./main list`
* `./main delete 2` (assuming "Call mom about weekend plans" got ID 2)
* `./main list`








CLI To-Do List App