aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md7
-rw-r--r--README.md4
-rw-r--r--mingling_core/src/program/once_exec.rs194
-rw-r--r--mingling_core/src/program/repl_exec.rs126
4 files changed, 114 insertions, 217 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index da1c26a..9890439 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -171,14 +171,13 @@ None
_No migration is required — these are purely additive derives that expand the type's capabilities without affecting existing behavior._
-**[`core`]** Added the `build` feature (renamed from `builds`) to `mingling_core` and `mingling`. The old `builds` feature has been deprecated in favor of `build`, with a backward-compatibility alias retained in `mingling/Cargo.toml`:
+8. **[`core`]** Added the `build` feature (renamed from `builds`) to `mingling_core` and `mingling`. The old `builds` feature has been deprecated in favor of `build`, with a backward-compatibility alias retained in `mingling/Cargo.toml`:
- **`mingling_core/Cargo.toml`**: Renamed the feature from `builds` to `build`.
- **`mingling/Cargo.toml`**: Changed the feature dependency from `mingling_core/builds` to `mingling_core/build`. A deprecated `builds` feature alias is kept as `builds = ["mingling_core/build"]` with a note indicating it will be removed in a future breaking change.
_No behavioral changes — the `build` feature provides identical functionality to the old `builds` feature. Downstream code using `builds` continues to work via the alias, but should migrate to `build`._
-
-8. **[`core`]** Renamed `ResourceMarker` methods from public names (`res_clone`, `res_default`, `modify`) to doc-hidden internal names (`__resource_marker_clone`, `__resource_marker_default`, `__resource_marker_modify`). These methods are internal implementation details of the resource injection system and should not be called directly by user code. By prefixing with `__` and adding `#[doc(hidden)]`, they are still technically accessible but hidden from documentation and tooling, reducing API surface confusion.
+9. **[`core`]** Renamed `ResourceMarker` methods from public names (`res_clone`, `res_default`, `modify`) to doc-hidden internal names (`__resource_marker_clone`, `__resource_marker_default`, `__resource_marker_modify`). These methods are internal implementation details of the resource injection system and should not be called directly by user code. By prefixing with `__` and adding `#[doc(hidden)]`, they are still technically accessible but hidden from documentation and tooling, reducing API surface confusion.
- **`res_clone()`** → **`__resource_marker_clone()`** — Internal method for cloning a resource value during resource injection.
- **`res_default()`** → **`__resource_marker_default()`** — Internal method for creating a default resource value during resource injection.
@@ -188,7 +187,7 @@ None
A new module `mingling_core::asset::core_invokes` has been added to provide a centralized location for internal invocation helpers.
-9. **[`core:exec`]** Refactored the program execution pipeline (`exec` and `exec_with_args`) to use the `might_be_async` crate instead of manual `#[cfg(feature = "async")]` duplication. The previously separate sync and async implementations have been consolidated into a single `#[might_be_async::func]`-annotated function, with `might_be_async::invoke!()` wrapping the `C::do_chain(current)` call inside `exec_with_args` and the delegation from `exec` to `exec_with_args`.
+10. **[`core:exec`]** Refactored the program execution pipeline (`exec` and `exec_with_args`) to use the `might_be_async` crate instead of manual `#[cfg(feature = "async")]` duplication. The previously separate sync and async implementations have been consolidated into a single `#[might_be_async::func]`-annotated function, with `might_be_async::invoke!()` wrapping the `C::do_chain(current)` call inside `exec_with_args` and the delegation from `exec` to `exec_with_args`.
The `exec` function no longer contains the full execution loop inline. Instead, it delegates to `exec_with_args` (which now also carries the `#[might_be_async::func]` annotation), reducing code duplication and centralizing the execution logic.
diff --git a/README.md b/README.md
index 753413d..2066f18 100644
--- a/README.md
+++ b/README.md
@@ -130,11 +130,11 @@ features = []
- [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
+ - [x] **Mingling** Linter
- [ ] **Mingling** Project Generator
- [ ] **Mingling** Program Installer & Manager (For development)
- [ ] Helpdoc Editor
- - [ ] [`picker`] A more efficient and intelligent argument parser
+ - [x] [`picker`] A more efficient and intelligent argument parser
- [x] [`macros`] ~~Remove r_print! / r_println! macros~~ (see below)
- [x] [`macros`] Make implicit modifications to functions explicit
- [ ] Milestone.3 "Unplanned"
diff --git a/mingling_core/src/program/once_exec.rs b/mingling_core/src/program/once_exec.rs
index a846d04..96723fd 100644
--- a/mingling_core/src/program/once_exec.rs
+++ b/mingling_core/src/program/once_exec.rs
@@ -1,28 +1,10 @@
use crate::THIS_PROGRAM;
use crate::{Program, ProgramCollect, RenderResult, error::ProgramExecuteError};
-// Async program
-#[cfg(feature = "async")]
impl<C> Program<C>
where
C: ProgramCollect<Enum = C>,
{
- pub(crate) async fn exec_wrapper<F, Fut>(self, f: F) -> Fut::Output
- where
- C: 'static + Send + Sync,
- F: FnOnce(&'static Program<C>) -> Fut + Send + Sync,
- Fut: Future + Send,
- {
- THIS_PROGRAM.set(Box::new(self));
- let program = THIS_PROGRAM
- .get_raw()
- .unwrap()
- .downcast_ref::<Program<C>>()
- .unwrap();
-
- f(program).await
- }
-
/// Run the command line program
///
/// # Errors
@@ -33,7 +15,8 @@ where
/// # Panics
///
/// Panics if the program encounters a non-recoverable internal error.
- pub async fn exec_without_render(mut self) -> Result<RenderResult, ProgramExecuteError>
+ #[might_be_async::func]
+ pub fn exec_without_render(mut self) -> Result<RenderResult, ProgramExecuteError>
where
C: 'static + Send + Sync,
{
@@ -42,21 +25,56 @@ where
self.args = self.args.iter().skip(1).cloned().collect();
- return self
- .exec_wrapper(|p| async { crate::exec::exec(p).await.map_err(|e| e.into()) })
- .await;
+ #[cfg(not(feature = "async"))]
+ {
+ #[cfg(panic = "abort")]
+ return self.exec_wrapper(|p| crate::exec::exec(p).map_err(|e| e.into()));
+
+ #[cfg(not(panic = "abort"))]
+ match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
+ self.exec_wrapper(|p| crate::exec::exec(p).map_err(std::convert::Into::into))
+ })) {
+ Ok(result) => result,
+ Err(panic_info) => {
+ let panic_payload = crate::error::ProgramPanic {
+ payload: panic_info,
+ };
+
+ let program = THIS_PROGRAM
+ .get_raw()
+ .unwrap()
+ .downcast_ref::<Program<C>>()
+ .unwrap();
+
+ #[cfg(not(feature = "async"))]
+ program.run_hook_exec_panic(crate::hook::HookPanicInfo {
+ panic: &panic_payload,
+ });
+
+ Err(ProgramExecuteError::Panic(panic_payload))
+ }
+ }
+ }
+
+ #[cfg(feature = "async")]
+ {
+ return self
+ .exec_wrapper(|p| async { crate::exec::exec(p).await.map_err(|e| e.into()) })
+ .await;
+ }
}
/// Run the command line program
#[must_use]
- pub async fn exec(self) -> i32
+ #[might_be_async::func]
+ pub fn exec(self) -> i32
where
C: 'static + Send + Sync,
{
use crate::error::ProgramExecuteError;
let stdout_setting = self.stdout_setting.clone();
- let result = match self.exec_without_render().await {
+ let result = match might_be_async::invoke!(self.exec_without_render()) {
Ok(r) => r,
Err(e) => match e {
ProgramExecuteError::DispatcherNotFound => {
@@ -88,11 +106,12 @@ where
}
/// Run the command line program, then exit
- pub async fn exec_and_exit(self)
+ #[might_be_async::func]
+ pub fn exec_and_exit(self)
where
C: 'static + Send + Sync,
{
- let exit_code = self.exec().await;
+ let exit_code = might_be_async::invoke!(self.exec());
// SAFETY: exec() is synchronous — it returns only after all
// chain handlers and renderers have finished. No code still
// holds references from get_raw() at this point.
@@ -101,6 +120,29 @@ where
}
}
+// Async program
+#[cfg(feature = "async")]
+impl<C> Program<C>
+where
+ C: ProgramCollect<Enum = C>,
+{
+ pub(crate) async fn exec_wrapper<F, Fut>(self, f: F) -> Fut::Output
+ where
+ C: 'static + Send + Sync,
+ F: FnOnce(&'static Program<C>) -> Fut + Send + Sync,
+ Fut: Future + Send,
+ {
+ THIS_PROGRAM.set(Box::new(self));
+ let program = THIS_PROGRAM
+ .get_raw()
+ .unwrap()
+ .downcast_ref::<Program<C>>()
+ .unwrap();
+
+ f(program).await
+ }
+}
+
// Sync program
#[cfg(not(feature = "async"))]
impl<C> Program<C>
@@ -126,104 +168,4 @@ where
f(program)
}
-
- /// Run the command line program
- ///
- /// # Errors
- ///
- /// Returns `Err(ProgramExecuteError)` if execution fails,
- /// e.g., if no dispatcher is found or a chain error occurs.
- ///
- /// # Panics
- ///
- /// Panics if the program encounters a non-recoverable internal error.
- pub fn exec_without_render(mut self) -> Result<RenderResult, ProgramExecuteError>
- where
- C: 'static + Send + Sync,
- {
- // Run hooks
- self.run_hook_on_begin(crate::hook::HookBeginInfo {});
-
- self.args = self.args.iter().skip(1).cloned().collect();
-
- #[cfg(panic = "abort")]
- return self.exec_wrapper(|p| crate::exec::exec(p).map_err(|e| e.into()));
-
- #[cfg(not(panic = "abort"))]
- match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
- self.exec_wrapper(|p| crate::exec::exec(p).map_err(std::convert::Into::into))
- })) {
- Ok(result) => result,
- Err(panic_info) => {
- let panic_payload = crate::error::ProgramPanic {
- payload: panic_info,
- };
-
- let program = THIS_PROGRAM
- .get_raw()
- .unwrap()
- .downcast_ref::<Program<C>>()
- .unwrap();
-
- program.run_hook_exec_panic(crate::hook::HookPanicInfo {
- panic: &panic_payload,
- });
-
- Err(ProgramExecuteError::Panic(panic_payload))
- }
- }
- }
-
- /// Run the command line program
- #[must_use]
- pub fn exec(self) -> i32
- where
- C: 'static + Send + Sync,
- {
- use crate::error::ProgramExecuteError;
-
- let stdout_setting = self.stdout_setting.clone();
- let result = match self.exec_without_render() {
- Ok(r) => r,
- Err(e) => match e {
- ProgramExecuteError::DispatcherNotFound => {
- eprintln!("Dispatcher not found");
- return 1;
- }
- ProgramExecuteError::RendererNotFound(renderer_name) => {
- eprintln!("Renderer `{renderer_name}` not found");
- return 1;
- }
- ProgramExecuteError::Other(e) => {
- eprintln!("{e}");
- return 1;
- }
- ProgramExecuteError::Panic(unwinded_error) => {
- eprintln!("{unwinded_error}");
- return 1;
- }
- },
- };
-
- // Read exit code
- // Render result
- if stdout_setting.render_output {
- result.std_print();
- }
-
- result.exit_code
- }
-
- /// Run the command line program, then exit
- pub fn exec_and_exit(self)
- where
- C: 'static + Send + Sync,
- {
- let exit_code = self.exec();
- // SAFETY: exec() is synchronous — it returns only after all
- // chain handlers and renderers have finished. No code still
- // holds references from get_raw() at this point.
- drop(unsafe { THIS_PROGRAM.take() });
- std::process::exit(exit_code)
- }
}
diff --git a/mingling_core/src/program/repl_exec.rs b/mingling_core/src/program/repl_exec.rs
index cbda9da..f84b291 100644
--- a/mingling_core/src/program/repl_exec.rs
+++ b/mingling_core/src/program/repl_exec.rs
@@ -13,7 +13,6 @@ use crate::program::repl_exec::splitter::split_input_string;
use crate::{Program, ProgramCollect, RenderResult};
use crate::{program::repl_exec::res::ResREPL, this};
-#[cfg(not(feature = "async"))]
impl<C> Program<C>
where
C: ProgramCollect<Enum = C> + Send + Sync + 'static,
@@ -22,106 +21,63 @@ where
///
/// This method starts an infinite loop that continuously reads user input, parses commands, executes them,
/// and displays the execution result or error message. It is suitable for scenarios requiring command-line interaction with the user.
+ ///
+ /// **Note:** When the `async` feature is enabled, panic unwinding is not supported.
+ /// Any panics during command execution will result in an abort rather than being caught and handled gracefully.
+ #[might_be_async::func]
pub fn exec_repl(mut self) {
// Inject default REPL resource
self.with_resource(ResREPL::default());
self.run_hook_repl_on_begin(crate::hook::HookREPLBeginInfo {});
- self.exec_wrapper(|p| -> () {
- loop {
- p.run_hook_repl_pre_readline(crate::hook::HookREPLPreReadlineInfo {});
- let mut readline = p
- .run_hook_repl_readline(crate::hook::HookREPLReadlineInfo {})
- .unwrap_or_default();
- p.run_hook_repl_post_readline(crate::hook::HookREPLPostReadlineInfo {
- line: &mut readline,
- });
-
- let args = split_input_string(readline.clone());
-
- p.run_hook_repl_pre_exec(crate::hook::HookREPLPreExecInfo { args: &args });
- match exec_once(p, args) {
- Ok(r) => {
- p.run_hook_repl_on_receive_result(
- crate::hook::HookREPLOnReceiveResultInfo { result: &r },
- );
- }
- Err(ProgramInternalExecuteError::REPLPanic(panic)) => {
- p.run_hook_repl_on_panic(crate::hook::HookREPLOnPanicInfo {
- panic: &panic,
- });
- }
- _ => {}
- }
- p.run_hook_repl_post_exec(crate::hook::HookREPLPostExecInfo {});
-
- if this::<C>().res::<ResREPL>().unwrap().exit {
- p.run_hook_repl_exit(crate::hook::HookREPLExitInfo {});
- break;
- }
-
- p.run_hook_repl_loop_once(crate::hook::HookREPLLoopOnceInfo {});
- }
- });
+ might_be_async::select!(
+ self.exec_wrapper(async |p| -> () {
+ repl_loop(p).await;
+ })
+ else
+ self.exec_wrapper(|p| -> () { repl_loop(p); }
+ )
+ );
}
}
-#[cfg(feature = "async")]
-impl<C> Program<C>
+#[might_be_async::func]
+fn repl_loop<C>(p: &'static Program<C>)
where
C: ProgramCollect<Enum = C> + Send + Sync + 'static,
{
- /// Executes the REPL interactive CLI mode.
- ///
- /// This method starts an infinite loop that continuously reads user input, parses commands, executes them,
- /// and displays the execution result or error message. It is suitable for scenarios requiring command-line interaction with the user.
- ///
- /// **Note:** When the `async` feature is enabled, panic unwinding is not supported.
- /// Any panics during command execution will result in an abort rather than being caught and handled gracefully.
- pub async fn exec_repl(mut self) {
- // Inject default REPL resource
- self.with_resource(ResREPL::default());
+ loop {
+ p.run_hook_repl_pre_readline(crate::hook::HookREPLPreReadlineInfo {});
+ let mut readline = p
+ .run_hook_repl_readline(crate::hook::HookREPLReadlineInfo {})
+ .unwrap_or_default();
+ p.run_hook_repl_post_readline(crate::hook::HookREPLPostReadlineInfo {
+ line: &mut readline,
+ });
- self.run_hook_repl_on_begin(crate::hook::HookREPLBeginInfo {});
+ let args = split_input_string(readline.clone());
- self.exec_wrapper(async |p| -> () {
- loop {
- p.run_hook_repl_pre_readline(crate::hook::HookREPLPreReadlineInfo {});
- let mut readline = p
- .run_hook_repl_readline(crate::hook::HookREPLReadlineInfo {})
- .unwrap_or_default();
- p.run_hook_repl_post_readline(crate::hook::HookREPLPostReadlineInfo {
- line: &mut readline,
+ p.run_hook_repl_pre_exec(crate::hook::HookREPLPreExecInfo { args: &args });
+ match might_be_async::invoke!(exec_once(p, args)) {
+ Ok(r) => {
+ p.run_hook_repl_on_receive_result(crate::hook::HookREPLOnReceiveResultInfo {
+ result: &r,
});
-
- let args = split_input_string(readline.clone());
-
- p.run_hook_repl_pre_exec(crate::hook::HookREPLPreExecInfo { args: &args });
- match exec_once(p, args).await {
- Ok(r) => {
- p.run_hook_repl_on_receive_result(
- crate::hook::HookREPLOnReceiveResultInfo { result: &r },
- );
- }
- Err(ProgramInternalExecuteError::REPLPanic(panic)) => {
- p.run_hook_repl_on_panic(crate::hook::HookREPLOnPanicInfo {
- panic: &panic,
- });
- }
- _ => {}
- }
- p.run_hook_repl_post_exec(crate::hook::HookREPLPostExecInfo {});
-
- if this::<C>().res::<ResREPL>().unwrap().exit {
- p.run_hook_repl_exit(crate::hook::HookREPLExitInfo {});
- break;
- }
-
- p.run_hook_repl_loop_once(crate::hook::HookREPLLoopOnceInfo {});
}
- })
- .await;
+ Err(ProgramInternalExecuteError::REPLPanic(panic)) => {
+ p.run_hook_repl_on_panic(crate::hook::HookREPLOnPanicInfo { panic: &panic });
+ }
+ _ => {}
+ }
+ p.run_hook_repl_post_exec(crate::hook::HookREPLPostExecInfo {});
+
+ if this::<C>().res::<ResREPL>().unwrap().exit {
+ p.run_hook_repl_exit(crate::hook::HookREPLExitInfo {});
+ break;
+ }
+
+ p.run_hook_repl_loop_once(crate::hook::HookREPLLoopOnceInfo {});
}
}