diff options
Diffstat (limited to 'README.md')
| -rw-r--r-- | README.md | 135 |
1 files changed, 88 insertions, 47 deletions
@@ -18,6 +18,7 @@ <img src="https://img.shields.io/crates/size/mingling"> <img src="https://img.shields.io/crates/v/mingling?style=flat"> <img src="https://img.shields.io/docsrs/mingling?style=flat"> + <img src="https://img.shields.io/github/actions/workflow/status/mingling-rs/mingling/ci.yml"> </p> > [!WARNING] @@ -53,9 +54,8 @@ - 📖 [Mainpage](https://mingling-rs.github.io/mingling/) - 📖 [Examples](https://mingling-rs.github.io/mingling/docs/examples.html) - 📖 [docs.rs](https://docs.rs/mingling/latest/mingling/) -- 📖 Helpdoc [EN](https://mingling-rs.github.io/mingling/docs/doc.html#/) [中文](https://mingling-rs.github.io/mingling/docs/_zh_CN/index.html#/) - - +- 📖 [Helpdoc](https://mingling-rs.github.io/mingling/docs/doc.html#/) Or [帮助文档](https://mingling-rs.github.io/mingling/docs/_zh_CN/index.html#/) +- 🔍 [Devdoc](https://mingling-rs.github.io/mingling/docs/dev/) <h1 align="center"> Getting Started @@ -65,7 +65,7 @@ Add Mingling to your `Cargo.toml`: ```toml [dependencies.mingling] -version = "0.2.0" +version = "0.3.0" features = [] ``` @@ -102,11 +102,11 @@ User Input → [Dispatcher] → Entry → [Chain(s)] → Result → [Renderer] **Step4: Render** — A **Renderer** takes that result and writes it to the terminal. > [!NOTE] -> A Chain can produce a **State** type to be passed to the next Chain for further processing, +> A Chain can produce a **State** type to be passed to the next Chain for further processing, > -> or it can produce a **Result** type to be handed off to a Renderer. +> or it can produce a **Result** type to be handed off to a Renderer. -Everything in this pipeline is a **plain Rust function** with an attribute macro on top. +Everything in this pipeline is a **plain Rust function** with an attribute macro on top. You never need to manually implement traits or construct boilerplate. @@ -181,27 +181,36 @@ Key points: ### 3. The Renderer — "#[renderer]" — How Output Works -The `#[renderer]` attribute turns a function into an output handler. It receives the final result of a chain and writes it to the terminal. +The `#[renderer]` attribute turns a function into an output handler. It receives the final result of a chain and returns a `RenderResult`. ```rust +use mingling::macros::pack; +use mingling::prelude::*; +use std::io::Write; + pack!(ResultGreeting = String); #[renderer] -fn render_greeting(greeting: ResultGreeting) { - r_println!("Hello, {}!", *greeting); +fn render_greeting(greeting: ResultGreeting) -> RenderResult { + let mut result = RenderResult::new(); + writeln!(result, "Hello, {}!", *greeting).ok(); + result } ``` -Inside a renderer, use `r_print!` / `r_println!` to write to the output buffer. This is not `println!` — it writes into Mingling's internal `RenderResult` buffer, which is flushed at the end of the pipeline. +Inside a renderer, create a `RenderResult`, write to it using `write!` / `writeln!` (from [`std::io::Write`](https://doc.rust-lang.org/std/io/trait.Write.html)), and return it. The output is captured in the buffer and flushed by the framework at the end of the pipeline. You can write renderers for **any type** in your program, including error types: ```rust use mingling::prelude::*; +use std::io::Write; #[renderer] -fn render_dispatcher_not_found(err: ErrorDispatcherNotFound) { - r_println!("Command not found: [{}]", err.join(" ")); +fn render_dispatcher_not_found(err: ErrorDispatcherNotFound) -> RenderResult { + let mut result = RenderResult::new(); + writeln!(result, "Command not found: [{}]", err.join(" ")).ok(); + result } ``` @@ -283,14 +292,17 @@ Help is just another attribute macro. When the user passes `--help` or `-h`, the Enable it by adding `BasicProgramSetup`: ```rust -use mingling::{macros::help, setup::BasicProgramSetup}; +use mingling::{macros::help, prelude::*, setup::BasicProgramSetup}; +use std::io::Write; dispatcher!("greet", CMDGreet => EntryGreet); #[help] -fn help_greet(_prev: EntryGreet) { - r_println!("Usage: greet <NAME>"); - r_println!("Greets the user with the given name."); +fn help_greet(_prev: EntryGreet) -> RenderResult { + let mut result = RenderResult::new(); + writeln!(result, "Usage: greet <NAME>").ok(); + writeln!(result, "Greets the user with the given name.").ok(); + result } fn main() { @@ -362,11 +374,9 @@ fn main() { In your `build.rs`, generate the shell scripts: ```rust +// BUILD TIME // Features: ["comp", "builds"] - -fn main() { - mingling::build::build_comp_scripts(env!("CARGO_PKG_NAME")).unwrap(); -} +mingling::build::build_comp_scripts(env!("CARGO_PKG_NAME")).unwrap(); ``` For enum-based completions, use `suggest_enum!`: @@ -400,6 +410,10 @@ fn complete_lang(_: &ShellContext) -> Suggest { Mingling doesn't use `?` operator propagation. Instead, errors are just **alternative results** that flow through the same chain/render pipeline. Create error types with `pack!` and route to them with `.to_render()`: ```rust +use mingling::macros::pack; +use mingling::prelude::*; +use std::io::Write; + dispatcher!("hello", CMDHello => EntryHello); pack!(ResultName = String); pack!(ErrorNoNameProvided = ()); @@ -419,13 +433,17 @@ fn handle(args: EntryHello) -> Next { } #[renderer] -fn render_no_name(_: ErrorNoNameProvided) { - r_println!("No name provided"); +fn render_no_name(_: ErrorNoNameProvided) -> RenderResult { + let mut result = RenderResult::new(); + writeln!(result, "No name provided").ok(); + result } #[renderer] -fn render_too_long(len: ErrorNameTooLong) { - r_println!("Name too long: {} > 10", *len); +fn render_too_long(len: ErrorNameTooLong) -> RenderResult { + let mut result = RenderResult::new(); + writeln!(result, "Name too long: {} > 10", *len).ok(); + result } ``` @@ -482,6 +500,8 @@ fn change_dir(prev: EntryCd, current_dir: &mut ResCurrentDir) -> Next { Resources can also be injected into `#[renderer]`: ```rust +use mingling::prelude::*; +use std::io::Write; dispatcher!("current", CMDCurrent => EntryCurrent); @@ -491,8 +511,10 @@ struct ResCurrentDir { } #[renderer] -fn render_current(_: EntryCurrent, current_dir: &ResCurrentDir) { - r_println!("Current directory: {}", current_dir.current_dir.display()); +fn render_current(_: EntryCurrent, current_dir: &ResCurrentDir) -> RenderResult { + let mut result = RenderResult::new(); + writeln!(result, "Current directory: {}", current_dir.current_dir.display()).ok(); + result } ``` @@ -538,7 +560,8 @@ If you prefer clap's powerful argument parsing, use `#[dispatcher_clap]`. It gen // clap = "4" use mingling::macros::dispatcher_clap; -use mingling::Groupped; +use mingling::prelude::*; +use std::io::Write; #[derive(Default, clap::Parser, Groupped)] #[dispatcher_clap( @@ -555,15 +578,19 @@ pub struct EntryGreet { } #[renderer] -fn render_greet(greet: EntryGreet) { - r_print!("Hello, "); - for _ in 0..greet.repeat { r_print!("{}", greet.name); } - r_println!("!"); +fn render_greet(greet: EntryGreet) -> RenderResult { + let mut result = RenderResult::new(); + write!(result, "Hello, ").ok(); + for _ in 0..greet.repeat { write!(result, "{}", greet.name).ok(); } + writeln!(result, "!").ok(); + result } #[renderer] -fn render_parse_error(err: ErrorGreetParsed) { - r_println!("{}", *err); +fn render_parse_error(err: ErrorGreetParsed) -> RenderResult { + let mut result = RenderResult::new(); + writeln!(result, "{}", *err).ok(); + result } ``` @@ -689,6 +716,7 @@ use mingling::{prelude::*, setup::StructuralRendererSetup}; use mingling::Groupped; use mingling::StructuralData; use serde::Serialize; +use std::io::Write; dispatcher!("render", CMDRender => EntryRender); @@ -705,8 +733,10 @@ fn render_info(args: EntryRender) -> Next { } #[renderer] -fn render_info_result(info: ResultInfo) { - r_println!("{} is {} years old", info.name, info.age); +fn render_info_result(info: ResultInfo) -> RenderResult { + let mut result = RenderResult::new(); + writeln!(result, "{} is {} years old", info.name, info.age).ok(); + result } fn main() { @@ -742,6 +772,7 @@ Enable the `async` feature to use `async fn` inside `#[chain]`: // Dependencies: // tokio = { version = "1", features = ["full"] } +use std::io::Write; use std::time::Duration; dispatcher!("download", CMDDownload => EntryDownload); @@ -759,8 +790,10 @@ async fn download_file(name: String) -> ResultDownloaded { } #[renderer] -fn render_downloaded(result: ResultDownloaded) { - r_println!("\"{}\" downloaded.", *result); +fn render_downloaded(result: ResultDownloaded) -> RenderResult { + let mut r = RenderResult::new(); + writeln!(r, "\"{}\" downloaded.", *result).ok(); + r } ``` @@ -789,6 +822,10 @@ It must be placed **after** all your `dispatcher!`, `pack!`, `#[chain]`, `#[rend Here's a complete, runnable program: ```rust +use mingling::macros::pack; +use mingling::prelude::*; +use std::io::Write; + dispatcher!("greet", CMDGreet => EntryGreet); fn main() { @@ -810,8 +847,10 @@ fn handle_greet(args: EntryGreet) -> Next { } #[renderer] -fn render_greeting(greeting: ResultGreeting) { - r_println!("Hello, {}!", *greeting); +fn render_greeting(greeting: ResultGreeting) -> RenderResult { + let mut result = RenderResult::new(); + writeln!(result, "Hello, {}!", *greeting).ok(); + result } gen_program!(); @@ -829,7 +868,7 @@ Hello, Alice! 🗺️ Roadmap 🗺️ </h1> -- [ ] Milestone.1 "MVP" +- [x] Milestone.1 "MVP" 🎉 - [x] [[0.1.4](https://docs.rs/mingling/0.1.4/mingling/)] [`core`] [`structural_renderer`] **Mingling** can render data into serializable formats via `--json` and `--yaml` flags - [x] [[0.1.5](https://docs.rs/mingling/0.1.5/mingling/)] [`core`] [`comp`] **Mingling** can dynamically invoke itself to provide completions for shells like `bash`, `zsh`, `fish`, and `pwsh` - [x] [[0.1.6](https://docs.rs/mingling/0.1.6/mingling/)] [`core`] [`comp`] **Mingling** can gather more context for smarter completions @@ -839,13 +878,15 @@ Hello, Alice! - [x] [[0.1.8](https://docs.rs/mingling/0.1.8/mingling/)] [`core`] [`dispatch_tree`] Converts the subcommand list into a prefix tree to improve command matching speed - [x] [[0.1.9](https://docs.rs/mingling/0.1.9/mingling/)] [`core`] [`dev_toolkits`] Provides debugging interfaces for developers to capture invocation information when issues arise (`InvokeStackDisplay`) (indirectly implemented via `ProgramHook`) - [x] [[0.1.9](https://docs.rs/mingling/0.1.9/mingling/)] [`core`] [`repl`] Provides REPL capability (`program.exec_repl();`) - - [ ] [**0.2.0**] Complete documentation, tests, and examples - + - [x] [[0.2.0](https://docs.rs/mingling/0.2.0/mingling/)] Complete documentation, tests, and examples - [ ] Milestone.2 "More Comfortable Dev and User Experience" - - [ ] [**0.2.1**] [`macros`] `r_println!` in `#[chain]` support. - - [ ] [**0.2.5**] [`mling`] Helpdoc Maker - - [ ] [**0.2.8**] [`picker`] A more efficient and intelligent argument parser - + - [ ] [`mling` / `mingling-cli`] + - [ ] **Mingling** Linter + - [ ] **Mingling** Project Generator + - [ ] **Mingling** Program Installer & Manager (For development) + - [ ] Helpdoc Editor + - [ ] [`picker`] A more efficient and intelligent argument parser + - [x] [`macros`] Remove `r_print!` / `r_println!` macros - [ ] Milestone.3 "Unplanned" - [ ] ... |
