aboutsummaryrefslogtreecommitdiff
path: root/examples/full-todolist/src
diff options
context:
space:
mode:
Diffstat (limited to 'examples/full-todolist/src')
-rw-r--r--examples/full-todolist/src/help.rs93
-rw-r--r--examples/full-todolist/src/main.rs197
-rw-r--r--examples/full-todolist/src/todolist.rs66
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();
+}