aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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
-rwxr-xr-x[-rw-r--r--].run/src/bin/clippy-fix.sh0
-rw-r--r--.run/src/bin/package-all.rs121
-rw-r--r--.run/src/dependency_order.rs17
-rw-r--r--CHANGELOG.md86
-rw-r--r--README.md12
-rw-r--r--arg_picker/test/src/test/value_flag_test.rs10
-rw-r--r--docs/_zh_CN/pages/11-resource-system.md2
-rw-r--r--docs/_zh_CN/pages/3-define-a-chain.md6
-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/advanced/2-structural-renderer.md2
-rw-r--r--docs/_zh_CN/pages/other/features.md4
-rw-r--r--docs/pages/11-resource-system.md2
-rw-r--r--docs/pages/3-define-a-chain.md6
-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/advanced/2-structural-renderer.md2
-rw-r--r--docs/pages/other/features.md4
-rw-r--r--examples/example-argument-parse/src/main.rs2
-rw-r--r--examples/example-async-support/src/main.rs2
-rw-r--r--examples/example-basic/src/main.rs2
-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-enum-tag/src/main.rs2
-rw-r--r--examples/example-hook/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--mingling/src/example_docs.rs18
-rw-r--r--mingling/src/lib.md2
-rw-r--r--mingling/src/setups/exit_code.rs11
-rw-r--r--mingling_core/src/asset/global_resource.rs7
-rw-r--r--mingling_core/src/renderer/render_result.rs50
-rw-r--r--mingling_macros/src/chain.rs200
-rw-r--r--mingling_macros/src/help.rs45
-rw-r--r--mingling_macros/src/lib.rs5
-rw-r--r--mingling_macros/src/renderer.rs47
-rw-r--r--mingling_macros/src/res_injection.rs25
59 files changed, 868 insertions, 365 deletions
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/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/package-all.rs b/.run/src/bin/package-all.rs
index 59f0f89..5d7cbbb 100644
--- a/.run/src/bin/package-all.rs
+++ b/.run/src/bin/package-all.rs
@@ -1,6 +1,8 @@
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,
@@ -41,8 +43,8 @@ fn main() {
"cargo metadata --format-version 1 --no-deps",
&workspace_root,
)
- .unwrap_or_else(|(_code, msg)| {
- eprintln_cargo_style!("cargo metadata failed (exit {code}):\n{msg}");
+ .unwrap_or_else(|(code, _msg)| {
+ eprintln_cargo_style!(format!("cargo metadata failed (exit {code}):\n{{msg}}"));
std::process::exit(1);
});
@@ -171,7 +173,7 @@ fn main() {
}
Err((code, msg)) => {
// Print output but don't fail yet
- eprintln_cargo_style!("cargo package exited with code {code}:");
+ eprintln_cargo_style!(format!("cargo package exited with code {code}:"));
println!("{msg}");
}
}
@@ -202,7 +204,7 @@ fn main() {
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().map_or(true, |e| e != "crate") {
+ if path.extension().is_none_or(|e| e != "crate") {
continue;
}
@@ -217,11 +219,7 @@ fn main() {
.and_then(|dash| {
// Check if what follows looks like a semver
let ver_part = &fname[dash + 1..];
- if ver_part
- .chars()
- .next()
- .map_or(false, |c| c.is_ascii_digit())
- {
+ if ver_part.chars().next().is_some_and(|c| c.is_ascii_digit()) {
Some(&fname[..dash])
} else {
None
@@ -234,20 +232,18 @@ fn main() {
std::fs::create_dir_all(&target_dir)
.unwrap_or_else(|e| panic!("failed to create {}: {e}", target_dir.display()));
- // Extract using tar + gzip
- let extract_cmd = format!(
- "tar -xzf \"{}\" -C \"{}\"",
- path.display(),
- target_dir.display()
- );
- let extract_ok = std::process::Command::new("sh")
- .arg("-c")
- .arg(&extract_cmd)
- .status()
- .expect("failed to run tar");
-
- if !extract_ok.success() {
- eprintln_cargo_style!("Failed to extract {}", fname);
+ // 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;
}
@@ -317,31 +313,30 @@ fn patch_workspace_deps(
in_workspace_deps = false;
}
- if in_workspace_deps {
- if let Some(dep_name) = trimmed.split('=').next().map(|s| s.trim()) {
- if 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;
- }
+ 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);
@@ -391,25 +386,21 @@ fn patch_member_path_deps(
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 = \"")
{
- if let Some(dep_name) = trimmed.split('=').next().map(|s| s.trim()) {
- let dep_name = dep_name.trim_end_matches(".workspace");
- if let Some(version) = version_map.get(dep_name) {
- if !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;
- }
- }
- }
+ 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);
diff --git a/.run/src/dependency_order.rs b/.run/src/dependency_order.rs
index 99317ae..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() {
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ac00643..49b1f35 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -108,6 +108,59 @@ None
_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
+ }
+ ```
+
#### **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.
@@ -127,6 +180,39 @@ None
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.
+
---
## Release 0.2.2 (2026-07-10)
diff --git a/README.md b/README.md
index 50c66c6..f67e6d4 100644
--- a/README.md
+++ b/README.md
@@ -166,7 +166,7 @@ fn handle_greet(args: EntryGreet) -> Next {
.first()
.cloned()
.unwrap_or_else(|| "World".to_string());
- ResultGreeting::new(greeting)
+ ResultGreeting::new(greeting).into()
}
```
@@ -234,7 +234,7 @@ fn handle_greet(args: EntryGreet) -> Next {
.pick::<String>(()) // positional: first string
.pick_or::<u8>(["-r", "--repeat"], 1) // optional flag with default
.unpack();
- ResultGreeting::new(format!("{} x{}", name, count))
+ ResultGreeting::new(format!("{} x{}", name, count)).into()
}
```
@@ -252,7 +252,7 @@ fn handle(args: EntryGreet) -> Next {
.pick::<Option<String>>(())
.pick_or::<u8>(["-r", "--repeat"], 1)
.unpack();
- ResultGreeting::new(format!("{} x{}", name.unwrap_or_default(), count))
+ ResultGreeting::new(format!("{} x{}", name.unwrap_or_default(), count)).into()
}
```
@@ -279,7 +279,7 @@ impl PickableEnum for Language {}
#[chain]
fn handle(args: EntryLang) -> Next {
let lang: Language = args.pick(()).unpack();
- lang
+ lang.into()
}
```
@@ -781,7 +781,7 @@ pack!(ResultDownloaded = String);
#[chain]
pub async fn handle_download(args: EntryDownload) -> Next {
let file = args.pick(()).unpack();
- download_file(file).await
+ download_file(file).await.into()
}
async fn download_file(name: String) -> ResultDownloaded {
@@ -843,7 +843,7 @@ fn handle_greet(args: EntryGreet) -> Next {
.first()
.cloned()
.unwrap_or_else(|| "World".to_string());
- ResultGreeting::new(greeting)
+ ResultGreeting::new(greeting).into()
}
#[renderer]
diff --git a/arg_picker/test/src/test/value_flag_test.rs b/arg_picker/test/src/test/value_flag_test.rs
index 3df053e..c60ab1e 100644
--- a/arg_picker/test/src/test/value_flag_test.rs
+++ b/arg_picker/test/src/test/value_flag_test.rs
@@ -1,5 +1,5 @@
use arg_picker::value::Flag;
-use arg_picker::{macros::arg, IntoPicker};
+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/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..b2850c5 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()
}
```
@@ -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/advanced/2-structural-renderer.md b/docs/_zh_CN/pages/advanced/2-structural-renderer.md
index 9a4f111..6b6b0f9 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]
diff --git a/docs/_zh_CN/pages/other/features.md b/docs/_zh_CN/pages/other/features.md
index bfd9efc..d92e597 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();
// ... 此处为断言逻辑
}
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..b2822bc 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()
}
```
@@ -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/advanced/2-structural-renderer.md b/docs/pages/advanced/2-structural-renderer.md
index f31f758..fab7530 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]
diff --git a/docs/pages/other/features.md b/docs/pages/other/features.md
index 813ccdd..325bb75 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
}
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-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-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-enum-tag/src/main.rs b/examples/example-enum-tag/src/main.rs
index 01c7767..91c5358 100644
--- a/examples/example-enum-tag/src/main.rs
+++ b/examples/example-enum-tag/src/main.rs
@@ -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-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/mingling/src/example_docs.rs b/mingling/src/example_docs.rs
index 4699a50..c4f59c9 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 = ());
@@ -199,7 +199,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 +292,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`
@@ -672,7 +672,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.
@@ -1026,7 +1026,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 +1428,7 @@ pub mod example_help {}
/// .cloned()
/// .unwrap_or_else(|| "World".to_string())
/// .into();
-/// name
+/// name.into()
/// }
///
/// /// Renders the greeting message with the provided name.
@@ -1967,7 +1967,7 @@ pub mod example_pack_err {}
/// // Panic happens here, will be caught
/// panic!("{}", s)
/// }
-/// None => NotPanic::default(),
+/// None => NotPanic::default().into(),
/// }
/// }
///
@@ -2161,7 +2161,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 +2323,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
diff --git a/mingling/src/lib.md b/mingling/src/lib.md
index cd89b96..03fa61d 100644
--- a/mingling/src/lib.md
+++ b/mingling/src/lib.md
@@ -40,7 +40,7 @@ fn handle_greet(args: EntryGreet) -> Next {
.cloned()
.unwrap_or_else(|| "World".to_string())
.into();
- name
+ name.into()
}
#[renderer]
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_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/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..5044227 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,8 +20,9 @@ fn is_unit_return_type(sig: &Signature) -> bool {
}
}
+/// Validates that the return type is `Next`, `ChainProcess<ThisProgram>`, `()`, or omitted.
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(());
}
@@ -31,38 +31,36 @@ fn validate_return_type(sig: &Signature) -> Result<(), proc_macro2::TokenStream>
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());
+ let ident_str = last_segment.ident.to_string();
+ if ident_str == "Next" || ident_str == "ChainProcess" {
+ return Ok(());
}
- }
- _ => {
- return Err(syn::Error::new(
+ Err(syn::Error::new(
ty.span(),
- "Chain function must return `Next` or `()`",
+ "Chain function must return `Next`, `ChainProcess<ThisProgram>`, `()`, or omit the return type",
)
- .to_compile_error());
+ .to_compile_error())
}
+ _ => Err(syn::Error::new(
+ ty.span(),
+ "Chain function must return `Next`, `ChainProcess<ThisProgram>`, `()`, or omit the return type",
+ )
+ .to_compile_error()),
},
ReturnType::Default => {
- return Err(syn::Error::new(
+ Err(syn::Error::new(
sig.span(),
- "Chain function must specify a return type (must be `Next` or `()`)",
+ "Chain function must specify a return type (must be `Next`, `ChainProcess<ThisProgram>`, or `()`)",
)
- .to_compile_error());
+ .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,7 +68,6 @@ 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,
@@ -78,59 +75,42 @@ fn generate_proc_fn(
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::Groupped::<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
+ #wrapped_body;
<crate::ResultEmpty as ::mingling::Groupped::<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 }
};
+ // Use a let-binding with explicit type annotation so that the inner
+ // body's `.into()` calls resolve correctly (same as the original function).
quote! {
- #call
+ let __chain_result: ::mingling::ChainProcess<#program_type> = { #body };
+ __chain_result
}
};
@@ -138,7 +118,7 @@ fn generate_proc_fn(
{
quote! {
async fn proc(#prev_param: #previous_type) -> ::mingling::ChainProcess<#program_type> {
- #call_or_wrapped
+ #proc_body
}
}
}
@@ -147,73 +127,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 +143,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 +159,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 +217,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,14 +234,13 @@ 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
+ // 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,
@@ -331,20 +249,14 @@ pub fn chain_attr(attr: TokenStream, item: TokenStream) -> TokenStream {
is_unit_return,
);
- // 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 +268,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/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..9419f39 100644
--- a/mingling_macros/src/lib.rs
+++ b/mingling_macros/src/lib.rs
@@ -867,7 +867,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 +1235,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
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