diff options
| author | Weicao-CatilGrass <1992414357@qq.com> | 2026-06-09 06:34:55 +0800 |
|---|---|---|
| committer | Weicao-CatilGrass <1992414357@qq.com> | 2026-06-09 06:34:55 +0800 |
| commit | ab796e81ab4e3e9fa1a26f8217635eebec658b5e (patch) | |
| tree | bbaf957bd9f952e93b767f5d1228d3c99d450a0a /examples/full-todolist/src | |
| parent | 78f282007980fe9c9ef143a6bc6fb76282957ab6 (diff) | |
Add full-todolist example project
Diffstat (limited to 'examples/full-todolist/src')
| -rw-r--r-- | examples/full-todolist/src/help.rs | 93 | ||||
| -rw-r--r-- | examples/full-todolist/src/main.rs | 197 | ||||
| -rw-r--r-- | examples/full-todolist/src/todolist.rs | 66 |
3 files changed, 356 insertions, 0 deletions
diff --git a/examples/full-todolist/src/help.rs b/examples/full-todolist/src/help.rs new file mode 100644 index 0000000..ab33176 --- /dev/null +++ b/examples/full-todolist/src/help.rs @@ -0,0 +1,93 @@ +//! This module provides help information for the `todolist` command line program + +use mingling::macros::{help, r_println}; + +use crate::{EntryAdd, EntryClean, EntryComplete, EntryList, ErrorDispatcherNotFound}; + +#[help] +pub fn help_global(_p: ErrorDispatcherNotFound) { + r_println!( + "{}", + r" +Usage: todolist [command] [args] + +Commands: + add -- Add a new task + list -- List all tasks + complete -- Mark a task as complete + clean -- Clean up completed tasks + +Args: + -h, --help -- Show this help message + -V, --version -- Show the version + -A, --all -- All tasks (Clean all / List all) + " + .trim() + ); +} + +#[help] +pub fn help_add(_p: EntryAdd) { + r_println!( + "{}", + r" +Usage: todolist add [task description] + +Add a new task to the todo list. + +Example: + todolist add 'Buy groceries' + todolist add 'Finish Rust project' + " + .trim() + ); +} + +#[help] +pub fn help_list(_p: EntryList) { + r_println!( + "{}", + r" +Usage: todolist list + +List all tasks. + +Example: + todolist list + " + .trim() + ); +} + +#[help] +pub fn help_complete(_p: EntryComplete) { + r_println!( + "{}", + r" +Usage: todolist complete [task_id] + +Mark a task as complete by its ID. + +Example: + todolist complete 1 + todolist complete 3 + " + .trim() + ); +} + +#[help] +pub fn help_clean(_p: EntryClean) { + r_println!( + "{}", + r" +Usage: todolist clean + +Remove all completed tasks from the list. + +Example: + todolist clean + " + .trim() + ); +} diff --git a/examples/full-todolist/src/main.rs b/examples/full-todolist/src/main.rs new file mode 100644 index 0000000..fdac1c7 --- /dev/null +++ b/examples/full-todolist/src/main.rs @@ -0,0 +1,197 @@ +//! Full Example - Todo List +//! +//! This example introduces how to use Mingling's features to build a complete CLI program +//! +//! > HAHA: +//! > +//! > This is truly a cliché example, as common as `Hello World`! + +use mingling::{ + macros::route, + prelude::*, + res::ResExitCode, + setup::{ExitCodeSetup, GeneralRendererSetup, HelpFlagSetup}, + LazyInit, LazyRes, +}; + +mod help; +pub use help::*; + +mod todolist; +pub use todolist::*; + +#[derive(Default, Clone)] +pub struct ResProgramFlags { + pub all: bool, +} + +// Define dispatchers + +dispatcher!("add"); +dispatcher!("list"); +dispatcher!("complete"); +dispatcher!("clean"); + +// Define states + +pack!(StateAddTodo = String); +pack!(StateCompleteTodo = i32); +pack!(StateListTodo = bool); + +// Define errors + +pack!(ErrorNoTaskDescriptionProvided = ()); +pack!(ErrorNoIndexProvided = ()); +pack!(ErrorIndexOutOfBounds = ()); + +fn main() { + let mut program = ThisProgram::new(); + + // Setups + program.with_setup(ExitCodeSetup::default()); + program.with_setup(GeneralRendererSetup); + program.with_setup(HelpFlagSetup::new(["--help", "-h"])); + + // Flags + let all = program.pick_global_flag(["-A", "--all"]); + + // Resources + program.with_resource( + // Load on use + ResTodoList::lazy_init(read_todo_list) + // Write on drop + .with_on_drop(write_todo_list), + ); + program.with_resource(ResProgramFlags { all }); + + // Dispatchers + program.with_dispatcher(CMDAdd); + program.with_dispatcher(CMDComplete); + program.with_dispatcher(CMDList); + program.with_dispatcher(CMDClean); + + program.exec_and_exit(); +} + +// Command `add` + +#[chain] +fn handle_add(args: EntryAdd) -> Next { + let task: String = route! { + args + .pick_or_route((), ErrorNoTaskDescriptionProvided::new(()).to_render()) + .unpack() + }; + StateAddTodo::new(task).to_chain() +} + +#[chain] +fn handle_state_add_todo( + state: StateAddTodo, + // Inject ResTodoList resource + todolist: &mut LazyRes<ResTodoList>, +) -> Next { + let todolist = todolist.get_mut(); + + // Unpack state and read description + let description = state.inner; + + todolist.items.push(Todo { + item: description, + completed: false, + }); + + // Clone todolist to render + let todolist = todolist.clone(); + todolist.to_render() +} + +// Command `list` + +#[chain] +fn handle_list(_args: EntryList, todolist: &mut LazyRes<ResTodoList>) -> Next { + let todolist = todolist.get_mut().clone(); + todolist.to_render() +} + +// Command `complete` + +#[chain] +fn handle_complete(args: EntryComplete) -> Next { + let index: i32 = route! { + args.pick_or_route((), ErrorNoIndexProvided::new(()).to_render()).unpack() + }; + StateCompleteTodo::new(index).to_chain() +} + +#[chain] +fn handle_state_complete_todo( + state: StateCompleteTodo, + todolist: &mut LazyRes<ResTodoList>, +) -> Next { + let todolist = todolist.get_mut(); + let index = state.inner as usize; + if index < todolist.items.len() { + todolist.items[index].completed = true; + todolist.clone().to_render() + } else { + ErrorIndexOutOfBounds::new(()).to_render() + } +} + +// Command `clean` + +#[chain] +fn handle_clean( + _args: EntryClean, + todolist: &mut LazyRes<ResTodoList>, + program_flags: &ResProgramFlags, +) -> Next { + let todolist = todolist.get_mut(); + if program_flags.all { + todolist.items.clear(); + } else { + todolist.items.retain(|item| !item.completed); + } + if todolist.items.is_empty() { + empty_result!() + } else { + todolist.clone().to_render() + } +} + +#[renderer] +fn render_error_no_task_description_provided( + _err: ErrorNoTaskDescriptionProvided, + // ExitCode + ec: &mut ResExitCode, +) { + r_println!("No task description provided!"); + r_println!(""); + r_println!("Use `todolist add <desc>` to add a task"); + ec.exit_code = 1; +} + +#[renderer] +fn render_error_no_index_provided( + _err: ErrorNoIndexProvided, + // ExitCode + ec: &mut ResExitCode, +) { + r_println!("No index provided!"); + r_println!(""); + r_println!("Use `todolist list` to query indexes"); + ec.exit_code = 2; +} + +#[renderer] +fn render_error_index_out_of_bounds( + _err: ErrorIndexOutOfBounds, + // ExitCode + ec: &mut ResExitCode, +) { + r_println!("Index out of bounds!"); + ec.exit_code = 3; +} + +gen_program!(); diff --git a/examples/full-todolist/src/todolist.rs b/examples/full-todolist/src/todolist.rs new file mode 100644 index 0000000..e447f5d --- /dev/null +++ b/examples/full-todolist/src/todolist.rs @@ -0,0 +1,66 @@ +//! Data structures, read and write logic for the todo list + +use mingling::{ + macros::{r_println, renderer}, + Groupped, +}; +use serde::{Deserialize, Serialize}; +use std::{env::current_dir, path::PathBuf}; + +use crate::ResProgramFlags; + +#[derive(Debug, Serialize, Deserialize, Clone, Default, Groupped)] +pub struct ResTodoList { + pub items: Vec<Todo>, +} + +#[derive(Debug, Serialize, Deserialize, Clone, Default)] +pub struct Todo { + pub item: String, + pub completed: bool, +} + +fn get_todo_list_path() -> PathBuf { + current_dir().unwrap().join(".todo.json") +} + +pub fn read_todo_list() -> ResTodoList { + let path = get_todo_list_path(); + if !path.exists() { + return ResTodoList::default(); + } + let file = match std::fs::File::open(&path) { + Ok(f) => f, + Err(_) => return ResTodoList::default(), + }; + let reader = std::io::BufReader::new(file); + serde_json::from_reader(reader).unwrap_or_default() +} + +#[renderer] +pub fn render_res_todo_list(todo_list: ResTodoList, program_flags: &ResProgramFlags) { + let mut idx = 0; + r_println!("TODO: "); + for item in &todo_list.items { + if item.completed && !program_flags.all { + idx += 1; + continue; + } + r_println!( + " {idx}. [{}] - \"{}\"", + if item.completed { "x" } else { " " }, + item.item + ); + idx += 1; + } +} + +pub fn write_todo_list(todo_list: ResTodoList) { + let path = get_todo_list_path(); + if let Some(parent) = path.parent() { + std::fs::create_dir_all(parent).unwrap(); + } + let file = std::fs::File::create(path).unwrap(); + let writer = std::io::BufWriter::new(file); + serde_json::to_writer(writer, &todo_list).unwrap(); +} |
