diff options
66 files changed, 930 insertions, 1344 deletions
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0a6a9a6..71b63ca 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -82,8 +82,11 @@ jobs: uses: actions/checkout@v4 - uses: actions-rust-lang/setup-rust-toolchain@v1 + - name: Install nightly toolchain + run: rustup toolchain install nightly + - name: Build API docs - run: cargo run --manifest-path .run/Cargo.toml --bin deploy-api-docs + run: cargo +nightly run --manifest-path .run/Cargo.toml --bin deploy-api-docs -- --docsrs - name: Install cargo-llvm-cov run: cargo install cargo-llvm-cov @@ -91,12 +94,17 @@ jobs: - name: Run cov-test run: cargo run --manifest-path .run/Cargo.toml --bin cov-test + - name: Delete .temp directory before deployment + run: rm -rf .temp + - name: Setup Pages uses: actions/configure-pages@v5 + - name: Upload artifact uses: actions/upload-pages-artifact@v3 with: path: "." + - name: Deploy to GitHub Pages id: deployment uses: actions/deploy-pages@v5 diff --git a/.run/Cargo.lock b/.run/Cargo.lock index c255ad0..3117d0b 100644 --- a/.run/Cargo.lock +++ b/.run/Cargo.lock @@ -9,6 +9,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] +name = "arg-picker" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd62c395708a956e98e06b6b18200b2a65a5ae63448ed7d59cca32833aaf7265" +dependencies = [ + "arg-picker-macros", + "just_fmt 0.2.0", +] + +[[package]] +name = "arg-picker-macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04fddbb5c1f26450cfc6aded0559b759b292d400dba1ff4e45128f3e0e1ffe6b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] name = "bitflags" version = "2.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -175,12 +196,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5454cda0d57db59778608d7a47bff5b16c6705598265869fb052b657f66cf05e" [[package]] +name = "just_fmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6170dccbc3ea15dfb7f2da964097f814aba1dd8f746d4ffc56f33245c38e6d96" + +[[package]] name = "just_template" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db3edb658c34b10b69c4b3b58f7ba989cd09c82c0621dee1eef51843c2327225" dependencies = [ - "just_fmt", + "just_fmt 0.1.2", ] [[package]] @@ -240,9 +267,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.45" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" dependencies = [ "proc-macro2", ] @@ -332,9 +359,9 @@ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "syn" -version = "2.0.117" +version = "2.0.119" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" dependencies = [ "proc-macro2", "quote", @@ -418,10 +445,11 @@ checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" name = "tools" version = "0.1.0" dependencies = [ + "arg-picker", "colored", "flate2", "indicatif", - "just_fmt", + "just_fmt 0.1.2", "just_template", "serde", "serde_json", diff --git a/.run/Cargo.toml b/.run/Cargo.toml index caeb506..4935f2d 100644 --- a/.run/Cargo.toml +++ b/.run/Cargo.toml @@ -21,5 +21,6 @@ tokio = { version = "1", features = ["rt-multi-thread", "macros"] } indicatif = "0.18.4" flate2 = "1" tar = "0.4" +arg-picker = "0.1.0" [workspace] diff --git a/.run/src/bin/ci.rs b/.run/src/bin/ci.rs index f3ae9e8..4b6f973 100644 --- a/.run/src/bin/ci.rs +++ b/.run/src/bin/ci.rs @@ -1,6 +1,7 @@ use std::io::Write as _; use std::process::exit; +use arg_picker::{Picker, macros::arg}; use tools::{ cargo_tomls, crate_name_from, eprintln_cargo_style, println_cargo_style, run_cmd, run_parallel, }; @@ -35,19 +36,20 @@ fn main() { let _ = colored::control::set_virtual_terminal(true); println!("{}", include_str!("../../../docs/res/ci_banner.txt")); - let args: Vec<String> = std::env::args().collect(); + let (auto_yes, dirty, test_docs, refresh_docs, test_codes, help) = Picker::from_args() + .pick_or_default(&arg![yes: bool, 'y']) + .pick_or_default(&arg![dirty: bool]) + .pick_or_default(&arg![test_docs: bool]) + .pick_or_default(&arg![refresh_docs: bool]) + .pick_or_default(&arg![test_codes: bool]) + .pick_or_default(&arg![help: bool, 'h']) + .unwrap(); - if args.iter().any(|a| a == "-h" || a == "--help") { + if help { print_help(); return; } - let auto_yes = args.iter().any(|a| a == "-y"); - let dirty = args.iter().any(|a| a == "--dirty"); - - let test_docs = args.iter().any(|a| a == "--test-docs"); - let refresh_docs = args.iter().any(|a| a == "--refresh-docs"); - let test_codes = args.iter().any(|a| a == "--test-codes"); let any_specified = test_docs || refresh_docs || test_codes; let run_all = !any_specified; @@ -140,6 +142,11 @@ fn ci(test_docs: bool, test_codes: bool, run_all: bool) -> Result<(), i32> { exit_code = exit_code.max(code); } + println_cargo_style!("Phase: Try Build API docs"); + if let Err(code) = deploy_api_docs() { + exit_code = exit_code.max(code); + } + if exit_code != 0 { return Err(exit_code); } @@ -223,6 +230,12 @@ fn test_all() -> Result<(), i32> { run_parallel("Testing", tasks) } +fn deploy_api_docs() -> Result<(), i32> { + run_cmd!( + "cargo run --manifest-path .run/Cargo.toml --color always --bin deploy-api-docs -- --docsrs" + ) +} + fn docs_refresh() -> Result<(), i32> { println_cargo_style!("Refresh: document at `./docs/`"); diff --git a/.run/src/bin/clippy.sh b/.run/src/bin/clippy.sh index b393545..b393545 100644..100755 --- a/.run/src/bin/clippy.sh +++ b/.run/src/bin/clippy.sh diff --git a/.run/src/bin/deploy-api-docs.rs b/.run/src/bin/deploy-api-docs.rs index 7aa093c..961eb04 100644 --- a/.run/src/bin/deploy-api-docs.rs +++ b/.run/src/bin/deploy-api-docs.rs @@ -1,10 +1,15 @@ use std::path::Path; +use arg_picker::{Picker, macros::arg}; use tools::{println_cargo_style, run_cmd}; const OUTPUT_DIR: &str = "docs/api-docs"; fn main() { + let using_docsrs = Picker::from_args() + .pick_or_default(&arg![docsrs: bool]) + .unwrap(); + let repo_root = find_git_repo().expect("Failed to find git repository root"); // Read features from [package.metadata.docs.rs] @@ -20,11 +25,19 @@ fn main() { std::fs::create_dir_all(&output_path).expect("Failed to create output directory"); // Build cargo doc command - let cmd = format!( - "cargo doc --no-deps --features \"{}\" -p mingling --target-dir \"{}\" --color always", - features_arg, - output_path.join("target").to_string_lossy() - ); + let cmd = if using_docsrs { + format!( + "cargo +nightly rustdoc --features \"{}\" -p mingling --target-dir \"{}\" --color always -- --cfg docsrs", + features_arg, + output_path.join("target").to_string_lossy() + ) + } else { + format!( + "cargo doc --no-deps --features \"{}\" -p mingling --target-dir \"{}\" --color always", + features_arg, + output_path.join("target").to_string_lossy() + ) + }; println_cargo_style!("Features: {}", features_arg); println_cargo_style!("Output: {}", output_path.display()); diff --git a/.run/src/bin/doc-nightly.ps1 b/.run/src/bin/doc-nightly.ps1 new file mode 100644 index 0000000..30d6aaf --- /dev/null +++ b/.run/src/bin/doc-nightly.ps1 @@ -0,0 +1,6 @@ +cargo +nightly rustdoc ` + --manifest-path mingling/Cargo.toml ` + --features docs_rs,core,macros,builds,structural_renderer,repl,comp,parser,picker,clap,extra_macros ` + --open ` + -- ` + --cfg docsrs diff --git a/.run/src/bin/doc-nightly.sh b/.run/src/bin/doc-nightly.sh new file mode 100644 index 0000000..944f4b3 --- /dev/null +++ b/.run/src/bin/doc-nightly.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +cargo rustdoc \ + --manifest-path mingling/Cargo.toml \ + --features docs_rs,core,macros,builds,structural_renderer,repl,comp,parser,picker,clap,extra_macros \ + --open \ + -- \ + --cfg docsrs diff --git a/.run/version-files.toml b/.run/version-files.toml index ca104d2..b69a568 100644 --- a/.run/version-files.toml +++ b/.run/version-files.toml @@ -17,3 +17,7 @@ pattern = "version = \"{VER}\"" [[file]] file = "./docs/res/guide.txt" pattern = "mingling = \"{VER}\"" + +[[file]] +file = "./mingling_core/src/lib.rs" +pattern = "mingling = \"{VER}\"" diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ed528c..62ec971 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -52,6 +52,16 @@ None ### Release 0.3.0 (Unreleased) +> In detail, the changes in Mingling 0.3.0 are as follows: + +1. **Added `arg-picker`** — Mingling has never had a comfortable argument parsing solution. You either suffered with `parser` or went all-in on `clap`. So I wrote a smarter `arg-picker`. The API style is close to the original `parser`, but it's more type-safe, more robust, and more extensible. See the main text for details. + +2. **Made implicit behavior explicit**. For a long time, Mingling's attribute macros have been making **implicit** modifications to the original function — I have to admit, that's dirty. In the new version, I've removed **all** implicit modifications to the original function by attribute macros. In other words, `#[chain]`, `#[renderer]`, `#[help]`, and `#[completion]` will no longer modify your original function in any way unless you explicitly specify it. Use the Extension Attribute (`#[chain(/* ... */)]`) mechanism to explicitly inject implicit behavior into your functions. + +3. I was originally planning to remove `r_println!` because I couldn't stand that `__renderer_inner_result` thing implicitly injected by the `#[renderer]` macro. But now I've rewritten it: `#[buffer]` injects an implicit `__render_result_buffer` value into the function, and then `r_println!` calls it. It's an extra step, but it also means: the dirt is your choice, not something I'm forcing on you :) + +4. **Finally**, a philosophical point. Mingling will move forward with a preference for **"selectively dirty"** over **"invisibly, forcibly dirty"** — this is the biggest direction going forward, building a more comfortable API on this foundation. + #### Fixes: 1. **[`pathf:patterns`]** Fixed pattern detection logic in `mingling_pathf` for multiple patterns to correctly detect opening bracket forms (e.g., `[chain`, `[renderer`, `[help`, `[completion`) in addition to the previously supported closing bracket forms (e.g., `chain]`, `renderer]`, `help]`, `completion]`). This ensures that attribute macro usages like `#[chain]`, `#[renderer]`, `#[help]`, and `#[completion]` are properly detected regardless of which side of the attribute the pattern matcher examines. @@ -154,6 +164,20 @@ None _No behavioral changes — this is purely a type-system refactoring to enable `group_structural!` to work with external types. Since both traits are defined in `::mingling::__private`, this change has **no impact on the public API** — end users interact with `StructuralData` only through auto-generated derive macros and `pack_structural!`/`group_structural!` macros, which are automatically updated. Only users with manual `impl StructuralData` blocks (an advanced/rare case) need to update their code. +7. **[`core`]** **`RenderResult` now derives `Clone` and `Eq` in addition to `Default`, `Debug`, and `PartialEq`.** Added `Clone` and `Eq` derive macros to the `RenderResult` struct in `mingling_core/src/renderer/render_result.rs`. These additions enable `RenderResult` values to be explicitly cloned and support equality comparisons that are both reflexive and transitive. + + - **`Clone`** — Allows a `RenderResult` to be duplicated via `.clone()`, which is useful for scenarios where the same render output needs to be reused or stored in multiple locations. + - **`Eq`** — Enables `RenderResult` to be used in contexts that require full equivalence (e.g., `assert_eq!` with `Eq` bounds, `HashMap`/`HashSet` keys when combined with `Hash`). + + _No migration is required — these are purely additive derives that expand the type's capabilities without affecting existing behavior._ + +**[`core`]** Added the `build` feature (renamed from `builds`) to `mingling_core` and `mingling`. The old `builds` feature has been deprecated in favor of `build`, with a backward-compatibility alias retained in `mingling/Cargo.toml`: + +- **`mingling_core/Cargo.toml`**: Renamed the feature from `builds` to `build`. +- **`mingling/Cargo.toml`**: Changed the feature dependency from `mingling_core/builds` to `mingling_core/build`. A deprecated `builds` feature alias is kept as `builds = ["mingling_core/build"]` with a note indicating it will be removed in a future breaking change. + + _No behavioral changes — the `build` feature provides identical functionality to the old `builds` feature. Downstream code using `builds` continues to work via the alias, but should migrate to `build`._ + #### Features: 1. **[`core`]** Added `RenderResult::new()` method for creating a new `RenderResult` with default values (empty text and exit code 0). This provides a more explicit and discoverable constructor compared to `RenderResult::default()`, making it clearer when a fresh result is being created for use with `write!`/`writeln!`. @@ -556,6 +580,41 @@ None All examples and internal usages have been updated across the codebase to reflect these changes (e.g., `repl_basic_setup` now calls `println!("{}", r.result)` instead of `println!("{}", r.result.trim())`, since `Display` no longer adds a trailing newline). +7. **[`any`]** **[`macros`]** Made `AnyOutput`'s `type_id` and `member_id` fields private (`pub(crate)`) and added public accessor methods `type_id()` and `member_id()`. Added the `unsafe fn new_bare<T>(value: T, member_id: G) -> Self` constructor that bypasses the `Grouped` trait, allowing manual specification of `member_id` without requiring the concrete type to implement `Grouped`. + + - **`type_id`** field changed from `pub` to `pub(crate)` — accessible via `type_id()` accessor. + - **`member_id`** field changed from `pub` to `pub(crate)` — accessible via `member_id()` accessor (requires `G: Copy`). + - **`new_bare`** — Unsafe constructor that takes a raw `member_id` value without invoking `Grouped::member_id()`. The caller must ensure the provided `member_id` correctly corresponds to the concrete type `T`. + - Updated all internal `match any.member_id { ... }` patterns in `gen_program.rs` to use `match any.member_id() { ... }` instead. + - Updated the panic message in `do_chain` (both sync and async) from `any.type_id` to `any.type_id()`. + - Updated the example-hook `main.rs` to call `info.output.member_id()` instead of accessing `info.output.member_id` directly. + - Added `Copy` derive to the generated enum to enable `member_id()`'s `Copy` requirement on the enum type. + + _No behavioral changes for existing code — the accessor methods provide the same values as the previously-public fields._ + +8. **[`any`]** **[`macros`]** **[BREAKING]** Marked `Grouped` trait as `unsafe trait`. The `Grouped` trait has always been inherently unsafe — the `member_id()` return value must exactly correspond to the variant registered by `register_type!` for the concrete type, otherwise dispatching on that type will result in **undefined behavior**. This unsoundness has existed since the trait's inception but was previously unenforced at the type system level. + + By making `Grouped` an `unsafe trait`, implementors must now explicitly acknowledge this safety contract with `unsafe impl Grouped<...> for ...`. This change makes the existing safety invariant visible to developers and enables soundness warnings at compile time. + + **Changes made:** + + - **`Grouped` trait** in `mingling_core/src/any/group.rs` changed from `pub trait Grouped<Group>` to `pub unsafe trait Grouped<Group>`, with a safety doc comment explaining that manually implementing the trait with an incorrect `member_id` leads to undefined behavior. + + - **Derive macros** (`#[derive(Grouped)]`, `#[derive(GroupedSerialize)]`) now generate `unsafe impl` instead of `impl`, with a SAFETY comment stating that the derive macro guarantees correctness because the `Ident` used in `register_type!` matches the `Ident` returned by `member_id()`. + + - **`pack!`, `pack_structural!`, `group!`, `group_structural!`** macros now generate `unsafe impl` instead of `impl`, with analogous SAFETY comments. + + - **All manual test implementations** of `Grouped` across the codebase (in `any.rs` tests, `hook.rs` tests, `mock.rs`) updated to `unsafe impl` with SAFETY comments explaining why they are safe in their test contexts. + + - **`MockProgramCollect::member_id()`** changed from `MockProgramCollect::Foo` to `panic!("Attempting to read an unsafe enum type")` to prevent accidental execution in production paths. + + **Migration guide:** + + - Existing code that uses `Grouped` only through the derive macro or `pack!`/`group!` macros is automatically migrated — no changes needed. + - Code with **manual** `impl Grouped<...> for ...` blocks must add `unsafe` before `impl` and verify that the `member_id()` return value correctly corresponds to the type's registered variant. Only proceed if the correspondence is guaranteed. + + _This is a breaking change only for code with manual `Grouped` implementations._ + --- ## Release 0.2.2 (2026-07-10) @@ -270,6 +270,10 @@ dependencies = [ ] [[package]] +name = "mingling-workspace" +version = "0.3.0" + +[[package]] name = "mingling_core" version = "0.3.0" dependencies = [ @@ -77,3 +77,9 @@ lto = "fat" codegen-units = 1 panic = "abort" strip = true + +[package] +name = "mingling-workspace" +version.workspace = true +edition.workspace = true +publish = false diff --git a/GETTING_STARTED.md b/GETTING_STARTED.md index 1aad8d5..516122e 100644 --- a/GETTING_STARTED.md +++ b/GETTING_STARTED.md @@ -239,7 +239,7 @@ In your `build.rs`, generate the shell scripts: ```rust // BUILD TIME -// Features: ["comp", "builds"] +// Features: ["comp", "build"] mingling::build::build_comp_scripts(env!("CARGO_PKG_NAME")).unwrap(); ``` @@ -551,7 +551,7 @@ fn main() { .on_pre_chain(|info| { println!("[DEBUG] Pre chain: {}", info.input); }) - .on_post_chain(|info| println!("[DEBUG] Post chain: {}", info.output.member_id)) + .on_post_chain(|info| println!("[DEBUG] Post chain: {}", info.output.member_id())) .on_finish(|_| { println!("[DEBUG] Loop end"); ProgramControlUnit::OverrideExitCode(0) // Override exit code diff --git a/arg_picker/Cargo.toml b/arg_picker/Cargo.toml index d87a844..ac4041d 100644 --- a/arg_picker/Cargo.toml +++ b/arg_picker/Cargo.toml @@ -3,7 +3,7 @@ name = "arg-picker" version = "0.1.0" edition = "2024" license = "MIT OR Apache-2.0" -repository = "https://github.com/mingling-rs/mingling/tree/main/arg-picker" +repository = "https://github.com/mingling-rs/mingling/tree/main/arg_picker" authors = ["Weicao-CatilGrass"] readme = "README.md" description = "A lightweight, type-safe CLI argument parser" diff --git a/arg_picker_macros/Cargo.toml b/arg_picker_macros/Cargo.toml index c83cb9e..c329274 100644 --- a/arg_picker_macros/Cargo.toml +++ b/arg_picker_macros/Cargo.toml @@ -3,7 +3,7 @@ name = "arg-picker-macros" version = "0.1.0" edition = "2024" license = "MIT OR Apache-2.0" -repository = "https://github.com/mingling-rs/mingling/tree/main/arg-picker" +repository = "https://github.com/mingling-rs/mingling/tree/main/arg_picker" authors = ["Weicao-CatilGrass"] readme = "README.md" description = "Mingling's lightweight argument parser macros" diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..951ecf8 --- /dev/null +++ b/build.rs @@ -0,0 +1,13 @@ +use std::{env::current_dir, fs}; + +fn main() { + gen_fake_cargo_toml_in_temp_dir(); +} + +fn gen_fake_cargo_toml_in_temp_dir() { + fs::write( + current_dir().unwrap().join(".temp").join("Cargo.toml"), + "[workspace]", + ) + .unwrap(); +} diff --git a/docs/_zh_CN/pages/13-hook.md b/docs/_zh_CN/pages/13-hook.md index 6d6018a..ad3008a 100644 --- a/docs/_zh_CN/pages/13-hook.md +++ b/docs/_zh_CN/pages/13-hook.md @@ -71,7 +71,7 @@ fn main() { eprintln!("[hook] executing chain for: {}", info.input); }) .on_post_chain(|info| { - eprintln!("[hook] chain output: {}", info.output.member_id); + eprintln!("[hook] chain output: {}", info.output.member_id()); }), ); diff --git a/docs/_zh_CN/pages/advanced/1-completion.md b/docs/_zh_CN/pages/advanced/1-completion.md index 3941404..a6500db 100644 --- a/docs/_zh_CN/pages/advanced/1-completion.md +++ b/docs/_zh_CN/pages/advanced/1-completion.md @@ -15,8 +15,8 @@ features = ["comp"] [build-dependencies.mingling] features = [ "comp", - # 启用 `builds` 特性以提供构建期支持 - "builds" + # 启用 `build` 特性以提供构建期支持 + "build" ] ``` diff --git a/docs/pages/13-hook.md b/docs/pages/13-hook.md index 26f8712..d927a9c 100644 --- a/docs/pages/13-hook.md +++ b/docs/pages/13-hook.md @@ -71,7 +71,7 @@ fn main() { eprintln!("[hook] executing chain for: {}", info.input); }) .on_post_chain(|info| { - eprintln!("[hook] chain output: {}", info.output.member_id); + eprintln!("[hook] chain output: {}", info.output.member_id()); }), ); diff --git a/docs/pages/advanced/1-completion.md b/docs/pages/advanced/1-completion.md index a90c3ce..52350db 100644 --- a/docs/pages/advanced/1-completion.md +++ b/docs/pages/advanced/1-completion.md @@ -15,8 +15,8 @@ features = ["comp"] [build-dependencies.mingling] features = [ "comp", - # Enable `builds` for build-time support - "builds" + # Enable `build` for build-time support + "build" ] ``` diff --git a/examples/example-completion/Cargo.toml b/examples/example-completion/Cargo.toml index 1c6b327..00c7f8c 100644 --- a/examples/example-completion/Cargo.toml +++ b/examples/example-completion/Cargo.toml @@ -20,8 +20,8 @@ features = [ "comp", # If you want to build completion scripts, - # enable `builds` features - "builds", + # enable `build` features + "build", ] [workspace] diff --git a/examples/example-completion/src/main.rs b/examples/example-completion/src/main.rs index 45cc8ef..d65be49 100644 --- a/examples/example-completion/src/main.rs +++ b/examples/example-completion/src/main.rs @@ -7,7 +7,7 @@ //! To make your completions work, you need to generate a completion script using Mingling's tools //! //! 1. Enable features -//! You need to enable the `builds` and `comp` features for `mingling` in `[build-dependencies]` +//! You need to enable the `build` and `comp` features for `mingling` in `[build-dependencies]` //! //! 2. Write `build.rs` //! Write the following in `build.rs` diff --git a/examples/example-hook/src/main.rs b/examples/example-hook/src/main.rs index 23b87c7..da92045 100644 --- a/examples/example-hook/src/main.rs +++ b/examples/example-hook/src/main.rs @@ -40,7 +40,7 @@ fn main() { .on_pre_chain(|info| { println!("[DEBUG] Pre chain: {}", info.input); }) - .on_post_chain(|info| println!("[DEBUG] Post chain: {}", info.output.member_id)) + .on_post_chain(|info| println!("[DEBUG] Post chain: {}", info.output.member_id())) .on_finish(|_| { println!("[DEBUG] Loop end"); ProgramControlUnit::OverrideExitCode(0) // Override exit code diff --git a/examples/example-pathfinder/Cargo.toml b/examples/example-pathfinder/Cargo.toml index bd362f2..bc41bd2 100644 --- a/examples/example-pathfinder/Cargo.toml +++ b/examples/example-pathfinder/Cargo.toml @@ -18,8 +18,8 @@ features = [ # Enable `pathf` features "pathf", - # Enable the `builds` feature for build-time support - "builds", + # Enable the `build` feature for build-time support + "build", ] [workspace] diff --git a/mingling/Cargo.toml b/mingling/Cargo.toml index 351384f..9bf82a7 100644 --- a/mingling/Cargo.toml +++ b/mingling/Cargo.toml @@ -43,7 +43,8 @@ macros = ["dep:mingling_macros", "mingling_macros/default"] nightly = ["mingling_core/nightly", "mingling_macros/nightly"] debug = ["mingling_core/debug"] async = ["mingling_core/async", "mingling_macros/async"] -builds = ["mingling_core/builds"] + +build = ["mingling_core/build"] default = ["core", "macros"] @@ -92,6 +93,9 @@ extra_macros = ["mingling_macros/extra_macros"] # Section only shown in docs.rs docs_rs = [] +# This is an old name, will be Breaking Change in the future +builds = ["mingling_core/build"] + [dependencies] mingling_core = { workspace = true, optional = true } mingling_macros = { workspace = true, optional = true } diff --git a/mingling/src/example_docs.rs b/mingling/src/example_docs.rs index 3b36f00..35cf024 100644 --- a/mingling/src/example_docs.rs +++ b/mingling/src/example_docs.rs @@ -777,7 +777,7 @@ pub mod example_combine_pathf_dispatch_tree {} /// To make your completions work, you need to generate a completion script using Mingling's tools /// /// 1. Enable features -/// You need to enable the `builds` and `comp` features for `mingling` in `[build-dependencies]` +/// You need to enable the `build` and `comp` features for `mingling` in `[build-dependencies]` /// /// 2. Write `build.rs` /// Write the following in `build.rs` @@ -838,8 +838,8 @@ pub mod example_combine_pathf_dispatch_tree {} /// "comp", /// /// # If you want to build completion scripts, -/// # enable `builds` features -/// "builds", +/// # enable `build` features +/// "build", /// ] /// /// [workspace] @@ -1649,7 +1649,7 @@ pub mod example_help {} /// .on_pre_chain(|info| { /// println!("[DEBUG] Pre chain: {}", info.input); /// }) -/// .on_post_chain(|info| println!("[DEBUG] Post chain: {}", info.output.member_id)) +/// .on_post_chain(|info| println!("[DEBUG] Post chain: {}", info.output.member_id())) /// .on_finish(|_| { /// println!("[DEBUG] Loop end"); /// ProgramControlUnit::OverrideExitCode(0) // Override exit code @@ -2268,8 +2268,8 @@ pub mod example_panic_unwind {} /// # Enable `pathf` features /// "pathf", /// -/// # Enable the `builds` feature for build-time support -/// "builds", +/// # Enable the `build` feature for build-time support +/// "build", /// ] /// /// [workspace] diff --git a/mingling/src/features.rs b/mingling/src/features.rs index 78d6226..0dde333 100644 --- a/mingling/src/features.rs +++ b/mingling/src/features.rs @@ -20,6 +20,17 @@ pub const MINGLING_ASYNC: bool = false; #[cfg(feature = "async")] #[allow(unused)] pub const MINGLING_ASYNC: bool = true; +/// Whether the `build` feature is enabled +/// Current: `disabled` +#[cfg(not(feature = "build"))] +#[allow(unused)] +pub const MINGLING_BUILD: bool = false; + +/// Whether the `build` feature is enabled +/// Current: `enabled` +#[cfg(feature = "build")] +#[allow(unused)] +pub const MINGLING_BUILD: bool = true; /// Whether the `builds` feature is enabled /// Current: `disabled` #[cfg(not(feature = "builds"))] diff --git a/mingling/src/lib.rs b/mingling/src/lib.rs index fd274aa..19f2188 100644 --- a/mingling/src/lib.rs +++ b/mingling/src/lib.rs @@ -4,6 +4,7 @@ )] #![deny(missing_docs)] #![doc = include_str!("lib.md")] +#![cfg_attr(docsrs, feature(doc_cfg))] #[cfg(feature = "core")] mod example_docs; diff --git a/mingling_cli/Cargo.lock b/mingling_cli/Cargo.lock index e617d0a..ae28d9a 100644 --- a/mingling_cli/Cargo.lock +++ b/mingling_cli/Cargo.lock @@ -155,6 +155,12 @@ dependencies = [ ] [[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] name = "errno" version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -171,12 +177,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] name = "ident_case" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] name = "itoa" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -259,6 +281,7 @@ dependencies = [ "serde_json", "syn 3.0.2", "tokio", + "toml_edit", ] [[package]] @@ -517,6 +540,43 @@ dependencies = [ ] [[package]] +name = "toml_datetime" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.25.13+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6975367e4d2ef766d86af01ffad14b622fecc8d4357a998fbc4deb6e9bacaf9b" +dependencies = [ + "indexmap", + "toml_datetime", + "toml_parser", + "toml_writer", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" +dependencies = [ + "winnow", +] + +[[package]] +name = "toml_writer" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2" + +[[package]] name = "unicode-ident" version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -550,6 +610,15 @@ dependencies = [ ] [[package]] +name = "winnow" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" +dependencies = [ + "memchr", +] + +[[package]] name = "zmij" version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" diff --git a/mingling_cli/Cargo.toml b/mingling_cli/Cargo.toml index 5b36c6b..330ef5a 100644 --- a/mingling_cli/Cargo.toml +++ b/mingling_cli/Cargo.toml @@ -13,6 +13,10 @@ categories = ["command-line-interface"] [[bin]] name = "mling" +path = "src/bin/wrapper.rs" + +[[bin]] +name = "mingling-cli" path = "src/main.rs" [dependencies.mingling] @@ -27,7 +31,7 @@ features = [ [build-dependencies.mingling] path = "../mingling" features = [ - "builds", + "build", "pathf", ] @@ -48,6 +52,7 @@ serde_json = "1.0.151" # Parallelism tokio = { version = "1.53.1", features = ["full"] } +toml_edit = "0.25.13" [build-dependencies] # Configure & Serialization @@ -57,4 +62,10 @@ serde_json = "1.0.151" # Code gen just_template = "0.2.0" +[profile.release] +opt-level = "z" +lto = true +codegen-units = 1 +strip = true + [workspace] diff --git a/mingling_cli/README.md b/mingling_cli/README.md new file mode 100644 index 0000000..419534b --- /dev/null +++ b/mingling_cli/README.md @@ -0,0 +1,2 @@ +> [!WARNING] +> `mling`, as the scaffolding tool for Mingling, is currently under development. For completed features, please refer to the Roadmap section in the Mingling README.md. diff --git a/mingling_cli/expand.rs b/mingling_cli/expand.rs deleted file mode 100644 index 2e8bc4f..0000000 --- a/mingling_cli/expand.rs +++ /dev/null @@ -1,1121 +0,0 @@ -#![feature(prelude_import)] -extern crate std; -#[prelude_import] -use std::prelude::rust_2024::*; -use crate::{linter::MinglingLinterSetup, metadata::MinglingMetadataSetup}; -use mingling::{ - macros::gen_program, - setup::{ExitCodeSetup, picker::{HelpFlagSetup, StructuralRendererSetup}}, -}; -pub mod errors { - pub mod serde_json { - use mingling::macros::{buffer, group, r_println, renderer}; - pub type ErrorSerdeJson = serde_json::Error; - #[allow(non_camel_case_types)] - mod internal_group_serde_json_error { - use crate::ThisProgram as __MinglingProgram; - #[allow(unused_imports)] - use serde_json::Error; - use super::ErrorSerdeJson; - impl ::mingling::Grouped<__MinglingProgram> for ErrorSerdeJson { - fn member_id() -> __MinglingProgram { - __MinglingProgram::ErrorSerdeJson - } - } - } - #[doc(hidden)] - #[allow(non_camel_case_types)] - pub struct __internal_renderer_render_error_serde_json; - impl ::mingling::Renderer for __internal_renderer_render_error_serde_json { - type Previous = ErrorSerdeJson; - fn render(prev: Self::Previous) -> ::mingling::RenderResult { - let __renderer_result = { render_error_serde_json(prev) }; - ::std::convert::Into::into(__renderer_result) - } - } - pub fn render_error_serde_json( - _err: ErrorSerdeJson, - ) -> ::mingling::RenderResult { - let mut __render_result_buffer = ::mingling::RenderResult::new(); - { - __render_result_buffer - .println( - ::alloc::__export::must_use({ - ::alloc::fmt::format(format_args!("serde")) - }), - ); - } - __render_result_buffer - } - } -} -pub mod linter { - use mingling::{Program, macros::program_setup}; - use crate::linter::cmd_mlint::CMDMinglingLinter; - pub mod cmd_mlint { - use mingling::macros::{chain, dispatcher}; - pub struct CMDMinglingLinter; - #[automatically_derived] - impl ::core::fmt::Debug for CMDMinglingLinter { - #[inline] - fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { - ::core::fmt::Formatter::write_str(f, "CMDMinglingLinter") - } - } - #[automatically_derived] - impl ::core::default::Default for CMDMinglingLinter { - #[inline] - fn default() -> CMDMinglingLinter { - CMDMinglingLinter {} - } - } - pub struct EntryMinglingLinter { - pub(crate) inner: Vec<String>, - } - impl EntryMinglingLinter { - /// Creates a new instance of the wrapper type - pub fn new(inner: Vec<String>) -> Self { - Self { inner } - } - } - impl From<Vec<String>> for EntryMinglingLinter { - fn from(inner: Vec<String>) -> Self { - Self::new(inner) - } - } - impl From<EntryMinglingLinter> for Vec<String> { - fn from(wrapper: EntryMinglingLinter) -> Vec<String> { - wrapper.inner - } - } - impl ::std::convert::AsRef<Vec<String>> for EntryMinglingLinter { - fn as_ref(&self) -> &Vec<String> { - &self.inner - } - } - impl ::std::convert::AsMut<Vec<String>> for EntryMinglingLinter { - fn as_mut(&mut self) -> &mut Vec<String> { - &mut self.inner - } - } - impl ::std::ops::Deref for EntryMinglingLinter { - type Target = Vec<String>; - fn deref(&self) -> &Self::Target { - &self.inner - } - } - impl ::std::ops::DerefMut for EntryMinglingLinter { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.inner - } - } - impl ::std::default::Default for EntryMinglingLinter - where - Vec<String>: ::std::default::Default, - { - fn default() -> Self { - Self::new(::std::default::Default::default()) - } - } - impl Into<mingling::AnyOutput<crate::ThisProgram>> for EntryMinglingLinter { - fn into(self) -> mingling::AnyOutput<crate::ThisProgram> { - mingling::AnyOutput::new(self) - } - } - impl Into<mingling::ChainProcess<crate::ThisProgram>> for EntryMinglingLinter { - fn into(self) -> mingling::ChainProcess<crate::ThisProgram> { - mingling::AnyOutput::new(self).route_chain() - } - } - impl ::mingling::Grouped<crate::ThisProgram> for EntryMinglingLinter { - fn member_id() -> crate::ThisProgram { - crate::ThisProgram::EntryMinglingLinter - } - } - impl ::mingling::Dispatcher<crate::ThisProgram> for CMDMinglingLinter { - fn node(&self) -> ::mingling::Node { - mingling::Node::default().join("mlint") - } - fn begin( - &self, - args: Vec<String>, - ) -> ::mingling::ChainProcess<crate::ThisProgram> { - use ::mingling::Grouped; - ::mingling::Routable::to_chain(EntryMinglingLinter::new(args)) - } - fn clone_dispatcher( - &self, - ) -> Box<dyn ::mingling::Dispatcher<crate::ThisProgram>> { - Box::new(CMDMinglingLinter) - } - } - #[doc(hidden)] - #[allow(non_camel_case_types)] - pub struct __internal_chain_handle_mlint; - impl ::mingling::Chain<crate::ThisProgram> for __internal_chain_handle_mlint { - type Previous = EntryMinglingLinter; - fn proc( - prev: EntryMinglingLinter, - ) -> ::mingling::ChainProcess<crate::ThisProgram> { - handle_mlint(prev); - <crate::ResultEmpty as ::mingling::Routable< - crate::ThisProgram, - >>::to_chain(crate::ResultEmpty) - } - } - pub fn handle_mlint(_args: EntryMinglingLinter) {} - } - #[doc(hidden)] - pub struct MinglingLinterSetup; - impl ::mingling::setup::ProgramSetup<crate::ThisProgram> for MinglingLinterSetup { - fn setup(self, program: &mut ::mingling::Program<crate::ThisProgram>) { - mingling_linter_setup(program); - } - } - pub fn mingling_linter_setup(program: &mut Program<crate::ThisProgram>) { - { - program.with_setup(MinglingLinterCommandSetup); - } - } - #[doc(hidden)] - pub struct MinglingLinterCommandSetup; - impl ::mingling::setup::ProgramSetup<crate::ThisProgram> - for MinglingLinterCommandSetup { - fn setup(self, program: &mut ::mingling::Program<crate::ThisProgram>) { - mingling_linter_command_setup(program); - } - } - pub fn mingling_linter_command_setup(program: &mut Program<crate::ThisProgram>) { - { - program.with_dispatcher(CMDMinglingLinter); - } - } -} -pub mod metadata { - use cargo_metadata::Metadata; - use mingling::{ - AnyOutput, Program, ProgramCollect, RenderResult, - macros::{buffer, group_structural, program_setup, r_println, renderer, routeify}, - }; - use crate::metadata::{cmd_metadata::CMDMetadata, setup::CargoMetadataSetup}; - pub mod cmd_metadata { - use cargo_metadata::Metadata; - use mingling::{LazyRes, macros::{chain, dispatcher, pack_structural}}; - use crate::metadata::setup::ResMetadata; - pub struct CMDMetadata; - #[automatically_derived] - impl ::core::fmt::Debug for CMDMetadata { - #[inline] - fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { - ::core::fmt::Formatter::write_str(f, "CMDMetadata") - } - } - #[automatically_derived] - impl ::core::default::Default for CMDMetadata { - #[inline] - fn default() -> CMDMetadata { - CMDMetadata {} - } - } - pub struct EntryMetadata { - pub(crate) inner: Vec<String>, - } - impl EntryMetadata { - /// Creates a new instance of the wrapper type - pub fn new(inner: Vec<String>) -> Self { - Self { inner } - } - } - impl From<Vec<String>> for EntryMetadata { - fn from(inner: Vec<String>) -> Self { - Self::new(inner) - } - } - impl From<EntryMetadata> for Vec<String> { - fn from(wrapper: EntryMetadata) -> Vec<String> { - wrapper.inner - } - } - impl ::std::convert::AsRef<Vec<String>> for EntryMetadata { - fn as_ref(&self) -> &Vec<String> { - &self.inner - } - } - impl ::std::convert::AsMut<Vec<String>> for EntryMetadata { - fn as_mut(&mut self) -> &mut Vec<String> { - &mut self.inner - } - } - impl ::std::ops::Deref for EntryMetadata { - type Target = Vec<String>; - fn deref(&self) -> &Self::Target { - &self.inner - } - } - impl ::std::ops::DerefMut for EntryMetadata { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.inner - } - } - impl ::std::default::Default for EntryMetadata - where - Vec<String>: ::std::default::Default, - { - fn default() -> Self { - Self::new(::std::default::Default::default()) - } - } - impl Into<mingling::AnyOutput<crate::ThisProgram>> for EntryMetadata { - fn into(self) -> mingling::AnyOutput<crate::ThisProgram> { - mingling::AnyOutput::new(self) - } - } - impl Into<mingling::ChainProcess<crate::ThisProgram>> for EntryMetadata { - fn into(self) -> mingling::ChainProcess<crate::ThisProgram> { - mingling::AnyOutput::new(self).route_chain() - } - } - impl ::mingling::Grouped<crate::ThisProgram> for EntryMetadata { - fn member_id() -> crate::ThisProgram { - crate::ThisProgram::EntryMetadata - } - } - impl ::mingling::Dispatcher<crate::ThisProgram> for CMDMetadata { - fn node(&self) -> ::mingling::Node { - mingling::Node::default().join("metadata") - } - fn begin( - &self, - args: Vec<String>, - ) -> ::mingling::ChainProcess<crate::ThisProgram> { - use ::mingling::Grouped; - ::mingling::Routable::to_chain(EntryMetadata::new(args)) - } - fn clone_dispatcher( - &self, - ) -> Box<dyn ::mingling::Dispatcher<crate::ThisProgram>> { - Box::new(CMDMetadata) - } - } - pub struct ResultMetadata { - pub inner: ResMetadata, - } - #[doc(hidden)] - #[allow( - non_upper_case_globals, - unused_attributes, - unused_qualifications, - clippy::absolute_paths, - )] - const _: () = { - #[allow(unused_extern_crates, clippy::useless_attribute)] - extern crate serde as _serde; - #[automatically_derived] - impl _serde::Serialize for ResultMetadata { - fn serialize<__S>( - &self, - __serializer: __S, - ) -> _serde::__private229::Result<__S::Ok, __S::Error> - where - __S: _serde::Serializer, - { - let mut __serde_state = _serde::Serializer::serialize_struct( - __serializer, - "ResultMetadata", - false as usize + 1, - )?; - _serde::ser::SerializeStruct::serialize_field( - &mut __serde_state, - "inner", - &self.inner, - )?; - _serde::ser::SerializeStruct::end(__serde_state) - } - } - }; - impl ResultMetadata { - pub fn new(inner: ResMetadata) -> Self { - Self { inner } - } - } - impl From<ResMetadata> for ResultMetadata { - fn from(inner: ResMetadata) -> Self { - Self::new(inner) - } - } - impl From<ResultMetadata> for ResMetadata { - fn from(wrapper: ResultMetadata) -> ResMetadata { - wrapper.inner - } - } - impl ::std::convert::AsRef<ResMetadata> for ResultMetadata { - fn as_ref(&self) -> &ResMetadata { - &self.inner - } - } - impl ::std::convert::AsMut<ResMetadata> for ResultMetadata { - fn as_mut(&mut self) -> &mut ResMetadata { - &mut self.inner - } - } - impl ::std::ops::Deref for ResultMetadata { - type Target = ResMetadata; - fn deref(&self) -> &Self::Target { - &self.inner - } - } - impl ::std::ops::DerefMut for ResultMetadata { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.inner - } - } - impl ::std::default::Default for ResultMetadata - where - ResMetadata: ::std::default::Default, - { - fn default() -> Self { - Self::new(::std::default::Default::default()) - } - } - impl ::mingling::__private::StructuralDataSealed<crate::ThisProgram> - for ResultMetadata {} - impl ::mingling::__private::StructuralData<crate::ThisProgram> - for ResultMetadata {} - impl Into<::mingling::AnyOutput<crate::ThisProgram>> for ResultMetadata { - fn into(self) -> ::mingling::AnyOutput<crate::ThisProgram> { - ::mingling::AnyOutput::new(self) - } - } - impl Into<::mingling::ChainProcess<crate::ThisProgram>> for ResultMetadata { - fn into(self) -> ::mingling::ChainProcess<crate::ThisProgram> { - ::mingling::AnyOutput::new(self).route_chain() - } - } - impl ::mingling::Grouped<crate::ThisProgram> for ResultMetadata { - fn member_id() -> crate::ThisProgram { - crate::ThisProgram::ResultMetadata - } - } - #[doc(hidden)] - #[allow(non_camel_case_types)] - pub struct __internal_chain_handle_metadata; - impl ::mingling::Chain<crate::ThisProgram> for __internal_chain_handle_metadata { - type Previous = EntryMetadata; - fn proc( - prev: EntryMetadata, - ) -> ::mingling::ChainProcess<crate::ThisProgram> { - let __chain_result = { - ::mingling::this::<crate::ThisProgram>() - .__modify_res_and_return_route(| - metadata: &mut LazyRes<ResMetadata>| - { - ::mingling::Routable::< - crate::ThisProgram, - >::to_chain(handle_metadata(prev, metadata)) - }) - }; - ::mingling::Routable::<crate::ThisProgram>::to_chain(__chain_result) - } - } - pub fn handle_metadata( - _: EntryMetadata, - metadata: &mut LazyRes<ResMetadata>, - ) -> Metadata { - let metadata = metadata.get_ref().clone(); - metadata.data().clone() - } - } - pub mod setup { - use std::{env::current_dir, path::PathBuf}; - use cargo_metadata::{CargoOpt, MetadataCommand}; - use mingling::{ - LazyInit, Program, consts::REMAINS, macros::program_setup, - picker::{IntoPicker, value::Flag}, - prelude::*, res::ResCurrentDir, - }; - /// Name of Cargo manifest file - pub const CARGO_TOML: &str = "Cargo.toml"; - use cargo_metadata::Metadata; - use serde::Serialize; - pub struct ResMetadata { - data: Option<Metadata>, - } - #[automatically_derived] - impl ::core::default::Default for ResMetadata { - #[inline] - fn default() -> ResMetadata { - ResMetadata { - data: ::core::default::Default::default(), - } - } - } - #[automatically_derived] - impl ::core::clone::Clone for ResMetadata { - #[inline] - fn clone(&self) -> ResMetadata { - ResMetadata { - data: ::core::clone::Clone::clone(&self.data), - } - } - } - #[doc(hidden)] - #[allow( - non_upper_case_globals, - unused_attributes, - unused_qualifications, - clippy::absolute_paths, - )] - const _: () = { - #[allow(unused_extern_crates, clippy::useless_attribute)] - extern crate serde as _serde; - #[automatically_derived] - impl _serde::Serialize for ResMetadata { - fn serialize<__S>( - &self, - __serializer: __S, - ) -> _serde::__private229::Result<__S::Ok, __S::Error> - where - __S: _serde::Serializer, - { - let mut __serde_state = _serde::Serializer::serialize_struct( - __serializer, - "ResMetadata", - false as usize + 1, - )?; - _serde::ser::SerializeStruct::serialize_field( - &mut __serde_state, - "data", - &self.data, - )?; - _serde::ser::SerializeStruct::end(__serde_state) - } - } - }; - impl ResMetadata { - /// Returns a reference to the parsed `cargo metadata`. - /// - /// # Panics - /// - /// This function does **not** panic in practice, because `ResMetadata` is - /// always initialized via [`LazyInit`] inside the program setup, and the - /// initialization either succeeds (setting `data` to `Some(...)`) or fails - /// by propagating the error from `cmd.exec().unwrap()`. Therefore, by the - /// time this getter is called, `self.data` is guaranteed to be `Some`. - pub fn data(&self) -> &Metadata { - self.data.as_ref().unwrap() - } - } - #[doc(hidden)] - pub struct CargoMetadataSetup; - impl ::mingling::setup::ProgramSetup<crate::ThisProgram> for CargoMetadataSetup { - fn setup(self, program: &mut ::mingling::Program<crate::ThisProgram>) { - cargo_metadata_setup(program); - } - } - pub fn cargo_metadata_setup(program: &mut Program<crate::ThisProgram>) { - { - let args = program.take_args(); - let ( - feature_args, - manifest_path, - message_format, - enable_all_features, - no_default_features, - no_deps, - args, - ) = args - .pick( - &::mingling::picker::PickerArg::<Vec<String>> { - full: &["features"], - short: ::std::option::Option::None, - positional: false, - internal_type: ::std::marker::PhantomData, - }, - ) - .pick( - &::mingling::picker::PickerArg::<Option<String>> { - full: &["manifest_path"], - short: ::std::option::Option::None, - positional: false, - internal_type: ::std::marker::PhantomData, - }, - ) - .pick_or( - &::mingling::picker::PickerArg::<String> { - full: &["message_format"], - short: ::std::option::Option::None, - positional: false, - internal_type: ::std::marker::PhantomData, - }, - || "disable".to_string(), - ) - .pick( - &::mingling::picker::PickerArg::<Flag> { - full: &["all_features"], - short: ::std::option::Option::None, - positional: false, - internal_type: ::std::marker::PhantomData, - }, - ) - .pick( - &::mingling::picker::PickerArg::<Flag> { - full: &["no_default_features"], - short: ::std::option::Option::None, - positional: false, - internal_type: ::std::marker::PhantomData, - }, - ) - .pick( - &::mingling::picker::PickerArg::<Flag> { - full: &["no_deps"], - short: ::std::option::Option::None, - positional: false, - internal_type: ::std::marker::PhantomData, - }, - ) - .pick(&REMAINS) - .unwrap(); - program.replace_args(args.into()); - program.structural_renderer_name = message_format.into(); - let current_dir = current_dir().unwrap(); - let feature_args_leaked: &[String] = Box::leak( - feature_args.into_boxed_slice(), - ); - let manifest_path_clone = manifest_path.clone(); - let current_dir_clone = current_dir.clone(); - program - .with_resource( - ResMetadata::lazy_init(move || { - let metadata_path = match manifest_path_clone { - Some(ref path_str) => PathBuf::from(path_str), - None => find_manifest().unwrap(), - }; - let mut cmd = MetadataCommand::new(); - cmd.manifest_path(metadata_path) - .current_dir(¤t_dir_clone); - if *enable_all_features { - cmd.features(CargoOpt::AllFeatures); - } - if *no_default_features { - cmd.features(CargoOpt::NoDefaultFeatures); - } - if *no_deps { - cmd.no_deps(); - } - cmd.features( - CargoOpt::SomeFeatures(feature_args_leaked.to_vec()), - ); - ResMetadata { - data: Some(cmd.exec().unwrap()), - } - }), - ); - program.with_resource(ResCurrentDir::from(current_dir)); - } - } - /// Find `Cargo.toml` by searching upward from `current_dir`. - fn find_manifest() -> Option<PathBuf> { - let mut dir = current_dir().ok()?; - loop { - let candidate = dir.join(CARGO_TOML); - if candidate.is_file() { - return Some(candidate); - } - if !dir.pop() { - return None; - } - } - } - } - #[doc(hidden)] - pub struct MinglingMetadataSetup; - impl ::mingling::setup::ProgramSetup<crate::ThisProgram> for MinglingMetadataSetup { - fn setup(self, program: &mut ::mingling::Program<crate::ThisProgram>) { - mingling_metadata_setup(program); - } - } - pub fn mingling_metadata_setup(program: &mut Program<crate::ThisProgram>) { - { - program.with_setup(CargoMetadataSetup); - program.with_dispatcher(CMDMetadata); - } - } - #[allow(non_camel_case_types)] - mod internal_group_metadata { - use crate::ThisProgram as __MinglingProgram; - #[allow(unused_imports)] - use super::Metadata; - impl ::mingling::Grouped<__MinglingProgram> for Metadata { - fn member_id() -> __MinglingProgram { - __MinglingProgram::Metadata - } - } - impl ::mingling::__private::StructuralDataSealed<crate::ThisProgram> - for Metadata {} - impl ::mingling::__private::StructuralData<crate::ThisProgram> for Metadata {} - } - #[doc(hidden)] - #[allow(non_camel_case_types)] - pub struct __internal_renderer_render_metadata; - impl ::mingling::Renderer for __internal_renderer_render_metadata { - type Previous = Metadata; - fn render(prev: Self::Previous) -> ::mingling::RenderResult { - let __renderer_result = { render_metadata(prev) }; - ::std::convert::Into::into(__renderer_result) - } - } - pub fn render_metadata(metadata: Metadata) -> RenderResult { - let mut r = RenderResult::new(); - r.println( - ::alloc::__export::must_use({ - ::alloc::fmt::format( - format_args!("{0}", serde_json::to_string(&metadata)?), - ) - }), - ); - r - } -} -fn main() { - let mut program = ThisProgram::new(); - program.with_setup(HelpFlagSetup::default()); - program.with_setup(ExitCodeSetup::default()); - program.with_setup(StructuralRendererSetup); - program.with_setup(MinglingMetadataSetup); - program.with_setup(MinglingLinterSetup); - program.exec_and_exit(); -} -/// Alias for the current program type `crate::ThisProgram` -pub type Next = ::mingling::ChainProcess<crate::ThisProgram>; -impl ::mingling::Routable<crate::ThisProgram> -for ::mingling::ChainProcess<crate::ThisProgram> { - fn to_chain(self) -> ::mingling::ChainProcess<crate::ThisProgram> { - match self { - ::mingling::ChainProcess::Ok((any, _)) => { - ::mingling::ChainProcess::Ok((any, mingling::NextProcess::Chain)) - } - other => other, - } - } - fn to_render(self) -> ::mingling::ChainProcess<crate::ThisProgram> { - match self { - ::mingling::ChainProcess::Ok((any, _)) => { - ::mingling::ChainProcess::Ok((any, mingling::NextProcess::Renderer)) - } - other => other, - } - } -} -pub struct ErrorRendererNotFound { - pub(crate) inner: String, -} -impl ErrorRendererNotFound { - /// Creates a new instance of the wrapper type - pub fn new(inner: String) -> Self { - Self { inner } - } -} -impl From<String> for ErrorRendererNotFound { - fn from(inner: String) -> Self { - Self::new(inner) - } -} -impl From<ErrorRendererNotFound> for String { - fn from(wrapper: ErrorRendererNotFound) -> String { - wrapper.inner - } -} -impl ::std::convert::AsRef<String> for ErrorRendererNotFound { - fn as_ref(&self) -> &String { - &self.inner - } -} -impl ::std::convert::AsMut<String> for ErrorRendererNotFound { - fn as_mut(&mut self) -> &mut String { - &mut self.inner - } -} -impl ::std::ops::Deref for ErrorRendererNotFound { - type Target = String; - fn deref(&self) -> &Self::Target { - &self.inner - } -} -impl ::std::ops::DerefMut for ErrorRendererNotFound { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.inner - } -} -impl ::std::default::Default for ErrorRendererNotFound -where - String: ::std::default::Default, -{ - fn default() -> Self { - Self::new(::std::default::Default::default()) - } -} -impl Into<mingling::AnyOutput<crate::ThisProgram>> for ErrorRendererNotFound { - fn into(self) -> mingling::AnyOutput<crate::ThisProgram> { - mingling::AnyOutput::new(self) - } -} -impl Into<mingling::ChainProcess<crate::ThisProgram>> for ErrorRendererNotFound { - fn into(self) -> mingling::ChainProcess<crate::ThisProgram> { - mingling::AnyOutput::new(self).route_chain() - } -} -impl ::mingling::Grouped<crate::ThisProgram> for ErrorRendererNotFound { - fn member_id() -> crate::ThisProgram { - crate::ThisProgram::ErrorRendererNotFound - } -} -pub struct ErrorDispatcherNotFound { - pub(crate) inner: Vec<String>, -} -impl ErrorDispatcherNotFound { - /// Creates a new instance of the wrapper type - pub fn new(inner: Vec<String>) -> Self { - Self { inner } - } -} -impl From<Vec<String>> for ErrorDispatcherNotFound { - fn from(inner: Vec<String>) -> Self { - Self::new(inner) - } -} -impl From<ErrorDispatcherNotFound> for Vec<String> { - fn from(wrapper: ErrorDispatcherNotFound) -> Vec<String> { - wrapper.inner - } -} -impl ::std::convert::AsRef<Vec<String>> for ErrorDispatcherNotFound { - fn as_ref(&self) -> &Vec<String> { - &self.inner - } -} -impl ::std::convert::AsMut<Vec<String>> for ErrorDispatcherNotFound { - fn as_mut(&mut self) -> &mut Vec<String> { - &mut self.inner - } -} -impl ::std::ops::Deref for ErrorDispatcherNotFound { - type Target = Vec<String>; - fn deref(&self) -> &Self::Target { - &self.inner - } -} -impl ::std::ops::DerefMut for ErrorDispatcherNotFound { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.inner - } -} -impl ::std::default::Default for ErrorDispatcherNotFound -where - Vec<String>: ::std::default::Default, -{ - fn default() -> Self { - Self::new(::std::default::Default::default()) - } -} -impl Into<mingling::AnyOutput<crate::ThisProgram>> for ErrorDispatcherNotFound { - fn into(self) -> mingling::AnyOutput<crate::ThisProgram> { - mingling::AnyOutput::new(self) - } -} -impl Into<mingling::ChainProcess<crate::ThisProgram>> for ErrorDispatcherNotFound { - fn into(self) -> mingling::ChainProcess<crate::ThisProgram> { - mingling::AnyOutput::new(self).route_chain() - } -} -impl ::mingling::Grouped<crate::ThisProgram> for ErrorDispatcherNotFound { - fn member_id() -> crate::ThisProgram { - crate::ThisProgram::ErrorDispatcherNotFound - } -} -pub struct ResultEmpty; -#[doc(hidden)] -#[allow( - non_upper_case_globals, - unused_attributes, - unused_qualifications, - clippy::absolute_paths, -)] -const _: () = { - #[allow(unused_extern_crates, clippy::useless_attribute)] - extern crate serde as _serde; - #[automatically_derived] - impl _serde::Serialize for ResultEmpty { - fn serialize<__S>( - &self, - __serializer: __S, - ) -> _serde::__private229::Result<__S::Ok, __S::Error> - where - __S: _serde::Serializer, - { - _serde::Serializer::serialize_unit_struct(__serializer, "ResultEmpty") - } - } -}; -impl ::mingling::__private::StructuralDataSealed<crate::ThisProgram> for ResultEmpty {} -impl ::mingling::__private::StructuralData<crate::ThisProgram> for ResultEmpty {} -impl ::mingling::Grouped<crate::ThisProgram> for ResultEmpty { - fn member_id() -> crate::ThisProgram { - crate::ThisProgram::ResultEmpty - } -} -impl ::std::convert::Into<::mingling::AnyOutput<crate::ThisProgram>> for ResultEmpty { - fn into(self) -> ::mingling::AnyOutput<crate::ThisProgram> { - ::mingling::AnyOutput::new(self) - } -} -impl ::std::convert::Into<::mingling::ChainProcess<crate::ThisProgram>> for ResultEmpty { - fn into(self) -> ::mingling::ChainProcess<crate::ThisProgram> { - ::mingling::AnyOutput::new(self).route_chain() - } -} -#[automatically_derived] -impl ::core::default::Default for ResultEmpty { - #[inline] - fn default() -> ResultEmpty { - ResultEmpty {} - } -} -#[repr(u8)] -#[allow(nonstandard_style)] -pub enum ThisProgram { - EntryMetadata, - EntryMinglingLinter, - ErrorDispatcherNotFound, - ErrorRendererNotFound, - ErrorSerdeJson, - Metadata, - ResultEmpty, - ResultMetadata, -} -#[automatically_derived] -#[allow(nonstandard_style)] -impl ::core::fmt::Debug for ThisProgram { - #[inline] - fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { - ::core::fmt::Formatter::write_str( - f, - match self { - ThisProgram::EntryMetadata => "EntryMetadata", - ThisProgram::EntryMinglingLinter => "EntryMinglingLinter", - ThisProgram::ErrorDispatcherNotFound => "ErrorDispatcherNotFound", - ThisProgram::ErrorRendererNotFound => "ErrorRendererNotFound", - ThisProgram::ErrorSerdeJson => "ErrorSerdeJson", - ThisProgram::Metadata => "Metadata", - ThisProgram::ResultEmpty => "ResultEmpty", - ThisProgram::ResultMetadata => "ResultMetadata", - }, - ) - } -} -#[automatically_derived] -#[allow(nonstandard_style)] -impl ::core::marker::StructuralPartialEq for ThisProgram {} -#[automatically_derived] -#[allow(nonstandard_style)] -impl ::core::cmp::PartialEq for ThisProgram { - #[inline] - fn eq(&self, other: &ThisProgram) -> bool { - let __self_discr = ::core::intrinsics::discriminant_value(self); - let __arg1_discr = ::core::intrinsics::discriminant_value(other); - __self_discr == __arg1_discr - } -} -#[automatically_derived] -#[allow(nonstandard_style)] -impl ::core::cmp::Eq for ThisProgram { - #[doc(hidden)] - #[coverage(off)] - fn assert_fields_are_eq(&self) {} -} -#[automatically_derived] -#[allow(nonstandard_style)] -impl ::core::clone::Clone for ThisProgram { - #[inline] - fn clone(&self) -> ThisProgram { - match self { - ThisProgram::EntryMetadata => ThisProgram::EntryMetadata, - ThisProgram::EntryMinglingLinter => ThisProgram::EntryMinglingLinter, - ThisProgram::ErrorDispatcherNotFound => ThisProgram::ErrorDispatcherNotFound, - ThisProgram::ErrorRendererNotFound => ThisProgram::ErrorRendererNotFound, - ThisProgram::ErrorSerdeJson => ThisProgram::ErrorSerdeJson, - ThisProgram::Metadata => ThisProgram::Metadata, - ThisProgram::ResultEmpty => ThisProgram::ResultEmpty, - ThisProgram::ResultMetadata => ThisProgram::ResultMetadata, - } - } -} -impl ::std::fmt::Display for ThisProgram { - fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { - match self { - ThisProgram::EntryMetadata => f.write_fmt(format_args!("EntryMetadata")), - ThisProgram::EntryMinglingLinter => { - f.write_fmt(format_args!("EntryMinglingLinter")) - } - ThisProgram::ErrorDispatcherNotFound => { - f.write_fmt(format_args!("ErrorDispatcherNotFound")) - } - ThisProgram::ErrorRendererNotFound => { - f.write_fmt(format_args!("ErrorRendererNotFound")) - } - ThisProgram::ErrorSerdeJson => f.write_fmt(format_args!("ErrorSerdeJson")), - ThisProgram::Metadata => f.write_fmt(format_args!("Metadata")), - ThisProgram::ResultEmpty => f.write_fmt(format_args!("ResultEmpty")), - ThisProgram::ResultMetadata => f.write_fmt(format_args!("ResultMetadata")), - } - } -} -impl ::mingling::ProgramCollect for ThisProgram { - type Enum = ThisProgram; - type ErrorDispatcherNotFound = ErrorDispatcherNotFound; - type ErrorRendererNotFound = ErrorRendererNotFound; - type ResultEmpty = ResultEmpty; - fn build_renderer_not_found( - member_id: Self::Enum, - ) -> ::mingling::AnyOutput<Self::Enum> { - ::mingling::AnyOutput::new(ErrorRendererNotFound::new(member_id.to_string())) - } - fn build_dispatcher_not_found( - args: Vec<String>, - ) -> ::mingling::AnyOutput<Self::Enum> { - ::mingling::AnyOutput::new(ErrorDispatcherNotFound::new(args)) - } - fn build_empty_result() -> ::mingling::AnyOutput<Self::Enum> { - ::mingling::AnyOutput::new(ResultEmpty) - } - fn render(any: ::mingling::AnyOutput<Self::Enum>) -> ::mingling::RenderResult { - match any.member_id { - Self::ErrorSerdeJson => { - let value = unsafe { - any.downcast::<crate::errors::serde_json::ErrorSerdeJson>() - .unwrap_unchecked() - }; - <crate::errors::serde_json::__internal_renderer_render_error_serde_json as ::mingling::Renderer>::render( - value, - ) - } - Self::Metadata => { - let value = unsafe { - any.downcast::<cargo_metadata::Metadata>().unwrap_unchecked() - }; - <crate::metadata::__internal_renderer_render_metadata as ::mingling::Renderer>::render( - value, - ) - } - _ => ::mingling::RenderResult::default(), - } - } - fn do_chain( - any: ::mingling::AnyOutput<Self::Enum>, - ) -> ::mingling::ChainProcess<Self::Enum> { - match any.member_id { - Self::EntryMetadata => { - let value = unsafe { - any.downcast::<crate::metadata::cmd_metadata::EntryMetadata>() - .unwrap_unchecked() - }; - <crate::metadata::cmd_metadata::__internal_chain_handle_metadata as ::mingling::Chain< - Self::Enum, - >>::proc(value) - } - Self::EntryMinglingLinter => { - let value = unsafe { - any.downcast::<crate::linter::cmd_mlint::EntryMinglingLinter>() - .unwrap_unchecked() - }; - <crate::linter::cmd_mlint::__internal_chain_handle_mlint as ::mingling::Chain< - Self::Enum, - >>::proc(value) - } - _ => { - ::core::panicking::panic_fmt( - format_args!("No chain found for type id: {0:?}", any.type_id), - ); - } - } - } - fn render_help(any: ::mingling::AnyOutput<Self::Enum>) -> ::mingling::RenderResult { - #[allow(unused_imports)] - use crate::metadata::cmd_metadata::__internal_chain_handle_metadata; - use crate::metadata::cmd_metadata::ResultMetadata; - use crate::linter::cmd_mlint::__internal_chain_handle_mlint; - use crate::metadata::__internal_renderer_render_metadata; - use crate::metadata::cmd_metadata::CMDMetadata; - use crate::metadata::setup::ResMetadata; - use crate::metadata::cmd_metadata::EntryMetadata; - use crate::linter::cmd_mlint::CMDMinglingLinter; - use crate::errors::serde_json::ErrorSerdeJson; - use cargo_metadata::Metadata; - use crate::errors::serde_json::__internal_renderer_render_error_serde_json; - use crate::linter::cmd_mlint::EntryMinglingLinter; - match any.member_id { - _ => ::mingling::RenderResult::default(), - } - } - fn has_renderer(any: &::mingling::AnyOutput<Self::Enum>) -> bool { - match any.member_id { - Self::ErrorSerdeJson => true, - Self::Metadata => true, - _ => false, - } - } - fn has_chain(any: &::mingling::AnyOutput<Self::Enum>) -> bool { - match any.member_id { - Self::EntryMetadata => true, - Self::EntryMinglingLinter => true, - _ => false, - } - } - fn structural_render( - any: ::mingling::AnyOutput<Self::Enum>, - setting: &::mingling::StructuralRendererSetting, - ) -> Result< - ::mingling::RenderResult, - ::mingling::error::StructuralRendererSerializeError, - > { - #[allow(unused_imports)] - use crate::metadata::cmd_metadata::__internal_chain_handle_metadata; - use crate::metadata::cmd_metadata::ResultMetadata; - use crate::linter::cmd_mlint::__internal_chain_handle_mlint; - use crate::metadata::__internal_renderer_render_metadata; - use crate::metadata::cmd_metadata::CMDMetadata; - use crate::metadata::setup::ResMetadata; - use crate::metadata::cmd_metadata::EntryMetadata; - use crate::linter::cmd_mlint::CMDMinglingLinter; - use crate::errors::serde_json::ErrorSerdeJson; - use cargo_metadata::Metadata; - use crate::errors::serde_json::__internal_renderer_render_error_serde_json; - use crate::linter::cmd_mlint::EntryMinglingLinter; - match any.member_id { - Self::Metadata => { - let raw = unsafe { any.restore::<Metadata>().unwrap_unchecked() }; - let mut __renderer_inner_result = ::mingling::RenderResult::default(); - ::mingling::StructuralRenderer::render( - &raw, - setting, - &mut __renderer_inner_result, - )?; - Ok(__renderer_inner_result) - } - _ => { - let mut r = ::mingling::RenderResult::default(); - ::mingling::StructuralRenderer::render(&ResultEmpty, setting, &mut r)?; - Ok(r) - } - } - } -} -impl ThisProgram { - /// Creates a new `Program<#name>` instance with default configuration. - pub fn new() -> ::mingling::Program<ThisProgram> { - ::mingling::Program::new() - } - /// Returns a static reference to the global `Program<#name>` singleton. - pub fn this() -> &'static ::mingling::Program<ThisProgram> { - &::mingling::this::<ThisProgram>() - } -} diff --git a/mingling_cli/help/help.txt b/mingling_cli/help/help.txt new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/mingling_cli/help/help.txt diff --git a/mingling_cli/src/bin/wrapper.rs b/mingling_cli/src/bin/wrapper.rs new file mode 100644 index 0000000..b1aecf6 --- /dev/null +++ b/mingling_cli/src/bin/wrapper.rs @@ -0,0 +1,47 @@ +use std::env; +use std::ffi::OsString; +use std::path::PathBuf; +use std::process::{self, Command}; + +fn main() { + exec(); +} + +fn exec() { + fn target_exe_path() -> PathBuf { + let mut path = env::current_exe().expect("Fail to read current_exe"); + path.pop(); + + let target_name = if cfg!(target_os = "windows") { + "mingling-cli.exe" + } else { + "mingling-cli" + }; + + path.push(target_name); + path + } + + let mut args: Vec<OsString> = env::args_os().collect(); + let pass_args = if args.len() > 1 { + args.split_off(1) + } else { + vec![] + }; + + let target = target_exe_path(); + + let status = Command::new(&target) + .args(&pass_args) + .status() + .unwrap_or_else(|_| { + process::exit(1); + }); + + match status.code() { + Some(code) => process::exit(code), + None => { + process::exit(1); + } + } +} diff --git a/mingling_cli/src/errors.rs b/mingling_cli/src/errors.rs index cef9616..5cdc43f 100644 --- a/mingling_cli/src/errors.rs +++ b/mingling_cli/src/errors.rs @@ -1 +1,3 @@ +pub mod io_error; pub mod serde_json; + diff --git a/mingling_cli/src/errors/io_error.rs b/mingling_cli/src/errors/io_error.rs new file mode 100644 index 0000000..97509c9 --- /dev/null +++ b/mingling_cli/src/errors/io_error.rs @@ -0,0 +1,8 @@ +use mingling::macros::{group, renderer}; + +group!(ErrorIo = std::io::Error); + +#[renderer] +pub fn handle_error_io(err: ErrorIo) { + panic!("{}", err.to_string()) +} diff --git a/mingling_cli/src/linter.rs b/mingling_cli/src/linter.rs index c85afc1..859ffc5 100644 --- a/mingling_cli/src/linter.rs +++ b/mingling_cli/src/linter.rs @@ -4,7 +4,7 @@ use mingling::{ }; use crate::{ - linter::cmd_mlint::{CMDMinglingLinter, EntryMinglingLinter}, + linter::cmd_mlint::{CMDLint, EntryLint}, metadata::setup::ResUsingJson, }; @@ -19,7 +19,7 @@ pub fn mingling_linter_setup(program: &mut Program<crate::ThisProgram>) { #[program_setup] pub fn mingling_linter_command_setup(program: &mut Program<crate::ThisProgram>) { - program.with_dispatcher(CMDMinglingLinter); + program.with_dispatcher(CMDLint); program.with_dispatcher(CMDLinterSupportRustAnalyzer); program.with_dispatcher(CMDLinterSupportRustAnalyzerWithClippy); program.with_dispatcher(CMDLinterSupportRustAnalyzerWithCheck); @@ -40,10 +40,7 @@ dispatcher!("ra-lint", ); #[chain] -pub fn handle_ra_lint( - _: EntryLinterSupportRustAnalyzer, - use_json: &mut ResUsingJson, -) -> EntryMinglingLinter { +pub fn handle_ra_lint(_: EntryLinterSupportRustAnalyzer, use_json: &mut ResUsingJson) -> EntryLint { use_json.using = true; entry!("--message-format=json") } @@ -52,7 +49,7 @@ pub fn handle_ra_lint( pub fn handle_ra_lint_check( _: EntryLinterSupportRustAnalyzerWithCheck, use_json: &mut ResUsingJson, -) -> EntryMinglingLinter { +) -> EntryLint { use_json.using = true; entry!("--message-format=json", "--with-checker=cargo,check") } @@ -61,7 +58,7 @@ pub fn handle_ra_lint_check( pub fn handle_ra_lint_clippy( _: EntryLinterSupportRustAnalyzerWithClippy, use_json: &mut ResUsingJson, -) -> EntryMinglingLinter { +) -> EntryLint { use_json.using = true; entry!("--message-format=json", "--with-checker=cargo,clippy") } diff --git a/mingling_cli/src/linter/cmd_mlint.rs b/mingling_cli/src/linter/cmd_mlint.rs index 90871af..1dfd02b 100644 --- a/mingling_cli/src/linter/cmd_mlint.rs +++ b/mingling_cli/src/linter/cmd_mlint.rs @@ -4,8 +4,9 @@ use mingling::LazyRes; use mingling::consts::REMAINS; use mingling::macros::{arg, chain, dispatcher, pack}; use mingling::picker::EntryPicker; +use tokio::task::JoinSet; -dispatcher!("lint", CMDMinglingLinter => EntryMinglingLinter); +dispatcher!("lint", CMDLint => EntryLint); /// Main linting function that processes all packages in the metadata. /// @@ -13,42 +14,54 @@ dispatcher!("lint", CMDMinglingLinter => EntryMinglingLinter); /// reads Rust source files (`.rs`), parses them into ASTs, runs lint checks, /// and enriches each report with metadata information. async fn linter_main(metadata: &Metadata) -> Vec<MlintReport> { - // Vector to accumulate all lint reports - let mut all_reports = Vec::new(); + let mut join_set = JoinSet::new(); - // Iterate over all packages in the metadata for package in &metadata.packages { - // Iterate over all targets within a package (e.g., bin, lib, test, etc.) for target in &package.targets { let path = &target.src_path; // Only process Rust source files (with `.rs` extension) if !path.as_str().ends_with(".rs") { continue; } - // Read the source file content - let source = match std::fs::read_to_string(path.as_str()) { - Ok(s) => s, - Err(_) => continue, - }; - // Parse the source file into an AST (Abstract Syntax Tree) - let ast = match syn::parse_file(&source) { - Ok(f) => f, - Err(_) => continue, - }; - - // Run all lint checks and collect reports - let reports = crate::lints::run_all_lints(&ast, &source); - // - for mut r in reports { - // Enrich report with metadata information - r.file_name = path.as_str().to_string(); - r.source_code = source.clone(); - r.package_id = Some(package.id.to_string()); - r.target_name = Some(target.name.clone()); - r.target_kind = target.kind.first().map(|k| k.to_string()); - r.target_src_path = Some(path.as_str().to_string()); - all_reports.push(r); - } + + // Clone/move all data needed inside the blocking closure + let path_str = path.as_str().to_string(); + let package_id = package.id.to_string(); + let target_name = target.name.clone(); + let target_kind = target.kind.first().map(|k| k.to_string()); + + join_set.spawn_blocking(move || { + // Read the source file content + let source = std::fs::read_to_string(&path_str).ok()?; + // Parse the source file into an AST + let ast = syn::parse_file(&source).ok()?; + // Run all lint checks and collect reports + let reports = crate::lints::run_all_lints(&ast, &source); + + // Enrich each report with metadata information + let enriched: Vec<MlintReport> = reports + .into_iter() + .map(|mut r| { + r.file_name = path_str.clone(); + r.source_code = source.clone(); + r.package_id = Some(package_id.clone()); + r.target_name = Some(target_name.clone()); + r.target_kind = target_kind.clone(); + r.target_src_path = Some(path_str.clone()); + r + }) + .collect(); + + Some(enriched) + }); + } + } + + let mut all_reports = Vec::new(); + while let Some(res) = join_set.join_next().await { + // `spawn_blocking` panics are propagated, `None` means task skipped (read/parse failure) + if let Ok(Some(reports)) = res { + all_reports.extend(reports); } } @@ -58,7 +71,7 @@ async fn linter_main(metadata: &Metadata) -> Vec<MlintReport> { pack!(StateBeginLinter = ()); #[chain] -pub fn handle_lint(args: EntryMinglingLinter) -> StateBeginLinter { +pub fn handle_lint(args: EntryLint) -> StateBeginLinter { let (with_checker, checker_args) = args .pick_or(&arg![with_checker: Option<String>], || { Some("cargo,check".to_string()) diff --git a/mingling_cli/src/linter/mlint_report.rs b/mingling_cli/src/linter/mlint_report.rs index 8195f77..0472056 100644 --- a/mingling_cli/src/linter/mlint_report.rs +++ b/mingling_cli/src/linter/mlint_report.rs @@ -51,8 +51,8 @@ pub struct MlintReport { /// Compilation target source file path that this report belongs to pub target_src_path: Option<String>, - /// A suggestion for automatic fix (shown as diff in annotated output) - pub suggestion: Option<LintSuggestion>, + /// Suggestions for automatic fix (shown as diff in annotated output) + pub suggestions: Vec<LintSuggestion>, } /// Report severity level, indicating the seriousness of the Lint result. @@ -248,8 +248,8 @@ impl MlintReport { group = group.element(msg); } - // Render suggestion as a diff (Element::Suggestion) - if let Some(sugg) = &self.suggestion { + // Render suggestions as diffs + for sugg in &self.suggestions { let patch_snippet = Snippet::source(sugg.source.clone()) .line_start(sugg.line_start) .path(&self.file_name) @@ -404,8 +404,8 @@ impl MlintReport { .collect::<Vec<_>>(), ) .label(span.label.clone()) - .suggested_replacement(self.suggestion.as_ref().map(|s| s.replacement.clone())) - .suggestion_applicability(if self.suggestion.is_some() { + .suggested_replacement(self.suggestions.first().map(|s| s.replacement.clone())) + .suggestion_applicability(if !self.suggestions.is_empty() { Some(cargo_metadata::diagnostic::Applicability::MachineApplicable) } else { None diff --git a/mingling_cli/src/lints.rs b/mingling_cli/src/lints.rs index 89f5ca3..8cc1e6e 100644 --- a/mingling_cli/src/lints.rs +++ b/mingling_cli/src/lints.rs @@ -1,3 +1,5 @@ +// This file is auto-generated by pre/lint_registry.rs + #![allow(unused)] use crate::linter::mlint_report::{MlintLevel, MlintReport}; diff --git a/mingling_cli/src/lints/non_mingling_naming_style.rs b/mingling_cli/src/lints/non_mingling_naming_style.rs index 6481926..83168b7 100644 --- a/mingling_cli/src/lints/non_mingling_naming_style.rs +++ b/mingling_cli/src/lints/non_mingling_naming_style.rs @@ -153,12 +153,12 @@ fn check_fn_name(func: &syn::ItemFn, source: &str) -> Vec<MlintReport> { lint_code: "non_mingling_naming_style".into(), message: msg, spans: vec![span], - suggestion: Some(LintSuggestion { + suggestions: vec![LintSuggestion { source: source_line.to_string(), line_start: func.sig.span().start().line, byte_range, replacement: suggestion_target, - }), + }], attached_reports: vec![MlintReport { level: MlintLevel::Help, message: format!("expected `{expected_fn}` ↔ `{expected_type}`"), diff --git a/mingling_cli/src/lints/template_linter.rs b/mingling_cli/src/lints/template_linter.rs index 271aa0c..4836e24 100644 --- a/mingling_cli/src/lints/template_linter.rs +++ b/mingling_cli/src/lints/template_linter.rs @@ -13,7 +13,7 @@ //! > This section is the **Metadata** section, which needs to be filled in correctly. //! > These contents will eventually be compiled as the Linter's behavior. //! -//! Author: `Weicao-CatilGrass` +//! Author: `Your-Name` //! Default: `allow` // ^^^^^ Supported parameters: `warn`, `allow`, `deny` diff --git a/mingling_cli/src/lints/unnecessary_render_result_creation.rs b/mingling_cli/src/lints/unnecessary_render_result_creation.rs index 03f81d2..1450c1a 100644 --- a/mingling_cli/src/lints/unnecessary_render_result_creation.rs +++ b/mingling_cli/src/lints/unnecessary_render_result_creation.rs @@ -14,8 +14,9 @@ //! Author: `Weicao-CatilGrass` //! Default: `warn` -use crate::linter::mlint_report::{MlintLevel, MlintReport}; +use crate::linter::mlint_report::{LintSuggestion, MlintLevel, MlintReport}; use quote::ToTokens; +use syn::spanned::Spanned; pub fn linter(ast: syn::ItemFn, source: &str) -> Vec<MlintReport> { if !has_renderer_attr(&ast) { @@ -45,6 +46,31 @@ pub fn linter(ast: syn::ItemFn, source: &str) -> Vec<MlintReport> { if only_print_and_return && print_count > 0 { let span = MlintReport::span_from_syn(&ast.sig, source); + let mut suggestions = Vec::new(); + + // 1. Attribute change: #[renderer] → #[renderer(buffer)] + if let Some(sugg) = make_attr_suggestion(&ast, source) { + suggestions.push(sugg); + } + + // 2. Remove -> RenderResult from function signature + if let Some(sugg) = make_return_type_suggestion(&ast, source) { + suggestions.push(sugg); + } + + // 3. Remove let mut r = RenderResult::... + if let Some(sugg) = make_let_removal_suggestion(stmts, let_idx, source) { + suggestions.push(sugg); + } + + // 4. Fix r_println!(r, ...) → r_println!(...) for all r_xxx macros + suggestions.extend(make_macro_arg_suggestions(stmts, &r_name, source)); + + // 5. Remove return 'r' expression + if let Some(sugg) = make_return_removal_suggestion(stmts, &r_name, source) { + suggestions.push(sugg); + } + vec![MlintReport { source_code: source.to_string(), level: MlintLevel::Warning, @@ -54,6 +80,7 @@ pub fn linter(ast: syn::ItemFn, source: &str) -> Vec<MlintReport> { ast.sig.ident, ), spans: vec![span], + suggestions, attached_reports: vec![MlintReport { level: MlintLevel::Help, message: format!( @@ -185,6 +212,198 @@ fn check_stmt_usage(stmt: &syn::Stmt, r_name: &str, print_count: &mut usize) -> true } +/// Build suggestion: `#[renderer]` → `#[renderer(buffer)]` +fn make_attr_suggestion(ast: &syn::ItemFn, source: &str) -> Option<LintSuggestion> { + let attr = ast.attrs.iter().find(|a| { + let name = a.path().to_token_stream().to_string(); + name.ends_with("renderer") + })?; + + let line_idx = attr.span().start().line.saturating_sub(1); + let line = source.lines().nth(line_idx)?; + + // Replace `renderer]` with `renderer(buffer)]` + // This handles both `#[renderer]` and `#[::mingling::macros::renderer]` + let line_str = line; + let replacement = line_str.replacen("renderer]", "renderer(buffer)]", 1); + + if replacement == line_str { + return None; + } + + Some(LintSuggestion { + source: line_str.to_string(), + line_start: line_idx + 1, + byte_range: 0..line_str.len(), + replacement, + }) +} + +/// Build suggestion: remove ` -> RenderResult` from function signature +fn make_return_type_suggestion(ast: &syn::ItemFn, source: &str) -> Option<LintSuggestion> { + let syn::ReturnType::Type(arrow, ret_type) = &ast.sig.output else { + return None; + }; + + let sig_line_idx = ast.sig.span().start().line.saturating_sub(1); + let line = source.lines().nth(sig_line_idx)?; + + // proc-macro2 column is 0-based byte offset from line start + let arrow_byte_col = arrow.span().start().column; + let ret_end_byte_col = ret_type.span().end().column; + + // Include the space before `->` + let range_start = if arrow_byte_col > 0 { + arrow_byte_col - 1 + } else { + arrow_byte_col + }; + + Some(LintSuggestion { + source: line.to_string(), + line_start: sig_line_idx + 1, + byte_range: range_start..ret_end_byte_col, + replacement: String::new(), + }) +} + +/// Build suggestion: remove `let mut r = RenderResult::...` line +fn make_let_removal_suggestion( + stmts: &[syn::Stmt], + let_idx: usize, + source: &str, +) -> Option<LintSuggestion> { + let stmt = &stmts[let_idx]; + let line_idx = stmt.span().start().line.saturating_sub(1); + let line = source.lines().nth(line_idx)?; + + Some(LintSuggestion { + source: line.to_string(), + line_start: line_idx + 1, + byte_range: 0..line.len(), + replacement: String::new(), + }) +} + +/// Build suggestions: fix `r_println!(r, ...)` → `r_println!(...)` for all r_xxx macros +fn make_macro_arg_suggestions( + stmts: &[syn::Stmt], + r_name: &str, + source: &str, +) -> Vec<LintSuggestion> { + let r_macros = ["r_println", "r_eprintln", "r_print", "r_eprint"]; + + stmts + .iter() + .filter_map(|stmt| { + let syn::Stmt::Macro(stmt_mac) = stmt else { + return None; + }; + + let macro_name = stmt_mac + .mac + .path + .segments + .last() + .map(|s| s.ident.to_string()) + .unwrap_or_default(); + + if !r_macros.contains(¯o_name.as_str()) { + return None; + } + + // Check that the first token is the r_name + let first_token = stmt_mac.mac.tokens.clone().into_iter().next()?; + if first_token.to_string() != *r_name { + return None; + } + + let line_idx = stmt.span().start().line.saturating_sub(1); + let line = source.lines().nth(line_idx)?; + + // Find pattern: macro_name!(r_name, ... + let macro_str = format!("{}!(", macro_name); + let macro_pos = line.find(¯o_str)?; + let after_open = macro_pos + macro_str.len(); + + // The first argument is `r_name` followed by `,` and possibly a space + // We need to find and remove `r_name, ` or `r_name,` + let first_arg = r_name; + if line[after_open..].starts_with(first_arg) { + // Find the end of the first argument (including `,` and any whitespace) + let arg_end = after_open + first_arg.len(); + if arg_end < line.len() { + let rest = &line[arg_end..]; + // Skip `,` and optional whitespace + let skip = if rest.starts_with(", ") { + 2 + } else if rest.starts_with(',') { + 1 + } else { + // Not followed by comma — not our pattern + return None; + }; + let range_end = arg_end + skip; + + Some(LintSuggestion { + source: line.to_string(), + line_start: line_idx + 1, + byte_range: after_open..range_end, + replacement: String::new(), + }) + } else { + None + } + } else { + None + } + }) + .collect() +} + +/// Build suggestion: remove the return `r` expression +fn make_return_removal_suggestion( + stmts: &[syn::Stmt], + r_name: &str, + source: &str, +) -> Option<LintSuggestion> { + let last = stmts.last()?; + + let is_r_return = match last { + // `r` (bare expression, no semicolon) or `r;` (with semicolon) + syn::Stmt::Expr(expr, _) => { + if let syn::Expr::Path(p) = expr { + p.path.is_ident(r_name) + } else if let syn::Expr::Return(ret) = expr { + ret.expr.as_ref().is_some_and(|e| { + if let syn::Expr::Path(p) = e.as_ref() { + p.path.is_ident(r_name) + } else { + false + } + }) + } else { + false + } + } + _ => false, + }; + + if !is_r_return { + return None; + } + + let line_idx = last.span().start().line.saturating_sub(1); + let line = source.lines().nth(line_idx)?; + + Some(LintSuggestion { + source: line.to_string(), + line_start: line_idx + 1, + byte_range: 0..line.len(), + replacement: String::new(), + }) +} + #[cfg(test)] mod lint_test { use crate::{assert_detected, assert_not_detected}; diff --git a/mingling_cli/tmpls/lints.tmpl b/mingling_cli/tmpls/lints.tmpl index 590828a..e177079 100644 --- a/mingling_cli/tmpls/lints.tmpl +++ b/mingling_cli/tmpls/lints.tmpl @@ -1,3 +1,5 @@ +// This file is auto-generated by pre/lint_registry.rs + #![allow(unused)] use crate::linter::mlint_report::{MlintLevel, MlintReport}; diff --git a/mingling_core/Cargo.toml b/mingling_core/Cargo.toml index 14140c6..f525609 100644 --- a/mingling_core/Cargo.toml +++ b/mingling_core/Cargo.toml @@ -14,7 +14,7 @@ categories = ["command-line-interface"] nightly = [] default = [] async = [] -builds = [] +build = [] picker = [] dispatch_tree = [] diff --git a/mingling_core/src/any.rs b/mingling_core/src/any.rs index ec29a1b..e922e2e 100644 --- a/mingling_core/src/any.rs +++ b/mingling_core/src/any.rs @@ -1,8 +1,8 @@ +use crate::ProgramCollect; use crate::error::ChainProcessError; -use crate::{Grouped, ProgramCollect}; -#[doc(hidden)] -pub mod group; +mod group; +pub use group::*; /// Any type output /// @@ -22,14 +22,14 @@ pub struct AnyOutput<G> { /// /// This is set during construction and used for type-checking /// in downcast, restore, and is methods. - pub type_id: std::any::TypeId, + pub(crate) type_id: std::any::TypeId, /// The variant identifier returned by [`Grouped::member_id`] for the /// concrete type stored in `inner`. /// /// This is used by the scheduler to dispatch on the correct enum /// variant when routing the output. - pub member_id: G, + pub(crate) member_id: G, } impl<G> AnyOutput<G> { @@ -45,6 +45,52 @@ impl<G> AnyOutput<G> { } } + /// Create an `AnyOutput` from a raw value with a manually specified member_id. + /// + /// This function bypasses the [`Grouped`] trait, meaning the `member_id` you provide + /// does **not** have to match the actual concrete type `T`. The scheduler uses + /// `member_id` to determine which enum variant the output belongs to, and later + /// attempts to restore the value to the concrete type `T` based on that variant. + /// + /// # Safety + /// + /// - The caller must ensure that `member_id` correctly corresponds to the concrete + /// type `T` according to the scheduling logic. If `member_id` does not match, + /// calling [`restore`](Self::restore) or [`downcast`](Self::downcast) with the + /// type associated with `member_id` will cause **undefined behavior**. + /// - This safety contract is the caller's responsibility; the compiler cannot + /// enforce the correspondence between `member_id` and the stored type. + pub unsafe fn new_bare<T>(value: T, member_id: G) -> Self + where + T: Send + 'static, + { + Self { + inner: Box::new(value), + type_id: std::any::TypeId::of::<T>(), + member_id, + } + } + + /// Get the [`TypeId`] of the concrete type stored in `inner`. + /// + /// The `TypeId` is set during construction (via [`AnyOutput::new`] or [`AnyOutput::new_bare`]) + /// and is used for subsequent downcasting and type checking. + pub fn type_id(&self) -> std::any::TypeId { + self.type_id + } + + /// Get the [`member_id`] of the concrete type stored in `inner`. + /// + /// [`member_id`] is set during construction (via [`AnyOutput::new`] or [`AnyOutput::new_bare`]) + /// and identifies which variant of the output enum this value corresponds to. + /// The scheduler uses this value to dispatch the output to the correct next step. + pub fn member_id(&self) -> G + where + G: Copy, + { + self.member_id + } + /// Attempt to downcast the `AnyOutput` to a concrete type. /// /// # Errors @@ -190,7 +236,17 @@ mod tests { value: i32, } - impl Grouped<MockGroup> for AlphaData { + /// # Safety + /// + /// This implementation is only for testing purposes to satisfy trait bounds. + /// Since this code only constructs `AnyOutput` and calls methods like + /// `downcast`, `is`, `restore`, `route_chain`, and `route_renderer` — + /// none of which involve `ProgramCollect::do_chain` or + /// `ProgramCollect::render` — the type/member_id correspondence is + /// never exploited in an unsafe way here. + /// The caller must ensure that the associated `member_id` correctly + /// corresponds to the type's role in the group. + unsafe impl Grouped<MockGroup> for AlphaData { fn member_id() -> MockGroup { MockGroup::Alpha } @@ -202,7 +258,17 @@ mod tests { name: String, } - impl Grouped<MockGroup> for BetaData { + /// # Safety + /// + /// This implementation is only for testing purposes to satisfy trait bounds. + /// Since this code only constructs `AnyOutput` and calls methods like + /// `downcast`, `is`, `restore`, `route_chain`, and `route_renderer` — + /// none of which involve `ProgramCollect::do_chain` or + /// `ProgramCollect::render` — the type/member_id correspondence is + /// never exploited in an unsafe way here. + /// The caller must ensure that the associated `member_id` correctly + /// corresponds to the type's role in the group. + unsafe impl Grouped<MockGroup> for BetaData { fn member_id() -> MockGroup { MockGroup::Beta } @@ -213,7 +279,17 @@ mod tests { #[cfg_attr(feature = "structural_renderer", derive(serde::Serialize))] struct GammaData; - impl Grouped<MockGroup> for GammaData { + /// # Safety + /// + /// This implementation is only for testing purposes to satisfy trait bounds. + /// Since this code only constructs `AnyOutput` and calls methods like + /// `downcast`, `is`, `restore`, `route_chain`, and `route_renderer` — + /// none of which involve `ProgramCollect::do_chain` or + /// `ProgramCollect::render` — the type/member_id correspondence is + /// never exploited in an unsafe way here. + /// The caller must ensure that the associated `member_id` correctly + /// corresponds to the type's role in the group. + unsafe impl Grouped<MockGroup> for GammaData { fn member_id() -> MockGroup { MockGroup::Gamma } @@ -369,7 +445,17 @@ mod tests { x: i32, } - impl Grouped<MockGroup> for SerData { + /// SAFETY: + /// + /// This implementation is only for testing purposes to satisfy trait bounds. + /// Since this code only constructs `AnyOutput` and calls methods like + /// `downcast`, `is`, `restore`, `route_chain`, and `route_renderer` — + /// none of which involve `ProgramCollect::do_chain` or + /// `ProgramCollect::render` — the type/member_id correspondence is + /// never exploited in an unsafe way here. + /// The caller must ensure that the associated `member_id` correctly + /// corresponds to the type's role in the group. + unsafe impl Grouped<MockGroup> for SerData { fn member_id() -> MockGroup { MockGroup::Gamma } @@ -396,13 +482,33 @@ mod tests { b: String, } - impl Grouped<MockGroup> for SerA { + /// SAFETY: + /// + /// This implementation is only for testing purposes to satisfy trait bounds. + /// Since this code only constructs `AnyOutput` and calls methods like + /// `downcast`, `is`, `restore`, `route_chain`, and `route_renderer` — + /// none of which involve `ProgramCollect::do_chain` or + /// `ProgramCollect::render` — the type/member_id correspondence is + /// never exploited in an unsafe way here. + /// The caller must ensure that the associated `member_id` correctly + /// corresponds to the type's role in the group. + unsafe impl Grouped<MockGroup> for SerA { fn member_id() -> MockGroup { MockGroup::Alpha } } - impl Grouped<MockGroup> for SerB { + /// SAFETY: + /// + /// This implementation is only for testing purposes to satisfy trait bounds. + /// Since this code only constructs `AnyOutput` and calls methods like + /// `downcast`, `is`, `restore`, `route_chain`, and `route_renderer` — + /// none of which involve `ProgramCollect::do_chain` or + /// `ProgramCollect::render` — the type/member_id correspondence is + /// never exploited in an unsafe way here. + /// The caller must ensure that the associated `member_id` correctly + /// corresponds to the type's role in the group. + unsafe impl Grouped<MockGroup> for SerB { fn member_id() -> MockGroup { MockGroup::Beta } diff --git a/mingling_core/src/any/group.rs b/mingling_core/src/any/group.rs index 2813ad5..5e5e347 100644 --- a/mingling_core/src/any/group.rs +++ b/mingling_core/src/any/group.rs @@ -2,10 +2,14 @@ use crate::{AnyOutput, ChainProcess, ProgramCollect, Routable}; /// Used to mark a type with a unique enum ID, assisting dynamic dispatch /// -/// **Note:** Unlike earlier versions, `Grouped` no longer requires `Serialize` -/// even when the `structural_renderer` feature is enabled. Structured output is -/// controlled separately via the \[`StructuralData`\] trait. -pub trait Grouped<Group> +/// # Safety +/// +/// The returned `Group` value is an enum variant created by `register_type!` when +/// registering the type's ID. Whether the variant matches correctly is guaranteed +/// by `Grouped derive` or macros like `pack!`. If implemented manually, and the +/// type name written in `member_id()` does not match the actually registered type, +/// dispatching to that type will result in **100% undefined behavior**. +pub unsafe trait Grouped<Group> where Self: Sized + 'static, { diff --git a/mingling_core/src/asset.rs b/mingling_core/src/asset.rs index 9b9a5d4..f26952b 100644 --- a/mingling_core/src/asset.rs +++ b/mingling_core/src/asset.rs @@ -1,26 +1,9 @@ -#[doc(hidden)] -pub mod chain; - -#[doc(hidden)] -pub mod dispatcher; - -#[doc(hidden)] -pub mod enum_tag; - -#[doc(hidden)] -pub mod global_resource; - -#[doc(hidden)] -pub mod help; - -#[doc(hidden)] -pub mod lazy_resource; - -#[doc(hidden)] -pub mod node; - -#[doc(hidden)] -pub mod renderer; - -#[doc(hidden)] -pub mod routable; +pub(crate) mod chain; +pub(crate) mod dispatcher; +pub(crate) mod enum_tag; +pub(crate) mod global_resource; +pub(crate) mod help; +pub(crate) mod lazy_resource; +pub(crate) mod node; +pub(crate) mod renderer; +pub(crate) mod routable; diff --git a/mingling_core/src/asset/global_resource.rs b/mingling_core/src/asset/global_resource.rs index a610378..19374e7 100644 --- a/mingling_core/src/asset/global_resource.rs +++ b/mingling_core/src/asset/global_resource.rs @@ -184,7 +184,10 @@ pub trait ResourceMarker { C: ProgramCollect<Enum = C> + 'static; } -impl<T: Default + Clone + Send + Sync + 'static> ResourceMarker for T { +impl<T> ResourceMarker for T +where + T: Default + Clone + Send + Sync + 'static, +{ fn res_clone(&self) -> Self { Clone::clone(self) } diff --git a/mingling_core/src/build.rs b/mingling_core/src/build.rs new file mode 100644 index 0000000..7918f3a --- /dev/null +++ b/mingling_core/src/build.rs @@ -0,0 +1,13 @@ +#[doc(hidden)] +#[cfg(feature = "comp")] +mod comp; + +#[cfg(feature = "comp")] +pub use comp::*; + +#[doc(hidden)] +#[cfg(feature = "pathf")] +mod pathf; + +#[cfg(feature = "pathf")] +pub use pathf::*; diff --git a/mingling_core/src/builds/comp.rs b/mingling_core/src/build/comp.rs index 2abb1e1..2abb1e1 100644 --- a/mingling_core/src/builds/comp.rs +++ b/mingling_core/src/build/comp.rs diff --git a/mingling_core/src/builds/pathf.rs b/mingling_core/src/build/pathf.rs index d8d4698..d8d4698 100644 --- a/mingling_core/src/builds/pathf.rs +++ b/mingling_core/src/build/pathf.rs diff --git a/mingling_core/src/builds.rs b/mingling_core/src/builds.rs deleted file mode 100644 index 3c52907..0000000 --- a/mingling_core/src/builds.rs +++ /dev/null @@ -1,7 +0,0 @@ -#[doc(hidden)] -#[cfg(feature = "comp")] -pub mod comp; - -#[doc(hidden)] -#[cfg(feature = "pathf")] -pub mod pathf; diff --git a/mingling_core/src/lib.rs b/mingling_core/src/lib.rs index 31476c9..b9dbd06 100644 --- a/mingling_core/src/lib.rs +++ b/mingling_core/src/lib.rs @@ -1,3 +1,4 @@ +// #![deny(missing_docs)] //! Mingling Core //! //! # Intro @@ -8,7 +9,7 @@ //! //! Recommended to import [mingling](https://crates.io/crates/mingling) to use its features. -#![deny(missing_docs)] +// Private Modules mod any; mod asset; @@ -16,20 +17,65 @@ mod program; mod renderer; mod tester; +/// Module for setting up a `Mingling` program. +/// +/// This module provides the [`ProgramSetup`] type, which allows users to configure +/// and initialize the program's execution environment. +pub mod setup { + pub use crate::program::setup::ProgramSetup; +} + +/// This module provides result types for Mingling components. +/// +/// These are re-exported at the top level via `mingling::res`. +#[doc(hidden)] +pub mod core_res { + #[cfg(feature = "repl")] + pub use crate::program::repl_exec::res::*; +} + +/// Provides the runtime logic for Mingling's dynamic completion system. +/// +/// This module contains the core functionality for the "comp" (completion) feature, +/// which enables dynamic tab-completion and input suggestion capabilities within +/// Mingling applications. +#[cfg(feature = "comp")] +pub(crate) mod comp; + +/// Provides Mingling's build script module, used in `build.rs` to provide build-time behavior for certain features. +/// +/// To use it, add the following to your `Cargo.toml` under `[build-dependencies]`, and enable the features +/// that require build-time behavior from the crate: +/// +/// ```toml +/// [build-dependencies.mingling] +/// version = "0.3.0" +/// features = [ +/// "build", // Enable it +/// "comp", // If you need completion-related build-time behavior, enable this as well +/// ] +/// ``` +#[cfg(feature = "build")] +#[doc(hidden)] +pub mod build; + +// Public Modules + /// Provides a toolkit for `Mingling` testing capabilities. pub mod test { pub use crate::tester::*; } -#[cfg(feature = "structural_renderer")] -pub use crate::renderer::structural::StructuralRenderer; +/// Provided for framework developers +pub mod debug; // NOT re-exported at top level: the `StructuralData` trait is sealed and only // accessible through the derive macro. Users who need the trait can access it // via `mingling::renderer::structural::StructuralData` (through the inner alias). -pub use crate::any::group::*; -pub use crate::any::*; +#[cfg(feature = "structural_renderer")] +pub use crate::renderer::structural::StructuralRenderer; +pub use crate::any::*; pub use crate::asset::chain::*; pub use crate::asset::dispatcher::*; pub use crate::asset::enum_tag::*; @@ -39,74 +85,32 @@ pub use crate::asset::lazy_resource::*; pub use crate::asset::node::*; pub use crate::asset::renderer::*; pub use crate::asset::routable::*; +#[cfg(feature = "comp")] +pub use crate::comp::*; +pub use crate::program::*; +pub use crate::renderer::render_result::*; /// All error types of `Mingling` pub mod error { pub use crate::asset::chain::error::*; pub use crate::exec::error::*; pub use crate::program::error::*; + #[cfg(feature = "structural_renderer")] pub use crate::renderer::structural::error::*; - #[cfg(feature = "pathf")] - pub use mingling_pathf::error::*; -} - -pub use crate::program::*; - -pub use crate::renderer::render_result::*; - -#[cfg(feature = "builds")] -#[doc(hidden)] -pub mod builds; - -/// Provides build scripts for users -#[cfg(feature = "builds")] -pub mod build { - #[cfg(feature = "comp")] - pub use crate::builds::comp::*; #[cfg(feature = "pathf")] - pub use crate::builds::pathf::*; + pub use mingling_pathf::error::*; } -/// Provided for framework developers -pub mod debug; - -#[cfg(feature = "comp")] #[doc(hidden)] -pub mod comp; - -#[cfg(feature = "comp")] -pub use crate::comp::*; +mod private; -/// Module for setting up a `Mingling` program. +/// Internal API provided by Mingling Core /// -/// This module provides the [`ProgramSetup`] type, which allows users to configure -/// and initialize the program's execution environment. -pub mod setup { - pub use crate::program::setup::ProgramSetup; -} - -/// Private API — not intended for direct use. +/// These are used by macros and are not exposed to users, but are still accessible externally. #[doc(hidden)] +#[allow(unused_imports)] pub mod __private { - use crate::ProgramCollect; - - /// Sealed trait for `StructuralData` — only implementable via derive macro. - pub trait StructuralDataSealed<C> - where - C: ProgramCollect<Enum = C>, - { - } - - /// Re-export so the derive macro can reference the trait without - /// conflicting with the derive macro name at `::mingling::StructuralData`. - #[cfg(feature = "structural_renderer")] - pub use crate::renderer::structural::structural_data::StructuralData; -} - -#[doc(hidden)] -pub mod core_res { - #[cfg(feature = "repl")] - pub use crate::program::repl_exec::res::ResREPL; + pub use crate::private::*; } diff --git a/mingling_core/src/private.rs b/mingling_core/src/private.rs new file mode 100644 index 0000000..371ada2 --- /dev/null +++ b/mingling_core/src/private.rs @@ -0,0 +1,12 @@ +/// Sealed trait for `StructuralData` — only implementable via derive macro. +#[cfg(feature = "structural_renderer")] +pub trait StructuralDataSealed<C> +where + C: crate::ProgramCollect<Enum = C>, +{ +} + +/// Re-export so the derive macro can reference the trait without +/// conflicting with the derive macro name at `::mingling::StructuralData`. +#[cfg(feature = "structural_renderer")] +pub use crate::renderer::structural::structural_data::StructuralData; diff --git a/mingling_core/src/program/collection/mock.rs b/mingling_core/src/program/collection/mock.rs index 5847f10..dbe4789 100644 --- a/mingling_core/src/program/collection/mock.rs +++ b/mingling_core/src/program/collection/mock.rs @@ -23,9 +23,12 @@ pub enum MockProgramCollect { Bar, } -impl Grouped<MockProgramCollect> for MockProgramCollect { +/// SAFETY: This is a mock type used only for temporary testing. +/// It will never actually enter the macro system. +/// The internal `panic!` ensures that `member_id` will never be executed. +unsafe impl Grouped<MockProgramCollect> for MockProgramCollect { fn member_id() -> MockProgramCollect { - MockProgramCollect::Foo + panic!("Attempting to read an unsafe enum type"); } } diff --git a/mingling_core/src/program/hook.rs b/mingling_core/src/program/hook.rs index 48f632f..7d94a21 100644 --- a/mingling_core/src/program/hook.rs +++ b/mingling_core/src/program/hook.rs @@ -695,7 +695,17 @@ mod tests { } } - impl Grouped<MockHookEnum> for MockHookEnum { + /// SAFETY: + /// + /// This implementation is only for testing purposes to satisfy trait bounds. + /// Since this code only constructs `AnyOutput` and calls methods like + /// `downcast`, `is`, `restore`, `route_chain`, and `route_renderer` — + /// none of which involve `ProgramCollect::do_chain` or + /// `ProgramCollect::render` — the type/member_id correspondence is + /// never exploited in an unsafe way here. + /// The caller must ensure that the associated `member_id` correctly + /// corresponds to the type's role in the group. + unsafe impl Grouped<MockHookEnum> for MockHookEnum { fn member_id() -> MockHookEnum { MockHookEnum::A } diff --git a/mingling_core/src/renderer/render_result.rs b/mingling_core/src/renderer/render_result.rs index c38522a..8757376 100644 --- a/mingling_core/src/renderer/render_result.rs +++ b/mingling_core/src/renderer/render_result.rs @@ -6,7 +6,7 @@ use std::{ use crate::RenderResultMode::{Stderr, Stdout}; /// Render result, containing the rendered text content. -#[derive(Default, Debug, PartialEq)] +#[derive(Default, Debug, Clone, PartialEq, Eq)] pub struct RenderResult { /// Whether the output should be written immediately. /// diff --git a/mingling_core/tests/test-all/tests/integration.rs b/mingling_core/tests/test-all/tests/integration.rs index d36b9df..acb12db 100644 --- a/mingling_core/tests/test-all/tests/integration.rs +++ b/mingling_core/tests/test-all/tests/integration.rs @@ -7,9 +7,9 @@ use mingling::StringVec; use mingling::StructuralData; use mingling::StructuralRenderer; use mingling::StructuralRendererSetting; -use mingling::comp::{ShellContext, ShellFlag, Suggest}; use mingling::core_res::ResREPL; use mingling::hook::ProgramHook; +use mingling::{ShellContext, ShellFlag, Suggest}; use serde::Serialize; use std::sync::atomic::{AtomicBool, Ordering}; diff --git a/mingling_core/tests/test-comp/tests/integration.rs b/mingling_core/tests/test-comp/tests/integration.rs index 37aa716..5c5a9e4 100644 --- a/mingling_core/tests/test-comp/tests/integration.rs +++ b/mingling_core/tests/test-comp/tests/integration.rs @@ -1,6 +1,6 @@ use mingling::MockProgramCollect; use mingling::Program; -use mingling::comp::{ShellContext, ShellFlag, Suggest, SuggestItem}; +use mingling::{ShellContext, ShellFlag, Suggest, SuggestItem}; #[test] fn test_shell_context_parsing_full() { diff --git a/mingling_macros/src/derive/grouped.rs b/mingling_macros/src/derive/grouped.rs index 307aab6..a00eea1 100644 --- a/mingling_macros/src/derive/grouped.rs +++ b/mingling_macros/src/derive/grouped.rs @@ -16,7 +16,11 @@ pub(crate) fn derive_grouped(input: TokenStream) -> TokenStream { let expanded = quote! { ::mingling::macros::register_type!(#struct_name); - impl ::mingling::Grouped<#group_ident> for #struct_name { + /// SAFETY: This is an internal implementation of the `Grouped` derive macro, + /// guaranteeing that the enum value registered by the `register_type!` macro + /// is exactly the same as the actual return value, + /// which can be confirmed via the `Ident` in the `quote!` block. + unsafe impl ::mingling::Grouped<#group_ident> for #struct_name { fn member_id() -> #group_ident { #group_ident::#struct_name } @@ -46,7 +50,11 @@ pub fn derive_grouped_serialize(input: TokenStream) -> TokenStream { ::mingling::macros::register_type!(#struct_name); - impl ::mingling::Grouped<#group_ident> for #struct_name { + /// SAFETY: This is an internal implementation of the `Grouped` derive macro, + /// guaranteeing that the enum value registered by the `register_type!` macro + /// is exactly the same as the actual return value, + /// which can be confirmed via the `Ident` in the `quote!` block. + unsafe impl ::mingling::Grouped<#group_ident> for #struct_name { fn member_id() -> #group_ident { #group_ident::#struct_name } diff --git a/mingling_macros/src/func/gen_program.rs b/mingling_macros/src/func/gen_program.rs index de4da06..db82504 100644 --- a/mingling_macros/src/func/gen_program.rs +++ b/mingling_macros/src/func/gen_program.rs @@ -342,7 +342,7 @@ fn main() { ) -> Result<::mingling::RenderResult, ::mingling::error::StructuralRendererSerializeError> { #[allow(unused_imports)] #(#pathf_uses)* - match any.member_id { + match any.member_id() { #(#structural_renderer_tokens)* _ => { // Non-structural types: render ResultEmpty (which implements @@ -408,7 +408,7 @@ fn main() { fn do_comp(any: &::mingling::AnyOutput<Self::Enum>, ctx: &::mingling::ShellContext) -> ::mingling::Suggest { #[allow(unused_imports)] #(#pathf_uses)* - match any.member_id { + match any.member_id() { #(#completion_tokens)* _ => ::mingling::Suggest::FileCompletion, } @@ -442,7 +442,7 @@ fn main() { }).collect(); quote! { fn render(any: ::mingling::AnyOutput<Self::Enum>) -> ::mingling::RenderResult { - match any.member_id { + match any.member_id() { #(#render_arms)* _ => ::mingling::RenderResult::default(), } @@ -494,9 +494,9 @@ fn main() { fn do_chain( any: ::mingling::AnyOutput<Self::Enum>, ) -> ::std::pin::Pin<::std::boxed::Box<dyn ::std::future::Future<Output = ::mingling::ChainProcess<Self::Enum>> + ::std::marker::Send>> { - match any.member_id { + match any.member_id() { #(#chain_arms_async)* - _ => ::core::panic!("No chain found for type id: {:?}", any.type_id), + _ => ::core::panic!("No chain found for type id: {:?}", any.type_id()), } } } @@ -505,9 +505,9 @@ fn main() { fn do_chain( any: ::mingling::AnyOutput<Self::Enum>, ) -> ::mingling::ChainProcess<Self::Enum> { - match any.member_id { + match any.member_id() { #(#chain_arms_sync)* - _ => ::core::panic!("No chain found for type id: {:?}", any.type_id), + _ => ::core::panic!("No chain found for type id: {:?}", any.type_id()), } } } @@ -535,7 +535,7 @@ fn main() { let expanded = quote! { #pathf_hint - #[derive(Debug, PartialEq, Eq, Clone)] + #[derive(Debug, PartialEq, Eq, Clone, Copy)] #[repr(#repr_type)] #[allow(nonstandard_style)] pub enum #name { @@ -569,19 +569,19 @@ fn main() { fn render_help(any: ::mingling::AnyOutput<Self::Enum>) -> ::mingling::RenderResult { #[allow(unused_imports)] #(#pathf_uses)* - match any.member_id { + match any.member_id() { #(#help_tokens)* _ => ::mingling::RenderResult::default(), } } fn has_renderer(any: &::mingling::AnyOutput<Self::Enum>) -> bool { - match any.member_id { + match any.member_id() { #(#renderer_exist_tokens)* _ => false } } fn has_chain(any: &::mingling::AnyOutput<Self::Enum>) -> bool { - match any.member_id { + match any.member_id() { #(#chain_exist_tokens)* _ => false } diff --git a/mingling_macros/src/func/group.rs b/mingling_macros/src/func/group.rs index b865913..edb1fe1 100644 --- a/mingling_macros/src/func/group.rs +++ b/mingling_macros/src/func/group.rs @@ -133,7 +133,11 @@ pub(crate) fn group_macro(input: TokenStream) -> TokenStream { #type_use #alias_use - impl ::mingling::Grouped<__MinglingProgram> for #type_name { + /// SAFETY: This is an internal implementation of the `group!` macro, + /// guaranteeing that the enum value registered by the `register_type!` macro + /// is exactly the same as the actual return value, + /// which can be confirmed via the `Ident` in the `quote!` block. + unsafe impl ::mingling::Grouped<__MinglingProgram> for #type_name { fn member_id() -> __MinglingProgram { __MinglingProgram::#type_name } diff --git a/mingling_macros/src/func/pack.rs b/mingling_macros/src/func/pack.rs index a1a7e6b..7206b8e 100644 --- a/mingling_macros/src/func/pack.rs +++ b/mingling_macros/src/func/pack.rs @@ -138,7 +138,11 @@ pub(crate) fn pack(input: TokenStream) -> TokenStream { } } - impl ::mingling::Grouped<#group_name> for #type_name { + /// SAFETY: This is an internal implementation of the `pack!` macro, + /// guaranteeing that the enum value registered by the `register_type!` macro + /// is exactly the same as the actual return value, + /// which can be confirmed via the `Ident` in the `quote!` block. + unsafe impl ::mingling::Grouped<#group_name> for #type_name { fn member_id() -> #group_name { #group_name::#type_name } diff --git a/mingling_macros/src/systems/structural_data.rs b/mingling_macros/src/systems/structural_data.rs index 0a783ec..46d7cf8 100644 --- a/mingling_macros/src/systems/structural_data.rs +++ b/mingling_macros/src/systems/structural_data.rs @@ -176,7 +176,11 @@ pub(crate) fn pack_structural(input: TokenStream) -> TokenStream { } } - impl ::mingling::Grouped<#program_path> for #type_name { + /// SAFETY: This is an internal implementation of the `pack_structural!` macro, + /// guaranteeing that the enum value registered by the `register_type!` macro + /// is exactly the same as the actual return value, + /// which can be confirmed via the `Ident` in the `quote!` block. + unsafe impl ::mingling::Grouped<#program_path> for #type_name { fn member_id() -> #program_path { #program_path::#type_name } @@ -297,7 +301,11 @@ pub(crate) fn group_structural(input: TokenStream) -> TokenStream { #type_use #alias_use - impl ::mingling::Grouped<__MinglingProgram> for #type_name { + /// SAFETY: This is an internal implementation of the `pack_structural!` macro, + /// guaranteeing that the enum value registered by the `register_type!` macro + /// is exactly the same as the actual return value, + /// which can be confirmed via the `Ident` in the `quote!` block. + unsafe impl ::mingling::Grouped<__MinglingProgram> for #type_name { fn member_id() -> __MinglingProgram { __MinglingProgram::#type_name } diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..f328e4d --- /dev/null +++ b/src/main.rs @@ -0,0 +1 @@ +fn main() {} |
