aboutsummaryrefslogtreecommitdiff
path: root/examples/example-repl-basic/src
diff options
context:
space:
mode:
authorWeicao-CatilGrass <1992414357@qq.com>2026-05-23 23:41:04 +0800
committerWeicao-CatilGrass <1992414357@qq.com>2026-05-23 23:49:34 +0800
commit0a2ef958c0dca21d19e4ffc38ba5a7c4078e182a (patch)
treec82fc4242ed393b132ba514eb434d722e7d9c387 /examples/example-repl-basic/src
parentccab1940c019dfbfb7dfcbbe4cb927258933755f (diff)
Rework examples and add entry macro for testing
Diffstat (limited to 'examples/example-repl-basic/src')
-rw-r--r--examples/example-repl-basic/src/main.rs174
1 files changed, 174 insertions, 0 deletions
diff --git a/examples/example-repl-basic/src/main.rs b/examples/example-repl-basic/src/main.rs
new file mode 100644
index 0000000..f02c2f8
--- /dev/null
+++ b/examples/example-repl-basic/src/main.rs
@@ -0,0 +1,174 @@
+//! Example REPL Basic
+//!
+//! > This example demonstrates how to develop a REPL program using the `repl` feature
+//!
+//! Run:
+//! ```bash
+//! cargo run --manifest-path examples/example-repl-basic/Cargo.toml --quiet
+//! ```
+
+use mingling::{
+ REPL,
+ hook::ProgramHook,
+ prelude::*,
+ setup::{BasicREPLOutputSetup, BasicREPLPromptSetup, BasicREPLReadlineSetup},
+ this,
+};
+use std::{env::current_dir, path::PathBuf};
+
+// Resource to store the current directory
+#[derive(Clone)]
+struct ResCurrentDir {
+ dir: PathBuf,
+}
+
+impl Default for ResCurrentDir {
+ fn default() -> Self {
+ Self {
+ dir: current_dir().unwrap(),
+ }
+ }
+}
+
+fn main() {
+ let mut program = ThisProgram::new();
+
+ // Resource
+ program.with_resource(ResCurrentDir::default());
+
+ // Dispatchers
+ program.with_dispatcher(ChangeDirectoryCommand);
+ program.with_dispatcher(ListCommand);
+ program.with_dispatcher(ExitCommand);
+ program.with_dispatcher(ClearCommand);
+
+ // Setups
+ // Enable basic std::io::stdin().read_line(&mut input)
+ program.with_setup(BasicREPLReadlineSetup);
+
+ // Enable basic output, using println! after Renderer finishes drawing
+ program.with_setup(BasicREPLOutputSetup);
+
+ // Enable basic Prompt display, with custom display logic
+ program.with_setup(BasicREPLPromptSetup::func(|| {
+ // Get the ResCurrentDir resource from the program
+ let res = this::<ThisProgram>().res::<ResCurrentDir>().unwrap();
+ let dir_str: String = res.dir.to_string_lossy().into();
+ let prompt = format!(
+ "{}> ",
+ dir_str
+ .replace(&['/', '\\'][..], ">")
+ .trim_start_matches('>')
+ .trim_end_matches('>')
+ );
+ prompt
+ }));
+
+ // Add hooks to handle REPL-related events
+ program.with_hook(ProgramHook::empty().on_repl_begin(|| {
+ // Print welcome message
+ println!("Welcome!")
+ }));
+
+ // 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);
+dispatcher!("clear", ClearCommand => ClearEntry);
+
+// 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 ResCurrentDir) -> Next {
+ use just_fmt::fmt_path::fmt_path;
+
+ let join = prev.inner;
+ let new_dir = 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: &ResCurrentDir) -> 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 clear command event
+#[chain]
+fn handle_clear(_prev: ClearEntry) {
+ // Clear the terminal screen
+ print!("\x1B[2J\x1B[1;1H");
+}
+
+// Handle path not found event
+#[renderer]
+fn render_error_directory_not_exist(err: ErrorDirectoryNotExist) {
+ r_println!("Directory not found: {}", err.inner.display())
+}
+
+// Handle dispatcher not found event
+#[renderer]
+fn dispatcher_not_found(prev: DispatcherNotFound) {
+ r_println!("Command not found: \"{}\"", prev.join(", "))
+}
+
+gen_program!();