aboutsummaryrefslogtreecommitdiff
path: root/mingling
diff options
context:
space:
mode:
author魏曹先生 <1992414357@qq.com>2026-05-19 22:48:23 +0800
committer魏曹先生 <1992414357@qq.com>2026-05-19 22:48:23 +0800
commit39d66182f0290bacc10886c2659874bd9edc2d4b (patch)
tree7b0e9463575a8350cd5a61bd0d02c9bada892b7f /mingling
parentbd4b09b06181093c95e865b04d4a9cdda7dd0728 (diff)
Add `empty_result!` macro and `REPL` resource, improve examples
Diffstat (limited to 'mingling')
-rw-r--r--mingling/src/example_docs.rs173
-rw-r--r--mingling/src/lib.rs4
2 files changed, 170 insertions, 7 deletions
diff --git a/mingling/src/example_docs.rs b/mingling/src/example_docs.rs
index 806553f..ab725de 100644
--- a/mingling/src/example_docs.rs
+++ b/mingling/src/example_docs.rs
@@ -294,7 +294,7 @@ pub mod example_completion {}
///
/// ```bash
/// cargo expand --manifest-path examples/example-dispatch-tree/Cargo.toml > expanded.rs
-/// cat expanded.rs | grep dispatch_args_trie -A 264
+/// cat expanded.rs
/// ```
///
/// Cargo.toml
@@ -317,11 +317,10 @@ pub mod example_completion {}
/// fn main() {
/// let mut program = ThisProgram::new();
///
-/// // After enabling `dispatch_tree`, this method will no longer exist
+/// // // After enabling `dispatch_tree`, this method will no longer exist
/// // program.with_dispatcher(CommandGreet);
/// //
-/// // The `CompletionDispatcher` automatically generated by `comp` will also be imported
-/// // automatically
+/// // // The `CompletionDispatcher` automatically generated by `comp` will also be imported automatically
/// // program.with_dispatcher(CompletionDispatcher);
///
/// program.exec();
@@ -370,7 +369,7 @@ pub mod example_dispatch_tree {}
/// ```ignore
/// use mingling::prelude::*;
/// use mingling::{
-/// res::{exit_code, update_exit_code},
+/// res::{ExitCode, exit_code},
/// setup::ExitCodeSetup,
/// };
///
@@ -385,8 +384,8 @@ pub mod example_dispatch_tree {}
/// pack!(ResultError = ());
///
/// #[chain]
-/// fn handle_error_entry(_prev: ErrorEntry) -> Next {
-/// update_exit_code::<ThisProgram>(1);
+/// fn handle_error_entry(_prev: ErrorEntry, ec: &mut ExitCode) -> Next {
+/// ec.exit_code = 1;
/// return ResultError::default();
/// }
///
@@ -569,6 +568,166 @@ pub mod example_general_renderer {}
/// gen_program!();
/// ```
pub mod example_picker {}
+
+///
+/// Cargo.toml
+/// ```ignore
+/// [package]
+/// name = "example-repl"
+/// version = "0.0.1"
+/// edition = "2024"
+///
+/// [dependencies]
+/// mingling = { path = "../../mingling", features = ["repl", "parser"] }
+/// just_fmt = "0.1.2"
+/// ```
+///
+/// main.rs
+/// ```ignore
+/// use mingling::{REPL, hook::ProgramHook, prelude::*, this};
+/// use std::{env::current_dir, path::PathBuf};
+///
+/// // Resource to store the current directory
+/// #[derive(Clone)]
+/// struct CurrentDir {
+/// dir: PathBuf,
+/// }
+///
+/// impl Default for CurrentDir {
+/// fn default() -> Self {
+/// Self {
+/// dir: current_dir().unwrap(),
+/// }
+/// }
+/// }
+///
+/// fn main() {
+/// let mut program = ThisProgram::new();
+///
+/// // Add resource
+/// program.with_resource(CurrentDir::default());
+///
+/// // Add dispatchers
+/// program.with_dispatcher(ChangeDirectoryCommand);
+/// program.with_dispatcher(ListCommand);
+/// program.with_dispatcher(ExitCommand);
+///
+/// // Add hooks to handle REPL-related events
+/// program.with_hook(
+/// ProgramHook::empty()
+/// .on_repl_begin(|| {
+/// // Print welcome message
+/// println!("Welcome!")
+/// })
+/// .on_repl_pre_readline(|| {
+/// // Print prompt
+/// let res = this::<ThisProgram>().res::<CurrentDir>().unwrap();
+/// let dir_str: String = res.dir.to_string_lossy().into();
+/// let prompt = format!(
+/// "{}> ",
+/// dir_str
+/// .replace(&['/', '\\'][..], ">")
+/// .trim_start_matches('>')
+/// .trim_end_matches('>')
+/// );
+/// print!("{}", prompt)
+/// })
+/// .on_repl_receive_result(|r| {
+/// // Print output
+/// if !r.is_empty() {
+/// println!("{}", r.trim())
+/// }
+/// }),
+/// );
+///
+/// // Start the REPL loop
+/// program.exec_repl();
+/// }
+///
+/// // Create error route
+/// pack!(ErrorDirectoryNotExist = PathBuf);
+///
+/// // Create commands: cd ls exit
+/// dispatcher!("cd", ChangeDirectoryCommand => ChangeDirectoryEntry);
+/// dispatcher!("ls", ListCommand => ListEntry);
+/// dispatcher!("exit", ExitCommand => ExitEntry);
+///
+/// // Define data needed for the cd command's execution phase
+/// pack!(StateChangeDirectory = String);
+///
+/// // Define data needed for the ls command's rendering phase
+/// pack!(ResultList = Vec<String>);
+///
+/// // Parse cd command arguments
+/// #[chain]
+/// fn parse_cd_args(prev: ChangeDirectoryEntry) -> Next {
+/// let join = prev.pick(()).unpack();
+/// StateChangeDirectory::new(join)
+/// }
+///
+/// // Execute directory change
+/// #[chain]
+/// fn handle_cd(prev: StateChangeDirectory, current_dir: &mut CurrentDir) -> Next {
+/// let join = prev.inner;
+/// let new_dir = just_fmt::fmt_path::fmt_path(current_dir.dir.join(join)).unwrap_or_default();
+///
+/// // If the path is not found, route to error handling
+/// if !new_dir.exists() {
+/// return ErrorDirectoryNotExist::new(new_dir).to_render();
+/// }
+///
+/// current_dir.dir = new_dir;
+/// empty_result!()
+/// }
+///
+/// // Get directory contents via the CurrentDir resource
+/// #[chain]
+/// fn handle_ls(_prev: ListEntry, current_dir: &CurrentDir) -> Next {
+/// let dir = &current_dir.dir;
+/// let entries: Vec<String> = std::fs::read_dir(dir)
+/// .into_iter()
+/// .flat_map(|rd| rd.filter_map(|e| e.ok()))
+/// .map(|e| {
+/// let name = e.file_name().to_string_lossy().to_string();
+/// if e.file_type().map(|t| t.is_dir()).unwrap_or(false) {
+/// format!("{}/", name)
+/// } else {
+/// name
+/// }
+/// })
+/// .collect();
+///
+/// // Render ResultList
+/// ResultList::new(entries).to_render()
+/// }
+///
+/// // Render ResultList data
+/// #[renderer]
+/// fn render_list(list: ResultList) {
+/// for item in list.inner {
+/// r_println!("{}", item)
+/// }
+/// }
+///
+/// // Handle exit command event
+/// #[chain]
+/// fn handle_exit(
+/// _prev: ExitEntry,
+/// repl: &mut REPL, // Import REPL resource, registered in `exec_repl`, usable directly
+/// ) {
+/// // Set the REPL exit flag; REPL will exit after this loop iteration
+/// repl.exit = true;
+/// }
+///
+/// // Handle path not found event
+/// #[renderer]
+/// fn render_error_directory_not_exist(err: ErrorDirectoryNotExist) {
+/// r_println!("Directory not found: {}", err.inner.display())
+/// }
+///
+/// gen_program!();
+/// ```
+pub mod example_repl {}
/// `Mingling` Example - Global Resource Injection
///
/// This example demonstrates how to use global resource injection in `#[chain]` functions.
diff --git a/mingling/src/lib.rs b/mingling/src/lib.rs
index 4803c23..a8579d4 100644
--- a/mingling/src/lib.rs
+++ b/mingling/src/lib.rs
@@ -83,6 +83,8 @@ pub mod macros {
/// Used to create a dispatcher with clap argument parsing
#[cfg(feature = "clap")]
pub use mingling_macros::dispatcher_clap;
+ /// Used to create an empty result value for early return from a chain function
+ pub use mingling_macros::empty_result;
/// Used to collect data and create a command-line context
pub use mingling_macros::gen_program;
/// Used to generate a struct implementing the `HelpRequest` trait via a method
@@ -211,6 +213,8 @@ pub mod prelude {
pub use crate::macros::chain;
/// Re-export of the `dispatcher` macro for routing commands.
pub use crate::macros::dispatcher;
+ /// Re-export of the `empty_result` macro for creating an empty result value for early return.
+ pub use crate::macros::empty_result;
/// Re-export of the `gen_program` macro for generating the program entry point.
pub use crate::macros::gen_program;
/// Re-export of the `pack` macro for creating wrapper types.