diff options
| author | 魏曹先生 <1992414357@qq.com> | 2026-04-14 22:47:08 +0800 |
|---|---|---|
| committer | 魏曹先生 <1992414357@qq.com> | 2026-04-14 22:47:08 +0800 |
| commit | b9edeb9848e4a423e133fa2a13dede6d128d6f08 (patch) | |
| tree | f8f9e03bf3e64b98f3e5de57fd5d0686bf4739b2 | |
| parent | de54d14f315c96a1003b8956086828fc75ddf49d (diff) | |
Add documentation for Mingling CLI framework
| -rw-r--r-- | docs/.nojekyll | 0 | ||||
| -rw-r--r-- | docs/LICENSE | 21 | ||||
| -rw-r--r-- | docs/README.md | 157 | ||||
| -rw-r--r-- | docs/_sidebar.md | 9 | ||||
| -rw-r--r-- | docs/css/vue.css | 885 | ||||
| -rw-r--r-- | docs/index.html | 55 | ||||
| -rw-r--r-- | docs/pages/1-get-started.md | 69 | ||||
| -rw-r--r-- | docs/pages/2-basic.md | 16 | ||||
| -rw-r--r-- | docs/pages/2-basic/1-program.md | 117 | ||||
| -rw-r--r-- | docs/pages/2-basic/2-setup.md | 169 | ||||
| -rw-r--r-- | docs/pages/2-basic/3-dispatcher.md | 12 | ||||
| -rw-r--r-- | docs/pages/2-basic/4-chain.md | 10 | ||||
| -rw-r--r-- | docs/pages/2-basic/5-renderer.md | 6 |
13 files changed, 1526 insertions, 0 deletions
diff --git a/docs/.nojekyll b/docs/.nojekyll new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/docs/.nojekyll diff --git a/docs/LICENSE b/docs/LICENSE new file mode 100644 index 0000000..bec4d76 --- /dev/null +++ b/docs/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 docsifyjs + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..15e3230 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,157 @@ +<p align="center"> + <a href="https://github.com/CatilGrass/mingling"> + <img alt="Mingling" src="res/icon_shadow.png" width="30%"> + </a> +</p> +<h1 align="center">Mìng Lìng - 命令</h1> + +<p align="center"> + The Rust CLI Framework +</p> +<p align="center"> + <img src="https://img.shields.io/github/stars/CatilGrass/mingling?style=for-the-badge"> + <a href="https://crates.io/crates/mingling"> + <img src="https://img.shields.io/crates/v/mingling?style=for-the-badge"> + </a> + <a href="https://docs.rs/mingling/0.1.5/mingling/"> + <img src="https://img.shields.io/docsrs/mingling?style=for-the-badge"> + </a> + <img src="https://img.shields.io/badge/Current-0.1.5-green?style=for-the-badge"> +</p> + +> [!WARNING] +> +> **Note**: Mingling is still under active development, and its API may change. Feel free to try it out and give us feedback! +> **Hint**: This note will be removed in version `0.2.0` + +## Contents + +- [Intro](#intro) +- [Quick Start](#quick-start) +- [Core Concepts](#core-concepts) +- [Project Structure](#project-structure) +- [Example Projects](#example-projects) +- [Next Steps](#next-steps) +- [Roadmap](#roadmap) +- [Unplanned Features](#unplanned-features) +- [License](#license) + +## Intro + +[`Mingling`](https://github.com/CatilGrass/mingling) is a **proc-macro and type system-based** Rust CLI framework, suitable for developing complex command-line programs with numerous subcommands. + +> BTW: Its name comes from the Chinese Pinyin "mìng lìng", meaning "Command". 😄 + + +## Quick Start + +The example below shows how to use `Mingling` to create a simple command-line program: + +```rust +use mingling::macros::{dispatcher, gen_program, r_println, renderer}; + +#[tokio::main] +async fn main() { + let mut program = ThisProgram::new(); + program.with_dispatcher(HelloCommand); + + // Execute + program.exec().await; +} + +// Define command: "<bin> hello" +dispatcher!("hello", HelloCommand => HelloEntry); + +// Render HelloEntry +#[renderer] +fn render_hello_world(_prev: HelloEntry) { + r_println!("Hello, World!") +} + +// Fallbacks +#[renderer] +fn fallback_dispatcher_not_found(prev: DispatcherNotFound) { + r_println!("Dispatcher not found for command `{}`", prev.join(", ")) +} + +#[renderer] +fn fallback_renderer_not_found(prev: RendererNotFound) { + r_println!("Renderer not found `{}`", *prev) +} + +// Collect renderers and chains to generate ThisProgram +gen_program!(); +``` + +Output: + +``` +> mycmd hello +Hello, World! +> mycmd hallo +Dispatcher not found for command `hallo` +``` + +## Core Concepts + +Mingling abstracts command execution into the following parts: + +1. **Dispatcher** - Routes user input to a specific renderer or chain based on the command node name. +2. **Chain** - Transforms the incoming type into another type, passing it to the next chain or renderer. +3. **Renderer** - Stops the chain and prints the currently processed type to the terminal. +4. **Program** - Manages the lifecycle and configuration of the entire CLI application. + +<details> + <summary>Architecture Diagram (click to expand)</summary> + <p align="center"> + <a href="https://github.com/CatilGrass/mingling"> + <img alt="Mingling" src="res/graph.png" width="75%"> + </a> + </p> +</details> + +## Project Structure + +The Mingling project consists of two main parts: + +- **[mingling/](mingling/)** - The core runtime library, containing type definitions, error handling, and basic functionality. +- **[mingling_macros/](mingling_macros/)** - The procedural macro library, providing declarative macros to simplify development. + +## Example Projects + +- **[`examples/example-basic/`](examples/example-basic/src/main.rs)** - A simple "Hello, World!" example demonstrating the most basic usage of a Dispatcher and Renderer. +- **[`examples/example-picker/`](examples/example-picker/src/main.rs)** - Demonstrates how to use a Chain to process and transform command arguments. +- **[`examples/example-general-renderer/`](examples/example-general-renderer/src/main.rs)** - Shows how to use a general renderer for different data types (e.g., JSON, YAML, TOML, RON). +- **[`examples/example-completion/`](examples/example-completion/src/main.rs)** - An example implementing auto-completion for the shell. + +## Next Steps + +You can read the following docs to learn more about the `Mingling` framework: + +- Check out **[Mingling Examples](examples/)** to learn about the core library. +- Check out **[mingling_macros/README.md](mingling_macros/README.md)** to learn how to use the macro system. + +## Roadmap + +- [x] core: \[[0.1.4](https://docs.rs/mingling/0.1.4/mingling/)\] General Renderers *( Json, Yaml, Toml, Ron )* +- [x] core: \[[0.1.5](https://docs.rs/mingling/0.1.5/mingling/)\] Completion *( Bash Zsh Fish Pwsh )* +- [ ] core: \[**0.2.0**\] Parallel Chains +- [ ] \[**0.2.1**\] Helpdoc +- [ ] \[**unplanned**\] Parser Theme +- [ ] ... + +## Unplanned Features + +While Mingling has several common CLI features that are **not planned** to be directly included in the framework. +This is because the Rust ecosystem already has excellent and mature crates to handle these issues, and Mingling's design is intended to be used in combination with them. + +- **Colored Output**: To add color and styles (bold, italic, etc.) to terminal output, consider using crates like [`colored`](https://crates.io/crates/colored) or [`owo-colors`](https://crates.io/crates/owo-colors). You can integrate their types directly into your renderers. +- **I18n**: To translate your CLI application, the [`rust-i18n`](https://crates.io/crates/rust-i18n) crate provides a powerful internationalization solution that you can use in your command logic and renderers. +- **Progress Bars**: To display progress indicators, the [`indicatif`](https://crates.io/crates/indicatif) crate is the standard choice. +- **TUI**: To build full-screen interactive terminal applications, it is recommended to use a framework like [`ratatui`](https://crates.io/crates/ratatui) (formerly `tui-rs`). + +## License + +This project is licensed under the MIT License. + +See [LICENSE-MIT](LICENSE-MIT) or [LICENSE-APACHE](LICENSE-APACHE) file for details. diff --git a/docs/_sidebar.md b/docs/_sidebar.md new file mode 100644 index 0000000..9cf9351 --- /dev/null +++ b/docs/_sidebar.md @@ -0,0 +1,9 @@ +- [Welcome!](README) +- [Get Started](./pages/1-get-started) + +- [Basic](./pages/2-basic) + - [Program](./pages/2-basic/1-program) + - [Setup](./pages/2-basic/2-setup) + - [Dispatcher](./pages/2-basic/3-dispatcher) + - [Chain](./pages/2-basic/4-chain) + - [Renderer](./pages/2-basic/5-renderer) diff --git a/docs/css/vue.css b/docs/css/vue.css new file mode 100644 index 0000000..499bf1c --- /dev/null +++ b/docs/css/vue.css @@ -0,0 +1,885 @@ +/* + * Based on vue.css + * Original: https://cdn.jsdelivr.net/npm/docsify@4.13.1/lib/themes/vue.css + */ + +@import url("https://fonts.googleapis.com/css?family=Roboto+Mono|Source+Sans+Pro:300,400,600"); +* { + -webkit-font-smoothing: antialiased; + -webkit-overflow-scrolling: touch; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); + -webkit-text-size-adjust: none; + -webkit-touch-callout: none; + box-sizing: border-box; +} +body:not(.ready) { + overflow: hidden; +} +body:not(.ready) .app-nav, +body:not(.ready) > nav, +body:not(.ready) [data-cloak] { + display: none; +} +div#app { + font-size: 30px; + font-weight: lighter; + margin: 40vh auto; + text-align: center; +} +div#app:empty:before { + content: "Loading..."; +} +img.emoji { + height: 1.2em; +} +img.emoji, +span.emoji { + vertical-align: middle; +} +span.emoji { + font-family: + Apple Color Emoji, + Segoe UI Emoji, + Segoe UI Symbol, + Noto Color Emoji; + font-size: 1.2em; +} +.progress { + background-color: #673ab7; + background-color: var(--theme-color, #673ab7); + height: 2px; + left: 0; + position: fixed; + right: 0; + top: 0; + transition: + width 0.2s, + opacity 0.4s; + width: 0; + z-index: 999999; +} +.search .search-keyword, +.search a:hover { + color: #673ab7; + color: var(--theme-color, #673ab7); +} +.search .search-keyword { + font-style: normal; + font-weight: 700; +} +body, +html { + height: 100%; +} +body { + -moz-osx-font-smoothing: grayscale; + -webkit-font-smoothing: antialiased; + color: #34495e; + font-family: + Source Sans Pro, + Helvetica Neue, + Arial, + sans-serif; + font-size: 15px; + letter-spacing: 0; + margin: 0; + overflow-x: hidden; +} +img { + max-width: 100%; +} +a[disabled] { + cursor: not-allowed; + opacity: 0.6; +} +kbd { + border: 1px solid #ccc; + border-radius: 3px; + display: inline-block; + font-size: 12px !important; + line-height: 12px; + margin-bottom: 3px; + padding: 3px 5px; + vertical-align: middle; +} +li input[type="checkbox"] { + margin: 0 0.2em 0.25em 0; + vertical-align: middle; +} +.app-nav { + margin: 25px 60px 0 0; + position: absolute; + right: 0; + text-align: right; + z-index: 10; +} +.app-nav.no-badge { + margin-right: 25px; +} +.app-nav p { + margin: 0; +} +.app-nav > a { + margin: 0 1rem; + padding: 5px 0; +} +.app-nav li, +.app-nav ul { + display: inline-block; + list-style: none; + margin: 0; +} +.app-nav a { + color: inherit; + font-size: 16px; + text-decoration: none; + transition: color 0.3s; +} +.app-nav a.active, +.app-nav a:hover { + color: #673ab7; + color: var(--theme-color, #673ab7); +} +.app-nav a.active { + border-bottom: 2px solid #673ab7; + border-bottom: 2px solid var(--theme-color, #673ab7); +} +.app-nav li { + display: inline-block; + margin: 0 1rem; + padding: 5px 0; + position: relative; + cursor: pointer; +} +.app-nav li ul { + background-color: #fff; + border: 1px solid; + border-color: #ddd #ddd #ccc; + border-radius: 4px; + box-sizing: border-box; + display: none; + max-height: calc(100vh - 61px); + overflow-y: auto; + padding: 10px 0; + position: absolute; + right: -15px; + text-align: left; + top: 100%; + white-space: nowrap; +} +.app-nav li ul li { + display: block; + font-size: 14px; + line-height: 1rem; + margin: 8px 14px; + white-space: nowrap; +} +.app-nav li ul a { + display: block; + font-size: inherit; + margin: 0; + padding: 0; +} +.app-nav li ul a.active { + border-bottom: 0; +} +.app-nav li:hover ul { + display: block; +} +.github-corner { + border-bottom: 0; + position: fixed; + right: 0; + text-decoration: none; + top: 0; + z-index: 1; +} +.github-corner:hover .octo-arm { + animation: octocat-wave 0.56s ease-in-out; +} +.github-corner svg { + color: #fff; + fill: #673ab7; + fill: var(--theme-color, #673ab7); + height: 80px; + width: 80px; +} +main { + display: block; + position: relative; + width: 100vw; + height: 100%; + z-index: 0; +} +main.hidden { + display: none; +} +.anchor { + display: inline-block; + text-decoration: none; + transition: all 0.3s; +} +.anchor span { + color: #34495e; +} +.anchor:hover { + text-decoration: underline; +} +.sidebar { + border-right: 1px solid rgba(0, 0, 0, 0.07); + overflow-y: auto; + padding: 40px 0 0; + position: absolute; + top: 0; + bottom: 0; + left: 0; + transition: transform 0.25s ease-out; + width: 300px; + z-index: 20; +} +.sidebar > h1 { + margin: 0 auto 1rem; + font-size: 1.5rem; + font-weight: 300; + text-align: center; +} +.sidebar > h1 a { + color: inherit; + text-decoration: none; +} +.sidebar > h1 .app-nav { + display: block; + position: static; +} +.sidebar .sidebar-nav { + line-height: 2em; + padding-bottom: 40px; +} +.sidebar li.collapse .app-sub-sidebar { + display: none; +} +.sidebar ul { + margin: 0 0 0 15px; + padding: 0; +} +.sidebar li > p { + font-weight: 700; + margin: 0; +} +.sidebar ul, +.sidebar ul li { + list-style: none; +} +.sidebar ul li a { + border-bottom: none; + display: block; +} +.sidebar ul li ul { + padding-left: 20px; +} +.sidebar::-webkit-scrollbar { + width: 4px; +} +.sidebar::-webkit-scrollbar-thumb { + background: transparent; + border-radius: 4px; +} +.sidebar:hover::-webkit-scrollbar-thumb { + background: hsla(0, 0%, 53.3%, 0.4); +} +.sidebar:hover::-webkit-scrollbar-track { + background: hsla(0, 0%, 53.3%, 0.1); +} +.sidebar-toggle { + background-color: transparent; + background-color: hsla(0, 0%, 100%, 0.8); + border: 0; + outline: none; + padding: 10px; + position: absolute; + bottom: 0; + left: 0; + text-align: center; + transition: opacity 0.3s; + width: 284px; + z-index: 30; + cursor: pointer; +} +.sidebar-toggle:hover .sidebar-toggle-button { + opacity: 0.4; +} +.sidebar-toggle span { + background-color: #673ab7; + background-color: var(--theme-color, #673ab7); + display: block; + margin-bottom: 4px; + width: 16px; + height: 2px; +} +body.sticky .sidebar, +body.sticky .sidebar-toggle { + position: fixed; +} +.content { + padding-top: 60px; + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 300px; + transition: left 0.25s ease; +} +.markdown-section { + margin: 0 auto; + max-width: 80%; + padding: 30px 15px 40px; + position: relative; +} +.markdown-section > * { + box-sizing: border-box; + font-size: inherit; +} +.markdown-section > :first-child { + margin-top: 0 !important; +} +.markdown-section hr { + border: none; + border-bottom: 1px solid #eee; + margin: 2em 0; +} +.markdown-section iframe { + border: 1px solid #eee; + width: 1px; + min-width: 100%; +} +.markdown-section table { + border-collapse: collapse; + border-spacing: 0; + display: block; + margin-bottom: 1rem; + overflow: auto; + width: 100%; +} +.markdown-section th { + font-weight: 700; +} +.markdown-section td, +.markdown-section th { + border: 1px solid #ddd; + padding: 6px 13px; +} +.markdown-section tr { + border-top: 1px solid #ccc; +} +.markdown-section p.tip, +.markdown-section tr:nth-child(2n) { + background-color: #f8f8f8; +} +.markdown-section p.tip { + border-bottom-right-radius: 2px; + border-left: 4px solid #ff4081; + border-top-right-radius: 2px; + margin: 2em 0; + padding: 12px 24px 12px 30px; + position: relative; +} +.markdown-section p.tip:before { + background-color: #ff4081; + border-radius: 100%; + color: #fff; + content: "!"; + font-family: + Dosis, + Source Sans Pro, + Helvetica Neue, + Arial, + sans-serif; + font-size: 14px; + font-weight: 700; + left: -12px; + line-height: 20px; + position: absolute; + height: 20px; + width: 20px; + text-align: center; + top: 14px; +} +.markdown-section p.tip code { + background-color: #efefef; +} +.markdown-section p.tip em { + color: #34495e; +} +.markdown-section p.warn { + background: rgba(66, 185, 131, 0.1); + border-radius: 2px; + padding: 1rem; +} +.markdown-section ul.task-list > li { + list-style-type: none; +} +body.close .sidebar { + transform: translateX(-300px); +} +body.close .sidebar-toggle { + width: auto; +} +body.close .content { + left: 0; +} +@media print { + .app-nav, + .github-corner, + .sidebar, + .sidebar-toggle { + display: none; + } +} +@media screen and (max-width: 768px) { + .github-corner, + .sidebar, + .sidebar-toggle { + position: fixed; + } + .app-nav { + margin-top: 16px; + } + .app-nav li ul { + top: 30px; + } + main { + height: auto; + min-height: 100vh; + overflow-x: hidden; + } + .sidebar { + left: -300px; + transition: transform 0.25s ease-out; + } + .content { + left: 0; + max-width: 100vw; + position: static; + padding-top: 20px; + transition: transform 0.25s ease; + } + .app-nav, + .github-corner { + transition: transform 0.25s ease-out; + } + .sidebar-toggle { + background-color: transparent; + width: auto; + padding: 30px 30px 10px 10px; + } + body.close .sidebar { + transform: translateX(300px); + } + body.close .sidebar-toggle { + background-color: hsla(0, 0%, 100%, 0.8); + transition: background-color 1s; + width: 284px; + padding: 10px; + } + body.close .content { + transform: translateX(300px); + } + body.close .app-nav, + body.close .github-corner { + display: none; + } + .github-corner:hover .octo-arm { + animation: none; + } + .github-corner .octo-arm { + animation: octocat-wave 0.56s ease-in-out; + } +} +@keyframes octocat-wave { + 0%, + to { + transform: rotate(0); + } + 20%, + 60% { + transform: rotate(-25deg); + } + 40%, + 80% { + transform: rotate(10deg); + } +} +section.cover { + position: relative; + align-items: center; + background-position: 50%; + background-repeat: no-repeat; + background-size: cover; + min-height: 100vh; + width: 100%; + display: none; +} +section.cover.show { + display: flex; +} +section.cover.has-mask .mask { + background-color: #fff; + opacity: 0.8; + position: absolute; + top: 0; + bottom: 0; + width: 100%; +} +section.cover .cover-main { + flex: 1; + margin: 0 16px; + text-align: center; + position: relative; +} +section.cover a { + color: inherit; +} +section.cover a, +section.cover a:hover { + text-decoration: none; +} +section.cover p { + line-height: 1.5rem; + margin: 1em 0; +} +section.cover h1 { + color: inherit; + font-size: 2.5rem; + font-weight: 300; + margin: 0.625rem 0 2.5rem; + position: relative; + text-align: center; +} +section.cover h1 a { + display: block; +} +section.cover h1 small { + bottom: -0.4375rem; + font-size: 1rem; + position: absolute; +} +section.cover blockquote { + font-size: 1.5rem; + text-align: center; +} +section.cover ul { + line-height: 1.8; + list-style-type: none; + margin: 1em auto; + max-width: 500px; + padding: 0; +} +section.cover .cover-main > p:last-child a { + border-radius: 2rem; + border: 1px solid #673ab7; + border-color: var(--theme-color, #673ab7); + box-sizing: border-box; + color: #673ab7; + color: var(--theme-color, #673ab7); + display: inline-block; + font-size: 1.05rem; + letter-spacing: 0.1rem; + margin: 0.5rem 1rem; + padding: 0.75em 2rem; + text-decoration: none; + transition: all 0.15s ease; +} +section.cover .cover-main > p:last-child a:last-child { + background-color: #673ab7; + background-color: var(--theme-color, #673ab7); + color: #fff; +} +section.cover .cover-main > p:last-child a:last-child:hover { + color: inherit; + opacity: 0.8; +} +section.cover .cover-main > p:last-child a:hover { + color: inherit; +} +section.cover blockquote > p > a { + border-bottom: 2px solid #673ab7; + border-bottom: 2px solid var(--theme-color, #673ab7); + transition: color 0.3s; +} +section.cover blockquote > p > a:hover { + color: #673ab7; + color: var(--theme-color, #673ab7); +} +.sidebar, +body { + background-color: #fefefe; +} +.sidebar { + color: #364149; +} +.sidebar li { + margin: 6px 0; +} +.sidebar ul li a { + color: #505d6b; + font-size: 14px; + font-weight: 400; + overflow: hidden; + text-decoration: none; + text-overflow: ellipsis; + white-space: nowrap; +} +.sidebar ul li a:hover { + text-decoration: underline; +} +.sidebar ul li ul { + padding: 0; +} +.sidebar ul li.active > a { + border-right: 2px solid; + color: #673ab7; + color: var(--theme-color, #673ab7); + font-weight: 600; +} +.app-sub-sidebar li:before { + content: "-"; + padding-right: 4px; + float: left; +} +.markdown-section h1, +.markdown-section h2, +.markdown-section h3, +.markdown-section h4, +.markdown-section strong { + color: #2c3e50; + font-weight: 600; +} +.markdown-section a { + color: #673ab7; + color: var(--theme-color, #673ab7); + font-weight: 600; +} +.markdown-section h1 { + font-size: 2rem; + margin: 0 0 1rem; +} +.markdown-section h2 { + font-size: 1.75rem; + margin: 45px 0 0.8rem; +} +.markdown-section h3 { + font-size: 1.5rem; + margin: 40px 0 0.6rem; +} +.markdown-section h4 { + font-size: 1.25rem; +} +.markdown-section h5 { + font-size: 1rem; +} +.markdown-section h6 { + color: #777; + font-size: 1rem; +} +.markdown-section figure, +.markdown-section p { + margin: 1.2em 0; +} +.markdown-section ol, +.markdown-section p, +.markdown-section ul { + line-height: 1.6rem; + word-spacing: 0.05rem; +} +.markdown-section ol, +.markdown-section ul { + padding-left: 1.5rem; +} +.markdown-section blockquote { + border-left: 4px solid #673ab7; + border-left: 4px solid var(--theme-color, #673ab7); + color: #858585; + margin: 2em 0; + padding-left: 20px; +} +.markdown-section blockquote p { + font-weight: 600; + margin-left: 0; +} +.markdown-section iframe { + margin: 1em 0; +} +.markdown-section em { + color: #7f8c8d; +} +.markdown-section code, +.markdown-section output:after, +.markdown-section pre { + font-family: + Roboto Mono, + Monaco, + courier, + monospace; +} +.markdown-section code, +.markdown-section pre { + background-color: #f2f2f2; +} +.markdown-section output, +.markdown-section pre { + margin: 1.2em 0; + position: relative; + border-radius: 6px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); +} +.markdown-section output, +.markdown-section pre > code { + border-radius: 2px; + display: block; +} +.markdown-section output:after, +.markdown-section pre > code { + -moz-osx-font-smoothing: initial; + -webkit-font-smoothing: initial; +} +.markdown-section code { + border-radius: 2px; + color: #ff4182; + margin: 0 2px; + padding: 3px 5px; + white-space: pre-wrap; +} +.markdown-section > :not(h1):not(h2):not(h3):not(h4):not(h5):not(h6) code { + font-size: 0.8rem; +} +.markdown-section pre { + padding: 0 1.4rem; + line-height: 1.5rem; + overflow: auto; + word-wrap: normal; +} +.markdown-section pre > code { + color: #525252; + font-size: 0.8rem; + padding: 2.2em 5px; + line-height: inherit; + margin: 0 2px; + max-width: inherit; + overflow: inherit; + white-space: inherit; +} +.markdown-section output { + padding: 1.7rem 1.4rem; + border: 1px dotted #ccc; +} +.markdown-section output > :first-child { + margin-top: 0; +} +.markdown-section output > :last-child { + margin-bottom: 0; +} +.markdown-section code:after, +.markdown-section code:before, +.markdown-section output:after, +.markdown-section output:before { + letter-spacing: 0.05rem; +} +.markdown-section output:after, +.markdown-section pre:after { + color: #ccc; + font-size: 0.6rem; + font-weight: 600; + height: 15px; + line-height: 15px; + padding: 5px 10px 0; + position: absolute; + right: 0; + text-align: right; + top: 0; + content: attr(data-lang); +} +.token.cdata, +.token.comment, +.token.doctype, +.token.prolog { + color: #8e908c; +} +.token.namespace { + opacity: 0.7; +} +.token.boolean, +.token.number { + color: #4caf50; +} +.token.punctuation { + color: #525252; +} +.token.property { + color: #c08b30; +} +.token.tag { + color: #673ab7; +} +.token.string { + color: #4caf50; + color: var(--theme-color, #4caf50); +} +.token.selector { + color: #6679cc; +} +.token.attr-name { + color: #2973b7; +} +.language-css .token.string, +.style .token.string, +.token.entity, +.token.url { + color: #22a2c9; +} +.token.attr-value, +.token.control, +.token.directive, +.token.unit { + color: #673ab7; + color: var(--theme-color, #673ab7); +} +.token.function, +.token.keyword { + color: #ff4182; +} +.token.atrule, +.token.regex, +.token.statement { + color: #22a2c9; +} +.token.placeholder, +.token.variable { + color: #3d8fd1; +} +.token.deleted { + text-decoration: line-through; +} +.token.inserted { + border-bottom: 1px dotted #202746; + text-decoration: none; +} +.token.italic { + font-style: italic; +} +.token.bold, +.token.important { + font-weight: 700; +} +.token.important { + color: #c94922; +} +.token.entity { + cursor: help; +} +code .token { + -moz-osx-font-smoothing: initial; + -webkit-font-smoothing: initial; + min-height: 1.5rem; + position: relative; + left: auto; +} diff --git a/docs/index.html b/docs/index.html new file mode 100644 index 0000000..7e3c5e3 --- /dev/null +++ b/docs/index.html @@ -0,0 +1,55 @@ +<!doctype html> +<html lang="en"> + <head> + <meta charset="utf-8" /> + <meta + name="viewport" + content="width=device-width, initial-scale=1, minimum-scale=1.0, shrink-to-fit=no, viewport-fit=cover" + /> + + <title>Mingling Documents</title> + <meta + name="Mingling Documents" + content="Quick start with Mingling and build your command-line program!" + /> + + <link rel="stylesheet" href="css/vue.css" /> + </head> + + <body> + <nav> + <a href="https://docs.rs/mingling/latest/mingling/">Docs.rs</a> + <a href="https://crates.io/crates/mingling">Crates.io</a> + | + <a href="https://github.com/CatilGrass/mingling">GitHub</a> + | + <a href="https://git.catilgrass.cn/catilgrass/mingling.git" + ><b>Source</b></a + > + </nav> + <div id="app"></div> + + <script> + window.$docsify = { + name: "Mingling Document", + auto2top: true, + loadSidebar: true, + maxLevel: 0, + subMaxLevel: 3, + search: { + placeholder: "Search", + noData: "No matches found.", + depth: 2, + }, + }; + </script> + + <script src="https://cdn.jsdelivr.net/npm/docsify@4/lib/docsify.min.js"></script> + + <script src="https://cdn.jsdelivr.net/npm/docsify@4/lib/plugins/zoom-image.min.js"></script> + <script src="https://cdn.jsdelivr.net/npm/docsify@4/lib/plugins/search.js"></script> + <script src="https://cdn.jsdelivr.net/npm/prismjs@1/components/prism-bash.min.js"></script> + <script src="https://cdn.jsdelivr.net/npm/prismjs@1/components/prism-toml.min.js"></script> + <script src="https://cdn.jsdelivr.net/npm/prismjs@1/components/prism-rust.js"></script> + </body> +</html> diff --git a/docs/pages/1-get-started.md b/docs/pages/1-get-started.md new file mode 100644 index 0000000..f4edfc0 --- /dev/null +++ b/docs/pages/1-get-started.md @@ -0,0 +1,69 @@ +# Get Started +This article explains how to quickly create your first **Mingling** command-line program. + +## Quick Start +1. Add `mingling` to your Rust project. +```bash +cargo add mingling +``` +Or add the following to your `Cargo.toml`: +```toml +[dependencies] +mingling = "0.1.5" +``` + +> **Mingling** is an **async program**, so please use `async-std`, `tokio`, or another async runtime. + +2. This article assumes you are using the `tokio` async runtime. Add the following to your `Cargo.toml`: +```toml +tokio = { + version = "1", + features = [ + "macros", + "rt", + "rt-multi-thread" + ] +} +``` + +3. Write the basic code in your `main.rs` or other program entry point. +```rust +use mingling::macros::{dispatcher, gen_program, r_println, renderer}; + +#[tokio::main] +async fn main() { + // Create ThisProgram + let mut program = ThisProgram::new(); + + // Import the dispatcher `HelloCommand` + program.with_dispatcher(HelloCommand); + + // Run the program + program.exec().await; +} + +// Define the dispatcher `HelloCommand`, which routes the "hello" subcommand to `HelloEntry` +dispatcher!("hello", HelloCommand => HelloEntry); + +// Define the renderer, which receives `HelloEntry` and renders the content +#[renderer] +fn render_hello(_prev: HelloEntry) { + r_println!("Hello, World!") +} + +// Create ThisProgram at the end of the code +gen_program!(); +``` + +4. Install your command-line program and run it. +```bash +cargo install --path ./ +your_bin hello +``` +Result: +```bash +Hello, World! +``` + +## 💡 Next Steps +> **Mingling**'s basic components [Go](./pages/2-basic) diff --git a/docs/pages/2-basic.md b/docs/pages/2-basic.md new file mode 100644 index 0000000..78d2fda --- /dev/null +++ b/docs/pages/2-basic.md @@ -0,0 +1,16 @@ +<h1 align="center">Mingling's Basic Components</h1> +<p align="center"> + Table of Contents +</p> + +--- + +Mingling abstracts the lifecycle of a CLI program into the following types: + +| Component | Description | +| :--- | :--- | +| [Program](./pages/2-basic/1-program) | Records resources for the current context | +| [Setup](./pages/2-basic/2-setup) | Bundles commonly used functionalities | +| [Dispatcher](./pages/2-basic/3-dispatcher) | Dispatches user input to specific types | +| [Chain](./pages/2-basic/4-chain) | Handles type conversion | +| [Renderer](./pages/2-basic/5-renderer) | Handles type rendering | diff --git a/docs/pages/2-basic/1-program.md b/docs/pages/2-basic/1-program.md new file mode 100644 index 0000000..fd8e986 --- /dev/null +++ b/docs/pages/2-basic/1-program.md @@ -0,0 +1,117 @@ +<h1 align="center">Program</h1> +<p align="center"> + Mingling's Basic Components +</p> + +--- + +## Intro + +`Program` is the data structure that holds the state for **Mingling** CLI programs. It manages the user's context and enables type-based dispatch. + +`Program` needs to implement the `ProgramCollect` trait, + +> but, you don't have to do this manually — + +The `mingling_macros` crate provides the `gen_program!()` macro, which can auto collect resources defined by the `dispatcher!`, `chain!`, and the `completion!` macro of the `comp` feature. + +```rust +// Define Dispatcher +dispatcher!("hello", HelloCommand => HelloEntry); + +// Define Renderer +#[renderer] +fn render_hello(_prev: HelloEntry) { + r_println!("Hello, World!") +} + +// Collect all resources here and generate ThisProgram +gen_program!(); + +// You can also explicitly declare a Program +// with a different name like this: +// gen_program!(MyProgram); +``` + +## Adding Setup + +You can use the `with_setup` function to add preset [Setup](pages/2-basic/2-setup) to your program, which provide reusable functionality. + +For example, you can use the following code to add parsing for global flags like `--confirm` / `--help` / `--quiet` to your program: + +```rust +use mingling::{ + macros::gen_program, + setup::BasicProgramSetup +}; + +#[tokio::main] +async fn main() { + let mut program = ThisProgram::new(); + // Add `BasicProgramSetup` + program.with_setup(BasicProgramSetup); + program.exec().await; +} + +// Generate `ThisProgram` +gen_program!(); +``` + +## Adding Dispatcher + +You can use `with_dispatcher` or `with_dispatchers` to add [Dispatchers](pages/2-basic/3-dispatcher) to your program to make it work: + +```rust +// Define two Dispatchers using `dispatcher!` +dispatcher!("member.add", + AddMemberCommand => AddMemberEntry); +dispatcher!("member.rm", + RemoveMemberCommand => RemoveMemberEntry); + +#[tokio::main] +async fn main() { + let mut program = ThisProgram::new(); + + // Register Dispatchers + program.with_dispatcher(AddMemberCommand); + program.with_dispatcher(RemoveMemberCommand); + + // Or use `with_dispatchers` + program.with_dispatchers(( + AddMemberCommand, + RemoveMemberCommand + )); + + program.exec().await; +} +``` + +## Parsing Global Args + +You can extract global arguments before the program runs to control the global state of the `Program`: + +```rust +#[tokio::main] +async fn main() { + let mut program = ThisProgram::new(); + + let mut output = current_dir().unwrap(); + + // Pick the "--quiet" or "-q" flag + program.global_flag(["--quiet", "-q"], |p| { + // Disable render output + p.stdout_setting.render_output = false; + }); + + // Pick the "--output" or "-O" flag, write to output + program.global_argument( + ["--output", "-O"], + |_, v| output = PathBuf::from(v) + ); + + program.exec().await; +} +``` + +## 💡 Next Page +> **Basic Component** - Setup [Go](./pages/2-basic/2-setup) diff --git a/docs/pages/2-basic/2-setup.md b/docs/pages/2-basic/2-setup.md new file mode 100644 index 0000000..8dbdd76 --- /dev/null +++ b/docs/pages/2-basic/2-setup.md @@ -0,0 +1,169 @@ +<h1 align="center">Setup</h1> +<p align="center"> + Mingling's Basic Components +</p> + +--- + +## Usage + +`Setup` is used to organize and package the initialization process of a `Program`, making the project easier to manage. It is defined as follows: + +```rust +struct MySetup; +impl ProgramSetup<ThisProgram, ThisProgram> + for MySetup +{ + fn setup( + &mut self, + program: &mut Program<ThisProgram, ThisProgram> + ) { + // Your setup logic + } +} +``` + +For example: + +```rust +use std::{env::current_dir, path::PathBuf}; + +use mingling::{ + Program, + macros::{dispatcher, gen_program, renderer}, + setup::ProgramSetup, +}; + +// Global state +static OUTPUT_PATH: std::sync::OnceLock<PathBuf> + = std::sync::OnceLock::new(); + +#[tokio::main] +async fn main() { + let mut program = ThisProgram::new(); + program.with_setup(MySetup); + program.exec().await; +} + +// Define two Dispatchers using `dispatcher!` +dispatcher!("member.add", + AddMemberCommand => AddMemberEntry); +dispatcher!("member.rm", + RemoveMemberCommand => RemoveMemberEntry); + +struct MySetup; +impl ProgramSetup<ThisProgram, ThisProgram> for MySetup { + fn setup( + &mut self, program: &mut Program<ThisProgram, ThisProgram> + ) { + // Register Dispatchers + program.with_dispatcher(AddMemberCommand); + program.with_dispatcher(RemoveMemberCommand); + + // Initialize global output once + OUTPUT_PATH.get_or_init(|| current_dir().unwrap()); + + // Pick the "--quiet" or "-q" flag + program.global_flag(["--quiet", "-q"], |p| { + // Disable render output + p.stdout_setting.render_output = false; + }); + + // Pick the "--output" or "-O" flag, write to output + program.global_argument(["--output", "-O"], |_, v| { + let _ = OUTPUT_PATH.set(PathBuf::from(v)); + }); + } +} + +// Define empty renderer types to give the two types type IDs + +#[renderer] +fn phantom_renderer_add_member( + _prev: AddMemberEntry +) {} + +#[renderer] +fn phantom_renderer_remove_member( + _prev: RemoveMemberEntry +) {} + +gen_program!(); +``` + +## Simplified Syntax + +If you find the above declaration method too **verbose**, you can use the `program_setup!` macro to simplify it. The format is: + +```rust +#[program_setup] +fn my_setup( + program: &mut Program<ThisProgram, ThisProgram> +) { + // Your setup logic +} +``` + +For example: + +```rust +use std::{env::current_dir, path::PathBuf}; + +use mingling::{ + Program, + macros::{ + dispatcher, + gen_program, + program_setup, + renderer + }, +}; + +static OUTPUT_PATH: std::sync::OnceLock<PathBuf> + = std::sync::OnceLock::new(); + +#[tokio::main] +async fn main() { + let mut program = ThisProgram::new(); + program.with_setup(MySetup); + program.exec().await; +} + +dispatcher!("member.add", + AddMemberCommand => AddMemberEntry); +dispatcher!("member.rm", + RemoveMemberCommand => RemoveMemberEntry); + +#[program_setup] +fn my_setup( + program: &mut Program<ThisProgram, ThisProgram> +) { + program.with_dispatcher(AddMemberCommand); + program.with_dispatcher(RemoveMemberCommand); + + OUTPUT_PATH.get_or_init(|| current_dir().unwrap()); + + program.global_flag(["--quiet", "-q"], |p| { + p.stdout_setting.render_output = false; + }); + + program.global_argument(["--output", "-O"], |_, v| { + let _ = OUTPUT_PATH.set(PathBuf::from(v)); + }); +} + +#[renderer] +fn phantom_renderer_add_member( + _prev: AddMemberEntry +) {} + +#[renderer] +fn phantom_renderer_remove_member( + _prev: RemoveMemberEntry +) {} + +gen_program!(); +``` + +## 💡 Next Page +> **Basic Component** - Dispatcher [Go](./pages/2-basic/3-dispatcher) diff --git a/docs/pages/2-basic/3-dispatcher.md b/docs/pages/2-basic/3-dispatcher.md new file mode 100644 index 0000000..643a752 --- /dev/null +++ b/docs/pages/2-basic/3-dispatcher.md @@ -0,0 +1,12 @@ +<h1 align="center">Dispatcher</h1> +<p align="center"> + Mingling's Basic Components +</p> + +--- + + + + +## 💡 Next Page +> **Basic Component** - Chain [Go](./pages/2-basic/4-chain) diff --git a/docs/pages/2-basic/4-chain.md b/docs/pages/2-basic/4-chain.md new file mode 100644 index 0000000..e9676c2 --- /dev/null +++ b/docs/pages/2-basic/4-chain.md @@ -0,0 +1,10 @@ +<h1 align="center">Chain</h1> +<p align="center"> + Mingling's Basic Components +</p> + +--- + + +## 💡 Next Page +> **Basic Component** - Renderer [Go](./pages/2-basic/5-renderer) diff --git a/docs/pages/2-basic/5-renderer.md b/docs/pages/2-basic/5-renderer.md new file mode 100644 index 0000000..f3e09de --- /dev/null +++ b/docs/pages/2-basic/5-renderer.md @@ -0,0 +1,6 @@ +<h1 align="center">Renderer</h1> +<p align="center"> + Mingling's Basic Components +</p> + +--- |
