aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/ci.yml5
-rw-r--r--.gitignore10
-rw-r--r--.idea/.gitignore10
-rw-r--r--.idea/mingling.iml18
-rw-r--r--.idea/modules.xml8
-rw-r--r--.idea/runConfigurations/Check__All.xml26
-rw-r--r--.idea/runConfigurations/Check__Codes.xml26
-rw-r--r--.idea/runConfigurations/Check__Documents.xml26
-rw-r--r--.idea/runConfigurations/Refresh_All.xml27
-rw-r--r--.idea/runConfigurations/Run__Cargo_Format.xml23
-rw-r--r--.idea/runConfigurations/Run__Deploy_API_Docs.xml28
-rw-r--r--.idea/runConfigurations/Run__Fix_Documents_Codebox.xml28
-rw-r--r--.idea/runConfigurations/Run__Generate_Docsify_Sidebar.xml28
-rw-r--r--.idea/runConfigurations/Run__Package_All_Crates.xml28
-rw-r--r--.idea/runConfigurations/Run__Sync_Examples_To_Crates.xml28
-rw-r--r--.idea/runConfigurations/Run__Sync_Examples_To_Helpdoc.xml28
-rw-r--r--.idea/runConfigurations/Run__Sync_Feature_List.xml28
-rw-r--r--.idea/vcs.xml6
-rw-r--r--.run/Cargo.lock109
-rw-r--r--.run/Cargo.toml2
-rw-r--r--.run/src/bin/RELEASE-WORKSPACE.rs145
-rwxr-xr-x[-rw-r--r--].run/src/bin/clippy-fix.sh0
-rw-r--r--.run/src/bin/deploy-api-docs.rs132
-rw-r--r--.run/src/bin/package-all.rs497
-rw-r--r--.run/src/dependency_order.rs21
-rw-r--r--.run/version-files.toml12
-rw-r--r--.vscode/settings.json13
-rw-r--r--.zed/settings.json13
-rw-r--r--CHANGELOG.md481
-rw-r--r--CONTRIBUTING.md54
-rw-r--r--Cargo.lock37
-rw-r--r--Cargo.toml9
-rw-r--r--GETTING_STARTED.md775
-rw-r--r--README.md893
-rw-r--r--arg_picker/Cargo.toml16
l---------arg_picker/LICENSE-APACHE1
l---------arg_picker/LICENSE-MIT1
-rw-r--r--arg_picker/README.md (renamed from mingling_picker/README.md)10
-rw-r--r--arg_picker/src/arg.rs (renamed from mingling_picker/src/arg.rs)26
-rw-r--r--arg_picker/src/builtin.rs (renamed from mingling_picker/src/builtin.rs)1
-rw-r--r--arg_picker/src/builtin/pick_bool.rs (renamed from mingling_picker/src/builtin/pick_bool.rs)0
-rw-r--r--arg_picker/src/builtin/pick_flag.rs (renamed from mingling_picker/src/builtin/pick_flag.rs)0
-rw-r--r--arg_picker/src/builtin/pick_numbers.rs (renamed from mingling_picker/src/builtin/pick_numbers.rs)0
-rw-r--r--arg_picker/src/builtin/pick_picker_args.rs21
-rw-r--r--arg_picker/src/builtin/pick_string.rs (renamed from mingling_picker/src/builtin/pick_string.rs)0
-rw-r--r--arg_picker/src/corebind.rs2
-rw-r--r--arg_picker/src/infos.rs (renamed from mingling_picker/src/infos.rs)22
-rw-r--r--arg_picker/src/lib.rs (renamed from mingling_picker/src/lib.rs)15
-rw-r--r--arg_picker/src/parselib.rs (renamed from mingling_picker/src/parselib.rs)23
-rw-r--r--arg_picker/src/parselib/arg_matcher.rs (renamed from mingling_picker/src/parselib/arg_matcher.rs)0
-rw-r--r--arg_picker/src/parselib/flag_matcher.rs (renamed from mingling_picker/src/parselib/flag_matcher.rs)0
-rw-r--r--arg_picker/src/parselib/multi_arg_matcher.rs (renamed from mingling_picker/src/parselib/multi_arg_matcher.rs)8
-rw-r--r--arg_picker/src/parselib/pos_matcher.rs (renamed from mingling_picker/src/parselib/pos_matcher.rs)0
-rw-r--r--arg_picker/src/parselib/single_matcher.rs (renamed from mingling_picker/src/parselib/single_matcher.rs)0
-rw-r--r--arg_picker/src/parselib/style.rs (renamed from mingling_picker/src/parselib/style.rs)12
-rw-r--r--arg_picker/src/parselib/utils.rs (renamed from mingling_picker/src/parselib/utils.rs)0
-rw-r--r--arg_picker/src/pickable.rs (renamed from mingling_picker/src/pickable.rs)0
-rw-r--r--arg_picker/src/pickable/multi_pickable.rs (renamed from mingling_picker/src/pickable/multi_pickable.rs)3
-rw-r--r--arg_picker/src/pickable/single_pickable.rs (renamed from mingling_picker/src/pickable/single_pickable.rs)0
-rw-r--r--arg_picker/src/picker.rs (renamed from mingling_picker/src/picker.rs)130
-rw-r--r--arg_picker/src/picker/parse.rs (renamed from mingling_picker/src/picker/parse.rs)12
-rw-r--r--arg_picker/src/picker/patterns.rs (renamed from mingling_picker/src/picker/patterns.rs)104
-rw-r--r--arg_picker/src/picker/result.rs (renamed from mingling_picker/src/picker/result.rs)10
-rw-r--r--arg_picker/src/value.rs (renamed from mingling_picker/src/value.rs)0
-rw-r--r--arg_picker/src/value/flag.rs (renamed from mingling_picker/src/value/flag.rs)6
-rw-r--r--arg_picker/src/value/vec_until.rs (renamed from mingling_picker/src/value/vec_until.rs)0
-rw-r--r--arg_picker/test/Cargo.lock (renamed from mingling_picker/test/Cargo.lock)36
-rw-r--r--arg_picker/test/Cargo.toml (renamed from mingling_picker/test/Cargo.toml)4
-rw-r--r--arg_picker/test/src/lib.rs (renamed from mingling_picker/test/src/lib.rs)2
-rw-r--r--arg_picker/test/src/test.rs (renamed from mingling_picker/test/src/test.rs)0
-rw-r--r--arg_picker/test/src/test/arg_matcher_test.rs (renamed from mingling_picker/test/src/test/arg_matcher_test.rs)4
-rw-r--r--arg_picker/test/src/test/basic_test.rs (renamed from mingling_picker/test/src/test/basic_test.rs)2
-rw-r--r--arg_picker/test/src/test/multi_arg_test.rs (renamed from mingling_picker/test/src/test/multi_arg_test.rs)25
-rw-r--r--arg_picker/test/src/test/multi_value_test.rs (renamed from mingling_picker/test/src/test/multi_value_test.rs)4
-rw-r--r--arg_picker/test/src/test/pos_matcher_test.rs (renamed from mingling_picker/test/src/test/pos_matcher_test.rs)4
-rw-r--r--arg_picker/test/src/test/priority_test.rs (renamed from mingling_picker/test/src/test/priority_test.rs)4
-rw-r--r--arg_picker/test/src/test/route_test.rs (renamed from mingling_picker/test/src/test/route_test.rs)4
-rw-r--r--arg_picker/test/src/test/style_test.rs (renamed from mingling_picker/test/src/test/style_test.rs)8
-rw-r--r--arg_picker/test/src/test/value_flag_test.rs (renamed from mingling_picker/test/src/test/value_flag_test.rs)12
-rw-r--r--arg_picker/test/src/test/value_string_test.rs (renamed from mingling_picker/test/src/test/value_string_test.rs)2
-rw-r--r--arg_picker_macros/Cargo.toml (renamed from mingling_picker_macros/Cargo.toml)10
l---------arg_picker_macros/LICENSE-APACHE1
l---------arg_picker_macros/LICENSE-MIT1
-rw-r--r--arg_picker_macros/README.md (renamed from mingling_picker_macros/README.md)0
-rw-r--r--arg_picker_macros/src/arg.rs (renamed from mingling_picker_macros/src/arg.rs)2
-rw-r--r--arg_picker_macros/src/internal_repeat.rs (renamed from mingling_picker_macros/src/internal_repeat.rs)0
-rw-r--r--arg_picker_macros/src/lib.rs (renamed from mingling_picker_macros/src/lib.rs)2
-rw-r--r--docs/_zh_CN/pages/11-resource-system.md2
-rw-r--r--docs/_zh_CN/pages/3-define-a-chain.md8
-rw-r--r--docs/_zh_CN/pages/4-render-result.md2
-rw-r--r--docs/_zh_CN/pages/5-multiple-commands.md8
-rw-r--r--docs/_zh_CN/pages/6-argument-parse-picker.md16
-rw-r--r--docs/_zh_CN/pages/7-argument-parse-clap.md4
-rw-r--r--docs/_zh_CN/pages/advanced/2-structural-renderer.md6
-rw-r--r--docs/_zh_CN/pages/concepts/3-any-output.md10
-rw-r--r--docs/_zh_CN/pages/other/features.md22
-rw-r--r--docs/_zh_CN/pages/other/naming_rule.md2
-rw-r--r--docs/dev/pages/abouts/ci.md35
-rw-r--r--docs/dev/pages/issues/add-picker2.md2
-rw-r--r--docs/example-pages/examples.json15
-rw-r--r--docs/pages/11-resource-system.md2
-rw-r--r--docs/pages/3-define-a-chain.md8
-rw-r--r--docs/pages/4-render-result.md2
-rw-r--r--docs/pages/5-multiple-commands.md8
-rw-r--r--docs/pages/6-argument-parse-picker.md14
-rw-r--r--docs/pages/7-argument-parse-clap.md4
-rw-r--r--docs/pages/advanced/2-structural-renderer.md6
-rw-r--r--docs/pages/concepts/3-any-output.md10
-rw-r--r--docs/pages/other/features.md22
-rw-r--r--docs/pages/other/naming_rule.md2
-rw-r--r--examples/example-argument-parse/src/main.rs2
-rw-r--r--examples/example-argument-picker/Cargo.lock94
-rw-r--r--examples/example-argument-picker/Cargo.toml12
-rw-r--r--examples/example-argument-picker/page.toml10
-rw-r--r--examples/example-argument-picker/src/main.rs225
-rw-r--r--examples/example-async-support/src/main.rs2
-rw-r--r--examples/example-basic/src/main.rs2
-rw-r--r--examples/example-clap-binding/src/main.rs8
-rw-r--r--examples/example-combine-pathf-dispatch-tree/src/sub/mod.rs2
-rw-r--r--examples/example-completion/src/main.rs2
-rw-r--r--examples/example-custom-pickable/src/main.rs8
-rw-r--r--examples/example-enum-tag/src/main.rs6
-rw-r--r--examples/example-hook/src/main.rs2
-rw-r--r--examples/example-outside-type/src/main.rs2
-rw-r--r--examples/example-panic-unwind/src/main.rs2
-rw-r--r--examples/example-pathfinder/src/sub/mod.rs2
-rw-r--r--examples/example-repl-basic/src/main.rs2
-rw-r--r--examples/example-resources/src/main.rs2
-rw-r--r--examples/example-structural-renderer/src/main.rs14
-rw-r--r--examples/full-todolist/src/todolist.rs4
-rw-r--r--examples/test-examples.toml35
-rw-r--r--mingling/Cargo.toml4
-rw-r--r--mingling/src/constants.rs8
-rw-r--r--mingling/src/constants/exit_codes.rs14
-rw-r--r--mingling/src/constants/picker.rs129
-rw-r--r--mingling/src/example_docs.rs299
-rw-r--r--mingling/src/features.rs22
-rw-r--r--mingling/src/lib.md96
-rw-r--r--mingling/src/lib.rs101
-rw-r--r--mingling/src/parser/picker.rs4
-rw-r--r--mingling/src/picker.rs13
-rw-r--r--mingling/src/picker/entry_picker.rs126
-rw-r--r--mingling/src/picker/global.rs35
-rw-r--r--mingling/src/setups.rs7
-rw-r--r--mingling/src/setups/basic.rs26
-rw-r--r--mingling/src/setups/exit_code.rs11
-rw-r--r--mingling/src/setups/picker.rs7
-rw-r--r--mingling/src/setups/picker/basic.rs123
-rw-r--r--mingling/src/setups/picker/structural_renderer.rs76
-rw-r--r--mingling/src/setups/structural_renderer.rs14
-rw-r--r--mingling_core/src/any.rs22
-rw-r--r--mingling_core/src/any/group.rs20
-rw-r--r--mingling_core/src/asset.rs3
-rw-r--r--mingling_core/src/asset/global_resource.rs7
-rw-r--r--mingling_core/src/asset/routable.rs22
-rw-r--r--mingling_core/src/comp.rs2
-rw-r--r--mingling_core/src/lib.rs1
-rw-r--r--mingling_core/src/program.rs27
-rw-r--r--mingling_core/src/program/collection.rs8
-rw-r--r--mingling_core/src/program/collection/mock.rs4
-rw-r--r--mingling_core/src/program/hook.rs4
-rw-r--r--mingling_core/src/program/setup.rs17
-rw-r--r--mingling_core/src/renderer/render_result.rs50
-rw-r--r--mingling_macros/src/chain.rs216
-rw-r--r--mingling_macros/src/dispatcher.rs2
-rw-r--r--mingling_macros/src/group_impl.rs4
-rw-r--r--mingling_macros/src/grouped.rs (renamed from mingling_macros/src/groupped.rs)12
-rw-r--r--mingling_macros/src/help.rs45
-rw-r--r--mingling_macros/src/lib.rs120
-rw-r--r--mingling_macros/src/pack.rs2
-rw-r--r--mingling_macros/src/pack_err.rs8
-rw-r--r--mingling_macros/src/renderer.rs47
-rw-r--r--mingling_macros/src/res_injection.rs25
-rw-r--r--mingling_macros/src/structural_data.rs4
-rw-r--r--mingling_pathf/README.md2
-rw-r--r--mingling_pathf/src/pattern_analyzer.rs2
-rw-r--r--mingling_pathf/src/patterns.rs4
-rw-r--r--mingling_pathf/src/patterns/grouped_derive.rs (renamed from mingling_pathf/src/patterns/groupped_derive.rs)34
-rw-r--r--mingling_pathf/test/src/lib.rs4
-rw-r--r--mingling_pathf/test/src/test_files/test_grouped_derive.rs (renamed from mingling_pathf/test/src/test_files/test_groupped_derive.rs)16
-rw-r--r--mingling_picker/Cargo.toml17
-rw-r--r--mingling_picker/src/corebind.rs1
-rw-r--r--mling/src/cli.rs5
-rw-r--r--mling/src/proj_mgr/generator.rs2
-rw-r--r--mling/src/proj_mgr/show_binaries.rs4
-rw-r--r--mling/src/proj_mgr/show_directories.rs6
-rwxr-xr-x[-rw-r--r--]run.sh0
-rw-r--r--verified-docs.toml1
188 files changed, 4695 insertions, 1962 deletions
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index faa513a..b81c1ef 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -73,6 +73,11 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
+ - uses: actions-rust-lang/setup-rust-toolchain@v1
+
+ - name: Build API docs
+ run: cargo run --manifest-path .run/Cargo.toml --bin deploy-api-docs
+
- name: Setup Pages
uses: actions/configure-pages@v5
- name: Upload artifact
diff --git a/.gitignore b/.gitignore
index c868070..274c6cf 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,7 +1,10 @@
.temp
-# Temp file for dev_tools/scripts/windows-folder-hide.ps1
-dev_tools/scripts/last_check
+# Temp file for .run/src/bin/windows-folder-hide.ps1
+.run/src/bin/last_check
+
+# Temp file for .run/src/bin/deploy-api-docs.rs
+docs/api-docs/
# Drafts
__*.md
@@ -9,6 +12,3 @@ __*/
# Fuck
nul
-
-# Release script
-Cargo.toml.bkup
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..30cf57e
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,10 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Ignored default folder with query files
+/queries/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/.idea/mingling.iml b/.idea/mingling.iml
new file mode 100644
index 0000000..ca57e6c
--- /dev/null
+++ b/.idea/mingling.iml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="EMPTY_MODULE" version="4">
+ <component name="NewModuleRootManager">
+ <content url="file://$MODULE_DIR$">
+ <sourceFolder url="file://$MODULE_DIR$/arg_picker/src" isTestSource="false" />
+ <sourceFolder url="file://$MODULE_DIR$/arg_picker_macros/src" isTestSource="false" />
+ <sourceFolder url="file://$MODULE_DIR$/mingling/src" isTestSource="false" />
+ <sourceFolder url="file://$MODULE_DIR$/mingling_core/src" isTestSource="false" />
+ <sourceFolder url="file://$MODULE_DIR$/mingling_core/tests" isTestSource="true" />
+ <sourceFolder url="file://$MODULE_DIR$/mingling_macros/src" isTestSource="false" />
+ <sourceFolder url="file://$MODULE_DIR$/mingling_pathf/src" isTestSource="false" />
+ <sourceFolder url="file://$MODULE_DIR$/mling/src" isTestSource="false" />
+ <excludeFolder url="file://$MODULE_DIR$/target" />
+ </content>
+ <orderEntry type="inheritedJdk" />
+ <orderEntry type="sourceFolder" forTests="false" />
+ </component>
+</module> \ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..764f612
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="ProjectModuleManager">
+ <modules>
+ <module fileurl="file://$PROJECT_DIR$/.idea/mingling.iml" filepath="$PROJECT_DIR$/.idea/mingling.iml" />
+ </modules>
+ </component>
+</project> \ No newline at end of file
diff --git a/.idea/runConfigurations/Check__All.xml b/.idea/runConfigurations/Check__All.xml
new file mode 100644
index 0000000..95af540
--- /dev/null
+++ b/.idea/runConfigurations/Check__All.xml
@@ -0,0 +1,26 @@
+<component name="ProjectRunConfigurationManager">
+ <configuration
+ default="false"
+ name="Check: All"
+ type="CargoCommandRunConfiguration"
+ factoryName="Cargo Command"
+ folderName="CI"
+ >
+ <option
+ name="command"
+ value="run --manifest-path .run/Cargo.toml --bin ci --quiet -- -y"
+ />
+ <option name="workingDirectory" value="file://$PROJECT_DIR$" />
+ <envs />
+ <option name="emulateTerminal" value="true" />
+ <option name="channel" value="DEFAULT" />
+ <option name="requiredFeatures" value="true" />
+ <option name="allFeatures" value="false" />
+ <option name="withSudo" value="false" />
+ <option name="buildTarget" value="REMOTE" />
+ <option name="backtrace" value="SHORT" />
+ <option name="isRedirectInput" value="false" />
+ <option name="redirectInputPath" value="" />
+ <method v="2" />
+ </configuration>
+</component>
diff --git a/.idea/runConfigurations/Check__Codes.xml b/.idea/runConfigurations/Check__Codes.xml
new file mode 100644
index 0000000..f117f8f
--- /dev/null
+++ b/.idea/runConfigurations/Check__Codes.xml
@@ -0,0 +1,26 @@
+<component name="ProjectRunConfigurationManager">
+ <configuration
+ default="false"
+ name="Check: Codes"
+ type="CargoCommandRunConfiguration"
+ factoryName="Cargo Command"
+ folderName="CI"
+ >
+ <option
+ name="command"
+ value="run --manifest-path .run/Cargo.toml --bin ci --quiet -- --test-codes -y"
+ />
+ <option name="workingDirectory" value="file://$PROJECT_DIR$" />
+ <envs />
+ <option name="emulateTerminal" value="true" />
+ <option name="channel" value="DEFAULT" />
+ <option name="requiredFeatures" value="true" />
+ <option name="allFeatures" value="false" />
+ <option name="withSudo" value="false" />
+ <option name="buildTarget" value="REMOTE" />
+ <option name="backtrace" value="SHORT" />
+ <option name="isRedirectInput" value="false" />
+ <option name="redirectInputPath" value="" />
+ <method v="2" />
+ </configuration>
+</component>
diff --git a/.idea/runConfigurations/Check__Documents.xml b/.idea/runConfigurations/Check__Documents.xml
new file mode 100644
index 0000000..b5a503b
--- /dev/null
+++ b/.idea/runConfigurations/Check__Documents.xml
@@ -0,0 +1,26 @@
+<component name="ProjectRunConfigurationManager">
+ <configuration
+ default="false"
+ name="Check: Documents"
+ type="CargoCommandRunConfiguration"
+ factoryName="Cargo Command"
+ folderName="CI"
+ >
+ <option
+ name="command"
+ value="run --manifest-path .run/Cargo.toml --bin ci --quiet -- --test-docs -y"
+ />
+ <option name="workingDirectory" value="file://$PROJECT_DIR$" />
+ <envs />
+ <option name="emulateTerminal" value="true" />
+ <option name="channel" value="DEFAULT" />
+ <option name="requiredFeatures" value="true" />
+ <option name="allFeatures" value="false" />
+ <option name="withSudo" value="false" />
+ <option name="buildTarget" value="REMOTE" />
+ <option name="backtrace" value="SHORT" />
+ <option name="isRedirectInput" value="false" />
+ <option name="redirectInputPath" value="" />
+ <method v="2" />
+ </configuration>
+</component>
diff --git a/.idea/runConfigurations/Refresh_All.xml b/.idea/runConfigurations/Refresh_All.xml
new file mode 100644
index 0000000..24be845
--- /dev/null
+++ b/.idea/runConfigurations/Refresh_All.xml
@@ -0,0 +1,27 @@
+<component name="ProjectRunConfigurationManager">
+ <configuration
+ default="false"
+ name="Refresh All"
+ type="CompoundRunConfigurationType"
+ >
+ <toRun name="Run: Cargo Format" type="CargoCommandRunConfiguration" />
+ <toRun
+ name="Run: Fix Documents Codebox"
+ type="CargoCommandRunConfiguration"
+ />
+ <toRun
+ name="Run: Generate Docsify Sidebar"
+ type="CargoCommandRunConfiguration"
+ />
+ <toRun
+ name="Run: Sync Examples To Crates"
+ type="CargoCommandRunConfiguration"
+ />
+ <toRun
+ name="Run: Sync Examples To Helpdoc"
+ type="CargoCommandRunConfiguration"
+ />
+ <toRun name="Run: Sync Feature List" type="CargoCommandRunConfiguration" />
+ <method v="2" />
+ </configuration>
+</component>
diff --git a/.idea/runConfigurations/Run__Cargo_Format.xml b/.idea/runConfigurations/Run__Cargo_Format.xml
new file mode 100644
index 0000000..a99a91d
--- /dev/null
+++ b/.idea/runConfigurations/Run__Cargo_Format.xml
@@ -0,0 +1,23 @@
+<component name="ProjectRunConfigurationManager">
+ <configuration
+ default="false"
+ name="Run: Cargo Format"
+ type="CargoCommandRunConfiguration"
+ factoryName="Cargo Command"
+ folderName="Tools"
+ >
+ <option name="command" value="fmt" />
+ <option name="workingDirectory" value="file://$PROJECT_DIR$" />
+ <envs />
+ <option name="emulateTerminal" value="true" />
+ <option name="channel" value="DEFAULT" />
+ <option name="requiredFeatures" value="true" />
+ <option name="allFeatures" value="false" />
+ <option name="withSudo" value="false" />
+ <option name="buildTarget" value="REMOTE" />
+ <option name="backtrace" value="SHORT" />
+ <option name="isRedirectInput" value="false" />
+ <option name="redirectInputPath" value="" />
+ <method v="2" />
+ </configuration>
+</component>
diff --git a/.idea/runConfigurations/Run__Deploy_API_Docs.xml b/.idea/runConfigurations/Run__Deploy_API_Docs.xml
new file mode 100644
index 0000000..1a32a64
--- /dev/null
+++ b/.idea/runConfigurations/Run__Deploy_API_Docs.xml
@@ -0,0 +1,28 @@
+<component name="ProjectRunConfigurationManager">
+ <configuration
+ default="false"
+ name="Run: Deploy API Docs"
+ type="CargoCommandRunConfiguration"
+ factoryName="Cargo Command"
+ folderName="Tools"
+ >
+ <option
+ name="command"
+ value="run --manifest-path .run/Cargo.toml --quiet --bin deploy-api-docs"
+ />
+ <option name="workingDirectory" value="file://$PROJECT_DIR$" />
+ <envs />
+ <option name="emulateTerminal" value="true" />
+ <option name="channel" value="DEFAULT" />
+ <option name="requiredFeatures" value="true" />
+ <option name="allFeatures" value="false" />
+ <option name="withSudo" value="false" />
+ <option name="buildTarget" value="REMOTE" />
+ <option name="backtrace" value="SHORT" />
+ <option name="isRedirectInput" value="false" />
+ <option name="redirectInputPath" value="" />
+ <method v="2">
+ <option name="CARGO.BUILD_TASK_PROVIDER" enabled="true" />
+ </method>
+ </configuration>
+</component>
diff --git a/.idea/runConfigurations/Run__Fix_Documents_Codebox.xml b/.idea/runConfigurations/Run__Fix_Documents_Codebox.xml
new file mode 100644
index 0000000..0863ef1
--- /dev/null
+++ b/.idea/runConfigurations/Run__Fix_Documents_Codebox.xml
@@ -0,0 +1,28 @@
+<component name="ProjectRunConfigurationManager">
+ <configuration
+ default="false"
+ name="Run: Fix Documents Codebox"
+ type="CargoCommandRunConfiguration"
+ factoryName="Cargo Command"
+ folderName="Tools"
+ >
+ <option
+ name="command"
+ value="run --manifest-path .run/Cargo.toml --quiet --bin docs-code-box-fix"
+ />
+ <option name="workingDirectory" value="file://$PROJECT_DIR$" />
+ <envs />
+ <option name="emulateTerminal" value="true" />
+ <option name="channel" value="DEFAULT" />
+ <option name="requiredFeatures" value="true" />
+ <option name="allFeatures" value="false" />
+ <option name="withSudo" value="false" />
+ <option name="buildTarget" value="REMOTE" />
+ <option name="backtrace" value="SHORT" />
+ <option name="isRedirectInput" value="false" />
+ <option name="redirectInputPath" value="" />
+ <method v="2">
+ <option name="CARGO.BUILD_TASK_PROVIDER" enabled="true" />
+ </method>
+ </configuration>
+</component>
diff --git a/.idea/runConfigurations/Run__Generate_Docsify_Sidebar.xml b/.idea/runConfigurations/Run__Generate_Docsify_Sidebar.xml
new file mode 100644
index 0000000..a50a2dd
--- /dev/null
+++ b/.idea/runConfigurations/Run__Generate_Docsify_Sidebar.xml
@@ -0,0 +1,28 @@
+<component name="ProjectRunConfigurationManager">
+ <configuration
+ default="false"
+ name="Run: Generate Docsify Sidebar"
+ type="CargoCommandRunConfiguration"
+ factoryName="Cargo Command"
+ folderName="Tools"
+ >
+ <option
+ name="command"
+ value="run --manifest-path .run/Cargo.toml --quiet --bin docsify-sidebar-gen"
+ />
+ <option name="workingDirectory" value="file://$PROJECT_DIR$" />
+ <envs />
+ <option name="emulateTerminal" value="true" />
+ <option name="channel" value="DEFAULT" />
+ <option name="requiredFeatures" value="true" />
+ <option name="allFeatures" value="false" />
+ <option name="withSudo" value="false" />
+ <option name="buildTarget" value="REMOTE" />
+ <option name="backtrace" value="SHORT" />
+ <option name="isRedirectInput" value="false" />
+ <option name="redirectInputPath" value="" />
+ <method v="2">
+ <option name="CARGO.BUILD_TASK_PROVIDER" enabled="true" />
+ </method>
+ </configuration>
+</component>
diff --git a/.idea/runConfigurations/Run__Package_All_Crates.xml b/.idea/runConfigurations/Run__Package_All_Crates.xml
new file mode 100644
index 0000000..336c9b3
--- /dev/null
+++ b/.idea/runConfigurations/Run__Package_All_Crates.xml
@@ -0,0 +1,28 @@
+<component name="ProjectRunConfigurationManager">
+ <configuration
+ default="false"
+ name="Run: Package All Crates"
+ type="CargoCommandRunConfiguration"
+ factoryName="Cargo Command"
+ folderName="Tools"
+ >
+ <option
+ name="command"
+ value="run --manifest-path .run/Cargo.toml --quiet --bin package-all"
+ />
+ <option name="workingDirectory" value="file://$PROJECT_DIR$" />
+ <envs />
+ <option name="emulateTerminal" value="true" />
+ <option name="channel" value="DEFAULT" />
+ <option name="requiredFeatures" value="true" />
+ <option name="allFeatures" value="false" />
+ <option name="withSudo" value="false" />
+ <option name="buildTarget" value="REMOTE" />
+ <option name="backtrace" value="SHORT" />
+ <option name="isRedirectInput" value="false" />
+ <option name="redirectInputPath" value="" />
+ <method v="2">
+ <option name="CARGO.BUILD_TASK_PROVIDER" enabled="true" />
+ </method>
+ </configuration>
+</component>
diff --git a/.idea/runConfigurations/Run__Sync_Examples_To_Crates.xml b/.idea/runConfigurations/Run__Sync_Examples_To_Crates.xml
new file mode 100644
index 0000000..43db9bb
--- /dev/null
+++ b/.idea/runConfigurations/Run__Sync_Examples_To_Crates.xml
@@ -0,0 +1,28 @@
+<component name="ProjectRunConfigurationManager">
+ <configuration
+ default="false"
+ name="Run: Sync Examples To Crates"
+ type="CargoCommandRunConfiguration"
+ factoryName="Cargo Command"
+ folderName="Tools"
+ >
+ <option
+ name="command"
+ value="run --manifest-path .run/Cargo.toml --quiet --bin refresh-docs"
+ />
+ <option name="workingDirectory" value="file://$PROJECT_DIR$" />
+ <envs />
+ <option name="emulateTerminal" value="true" />
+ <option name="channel" value="DEFAULT" />
+ <option name="requiredFeatures" value="true" />
+ <option name="allFeatures" value="false" />
+ <option name="withSudo" value="false" />
+ <option name="buildTarget" value="REMOTE" />
+ <option name="backtrace" value="SHORT" />
+ <option name="isRedirectInput" value="false" />
+ <option name="redirectInputPath" value="" />
+ <method v="2">
+ <option name="CARGO.BUILD_TASK_PROVIDER" enabled="true" />
+ </method>
+ </configuration>
+</component>
diff --git a/.idea/runConfigurations/Run__Sync_Examples_To_Helpdoc.xml b/.idea/runConfigurations/Run__Sync_Examples_To_Helpdoc.xml
new file mode 100644
index 0000000..8e7faff
--- /dev/null
+++ b/.idea/runConfigurations/Run__Sync_Examples_To_Helpdoc.xml
@@ -0,0 +1,28 @@
+<component name="ProjectRunConfigurationManager">
+ <configuration
+ default="false"
+ name="Run: Sync Examples To Helpdoc"
+ type="CargoCommandRunConfiguration"
+ factoryName="Cargo Command"
+ folderName="Tools"
+ >
+ <option
+ name="command"
+ value="run --manifest-path .run/Cargo.toml --quiet --bin sync-examples"
+ />
+ <option name="workingDirectory" value="file://$PROJECT_DIR$" />
+ <envs />
+ <option name="emulateTerminal" value="true" />
+ <option name="channel" value="DEFAULT" />
+ <option name="requiredFeatures" value="true" />
+ <option name="allFeatures" value="false" />
+ <option name="withSudo" value="false" />
+ <option name="buildTarget" value="REMOTE" />
+ <option name="backtrace" value="SHORT" />
+ <option name="isRedirectInput" value="false" />
+ <option name="redirectInputPath" value="" />
+ <method v="2">
+ <option name="CARGO.BUILD_TASK_PROVIDER" enabled="true" />
+ </method>
+ </configuration>
+</component>
diff --git a/.idea/runConfigurations/Run__Sync_Feature_List.xml b/.idea/runConfigurations/Run__Sync_Feature_List.xml
new file mode 100644
index 0000000..1e6ed8f
--- /dev/null
+++ b/.idea/runConfigurations/Run__Sync_Feature_List.xml
@@ -0,0 +1,28 @@
+<component name="ProjectRunConfigurationManager">
+ <configuration
+ default="false"
+ name="Run: Sync Feature List"
+ type="CargoCommandRunConfiguration"
+ factoryName="Cargo Command"
+ folderName="Tools"
+ >
+ <option
+ name="command"
+ value="run --manifest-path .run/Cargo.toml --quiet --bin refresh-feature-mod"
+ />
+ <option name="workingDirectory" value="file://$PROJECT_DIR$" />
+ <envs />
+ <option name="emulateTerminal" value="true" />
+ <option name="channel" value="DEFAULT" />
+ <option name="requiredFeatures" value="true" />
+ <option name="allFeatures" value="false" />
+ <option name="withSudo" value="false" />
+ <option name="buildTarget" value="REMOTE" />
+ <option name="backtrace" value="SHORT" />
+ <option name="isRedirectInput" value="false" />
+ <option name="redirectInputPath" value="" />
+ <method v="2">
+ <option name="CARGO.BUILD_TASK_PROVIDER" enabled="true" />
+ </method>
+ </configuration>
+</component>
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..35eb1dd
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="VcsDirectoryMappings">
+ <mapping directory="" vcs="Git" />
+ </component>
+</project> \ No newline at end of file
diff --git a/.run/Cargo.lock b/.run/Cargo.lock
index bb510bd..c255ad0 100644
--- a/.run/Cargo.lock
+++ b/.run/Cargo.lock
@@ -3,6 +3,18 @@
version = 4
[[package]]
+name = "adler2"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
+
+[[package]]
+name = "bitflags"
+version = "2.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da"
+
+[[package]]
name = "bumpalo"
version = "3.20.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -36,6 +48,15 @@ dependencies = [
]
[[package]]
+name = "crc32fast"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
name = "encode_unicode"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -48,6 +69,36 @@ 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"
+checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
+dependencies = [
+ "libc",
+ "windows-sys",
+]
+
+[[package]]
+name = "filetime"
+version = "0.2.29"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c287a33c7f0a620c38e641e7f60827713987b3c0f26e8ddc9462cc69cf75759"
+dependencies = [
+ "cfg-if",
+ "libc",
+]
+
+[[package]]
+name = "flate2"
+version = "1.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c"
+dependencies = [
+ "crc32fast",
+ "miniz_oxide",
+]
+
+[[package]]
name = "futures-core"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -139,12 +190,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
[[package]]
+name = "linux-raw-sys"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
+
+[[package]]
name = "memchr"
version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
[[package]]
+name = "miniz_oxide"
+version = "0.8.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
+dependencies = [
+ "adler2",
+ "simd-adler32",
+]
+
+[[package]]
name = "once_cell"
version = "1.21.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -181,6 +248,19 @@ dependencies = [
]
[[package]]
+name = "rustix"
+version = "1.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
+dependencies = [
+ "bitflags",
+ "errno",
+ "libc",
+ "linux-raw-sys",
+ "windows-sys",
+]
+
+[[package]]
name = "rustversion"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -239,6 +319,12 @@ dependencies = [
]
[[package]]
+name = "simd-adler32"
+version = "0.3.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea"
+
+[[package]]
name = "slab"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -256,6 +342,17 @@ dependencies = [
]
[[package]]
+name = "tar"
+version = "0.4.46"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f6221d9a6003c78398e3b239969f352578258df48c8eb051caadae0015bc840"
+dependencies = [
+ "filetime",
+ "libc",
+ "xattr",
+]
+
+[[package]]
name = "tokio"
version = "1.52.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -322,11 +419,13 @@ name = "tools"
version = "0.1.0"
dependencies = [
"colored",
+ "flate2",
"indicatif",
"just_fmt",
"just_template",
"serde",
"serde_json",
+ "tar",
"tokio",
"toml",
]
@@ -429,6 +528,16 @@ dependencies = [
]
[[package]]
+name = "xattr"
+version = "1.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156"
+dependencies = [
+ "libc",
+ "rustix",
+]
+
+[[package]]
name = "zmij"
version = "1.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/.run/Cargo.toml b/.run/Cargo.toml
index 283aa47..caeb506 100644
--- a/.run/Cargo.toml
+++ b/.run/Cargo.toml
@@ -19,5 +19,7 @@ serde = { version = "1", features = ["derive"] }
serde_json = "1"
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
indicatif = "0.18.4"
+flate2 = "1"
+tar = "0.4"
[workspace]
diff --git a/.run/src/bin/RELEASE-WORKSPACE.rs b/.run/src/bin/RELEASE-WORKSPACE.rs
deleted file mode 100644
index 8cb4d1b..0000000
--- a/.run/src/bin/RELEASE-WORKSPACE.rs
+++ /dev/null
@@ -1,145 +0,0 @@
-use std::path::{Path, PathBuf};
-use std::process;
-use tools::dependency_order::{display_dependency_order, find_workspace_root};
-use tools::{
- eprintln_cargo_style, println_cargo_style, run_cmd_capture_with_dir, run_cmd_with_dir,
-};
-
-/// Read the version from `[workspace.package].version` in the root Cargo.toml.
-fn read_workspace_version(workspace_root: &Path) -> Option<String> {
- let content = std::fs::read_to_string(workspace_root.join("Cargo.toml")).ok()?;
- let value: toml::Value = content.parse().ok()?;
- value
- .get("workspace")?
- .get("package")?
- .get("version")?
- .as_str()
- .map(String::from)
-}
-
-/// Replace `path = "crate_name"` with `version = "VERSION"` in the file content.
-fn update_path_to_version(content: &str, crate_name: &str, version: &str) -> String {
- let old = format!("path = \"{}\"", crate_name);
- let new = format!("version = \"{}\"", version);
- content.replace(&old, &new)
-}
-
-/// Restore the backup and abort.
-fn abort(toml_path: &Path) -> ! {
- let backup_path = toml_path.with_extension("toml.bkup");
- if backup_path.exists() {
- if let Err(e) = std::fs::copy(&backup_path, toml_path) {
- eprintln_cargo_style!("failed to restore Cargo.toml from backup: {}", e);
- } else {
- eprintln_cargo_style!("restored Cargo.toml from backup");
- }
- }
- process::exit(1);
-}
-
-fn main() {
- // 1. Compute dependency order
- let order = display_dependency_order();
- if order.is_empty() {
- eprintln_cargo_style!("could not find workspace root or mingling crates");
- process::exit(1);
- }
-
- // 2. Locate workspace root
- let cwd = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
- let workspace_root = match find_workspace_root(&cwd) {
- Some(r) => r,
- None => {
- eprintln_cargo_style!("could not find workspace root");
- process::exit(1);
- }
- };
-
- // 3. Read current version
- let version = match read_workspace_version(&workspace_root) {
- Some(v) => v,
- None => {
- eprintln_cargo_style!("could not read workspace.package.version from Cargo.toml");
- process::exit(1);
- }
- };
-
- println_cargo_style!("Version: detected version {}", version);
-
- let toml_path = workspace_root.join("Cargo.toml");
- let backup_path = workspace_root.join("Cargo.toml.bkup");
-
- // 4. Backup Cargo.toml
- if let Err(e) = std::fs::copy(&toml_path, &backup_path) {
- eprintln_cargo_style!("failed to backup Cargo.toml: {}", e);
- process::exit(1);
- }
- println_cargo_style!("Backup: created Cargo.toml.bkup");
-
- // 5. Iterate crates in dependency order
- for crate_name in &order {
- let name = match crate_name.to_str() {
- Some(n) => n,
- None => {
- eprintln_cargo_style!("invalid crate path: {}", crate_name.display());
- abort(&toml_path);
- }
- };
-
- let crate_dir = workspace_root.join(crate_name);
-
- // 5a. Publish (capture output to check for warnings)
- println_cargo_style!("Publish: {}", name);
- let output = run_cmd_capture_with_dir("cargo publish", &crate_dir);
- let output_str = match output {
- Ok(o) => o,
- Err((code, _)) => {
- eprintln_cargo_style!("{} publish failed (exit code {})", name, code);
- abort(&toml_path);
- }
- };
-
- // Reject any warnings
- let warnings: Vec<&str> = output_str
- .lines()
- .filter(|l| l.to_lowercase().contains("warning:"))
- .collect();
- if !warnings.is_empty() {
- for w in &warnings {
- eprintln_cargo_style!("warning detected: {}", w);
- }
- eprintln_cargo_style!(
- "{} publish rejected due to {} warning(s)",
- name,
- warnings.len()
- );
- abort(&toml_path);
- }
-
- // 5b. Replace path with version in Cargo.toml
- let content = match std::fs::read_to_string(&toml_path) {
- Ok(c) => c,
- Err(e) => {
- eprintln_cargo_style!("failed to read Cargo.toml: {}", e);
- abort(&toml_path);
- }
- };
- let new_content = update_path_to_version(&content, name, &version);
- if let Err(e) = std::fs::write(&toml_path, &new_content) {
- eprintln_cargo_style!("failed to write Cargo.toml: {}", e);
- abort(&toml_path);
- }
- println_cargo_style!("Dependency: {} path -> version \"{}\"", name, version);
-
- // 5c. Verify workspace compiles
- println_cargo_style!("Check: workspace");
- if let Err(code) = run_cmd_with_dir("cargo check --workspace", &workspace_root) {
- eprintln_cargo_style!("cargo check --workspace failed (exit code {})", code);
- abort(&toml_path);
- }
-
- println_cargo_style!("Done: `{}` published", name);
- }
-
- println_cargo_style!("Done: all crates published successfully");
-}
diff --git a/.run/src/bin/clippy-fix.sh b/.run/src/bin/clippy-fix.sh
index 9771ad4..9771ad4 100644..100755
--- a/.run/src/bin/clippy-fix.sh
+++ b/.run/src/bin/clippy-fix.sh
diff --git a/.run/src/bin/deploy-api-docs.rs b/.run/src/bin/deploy-api-docs.rs
new file mode 100644
index 0000000..ea52886
--- /dev/null
+++ b/.run/src/bin/deploy-api-docs.rs
@@ -0,0 +1,132 @@
+use std::path::Path;
+
+use tools::{println_cargo_style, run_cmd};
+
+const DOCS_RS_MANIFEST: &str = "mingling/Cargo.toml";
+const OUTPUT_DIR: &str = "docs/api-docs";
+
+fn main() {
+ let repo_root = find_git_repo().expect("Failed to find git repository root");
+
+ let manifest_path = repo_root.join(DOCS_RS_MANIFEST);
+ if !manifest_path.exists() {
+ eprintln!("Error: Manifest not found at {}", manifest_path.display());
+ std::process::exit(1);
+ }
+
+ // Read and parse [package.metadata.docs.rs]
+ let manifest_content =
+ std::fs::read_to_string(&manifest_path).expect("Failed to read Cargo.toml");
+ let cargo_toml: toml::Value = manifest_content
+ .parse()
+ .expect("Failed to parse Cargo.toml");
+
+ let doc_features = cargo_toml
+ .get("package")
+ .and_then(|p| p.get("metadata"))
+ .and_then(|m| m.get("docs"))
+ .and_then(|d| d.get("rs"))
+ .and_then(|rs| rs.get("features"))
+ .and_then(|f| f.as_array())
+ .unwrap_or_else(|| {
+ eprintln!("Error: [package.metadata.docs.rs] or its 'features' key not found in mingling/Cargo.toml");
+ std::process::exit(1);
+ });
+
+ let features: Vec<&str> = doc_features.iter().filter_map(|v| v.as_str()).collect();
+
+ if features.is_empty() {
+ eprintln!("Error: No features defined in [package.metadata.docs.rs]");
+ std::process::exit(1);
+ }
+
+ let features_arg = features.join(",");
+
+ // Ensure output directory exists
+ let output_path = repo_root.join(OUTPUT_DIR);
+ 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()
+ );
+
+ println_cargo_style!("Features: {}", features_arg);
+ println_cargo_style!("Output: {}", output_path.display());
+
+ // Run cargo doc, then copy generated docs to output directory
+ println_cargo_style!("Building: docs (cargo doc --no-deps)");
+ run_cmd!(&cmd).unwrap_or_else(|code| {
+ eprintln!("Error: cargo doc failed with exit code {}", code);
+ std::process::exit(code);
+ });
+
+ // Copy generated docs from target/doc to OUTPUT_DIR (top level)
+ let doc_source = output_path.join("target").join("doc");
+ let doc_dest = &output_path;
+
+ if doc_source.exists() {
+ println_cargo_style!("Copying: docs to output directory");
+ // Remove old docs in destination (except target/)
+ if let Ok(entries) = std::fs::read_dir(doc_dest) {
+ for entry in entries.flatten() {
+ let path = entry.path();
+ if path.file_name().and_then(|n| n.to_str()) == Some("target") {
+ continue;
+ }
+ if path.is_dir() {
+ std::fs::remove_dir_all(&path).ok();
+ } else {
+ std::fs::remove_file(&path).ok();
+ }
+ }
+ }
+ copy_dir_recursively(&doc_source, doc_dest).expect("Failed to copy documentation");
+ }
+
+ // Clean up the intermediate target directory to save space
+ std::fs::remove_dir_all(output_path.join("target")).ok();
+
+ println_cargo_style!("Done: API docs deployed to {}", output_path.display());
+}
+
+fn copy_dir_recursively(src: &Path, dst: &Path) -> std::io::Result<()> {
+ if !dst.exists() {
+ std::fs::create_dir_all(dst)?;
+ }
+
+ for entry in std::fs::read_dir(src)? {
+ let entry = entry?;
+ let file_type = entry.file_type()?;
+ let src_path = entry.path();
+ let file_name = src_path.file_name().expect("Failed to get file name");
+ let dst_path = dst.join(file_name);
+
+ if file_type.is_dir() {
+ copy_dir_recursively(&src_path, &dst_path)?;
+ } else {
+ std::fs::copy(&src_path, &dst_path)?;
+ }
+ }
+
+ Ok(())
+}
+
+fn find_git_repo() -> Option<std::path::PathBuf> {
+ let mut current_dir = std::env::current_dir().ok()?;
+
+ loop {
+ let git_dir = current_dir.join(".git");
+ if git_dir.exists() && git_dir.is_dir() {
+ return Some(current_dir);
+ }
+
+ if !current_dir.pop() {
+ break;
+ }
+ }
+
+ None
+}
diff --git a/.run/src/bin/package-all.rs b/.run/src/bin/package-all.rs
new file mode 100644
index 0000000..5d7cbbb
--- /dev/null
+++ b/.run/src/bin/package-all.rs
@@ -0,0 +1,497 @@
+use std::path::{Path, PathBuf};
+
+use flate2::read::GzDecoder;
+use serde::Deserialize;
+use tar::Archive;
+use tools::{
+ dependency_order::find_workspace_root, eprintln_cargo_style, println_cargo_style,
+ run_cmd_capture_with_dir, wprintln_cargo_style,
+};
+
+/// A single member from `cargo metadata` output.
+#[derive(Deserialize, Debug)]
+struct MetadataPackage {
+ name: String,
+ version: String,
+ manifest_path: String,
+}
+
+/// The top-level metadata structure.
+#[derive(Deserialize, Debug)]
+struct Metadata {
+ #[allow(dead_code)]
+ workspace_root: String,
+ packages: Vec<MetadataPackage>,
+}
+
+fn main() {
+ // 1. Determine project root
+ let cwd = std::env::current_dir().expect("failed to get current working directory");
+ let workspace_root = find_workspace_root(&cwd).expect("not inside a Cargo workspace");
+ println_cargo_style!("Workspace: {}", workspace_root.display());
+
+ let pre_release_dir = workspace_root.join(".temp/pre-release");
+
+ // 2. Clean `.temp/pre-release/`
+ println_cargo_style!("Clean: .temp/pre-release/");
+ let _ = std::fs::remove_dir_all(&pre_release_dir);
+ std::fs::create_dir_all(&pre_release_dir).expect("failed to create .temp/pre-release/");
+
+ // 3. Run `cargo metadata` to get workspace members info
+ println_cargo_style!("Metadata: querying workspace members");
+ let metadata_json = run_cmd_capture_with_dir(
+ "cargo metadata --format-version 1 --no-deps",
+ &workspace_root,
+ )
+ .unwrap_or_else(|(code, _msg)| {
+ eprintln_cargo_style!(format!("cargo metadata failed (exit {code}):\n{{msg}}"));
+ std::process::exit(1);
+ });
+
+ let metadata: Metadata = serde_json::from_str(&metadata_json).unwrap_or_else(|e| {
+ eprintln_cargo_style!("failed to parse cargo metadata: {}", e);
+ std::process::exit(1);
+ });
+
+ // Filter workspace members: skip the root virtual manifest
+ let workspace_root_str = workspace_root.to_string_lossy().replace('\\', "/");
+ let members: Vec<&MetadataPackage> = metadata
+ .packages
+ .iter()
+ .filter(|p| {
+ let mp = p.manifest_path.replace('\\', "/");
+ mp.starts_with(&workspace_root_str)
+ && mp != format!("{}/Cargo.toml", workspace_root_str)
+ })
+ .collect();
+
+ if members.is_empty() {
+ eprintln_cargo_style!("No workspace members found!");
+ std::process::exit(1);
+ }
+
+ // Print member info
+ for m in &members {
+ println_cargo_style!("Member: {}@{}", m.name, m.version);
+ }
+
+ // Build version map: crate_name -> version
+ let mut version_map: std::collections::HashMap<String, String> =
+ std::collections::HashMap::new();
+ for m in &members {
+ version_map.insert(m.name.clone(), m.version.clone());
+ }
+
+ // Collect unique member directories that need to be copied
+ // Compute relative path by stripping workspace_root prefix
+ let mut member_dirs: Vec<PathBuf> = Vec::new();
+ for m in &members {
+ let dir = Path::new(&m.manifest_path)
+ .parent()
+ .expect("manifest_path has no parent");
+ let canonical_dir = std::fs::canonicalize(dir).unwrap_or_else(|_| dir.to_path_buf());
+ let canonical_root =
+ std::fs::canonicalize(&workspace_root).unwrap_or_else(|_| workspace_root.to_path_buf());
+ let relative = canonical_dir
+ .strip_prefix(&canonical_root)
+ .map(|p| p.to_path_buf())
+ .unwrap_or_else(|_| PathBuf::from(dir.file_name().unwrap_or_default()));
+ if !member_dirs.contains(&relative) {
+ member_dirs.push(relative);
+ }
+ }
+
+ // 4. Copy files to the temp directory, preserving the workspace directory structure
+ println_cargo_style!("Copy: project structure to .temp/pre-release/");
+
+ // Copy .cargo directory
+ copy_dir(
+ &workspace_root.join(".cargo"),
+ &pre_release_dir.join(".cargo"),
+ );
+
+ // Copy each member directory
+ for dir in &member_dirs {
+ let src = workspace_root.join(dir);
+ let dst = pre_release_dir.join(dir);
+ copy_dir(&src, &dst);
+ }
+
+ // Copy root Cargo.toml and Cargo.lock
+ copy_file(
+ &workspace_root.join("Cargo.toml"),
+ &pre_release_dir.join("Cargo.toml"),
+ );
+ copy_file(
+ &workspace_root.join("Cargo.lock"),
+ &pre_release_dir.join("Cargo.lock"),
+ );
+
+ // 5. Replace workspace dependency paths with version numbers in the root Cargo.toml
+ // For workspace-member crates, keep path so cargo can resolve locally during
+ // `cargo package --workspace`. `cargo package` automatically converts path deps
+ // to version deps in the final .crate manifest.
+ println_cargo_style!("Patch: resolve workspace dependency versions");
+
+ let pre_release_cargo = pre_release_dir.join("Cargo.toml");
+ let content = std::fs::read_to_string(&pre_release_cargo)
+ .unwrap_or_else(|e| panic!("failed to read {}: {e}", pre_release_cargo.display()));
+
+ let patched = patch_workspace_deps(&content, &version_map);
+
+ std::fs::write(&pre_release_cargo, &patched)
+ .unwrap_or_else(|e| panic!("failed to write {}: {e}", pre_release_cargo.display()));
+
+ // Member cargo.toml files: replace direct `path = "..."` deps (pointing to other
+ // workspace members) with version qualification, so `cargo package` can produce
+ // a valid .crate without path dependencies.
+ for dir in &member_dirs {
+ let member_cargo = pre_release_dir.join(dir).join("Cargo.toml");
+ if !member_cargo.exists() {
+ continue;
+ }
+ let member_content = std::fs::read_to_string(&member_cargo)
+ .unwrap_or_else(|e| panic!("failed to read {}: {e}", member_cargo.display()));
+ let member_patched = patch_member_path_deps(&member_content, &version_map);
+ if member_patched != member_content {
+ std::fs::write(&member_cargo, &member_patched)
+ .unwrap_or_else(|e| panic!("failed to write {}: {e}", member_cargo.display()));
+ }
+ }
+
+ println_cargo_style!("Package: running cargo package --workspace --no-verify");
+
+ // 6. Run cargo package in the temp directory
+ let package_ok = run_cmd_capture_with_dir(
+ "cargo package --workspace --no-verify --color always",
+ &pre_release_dir,
+ );
+
+ match &package_ok {
+ Ok(out) => {
+ println!("{out}");
+ }
+ Err((code, msg)) => {
+ // Print output but don't fail yet
+ eprintln_cargo_style!(format!("cargo package exited with code {code}:"));
+ println!("{msg}");
+ }
+ }
+
+ // 7. Copy built packages back to .temp/target/package
+ let temp_package_dir = workspace_root.join(".temp/target/package");
+ std::fs::create_dir_all(&temp_package_dir)
+ .unwrap_or_else(|e| panic!("failed to create {}: {e}", temp_package_dir.display()));
+
+ // cargo package puts .crate files in target/package
+ let pre_release_target_package = pre_release_dir.join(".temp/target/package");
+ if pre_release_target_package.exists() {
+ println_cargo_style!("Copy: packages to .temp/target/package");
+ copy_dir_contents(&pre_release_target_package, &temp_package_dir);
+ } else {
+ wprintln_cargo_style!("No packages found in .temp/pre-release/.temp/target/package");
+ }
+
+ // 8. Export each crate as a standalone project from the .crate packages.
+ // The .crate files contain the final publish-ready Cargo.toml with all
+ // workspace/path deps already resolved by `cargo package`.
+ let release_dir = workspace_root.join(".temp/release");
+ println_cargo_style!("Export: standalone crates to .temp/release/");
+ let _ = std::fs::remove_dir_all(&release_dir);
+ std::fs::create_dir_all(&release_dir)
+ .unwrap_or_else(|e| panic!("failed to create {}: {e}", release_dir.display()));
+
+ for entry in std::fs::read_dir(&temp_package_dir).expect("failed to read target/package") {
+ let entry = entry.expect("failed to read entry");
+ let path = entry.path();
+ if path.extension().is_none_or(|e| e != "crate") {
+ continue;
+ }
+
+ // .crate files are gzipped tarballs. Extract to .temp/release/<name>/
+ // fname is like "mingling-0.3.0"
+ let fname = path.file_stem().unwrap().to_string_lossy().to_string();
+
+ // Derive crate directory name by stripping the version suffix
+ // mingling-0.3.0 -> mingling, arg-picker-0.1.0 -> arg-picker
+ let crate_dir_name = fname
+ .rfind('-')
+ .and_then(|dash| {
+ // Check if what follows looks like a semver
+ let ver_part = &fname[dash + 1..];
+ if ver_part.chars().next().is_some_and(|c| c.is_ascii_digit()) {
+ Some(&fname[..dash])
+ } else {
+ None
+ }
+ })
+ .unwrap_or(&fname)
+ .to_string();
+
+ let target_dir = release_dir.join(&crate_dir_name);
+ std::fs::create_dir_all(&target_dir)
+ .unwrap_or_else(|e| panic!("failed to create {}: {e}", target_dir.display()));
+
+ // Extract using flate2 + tar (cross-platform)
+ let file = match std::fs::File::open(&path) {
+ Ok(f) => f,
+ Err(e) => {
+ eprintln_cargo_style!("Failed to open {}: {e}", path.display());
+ continue;
+ }
+ };
+ let decoder = GzDecoder::new(file);
+ let mut archive = Archive::new(decoder);
+ if let Err(e) = archive.unpack(&target_dir) {
+ eprintln_cargo_style!("Failed to extract {}: {e}", fname);
+ continue;
+ }
+
+ // Move the contents from the inner dir up one level
+ // .crate contains a single top-level dir named after the package
+ let inner = target_dir.join(&fname);
+ if inner.exists() {
+ for inner_entry in std::fs::read_dir(&inner).expect("failed to read inner dir") {
+ let inner_entry = inner_entry.expect("failed to read entry");
+ let inner_path = inner_entry.path();
+ let dest = target_dir.join(inner_path.file_name().unwrap());
+ if dest.exists() {
+ let _ = std::fs::remove_dir_all(&dest);
+ }
+ std::fs::rename(&inner_path, &dest).unwrap_or_else(|e| {
+ panic!(
+ "failed to rename {} -> {}: {e}",
+ inner_path.display(),
+ dest.display()
+ )
+ });
+ }
+ let _ = std::fs::remove_dir_all(&inner);
+ }
+
+ // Clean up: remove .orig file (only the normalized Cargo.toml is needed)
+ let _ = std::fs::remove_file(target_dir.join("Cargo.toml.orig"));
+ // Also remove Cargo.lock — standalone crate doesn't need it for publish
+ let _ = std::fs::remove_file(target_dir.join("Cargo.lock"));
+
+ println_cargo_style!("Export: {}", crate_dir_name);
+ }
+
+ println_cargo_style!("Done: .temp/release/ is ready");
+
+ // If package failed, report it
+ if package_ok.is_err() {
+ eprintln_cargo_style!("cargo package reported errors above");
+ std::process::exit(1);
+ }
+}
+
+/// Replace path-based workspace dependencies with version strings.
+///
+/// Keeps the path form so that `cargo package --workspace` resolves to local workspace
+/// members, but also adds `version = "..."` so the generated .crate has the correct
+/// version dependency.
+fn patch_workspace_deps(
+ content: &str,
+ version_map: &std::collections::HashMap<String, String>,
+) -> String {
+ let mut result = String::new();
+ let mut in_workspace_deps = false;
+
+ for line in content.lines() {
+ let trimmed = line.trim();
+
+ if trimmed == "[workspace.dependencies]" {
+ in_workspace_deps = true;
+ result.push_str(line);
+ result.push('\n');
+ continue;
+ }
+
+ // Detect end of workspace.dependencies section
+ if in_workspace_deps && trimmed.starts_with('[') {
+ in_workspace_deps = false;
+ }
+
+ if in_workspace_deps
+ && let Some(dep_name) = trimmed.split('=').next().map(|s| s.trim())
+ && let Some(version) = version_map.get(dep_name)
+ {
+ let indent = line
+ .chars()
+ .take_while(|c| c.is_whitespace())
+ .collect::<String>();
+
+ if trimmed.contains("path =") {
+ let path_value = extract_path_value(trimmed);
+ let patched_line: String = if let Some(pv) = path_value {
+ trimmed.replace(
+ &format!("path = \"{}\"", pv),
+ &format!("path = \"{}\", version = \"{}\"", pv, version),
+ )
+ } else {
+ trimmed.to_string()
+ };
+ result.push_str(&format!("{indent}{patched_line}\n"));
+ } else {
+ result.push_str(&format!("{indent}{dep_name} = \"{version}\"\n"));
+ }
+ continue;
+ }
+
+ result.push_str(line);
+ result.push('\n');
+ }
+
+ result
+}
+
+/// Extract the path value from a dependency line like:
+/// `mingling_core = { path = "mingling_core", default-features = false }`
+fn extract_path_value(line: &str) -> Option<String> {
+ let line = line.trim();
+ if let Some(start) = line.find("path = \"") {
+ let after_path = &line[start + 8..];
+ if let Some(end) = after_path.find('"') {
+ return Some(after_path[..end].to_string());
+ }
+ }
+ None
+}
+
+/// Patch a member crate's Cargo.toml: add `version = "..."` to direct `path = "..."`
+/// dependencies that point to other workspace members.
+fn patch_member_path_deps(
+ content: &str,
+ version_map: &std::collections::HashMap<String, String>,
+) -> String {
+ let mut result = String::new();
+ let mut in_deps = false;
+ let mut in_build_deps = false;
+
+ for line in content.lines() {
+ let trimmed = line.trim();
+
+ if trimmed == "[dependencies]" || trimmed.starts_with("[dependencies.") {
+ in_deps = true;
+ in_build_deps = false;
+ } else if trimmed == "[build-dependencies]" || trimmed.starts_with("[build-dependencies.") {
+ in_build_deps = true;
+ in_deps = false;
+ } else if trimmed.starts_with('[') {
+ in_deps = false;
+ in_build_deps = false;
+ }
+
+ if (in_deps || in_build_deps)
+ && trimmed.contains("path = \"")
+ && !trimmed.contains("workspace = true")
+ && let Some(dep_name) = trimmed.split('=').next().map(|s| s.trim())
+ && let Some(version) = version_map.get(dep_name.trim_end_matches(".workspace"))
+ && !trimmed.contains("version = \"")
+ {
+ let indent = line
+ .chars()
+ .take_while(|c| c.is_whitespace())
+ .collect::<String>();
+ let path_val = extract_path_value(trimmed).unwrap_or_default();
+ let patched = trimmed.replace(
+ &format!("path = \"{path_val}\""),
+ &format!("path = \"{path_val}\", version = \"{version}\""),
+ );
+ result.push_str(&format!("{indent}{patched}\n"));
+ continue;
+ }
+
+ result.push_str(line);
+ result.push('\n');
+ }
+
+ result
+}
+
+/// Recursively copy a directory.
+fn copy_dir(src: &Path, dst: &Path) {
+ copy_dir_filtered(src, dst, &|_: &Path| true)
+}
+
+/// Recursively copy a directory with a filter function.
+/// The filter receives the source path and returns `true` if the entry should be copied.
+fn copy_dir_filtered(src: &Path, dst: &Path, filter: &dyn Fn(&Path) -> bool) {
+ if !src.exists() {
+ return;
+ }
+ if !filter(src) {
+ return;
+ }
+ std::fs::create_dir_all(dst)
+ .unwrap_or_else(|e| panic!("failed to create {}: {e}", dst.display()));
+
+ for entry in std::fs::read_dir(src).expect("failed to read directory") {
+ let entry = entry.expect("failed to read entry");
+ let entry_type = entry.file_type().expect("failed to get file type");
+ let src_path = entry.path();
+ let dst_path = dst.join(entry.file_name());
+
+ if !filter(&src_path) {
+ continue;
+ }
+
+ if entry_type.is_dir() {
+ copy_dir_filtered(&src_path, &dst_path, filter);
+ } else if entry_type.is_file() || entry_type.is_symlink() {
+ copy_file(&src_path, &dst_path);
+ }
+ }
+}
+
+/// Copy a file, creating parent directories as needed.
+/// If src is a symlink, copies the target content (follow symlinks).
+fn copy_file(src: &Path, dst: &Path) {
+ if let Some(parent) = dst.parent() {
+ std::fs::create_dir_all(parent)
+ .unwrap_or_else(|e| panic!("failed to create {}: {e}", parent.display()));
+ }
+
+ let resolved = if src.is_symlink() {
+ let target = std::fs::read_link(src)
+ .unwrap_or_else(|e| panic!("failed to read symlink {}: {e}", src.display()));
+ if target.is_relative() {
+ src.parent().unwrap().join(target)
+ } else {
+ target
+ }
+ } else {
+ src.to_path_buf()
+ };
+
+ std::fs::copy(&resolved, dst).unwrap_or_else(|e| {
+ panic!(
+ "failed to copy {} -> {}: {e}",
+ resolved.display(),
+ dst.display()
+ )
+ });
+}
+
+/// Copy all files/directories from one directory into another.
+fn copy_dir_contents(src: &Path, dst: &Path) {
+ if !src.exists() {
+ return;
+ }
+ std::fs::create_dir_all(dst)
+ .unwrap_or_else(|e| panic!("failed to create {}: {e}", dst.display()));
+
+ for entry in std::fs::read_dir(src).expect("failed to read directory") {
+ let entry = entry.expect("failed to read entry");
+ let entry_type = entry.file_type().expect("failed to get file type");
+ let src_path = entry.path();
+ let dst_path = dst.join(entry.file_name());
+
+ if entry_type.is_dir() {
+ copy_dir(&src_path, &dst_path);
+ } else if entry_type.is_file() || entry_type.is_symlink() {
+ copy_file(&src_path, &dst_path);
+ }
+ }
+}
diff --git a/.run/src/dependency_order.rs b/.run/src/dependency_order.rs
index 7235f12..145bdbd 100644
--- a/.run/src/dependency_order.rs
+++ b/.run/src/dependency_order.rs
@@ -1,5 +1,5 @@
use std::collections::{HashMap, HashSet};
-use std::path::PathBuf;
+use std::path::{Path, PathBuf};
/// Parse Cargo.toml content and return dependency names that start with `prefix`.
fn parse_mingling_deps(content: &str, prefix: &str) -> Vec<String> {
@@ -119,10 +119,23 @@ fn topological_sort(
result
}
+/// Strip the `\\?\` prefix that `std::fs::canonicalize` may add on Windows.
+fn strip_verbatim_prefix(p: &Path) -> PathBuf {
+ let s = p.to_string_lossy();
+ let s_ref: &str = &s;
+ if let Some(rest) = s_ref.strip_prefix("\\\\?\\") {
+ PathBuf::from(rest)
+ } else {
+ p.to_path_buf()
+ }
+}
+
/// Find the workspace root by looking for a Cargo.toml with `[workspace]` members.
/// Starts from `start` and walks up the directory tree.
pub fn find_workspace_root(start: &std::path::Path) -> Option<PathBuf> {
- let mut current = Some(std::fs::canonicalize(start).unwrap_or_else(|_| start.to_path_buf()));
+ let mut current = Some(strip_verbatim_prefix(
+ &std::fs::canonicalize(start).unwrap_or_else(|_| start.to_path_buf()),
+ ));
while let Some(dir) = current {
let members = get_workspace_members(&dir);
if !members.is_empty() {
@@ -146,10 +159,10 @@ pub fn display_dependency_order() -> Vec<PathBuf> {
// Read workspace members from root Cargo.toml
let members = get_workspace_members(&workspace_root);
- // Filter to crates starting with "mingling"
+ // Filter to crates starting with "mingling" or "arg"
let mingling_crates: HashSet<String> = members
.into_iter()
- .filter(|m| m.starts_with("mingling"))
+ .filter(|m| m.starts_with("mingling") || m.starts_with("arg"))
.collect();
if mingling_crates.is_empty() {
diff --git a/.run/version-files.toml b/.run/version-files.toml
index 42d9b4d..ca104d2 100644
--- a/.run/version-files.toml
+++ b/.run/version-files.toml
@@ -17,15 +17,3 @@ pattern = "version = \"{VER}\""
[[file]]
file = "./docs/res/guide.txt"
pattern = "mingling = \"{VER}\""
-
-[[file]]
-file = "./mingling_picker/README.md"
-pattern = "version = \"{VER}\""
-
-[[file]]
-file = "./mingling_picker/README.md"
-pattern = "mingling_picker = \"{VER}\""
-
-[[file]]
-file = "./mingling_picker_macros/README.md"
-pattern = "version = \"{VER}\""
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 7472e1c..1b585d1 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -3,9 +3,16 @@
"rust-analyzer.checkOnSave": true,
"rust-analyzer.linkedProjects": [
".run/Cargo.toml",
- "mingling_picker/Cargo.toml",
"mingling_pathf/test/Cargo.toml",
- "mingling_picker/test/Cargo.toml",
+ "arg_picker/Cargo.toml",
+ "arg_picker/test/Cargo.toml",
+ ],
+ "rust-analyzer.cargo.features": [
+ "structural_renderer",
+ "mingling_support",
+ "all_serde_fmt",
+ "picker",
+ "parser",
+ "comp",
],
- "rust-analyzer.cargo.features": ["mingling_support"],
}
diff --git a/.zed/settings.json b/.zed/settings.json
index 12c901e..727f955 100644
--- a/.zed/settings.json
+++ b/.zed/settings.json
@@ -6,12 +6,19 @@
"check": { "command": "clippy" },
"linkedProjects": [
".run/Cargo.toml",
- "mingling_picker/Cargo.toml",
"mingling_pathf/test/Cargo.toml",
- "mingling_picker/test/Cargo.toml",
+ "arg_picker/Cargo.toml",
+ "arg_picker/test/Cargo.toml",
],
"cargo": {
- "features": ["mingling_support"],
+ "features": [
+ "structural_renderer",
+ "mingling_support",
+ "all_serde_fmt",
+ "picker",
+ "parser",
+ "comp",
+ ],
},
},
},
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 361338f..997eac0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -58,38 +58,227 @@ None
#### Optimizations:
-None
+1. **[`macros`]** Updated `route!` macro to use `Routable` trait instead of `Grouped` trait for error conversion, making the semantics clearer. The `route!` macro now calls `::mingling::Routable::to_chain(e)` on the error branch instead of `::mingling::Grouped::to_chain(e)`.
+
+ Additionally, `ChainProcess<ThisProgram>` now implements `Routable<ThisProgram>`, allowing `route!` to work with `Result<Ok, ChainProcess<ThisProgram>>` patterns — where the error side is already a `ChainProcess` value that should be routed directly:
+
+ When `ChainProcess` implements `Routable`, `to_chain()` re-routes the inner `AnyOutput` to the chain pipeline (preserving the existing `NextProcess::Chain`/`Renderer` flag), while `to_render()` re-routes it to the render pipeline. This enables seamless propagation of already-routed chain process values through the `route!` macro without double-wrapping.
+
+ The `Routable` trait is defined in `mingling_core::asset::routable` and provides unified routing capabilities (`to_chain` / `to_render`) for any type that can be dispatched into the program's pipeline. A blanket implementation is provided for all `T: Grouped<C> + Send`, ensuring backward compatibility — existing types that implement `Grouped` automatically implement `Routable`.
#### 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!`.
- ```rust
- let mut result = RenderResult::new();
- writeln!(result, "Hello!").ok();
- result
- ```
+ ```rust
+ let mut result = RenderResult::new();
+ writeln!(result, "Hello!").ok();
+ result
+ ```
+
+ The method is equivalent to `RenderResult::default()` but serves as a more idiomatic entry point for renderer functions.
+
+2. **[`core`]** **[`macros`]** Added `core` and `macros` features to the `mingling` crate, both enabled by default. These features allow selective exclusion of `mingling_core` and `mingling_macros` dependencies respectively.
+
+ - When `core` is **disabled** (`default-features = false, features = ["macros"]`), the `mingling` crate only re-exports proc-macros and macro-related items, without linking `mingling_core`.
+ - When `macros` is **disabled** (`default-features = false, features = ["core"]`), the `mingling` crate only provides runtime types and traits without any proc-macro re-exports.
+ - When **both are disabled** (`default-features = false`), the `mingling` crate provides a minimal re-export surface.
+
+ This enables downstream projects to fine-tune dependency weight — e.g., a library crate that only uses `mingling_macros` can disable `core` to avoid pulling in unused runtime types.
+
+3. **[`picker`]** Added the new `picker` system — a **non‑breaking companion** to the existing `parser` feature, not a replacement. The `picker` feature provides a chained argument parsing API and a parsing function library (`parselib`) for analyzing command-line argument structure. Key components:
+
+ - **Chained Argument Parser with `.pick()`** — A chained-call API for declaring and extracting arguments:
+
+ ```rust
+ use arg_picker::prelude::*;
+
+ let args: Vec<&str> = vec!["--name", "Bob", "--age", "24"];
+
+ let (name, age) = args
+ .pick(&arg![name: String])
+ .or(|| "Alice".to_string())
+ .pick(&arg![age: i32])
+ .or(|| 24)
+ .post(|num| num.clamp(0, 120))
+ .unwrap();
+ ```
+
+ - **Pure function library `parselib`** — Provides utility functions for analyzing command-line argument structure:
+
+ ```rust
+ use arg_picker::parselib::*;
+ ```
+
+ The `picker` system is **additive** — the old `parser` feature remains fully functional and is **not deprecated**. Users may choose either or both systems in the same project. The `picker` feature is available both as the `mingling/picker` feature (enabled by default) and as a standalone `arg_picker` crate.
+
+ _No migration is required for existing `parser` users — the old API continues to work unchanged._
+
+4. **[`core`]** Added multiple `From` implementations for `RenderResult`:
+
+ - **From `()`** — Allows constructing an empty `RenderResult` from a unit value, enabling ergonomic returns like `fn my_renderer() -> RenderResult { }` (via `}` → `}` with implicit `()` return).
+ - **From integer types** (`i32`, `i16`, `i8`, `u32`, `u16`, `u8`, `usize`) — Allows constructing a `RenderResult` with a specific exit code and empty text, enabling `fn my_renderer() -> RenderResult { 0 }` or `42.into()`.
+ - **From `String`**, **`&String`**, and **`&str`** — Allows constructing a `RenderResult` with the given text and exit code `0`, enabling `fn my_renderer() -> RenderResult { "Hello".into() }` or passing a `String` directly.
+
+ These implementations make `RenderResult` more flexible as a return type, allowing renderer functions to return simple values without manually constructing a `RenderResult` via `new()` and `write!`/`writeln!`.
+
+5. **[`macros:renderer`]** Removed the restriction that `#[renderer]` functions must return `RenderResult`. The `#[renderer]` macro now accepts any return type (including no return type), and automatically converts the return value to `RenderResult` via `Into::into`.
+
+ - Functions returning `RenderResult` work as before.
+ - Functions returning other types (e.g., `String`, `i32`, `()`) are converted via the `Into<RenderResult>` trait.
+ - Functions with no return type (`-> ()` or omitted) return `()` which is converted to an empty `RenderResult` via `From<()>`.
+
+ This makes `#[renderer]` more flexible and consistent with the ergonomic `From` implementations added in item 4 above.
+
+ ```rust
+ #[renderer]
+ fn render_greeting(prev: ResultGreeting) -> String {
+ format!("Hello, {}!", *prev)
+ }
+
+ #[renderer]
+ fn render_exit_code(prev: ResultExit) -> i32 {
+ 42
+ }
+
+ #[renderer]
+ fn render_void(prev: ResultVoid) {
+ // side effects only, returns empty RenderResult
+ }
+ ```
+
+6. **[`macros:help`]** Removed the restriction that `#[help]` functions must return `::mingling::RenderResult`. The `#[help]` macro now accepts any return type (including no return type), and automatically converts the return value to `RenderResult` via `Into::into`.
+
+ - Functions returning `RenderResult` work as before.
+ - Functions returning other types (e.g., `String`, `i32`, `()`) are converted via `Into<RenderResult>`.
+ - Functions with no return type (`-> ()` or omitted) return `()` which is converted to an empty `RenderResult` via `From<()>`.
+
+ This makes `#[help]` consistent with the `#[renderer]` macro's ergonomic return type handling introduced in item 5 above.
+
+ ```rust
+ #[help]
+ fn help_greeting(prev: EntryGreeting) -> String {
+ format!("Displaying help for greeting: {}", *prev)
+ }
+
+ #[help]
+ fn help_void(prev: EntryVoid) {
+ // side effects only, returns empty RenderResult
+ }
+ ```
+
+7. **[`setups`]** Refactored `BasicProgramSetup`, `HelpFlagSetup`, `QuietFlagSetup`, `ConfirmFlagSetup`, `StructuralRendererSetup`, and `StructuralRendererSimpleSetup` into the `picker` subsystem under `mingling::setups::picker`. These setups now use the `arg_picker` (`picker`) chained argument parsing API internally instead of directly manipulating global arguments.
+
+ - The `BasicProgramSetup`, `HelpFlagSetup`, `QuietFlagSetup`, and `ConfirmFlagSetup` structs now use `PickerArg<Flag>` and chained `.pick()` calls to detect flags from the argument list, replacing the previous `global_argument`-based approach.
+ - The `StructuralRendererSetup` struct now uses `PickerArg<Flag>` constants (e.g., `JSON_FLAG`, `YAML_FLAG`) and chained `.pick()` calls to detect format-specifying flags, replacing the previous `global_argument` approach.
+ - The `StructuralRendererSimpleSetup` struct still uses the legacy `global_argument("--renderer", ...)` approach, preserving backward compatibility with the `--renderer <FORMAT>` syntax.
+ - New `PickerArg<Flag>` constants have been added in `mingling::setups::picker::consts`: `HELP_FLAG`, `QUIET_FLAG`, `CONFIRM_FLAG`, `JSON_FLAG`, `JSON_PRETTY_FLAG`, `YAML_FLAG`, `TOML_FLAG`, `RON_FLAG`, and `RON_PRETTY_FLAG`. The format-specific flags are feature-gated behind their respective `json_serde_fmt`, `yaml_serde_fmt`, `toml_serde_fmt`, and `ron_serde_fmt` features.
+ - The module structure is:
+ - `mingling::setups::picker` — re-exports all picker-based setup types
+ - `mingling::setups::picker::basic` — `BasicProgramSetup`, `HelpFlagSetup`, `QuietFlagSetup`, `ConfirmFlagSetup`
+ - `mingling::setups::picker::consts` — reusable `PickerArg<Flag>` constants
+ - `mingling::setups::picker::structural_renderer` — `StructuralRendererSetup`, `StructuralRendererSimpleSetup`
+ - All setup types remain available from `mingling::setups::*` as before — this is purely an internal refactoring; no public API surface changes.
+
+ The `picker` feature must be enabled for these refactored setups to be available. When the feature is disabled, the original implementations (using `global_argument`) remain in effect.
+
+8. **[`core`]** Added `get_args_mut()`, `take_args()`, and `replace_args()` methods to `Program` for more flexible argument manipulation:
+
+ - **`get_args_mut(&mut self) -> &mut [String]`** — Returns a mutable reference to the program's command-line arguments, allowing in-place modification of individual arguments.
+ - **`take_args(&mut self) -> Vec<String>`** — Takes ownership of the program's command-line arguments, replacing them with an empty `Vec`. Useful for transferring arguments to another context or processing them with ownership.
+ - **`replace_args(&mut self, args: Vec<String>) -> Vec<String>`** — Replaces the program's command-line arguments with a new set and returns the old ones. Enables swapping argument sets during program execution.
+
+ These methods complement the existing read-only `get_args(&self)` method, providing full control over argument mutation and ownership.
- The method is equivalent to `RenderResult::default()` but serves as a more idiomatic entry point for renderer functions.
+9. **[`macros:chain`]** Relaxed the `#[chain]` return type validation. Previously, `#[chain]` functions were restricted to returning `Next`, `ChainProcess<ThisProgram>`, `()`, or omitting the return type. Now, any return type is accepted, and the generated `proc` function performs an explicit `Into<ChainProcess<ThisProgram>>` conversion using a fully-qualified turbofish based on the user-declared return type.
+
+ This means `#[chain]` functions can now return any pack type directly, without needing an explicit `.into()` call in the function body:
+
+ ```rust
+ // Before — required explicit .into()
+ #[chain]
+ fn handle_greet(args: EntryGreet) -> Next {
+ let name = /* ... */;
+ ResultGreeting::new(name).into()
+ }
+
+ // After — return any pack type directly
+ #[chain]
+ fn handle_greet(args: EntryGreet) -> ResultGreeting {
+ let name = /* ... */;
+ ResultGreeting::new(name)
+ }
+ ```
+
+ The generated `proc` function now wraps the body result in `<UserReturnType as Into<ChainProcess<ThisProgram>>>::into(...)`, which:
+ - Works for `Next` / `ChainProcess` via the identity `From<T> for T` implementation.
+ - Works for any pack type (`ResultGreeting`, etc.) via the `.into()` conversion generated by `pack!` / `#[derive(Grouped)]`.
+ - Works for `()` via the `From<()>` implementation on `ChainProcess`.
+
+ The return type validation has been removed entirely — any valid Rust return type is accepted. If the type does not implement `Into<ChainProcess<ThisProgram>>`, a standard Rust compilation error will be produced at the call site.
#### **BREAKING CHANGES** (API CHANGES):
1. **[`macros:renderer`]** **[`macros:help`]** Removed `r_println!` and `r_print!` macros. The `#[renderer]` and `#[help]` macros no longer implicitly inject an internal `RenderResult` variable or provide `r_println!` / `r_print!` macros.
- Renderers and help functions must now explicitly create and return a `RenderResult`:
+ Renderers and help functions must now explicitly create and return a `RenderResult`:
- ```rust
- use mingling::prelude::*;
+ ```rust
+ use mingling::prelude::*;
- #[renderer]
- fn render_greeting(greeting: ResultGreeting) -> RenderResult {
- let mut result = RenderResult::new();
- writeln!(result, "Hello, {}!", *greeting).ok();
- result
- }
- ```
+ #[renderer]
+ fn render_greeting(greeting: ResultGreeting) -> RenderResult {
+ let mut result = RenderResult::new();
+ writeln!(result, "Hello, {}!", *greeting).ok();
+ result
+ }
+ ```
- All examples, docs, and test cases across the repository have been updated to use the new pattern: creating a `RenderResult` with `RenderResult::new()` or `RenderResult::default()`, writing with `write!`/`writeln!` from `std::io::Write`, and returning the result.
+ All examples, docs, and test cases across the repository have been updated to use the new pattern: creating a `RenderResult` with `RenderResult::new()` or `RenderResult::default()`, writing with `write!`/`writeln!` from `std::io::Write`, and returning the result.
+
+2. **[`macros:chain`]** The `#[chain]` macro's return type requirement has been relaxed. Previously, chain functions were required to return `Next` or `()` (with `()` auto-converting to `ResultEmpty`). Now, chain functions can also return `ChainProcess<ThisProgram>` directly, or omit the return type entirely (which defaults to `()` → `ResultEmpty`).
+
+ The return value of chain functions is now wrapped in an explicit `.into()` call inside the generated `proc` function, ensuring consistent conversion to `ChainProcess<ProgramType>`. As a result, **all downstream code that previously relied on implicit conversion from packed types to `Next`/`ChainProcess` must now call `.into()` explicitly**.
+
+ ```rust
+ // Before — implicit conversion worked because the generated proc
+ // function was `fn proc(...) -> impl Into<ChainProcess<...>>`
+ #[chain]
+ fn handle_greet(args: EntryGreet) -> Next {
+ let name = /* ... */;
+ ResultGreeting::new(name) // implicitly converted
+ }
+
+ // After — the generated proc function is `fn proc(...) -> ChainProcess<...>`,
+ // so the body must produce ChainProcess explicitly
+ #[chain]
+ fn handle_greet(args: EntryGreet) -> Next {
+ let name = /* ... */;
+ ResultGreeting::new(name).into() // explicit conversion required
+ }
+ ```
+
+ The key advantage of this design is that **the original function body and the expanded `proc` function body are now identical** — the macro only adjusts the function signature and inserts an outermost `.into()` wrapper, without rewriting the internal return expressions. This means the semantics of the original code are perfectly preserved: there is no invisible type coercion happening mid-body, and the behavior you write in the source is exactly what executes at runtime. If a bug arises, the expanded code mirrors the source almost one-to-one, making debugging straightforward.
+
+ This change also applies to:
+ - Chain functions returning `()` (unit), where the body's final expression with `.into()` is replaced by an explicit `ResultEmpty::to_chain()` call.
+ - Chain functions using `&mut` resource injection with non-unit returns: the inner closure now calls `__modify_res_and_return_route` (which returns `ChainProcess<C>` directly) instead of relying on `.into()` conversion.
+ - The `__modify_res_and_return_route` method signature changed from accepting `impl Into<ChainProcess<C>>` to returning `ChainProcess<C>` directly.
+
+ All examples, docs, and test cases across the repository have been updated to use `.into()` where packed types are returned from chain functions.
+
+3. **[`core`]** **[`ExitCodeSetup`]** Updated `ExitCodeSetup` to only override the exit code when `ResExitCode` has been modified (i.e., `exit_code != 0`). Previously, it unconditionally overrode the exit code, which could interfere with exit codes set by other hooks or the program's default exit flow. The `on_finish` hook now returns `ProgramControlUnit::OverrideExitCode(...)` only when the exit code is non-zero, and `ProgramControls::Empty` otherwise. The import of `ProgramControls` has been added accordingly.
+
+4. **[`core`]** **[`macros`]** Renamed `Groupped` (typo) to `Grouped`. All references to the trait, derive macro, module files, and related types have been corrected throughout the codebase:
+
+ - Trait: `Groupped<Group>` → `Grouped<Group>`
+ - Derive macro: `#[derive(Groupped)]` → `#[derive(Grouped)]`
+ - Serialize variant: `GrouppedSerialize` → `GroupedSerialize`
+ - Source files: `groupped.rs` → `grouped.rs`
+ - Pattern matcher: `GrouppedDerivePattern` → `GroupedDerivePattern`
+ - All `use` imports, type annotations, and trait bound references updated accordingly.
+
+ This is a pure rename — no behavioral changes. All functionality remains identical. Downstream code using the old `Groupped` name must migrate to `Grouped`.
---
@@ -124,7 +313,7 @@ None
`"=> EntryList"` would incorrectly match as a substring of `"=> EntryListAlias,"`, causing a false duplicate registration detection.
Now changed to use `find` + trailing character boundary validation, ensuring the character immediately after the match is not an identifier character (letter/digit/underscore).
- Affected scope: Deduplication logic for `#[chain]`, `#[renderer]`, `#[help]`, and `#[completion]` registration.
+ Affected scope: Deduplication logic for `#[chain]`, `#[renderer]`, `#[help]`, and `#[completion]` registration.
#### Optimizations:
@@ -153,36 +342,36 @@ None
1. **[`core`]** - **Added complete unit test coverage**, adding `#[cfg(test)]` test modules for 23 modules in `mingling_core` that previously lacked tests, covering:
- - **Core types** (`any.rs`): `AnyOutput` creation, downcast, type judgment, route routing, restore deserialization; `ChainProcess` type conversion; `NextProcess` formatting
- - **Dispatcher** (`dispatcher.rs`): Conversion of `Dispatchers` from 1~7 tuples, Vec, Box; Deref dereferencing; clone behavior
- - **Node** (`node.rs`): Construction, join, kebab-case conversion, equality comparison, sorting
- - **Global resource** (`global_resource.rs`): `GlobalResource` new, Deref, AsRef; three default implementations of `ResourceMarker`
- - **Lazy resource** (`lazy_resource.rs`): Coverage of all 18+ methods of `LazyRes`, including initialization triggering, get_ref/get_mut/get_clone, into_inner/unwrap, Drop callback, `ResourceMarker` integration
- - **Error types** (`chain/error.rs`, `program/error.rs`): All Display, Error source, From conversions
- - **Configuration structs** (`config.rs`): Default values for `ProgramStdoutSetting`, `ProgramUserContext`; FromStr parsing and Display output of `StructuralRendererSetting` (feature-gated)
- - **Flag system** (`flag.rs`): Added 8 From conversions, Deref, AsRef for `Flag`
- - **String wrapper** (`string_vec.rs`): 6 From conversions, Deref, Into\<Vec\>
- - **Render result** (`render_result.rs`): print/println/clear/is_empty, Write trait, Display, Deref, From conversions
- - **Render error** (`structural/error.rs`): Construction, From, Deref, Into\<String\>
- - **Structural renderer** (`structural.rs`): Rendering in Disable/JSON/YAML/TOML/RON formats (feature-gated)
- - **Completion suggestions** (`suggest.rs`): All construction, access, modification, sorting, and conversion methods for `Suggest` and `SuggestItem`
- - **Shell context** (`shell_ctx.rs`): Added `filling_argument`, `filling_argument_first`, `typing_argument`, `strip_typed_argument`, `get_typed_arguments`
- - **Hook system** (`hook.rs`): `ProgramHook::empty` and all 8 builder methods
- - **Singleton management** (`single_instance.rs`): `ProgramCell` set/get_raw/take/double-set-panic
- - **Program setup** (`setup.rs`): Verification of `with_setup` invocation
- - **Completion detection** (`comp_ctx.rs`): Three scenarios for `is_completing`
- - **Build script** (`builds/comp.rs`): `get_tmpl` for four Shells and Other fallback
+ - **Core types** (`any.rs`): `AnyOutput` creation, downcast, type judgment, route routing, restore deserialization; `ChainProcess` type conversion; `NextProcess` formatting
+ - **Dispatcher** (`dispatcher.rs`): Conversion of `Dispatchers` from 1~7 tuples, Vec, Box; Deref dereferencing; clone behavior
+ - **Node** (`node.rs`): Construction, join, kebab-case conversion, equality comparison, sorting
+ - **Global resource** (`global_resource.rs`): `GlobalResource` new, Deref, AsRef; three default implementations of `ResourceMarker`
+ - **Lazy resource** (`lazy_resource.rs`): Coverage of all 18+ methods of `LazyRes`, including initialization triggering, get_ref/get_mut/get_clone, into_inner/unwrap, Drop callback, `ResourceMarker` integration
+ - **Error types** (`chain/error.rs`, `program/error.rs`): All Display, Error source, From conversions
+ - **Configuration structs** (`config.rs`): Default values for `ProgramStdoutSetting`, `ProgramUserContext`; FromStr parsing and Display output of `StructuralRendererSetting` (feature-gated)
+ - **Flag system** (`flag.rs`): Added 8 From conversions, Deref, AsRef for `Flag`
+ - **String wrapper** (`string_vec.rs`): 6 From conversions, Deref, Into\<Vec\>
+ - **Render result** (`render_result.rs`): print/println/clear/is_empty, Write trait, Display, Deref, From conversions
+ - **Render error** (`structural/error.rs`): Construction, From, Deref, Into\<String\>
+ - **Structural renderer** (`structural.rs`): Rendering in Disable/JSON/YAML/TOML/RON formats (feature-gated)
+ - **Completion suggestions** (`suggest.rs`): All construction, access, modification, sorting, and conversion methods for `Suggest` and `SuggestItem`
+ - **Shell context** (`shell_ctx.rs`): Added `filling_argument`, `filling_argument_first`, `typing_argument`, `strip_typed_argument`, `get_typed_arguments`
+ - **Hook system** (`hook.rs`): `ProgramHook::empty` and all 8 builder methods
+ - **Singleton management** (`single_instance.rs`): `ProgramCell` set/get_raw/take/double-set-panic
+ - **Program setup** (`setup.rs`): Verification of `with_setup` invocation
+ - **Completion detection** (`comp_ctx.rs`): Three scenarios for `is_completing`
+ - **Build script** (`builds/comp.rs`): `get_tmpl` for four Shells and Other fallback
2. **[`core`]** - **Added 6 integration test crates**, testing public APIs under different feature combinations:
- - `test-basic`: Basic type tests with default features (Node, Flag, RenderResult, NextProcess, StringVec)
- - `test-comp`: ShellContext, Suggest, SuggestItem, is_completing with `comp + builds` features
- - `test-structural-renderer`: StructuralRenderer output in various formats with `structural_renderer_full + parser` features
- - `test-repl`: ResREPL and basic types with `repl + extra_macros` features
- - `test-dispatch-tree`: Basic types with `dispatch_tree` feature
- - `test-all`: Comprehensive testing with all feature combinations (ShellContext, Suggest, ResREPL, StructuralRenderer, Hooks, basic types, etc.)
+ - `test-basic`: Basic type tests with default features (Node, Flag, RenderResult, NextProcess, StringVec)
+ - `test-comp`: ShellContext, Suggest, SuggestItem, is_completing with `comp + builds` features
+ - `test-structural-renderer`: StructuralRenderer output in various formats with `structural_renderer_full + parser` features
+ - `test-repl`: ResREPL and basic types with `repl + extra_macros` features
+ - `test-dispatch-tree`: Basic types with `dispatch_tree` feature
+ - `test-all`: Comprehensive testing with all feature combinations (ShellContext, Suggest, ResREPL, StructuralRenderer, Hooks, basic types, etc.)
- These crates are located in `mingling_core/tests/test-*/`, each marked as an independent workspace via `[workspace]`, isolated from the main workspace.
+ These crates are located in `mingling_core/tests/test-*/`, each marked as an independent workspace via `[workspace]`, isolated from the main workspace.
3. **[`workspace`]** - **Added workspace exclude rules for the 6 test crates in the root `Cargo.toml`**, ensuring that integration test crates are not captured by the workspace's implicit member rules.
@@ -193,33 +382,33 @@ None
2. **[`core:comp`]** Fixed `default_completion` jumping to the next subcommand level on partial input (e.g. typing `b` for `bind` would skip `bind` and directly suggest third-level commands `add`/`ls`/`rm`). Now if the last input word is only a partial match (`starts_with` but not equal), the current-level word is suggested instead of skipping ahead
3. **[`core`]** Replaced `OnceLock<Option<Box<dyn Any>>>` with a custom `ProgramCell` type backed by `UnsafeCell` and `AtomicBool`. The new `ProgramCell` replaces `OnceLock`'s `get_or_init` / `get` / `as_ref` calls with a direct `set` / `get_raw` / `take` API. This change:
- - Eliminates the double indirection (`OnceLock<Option<Box<...>>>` → `UnsafeCell<Option<Box<...>>>`)
- - Allows the program instance to be **taken** (moved out) via an `unsafe fn take()` after execution completes, enabling proper cleanup before `std::process::exit()` in `exec_and_exit`
- - Is paired with corresponding simplifications in `once_exec.rs` and `repl_exec.rs` that switch from `THIS_PROGRAM.get().unwrap().as_ref()` to `THIS_PROGRAM.get_raw().unwrap()`
+ - Eliminates the double indirection (`OnceLock<Option<Box<...>>>` → `UnsafeCell<Option<Box<...>>>`)
+ - Allows the program instance to be **taken** (moved out) via an `unsafe fn take()` after execution completes, enabling proper cleanup before `std::process::exit()` in `exec_and_exit`
+ - Is paired with corresponding simplifications in `once_exec.rs` and `repl_exec.rs` that switch from `THIS_PROGRAM.get().unwrap().as_ref()` to `THIS_PROGRAM.get_raw().unwrap()`
4. **[`macros:dispatcher_clap`]** Added `dispatch_tree` feature integration for `#[dispatcher_clap]`. When the `dispatch_tree` feature is enabled, `#[dispatcher_clap]` will now automatically register the dispatcher and entry in the dispatch tree via `register_dispatcher!`, matching the behavior already present in the `dispatcher!` macro. When the feature is disabled, no additional code is generated.
5. **[`macros`]** The four macros `#[chain]`, `#[renderer]`, `#[help]`, and `#[completion]` now support using fully qualified type paths with `::` (e.g. `crate::EntryFine`) as type inputs. Previously, these macros required types to be bare single-segment idents (e.g. `EntryFine`), rejecting reasonable paths like `crate::EntryFine`. Specific changes:
- - `res_injection::extract_args_info` (shared by `#[chain]` and `#[renderer]`): Removed the single-segment validation for the first parameter type
- - `#[renderer]` / `#[help]`: Removed respective `check_single_segment_type` calls
- - `#[completion]`: Attribute parameter parsing changed from `Ident` to `TypePath`, supporting `#[completion(crate::EntryFine)]`
- - Fixed code generation in `build_chain_arm`, `build_chain_exist_arm`, `build_renderer_entry`, `build_renderer_exist_entry`, `build_general_renderer_entry`, and completion entry: `Self::#variant` match arms now only take the last segment ident of the type path (e.g. `Self::EntryFine`), rather than concatenating the full path directly (which would generate invalid syntax like `Self::crate::EntryFine`), while `downcast::<T>()` and `type Previous = T` still use the full path to ensure correct type resolution
+ - `res_injection::extract_args_info` (shared by `#[chain]` and `#[renderer]`): Removed the single-segment validation for the first parameter type
+ - `#[renderer]` / `#[help]`: Removed respective `check_single_segment_type` calls
+ - `#[completion]`: Attribute parameter parsing changed from `Ident` to `TypePath`, supporting `#[completion(crate::EntryFine)]`
+ - Fixed code generation in `build_chain_arm`, `build_chain_exist_arm`, `build_renderer_entry`, `build_renderer_exist_entry`, `build_general_renderer_entry`, and completion entry: `Self::#variant` match arms now only take the last segment ident of the type path (e.g. `Self::EntryFine`), rather than concatenating the full path directly (which would generate invalid syntax like `Self::crate::EntryFine`), while `downcast::<T>()` and `type Previous = T` still use the full path to ensure correct type resolution
6. **[`macros:register`]** Added compile-time duplicate variant detection for chain, renderer, help, and completion registrations. When two `#[chain]` (or `#[renderer]`, `#[help]`, `#[completion]`) functions register the same type variant, the compiler now emits a clear error at the registration site (e.g. `fn handle_state_prev1(_p: StatePrev1)`) instead of silently producing an unreachable match arm that only manifests as dead code in the generated `do_chain()`/`render()` dispatch.
- Affected registration points:
- - `register_chain` — checks `CHAINS` set for existing entries with the same variant
- - `register_renderer` — checks `RENDERERS` set
- - `help_attr` (via `#[help]`) + `register_help` — checks `HELP_REQUESTS`; `register_help` also serves as a public escape hatch for manual help registration, automatically skipping the duplicate check when the exact same entry was pre-inserted by `#[help]`
- - `completion_attr` (via `#[completion]`) — checks `COMPLETIONS` set
+ Affected registration points:
+ - `register_chain` — checks `CHAINS` set for existing entries with the same variant
+ - `register_renderer` — checks `RENDERERS` set
+ - `help_attr` (via `#[help]`) + `register_help` — checks `HELP_REQUESTS`; `register_help` also serves as a public escape hatch for manual help registration, automatically skipping the duplicate check when the exact same entry was pre-inserted by `#[help]`
+ - `completion_attr` (via `#[completion]`) — checks `COMPLETIONS` set
7. **[`macros:dispatch_tree`]** Fixed the static name generation for dispatch tree nodes to use `snake_case` conversion instead of simple `.` → `_` replacement, and fixed the `__comp` completion dispatcher static name from `__internal_dispatcher___comp` (triple underscore) to `__internal_dispatcher_comp` (double underscore), resolving a mismatch between the name generated by `register_dispatcher!` and the name used in `program_comp_gen`.
8. **[`core`]** Changed the `exec_without_render` and `exec` methods' behavior: when `stdout_setting.render_output` is `false` or the result is empty, the exit code from the result is now returned instead of hardcoded `0`. This ensures that programs which set a non-zero exit code without producing renderable output (e.g., via `ExitCodeSetup` or `ProgramControlUnit::OverrideExitCode`) will exit with the correct code. Specific changes in `once_exec.rs`:
- - `exec()` (async) and `exec_without_render_and_print()` (sync): The `exit_code` is now read from the result before the render check, and returned as the fallback value instead of `0` when output is not printed.
- - This means `ExitCode` / `ProgramControls` overrides are now respected regardless of whether any output is rendered.
+ - `exec()` (async) and `exec_without_render_and_print()` (sync): The `exit_code` is now read from the result before the render check, and returned as the fallback value instead of `0` when output is not printed.
+ - This means `ExitCode` / `ProgramControls` overrides are now respected regardless of whether any output is rendered.
#### Optimizations:
@@ -421,20 +610,20 @@ fn main() {
The pathfinder system consists of:
- **`mingling_pathf` sub-crate** — A standalone crate for build-time source analysis:
- - `module_pathf::analyze()` — Scans the crate's source tree and infers module paths from the directory structure
- - `pattern_analyzer::init()` — Creates a `PatternAnalyzer` registered with all supported Mingling patterns
- - `analyze_and_build_type_mapping()` / `analyze_and_build_type_mapping_for()` — Convenience functions for build scripts
- - **Pattern matchers** — Individual pattern implementations for each Mingling macro:
- - `PackPattern` — Matches `pack!`, `pack_err!`, `pack_structural!`, `pack_err_structural!` invocations
- - `GroupPattern` — Matches `group!` and `group_structural!` invocations
- - `GrouppedDerivePattern` — Matches `#[derive(Groupped)]` and `#[derive(GrouppedSerialize)]`
- - `ChainPattern` — Matches `#[chain]` functions, extracts `__internal_chain_*` names
- - `RendererPattern` — Matches `#[renderer]` functions, extracts `__internal_renderer_*` names
- - `HelpPattern` — Matches `#[help]` functions, extracts `__internal_help_*` names
- - `CompletionPattern` — Matches `#[completion(T)]` functions, extracts `__internal_completion_*` names
- - `DispatcherPattern` — Matches `dispatcher!` invocations, extracts entry type names (supports both explicit and implicit forms)
- - `DispatcherClapPattern` — Matches `#[dispatcher_clap]` structs, extracts struct names
- - `type_mapping_builder` — Assembles the mapping from all analyzed files and writes `MAPPING` and `type_using.rs` output files
+ - `module_pathf::analyze()` — Scans the crate's source tree and infers module paths from the directory structure
+ - `pattern_analyzer::init()` — Creates a `PatternAnalyzer` registered with all supported Mingling patterns
+ - `analyze_and_build_type_mapping()` / `analyze_and_build_type_mapping_for()` — Convenience functions for build scripts
+ - **Pattern matchers** — Individual pattern implementations for each Mingling macro:
+ - `PackPattern` — Matches `pack!`, `pack_err!`, `pack_structural!`, `pack_err_structural!` invocations
+ - `GroupPattern` — Matches `group!` and `group_structural!` invocations
+ - `GrouppedDerivePattern` — Matches `#[derive(Groupped)]` and `#[derive(GrouppedSerialize)]`
+ - `ChainPattern` — Matches `#[chain]` functions, extracts `__internal_chain_*` names
+ - `RendererPattern` — Matches `#[renderer]` functions, extracts `__internal_renderer_*` names
+ - `HelpPattern` — Matches `#[help]` functions, extracts `__internal_help_*` names
+ - `CompletionPattern` — Matches `#[completion(T)]` functions, extracts `__internal_completion_*` names
+ - `DispatcherPattern` — Matches `dispatcher!` invocations, extracts entry type names (supports both explicit and implicit forms)
+ - `DispatcherClapPattern` — Matches `#[dispatcher_clap]` structs, extracts struct names
+ - `type_mapping_builder` — Assembles the mapping from all analyzed files and writes `MAPPING` and `type_using.rs` output files
- **Integration with `gen_program!()`** — When the `pathf` feature is enabled, `gen_program!()` includes the generated `type_using.rs` file via `include!()`, making all type paths available in scope for the generated dispatch code.
@@ -468,29 +657,29 @@ The following explicit syntaxes are **removed**:
1. **[`core`]** **[`structural_renderer`]** Renamed the `general_renderer` feature to `structural_renderer`. All associated types, structs, and APIs have been renamed accordingly:
- - Feature flag: `general_renderer` → `structural_renderer`
- - Setup struct: `GeneralRendererSetup` → `StructuralRendererSetup`
- - Simple setup struct: `GeneralRendererSimpleSetup` → `StructuralRendererSimpleSetup`
- - Renderer type: `GeneralRenderer` → `StructuralRenderer`
- - Setting enum: `GeneralRendererSetting` → `StructuralRendererSetting`
- - Error type: `GeneralRendererSerializeError` → `StructuralRendererSerializeError`
- - Field name: `program.general_renderer_name` → `program.structural_renderer_name`
- - Trait method: `ProgramCollect::general_render()` → `ProgramCollect::structural_render()`
- - Internal module: `mingling_core::renderer::general` → `mingling::renderer::structural`
- - Internal static: `GENERAL_RENDERERS` → `STRUCTURAL_RENDERERS`
- - Feature gate attributes: `#[cfg(feature = "general_renderer")]` → `#[cfg(feature = "structural_renderer")]`
- - Sub-features: `general_renderer_empty` → `structural_renderer_empty`, `general_renderer_full` → `structural_renderer_full`
- - Runtime feature constant: `MINGLING_GENERAL_RENDERER` → `MINGLING_STRUCTURAL_RENDERER` (and similarly for `_EMPTY` and `_FULL`)
- - Derive macro feature gate: `#[cfg(feature = "general_renderer")]` on `#[derive(StructuralData)]` and `#[derive(GrouppedSerialize)]` → `#[cfg(feature = "structural_renderer")]`
- - Example project: `example-general-renderer` renamed to `example-structural-renderer`
- - Test crate: `test-general-renderer` renamed to `test-structural-renderer`
+ - Feature flag: `general_renderer` → `structural_renderer`
+ - Setup struct: `GeneralRendererSetup` → `StructuralRendererSetup`
+ - Simple setup struct: `GeneralRendererSimpleSetup` → `StructuralRendererSimpleSetup`
+ - Renderer type: `GeneralRenderer` → `StructuralRenderer`
+ - Setting enum: `GeneralRendererSetting` → `StructuralRendererSetting`
+ - Error type: `GeneralRendererSerializeError` → `StructuralRendererSerializeError`
+ - Field name: `program.general_renderer_name` → `program.structural_renderer_name`
+ - Trait method: `ProgramCollect::general_render()` → `ProgramCollect::structural_render()`
+ - Internal module: `mingling_core::renderer::general` → `mingling::renderer::structural`
+ - Internal static: `GENERAL_RENDERERS` → `STRUCTURAL_RENDERERS`
+ - Feature gate attributes: `#[cfg(feature = "general_renderer")]` → `#[cfg(feature = "structural_renderer")]`
+ - Sub-features: `general_renderer_empty` → `structural_renderer_empty`, `general_renderer_full` → `structural_renderer_full`
+ - Runtime feature constant: `MINGLING_GENERAL_RENDERER` → `MINGLING_STRUCTURAL_RENDERER` (and similarly for `_EMPTY` and `_FULL`)
+ - Derive macro feature gate: `#[cfg(feature = "general_renderer")]` on `#[derive(StructuralData)]` and `#[derive(GrouppedSerialize)]` → `#[cfg(feature = "structural_renderer")]`
+ - Example project: `example-general-renderer` renamed to `example-structural-renderer`
+ - Test crate: `test-general-renderer` renamed to `test-structural-renderer`
2. **[`core`]** Changed the signature of `ProgramSetup::setup` from `fn setup(&mut self, program: &mut Program<C>) -> S` to `fn setup(self, program: &mut Program<C>)`, consuming `self` instead of taking a mutable reference. Correspondingly, `Program::with_setup` now accepts `S` by value (`&mut self, setup: S`) instead of by mutable reference (`&mut self, setup: &mut S`).
3. **[`core`]** Consolidated resource naming for `ExitCode` and `REPL`:
- - Renamed `ExitCode` to `ResExitCode` and moved `ResREPL` from the `mingling` root to `mingling::res::ResREPL` (the `mingling::ResREPL` re-export is removed).
- - This aligns with the naming convention where resources are prefixed with `Res`.
- - The corresponding setup `ExitCodeSetup` and resource injection remain unchanged.
+ - Renamed `ExitCode` to `ResExitCode` and moved `ResREPL` from the `mingling` root to `mingling::res::ResREPL` (the `mingling::ResREPL` re-export is removed).
+ - This aligns with the naming convention where resources are prefixed with `Res`.
+ - The corresponding setup `ExitCodeSetup` and resource injection remain unchanged.
```rust
// Before
@@ -502,25 +691,25 @@ use mingling::{res::ResExitCode, res::ResREPL};
4. **[`core`]** **[`macros`]** Migrated `to_chain()` and `to_render()` methods from being generated individually per type by `#[derive(Groupped)]` and `pack!` macros, to being provided as default trait methods on the `Groupped` trait itself.
- Previously, each packed or derived type had its own inherent `to_chain()` and `to_render()` methods generated by the macros. Now, these methods are defined on the `Groupped<Group>` trait with default implementations, making them available to all types that implement the trait without redundant code generation.
+ Previously, each packed or derived type had its own inherent `to_chain()` and `to_render()` methods generated by the macros. Now, these methods are defined on the `Groupped<Group>` trait with default implementations, making them available to all types that implement the trait without redundant code generation.
- ```rust
- // Before (generated per type by macros):
- impl MyType {
- pub fn to_chain(self) -> ChainProcess<Group> {
- AnyOutput::new(self).route_chain()
- }
- pub fn to_render(self) -> ChainProcess<Group> {
- AnyOutput::new(self).route_renderer()
- }
- }
+ ```rust
+ // Before (generated per type by macros):
+ impl MyType {
+ pub fn to_chain(self) -> ChainProcess<Group> {
+ AnyOutput::new(self).route_chain()
+ }
+ pub fn to_render(self) -> ChainProcess<Group> {
+ AnyOutput::new(self).route_renderer()
+ }
+ }
- // After (provided by Groupped trait default methods):
- // just ensure Groupped is implemented — to_chain() and to_render()
- // are automatically available
- ```
+ // After (provided by Groupped trait default methods):
+ // just ensure Groupped is implemented — to_chain() and to_render()
+ // are automatically available
+ ```
- Removed the per-type inherent method generation from both `groupped.rs` and `pack.rs` in `mingling_macros`.
+ Removed the per-type inherent method generation from both `groupped.rs` and `pack.rs` in `mingling_macros`.
5. **[`macros`]** Changed the `route!()` macro's error branch from `return e` to `return ::mingling::Groupped::to_chain(e)`, so that the error type no longer needs to be pre-converted to `ChainProcess` via `.to_chain()` or `.to_render()`. The macro now accepts any type implementing `Groupped` in the error position and automatically converts it.
@@ -534,43 +723,43 @@ let value = route!(prev.pick_or_route((), Error::default()).unpack());
6. **[`core`]** **[`hook`]** Refactored the hook system to use structured info types and return `ProgramControls<C>` instead of raw values.
- The hook system has been redesigned for better type safety, extensibility, and control flow management:
+ The hook system has been redesigned for better type safety, extensibility, and control flow management:
- - **All hook callbacks now receive structured info types** (e.g., `&HookPreDispatchInfo`, `&HookPostChainInfo<C>`) instead of raw tuples or bare values. Each hook event has a dedicated info struct with named fields, making hook signatures self-documenting and easier to evolve.
+ - **All hook callbacks now receive structured info types** (e.g., `&HookPreDispatchInfo`, `&HookPostChainInfo<C>`) instead of raw tuples or bare values. Each hook event has a dedicated info struct with named fields, making hook signatures self-documenting and easier to evolve.
- - Hook signatures changed from `fn(...)` to `Box<dyn Fn(&InfoType) -> R>`, with `R: Into<ProgramControls<C>>`. Closures that return `()`are automatically converted to`ProgramControls::Empty`via the`From<()>` impl.
+ - Hook signatures changed from `fn(...)` to `Box<dyn Fn(&InfoType) -> R>`, with `R: Into<ProgramControls<C>>`. Closures that return `()`are automatically converted to`ProgramControls::Empty`via the`From<()>` impl.
- ```rust
- // Before
- .on_begin(|| println!("Program started"))
- .on_pre_dispatch(|args| println!("Dispatching: {args:?}"))
- .on_finish(|| 0) // returns i32 as exit code
+ ```rust
+ // Before
+ .on_begin(|| println!("Program started"))
+ .on_pre_dispatch(|args| println!("Dispatching: {args:?}"))
+ .on_finish(|| 0) // returns i32 as exit code
- // After
- .on_begin::<_, ()>(|_| println!("Program started"))
- .on_pre_dispatch(|info| println!("Dispatching: {}", info.arguments.join(" ")))
- .on_finish(|_| ProgramControlUnit::OverrideExitCode(0))
- ```
+ // After
+ .on_begin::<_, ()>(|_| println!("Program started"))
+ .on_pre_dispatch(|info| println!("Dispatching: {}", info.arguments.join(" ")))
+ .on_finish(|_| ProgramControlUnit::OverrideExitCode(0))
+ ```
- - **Added `ProgramControls<C>` and `ProgramControlUnit<C>`** — a new control flow system that replaces the previous approach where only the `finish` hook could return a value (exit code). Now any hook can issue control instructions:
- - `ProgramControlUnit::OverrideExitCode(i32)` — override the program's exit code
- - `ProgramControlUnit::RouteToChain(AnyOutput<C>)` — route to another chain processor
- - `ProgramControlUnit::RouteToRender(AnyOutput<C>)` — route directly to the renderer
- - `ProgramControlUnit::RouteToHelp(AnyOutput<C>)` — route to help display
+ - **Added `ProgramControls<C>` and `ProgramControlUnit<C>`** — a new control flow system that replaces the previous approach where only the `finish` hook could return a value (exit code). Now any hook can issue control instructions:
+ - `ProgramControlUnit::OverrideExitCode(i32)` — override the program's exit code
+ - `ProgramControlUnit::RouteToChain(AnyOutput<C>)` — route to another chain processor
+ - `ProgramControlUnit::RouteToRender(AnyOutput<C>)` — route directly to the renderer
+ - `ProgramControlUnit::RouteToHelp(AnyOutput<C>)` — route to help display
- - **Added `handle_program_control` function** in `exec.rs` that processes `ProgramControls` returned by hooks, updating the current execution state (exit code, current `AnyOutput`) or triggering early returns (e.g., routing to render/help).
+ - **Added `handle_program_control` function** in `exec.rs` that processes `ProgramControls` returned by hooks, updating the current execution state (exit code, current `AnyOutput`) or triggering early returns (e.g., routing to render/help).
- - **`ExitCodeSetup` updated** — its `on_finish` hook now returns `ProgramControlUnit::OverrideExitCode(this.exit_code)` instead of just `this.exit_code`.
+ - **`ExitCodeSetup` updated** — its `on_finish` hook now returns `ProgramControlUnit::OverrideExitCode(this.exit_code)` instead of just `this.exit_code`.
- - **`HookPostReadlineInfo` now wraps `line: &mut String`** — the `repl_post_readline` hook receives a structured info object instead of a raw `&mut String`.
+ - **`HookPostReadlineInfo` now wraps `line: &mut String`** — the `repl_post_readline` hook receives a structured info object instead of a raw `&mut String`.
- - **`HookOnReceiveResultInfo` now wraps `result: &RenderResult`** — the `repl_on_receive_result` hook receives the result through an info struct with a `.result` field instead of directly.
+ - **`HookOnReceiveResultInfo` now wraps `result: &RenderResult`** — the `repl_on_receive_result` hook receives the result through an info struct with a `.result` field instead of directly.
- - **`hook` module made public** — moved from `#[doc(hidden)]` to a documented public module (`pub mod hook`), along with all associated info types and control unit types.
+ - **`hook` module made public** — moved from `#[doc(hidden)]` to a documented public module (`pub mod hook`), along with all associated info types and control unit types.
- - **Added `dispatch_args_trie` default method** on `ProgramCollect` (behind `#[cfg(not(feature = "dispatch_tree"))]`) that calls `unreachable!()` by default, avoiding `#[cfg]` gymnastics in `exec.rs`.
+ - **Added `dispatch_args_trie` default method** on `ProgramCollect` (behind `#[cfg(not(feature = "dispatch_tree"))]`) that calls `unreachable!()` by default, avoiding `#[cfg]` gymnastics in `exec.rs`.
- - **Examples and internal callers updated** throughout the codebase to use the new hook API patterns.
+ - **Examples and internal callers updated** throughout the codebase to use the new hook API patterns.
7. **[`core`]** **[`structural_renderer`]** Added the `pack_err_structural!`, `pack_structural!`, and `group_structural!` macros for creating types that support structured output (JSON/YAML/TOML/RON). These are like `pack_err!`, `pack!`, and `group!` respectively, but also mark the type with the `StructuralData` trait, enabling the `StructuralRenderer` to serialize them.
@@ -598,8 +787,8 @@ When the `structural_renderer` feature is enabled, `ResultEmpty` also derives `S
#### Fixes:
1. **[`macros:dispatcher_clap`]** Fixed the issue where clap error messages (`DisplayHelp` and parse errors from `try_parse_from`) could not output ANSI
- - For error paths, use `e.render().ansi()` instead of `e.to_string()` to prevent ANSI codes from being stripped by `strip_str` in `StyledStr::Display`
- - For help info paths, use with `BasicProgramSetup`, output ANSI-colored help content through the mingling framework's `render_help` flow
+ - For error paths, use `e.render().ansi()` instead of `e.to_string()` to prevent ANSI codes from being stripped by `strip_str` in `StyledStr::Display`
+ - For help info paths, use with `BasicProgramSetup`, output ANSI-colored help content through the mingling framework's `render_help` flow
#### Optimizations:
@@ -639,8 +828,8 @@ fn handle_path_pick(prev: PathPick) {
```
3. **[`macros`]** Extended the `#[renderer]` attribute to support custom return types. Previously, `#[renderer]` functions could only return `()`, and the generated helper function always returned `RenderResult`. Now:
- - **`fn foo(x: T)` / `fn foo(x: T) -> ()`** → The generated helper function returns `()`. If the internal `RenderResult` (`dummy_r`) is non-empty, it is automatically printed to stdout.
- - **`fn foo(x: T) -> U`** → The generated helper function returns `U`. The internal `RenderResult` is converted via `dummy_r.into()`, and no automatic printing occurs.
+ - **`fn foo(x: T)` / `fn foo(x: T) -> ()`** → The generated helper function returns `()`. If the internal `RenderResult` (`dummy_r`) is non-empty, it is automatically printed to stdout.
+ - **`fn foo(x: T) -> U`** → The generated helper function returns `U`. The internal `RenderResult` is converted via `dummy_r.into()`, and no automatic printing occurs.
4. **[`macros`]** Resource injection is now shared between `#[chain]` and `#[renderer]`.
Extracted the common resource injection infrastructure (`ResourceInjection`, `extract_args_info`, `generate_immut_resource_bindings`, `wrap_body_with_mut_resources`) from `chain.rs` into a new `res_injection.rs` module. Both `#[chain]` and `#[renderer]` now reuse the same logic.
@@ -787,10 +976,10 @@ None
1. **[`core`]** The core library no longer depends on `thiserror`
2. **[`mingling`]** Split the monolithic `general_renderer` feature into separate format-specific features:
- - `general_renderer` now only includes core serialization support without any specific format
- - `general_renderer_full` bundles all available serialization formats
- - Individual format features: `json_serde_fmt`, `yaml_serde_fmt`, `toml_serde_fmt`, `ron_serde_fmt`
- - A meta feature `all_serde_fmt` enables all format features at once
+ - `general_renderer` now only includes core serialization support without any specific format
+ - `general_renderer_full` bundles all available serialization formats
+ - Individual format features: `json_serde_fmt`, `yaml_serde_fmt`, `toml_serde_fmt`, `ron_serde_fmt`
+ - A meta feature `all_serde_fmt` enables all format features at once
#### Features:
@@ -910,9 +1099,9 @@ fn my_chain(prev: Prev) -> Next {
1. **[`core`]** The signature of `exec` has been changed to `exec(self) -> i32` (previously was `exec(self)`)
2. **[`macros`]** All proc macros that accept a program/group name parameter (e.g. `pack!`, `dispatcher!`, `#[chain]`, `#[program_setup]`, `#[dispatcher_clap]`, `#[derive(Groupped)]`) now parse the name as a `syn::Path` instead of a bare `Ident`. This means:
- - You can now use paths like `crate::MyProgram` or `my_crate::MyProgram` in addition to plain `MyProgram`.
- - The default program name `ThisProgram` is no longer re-exported or required as an import — generated code references `crate::ThisProgram` directly.
- - If you previously imported `ThisProgram` from `crate` only for macro use, that import is no longer needed and can be removed.
+ - You can now use paths like `crate::MyProgram` or `my_crate::MyProgram` in addition to plain `MyProgram`.
+ - The default program name `ThisProgram` is no longer re-exported or required as an import — generated code references `crate::ThisProgram` directly.
+ - If you previously imported `ThisProgram` from `crate` only for macro use, that import is no longer needed and can be removed.
```rust
use crate::ThisProgram; // Can be removed if not used directly
@@ -1089,9 +1278,9 @@ gen_program!();
```
6. **[`picker`]** Simplified `Picker` logic:
- - `Picker` no longer requires the generic parameter `<G>` by default; it only needs it when using `pick_or_route` or `after_or_route`
+ - `Picker` no longer requires the generic parameter `<G>` by default; it only needs it when using `pick_or_route` or `after_or_route`
- - Additionally, if no `or_route` operations are used, the `unpack_directly` function is no longer available; `unpack` will directly extract the inner value
+ - Additionally, if no `or_route` operations are used, the `unpack_directly` function is no longer available; `unpack` will directly extract the inner value
```rust
// Before
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index b10e96d..e729295 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -6,22 +6,22 @@ Before contributing, we recommend reading [README](README.md) to get an overview
## 1. Project Structure 📦
-| Category | Path/Name | Description |
-| --------------------------- | ------------------------- | ------------------------------------------------------------------ |
-| **Entry crate** | `mingling/` | Project entry point |
-| **Core library** | `mingling_core/` | Imported as an external dependency |
-| **Macro library** | `mingling_macros/` | Imported as an external dependency |
-| **Mingling Pathfinder** | `mingling_pathf/` | Build-time module path resolution for types |
-| **Mingling Picker2** | `mingling_picker/` | Mingling Arguments Parser |
-| **Mingling Picker2 Macros** | `mingling_picker_macros/` | Mingling Arguments Parser Macros |
-| **Scaffolding tool** | `mling/` | Scaffolding tool `mingling-cli` |
-| **Examples** | `examples/` | To add expected output tests, modify `examples/test-examples.toml` |
-| **Documents** | `docs/` | All documents |
-| **Dev Documents** | `docs/dev/` | Internal documents |
-| **Resources** | `docs/res/` | All resources |
-| **Development tools** | `.run/src/bin` | Contains scripts and Rust tools |
-| **CI** | `.run/src/bin/ci.rs` | Can be invoked directly via `cargo ci` |
-| **Temporary files** | `.temp/` | Ignored by `.gitignore` |
+| Category | Path/Name | Description |
+| --------------------------- | -------------------- | ------------------------------------------------------------------ |
+| **Entry crate** | `mingling/` | Project entry point |
+| **Core library** | `mingling_core/` | Imported as an external dependency |
+| **Macro library** | `mingling_macros/` | Imported as an external dependency |
+| **Mingling Pathfinder** | `mingling_pathf/` | Build-time module path resolution for types |
+| **Mingling Picker2** | `arg_picker/` | Mingling Arguments Parser |
+| **Mingling Picker2 Macros** | `arg_picker_macros/` | Mingling Arguments Parser Macros |
+| **Scaffolding tool** | `mling/` | Scaffolding tool `mingling-cli` |
+| **Examples** | `examples/` | To add expected output tests, modify `examples/test-examples.toml` |
+| **Documents** | `docs/` | All documents |
+| **Dev Documents** | `docs/dev/` | Internal documents |
+| **Resources** | `docs/res/` | All resources |
+| **Development tools** | `.run/src/bin` | Contains scripts and Rust tools |
+| **CI** | `.run/src/bin/ci.rs` | Can be invoked directly via `cargo ci` |
+| **Temporary files** | `.temp/` | Ignored by `.gitignore` |
## 2. How to Contribute
@@ -113,28 +113,28 @@ No strict requirements here — just modify the relevant `*.html` files. Preview
## 3. Submission Guide 🖊
1. **Pull Request**
- - Submit a GitHub Pull Request and @Reviewer **[Weicao-CatilGrass](https://github.com/Weicao-CatilGrass)** for review
- - Or send patches to **catil_grass@qq.com**
+ - Submit a GitHub Pull Request and @Reviewer **[Weicao-CatilGrass](https://github.com/Weicao-CatilGrass)** for review
+ - Or send patches to **catil_grass@qq.com**
2. **Commit Messages**
- - Clearly and concisely describe the changes, no stringent requirements
- - Provide more detail for complex changes, keep it brief for simple changes
- - But: if you use [Conventional Commits](https://www.conventionalcommits.org/), it would make me even happier :)
+ - Clearly and concisely describe the changes, no stringent requirements
+ - Provide more detail for complex changes, keep it brief for simple changes
+ - But: if you use [Conventional Commits](https://www.conventionalcommits.org/), it would make me even happier :)
3. **CHANGELOG**
- - If the submission includes functional changes or fixes, **the PR must include modifications to CHANGELOG.md** to describe the changes
- - For minor changes like typo fixes, **CHANGELOG.md modification is not required**, and we will merge faster
+ - If the submission includes functional changes or fixes, **the PR must include modifications to CHANGELOG.md** to describe the changes
+ - For minor changes like typo fixes, **CHANGELOG.md modification is not required**, and we will merge faster
4. **Multi-commit PR**
- - A PR can contain multiple commits
- - However, at least one commit must modify CHANGELOG.md
+ - A PR can contain multiple commits
+ - However, at least one commit must modify CHANGELOG.md
5. **Review**
- - After submission, please notify [Weicao-CatilGrass](https://github.com/Weicao-CatilGrass) for review — this is the most efficient way to get feedback
+ - After submission, please notify [Weicao-CatilGrass](https://github.com/Weicao-CatilGrass) for review — this is the most efficient way to get feedback
6. **Binary Resources**
- - For binary resource files (images, etc.), please be cautious about adding them to avoid repository bloat
+ - For binary resource files (images, etc.), please be cautious about adding them to avoid repository bloat
## 4. Documentation Contribution 📕
diff --git a/Cargo.lock b/Cargo.lock
index a283e0f..714e4d4 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -71,6 +71,23 @@ dependencies = [
]
[[package]]
+name = "arg-picker"
+version = "0.1.0"
+dependencies = [
+ "arg-picker-macros",
+ "just_fmt 0.2.0",
+]
+
+[[package]]
+name = "arg-picker-macros"
+version = "0.1.0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
name = "autocfg"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -409,10 +426,10 @@ checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
name = "mingling"
version = "0.3.0"
dependencies = [
+ "arg-picker",
"mingling",
"mingling_core",
"mingling_macros",
- "mingling_picker",
"serde",
"size",
"tokio",
@@ -468,24 +485,6 @@ dependencies = [
]
[[package]]
-name = "mingling_picker"
-version = "0.3.0"
-dependencies = [
- "just_fmt 0.2.0",
- "mingling_core",
- "mingling_picker_macros",
-]
-
-[[package]]
-name = "mingling_picker_macros"
-version = "0.3.0"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
-
-[[package]]
name = "mio"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/Cargo.toml b/Cargo.toml
index 90989fc..99523c7 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -10,8 +10,8 @@ members = [
"mingling_pathf",
# Picker2
- "mingling_picker",
- "mingling_picker_macros",
+ "arg_picker",
+ "arg_picker_macros",
# Scaffolding tool
"mling"
@@ -36,8 +36,9 @@ exclude = [
mingling_core = { path = "mingling_core", default-features = false }
mingling_macros = { path = "mingling_macros", default-features = false }
mingling_pathf = { path = "mingling_pathf", default-features = false }
-mingling_picker = { path = "mingling_picker", default-features = false }
-mingling_picker_macros = { path = "mingling_picker_macros", default-features = false }
+
+arg-picker = { path = "arg_picker", default-features = false }
+arg-picker-macros = { path = "arg_picker_macros", default-features = false }
just_fmt = "0.2.0"
just_template = "0.2.0"
diff --git a/GETTING_STARTED.md b/GETTING_STARTED.md
new file mode 100644
index 0000000..e08d17b
--- /dev/null
+++ b/GETTING_STARTED.md
@@ -0,0 +1,775 @@
+# Writing with Mingling
+
+## The Big Picture
+
+Mingling organizes your CLI program into three distinct phases:
+
+```
+Input → [Dispatcher] → Entry → [Chain(s)] → Result → [Renderer] → Output
+```
+
+**Step 1: Input** — The user's raw arguments flow in.
+**Step 2: Dispatch** — The **Dispatcher** receives them and wraps them into an **Entry** type.
+**Step 3: Chain** — The Entry is handed to a **Chain** function for processing.
+**Step 4: Render** — The **Renderer** receives the result and writes it to the terminal.
+
+> [!NOTE]
+> A Chain can produce a **State** type, passed to the next Chain for further processing,
+>
+> or it can produce a **Result** type, handed to the Renderer for output.
+
+Every step in this pipeline is a **pure function** annotated with an attribute macro, and the framework recognizes them as long as they meet the requirements.
+
+---
+
+## 1. Defining Commands — `dispatcher!`
+
+The entry point for every subcommand is the `dispatcher!` macro. It generates two structs for you: a **Dispatcher** (used to register the command with the program) and an **Entry** (a wrapper around `Vec<String>` that holds the raw arguments).
+
+```rust
+use mingling::prelude::*;
+
+// command.name Dispatcher EntryType
+// │ │ │
+dispatcher!("greet", CMDGreet => EntryGreet);
+
+// Nested subcommand: `remote add`
+dispatcher!("remote.add", CMDRemoteAdd => EntryRemoteAdd);
+```
+
+Then in `main()`, register the dispatcher with the program:
+
+```rust
+dispatcher!("greet", CMDGreet => EntryGreet);
+
+fn main() {
+ let mut program = ThisProgram::new();
+ program.with_dispatcher(CMDGreet);
+ program.exec_and_exit();
+}
+```
+
+Mingling also supports an abbreviated form (with the `extra_macros` feature):
+
+```rust
+// Features: ["extra_macros"]
+
+// Auto-generates CMDGreet / EntryGreet from "greet"
+dispatcher!("greet");
+```
+
+---
+
+## 2. The Chain — "#[chain]" — Where Logic Lives
+
+The `#[chain]` attribute turns a plain function into an execution step. Think of it as "the logic that transforms one typed value into another."
+
+```rust
+dispatcher!("greet", CMDGreet => EntryGreet);
+
+pack!(ResultGreeting = String);
+
+#[chain]
+fn handle_greet(args: EntryGreet) -> Next {
+ let greeting = args
+ .inner
+ .first()
+ .cloned()
+ .unwrap_or_else(|| "World".to_string());
+ ResultGreeting::new(greeting).into()
+}
+```
+
+Key points:
+
+- The return type is `Next` — a type alias for `ChainProcess<ThisProgram>`.
+- You chain results by calling `.to_chain()` on any `pack!`-ed type.
+- You can have **multiple chain functions** for the same command, each transforming the data further.
+- With the `async` feature, chain functions can be `async fn`.
+
+---
+
+## 3. The Renderer — "#[renderer]" — How Output Works
+
+The `#[renderer]` attribute turns a function into an output handler. It receives the final result of a chain and returns a `RenderResult`.
+
+```rust
+use mingling::macros::pack;
+use mingling::prelude::*;
+use std::io::Write;
+
+pack!(ResultGreeting = String);
+
+#[renderer]
+fn render_greeting(greeting: ResultGreeting) -> RenderResult {
+ let mut result = RenderResult::new();
+ writeln!(result, "Hello, {}!", *greeting).ok();
+ result
+}
+```
+
+Inside a renderer, create a `RenderResult`, write to it using `write!` / `writeln!` (from [`std::io::Write`](https://doc.rust-lang.org/std/io/trait.Write.html)), and return it. The output is captured in the buffer and flushed by the framework at the end of the pipeline.
+
+You can write renderers for **any type** in your program, including error types:
+
+```rust
+use mingling::prelude::*;
+use std::io::Write;
+
+#[renderer]
+fn render_dispatcher_not_found(err: ErrorDispatcherNotFound) -> RenderResult {
+ let mut result = RenderResult::new();
+ writeln!(result, "Command not found: [{}]", err.join(" ")).ok();
+ result
+}
+```
+
+---
+
+## 4. Parsing Arguments — The Picker
+
+Mingling provides a **Picker** for zero-cost argument extraction. You use `pick()` or `pick_or()` on an entry to extract typed values, then `unpack()` to get the final tuple.
+
+```rust
+// Features: ["parser"]
+
+use mingling::parser::Picker;
+
+dispatcher!("greet", CMDGreet => EntryGreet);
+pack!(ResultGreeting = String);
+
+#[chain]
+fn handle_greet(args: EntryGreet) -> Next {
+ let (name, count) = Picker::new(args.inner)
+ .pick::<String>(()) // positional: first string
+ .pick_or::<u8>(["-r", "--repeat"], 1) // optional flag with default
+ .unpack();
+ ResultGreeting::new(format!("{} x{}", name, count)).into()
+}
+```
+
+With the `parser` feature, the `AsPicker` trait provides a shorthand directly on entries:
+
+```rust
+// Features: ["parser"]
+
+dispatcher!("greet", CMDGreet => EntryGreet);
+pack!(ResultGreeting = String);
+
+#[chain]
+fn handle(args: EntryGreet) -> Next {
+ let (name, count) = args
+ .pick::<Option<String>>(())
+ .pick_or::<u8>(["-r", "--repeat"], 1)
+ .unpack();
+ ResultGreeting::new(format!("{} x{}", name.unwrap_or_default(), count)).into()
+}
+```
+
+For enums, derive `EnumTag` and implement `PickableEnum` to parse enum variants from strings:
+
+```rust
+// Features: ["parser", "extra_macros"]
+
+use mingling::{EnumTag, Grouped};
+use mingling::parser::PickableEnum;
+
+dispatcher!("lang.select", CMDLang => EntryLang);
+
+#[derive(Debug, Default, EnumTag, Grouped)]
+pub enum Language {
+ #[default]
+ Rust,
+ #[enum_rename("C++")]
+ CPlusPlus,
+}
+
+impl PickableEnum for Language {}
+
+#[chain]
+fn handle(args: EntryLang) -> Next {
+ let lang: Language = args.pick(()).unpack();
+ lang.into()
+}
+```
+
+---
+
+## 5. The Help System — "#[help]"
+
+Help is just another attribute macro. When the user passes `--help` or `-h`, the program skips the normal chain/render pipeline and routes directly to your `#[help]` function.
+
+Enable it by adding `BasicProgramSetup`:
+
+```rust
+use mingling::{macros::help, prelude::*, setup::BasicProgramSetup};
+use std::io::Write;
+
+dispatcher!("greet", CMDGreet => EntryGreet);
+
+#[help]
+fn help_greet(_prev: EntryGreet) -> RenderResult {
+ let mut result = RenderResult::new();
+ writeln!(result, "Usage: greet <NAME>").ok();
+ writeln!(result, "Greets the user with the given name.").ok();
+ result
+}
+
+fn main() {
+ let mut program = ThisProgram::new();
+ program.with_setup(BasicProgramSetup); // enables --help / -h
+ program.with_dispatcher(CMDGreet);
+ program.exec_and_exit();
+}
+
+gen_program!();
+```
+
+The flow is:
+
+- User types `greet --help`
+- `BasicProgramSetup` sets `program.user_context.help = true`
+- The dispatcher sees this flag and routes to the `#[help]` function instead of the `#[chain]`
+
+---
+
+## 6. Completion — "#[completion]" — Dynamic Shell Completions
+
+With the `comp` feature, Mingling provides a fully dynamic completion system. You write a function that returns `Suggest` based on the current shell context, and Mingling generates the completion scripts for bash, zsh, fish, and pwsh.
+
+```rust
+// Features: ["comp", "extra_macros"]
+
+use mingling::{macros::suggest, ShellContext, Suggest};
+
+dispatcher!("greet", CMDGreet => EntryGreet);
+pack!(ResultName = (u8, String));
+
+#[completion(EntryGreet)]
+fn complete_greet(ctx: &ShellContext) -> Suggest {
+ // Suggest positional arguments
+ if ctx.previous_word == "greet" {
+ return suggest! {
+ "Alice": "Likes to receive messages",
+ "Bob": "Likes to pass messages",
+ "World"
+ };
+ }
+
+ // Suggest flag arguments
+ if ctx.typing_argument() {
+ return suggest! {
+ "-r": "Number of repetitions",
+ "--repeat": "Number of repetitions",
+ }
+ .strip_typed_argument(ctx);
+ }
+
+ suggest!() // no suggestions
+}
+```
+
+You also need to register the built-in completion dispatcher:
+
+```rust
+// Features: ["comp"]
+
+fn main() {
+ let mut program = ThisProgram::new();
+ program.with_dispatcher(crate::CMDCompletion);
+ program.exec_and_exit();
+}
+```
+
+In your `build.rs`, generate the shell scripts:
+
+```rust
+// BUILD TIME
+// Features: ["comp", "builds"]
+mingling::build::build_comp_scripts(env!("CARGO_PKG_NAME")).unwrap();
+```
+
+For enum-based completions, use `suggest_enum!`:
+
+```rust
+// Features: ["comp", "extra_macros"]
+
+use mingling::{ShellContext, Suggest};
+use mingling::macros::suggest_enum;
+use mingling::EnumTag;
+
+dispatcher!("lang.select", CMDLang => EntryLang);
+
+#[derive(EnumTag)]
+pub enum ProgrammingLanguages {
+ Rust,
+ Python,
+ JavaScript,
+}
+
+#[completion(EntryLang)]
+fn complete_lang(_: &ShellContext) -> Suggest {
+ suggest_enum!(ProgrammingLanguages)
+}
+```
+
+---
+
+## 7. Error Handling
+
+Mingling doesn't use `?` operator propagation. Instead, errors are just **alternative results** that flow through the same chain/render pipeline. Create error types with `pack!` and route to them with `.to_render()`:
+
+```rust
+use mingling::macros::pack;
+use mingling::prelude::*;
+use std::io::Write;
+
+dispatcher!("hello", CMDHello => EntryHello);
+pack!(ResultName = String);
+pack!(ErrorNoNameProvided = ());
+pack!(ErrorNameTooLong = u16);
+
+#[chain]
+fn handle(args: EntryHello) -> Next {
+ let Some(name) = args.inner.first().cloned() else {
+ return ErrorNoNameProvided::default().to_render(); // ← early return to error renderer
+ };
+
+ if name.len() > 10 {
+ return ErrorNameTooLong::new(name.len() as u16).to_render();
+ }
+
+ ResultName::new(name).to_render() // ← success path
+}
+
+#[renderer]
+fn render_no_name(_: ErrorNoNameProvided) -> RenderResult {
+ let mut result = RenderResult::new();
+ writeln!(result, "No name provided").ok();
+ result
+}
+
+#[renderer]
+fn render_too_long(len: ErrorNameTooLong) -> RenderResult {
+ let mut result = RenderResult::new();
+ writeln!(result, "Name too long: {} > 10", *len).ok();
+ result
+}
+```
+
+Two built-in fallback types are always available:
+
+- `ErrorDispatcherNotFound` — rendered when no dispatcher matches the input
+- `ErrorRendererNotFound` — rendered when no renderer is found for a result type
+
+---
+
+## 8. Resource Injection
+
+Chain and renderer functions can accept **additional parameters** for the program's global state. Resources are singleton values registered with `program.with_resource(...)`.
+
+```rust
+// Features: ["parser", "extra_macros"]
+
+use std::path::PathBuf;
+
+dispatcher!("current", CMDCurrent => EntryCurrent);
+dispatcher!("cd", CMDCd => EntryCd);
+
+#[derive(Default, Clone)]
+struct ResCurrentDir {
+ current_dir: PathBuf,
+}
+
+fn main() {
+ let mut program = ThisProgram::new();
+ program.with_resource(ResCurrentDir {
+ current_dir: std::env::current_dir().unwrap(),
+ });
+ program.with_dispatcher(CMDCurrent);
+ program.with_dispatcher(CMDCd);
+ program.exec_and_exit();
+}
+
+// Read-only access (shared reference):
+#[chain]
+fn show_current(_prev: EntryCurrent, current_dir: &ResCurrentDir) -> Next {
+ println!("Current: {}", current_dir.current_dir.display());
+ empty_result!()
+}
+
+// Mutable access:
+#[chain]
+fn change_dir(prev: EntryCd, current_dir: &mut ResCurrentDir) -> Next {
+ let path: String = prev.pick(()).unpack();
+ current_dir.current_dir = current_dir.current_dir.join(path);
+ empty_result!()
+}
+```
+
+Resources can also be injected into `#[renderer]`:
+
+```rust
+use mingling::prelude::*;
+use std::io::Write;
+
+dispatcher!("current", CMDCurrent => EntryCurrent);
+
+#[derive(Default, Clone)]
+struct ResCurrentDir {
+ current_dir: std::path::PathBuf,
+}
+
+#[renderer]
+fn render_current(_: EntryCurrent, current_dir: &ResCurrentDir) -> RenderResult {
+ let mut result = RenderResult::new();
+ writeln!(result, "Current directory: {}", current_dir.current_dir.display()).ok();
+ result
+}
+```
+
+---
+
+## 9. Dispatch Tree — Compile-Time Command Trie
+
+As your program grows to dozens or hundreds of subcommands, linear dispatcher lookup becomes slow. Enable the `dispatch_tree` feature to convert the command structure into a **prefix tree (Trie)** at compile time.
+
+```rust
+// Features: ["dispatch_tree"]
+
+dispatcher!("cmd1", CMD1 => Entry1);
+dispatcher!("cmd2.sub1", CMD2Sub1 => Entry2Sub1);
+dispatcher!("cmd2.sub2", CMD2Sub2 => Entry2Sub2);
+dispatcher!("cmd3.sub1.leaf1", CMD3Sub1Leaf1 => Entry3Sub1Leaf1);
+dispatcher!("cmd3.sub1.leaf2", CMD3Sub1Leaf2 => Entry3Sub1Leaf2);
+// ... dozens more
+
+fn main() {
+ let program = ThisProgram::new();
+ // No more with_dispatcher calls — it's all compile-time!
+ program.exec_and_exit();
+}
+```
+
+With `dispatch_tree` enabled:
+
+- Dispatchers are auto-collected at compile time
+- `Program` no longer stores a dispatcher list
+- `program.with_dispatcher(...)` is not compiled
+- Lookup is **O(n)** where _n_ is input length, not number of commands
+
+---
+
+## 10. Clap Binding — Using Clap's Parser
+
+If you prefer clap's powerful argument parsing, use `#[dispatcher_clap]`. It generates a dispatcher from a `clap::Parser` struct.
+
+```rust
+// Features: ["clap"]
+// Dependencies:
+// clap = "4"
+
+use mingling::macros::dispatcher_clap;
+use mingling::prelude::*;
+use std::io::Write;
+
+#[derive(Default, clap::Parser, Grouped)]
+#[dispatcher_clap(
+ "greet", CMDGreet,
+ help = true, // auto-generate #[help] from clap
+ error = ErrorGreetParsed, // capture parse errors as a renderable type
+)]
+pub struct EntryGreet {
+ #[clap(default_value = "World")]
+ name: String,
+
+ #[arg(short, long, default_value_t = 1)]
+ repeat: i32,
+}
+
+#[renderer]
+fn render_greet(greet: EntryGreet) -> RenderResult {
+ let mut result = RenderResult::new();
+ write!(result, "Hello, ").ok();
+ for _ in 0..greet.repeat { write!(result, "{}", greet.name).ok(); }
+ writeln!(result, "!").ok();
+ result
+}
+
+#[renderer]
+fn render_parse_error(err: ErrorGreetParsed) -> RenderResult {
+ let mut result = RenderResult::new();
+ writeln!(result, "{}", *err).ok();
+ result
+}
+```
+
+You can control how clap help is displayed:
+
+```rust
+// Features: ["clap"]
+
+dispatcher!("greet", CMDGreet => EntryGreet);
+
+fn main() {
+ let mut program = ThisProgram::new();
+ program.with_dispatcher(CMDGreet);
+ program.stdout_setting.clap_help_print_behaviour =
+ mingling::ClapHelpPrintBehaviour::WriteToRenderResult;
+ // or: PrintDirectly — writes clap help straight to stdout
+ program.exec_and_exit();
+}
+```
+
+---
+
+## 11. REPL Mode
+
+With the `repl` feature, turn your CLI into an interactive shell with one method call:
+
+```rust
+// Features: ["repl"]
+
+fn main() {
+ ThisProgram::new().exec_repl();
+}
+```
+
+Mingling provides built-in REPL setups:
+
+```rust
+// Features: ["repl", "extra_macros"]
+
+use mingling::{
+ res::ResREPL,
+ setup::{BasicREPLReadlineSetup, BasicREPLOutputSetup, BasicREPLPromptSetup},
+};
+
+dispatcher!("cd", CMDCd => EntryCd);
+dispatcher!("exit", CMDExit => EntryExit);
+
+fn main() {
+ let mut program = ThisProgram::new();
+
+ program.with_dispatcher(CMDCd);
+ program.with_dispatcher(CMDExit);
+
+ // Enable line reading from stdin
+ program.with_setup(BasicREPLReadlineSetup);
+
+ // Enable output flushing after each render
+ program.with_setup(BasicREPLOutputSetup);
+
+ // Custom prompt
+ program.with_setup(BasicREPLPromptSetup::func(|| "> ".to_string()));
+
+ program.exec_repl(); // ← interactive loop
+}
+
+// Exit the REPL via the ResREPL resource:
+#[chain]
+fn handle_exit(_prev: EntryExit, repl: &mut ResREPL) {
+ repl.exit = true;
+}
+```
+
+---
+
+## 12. Hooks — Observing the Pipeline
+
+Mingling provides a `ProgramHook` system for observing every stage of the execution pipeline. Useful for debugging, logging, or telemetry.
+
+```rust
+use mingling::{
+ hook::{ProgramControlUnit, ProgramHook},
+};
+
+dispatcher!("greet", CMDGreet => EntryGreet);
+
+fn main() {
+ let mut program = ThisProgram::new();
+
+ program.with_hook(
+ ProgramHook::<ThisProgram>::empty()
+ .on_begin::<_, ()>(|_| println!("[DEBUG] Program is begin"))
+ .on_pre_dispatch(|info| println!("[DEBUG] Pre dispatch: {}", info.arguments.join(" ")))
+ .on_post_dispatch(|info| println!("[DEBUG] Post dispatch: {}", info.entry))
+ .on_pre_chain(|info| {
+ println!("[DEBUG] Pre chain: {}", info.input);
+ })
+ .on_post_chain(|info| println!("[DEBUG] Post chain: {}", info.output.member_id))
+ .on_finish(|_| {
+ println!("[DEBUG] Loop end");
+ ProgramControlUnit::OverrideExitCode(0) // Override exit code
+ })
+ .on_pre_render(|info| println!("[DEBUG] Pre render: {}", info.input))
+ .on_post_render(|_| println!("[DEBUG] Post render")),
+ );
+
+ program.with_dispatcher(CMDGreet);
+ program.exec_and_exit();
+}
+```
+
+---
+
+## 13. Structural Renderer — Structured Output (JSON/YAML)
+
+With the `structural_renderer` feature, users can add `--json` or `--yaml` flags to get structured output instead of human-readable text.
+
+```rust
+// Features: ["structural_renderer", "parser"]
+// Dependencies:
+// serde = "1"
+
+use mingling::{prelude::*, setup::StructuralRendererSetup};
+use mingling::Grouped;
+use mingling::StructuralData;
+use serde::Serialize;
+use std::io::Write;
+
+dispatcher!("render", CMDRender => EntryRender);
+
+#[derive(Default, StructuralData, Serialize, Grouped)]
+struct ResultInfo {
+ name: String,
+ age: i32,
+}
+
+#[chain]
+fn render_info(args: EntryRender) -> Next {
+ let (name, age) = args.pick::<String>(()).pick::<i32>(()).unpack();
+ ResultInfo { name, age }.to_chain()
+}
+
+#[renderer]
+fn render_info_result(info: ResultInfo) -> RenderResult {
+ let mut result = RenderResult::new();
+ writeln!(result, "{} is {} years old", info.name, info.age).ok();
+ result
+}
+
+fn main() {
+ let mut program = ThisProgram::new();
+ program.with_setup(StructuralRendererSetup); // enables --json / --yaml
+ program.with_dispatcher(CMDRender);
+ let _ = program.exec();
+}
+```
+
+Then users can do:
+
+```bash
+$ myapp render Bob 22
+Bob is 22 years old
+
+$ myapp render Bob 22 --json
+{"name":"Bob","age":22}
+
+$ myapp render Bob 22 --yaml
+name: Bob
+age: 22
+```
+
+---
+
+## 14. Async Support
+
+Enable the `async` feature to use `async fn` inside `#[chain]`:
+
+```rust
+// Features: ["async", "parser"]
+// Dependencies:
+// tokio = { version = "1", features = ["full"] }
+
+use std::io::Write;
+use std::time::Duration;
+
+dispatcher!("download", CMDDownload => EntryDownload);
+pack!(ResultDownloaded = String);
+
+#[chain]
+pub async fn handle_download(args: EntryDownload) -> Next {
+ let file = args.pick(()).unpack();
+ download_file(file).await.into()
+}
+
+async fn download_file(name: String) -> ResultDownloaded {
+ tokio::time::sleep(Duration::from_secs(1)).await;
+ ResultDownloaded::new(name)
+}
+
+#[renderer]
+fn render_downloaded(result: ResultDownloaded) -> RenderResult {
+ let mut r = RenderResult::new();
+ writeln!(r, "\"{}\" downloaded.", *result).ok();
+ r
+}
+```
+
+> [!NOTE]
+>
+> `#[renderer]` functions cannot be async. When `async` is enabled, `program.exec_and_exit().await` returns a Future.
+
+---
+
+## 15. Wrapping Up — `gen_program!()`
+
+At the very end of your crate root (main.rs / lib.rs), call `gen_program!()` to generate the `ThisProgram` struct, the `Next` type alias, and all internal plumbing.
+
+```rust
+use mingling::macros::gen_program;
+
+gen_program!();
+```
+
+It must be placed **after** all your `dispatcher!`, `pack!`, `#[chain]`, `#[renderer]`, and `#[help]` declarations.
+
+---
+
+## Putting It All Together
+
+Here's a complete, runnable program:
+
+```rust
+use mingling::macros::pack;
+use mingling::prelude::*;
+use std::io::Write;
+
+dispatcher!("greet", CMDGreet => EntryGreet);
+
+fn main() {
+ let mut program = ThisProgram::new();
+ program.with_dispatcher(CMDGreet);
+ program.exec_and_exit();
+}
+
+pack!(ResultGreeting = String);
+
+#[chain]
+fn handle_greet(args: EntryGreet) -> Next {
+ let greeting = args
+ .inner
+ .first()
+ .cloned()
+ .unwrap_or_else(|| "World".to_string());
+ ResultGreeting::new(greeting).into()
+}
+
+#[renderer]
+fn render_greeting(greeting: ResultGreeting) -> RenderResult {
+ let mut result = RenderResult::new();
+ writeln!(result, "Hello, {}!", *greeting).ok();
+ result
+}
+
+gen_program!();
+```
+
+```bash
+$ myapp greet
+Hello, World!
+
+$ myapp greet Alice
+Hello, Alice!
+```
diff --git a/README.md b/README.md
index 50c66c6..e24fb7e 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
<p align="center">
<a href="https://github.com/mingling-rs/mingling">
- <img alt="Mingling" src="https://github.com/mingling-rs/mingling/raw/main/docs/res/icon2.png" width="50%">
+ <img alt="Mingling" src="https://github.com/mingling-rs/mingling/raw/main/docs/res/icon2.png" width="30%">
</a>
</p>
<h1 align="center">Mìng Lìng - 命令</h1>
@@ -21,852 +21,100 @@
<img src="https://img.shields.io/github/actions/workflow/status/mingling-rs/mingling/ci.yml">
</p>
-> [!WARNING]
->
-> **Note**: Mingling is still under active development, and its API may change. Feel free to try it out and give us feedback!
-> **Hint**: This note will be removed in version `0.5.0`
+## What is Mingling?
-<h1 align="center">
- What is Mingling?
-</h1>
+[`Mingling`](https://github.com/mingling-rs/mingling) is a **state-driven and data-driven** CLI workflow orchestration framework built in Rust.
-[`Mingling`](https://github.com/mingling-rs/mingling) is a **proc-macro and type-system based** Rust CLI framework, suitable for developing complex command-line programs with numerous subcommands.
+💡 Its name comes from the Chinese pinyin **"Mìng Lìng"**, which means **"command"**.
-> Its name comes from the Chinese Pinyin **"Mìng Lìng"**, meaning **"Command"**.
+## WARNING
-### Mingling's Core Capabilities
+Mingling is currently usable at a basic level, but it is still under active development, so many APIs are not yet mature. Any changes to the public API will be documented in detail in the [Changelog](https://github.com/mingling-rs/mingling/blob/main/CHANGELOG.md).
-1. **Separation of Concerns, Clear Logic**: Mingling decouples logic by responsibility, helping you organize your CLI program more clearly.
- See example: [Example](https://github.com/mingling-rs/mingling/blob/main/examples/example-basic/src/main.rs)
-2. **"All Logic is Functions"**: Execution logic, rendering logic, completion logic, help logic — everything is a function. Just attach the corresponding attribute macro to bind them to your program.
-3. **Fully Dynamic Completion System**: With the `comp` feature, you can flexibly implement dynamic completion logic for any subcommand.
- See examples: [Example](https://github.com/mingling-rs/mingling/blob/main/examples/example-completion/src/main.rs)
-4. **Lightning-Fast Subcommand Dispatch**: With the `dispatch_tree` feature, Mingling hardens the subcommand structure into a prefix tree at **compile time**, enabling blazing-fast subcommand lookup.
- See examples: [Example](https://github.com/mingling-rs/mingling/blob/main/examples/example-dispatch-tree/src/main.rs)
-5. **Lightweight Dependencies, On-Demand Importing**: Minimal core dependencies keep builds fast; enhanced features are imported on demand through fine-grained feature flags.
-6. **Structured Output**: Enabling the `structural_renderer` feature adds support for flags like `--json` and `--yaml`, providing structured output capabilities.
- See examples: [Example](https://github.com/mingling-rs/mingling/blob/main/examples/example-structural-renderer/src/main.rs)
+Additionally, the project is currently developed by me alone ([Weicao-CatilGrass](https://github.com/Weicao-CatilGrass)). If you are interested in this project, I warmly welcome your [contributions](https://github.com/mingling-rs/mingling/blob/main/CONTRIBUTING.md). You can reach me directly via [Github Issue](https://github.com/mingling-rs/mingling/issues).
----
+## About Mingling's Design
-**💡 To learn more, check out the following links:**
-
-- 📖 [Mainpage](https://mingling-rs.github.io/mingling/)
-- 📖 [Examples](https://mingling-rs.github.io/mingling/docs/examples.html)
-- 📖 [docs.rs](https://docs.rs/mingling/latest/mingling/)
-- 📖 [Helpdoc](https://mingling-rs.github.io/mingling/docs/doc.html#/) Or [帮助文档](https://mingling-rs.github.io/mingling/docs/_zh_CN/index.html#/)
-- 🔍 [Devdoc](https://mingling-rs.github.io/mingling/docs/dev/)
-
-<h1 align="center">
- Getting Started
-</h1>
-
-Add Mingling to your `Cargo.toml`:
-
-```toml
-[dependencies.mingling]
-version = "0.3.0"
-features = []
-```
-
-Or use the github version
-
-```toml
-[dependencies.mingling]
-git = "https://github.com/mingling-rs/mingling.git"
-tag = "unreleased"
-features = []
-```
-
-Or use the [template project](https://github.com/mingling-rs/mingling-template):
-
-```bash
-cargo generate --git mingling-rs/mingling-template
-```
-
-<h1 align="center">
- Writing with Mingling
-</h1>
-
-### The Big Picture
-
-Mingling organizes your CLI program into three distinct phases:
-
-```
-User Input → [Dispatcher] → Entry → [Chain(s)] → Result → [Renderer] → Output
-```
-
-**Step1: Input** — The user's raw arguments flow in.
-**Step2: Dispatch** — A **Dispatcher** picks them up and wraps them into an **Entry** type.
-**Step3: Chain** — The entry is handed off to a **Chain** function, which processes it.
-**Step4: Render** — A **Renderer** takes that result and writes it to the terminal.
-
-> [!NOTE]
-> A Chain can produce a **State** type to be passed to the next Chain for further processing,
->
-> or it can produce a **Result** type to be handed off to a Renderer.
-
-Everything in this pipeline is a **plain Rust function** with an attribute macro on top.
-
-You never need to manually implement traits or construct boilerplate.
-
----
-
-### 1. Defining Commands — `dispatcher!`
-
-The entry point for every subcommand is the `dispatcher!` macro. It generates two structs for you: a **Dispatcher** (used to register the command with the program) and an **Entry** (a wrapper around `Vec<String>` that holds the raw arguments).
-
-```rust
-use mingling::prelude::*;
-
-// "command.name" Dispatcher EntryType
-// │ │ │
-dispatcher!("greet", CMDGreet => EntryGreet);
-
-// Nested subcommand: `remote add`
-dispatcher!("remote.add", CMDRemoteAdd => EntryRemoteAdd);
-```
-
-Then in `main()`, register the dispatcher with the program:
-
-```rust
-dispatcher!("greet", CMDGreet => EntryGreet);
-
-fn main() {
- let mut program = ThisProgram::new();
- program.with_dispatcher(CMDGreet);
- program.exec_and_exit();
-}
-```
-
-Mingling also supports an abbreviated form (with the `extra_macros` feature):
-
-```rust
-// Features: ["extra_macros"]
-
-// Auto-generates CMDGreet / EntryGreet from "greet"
-dispatcher!("greet");
-```
-
----
-
-### 2. The Chain — "#[chain]" — Where Logic Lives
-
-The `#[chain]` attribute turns a plain function into an execution step. Think of it as "the logic that transforms one typed value into another."
-
-```rust
-dispatcher!("greet", CMDGreet => EntryGreet);
-
-pack!(ResultGreeting = String);
-
-#[chain]
-fn handle_greet(args: EntryGreet) -> Next {
- let greeting = args
- .inner
- .first()
- .cloned()
- .unwrap_or_else(|| "World".to_string());
- ResultGreeting::new(greeting)
-}
-```
-
-Key points:
-
-- The return type is `Next` — a type alias for `ChainProcess<ThisProgram>`.
-- You chain results by calling `.to_chain()` on any `pack!`-ed type.
-- You can have **multiple chain functions** for the same command, each transforming the data further.
-- With the `async` feature, chain functions can be `async fn`.
-
----
-
-### 3. The Renderer — "#[renderer]" — How Output Works
-
-The `#[renderer]` attribute turns a function into an output handler. It receives the final result of a chain and returns a `RenderResult`.
-
-```rust
-use mingling::macros::pack;
-use mingling::prelude::*;
-use std::io::Write;
-
-pack!(ResultGreeting = String);
-
-#[renderer]
-fn render_greeting(greeting: ResultGreeting) -> RenderResult {
- let mut result = RenderResult::new();
- writeln!(result, "Hello, {}!", *greeting).ok();
- result
-}
-```
-
-Inside a renderer, create a `RenderResult`, write to it using `write!` / `writeln!` (from [`std::io::Write`](https://doc.rust-lang.org/std/io/trait.Write.html)), and return it. The output is captured in the buffer and flushed by the framework at the end of the pipeline.
-
-You can write renderers for **any type** in your program, including error types:
-
-```rust
-use mingling::prelude::*;
-use std::io::Write;
-
-#[renderer]
-fn render_dispatcher_not_found(err: ErrorDispatcherNotFound) -> RenderResult {
- let mut result = RenderResult::new();
- writeln!(result, "Command not found: [{}]", err.join(" ")).ok();
- result
-}
-```
-
----
-
-### 4. Parsing Arguments — The Picker
-
-Mingling provides a **Picker** for zero-cost argument extraction. You use `pick()` or `pick_or()` on an entry to extract typed values, then `unpack()` to get the final tuple.
-
-```rust
-// Features: ["parser"]
-
-use mingling::parser::Picker;
-
-dispatcher!("greet", CMDGreet => EntryGreet);
-pack!(ResultGreeting = String);
-
-#[chain]
-fn handle_greet(args: EntryGreet) -> Next {
- let (name, count) = Picker::new(args.inner)
- .pick::<String>(()) // positional: first string
- .pick_or::<u8>(["-r", "--repeat"], 1) // optional flag with default
- .unpack();
- ResultGreeting::new(format!("{} x{}", name, count))
-}
-```
-
-With the `parser` feature, the `AsPicker` trait provides a shorthand directly on entries:
-
-```rust
-// Features: ["parser"]
-
-dispatcher!("greet", CMDGreet => EntryGreet);
-pack!(ResultGreeting = String);
-
-#[chain]
-fn handle(args: EntryGreet) -> Next {
- let (name, count) = args
- .pick::<Option<String>>(())
- .pick_or::<u8>(["-r", "--repeat"], 1)
- .unpack();
- ResultGreeting::new(format!("{} x{}", name.unwrap_or_default(), count))
-}
-```
-
-For enums, derive `EnumTag` and implement `PickableEnum` to parse enum variants from strings:
+Mingling abstracts the behavior of a program's lifecycle into three phases: **Dispatch**, **Execution**, and **Rendering**. Each phase is connected by types — the output of the current phase becomes the input of the next phase. For example:
```rust
-// Features: ["parser", "extra_macros"]
-
-use mingling::{EnumTag, Groupped};
-use mingling::parser::PickableEnum;
-
-dispatcher!("lang.select", CMDLang => EntryLang);
-
-#[derive(Debug, Default, EnumTag, Groupped)]
-pub enum Language {
- #[default]
- Rust,
- #[enum_rename("C++")]
- CPlusPlus,
-}
-
-impl PickableEnum for Language {}
-
-#[chain]
-fn handle(args: EntryLang) -> Next {
- let lang: Language = args.pick(()).unpack();
- lang
-}
-```
-
----
-
-### 5. The Help System — "#[help]"
-
-Help is just another attribute macro. When the user passes `--help` or `-h`, the program skips the normal chain/render pipeline and routes directly to your `#[help]` function.
-
-Enable it by adding `BasicProgramSetup`:
-
-```rust
-use mingling::{macros::help, prelude::*, setup::BasicProgramSetup};
-use std::io::Write;
-
-dispatcher!("greet", CMDGreet => EntryGreet);
-
-#[help]
-fn help_greet(_prev: EntryGreet) -> RenderResult {
- let mut result = RenderResult::new();
- writeln!(result, "Usage: greet <NAME>").ok();
- writeln!(result, "Greets the user with the given name.").ok();
- result
-}
-
-fn main() {
- let mut program = ThisProgram::new();
- program.with_setup(BasicProgramSetup); // enables --help / -h
- program.with_dispatcher(CMDGreet);
- program.exec_and_exit();
-}
-
-gen_program!();
-```
-
-The flow is:
-
-- User types `greet --help`
-- `BasicProgramSetup` sets `program.user_context.help = true`
-- The dispatcher sees this flag and routes to the `#[help]` function instead of the `#[chain]`
-
----
-
-### 6. Completion — "#[completion]" — Dynamic Shell Completions
-
-With the `comp` feature, Mingling provides a fully dynamic completion system. You write a function that returns `Suggest` based on the current shell context, and Mingling generates the completion scripts for bash, zsh, fish, and pwsh.
-
-```rust
-// Features: ["comp", "extra_macros"]
-
-use mingling::{macros::suggest, ShellContext, Suggest};
-
-dispatcher!("greet", CMDGreet => EntryGreet);
-pack!(ResultName = (u8, String));
-
-#[completion(EntryGreet)]
-fn complete_greet(ctx: &ShellContext) -> Suggest {
- // Suggest positional arguments
- if ctx.previous_word == "greet" {
- return suggest! {
- "Alice": "Likes to receive messages",
- "Bob": "Likes to pass messages",
- "World"
- };
- }
-
- // Suggest flag arguments
- if ctx.typing_argument() {
- return suggest! {
- "-r": "Number of repetitions",
- "--repeat": "Number of repetitions",
- }
- .strip_typed_argument(ctx);
- }
-
- suggest!() // no suggestions
-}
-```
-
-You also need to register the built-in completion dispatcher:
-
-```rust
-// Features: ["comp"]
-
-fn main() {
- let mut program = ThisProgram::new();
- program.with_dispatcher(crate::CMDCompletion);
- program.exec_and_exit();
-}
-```
-
-In your `build.rs`, generate the shell scripts:
-
-```rust
-// BUILD TIME
-// Features: ["comp", "builds"]
-mingling::build::build_comp_scripts(env!("CARGO_PKG_NAME")).unwrap();
-```
-
-For enum-based completions, use `suggest_enum!`:
-
-```rust
-// Features: ["comp", "extra_macros"]
-
-use mingling::{ShellContext, Suggest};
-use mingling::macros::suggest_enum;
-use mingling::EnumTag;
-
-dispatcher!("lang.select", CMDLang => EntryLang);
-
-#[derive(EnumTag)]
-pub enum ProgrammingLanguages {
- Rust,
- Python,
- JavaScript,
-}
-
-#[completion(EntryLang)]
-fn complete_lang(_: &ShellContext) -> Suggest {
- suggest_enum!(ProgrammingLanguages)
-}
-```
-
----
-
-### 7. Error Handling
-
-Mingling doesn't use `?` operator propagation. Instead, errors are just **alternative results** that flow through the same chain/render pipeline. Create error types with `pack!` and route to them with `.to_render()`:
-
-```rust
-use mingling::macros::pack;
-use mingling::prelude::*;
-use std::io::Write;
-
-dispatcher!("hello", CMDHello => EntryHello);
-pack!(ResultName = String);
-pack!(ErrorNoNameProvided = ());
-pack!(ErrorNameTooLong = u16);
-
-#[chain]
-fn handle(args: EntryHello) -> Next {
- let Some(name) = args.inner.first().cloned() else {
- return ErrorNoNameProvided::default().to_render(); // ← early return to error renderer
- };
-
- if name.len() > 10 {
- return ErrorNameTooLong::new(name.len() as u16).to_render();
- }
-
- ResultName::new(name).to_render() // ← success path
-}
-
-#[renderer]
-fn render_no_name(_: ErrorNoNameProvided) -> RenderResult {
- let mut result = RenderResult::new();
- writeln!(result, "No name provided").ok();
- result
-}
-
-#[renderer]
-fn render_too_long(len: ErrorNameTooLong) -> RenderResult {
- let mut result = RenderResult::new();
- writeln!(result, "Name too long: {} > 10", *len).ok();
- result
-}
-```
-
-Two built-in fallback types are always available:
-
-- `ErrorDispatcherNotFound` — rendered when no dispatcher matches the input
-- `ErrorRendererNotFound` — rendered when no renderer is found for a result type
-
----
-
-### 8. Resource Injection
-
-Chain and renderer functions can accept **additional parameters** for the program's global state. Resources are singleton values registered with `program.with_resource(...)`.
-
-```rust
-// Features: ["parser", "extra_macros"]
-
-use std::path::PathBuf;
-
dispatcher!("current", CMDCurrent => EntryCurrent);
-dispatcher!("cd", CMDCd => EntryCd);
+pack!(StateNext = ());
-#[derive(Default, Clone)]
-struct ResCurrentDir {
- current_dir: PathBuf,
-}
-
-fn main() {
- let mut program = ThisProgram::new();
- program.with_resource(ResCurrentDir {
- current_dir: std::env::current_dir().unwrap(),
- });
- program.with_dispatcher(CMDCurrent);
- program.with_dispatcher(CMDCd);
- program.exec_and_exit();
-}
-
-// Read-only access (shared reference):
#[chain]
-fn show_current(_prev: EntryCurrent, current_dir: &ResCurrentDir) -> Next {
- println!("Current: {}", current_dir.current_dir.display());
- empty_result!()
-}
-
-// Mutable access:
-#[chain]
-fn change_dir(prev: EntryCd, current_dir: &mut ResCurrentDir) -> Next {
- let path: String = prev.pick(()).unpack();
- current_dir.current_dir = current_dir.current_dir.join(path);
- empty_result!()
+fn handle_current(_: EntryCurrent) -> StateNext {
+ // 1. The first phase outputs the StateNext value
+ StateNext::default() // ^^^^^^^^^
+} // |
+ // |
+ // 2. The second phase takes StateNext as input
+#[chain] // |
+fn handle_state_next(_: StateNext) {
+ todo!()
}
```
-Resources can also be injected into `#[renderer]`:
+See? `handle_current` and `handle_state_next` have no direct connection!
-```rust
-use mingling::prelude::*;
-use std::io::Write;
-
-dispatcher!("current", CMDCurrent => EntryCurrent);
-
-#[derive(Default, Clone)]
-struct ResCurrentDir {
- current_dir: std::path::PathBuf,
-}
-
-#[renderer]
-fn render_current(_: EntryCurrent, current_dir: &ResCurrentDir) -> RenderResult {
- let mut result = RenderResult::new();
- writeln!(result, "Current directory: {}", current_dir.current_dir.display()).ok();
- result
-}
-```
-
----
-
-### 9. Dispatch Tree — Compile-Time Command Trie
-
-As your program grows to dozens or hundreds of subcommands, linear dispatcher lookup becomes slow. Enable the `dispatch_tree` feature to convert the command structure into a **prefix tree (Trie)** at compile time.
-
-```rust
-// Features: ["dispatch_tree"]
-
-dispatcher!("cmd1", CMD1 => Entry1);
-dispatcher!("cmd2.sub1", CMD2Sub1 => Entry2Sub1);
-dispatcher!("cmd2.sub2", CMD2Sub2 => Entry2Sub2);
-dispatcher!("cmd3.sub1.leaf1", CMD3Sub1Leaf1 => Entry3Sub1Leaf1);
-dispatcher!("cmd3.sub1.leaf2", CMD3Sub1Leaf2 => Entry3Sub1Leaf2);
-// ... dozens more
-
-fn main() {
- let program = ThisProgram::new();
- // No more with_dispatcher calls — it's all compile-time!
- program.exec_and_exit();
-}
-```
-
-With `dispatch_tree` enabled:
-
-- Dispatchers are auto-collected at compile time
-- `Program` no longer stores a dispatcher list
-- `program.with_dispatcher(...)` is not compiled
-- Lookup is **O(n)** where _n_ is input length, not number of commands
-
----
-
-### 10. Clap Binding — Using Clap's Parser
-
-If you prefer clap's powerful argument parsing, use `#[dispatcher_clap]`. It generates a dispatcher from a `clap::Parser` struct.
-
-```rust
-// Features: ["clap"]
-// Dependencies:
-// clap = "4"
-
-use mingling::macros::dispatcher_clap;
-use mingling::prelude::*;
-use std::io::Write;
-
-#[derive(Default, clap::Parser, Groupped)]
-#[dispatcher_clap(
- "greet", CMDGreet,
- help = true, // auto-generate #[help] from clap
- error = ErrorGreetParsed, // capture parse errors as a renderable type
-)]
-pub struct EntryGreet {
- #[clap(default_value = "World")]
- name: String,
-
- #[arg(short, long, default_value_t = 1)]
- repeat: i32,
-}
-
-#[renderer]
-fn render_greet(greet: EntryGreet) -> RenderResult {
- let mut result = RenderResult::new();
- write!(result, "Hello, ").ok();
- for _ in 0..greet.repeat { write!(result, "{}", greet.name).ok(); }
- writeln!(result, "!").ok();
- result
-}
-
-#[renderer]
-fn render_parse_error(err: ErrorGreetParsed) -> RenderResult {
- let mut result = RenderResult::new();
- writeln!(result, "{}", *err).ok();
- result
-}
-```
-
-You can control how clap help is displayed:
-
-```rust
-// Features: ["clap"]
-
-dispatcher!("greet", CMDGreet => EntryGreet);
-
-fn main() {
- let mut program = ThisProgram::new();
- program.with_dispatcher(CMDGreet);
- program.stdout_setting.clap_help_print_behaviour =
- mingling::ClapHelpPrintBehaviour::WriteToRenderResult;
- // or: PrintDirectly — writes clap help straight to stdout
- program.exec_and_exit();
-}
-```
-
----
-
-### 11. REPL Mode
-
-With the `repl` feature, turn your CLI into an interactive shell with one method call:
-
-```rust
-// Features: ["repl"]
-
-fn main() {
- ThisProgram::new().exec_repl();
-}
-```
+They are bridged by `StateNext` and automatically linked by the framework.
-Mingling provides built-in REPL setups:
+You can use this approach to separate computation from result rendering, like this:
```rust
-// Features: ["repl", "extra_macros"]
+// Features: ["picker"]
+dispatcher!("calc", CMDCalculate => EntryCalculate);
+pack!(StateSumNumbers = Vec<i32>);
+pack!(ResultNumber = i32);
-use mingling::{
- res::ResREPL,
- setup::{BasicREPLReadlineSetup, BasicREPLOutputSetup, BasicREPLPromptSetup},
-};
-
-dispatcher!("cd", CMDCd => EntryCd);
-dispatcher!("exit", CMDExit => EntryExit);
-
-fn main() {
- let mut program = ThisProgram::new();
-
- program.with_dispatcher(CMDCd);
- program.with_dispatcher(CMDExit);
-
- // Enable line reading from stdin
- program.with_setup(BasicREPLReadlineSetup);
-
- // Enable output flushing after each render
- program.with_setup(BasicREPLOutputSetup);
-
- // Custom prompt
- program.with_setup(BasicREPLPromptSetup::func(|| "> ".to_string()));
-
- program.exec_repl(); // ← interactive loop
-}
-
-// Exit the REPL via the ResREPL resource:
+// Entry: parse arguments and pass state to the calculation step
#[chain]
-fn handle_exit(_prev: EntryExit, repl: &mut ResREPL) {
- repl.exit = true;
-}
-```
-
----
-
-### 12. Hooks — Observing the Pipeline
-
-Mingling provides a `ProgramHook` system for observing every stage of the execution pipeline. Useful for debugging, logging, or telemetry.
-
-```rust
-use mingling::{
- hook::{ProgramControlUnit, ProgramHook},
-};
-
-dispatcher!("greet", CMDGreet => EntryGreet);
-
-fn main() {
- let mut program = ThisProgram::new();
-
- program.with_hook(
- ProgramHook::<ThisProgram>::empty()
- .on_begin::<_, ()>(|_| println!("[DEBUG] Program is begin"))
- .on_pre_dispatch(|info| println!("[DEBUG] Pre dispatch: {}", info.arguments.join(" ")))
- .on_post_dispatch(|info| println!("[DEBUG] Post dispatch: {}", info.entry))
- .on_pre_chain(|info| {
- println!("[DEBUG] Pre chain: {}", info.input);
- })
- .on_post_chain(|info| println!("[DEBUG] Post chain: {}", info.output.member_id))
- .on_finish(|_| {
- println!("[DEBUG] Loop end");
- ProgramControlUnit::OverrideExitCode(0) // Override exit code
- })
- .on_pre_render(|info| println!("[DEBUG] Pre render: {}", info.input))
- .on_post_render(|_| println!("[DEBUG] Post render")),
- );
-
- program.with_dispatcher(CMDGreet);
- program.exec_and_exit();
-}
-```
-
----
-
-### 13. Structural Renderer — Structured Output (JSON/YAML)
-
-With the `structural_renderer` feature, users can add `--json` or `--yaml` flags to get structured output instead of human-readable text.
-
-```rust
-// Features: ["structural_renderer", "parser"]
-// Dependencies:
-// serde = "1"
-
-use mingling::{prelude::*, setup::StructuralRendererSetup};
-use mingling::Groupped;
-use mingling::StructuralData;
-use serde::Serialize;
-use std::io::Write;
-
-dispatcher!("render", CMDRender => EntryRender);
-
-#[derive(Default, StructuralData, Serialize, Groupped)]
-struct ResultInfo {
- name: String,
- age: i32,
+fn handle_calc(args: EntryCalculate) -> StateSumNumbers {
+ let numbers = args.pick(&arg![Vec<i32>]).unwrap();
+ StateSumNumbers::new(numbers)
}
+// Calculate: pass the result to the rendering step
#[chain]
-fn render_info(args: EntryRender) -> Next {
- let (name, age) = args.pick::<String>(()).pick::<i32>(()).unpack();
- ResultInfo { name, age }.to_chain()
+fn handle_state_sum_numbers(sum: StateSumNumbers) -> ResultNumber {
+ let numbers = sum.inner;
+ let total: i32 = numbers.iter().sum();
+ ResultNumber::new(total)
}
+// Renderer: return the render result and let the framework handle output
#[renderer]
-fn render_info_result(info: ResultInfo) -> RenderResult {
+fn render_number(number: ResultNumber) -> RenderResult {
let mut result = RenderResult::new();
- writeln!(result, "{} is {} years old", info.name, info.age).ok();
+ writeln!(result, "Number: {}", *number).ok();
result
}
-
-fn main() {
- let mut program = ThisProgram::new();
- program.with_setup(StructuralRendererSetup); // enables --json / --yaml
- program.with_dispatcher(CMDRender);
- let _ = program.exec();
-}
```
-Then users can do:
+Although this may make your program slightly more verbose, each step is a **pure function**, making it extremely easy to test!
-```bash
-$ myapp render Bob 22
-Bob is 22 years old
+## Getting Started
-$ myapp render Bob 22 --json
-{"name":"Bob","age":22}
+Add Mingling to your `Cargo.toml`:
-$ myapp render Bob 22 --yaml
-name: Bob
-age: 22
+```toml
+[dependencies.mingling]
+version = "0.3.0"
+features = []
```
----
-
-### 14. Async Support
-
-Enable the `async` feature to use `async fn` inside `#[chain]`:
-
-```rust
-// Features: ["async", "parser"]
-// Dependencies:
-// tokio = { version = "1", features = ["full"] }
-
-use std::io::Write;
-use std::time::Duration;
-
-dispatcher!("download", CMDDownload => EntryDownload);
-pack!(ResultDownloaded = String);
-
-#[chain]
-pub async fn handle_download(args: EntryDownload) -> Next {
- let file = args.pick(()).unpack();
- download_file(file).await
-}
-
-async fn download_file(name: String) -> ResultDownloaded {
- tokio::time::sleep(Duration::from_secs(1)).await;
- ResultDownloaded::new(name)
-}
+Or use the github version
-#[renderer]
-fn render_downloaded(result: ResultDownloaded) -> RenderResult {
- let mut r = RenderResult::new();
- writeln!(r, "\"{}\" downloaded.", *result).ok();
- r
-}
+```toml
+[dependencies.mingling]
+git = "https://github.com/mingling-rs/mingling.git"
+tag = "unreleased"
+features = []
```
> [!NOTE]
->
-> `#[renderer]` functions cannot be async. When `async` is enabled, `program.exec_and_exit().await` returns a Future.
-
----
-
-### 15. Wrapping Up — `gen_program!()`
-
-At the very end of your crate root (main.rs / lib.rs), call `gen_program!()` to generate the `ThisProgram` struct, the `Next` type alias, and all internal plumbing.
-
-```rust
-use mingling::macros::gen_program;
-
-gen_program!();
-```
-
-It must be placed **after** all your `dispatcher!`, `pack!`, `#[chain]`, `#[renderer]`, and `#[help]` declarations.
-
----
-
-### Putting It All Together
-
-Here's a complete, runnable program:
-
-```rust
-use mingling::macros::pack;
-use mingling::prelude::*;
-use std::io::Write;
+> To learn more, check out [Writing with Mingling](https://github.com/mingling-rs/mingling/blob/main/GETTING_STARTED.md)
-dispatcher!("greet", CMDGreet => EntryGreet);
-
-fn main() {
- let mut program = ThisProgram::new();
- program.with_dispatcher(CMDGreet);
- program.exec_and_exit();
-}
-
-pack!(ResultGreeting = String);
-
-#[chain]
-fn handle_greet(args: EntryGreet) -> Next {
- let greeting = args
- .inner
- .first()
- .cloned()
- .unwrap_or_else(|| "World".to_string());
- ResultGreeting::new(greeting)
-}
-
-#[renderer]
-fn render_greeting(greeting: ResultGreeting) -> RenderResult {
- let mut result = RenderResult::new();
- writeln!(result, "Hello, {}!", *greeting).ok();
- result
-}
-
-gen_program!();
-```
-
-```bash
-$ myapp greet
-Hello, World!
-
-$ myapp greet Alice
-Hello, Alice!
-```
-
-<h1 align="center">
- 🗺️ Roadmap 🗺️
-</h1>
+## Roadmap
- [x] Milestone.1 "MVP" 🎉
- [x] [[0.1.4](https://docs.rs/mingling/0.1.4/mingling/)] [`core`] [`structural_renderer`] **Mingling** can render data into serializable formats via `--json` and `--yaml` flags
@@ -880,19 +128,17 @@ Hello, Alice!
- [x] [[0.1.9](https://docs.rs/mingling/0.1.9/mingling/)] [`core`] [`repl`] Provides REPL capability (`program.exec_repl();`)
- [x] [[0.2.0](https://docs.rs/mingling/0.2.0/mingling/)] Complete documentation, tests, and examples
- [ ] Milestone.2 "More Comfortable Dev and User Experience"
- - [ ] [`mling` / `mingling-cli`]
- - [ ] **Mingling** Linter
- - [ ] **Mingling** Project Generator
- - [ ] **Mingling** Program Installer & Manager (For development)
- - [ ] Helpdoc Editor
- - [ ] [`picker`] A more efficient and intelligent argument parser
- - [x] [`macros`] Remove `r_print!` / `r_println!` macros
+ - [ ] [`mling` / `mingling-cli`]
+ - [ ] **Mingling** Linter
+ - [ ] **Mingling** Project Generator
+ - [ ] **Mingling** Program Installer & Manager (For development)
+ - [ ] Helpdoc Editor
+ - [ ] [`picker`] A more efficient and intelligent argument parser
+ - [x] [`macros`] Remove `r_print!` / `r_println!` macros
- [ ] Milestone.3 "Unplanned"
- - [ ] ...
+ - [ ] ...
-<h1 align="center">
- 🚫 Unplanned Features 🚫
-</h1>
+## Unplanned Features
While Mingling has several common CLI features that are **NOT PLANNED** to be directly included in the framework.
This is because the Rust ecosystem already has excellent and mature crates to handle these issues, and Mingling's design is intended to be used in combination with them.
@@ -902,10 +148,19 @@ This is because the Rust ecosystem already has excellent and mature crates to ha
- **Progress Bars**: To display progress indicators, the [`indicatif`](https://crates.io/crates/indicatif) crate is the standard choice.
- **TUI**: To build full-screen interactive terminal applications, it is recommended to use a framework like [`ratatui`](https://crates.io/crates/ratatui) (formerly `tui-rs`).
-<h1 align="center">
- 📄 Open Source License 📄
-</h1>
+## License
This project is licensed under the MIT License.
See [LICENSE-MIT](LICENSE-MIT) or [LICENSE-APACHE](LICENSE-APACHE) file for details.
+
+## Learn More
+
+**To learn more, check out the following links:**
+
+- 📦 Repo - [Github](https://github.com/mingling-rs/mingling) | [Gitee](https://gitee.com/mingling-rs/mingling) | [Origin](https://catilgrass.cn/mingling.git)
+- 🚪 Mainpage - [Github](https://mingling-rs.github.io/mingling/) | [crates.io](https://crates.io/crates/mingling)
+- 💡 Examples - [Github](https://mingling-rs.github.io/mingling/docs/examples.html)
+- 📖 Help Doc - [EN](https://mingling-rs.github.io/mingling/docs/doc.html#/) | [中文](https://mingling-rs.github.io/mingling/docs/_zh_CN/index.html#/)
+- 📖 API Doc - [docs.rs](https://docs.rs/mingling/latest/mingling/) | [latest](https://mingling-rs.github.io/mingling/docs/api-docs/mingling/)
+- 📖 Dev Doc - [Github](https://mingling-rs.github.io/mingling/docs/dev/)
diff --git a/arg_picker/Cargo.toml b/arg_picker/Cargo.toml
new file mode 100644
index 0000000..d87a844
--- /dev/null
+++ b/arg_picker/Cargo.toml
@@ -0,0 +1,16 @@
+[package]
+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"
+authors = ["Weicao-CatilGrass"]
+readme = "README.md"
+description = "A lightweight, type-safe CLI argument parser"
+
+[features]
+mingling_support = ["arg-picker-macros/mingling_support"]
+
+[dependencies]
+arg-picker-macros.workspace = true
+just_fmt.workspace = true
diff --git a/arg_picker/LICENSE-APACHE b/arg_picker/LICENSE-APACHE
new file mode 120000
index 0000000..965b606
--- /dev/null
+++ b/arg_picker/LICENSE-APACHE
@@ -0,0 +1 @@
+../LICENSE-APACHE \ No newline at end of file
diff --git a/arg_picker/LICENSE-MIT b/arg_picker/LICENSE-MIT
new file mode 120000
index 0000000..76219eb
--- /dev/null
+++ b/arg_picker/LICENSE-MIT
@@ -0,0 +1 @@
+../LICENSE-MIT \ No newline at end of file
diff --git a/mingling_picker/README.md b/arg_picker/README.md
index db67825..14051f3 100644
--- a/mingling_picker/README.md
+++ b/arg_picker/README.md
@@ -1,4 +1,4 @@
-# Mingling Picker
+# Argument Picker
A command-line argument parser for [Mingling](https://github.com/mingling-rs/mingling), enabled by the `mingling/picker` feature.
@@ -10,11 +10,11 @@ features = [
]
```
-Of course, you can also use it as a standalone crate by replacing `mingling::picker` with `mingling_picker`:
+Of course, you can also use it as a standalone crate by replacing `mingling::picker` with `arg_picker`:
```toml
[dependencies]
-mingling_picker = "0.3.0"
+arg-picker = "0.1.0"
```
## Chained Argument Parser
@@ -22,7 +22,7 @@ mingling_picker = "0.3.0"
Provides a clean chained-call API for declaring arguments to parse:
```rust
-use mingling_picker::prelude::*;
+use arg_picker::prelude::*;
let args: Vec<&str> = vec!["--name", "Bob", "--age", "24"];
@@ -43,5 +43,5 @@ assert_eq!(age, 24);
Provides a pure function library `parselib` for analyzing the structure of command-line arguments.
```rust
-use mingling_picker::parselib::*;
+use arg_picker::parselib::*;
```
diff --git a/mingling_picker/src/arg.rs b/arg_picker/src/arg.rs
index 78ad539..a352418 100644
--- a/mingling_picker/src/arg.rs
+++ b/arg_picker/src/arg.rs
@@ -138,9 +138,27 @@ where
/// Describes the attribute (behavior) of a command-line parameter.
///
/// The ordering reflects parse priority (higher = parsed first):
-/// `PositionalMulti < Positional < Flag < Single < Multi`
+/// `Postprocess < Final < PositionalMulti < Positional < Flag < Single < Multi < Begin < Preprocess`
+///
+/// # Variants
+///
+/// - `Postprocess` — Reserved lowest priority, used only in special cases.
+/// - `Final` — Reserved post-processing priority, used only in special cases.
+/// - `PositionalMulti` — Positional argument that accepts multiple values (e.g., multiple input files).
+/// - `Positional` — Positional argument matched by its position (e.g., an input file).
+/// - `Flag` — Boolean flag with no associated value (e.g., `--verbose`).
+/// - `Single` — Accepts a single value (e.g., `--name Alice`).
+/// - `Multi` — Accepts multiple values (e.g., `--file a.txt --file b.txt`).
+/// - `Begin` — Reserved pre-processing priority, used only in special cases.
+/// - `Preprocess` — Reserved highest priority, used only in special cases.
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum PickerArgAttr {
+ /// Reserved lowest priority, used only in special cases.
+ Postprocess,
+
+ /// Reserved post-processing priority, used only in special cases.
+ Final,
+
/// Positional argument that accepts multiple values (e.g., multiple input files).
PositionalMulti,
@@ -156,6 +174,12 @@ pub enum PickerArgAttr {
/// Accepts multiple values (e.g., `--file a.txt --file b.txt`).
Multi,
+
+ /// Reserved pre-processing priority, used only in special cases.
+ Begin,
+
+ /// Reserved highest priority, used only in special cases.
+ Preprocess,
}
impl PickerArgAttr {
diff --git a/mingling_picker/src/builtin.rs b/arg_picker/src/builtin.rs
index e855b08..1c698ba 100644
--- a/mingling_picker/src/builtin.rs
+++ b/arg_picker/src/builtin.rs
@@ -1,4 +1,5 @@
mod pick_bool;
mod pick_flag;
mod pick_numbers;
+mod pick_picker_args;
mod pick_string;
diff --git a/mingling_picker/src/builtin/pick_bool.rs b/arg_picker/src/builtin/pick_bool.rs
index ccc4424..ccc4424 100644
--- a/mingling_picker/src/builtin/pick_bool.rs
+++ b/arg_picker/src/builtin/pick_bool.rs
diff --git a/mingling_picker/src/builtin/pick_flag.rs b/arg_picker/src/builtin/pick_flag.rs
index b642a9a..b642a9a 100644
--- a/mingling_picker/src/builtin/pick_flag.rs
+++ b/arg_picker/src/builtin/pick_flag.rs
diff --git a/mingling_picker/src/builtin/pick_numbers.rs b/arg_picker/src/builtin/pick_numbers.rs
index a5ab0a9..a5ab0a9 100644
--- a/mingling_picker/src/builtin/pick_numbers.rs
+++ b/arg_picker/src/builtin/pick_numbers.rs
diff --git a/arg_picker/src/builtin/pick_picker_args.rs b/arg_picker/src/builtin/pick_picker_args.rs
new file mode 100644
index 0000000..419cbc8
--- /dev/null
+++ b/arg_picker/src/builtin/pick_picker_args.rs
@@ -0,0 +1,21 @@
+use crate::{PickerArgResult::Parsed, PickerArgs, parselib::build_masked_args, pickable_needed::*};
+
+impl<'a> Pickable<'a> for PickerArgs<'a> {
+ fn get_attr(_flag: &'a PickerArg<'a, Self>) -> PickerArgAttr {
+ // Use the lowest priority attribute
+ PickerArgAttr::Postprocess
+ }
+
+ fn tag(ctx: TagPhaseContext) -> Vec<usize> {
+ // Collect all remaining raw index values
+ build_masked_args(ctx.args, ctx.mask)
+ .iter()
+ .map(|m| m.raw_idx)
+ .collect()
+ }
+
+ fn pick(raw_strs: &[&str]) -> PickerArgResult<Self> {
+ let remains: Vec<String> = raw_strs.iter().map(|s| s.to_string()).collect();
+ Parsed(PickerArgs::Owned(remains))
+ }
+}
diff --git a/mingling_picker/src/builtin/pick_string.rs b/arg_picker/src/builtin/pick_string.rs
index c96f667..c96f667 100644
--- a/mingling_picker/src/builtin/pick_string.rs
+++ b/arg_picker/src/builtin/pick_string.rs
diff --git a/arg_picker/src/corebind.rs b/arg_picker/src/corebind.rs
new file mode 100644
index 0000000..3581871
--- /dev/null
+++ b/arg_picker/src/corebind.rs
@@ -0,0 +1,2 @@
+mod entry_picker;
+pub use entry_picker::*;
diff --git a/mingling_picker/src/infos.rs b/arg_picker/src/infos.rs
index d2a0fce..074539a 100644
--- a/mingling_picker/src/infos.rs
+++ b/arg_picker/src/infos.rs
@@ -51,7 +51,7 @@ impl<Type> PickerArgResult<Type> {
/// # Examples
///
/// ```
- /// use mingling_picker::PickerArgResult;
+ /// use arg_picker::PickerArgResult;
///
/// let result: PickerArgResult<i32> = PickerArgResult::Parsed(42);
/// assert!(result.is_parsed());
@@ -70,7 +70,7 @@ impl<Type> PickerArgResult<Type> {
/// # Examples
///
/// ```
- /// use mingling_picker::PickerArgResult;
+ /// use arg_picker::PickerArgResult;
///
/// let result: PickerArgResult<i32> = PickerArgResult::Parsed(42);
/// assert!(result.is_found());
@@ -87,7 +87,7 @@ impl<Type> PickerArgResult<Type> {
/// # Examples
///
/// ```
- /// use mingling_picker::PickerArgResult;
+ /// use arg_picker::PickerArgResult;
///
/// let result: PickerArgResult<i32> = PickerArgResult::Unparsed;
/// assert!(result.is_err());
@@ -104,7 +104,7 @@ impl<Type> PickerArgResult<Type> {
/// # Examples
///
/// ```
- /// use mingling_picker::PickerArgResult;
+ /// use arg_picker::PickerArgResult;
///
/// let result: PickerArgResult<i32> = PickerArgResult::Parsed(42);
/// assert_eq!(result.parsed(), Some(&42));
@@ -128,7 +128,7 @@ impl<Type> PickerArgResult<Type> {
/// # Examples
///
/// ```should_panic
- /// use mingling_picker::PickerArgResult;
+ /// use arg_picker::PickerArgResult;
///
/// let result: PickerArgResult<i32> = PickerArgResult::NotFound;
/// result.expect("expected a parsed value");
@@ -148,14 +148,14 @@ impl<Type> PickerArgResult<Type> {
/// # Examples
///
/// ```
- /// use mingling_picker::PickerArgResult;
+ /// use arg_picker::PickerArgResult;
///
/// let result: PickerArgResult<i32> = PickerArgResult::Parsed(42);
/// assert_eq!(result.unwrap(), 42);
/// ```
///
/// ```should_panic
- /// use mingling_picker::PickerArgResult;
+ /// use arg_picker::PickerArgResult;
///
/// let result: PickerArgResult<i32> = PickerArgResult::NotFound;
/// result.unwrap();
@@ -177,7 +177,7 @@ impl<Type> PickerArgResult<Type> {
/// # Examples
///
/// ```
- /// use mingling_picker::PickerArgResult;
+ /// use arg_picker::PickerArgResult;
///
/// let result: PickerArgResult<i32> = PickerArgResult::Parsed(42);
/// assert_eq!(result.unwrap_or(0), 42);
@@ -197,7 +197,7 @@ impl<Type> PickerArgResult<Type> {
/// # Examples
///
/// ```
- /// use mingling_picker::PickerArgResult;
+ /// use arg_picker::PickerArgResult;
///
/// let result: PickerArgResult<i32> = PickerArgResult::Parsed(42);
/// assert_eq!(result.unwrap_or_else(|| 0), 42);
@@ -217,7 +217,7 @@ impl<Type> PickerArgResult<Type> {
/// # Examples
///
/// ```
- /// use mingling_picker::PickerArgResult;
+ /// use arg_picker::PickerArgResult;
///
/// let result: PickerArgResult<i32> = PickerArgResult::Parsed(42);
/// assert_eq!(result.unwrap_or_default(), 42);
@@ -242,7 +242,7 @@ impl<Type> PickerArgResult<Type> {
/// # Examples
///
/// ```
- /// use mingling_picker::PickerArgResult;
+ /// use arg_picker::PickerArgResult;
///
/// let result: PickerArgResult<i32> = PickerArgResult::Parsed(42);
/// assert_eq!(result.to_option(), Some(42));
diff --git a/mingling_picker/src/lib.rs b/arg_picker/src/lib.rs
index ffa6e13..c65e793 100644
--- a/mingling_picker/src/lib.rs
+++ b/arg_picker/src/lib.rs
@@ -25,17 +25,17 @@ pub mod value;
///
/// This module is intended to be imported with a wildcard import:
///
-/// ```ignore
-/// use mingling_picker::prelude::*;
+/// ```
+/// use arg_picker::prelude::*;
/// ```
pub mod prelude {
pub use crate::IntoPicker;
pub use crate::macros::arg;
}
-/// Re-export of the `mingling_picker_macros` crate
+/// Re-export of the `arg_picker_macros` crate
pub mod macros {
- pub use mingling_picker_macros::arg;
+ pub use arg_picker_macros::arg;
}
/// Provides the types necessary for implementing the `Pickable` trait
@@ -48,10 +48,3 @@ pub mod matcher_needed {
pub use crate::PickerArgInfo;
pub use crate::parselib::{MaskedArg, Matcher, ParserStyle};
}
-
-#[cfg(feature = "mingling_support")]
-mod corebind;
-
-#[allow(unused_imports)]
-#[cfg(feature = "mingling_support")]
-pub use corebind::*;
diff --git a/mingling_picker/src/parselib.rs b/arg_picker/src/parselib.rs
index 7fbd606..0fcd583 100644
--- a/mingling_picker/src/parselib.rs
+++ b/arg_picker/src/parselib.rs
@@ -117,13 +117,32 @@ impl<'a> From<crate::TagPhaseContext<'a>> for MatcherContext<'a> {
}
}
+/// Checks whether the argument at index `idx` is already claimed (masked).
+///
+/// Returns `true` if `idx` is within the mask bounds and the mask value is non-zero,
+/// indicating the argument has been claimed by a previous matcher.
+///
+/// # Arguments
+///
+/// * `mask` - A byte slice where non-zero values indicate claimed arguments.
+/// * `idx` - The index to check in the mask.
#[inline(always)]
-fn is_masked(mask: &[u8], idx: usize) -> bool {
+pub fn is_masked(mask: &[u8], idx: usize) -> bool {
idx < mask.len() && mask[idx] != 0
}
+/// Builds a vector of [`MaskedArg`] from the given `PickerArgs` and mask.
+///
+/// Only arguments whose mask entry is `0` (i.e., available/not yet claimed) are included.
+/// Each resulting [`MaskedArg`] retains its original raw string and its index in the full
+/// argument list for later reference.
+///
+/// # Arguments
+///
+/// * `args` - The full set of parsed arguments.
+/// * `mask` - A byte slice where `0` means available and non-zero means already claimed.
#[inline(always)]
-fn build_masked_args<'a>(args: &'a PickerArgs, mask: &'a [u8]) -> Vec<MaskedArg<'a>> {
+pub fn build_masked_args<'a>(args: &'a PickerArgs, mask: &'a [u8]) -> Vec<MaskedArg<'a>> {
let mut cidx = 0;
args.iter()
.filter_map(|r| {
diff --git a/mingling_picker/src/parselib/arg_matcher.rs b/arg_picker/src/parselib/arg_matcher.rs
index 38bb9cc..38bb9cc 100644
--- a/mingling_picker/src/parselib/arg_matcher.rs
+++ b/arg_picker/src/parselib/arg_matcher.rs
diff --git a/mingling_picker/src/parselib/flag_matcher.rs b/arg_picker/src/parselib/flag_matcher.rs
index e93d35a..e93d35a 100644
--- a/mingling_picker/src/parselib/flag_matcher.rs
+++ b/arg_picker/src/parselib/flag_matcher.rs
diff --git a/mingling_picker/src/parselib/multi_arg_matcher.rs b/arg_picker/src/parselib/multi_arg_matcher.rs
index e8bdb71..748b1be 100644
--- a/mingling_picker/src/parselib/multi_arg_matcher.rs
+++ b/arg_picker/src/parselib/multi_arg_matcher.rs
@@ -65,9 +65,8 @@ impl Matcher for MultiArgMatcher {
let possible_flags = build_possible_flags(style, arg_info);
let end = seek_end_of_options(args, style);
let sep = style.value_separator;
- let is_flag = |raw: &str| {
- raw.starts_with(style.long_prefix) || raw.starts_with(style.short_prefix)
- };
+ let is_flag =
+ |raw: &str| raw.starts_with(style.long_prefix) || raw.starts_with(style.short_prefix);
let is_our_flag = |raw: &str| {
possible_flags
.iter()
@@ -118,7 +117,8 @@ impl Matcher for MultiArgMatcher {
impl MultiArgMatcher {
#[inline(always)]
fn flag_match(raw: &str, flag_str: &str, case_sensitive: bool, sep: char) -> bool {
- let eq = |r: &str, f: &str| r.len() > f.len() && r.as_bytes().get(f.len()) == Some(&(sep as u8));
+ let eq =
+ |r: &str, f: &str| r.len() > f.len() && r.as_bytes().get(f.len()) == Some(&(sep as u8));
if case_sensitive {
raw == flag_str || (raw.starts_with(flag_str) && eq(raw, flag_str))
diff --git a/mingling_picker/src/parselib/pos_matcher.rs b/arg_picker/src/parselib/pos_matcher.rs
index 279e01e..279e01e 100644
--- a/mingling_picker/src/parselib/pos_matcher.rs
+++ b/arg_picker/src/parselib/pos_matcher.rs
diff --git a/mingling_picker/src/parselib/single_matcher.rs b/arg_picker/src/parselib/single_matcher.rs
index 25c4741..25c4741 100644
--- a/mingling_picker/src/parselib/single_matcher.rs
+++ b/arg_picker/src/parselib/single_matcher.rs
diff --git a/mingling_picker/src/parselib/style.rs b/arg_picker/src/parselib/style.rs
index 3c37b2d..36ba8f0 100644
--- a/mingling_picker/src/parselib/style.rs
+++ b/arg_picker/src/parselib/style.rs
@@ -39,8 +39,8 @@ impl<'a> ParserStyle<'a> {
///
/// # Examples
///
- /// ```ignore
- /// use mingling_picker::parselib::{ParserStyle, FlagStr, UNIX_STYLE};
+ /// ```
+ /// # use arg_picker::parselib::{ParserStyle, FlagStr, UNIX_STYLE};
/// let style = &UNIX_STYLE;
///
/// assert_eq!(style.flag_string('v'), "-v");
@@ -76,7 +76,7 @@ impl<'a> ParserStyle<'a> {
/// # Examples
///
/// ```
-/// use mingling_picker::parselib::FlagStr;
+/// use arg_picker::parselib::FlagStr;
///
/// let short: FlagStr = 'v'.into();
/// let long: FlagStr = "verbose".into();
@@ -117,8 +117,8 @@ impl<'a> From<&'a String> for FlagStr<'a> {
/// # Examples
///
/// ```
-/// # use mingling_picker::IntoPicker;
-/// use mingling_picker::parselib::ParserStyleNamingCase;
+/// # use arg_picker::IntoPicker;
+/// use arg_picker::parselib::ParserStyleNamingCase;
///
/// let case = ParserStyleNamingCase::Kebab;
/// assert_eq!(
@@ -174,7 +174,7 @@ impl ParserStyleNamingCase {
/// # Examples
///
/// ```
- /// use mingling_picker::parselib::ParserStyleNamingCase;
+ /// use arg_picker::parselib::ParserStyleNamingCase;
///
/// let camel = ParserStyleNamingCase::Camel;
/// assert_eq!(camel.convert("brew_coffee".to_string()), "brewCoffee");
diff --git a/mingling_picker/src/parselib/utils.rs b/arg_picker/src/parselib/utils.rs
index 47c5b55..47c5b55 100644
--- a/mingling_picker/src/parselib/utils.rs
+++ b/arg_picker/src/parselib/utils.rs
diff --git a/mingling_picker/src/pickable.rs b/arg_picker/src/pickable.rs
index 758ae9a..758ae9a 100644
--- a/mingling_picker/src/pickable.rs
+++ b/arg_picker/src/pickable.rs
diff --git a/mingling_picker/src/pickable/multi_pickable.rs b/arg_picker/src/pickable/multi_pickable.rs
index 0ab0508..84a8068 100644
--- a/mingling_picker/src/pickable/multi_pickable.rs
+++ b/arg_picker/src/pickable/multi_pickable.rs
@@ -1,8 +1,7 @@
use crate::{
+ Pickable, PickerArg, PickerArgAttr, PickerArgResult, SinglePickable, TagPhaseContext,
matcher_needed::Matcher,
parselib::{MultiArgMatcher, ParserStyle},
- Pickable, PickerArg, PickerArgAttr, PickerArgResult,
- SinglePickable, TagPhaseContext,
};
/// Boundary check for multi-value positional parameters.
diff --git a/mingling_picker/src/pickable/single_pickable.rs b/arg_picker/src/pickable/single_pickable.rs
index 8a5b3e6..8a5b3e6 100644
--- a/mingling_picker/src/pickable/single_pickable.rs
+++ b/arg_picker/src/pickable/single_pickable.rs
diff --git a/mingling_picker/src/picker.rs b/arg_picker/src/picker.rs
index 69b1671..f31a5b6 100644
--- a/mingling_picker/src/picker.rs
+++ b/arg_picker/src/picker.rs
@@ -12,10 +12,10 @@ use crate::{Pickable, PickerArg, PickerArgResult};
#[doc = include_str!("../README.md")]
pub struct Picker<'a, Route = ()> {
- route_phantom: PhantomData<Route>,
+ pub(crate) route_phantom: PhantomData<Route>,
/// Internal arguments of Picker
- args: PickerArgs<'a>,
+ pub(crate) args: PickerArgs<'a>,
}
impl<'a> Picker<'a> {
@@ -73,6 +73,26 @@ pub enum PickerArgs<'a> {
Owned(Vec<String>),
}
+impl<'a> From<PickerArgs<'a>> for Vec<String> {
+ fn from(value: PickerArgs<'a>) -> Self {
+ match value {
+ PickerArgs::Slice(items) => items.iter().map(|s| s.to_string()).collect(),
+ PickerArgs::Vec(items) => items.into_iter().map(|s| s.to_string()).collect(),
+ PickerArgs::Owned(items) => items,
+ }
+ }
+}
+
+impl<'a> From<&'a PickerArgs<'a>> for Vec<&'a str> {
+ fn from(value: &'a PickerArgs<'a>) -> Self {
+ match value {
+ PickerArgs::Slice(items) => items.to_vec(),
+ PickerArgs::Vec(items) => items.clone(),
+ PickerArgs::Owned(items) => items.iter().map(|s| s.as_str()).collect(),
+ }
+ }
+}
+
impl<'a> Default for PickerArgs<'a> {
fn default() -> Self {
Self::Vec(vec![])
@@ -138,6 +158,15 @@ impl<'a> IntoIterator for &'a PickerArgs<'a> {
}
}
+impl<'a, Route> From<PickerArgs<'a>> for Picker<'a, Route> {
+ fn from(args: PickerArgs<'a>) -> Self {
+ Picker {
+ route_phantom: PhantomData,
+ args,
+ }
+ }
+}
+
impl<'a, Route> From<&'a [&'a str]> for Picker<'a, Route> {
fn from(value: &'a [&'a str]) -> Self {
Picker {
@@ -254,6 +283,59 @@ impl<'a> Iterator for PickerIter<'a> {
impl<'a> ExactSizeIterator for PickerIter<'a> {}
+impl<'a, Route> Picker<'a, Route> {
+ /// Creates a `PickerPattern1` from the given arg to start a picking chain.
+ ///
+ /// This method initiates a parameter picking chain with one arg.
+ /// The result is initially `Unparsed`.
+ pub fn pick<N>(self, arg: impl Into<&'a PickerArg<'a, N>>) -> PickerPattern1<'a, N, Route>
+ where
+ N: Pickable<'a>,
+ {
+ Self::build_pattern1(self.args, arg.into(), None::<Route>)
+ }
+
+ /// Creates a `PickerPattern1` from the given arg.
+ /// If parsing fails, attempts the fallback arg.
+ pub fn pick_or<N, F>(
+ self,
+ arg: impl Into<&'a PickerArg<'a, N>>,
+ or_arg: F,
+ ) -> PickerPattern1<'a, N, Route>
+ where
+ N: Pickable<'a>,
+ F: FnMut() -> N + 'static,
+ {
+ self.pick(arg).or(or_arg)
+ }
+
+ /// Creates a `PickerPattern1` from the given arg.
+ /// If parsing fails, uses the provided default value.
+ pub fn pick_or_default<N>(
+ self,
+ arg: impl Into<&'a PickerArg<'a, N>>,
+ ) -> PickerPattern1<'a, N, Route>
+ where
+ N: Pickable<'a> + Default,
+ {
+ self.pick(arg).or_default()
+ }
+
+ /// Creates a `PickerPattern1` from the given arg.
+ /// If parsing fails, switches to the given error route.
+ pub fn pick_or_route<N, F>(
+ self,
+ arg: impl Into<&'a PickerArg<'a, N>>,
+ error_route: F,
+ ) -> PickerPattern1<'a, N, Route>
+ where
+ N: Pickable<'a>,
+ F: FnMut() -> Route + 'static,
+ {
+ self.pick(arg).or_route(error_route)
+ }
+}
+
/// Trait for converting types into a `Picker`
///
/// Implemented for:
@@ -267,7 +349,7 @@ pub trait IntoPicker<'a> {
/// # Examples
///
/// ```
- /// use mingling_picker::{IntoPicker, Picker};
+ /// use arg_picker::{IntoPicker, Picker};
///
/// let args: Picker = (&["hello", "world"][..]).to_picker();
/// assert_eq!(args.len(), 2);
@@ -289,15 +371,7 @@ pub trait IntoPicker<'a> {
Self: Sized,
N: Pickable<'a> + Default + Sized,
{
- PickerPattern1 {
- args: self.to_picker().args,
- arg_1: arg.into(),
- result_1: PickerArgResult::Unparsed,
- default_1: None,
- route_1: None,
- post_1: None,
- error_route: None,
- }
+ Picker::build_pattern1(self.to_picker().args, arg.into(), None::<()>)
}
/// Converts the value into a `Picker` with a specified route type.
@@ -344,6 +418,16 @@ impl<'a> IntoPicker<'a> for Vec<&'a str> {
}
}
+impl<'a> IntoPicker<'a> for &'a Vec<String> {
+ fn to_picker(self) -> Picker<'a, ()> {
+ let slice: Vec<&str> = self.iter().map(|s| s.as_str()).collect();
+ Picker {
+ route_phantom: PhantomData,
+ args: PickerArgs::Vec(slice),
+ }
+ }
+}
+
impl<'a> IntoPicker<'a> for Vec<String> {
fn to_picker(self) -> Picker<'a, ()> {
Picker {
@@ -352,3 +436,25 @@ impl<'a> IntoPicker<'a> for Vec<String> {
}
}
}
+
+impl<'a, Route> Picker<'a, Route> {
+ /// Build the PickerPattern via Arguments
+ pub fn build_pattern1<N>(
+ args: PickerArgs<'a>,
+ arg: &'a PickerArg<'a, N>,
+ error_route: Option<Route>,
+ ) -> PickerPattern1<'a, N, Route>
+ where
+ N: Pickable<'a>,
+ {
+ PickerPattern1 {
+ args,
+ error_route,
+ arg_1: arg,
+ result_1: PickerArgResult::Unparsed,
+ route_1: None,
+ default_1: None,
+ post_1: None,
+ }
+ }
+}
diff --git a/mingling_picker/src/picker/parse.rs b/arg_picker/src/picker/parse.rs
index 8f7d514..9db5bd9 100644
--- a/mingling_picker/src/picker/parse.rs
+++ b/arg_picker/src/picker/parse.rs
@@ -11,7 +11,7 @@
#![allow(clippy::type_complexity)]
use crate::{Pickable, PickerArgAttr, PickerArgInfo, PickerArgResult, PickerArgs, TagPhaseContext};
-use mingling_picker_macros::internal_repeat;
+use arg_picker_macros::internal_repeat;
internal_repeat!(1..=32 => {
use crate::PickerPattern$;
@@ -21,14 +21,16 @@ internal_repeat!(1..=32 => {
internal_repeat!(1..=32 => {
impl<'a, (T$,+), Route> PickerPattern$<'a, (T$,+), Route>
where (T$: Pickable<'a>,+) {
- /// Unwraps the result, panicking if a route was selected.
+ /// Unwraps the result, panicking if a route was selected or a required
+ /// value is missing.
///
/// # Panics
///
- /// Panics if a route was selected.
+ /// Panics if a route was selected, or if a required argument was not
+ /// provided by the user.
pub fn unwrap(self) -> ((T$,+)) {
let p = self.parse();
- ((p.v$.unwrap(),+))
+ ((p.v$.expect(concat!("missing required argument at position ", $)),+))
}
/// Returns the individual option values without checking the route.
@@ -124,7 +126,7 @@ internal_repeat!(1..=32 => {
} else {
if error_route.is_none() {
if let Some(get_route) = self.route_$ {
- *error_route = Some(get_route());
+ *error_route = Some(get_route().into());
}
}
other
diff --git a/mingling_picker/src/picker/patterns.rs b/arg_picker/src/picker/patterns.rs
index 3c6e73f..5806605 100644
--- a/mingling_picker/src/picker/patterns.rs
+++ b/arg_picker/src/picker/patterns.rs
@@ -1,6 +1,6 @@
-use mingling_picker_macros::internal_repeat;
+use arg_picker_macros::internal_repeat;
-use crate::{Pickable, Picker, PickerArg, PickerArgResult, PickerArgs};
+use crate::{Pickable, PickerArg, PickerArgResult, PickerArgs};
internal_repeat!(1..=32 => {
#[doc(hidden)]
@@ -10,11 +10,11 @@ internal_repeat!(1..=32 => {
pub args: PickerArgs<'a>,
pub error_route: Option<Route>,
(
- pub arg_$: &'a PickerArg<'a, T$>,
- pub result_$: PickerArgResult<T$>,
- pub default_$: Option<Box<dyn FnOnce() -> T$>>,
- pub route_$: Option<Box<dyn FnOnce() -> Route>>,
- pub post_$: Option<Box<dyn FnOnce(T$) -> T$>>,
+ pub(crate) arg_$: &'a PickerArg<'a, T$>,
+ pub(crate) result_$: PickerArgResult<T$>,
+ pub(crate) default_$: Option<Box<dyn FnOnce() -> T$>>,
+ pub(crate) route_$: Option<Box<dyn FnOnce() -> Route>>,
+ pub(crate) post_$: Option<Box<dyn FnOnce(T$) -> T$>>,
+)
}
});
@@ -30,11 +30,6 @@ internal_repeat!(1..=32 => {
///
/// # Example
///
- /// ```ignore
- /// let pattern = picker
- /// .pick(&my_arg)
- /// .or(|| 42);
- /// ```
#[allow(clippy::type_complexity)]
pub fn or<F>(mut self, func: F) -> Self
where
@@ -52,11 +47,6 @@ internal_repeat!(1..=32 => {
///
/// # Example
///
- /// ```ignore
- /// let pattern = picker
- /// .pick(&my_arg)
- /// .or_default();
- /// ```
#[allow(clippy::type_complexity)]
pub fn or_default(mut self) -> Self
where
@@ -73,11 +63,6 @@ internal_repeat!(1..=32 => {
///
/// # Example
///
- /// ```ignore
- /// let pattern = picker
- /// .pick(&my_arg)
- /// .or_route(|| Redirect::home());
- /// ```
pub fn or_route<F>(mut self, func: F) -> Self
where
F: FnMut() -> Route,
@@ -99,7 +84,8 @@ internal_repeat!(1..=32 => {
/// for example when composing patterns from different contexts that use different
/// route enums.
#[allow(clippy::type_complexity)]
- pub fn with_route<NewRoute>(self) -> PickerPattern$<'a, (T$,+), NewRoute> {
+ pub fn with_route<NewRoute>(self) -> PickerPattern$<'a, (T$,+), NewRoute>
+ {
PickerPattern$ {
args: self.args,
error_route: None,
@@ -121,11 +107,6 @@ internal_repeat!(1..=32 => {
///
/// # Example
///
- /// ```ignore
- /// let pattern = picker
- /// .pick(&my_arg)
- /// .post(|val| val * 2);
- /// ```
#[allow(clippy::type_complexity)]
pub fn post<F>(mut self, func: F) -> Self
where
@@ -174,26 +155,51 @@ internal_repeat!(1..32 => {
+)
}
}
+
+ /// Picks a new arg with a default value provider in a single call.
+ ///
+ /// This is a shorthand for calling `.pick(arg).or(func)`.
+ ///
+ /// # Example
+ ///
+ #[allow(clippy::type_complexity)]
+ pub fn pick_or<N, F>(self, arg: impl Into<&'a PickerArg<'a, N>>, func: F) -> PickerPattern$+<'a, (T$,+), N, Route>
+ where
+ N: Pickable<'a>,
+ F: FnMut() -> N + 'static,
+ F: 'static,
+ {
+ self.pick(arg).or(func)
+ }
+
+ /// Picks a new arg with a default value in a single call.
+ ///
+ /// This is a shorthand for calling `.pick(arg).or_default()`.
+ ///
+ /// # Example
+ ///
+ #[allow(clippy::type_complexity)]
+ pub fn pick_or_default<N>(self, arg: impl Into<&'a PickerArg<'a, N>>) -> PickerPattern$+<'a, (T$,+), N, Route>
+ where
+ N: Pickable<'a> + Default,
+ {
+ self.pick(arg).or_default()
+ }
+
+ /// Picks a new arg with a route in a single call.
+ ///
+ /// This is a shorthand for calling `.pick(arg).or_route(func)`.
+ ///
+ /// # Example
+ ///
+ #[allow(clippy::type_complexity)]
+ pub fn pick_or_route<N, F>(self, arg: impl Into<&'a PickerArg<'a, N>>, func: F) -> PickerPattern$+<'a, (T$,+), N, Route>
+ where
+ N: Pickable<'a>,
+ F: FnMut() -> Route + 'static,
+ F: 'static,
+ {
+ self.pick(arg).or_route(func)
+ }
}
});
-
-impl<'a, Route> Picker<'a, Route> {
- /// Creates a `PickerPattern1` from the given arg to start a picking chain.
- ///
- /// This method initiates a parameter picking chain with one arg.
- /// The result is initially `Unparsed`.
- pub fn pick<N>(self, arg: impl Into<&'a PickerArg<'a, N>>) -> PickerPattern1<'a, N, Route>
- where
- N: Pickable<'a>,
- {
- PickerPattern1 {
- args: self.args,
- error_route: None::<Route>,
- arg_1: arg.into(),
- result_1: PickerArgResult::Unparsed,
- route_1: None,
- default_1: None,
- post_1: None,
- }
- }
-}
diff --git a/mingling_picker/src/picker/result.rs b/arg_picker/src/picker/result.rs
index 9cd78ae..2099825 100644
--- a/mingling_picker/src/picker/result.rs
+++ b/arg_picker/src/picker/result.rs
@@ -1,6 +1,6 @@
#![allow(clippy::type_complexity)] // Aha, Type Gymnastics!
-use mingling_picker_macros::internal_repeat;
+use arg_picker_macros::internal_repeat;
internal_repeat!(1..=32 => {
#[doc(hidden)]
@@ -21,13 +21,15 @@ internal_repeat!(1..=32 => {
internal_repeat!(1..=32 => {
impl<(T$,+), Route> PickerResult$<(T$,+), Route> {
- /// Unwraps the result, panicking if a route was selected.
+ /// Unwraps the result, panicking if a route was selected or a required
+ /// value is missing.
///
/// # Panics
///
- /// Panics if `self.route` is `Some(...)`.
+ /// Panics if `self.route` is `Some(...)`, or if a required argument was
+ /// not provided by the user.
pub fn unwrap(self) -> ((T$,+)) {
- ((self.v$.unwrap(),+))
+ ((self.v$.expect(concat!("missing required argument at position ", $)),+))
}
/// Returns the individual option values without checking the route.
diff --git a/mingling_picker/src/value.rs b/arg_picker/src/value.rs
index 995aa00..995aa00 100644
--- a/mingling_picker/src/value.rs
+++ b/arg_picker/src/value.rs
diff --git a/mingling_picker/src/value/flag.rs b/arg_picker/src/value/flag.rs
index ee0d6ee..c0673bd 100644
--- a/mingling_picker/src/value/flag.rs
+++ b/arg_picker/src/value/flag.rs
@@ -26,7 +26,7 @@ use std::{
/// directly in boolean contexts:
///
/// ```
-/// # use mingling_picker::value::Flag;
+/// # use arg_picker::value::Flag;
/// let flag = Flag::Active;
/// if *flag { /* runs */ }
/// ```
@@ -72,7 +72,7 @@ impl Flag {
/// # Examples
///
/// ```
- /// # use mingling_picker::value::Flag;
+ /// # use arg_picker::value::Flag;
/// assert!(Flag::Active.bool());
/// assert!(!Flag::Inactive.bool());
/// ```
@@ -116,7 +116,7 @@ impl From<Flag> for bool {
/// # Examples
///
/// ```
-/// # use mingling_picker::value::Flag;
+/// # use arg_picker::value::Flag;
/// let flag = Flag::Active;
/// if *flag {
/// println!("flag is set");
diff --git a/mingling_picker/src/value/vec_until.rs b/arg_picker/src/value/vec_until.rs
index 1b79641..1b79641 100644
--- a/mingling_picker/src/value/vec_until.rs
+++ b/arg_picker/src/value/vec_until.rs
diff --git a/mingling_picker/test/Cargo.lock b/arg_picker/test/Cargo.lock
index 0fef84b..5d44838 100644
--- a/mingling_picker/test/Cargo.lock
+++ b/arg_picker/test/Cargo.lock
@@ -3,22 +3,16 @@
version = 4
[[package]]
-name = "just_fmt"
-version = "0.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6170dccbc3ea15dfb7f2da964097f814aba1dd8f746d4ffc56f33245c38e6d96"
-
-[[package]]
-name = "mingling_picker"
-version = "0.3.0"
+name = "arg-picker"
+version = "0.1.0"
dependencies = [
+ "arg-picker-macros",
"just_fmt",
- "mingling_picker_macros",
]
[[package]]
-name = "mingling_picker_macros"
-version = "0.3.0"
+name = "arg-picker-macros"
+version = "0.1.0"
dependencies = [
"proc-macro2",
"quote",
@@ -26,6 +20,19 @@ dependencies = [
]
[[package]]
+name = "arg-picker-test"
+version = "0.1.0"
+dependencies = [
+ "arg-picker",
+]
+
+[[package]]
+name = "just_fmt"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6170dccbc3ea15dfb7f2da964097f814aba1dd8f746d4ffc56f33245c38e6d96"
+
+[[package]]
name = "proc-macro2"
version = "1.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -55,13 +62,6 @@ dependencies = [
]
[[package]]
-name = "test-mingling-picker"
-version = "0.1.0"
-dependencies = [
- "mingling_picker",
-]
-
-[[package]]
name = "unicode-ident"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/mingling_picker/test/Cargo.toml b/arg_picker/test/Cargo.toml
index 127546c..2c366c7 100644
--- a/mingling_picker/test/Cargo.toml
+++ b/arg_picker/test/Cargo.toml
@@ -1,9 +1,9 @@
[package]
-name = "test-mingling-picker"
+name = "arg-picker-test"
version = "0.1.0"
edition = "2024"
[workspace]
[dependencies]
-mingling_picker = { path = "../" }
+arg-picker = { path = "../" }
diff --git a/mingling_picker/test/src/lib.rs b/arg_picker/test/src/lib.rs
index eac6cad..0cfa575 100644
--- a/mingling_picker/test/src/lib.rs
+++ b/arg_picker/test/src/lib.rs
@@ -7,7 +7,7 @@
#[cfg(test)]
mod test;
-use mingling_picker::parselib::MaskedArg;
+use arg_picker::parselib::MaskedArg;
/// Create a single `MaskedArg` from a raw string and its original index.
pub fn make_masked(raw: &str, idx: usize) -> MaskedArg<'_> {
diff --git a/mingling_picker/test/src/test.rs b/arg_picker/test/src/test.rs
index 9c53514..9c53514 100644
--- a/mingling_picker/test/src/test.rs
+++ b/arg_picker/test/src/test.rs
diff --git a/mingling_picker/test/src/test/arg_matcher_test.rs b/arg_picker/test/src/test/arg_matcher_test.rs
index 7b37bc8..d1d363e 100644
--- a/mingling_picker/test/src/test/arg_matcher_test.rs
+++ b/arg_picker/test/src/test/arg_matcher_test.rs
@@ -1,5 +1,5 @@
-use mingling_picker::PickerArgInfo;
-use mingling_picker::parselib::{ArgMatcher, Matcher, POWERSHELL_STYLE, UNIX_STYLE};
+use arg_picker::parselib::{ArgMatcher, Matcher, POWERSHELL_STYLE, UNIX_STYLE};
+use arg_picker::PickerArgInfo;
use crate::make_args;
diff --git a/mingling_picker/test/src/test/basic_test.rs b/arg_picker/test/src/test/basic_test.rs
index 78b154e..b01adb6 100644
--- a/mingling_picker/test/src/test/basic_test.rs
+++ b/arg_picker/test/src/test/basic_test.rs
@@ -1,4 +1,4 @@
-use mingling_picker::{IntoPicker, macros::arg};
+use arg_picker::{macros::arg, IntoPicker};
// Basic bool flag — present / absent
diff --git a/mingling_picker/test/src/test/multi_arg_test.rs b/arg_picker/test/src/test/multi_arg_test.rs
index 377357e..49517f1 100644
--- a/mingling_picker/test/src/test/multi_arg_test.rs
+++ b/arg_picker/test/src/test/multi_arg_test.rs
@@ -1,5 +1,5 @@
-use mingling_picker::parselib::{Matcher, MultiArgMatcher, UNIX_STYLE};
-use mingling_picker::PickerArgInfo;
+use arg_picker::parselib::{Matcher, MultiArgMatcher, UNIX_STYLE};
+use arg_picker::PickerArgInfo;
use crate::make_args;
@@ -72,8 +72,12 @@ fn test_multi_all_two_occurrences() {
let mut info = PickerArgInfo::new();
info.set_long("val");
let args = make_args(&[
- ("--val", 0), ("a", 1), ("b", 2),
- ("--val", 3), ("c", 4), ("d", 5),
+ ("--val", 0),
+ ("a", 1),
+ ("b", 2),
+ ("--val", 3),
+ ("c", 4),
+ ("d", 5),
]);
let result = MultiArgMatcher::on_match_all(&args, &UNIX_STYLE, &info);
assert_eq!(result, vec![0, 1, 2, 3, 4, 5]);
@@ -85,9 +89,12 @@ fn test_multi_all_skips_non_matching_args() {
let mut info = PickerArgInfo::new();
info.set_long("val");
let args = make_args(&[
- ("--val", 0), ("a", 1),
- ("--other", 2), ("b", 3),
- ("--val", 4), ("c", 5),
+ ("--val", 0),
+ ("a", 1),
+ ("--other", 2),
+ ("b", 3),
+ ("--val", 4),
+ ("c", 5),
]);
let result = MultiArgMatcher::on_match_all(&args, &UNIX_STYLE, &info);
assert_eq!(result, vec![0, 1, 4, 5]);
@@ -120,9 +127,7 @@ fn test_multi_all_mixed_eq_and_regular() {
// --val=a b --val c d
let mut info = PickerArgInfo::new();
info.set_long("val");
- let args = make_args(&[
- ("--val=a", 0), ("b", 1), ("--val", 2), ("c", 3), ("d", 4),
- ]);
+ let args = make_args(&[("--val=a", 0), ("b", 1), ("--val", 2), ("c", 3), ("d", 4)]);
let result = MultiArgMatcher::on_match_all(&args, &UNIX_STYLE, &info);
assert_eq!(result, vec![0, 1, 2, 3, 4]);
}
diff --git a/mingling_picker/test/src/test/multi_value_test.rs b/arg_picker/test/src/test/multi_value_test.rs
index 0f56517..cbae645 100644
--- a/mingling_picker/test/src/test/multi_value_test.rs
+++ b/arg_picker/test/src/test/multi_value_test.rs
@@ -1,5 +1,5 @@
-use mingling_picker::value::{Flag, VecUntil};
-use mingling_picker::{IntoPicker, macros::arg};
+use arg_picker::value::{Flag, VecUntil};
+use arg_picker::{macros::arg, IntoPicker};
#[test]
fn test_vec_until_i16_named() {
diff --git a/mingling_picker/test/src/test/pos_matcher_test.rs b/arg_picker/test/src/test/pos_matcher_test.rs
index b5319f5..0d4f87e 100644
--- a/mingling_picker/test/src/test/pos_matcher_test.rs
+++ b/arg_picker/test/src/test/pos_matcher_test.rs
@@ -1,5 +1,5 @@
-use mingling_picker::PickerArgInfo;
-use mingling_picker::parselib::{MaskedArg, Matcher, PositionalMatcher, UNIX_STYLE, WINDOWS_STYLE};
+use arg_picker::parselib::{MaskedArg, Matcher, PositionalMatcher, UNIX_STYLE, WINDOWS_STYLE};
+use arg_picker::PickerArgInfo;
fn make_args<'a>(pairs: &'a [(&'a str, usize)]) -> Vec<MaskedArg<'a>> {
pairs
diff --git a/mingling_picker/test/src/test/priority_test.rs b/arg_picker/test/src/test/priority_test.rs
index 8179389..8f0a452 100644
--- a/mingling_picker/test/src/test/priority_test.rs
+++ b/arg_picker/test/src/test/priority_test.rs
@@ -1,5 +1,5 @@
-use mingling_picker::value::Flag;
-use mingling_picker::{IntoPicker, macros::arg};
+use arg_picker::value::Flag;
+use arg_picker::{macros::arg, IntoPicker};
// Same flag name, different Pickable types
//
diff --git a/mingling_picker/test/src/test/route_test.rs b/arg_picker/test/src/test/route_test.rs
index 9261db1..c9cd5ab 100644
--- a/mingling_picker/test/src/test/route_test.rs
+++ b/arg_picker/test/src/test/route_test.rs
@@ -1,4 +1,4 @@
-use mingling_picker::{IntoPicker, macros::arg};
+use arg_picker::{IntoPicker, macros::arg};
// Route mechanism — or_route
@@ -53,7 +53,7 @@ fn test_or_route_to_option_returns_none() {
}
#[test]
-#[should_panic(expected = "called `Option::unwrap()` on a `None` value")]
+#[should_panic(expected = "missing required argument")]
fn test_or_route_unwrap_panics() {
let args: Vec<&str> = vec![];
args.with_route::<&'static str>()
diff --git a/mingling_picker/test/src/test/style_test.rs b/arg_picker/test/src/test/style_test.rs
index 3c337b9..b470d4f 100644
--- a/mingling_picker/test/src/test/style_test.rs
+++ b/arg_picker/test/src/test/style_test.rs
@@ -1,8 +1,8 @@
-use mingling_picker::PickerArgInfo;
-use mingling_picker::parselib::{
- FlagMatcher, Matcher, POWERSHELL_STYLE, ParserStyle, ParserStyleNamingCase, UNIX_STYLE,
- WINDOWS_STYLE, build_possible_flags,
+use arg_picker::parselib::{
+ build_possible_flags, FlagMatcher, Matcher, ParserStyle, ParserStyleNamingCase,
+ POWERSHELL_STYLE, UNIX_STYLE, WINDOWS_STYLE,
};
+use arg_picker::PickerArgInfo;
use crate::make_masked;
diff --git a/mingling_picker/test/src/test/value_flag_test.rs b/arg_picker/test/src/test/value_flag_test.rs
index d267a27..c60ab1e 100644
--- a/mingling_picker/test/src/test/value_flag_test.rs
+++ b/arg_picker/test/src/test/value_flag_test.rs
@@ -1,5 +1,5 @@
-use mingling_picker::value::Flag;
-use mingling_picker::{IntoPicker, macros::arg};
+use arg_picker::value::Flag;
+use arg_picker::{IntoPicker, macros::arg};
// Basic Flag — present / absent
@@ -136,10 +136,10 @@ fn test_flag_to_option() {
#[test]
fn test_flag_converts_to_bool() {
let flag = Flag::Active;
- assert!(bool::from(flag));
+ assert_eq!(bool::from(flag), true);
let flag = Flag::Inactive;
- assert!(!bool::from(flag));
+ assert_eq!(bool::from(flag), false);
}
#[test]
@@ -151,10 +151,10 @@ fn test_flag_from_bool() {
#[test]
fn test_flag_deref_to_bool() {
let active = Flag::Active;
- assert!(*active);
+ assert_eq!(*active, true);
let inactive = Flag::Inactive;
- assert!(!*inactive);
+ assert_eq!(*inactive, false);
}
// Flag never triggers route (unlike bool)
diff --git a/mingling_picker/test/src/test/value_string_test.rs b/arg_picker/test/src/test/value_string_test.rs
index b1e5c0b..2c3b50b 100644
--- a/mingling_picker/test/src/test/value_string_test.rs
+++ b/arg_picker/test/src/test/value_string_test.rs
@@ -1,4 +1,4 @@
-use mingling_picker::{IntoPicker, macros::arg};
+use arg_picker::{macros::arg, IntoPicker};
// Basic named String — present / absent
diff --git a/mingling_picker_macros/Cargo.toml b/arg_picker_macros/Cargo.toml
index 58488ea..c83cb9e 100644
--- a/mingling_picker_macros/Cargo.toml
+++ b/arg_picker_macros/Cargo.toml
@@ -1,9 +1,9 @@
[package]
-name = "mingling_picker_macros"
-version.workspace = true
-edition.workspace = true
-license.workspace = true
-repository.workspace = true
+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"
authors = ["Weicao-CatilGrass"]
readme = "README.md"
description = "Mingling's lightweight argument parser macros"
diff --git a/arg_picker_macros/LICENSE-APACHE b/arg_picker_macros/LICENSE-APACHE
new file mode 120000
index 0000000..965b606
--- /dev/null
+++ b/arg_picker_macros/LICENSE-APACHE
@@ -0,0 +1 @@
+../LICENSE-APACHE \ No newline at end of file
diff --git a/arg_picker_macros/LICENSE-MIT b/arg_picker_macros/LICENSE-MIT
new file mode 120000
index 0000000..76219eb
--- /dev/null
+++ b/arg_picker_macros/LICENSE-MIT
@@ -0,0 +1 @@
+../LICENSE-MIT \ No newline at end of file
diff --git a/mingling_picker_macros/README.md b/arg_picker_macros/README.md
index 5a3f220..5a3f220 100644
--- a/mingling_picker_macros/README.md
+++ b/arg_picker_macros/README.md
diff --git a/mingling_picker_macros/src/arg.rs b/arg_picker_macros/src/arg.rs
index 70b27b0..55860c4 100644
--- a/mingling_picker_macros/src/arg.rs
+++ b/arg_picker_macros/src/arg.rs
@@ -73,7 +73,7 @@ pub(crate) fn arg(input: TokenStream) -> TokenStream {
let import = quote! { ::mingling::picker::PickerArg };
#[cfg(not(feature = "mingling_support"))]
- let import = quote! { ::mingling_picker::PickerArg };
+ let import = quote! { ::arg_picker::PickerArg };
if ty.is_some() {
quote! { #import::<#ty_ts> }
diff --git a/mingling_picker_macros/src/internal_repeat.rs b/arg_picker_macros/src/internal_repeat.rs
index 4b2242b..4b2242b 100644
--- a/mingling_picker_macros/src/internal_repeat.rs
+++ b/arg_picker_macros/src/internal_repeat.rs
diff --git a/mingling_picker_macros/src/lib.rs b/arg_picker_macros/src/lib.rs
index a12e3ed..18cce84 100644
--- a/mingling_picker_macros/src/lib.rs
+++ b/arg_picker_macros/src/lib.rs
@@ -18,7 +18,7 @@ pub fn internal_repeat(input: TokenStream) -> TokenStream {
/// # Syntax
///
/// ```ignore
-/// use mingling_picker_macros::flag;
+/// use arg_picker_macros::flag;
///
/// let basic = arg![name: String];
/// let with_short_name = arg![name: String, 'n'];
diff --git a/docs/_zh_CN/pages/11-resource-system.md b/docs/_zh_CN/pages/11-resource-system.md
index 152e5d6..a8aab4d 100644
--- a/docs/_zh_CN/pages/11-resource-system.md
+++ b/docs/_zh_CN/pages/11-resource-system.md
@@ -57,7 +57,7 @@ fn render_path(result: ResultPath) -> RenderResult {
#[chain]
fn handle_visit(_args: EntryVisit, counter: &mut ResVisitCount) -> Next {
counter.0 += 1;
- ResultDone::default()
+ ResultDone::default().into()
}
#[renderer]
diff --git a/docs/_zh_CN/pages/3-define-a-chain.md b/docs/_zh_CN/pages/3-define-a-chain.md
index 7ac5c60..f845d11 100644
--- a/docs/_zh_CN/pages/3-define-a-chain.md
+++ b/docs/_zh_CN/pages/3-define-a-chain.md
@@ -24,7 +24,7 @@ fn handle_greet(args: EntryGreet) -> Next {
// args 就是用户输入经过匹配后剩下的参数
let name = args.inner.first().cloned().unwrap_or_else(|| "World".to_string());
// 把结果包装成 Next,告诉调度器下一步去哪
- ResultName::new(name)
+ ResultName::new(name).into()
}
```
@@ -48,7 +48,7 @@ Chain 函数签名里写着它需要什么——`args: EntryGreet`
```rust
// pack!(ResultName = String) 大概生成了这样的代码
-#[derive(Groupped)]
+#[derive(Grouped)]
pub struct ResultName {
pub inner: String,
}
@@ -88,7 +88,7 @@ fn handle_greet(args: EntryGreet) -> Next {
.cloned()
.unwrap_or_else(|| "World".to_string());
- ResultName::new(name)
+ ResultName::new(name).into()
}
```
@@ -112,7 +112,7 @@ fn handle_greet(args: EntryGreet) -> Next {
.first()
.cloned()
.unwrap_or_else(|| "World".to_string());
- ResultName::new(name)
+ ResultName::new(name).into()
}
fn main() {
diff --git a/docs/_zh_CN/pages/4-render-result.md b/docs/_zh_CN/pages/4-render-result.md
index 7cf2d2b..81c5102 100644
--- a/docs/_zh_CN/pages/4-render-result.md
+++ b/docs/_zh_CN/pages/4-render-result.md
@@ -51,7 +51,7 @@ fn handle_greet(args: EntryGreet) -> Next {
.first()
.cloned()
.unwrap_or_else(|| "World".to_string());
- ResultName::new(name)
+ ResultName::new(name).into()
}
// 4. 用 Renderer 输出结果
diff --git a/docs/_zh_CN/pages/5-multiple-commands.md b/docs/_zh_CN/pages/5-multiple-commands.md
index 7d2617b..38ce2cf 100644
--- a/docs/_zh_CN/pages/5-multiple-commands.md
+++ b/docs/_zh_CN/pages/5-multiple-commands.md
@@ -20,13 +20,13 @@ pack!(ResultSum = i32);
#[chain]
fn handle_greet(args: EntryGreet) -> Next {
let name = args.inner.first().cloned().unwrap_or_else(|| "World".to_string());
- ResultGreeting::new(name)
+ ResultGreeting::new(name).into()
}
#[chain]
fn handle_add(args: EntryAdd) -> Next {
let sum: i32 = args.inner.iter().filter_map(|s| s.parse::<i32>().ok()).sum();
- ResultSum::new(sum)
+ ResultSum::new(sum).into()
}
#[renderer]
@@ -70,9 +70,9 @@ Sum: 6
@@@dispatcher!("add", CMDAdd => EntryAdd);
@@@pack!(ResultGreeting = String);
@@@pack!(ResultSum = i32);
-@@@#[chain] fn handle_greet(_args: EntryGreet) -> Next { ResultGreeting::new("ok".into()) }
+@@@#[chain] fn handle_greet(_args: EntryGreet) -> Next { ResultGreeting::new("ok".into()).into() }
@@@#[renderer] fn render_greet(_greeting: ResultGreeting) -> RenderResult { RenderResult::new() }
-@@@#[chain] fn handle_add(_args: EntryAdd) -> Next { ResultSum::new(0) }
+@@@#[chain] fn handle_add(_args: EntryAdd) -> Next { ResultSum::new(0).into() }
@@@#[renderer] fn render_sum(_sum: ResultSum) -> RenderResult { RenderResult::new() }
fn main() {
let mut program = ThisProgram::new();
diff --git a/docs/_zh_CN/pages/6-argument-parse-picker.md b/docs/_zh_CN/pages/6-argument-parse-picker.md
index b41f839..4bf162c 100644
--- a/docs/_zh_CN/pages/6-argument-parse-picker.md
+++ b/docs/_zh_CN/pages/6-argument-parse-picker.md
@@ -32,7 +32,7 @@ features = ["parser"]
#[chain]
fn handle_greet_entry(prev: EntryGreet) -> Next {
let name = prev.pick_or((), "World").unpack();
- ResultName::new(name)
+ ResultName::new(name).into()
}
```
@@ -47,7 +47,7 @@ fn handle_greet_entry(prev: EntryGreet) -> Next {
@@@#[chain]
@@@fn handle_greet_entry(prev: EntryGreet) -> Next {
let name = prev.pick_or((), "World").unpack();
-@@@ResultName::new(name)
+@@@ResultName::new(name).into()
@@@}
```
@@ -82,7 +82,7 @@ let name = prev.pick_or((), "World").unpack();
#[chain]
fn handle_greet_entry(prev: EntryGreet) -> Next {
let name = prev.pick_or(["--name", "-n"], "World").unpack();
- ResultName::new(name)
+ ResultName::new(name).into()
}
```
@@ -124,7 +124,7 @@ fn handle_test_entry(prev: EntryTest) -> Next {
.pick::<u32>(["--id", "-I"])
.unpack();
- ResultInfo::new((name, age, id))
+ ResultInfo::new((name, age, id)).into()
}
```
@@ -199,7 +199,7 @@ let name = match pick_result {
在您使用 `pick` 提取了用户输入后,可以使用 `after` 立刻处理该参数
-```rust
+````rust
// Features: ["parser"]
@@@dispatcher!("greet", CMDGreet => EntryGreet);
@@@pack!(ResultName = String);
@@ -217,7 +217,7 @@ fn handle_greet_entry(prev: EntryGreet) -> Next {
})
.unpack();
- ResultName::new(name)
+ ResultName::new(name).into()
}
```
@@ -338,7 +338,7 @@ impl Pickable for Address {
#[chain]
fn handle_connect_entry(prev: EntryConnect) -> Next {
let address: Address = prev.pick("--addr").unpack();
- ResultConnected::new(address)
+ ResultConnected::new(address).into()
}
#[renderer]
@@ -379,7 +379,7 @@ impl PickableEnum for Fruits {}
#[chain]
fn handle_eat_entry(prev: EntryEat) -> Next {
let fruit: Fruits = prev.pick("--fruit").unpack();
- ResultFruit::new(fruit)
+ ResultFruit::new(fruit).into()
}
#[renderer]
diff --git a/docs/_zh_CN/pages/7-argument-parse-clap.md b/docs/_zh_CN/pages/7-argument-parse-clap.md
index 7dce301..21f886c 100644
--- a/docs/_zh_CN/pages/7-argument-parse-clap.md
+++ b/docs/_zh_CN/pages/7-argument-parse-clap.md
@@ -25,7 +25,7 @@ features = ["derive", "color"]
// Dependencies:
// clap = "4"
@@@ use mingling::macros::dispatcher_clap;
-#[derive(Default, clap::Parser, Groupped)]
+#[derive(Default, clap::Parser, Grouped)]
#[dispatcher_clap("greet", CMDGreet, help = true, error = ErrorGreetParsed)]
pub struct EntryGreet {
#[clap(default_value = "World")]
@@ -64,7 +64,7 @@ fn render_greet_parse_failed(err: ErrorGreetParsed) -> RenderResult {
// clap = "4"
@@@use mingling::setup::BasicProgramSetup;
@@@use mingling::macros::dispatcher_clap;
-@@@#[derive(Default, clap::Parser, Groupped)]
+@@@#[derive(Default, clap::Parser, Grouped)]
@@@#[dispatcher_clap("greet", CMDGreet)]
@@@pub struct EntryGreet {
@@@ name: String,
diff --git a/docs/_zh_CN/pages/advanced/2-structural-renderer.md b/docs/_zh_CN/pages/advanced/2-structural-renderer.md
index 9a4f111..70bed79 100644
--- a/docs/_zh_CN/pages/advanced/2-structural-renderer.md
+++ b/docs/_zh_CN/pages/advanced/2-structural-renderer.md
@@ -37,7 +37,7 @@ pack_structural!(ResultInfo = (String, i32));
fn handle_render(args: EntryRender) -> Next {
let name = args.inner.first().cloned().unwrap_or_default();
let age = args.inner.get(1).and_then(|s| s.parse().ok()).unwrap_or(0);
- ResultInfo::new((name, age))
+ ResultInfo::new((name, age)).into()
}
#[renderer]
@@ -62,7 +62,7 @@ fn render_info(r: ResultInfo) -> RenderResult {
## 自定义输出结构
-`pack_structural!` 的默认输出包含 `inner` 字段。要完全控制输出结构,可以用 `#[derive(StructuralData, Serialize, Groupped)]` 手动定义类型:
+`pack_structural!` 的默认输出包含 `inner` 字段。要完全控制输出结构,可以用 `#[derive(StructuralData, Serialize, Grouped)]` 手动定义类型:
```rust
// Features: ["structural_renderer"]
@@ -74,7 +74,7 @@ fn render_info(r: ResultInfo) -> RenderResult {
@@@use serde::Serialize;
@@@dispatcher!("render", CMDRender => EntryRender);
-#[derive(Serialize, StructuralData, Groupped)]
+#[derive(Serialize, StructuralData, Grouped)]
struct Info {
name: String,
age: i32,
diff --git a/docs/_zh_CN/pages/concepts/3-any-output.md b/docs/_zh_CN/pages/concepts/3-any-output.md
index 9b820da..118e856 100644
--- a/docs/_zh_CN/pages/concepts/3-any-output.md
+++ b/docs/_zh_CN/pages/concepts/3-any-output.md
@@ -20,7 +20,7 @@ AnyOutput<G>
这里的 `G` 就是 `gen_program!()` 生成的程序枚举(也就是你熟知的 `ThisProgram`)。
-每个被 `pack!` 或 `#[derive(Groupped)]` 标记的类型都被分配到这个枚举的一个变体。
+每个被 `pack!` 或 `#[derive(Grouped)]` 标记的类型都被分配到这个枚举的一个变体。
## ChainProcess:数据 + 路由
@@ -38,17 +38,17 @@ ChainProcess<G>
调度器根据 `NextProcess` 决定是继续循环还是退出渲染。
-## Groupped:谁是谁
+## Grouped:谁是谁
-调度器如何知道 `AnyOutput` 里装的是 `ResultName` 还是 `ErrorUserBlocked`?答案是 `Groupped` trait:
+调度器如何知道 `AnyOutput` 里装的是 `ResultName` 还是 `ErrorUserBlocked`?答案是 `Grouped` trait:
```
-trait Groupped<G> {
+trait Grouped<G> {
fn member_id() -> G;
}
```
-当你用 `pack!(ResultName = String)` 时,宏自动为 `ResultName` 实现 `Groupped`,`member_id()` 返回枚举中对应的变体。调度器一看 `member_id`,就去找对应的 Chain 或 Renderer。
+当你用 `pack!(ResultName = String)` 时,宏自动为 `ResultName` 实现 `Grouped`,`member_id()` 返回枚举中对应的变体。调度器一看 `member_id`,就去找对应的 Chain 或 Renderer。
`to_chain()` 和 `to_render()` 本质上是 `AnyOutput` 的快捷方法,分别构造 `ChainProcess::Ok(any, Chain)` 和 `ChainProcess::Ok(any, Renderer)`。
diff --git a/docs/_zh_CN/pages/other/features.md b/docs/_zh_CN/pages/other/features.md
index bfd9efc..8bd386c 100644
--- a/docs/_zh_CN/pages/other/features.md
+++ b/docs/_zh_CN/pages/other/features.md
@@ -24,7 +24,7 @@ pack!(StateFoo = ());
#[chain]
async fn handle_state_foo(foo: StateFoo) -> Next {
- StateFoo::new(())
+ StateFoo::new(()).into()
}
```
@@ -160,7 +160,7 @@ use mingling::macros::entry;
pack!(EntryHello = Vec<String>);
fn main() {
- let result = handle_hello(entry!("--name", "Bob")).into();
+ let result: Next = handle_hello(entry!("--name", "Bob")).into();
// ... 此处为断言逻辑
}
@@ -171,7 +171,7 @@ fn handle_hello(args: EntryHello) {}
### `group!`
将外部类型注册为程序组成员,无需修改原始类型的定义。
-类型名会直接作为枚举变体,与 `pack!` 或 `#[derive(Groupped)]` 一致。
+类型名会直接作为枚举变体,与 `pack!` 或 `#[derive(Grouped)]` 一致。
```rust
// Features: ["extra_macros"]
@@ -274,12 +274,24 @@ analyze_and_build_type_mapping().unwrap();
**介绍:**
-启用解析器模块,提供文本解析和语法分析功能。
+启用参数解析器模块,提供参数解析功能。
-开启后可以使用 `Picker` 进行零成本的参数提取,支持 `pick()` 和 `pick_or()` 等方法。
+开启后可以使用 `Picker` 进行简易的参数提取,支持 `pick()` 和 `pick_or()` 等方法。
详见 [示例](https://mingling-rs.github.io/mingling/docs/example-viewer.html?name=example-argument-parse)
+## 特性 `picker`
+
+**介绍:**
+
+引入依赖 `arg-picker`,为 Mingling 提供更高级的参数解析能力。
+
+它可以与 `parser`、`clap` 特性共存,但建议不要和 `parser` 特性同时启用,因为两者的 API 极为相似。
+
+`picker` 是独立于 Mingling 的参数解析器,不依赖 `mingling_core` 的内置参数提取 API。
+
+详见 [示例](https://mingling-rs.github.io/mingling/docs/example-viewer.html?name=example-argument-picker)
+
## 特性 `repl`
**介绍:**
diff --git a/docs/_zh_CN/pages/other/naming_rule.md b/docs/_zh_CN/pages/other/naming_rule.md
index 5f2ac34..c854ba1 100644
--- a/docs/_zh_CN/pages/other/naming_rule.md
+++ b/docs/_zh_CN/pages/other/naming_rule.md
@@ -96,7 +96,7 @@ Result + 内容
| `ResultGreetSomeone` | 问候结果 |
| `ResultFruitList` | 水果列表结果 |
-结果结构体期望被 Renderer 消费,内部结构应该为了渲染美观而设计。一般用 `#[derive(Groupped)]` 代替 `pack!()` 包装,以获得更灵活的字段控制。
+结果结构体期望被 Renderer 消费,内部结构应该为了渲染美观而设计。一般用 `#[derive(Grouped)]` 代替 `pack!()` 包装,以获得更灵活的字段控制。
### 错误
diff --git a/docs/dev/pages/abouts/ci.md b/docs/dev/pages/abouts/ci.md
index 3a93c1c..8b59b72 100644
--- a/docs/dev/pages/abouts/ci.md
+++ b/docs/dev/pages/abouts/ci.md
@@ -37,11 +37,11 @@ cargo ci
- **Test all examples**: Runs the `test-examples` tool.
- **Verify Markdown code blocks compile**: Runs the `test-all-markdown-code` tool to check code blocks in all `*.md` files. See [ABOUT_CODE_VERIFY](docs/_ABOUT_CODE_VERIFY.md) for details.
- **Check if documentation is up to date**: Runs the following documentation refresh tools in sequence:
- - `docs-code-box-fix`
- - `docsify-sidebar-gen`
- - `refresh-docs`
- - `refresh-feature-mod`
- - `sync-examples`
+ - `docs-code-box-fix`
+ - `docsify-sidebar-gen`
+ - `refresh-docs`
+ - `refresh-feature-mod`
+ - `sync-examples`
- Finally, runs `cargo fmt` to unify code formatting.
### 3. File Normalization
@@ -53,11 +53,11 @@ Runs `git add --renormalize .` to ensure file attributes such as line endings co
To ensure reproducible CI results, `ci.rs` imposes strict requirements on the workspace state:
- If the current workspace is not clean and `--dirty` has not been specified, the script will prompt whether to create a temporary commit:
- - The commit message is `[DO NOT PUSH] CI TEMP [DO NOT PUSH]`.
- - Use `-y` to auto-confirm without interaction.
+ - The commit message is `[DO NOT PUSH] CI TEMP [DO NOT PUSH]`.
+ - Use `-y` to auto-confirm without interaction.
- After CI finishes, the script automatically restores the workspace:
- - First, `git reset --hard` discards all changes.
- - If a temporary commit was created, it then runs `git reset --soft HEAD~1` and unstages everything, restoring the state to before CI started.
+ - First, `git reset --hard` discards all changes.
+ - If a temporary commit was created, it then runs `git reset --soft HEAD~1` and unstages everything, restoring the state to before CI started.
- If `--dirty` is specified, the temporary commit and the final cleanliness check are skipped.
> **Warning**: `git reset --hard` is executed at the end of CI. If you use `--dirty`, ensure you have no unsaved important changes.
@@ -69,3 +69,20 @@ To ensure reproducible CI results, `ci.rs` imposes strict requirements on the wo
- Triggered on `push` to the `main` branch.
- Runs `cargo ci` in parallel on `ubuntu-latest` and `windows-latest`.
- After CI passes, the `unreleased` tag is automatically moved to the latest commit on `main`.
+
+### 4. API Documentation Deployment
+
+After all checks pass, the `Deploy-Github-Pages` job:
+
+- **Runs `deploy-api-docs`**: Executes `cargo run --manifest-path .run/Cargo.toml --bin deploy-api-docs`, which reads the `[package.metadata.docs.rs]` features from `mingling/Cargo.toml` and builds the crate's documentation via `cargo doc --no-deps`. The output is placed at `docs/api-docs/`.
+- **Deploys to GitHub Pages**: The entire repository (including the generated `docs/api-docs/`) is uploaded and published to GitHub Pages.
+
+You can view the published API documentation at:
+
+> [https://mingling-rs.github.io/mingling/docs/api-docs/mingling/](https://mingling-rs.github.io/mingling/docs/api-docs/mingling/)
+
+To generate API docs locally:
+
+```bash
+cargo run --manifest-path .run/Cargo.toml --bin deploy-api-docs
+```
diff --git a/docs/dev/pages/issues/add-picker2.md b/docs/dev/pages/issues/add-picker2.md
index f5c3ca7..6fe4418 100644
--- a/docs/dev/pages/issues/add-picker2.md
+++ b/docs/dev/pages/issues/add-picker2.md
@@ -40,7 +40,7 @@ fn handle_hello(args: EntryHello) {
```rust
#[chain]
fn handle_hello(args: EntryHello) -> Next {
- // requires impl Groupped<_>
+ // requires impl Grouped<_>
// |
let parsed = args // vvvvvvvvvvvvvvvvvvv
.pick_route::<String, _>(positional!(), ErrorNoNameProvided)
diff --git a/docs/example-pages/examples.json b/docs/example-pages/examples.json
index a539011..2a07365 100644
--- a/docs/example-pages/examples.json
+++ b/docs/example-pages/examples.json
@@ -31,6 +31,21 @@
]
},
{
+ "id": "example-argument-picker",
+ "name": "Argument Picker",
+ "icon": "📋",
+ "category": "parsing",
+ "desc": "Demonstrates how to use Mingling's `picker` feature and `Picker` to extract typed arguments from the command line.\n",
+ "tags": [
+ "arg-picker",
+ "SinglePickable"
+ ],
+ "files": [
+ "src/main.rs",
+ "Cargo.toml"
+ ]
+ },
+ {
"id": "example-async-support",
"name": "Async Support",
"icon": "⚡",
diff --git a/docs/pages/11-resource-system.md b/docs/pages/11-resource-system.md
index bc2197f..9491212 100644
--- a/docs/pages/11-resource-system.md
+++ b/docs/pages/11-resource-system.md
@@ -57,7 +57,7 @@ Use `&mut T` to inject a mutable resource:
#[chain]
fn handle_visit(_args: EntryVisit, counter: &mut ResVisitCount) -> Next {
counter.0 += 1;
- ResultDone::default()
+ ResultDone::default().into()
}
#[renderer]
diff --git a/docs/pages/3-define-a-chain.md b/docs/pages/3-define-a-chain.md
index c6d3f8e..450522b 100644
--- a/docs/pages/3-define-a-chain.md
+++ b/docs/pages/3-define-a-chain.md
@@ -24,7 +24,7 @@ fn handle_greet(args: EntryGreet) -> Next {
// args contains the remaining params after matching user input
let name = args.inner.first().cloned().unwrap_or_else(|| "World".to_string());
// Wrap the result into Next, telling the dispatcher where to go next
- ResultName::new(name)
+ ResultName::new(name).into()
}
```
@@ -48,7 +48,7 @@ You've probably guessed it — `pack!(ResultName = String)` defines a type that
```rust
// pack!(ResultName = String) generates code roughly like this
-#[derive(Groupped)]
+#[derive(Grouped)]
pub struct ResultName {
pub inner: String,
}
@@ -88,7 +88,7 @@ fn handle_greet(args: EntryGreet) -> Next {
.cloned()
.unwrap_or_else(|| "World".to_string());
- ResultName::new(name)
+ ResultName::new(name).into()
}
```
@@ -112,7 +112,7 @@ fn handle_greet(args: EntryGreet) -> Next {
.first()
.cloned()
.unwrap_or_else(|| "World".to_string());
- ResultName::new(name)
+ ResultName::new(name).into()
}
fn main() {
diff --git a/docs/pages/4-render-result.md b/docs/pages/4-render-result.md
index b1365b8..ca1e563 100644
--- a/docs/pages/4-render-result.md
+++ b/docs/pages/4-render-result.md
@@ -51,7 +51,7 @@ fn handle_greet(args: EntryGreet) -> Next {
.first()
.cloned()
.unwrap_or_else(|| "World".to_string());
- ResultName::new(name)
+ ResultName::new(name).into()
}
// 4. Output results with a Renderer
diff --git a/docs/pages/5-multiple-commands.md b/docs/pages/5-multiple-commands.md
index a56f7b0..d9a335a 100644
--- a/docs/pages/5-multiple-commands.md
+++ b/docs/pages/5-multiple-commands.md
@@ -20,13 +20,13 @@ pack!(ResultSum = i32);
#[chain]
fn handle_greet(args: EntryGreet) -> Next {
let name = args.inner.first().cloned().unwrap_or_else(|| "World".to_string());
- ResultGreeting::new(name)
+ ResultGreeting::new(name).into()
}
#[chain]
fn handle_add(args: EntryAdd) -> Next {
let sum: i32 = args.inner.iter().filter_map(|s| s.parse::<i32>().ok()).sum();
- ResultSum::new(sum)
+ ResultSum::new(sum).into()
}
#[renderer]
@@ -70,9 +70,9 @@ Notice `with_dispatchers`? When you need to register multiple dispatchers, just
@@@dispatcher!("add", CMDAdd => EntryAdd);
@@@pack!(ResultGreeting = String);
@@@pack!(ResultSum = i32);
-@@@#[chain] fn handle_greet(_args: EntryGreet) -> Next { ResultGreeting::new("ok".into()) }
+@@@#[chain] fn handle_greet(_args: EntryGreet) -> Next { ResultGreeting::new("ok".into()).into() }
@@@#[renderer] fn render_greet(_greeting: ResultGreeting) -> RenderResult { RenderResult::new() }
-@@@#[chain] fn handle_add(_args: EntryAdd) -> Next { ResultSum::new(0) }
+@@@#[chain] fn handle_add(_args: EntryAdd) -> Next { ResultSum::new(0).into() }
@@@#[renderer] fn render_sum(_sum: ResultSum) -> RenderResult { RenderResult::new() }
fn main() {
let mut program = ThisProgram::new();
diff --git a/docs/pages/6-argument-parse-picker.md b/docs/pages/6-argument-parse-picker.md
index c80c25c..398cd3c 100644
--- a/docs/pages/6-argument-parse-picker.md
+++ b/docs/pages/6-argument-parse-picker.md
@@ -32,7 +32,7 @@ Now let's look at `Picker` in action:
#[chain]
fn handle_greet_entry(prev: EntryGreet) -> Next {
let name = prev.pick_or((), "World").unpack();
- ResultName::new(name)
+ ResultName::new(name).into()
}
```
@@ -47,7 +47,7 @@ Breaking down the example above:
@@@#[chain]
@@@fn handle_greet_entry(prev: EntryGreet) -> Next {
let name = prev.pick_or((), "World").unpack();
-@@@ResultName::new(name)
+@@@ResultName::new(name).into()
@@@}
```
@@ -82,7 +82,7 @@ If your program needs to parse flag args (e.g., `greet --name Alice`), do this:
#[chain]
fn handle_greet_entry(prev: EntryGreet) -> Next {
let name = prev.pick_or(["--name", "-n"], "World").unpack();
- ResultName::new(name)
+ ResultName::new(name).into()
}
```
@@ -124,7 +124,7 @@ fn handle_test_entry(prev: EntryTest) -> Next {
.pick::<u32>(["--id", "-I"])
.unpack();
- ResultInfo::new((name, age, id))
+ ResultInfo::new((name, age, id)).into()
}
```
@@ -224,7 +224,7 @@ fn handle_greet_entry(prev: EntryGreet) -> Next {
})
.unpack();
- ResultName::new(name)
+ ResultName::new(name).into()
}
```
@@ -345,7 +345,7 @@ impl Pickable for Address {
#[chain]
fn handle_connect_entry(prev: EntryConnect) -> Next {
let address: Address = prev.pick("--addr").unpack();
- ResultConnected::new(address)
+ ResultConnected::new(address).into()
}
#[renderer]
@@ -387,7 +387,7 @@ impl PickableEnum for Fruits {}
#[chain]
fn handle_eat_entry(prev: EntryEat) -> Next {
let fruit: Fruits = prev.pick("--fruit").unpack();
- ResultFruit::new(fruit)
+ ResultFruit::new(fruit).into()
}
#[renderer]
diff --git a/docs/pages/7-argument-parse-clap.md b/docs/pages/7-argument-parse-clap.md
index b11e00e..86fce35 100644
--- a/docs/pages/7-argument-parse-clap.md
+++ b/docs/pages/7-argument-parse-clap.md
@@ -25,7 +25,7 @@ Add `#[dispatcher_clap]` on a `clap::Parser` struct to auto-generate a Dispatche
// Dependencies:
// clap = "4"
@@@ use mingling::macros::dispatcher_clap;
-#[derive(Default, clap::Parser, Groupped)]
+#[derive(Default, clap::Parser, Grouped)]
#[dispatcher_clap("greet", CMDGreet, help = true, error = ErrorGreetParsed)]
pub struct EntryGreet {
#[clap(default_value = "World")]
@@ -64,7 +64,7 @@ If you need `--help` support, register `BasicProgramSetup` in main and set the c
// clap = "4"
@@@use mingling::setup::BasicProgramSetup;
@@@use mingling::macros::dispatcher_clap;
-@@@#[derive(Default, clap::Parser, Groupped)]
+@@@#[derive(Default, clap::Parser, Grouped)]
@@@#[dispatcher_clap("greet", CMDGreet)]
@@@pub struct EntryGreet {
@@@ name: String,
diff --git a/docs/pages/advanced/2-structural-renderer.md b/docs/pages/advanced/2-structural-renderer.md
index f31f758..910b197 100644
--- a/docs/pages/advanced/2-structural-renderer.md
+++ b/docs/pages/advanced/2-structural-renderer.md
@@ -37,7 +37,7 @@ pack_structural!(ResultInfo = (String, i32));
fn handle_render(args: EntryRender) -> Next {
let name = args.inner.first().cloned().unwrap_or_default();
let age = args.inner.get(1).and_then(|s| s.parse().ok()).unwrap_or(0);
- ResultInfo::new((name, age))
+ ResultInfo::new((name, age)).into()
}
#[renderer]
@@ -62,7 +62,7 @@ When the user passes `--json`, the framework automatically serializes the render
## Customizing Output Structure
-The default output from `pack_structural!` includes an `inner` field. For full control over the output structure, define the type manually with `#[derive(StructuralData, Serialize, Groupped)]`:
+The default output from `pack_structural!` includes an `inner` field. For full control over the output structure, define the type manually with `#[derive(StructuralData, Serialize, Grouped)]`:
```rust
// Features: ["structural_renderer"]
@@ -74,7 +74,7 @@ The default output from `pack_structural!` includes an `inner` field. For full c
@@@use serde::Serialize;
@@@dispatcher!("render", CMDRender => EntryRender);
-#[derive(Serialize, StructuralData, Groupped)]
+#[derive(Serialize, StructuralData, Grouped)]
struct Info {
name: String,
age: i32,
diff --git a/docs/pages/concepts/3-any-output.md b/docs/pages/concepts/3-any-output.md
index f780377..f02805f 100644
--- a/docs/pages/concepts/3-any-output.md
+++ b/docs/pages/concepts/3-any-output.md
@@ -20,7 +20,7 @@ AnyOutput<G>
Here `G` is the program enum generated by `gen_program!()` (i.e., `ThisProgram` as you know it).
-Each type annotated with `pack!` or `#[derive(Groupped)]` is assigned to one variant of this enum.
+Each type annotated with `pack!` or `#[derive(Grouped)]` is assigned to one variant of this enum.
## ChainProcess: Data + Routing
@@ -38,17 +38,17 @@ This is why a Chain function returns `ChainProcess` instead of raw data—it bun
The dispatcher reads `NextProcess` to decide whether to continue the loop or exit to rendering.
-## Groupped: Who Is Who
+## Grouped: Who Is Who
-How does the dispatcher know whether an `AnyOutput` holds a `ResultName` or an `ErrorUserBlocked`? The answer is the `Groupped` trait:
+How does the dispatcher know whether an `AnyOutput` holds a `ResultName` or an `ErrorUserBlocked`? The answer is the `Grouped` trait:
```
-trait Groupped<G> {
+trait Grouped<G> {
fn member_id() -> G;
}
```
-When you use `pack!(ResultName = String)`, the macro automatically implements `Groupped` for `ResultName`, and `member_id()` returns the corresponding enum variant. The dispatcher looks at `member_id` and finds the matching Chain or Renderer.
+When you use `pack!(ResultName = String)`, the macro automatically implements `Grouped` for `ResultName`, and `member_id()` returns the corresponding enum variant. The dispatcher looks at `member_id` and finds the matching Chain or Renderer.
`to_chain()` and `to_render()` are essentially convenience methods on `AnyOutput` that construct `ChainProcess::Ok(any, Chain)` and `ChainProcess::Ok(any, Renderer)` respectively.
diff --git a/docs/pages/other/features.md b/docs/pages/other/features.md
index 813ccdd..7994d4c 100644
--- a/docs/pages/other/features.md
+++ b/docs/pages/other/features.md
@@ -24,7 +24,7 @@ pack!(StateFoo = ());
#[chain]
async fn handle_state_foo(foo: StateFoo) -> Next {
- StateFoo::new(())
+ StateFoo::new(()).into()
}
```
@@ -160,7 +160,7 @@ use mingling::macros::entry;
pack!(EntryHello = Vec<String>);
fn main() {
- let result = handle_hello(entry!("--name", "Bob")).into();
+ let result: Next = handle_hello(entry!("--name", "Bob")).into();
// ... assertion logic here
}
@@ -171,7 +171,7 @@ fn handle_hello(args: EntryHello) {}
### `group!`
Registers an external type as a member of the program group without modifying its definition.
-The type's simple name is used as the enum variant, just like `pack!` or `#[derive(Groupped)]`.
+The type's simple name is used as the enum variant, just like `pack!` or `#[derive(Grouped)]`.
```rust
// Features: ["extra_macros"]
@@ -274,12 +274,24 @@ See [example](https://mingling-rs.github.io/mingling/docs/example-viewer.html?na
**Description:**
-Enables the parser module, providing text parsing and analysis capabilities.
+Enables the argument parser module, providing argument parsing functionality.
-When enabled, you can use `Picker` for zero-cost argument extraction, supporting methods like `pick()` and `pick_or()`.
+When enabled, you can use `Picker` for simple argument extraction, supporting methods like `pick()` and `pick_or()`.
See [example](https://mingling-rs.github.io/mingling/docs/example-viewer.html?name=example-argument-parse)
+## Feature `picker`
+
+**Description:**
+
+Introduces the `arg-picker` dependency, providing more advanced argument parsing capabilities for Mingling.
+
+It can coexist with the `parser` and `clap` features, but it is recommended not to enable it alongside the `parser` feature, as their APIs are very similar.
+
+`picker` is an argument parser independent of Mingling and does not rely on the built-in argument extraction API of `mingling_core`.
+
+See [example](https://mingling-rs.github.io/mingling/docs/example-viewer.html?name=example-argument-picker)
+
## Feature `repl`
**Description:**
diff --git a/docs/pages/other/naming_rule.md b/docs/pages/other/naming_rule.md
index 2ede61f..089a711 100644
--- a/docs/pages/other/naming_rule.md
+++ b/docs/pages/other/naming_rule.md
@@ -96,7 +96,7 @@ Result + Content
| `ResultGreetSomeone` | Greeting result |
| `ResultFruitList` | Fruit list result |
-Result structs are expected to be consumed by the Renderer, and their internal structure should be designed for rendering aesthetics. Generally use `#[derive(Groupped)]` instead of `pack!()` wrapping for more flexible field control.
+Result structs are expected to be consumed by the Renderer, and their internal structure should be designed for rendering aesthetics. Generally use `#[derive(Grouped)]` instead of `pack!()` wrapping for more flexible field control.
### Error
diff --git a/examples/example-argument-parse/src/main.rs b/examples/example-argument-parse/src/main.rs
index 59499a2..eb07672 100644
--- a/examples/example-argument-parse/src/main.rs
+++ b/examples/example-argument-parse/src/main.rs
@@ -46,7 +46,7 @@ fn handle_transfer_parse(args: EntryTransfer) -> Next {
// Convert into ResultFile
.into();
// --------- IMPORTANT ---------
- result
+ result.into()
}
pack!(ErrorNoNameProvided = ());
diff --git a/examples/example-argument-picker/Cargo.lock b/examples/example-argument-picker/Cargo.lock
new file mode 100644
index 0000000..9a9baa4
--- /dev/null
+++ b/examples/example-argument-picker/Cargo.lock
@@ -0,0 +1,94 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "arg-picker"
+version = "0.1.0"
+dependencies = [
+ "arg-picker-macros",
+ "just_fmt",
+]
+
+[[package]]
+name = "arg-picker-macros"
+version = "0.1.0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "example-argument-picker"
+version = "0.1.0"
+dependencies = [
+ "mingling",
+]
+
+[[package]]
+name = "just_fmt"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6170dccbc3ea15dfb7f2da964097f814aba1dd8f746d4ffc56f33245c38e6d96"
+
+[[package]]
+name = "mingling"
+version = "0.3.0"
+dependencies = [
+ "arg-picker",
+ "mingling_core",
+ "mingling_macros",
+]
+
+[[package]]
+name = "mingling_core"
+version = "0.3.0"
+dependencies = [
+ "just_fmt",
+]
+
+[[package]]
+name = "mingling_macros"
+version = "0.3.0"
+dependencies = [
+ "just_fmt",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.107"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.47"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "syn"
+version = "2.0.119"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
diff --git a/examples/example-argument-picker/Cargo.toml b/examples/example-argument-picker/Cargo.toml
new file mode 100644
index 0000000..686b95b
--- /dev/null
+++ b/examples/example-argument-picker/Cargo.toml
@@ -0,0 +1,12 @@
+[package]
+name = "example-argument-picker"
+version = "0.1.0"
+edition = "2024"
+
+[dependencies.mingling]
+path = "../../mingling"
+
+# Enable `picker` features
+features = ["picker", "extra_macros"]
+
+[workspace]
diff --git a/examples/example-argument-picker/page.toml b/examples/example-argument-picker/page.toml
new file mode 100644
index 0000000..563bccc
--- /dev/null
+++ b/examples/example-argument-picker/page.toml
@@ -0,0 +1,10 @@
+[example]
+id = "example-argument-picker"
+name = "Argument Picker"
+icon = "📋"
+category = "parsing"
+desc = """
+Demonstrates how to use Mingling's `picker` feature and `Picker` to extract typed arguments from the command line.
+"""
+tags = ["arg-picker", "SinglePickable"]
+files = ["src/main.rs", "Cargo.toml"]
diff --git a/examples/example-argument-picker/src/main.rs b/examples/example-argument-picker/src/main.rs
new file mode 100644
index 0000000..7fcc5db
--- /dev/null
+++ b/examples/example-argument-picker/src/main.rs
@@ -0,0 +1,225 @@
+//! Example Argument Picker
+//!
+//! > Demonstrates how to use Mingling's `picker` feature and `Picker` to extract typed arguments from the command line.
+//!
+//! Run:
+//! ```bash
+//! cargo run --manifest-path examples/example-argument-picker/Cargo.toml --quiet -- calc 1 + 1
+//! cargo run --manifest-path examples/example-argument-picker/Cargo.toml --quiet -- calc 7 * 7
+//! cargo run --manifest-path examples/example-argument-picker/Cargo.toml --quiet -- calc
+//! cargo run --manifest-path examples/example-argument-picker/Cargo.toml --quiet -- calc 1
+//! cargo run --manifest-path examples/example-argument-picker/Cargo.toml --quiet -- calc 1 +
+//! cargo run --manifest-path examples/example-argument-picker/Cargo.toml --quiet -- calc 4 / 3
+//! cargo run --manifest-path examples/example-argument-picker/Cargo.toml --quiet -- calc 4 / 3 --round
+//! ```
+//!
+//! Output:
+//! ```plaintext
+//! Result: 2
+//! Result: 49
+//! Error: First number (number_a) was not provided.
+//! Error: Operator was not provided.
+//! Error: Second number (number_b) was not provided.
+//! Result: 1.3333334
+//! Result: 1
+//! ```
+
+use mingling::{
+ consts::REMAINS,
+ macros::route,
+ picker::{
+ IntoPicker, PickerArgResult, SinglePickable,
+ parselib::{ParserStyle, UNIX_STYLE},
+ value::Flag,
+ },
+ prelude::*,
+};
+
+// --------- IMPORTANT ---------
+// Use picker::BasicProgramSetup instead of the original BasicProgramSetup
+// It uses arg-picker to rewrite the logic of the original BasicProgramSetup
+use mingling::setup::picker::BasicProgramSetup;
+
+// --------- IMPORTANT ---------
+
+dispatcher!("calc", CMDCalculate => EntryCalculate);
+
+pack_err!(ErrorNumberANotProvided);
+pack_err!(ErrorNumberBNotProvided);
+pack_err!(ErrorNumberOperatorNotProvided);
+pack_err!(ErrorDivisionByZero);
+
+pack!(StateAdd = (f32, f32));
+pack!(StateSubtract = (f32, f32));
+pack!(StateMultiply = (f32, f32));
+pack!(StateDivide = (f32, f32));
+
+pack!(ResultNumber = f32);
+
+#[derive(Grouped)]
+struct StateCalculate {
+ number_a: f32,
+ operator: Operator,
+ number_b: f32,
+}
+
+#[derive(Debug, PartialEq, Eq)]
+enum Operator {
+ Plus,
+ Dash,
+ Slash,
+ Star,
+}
+
+// --------- IMPORTANT ---------
+// Define SinglePickable for type Operator
+// This allows the type to be picked as an argument
+impl SinglePickable for Operator {
+ fn pick_single(str: Option<&str>) -> PickerArgResult<Self> {
+ let Some(str) = str else {
+ return PickerArgResult::NotFound;
+ };
+ let op = match str.chars().next() {
+ Some('+') => Operator::Plus,
+ Some('-') => Operator::Dash,
+ Some('*') => Operator::Star,
+ Some('/') => Operator::Slash,
+ _ => return PickerArgResult::NotFound,
+ };
+ PickerArgResult::Parsed(op)
+ }
+}
+// --------- IMPORTANT ---------
+
+#[derive(Default, Clone)]
+struct ResNumberDisplaySetting {
+ round: bool,
+}
+
+fn main() {
+ let mut program = ThisProgram::new();
+
+ // Use ParserStyle to manage the arg-picker theme
+ ParserStyle::set_global_style(&UNIX_STYLE);
+
+ // Enable picker::BasicProgramSetup
+ program.with_setup(BasicProgramSetup);
+
+ // --------- IMPORTANT ---------
+ // Pre-process global arguments before executing commands
+ let (round, args) = program
+ .take_args()
+ // Use arg![round: Flag] to indicate the `--round` | `-R` flag
+ // |
+ // vvvvvvvvvvvvvvvv
+ .pick(&arg![round: Flag, 'R'])
+ // Use REMAINS to extract remaining arguments
+ // |
+ // vvvvvvvv
+ .pick(&REMAINS)
+ // Since Flag and REMAINS will not fail to parse,
+ // we can safely unwrap here
+ .unwrap();
+ program.replace_args(args.into());
+
+ program.with_resource(ResNumberDisplaySetting { round: *round });
+ // --------- IMPORTANT ---------
+
+ program.with_dispatcher(CMDCalculate);
+ program.exec_and_exit();
+}
+
+#[chain]
+fn handle_calc(args: EntryCalculate) -> Next {
+ // --------- IMPORTANT ---------
+ let (number_a, operator, number_b) = route!(
+ // Use the arg! macro to define a positional argument of type f32
+ // |
+ // vvvvvvvvvv
+ args.pick_or_route(&arg![f32], || ErrorNumberANotProvided::default().to_chain())
+ .pick_or_route(&arg![Operator], || {
+ ErrorNumberOperatorNotProvided::default().to_chain()
+ }) // Returns a routable type when not found or fails to parse
+ // |
+ // vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
+ .pick_or_route(&arg![f32], || ErrorNumberBNotProvided::default().to_chain())
+ // Use `to_result` to parse arguments
+ // and convert to Result<(Tuple, ...), Route> type
+ .to_result()
+ );
+ // --------- IMPORTANT ---------
+
+ if operator == Operator::Slash && number_b == 0. {
+ return ErrorDivisionByZero::default().to_chain();
+ }
+
+ StateCalculate {
+ number_a,
+ operator,
+ number_b,
+ }
+ .to_chain()
+}
+
+#[chain]
+fn handle_state_calculate(state: StateCalculate) -> Next {
+ match (state.operator, state.number_a, state.number_b) {
+ (Operator::Plus, a, b) => StateAdd::new((a, b)).to_chain(),
+ (Operator::Dash, a, b) => StateSubtract::new((a, b)).to_chain(),
+ (Operator::Slash, a, b) => StateDivide::new((a, b)).to_chain(),
+ (Operator::Star, a, b) => StateMultiply::new((a, b)).to_chain(),
+ }
+}
+
+#[chain]
+fn handle_state_add(state_add: StateAdd) -> ResultNumber {
+ let (a, b) = state_add.inner;
+ ResultNumber::new(a + b)
+}
+
+#[chain]
+fn handle_state_subtract(state_subtract: StateSubtract) -> ResultNumber {
+ let (a, b) = state_subtract.inner;
+ ResultNumber::new(a - b)
+}
+
+#[chain]
+fn handle_state_multiply(state_multiply: StateMultiply) -> ResultNumber {
+ let (a, b) = state_multiply.inner;
+ ResultNumber::new(a * b)
+}
+
+#[chain]
+fn handle_state_divide(state_divide: StateDivide) -> ResultNumber {
+ let (a, b) = state_divide.inner;
+ ResultNumber::new(a / b)
+}
+
+#[renderer]
+fn render_result_number(result: ResultNumber, setting: &ResNumberDisplaySetting) -> String {
+ let round = setting.round;
+ let result = if round { result.round() } else { result.inner };
+ format!("Result: {}", result)
+}
+
+#[renderer]
+fn render_error_division_by_zero(_: ErrorDivisionByZero) -> String {
+ "Error: Division by zero is not allowed!".to_string()
+}
+
+#[renderer]
+fn render_error_number_a_not_provided(_: ErrorNumberANotProvided) -> String {
+ "Error: First number (number_a) was not provided.".to_string()
+}
+
+#[renderer]
+fn render_error_number_b_not_provided(_: ErrorNumberBNotProvided) -> String {
+ "Error: Second number (number_b) was not provided.".to_string()
+}
+
+#[renderer]
+fn render_error_number_operator_not_provided(_: ErrorNumberOperatorNotProvided) -> String {
+ "Error: Operator was not provided.".to_string()
+}
+
+gen_program!();
diff --git a/examples/example-async-support/src/main.rs b/examples/example-async-support/src/main.rs
index ac85aaf..af86408 100644
--- a/examples/example-async-support/src/main.rs
+++ b/examples/example-async-support/src/main.rs
@@ -50,7 +50,7 @@ pack!(ResultDownloaded = String);
// vvvvv_ `async` keyword can be used directly here
pub async fn handle_download(args: EntryDownload) -> Next {
let file_name = args.pick(()).unpack();
- fake_download(file_name).await
+ fake_download(file_name).await.into()
}
/// Renders the downloaded file name.
diff --git a/examples/example-basic/src/main.rs b/examples/example-basic/src/main.rs
index 7fc0bce..17077e2 100644
--- a/examples/example-basic/src/main.rs
+++ b/examples/example-basic/src/main.rs
@@ -55,7 +55,7 @@ fn handle_greet(args: EntryGreet) -> Next {
.cloned()
.unwrap_or_else(|| "World".to_string())
.into();
- name
+ name.into()
}
// Define renderer `render_name`, used to render `ResultName`
diff --git a/examples/example-clap-binding/src/main.rs b/examples/example-clap-binding/src/main.rs
index d99f8d1..0f839c8 100644
--- a/examples/example-clap-binding/src/main.rs
+++ b/examples/example-clap-binding/src/main.rs
@@ -38,7 +38,7 @@
//! For more information, try '--help'.
//! ```
-use mingling::{Groupped, macros::dispatcher_clap, prelude::*, setup::BasicProgramSetup};
+use mingling::{Grouped, macros::dispatcher_clap, prelude::*, setup::BasicProgramSetup};
use std::io::Write;
fn main() {
@@ -67,10 +67,10 @@ fn main() {
// Implement Clap Parser, and bind to Dispatcher
// _______________________________ Default trait, provides fallback on parse failure
// / ______________________ clap::Parser, parsing logic implemented by Clap
-// | / ________ Implement mingling::Groupped
+// | / ________ Implement mingling::Grouped
// | | / to ensure Mingling can recognize the type
-// vvvvvvv vvvvvvvvvvvv vvvvvvvv
-#[derive(Default, clap::Parser, Groupped)]
+// vvvvvvv vvvvvvvvvvvv vvvvvvv
+#[derive(Default, clap::Parser, Grouped)]
#[dispatcher_clap(
"greet", CMDGreet, // Bind EntryGreet to "greet" command
help = true, // Generate clap help for EntryGreet
diff --git a/examples/example-combine-pathf-dispatch-tree/src/sub/mod.rs b/examples/example-combine-pathf-dispatch-tree/src/sub/mod.rs
index 5ab0ece..2b7aba9 100644
--- a/examples/example-combine-pathf-dispatch-tree/src/sub/mod.rs
+++ b/examples/example-combine-pathf-dispatch-tree/src/sub/mod.rs
@@ -14,7 +14,7 @@ pub fn handle_my(args: EntryHello) -> Next {
.cloned()
.unwrap_or_else(|| "World".to_string())
.into();
- name
+ name.into()
}
#[renderer]
diff --git a/examples/example-completion/src/main.rs b/examples/example-completion/src/main.rs
index 0159807..45cc8ef 100644
--- a/examples/example-completion/src/main.rs
+++ b/examples/example-completion/src/main.rs
@@ -114,7 +114,7 @@ fn handle_greet(args: EntryGreet) -> Next {
.pick_or((), "World")
.unpack()
.into();
- result
+ result.into()
}
/// Renders the greeting with the result name and repeat count.
diff --git a/examples/example-custom-pickable/src/main.rs b/examples/example-custom-pickable/src/main.rs
index d203815..b163278 100644
--- a/examples/example-custom-pickable/src/main.rs
+++ b/examples/example-custom-pickable/src/main.rs
@@ -14,15 +14,15 @@
//! Failed to parse address
//! ```
-use mingling::{macros::route, parser::Pickable, prelude::*, Groupped};
+use mingling::{macros::route, parser::Pickable, prelude::*, Grouped};
use std::io::Write;
// Define types that can be recognized by Mingling
// ________________________ `Pickable` trait needs to implement Default
-// / ________ The Groupped derive macro registers an ID for this type
+// / ________ The Grouped derive macro registers an ID for this type
// | / Mingling uses this ID to identify the type
-// vvvvvvv vvvvvvvv
-#[derive(Debug, Default, Clone, Groupped)]
+// vvvvvvv vvvvvvv
+#[derive(Debug, Default, Clone, Grouped)]
pub struct Address {
pub ip: [u8; 4],
pub port: u16,
diff --git a/examples/example-enum-tag/src/main.rs b/examples/example-enum-tag/src/main.rs
index 01c7767..b57511d 100644
--- a/examples/example-enum-tag/src/main.rs
+++ b/examples/example-enum-tag/src/main.rs
@@ -16,7 +16,7 @@
//! ```
use mingling::{
- macros::suggest_enum, parser::PickableEnum, prelude::*, EnumTag, Groupped, ShellContext,
+ macros::suggest_enum, parser::PickableEnum, prelude::*, EnumTag, Grouped, ShellContext,
Suggest,
};
use std::io::Write;
@@ -25,7 +25,7 @@ use std::io::Write;
// ________ adds metadata to the enum, enabling it to:
// / 1. Be used by the `suggest_enum!(Enum)` macro under the `comp` feature for autocompletion
// vvvvvvv 2. Implement the `PickableEnum` trait
-#[derive(Debug, Default, EnumTag, Groupped)]
+#[derive(Debug, Default, EnumTag, Grouped)]
pub enum ProgrammingLanguages {
#[enum_desc("An efficient and flexible compiled language widely used for system programming")]
C,
@@ -79,7 +79,7 @@ dispatcher!("lang-select", CMDLanguageSelection => EntryLanguageSelection);
fn handle_language_selection(args: EntryLanguageSelection) -> Next {
// You can use Picker to directly parse ProgrammingLanguages
let lang: ProgrammingLanguages = args.pick(()).unpack();
- lang
+ lang.into()
}
/// Renders the selected programming language with its name and description.
diff --git a/examples/example-hook/src/main.rs b/examples/example-hook/src/main.rs
index 41928ca..23b87c7 100644
--- a/examples/example-hook/src/main.rs
+++ b/examples/example-hook/src/main.rs
@@ -64,7 +64,7 @@ fn handle_greet(args: EntryGreet) -> Next {
.cloned()
.unwrap_or_else(|| "World".to_string())
.into();
- name
+ name.into()
}
/// Renders the greeting message with the provided name.
diff --git a/examples/example-outside-type/src/main.rs b/examples/example-outside-type/src/main.rs
index 925878a..3159d19 100644
--- a/examples/example-outside-type/src/main.rs
+++ b/examples/example-outside-type/src/main.rs
@@ -71,7 +71,7 @@ fn render_number(num: ParsedNumber) -> RenderResult {
/// Renderer for parse errors — using the outside `ParseIntError` type.
///
/// The `ParseIntError` type is registered via `group!` above, so it implements
-/// `Groupped<ThisProgram>` and can be used directly in a `#[renderer]` function.
+/// `Grouped<ThisProgram>` and can be used directly in a `#[renderer]` function.
#[renderer]
fn render_parse_error(err: ParseIntError) -> RenderResult {
let mut render_result = RenderResult::new();
diff --git a/examples/example-panic-unwind/src/main.rs b/examples/example-panic-unwind/src/main.rs
index a829158..d5f746f 100644
--- a/examples/example-panic-unwind/src/main.rs
+++ b/examples/example-panic-unwind/src/main.rs
@@ -47,7 +47,7 @@ fn handle_panic(prev: EntryPanic) -> Next {
// Panic happens here, will be caught
panic!("{}", s)
}
- None => NotPanic::default(),
+ None => NotPanic::default().into(),
}
}
diff --git a/examples/example-pathfinder/src/sub/mod.rs b/examples/example-pathfinder/src/sub/mod.rs
index 6d15930..8cbc1c5 100644
--- a/examples/example-pathfinder/src/sub/mod.rs
+++ b/examples/example-pathfinder/src/sub/mod.rs
@@ -13,7 +13,7 @@ pub fn handle_greet(args: EntryGreet) -> Next {
.cloned()
.unwrap_or_else(|| "World".to_string())
.into();
- name
+ name.into()
}
/// Renders the name.
diff --git a/examples/example-repl-basic/src/main.rs b/examples/example-repl-basic/src/main.rs
index d15319e..cfd00d1 100644
--- a/examples/example-repl-basic/src/main.rs
+++ b/examples/example-repl-basic/src/main.rs
@@ -94,7 +94,7 @@ pack!(ResultList = Vec<String>);
#[chain]
fn parse_cd_args(prev: EntryCd) -> Next {
let join = prev.pick(()).unpack();
- StateChangeDirectory::new(join)
+ StateChangeDirectory::new(join).into()
}
// Execute directory change
diff --git a/examples/example-resources/src/main.rs b/examples/example-resources/src/main.rs
index b08ee26..60c5c08 100644
--- a/examples/example-resources/src/main.rs
+++ b/examples/example-resources/src/main.rs
@@ -51,7 +51,7 @@ fn render_modify_current(args: EntryModifyCurrent, current_dir: &mut ResCurrentD
current_dir.current_dir = current_dir
.current_dir
.join(args.pick::<String>(()).unpack());
- EntryCurrent::default()
+ EntryCurrent::default().into()
}
// Define renderer for output current path _____________ Injected resource
diff --git a/examples/example-structural-renderer/src/main.rs b/examples/example-structural-renderer/src/main.rs
index 13c4c84..070e75d 100644
--- a/examples/example-structural-renderer/src/main.rs
+++ b/examples/example-structural-renderer/src/main.rs
@@ -18,7 +18,7 @@
//! ```
use mingling::prelude::*;
-use mingling::{Groupped, StructuralData, parser::Picker, setup::StructuralRendererSetup};
+use mingling::{parser::Picker, setup::StructuralRendererSetup, Grouped, StructuralData};
use serde::Serialize;
use std::io::Write;
@@ -35,12 +35,12 @@ fn main() {
// --------- IMPORTANT ---------
// For beautiful output structure, do not use `pack!` to wrap the types that need to be output.
// Instead, manually implement
-// __________________________________ Mark as structured data so it can be rendered
-// / ____________________ Implement serde::Serialize
-// | / _________ Implement mingling::Groupped
-// | | / to ensure Mingling can recognize the type
-// vvvvvvvvvvvv vvvvvvvvv vvvvvvvv
-#[derive(StructuralData, Serialize, Groupped)]
+// ____________________________________ Mark as structured data so it can be rendered
+// / ____________________ Implement serde::Serialize
+// | / _________ Implement mingling::Grouped
+// | | / to ensure Mingling can recognize the type
+// vvvvvvvvvvvv vvvvvvvvv vvvvvvv
+#[derive(StructuralData, Serialize, Grouped)]
struct Info {
#[serde(rename = "member_name")]
name: String,
diff --git a/examples/full-todolist/src/todolist.rs b/examples/full-todolist/src/todolist.rs
index 71338c3..d3582e3 100644
--- a/examples/full-todolist/src/todolist.rs
+++ b/examples/full-todolist/src/todolist.rs
@@ -1,13 +1,13 @@
//! Data structures, read and write logic for the todo list
-use mingling::{Groupped, RenderResult, macros::renderer};
+use mingling::{Grouped, RenderResult, macros::renderer};
use serde::{Deserialize, Serialize};
use std::io::Write;
use std::{env::current_dir, path::PathBuf};
use crate::ResProgramFlags;
-#[derive(Debug, Serialize, Deserialize, Clone, Default, Groupped)]
+#[derive(Debug, Serialize, Deserialize, Clone, Default, Grouped)]
pub struct ResTodoList {
pub items: Vec<Todo>,
}
diff --git a/examples/test-examples.toml b/examples/test-examples.toml
index d2fe602..e450919 100644
--- a/examples/test-examples.toml
+++ b/examples/test-examples.toml
@@ -277,3 +277,38 @@ expect.result = "Hello, Alice!"
command = "hello"
expect.exit-code = 0
expect.result = "Hello, World!"
+
+[[test.example-argument-picker]]
+command = "calc 1 + 1"
+expect.exit-code = 0
+expect.result = "Result: 2"
+
+[[test.example-argument-picker]]
+command = "calc 7 * 7"
+expect.exit-code = 0
+expect.result = "Result: 49"
+
+[[test.example-argument-picker]]
+command = "calc"
+expect.exit-code = 0
+expect.result = "Error: First number (number_a) was not provided."
+
+[[test.example-argument-picker]]
+command = "calc 1"
+expect.exit-code = 0
+expect.result = "Error: Operator was not provided."
+
+[[test.example-argument-picker]]
+command = "calc 1 +"
+expect.exit-code = 0
+expect.result = "Error: Second number (number_b) was not provided."
+
+[[test.example-argument-picker]]
+command = "calc 4 / 3"
+expect.exit-code = 0
+expect.result = "Result: 1.3333334"
+
+[[test.example-argument-picker]]
+command = "calc 4 / 3 --round"
+expect.exit-code = 0
+expect.result = "Result: 1"
diff --git a/mingling/Cargo.toml b/mingling/Cargo.toml
index c3f9511..adf900f 100644
--- a/mingling/Cargo.toml
+++ b/mingling/Cargo.toml
@@ -50,7 +50,7 @@ dispatch_tree = ["mingling_core/dispatch_tree", "mingling_macros/dispatch_tree"]
repl = ["mingling_core/repl", "mingling_macros/repl"]
comp = ["mingling_core/comp", "mingling_macros/comp"]
parser = ["dep:size"]
-picker = ["dep:mingling_picker", "mingling_picker/mingling_support"]
+picker = ["dep:arg-picker", "arg-picker/mingling_support"]
pathf = ["mingling_core/pathf", "mingling_macros/pathf"]
structural_renderer = [
@@ -90,6 +90,6 @@ extra_macros = ["mingling_macros/extra_macros"]
[dependencies]
mingling_core = { workspace = true, optional = true }
mingling_macros = { workspace = true, optional = true }
-mingling_picker = { workspace = true, optional = true }
+arg-picker = { workspace = true, optional = true }
serde = { workspace = true, optional = true }
size = { version = "0.5", optional = true }
diff --git a/mingling/src/constants.rs b/mingling/src/constants.rs
new file mode 100644
index 0000000..65d02e8
--- /dev/null
+++ b/mingling/src/constants.rs
@@ -0,0 +1,8 @@
+#[cfg(feature = "picker")]
+mod picker;
+
+#[cfg(feature = "picker")]
+pub use picker::*;
+
+mod exit_codes;
+pub use exit_codes::*;
diff --git a/mingling/src/constants/exit_codes.rs b/mingling/src/constants/exit_codes.rs
new file mode 100644
index 0000000..2359f42
--- /dev/null
+++ b/mingling/src/constants/exit_codes.rs
@@ -0,0 +1,14 @@
+/// Exit code indicating successful command execution.
+pub const EXIT_SUCCESS: i32 = 0;
+
+/// Exit code for general errors.
+pub const EXIT_GENERAL_ERR: i32 = 1;
+
+/// Exit code for incorrect command usage (or invalid arguments).
+pub const EXIT_USAGE_ERR: i32 = 2;
+
+/// Exit code indicating permission denied (or the command is not executable).
+pub const EXIT_PERM_DENIED: i32 = 126;
+
+/// Exit code for command not found (or PATH error).
+pub const EXIT_CMD_NOT_FOUND: i32 = 127;
diff --git a/mingling/src/constants/picker.rs b/mingling/src/constants/picker.rs
new file mode 100644
index 0000000..f2a448b
--- /dev/null
+++ b/mingling/src/constants/picker.rs
@@ -0,0 +1,129 @@
+use std::marker::PhantomData;
+
+use arg_picker::{PickerArg, PickerArgs, value::Flag};
+
+/// Remaining positional arguments (anything not consumed as an option).
+/// - `full`: `[]` (empty — not triggered by any `--` prefix).
+/// - `short`: (none)
+/// - `positional`: `false` (this is a meta‑argument that collects everything left).
+/// This constant is used internally to access any leftover arguments after
+/// all defined flags/options have been processed.
+pub const REMAINS: PickerArg<PickerArgs> = PickerArg::<PickerArgs> {
+ full: &[],
+ short: None,
+ positional: false,
+ internal_type: PhantomData,
+};
+
+/// Help flag: display usage information.
+/// - `full`: `["help"]`
+/// - `short`: `'h'`
+pub const HELP_FLAG: PickerArg<Flag> = PickerArg::<Flag> {
+ full: &["help"],
+ short: Some('h'),
+ positional: false,
+ internal_type: PhantomData,
+};
+
+/// Quiet flag: suppress output.
+/// - `full`: `["quiet"]`
+/// - `short`: `'q'`
+pub const QUIET_FLAG: PickerArg<Flag> = PickerArg::<Flag> {
+ full: &["quiet"],
+ short: Some('q'),
+ positional: false,
+ internal_type: PhantomData,
+};
+
+/// Confirm flag: require user confirmation before proceeding.
+/// - `full`: `["confirm"]`
+/// - `short`: `'C'`
+pub const CONFIRM_FLAG: PickerArg<Flag> = PickerArg::<Flag> {
+ full: &["confirm"],
+ short: Some('C'),
+ positional: false,
+ internal_type: PhantomData,
+};
+
+/// Renderer flag: explicitly specify the Structural Renderer argument.
+/// - `full`: `["renderer"]`
+/// - `short`: (none)
+#[cfg(feature = "structural_renderer")]
+pub const RENDERER_ARG: PickerArg<String> = PickerArg::<String> {
+ full: &["renderer"],
+ short: None,
+ positional: false,
+ internal_type: PhantomData,
+};
+
+/// JSON flag: enable JSON output format.
+/// - `full`: `["json"]`
+/// - `short`: (none)
+/// Available only when the `json_serde_fmt` feature is enabled.
+#[cfg(feature = "json_serde_fmt")]
+pub const JSON_FLAG: PickerArg<Flag> = PickerArg::<Flag> {
+ full: &["json"],
+ short: None,
+ positional: false,
+ internal_type: PhantomData,
+};
+
+/// JSON pretty flag: enable pretty-printed JSON output format.
+/// - `full`: `["json_pretty"]`
+/// - `short`: (none)
+/// Available only when the `json_serde_fmt` feature is enabled.
+#[cfg(feature = "json_serde_fmt")]
+pub const JSON_PRETTY_FLAG: PickerArg<Flag> = PickerArg::<Flag> {
+ full: &["json_pretty"],
+ short: None,
+ positional: false,
+ internal_type: PhantomData,
+};
+
+/// YAML flag: enable YAML output format.
+/// - `full`: `["yaml"]`
+/// - `short`: (none)
+/// Available only when the `yaml_serde_fmt` feature is enabled.
+#[cfg(feature = "yaml_serde_fmt")]
+pub const YAML_FLAG: PickerArg<Flag> = PickerArg::<Flag> {
+ full: &["yaml"],
+ short: None,
+ positional: false,
+ internal_type: PhantomData,
+};
+
+/// TOML flag: enable TOML output format.
+/// - `full`: `["toml"]`
+/// - `short`: (none)
+/// Available only when the `toml_serde_fmt` feature is enabled.
+#[cfg(feature = "toml_serde_fmt")]
+pub const TOML_FLAG: PickerArg<Flag> = PickerArg::<Flag> {
+ full: &["toml"],
+ short: None,
+ positional: false,
+ internal_type: PhantomData,
+};
+
+/// RON flag: enable RON output format.
+/// - `full`: `["ron"]`
+/// - `short`: (none)
+/// Available only when the `ron_serde_fmt` feature is enabled.
+#[cfg(feature = "ron_serde_fmt")]
+pub const RON_FLAG: PickerArg<Flag> = PickerArg::<Flag> {
+ full: &["ron"],
+ short: None,
+ positional: false,
+ internal_type: PhantomData,
+};
+
+/// RON pretty flag: enable pretty-printed RON output format.
+/// - `full`: `["ron_pretty"]`
+/// - `short`: (none)
+/// Available only when the `ron_serde_fmt` feature is enabled.
+#[cfg(feature = "ron_serde_fmt")]
+pub const RON_PRETTY_FLAG: PickerArg<Flag> = PickerArg::<Flag> {
+ full: &["ron_pretty"],
+ short: None,
+ positional: false,
+ internal_type: PhantomData,
+};
diff --git a/mingling/src/example_docs.rs b/mingling/src/example_docs.rs
index 4699a50..bdefb5a 100644
--- a/mingling/src/example_docs.rs
+++ b/mingling/src/example_docs.rs
@@ -66,7 +66,7 @@
/// // Convert into ResultFile
/// .into();
/// // --------- IMPORTANT ---------
-/// result
+/// result.into()
/// }
///
/// pack!(ErrorNoNameProvided = ());
@@ -124,6 +124,251 @@
/// }
/// ```
pub mod example_argument_parse {}
+/// Example Argument Picker
+///
+/// > Demonstrates how to use Mingling's `picker` feature and `Picker` to extract typed arguments from the command line.
+///
+/// Run:
+/// ```bash
+/// cargo run --manifest-path examples/example-argument-picker/Cargo.toml --quiet -- calc 1 + 1
+/// cargo run --manifest-path examples/example-argument-picker/Cargo.toml --quiet -- calc 7 * 7
+/// cargo run --manifest-path examples/example-argument-picker/Cargo.toml --quiet -- calc
+/// cargo run --manifest-path examples/example-argument-picker/Cargo.toml --quiet -- calc 1
+/// cargo run --manifest-path examples/example-argument-picker/Cargo.toml --quiet -- calc 1 +
+/// cargo run --manifest-path examples/example-argument-picker/Cargo.toml --quiet -- calc 4 / 3
+/// cargo run --manifest-path examples/example-argument-picker/Cargo.toml --quiet -- calc 4 / 3 --round
+/// ```
+///
+/// Output:
+/// ```plaintext
+/// Result: 2
+/// Result: 49
+/// Error: First number (number_a) was not provided.
+/// Error: Operator was not provided.
+/// Error: Second number (number_b) was not provided.
+/// Result: 1.3333334
+/// Result: 1
+/// ```
+///
+/// Source code (./Cargo.toml)
+/// ```toml
+/// [package]
+/// name = "example-argument-picker"
+/// version = "0.1.0"
+/// edition = "2024"
+///
+/// [dependencies.mingling]
+/// path = "../../mingling"
+///
+/// # Enable `picker` features
+/// features = ["picker", "extra_macros"]
+///
+/// [workspace]
+/// ```
+///
+/// Source code (./src/main.rs)
+/// ```ignore
+/// use mingling::{
+/// consts::REMAINS,
+/// macros::route,
+/// picker::{
+/// IntoPicker, PickerArgResult, SinglePickable,
+/// parselib::{ParserStyle, UNIX_STYLE},
+/// value::Flag,
+/// },
+/// prelude::*,
+/// };
+///
+/// // --------- IMPORTANT ---------
+/// // Use picker::BasicProgramSetup instead of the original BasicProgramSetup
+/// // It uses arg-picker to rewrite the logic of the original BasicProgramSetup
+/// use mingling::setup::picker::BasicProgramSetup;
+///
+/// // --------- IMPORTANT ---------
+///
+/// dispatcher!("calc", CMDCalculate => EntryCalculate);
+///
+/// pack_err!(ErrorNumberANotProvided);
+/// pack_err!(ErrorNumberBNotProvided);
+/// pack_err!(ErrorNumberOperatorNotProvided);
+/// pack_err!(ErrorDivisionByZero);
+///
+/// pack!(StateAdd = (f32, f32));
+/// pack!(StateSubtract = (f32, f32));
+/// pack!(StateMultiply = (f32, f32));
+/// pack!(StateDivide = (f32, f32));
+///
+/// pack!(ResultNumber = f32);
+///
+/// #[derive(Grouped)]
+/// struct StateCalculate {
+/// number_a: f32,
+/// operator: Operator,
+/// number_b: f32,
+/// }
+///
+/// #[derive(Debug, PartialEq, Eq)]
+/// enum Operator {
+/// Plus,
+/// Dash,
+/// Slash,
+/// Star,
+/// }
+///
+/// // --------- IMPORTANT ---------
+/// // Define SinglePickable for type Operator
+/// // This allows the type to be picked as an argument
+/// impl SinglePickable for Operator {
+/// fn pick_single(str: Option<&str>) -> PickerArgResult<Self> {
+/// let Some(str) = str else {
+/// return PickerArgResult::NotFound;
+/// };
+/// let op = match str.chars().next() {
+/// Some('+') => Operator::Plus,
+/// Some('-') => Operator::Dash,
+/// Some('*') => Operator::Star,
+/// Some('/') => Operator::Slash,
+/// _ => return PickerArgResult::NotFound,
+/// };
+/// PickerArgResult::Parsed(op)
+/// }
+/// }
+/// // --------- IMPORTANT ---------
+///
+/// #[derive(Default, Clone)]
+/// struct ResNumberDisplaySetting {
+/// round: bool,
+/// }
+///
+/// fn main() {
+/// let mut program = ThisProgram::new();
+///
+/// // Use ParserStyle to manage the arg-picker theme
+/// ParserStyle::set_global_style(&UNIX_STYLE);
+///
+/// // Enable picker::BasicProgramSetup
+/// program.with_setup(BasicProgramSetup);
+///
+/// // --------- IMPORTANT ---------
+/// // Pre-process global arguments before executing commands
+/// let (round, args) = program
+/// .take_args()
+/// // Use arg![round: Flag] to indicate the `--round` | `-R` flag
+/// // |
+/// // vvvvvvvvvvvvvvvv
+/// .pick(&arg![round: Flag, 'R'])
+/// // Use REMAINS to extract remaining arguments
+/// // |
+/// // vvvvvvvv
+/// .pick(&REMAINS)
+/// // Since Flag and REMAINS will not fail to parse,
+/// // we can safely unwrap here
+/// .unwrap();
+/// program.replace_args(args.into());
+///
+/// program.with_resource(ResNumberDisplaySetting { round: *round });
+/// // --------- IMPORTANT ---------
+///
+/// program.with_dispatcher(CMDCalculate);
+/// program.exec_and_exit();
+/// }
+///
+/// #[chain]
+/// fn handle_calc(args: EntryCalculate) -> Next {
+/// // --------- IMPORTANT ---------
+/// let (number_a, operator, number_b) = route!(
+/// // Use the arg! macro to define a positional argument of type f32
+/// // |
+/// // vvvvvvvvvv
+/// args.pick_or_route(&arg![f32], || ErrorNumberANotProvided::default().to_chain())
+/// .pick_or_route(&arg![Operator], || {
+/// ErrorNumberOperatorNotProvided::default().to_chain()
+/// }) // Returns a routable type when not found or fails to parse
+/// // |
+/// // vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
+/// .pick_or_route(&arg![f32], || ErrorNumberBNotProvided::default().to_chain())
+/// // Use `to_result` to parse arguments
+/// // and convert to Result<(Tuple, ...), Route> type
+/// .to_result()
+/// );
+/// // --------- IMPORTANT ---------
+///
+/// if operator == Operator::Slash && number_b == 0. {
+/// return ErrorDivisionByZero::default().to_chain();
+/// }
+///
+/// StateCalculate {
+/// number_a,
+/// operator,
+/// number_b,
+/// }
+/// .to_chain()
+/// }
+///
+/// #[chain]
+/// fn handle_state_calculate(state: StateCalculate) -> Next {
+/// match (state.operator, state.number_a, state.number_b) {
+/// (Operator::Plus, a, b) => StateAdd::new((a, b)).to_chain(),
+/// (Operator::Dash, a, b) => StateSubtract::new((a, b)).to_chain(),
+/// (Operator::Slash, a, b) => StateDivide::new((a, b)).to_chain(),
+/// (Operator::Star, a, b) => StateMultiply::new((a, b)).to_chain(),
+/// }
+/// }
+///
+/// #[chain]
+/// fn handle_state_add(state_add: StateAdd) -> ResultNumber {
+/// let (a, b) = state_add.inner;
+/// ResultNumber::new(a + b)
+/// }
+///
+/// #[chain]
+/// fn handle_state_subtract(state_subtract: StateSubtract) -> ResultNumber {
+/// let (a, b) = state_subtract.inner;
+/// ResultNumber::new(a - b)
+/// }
+///
+/// #[chain]
+/// fn handle_state_multiply(state_multiply: StateMultiply) -> ResultNumber {
+/// let (a, b) = state_multiply.inner;
+/// ResultNumber::new(a * b)
+/// }
+///
+/// #[chain]
+/// fn handle_state_divide(state_divide: StateDivide) -> ResultNumber {
+/// let (a, b) = state_divide.inner;
+/// ResultNumber::new(a / b)
+/// }
+///
+/// #[renderer]
+/// fn render_result_number(result: ResultNumber, setting: &ResNumberDisplaySetting) -> String {
+/// let round = setting.round;
+/// let result = if round { result.round() } else { result.inner };
+/// format!("Result: {}", result)
+/// }
+///
+/// #[renderer]
+/// fn render_error_division_by_zero(_: ErrorDivisionByZero) -> String {
+/// "Error: Division by zero is not allowed!".to_string()
+/// }
+///
+/// #[renderer]
+/// fn render_error_number_a_not_provided(_: ErrorNumberANotProvided) -> String {
+/// "Error: First number (number_a) was not provided.".to_string()
+/// }
+///
+/// #[renderer]
+/// fn render_error_number_b_not_provided(_: ErrorNumberBNotProvided) -> String {
+/// "Error: Second number (number_b) was not provided.".to_string()
+/// }
+///
+/// #[renderer]
+/// fn render_error_number_operator_not_provided(_: ErrorNumberOperatorNotProvided) -> String {
+/// "Error: Operator was not provided.".to_string()
+/// }
+///
+/// gen_program!();
+/// ```
+pub mod example_argument_picker {}
/// Example Async Runtime Support
///
/// > This example shows how to drive an async runtime using the `async` feature
@@ -199,7 +444,7 @@ pub mod example_argument_parse {}
/// // vvvvv_ `async` keyword can be used directly here
/// pub async fn handle_download(args: EntryDownload) -> Next {
/// let file_name = args.pick(()).unpack();
-/// fake_download(file_name).await
+/// fake_download(file_name).await.into()
/// }
///
/// /// Renders the downloaded file name.
@@ -292,7 +537,7 @@ pub mod example_async_support {}
/// .cloned()
/// .unwrap_or_else(|| "World".to_string())
/// .into();
-/// name
+/// name.into()
/// }
///
/// // Define renderer `render_name`, used to render `ResultName`
@@ -378,7 +623,7 @@ pub mod example_basic {}
///
/// Source code (./src/main.rs)
/// ```ignore
-/// use mingling::{Groupped, macros::dispatcher_clap, prelude::*, setup::BasicProgramSetup};
+/// use mingling::{Grouped, macros::dispatcher_clap, prelude::*, setup::BasicProgramSetup};
/// use std::io::Write;
///
/// fn main() {
@@ -407,10 +652,10 @@ pub mod example_basic {}
/// // Implement Clap Parser, and bind to Dispatcher
/// // _______________________________ Default trait, provides fallback on parse failure
/// // / ______________________ clap::Parser, parsing logic implemented by Clap
-/// // | / ________ Implement mingling::Groupped
+/// // | / ________ Implement mingling::Grouped
/// // | | / to ensure Mingling can recognize the type
-/// // vvvvvvv vvvvvvvvvvvv vvvvvvvv
-/// #[derive(Default, clap::Parser, Groupped)]
+/// // vvvvvvv vvvvvvvvvvvv vvvvvvv
+/// #[derive(Default, clap::Parser, Grouped)]
/// #[dispatcher_clap(
/// "greet", CMDGreet, // Bind EntryGreet to "greet" command
/// help = true, // Generate clap help for EntryGreet
@@ -672,7 +917,7 @@ pub mod example_combine_pathf_dispatch_tree {}
/// .pick_or((), "World")
/// .unpack()
/// .into();
-/// result
+/// result.into()
/// }
///
/// /// Renders the greeting with the result name and repeat count.
@@ -724,15 +969,15 @@ pub mod example_completion {}
///
/// Source code (./src/main.rs)
/// ```ignore
-/// use mingling::{macros::route, parser::Pickable, prelude::*, Groupped};
+/// use mingling::{macros::route, parser::Pickable, prelude::*, Grouped};
/// use std::io::Write;
///
/// // Define types that can be recognized by Mingling
/// // ________________________ `Pickable` trait needs to implement Default
-/// // / ________ The Groupped derive macro registers an ID for this type
+/// // / ________ The Grouped derive macro registers an ID for this type
/// // | / Mingling uses this ID to identify the type
-/// // vvvvvvv vvvvvvvv
-/// #[derive(Debug, Default, Clone, Groupped)]
+/// // vvvvvvv vvvvvvv
+/// #[derive(Debug, Default, Clone, Grouped)]
/// pub struct Address {
/// pub ip: [u8; 4],
/// pub port: u16,
@@ -963,7 +1208,7 @@ pub mod example_dispatch_tree {}
/// Source code (./src/main.rs)
/// ```ignore
/// use mingling::{
-/// macros::suggest_enum, parser::PickableEnum, prelude::*, EnumTag, Groupped, ShellContext,
+/// macros::suggest_enum, parser::PickableEnum, prelude::*, EnumTag, Grouped, ShellContext,
/// Suggest,
/// };
/// use std::io::Write;
@@ -972,7 +1217,7 @@ pub mod example_dispatch_tree {}
/// // ________ adds metadata to the enum, enabling it to:
/// // / 1. Be used by the `suggest_enum!(Enum)` macro under the `comp` feature for autocompletion
/// // vvvvvvv 2. Implement the `PickableEnum` trait
-/// #[derive(Debug, Default, EnumTag, Groupped)]
+/// #[derive(Debug, Default, EnumTag, Grouped)]
/// pub enum ProgrammingLanguages {
/// #[enum_desc("An efficient and flexible compiled language widely used for system programming")]
/// C,
@@ -1026,7 +1271,7 @@ pub mod example_dispatch_tree {}
/// fn handle_language_selection(args: EntryLanguageSelection) -> Next {
/// // You can use Picker to directly parse ProgrammingLanguages
/// let lang: ProgrammingLanguages = args.pick(()).unpack();
-/// lang
+/// lang.into()
/// }
///
/// /// Renders the selected programming language with its name and description.
@@ -1428,7 +1673,7 @@ pub mod example_help {}
/// .cloned()
/// .unwrap_or_else(|| "World".to_string())
/// .into();
-/// name
+/// name.into()
/// }
///
/// /// Renders the greeting message with the provided name.
@@ -1691,7 +1936,7 @@ pub mod example_lazy_resources {}
/// /// Renderer for parse errors — using the outside `ParseIntError` type.
/// ///
/// /// The `ParseIntError` type is registered via `group!` above, so it implements
-/// /// `Groupped<ThisProgram>` and can be used directly in a `#[renderer]` function.
+/// /// `Grouped<ThisProgram>` and can be used directly in a `#[renderer]` function.
/// #[renderer]
/// fn render_parse_error(err: ParseIntError) -> RenderResult {
/// let mut render_result = RenderResult::new();
@@ -1967,7 +2212,7 @@ pub mod example_pack_err {}
/// // Panic happens here, will be caught
/// panic!("{}", s)
/// }
-/// None => NotPanic::default(),
+/// None => NotPanic::default().into(),
/// }
/// }
///
@@ -2161,7 +2406,7 @@ pub mod example_pathfinder {}
/// #[chain]
/// fn parse_cd_args(prev: EntryCd) -> Next {
/// let join = prev.pick(()).unpack();
-/// StateChangeDirectory::new(join)
+/// StateChangeDirectory::new(join).into()
/// }
///
/// // Execute directory change
@@ -2323,7 +2568,7 @@ pub mod example_repl_basic {}
/// current_dir.current_dir = current_dir
/// .current_dir
/// .join(args.pick::<String>(()).unpack());
-/// EntryCurrent::default()
+/// EntryCurrent::default().into()
/// }
///
/// // Define renderer for output current path _____________ Injected resource
@@ -2436,7 +2681,7 @@ pub mod example_setup {}
/// Source code (./src/main.rs)
/// ```ignore
/// use mingling::prelude::*;
-/// use mingling::{Groupped, StructuralData, parser::Picker, setup::StructuralRendererSetup};
+/// use mingling::{parser::Picker, setup::StructuralRendererSetup, Grouped, StructuralData};
/// use serde::Serialize;
/// use std::io::Write;
///
@@ -2453,12 +2698,12 @@ pub mod example_setup {}
/// // --------- IMPORTANT ---------
/// // For beautiful output structure, do not use `pack!` to wrap the types that need to be output.
/// // Instead, manually implement
-/// // __________________________________ Mark as structured data so it can be rendered
-/// // / ____________________ Implement serde::Serialize
-/// // | / _________ Implement mingling::Groupped
-/// // | | / to ensure Mingling can recognize the type
-/// // vvvvvvvvvvvv vvvvvvvvv vvvvvvvv
-/// #[derive(StructuralData, Serialize, Groupped)]
+/// // ____________________________________ Mark as structured data so it can be rendered
+/// // / ____________________ Implement serde::Serialize
+/// // | / _________ Implement mingling::Grouped
+/// // | | / to ensure Mingling can recognize the type
+/// // vvvvvvvvvvvv vvvvvvvvv vvvvvvv
+/// #[derive(StructuralData, Serialize, Grouped)]
/// struct Info {
/// #[serde(rename = "member_name")]
/// name: String,
diff --git a/mingling/src/features.rs b/mingling/src/features.rs
index 32ca5de..cb474ae 100644
--- a/mingling/src/features.rs
+++ b/mingling/src/features.rs
@@ -53,6 +53,17 @@ pub const MINGLING_COMP: bool = false;
#[cfg(feature = "comp")]
#[allow(unused)]
pub const MINGLING_COMP: bool = true;
+/// Whether the `core` feature is enabled
+/// Current: `disabled`
+#[cfg(not(feature = "core"))]
+#[allow(unused)]
+pub const MINGLING_CORE: bool = false;
+
+/// Whether the `core` feature is enabled
+/// Current: `enabled`
+#[cfg(feature = "core")]
+#[allow(unused)]
+pub const MINGLING_CORE: bool = true;
/// Whether the `debug` feature is enabled
/// Current: `disabled`
#[cfg(not(feature = "debug"))]
@@ -108,6 +119,17 @@ pub const MINGLING_JSON_SERDE_FMT: bool = false;
#[cfg(feature = "json_serde_fmt")]
#[allow(unused)]
pub const MINGLING_JSON_SERDE_FMT: bool = true;
+/// Whether the `macros` feature is enabled
+/// Current: `disabled`
+#[cfg(not(feature = "macros"))]
+#[allow(unused)]
+pub const MINGLING_MACROS: bool = false;
+
+/// Whether the `macros` feature is enabled
+/// Current: `enabled`
+#[cfg(feature = "macros")]
+#[allow(unused)]
+pub const MINGLING_MACROS: bool = true;
/// Whether the `nightly` feature is enabled
/// Current: `disabled`
#[cfg(not(feature = "nightly"))]
diff --git a/mingling/src/lib.md b/mingling/src/lib.md
new file mode 100644
index 0000000..03fa61d
--- /dev/null
+++ b/mingling/src/lib.md
@@ -0,0 +1,96 @@
+<h1 align="center">Mìng Lìng - 命令</h1>
+
+<p align="center">
+ <b>/mɪŋ lɪŋ/</b>
+</p>
+
+<p align="center">
+ Macro magician in your CLI.
+</p>
+
+## Intro
+
+[`Mingling`](https://github.com/mingling-rs/mingling) is a **proc-macro and type system-based** Rust CLI framework, suitable for developing complex command-line programs with numerous subcommands.
+
+## Use
+
+Here is a basic project written using **Mingling**:
+
+- When the user types `greet`, the program outputs `Hello, World!`
+- When the user types `greet Alice`, the program outputs `Hello, Alice!`
+
+```rust
+use mingling::prelude::*;
+
+dispatcher!("greet", CMDGreet => EntryGreet);
+
+fn main() {
+ let mut program = ThisProgram::new();
+ program.with_dispatcher(CMDGreet);
+ program.exec_and_exit();
+}
+
+pack!(ResultName = String);
+
+#[chain]
+fn handle_greet(args: EntryGreet) -> Next {
+ let name: ResultName = args
+ .inner
+ .first()
+ .cloned()
+ .unwrap_or_else(|| "World".to_string())
+ .into();
+ name.into()
+}
+
+#[renderer]
+fn render_name(name: ResultName) -> RenderResult {
+ let mut result = RenderResult::default();
+ result.println(&format!("Hello, {}!", *name));
+ result
+}
+
+#[renderer]
+fn render_dispatcher_not_found(err: ErrorDispatcherNotFound) -> RenderResult {
+ let mut result = RenderResult::default();
+ if err.len() > 0 {
+ result.println(&format!("Command not found: [{}]", err.join(" ")));
+ }
+ result
+}
+
+gen_program!();
+```
+
+Output:
+
+```text
+> mycmd greet
+Hello, World!
+> mycmd greet Alice
+Hello, Alice!
+> mycmd great
+Command not found: [great]
+```
+
+## Examples
+
+`Mingling` provides detailed usage examples for your reference.
+See [Examples](_mingling_examples/index.html) or [Helpdoc](https://mingling-rs.github.io/mingling/docs/examples.html)
+
+## About Features
+
+All features of `Mingling` are opt-in. To learn what each feature provides, see [Features](feature/index.html) or [Helpdoc](https://mingling-rs.github.io/mingling/docs/doc.html#/pages/other/features)
+
+## Use unreleased version
+
+If you want to use the latest development version, you can pull from the [Unreleased](https://github.com/mingling-rs/mingling/tree/unreleased) branch on [GitHub](https://github.com/mingling-rs/mingling) by adding the following to your `Cargo.toml`:
+
+```toml
+[dependencies.mingling]
+git = "https://github.com/mingling-rs/mingling.git"
+tag = "unreleased"
+features = []
+```
+
+**Please note** that the documentation for the latest version may differ from this article. It is recommended to refer to the [latest documentation](https://mingling-rs.github.io/mingling/docs/api-docs/mingling/).
diff --git a/mingling/src/lib.rs b/mingling/src/lib.rs
index ba87f85..f4bc9dc 100644
--- a/mingling/src/lib.rs
+++ b/mingling/src/lib.rs
@@ -1,73 +1,4 @@
-//! Mingling
-//!
-//! # Intro
-//! [`Mingling`](https://github.com/mingling-rs/mingling) is a **proc-macro and type system-based** Rust CLI framework, suitable for developing complex command-line programs with numerous subcommands.
-//!
-//! # Use
-//! Here is a basic project written using **Mingling**:
-//! - When the user types `greet`, the program outputs `Hello, World!`
-//! - When the user types `greet Alice`, the program outputs `Hello, Alice!`
-//!
-//! ```rust
-//! use mingling::prelude::*;
-//!
-//! dispatcher!("greet", CMDGreet => EntryGreet);
-//!
-//! fn main() {
-//! let mut program = ThisProgram::new();
-//! program.with_dispatcher(CMDGreet);
-//! program.exec_and_exit();
-//! }
-//!
-//! pack!(ResultName = String);
-//!
-//! #[chain]
-//! fn handle_greet(args: EntryGreet) -> Next {
-//! let name: ResultName = args
-//! .inner
-//! .first()
-//! .cloned()
-//! .unwrap_or_else(|| "World".to_string())
-//! .into();
-//! name
-//! }
-//!
-//! #[renderer]
-//! fn render_name(name: ResultName) -> RenderResult {
-//! let mut result = RenderResult::default();
-//! result.println(&format!("Hello, {}!", *name));
-//! result
-//! }
-//!
-//! #[renderer]
-//! fn render_dispatcher_not_found(err: ErrorDispatcherNotFound) -> RenderResult {
-//! let mut result = RenderResult::default();
-//! if err.len() > 0 {
-//! result.println(&format!("Command not found: [{}]", err.join(" ")));
-//! }
-//! result
-//! }
-//!
-//! gen_program!();
-//! ```
-//!
-//! Output:
-//!
-//! ```text
-//! > mycmd greet
-//! Hello, World!
-//! > mycmd greet Alice
-//! Hello, Alice!
-//! > mycmd great
-//! Command not found: [great]
-//! ```
-//!
-//! # Examples
-//! `Mingling` provides detailed usage examples for your reference.
-//! See [Examples](_mingling_examples/index.html) or [Helpdoc](https://mingling-rs.github.io/mingling/docs/examples.html)
-//!
-//! # About Features
-//! All features of `Mingling` are opt-in. To learn what each feature provides, see [Features](feature/index.html) or [Helpdoc](https://mingling-rs.github.io/mingling/docs/doc.html#/pages/other/features)
+#![doc = include_str!("lib.md")]
#[cfg(feature = "core")]
mod example_docs;
@@ -85,12 +16,13 @@ pub mod parser;
/// `Mingling` argument parser (Picker2)
#[cfg(feature = "picker")]
-pub mod picker {
- pub use mingling_picker::*;
+pub mod picker;
- pub mod parselib {
- pub use mingling_picker::parselib::*;
- }
+mod constants;
+
+/// Constants used throughout the Mingling framework.
+pub mod consts {
+ pub use crate::constants::*;
}
/// Re-export of all macros from `mingling_macros`.
@@ -107,6 +39,9 @@ pub mod picker {
#[allow(unused_imports)]
#[cfg(feature = "macros")]
pub mod macros {
+ /// New Parser provided by the `picker` feature
+ #[cfg(feature = "picker")]
+ pub use arg_picker::macros::*;
/// `#[chain]` - Used to generate a struct implementing the `Chain` trait via a method
pub use mingling_macros::chain;
/// `#[completion(EntryType)]` - Used to generate completion entry
@@ -177,18 +112,15 @@ pub mod macros {
/// `suggest_enum!(EnumNames)` - Used to generate enum suggestions
#[cfg(feature = "comp")]
pub use mingling_macros::suggest_enum;
- /// New Parser provided by the `picker` feature
- #[cfg(feature = "picker")]
- pub use mingling_picker::macros::*;
}
/// derive macro `EnumTag`
#[cfg(feature = "macros")]
pub use mingling_macros::EnumTag;
-/// derive macro Groupped
+/// derive macro Grouped
#[cfg(feature = "macros")]
-pub use mingling_macros::Groupped;
+pub use mingling_macros::Grouped;
/// derive macro `StructuralData` — marks a type as supporting structured output
#[cfg(feature = "structural_renderer")]
@@ -239,9 +171,9 @@ pub mod res;
/// use mingling::prelude::*;
/// ```
pub mod prelude {
- /// Re-export of the `Groupped` derive macro for grouping types.
+ /// Re-export of the `Grouped` derive macro for grouping types.
#[cfg(feature = "core")]
- pub use crate::Groupped;
+ pub use crate::Grouped;
/// Re-export of the `RenderResult` struct for outputting rendering result
#[cfg(feature = "core")]
pub use crate::RenderResult;
@@ -286,7 +218,10 @@ pub mod prelude {
pub use crate::parser::AsPicker;
#[cfg(feature = "picker")]
- pub use mingling_picker::prelude::*;
+ pub use arg_picker::prelude::arg;
+
+ #[cfg(feature = "picker")]
+ pub use crate::picker::EntryPicker;
/// Used to enable the `writeln!` macro for `RenderResult`
#[cfg(feature = "core")]
diff --git a/mingling/src/parser/picker.rs b/mingling/src/parser/picker.rs
index 601cd3f..ca4561c 100644
--- a/mingling/src/parser/picker.rs
+++ b/mingling/src/parser/picker.rs
@@ -743,6 +743,10 @@ where
}
}
+/// Trait for types that can be converted into a `Picker` to extract values from command-line arguments.
+///
+/// This trait provides a convenient way to convert a value (such as `Vec<String>`, `&[String]`, etc.)
+/// into a `Picker` and immediately start extracting values associated with specific flags.
pub trait AsPicker
where
Self: Into<Vec<String>>,
diff --git a/mingling/src/picker.rs b/mingling/src/picker.rs
new file mode 100644
index 0000000..f370656
--- /dev/null
+++ b/mingling/src/picker.rs
@@ -0,0 +1,13 @@
+/// Provides the specific parsing logic for command-line arguments and common utilities,
+/// as well as customization of command-line argument styles.
+pub mod parselib {
+ pub use arg_picker::parselib::*;
+}
+
+pub use arg_picker::*;
+
+mod entry_picker;
+pub use entry_picker::*;
+
+mod global;
+pub use global::*;
diff --git a/mingling/src/picker/entry_picker.rs b/mingling/src/picker/entry_picker.rs
new file mode 100644
index 0000000..7364c50
--- /dev/null
+++ b/mingling/src/picker/entry_picker.rs
@@ -0,0 +1,126 @@
+use mingling_core::{ChainProcess, Grouped, ProgramCollect};
+
+use crate::{picker::Pickable, picker::Picker, picker::PickerArg, picker::PickerPattern1};
+
+/// Trait for converting Mingling entry types (types that implement `Grouped<R>` and `Into<Vec<String>>`)
+/// into [`Picker`] instances for a given route type `R`.
+///
+/// This trait provides a bridge between entry definitions created with the `mingling` framework
+/// and the picker argument system used for CLI argument parsing and routing.
+///
+/// # Type Parameters
+///
+/// * `'a` — The lifetime of the picker and its references to argument definitions.
+/// * `This` — The program type used for dispatching runtime routes; must implement [`ProgramCollect`].
+/// * `Route` — The route type used for dispatching; must implement [`Grouped<This>`].
+pub trait EntryPicker<'a, This> {
+ /// Converts `self` into a [`Picker`] for the given route type `Route`.
+ fn to_picker(self) -> Picker<'a, ChainProcess<This>>;
+
+ /// Starts building a picker pattern with the first argument.
+ ///
+ /// Returns a [`PickerPattern1`] that can be further chained with additional
+ /// arguments, defaults, routes, and post-processing.
+ ///
+ /// # Type Parameters
+ ///
+ /// * `Next` — The nominal type of the first argument; must implement [`Pickable`].
+ ///
+ /// # Parameters
+ ///
+ /// * `arg` — The argument definition, typically obtained from [`crate::macros::arg`].
+ fn pick<Next>(
+ self,
+ arg: impl Into<&'a PickerArg<'a, Next>>,
+ ) -> PickerPattern1<'a, Next, ChainProcess<This>>
+ where
+ Self: Sized,
+ Next: Pickable<'a> + Default + Sized,
+ {
+ let picker = Self::to_picker(self);
+ Picker::build_pattern1(picker.into_args(), arg.into(), None)
+ }
+
+ /// Starts building a picker pattern with the first argument, using a default value provider.
+ ///
+ /// This is a shorthand for calling `.pick(arg).or(func)`.
+ ///
+ /// # Type Parameters
+ ///
+ /// * `Next` — The nominal type of the first argument; must implement [`Pickable`].
+ ///
+ /// # Parameters
+ ///
+ /// * `arg` — The argument definition, typically obtained from [`crate::macros::arg`].
+ /// * `func` — A closure that provides a default value if the arg is not provided by the user.
+ fn pick_or<Next, F>(
+ self,
+ arg: impl Into<&'a PickerArg<'a, Next>>,
+ func: F,
+ ) -> PickerPattern1<'a, Next, ChainProcess<This>>
+ where
+ Self: Sized,
+ Next: Pickable<'a> + Default + Sized,
+ F: FnMut() -> Next + 'static,
+ {
+ self.pick(arg).or(func)
+ }
+
+ /// Starts building a picker pattern with the first argument, using a default value.
+ ///
+ /// This is a shorthand for calling `.pick(arg).or_default()`.
+ ///
+ /// # Type Parameters
+ ///
+ /// * `Next` — The nominal type of the first argument; must implement [`Pickable`] and [`Default`].
+ ///
+ /// # Parameters
+ ///
+ /// * `arg` — The argument definition, typically obtained from [`crate::macros::arg`].
+ fn pick_or_default<Next>(
+ self,
+ arg: impl Into<&'a PickerArg<'a, Next>>,
+ ) -> PickerPattern1<'a, Next, ChainProcess<This>>
+ where
+ Self: Sized,
+ Next: Pickable<'a> + Default + Sized,
+ {
+ self.pick(arg).or_default()
+ }
+
+ /// Starts building a picker pattern with the first argument, using a route if the arg is not provided.
+ ///
+ /// This is a shorthand for calling `.pick(arg).or_route(func)`.
+ ///
+ /// # Type Parameters
+ ///
+ /// * `Next` — The nominal type of the first argument; must implement [`Pickable`].
+ ///
+ /// # Parameters
+ ///
+ /// * `arg` — The argument definition, typically obtained from [`crate::macros::arg`].
+ /// * `func` — A closure that produces a route value if the arg is not provided by the user.
+ fn pick_or_route<Next, F>(
+ self,
+ arg: impl Into<&'a PickerArg<'a, Next>>,
+ func: F,
+ ) -> PickerPattern1<'a, Next, ChainProcess<This>>
+ where
+ Self: Sized,
+ Next: Pickable<'a> + Default + Sized,
+ F: FnMut() -> ChainProcess<This> + 'static,
+ {
+ self.pick(arg).or_route(func)
+ }
+}
+
+impl<'a, This, Bind> EntryPicker<'a, This> for Bind
+where
+ This: ProgramCollect<Enum = This>,
+ Bind: Grouped<This> + Into<Vec<String>>,
+{
+ fn to_picker(self) -> Picker<'a, ChainProcess<This>> {
+ let args = self.into();
+ Picker::from(args)
+ }
+}
diff --git a/mingling/src/picker/global.rs b/mingling/src/picker/global.rs
new file mode 100644
index 0000000..c203524
--- /dev/null
+++ b/mingling/src/picker/global.rs
@@ -0,0 +1,35 @@
+use arg_picker::{IntoPicker, Pickable, PickerArg, value::Flag};
+use mingling_core::{Program, ProgramCollect};
+
+use crate::consts::REMAINS;
+
+/// Picks a global flag from the program's arguments.
+///
+/// This function takes ownership of the program's current arguments, picks the specified `flag`
+/// from them, and then returns the remaining arguments back to the program. It returns the
+/// boolean value of the flag.
+pub fn pick_global_flag<C>(program: &mut Program<C>, flag: &PickerArg<Flag>) -> bool
+where
+ C: ProgramCollect<Enum = C>,
+{
+ let args = program.take_args();
+ let (flag, args) = args.pick(flag).pick(&REMAINS).unwrap();
+ program.replace_args(args.into());
+ *flag
+}
+
+/// Picks a global argument from the program's arguments.
+///
+/// This function takes ownership of the program's current arguments, picks the specified `arg`
+/// from them, and then returns the remaining arguments back to the program. It returns the
+/// picked argument value, or `None` if the argument was not present.
+pub fn pick_global_argument<C, A>(program: &mut Program<C>, arg: &PickerArg<A>) -> Option<A>
+where
+ A: for<'a> Pickable<'a> + Default,
+ C: ProgramCollect<Enum = C>,
+{
+ let args = program.take_args();
+ let (arg, remains) = args.pick(arg).pick(&REMAINS).unpack();
+ program.replace_args(remains.unwrap().into());
+ arg
+}
diff --git a/mingling/src/setups.rs b/mingling/src/setups.rs
index e572cf5..6fd728a 100644
--- a/mingling/src/setups.rs
+++ b/mingling/src/setups.rs
@@ -7,6 +7,13 @@ pub use dirs::*;
mod exit_code;
pub use exit_code::*;
+/// Picker's `ProgramSetup` variant.
+///
+/// Internally does not use its own argument parsing,
+/// but relies on `arg_picker`'s argument parsing capability.
+#[cfg(feature = "picker")]
+pub mod picker;
+
#[cfg(feature = "structural_renderer")]
mod structural_renderer;
diff --git a/mingling/src/setups/basic.rs b/mingling/src/setups/basic.rs
index 6a69733..e081c3e 100644
--- a/mingling/src/setups/basic.rs
+++ b/mingling/src/setups/basic.rs
@@ -1,3 +1,5 @@
+#![allow(deprecated)]
+
use mingling_core::{Flag, Program, ProgramCollect, setup::ProgramSetup};
/// Performs basic program initialization:
@@ -5,6 +7,12 @@ use mingling_core::{Flag, Program, ProgramCollect, setup::ProgramSetup};
/// - Collects `--quiet` flag to control message rendering
/// - Collects `--help` flag to enable help mode
/// - Collects `--confirm` flag to skip user confirmation
+#[cfg_attr(
+ feature = "picker",
+ deprecated(
+ note = "When the `picker` feature is enabled, you can use `mingling::setup::picker::BasicProgramSetup` instead"
+ )
+)]
pub struct BasicProgramSetup;
impl<C> ProgramSetup<C> for BasicProgramSetup
@@ -21,6 +29,12 @@ where
/// Provides setup for parsing the user help flag
///
/// The default value is `--help / -h`
+#[cfg_attr(
+ feature = "picker",
+ deprecated(
+ note = "When the `picker` feature is enabled, you can use `mingling::setup::picker::HelpFlagSetup` instead"
+ )
+)]
pub struct HelpFlagSetup {
flag: Flag,
}
@@ -54,6 +68,12 @@ impl Default for HelpFlagSetup {
/// Provides setup for parsing the quiet flag
///
/// The default value is `--quiet / -q`
+#[cfg_attr(
+ feature = "picker",
+ deprecated(
+ note = "When the `picker` feature is enabled, you can use `mingling::setup::picker::QuietFlagSetup` instead"
+ )
+)]
pub struct QuietFlagSetup {
flag: Flag,
}
@@ -88,6 +108,12 @@ impl Default for QuietFlagSetup {
/// Provides setup for parsing the confirm flag
///
/// The default value is `--confirm / -C`
+#[cfg_attr(
+ feature = "picker",
+ deprecated(
+ note = "When the `picker` feature is enabled, you can use `mingling::setup::picker::ConfirmFlagSetup` instead"
+ )
+)]
pub struct ConfirmFlagSetup {
flag: Flag,
}
diff --git a/mingling/src/setups/exit_code.rs b/mingling/src/setups/exit_code.rs
index 025ed8a..6b8a1ef 100644
--- a/mingling/src/setups/exit_code.rs
+++ b/mingling/src/setups/exit_code.rs
@@ -2,7 +2,7 @@ use std::marker::PhantomData;
use mingling_core::{
ProgramCollect,
- hook::{ProgramControlUnit, ProgramHook},
+ hook::{ProgramControlUnit, ProgramControls, ProgramHook},
setup::ProgramSetup,
this,
};
@@ -39,7 +39,14 @@ where
// Insert hook to override exit code before program ends
program.with_hook(ProgramHook::empty().on_finish(|_| {
let this = this::<C>().res_or_default::<ResExitCode>();
- ProgramControlUnit::OverrideExitCode(this.exit_code)
+ let ec = this.exit_code;
+
+ // Only override when ResExitCode has been modified
+ if ec != 0 {
+ ProgramControlUnit::OverrideExitCode(this.exit_code).into()
+ } else {
+ ProgramControls::Empty
+ }
}));
}
}
diff --git a/mingling/src/setups/picker.rs b/mingling/src/setups/picker.rs
new file mode 100644
index 0000000..0b7bd33
--- /dev/null
+++ b/mingling/src/setups/picker.rs
@@ -0,0 +1,7 @@
+mod basic;
+pub use basic::*;
+
+#[cfg(feature = "structural_renderer")]
+mod structural_renderer;
+#[cfg(feature = "structural_renderer")]
+pub use structural_renderer::*;
diff --git a/mingling/src/setups/picker/basic.rs b/mingling/src/setups/picker/basic.rs
new file mode 100644
index 0000000..c9f82b3
--- /dev/null
+++ b/mingling/src/setups/picker/basic.rs
@@ -0,0 +1,123 @@
+use arg_picker::{PickerArg, value::Flag};
+use mingling_core::{Program, ProgramCollect, setup::ProgramSetup};
+
+use crate::{
+ consts::{CONFIRM_FLAG, HELP_FLAG, QUIET_FLAG},
+ picker::pick_global_flag,
+};
+
+/// Performs basic program initialization:
+///
+/// - Collects `--quiet` flag to control message rendering
+/// - Collects `--help` flag to enable help mode
+/// - Collects `--confirm` flag to skip user confirmation
+pub struct BasicProgramSetup;
+
+impl<C> ProgramSetup<C> for BasicProgramSetup
+where
+ C: ProgramCollect<Enum = C>,
+{
+ fn setup(self, program: &mut Program<C>) {
+ program.with_setup(HelpFlagSetup::default());
+ program.with_setup(QuietFlagSetup::default());
+ program.with_setup(ConfirmFlagSetup::default());
+ }
+}
+
+/// Provides setup for parsing the user help flag
+///
+/// The default value is `--help / -h`
+pub struct HelpFlagSetup<'a> {
+ flag: &'a PickerArg<'a, Flag>,
+}
+
+impl<'a> HelpFlagSetup<'a> {
+ /// Creates a new `HelpFlagSetup` with the given flag aliases.
+ pub fn new(flag: &'a PickerArg<Flag>) -> Self {
+ Self { flag }
+ }
+}
+
+impl<'a, C> ProgramSetup<C> for HelpFlagSetup<'a>
+where
+ C: ProgramCollect<Enum = C>,
+{
+ fn setup(self, program: &mut Program<C>) {
+ let help = pick_global_flag(program, self.flag);
+ if help {
+ program.user_context.help = true;
+ }
+ }
+}
+
+impl<'a> Default for HelpFlagSetup<'a> {
+ fn default() -> Self {
+ Self { flag: &HELP_FLAG }
+ }
+}
+
+/// Provides setup for parsing the quiet flag
+///
+/// The default value is `--quiet / -q`
+pub struct QuietFlagSetup<'a> {
+ flag: &'a PickerArg<'a, Flag>,
+}
+
+impl<'a> QuietFlagSetup<'a> {
+ /// Creates a new `QuietFlagSetup` with the given flag aliases.
+ pub fn new(flag: &'a PickerArg<Flag>) -> Self {
+ Self { flag }
+ }
+}
+
+impl<'a, C> ProgramSetup<C> for QuietFlagSetup<'a>
+where
+ C: ProgramCollect<Enum = C>,
+{
+ fn setup(self, program: &mut Program<C>) {
+ let help = pick_global_flag(program, self.flag);
+ if help {
+ program.stdout_setting.quiet = true;
+ }
+ }
+}
+
+impl<'a> Default for QuietFlagSetup<'a> {
+ fn default() -> Self {
+ Self { flag: &QUIET_FLAG }
+ }
+}
+
+/// Provides setup for parsing the confirm flag
+///
+/// The default value is `--confirm / -C`
+pub struct ConfirmFlagSetup<'a> {
+ flag: &'a PickerArg<'a, Flag>,
+}
+
+impl<'a> ConfirmFlagSetup<'a> {
+ /// Creates a new `ConfirmFlagSetup` with the given flag aliases.
+ pub fn new(flag: &'a PickerArg<Flag>) -> Self {
+ Self { flag }
+ }
+}
+
+impl<'a, C> ProgramSetup<C> for ConfirmFlagSetup<'a>
+where
+ C: ProgramCollect<Enum = C>,
+{
+ fn setup(self, program: &mut Program<C>) {
+ let help = pick_global_flag(program, self.flag);
+ if help {
+ program.user_context.confirm = true;
+ }
+ }
+}
+
+impl<'a> Default for ConfirmFlagSetup<'a> {
+ fn default() -> Self {
+ Self {
+ flag: &CONFIRM_FLAG,
+ }
+ }
+}
diff --git a/mingling/src/setups/picker/structural_renderer.rs b/mingling/src/setups/picker/structural_renderer.rs
new file mode 100644
index 0000000..1fa48fd
--- /dev/null
+++ b/mingling/src/setups/picker/structural_renderer.rs
@@ -0,0 +1,76 @@
+use mingling_core::{Program, ProgramCollect, setup::ProgramSetup};
+
+use crate::{
+ consts::RENDERER_ARG,
+ picker::{pick_global_argument, pick_global_flag},
+};
+
+/// Sets up the structural renderer for the program:
+///
+/// - Adds a `--renderer` global argument to specify the renderer type
+pub struct StructuralRendererSimpleSetup;
+
+impl<C> ProgramSetup<C> for StructuralRendererSimpleSetup
+where
+ C: ProgramCollect<Enum = C>,
+{
+ fn setup(self, program: &mut Program<C>) {
+ if let Some(renderer) = pick_global_argument(program, &RENDERER_ARG) {
+ program.structural_renderer_name = renderer.into();
+ }
+ }
+}
+
+/// Sets up the structural renderer for the program:
+///
+/// - Adds global flags to specify the renderer type:
+/// * `--json` for JSON output
+/// * `--json-pretty` for pretty-printed JSON output
+/// * `--yaml` for YAML output
+/// * `--toml` for TOML output
+/// * `--ron` for RON output
+/// * `--ron-pretty` for pretty-printed RON output
+///
+/// # Flag priority
+///
+/// If multiple flags are specified, the last matching flag in the following
+/// declaration order takes precedence:
+/// 1. `--json`
+/// 2. `--json-pretty`
+/// 3. `--yaml`
+/// 4. `--toml`
+/// 5. `--ron`
+/// 6. `--ron-pretty`
+pub struct StructuralRendererSetup;
+
+impl<C> ProgramSetup<C> for StructuralRendererSetup
+where
+ C: ProgramCollect<Enum = C>,
+{
+ fn setup(self, program: &mut Program<C>) {
+ #[cfg(feature = "json_serde_fmt")]
+ if pick_global_flag(program, &crate::consts::JSON_FLAG) {
+ program.structural_renderer_name = crate::StructuralRendererSetting::Json;
+ }
+ #[cfg(feature = "json_serde_fmt")]
+ if pick_global_flag(program, &crate::consts::JSON_PRETTY_FLAG) {
+ program.structural_renderer_name = crate::StructuralRendererSetting::JsonPretty;
+ }
+ #[cfg(feature = "yaml_serde_fmt")]
+ if pick_global_flag(program, &crate::consts::YAML_FLAG) {
+ program.structural_renderer_name = crate::StructuralRendererSetting::Yaml;
+ }
+ #[cfg(feature = "toml_serde_fmt")]
+ if pick_global_flag(program, &crate::consts::TOML_FLAG) {
+ program.structural_renderer_name = crate::StructuralRendererSetting::Toml;
+ }
+ #[cfg(feature = "ron_serde_fmt")]
+ if pick_global_flag(program, &crate::consts::RON_FLAG) {
+ program.structural_renderer_name = crate::StructuralRendererSetting::Ron;
+ }
+ #[cfg(feature = "ron_serde_fmt")]
+ if pick_global_flag(program, &crate::consts::RON_PRETTY_FLAG) {
+ program.structural_renderer_name = crate::StructuralRendererSetting::RonPretty;
+ }
+ }
+}
diff --git a/mingling/src/setups/structural_renderer.rs b/mingling/src/setups/structural_renderer.rs
index af3ed91..0ab2347 100644
--- a/mingling/src/setups/structural_renderer.rs
+++ b/mingling/src/setups/structural_renderer.rs
@@ -1,8 +1,16 @@
+#![allow(deprecated)]
+
use mingling_core::{Program, ProgramCollect, setup::ProgramSetup};
/// Sets up the structural renderer for the program:
///
/// - Adds a `--renderer` global argument to specify the renderer type
+#[cfg_attr(
+ feature = "picker",
+ deprecated(
+ note = "When the `picker` feature is enabled, you can use `mingling::setup::picker::StructuralRendererSimpleSetup` instead"
+ )
+)]
pub struct StructuralRendererSimpleSetup;
impl<C> ProgramSetup<C> for StructuralRendererSimpleSetup
@@ -25,6 +33,12 @@ where
/// * `--toml` for TOML output
/// * `--ron` for RON output
/// * `--ron-pretty` for pretty-printed RON output
+#[cfg_attr(
+ feature = "picker",
+ deprecated(
+ note = "When the `picker` feature is enabled, you can use `mingling::setup::picker::StructuralRendererSetup` instead"
+ )
+)]
pub struct StructuralRendererSetup;
impl<C> ProgramSetup<C> for StructuralRendererSetup
diff --git a/mingling_core/src/any.rs b/mingling_core/src/any.rs
index 2680f43..e6b7406 100644
--- a/mingling_core/src/any.rs
+++ b/mingling_core/src/any.rs
@@ -1,12 +1,12 @@
use crate::error::ChainProcessError;
-use crate::{Groupped, ProgramCollect};
+use crate::{Grouped, ProgramCollect};
#[doc(hidden)]
pub mod group;
/// Any type output
///
-/// Accepts any type that implements `Send + Groupped<G>`
+/// Accepts any type that implements `Send + Grouped<G>`
/// After being passed into `AnyOutput`, it will be converted to `Box<dyn Any + Send + 'static>`
///
/// Note:
@@ -22,10 +22,10 @@ pub struct AnyOutput<G> {
}
impl<G> AnyOutput<G> {
- /// Create an `AnyOutput` from a `Send + Groupped<G>` type
+ /// Create an `AnyOutput` from a `Send + Grouped<G>` type
pub fn new<T>(value: T) -> Self
where
- T: Send + Groupped<G> + 'static,
+ T: Send + Grouped<G> + 'static,
{
Self {
inner: Box::new(value),
@@ -148,7 +148,7 @@ where
#[cfg(test)]
mod tests {
use super::*;
- use crate::Groupped;
+ use crate::Grouped;
/// Mock enum for testing AnyOutput
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -175,7 +175,7 @@ mod tests {
value: i32,
}
- impl Groupped<MockGroup> for AlphaData {
+ impl Grouped<MockGroup> for AlphaData {
fn member_id() -> MockGroup {
MockGroup::Alpha
}
@@ -187,7 +187,7 @@ mod tests {
name: String,
}
- impl Groupped<MockGroup> for BetaData {
+ impl Grouped<MockGroup> for BetaData {
fn member_id() -> MockGroup {
MockGroup::Beta
}
@@ -198,7 +198,7 @@ mod tests {
#[cfg_attr(feature = "structural_renderer", derive(serde::Serialize))]
struct GammaData;
- impl Groupped<MockGroup> for GammaData {
+ impl Grouped<MockGroup> for GammaData {
fn member_id() -> MockGroup {
MockGroup::Gamma
}
@@ -354,7 +354,7 @@ mod tests {
x: i32,
}
- impl Groupped<MockGroup> for SerData {
+ impl Grouped<MockGroup> for SerData {
fn member_id() -> MockGroup {
MockGroup::Gamma
}
@@ -381,13 +381,13 @@ mod tests {
b: String,
}
- impl Groupped<MockGroup> for SerA {
+ impl Grouped<MockGroup> for SerA {
fn member_id() -> MockGroup {
MockGroup::Alpha
}
}
- impl Groupped<MockGroup> for SerB {
+ 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 cb847d9..90ef9fc 100644
--- a/mingling_core/src/any/group.rs
+++ b/mingling_core/src/any/group.rs
@@ -1,11 +1,11 @@
-use crate::{AnyOutput, ChainProcess};
+use crate::{AnyOutput, ChainProcess, ProgramCollect, Routable};
/// Used to mark a type with a unique enum ID, assisting dynamic dispatch
///
-/// **Note:** Unlike earlier versions, `Groupped` no longer requires `Serialize`
+/// **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 Groupped<Group>
+pub trait Grouped<Group>
where
Self: Sized + 'static,
{
@@ -32,3 +32,17 @@ where
AnyOutput::new(self).route_renderer()
}
}
+
+impl<T, C> Routable<C> for T
+where
+ C: ProgramCollect<Enum = C>,
+ T: Grouped<C> + Send,
+{
+ fn to_chain(self) -> ChainProcess<C> {
+ T::to_chain(self)
+ }
+
+ fn to_render(self) -> ChainProcess<C> {
+ T::to_render(self)
+ }
+}
diff --git a/mingling_core/src/asset.rs b/mingling_core/src/asset.rs
index b1fd617..9b9a5d4 100644
--- a/mingling_core/src/asset.rs
+++ b/mingling_core/src/asset.rs
@@ -21,3 +21,6 @@ pub mod node;
#[doc(hidden)]
pub mod renderer;
+
+#[doc(hidden)]
+pub mod routable;
diff --git a/mingling_core/src/asset/global_resource.rs b/mingling_core/src/asset/global_resource.rs
index 3d0af7b..29e1136 100644
--- a/mingling_core/src/asset/global_resource.rs
+++ b/mingling_core/src/asset/global_resource.rs
@@ -45,13 +45,12 @@ where
/// Internal syntax for the `&mut MyResource` syntax of #[chain], do not use directly
#[doc(hidden)]
- pub fn __modify_res_and_return_route<Res, Return>(
+ pub fn __modify_res_and_return_route<Res>(
&self,
- f: impl FnOnce(&mut Res) -> Return,
- ) -> impl Into<ChainProcess<C>>
+ f: impl FnOnce(&mut Res) -> ChainProcess<C>,
+ ) -> ChainProcess<C>
where
Res: 'static + Default + ResourceMarker + Send + Sync,
- Return: Into<ChainProcess<C>>,
{
let Ok(mut guard) = self.resources.lock() else {
let mut default_res = Res::res_default();
diff --git a/mingling_core/src/asset/routable.rs b/mingling_core/src/asset/routable.rs
new file mode 100644
index 0000000..24b7bb1
--- /dev/null
+++ b/mingling_core/src/asset/routable.rs
@@ -0,0 +1,22 @@
+use crate::ChainProcess;
+
+/// Provides routing capabilities for converting an item into a `ChainProcess`
+/// directed to either the chain or render processing pipeline.
+///
+/// This trait enables items to be dispatched to different processing routes
+/// (chain or render) by wrapping them into an `AnyOutput` and routing them
+/// through the appropriate pipeline.
+pub trait Routable<Group>
+where
+ Self: Sized + 'static,
+{
+ /// Converts the routable item into a `ChainProcess` directed to the chain route.
+ ///
+ /// This wraps the item into an `AnyOutput` and routes it to the chain processing pipeline.
+ fn to_chain(self) -> ChainProcess<Group>;
+
+ /// Converts the routable item into a `ChainProcess` directed to the render route.
+ ///
+ /// This wraps the item into an `AnyOutput` and routes it to the render processing pipeline.
+ fn to_render(self) -> ChainProcess<Group>;
+}
diff --git a/mingling_core/src/comp.rs b/mingling_core/src/comp.rs
index f6fecd1..55e9952 100644
--- a/mingling_core/src/comp.rs
+++ b/mingling_core/src/comp.rs
@@ -96,7 +96,7 @@ impl CompletionHelper {
trace!("entry type: {}", any.member_id);
let dispatcher_not_found =
- <P::ErrorDispatcherNotFound as crate::Groupped<P>>::member_id();
+ <P::ErrorDispatcherNotFound as crate::Grouped<P>>::member_id();
if dispatcher_not_found == any.member_id {
debug!("dispatcher_not_found matched");
diff --git a/mingling_core/src/lib.rs b/mingling_core/src/lib.rs
index 3c2cf9b..4996b19 100644
--- a/mingling_core/src/lib.rs
+++ b/mingling_core/src/lib.rs
@@ -36,6 +36,7 @@ pub use crate::asset::help::*;
pub use crate::asset::lazy_resource::*;
pub use crate::asset::node::*;
pub use crate::asset::renderer::*;
+pub use crate::asset::routable::*;
/// All error types of `Mingling`
pub mod error {
diff --git a/mingling_core/src/program.rs b/mingling_core/src/program.rs
index 71d5290..0d409b7 100644
--- a/mingling_core/src/program.rs
+++ b/mingling_core/src/program.rs
@@ -130,6 +130,33 @@ where
&self.args
}
+ /// Returns a mutable reference to the program's command-line arguments.
+ #[must_use]
+ pub fn get_args_mut(&mut self) -> &mut [String] {
+ &mut self.args
+ }
+
+ /// Takes ownership of the program's command-line arguments, replacing them with an empty Vec.
+ /// This is useful when you need to transfer the arguments to another context or process them
+ /// and then replace them later.
+ #[must_use]
+ pub fn take_args(&mut self) -> Vec<String> {
+ std::mem::take(&mut self.args)
+ }
+
+ /// Replaces the program's command-line arguments with a new set and returns the old ones.
+ ///
+ /// # Arguments
+ ///
+ /// * `args` - The new command-line arguments to set.
+ ///
+ /// # Returns
+ ///
+ /// The previous command-line arguments.
+ pub fn replace_args(&mut self, args: Vec<String>) -> Vec<String> {
+ std::mem::replace(&mut self.args, args)
+ }
+
/// Get all registered dispatcher names from the program
#[must_use]
pub fn get_nodes(
diff --git a/mingling_core/src/program/collection.rs b/mingling_core/src/program/collection.rs
index d5aab4b..fe78979 100644
--- a/mingling_core/src/program/collection.rs
+++ b/mingling_core/src/program/collection.rs
@@ -4,7 +4,7 @@ use std::pin::Pin;
#[cfg(feature = "dispatch_tree")]
use crate::Dispatcher;
-use crate::{AnyOutput, ChainProcess, Groupped, RenderResult};
+use crate::{AnyOutput, ChainProcess, Grouped, RenderResult};
#[cfg(feature = "structural_renderer")]
use crate::{StructuralRendererSetting, error::StructuralRendererSerializeError};
@@ -21,9 +21,9 @@ pub use mock::*;
pub trait ProgramCollect {
/// Enum type representing internal IDs for the program
type Enum;
- type ErrorDispatcherNotFound: Groupped<Self::Enum>;
- type ErrorRendererNotFound: Groupped<Self::Enum>;
- type ResultEmpty: Groupped<Self::Enum>;
+ type ErrorDispatcherNotFound: Grouped<Self::Enum>;
+ type ErrorRendererNotFound: Grouped<Self::Enum>;
+ type ResultEmpty: Grouped<Self::Enum>;
/// Use a prefix tree to quickly match arguments and dispatch to an Entry
#[cfg(feature = "dispatch_tree")]
diff --git a/mingling_core/src/program/collection/mock.rs b/mingling_core/src/program/collection/mock.rs
index 9b2e7af..5847f10 100644
--- a/mingling_core/src/program/collection/mock.rs
+++ b/mingling_core/src/program/collection/mock.rs
@@ -4,7 +4,7 @@ use std::pin::Pin;
#[cfg(feature = "dispatch_tree")]
use crate::Dispatcher;
-use crate::{AnyOutput, ChainProcess, Groupped, ProgramCollect, RenderResult};
+use crate::{AnyOutput, ChainProcess, Grouped, ProgramCollect, RenderResult};
#[cfg(feature = "structural_renderer")]
use crate::{StructuralRendererSetting, error::StructuralRendererSerializeError};
@@ -23,7 +23,7 @@ pub enum MockProgramCollect {
Bar,
}
-impl Groupped<MockProgramCollect> for MockProgramCollect {
+impl Grouped<MockProgramCollect> for MockProgramCollect {
fn member_id() -> MockProgramCollect {
MockProgramCollect::Foo
}
diff --git a/mingling_core/src/program/hook.rs b/mingling_core/src/program/hook.rs
index 56d8e0e..db1691b 100644
--- a/mingling_core/src/program/hook.rs
+++ b/mingling_core/src/program/hook.rs
@@ -678,7 +678,7 @@ where
#[cfg(test)]
mod tests {
use super::*;
- use crate::Groupped;
+ use crate::Grouped;
use std::sync::atomic::{AtomicBool, Ordering};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -694,7 +694,7 @@ mod tests {
}
}
- impl Groupped<MockHookEnum> for MockHookEnum {
+ impl Grouped<MockHookEnum> for MockHookEnum {
fn member_id() -> MockHookEnum {
MockHookEnum::A
}
diff --git a/mingling_core/src/program/setup.rs b/mingling_core/src/program/setup.rs
index f248fb6..838c29a 100644
--- a/mingling_core/src/program/setup.rs
+++ b/mingling_core/src/program/setup.rs
@@ -1,9 +1,26 @@
use crate::{ProgramCollect, program::Program};
+/// Trait for defining initialization/setup logic for a `Program`.
+///
+/// Implementors can define custom setup behavior that will be executed
+/// when the program is initialized via [`Program::with_setup`].
+///
+/// # Type Parameters
+///
+/// * `C` - The program collect type, which must implement [`ProgramCollect`]
+/// and have `Enum = C` (i.e., it collects itself).
pub trait ProgramSetup<C>
where
C: ProgramCollect<Enum = C>,
{
+ /// Perform setup on the given program.
+ ///
+ /// This method consumes the setup instance (`self`) and is called once
+ /// during program initialization.
+ ///
+ /// # Arguments
+ ///
+ /// * `program` - A mutable reference to the [`Program`] being set up.
fn setup(self, program: &mut Program<C>);
}
diff --git a/mingling_core/src/renderer/render_result.rs b/mingling_core/src/renderer/render_result.rs
index 82a745c..e57a5b9 100644
--- a/mingling_core/src/renderer/render_result.rs
+++ b/mingling_core/src/renderer/render_result.rs
@@ -39,6 +39,56 @@ impl Deref for RenderResult {
}
}
+impl From<()> for RenderResult {
+ fn from(_value: ()) -> Self {
+ RenderResult::new()
+ }
+}
+
+macro_rules! impl_from_int {
+ ($($ty:ty),+ $(,)?) => {
+ $(
+ impl From<$ty> for RenderResult {
+ fn from(exit_code: $ty) -> Self {
+ RenderResult {
+ exit_code: exit_code as i32,
+ ..Default::default()
+ }
+ }
+ }
+ )+
+ };
+}
+
+impl_from_int!(i32, i16, i8, u32, u16, u8, usize);
+
+impl From<&String> for RenderResult {
+ fn from(value: &String) -> Self {
+ RenderResult {
+ render_text: value.clone(),
+ exit_code: 0,
+ }
+ }
+}
+
+impl From<String> for RenderResult {
+ fn from(value: String) -> Self {
+ RenderResult {
+ render_text: value,
+ exit_code: 0,
+ }
+ }
+}
+
+impl From<&str> for RenderResult {
+ fn from(value: &str) -> Self {
+ RenderResult {
+ render_text: value.to_string(),
+ exit_code: 0,
+ }
+ }
+}
+
impl From<RenderResult> for String {
fn from(result: RenderResult) -> Self {
result.render_text
diff --git a/mingling_macros/src/chain.rs b/mingling_macros/src/chain.rs
index 5f72422..ef31854 100644
--- a/mingling_macros/src/chain.rs
+++ b/mingling_macros/src/chain.rs
@@ -9,8 +9,7 @@ use quote::{ToTokens, quote};
use syn::spanned::Spanned;
use syn::{Ident, ItemFn, Pat, ReturnType, Signature, Type, TypePath, parse_macro_input};
-/// Validates that the return type of the function is `Next`.
-/// Checks whether the return type is `()` (unit).
+/// Checks whether the return type is `()`
fn is_unit_return_type(sig: &Signature) -> bool {
match &sig.output {
ReturnType::Type(_, ty) => match &**ty {
@@ -21,48 +20,22 @@ fn is_unit_return_type(sig: &Signature) -> bool {
}
}
+/// Validates that the return type is acceptable.
+/// Accepts `()`, `Next`, `ChainProcess<...>`, or any type that can
+/// be converted to `ChainProcess` via `.into()` (i.e. any pack type).
fn validate_return_type(sig: &Signature) -> Result<(), proc_macro2::TokenStream> {
- // If return type is `()`, it's valid (no Next required)
+ // `()` or omitted is always valid
if is_unit_return_type(sig) {
return Ok(());
}
- match &sig.output {
- ReturnType::Type(_, ty) => match &**ty {
- Type::Path(type_path) => {
- let last_segment = type_path.path.segments.last().unwrap();
- if last_segment.ident != "Next" {
- return Err(syn::Error::new(
- ty.span(),
- "Chain function must return `Next` or `()`",
- )
- .to_compile_error());
- }
- }
- _ => {
- return Err(syn::Error::new(
- ty.span(),
- "Chain function must return `Next` or `()`",
- )
- .to_compile_error());
- }
- },
- ReturnType::Default => {
- return Err(syn::Error::new(
- sig.span(),
- "Chain function must specify a return type (must be `Next` or `()`)",
- )
- .to_compile_error());
- }
- }
Ok(())
}
-/// Builds the `proc` function implementation that serves as the actual chain
-/// entry point inside the generated `Chain` impl.
+/// Builds the `proc` function implementation inside the generated `Chain` impl.
///
-/// * Without resources: delegates directly to the original function.
-/// * With resources: inlines the body and prepends resource bindings.
+/// The user's function body is inlined directly, and its result is converted
+/// via `.into()` to `ChainProcess<ProgramType>`.
#[allow(unused_variables)]
fn generate_proc_fn(
has_resources: bool,
@@ -70,67 +43,55 @@ fn generate_proc_fn(
program_type: &proc_macro2::TokenStream,
previous_type: &TypePath,
prev_param: &Pat,
- fn_name: &Ident,
fn_body_stmts: &[syn::Stmt],
is_async_fn: bool,
is_unit_return: bool,
+ origin_return_type: &proc_macro2::TokenStream,
) -> proc_macro2::TokenStream {
let immut_resource_stmts = generate_immut_resource_bindings(resources.iter(), program_type);
let mut_resources: Vec<_> = resources.iter().filter(|r| r.is_mut).collect();
- let body_stmts: &[syn::Stmt] = if is_unit_return && has_resources {
- let mut stmts = fn_body_stmts.to_vec();
- stmts.push(syn::Stmt::Expr(
- syn::parse_quote! {
- <crate::ResultEmpty as ::mingling::Groupped::<crate::ThisProgram>>
- ::to_chain(crate::ResultEmpty)
- },
- None,
- ));
- // Box::leak to get a &'static [syn::Stmt]
- Box::leak(Box::new(stmts))
- } else {
- fn_body_stmts
- };
-
let wrapped_body = if is_async_fn && !mut_resources.is_empty() {
- wrap_body_with_mut_resources_async(body_stmts, &mut_resources, program_type)
+ wrap_body_with_mut_resources_async(fn_body_stmts, &mut_resources, program_type)
} else {
- wrap_body_with_mut_resources(body_stmts, &mut_resources, program_type)
+ wrap_body_with_mut_resources(fn_body_stmts, &mut_resources, program_type, is_unit_return)
};
- // When the function returns `()`, wrap the result with ResultEmpty
- let call_or_wrapped = if is_unit_return {
- if has_resources {
+ let proc_body = if is_unit_return {
+ let body_with_ending = if has_resources {
quote! {
#(#immut_resource_stmts)*
- #wrapped_body
+ #wrapped_body;
+ <crate::ResultEmpty as ::mingling::Grouped::<crate::ThisProgram>>
+ ::to_chain(crate::ResultEmpty)
}
} else {
- let call = if is_async_fn {
- quote! { #fn_name(#prev_param).await; }
- } else {
- quote! { #fn_name(#prev_param); }
- };
quote! {
- #call
- <crate::ResultEmpty as ::mingling::Groupped::<crate::ThisProgram>>
+ #wrapped_body;
+ <crate::ResultEmpty as ::mingling::Grouped::<crate::ThisProgram>>
::to_chain(crate::ResultEmpty)
}
- }
- } else if has_resources {
- quote! {
- #(#immut_resource_stmts)*
- #wrapped_body
- }
+ };
+ quote! { #body_with_ending }
} else {
- let call = if is_async_fn {
- quote! { #fn_name(#prev_param).await.into() }
+ let body = if has_resources {
+ quote! {
+ #(#immut_resource_stmts)*
+ #wrapped_body
+ }
} else {
- quote! { #fn_name(#prev_param).into() }
+ quote! { #wrapped_body }
};
+ // Convert the body result to `ChainProcess` using the user-declared
+ // return type as the source type for a fully-qualified `Into` call.
+ // This works for both:
+ // - `-> Next` / `-> ChainProcess`: identity `From<T> for T`
+ // - `-> PackType`: `Into<ChainProcess>` from pack!/derive
quote! {
- #call
+ let __chain_result = { #body };
+ <#origin_return_type as ::std::convert::Into<
+ ::mingling::ChainProcess<#program_type>
+ >>::into(__chain_result)
}
};
@@ -138,7 +99,7 @@ fn generate_proc_fn(
{
quote! {
async fn proc(#prev_param: #previous_type) -> ::mingling::ChainProcess<#program_type> {
- #call_or_wrapped
+ #proc_body
}
}
}
@@ -147,73 +108,14 @@ fn generate_proc_fn(
{
quote! {
fn proc(#prev_param: #previous_type) -> ::mingling::ChainProcess<#program_type> {
- #call_or_wrapped
- }
- }
- }
-}
-
-/// Generates the original function signature (kept for backwards compatibility /
-/// internal use), with its return type changed to `impl Into<ChainProcess<..>>`.
-#[allow(unused_variables)]
-fn generate_original_fn(
- fn_attrs: &[syn::Attribute],
- vis: &syn::Visibility,
- fn_name: &Ident,
- inputs: &syn::punctuated::Punctuated<syn::FnArg, syn::Token![,]>,
- fn_body: &syn::Block,
- is_async_fn: bool,
- program_type: &proc_macro2::TokenStream,
- is_unit_return: bool,
-) -> proc_macro2::TokenStream {
- // Both unit and Next return types need to produce `impl Into<ChainProcess<ProgramType>>`
- let return_type = quote! { impl Into<::mingling::ChainProcess<#program_type>> };
-
- let body = if is_unit_return {
- quote! {
- {
- #fn_body
- <crate::ResultEmpty as ::mingling::Groupped::<crate::ThisProgram>>::to_chain(crate::ResultEmpty)
- }
- }
- } else {
- quote! {
- {
- let _: crate::Next;
- let _: Next;
- #fn_body
- }
- }
- };
-
- #[cfg(feature = "async")]
- {
- let async_kw = if is_async_fn {
- quote! { async }
- } else {
- quote! {}
- };
- quote! {
- #(#fn_attrs)*
- #vis #async_kw fn #fn_name(#inputs) -> #return_type {
- #body
- }
- }
- }
-
- #[cfg(not(feature = "async"))]
- {
- quote! {
- #(#fn_attrs)*
- #vis fn #fn_name(#inputs) -> #return_type {
- #body
+ #proc_body
}
}
}
}
/// Assembles the final expanded output: hidden struct, `register_chain!` invocation,
-/// `Chain` impl with the `proc` method, and the original function.
+/// `Chain` impl with the `proc` method, and the preserved original function.
fn generate_struct_and_impl(
fn_attrs: &[syn::Attribute],
vis: &syn::Visibility,
@@ -222,7 +124,7 @@ fn generate_struct_and_impl(
previous_type_str: &proc_macro2::TokenStream,
program_type: &proc_macro2::TokenStream,
proc_fn: &proc_macro2::TokenStream,
- origin_proc_fn: &proc_macro2::TokenStream,
+ original_fn: &proc_macro2::TokenStream,
) -> proc_macro2::TokenStream {
quote! {
#(#fn_attrs)*
@@ -238,8 +140,8 @@ fn generate_struct_and_impl(
#proc_fn
}
- // Keep the original function for internal use
- #origin_proc_fn
+ // Keep the original function unchanged
+ #original_fn
}
}
@@ -296,8 +198,6 @@ pub fn chain_attr(attr: TokenStream, item: TokenStream) -> TokenStream {
};
// Prepare building blocks
- let sig = &input_fn.sig;
- let inputs = &sig.inputs;
let fn_body = &input_fn.block;
let mut fn_attrs = input_fn.attrs.clone();
fn_attrs.retain(|attr| !attr.path().is_ident("chain"));
@@ -315,36 +215,36 @@ pub fn chain_attr(attr: TokenStream, item: TokenStream) -> TokenStream {
// Always use the default crate-defined program path
let program_type = crate::default_program_path();
- // Generate the `proc` function
+ // Extract the user's return type for the explicit Into turbofish
+ let origin_return_type = match &input_fn.sig.output {
+ ReturnType::Type(_, ty) => quote! { #ty },
+ ReturnType::Default => quote! { () },
+ };
+
+ // Generate the `proc` function for the Chain impl
let proc_fn = generate_proc_fn(
has_resources,
&resources,
&program_type,
&previous_type,
&prev_param,
- fn_name,
&fn_body.stmts,
#[cfg(feature = "async")]
is_async_fn,
#[cfg(not(feature = "async"))]
false,
is_unit_return,
+ &origin_return_type,
);
- // Generate the original function
- let origin_proc_fn = generate_original_fn(
- &fn_attrs,
- vis,
- fn_name,
- inputs,
- fn_body,
- #[cfg(feature = "async")]
- is_async_fn,
- #[cfg(not(feature = "async"))]
- false,
- &program_type,
- is_unit_return,
- );
+ // Preserve the original function untouched, with dead_code allowed
+ // since it may only be called through the Chain trait dispatch.
+ // Note: do NOT add `#vis` here — `input_fn` (ItemFn) already contains its own visibility.
+ let original_fn = quote! {
+ #[allow(dead_code)]
+ #(#fn_attrs)*
+ #input_fn
+ };
// Assemble the final output
let previous_type_str = quote! { #previous_type };
@@ -356,7 +256,7 @@ pub fn chain_attr(attr: TokenStream, item: TokenStream) -> TokenStream {
&previous_type_str,
&program_type,
&proc_fn,
- &origin_proc_fn,
+ &original_fn,
);
expanded.into()
diff --git a/mingling_macros/src/dispatcher.rs b/mingling_macros/src/dispatcher.rs
index 2a7c850..3698ede 100644
--- a/mingling_macros/src/dispatcher.rs
+++ b/mingling_macros/src/dispatcher.rs
@@ -134,7 +134,7 @@ pub fn dispatcher(input: TokenStream) -> TokenStream {
::mingling::macros::node!(#command_name_str)
}
fn begin(&self, args: Vec<String>) -> ::mingling::ChainProcess<#program_type> {
- use ::mingling::Groupped;
+ use ::mingling::Grouped;
#pack::new(args).to_chain()
}
fn clone_dispatcher(&self) -> Box<dyn ::mingling::Dispatcher<#program_type>> {
diff --git a/mingling_macros/src/group_impl.rs b/mingling_macros/src/group_impl.rs
index 59da9dd..cac2734 100644
--- a/mingling_macros/src/group_impl.rs
+++ b/mingling_macros/src/group_impl.rs
@@ -124,7 +124,7 @@ pub fn group_macro(input: TokenStream) -> TokenStream {
quote! {}
};
- // Generate the module with the Groupped implementation
+ // Generate the module with the Grouped implementation
let expanded = quote! {
#alias_stmt
#[allow(non_camel_case_types)]
@@ -133,7 +133,7 @@ pub fn group_macro(input: TokenStream) -> TokenStream {
#type_use
#alias_use
- impl ::mingling::Groupped<__MinglingProgram> for #type_name {
+ impl ::mingling::Grouped<__MinglingProgram> for #type_name {
fn member_id() -> __MinglingProgram {
__MinglingProgram::#type_name
}
diff --git a/mingling_macros/src/groupped.rs b/mingling_macros/src/grouped.rs
index 8aee003..9014c37 100644
--- a/mingling_macros/src/groupped.rs
+++ b/mingling_macros/src/grouped.rs
@@ -2,7 +2,7 @@ use proc_macro::TokenStream;
use quote::quote;
use syn::{DeriveInput, Ident, parse_macro_input};
-pub fn derive_groupped(input: TokenStream) -> TokenStream {
+pub fn derive_grouped(input: TokenStream) -> TokenStream {
// Parse the input struct/enum
let input = parse_macro_input!(input as DeriveInput);
let struct_name = input.ident;
@@ -12,11 +12,11 @@ pub fn derive_groupped(input: TokenStream) -> TokenStream {
let any_output_convert_impls =
proc_macro2::TokenStream::from(build_any_output_convert_impls(&struct_name, &group_ident));
- // Generate the Groupped trait implementation
+ // Generate the Grouped trait implementation
let expanded = quote! {
::mingling::macros::register_type!(#struct_name);
- impl ::mingling::Groupped<#group_ident> for #struct_name {
+ impl ::mingling::Grouped<#group_ident> for #struct_name {
fn member_id() -> #group_ident {
#group_ident::#struct_name
}
@@ -29,7 +29,7 @@ pub fn derive_groupped(input: TokenStream) -> TokenStream {
}
#[cfg(feature = "structural_renderer")]
-pub fn derive_groupped_serialize(input: TokenStream) -> TokenStream {
+pub fn derive_grouped_serialize(input: TokenStream) -> TokenStream {
// Parse the input struct/enum
let input_parsed = parse_macro_input!(input as DeriveInput);
let struct_name = input_parsed.ident.clone();
@@ -39,14 +39,14 @@ pub fn derive_groupped_serialize(input: TokenStream) -> TokenStream {
let any_output_convert_impls =
proc_macro2::TokenStream::from(build_any_output_convert_impls(&struct_name, &group_ident));
- // Generate both Serialize and Groupped implementations
+ // Generate both Serialize and Grouped implementations
let expanded = quote! {
#[derive(serde::Serialize)]
#input_parsed
::mingling::macros::register_type!(#struct_name);
- impl ::mingling::Groupped<#group_ident> for #struct_name {
+ impl ::mingling::Grouped<#group_ident> for #struct_name {
fn member_id() -> #group_ident {
#group_ident::#struct_name
}
diff --git a/mingling_macros/src/help.rs b/mingling_macros/src/help.rs
index 1903d07..6e0d12e 100644
--- a/mingling_macros/src/help.rs
+++ b/mingling_macros/src/help.rs
@@ -1,34 +1,16 @@
use proc_macro::TokenStream;
use quote::{ToTokens, quote};
use syn::spanned::Spanned;
-use syn::{Ident, ItemFn, ReturnType, Type, TypePath, parse_macro_input};
+use syn::{Ident, ItemFn, ReturnType, Signature, TypePath, parse_macro_input};
use crate::get_global_set;
use crate::res_injection::{extract_args_info, generate_immut_resource_bindings};
-/// Validates that the function returns `::mingling::RenderResult`.
-fn validate_render_result_return(sig: &syn::Signature) -> syn::Result<()> {
+/// Extracts the user's return type, returning `None` for no return type.
+fn extract_user_return_type(sig: &Signature) -> Option<proc_macro2::TokenStream> {
match &sig.output {
- ReturnType::Type(_, ty) => match &**ty {
- Type::Path(type_path) => {
- let last_seg = type_path.path.segments.last().map(|s| s.ident.to_string());
- match last_seg.as_deref() {
- Some("RenderResult") => Ok(()),
- _ => Err(syn::Error::new(
- ty.span(),
- "Help function must return `RenderResult`",
- )),
- }
- }
- _ => Err(syn::Error::new(
- ty.span(),
- "Help function must return `RenderResult`",
- )),
- },
- ReturnType::Default => Err(syn::Error::new(
- sig.span(),
- "Help function must have a return type `-> RenderResult`",
- )),
+ ReturnType::Type(_, ty) => Some(quote! { #ty }),
+ ReturnType::Default => None,
}
}
@@ -49,10 +31,8 @@ pub fn help_attr(item: TokenStream) -> TokenStream {
Err(e) => return e.to_compile_error().into(),
};
- // Validate return type is RenderResult
- if let Err(e) = validate_render_result_return(&input_fn.sig) {
- return e.to_compile_error().into();
- }
+ // Determine the user's return type for preserving the original function
+ let user_return_type = extract_user_return_type(&input_fn.sig);
// Get the function body
let fn_body = &input_fn.block;
@@ -69,8 +49,8 @@ pub fn help_attr(item: TokenStream) -> TokenStream {
let fn_name = &input_fn.sig.ident;
// Get original inputs to keep the original function
-
let original_inputs = input_fn.sig.inputs.clone();
+ let original_return_type = user_return_type.clone().unwrap_or(quote! { () });
// Generate internal name using snake_case
let internal_name = format!(
@@ -149,17 +129,18 @@ pub fn help_attr(item: TokenStream) -> TokenStream {
type Entry = #entry_type;
fn render_help(#prev_param: Self::Entry) -> ::mingling::RenderResult {
- #help_render_body
+ let __help_result = { #help_render_body };
+ ::std::convert::Into::into(__help_result)
}
}
::mingling::macros::register_help!(#entry_type, #struct_name);
// Keep the original function unchanged
-
+ #[allow(dead_code)]
#(#fn_attrs)*
- #vis fn #fn_name(#original_inputs) -> ::mingling::RenderResult {
- #fn_body
+ #vis fn #fn_name(#original_inputs) -> #original_return_type {
+ #(#fn_body_stmts)*
}
};
diff --git a/mingling_macros/src/lib.rs b/mingling_macros/src/lib.rs
index d0f603a..ea33577 100644
--- a/mingling_macros/src/lib.rs
+++ b/mingling_macros/src/lib.rs
@@ -13,7 +13,7 @@
//! ┌──────────────────────────────────────────────────────────────────┐
//! │ Phase 1: Declaration │
//! │ │
-//! │ dispatcher! pack! node! #[derive(Groupped)] │
+//! │ dispatcher! pack! node! #[derive(Grouped)] │
//! │ │ │ │ │ │
//! │ V V V V │
//! │ Declares Wraps a Builds Makes a type │
@@ -55,7 +55,7 @@
//! | `pack_err!` | Creates an error struct with automatic `name` field |
//! | `pack_err_structural!` | Like `pack_err!` but also derives `StructuralData` for structured output |
//! | `entry!` | Creates a packed entry from string literals |
-//! | [`#[derive(Groupped)]`](derive@Groupped) | Makes a type recognizable by the framework's type registry |
+//! | [`#[derive(Grouped)]`](derive@Grouped) | Makes a type recognizable by the framework's type registry |
//! | `#[derive(StructuralData)]` | Marks a type as eligible for structured output (JSON/YAML/etc.) |
//! | [`#[derive(EnumTag)]`](derive@EnumTag) | Adds enum variant metadata (name, description) |
//!
@@ -161,7 +161,7 @@ mod entry;
mod enum_tag;
#[cfg(feature = "extra_macros")]
mod group_impl;
-mod groupped;
+mod grouped;
mod help;
mod node;
mod pack;
@@ -265,7 +265,7 @@ fn entry_has_variant(entry: &str, variant_name: &str) -> bool {
/// Registers an outside-type as a member of a program group without modifying its definition.
///
/// This macro allows you to use outside-types from external crates (like `std::io::Error`)
-/// within the Mingling framework by generating a `Groupped` implementation and registering
+/// within the Mingling framework by generating a `Grouped` implementation and registering
/// the type's simple name as an enum variant.
///
/// # Syntax
@@ -281,11 +281,11 @@ fn entry_has_variant(entry: &str, variant_name: &str) -> bool {
///
/// The macro generates a module containing:
/// - A `use` import for the program path and the outside-type
-/// - An `impl Groupped<Program>` for the outside-type
+/// - An `impl Grouped<Program>` for the outside-type
/// - A `register_type!` call with the type's simple name
///
/// The type's simple name (e.g. `Error`) is used as the enum variant in the generated
-/// program enum, just like `#[derive(Groupped)]` or `pack!`.
+/// program enum, just like `#[derive(Grouped)]` or `pack!`.
///
/// # Example
///
@@ -297,7 +297,7 @@ fn entry_has_variant(entry: &str, variant_name: &str) -> bool {
/// ```
///
/// After expansion, the type can be used in chains and renderers like any
-/// `#[derive(Groupped)]` type.
+/// `#[derive(Grouped)]` type.
///
/// This macro is only available with the `extra_macros` feature.
#[cfg(feature = "extra_macros")]
@@ -402,7 +402,7 @@ pub fn node(input: TokenStream) -> TokenStream {
/// - `AsRef<String>`, `AsMut<String>`
/// - `Default` if `String: Default`
/// - `Into<AnyOutput<ThisProgram>>`, `Into<ChainProcess<ThisProgram>>`
-/// - Implements `Groupped<ThisProgram>` with `member_id()` returning the enum variant
+/// - Implements `Grouped<ThisProgram>` with `member_id()` returning the enum variant
///
/// The struct is also registered via `register_type!` so that `gen_program!`
/// can include it in the program enum.
@@ -438,7 +438,7 @@ pub fn pack_structural(input: TokenStream) -> TokenStream {
/// Creates an error struct with a `name: String` field and optional `info: Type` field.
///
-/// This macro provides a concise way to define error types that implement `Groupped`
+/// This macro provides a concise way to define error types that implement `Grouped`
/// and are registered for inclusion in the program enum.
///
/// The `name` field is automatically set to the snake_case version of the struct name
@@ -461,7 +461,7 @@ pub fn pack_structural(input: TokenStream) -> TokenStream {
/// For `pack_err!(ErrorNotFound)`:
///
/// ```rust,ignore
-/// #[derive(::mingling::Groupped)]
+/// #[derive(::mingling::Grouped)]
/// pub struct ErrorNotFound {
/// name: String,
/// }
@@ -478,7 +478,7 @@ pub fn pack_structural(input: TokenStream) -> TokenStream {
/// For `pack_err!(ErrorNotDir = PathBuf)`:
///
/// ```rust,ignore
-/// #[derive(::mingling::Groupped)]
+/// #[derive(::mingling::Grouped)]
/// pub struct ErrorNotDir {
/// name: String,
/// info: PathBuf,
@@ -521,20 +521,25 @@ pub fn pack_err_structural(input: TokenStream) -> TokenStream {
pack_err::pack_err_structural(input)
}
-/// Early-returns an error from a `Result`, converting the `Ok` branch to a
-/// `ChainProcess`.
+/// Early-returns the error from a `Result`, converting the `Ok` branch to the
+/// next chain process value.
///
/// This macro is equivalent to:
/// ```rust,ignore
/// match expr {
/// Ok(r) => r,
-/// Err(e) => return ::mingling::Groupped::to_chain(e),
+/// Err(e) => return ::mingling::Routable::to_chain(e),
/// }
/// ```
///
-/// It is useful inside chain functions where you have a `Result<SomeType, SomeType>`
-/// where both types implement `Groupped` and want to propagate the error case
-/// as an early return via `Groupped::to_chain()`.
+/// It is useful inside chain functions where you have a `Result<SuccessType, ErrorType>`
+/// where both types implement `Routable` and you want to propagate the error case
+/// as an early return via `Routable::to_chain()`.
+///
+/// The key difference from a simple `?` operator is that `route!` converts **both**
+/// the success and error types into the chain process — the `Ok` value is unwrapped
+/// directly, while the `Err` value is converted via `Routable::to_chain()` and
+/// returned early.
///
/// # Example
///
@@ -542,7 +547,7 @@ pub fn pack_err_structural(input: TokenStream) -> TokenStream {
/// use mingling::macros::{chain, route};
///
/// #[chain]
-/// fn process(prev: SomeEntry) -> ChainProcess<ThisProgram> {
+/// fn process(prev: SomeEntry) -> Next {
/// let value = route!(current_dir().map_err(|e| ErrorEntry::new(e.to_string_lossy().to_string())));
/// // value is the PathBuf from current_dir()
/// value.to_chain()
@@ -555,7 +560,7 @@ pub fn route(input: TokenStream) -> TokenStream {
let expanded = quote! {
match #expr {
Ok(r) => r,
- Err(e) => return ::mingling::Groupped::to_chain(e),
+ Err(e) => return ::mingling::Routable::to_chain(e),
}
};
TokenStream::from(expanded)
@@ -613,7 +618,7 @@ pub fn route(input: TokenStream) -> TokenStream {
#[proc_macro]
pub fn empty_result(_input: TokenStream) -> TokenStream {
let expanded = quote! {
- <crate::ResultEmpty as ::mingling::Groupped::<crate::ThisProgram>>::to_chain(crate::ResultEmpty)
+ <crate::ResultEmpty as ::mingling::Grouped::<crate::ThisProgram>>::to_chain(crate::ResultEmpty)
};
TokenStream::from(expanded)
}
@@ -867,7 +872,8 @@ pub fn dispatcher(input: TokenStream) -> TokenStream {
///
/// - The function must have at least **one** parameter (the previous type in the chain).
/// - The first parameter must be taken **by move**.
-/// - The function must return `Next` (the type alias generated by `gen_program!`, which equals `ChainProcess<ProgramName>`).
+/// - The function may return `Next`, `ChainProcess<ProgramName>`, `()`, or omit the return type.
+/// - The original function signature is preserved unchanged.
/// - With the `async` feature, async functions are supported; without it, async functions are rejected.
#[proc_macro_attribute]
pub fn chain(attr: TokenStream, item: TokenStream) -> TokenStream {
@@ -1234,7 +1240,7 @@ pub fn register_dispatcher(input: TokenStream) -> TokenStream {
///
/// - The function must have exactly one parameter (the entry type to provide help for).
/// - The parameter type must be a single-segment type path (e.g., `MyEntry`, not `other::MyEntry`).
-/// - The function must return `RenderResult`.
+/// - The function may return `RenderResult`, `()`, or any type that implements `Into<RenderResult>`.
/// - The function cannot be async.
///
/// # See also
@@ -1249,10 +1255,10 @@ pub fn help(_attr: TokenStream, item: TokenStream) -> TokenStream {
help::help_attr(item)
}
-/// Derive macro for automatically implementing the `Groupped` trait on a struct.
+/// Derive macro for automatically implementing the `Grouped` trait on a struct.
///
-/// The `#[derive(Groupped)]` macro:
-/// 1. Implements `Groupped<crate::ThisProgram>`.
+/// The `#[derive(Grouped)]` macro:
+/// 1. Implements `Grouped<crate::ThisProgram>`.
/// 2. Registers the type via `register_type!` so it's included in the program enum.
/// 3. Generates `Into<AnyOutput<Group>>` and `Into<ChainProcess<Group>>` conversions.
/// 4. Adds `to_chain()` and `to_render()` methods to the struct.
@@ -1260,7 +1266,7 @@ pub fn help(_attr: TokenStream, item: TokenStream) -> TokenStream {
/// # Syntax
///
/// ```rust,ignore
-/// #[derive(Groupped)]
+/// #[derive(Grouped)]
/// struct MyStruct {
/// // ...
/// }
@@ -1269,9 +1275,9 @@ pub fn help(_attr: TokenStream, item: TokenStream) -> TokenStream {
/// # Example
///
/// ```rust,ignore
-/// use mingling::macros::Groupped;
+/// use mingling::macros::Grouped;
///
-/// #[derive(Groupped)]
+/// #[derive(Grouped)]
/// struct Greeting {
/// name: String,
/// }
@@ -1279,9 +1285,9 @@ pub fn help(_attr: TokenStream, item: TokenStream) -> TokenStream {
///
/// This is equivalent to using `pack!` but works with custom structs that
/// have named fields. For simple wrappers, prefer `pack!`.
-#[proc_macro_derive(Groupped, attributes(group))]
-pub fn derive_groupped(input: TokenStream) -> TokenStream {
- groupped::derive_groupped(input)
+#[proc_macro_derive(Grouped, attributes(group))]
+pub fn derive_grouped(input: TokenStream) -> TokenStream {
+ grouped::derive_grouped(input)
}
/// Derive macro for automatically implementing the `EnumTag` trait on an enum
@@ -1350,18 +1356,18 @@ pub fn derive_structural_data(input: TokenStream) -> TokenStream {
structural_data::derive_structural_data(input)
}
-/// Derive macro for implementing both `Groupped` and `serde::Serialize` on a struct.
+/// Derive macro for implementing both `Grouped` and `serde::Serialize` on a struct.
///
/// **This macro is only available with the `structural_renderer` feature.**
///
-/// This is identical to `#[derive(Groupped)]` but also adds `#[derive(serde::Serialize)]`
+/// This is identical to `#[derive(Grouped)]` but also adds `#[derive(serde::Serialize)]`
/// to the struct, which is required for the structural renderer to serialize output
/// to formats like JSON, YAML, TOML, or RON.
///
/// # Syntax
///
/// ```rust,ignore
-/// #[derive(GrouppedSerialize)]
+/// #[derive(GroupedSerialize)]
/// struct Info {
/// name: String,
/// age: i32,
@@ -1371,19 +1377,19 @@ pub fn derive_structural_data(input: TokenStream) -> TokenStream {
/// # Example
///
/// ```rust,ignore
-/// use mingling::GrouppedSerialize;
+/// use mingling::GroupedSerialize;
/// use serde::Serialize;
///
-/// #[derive(GrouppedSerialize)]
+/// #[derive(GroupedSerialize)]
/// struct Info {
/// name: String,
/// age: i32,
/// }
/// ```
#[cfg(feature = "structural_renderer")]
-#[proc_macro_derive(GrouppedSerialize, attributes(group))]
-pub fn derive_groupped_serialize(input: TokenStream) -> TokenStream {
- groupped::derive_groupped_serialize(input)
+#[proc_macro_derive(GroupedSerialize, attributes(group))]
+pub fn derive_grouped_serialize(input: TokenStream) -> TokenStream {
+ grouped::derive_grouped_serialize(input)
}
/// Generates the program enum and all collected types, chains, and renderers.
@@ -1445,8 +1451,30 @@ pub fn gen_program(_input: TokenStream) -> TokenStream {
let comp_gen = quote! {};
TokenStream::from(quote! {
+ /// 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,
+ }
+ }
+ }
+
#comp_gen
::mingling::macros::program_fallback_gen!();
::mingling::macros::program_final_gen!();
@@ -1473,7 +1501,7 @@ pub fn program_comp_gen(_input: TokenStream) -> TokenStream {
#[doc(hidden)]
#[::mingling::macros::chain]
pub async fn __exec_completion(prev: CompletionContext) -> Next {
- use ::mingling::Groupped;
+ use ::mingling::Grouped;
let read_ctx = ::mingling::ShellContext::try_from(prev.inner);
match read_ctx {
@@ -1491,7 +1519,7 @@ pub fn program_comp_gen(_input: TokenStream) -> TokenStream {
#[doc(hidden)]
#[::mingling::macros::chain]
pub fn __exec_completion(prev: CompletionContext) -> Next {
- use ::mingling::Groupped;
+ use ::mingling::Grouped;
let read_ctx = ::mingling::ShellContext::try_from(prev.inner);
match read_ctx {
@@ -1515,7 +1543,7 @@ pub fn program_comp_gen(_input: TokenStream) -> TokenStream {
let comp_dispatcher = quote! {
#[doc(hidden)]
mod __internal_completion_mod {
- use ::mingling::Groupped;
+ use ::mingling::Grouped;
::mingling::macros::dispatcher!("__comp", CMDCompletion => CompletionContext);
::mingling::macros::pack!(
CompletionSuggest = (::mingling::ShellContext, ::mingling::Suggest)
@@ -1547,7 +1575,7 @@ pub fn program_comp_gen(_input: TokenStream) -> TokenStream {
/// Registers a type into the global packed types registry for inclusion in
/// the program enum generated by `gen_program!`.
///
-/// This macro is called internally by `pack!` and `#[derive(Groupped)]`(`macro.derive_groupped.html`)
+/// This macro is called internally by `pack!` and `#[derive(Grouped)]`(`macro.derive_grouped.html`)
/// and is generally not needed in user code. However, it can be used for manual
/// registration if you are implementing custom type registration outside of
/// the standard macros.
@@ -1652,13 +1680,13 @@ pub fn register_renderer(input: TokenStream) -> TokenStream {
pub fn program_fallback_gen(_input: TokenStream) -> TokenStream {
#[cfg(feature = "structural_renderer")]
let pack_empty = quote! {
- #[derive(::serde::Serialize, ::mingling::StructuralData, ::mingling::Groupped, Default)]
+ #[derive(::serde::Serialize, ::mingling::StructuralData, ::mingling::Grouped, Default)]
pub struct ResultEmpty;
};
#[cfg(not(feature = "structural_renderer"))]
let pack_empty = quote! {
- #[derive(::mingling::Groupped, Default)]
+ #[derive(::mingling::Grouped, Default)]
pub struct ResultEmpty;
};
@@ -1674,7 +1702,7 @@ pub fn program_fallback_gen(_input: TokenStream) -> TokenStream {
/// and its `ProgramCollect` implementation.
///
/// This is the core code generation macro that:
-/// 1. Collects all registered types (from `pack!`, `#[derive(Groupped)]`, etc.) and
+/// 1. Collects all registered types (from `pack!`, `#[derive(Grouped)]`, etc.) and
/// creates an enum with each type as a variant.
/// 2. Generates the `Display` implementation for the enum.
/// 3. Generates the `ProgramCollect` implementation that dispatches to all
diff --git a/mingling_macros/src/pack.rs b/mingling_macros/src/pack.rs
index 7f05232..0af1b4c 100644
--- a/mingling_macros/src/pack.rs
+++ b/mingling_macros/src/pack.rs
@@ -138,7 +138,7 @@ pub fn pack(input: TokenStream) -> TokenStream {
}
}
- impl ::mingling::Groupped<#group_name> for #type_name {
+ impl ::mingling::Grouped<#group_name> for #type_name {
fn member_id() -> #group_name {
#group_name::#type_name
}
diff --git a/mingling_macros/src/pack_err.rs b/mingling_macros/src/pack_err.rs
index ba7cf17..a747aa9 100644
--- a/mingling_macros/src/pack_err.rs
+++ b/mingling_macros/src/pack_err.rs
@@ -42,7 +42,7 @@ pub fn pack_err(input: TokenStream) -> TokenStream {
// Note: No longer derives Serialize under structural_renderer.
// Use pack_err_structural for structured output support.
let derive = quote! {
- #[derive(::mingling::Groupped)]
+ #[derive(::mingling::Grouped)]
};
let expanded = quote! {
@@ -75,7 +75,7 @@ pub fn pack_err(input: TokenStream) -> TokenStream {
// Note: No longer derives Serialize under structural_renderer.
// Use pack_err_structural for structured output support.
let derive = quote! {
- #[derive(::mingling::Groupped)]
+ #[derive(::mingling::Grouped)]
};
let expanded = quote! {
@@ -150,7 +150,7 @@ pub fn pack_err_structural(input: TokenStream) -> TokenStream {
let snake_name = snake_case!(&name_str);
let expanded = quote! {
- #[derive(::mingling::Groupped, ::serde::Serialize)]
+ #[derive(::mingling::Grouped, ::serde::Serialize)]
pub struct #type_name {
/// The snake_case name of this error, automatically set at compile time.
pub name: String,
@@ -179,7 +179,7 @@ pub fn pack_err_structural(input: TokenStream) -> TokenStream {
let snake_name = snake_case!(&name_str);
let expanded = quote! {
- #[derive(::mingling::Groupped, ::serde::Serialize)]
+ #[derive(::mingling::Grouped, ::serde::Serialize)]
pub struct #type_name {
/// The snake_case name of this error, automatically set at compile time.
pub name: String,
diff --git a/mingling_macros/src/renderer.rs b/mingling_macros/src/renderer.rs
index f47511c..d124ec9 100644
--- a/mingling_macros/src/renderer.rs
+++ b/mingling_macros/src/renderer.rs
@@ -1,38 +1,16 @@
use proc_macro::TokenStream;
use quote::{ToTokens, quote};
use syn::spanned::Spanned;
-use syn::{ItemFn, ReturnType, Signature, Type, TypePath, parse_macro_input};
+use syn::{ItemFn, ReturnType, Signature, TypePath, parse_macro_input};
use crate::get_global_set;
use crate::res_injection::{extract_args_info, generate_immut_resource_bindings};
-/// Validates that the function returns `::mingling::RenderResult`.
-fn validate_render_result_return(sig: &Signature) -> syn::Result<()> {
+/// Extracts the user's return type, returning `None` for no return type.
+fn extract_user_return_type(sig: &Signature) -> Option<proc_macro2::TokenStream> {
match &sig.output {
- ReturnType::Type(_, ty) => {
- // Check if the return type is RenderResult
- match &**ty {
- Type::Path(type_path) => {
- let segments = &type_path.path.segments;
- let last_seg = segments.last().map(|s| s.ident.to_string());
- match last_seg.as_deref() {
- Some("RenderResult") => Ok(()),
- _ => Err(syn::Error::new(
- ty.span(),
- "Renderer function must return `RenderResult`",
- )),
- }
- }
- _ => Err(syn::Error::new(
- ty.span(),
- "Renderer function must return `RenderResult`",
- )),
- }
- }
- ReturnType::Default => Err(syn::Error::new(
- sig.span(),
- "Renderer function must have a return type `-> RenderResult`",
- )),
+ ReturnType::Type(_, ty) => Some(quote! { #ty }),
+ ReturnType::Default => None,
}
}
@@ -59,10 +37,8 @@ pub fn renderer_attr(attr: TokenStream, item: TokenStream) -> TokenStream {
Err(e) => return e.to_compile_error().into(),
};
- // Validate that the function returns RenderResult
- if let Err(e) = validate_render_result_return(&input_fn.sig) {
- return e.to_compile_error().into();
- }
+ // Determine the user's return type and whether it needs to be converted to RenderResult
+ let user_return_type = extract_user_return_type(&input_fn.sig);
// Get function body statements
let fn_body_stmts: Vec<syn::Stmt> = input_fn.block.stmts.clone();
@@ -123,10 +99,7 @@ pub fn renderer_attr(attr: TokenStream, item: TokenStream) -> TokenStream {
// The original function preserves the user's exact signature and body.
// Resource parameters are passed directly by the caller, NOT injected from context.
let original_inputs = input_fn.sig.inputs.clone();
- let original_return_type = match &input_fn.sig.output {
- ReturnType::Type(_, ty) => quote! { #ty },
- ReturnType::Default => unreachable!("Already validated that return type is RenderResult"),
- };
+ let original_return_type = user_return_type.clone().unwrap_or(quote! { () });
let expanded = quote! {
#(#fn_attrs)*
@@ -140,11 +113,13 @@ pub fn renderer_attr(attr: TokenStream, item: TokenStream) -> TokenStream {
type Previous = #previous_type;
fn render(#prev_param: Self::Previous) -> ::mingling::RenderResult {
- #render_fn_body
+ let __renderer_result = { #render_fn_body };
+ ::std::convert::Into::into(__renderer_result)
}
}
// Keep the original function unchanged
+ #[allow(dead_code)]
#(#fn_attrs)*
#vis fn #fn_name(#original_inputs) -> #original_return_type {
#(#fn_body_stmts)*
diff --git a/mingling_macros/src/res_injection.rs b/mingling_macros/src/res_injection.rs
index 09da889..606b9a6 100644
--- a/mingling_macros/src/res_injection.rs
+++ b/mingling_macros/src/res_injection.rs
@@ -163,8 +163,10 @@ fn mut_res_binding_name(var_name: &Ident) -> Ident {
syn::Ident::new(&format!("__{}_binding", var_name), var_name.span())
}
-/// Wraps the function body in nested `__modify_res_and_return_route` closures for
-/// each mutable resource parameter (sync version).
+/// Wraps the function body in mutable resource closures (sync version).
+///
+/// For unit return types: uses `modify_res` (closure returns `()`).
+/// For non-unit return types: uses `__modify_res_and_return_route` (closure returns `ChainProcess<C>`).
///
/// The innermost closure gets the original body, and each mutable parameter wraps
/// outward from last to first.
@@ -172,6 +174,7 @@ pub(crate) fn wrap_body_with_mut_resources(
fn_body_stmts: &[syn::Stmt],
mut_resources: &[&ResourceInjection],
program_type: &proc_macro2::TokenStream,
+ is_unit_return: bool,
) -> proc_macro2::TokenStream {
let mut wrapped = quote! {
#(#fn_body_stmts)*
@@ -180,11 +183,19 @@ pub(crate) fn wrap_body_with_mut_resources(
for res in mut_resources {
let var_name = &res.var_name;
let inner_type = &res.inner_type;
- wrapped = quote! {
- ::mingling::this::<#program_type>().__modify_res_and_return_route(|#var_name: &mut #inner_type| {
- #wrapped
- }).into()
- };
+ if is_unit_return {
+ wrapped = quote! {
+ ::mingling::this::<#program_type>().modify_res(|#var_name: &mut #inner_type| {
+ #wrapped
+ })
+ };
+ } else {
+ wrapped = quote! {
+ ::mingling::this::<#program_type>().__modify_res_and_return_route(|#var_name: &mut #inner_type| {
+ #wrapped
+ })
+ };
+ }
}
wrapped
diff --git a/mingling_macros/src/structural_data.rs b/mingling_macros/src/structural_data.rs
index 5350d7e..74bcf09 100644
--- a/mingling_macros/src/structural_data.rs
+++ b/mingling_macros/src/structural_data.rs
@@ -176,7 +176,7 @@ pub(crate) fn pack_structural(input: TokenStream) -> TokenStream {
}
}
- impl ::mingling::Groupped<#program_path> for #type_name {
+ impl ::mingling::Grouped<#program_path> for #type_name {
fn member_id() -> #program_path {
#program_path::#type_name
}
@@ -297,7 +297,7 @@ pub(crate) fn group_structural(input: TokenStream) -> TokenStream {
#type_use
#alias_use
- impl ::mingling::Groupped<__MinglingProgram> for #type_name {
+ impl ::mingling::Grouped<__MinglingProgram> for #type_name {
fn member_id() -> __MinglingProgram {
__MinglingProgram::#type_name
}
diff --git a/mingling_pathf/README.md b/mingling_pathf/README.md
index 89d1811..9706f49 100644
--- a/mingling_pathf/README.md
+++ b/mingling_pathf/README.md
@@ -7,7 +7,7 @@
## Overview
-`mingling_pathf` provides the `pathf` feature for Mingling. It automatically analyzes the full module paths of all Mingling types in a crate at build-time (types defined via `pack!`, `#[derive(Groupped)]`, `#[chain]`, `#[renderer]`, etc.), and generates a mapping from type names to module paths for consumption by `gen_program!()` at compile-time.
+`mingling_pathf` provides the `pathf` feature for Mingling. It automatically analyzes the full module paths of all Mingling types in a crate at build-time (types defined via `pack!`, `#[derive(Grouped)]`, `#[chain]`, `#[renderer]`, etc.), and generates a mapping from type names to module paths for consumption by `gen_program!()` at compile-time.
When enabled, `gen_program!()` uses full module paths for type references in the generated dispatch code (e.g., `downcast::<myapp::sub::ResultMyName>()`), eliminating the need to `use` all types in the module where `gen_program!()` is called. This allows for a more flexible module organization without the constraint of centralized `use` statements.
diff --git a/mingling_pathf/src/pattern_analyzer.rs b/mingling_pathf/src/pattern_analyzer.rs
index 3765971..5bbc3b4 100644
--- a/mingling_pathf/src/pattern_analyzer.rs
+++ b/mingling_pathf/src/pattern_analyzer.rs
@@ -31,7 +31,7 @@ pub fn init_with_config(config: PathfinderConfig) -> PatternAnalyzer {
analyzer.add_pattern(BasicStructPattern);
analyzer.add_pattern(PackPattern);
analyzer.add_pattern(GroupPattern);
- analyzer.add_pattern(GrouppedDerivePattern);
+ analyzer.add_pattern(GroupedDerivePattern);
analyzer.add_pattern(ChainPattern);
analyzer.add_pattern(RendererPattern);
analyzer.add_pattern(HelpPattern);
diff --git a/mingling_pathf/src/patterns.rs b/mingling_pathf/src/patterns.rs
index 9801e9b..9845b73 100644
--- a/mingling_pathf/src/patterns.rs
+++ b/mingling_pathf/src/patterns.rs
@@ -6,7 +6,7 @@ pub use completion::*;
pub use dispatcher::*;
pub use dispatcher_clap::*;
pub use group::*;
-pub use groupped_derive::*;
+pub use grouped_derive::*;
pub use help::*;
pub use pack::*;
pub use renderer::*;
@@ -17,7 +17,7 @@ mod completion;
mod dispatcher;
mod dispatcher_clap;
mod group;
-mod groupped_derive;
+mod grouped_derive;
mod help;
mod pack;
mod renderer;
diff --git a/mingling_pathf/src/patterns/groupped_derive.rs b/mingling_pathf/src/patterns/grouped_derive.rs
index 91daaef..9522c1f 100644
--- a/mingling_pathf/src/patterns/groupped_derive.rs
+++ b/mingling_pathf/src/patterns/grouped_derive.rs
@@ -1,5 +1,5 @@
-//! The `GrouppedDerivePattern` matches structs, enums, and unions annotated with
-//! `#[derive(Groupped)]` or `#[derive(GrouppedSerialize)]` (or any combination
+//! The `GroupedDerivePattern` matches structs, enums, and unions annotated with
+//! `#[derive(Grouped)]` or `#[derive(GroupedSerialize)]` (or any combination
//! with other derives). It also recurses into `mod` items to find nested types.
//! This is used to track grouped items for code generation or analysis.
@@ -7,17 +7,17 @@ use syn::Item;
use crate::pattern_analyzer::{AnalyzeItem, AnalyzePattern};
-/// Matches `#[derive(Groupped)]` and `#[derive(GrouppedSerialize)]`.
+/// Matches `#[derive(Grouped)]` and `#[derive(GroupedSerialize)]`.
///
/// Covers the forms:
-/// - `#[derive(Groupped)] struct T { ... }`
-/// - `#[derive(Groupped, Serialize, ...)] struct T { ... }`
-/// - `#[derive(GrouppedSerialize)] struct T { ... }`
-pub struct GrouppedDerivePattern;
+/// - `#[derive(Grouped)] struct T { ... }`
+/// - `#[derive(Grouped, Serialize, ...)] struct T { ... }`
+/// - `#[derive(GroupedSerialize)] struct T { ... }`
+pub struct GroupedDerivePattern;
-impl AnalyzePattern for GrouppedDerivePattern {
+impl AnalyzePattern for GroupedDerivePattern {
fn contains(&self, content: &str) -> bool {
- content.contains("Groupped")
+ content.contains("Grouped")
}
fn analyze(&self, content: &str) -> Vec<AnalyzeItem> {
@@ -29,19 +29,19 @@ impl AnalyzePattern for GrouppedDerivePattern {
for item in &syntax.items {
match item {
- Item::Struct(s) if has_groupped_derive(&s.attrs) => {
+ Item::Struct(s) if has_grouped_derive(&s.attrs) => {
items.push(AnalyzeItem {
module: String::new(),
item_name: s.ident.to_string(),
});
}
- Item::Enum(e) if has_groupped_derive(&e.attrs) => {
+ Item::Enum(e) if has_grouped_derive(&e.attrs) => {
items.push(AnalyzeItem {
module: String::new(),
item_name: e.ident.to_string(),
});
}
- Item::Union(u) if has_groupped_derive(&u.attrs) => {
+ Item::Union(u) if has_grouped_derive(&u.attrs) => {
items.push(AnalyzeItem {
module: String::new(),
item_name: u.ident.to_string(),
@@ -51,13 +51,13 @@ impl AnalyzePattern for GrouppedDerivePattern {
if let Some((_, nested)) = &item_mod.content {
for n in nested {
match n {
- Item::Struct(s) if has_groupped_derive(&s.attrs) => {
+ Item::Struct(s) if has_grouped_derive(&s.attrs) => {
items.push(AnalyzeItem {
module: item_mod.ident.to_string(),
item_name: s.ident.to_string(),
});
}
- Item::Enum(e) if has_groupped_derive(&e.attrs) => {
+ Item::Enum(e) if has_grouped_derive(&e.attrs) => {
items.push(AnalyzeItem {
module: item_mod.ident.to_string(),
item_name: e.ident.to_string(),
@@ -76,10 +76,10 @@ impl AnalyzePattern for GrouppedDerivePattern {
}
}
-fn has_groupped_derive(attrs: &[syn::Attribute]) -> bool {
+fn has_grouped_derive(attrs: &[syn::Attribute]) -> bool {
attrs.iter().any(|attr| {
if attr.path().is_ident("derive") {
- // Correctly parse comma-separated paths in #[derive(Groupped, Debug, ...)]
+ // Correctly parse comma-separated paths in #[derive(Grouped, Debug, ...)]
attr.parse_args_with(|input: syn::parse::ParseStream| {
let paths =
syn::punctuated::Punctuated::<syn::Path, syn::Token![,]>::parse_terminated(
@@ -87,7 +87,7 @@ fn has_groupped_derive(attrs: &[syn::Attribute]) -> bool {
)?;
Ok(paths.iter().any(|p| {
let name = p.segments.last().unwrap().ident.to_string();
- name == "Groupped" || name == "GrouppedSerialize"
+ name == "Grouped" || name == "GroupedSerialize"
}))
})
.unwrap_or(false)
diff --git a/mingling_pathf/test/src/lib.rs b/mingling_pathf/test/src/lib.rs
index 824cbbf..7e7cbd5 100644
--- a/mingling_pathf/test/src/lib.rs
+++ b/mingling_pathf/test/src/lib.rs
@@ -222,11 +222,11 @@ fn test_group_analyze() {
}
#[test]
-fn test_groupped_derive_analyze() {
+fn test_grouped_derive_analyze() {
let analyzer = mingling_pathf::pattern_analyzer::init();
let file = current_dir()
.unwrap()
- .join("src/test_files/test_groupped_derive.rs");
+ .join("src/test_files/test_grouped_derive.rs");
let r = analyzer.analyze_file(file).unwrap();
let required: Vec<&str> = vec![
diff --git a/mingling_pathf/test/src/test_files/test_groupped_derive.rs b/mingling_pathf/test/src/test_files/test_grouped_derive.rs
index 913587c..20055e9 100644
--- a/mingling_pathf/test/src/test_files/test_groupped_derive.rs
+++ b/mingling_pathf/test/src/test_files/test_grouped_derive.rs
@@ -1,42 +1,42 @@
-#[derive(Groupped)]
+#[derive(Grouped)]
struct Derived1 {
value: String,
}
-#[derive(Groupped, Debug, Clone)]
+#[derive(Grouped, Debug, Clone)]
struct Derived2 {
value: i32,
}
-#[derive(GrouppedSerialize)]
+#[derive(GroupedSerialize)]
struct Derived3 {
value: bool,
}
-#[derive(Groupped)]
+#[derive(Grouped)]
enum EnumDerived1 {
A,
B,
}
-#[derive(GrouppedSerialize)]
+#[derive(GroupedSerialize)]
enum EnumDerived2 {
X(String),
Y(i32),
}
pub mod sub {
- #[derive(Groupped)]
+ #[derive(Grouped)]
struct Derived1 {
value: String,
}
- #[derive(GrouppedSerialize)]
+ #[derive(GroupedSerialize)]
struct Derived3 {
value: bool,
}
- #[derive(Groupped)]
+ #[derive(Grouped)]
enum EnumDerived1 {
A,
}
diff --git a/mingling_picker/Cargo.toml b/mingling_picker/Cargo.toml
deleted file mode 100644
index ddab33c..0000000
--- a/mingling_picker/Cargo.toml
+++ /dev/null
@@ -1,17 +0,0 @@
-[package]
-name = "mingling_picker"
-version.workspace = true
-edition.workspace = true
-license.workspace = true
-repository.workspace = true
-authors = ["Weicao-CatilGrass"]
-readme = "README.md"
-description = "Mingling's lightweight argument parser"
-
-[features]
-mingling_support = ["dep:mingling_core", "mingling_picker_macros/mingling_support"]
-
-[dependencies]
-mingling_core = { workspace = true, optional = true }
-mingling_picker_macros.workspace = true
-just_fmt.workspace = true
diff --git a/mingling_picker/src/corebind.rs b/mingling_picker/src/corebind.rs
deleted file mode 100644
index 8b13789..0000000
--- a/mingling_picker/src/corebind.rs
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/mling/src/cli.rs b/mling/src/cli.rs
index 67d2ef0..69e4fe9 100644
--- a/mling/src/cli.rs
+++ b/mling/src/cli.rs
@@ -1,3 +1,6 @@
+// `mling` has not been replaced from `parser` to `picker` yet
+#![allow(deprecated)]
+
use crate::{
CMDCompletion, ErrorDispatcherNotFound, Next, ThisProgram,
display::markdown,
@@ -8,7 +11,7 @@ use crate::{
};
use colored::Colorize;
use mingling::{
- Groupped, Program, RenderResult,
+ Grouped, Program, RenderResult,
hook::ProgramHook,
macros::{chain, help, pack, program_setup, renderer},
res::ResExitCode,
diff --git a/mling/src/proj_mgr/generator.rs b/mling/src/proj_mgr/generator.rs
index 4f965d9..f0dbdd7 100644
--- a/mling/src/proj_mgr/generator.rs
+++ b/mling/src/proj_mgr/generator.rs
@@ -1,7 +1,7 @@
use std::path::{self, PathBuf};
use mingling::{
- Groupped, RenderResult,
+ Grouped, RenderResult,
macros::{chain, pack, renderer, route},
};
use std::io::Write as _;
diff --git a/mling/src/proj_mgr/show_binaries.rs b/mling/src/proj_mgr/show_binaries.rs
index 9d5caf0..4fb5c28 100644
--- a/mling/src/proj_mgr/show_binaries.rs
+++ b/mling/src/proj_mgr/show_binaries.rs
@@ -2,7 +2,7 @@ use std::path::PathBuf;
use colored::Colorize;
use mingling::{
- Groupped, RenderResult,
+ Grouped, RenderResult,
macros::{chain, pack, renderer},
};
use serde::Serialize;
@@ -17,7 +17,7 @@ use crate::{
res::ResManifestPath,
};
-#[derive(Serialize, Groupped)]
+#[derive(Serialize, Grouped)]
pub struct ResultBinaries {
pub binaries: Vec<DataBinary>,
}
diff --git a/mling/src/proj_mgr/show_directories.rs b/mling/src/proj_mgr/show_directories.rs
index 7d7c074..5c5f448 100644
--- a/mling/src/proj_mgr/show_directories.rs
+++ b/mling/src/proj_mgr/show_directories.rs
@@ -1,6 +1,6 @@
use colored::Colorize;
use mingling::{
- Groupped, RenderResult,
+ Grouped, RenderResult,
macros::{chain, pack, renderer},
};
use serde::Serialize;
@@ -12,12 +12,12 @@ use crate::{
res::ResManifestPath,
};
-#[derive(Serialize, Groupped)]
+#[derive(Serialize, Grouped)]
pub struct ResultWorkspaceDirectory {
pub path: String,
}
-#[derive(Serialize, Groupped)]
+#[derive(Serialize, Grouped)]
pub struct ResultTargetDirectory {
pub path: String,
}
diff --git a/run.sh b/run.sh
index c4079cb..c4079cb 100644..100755
--- a/run.sh
+++ b/run.sh
diff --git a/verified-docs.toml b/verified-docs.toml
index d66967f..f3b1f85 100644
--- a/verified-docs.toml
+++ b/verified-docs.toml
@@ -3,5 +3,6 @@
[verified]
readme = "./README.md"
+getting_started = "./GETTING_STARTED.md"
documents_en_us = "./docs/pages/**"
documents_zh_cn = "./docs/_zh_CN/pages/**"