diff options
30 files changed, 592 insertions, 486 deletions
diff --git a/mingling_core/src/any.rs b/mingling_core/src/any.rs index e922e2e..1c4cabe 100644 --- a/mingling_core/src/any.rs +++ b/mingling_core/src/any.rs @@ -45,7 +45,7 @@ impl<G> AnyOutput<G> { } } - /// Create an `AnyOutput` from a raw value with a manually specified member_id. + /// Create an `AnyOutput` from a raw value with a manually specified [`member_id`]. /// /// This function bypasses the [`Grouped`] trait, meaning the `member_id` you provide /// does **not** have to match the actual concrete type `T`. The scheduler uses @@ -75,16 +75,16 @@ impl<G> AnyOutput<G> { /// /// The `TypeId` is set during construction (via [`AnyOutput::new`] or [`AnyOutput::new_bare`]) /// and is used for subsequent downcasting and type checking. - pub fn type_id(&self) -> std::any::TypeId { + pub const fn type_id(&self) -> std::any::TypeId { self.type_id } - /// Get the [`member_id`] of the concrete type stored in `inner`. + /// Get the `member_id` of the concrete type stored in `inner`. /// - /// [`member_id`] is set during construction (via [`AnyOutput::new`] or [`AnyOutput::new_bare`]) + /// `member_id` is set during construction (via [`AnyOutput::new`] or [`AnyOutput::new_bare`]) /// and identifies which variant of the output enum this value corresponds to. /// The scheduler uses this value to dispatch the output to the correct next step. - pub fn member_id(&self) -> G + pub const fn member_id(&self) -> G where G: Copy, { @@ -114,12 +114,12 @@ impl<G> AnyOutput<G> { } /// Route the output to the next Chain - pub fn route_chain(self) -> ChainProcess<G> { + pub const fn route_chain(self) -> ChainProcess<G> { ChainProcess::Ok((self, NextProcess::Chain)) } /// Route the output to the Renderer, ending execution - pub fn route_renderer(self) -> ChainProcess<G> { + pub const fn route_renderer(self) -> ChainProcess<G> { ChainProcess::Ok((self, NextProcess::Renderer)) } @@ -132,10 +132,9 @@ impl<G> AnyOutput<G> { /// `member_id` before calling `restore`. pub fn restore<T: 'static>(self) -> Option<T> { if self.type_id == std::any::TypeId::of::<T>() { - match self.inner.downcast::<T>() { - Ok(boxed) => Some(*boxed), - Err(_) => None, - } + self.inner + .downcast::<T>() + .map_or_else(|_| None, |boxed| Some(*boxed)) } else { None } @@ -159,9 +158,9 @@ impl<G> std::ops::DerefMut for AnyOutput<G> { /// Chain exec result type /// /// Stores `Ok` and `Err` types of execution results, used to notify the scheduler what to execute next -/// - Returns `Ok((`[`AnyOutput`](./struct.AnyOutput.html)`, `[`NextProcess::Chain`](./enum.NextProcess.html)`))` to continue execution with this type next -/// - Returns `Ok((`[`AnyOutput`](./struct.AnyOutput.html)`, `[`NextProcess::Renderer`](./enum.NextProcess.html)`))` to render this type next and output to the terminal -/// - Returns `Err(`[`ChainProcessError`](./error/enum.ChainProcessError.html)`]` to terminate the program directly +/// - Returns <code>Ok(([AnyOutput](./struct.AnyOutput.html), [NextProcess::Chain](./enum.NextProcess.html)))</code> to continue execution with this type next +/// - Returns <code>Ok(([AnyOutput](./struct.AnyOutput.html), [NextProcess::Renderer](./enum.NextProcess.html)))</code> to render this type next and output to the terminal +/// - Returns <code>Err([ChainProcessError](./error/enum.ChainProcessError.html)]</code> to terminate the program directly pub enum ChainProcess<G> { /// Indicates success, containing the output value and the next step to execute. Ok((AnyOutput<G>, NextProcess)), @@ -185,15 +184,15 @@ pub enum NextProcess { impl std::fmt::Display for NextProcess { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - NextProcess::Chain => write!(f, "Chain"), - NextProcess::Renderer => write!(f, "Renderer"), + Self::Chain => write!(f, "Chain"), + Self::Renderer => write!(f, "Renderer"), } } } impl<G> From<AnyOutput<G>> for ChainProcess<G> { fn from(value: AnyOutput<G>) -> Self { - ChainProcess::Ok((value, NextProcess::Chain)) + Self::Ok((value, NextProcess::Chain)) } } @@ -211,7 +210,7 @@ mod tests { use super::*; use crate::Grouped; - /// Mock enum for testing AnyOutput + /// Mock enum for testing `AnyOutput` #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[allow(dead_code)] enum MockGroup { @@ -223,9 +222,9 @@ mod tests { impl std::fmt::Display for MockGroup { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - MockGroup::Alpha => write!(f, "Alpha"), - MockGroup::Beta => write!(f, "Beta"), - MockGroup::Gamma => write!(f, "Gamma"), + Self::Alpha => write!(f, "Alpha"), + Self::Beta => write!(f, "Beta"), + Self::Gamma => write!(f, "Gamma"), } } } @@ -242,7 +241,7 @@ mod tests { /// Since this code only constructs `AnyOutput` and calls methods like /// `downcast`, `is`, `restore`, `route_chain`, and `route_renderer` — /// none of which involve `ProgramCollect::do_chain` or - /// `ProgramCollect::render` — the type/member_id correspondence is + /// `ProgramCollect::render` — the `type`/`member_id` correspondence is /// never exploited in an unsafe way here. /// The caller must ensure that the associated `member_id` correctly /// corresponds to the type's role in the group. @@ -264,7 +263,7 @@ mod tests { /// Since this code only constructs `AnyOutput` and calls methods like /// `downcast`, `is`, `restore`, `route_chain`, and `route_renderer` — /// none of which involve `ProgramCollect::do_chain` or - /// `ProgramCollect::render` — the type/member_id correspondence is + /// `ProgramCollect::render` — the `type`/`member_id` correspondence is /// never exploited in an unsafe way here. /// The caller must ensure that the associated `member_id` correctly /// corresponds to the type's role in the group. @@ -285,7 +284,7 @@ mod tests { /// Since this code only constructs `AnyOutput` and calls methods like /// `downcast`, `is`, `restore`, `route_chain`, and `route_renderer` — /// none of which involve `ProgramCollect::do_chain` or - /// `ProgramCollect::render` — the type/member_id correspondence is + /// `ProgramCollect::render` — the `type`/`member_id` correspondence is /// never exploited in an unsafe way here. /// The caller must ensure that the associated `member_id` correctly /// corresponds to the type's role in the group. @@ -358,7 +357,7 @@ mod tests { assert_eq!(any.member_id, MockGroup::Alpha); assert_eq!(next, NextProcess::Chain); } - _ => panic!("Expected ChainProcess::Ok"), + ChainProcess::Err(_) => panic!("Expected ChainProcess::Ok"), } } @@ -375,7 +374,7 @@ mod tests { assert_eq!(any.member_id, MockGroup::Alpha); assert_eq!(next, NextProcess::Renderer); } - _ => panic!("Expected ChainProcess::Ok"), + ChainProcess::Err(_) => panic!("Expected ChainProcess::Ok"), } } @@ -417,7 +416,7 @@ mod tests { assert_eq!(any.member_id, MockGroup::Alpha); assert_eq!(next, NextProcess::Chain); } - _ => panic!("Expected ChainProcess::Ok"), + ChainProcess::Err(_) => panic!("Expected ChainProcess::Ok"), } } @@ -451,7 +450,7 @@ mod tests { /// Since this code only constructs `AnyOutput` and calls methods like /// `downcast`, `is`, `restore`, `route_chain`, and `route_renderer` — /// none of which involve `ProgramCollect::do_chain` or - /// `ProgramCollect::render` — the type/member_id correspondence is + /// `ProgramCollect::render` — the `type`/`member_id` correspondence is /// never exploited in an unsafe way here. /// The caller must ensure that the associated `member_id` correctly /// corresponds to the type's role in the group. @@ -488,7 +487,7 @@ mod tests { /// Since this code only constructs `AnyOutput` and calls methods like /// `downcast`, `is`, `restore`, `route_chain`, and `route_renderer` — /// none of which involve `ProgramCollect::do_chain` or - /// `ProgramCollect::render` — the type/member_id correspondence is + /// `ProgramCollect::render` — the `type`/`member_id` correspondence is /// never exploited in an unsafe way here. /// The caller must ensure that the associated `member_id` correctly /// corresponds to the type's role in the group. @@ -504,7 +503,7 @@ mod tests { /// Since this code only constructs `AnyOutput` and calls methods like /// `downcast`, `is`, `restore`, `route_chain`, and `route_renderer` — /// none of which involve `ProgramCollect::do_chain` or - /// `ProgramCollect::render` — the type/member_id correspondence is + /// `ProgramCollect::render` — the `type`/`member_id` correspondence is /// never exploited in an unsafe way here. /// The caller must ensure that the associated `member_id` correctly /// corresponds to the type's role in the group. diff --git a/mingling_core/src/asset.rs b/mingling_core/src/asset.rs index fc1c81b..527607a 100644 --- a/mingling_core/src/asset.rs +++ b/mingling_core/src/asset.rs @@ -1,3 +1,4 @@ +#![allow(clippy::redundant_pub_crate)] pub(crate) mod chain; pub(crate) mod core_invokes; pub(crate) mod dispatcher; diff --git a/mingling_core/src/asset/chain/error.rs b/mingling_core/src/asset/chain/error.rs index ad64195..bb5f679 100644 --- a/mingling_core/src/asset/chain/error.rs +++ b/mingling_core/src/asset/chain/error.rs @@ -13,8 +13,8 @@ pub enum ChainProcessError { impl std::fmt::Display for ChainProcessError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - ChainProcessError::Other(s) => write!(f, "Other error: {s}"), - ChainProcessError::IO(e) => write!(f, "IO error: {e}"), + Self::Other(s) => write!(f, "Other error: {s}"), + Self::IO(e) => write!(f, "IO error: {e}"), } } } @@ -22,15 +22,15 @@ impl std::fmt::Display for ChainProcessError { impl std::error::Error for ChainProcessError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { - ChainProcessError::IO(e) => Some(e), - ChainProcessError::Other(_) => None, + Self::IO(e) => Some(e), + Self::Other(_) => None, } } } impl From<std::io::Error> for ChainProcessError { fn from(e: std::io::Error) -> Self { - ChainProcessError::IO(e) + Self::IO(e) } } @@ -38,17 +38,15 @@ impl From<ProgramInternalExecuteError> for ChainProcessError { fn from(value: ProgramInternalExecuteError) -> Self { match value { ProgramInternalExecuteError::DispatcherNotFound => { - ChainProcessError::Other("DispatcherNotFound".into()) + Self::Other("DispatcherNotFound".into()) } ProgramInternalExecuteError::RendererNotFound(r) => { - ChainProcessError::Other(format!("RendererNotFound: {r}")) - } - ProgramInternalExecuteError::Other(e) => ChainProcessError::Other(e), - ProgramInternalExecuteError::IO(e) => { - ChainProcessError::Other(format!("IOError: {e:?}")) + Self::Other(format!("RendererNotFound: {r}")) } + ProgramInternalExecuteError::Other(e) => Self::Other(e), + ProgramInternalExecuteError::IO(e) => Self::Other(format!("IOError: {e:?}")), ProgramInternalExecuteError::REPLPanic(program_panic) => { - ChainProcessError::Other(format!("REPLPanic: {program_panic}")) + Self::Other(format!("REPLPanic: {program_panic}")) } } } diff --git a/mingling_core/src/asset/core_invokes.rs b/mingling_core/src/asset/core_invokes.rs index 6a3da2d..07e5092 100644 --- a/mingling_core/src/asset/core_invokes.rs +++ b/mingling_core/src/asset/core_invokes.rs @@ -48,18 +48,17 @@ where C: ProgramCollect<Enum = C> + 'static, T: Grouped<C>, { - if !self.create_by_res_injection { - panic!( - "The current RendererInvoker was not created by the resource injection system, so it cannot be executed!" - ); - } + assert!( + self.create_by_res_injection, + "The current RendererInvoker was not created by the resource injection system, so it cannot be executed!" + ); C::render(AnyOutput::new(value)) } } impl<T> ResourceMarker for RendererInvoker<T> { fn __resource_marker_clone(&self) -> Self { - RendererInvoker { + Self { phantom: PhantomData, create_by_res_injection: self.create_by_res_injection, } @@ -71,7 +70,7 @@ impl<T> ResourceMarker for RendererInvoker<T> { // Reason: RendererInvoker is designed to only be created through resource injection. // When the resource injection does not find a corresponding value, // this method will be used to generate a default value to pass in. - RendererInvoker { + Self { phantom: PhantomData, create_by_res_injection: true, } @@ -190,7 +189,7 @@ where } } - /// Continuously execute the chain until it is rendered into a RenderResult + /// Continuously execute the chain until it is rendered into a `RenderResult` /// /// This function can only be called when the `ChainInvoker` was created by the resource injection system /// (i.e., via `__resource_marker_default`). If the invoker was created manually or cloned outside @@ -198,7 +197,7 @@ where /// /// # Special Behavior /// - /// If an error occurs during rendering, or the result type does not contain a renderer, it will be rendered as an empty RenderResult + /// If an error occurs during rendering, or the result type does not contain a renderer, it will be rendered as an empty `RenderResult` /// /// # Panics /// @@ -229,19 +228,17 @@ where } } - #[inline(always)] fn pre_check(&self) { - if !self.create_by_res_injection { - panic!( - "The current ChainInvoker was not created by the resource injection system, so it cannot be executed!" - ); - } + assert!( + self.create_by_res_injection, + "The current ChainInvoker was not created by the resource injection system, so it cannot be executed!" + ); } } impl<T> ResourceMarker for ChainInvoker<T> { fn __resource_marker_clone(&self) -> Self { - ChainInvoker { + Self { phantom: PhantomData, create_by_res_injection: self.create_by_res_injection, } @@ -253,7 +250,7 @@ impl<T> ResourceMarker for ChainInvoker<T> { // Reason: ChainInvoker is designed to only be created through resource injection. // When the resource injection does not find a corresponding value, // this method will be used to generate a default value to pass in. - ChainInvoker { + Self { phantom: PhantomData, create_by_res_injection: true, } diff --git a/mingling_core/src/asset/global_resource.rs b/mingling_core/src/asset/global_resource.rs index 18e4446..f37edd0 100644 --- a/mingling_core/src/asset/global_resource.rs +++ b/mingling_core/src/asset/global_resource.rs @@ -6,7 +6,10 @@ use std::{ use crate::{ChainProcess, Program, ProgramCollect, this}; -pub(crate) type GlobalResources = Arc<Mutex<HashMap<TypeId, Box<dyn Any + Sync + Send>>>>; +/// A thread-safe, type-erased container for storing global resources keyed by their type. +/// +/// Use `Program::with_resource` to insert resources and `Program::res` to retrieve them. +pub type GlobalResources = Arc<Mutex<HashMap<TypeId, Box<dyn Any + Sync + Send>>>>; impl<C> Program<C> where @@ -87,17 +90,16 @@ where let Ok(mut guard) = self.resources.lock() else { return Res::__resource_marker_default(); }; - if let Some(arc_res) = guard + guard .get_mut(&TypeId::of::<Res>()) .and_then(|a| a.downcast_mut::<Arc<Res>>()) - { - match Arc::try_unwrap(std::mem::take(arc_res)) { - Ok(val) => val, - Err(arc) => (*arc).__resource_marker_clone(), - } - } else { - Res::__resource_marker_default() - } + .map_or_else( + Res::__resource_marker_default, + |arc_res| match Arc::try_unwrap(std::mem::take(arc_res)) { + Ok(val) => val, + Err(arc) => (*arc).__resource_marker_clone(), + }, + ) } /// Internal syntax for the `&mut MyResource` syntax of async #[chain], do not use directly. @@ -116,18 +118,23 @@ where let guard = self.resources.lock().ok()?; let boxed_any = guard.get(&TypeId::of::<Res>())?; let arc_res = boxed_any.as_ref().downcast_ref::<Arc<Res>>()?; - Some(GlobalResource::from(Arc::clone(arc_res))) + let result = GlobalResource::from(Arc::clone(arc_res)); + drop(guard); + Some(result) } - /// Get a resource by type, returning `GlobalResource<Res>` if present + /// Get a resource by type, returning `GlobalResource<Res>` if present. + /// + /// If the resource is not present, returns the provided [`ChainProcess`] as an `Err`. + /// + /// # Errors + /// + /// Returns `Err(route)` when the resource of type `Res` is not present in the store. pub fn res_or_route<Res: 'static + Send + Sync>( &self, route: ChainProcess<C>, ) -> Result<GlobalResource<Res>, ChainProcess<C>> { - match self.res() { - Some(r) => Ok(r), - None => Err(route), - } + self.res().map_or_else(|| Err(route), Ok) } /// Get a resource by type, returning `GlobalResource<Res>` or inserting a default diff --git a/mingling_core/src/asset/lazy_resource.rs b/mingling_core/src/asset/lazy_resource.rs index 3cb7563..f2a5e3d 100644 --- a/mingling_core/src/asset/lazy_resource.rs +++ b/mingling_core/src/asset/lazy_resource.rs @@ -54,12 +54,10 @@ impl<T: Send + Sync + 'static> LazyRes<T> { /// dropped. If the resource has not yet been initialized, the callback is stored /// and will be invoked once initialization happens *and* the `LazyRes` is later /// dropped. + #[must_use] pub fn with_on_drop(mut self, on_drop: impl FnOnce(T) + Send + Sync + 'static) -> Self { match &mut self.inner { - LazyInner::Uninit(_, existing_opt) => { - *existing_opt = Some(Box::new(on_drop)); - } - LazyInner::Init(_, existing_opt) => { + LazyInner::Uninit(_, existing_opt) | LazyInner::Init(_, existing_opt) => { *existing_opt = Some(Box::new(on_drop)); } } @@ -72,17 +70,14 @@ impl<T: Send + Sync + 'static> LazyRes<T> { /// upon initialization. pub fn set_on_drop(&mut self, on_drop: impl FnOnce(T) + Send + Sync + 'static) { match &mut self.inner { - LazyInner::Uninit(_, existing_opt) => { - *existing_opt = Some(Box::new(on_drop)); - } - LazyInner::Init(_, existing_opt) => { + LazyInner::Uninit(_, existing_opt) | LazyInner::Init(_, existing_opt) => { *existing_opt = Some(Box::new(on_drop)); } } } /// Returns `true` if the resource has been initialized. - pub fn is_initialized(&self) -> bool { + pub const fn is_initialized(&self) -> bool { matches!(&self.inner, LazyInner::Init(_, _)) } @@ -157,6 +152,10 @@ impl<T: Send + Sync + 'static> LazyRes<T> { /// /// If the resource has not been initialized, the initializer is called first. /// This is different from `into_inner()` which returns `None` if uninitialized. + /// + /// # Panics + /// + /// Panics if the resource has not been initialized. pub fn unwrap(mut self) -> T { match std::mem::replace( &mut self.inner, @@ -216,10 +215,7 @@ impl<T: Send + Sync + 'static> Drop for LazyRes<T> { } } -impl<T: Send + Sync + 'static> Default for LazyRes<T> -where - T: Default, -{ +impl<T: Send + Sync + Default + 'static> Default for LazyRes<T> { /// Creates an uninitialized `LazyRes<T>` whose initializer returns `T::default()`. fn default() -> Self { Self::new(|| T::default()) @@ -237,6 +233,7 @@ impl<T: Send + Sync + 'static> From<T> for LazyRes<T> { impl<T: Send + Sync + 'static> LazyRes<T> { /// Creates a lazily initialized resource using `T::default()` as the initializer. + #[must_use] pub fn lazy_default() -> Self where T: Default, @@ -256,6 +253,7 @@ impl<T: Send + Sync + 'static> LazyRes<T> { /// create a corresponding `LazyRes<T>`. pub trait LazyInit: Send + Sync + 'static { /// Creates a lazily initialized resource for this type using `Default` as the initializer. + #[must_use] fn lazy_default() -> LazyRes<Self> where Self: Default, @@ -274,10 +272,7 @@ pub trait LazyInit: Send + Sync + 'static { impl<T: Send + Sync + 'static> LazyInit for T {} -impl<T: Send + Sync + 'static> ResourceMarker for LazyRes<T> -where - T: Default + Clone, -{ +impl<T: Send + Sync + 'static + Default + Clone> ResourceMarker for LazyRes<T> { /// Clones the lazy resource. The cloned resource retains any initialized value, /// but the initializer is reset to `T::default()`. fn __resource_marker_clone(&self) -> Self { @@ -290,10 +285,7 @@ where } /// Returns a default lazy resource (uninitialized, using `T::default()` as the initializer). - fn __resource_marker_default() -> Self - where - T: Default, - { + fn __resource_marker_default() -> Self { Self::default() } diff --git a/mingling_core/src/asset/node.rs b/mingling_core/src/asset/node.rs index caf34ec..b9002b6 100644 --- a/mingling_core/src/asset/node.rs +++ b/mingling_core/src/asset/node.rs @@ -12,24 +12,24 @@ pub struct Node { impl Node { /// Append a new part to the node path. #[must_use] - pub fn join(self, node: impl Into<String>) -> Node { + pub fn join(self, node: impl Into<String>) -> Self { let mut new_node = self.node; new_node.push(node.into()); - Node { node: new_node } + Self { node: new_node } } } impl From<&str> for Node { fn from(s: &str) -> Self { let node = s.split('.').map(|part| kebab_case!(part)).collect(); - Node { node } + Self { node } } } impl From<String> for Node { fn from(s: String) -> Self { let node = s.split('.').map(|part| kebab_case!(part)).collect(); - Node { node } + Self { node } } } diff --git a/mingling_core/src/comp.rs b/mingling_core/src/comp.rs index a49ea0c..7312a06 100644 --- a/mingling_core/src/comp.rs +++ b/mingling_core/src/comp.rs @@ -23,7 +23,10 @@ pub use shell_ctx::*; #[doc(hidden)] pub use suggest::*; -use crate::{ProgramCollect, debug, metadata::Description, only_debug, this, trace}; +use crate::{ProgramCollect, debug, metadata::Description, this, trace}; + +#[cfg(feature = "debug")] +use crate::debug::init_env_logger; #[cfg(not(feature = "dispatch_tree"))] use crate::ChainProcess; @@ -104,10 +107,11 @@ impl CompletionHelper { where P: ProgramCollect<Enum = P> + Display + PartialEq + 'static + std::fmt::Debug, { - only_debug! { - crate::debug::init_env_logger(); + #[cfg(feature = "debug")] + { + init_env_logger(); trace_ctx(ctx); - }; + } // Everything before the first argument that matches a command node // is treated as global parameters (flags and their values), which do @@ -116,10 +120,7 @@ impl CompletionHelper { // style invocations. let all_args = ctx.all_words.iter().skip(1).cloned().collect::<Vec<_>>(); let first_cmd_match = first_command_arg_index::<P>(&all_args); - let args = match first_cmd_match { - Some(start) => all_args[start..].to_vec(), - None => Vec::new(), - }; + let args = first_cmd_match.map_or_else(Vec::new, |start| all_args[start..].to_vec()); trace!("arguments=\"{}\"", args.join(", ")); #[cfg(not(feature = "dispatch_tree"))] @@ -168,26 +169,8 @@ impl CompletionHelper { None }; - match suggest { - Some(suggest) => { - // A concrete entry was dispatched. Merge the entry's own - // completion with the default subcommand suggestions so that, - // e.g. `thanks <tab>`, suggests both the leaf nodes (`bob`, - // `alice`) and the `thanks` entry's own completion. - trace!("using custom completion: {:?}", suggest); - let default = default_completion::<P>(ctx); - if suggest == Suggest::FileCompletion { - trace!( - "custom completion is FileCompletion, using default: {:?}", - default - ); - default - } else { - trace!("combining custom completion with default"); - suggest.combine(default) - } - } - None => { + suggest.map_or_else( + || { if first_cmd_match.is_some() { // A command node has been matched: the global // EntryFallback must not run afterwards, only the @@ -198,14 +181,45 @@ impl CompletionHelper { trace!("using default completion"); let fallback = P::do_comp(&P::build_entry_fallback(vec![]), ctx); let default = default_completion::<P>(ctx); - if fallback == Suggest::FileCompletion { + match fallback { + Suggest::FileCompletion => default, + _ => fallback.combine(default), + } + } + }, + |custom_suggest| { + // A concrete entry was dispatched. Merge the entry's own + // completion with the default subcommand suggestions so that, + // e.g. `thanks <tab>`, suggests both the leaf nodes (`bob`, + // `alice`) and the `thanks` entry's own completion. + trace!("using custom completion: {:?}", &custom_suggest); + let default = default_completion::<P>(ctx); + match custom_suggest { + Suggest::FileCompletion => { + trace!( + "custom completion is FileCompletion, using default: {:?}", + default + ); default - } else { - fallback.combine(default) + } + Suggest::Suggest(a) => { + trace!("combining custom completion with default"); + match default { + Suggest::Suggest(b) => { + let mut combined = a; + combined.extend(b); + Suggest::Suggest(combined) + } + Suggest::FileCompletion => { + // If custom completion has items and default is file + // completion, the custom items take priority. + Suggest::Suggest(a) + } + } } } - } - } + }, + ) } /// Renders the completion suggestions to standard output. @@ -219,7 +233,7 @@ impl CompletionHelper { /// - If the suggestion is [`Suggest::Suggest`] with a set of candidates, it /// formats and prints them according to the shell type (Zsh/PowerShell, Fish, /// or default). - pub fn render_suggest<P>(ctx: ShellContext, suggest: Suggest) + pub fn render_suggest<P>(ctx: &ShellContext, suggest: Suggest) where P: ProgramCollect<Enum = P> + Display + 'static, { @@ -296,7 +310,7 @@ where let lazy_member = match match_user_input(this::<P>(), &words) { Ok((dispatcher, args)) => match dispatcher.begin(args) { ChainProcess::Ok((any, _)) => Some(any.member_id), - _ => None, + ChainProcess::Err(_) => None, }, Err(_) => None, }; @@ -305,7 +319,10 @@ where P::get_metadata::<Description>(member_id).map(String::from) } -fn default_completion<P>(ctx: &ShellContext) -> Suggest +/// Builds suggestions from command nodes given an input path. +/// +/// Extracted from `default_completion` to keep it under the line-count limit. +fn build_node_suggestions<P>(ctx: &ShellContext, input_path: &[&str]) -> Suggest where P: ProgramCollect<Enum = P> + Display + 'static, { @@ -315,61 +332,22 @@ where .filter(|(s, _)| !s.starts_with('_')) .map(|(s, _)| s) .collect(); - debug!("cmd_nodes: {:?}", cmd_nodes); - - // If the current position is less than 1, do not perform completion - if ctx.word_index < 1 { - debug!("word_index < 1, returning file suggestions"); - return file_suggest(); - } - - // Get the current input path - let input_end = ctx.word_index.min(ctx.all_words.len()); - - debug!( - "input_path before filter: {:?}", - &ctx.all_words.get(1..input_end).unwrap_or(&[]) - ); - - // Skip global parameters (arguments before the first command node match) - // when resolving the command path, so `prog [PARAM]... <subcommand>` - // style invocations suggest the subcommand. - let input_slice = ctx.all_words.get(1..input_end).unwrap_or(&[]); - let input_path: Vec<&str> = match first_command_arg_index::<P>(input_slice) { - Some(start) => input_slice[start..] - .iter() - .map(std::string::String::as_str) - .collect(), - None => Vec::new(), - }; - debug!( - "input_path={:?}, current_word='{}'", - input_path, ctx.current_word - ); - debug!("input_path after filter: {:?}", input_path); - - debug!( - "default_completion: input_path = {:?}, word_index = {}, all_words = {:?}", - input_path, ctx.word_index, ctx.all_words - ); // Build a suggestion item for `token`, attaching the owning entry's // `Description` metadata (resolved via `node_path`) when one is available. let make_item = |token: &str, node_path: &str| -> SuggestItem { - match entry_description::<P>(node_path) { - Some(desc) => SuggestItem::new_with_desc(token.to_string(), desc), - None => SuggestItem::new(token.to_string()), - } + entry_description::<P>(node_path).map_or_else( + || SuggestItem::new(token.to_string()), + |desc| SuggestItem::new_with_desc(token.to_string(), desc), + ) }; // Track both the suggestion text and the node path used to look up its // description, then deduplicate by suggestion text. - let mut suggestions: std::collections::BTreeSet<SuggestItem> = - std::collections::BTreeSet::new(); + let mut suggestions: BTreeSet<SuggestItem> = BTreeSet::new(); // Special case: if input_path is empty, return all first-level commands if input_path.is_empty() { - debug!("input_path empty, returning first-level commands"); for node in cmd_nodes { let node_parts: Vec<&str> = node.split(' ').collect(); if let Some(first) = node_parts.first() { @@ -377,7 +355,6 @@ where } } } else { - debug!("input_path NOT empty, doing next-level suggestions"); // Get the current word let current_word = input_path.last().unwrap(); @@ -396,10 +373,6 @@ where // If suggestions for the current word are found, return directly if !suggestions.is_empty() { - debug!( - "default_completion: current word suggestions = {:?}", - suggestions - ); return Suggest::Suggest(suggestions); } } @@ -408,8 +381,6 @@ where for node in cmd_nodes { let node_parts: Vec<&str> = node.split(' ').collect(); - debug!("Checking node: '{}', parts: {:?}", node, node_parts); - // If input path is longer than node parts, skip if input_path.len() > node_parts.len() { continue; @@ -463,8 +434,6 @@ where } } - debug!("default_completion: suggestions = {:?}", suggestions); - if suggestions.is_empty() { file_suggest() } else { @@ -472,8 +441,49 @@ where } } -fn file_suggest() -> Suggest { - trace!("file_suggest called"); +fn default_completion<P>(ctx: &ShellContext) -> Suggest +where + P: ProgramCollect<Enum = P> + Display + 'static, +{ + debug!("cmd_nodes: {:?}", { + let nodes: BTreeSet<String> = this::<P>() + .get_nodes() + .into_iter() + .filter(|(s, _)| !s.starts_with('_')) + .map(|(s, _)| s) + .collect(); + nodes + }); + + // If the current position is less than 1, do not perform completion + if ctx.word_index < 1 { + debug!("word_index < 1, returning file suggestions"); + return file_suggest(); + } + + // Get the current input path + let input_end = ctx.word_index.min(ctx.all_words.len()); + + // Skip global parameters (arguments before the first command node match) + // when resolving the command path, so `prog [PARAM]... <subcommand>` + // style invocations suggest the subcommand. + let input_slice = ctx.all_words.get(1..input_end).unwrap_or(&[]); + let input_path: Vec<&str> = + first_command_arg_index::<P>(input_slice).map_or_else(Vec::new, |start| { + input_slice[start..] + .iter() + .map(std::string::String::as_str) + .collect() + }); + debug!( + "input_path={:?}, current_word='{}'", + input_path, ctx.current_word + ); + + build_node_suggestions::<P>(ctx, &input_path) +} + +const fn file_suggest() -> Suggest { Suggest::FileCompletion } diff --git a/mingling_core/src/comp/comp_ctx.rs b/mingling_core/src/comp/comp_ctx.rs index 8d7fa5c..6384909 100644 --- a/mingling_core/src/comp/comp_ctx.rs +++ b/mingling_core/src/comp/comp_ctx.rs @@ -10,6 +10,7 @@ where /// (defined by [`COMPLETION_SUBCOMMAND`]) appears among the parsed arguments. /// When `true`, the program should generate shell completions instead of /// running its normal execution path. + #[must_use] pub fn is_completing(&self) -> bool { // Check if the first argument (args[1]) is the completion subcommand self.args diff --git a/mingling_core/src/comp/flags.rs b/mingling_core/src/comp/flags.rs index 8aecf1b..0867122 100644 --- a/mingling_core/src/comp/flags.rs +++ b/mingling_core/src/comp/flags.rs @@ -23,11 +23,11 @@ pub enum ShellFlag { impl From<String> for ShellFlag { fn from(s: String) -> Self { match s.trim().to_lowercase().as_str() { - "zsh" => ShellFlag::Zsh, - "bash" => ShellFlag::Bash, - "fish" => ShellFlag::Fish, - "pwsh" | "ps1" | "powershell" => ShellFlag::Powershell, - other => ShellFlag::Other(snake_case!(other)), + "zsh" => Self::Zsh, + "bash" => Self::Bash, + "fish" => Self::Fish, + "pwsh" | "ps1" | "powershell" => Self::Powershell, + other => Self::Other(snake_case!(other)), } } } diff --git a/mingling_core/src/comp/shell_ctx.rs b/mingling_core/src/comp/shell_ctx.rs index 734b5d2..ceb433f 100644 --- a/mingling_core/src/comp/shell_ctx.rs +++ b/mingling_core/src/comp/shell_ctx.rs @@ -64,7 +64,7 @@ impl TryFrom<Vec<String>> for ShellContext { .map(|s| s.replace('^', "-")) .collect(); - Ok(ShellContext { + Ok(Self { command_line: command_line.replace('^', "-"), cursor_position, current_word: current_word.replace('^', "-"), @@ -201,7 +201,7 @@ impl ShellContext { } #[cfg(not(target_os = "windows"))] { - self.current_word.starts_with("-") + self.current_word.starts_with('-') } } diff --git a/mingling_core/src/comp/suggest.rs b/mingling_core/src/comp/suggest.rs index 804d622..27b227f 100644 --- a/mingling_core/src/comp/suggest.rs +++ b/mingling_core/src/comp/suggest.rs @@ -18,15 +18,15 @@ pub enum Suggest { } impl Suggest { - /// Creates a new Suggest variant containing a `BTreeSet` of suggestions. + /// Creates a new `Suggest` variant containing an empty `BTreeSet` of suggestions. #[must_use] - pub fn new() -> Self { + pub const fn new() -> Self { Self::Suggest(BTreeSet::new()) } /// Creates a `FileCompletion` variant. #[must_use] - pub fn file_comp() -> Self { + pub const fn file_comp() -> Self { Self::FileCompletion } @@ -47,11 +47,12 @@ impl Suggest { /// If both values are `Suggest::Suggest`, their `BTreeSet`s are merged /// (all items from `other` are added into `self`). Otherwise, the first /// `Suggest::Suggest` (or `FileCompletion`) is returned unchanged. - pub fn combine(self, other: impl Into<Suggest>) -> Self { + #[must_use] + pub fn combine(self, other: impl Into<Self>) -> Self { let other = other.into(); match (self, other) { - (Suggest::Suggest(suggest), Suggest::Suggest(other)) => { - Suggest::Suggest(suggest.into_iter().chain(other).collect()) + (Self::Suggest(suggest), Self::Suggest(other)) => { + Self::Suggest(suggest.into_iter().chain(other).collect()) } (suggest, _) => suggest, } @@ -109,10 +110,11 @@ impl Suggest { /// A new `Suggest` value where each item's suggestion text is prefixed /// with the given string. For example, `["foo", "bar"]` with prefix `"--"` /// becomes `["--foo", "--bar"]`. - pub fn add_prefix(self, prefix: impl Into<String>) -> Suggest { + #[must_use] + pub fn add_prefix(self, prefix: impl Into<String>) -> Self { let suggest = match self { - Suggest::Suggest(s) => s, - Suggest::FileCompletion => return Suggest::FileCompletion, + Self::Suggest(s) => s, + Self::FileCompletion => return Self::FileCompletion, }; let prefix = prefix.into(); let prefixed = suggest @@ -123,7 +125,7 @@ impl Suggest { new_item }) .collect(); - Suggest::Suggest(prefixed) + Self::Suggest(prefixed) } /// Appends a suffix to every suggestion in the `Suggest` set. @@ -142,10 +144,11 @@ impl Suggest { /// A new `Suggest` value where each item's suggestion text is suffixed /// with the given string. For example, `["foo", "bar"]` with suffix `"="` /// becomes `["foo=", "bar="]`. - pub fn add_suffix(self, suffix: impl Into<String>) -> Suggest { + #[must_use] + pub fn add_suffix(self, suffix: impl Into<String>) -> Self { let suggest = match self { - Suggest::Suggest(s) => s, - Suggest::FileCompletion => return Suggest::FileCompletion, + Self::Suggest(s) => s, + Self::FileCompletion => return Self::FileCompletion, }; let suffix = suffix.into(); let suffixed = suggest @@ -156,7 +159,7 @@ impl Suggest { new_item }) .collect(); - Suggest::Suggest(suffixed) + Self::Suggest(suffixed) } } @@ -170,7 +173,7 @@ where .into_iter() .map(|item| SuggestItem::new(item.into())) .collect(); - Suggest::Suggest(suggests) + Self::Suggest(suggests) } } @@ -213,7 +216,7 @@ pub enum SuggestItem { impl Default for SuggestItem { fn default() -> Self { - SuggestItem::Simple(String::new()) + Self::Simple(String::new()) } } @@ -232,13 +235,13 @@ impl Ord for SuggestItem { impl SuggestItem { /// Creates a new simple suggestion without description. #[must_use] - pub fn new(suggest: String) -> Self { + pub const fn new(suggest: String) -> Self { Self::Simple(suggest) } /// Creates a new suggestion with a description. #[must_use] - pub fn new_with_desc(suggest: String, description: String) -> Self { + pub const fn new_with_desc(suggest: String, description: String) -> Self { Self::WithDescription(suggest, description) } @@ -254,7 +257,7 @@ impl SuggestItem { /// Returns the suggestion text. #[must_use] - pub fn suggest(&self) -> &String { + pub const fn suggest(&self) -> &String { match self { Self::Simple(suggest) | Self::WithDescription(suggest, _) => suggest, } @@ -269,7 +272,7 @@ impl SuggestItem { /// Returns the description if present. #[must_use] - pub fn description(&self) -> Option<&String> { + pub const fn description(&self) -> Option<&String> { match self { Self::Simple(_) => None, Self::WithDescription(_, description) => Some(description), diff --git a/mingling_core/src/lib.rs b/mingling_core/src/lib.rs index 3db3d31..9de1f00 100644 --- a/mingling_core/src/lib.rs +++ b/mingling_core/src/lib.rs @@ -9,8 +9,12 @@ //! //! Recommended to import [mingling](https://crates.io/crates/mingling) to use its features. -// Private Modules +#![deny(clippy::pedantic)] +#![deny(clippy::nursery)] +// Using `"".to_string()` is clearer than `String::new()` for expressing "creating an empty value" +#![allow(clippy::manual_string_new)] +// Private Modules mod any; mod asset; mod program; diff --git a/mingling_core/src/program.rs b/mingling_core/src/program.rs index 11e1bbf..3c43cee 100644 --- a/mingling_core/src/program.rs +++ b/mingling_core/src/program.rs @@ -109,7 +109,7 @@ where /// Creates a new Program instance with the provided command-line arguments. pub fn new_with_args(args: impl Into<StringVec>) -> Self { - Program { + Self { collect: std::marker::PhantomData, args: args.into().into(), @@ -133,14 +133,14 @@ where /// # Panics /// /// Panics if the program has not been initialized yet. - pub fn this_program() -> &'static Program<C> + pub fn this_program() -> &'static Self where C: 'static, { THIS_PROGRAM .get_raw() .unwrap() - .downcast_ref::<Program<C>>() + .downcast_ref::<Self>() .unwrap() } @@ -173,7 +173,7 @@ where /// # Returns /// /// The previous command-line arguments. - pub fn replace_args(&mut self, args: Vec<String>) -> Vec<String> { + pub const fn replace_args(&mut self, args: Vec<String>) -> Vec<String> { std::mem::replace(&mut self.args, args) } diff --git a/mingling_core/src/program/collection.rs b/mingling_core/src/program/collection.rs index 5b1152a..66ec9f1 100644 --- a/mingling_core/src/program/collection.rs +++ b/mingling_core/src/program/collection.rs @@ -41,6 +41,10 @@ pub trait ProgramCollect { #[cfg(not(feature = "dispatch_tree"))] /// Use a prefix tree to quickly match arguments and dispatch to an Entry + /// + /// # Errors + /// + /// Returns an error if the program fails to execute the given arguments. fn dispatch_args_trie( _raw: &[String], ) -> Result<AnyOutput<Self::Enum>, crate::error::ProgramInternalExecuteError> { diff --git a/mingling_core/src/program/collection/mock.rs b/mingling_core/src/program/collection/mock.rs index cd2abf5..52fbd17 100644 --- a/mingling_core/src/program/collection/mock.rs +++ b/mingling_core/src/program/collection/mock.rs @@ -26,17 +26,17 @@ pub enum MockProgramCollect { /// SAFETY: This is a mock type used only for temporary testing. /// It will never actually enter the macro system. /// The internal `panic!` ensures that `member_id` will never be executed. -unsafe impl Grouped<MockProgramCollect> for MockProgramCollect { - fn member_id() -> MockProgramCollect { +unsafe impl Grouped<Self> for MockProgramCollect { + fn member_id() -> Self { panic!("Attempting to read an unsafe enum type"); } } impl ProgramCollect for MockProgramCollect { - type Enum = MockProgramCollect; - type EntryFallback = MockProgramCollect; - type ErrorRendererNotFound = MockProgramCollect; - type ResultEmpty = MockProgramCollect; + type Enum = Self; + type EntryFallback = Self; + type ErrorRendererNotFound = Self; + type ResultEmpty = Self; #[cfg(feature = "dispatch_tree")] fn dispatch_args_trie( diff --git a/mingling_core/src/program/config.rs b/mingling_core/src/program/config.rs index c5f91da..b098961 100644 --- a/mingling_core/src/program/config.rs +++ b/mingling_core/src/program/config.rs @@ -1,41 +1,95 @@ +/// Output mode for error messages +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ErrorOutput { + /// Show error messages + Show, + /// Hide error messages + Hide, +} + +/// Output mode for rendered results +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum RenderOutput { + /// Render results and output + Show, + /// Hide rendered results + Hide, +} + +/// Panic message handling +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum PanicSilence { + /// Allow panic messages to be shown + Show, + /// Silence panic messages + Silence, +} + +/// Verbosity level for program output +/// +/// **NOTE**: Convention only, not a configuration +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Verbosity { + /// Normal output + Normal, + /// Verbose output: provide detailed information + Verbose, + /// Quiet mode: suppress status messages, show only errors and results + Quiet, + /// Debug mode: output internal state and detailed diagnostics + Debug, +} + +/// Color output mode +/// +/// **NOTE**: Convention only, not a configuration +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ColorOutput { + /// Enable colored output + Enabled, + /// Disable colored output + Disabled, +} + +/// Progress indicator mode +/// +/// Automatically disabled when stdout is not a tty. +/// +/// **NOTE**: Convention only, not a configuration +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ProgressOutput { + /// Show progress indicators (e.g. progress bars, spinners) + Enabled, + /// Hide progress indicators + Disabled, +} + /// Program stdout settings #[derive(Debug, Clone)] pub struct ProgramStdoutSetting { /// Output error messages - pub error_output: bool, + pub error_output: ErrorOutput, /// Render results and output - pub render_output: bool, + pub render_output: RenderOutput, /// Silence panic messages - pub silence_panic: bool, + pub silence_panic: PanicSilence, - /// Verbose output: provide detailed information + /// Verbosity level for program output /// /// **NOTE**: Convention only, not a configuration - pub verbose: bool, - - /// Quiet mode: suppress status messages, show only errors and results - /// - /// **NOTE**: Convention only, not a configuration - pub quiet: bool, - - /// Debug mode: output internal state and detailed diagnostics - /// - /// **NOTE**: Convention only, not a configuration - pub debug: bool, + pub verbosity: Verbosity, /// Enable colored output /// /// **NOTE**: Convention only, not a configuration - pub color: bool, + pub color: ColorOutput, /// Show progress indicators (e.g. progress bars, spinners) /// - /// Automatically disabled when stdout is not a tty. - /// /// **NOTE**: Convention only, not a configuration - pub progress: bool, + pub progress: ProgressOutput, #[cfg(feature = "clap")] /// Behavior when Clap Dispatcher outputs help information @@ -63,21 +117,65 @@ pub enum ClapHelpPrintBehaviour { impl Default for ProgramStdoutSetting { fn default() -> Self { - ProgramStdoutSetting { - error_output: true, - render_output: true, - silence_panic: false, - verbose: false, - quiet: false, - debug: false, - color: true, - progress: true, + Self { + error_output: ErrorOutput::Show, + render_output: RenderOutput::Show, + silence_panic: PanicSilence::Show, + verbosity: Verbosity::Normal, + color: ColorOutput::Enabled, + progress: ProgressOutput::Enabled, #[cfg(feature = "clap")] clap_help_print_behaviour: ClapHelpPrintBehaviour::default(), } } } +/// Confirmation mode for user prompts +/// +/// **NOTE**: Convention only, not a configuration +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ConfirmationMode { + /// Require confirmation from the user + Confirm, + /// Skip user confirmation step + Skip, +} + +/// Execution mode +/// +/// **NOTE**: Convention only, not a configuration +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ExecutionMode { + /// Normal execution + Normal, + /// Dry-run mode: simulate actions without making changes + DryRun, + /// Force execution, skipping safety checks + Force, +} + +/// Interaction mode +/// +/// **NOTE**: Convention only, not a configuration +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum InteractionMode { + /// Interactive terminal (has a tty) + Interactive, + /// Non-interactive terminal + NonInteractive, +} + +/// Yes assumption mode for prompts +/// +/// **NOTE**: Convention only, not a configuration +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum YesAssumption { + /// Do not assume "yes" for any prompt + None, + /// Assume "yes" for all confirmation prompts + AssumeYes, +} + /// Program user context #[derive(Debug, Clone)] pub struct ProgramUserContext { @@ -87,30 +185,25 @@ pub struct ProgramUserContext { /// Execute hooks during the program lifecycle pub run_hook: bool, - /// Skip user confirmation step + /// Confirmation mode for user prompts /// /// **NOTE**: Convention only, not a configuration - pub confirm: bool, + pub confirmation: ConfirmationMode, - /// Dry-run mode: simulate actions without making changes + /// Execution mode /// /// **NOTE**: Convention only, not a configuration - pub dry_run: bool, - - /// Force execution, skipping safety checks - /// - /// **NOTE**: Convention only, not a configuration - pub force: bool, + pub execution: ExecutionMode, /// Whether the program is running in an interactive terminal (has a tty) /// /// **NOTE**: Convention only, not a configuration - pub interactive: bool, + pub interaction: InteractionMode, - /// Assume "yes" for all confirmation prompts + /// Whether to assume "yes" for all confirmation prompts /// /// **NOTE**: Convention only, not a configuration - pub assume_yes: bool, + pub yes_assumption: YesAssumption, } impl Default for ProgramUserContext { @@ -118,11 +211,10 @@ impl Default for ProgramUserContext { Self { help: false, run_hook: true, - confirm: false, - dry_run: false, - force: false, - interactive: false, - assume_yes: false, + confirmation: ConfirmationMode::Confirm, + execution: ExecutionMode::Normal, + interaction: InteractionMode::NonInteractive, + yes_assumption: YesAssumption::None, } } } @@ -162,19 +254,19 @@ impl std::str::FromStr for StructuralRendererSetting { fn from_str(s: &str) -> Result<Self, Self::Err> { match just_fmt::kebab_case!(s).as_str() { - "disable" => Ok(StructuralRendererSetting::Disable), + "disable" => Ok(Self::Disable), #[cfg(feature = "json_serde_fmt")] - "json" => Ok(StructuralRendererSetting::Json), + "json" => Ok(Self::Json), #[cfg(feature = "json_serde_fmt")] - "json-pretty" => Ok(StructuralRendererSetting::JsonPretty), + "json-pretty" => Ok(Self::JsonPretty), #[cfg(feature = "yaml_serde_fmt")] - "yaml" => Ok(StructuralRendererSetting::Yaml), + "yaml" => Ok(Self::Yaml), #[cfg(feature = "toml_serde_fmt")] - "toml" => Ok(StructuralRendererSetting::Toml), + "toml" => Ok(Self::Toml), #[cfg(feature = "ron_serde_fmt")] - "ron" => Ok(StructuralRendererSetting::Ron), + "ron" => Ok(Self::Ron), #[cfg(feature = "ron_serde_fmt")] - "ron-pretty" => Ok(StructuralRendererSetting::RonPretty), + "ron-pretty" => Ok(Self::RonPretty), _ => Err(format!("Invalid renderer: '{s}'")), } } @@ -183,7 +275,7 @@ impl std::str::FromStr for StructuralRendererSetting { #[cfg(feature = "structural_renderer")] impl From<&str> for StructuralRendererSetting { fn from(s: &str) -> Self { - s.parse().unwrap_or(StructuralRendererSetting::Disable) + s.parse().unwrap_or(Self::Disable) } } @@ -198,19 +290,19 @@ impl From<String> for StructuralRendererSetting { impl std::fmt::Display for StructuralRendererSetting { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - StructuralRendererSetting::Disable => write!(f, "disable"), + Self::Disable => write!(f, "disable"), #[cfg(feature = "json_serde_fmt")] - StructuralRendererSetting::Json => write!(f, "json"), + Self::Json => write!(f, "json"), #[cfg(feature = "json_serde_fmt")] - StructuralRendererSetting::JsonPretty => write!(f, "json-pretty"), + Self::JsonPretty => write!(f, "json-pretty"), #[cfg(feature = "yaml_serde_fmt")] - StructuralRendererSetting::Yaml => write!(f, "yaml"), + Self::Yaml => write!(f, "yaml"), #[cfg(feature = "toml_serde_fmt")] - StructuralRendererSetting::Toml => write!(f, "toml"), + Self::Toml => write!(f, "toml"), #[cfg(feature = "ron_serde_fmt")] - StructuralRendererSetting::Ron => write!(f, "ron"), + Self::Ron => write!(f, "ron"), #[cfg(feature = "ron_serde_fmt")] - StructuralRendererSetting::RonPretty => write!(f, "ron-pretty"), + Self::RonPretty => write!(f, "ron-pretty"), } } } @@ -222,14 +314,12 @@ mod tests { #[test] fn program_stdout_setting_default() { let s = ProgramStdoutSetting::default(); - assert!(s.error_output); - assert!(s.render_output); - assert!(!s.silence_panic); - assert!(!s.verbose); - assert!(!s.quiet); - assert!(!s.debug); - assert!(s.color); - assert!(s.progress); + assert_eq!(s.error_output, ErrorOutput::Show); + assert_eq!(s.render_output, RenderOutput::Show); + assert_eq!(s.silence_panic, PanicSilence::Show); + assert_eq!(s.verbosity, Verbosity::Normal); + assert_eq!(s.color, ColorOutput::Enabled); + assert_eq!(s.progress, ProgressOutput::Enabled); } #[test] @@ -237,11 +327,10 @@ mod tests { let ctx = ProgramUserContext::default(); assert!(!ctx.help); assert!(ctx.run_hook); - assert!(!ctx.confirm); - assert!(!ctx.dry_run); - assert!(!ctx.force); - assert!(!ctx.interactive); - assert!(!ctx.assume_yes); + assert_eq!(ctx.confirmation, ConfirmationMode::Confirm); + assert_eq!(ctx.execution, ExecutionMode::Normal); + assert_eq!(ctx.interaction, InteractionMode::NonInteractive); + assert_eq!(ctx.yes_assumption, YesAssumption::None); } #[cfg(feature = "structural_renderer")] diff --git a/mingling_core/src/program/error.rs b/mingling_core/src/program/error.rs index 144b5ab..0a5bfd0 100644 --- a/mingling_core/src/program/error.rs +++ b/mingling_core/src/program/error.rs @@ -21,7 +21,7 @@ impl fmt::Display for ProgramPanic { impl ProgramPanic { #[must_use] pub fn new(payload: Box<dyn Any + Send>) -> Self { - ProgramPanic { payload } + Self { payload } } } diff --git a/mingling_core/src/program/exec.rs b/mingling_core/src/program/exec.rs index d9b4dd8..df42249 100644 --- a/mingling_core/src/program/exec.rs +++ b/mingling_core/src/program/exec.rs @@ -1,4 +1,5 @@ #![allow(clippy::borrowed_box)] +#![allow(clippy::too_many_lines)] use crate::{ AnyOutput, ChainProcess, Dispatcher, NextProcess, Program, ProgramCollect, RenderResult, @@ -49,7 +50,7 @@ where // Run hooks control!( - program.run_hook_pre_dispatch(crate::hook::HookPreDispatchInfo { arguments: args }), + program.run_hook_pre_dispatch(&crate::hook::HookPreDispatchInfo { arguments: args }), current ); @@ -62,7 +63,7 @@ where // Run hook control!( - program.run_hook_post_dispatch(crate::hook::HookPostDispatchInfo { + program.run_hook_post_dispatch(&crate::hook::HookPostDispatchInfo { entry: ¤t.member_id, }), current @@ -75,7 +76,7 @@ where let mut render_result = render_help::<C>(program, current); // Run hook - control!(program.run_hook_finish(crate::hook::HookFinishInfo {})); + control!(program.run_hook_finish(&crate::hook::HookFinishInfo {})); render_result.exit_code = exit_code; return Ok(render_result); @@ -89,7 +90,7 @@ where if C::has_chain(¤t) { // Run hook control!( - program.run_hook_pre_chain(crate::hook::HookPreChainInfo { + program.run_hook_pre_chain(&crate::hook::HookPreChainInfo { input: ¤t.member_id, raw: current.inner.as_ref(), }), @@ -102,7 +103,7 @@ where let mut render_result = render::<C>(program, any); // Run hook - control!(program.run_hook_finish(crate::hook::HookFinishInfo {})); + control!(program.run_hook_finish(&crate::hook::HookFinishInfo {})); render_result.exit_code = exit_code; return Ok(render_result); @@ -111,7 +112,7 @@ where ChainProcess::Ok((mut any, NextProcess::Chain)) => { // Run hook control!( - program.run_hook_post_chain(crate::hook::HookPostChainInfo { + program.run_hook_post_chain(&crate::hook::HookPostChainInfo { output: &any }), any @@ -121,7 +122,7 @@ where ChainProcess::Err(e) => { // Run hook control!( - program.run_hook_finish(crate::hook::HookFinishInfo {}), + program.run_hook_finish(&crate::hook::HookFinishInfo {}), &mut C::build_empty_result() ); return Err(e.into()); @@ -132,7 +133,7 @@ where else if C::has_renderer(¤t) { // Run hook control!( - program.run_hook_pre_render(crate::hook::HookPreRenderInfo { + program.run_hook_pre_render(&crate::hook::HookPreRenderInfo { input: ¤t.member_id, raw: current.inner.as_ref(), }), @@ -143,12 +144,12 @@ where // Run hooks control!( - program.run_hook_post_render(crate::hook::HookPostRenderInfo { + program.run_hook_post_render(&crate::hook::HookPostRenderInfo { result: &render_result, }) ); - control!(program.run_hook_finish(crate::hook::HookFinishInfo {})); + control!(program.run_hook_finish(&crate::hook::HookFinishInfo {})); render_result.exit_code = exit_code; return Ok(render_result); @@ -168,7 +169,7 @@ where // Run hook control!( - program.run_hook_finish(crate::hook::HookFinishInfo {}), + program.run_hook_finish(&crate::hook::HookFinishInfo {}), current ); render_result.exit_code = exit_code; @@ -252,7 +253,7 @@ pub(crate) fn handle_program_control<C: ProgramCollect<Enum = C>>( mut current: Option<&mut AnyOutput<C>>, exit_code: &mut i32, ) -> Option<RenderResult> { - for unit in controls.into_iter() { + for unit in controls { match unit { super::hook::ProgramControlUnit::OverrideExitCode(c) => *exit_code = c, super::hook::ProgramControlUnit::RouteToChain(any_output) => { @@ -264,7 +265,7 @@ pub(crate) fn handle_program_control<C: ProgramCollect<Enum = C>>( // Note: Hooks triggered by ProgramControl will not trigger ProgramControl again // Pre render - let _ = program.run_hook_pre_render(crate::hook::HookPreRenderInfo { + let _ = program.run_hook_pre_render(&crate::hook::HookPreRenderInfo { input: &any_output.member_id, raw: any_output.inner.as_ref(), }); @@ -273,7 +274,7 @@ pub(crate) fn handle_program_control<C: ProgramCollect<Enum = C>>( r.exit_code = *exit_code; // Post render - program.run_hook_post_render(crate::hook::HookPostRenderInfo { result: &r }); + program.run_hook_post_render(&crate::hook::HookPostRenderInfo { result: &r }); return Some(r); } diff --git a/mingling_core/src/program/exec/error.rs b/mingling_core/src/program/exec/error.rs index 944e89a..c8cb15a 100644 --- a/mingling_core/src/program/exec/error.rs +++ b/mingling_core/src/program/exec/error.rs @@ -24,12 +24,12 @@ pub enum ProgramExecuteError { impl fmt::Display for ProgramExecuteError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - ProgramExecuteError::DispatcherNotFound => write!(f, "No Dispatcher Found"), - ProgramExecuteError::RendererNotFound(s) => { + Self::DispatcherNotFound => write!(f, "No Dispatcher Found"), + Self::RendererNotFound(s) => { write!(f, "No Renderer (`{s}`) Found") } - ProgramExecuteError::Panic(p) => write!(f, "Panic: {p:?}"), - ProgramExecuteError::Other(s) => write!(f, "Other error: {s}"), + Self::Panic(p) => write!(f, "Panic: {p:?}"), + Self::Other(s) => write!(f, "Other error: {s}"), } } } @@ -38,7 +38,7 @@ impl std::error::Error for ProgramExecuteError {} impl From<ProgramPanic> for ProgramExecuteError { fn from(value: ProgramPanic) -> Self { - ProgramExecuteError::Panic(value) + Self::Panic(value) } } @@ -70,17 +70,11 @@ pub enum ProgramInternalExecuteError { impl fmt::Display for ProgramInternalExecuteError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - ProgramInternalExecuteError::DispatcherNotFound => { - write!(f, "No Dispatcher Found") - } - ProgramInternalExecuteError::RendererNotFound(s) => { - write!(f, "No Renderer (`{s}`) Found") - } - ProgramInternalExecuteError::Other(s) => write!(f, "Other error: {s}"), - ProgramInternalExecuteError::IO(e) => write!(f, "IO error: {e}"), - ProgramInternalExecuteError::REPLPanic(panic) => { - write!(f, "A single REPL execution failed: {panic}") - } + Self::DispatcherNotFound => write!(f, "No Dispatcher Found"), + Self::RendererNotFound(s) => write!(f, "No Renderer (`{s}`) Found"), + Self::Other(s) => write!(f, "Other error: {s}"), + Self::IO(e) => write!(f, "IO error: {e}"), + Self::REPLPanic(panic) => write!(f, "A single REPL execution failed: {panic}"), } } } @@ -88,7 +82,7 @@ impl fmt::Display for ProgramInternalExecuteError { impl std::error::Error for ProgramInternalExecuteError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { - ProgramInternalExecuteError::IO(e) => Some(e), + Self::IO(e) => Some(e), _ => None, } } @@ -96,23 +90,19 @@ impl std::error::Error for ProgramInternalExecuteError { impl From<std::io::Error> for ProgramInternalExecuteError { fn from(e: std::io::Error) -> Self { - ProgramInternalExecuteError::IO(e) + Self::IO(e) } } impl From<ProgramInternalExecuteError> for ProgramExecuteError { fn from(value: ProgramInternalExecuteError) -> Self { match value { - ProgramInternalExecuteError::DispatcherNotFound => { - ProgramExecuteError::DispatcherNotFound - } - ProgramInternalExecuteError::RendererNotFound(s) => { - ProgramExecuteError::RendererNotFound(s) - } - ProgramInternalExecuteError::Other(s) => ProgramExecuteError::Other(s), - ProgramInternalExecuteError::IO(e) => ProgramExecuteError::Other(format!("{e}")), + ProgramInternalExecuteError::DispatcherNotFound => Self::DispatcherNotFound, + ProgramInternalExecuteError::RendererNotFound(s) => Self::RendererNotFound(s), + ProgramInternalExecuteError::Other(s) => Self::Other(s), + ProgramInternalExecuteError::IO(e) => Self::Other(format!("{e}")), ProgramInternalExecuteError::REPLPanic(p) => { - ProgramExecuteError::Other(format!("A single REPL execution failed: {p}")) + Self::Other(format!("A single REPL execution failed: {p}")) } } } @@ -121,8 +111,8 @@ impl From<ProgramInternalExecuteError> for ProgramExecuteError { impl From<ChainProcessError> for ProgramInternalExecuteError { fn from(value: ChainProcessError) -> Self { match value { - ChainProcessError::Other(s) => ProgramInternalExecuteError::Other(s), - ChainProcessError::IO(error) => ProgramInternalExecuteError::IO(error), + ChainProcessError::Other(s) => Self::Other(s), + ChainProcessError::IO(error) => Self::IO(error), } } } diff --git a/mingling_core/src/program/flag.rs b/mingling_core/src/program/flag.rs index 6cf126d..81d83e6 100644 --- a/mingling_core/src/program/flag.rs +++ b/mingling_core/src/program/flag.rs @@ -44,27 +44,27 @@ pub struct Flag { vec: Vec<&'static str>, } -impl From<&Flag> for Flag { - fn from(value: &Flag) -> Self { +impl From<&Self> for Flag { + fn from(value: &Self) -> Self { value.clone() } } impl From<()> for Flag { fn from((): ()) -> Self { - Flag { vec: vec![] } + Self { vec: vec![] } } } impl From<&'static str> for Flag { fn from(s: &'static str) -> Self { - Flag { vec: vec![s] } + Self { vec: vec![s] } } } impl From<&'static [&'static str]> for Flag { fn from(slice: &'static [&'static str]) -> Self { - Flag { + Self { vec: slice.to_vec(), } } @@ -72,7 +72,7 @@ impl From<&'static [&'static str]> for Flag { impl<const N: usize> From<[&'static str; N]> for Flag { fn from(slice: [&'static str; N]) -> Self { - Flag { + Self { vec: slice.to_vec(), } } @@ -80,7 +80,7 @@ impl<const N: usize> From<[&'static str; N]> for Flag { impl<const N: usize> From<&'static [&'static str; N]> for Flag { fn from(slice: &'static [&'static str; N]) -> Self { - Flag { + Self { vec: slice.to_vec(), } } @@ -169,7 +169,7 @@ where /// Registers a global argument (with value) and its handler. pub fn global_argument<F, A>(&mut self, arguments: A, mut do_fn: F) where - F: FnMut(&mut Program<C>, String), + F: FnMut(&mut Self, String), A: Into<Flag>, { let flag = arguments.into(); @@ -185,7 +185,7 @@ where /// Registers a global flag (boolean) and its handler. pub fn global_flag<F, A>(&mut self, flag: A, mut do_fn: F) where - F: FnMut(&mut Program<C>), + F: FnMut(&mut Self), A: Into<Flag>, { let flag = flag.into(); diff --git a/mingling_core/src/program/hook.rs b/mingling_core/src/program/hook.rs index 50c53d7..0bcc051 100644 --- a/mingling_core/src/program/hook.rs +++ b/mingling_core/src/program/hook.rs @@ -149,19 +149,19 @@ where self } - pub(crate) fn run_hook_on_begin(&self, info: HookBeginInfo) { + pub(crate) fn run_hook_on_begin(&self, info: &HookBeginInfo) { if !self.user_context.run_hook { return; } for hook in &self.hooks { if let Some(ref begin) = hook.begin { - begin(&info); + begin(info); } } } - pub(crate) fn run_hook_pre_dispatch(&self, info: HookPreDispatchInfo) -> ProgramControls<C> { + pub(crate) fn run_hook_pre_dispatch(&self, info: &HookPreDispatchInfo) -> ProgramControls<C> { if !self.user_context.run_hook { return ProgramControls::Empty; } @@ -169,7 +169,7 @@ where let mut controls = ProgramControls::Empty; for hook in &self.hooks { if let Some(ref pre_dispatch) = hook.pre_dispatch { - controls = pre_dispatch(&info); + controls = pre_dispatch(info); } } controls @@ -177,7 +177,7 @@ where pub(crate) fn run_hook_post_dispatch( &self, - info: HookPostDispatchInfo<C>, + info: &HookPostDispatchInfo<C>, ) -> ProgramControls<C> { if !self.user_context.run_hook { return ProgramControls::Empty; @@ -186,13 +186,13 @@ where let mut controls = ProgramControls::Empty; for hook in &self.hooks { if let Some(ref post_dispatch) = hook.post_dispatch { - controls = post_dispatch(&info); + controls = post_dispatch(info); } } controls } - pub(crate) fn run_hook_pre_chain(&self, info: HookPreChainInfo<C>) -> ProgramControls<C> { + pub(crate) fn run_hook_pre_chain(&self, info: &HookPreChainInfo<C>) -> ProgramControls<C> { if !self.user_context.run_hook { return ProgramControls::Empty; } @@ -200,13 +200,13 @@ where let mut controls = ProgramControls::Empty; for hook in &self.hooks { if let Some(ref pre_chain) = hook.pre_chain { - controls = pre_chain(&info); + controls = pre_chain(info); } } controls } - pub(crate) fn run_hook_post_chain(&self, info: HookPostChainInfo<C>) -> ProgramControls<C> { + pub(crate) fn run_hook_post_chain(&self, info: &HookPostChainInfo<C>) -> ProgramControls<C> { if !self.user_context.run_hook { return ProgramControls::Empty; } @@ -214,13 +214,13 @@ where let mut controls = ProgramControls::Empty; for hook in &self.hooks { if let Some(ref post_chain) = hook.post_chain { - controls = post_chain(&info); + controls = post_chain(info); } } controls } - pub(crate) fn run_hook_pre_render(&self, info: HookPreRenderInfo<C>) -> ProgramControls<C> { + pub(crate) fn run_hook_pre_render(&self, info: &HookPreRenderInfo<C>) -> ProgramControls<C> { if !self.user_context.run_hook { return ProgramControls::Empty; } @@ -228,13 +228,13 @@ where let mut controls = ProgramControls::Empty; for hook in &self.hooks { if let Some(ref pre_render) = hook.pre_render { - controls = pre_render(&info); + controls = pre_render(info); } } controls } - pub(crate) fn run_hook_post_render(&self, info: HookPostRenderInfo) -> ProgramControls<C> { + pub(crate) fn run_hook_post_render(&self, info: &HookPostRenderInfo) -> ProgramControls<C> { if !self.user_context.run_hook { return ProgramControls::Empty; } @@ -242,7 +242,7 @@ where let mut controls = ProgramControls::Empty; for hook in &self.hooks { if let Some(ref post_render) = hook.post_render { - controls = post_render(&info); + controls = post_render(info); } } controls @@ -250,19 +250,19 @@ where #[allow(dead_code)] #[cfg(not(feature = "async"))] - pub(crate) fn run_hook_exec_panic(&self, info: HookPanicInfo) { + pub(crate) fn run_hook_exec_panic(&self, info: &HookPanicInfo) { if !self.user_context.run_hook { return; } for hook in &self.hooks { if let Some(ref exec_panic) = hook.exec_panic { - exec_panic(&info); + exec_panic(info); } } } - pub(crate) fn run_hook_finish(&self, info: HookFinishInfo) -> ProgramControls<C> { + pub(crate) fn run_hook_finish(&self, info: &HookFinishInfo) -> ProgramControls<C> { if !self.user_context.run_hook { return ProgramControls::Empty; } @@ -270,7 +270,7 @@ where let mut controls = ProgramControls::Empty; for hook in &self.hooks { if let Some(ref finish) = hook.finish { - controls = finish(&info); + controls = finish(info); } } controls @@ -278,28 +278,28 @@ where /// Runs the REPL begin hooks (only available with `repl` feature) #[cfg(feature = "repl")] - pub(crate) fn run_hook_repl_on_begin(&self, info: HookREPLBeginInfo) { + pub(crate) fn run_hook_repl_on_begin(&self, info: &HookREPLBeginInfo) { if !self.user_context.run_hook { return; } for hook in &self.hooks { if let Some(ref repl_on_begin) = hook.repl_on_begin { - repl_on_begin(&info); + repl_on_begin(info); } } } /// Runs the REPL pre-readline hooks (only available with `repl` feature) #[cfg(feature = "repl")] - pub(crate) fn run_hook_repl_pre_readline(&self, info: HookREPLPreReadlineInfo) { + pub(crate) fn run_hook_repl_pre_readline(&self, info: &HookREPLPreReadlineInfo) { if !self.user_context.run_hook { return; } for hook in &self.hooks { if let Some(ref repl_pre_readline) = hook.repl_pre_readline { - repl_pre_readline(&info); + repl_pre_readline(info); } } } @@ -307,14 +307,14 @@ where /// Runs the custom REPL readline hook (only available with `repl` feature) /// Returns `Some(line)` if a hook was set and returned Some, otherwise `None`. #[cfg(feature = "repl")] - pub(crate) fn run_hook_repl_readline(&self, info: HookREPLReadlineInfo) -> Option<String> { + pub(crate) fn run_hook_repl_readline(&self, info: &HookREPLReadlineInfo) -> Option<String> { if !self.user_context.run_hook { return None; } for hook in &self.hooks { if let Some(ref repl_readline) = hook.repl_readline { - return repl_readline(&info); + return repl_readline(info); } } None @@ -322,98 +322,98 @@ where /// Runs the REPL post-readline hooks (only available with `repl` feature) #[cfg(feature = "repl")] - pub(crate) fn run_hook_repl_post_readline(&self, info: HookREPLPostReadlineInfo) { + pub(crate) fn run_hook_repl_post_readline(&self, info: &HookREPLPostReadlineInfo) { if !self.user_context.run_hook { return; } for hook in &self.hooks { if let Some(ref repl_post_readline) = hook.repl_post_readline { - repl_post_readline(&info); + repl_post_readline(info); } } } /// Runs the REPL pre-exec hooks (only available with `repl` feature) #[cfg(feature = "repl")] - pub(crate) fn run_hook_repl_pre_exec(&self, info: HookREPLPreExecInfo) { + pub(crate) fn run_hook_repl_pre_exec(&self, info: &HookREPLPreExecInfo) { if !self.user_context.run_hook { return; } for hook in &self.hooks { if let Some(ref repl_pre_exec) = hook.repl_pre_exec { - repl_pre_exec(&info); + repl_pre_exec(info); } } } /// Runs the REPL post-exec hooks (only available with `repl` feature) #[cfg(feature = "repl")] - pub(crate) fn run_hook_repl_post_exec(&self, info: HookREPLPostExecInfo) { + pub(crate) fn run_hook_repl_post_exec(&self, info: &HookREPLPostExecInfo) { if !self.user_context.run_hook { return; } for hook in &self.hooks { if let Some(ref repl_post_exec) = hook.repl_post_exec { - repl_post_exec(&info); + repl_post_exec(info); } } } /// Runs the REPL receive result hooks (only available with `repl` feature) #[cfg(feature = "repl")] - pub(crate) fn run_hook_repl_on_receive_result(&self, info: HookREPLOnReceiveResultInfo) { + pub(crate) fn run_hook_repl_on_receive_result(&self, info: &HookREPLOnReceiveResultInfo) { if !self.user_context.run_hook { return; } for hook in &self.hooks { if let Some(ref repl_on_receive_result) = hook.repl_on_receive_result { - repl_on_receive_result(&info); + repl_on_receive_result(info); } } } /// Runs the REPL panic hooks (only available with `repl` feature) #[cfg(all(feature = "repl", not(feature = "async")))] - pub(crate) fn run_hook_repl_on_panic(&self, info: HookREPLOnPanicInfo) { + pub(crate) fn run_hook_repl_on_panic(&self, info: &HookREPLOnPanicInfo) { if !self.user_context.run_hook { return; } for hook in &self.hooks { if let Some(ref repl_on_panic) = hook.repl_on_panic { - repl_on_panic(&info); + repl_on_panic(info); } } } /// Runs the REPL exit hooks (only available with `repl` feature) #[cfg(feature = "repl")] - pub(crate) fn run_hook_repl_exit(&self, info: HookREPLExitInfo) { + pub(crate) fn run_hook_repl_exit(&self, info: &HookREPLExitInfo) { if !self.user_context.run_hook { return; } for hook in &self.hooks { if let Some(ref repl_exit) = hook.repl_exit { - repl_exit(&info); + repl_exit(info); } } } - /// Runs the REPL loop_once hooks (only available with `repl` feature) + /// Runs the REPL [`loop_once`] hooks (only available with `repl` feature) #[cfg(feature = "repl")] - pub(crate) fn run_hook_repl_loop_once(&self, info: HookREPLLoopOnceInfo) { + pub(crate) fn run_hook_repl_loop_once(&self, info: &HookREPLLoopOnceInfo) { if !self.user_context.run_hook { return; } for hook in &self.hooks { if let Some(ref repl_loop_once) = hook.repl_loop_once { - repl_loop_once(&info); + repl_loop_once(info); } } } @@ -663,7 +663,7 @@ where self } - /// Sets the handler for the REPL loop_once event (only available with `repl` feature). + /// Sets the handler for the REPL [`loop_once`] event (only available with `repl` feature). /// This hook runs after each REPL loop iteration. #[cfg(feature = "repl")] #[must_use] @@ -691,7 +691,7 @@ mod tests { impl std::fmt::Display for MockHookEnum { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{:?}", self) + write!(f, "{self:?}") } } @@ -701,65 +701,62 @@ mod tests { /// Since this code only constructs `AnyOutput` and calls methods like /// `downcast`, `is`, `restore`, `route_chain`, and `route_renderer` — /// none of which involve `ProgramCollect::do_chain` or - /// `ProgramCollect::render` — the type/member_id correspondence is + /// `ProgramCollect::render` — the `type`/`member_id` correspondence is /// never exploited in an unsafe way here. /// The caller must ensure that the associated `member_id` correctly /// corresponds to the type's role in the group. - unsafe impl Grouped<MockHookEnum> for MockHookEnum { - fn member_id() -> MockHookEnum { - MockHookEnum::A + unsafe impl Grouped<Self> for MockHookEnum { + fn member_id() -> Self { + Self::A } } impl ProgramCollect for MockHookEnum { - type Enum = MockHookEnum; - type EntryFallback = MockHookEnum; - type ErrorRendererNotFound = MockHookEnum; - type ResultEmpty = MockHookEnum; + type Enum = Self; + type EntryFallback = Self; + type ErrorRendererNotFound = Self; + type ResultEmpty = Self; - fn build_renderer_not_found(_member_id: MockHookEnum) -> crate::AnyOutput<MockHookEnum> { + fn build_renderer_not_found(_member_id: Self) -> crate::AnyOutput<Self> { unreachable!() } - fn build_entry_fallback(_args: Vec<String>) -> crate::AnyOutput<MockHookEnum> { + fn build_entry_fallback(_args: Vec<String>) -> crate::AnyOutput<Self> { unreachable!() } - fn build_empty_result() -> crate::AnyOutput<MockHookEnum> { + fn build_empty_result() -> crate::AnyOutput<Self> { unreachable!() } - fn render(_any: crate::AnyOutput<MockHookEnum>) -> crate::RenderResult { + fn render(_any: crate::AnyOutput<Self>) -> crate::RenderResult { unreachable!() } - fn render_help(_any: crate::AnyOutput<MockHookEnum>) -> crate::RenderResult { + fn render_help(_any: crate::AnyOutput<Self>) -> crate::RenderResult { unreachable!() } - fn do_chain(_any: crate::AnyOutput<MockHookEnum>) -> crate::ChainProcess<MockHookEnum> { + fn do_chain(_any: crate::AnyOutput<Self>) -> crate::ChainProcess<Self> { unreachable!() } - fn has_renderer(_any: &crate::AnyOutput<MockHookEnum>) -> bool { + fn has_renderer(_any: &crate::AnyOutput<Self>) -> bool { unreachable!() } - fn has_chain(_any: &crate::AnyOutput<MockHookEnum>) -> bool { + fn has_chain(_any: &crate::AnyOutput<Self>) -> bool { unreachable!() } #[cfg(feature = "comp")] - fn do_comp( - _any: &crate::AnyOutput<MockHookEnum>, - _ctx: &crate::ShellContext, - ) -> crate::Suggest { + fn do_comp(_any: &crate::AnyOutput<Self>, _ctx: &crate::ShellContext) -> crate::Suggest { unreachable!() } #[cfg(feature = "structural_renderer")] fn structural_render( - _any: crate::AnyOutput<MockHookEnum>, + _any: crate::AnyOutput<Self>, _setting: &crate::StructuralRendererSetting, ) -> Result<crate::RenderResult, crate::error::StructuralRendererSerializeError> { unreachable!() diff --git a/mingling_core/src/program/hook/control_unit.rs b/mingling_core/src/program/hook/control_unit.rs index 5bf0e8c..d71a203 100644 --- a/mingling_core/src/program/hook/control_unit.rs +++ b/mingling_core/src/program/hook/control_unit.rs @@ -22,8 +22,8 @@ where C: ProgramCollect<Enum = C>, { /// Returns `true` if the collection is empty. - pub fn is_empty(&self) -> bool { - matches!(self, ProgramControls::Empty) + pub const fn is_empty(&self) -> bool { + matches!(self, Self::Empty) } } @@ -31,7 +31,7 @@ impl<C> From<()> for ProgramControls<C> where C: ProgramCollect<Enum = C>, { - fn from(_: ()) -> Self { + fn from((): ()) -> Self { Self::Empty } } @@ -63,13 +63,13 @@ where fn into_iter(self) -> Self::IntoIter { match self { - ProgramControls::Empty => ProgramControlsIter { + Self::Empty => ProgramControlsIter { inner: vec![].into_iter(), }, - ProgramControls::Single(unit) => ProgramControlsIter { + Self::Single(unit) => ProgramControlsIter { inner: vec![unit].into_iter(), }, - ProgramControls::Multi(units) => ProgramControlsIter { + Self::Multi(units) => ProgramControlsIter { inner: units.into_iter(), }, } @@ -139,17 +139,19 @@ where RouteToHelp(AnyOutput<C>), } -impl<C> From<ChainProcess<C>> for ProgramControlUnit<C> +impl<C> TryFrom<ChainProcess<C>> for ProgramControlUnit<C> where C: ProgramCollect<Enum = C>, { - fn from(val: ChainProcess<C>) -> Self { + type Error = String; + + fn try_from(val: ChainProcess<C>) -> Result<Self, Self::Error> { match val { ChainProcess::Ok((any, next)) => match next { - NextProcess::Chain => ProgramControlUnit::RouteToChain(any), - NextProcess::Renderer => ProgramControlUnit::RouteToRender(any), + NextProcess::Chain => Ok(Self::RouteToChain(any)), + NextProcess::Renderer => Ok(Self::RouteToRender(any)), }, - ChainProcess::Err(e) => panic!("{}", &e), + ChainProcess::Err(e) => Err(e.to_string()), } } } @@ -159,7 +161,14 @@ where C: ProgramCollect<Enum = C>, { fn from(val: ChainProcess<C>) -> Self { - let unit: ProgramControlUnit<C> = val.into(); - unit.into() + match val { + ChainProcess::Ok((any, next)) => match next { + NextProcess::Chain => Self::Single(ProgramControlUnit::RouteToChain(any)), + NextProcess::Renderer => Self::Single(ProgramControlUnit::RouteToRender(any)), + }, + ChainProcess::Err(e) => Self::Single(ProgramControlUnit::OverrideExitCode( + e.to_string().parse::<i32>().unwrap_or(1), + )), + } } } diff --git a/mingling_core/src/program/once_exec.rs b/mingling_core/src/program/once_exec.rs index 96723fd..18e06ef 100644 --- a/mingling_core/src/program/once_exec.rs +++ b/mingling_core/src/program/once_exec.rs @@ -21,7 +21,7 @@ where C: 'static + Send + Sync, { // Run hooks - self.run_hook_on_begin(crate::hook::HookBeginInfo {}); + self.run_hook_on_begin(&crate::hook::HookBeginInfo {}); self.args = self.args.iter().skip(1).cloned().collect(); @@ -43,11 +43,11 @@ where let program = THIS_PROGRAM .get_raw() .unwrap() - .downcast_ref::<Program<C>>() + .downcast_ref::<Self>() .unwrap(); #[cfg(not(feature = "async"))] - program.run_hook_exec_panic(crate::hook::HookPanicInfo { + program.run_hook_exec_panic(&crate::hook::HookPanicInfo { panic: &panic_payload, }); @@ -98,7 +98,7 @@ where // Read exit code // Render result - if stdout_setting.render_output { + if stdout_setting.render_output == crate::RenderOutput::Show { result.std_print(); } @@ -152,17 +152,17 @@ where pub(crate) fn exec_wrapper<F, R>(self, f: F) -> R where C: 'static + Send + Sync, - F: FnOnce(&'static Program<C>) -> R + Send + Sync, + F: FnOnce(&'static Self) -> R + Send + Sync, { THIS_PROGRAM.set(Box::new(self)); let program = THIS_PROGRAM .get_raw() .unwrap() - .downcast_ref::<Program<C>>() + .downcast_ref::<Self>() .unwrap(); #[cfg(not(panic = "abort"))] - if program.stdout_setting.silence_panic { + if program.stdout_setting.silence_panic == super::PanicSilence::Silence { std::panic::set_hook(Box::new(|_| {})); } diff --git a/mingling_core/src/program/repl_exec.rs b/mingling_core/src/program/repl_exec.rs index f84b291..ea36c75 100644 --- a/mingling_core/src/program/repl_exec.rs +++ b/mingling_core/src/program/repl_exec.rs @@ -29,7 +29,7 @@ where // Inject default REPL resource self.with_resource(ResREPL::default()); - self.run_hook_repl_on_begin(crate::hook::HookREPLBeginInfo {}); + self.run_hook_repl_on_begin(&crate::hook::HookREPLBeginInfo {}); might_be_async::select!( self.exec_wrapper(async |p| -> () { @@ -48,43 +48,43 @@ where C: ProgramCollect<Enum = C> + Send + Sync + 'static, { loop { - p.run_hook_repl_pre_readline(crate::hook::HookREPLPreReadlineInfo {}); + p.run_hook_repl_pre_readline(&crate::hook::HookREPLPreReadlineInfo {}); let mut readline = p - .run_hook_repl_readline(crate::hook::HookREPLReadlineInfo {}) + .run_hook_repl_readline(&crate::hook::HookREPLReadlineInfo {}) .unwrap_or_default(); - p.run_hook_repl_post_readline(crate::hook::HookREPLPostReadlineInfo { + p.run_hook_repl_post_readline(&crate::hook::HookREPLPostReadlineInfo { line: &mut readline, }); - let args = split_input_string(readline.clone()); + let args = split_input_string(&readline); - p.run_hook_repl_pre_exec(crate::hook::HookREPLPreExecInfo { args: &args }); - match might_be_async::invoke!(exec_once(p, args)) { + 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 { + 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_on_panic(&crate::hook::HookREPLOnPanicInfo { panic: &panic }); } _ => {} } - p.run_hook_repl_post_exec(crate::hook::HookREPLPostExecInfo {}); + p.run_hook_repl_post_exec(&crate::hook::HookREPLPostExecInfo {}); if this::<C>().res::<ResREPL>().unwrap().exit { - p.run_hook_repl_exit(crate::hook::HookREPLExitInfo {}); + p.run_hook_repl_exit(&crate::hook::HookREPLExitInfo {}); break; } - p.run_hook_repl_loop_once(crate::hook::HookREPLLoopOnceInfo {}); + p.run_hook_repl_loop_once(&crate::hook::HookREPLLoopOnceInfo {}); } } #[cfg(not(feature = "async"))] fn exec_once<C>( p: &'static Program<C>, - args: Vec<String>, + args: &[String], ) -> Result<RenderResult, ProgramInternalExecuteError> where C: ProgramCollect<Enum = C> + Send + Sync + 'static, @@ -95,7 +95,7 @@ where #[cfg(not(panic = "abort"))] let exec_result = { let exec_unwind_result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { - super::exec::exec_with_args(p, &args) + super::exec::exec_with_args(p, args) })); match exec_unwind_result { @@ -108,7 +108,7 @@ where .unwrap() .downcast_ref::<Program<C>>() .unwrap(); - program.run_hook_repl_on_panic(crate::hook::HookREPLOnPanicInfo { + program.run_hook_repl_on_panic(&crate::hook::HookREPLOnPanicInfo { panic: &panic_payload, }); Err(ProgramInternalExecuteError::REPLPanic(panic_payload)) @@ -123,7 +123,7 @@ where #[cfg(feature = "async")] async fn exec_once<C>( p: &'static Program<C>, - args: Vec<String>, + args: &[String], ) -> Result<RenderResult, ProgramInternalExecuteError> where C: ProgramCollect<Enum = C> + Send + Sync + 'static, diff --git a/mingling_core/src/program/repl_exec/splitter.rs b/mingling_core/src/program/repl_exec/splitter.rs index 267f42c..c74a3f1 100644 --- a/mingling_core/src/program/repl_exec/splitter.rs +++ b/mingling_core/src/program/repl_exec/splitter.rs @@ -1,14 +1,14 @@ /// Wraps `split_input` to work with owned `String` inputs. -pub(crate) fn split_input_string(input: String) -> Vec<String> { - split_input(&input) +pub fn split_input_string(input: &str) -> Vec<String> { + split_input(input) } /// Splits a string input into arguments, respecting single quotes, double quotes, /// and backslash escaping. -pub(crate) fn split_input(input: &str) -> Vec<String> { +pub fn split_input(input: &str) -> Vec<String> { let mut result: Vec<String> = Vec::new(); let mut current = String::new(); - let mut chars = input.chars().peekable(); + let mut chars = input.chars(); while let Some(ch) = chars.next() { match ch { diff --git a/mingling_core/src/program/single_instance.rs b/mingling_core/src/program/single_instance.rs index 8b165bf..083897d 100644 --- a/mingling_core/src/program/single_instance.rs +++ b/mingling_core/src/program/single_instance.rs @@ -13,7 +13,7 @@ use crate::{Program, ProgramCollect}; /// the inner value is immutable once set until `take()`). /// - `take()` is called only after execution completes, when no code still /// holds a reference from `get_raw()`. -pub(crate) struct ProgramCell { +pub struct ProgramCell { initialized: AtomicBool, inner: UnsafeCell<Option<Box<dyn std::any::Any + Send + Sync>>>, } @@ -88,6 +88,7 @@ impl ProgramCell { } /// Global static reference to the current program instance +#[allow(clippy::redundant_pub_crate)] pub(crate) static THIS_PROGRAM: ProgramCell = ProgramCell::new(); /// Returns a reference to the current program instance, panics if not set. diff --git a/mingling_core/src/program/string_vec.rs b/mingling_core/src/program/string_vec.rs index c2e6220..a2eb433 100644 --- a/mingling_core/src/program/string_vec.rs +++ b/mingling_core/src/program/string_vec.rs @@ -20,7 +20,7 @@ impl From<StringVec> for Vec<String> { impl<const N: usize> From<[&str; N]> for StringVec { fn from(slice: [&str; N]) -> Self { - StringVec { + Self { vec: slice.iter().map(|&s| s.to_string()).collect(), } } @@ -28,21 +28,20 @@ impl<const N: usize> From<[&str; N]> for StringVec { impl From<&[&str]> for StringVec { fn from(slice: &[&str]) -> Self { - StringVec { + Self { vec: slice.iter().map(|&s| s.to_string()).collect(), } } } - impl From<Vec<String>> for StringVec { fn from(vec: Vec<String>) -> Self { - StringVec { vec } + Self { vec } } } impl From<&[String]> for StringVec { fn from(slice: &[String]) -> Self { - StringVec { + Self { vec: slice.to_vec(), } } @@ -50,7 +49,7 @@ impl From<&[String]> for StringVec { impl From<Vec<&str>> for StringVec { fn from(vec: Vec<&str>) -> Self { - StringVec { + Self { vec: vec.iter().map(|&s| s.to_string()).collect(), } } diff --git a/mingling_core/src/renderer/render_result.rs b/mingling_core/src/renderer/render_result.rs index 3e63a00..a14bfda 100644 --- a/mingling_core/src/renderer/render_result.rs +++ b/mingling_core/src/renderer/render_result.rs @@ -50,7 +50,7 @@ pub enum RenderResultMode { impl<F> From<F> for RenderResult where - F: FnOnce() -> RenderResult, + F: FnOnce() -> Self, { fn from(value: F) -> Self { value() @@ -79,7 +79,7 @@ impl Display for RenderResult { impl From<()> for RenderResult { fn from(_value: ()) -> Self { - RenderResult::new() + Self::new() } } @@ -88,8 +88,8 @@ macro_rules! impl_from_int { $( impl From<$ty> for RenderResult { fn from(exit_code: $ty) -> Self { - RenderResult { - exit_code: exit_code as i32, + Self { + exit_code: <i32>::try_from(exit_code).unwrap_or_default(), ..Default::default() } } @@ -102,13 +102,13 @@ impl_from_int!(i32, i16, i8, u32, u16, u8, usize); impl From<RenderResult> for ExitCode { fn from(value: RenderResult) -> Self { - ExitCode::from(value.exit_code as u8) + Self::from(u8::try_from(value.exit_code).unwrap_or_default()) } } impl From<&RenderResult> for ExitCode { fn from(value: &RenderResult) -> Self { - ExitCode::from(value.exit_code as u8) + Self::from(u8::try_from(value.exit_code).unwrap_or_default()) } } @@ -156,6 +156,7 @@ impl RenderResult { /// assert_eq!(result.exit_code, 0); /// assert!(result.is_empty()); /// ``` + #[must_use] pub fn new() -> Self { Self::default() } @@ -175,7 +176,7 @@ impl RenderResult { /// let mut result = RenderResult::default(); /// result.immediate_output(); /// ``` - pub fn immediate_output(&mut self) -> &mut Self { + pub const fn immediate_output(&mut self) -> &mut Self { self.immediate_output = true; self } @@ -258,7 +259,7 @@ impl RenderResult { /// dest.append_other(src); /// assert_eq!(dest.to_string(), "Hello Error"); /// ``` - pub fn append_other(&mut self, other: impl Into<RenderResult>) { + pub fn append_other(&mut self, other: impl Into<Self>) { let other = other.into(); // If self has immediate output enabled, but the input does not, the input needs immediate output. @@ -290,7 +291,7 @@ impl RenderResult { pub fn print(&mut self, text: impl Into<String>) { let text = text.into(); if self.immediate_output { - print!("{}", text) + print!("{text}"); } self.append_to_buffer(text, Stdout); } @@ -310,7 +311,7 @@ impl RenderResult { pub fn println(&mut self, text: impl Into<String>) { let text = text.into(); if self.immediate_output { - println!("{}", text) + println!("{text}"); } self.append_line_to_buffer(text, Stdout); } @@ -330,7 +331,7 @@ impl RenderResult { pub fn eprint(&mut self, text: impl Into<String>) { let text = text.into(); if self.immediate_output { - eprint!("{}", text) + eprint!("{text}"); } self.append_to_buffer(text, Stderr); } @@ -350,7 +351,7 @@ impl RenderResult { pub fn eprintln(&mut self, text: impl Into<String>) { let text = text.into(); if self.immediate_output { - eprintln!("{}", text) + eprintln!("{text}"); } self.append_line_to_buffer(text, Stderr); } @@ -392,10 +393,10 @@ impl RenderResult { /// result.std_print(); // prints "Hello" to stdout and "Error" to stderr /// ``` pub fn std_print(&self) { - for (content, mode) in self.render_buffer.iter() { + for (content, mode) in &self.render_buffer { match mode { - Stdout => print!("{}", content), - Stderr => eprint!("{}", content), + Stdout => print!("{content}"), + Stderr => eprint!("{content}"), } } } @@ -415,6 +416,7 @@ impl RenderResult { /// result.print(", 世界"); /// assert_eq!(result.len(), 9); // "Hello, 世界" has 9 chars /// ``` + #[must_use] pub fn len(&self) -> usize { self.render_buffer .iter() @@ -434,6 +436,7 @@ impl RenderResult { /// result.print("Hello"); /// assert!(!result.is_empty()); /// ``` + #[must_use] pub fn is_empty(&self) -> bool { self.len() == 0 } @@ -465,7 +468,8 @@ impl RenderResult { /// let trimmed = result.trim_buffer(); /// assert_eq!(trimmed.to_string().trim(), "Hello, world!"); /// ``` - pub fn trim_buffer(self) -> RenderResult { + #[must_use] + pub fn trim_buffer(self) -> Self { if self.render_buffer.is_empty() { return self; } @@ -490,7 +494,7 @@ impl RenderResult { buffer.push((trimmed_last, last_mode)); } - RenderResult { + Self { render_buffer: buffer, immediate_output: self.immediate_output, exit_code: self.exit_code, @@ -516,16 +520,16 @@ impl RenderResult { } } -#[inline(always)] +#[inline] fn render_result_to_string(result: &RenderResult) -> String { let mut buffer = String::new(); - for item in result.render_buffer.iter() { + for item in &result.render_buffer { buffer += &item.0; } buffer } -#[inline(always)] +#[inline] fn string_to_render_result(string: impl Into<String>, mode: RenderResultMode) -> RenderResult { RenderResult { render_buffer: vec![(string.into(), mode)], @@ -573,7 +577,7 @@ mod tests { fn display_trims_trailing_whitespace() { let mut result = RenderResult::default(); result.print(" hello world \n"); - let formatted = format!("{}", result); + let formatted = format!("{result}"); assert_eq!(formatted, "hello world"); } diff --git a/mingling_core/src/renderer/structural/error.rs b/mingling_core/src/renderer/structural/error.rs index 63ded81..2787dff 100644 --- a/mingling_core/src/renderer/structural/error.rs +++ b/mingling_core/src/renderer/structural/error.rs @@ -11,7 +11,7 @@ pub struct StructuralRendererSerializeError { impl StructuralRendererSerializeError { /// Creates a new `StructuralRendererSerializeError` with the given error message. #[must_use] - pub fn new(error: String) -> Self { + pub const fn new(error: String) -> Self { Self { error } } } |
