diff options
| author | 魏曹先生 <1992414357@qq.com> | 2026-01-23 04:49:34 +0800 |
|---|---|---|
| committer | 魏曹先生 <1992414357@qq.com> | 2026-01-23 04:49:34 +0800 |
| commit | 626536ce51975b051fea087620bc1eb7f6bc69d3 (patch) | |
| tree | b24916b22012d9b8b3b09366bca9911781e6d596 /src/systems | |
| parent | f607f6ac3b98d00370f613e674da7beb4c61ce58 (diff) | |
Reorganize project structure into systems and assets
Diffstat (limited to 'src/systems')
| -rw-r--r-- | src/systems/cmd.rs | 5 | ||||
| -rw-r--r-- | src/systems/cmd/cmd_system.rs | 100 | ||||
| -rw-r--r-- | src/systems/cmd/errors.rs | 139 | ||||
| -rw-r--r-- | src/systems/cmd/processer.rs | 41 | ||||
| -rw-r--r-- | src/systems/cmd/renderer.rs | 54 |
5 files changed, 339 insertions, 0 deletions
diff --git a/src/systems/cmd.rs b/src/systems/cmd.rs new file mode 100644 index 0000000..8983892 --- /dev/null +++ b/src/systems/cmd.rs @@ -0,0 +1,5 @@ +pub mod _registry; +pub mod cmd_system; +pub mod errors; +pub mod processer; +pub mod renderer; diff --git a/src/systems/cmd/cmd_system.rs b/src/systems/cmd/cmd_system.rs new file mode 100644 index 0000000..65f972d --- /dev/null +++ b/src/systems/cmd/cmd_system.rs @@ -0,0 +1,100 @@ +use serde::Serialize; + +use crate::{ + r_println, + systems::cmd::{ + errors::{CmdExecuteError, CmdPrepareError, CmdProcessError, CmdRenderError}, + renderer::{JVRenderResult, JVResultRenderer}, + }, +}; +use std::future::Future; + +pub struct JVCommandContext { + pub help: bool, + pub confirmed: bool, +} + +pub trait JVCommand<Argument, Input, Output, Renderer> +where + Argument: clap::Parser + Send + Sync, + Input: Send + Sync, + Output: Serialize + Send + Sync, + 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( + args: Vec<String>, + ctx: JVCommandContext, + renderer: String, + ) -> impl Future<Output = Result<JVRenderResult, CmdProcessError>> + Send + where + Self: Sync, + { + async move { + let renderer_str = renderer.as_str(); + include!("_renderers.rs") + } + } + + /// performing any necessary post-execution processing + fn process( + args: Vec<String>, + ctx: JVCommandContext, + ) -> impl Future<Output = Result<JVRenderResult, CmdProcessError>> + Send + where + Self: Sync, + { + Self::process_with_renderer::<Renderer>(args, ctx) + } + + /// Process the command output with a custom renderer, + /// performing any necessary post-execution processing + fn process_with_renderer<R: JVResultRenderer<Output> + Send + Sync>( + args: Vec<String>, + ctx: JVCommandContext, + ) -> impl Future<Output = Result<JVRenderResult, CmdProcessError>> + Send + where + Self: Sync, + { + async move { + let mut full_args = vec!["jv".to_string()]; + full_args.extend(args); + let parsed_args = match Argument::try_parse_from(full_args) { + Ok(args) => args, + 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 = match Self::prepare(parsed_args, ctx).await { + Ok(input) => input, + Err(e) => return Err(CmdProcessError::from(e)), + }; + let output = match Self::exec(input).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)), + } + } + } + + /// Prepare to run the command, + /// converting Clap input into the command's supported input + fn prepare( + args: Argument, + ctx: JVCommandContext, + ) -> impl Future<Output = Result<Input, CmdPrepareError>> + Send; + + /// Run the command phase, + /// returning an output structure, waiting for rendering + fn exec(input: Input) -> impl Future<Output = Result<Output, CmdExecuteError>> + Send; +} diff --git a/src/systems/cmd/errors.rs b/src/systems/cmd/errors.rs new file mode 100644 index 0000000..358d15a --- /dev/null +++ b/src/systems/cmd/errors.rs @@ -0,0 +1,139 @@ +use just_enough_vcs::vcs::data::{member::MemberId, sheet::SheetName}; + +#[derive(thiserror::Error, Debug)] +pub enum CmdPrepareError { + #[error("IO error: {0}")] + Io(#[from] std::io::Error), + + #[error("{0}")] + Error(String), + + #[error("LocalWorkspace not found")] + LocalWorkspaceNotFound, + + #[error("LocalConfig not found")] + LocalConfigNotFound, + + #[error("LatestInfo not found")] + LatestInfoNotFound, + + #[error("LatestFileData of {0} not found")] + LatestFileDataNotExist(MemberId), + + #[error("CachedSheet `{0}` not found")] + CachedSheetNotFound(SheetName), + + #[error("LocalSheet `{0}/{1}` not found")] + LocalSheetNotFound(MemberId, SheetName), + + #[error("LocalStatusAnalyzeFailed")] + LocalStatusAnalyzeFailed, + + #[error("No sheet in use")] + NoSheetInUse, +} + +impl CmdPrepareError { + pub fn new(msg: impl AsRef<str>) -> Self { + CmdPrepareError::Error(msg.as_ref().to_string()) + } +} + +#[derive(thiserror::Error, Debug)] +pub enum CmdExecuteError { + #[error("IO error: {0}")] + Io(#[from] std::io::Error), + + #[error("Content not prepared, cannot run")] + Prepare(#[from] CmdPrepareError), + + #[error("{0}")] + Error(String), +} + +impl CmdExecuteError { + pub fn new(msg: impl AsRef<str>) -> Self { + CmdExecuteError::Error(msg.as_ref().to_string()) + } +} + +#[derive(thiserror::Error, Debug)] +pub enum CmdRenderError { + #[error("IO error: {0}")] + Io(#[from] std::io::Error), + + #[error("Preparation failed, cannot render")] + Prepare(#[from] CmdPrepareError), + + #[error("Execution failed, no output content obtained before rendering")] + Execute(#[from] CmdExecuteError), + + #[error("{0}")] + Error(String), + + #[error("Serialize failed, {0}")] + SerializeFailed(String), + + #[error("Renderer `{0}` not found")] + RendererNotFound(String), +} + +impl CmdRenderError { + pub fn new(msg: impl AsRef<str>) -> Self { + CmdRenderError::Error(msg.as_ref().to_string()) + } +} + +#[derive(thiserror::Error, Debug)] +pub enum CmdProcessError { + #[error("Prepare error: {0}")] + Prepare(#[from] CmdPrepareError), + + #[error("Execute error: {0}")] + Execute(#[from] CmdExecuteError), + + #[error("Render error: {0}")] + Render(#[from] CmdRenderError), + + #[error("{0}")] + Error(String), + + #[error("Node `{0}` not found!")] + NoNodeFound(String), + + #[error("No matching command found")] + NoMatchingCommand, + + #[error("Ambiguous command, multiple matches found")] + AmbiguousCommand(Vec<String>), + + #[error("Parse error")] + ParseError(String), +} + +impl CmdProcessError { + pub fn new(msg: impl AsRef<str>) -> Self { + CmdProcessError::Error(msg.as_ref().to_string()) + } + + pub fn prepare_err(&self) -> Option<&CmdPrepareError> { + match self { + CmdProcessError::Prepare(e) => Some(e), + _ => None, + } + } + + pub fn execute_err(&self) -> Option<&CmdExecuteError> { + match self { + CmdProcessError::Execute(e) => Some(e), + _ => None, + } + } + + pub fn render_err(&self) -> Option<&CmdRenderError> { + match self { + CmdProcessError::Render(e) => Some(e), + _ => None, + } + } +} diff --git a/src/systems/cmd/processer.rs b/src/systems/cmd/processer.rs new file mode 100644 index 0000000..d357e44 --- /dev/null +++ b/src/systems/cmd/processer.rs @@ -0,0 +1,41 @@ +use crate::systems::cmd::_registry::{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; + +pub async fn jv_cmd_process( + args: Vec<String>, + ctx: JVCommandContext, + renderer_override: String, +) -> Result<JVRenderResult, CmdProcessError> { + let nodes = jv_cmd_nodes(); + let command = args.join(" "); + + // Find nodes that match the beginning of the command + let matching_nodes: Vec<&String> = nodes + .iter() + .filter(|node| command.starts_with(node.as_str())) + .collect(); + + match matching_nodes.len() { + 0 => { + // No matching node found + return Err(CmdProcessError::NoMatchingCommand); + } + 1 => { + let matched_prefix = matching_nodes[0]; + let prefix_len = matched_prefix.split_whitespace().count(); + let trimmed_args: Vec<String> = args.into_iter().skip(prefix_len).collect(); + return jv_cmd_process_node(matched_prefix, trimmed_args, ctx, renderer_override).await; + } + _ => { + // Multiple matching nodes found + return Err(CmdProcessError::AmbiguousCommand( + matching_nodes + .iter() + .map(|s| s.to_string()) + .collect::<Vec<String>>(), + )); + } + } +} diff --git a/src/systems/cmd/renderer.rs b/src/systems/cmd/renderer.rs new file mode 100644 index 0000000..bdd702d --- /dev/null +++ b/src/systems/cmd/renderer.rs @@ -0,0 +1,54 @@ +use std::fmt::{Display, Formatter}; + +use serde::Serialize; + +use crate::systems::cmd::errors::CmdRenderError; + +pub trait JVResultRenderer<Data> +where + Data: Serialize, +{ + fn render( + data: &Data, + ) -> impl Future<Output = Result<JVRenderResult, CmdRenderError>> + Send + Sync; +} + +#[derive(Default, Debug, PartialEq)] +pub struct JVRenderResult { + render_text: String, +} + +impl Display for JVRenderResult { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}\n", self.render_text.trim()) + } +} + +impl JVRenderResult { + pub fn print(&mut self, text: &str) { + self.render_text.push_str(text); + } + + pub fn println(&mut self, text: &str) { + self.render_text.push_str(text); + self.render_text.push('\n'); + } + + pub fn clear(&mut self) { + self.render_text.clear(); + } +} + +#[macro_export] +macro_rules! r_print { + ($result:expr, $($arg:tt)*) => { + $result.print(&format!($($arg)*)); + }; +} + +#[macro_export] +macro_rules! r_println { + ($result:expr, $($arg:tt)*) => { + $result.println(&format!($($arg)*)); + }; +} |
