aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author魏曹先生 <1992414357@qq.com>2026-04-11 22:28:26 +0800
committer魏曹先生 <1992414357@qq.com>2026-04-11 22:28:26 +0800
commitdc501290f7b25b72edf8c67555b5604ac5fc8a59 (patch)
treeb6c4064e46ddef919a561a846c9770f432f821d2
parentd6f0e3f8f97a27a464d9610500c29fb817a2ef88 (diff)
Add dev tools to generate example documentation
-rw-r--r--dev_tools/Cargo.lock26
-rw-r--r--dev_tools/Cargo.toml8
-rw-r--r--dev_tools/src/bin/refresh-examples.rs139
-rw-r--r--dev_tools/src/lib.rs1
-rw-r--r--mingling/src/example_docs.rs382
-rw-r--r--mingling/src/example_docs.rs.tmpl18
-rw-r--r--mingling/src/lib.rs162
-rw-r--r--mingling_core/src/lib.rs3
-rwxr-xr-xrun-tools.sh27
9 files changed, 608 insertions, 158 deletions
diff --git a/dev_tools/Cargo.lock b/dev_tools/Cargo.lock
new file mode 100644
index 0000000..39a1521
--- /dev/null
+++ b/dev_tools/Cargo.lock
@@ -0,0 +1,26 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "just_fmt"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5454cda0d57db59778608d7a47bff5b16c6705598265869fb052b657f66cf05e"
+
+[[package]]
+name = "just_template"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "db3edb658c34b10b69c4b3b58f7ba989cd09c82c0621dee1eef51843c2327225"
+dependencies = [
+ "just_fmt",
+]
+
+[[package]]
+name = "tools"
+version = "0.1.0"
+dependencies = [
+ "just_fmt",
+ "just_template",
+]
diff --git a/dev_tools/Cargo.toml b/dev_tools/Cargo.toml
new file mode 100644
index 0000000..a28b156
--- /dev/null
+++ b/dev_tools/Cargo.toml
@@ -0,0 +1,8 @@
+[package]
+name = "tools"
+version = "0.1.0"
+edition = "2024"
+
+[dependencies]
+just_template = "0.1.3"
+just_fmt = "0.1.2"
diff --git a/dev_tools/src/bin/refresh-examples.rs b/dev_tools/src/bin/refresh-examples.rs
new file mode 100644
index 0000000..9fdc7b5
--- /dev/null
+++ b/dev_tools/src/bin/refresh-examples.rs
@@ -0,0 +1,139 @@
+use std::path::PathBuf;
+
+use just_fmt::snake_case;
+use just_template::{Template, tmpl};
+
+const EXAMPLE_ROOT: &str = "./examples/";
+const OUTPUT_PATH: &str = "./mingling/src/example_docs.rs";
+
+const TEMPLATE_CONTENT: &str = include_str!("../../../mingling/src/example_docs.rs.tmpl");
+
+struct ExampleContent {
+ name: String,
+ header: String,
+ code: String,
+ cargo_toml: String,
+}
+
+impl ExampleContent {
+ pub fn read(name: &str) -> Self {
+ let repo = find_git_repo().unwrap();
+ let cargo_toml = Self::read_cargo_toml(&repo, name);
+ let (header, code) = Self::read_header_and_code(&repo, name);
+
+ let cargo_toml = cargo_toml
+ .lines()
+ .map(|line| format!("/// {}", line))
+ .collect::<Vec<_>>()
+ .join("\n");
+
+ let header = header
+ .lines()
+ .map(|line| format!("/// {}", line))
+ .collect::<Vec<_>>()
+ .join("\n");
+
+ let code = code
+ .lines()
+ .map(|line| format!("/// {}", line))
+ .collect::<Vec<_>>()
+ .join("\n");
+
+ ExampleContent {
+ name: name.to_string(),
+ header,
+ code,
+ cargo_toml,
+ }
+ }
+
+ fn read_header_and_code(repo: &PathBuf, name: &str) -> (String, String) {
+ let file_path = repo
+ .join(EXAMPLE_ROOT)
+ .join(name)
+ .join("src")
+ .join("main.rs");
+ let content = std::fs::read_to_string(&file_path).unwrap_or_default();
+ let mut lines = content.lines();
+ let mut header = String::new();
+ let mut code = String::new();
+
+ // Collect header lines (starting with //!)
+ while let Some(line) = lines.next() {
+ if line.trim_start().starts_with("//!") {
+ let trimmed = line.trim_start_matches("//!").trim();
+ header.push_str(trimmed);
+ header.push('\n');
+ } else {
+ // First non-header line found, start collecting code
+ code.push_str(line);
+ code.push('\n');
+ break;
+ }
+ }
+
+ // Collect remaining code lines
+ for line in lines {
+ code.push_str(line);
+ code.push('\n');
+ }
+
+ (header.trim().to_string(), code.trim().to_string())
+ }
+
+ fn read_cargo_toml(repo: &PathBuf, name: &str) -> String {
+ let file_path = repo.join(EXAMPLE_ROOT).join(name).join("Cargo.toml");
+ let content = std::fs::read_to_string(&file_path).unwrap_or_default();
+ content
+ }
+}
+
+fn main() {
+ let mut template = Template::from(TEMPLATE_CONTENT);
+ let repo_root = find_git_repo().unwrap();
+ let example_root = repo_root.join(EXAMPLE_ROOT);
+ let mut examples = Vec::new();
+ if let Ok(entries) = std::fs::read_dir(&example_root) {
+ for entry in entries.flatten() {
+ if let Ok(file_type) = entry.file_type() {
+ if file_type.is_dir() {
+ let example_name = entry.file_name().to_string_lossy().to_string();
+ let example_content = ExampleContent::read(&example_name);
+ examples.push(example_content);
+ }
+ }
+ }
+ }
+
+ for example in examples {
+ tmpl!(template += {
+ examples {
+ (
+ example_header = example.header,
+ example_import = example.cargo_toml,
+ example_code = example.code,
+ example_name = snake_case!(example.name)
+ )
+ }
+ });
+ }
+
+ std::fs::write(repo_root.join(OUTPUT_PATH), template.to_string()).unwrap();
+}
+
+fn find_git_repo() -> Option<std::path::PathBuf> {
+ let mut current_dir = std::env::current_dir().ok()?;
+
+ loop {
+ let git_dir = current_dir.join(".git");
+ if git_dir.exists() && git_dir.is_dir() {
+ return Some(current_dir);
+ }
+
+ if !current_dir.pop() {
+ break;
+ }
+ }
+
+ None
+}
diff --git a/dev_tools/src/lib.rs b/dev_tools/src/lib.rs
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/dev_tools/src/lib.rs
@@ -0,0 +1 @@
+
diff --git a/mingling/src/example_docs.rs b/mingling/src/example_docs.rs
new file mode 100644
index 0000000..ec8162e
--- /dev/null
+++ b/mingling/src/example_docs.rs
@@ -0,0 +1,382 @@
+// Auto generated
+
+/// `Mingling` Example - Basic
+///
+/// # How to Run
+/// ```bash
+/// cargo run --manifest-path ./examples/example-basic/Cargo.toml -- hello World
+/// ```
+///
+/// Cargo.toml
+/// ```
+/// [package]
+/// name = "example-basic"
+/// version = "0.0.1"
+/// edition = "2024"
+///
+/// [dependencies]
+/// mingling = { path = "../../mingling" }
+/// tokio = { version = "1", features = ["rt", "rt-multi-thread", "macros"] }
+/// ```
+///
+/// main.rs
+/// ```rust
+/// use mingling::{
+/// macros::{chain, dispatcher, gen_program, pack, r_println, renderer},
+/// marker::NextProcess,
+/// };
+///
+/// // Define dispatcher `HelloCommand`, directing subcommand "hello" to `HelloEntry`
+/// dispatcher!("hello", HelloCommand => HelloEntry);
+///
+/// #[tokio::main]
+/// async fn main() {
+/// // Create program
+/// let mut program = ThisProgram::new();
+///
+/// // Add dispatcher `HelloCommand`
+/// program.with_dispatcher(HelloCommand);
+///
+/// // Run program
+/// program.exec().await;
+/// }
+///
+/// // Register wrapper type `Hello`, setting inner to `String`
+/// pack!(Hello = String);
+///
+/// // Register chain to `ThisProgram`, handling logic from `HelloEntry`
+/// #[chain]
+/// async fn parse_name(prev: HelloEntry) -> NextProcess {
+/// // Extract string from `HelloEntry` as argument
+/// let name = prev.first().cloned().unwrap_or_else(|| "World".to_string());
+///
+/// // Build `Hello` type and route to renderer
+/// Hello::new(name).to_render()
+/// }
+///
+/// // Register renderer to `ThisProgram`, handling rendering of `Hello`
+/// #[renderer]
+/// fn render_hello_who(prev: Hello) {
+/// // Print message
+/// r_println!("Hello, {}!", *prev);
+///
+/// // Program ends here
+/// }
+///
+/// // Generate program, default is `ThisProgram`
+/// gen_program!();
+/// ```
+pub mod example_basic {}
+/// `Mingling` Example - Completion
+///
+/// # How to Deploy
+/// 1. Enable the `comp` feature
+/// ```toml
+/// mingling = { version = "0.1.5", features = [
+/// "comp", // Enable this feature
+/// "parser"
+/// ] }
+/// ```
+///
+/// 2. Write `build.rs` to generate completion scripts at compile time
+/// ```rust
+/// use mingling::build::{build_comp_scripts, build_comp_scripts_with_bin_name};
+/// fn main() {
+/// // Generate completion scripts for the current program
+/// build_comp_scripts().unwrap();
+///
+/// // Or specify a specific name
+/// // build_comp_scripts_with_bin_name("your_bin").unwrap();
+/// }
+/// ```
+///
+/// 3. Write `main.rs`, adding completion logic for your command entry point
+/// 4. Execute `cargo install --path ./`, then run the corresponding completion script in your shell
+///
+/// Cargo.toml
+/// ```
+/// [package]
+/// name = "example-completion"
+/// version = "0.0.1"
+/// edition = "2024"
+///
+/// [dependencies]
+/// mingling = { path = "../../mingling", features = ["comp", "parser"] }
+/// tokio = { version = "1", features = ["rt", "rt-multi-thread", "macros"] }
+/// ```
+///
+/// main.rs
+/// ```rust
+/// use mingling::{
+/// AnyOutput, Groupped, ShellContext, Suggest,
+/// macros::{chain, completion, dispatcher, gen_program, r_println, renderer, suggest},
+/// marker::NextProcess,
+/// parser::{Pickable, Picker},
+/// };
+///
+/// // Define dispatcher `FruitCommand`, directing subcommand "fruit" to `FruitEntry`
+/// dispatcher!("fruit", FruitCommand => FruitEntry);
+///
+/// #[completion(FruitEntry)]
+/// fn comp_fruit_command(ctx: &ShellContext) -> Suggest {
+/// if ctx.current_word.starts_with("-") {
+/// return suggest! {
+/// "--name": "Fruit name",
+/// "--type": "Fruit type"
+/// };
+/// }
+/// if ctx.previous_word == "--name" {
+/// return suggest!();
+/// }
+/// if ctx.previous_word == "--type" {
+/// return suggest! {
+/// "apple", "banana"
+/// };
+/// }
+/// return suggest!();
+/// }
+///
+/// #[tokio::main]
+/// async fn main() {
+/// let mut program = ThisProgram::new();
+/// program.with_dispatcher(CompletionDispatcher); // Add completion dispatcher
+/// program.with_dispatcher(FruitCommand);
+/// program.exec().await;
+/// }
+///
+/// #[derive(Groupped)]
+/// struct FruitInfo {
+/// name: String,
+/// fruit_type: FruitType,
+/// }
+///
+/// #[derive(Default, Debug)]
+/// enum FruitType {
+/// #[default]
+/// Apple,
+/// Banana,
+/// Other(String),
+/// }
+///
+/// impl Pickable for FruitType {
+/// type Output = FruitType;
+///
+/// fn pick(args: &mut mingling::parser::Argument, flag: mingling::Flag) -> Option<Self::Output> {
+/// let name = args.pick_argument(flag);
+/// match name {
+/// Some(name) => match name.as_str() {
+/// "apple" => Some(FruitType::Apple),
+/// "banana" => Some(FruitType::Banana),
+/// other => Some(FruitType::Other(other.to_string())),
+/// },
+/// None => None,
+/// }
+/// }
+/// }
+///
+/// #[chain]
+/// async fn parse_fruit_info(prev: FruitEntry) -> NextProcess {
+/// let picker = Picker::<ThisProgram>::from(prev.inner);
+/// let (fruit_name, fruit_type) = picker.pick("--name").pick("--type").unpack_directly();
+/// let info = FruitInfo {
+/// name: fruit_name,
+/// fruit_type,
+/// };
+/// AnyOutput::new(info).route_renderer()
+/// }
+///
+/// #[renderer]
+/// fn render_fruit(prev: FruitInfo) {
+/// if let FruitType::Other(other) = prev.fruit_type {
+/// r_println!("Fruit name: {}, Type: {:?} (Unknown)", prev.name, other);
+/// } else {
+/// r_println!("Fruit name: {}, Type: {:?}", prev.name, prev.fruit_type);
+/// }
+/// }
+///
+/// gen_program!();
+/// ```
+pub mod example_completion {}
+/// `Mingling` Example - General Renderer
+///
+/// ## Step1 - Enable Feature
+/// Enable the `general_renderer` feature for mingling in `Cargo.toml`
+/// ```toml
+/// [dependencies]
+/// mingling = { version = "...", features = ["general_renderer", "parser"] }
+/// ```
+///
+/// ## Step2 - Add Dependencies
+/// Add `serde` dependency to `Cargo.toml` for serialization support
+/// ```toml
+/// [dependencies]
+/// serde = { version = "1", features = ["derive"] }
+/// ```
+///
+/// ## Step3 - Write Code
+/// Write the following content into `main.rs`
+///
+/// ## Step3 - Build and Run
+/// ```bash
+/// cargo run --manifest-path ./examples/example-general-renderer/Cargo.toml -- render Bob 22
+/// cargo run --manifest-path ./examples/example-general-renderer/Cargo.toml -- render Bob 22 --json
+/// cargo run --manifest-path ./examples/example-general-renderer/Cargo.toml -- render Bob 22 --yaml
+/// ```
+///
+/// Will print:
+/// ```plain
+/// Bob is 22 years old
+/// {"member_name":"Bob","member_age":22}
+/// member_name: Bob
+/// member_age: 22
+/// ```
+///
+/// Cargo.toml
+/// ```
+/// [package]
+/// name = "example-general-renderer"
+/// version = "0.0.1"
+/// edition = "2024"
+///
+/// [dependencies]
+/// mingling = { path = "../../mingling", features = [
+/// "parser",
+/// "general_renderer",
+/// ] }
+/// serde = { version = "1", features = ["derive"] }
+/// tokio = { version = "1", features = ["rt", "rt-multi-thread", "macros"] }
+/// ```
+///
+/// main.rs
+/// ```rust
+/// use mingling::{
+/// AnyOutput, Groupped,
+/// macros::{chain, dispatcher, gen_program, r_println, renderer},
+/// marker::NextProcess,
+/// parser::Picker,
+/// setup::GeneralRendererSetup,
+/// };
+/// use serde::Serialize;
+///
+/// dispatcher!("render", RenderCommand => RenderCommandEntry);
+///
+/// #[tokio::main]
+/// async fn main() {
+/// let mut program = ThisProgram::new();
+/// // Add `GeneralRendererSetup` to receive user input `--json` `--yaml` parameters
+/// program.with_setup(GeneralRendererSetup);
+/// program.with_dispatcher(RenderCommand);
+/// program.exec().await;
+/// }
+///
+/// // Manually implement Info struct
+/// #[derive(Serialize, Groupped)]
+/// struct Info {
+/// #[serde(rename = "member_name")]
+/// name: String,
+/// #[serde(rename = "member_age")]
+/// age: i32,
+/// }
+///
+/// #[chain]
+/// async fn parse_render(prev: RenderCommandEntry) -> NextProcess {
+/// let (name, age) = Picker::<AnyOutput<ThisProgram>>::new(prev.inner)
+/// .pick::<String>(())
+/// .pick::<i32>(())
+/// .unpack_directly();
+/// AnyOutput::new(Info { name, age }).route_renderer()
+/// }
+///
+/// // Implement default renderer for when general_renderer is not specified
+/// #[renderer]
+/// fn render_info(prev: Info) {
+/// r_println!("{} is {} years old", prev.name, prev.age);
+/// }
+///
+/// gen_program!();
+/// ```
+pub mod example_general_renderer {}
+/// `Mingling` Example - Picker
+///
+/// ## Step1 - Enable Feature
+/// Enable the `parser` feature for mingling in `Cargo.toml`
+/// ```toml
+/// [dependencies]
+/// mingling = { version = "...", features = ["parser"] }
+/// ```
+///
+/// ## Step2 - Write Code
+/// Write the following content into `main.rs`
+///
+/// ## Step3 - Build and Run
+/// ```bash
+/// cargo run --manifest-path ./examples/example-picker/Cargo.toml -- pick Bob
+/// cargo run --manifest-path ./examples/example-picker/Cargo.toml -- pick Bob --age -15
+/// cargo run --manifest-path ./examples/example-picker/Cargo.toml -- pick --age 99
+/// ```
+///
+/// Cargo.toml
+/// ```
+/// [package]
+/// name = "example-picker"
+/// version = "0.0.1"
+/// edition = "2024"
+///
+/// [dependencies]
+/// mingling = { path = "../../mingling", features = ["parser"] }
+/// tokio = { version = "1", features = ["rt", "rt-multi-thread", "macros"] }
+/// ```
+///
+/// main.rs
+/// ```rust
+/// use mingling::{
+/// AnyOutput,
+/// macros::{chain, dispatcher, gen_program, pack, r_println, renderer},
+/// marker::NextProcess,
+/// parser::Picker,
+/// };
+///
+/// dispatcher!("pick", PickCommand => PickEntry);
+///
+/// #[tokio::main]
+/// async fn main() {
+/// let mut program = ThisProgram::new();
+/// program.with_dispatcher(PickCommand);
+/// program.exec().await;
+/// }
+///
+/// pack!(NoNameProvided = ());
+/// pack!(ParsedPickInput = (i32, String));
+///
+/// #[chain]
+/// async fn parse(prev: PickEntry) -> NextProcess {
+/// // Extract arguments from `PickEntry`'s inner and create a `Picker`
+/// let picker = Picker::new(prev.inner);
+/// let picked = picker
+/// // First extract the named argument
+/// .pick_or("--age", 20)
+/// .after(|n: i32| n.clamp(0, 100))
+/// // Then sequentially extract the remaining arguments
+/// .pick_or_route((), AnyOutput::new(NoNameProvided::default()))
+/// .unpack();
+///
+/// match picked {
+/// Ok(value) => ParsedPickInput::new(value).to_render(),
+/// Err(e) => e.route_renderer(),
+/// }
+/// }
+///
+/// #[renderer]
+/// fn render_parsed_pick_input(prev: ParsedPickInput) {
+/// let (age, name) = prev.inner;
+/// r_println!("Picked: name = {}, age = {}", name, age);
+/// }
+///
+/// #[renderer]
+/// fn render_no_name_input(_prev: NoNameProvided) {
+/// r_println!("No name provided.");
+/// }
+///
+/// gen_program!();
+/// ```
+pub mod example_picker {} \ No newline at end of file
diff --git a/mingling/src/example_docs.rs.tmpl b/mingling/src/example_docs.rs.tmpl
new file mode 100644
index 0000000..5a32126
--- /dev/null
+++ b/mingling/src/example_docs.rs.tmpl
@@ -0,0 +1,18 @@
+// Auto generated
+
+>>>>>>>>>> examples
+
+@@@ >>> examples
+<<<example_header>>>
+///
+/// Cargo.toml
+/// ```
+<<<example_import>>>
+/// ```
+///
+/// main.rs
+/// ```rust
+<<<example_code>>>
+/// ```
+pub mod <<<example_name>>> {}
+@@@ <<<
diff --git a/mingling/src/lib.rs b/mingling/src/lib.rs
index 8d0a8c2..2bd6b58 100644
--- a/mingling/src/lib.rs
+++ b/mingling/src/lib.rs
@@ -101,163 +101,9 @@ pub mod macros {
/// derive macro Groupped
pub use mingling_macros::Groupped;
-pub mod docs {
- pub mod basic {
- //! # Basic Usage
- //!
- //! This module demonstrates basic usage of Mingling with a simple "hello" command.
- //!
- //! ## Example
- //!
- //! ```rust
- //! use mingling::{
- //! macros::{chain, dispatcher, gen_program, pack, r_println, renderer},
- //! marker::NextProcess,
- //! };
- //!
- //! // Define dispatcher `HelloCommand`, directing subcommand "hello" to `HelloEntry`
- //! dispatcher!("hello", HelloCommand => HelloEntry);
- //!
- //! #[tokio::main]
- //! async fn main() {
- //! // Create program
- //! let mut program = ThisProgram::new();
- //!
- //! // Add dispatcher `HelloCommand`
- //! program.with_dispatcher(HelloCommand);
- //!
- //! // Run program
- //! program.exec().await;
- //! }
- //!
- //! // Register wrapper type `Hello`, setting inner to `String`
- //! pack!(Hello = String);
- //!
- //! // Register chain to `ThisProgram`, handling logic from `HelloEntry`
- //! #[chain]
- //! async fn parse_name(prev: HelloEntry) -> NextProcess {
- //! // Extract string from `HelloEntry` as argument
- //! let name = prev.get(0).cloned().unwrap_or_else(|| "World".to_string());
- //!
- //! // Build `Hello` type and route to renderer
- //! Hello::new(name).to_render()
- //! }
- //!
- //! // Register renderer to `ThisProgram`, handling rendering of `Hello`
- //! #[renderer]
- //! fn render_hello_who(prev: Hello) {
- //! // Print message
- //! r_println!("Hello, {}!", *prev);
- //!
- //! // Program ends here
- //! }
- //!
- //! // Generate program, default is `ThisProgram`
- //! gen_program!();
- //! ```
- //!
- //! ## Output
- //!
- //! ```text
- //! > mycmd hello
- //! Hello, World!
- //! > mycmd hello Alice
- //! Hello, Alice!
- //! ```
- }
+mod example_docs;
- pub mod parser {
- //! # Feature `parser` Usage
- //!
- //! This module demonstrates advanced usage of Mingling with the `Picker` utility for argument parsing.
- //!
- //! ## Example
- //!
- //! ```rust
- //! use mingling::{
- //! AnyOutput,
- //! macros::{chain, dispatcher, gen_program, pack, r_println, renderer},
- //! marker::NextProcess,
- //! parser::Picker,
- //! };
- //!
- //! // Define dispatcher `RepeatCommand`, directing subcommand "repeat" to `RepeatEntry`
- //! dispatcher!("repeat", RepeatCommand => RepeatEntry);
- //!
- //! #[tokio::main]
- //! async fn main() {
- //! // Create program
- //! let mut program = ThisProgram::new();
- //!
- //! // Add dispatcher `RepeatCommand`
- //! program.with_dispatcher(RepeatCommand);
- //!
- //! // Run program
- //! program.exec().await;
- //! }
- //!
- //! // Register wrapper type `RepeatArgument`, setting inner to `(i32, String)`
- //! pack!(RepeatArgument = (i32, String));
- //!
- //! // Register error type
- //! pack!(ErrorContentRequired = ());
- //!
- //! // Register chain to `ThisProgram`, handling logic for `RepeatEntry`
- //! #[chain]
- //! async fn parse_repeat_args(prev: RepeatEntry) -> NextProcess {
- //! let picker = Picker::new(prev.inner); // Create Picker from user arguments
- //! let picked = picker
- //! .pick_or::<i32>("--time", 1) // Extract argument `--time`
- //! .after(|n| n.clamp(1, 20)) // Clamp extracted number between 1 - 20
- //! // Extract first remaining argument as content, route to type `ErrorContentRequired` if not found
- //! .pick_or_route((), AnyOutput::new(ErrorContentRequired::default()))
- //! .unpack(); // Unpack
- //!
- //! match picked {
- //! Ok(args) => {
- //! // Build `RepeatArgument` type and route to renderer
- //! RepeatArgument::new(args).to_render()
- //! }
- //! Err(e) => {
- //! // Extraction failed, route to error type
- //! e.route_renderer()
- //! }
- //! }
- //! }
- //!
- //! // Render `RepeatArgument`
- //! #[renderer]
- //! fn render_repeat(prev: RepeatArgument) {
- //! let (times, content) = prev.inner;
- //! for _ in 0..times {
- //! r_println!("{}", content);
- //! }
- //! }
- //!
- //! // Render `ErrorContentRequired`
- //! #[renderer]
- //! fn render_error_content_required(_prev: ErrorContentRequired) {
- //! r_println!("Error: content is required");
- //! }
- //!
- //! // Generate program, default is `ThisProgram`
- //! gen_program!();
- //! ```
- //!
- //! ## Output
- //!
- //! ```text
- //! > mycmd repeat --time 3 Hello
- //! Hello
- //! Hello
- //! Hello
- //! > mycmd repeat --time 25 Hello
- //! Hello
- //! Hello
- //! Hello
- //! ... (repeated 20 times, clamped from 25)
- //! > mycmd repeat --time 3
- //! Error: content is required
- //! ```
- }
+/// Example projects for `Mingling`, for learning how to use `Mingling`
+pub mod _mingling_examples {
+ pub use crate::example_docs::*;
}
diff --git a/mingling_core/src/lib.rs b/mingling_core/src/lib.rs
index b4124a9..2c5cf55 100644
--- a/mingling_core/src/lib.rs
+++ b/mingling_core/src/lib.rs
@@ -51,9 +51,12 @@ pub mod setup {
#[doc(hidden)]
pub mod builds;
+
+/// Provides build scripts for users
pub mod build {
#[cfg(feature = "comp")]
pub use crate::builds::comp::*;
}
+/// Provided for framework developers
pub mod debug;
diff --git a/run-tools.sh b/run-tools.sh
new file mode 100755
index 0000000..7bfcd29
--- /dev/null
+++ b/run-tools.sh
@@ -0,0 +1,27 @@
+#!/bin/bash
+
+cd "$(dirname "$0")" || exit 1
+
+if [ $# -eq 0 ]; then
+ echo "Available:"
+ if [ -d "dev_tools/src/bin" ]; then
+ for file in dev_tools/src/bin/*.rs; do
+ if [ -f "$file" ]; then
+ basename "$file" .rs
+ fi
+ done
+ else
+ echo "Warning: dev_tools/src/bin directory does not exist"
+ fi
+ exit 1
+fi
+
+target_bin="$1"
+target_file="dev_tools/src/bin/${target_bin}.rs"
+
+if [ ! -f "$target_file" ]; then
+ echo "Error: target file '$target_file' does not exist"
+ exit 1
+fi
+
+cargo run --manifest-path dev_tools/Cargo.toml --bin "$1"