diff options
Diffstat (limited to 'src/systems')
| -rw-r--r-- | src/systems/cmd.rs | 4 | ||||
| -rw-r--r-- | src/systems/cmd/cmd_system.rs | 106 | ||||
| -rw-r--r-- | src/systems/cmd/errors.rs | 12 | ||||
| -rw-r--r-- | src/systems/cmd/macros.rs | 166 | ||||
| -rw-r--r-- | src/systems/cmd/processer.rs | 4 | ||||
| -rw-r--r-- | src/systems/render.rs | 2 | ||||
| -rw-r--r-- | src/systems/render/render_system.rs | 14 | ||||
| -rw-r--r-- | src/systems/render/renderer.rs (renamed from src/systems/cmd/renderer.rs) | 11 |
8 files changed, 265 insertions, 54 deletions
diff --git a/src/systems/cmd.rs b/src/systems/cmd.rs index ea8bbd7..ebfa4f1 100644 --- a/src/systems/cmd.rs +++ b/src/systems/cmd.rs @@ -1,6 +1,6 @@ -pub mod _registry; +pub mod _commands; pub mod cmd_system; pub mod errors; +pub mod macros; pub mod processer; -pub mod renderer; pub mod workspace_reader; diff --git a/src/systems/cmd/cmd_system.rs b/src/systems/cmd/cmd_system.rs index 20f5aef..030e711 100644 --- a/src/systems/cmd/cmd_system.rs +++ b/src/systems/cmd/cmd_system.rs @@ -1,64 +1,88 @@ -use serde::Serialize; - use crate::{ r_println, - systems::cmd::{ - errors::{CmdExecuteError, CmdPrepareError, CmdProcessError, CmdRenderError}, - renderer::{JVRenderResult, JVResultRenderer}, + systems::{ + cmd::errors::{CmdExecuteError, CmdPrepareError, CmdProcessError, CmdRenderError}, + render::{render_system::render, renderer::JVRenderResult}, }, }; -use std::future::Future; +use std::{ + any::{Any, TypeId}, + collections::HashMap, + future::Future, +}; pub struct JVCommandContext { pub help: bool, pub confirmed: bool, } -pub trait JVCommand<Argument, Input, Collect, Output, Renderer> +pub trait JVCommand<Argument, Input, Collect> where Argument: clap::Parser + Send, Input: Send, - Output: Serialize + Send + Sync, Collect: Send, - Renderer: JVResultRenderer<Output> + Send + Sync, { /// Get help string for the command fn get_help_str() -> String; - /// Process the command with a specified renderer, performing any necessary post-execution processing - fn process_with_renderer_flag( + /// Run the command and convert the result into type-agnostic serialized information, + /// then hand it over to the universal renderer for rendering. + /// Universal renderer: uses the renderer specified by the `--renderer` flag. + fn process_to_renderer_override( args: Vec<String>, ctx: JVCommandContext, - renderer: String, - ) -> impl Future<Output = Result<JVRenderResult, CmdProcessError>> + Send - where - Self: Sync, - { + renderer_override: String, + ) -> impl Future<Output = Result<JVRenderResult, CmdProcessError>> + Send { async move { - let renderer_str = renderer.as_str(); - include!("_renderers.rs") + // If the `--help` flag is used, + // skip execution and return an error, + // unlike `process_to_render_system`, + // when the `--renderer` flag specifies a renderer, `--help` output is not allowed + if ctx.help { + return Err(CmdProcessError::RendererOverrideButRequestHelp); + } + + let (data, type_name) = Self::process(args, ctx).await?; + + let renderer_override = renderer_override.as_str(); + + // Serialize the data based on its concrete type + let render_result = include!("../render/_override_renderer_entry.rs"); + + match render_result { + Ok(r) => Ok(r), + Err(e) => Err(CmdProcessError::Render(e)), + } } } - /// performing any necessary post-execution processing - fn process( + /// Run the command and hand it over to the rendering system + /// to select the appropriate renderer for the result + fn process_to_render_system( args: Vec<String>, ctx: JVCommandContext, - ) -> impl Future<Output = Result<JVRenderResult, CmdProcessError>> + Send - where - Self: Sync, - { - Self::process_with_renderer::<Renderer>(args, ctx) + ) -> impl Future<Output = Result<JVRenderResult, CmdProcessError>> + Send { + async { + // If the `--help` flag is used, + // skip execution and directly render help information + if ctx.help { + let mut r = JVRenderResult::default(); + r_println!(r, "{}", Self::get_help_str()); + return Ok(r); + } + + let (data, id_str) = Self::process(args, ctx).await?; + match render(data, id_str).await { + Ok(r) => Ok(r), + Err(e) => Err(CmdProcessError::Render(e)), + } + } } - /// Process the command output with a custom renderer, - /// performing any necessary post-execution processing - fn process_with_renderer<R: JVResultRenderer<Output> + Send>( + fn process( args: Vec<String>, ctx: JVCommandContext, - ) -> impl Future<Output = Result<JVRenderResult, CmdProcessError>> + Send - where - Self: Sync, + ) -> impl Future<Output = Result<(Box<dyn Any + Send + 'static>, String), CmdProcessError>> + Send { async move { let mut full_args = vec!["jv".to_string()]; @@ -70,13 +94,6 @@ where Err(_) => return Err(CmdProcessError::ParseError(Self::get_help_str())), }; - // If the help flag is used, skip execution and directly print help - if ctx.help { - let mut r = JVRenderResult::default(); - r_println!(r, "{}", Self::get_help_str()); - return Ok(r); - } - let (input, collect) = match tokio::try_join!( Self::prepare(&parsed_args, &ctx), Self::collect(&parsed_args, &ctx) @@ -85,17 +102,15 @@ where Err(e) => return Err(CmdProcessError::from(e)), }; - let output = match Self::exec(input, collect).await { + let data = match Self::exec(input, collect).await { Ok(output) => output, Err(e) => return Err(CmdProcessError::from(e)), }; - match R::render(&output).await { - Ok(r) => Ok(r), - Err(e) => Err(CmdProcessError::from(e)), - } + Ok(data) } } + /// Prepare /// Converts Argument input into parameters readable during the execution phase fn prepare( @@ -116,5 +131,8 @@ where fn exec( input: Input, collect: Collect, - ) -> impl Future<Output = Result<Output, CmdExecuteError>> + Send; + ) -> impl Future<Output = Result<(Box<dyn Any + Send + 'static>, String), CmdExecuteError>> + Send; + + /// Get output type mapping + fn get_output_type_mapping() -> HashMap<String, TypeId>; } diff --git a/src/systems/cmd/errors.rs b/src/systems/cmd/errors.rs index 358d15a..7ec5e1c 100644 --- a/src/systems/cmd/errors.rs +++ b/src/systems/cmd/errors.rs @@ -76,6 +76,12 @@ pub enum CmdRenderError { #[error("Renderer `{0}` not found")] RendererNotFound(String), + + #[error("Type mismatch: expected `{expected:?}`, got `{actual:?}`")] + TypeMismatch { + expected: std::any::TypeId, + actual: std::any::TypeId, + }, } impl CmdRenderError { @@ -109,6 +115,12 @@ pub enum CmdProcessError { #[error("Parse error")] ParseError(String), + + #[error("Renderer override mode is active, but user requested help")] + RendererOverrideButRequestHelp, + + #[error("Downcast failed")] + DowncastFailed, } impl CmdProcessError { diff --git a/src/systems/cmd/macros.rs b/src/systems/cmd/macros.rs new file mode 100644 index 0000000..c7d576d --- /dev/null +++ b/src/systems/cmd/macros.rs @@ -0,0 +1,166 @@ +#[macro_export] +/// # JVCS_CLI Command Definition Macro +/// +/// ## Import +/// +/// Add the following macro to your code +/// +/// ```ignore +/// crate::command_template!(); +/// ``` +/// +/// Then paste the following content into your code +/// +/// ```ignore +/// use cmd_system_macros::exec; +/// use crate::{ +/// cmd_output, +/// systems::cmd::{ +/// cmd_system::JVCommandContext, +/// errors::{CmdExecuteError, CmdPrepareError}, +/// workspace_reader::LocalWorkspaceReader, +/// }, +/// }; +/// +/// /// Define command type +/// /// Names should match the file name in the following format: +/// /// custom.rs matches JVCustomCommand, invoked using `jv custom <args...>` +/// /// get_data.rs matches JVGetDataCommand, invoked using `jv get data <args...>` +/// pub struct JVCustomCommand; +/// +/// /// Command type, should match the definition above +/// type Cmd = JVCustomCommand; +/// +/// /// Specify Argument +/// /// ```ignore +/// /// #[derive(Parser, Debug)] +/// /// pub struct JVCustomArgument; +/// /// ``` +/// type Arg = JVCustomArgument; +/// +/// /// Specify InputData +/// /// ```ignore +/// /// pub struct JVCustomInput; +/// /// ``` +/// type In = JVCustomInput; +/// +/// /// Specify CollectData +/// /// ```ignore +/// /// pub struct JVCustomCollect; +/// /// ``` +/// type Collect = JVCustomCollect; +/// +/// /// Return a string, rendered when the user needs help (command specifies `--help` or syntax error) +/// fn help_str() -> String { +/// todo!() +/// } +/// +/// /// Preparation phase, preprocess user input and convert to a data format friendly for the execution phase +/// async fn prepare(args: &Arg, ctx: &JVCommandContext) -> Result<In, CmdPrepareError> { +/// todo!() +/// } +/// +/// /// Collect necessary local information for execution +/// async fn collect(args: &Arg, ctx: &JVCommandContext) -> Result<Collect, CmdPrepareError> { +/// let reader = LocalWorkspaceReader::default(); +/// todo!() +/// } +/// +/// /// Execution phase, call core layer or other custom logic +/// #[exec] +/// async fn exec( +/// input: In, +/// collect: Collect, +/// ) -> Result<(Box<dyn std::any::Any + Send + 'static>, String), CmdExecuteError> { +/// todo!(); +/// +/// // Use the following method to return results +/// cmd_output!(output, JVCustomOutput) +/// } +/// ``` +/// +/// Of course, you can also use the comment-free version +/// +/// ```ignore +/// use cmd_system_macros::exec; +/// use crate::{ +/// cmd_output, +/// systems::cmd::{ +/// cmd_system::JVCommandContext, +/// errors::{CmdExecuteError, CmdPrepareError}, +/// workspace_reader::LocalWorkspaceReader, +/// }, +/// }; +/// +/// pub struct JVCustomCommand; +/// type Cmd = JVCustomCommand; +/// type Arg = JVCustomArgument; +/// type In = JVCustomInput; +/// type Collect = JVCustomCollect; +/// +/// fn help_str() -> String { +/// todo!() +/// } +/// +/// async fn prepare(args: &Arg, ctx: &JVCommandContext) -> Result<In, CmdPrepareError> { +/// todo!() +/// } +/// +/// async fn collect(args: &Arg, ctx: &JVCommandContext) -> Result<Collect, CmdPrepareError> { +/// let reader = LocalWorkspaceReader::default(); +/// todo!() +/// } +/// +/// #[exec] +/// async fn exec( +/// input: In, +/// collect: Collect, +/// ) -> Result<(Box<dyn std::any::Any + Send + 'static>, String), CmdExecuteError> { +/// todo!(); +/// cmd_output!(output, JVCustomOutput) +/// } +/// ``` +macro_rules! command_template { + () => { + impl $crate::systems::cmd::cmd_system::JVCommand<Arg, In, Collect> for Cmd { + fn get_help_str() -> String { + help_str() + } + + async fn prepare( + args: &Arg, + ctx: &$crate::systems::cmd::cmd_system::JVCommandContext, + ) -> Result<In, $crate::systems::cmd::errors::CmdPrepareError> { + prepare(args, ctx).await + } + + async fn collect( + args: &Arg, + ctx: &$crate::systems::cmd::cmd_system::JVCommandContext, + ) -> Result<Collect, $crate::systems::cmd::errors::CmdPrepareError> { + collect(args, ctx).await + } + + async fn exec( + input: In, + collect: Collect, + ) -> Result< + (Box<dyn std::any::Any + Send + 'static>, String), + $crate::systems::cmd::errors::CmdExecuteError, + > { + exec(input, collect).await + } + + fn get_output_type_mapping() -> std::collections::HashMap<String, std::any::TypeId> { + get_output_type_mapping() + } + } + }; +} + +#[macro_export] +macro_rules! cmd_output { + ($v:expr, $t:ty) => { + Ok((Box::new($v), stringify!($t).to_string())) + }; +} diff --git a/src/systems/cmd/processer.rs b/src/systems/cmd/processer.rs index 7c464a2..4bcaaeb 100644 --- a/src/systems/cmd/processer.rs +++ b/src/systems/cmd/processer.rs @@ -1,7 +1,7 @@ -use crate::systems::cmd::_registry::{jv_cmd_nodes, jv_cmd_process_node}; +use crate::systems::cmd::_commands::{jv_cmd_nodes, jv_cmd_process_node}; use crate::systems::cmd::cmd_system::JVCommandContext; use crate::systems::cmd::errors::CmdProcessError; -use crate::systems::cmd::renderer::JVRenderResult; +use crate::systems::render::renderer::JVRenderResult; pub async fn jv_cmd_process( args: &Vec<String>, diff --git a/src/systems/render.rs b/src/systems/render.rs new file mode 100644 index 0000000..8b387e7 --- /dev/null +++ b/src/systems/render.rs @@ -0,0 +1,2 @@ +pub mod render_system; +pub mod renderer; diff --git a/src/systems/render/render_system.rs b/src/systems/render/render_system.rs new file mode 100644 index 0000000..7371e7a --- /dev/null +++ b/src/systems/render/render_system.rs @@ -0,0 +1,14 @@ +use std::any::Any; + +use crate::systems::{ + cmd::errors::CmdRenderError, + render::renderer::{JVRenderResult, JVResultRenderer}, +}; + +pub async fn render( + data: Box<dyn Any + Send + 'static>, + type_name: String, +) -> Result<JVRenderResult, CmdRenderError> { + let type_name_str = type_name.as_str(); + include!("_specific_renderer_matching.rs") +} diff --git a/src/systems/cmd/renderer.rs b/src/systems/render/renderer.rs index 1849ee9..9060683 100644 --- a/src/systems/cmd/renderer.rs +++ b/src/systems/render/renderer.rs @@ -1,16 +1,15 @@ use std::fmt::{Display, Formatter}; - -use serde::Serialize; +use std::future::Future; use crate::systems::cmd::errors::CmdRenderError; -pub trait JVResultRenderer<Data> -where - Data: Serialize, -{ +pub trait JVResultRenderer<Data> { fn render( data: &Data, ) -> impl Future<Output = Result<JVRenderResult, CmdRenderError>> + Send + Sync; + + fn get_type_id(&self) -> std::any::TypeId; + fn get_data_type_id(&self) -> std::any::TypeId; } #[derive(Default, Debug, PartialEq)] |
