summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/bin/jvn.rs17
-rw-r--r--src/cmds.rs1
-rw-r--r--src/cmds/cmd/status.rs253
-rw-r--r--src/cmds/override.rs1
-rw-r--r--src/cmds/override/renderer/json.rs17
-rw-r--r--src/cmds/override/renderer/json_pretty.rs16
-rw-r--r--src/cmds/renderer/json.rs27
-rw-r--r--src/cmds/renderer/json_pretty.rs26
-rw-r--r--src/cmds/renderer/status.rs32
-rw-r--r--src/systems.rs1
-rw-r--r--src/systems/cmd.rs4
-rw-r--r--src/systems/cmd/cmd_system.rs106
-rw-r--r--src/systems/cmd/errors.rs12
-rw-r--r--src/systems/cmd/macros.rs166
-rw-r--r--src/systems/cmd/processer.rs4
-rw-r--r--src/systems/render.rs2
-rw-r--r--src/systems/render/render_system.rs14
-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)]