diff options
| author | 魏曹先生 <1992414357@qq.com> | 2026-02-04 00:27:16 +0800 |
|---|---|---|
| committer | 魏曹先生 <1992414357@qq.com> | 2026-02-04 00:27:16 +0800 |
| commit | d19e5d84ee21502fd3440511d4ffb1ee1f49d3b2 (patch) | |
| tree | fb8efef6f8e9a26c5b60d4ac220b11d6c6f0775e /src | |
| parent | 7ee0d3f20c875e7405bb8442c5eb0228d1599a03 (diff) | |
Refactor build system and implement complete renderer system
- Split monolithic build.rs into modular async generators
- Add renderer override system with type-safe dispatch
- Implement command template macro for consistent command definitions
- Add proc-macro crates for command and renderer systems
- Reorganize directory structure for better separation of concerns
- Update documentation to reflect new architecture
Diffstat (limited to 'src')
| -rw-r--r-- | src/bin/jvn.rs | 17 | ||||
| -rw-r--r-- | src/cmds.rs | 1 | ||||
| -rw-r--r-- | src/cmds/cmd/status.rs | 253 | ||||
| -rw-r--r-- | src/cmds/override.rs | 1 | ||||
| -rw-r--r-- | src/cmds/override/renderer/json.rs | 17 | ||||
| -rw-r--r-- | src/cmds/override/renderer/json_pretty.rs | 16 | ||||
| -rw-r--r-- | src/cmds/renderer/json.rs | 27 | ||||
| -rw-r--r-- | src/cmds/renderer/json_pretty.rs | 26 | ||||
| -rw-r--r-- | src/cmds/renderer/status.rs | 32 | ||||
| -rw-r--r-- | src/systems.rs | 1 | ||||
| -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 |
18 files changed, 456 insertions, 254 deletions
diff --git a/src/bin/jvn.rs b/src/bin/jvn.rs index 598be3d..1dbc517 100644 --- a/src/bin/jvn.rs +++ b/src/bin/jvn.rs @@ -3,7 +3,7 @@ use std::process::exit; use cli_utils::display::md; use cli_utils::env::current_locales; use cli_utils::levenshtein_distance::levenshtein_distance; -use just_enough_vcs_cli::systems::cmd::_registry::jv_cmd_nodes; +use just_enough_vcs_cli::systems::cmd::_commands::jv_cmd_nodes; use just_enough_vcs_cli::systems::cmd::cmd_system::JVCommandContext; use just_enough_vcs_cli::systems::cmd::errors::{CmdExecuteError, CmdPrepareError, CmdRenderError}; use just_enough_vcs_cli::systems::cmd::{errors::CmdProcessError, processer::jv_cmd_process}; @@ -120,6 +120,15 @@ async fn main() { eprintln!("{}", help) } } + CmdProcessError::RendererOverrideButRequestHelp => { + eprintln!( + "{}", + md(t!("process_error.renderer_override_but_request_help")) + ); + } + CmdProcessError::DowncastFailed => { + eprintln!("{}", md(t!("process_error.downcast_failed"))); + } } } std::process::exit(1); @@ -253,5 +262,11 @@ fn handle_render_error(cmd_render_error: CmdRenderError) { )) ); } + CmdRenderError::TypeMismatch { + expected: _, + actual: _, + } => { + eprintln!("{}", md(t!("render_error.type_mismatch"))); + } } } diff --git a/src/cmds.rs b/src/cmds.rs index 46057c8..92e587f 100644 --- a/src/cmds.rs +++ b/src/cmds.rs @@ -3,4 +3,5 @@ pub mod cmd; pub mod collect; pub mod r#in; pub mod out; +pub mod r#override; pub mod renderer; diff --git a/src/cmds/cmd/status.rs b/src/cmds/cmd/status.rs index f34dbb8..52f12d0 100644 --- a/src/cmds/cmd/status.rs +++ b/src/cmds/cmd/status.rs @@ -1,159 +1,156 @@ use std::{collections::HashMap, time::SystemTime}; -use just_enough_vcs::vcs::{ - constants::VAULT_HOST_NAME, data::local::workspace_analyzer::ModifiedRelativePathBuf, -}; - use crate::{ + cmd_output, cmds::{ arg::status::JVStatusArgument, collect::status::JVStatusCollect, r#in::status::JVStatusInput, out::status::{JVStatusOutput, JVStatusWrongModifyReason}, - renderer::status::JVStatusRenderer, }, systems::cmd::{ - cmd_system::{JVCommand, JVCommandContext}, + cmd_system::JVCommandContext, errors::{CmdExecuteError, CmdPrepareError}, workspace_reader::LocalWorkspaceReader, }, }; +use cmd_system_macros::exec; +use just_enough_vcs::vcs::{ + constants::VAULT_HOST_NAME, data::local::workspace_analyzer::ModifiedRelativePathBuf, +}; pub struct JVStatusCommand; +type Cmd = JVStatusCommand; +type Arg = JVStatusArgument; +type In = JVStatusInput; +type Collect = JVStatusCollect; -impl JVCommand<JVStatusArgument, JVStatusInput, JVStatusCollect, JVStatusOutput, JVStatusRenderer> - for JVStatusCommand -{ - async fn prepare( - _args: &JVStatusArgument, - _ctx: &JVCommandContext, - ) -> Result<JVStatusInput, CmdPrepareError> { - Ok(JVStatusInput) - } +fn help_str() -> String { + "".to_string() +} - async fn collect( - _args: &JVStatusArgument, - _ctx: &JVCommandContext, - ) -> Result<JVStatusCollect, CmdPrepareError> { - // Initialize a reader for the local workspace and a default result structure - let mut reader = LocalWorkspaceReader::default(); - let mut collect = JVStatusCollect::default(); - - // Analyze the current status of the local workspace - // (detects changes like created, modified, moved, etc.) - let analyzed = reader.analyze_local_status().await?; - - // Retrieve the current account (member) ID - let account = reader.current_account().await?; - - // Retrieve the name of the current sheet - let sheet_name = reader.sheet_name().await?; - - // Is Host Mode - let is_host_mode = reader.is_host_mode().await?; - - let cached_sheet = reader.cached_sheet(&sheet_name).await?; - let sheet_holder = cached_sheet.holder().cloned().unwrap_or_default(); - let is_ref_sheet = sheet_holder == VAULT_HOST_NAME; - - // Get Latest file data - let latest_file_data = reader.pop_latest_file_data(&account).await?; - - // Get the timestamp of the last update, defaulting to the current time if not available - let update_time = reader - .latest_info() - .await? - .update_instant - .unwrap_or(SystemTime::now()); - - // Record the current system time - let now_time = SystemTime::now(); - - // Populate the result structure with the gathered data - collect.current_account = account; - collect.current_sheet = sheet_name; - collect.is_host_mode = is_host_mode; - collect.in_ref_sheet = is_ref_sheet; - collect.analyzed_result = analyzed; - collect.update_time = update_time; - collect.now_time = now_time; - collect.latest_file_data = latest_file_data; - Ok(collect) - } +async fn prepare(_args: &Arg, _ctx: &JVCommandContext) -> Result<In, CmdPrepareError> { + Ok(JVStatusInput) +} - async fn exec( - _input: JVStatusInput, - collect: JVStatusCollect, - ) -> Result<JVStatusOutput, CmdExecuteError> { - let mut wrong_modified_items: HashMap<ModifiedRelativePathBuf, JVStatusWrongModifyReason> = - HashMap::new(); +async fn collect(_args: &Arg, _ctx: &JVCommandContext) -> Result<Collect, CmdPrepareError> { + // Initialize a reader for the local workspace and a default result structure + let mut reader = LocalWorkspaceReader::default(); + let mut collect = JVStatusCollect::default(); + + // Analyze the current status of the local workspace + // (detects changes like created, modified, moved, etc.) + let analyzed = reader.analyze_local_status().await?; + + // Retrieve the current account (member) ID + let account = reader.current_account().await?; + + // Retrieve the name of the current sheet + let sheet_name = reader.sheet_name().await?; + + // Is Host Mode + let is_host_mode = reader.is_host_mode().await?; + + let cached_sheet = reader.cached_sheet(&sheet_name).await?; + let sheet_holder = cached_sheet.holder().cloned().unwrap_or_default(); + let is_ref_sheet = sheet_holder == VAULT_HOST_NAME; + + // Get Latest file data + let latest_file_data = reader.pop_latest_file_data(&account).await?; + + // Get the timestamp of the last update, defaulting to the current time if not available + let update_time = reader + .latest_info() + .await? + .update_instant + .unwrap_or(SystemTime::now()); + + // Record the current system time + let now_time = SystemTime::now(); + + // Populate the result structure with the gathered data + collect.current_account = account; + collect.current_sheet = sheet_name; + collect.is_host_mode = is_host_mode; + collect.in_ref_sheet = is_ref_sheet; + collect.analyzed_result = analyzed; + collect.update_time = update_time; + collect.now_time = now_time; + collect.latest_file_data = latest_file_data; + Ok(collect) +} - let latest_file_data = &collect.latest_file_data; +#[exec] +async fn exec( + _input: In, + collect: Collect, +) -> Result<(Box<dyn std::any::Any + Send + 'static>, String), CmdExecuteError> { + let mut wrong_modified_items: HashMap<ModifiedRelativePathBuf, JVStatusWrongModifyReason> = + HashMap::new(); + + let latest_file_data = &collect.latest_file_data; + + // Calculate whether modifications are correc + let modified = &collect.analyzed_result.modified; + for item in modified { + // Get mapping + let Ok(mapping) = collect.local_sheet_data.mapping_data(&item) else { + continue; + }; - // Calculate whether modifications are correc - let modified = &collect.analyzed_result.modified; - for item in modified { - // Get mapping - let Ok(mapping) = collect.local_sheet_data.mapping_data(&item) else { + // Check base version + { + let base_version = mapping.version_when_updated().clone(); + let Some(latest_version) = latest_file_data + .file_version(mapping.mapping_vfid()) + .cloned() + else { continue; }; - // Check base version - { - let base_version = mapping.version_when_updated().clone(); - let Some(latest_version) = latest_file_data - .file_version(mapping.mapping_vfid()) - .cloned() - else { - continue; - }; - - // Base version dismatch - if base_version != latest_version { - wrong_modified_items.insert( - item.clone(), - JVStatusWrongModifyReason::BaseVersionMismatch { - base_version, - latest_version, - }, - ); - continue; - } - } - - // Check edit right (only check when current is not HOST) - if collect.current_account != VAULT_HOST_NAME { - let holder = latest_file_data.file_holder(mapping.mapping_vfid()); - if holder.is_none() { - wrong_modified_items.insert(item.clone(), JVStatusWrongModifyReason::NoHolder); - continue; - } - - let holder = holder.cloned().unwrap(); - if &collect.current_account != &holder { - wrong_modified_items.insert( - item.clone(), - JVStatusWrongModifyReason::ModifiedButNotHeld { holder: holder }, - ); - } + // Base version dismatch + if base_version != latest_version { + wrong_modified_items.insert( + item.clone(), + JVStatusWrongModifyReason::BaseVersionMismatch { + base_version, + latest_version, + }, + ); + continue; } } - let output = JVStatusOutput { - current_account: collect.current_account, - current_sheet: collect.current_sheet, - is_host_mode: collect.is_host_mode, - in_ref_sheet: collect.in_ref_sheet, - analyzed_result: collect.analyzed_result, - wrong_modified_items: wrong_modified_items, - update_time: collect.update_time, - now_time: collect.now_time, - }; + // Check edit right (only check when current is not HOST) + if collect.current_account != VAULT_HOST_NAME { + let holder = latest_file_data.file_holder(mapping.mapping_vfid()); + if holder.is_none() { + wrong_modified_items.insert(item.clone(), JVStatusWrongModifyReason::NoHolder); + continue; + } - Ok(output) + let holder = holder.cloned().unwrap(); + if &collect.current_account != &holder { + wrong_modified_items.insert( + item.clone(), + JVStatusWrongModifyReason::ModifiedButNotHeld { holder: holder }, + ); + } + } } - fn get_help_str() -> String { - "".to_string() - } + let output = JVStatusOutput { + current_account: collect.current_account, + current_sheet: collect.current_sheet, + is_host_mode: collect.is_host_mode, + in_ref_sheet: collect.in_ref_sheet, + analyzed_result: collect.analyzed_result, + wrong_modified_items: wrong_modified_items, + update_time: collect.update_time, + now_time: collect.now_time, + }; + + cmd_output!(output, JVStatusOutput) } + +crate::command_template!(); diff --git a/src/cmds/override.rs b/src/cmds/override.rs new file mode 100644 index 0000000..8cfd458 --- /dev/null +++ b/src/cmds/override.rs @@ -0,0 +1 @@ +pub mod renderer; diff --git a/src/cmds/override/renderer/json.rs b/src/cmds/override/renderer/json.rs new file mode 100644 index 0000000..b4e69f1 --- /dev/null +++ b/src/cmds/override/renderer/json.rs @@ -0,0 +1,17 @@ +use serde::Serialize; +use serde_json; + +use crate::{ + r_print, + systems::{cmd::errors::CmdRenderError, render::renderer::JVRenderResult}, +}; + +pub async fn render<T: Serialize + Send>(data: &T) -> Result<JVRenderResult, CmdRenderError> { + let mut r = JVRenderResult::default(); + let json_string = + serde_json::to_string(data).map_err(|e| CmdRenderError::SerializeFailed(e.to_string()))?; + + r_print!(r, "{}", json_string); + + Ok(r) +} diff --git a/src/cmds/override/renderer/json_pretty.rs b/src/cmds/override/renderer/json_pretty.rs new file mode 100644 index 0000000..3923117 --- /dev/null +++ b/src/cmds/override/renderer/json_pretty.rs @@ -0,0 +1,16 @@ +use serde::Serialize; + +use crate::{ + r_print, + systems::{cmd::errors::CmdRenderError, render::renderer::JVRenderResult}, +}; + +pub async fn render<T: Serialize + Send>(data: &T) -> Result<JVRenderResult, CmdRenderError> { + let mut r = JVRenderResult::default(); + let json_string = serde_json::to_string_pretty(data) + .map_err(|e| CmdRenderError::SerializeFailed(e.to_string()))?; + + r_print!(r, "{}", json_string); + + Ok(r) +} diff --git a/src/cmds/renderer/json.rs b/src/cmds/renderer/json.rs deleted file mode 100644 index 9a3105d..0000000 --- a/src/cmds/renderer/json.rs +++ /dev/null @@ -1,27 +0,0 @@ -use serde::Serialize; -use serde_json; - -use crate::{ - r_print, - systems::cmd::{ - errors::CmdRenderError, - renderer::{JVRenderResult, JVResultRenderer}, - }, -}; - -pub struct JVResultJsonRenderer; - -impl<T> JVResultRenderer<T> for JVResultJsonRenderer -where - T: Serialize + Sync, -{ - async fn render(data: &T) -> Result<JVRenderResult, CmdRenderError> { - let mut r = JVRenderResult::default(); - let json_string = serde_json::to_string(data) - .map_err(|e| CmdRenderError::SerializeFailed(e.to_string()))?; - - r_print!(r, "{}", json_string); - - Ok(r) - } -} diff --git a/src/cmds/renderer/json_pretty.rs b/src/cmds/renderer/json_pretty.rs deleted file mode 100644 index a4a3ba5..0000000 --- a/src/cmds/renderer/json_pretty.rs +++ /dev/null @@ -1,26 +0,0 @@ -use serde::Serialize; - -use crate::{ - r_print, - systems::cmd::{ - errors::CmdRenderError, - renderer::{JVRenderResult, JVResultRenderer}, - }, -}; - -pub struct JVResultPrettyJsonRenderer; - -impl<T> JVResultRenderer<T> for JVResultPrettyJsonRenderer -where - T: Serialize + Sync, -{ - async fn render(data: &T) -> Result<JVRenderResult, CmdRenderError> { - let mut r = JVRenderResult::default(); - let json_string = serde_json::to_string_pretty(data) - .map_err(|e| CmdRenderError::SerializeFailed(e.to_string()))?; - - r_print!(r, "{}", json_string); - - Ok(r) - } -} diff --git a/src/cmds/renderer/status.rs b/src/cmds/renderer/status.rs index 965ff87..573e74e 100644 --- a/src/cmds/renderer/status.rs +++ b/src/cmds/renderer/status.rs @@ -2,40 +2,36 @@ use cli_utils::{ display::{SimpleTable, md}, env::auto_update_outdate, }; +use render_system_macros::result_renderer; use rust_i18n::t; +use crate::cmds::out::status::JVStatusWrongModifyReason; use crate::{ - cmds::out::status::{JVStatusOutput, JVStatusWrongModifyReason}, + cmds::out::status::JVStatusOutput, r_println, - systems::cmd::{ - errors::CmdRenderError, - renderer::{JVRenderResult, JVResultRenderer}, - }, + systems::{cmd::errors::CmdRenderError, render::renderer::JVRenderResult}, }; -pub struct JVStatusRenderer; - enum Mode { StructuralChangesMode, ContentChangesMode, Clean, } -impl JVResultRenderer<JVStatusOutput> for JVStatusRenderer { - async fn render(data: &JVStatusOutput) -> Result<JVRenderResult, CmdRenderError> { - let mut r = JVRenderResult::default(); +#[result_renderer(JVStatusRenderer)] +pub async fn render(data: &JVStatusOutput) -> Result<JVRenderResult, CmdRenderError> { + let mut r = JVRenderResult::default(); - // Render Header - render_header(&mut r, data); + // Render Header + render_header(&mut r, data); - // Render Info and Mode - render_info_and_mode(&mut r, data); + // Render Info and Mode + render_info_and_mode(&mut r, data); - // Render Hint - render_hint(&mut r, data); + // Render Hint + render_hint(&mut r, data); - Ok(r) - } + Ok(r) } fn render_header(r: &mut JVRenderResult, data: &JVStatusOutput) { diff --git a/src/systems.rs b/src/systems.rs index 52958ec..7f77ca5 100644 --- a/src/systems.rs +++ b/src/systems.rs @@ -1 +1,2 @@ pub mod cmd; +pub mod render; 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)] |
