diff options
Diffstat (limited to 'examples')
| -rw-r--r-- | examples/full-todolist/.gitignore | 1 | ||||
| -rw-r--r-- | examples/full-todolist/Cargo.lock | 149 | ||||
| -rw-r--r-- | examples/full-todolist/Cargo.toml | 16 | ||||
| -rw-r--r-- | examples/full-todolist/page.toml | 10 | ||||
| -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 |
7 files changed, 532 insertions, 0 deletions
diff --git a/examples/full-todolist/.gitignore b/examples/full-todolist/.gitignore new file mode 100644 index 0000000..5af20de --- /dev/null +++ b/examples/full-todolist/.gitignore @@ -0,0 +1 @@ +.todo.json diff --git a/examples/full-todolist/Cargo.lock b/examples/full-todolist/Cargo.lock new file mode 100644 index 0000000..7a1ce38 --- /dev/null +++ b/examples/full-todolist/Cargo.lock @@ -0,0 +1,149 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "full-todolist" +version = "0.1.0" +dependencies = [ + "mingling", + "serde", + "serde_json", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "just_fmt" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5454cda0d57db59778608d7a47bff5b16c6705598265869fb052b657f66cf05e" + +[[package]] +name = "memchr" +version = "2.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8" + +[[package]] +name = "mingling" +version = "0.2.0" +dependencies = [ + "mingling_core", + "mingling_macros", + "serde", + "size", +] + +[[package]] +name = "mingling_core" +version = "0.2.0" +dependencies = [ + "just_fmt", + "serde", + "serde_json", +] + +[[package]] +name = "mingling_macros" +version = "0.2.0" +dependencies = [ + "just_fmt", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.150" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "size" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b6709c7b6754dca1311b3c73e79fcce40dd414c782c66d88e8823030093b02b" + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/examples/full-todolist/Cargo.toml b/examples/full-todolist/Cargo.toml new file mode 100644 index 0000000..e55d5e9 --- /dev/null +++ b/examples/full-todolist/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "full-todolist" +version = "0.1.0" +edition = "2024" + +[dependencies] +serde = { version = "1.0.228", features = ["derive"] } +serde_json = "1.0.150" + +[dependencies.mingling] +path = "../../mingling" +features = [ + "parser", + "extra_macros", + "general_renderer", +] diff --git a/examples/full-todolist/page.toml b/examples/full-todolist/page.toml new file mode 100644 index 0000000..9bdc7f1 --- /dev/null +++ b/examples/full-todolist/page.toml @@ -0,0 +1,10 @@ +[example] +id = "full-todolist" +name = "Todo List" +icon = "📝" +category = "full" +desc = """ +This is a complete example project that demonstrates how to develop a todo list application using Mingling +""" +tags = ["todolist", "CRUD"] +files = ["src/main.rs", "src/todolist.rs", "src/help.rs", "Cargo.toml"] 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(); +} |
