diff options
| author | 魏曹先生 <1992414357@qq.com> | 2026-01-22 14:24:50 +0800 |
|---|---|---|
| committer | 魏曹先生 <1992414357@qq.com> | 2026-01-22 14:24:50 +0800 |
| commit | 47bf9b75f6dd8c2d3c3f1fb947a16e0e055f49cf (patch) | |
| tree | c767e331e68134e19caeeb03269525be7b4fe6e1 | |
| parent | 53c26d656f975f93319dd432e409c1ea740ce06d (diff) | |
Add renderer system and implement status command
| -rw-r--r-- | .gitignore | 1 | ||||
| -rw-r--r-- | Registry.toml | 24 | ||||
| -rw-r--r-- | build.rs | 120 | ||||
| -rw-r--r-- | src/bin/jvn.rs | 10 | ||||
| -rw-r--r-- | src/cmd.rs | 1 | ||||
| -rw-r--r-- | src/cmd/cmd_system.rs | 29 | ||||
| -rw-r--r-- | src/cmd/cmds/status.rs | 105 | ||||
| -rw-r--r-- | src/cmd/cmds/template.rs | 2 | ||||
| -rw-r--r-- | src/cmd/errors.rs | 32 | ||||
| -rw-r--r-- | src/cmd/processer.rs | 3 | ||||
| -rw-r--r-- | src/cmd/renderers.rs | 1 | ||||
| -rw-r--r-- | src/cmd/renderers/json_renderer.rs | 44 | ||||
| -rw-r--r-- | src/utils.rs | 1 | ||||
| -rw-r--r-- | src/utils/workspace_reader.rs | 256 | ||||
| -rw-r--r-- | templates/_registry.rs.template | 12 | ||||
| -rw-r--r-- | templates/renderer_list.txt | 16 |
16 files changed, 621 insertions, 36 deletions
@@ -24,3 +24,4 @@ /src/data/compile_info.rs /setup/windows/setup_jv_cli.iss /src/cmd/cmds/_registry.rs +/src/cmd/renderers/renderer_list.txt diff --git a/Registry.toml b/Registry.toml index 48c1d3d..f3f6fdd 100644 --- a/Registry.toml +++ b/Registry.toml @@ -1,3 +1,25 @@ -[status] +################ +### Commands ### +################ + +[cmd.status] node = "status" type = "cmd::cmds::status::JVStatusCommand" + +################# +### Renderers ### +################# + +# Default Renderer +[renderer.default] +name = "default" +type = "Renderer" + +# Json Renderer +[renderer.json] +name = "json" +type = "crate::cmd::renderers::json_renderer::JVResultJsonRenderer" + +[renderer.json_pretty] +name = "json-pretty" +type = "crate::cmd::renderers::json_renderer::JVResultPrettyJsonRenderer" @@ -10,6 +10,10 @@ const SETUP_JV_CLI_ISS_TEMPLATE: &str = "./templates/setup_jv_cli.iss"; const REGISTRY_RS: &str = "./src/cmd/cmds/_registry.rs"; const REGISTRY_RS_TEMPLATE: &str = "./templates/_registry.rs.template"; + +const RENDERER_LIST_TEMPLATE: &str = "./templates/renderer_list.txt"; +const RENDERER_LIST: &str = "./src/cmd/renderers/renderer_list.txt"; + const REGISTRY_TOML: &str = "./Registry.toml"; fn main() { @@ -30,10 +34,15 @@ fn main() { std::process::exit(1); } - if let Err(e) = generate_registry_file(&repo_root) { + if let Err(e) = generate_cmd_registry_file(&repo_root) { eprintln!("Failed to generate registry file: {}", e); std::process::exit(1); } + + if let Err(e) = generate_renderer_list_file(&repo_root) { + eprintln!("Failed to generate renderer list: {}", e); + std::process::exit(1); + } } /// Generate Inno Setup installer script (Windows only) @@ -223,7 +232,7 @@ fn get_git_commit() -> Result<String, Box<dyn std::error::Error>> { } /// Generate registry file from Registry.toml configuration -fn generate_registry_file(repo_root: &PathBuf) -> Result<(), Box<dyn std::error::Error>> { +fn generate_cmd_registry_file(repo_root: &PathBuf) -> Result<(), Box<dyn std::error::Error>> { let template_path = repo_root.join(REGISTRY_RS_TEMPLATE); let output_path = repo_root.join(REGISTRY_RS); let config_path = repo_root.join(REGISTRY_TOML); @@ -240,15 +249,19 @@ fn generate_registry_file(repo_root: &PathBuf) -> Result<(), Box<dyn std::error: let mut nodes = Vec::new(); if let Some(table) = config.as_table() { - for (key, value) in table { - if let Some(cmd_table) = value.as_table() { - if let (Some(node), Some(cmd_type)) = ( - cmd_table.get("node").and_then(|v| v.as_str()), - cmd_table.get("type").and_then(|v| v.as_str()), - ) { - let n = node.replace(".", " "); - nodes.push(n.clone()); - commands.push((key.to_string(), n, cmd_type.to_string())); + if let Some(cmd_table) = table.get("cmd") { + if let Some(cmd_table) = cmd_table.as_table() { + for (key, cmd_value) in cmd_table { + if let Some(cmd_config) = cmd_value.as_table() { + if let (Some(node), Some(cmd_type)) = ( + cmd_config.get("node").and_then(|v| v.as_str()), + cmd_config.get("type").and_then(|v| v.as_str()), + ) { + let n = node.replace(".", " "); + nodes.push(n.clone()); + commands.push((key.to_string(), n, cmd_type.to_string())); + } + } } } } @@ -316,3 +329,88 @@ fn generate_registry_file(repo_root: &PathBuf) -> Result<(), Box<dyn std::error: println!("Generated registry file with {} commands", commands.len()); Ok(()) } + +/// Generate renderer list file from Registry.toml configuration +fn generate_renderer_list_file(repo_root: &PathBuf) -> Result<(), Box<dyn std::error::Error>> { + let template_path = repo_root.join(RENDERER_LIST_TEMPLATE); + let output_path = repo_root.join(RENDERER_LIST); + let config_path = repo_root.join(REGISTRY_TOML); + + // Read the template + let template = std::fs::read_to_string(&template_path)?; + + // Read and parse the TOML configuration + let config_content = std::fs::read_to_string(&config_path)?; + let config: toml::Value = toml::from_str(&config_content)?; + + // Collect all renderer configurations + let mut renderers = Vec::new(); + + if let Some(table) = config.as_table() { + if let Some(renderer_table) = table.get("renderer") { + if let Some(renderer_table) = renderer_table.as_table() { + for (_, renderer_value) in renderer_table { + if let Some(renderer_config) = renderer_value.as_table() { + if let (Some(name), Some(renderer_type)) = ( + renderer_config.get("name").and_then(|v| v.as_str()), + renderer_config.get("type").and_then(|v| v.as_str()), + ) { + renderers.push((name.to_string(), renderer_type.to_string())); + } + } + } + } + } + } + + // Extract the template section from the template content + const MATCH_MARKER: &str = "// MATCH"; + const TEMPLATE_START: &str = "// -- TEMPLATE START --"; + const TEMPLATE_END: &str = "// -- TEMPLATE END --"; + + let template_start_index = template + .find(TEMPLATE_START) + .ok_or("Template start marker not found")?; + let template_end_index = template + .find(TEMPLATE_END) + .ok_or("Template end marker not found")?; + + let template_slice = &template[template_start_index..template_end_index + TEMPLATE_END.len()]; + let renderer_template = template_slice + .trim_start_matches(TEMPLATE_START) + .trim_end_matches(TEMPLATE_END) + .trim_matches('\n'); + + // Generate the match arms for each renderer + let match_arms: String = renderers + .iter() + .map(|(name, renderer_type)| { + renderer_template + .replace("<<NAME>>", name) + .replace("<<TYPE>>", renderer_type) + .trim_matches('\n') + .to_string() + }) + .collect::<Vec<String>>() + .join("\n"); + + // Replace the template section with the generated match arms + let final_content = template + .replace(renderer_template, "") + .replace(TEMPLATE_START, "") + .replace(TEMPLATE_END, "") + .replace(MATCH_MARKER, &match_arms) + .lines() + .filter(|line| !line.trim().is_empty()) + .collect::<Vec<_>>() + .join("\n"); + + // Write the generated code + std::fs::write(output_path, final_content)?; + + println!( + "Generated renderer list file with {} renderers", + renderers.len() + ); + Ok(()) +} diff --git a/src/bin/jvn.rs b/src/bin/jvn.rs index 79a034b..9943ab4 100644 --- a/src/bin/jvn.rs +++ b/src/bin/jvn.rs @@ -53,13 +53,21 @@ async fn main() { #[cfg(windows)] colored::control::set_virtual_terminal(true).unwrap(); + let renderer_override = special_argument!(args, "--renderer").unwrap_or("default".to_string()); + let no_error_logs = special_flag!(args, "--no-error-logs"); let quiet = special_flag!(args, "--quiet") || special_flag!(args, "-q"); let help = special_flag!(args, "--help") || special_flag!(args, "-h"); let confirmed = special_flag!(args, "--confirm") || special_flag!(args, "-C"); // Process commands - let render_result = match jv_cmd_process(args, JVCommandContext { help, confirmed }).await { + let render_result = match jv_cmd_process( + args, + JVCommandContext { help, confirmed }, + renderer_override, + ) + .await + { Ok(result) => result, Err(e) => { if !no_error_logs { @@ -3,3 +3,4 @@ pub mod cmds; pub mod errors; pub mod processer; pub mod renderer; +pub mod renderers; diff --git a/src/cmd/cmd_system.rs b/src/cmd/cmd_system.rs index 3edd5cc..229c7f0 100644 --- a/src/cmd/cmd_system.rs +++ b/src/cmd/cmd_system.rs @@ -2,7 +2,7 @@ use serde::Serialize; use crate::{ cmd::{ - errors::{CmdExecuteError, CmdPrepareError, CmdProcessError}, + errors::{CmdExecuteError, CmdPrepareError, CmdProcessError, CmdRenderError}, renderer::{JVRenderResult, JVResultRenderer}, }, r_println, @@ -24,11 +24,30 @@ where /// Get help string for the command fn get_help_str() -> String; + /// Process the command with a specified renderer, performing any necessary post-execution processing + #[rustfmt::skip] + 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!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/src/cmd/renderers/renderer_list.txt" + )) + } + } + /// performing any necessary post-execution processing fn process( args: Vec<String>, ctx: JVCommandContext, - ) -> impl Future<Output = Result<JVRenderResult, CmdProcessError>> + Send + Sync + ) -> impl Future<Output = Result<JVRenderResult, CmdProcessError>> + Send where Self: Sync, { @@ -40,7 +59,7 @@ where fn process_with_renderer<R: JVResultRenderer<Output> + Send + Sync>( args: Vec<String>, ctx: JVCommandContext, - ) -> impl Future<Output = Result<JVRenderResult, CmdProcessError>> + Send + Sync + ) -> impl Future<Output = Result<JVRenderResult, CmdProcessError>> + Send where Self: Sync, { @@ -77,9 +96,9 @@ where fn prepare( args: Argument, ctx: JVCommandContext, - ) -> impl Future<Output = Result<Input, CmdPrepareError>> + Send + Sync; + ) -> impl Future<Output = Result<Input, CmdPrepareError>> + Send; /// Run the command phase, /// returning an output structure, waiting for rendering - fn exec(args: Input) -> impl Future<Output = Result<Output, CmdExecuteError>> + Send + Sync; + fn exec(input: Input) -> impl Future<Output = Result<Output, CmdExecuteError>> + Send; } diff --git a/src/cmd/cmds/status.rs b/src/cmd/cmds/status.rs index bbc78e8..d73a28a 100644 --- a/src/cmd/cmds/status.rs +++ b/src/cmd/cmds/status.rs @@ -1,10 +1,28 @@ +use std::{ + collections::{HashMap, HashSet}, + path::PathBuf, + time::SystemTime, +}; + use clap::Parser; +use just_enough_vcs::vcs::data::{ + local::workspace_analyzer::{ + CreatedRelativePathBuf, FromRelativePathBuf, LostRelativePathBuf, ModifiedRelativePathBuf, + ToRelativePathBuf, + }, + member::MemberId, + sheet::SheetName, + vault::virtual_file::VirtualFileId, +}; use serde::Serialize; -use crate::cmd::{ - cmd_system::{JVCommand, JVCommandContext}, - errors::{CmdExecuteError, CmdPrepareError, CmdRenderError}, - renderer::{JVRenderResult, JVResultRenderer}, +use crate::{ + cmd::{ + cmd_system::{JVCommand, JVCommandContext}, + errors::{CmdExecuteError, CmdPrepareError, CmdRenderError}, + renderer::{JVRenderResult, JVResultRenderer}, + }, + utils::workspace_reader::LocalWorkspaceReader, }; pub struct JVStatusCommand; @@ -12,23 +30,82 @@ pub struct JVStatusCommand; #[derive(Parser, Debug)] pub struct JVStatusArgument; -pub struct JVStatusInput; - #[derive(Serialize)] -pub struct JVStatusOutput; +pub struct JVStatusResult { + pub current_account: MemberId, + pub current_sheet: SheetName, + pub moved: HashMap<VirtualFileId, (FromRelativePathBuf, ToRelativePathBuf)>, + pub created: HashSet<CreatedRelativePathBuf>, + pub lost: HashSet<LostRelativePathBuf>, + pub erased: HashSet<PathBuf>, + pub modified: HashSet<ModifiedRelativePathBuf>, + pub update_time: SystemTime, + pub now_time: SystemTime, +} + +impl Default for JVStatusResult { + fn default() -> Self { + Self { + current_account: MemberId::default(), + current_sheet: SheetName::default(), + moved: HashMap::default(), + created: HashSet::default(), + lost: HashSet::default(), + erased: HashSet::default(), + modified: HashSet::default(), + update_time: SystemTime::now(), + now_time: SystemTime::now(), + } + } +} -impl JVCommand<JVStatusArgument, JVStatusInput, JVStatusOutput, JVStatusRenderer> +impl JVCommand<JVStatusArgument, JVStatusResult, JVStatusResult, JVStatusRenderer> for JVStatusCommand { async fn prepare( _args: JVStatusArgument, _ctx: JVCommandContext, - ) -> Result<JVStatusInput, CmdPrepareError> { - Ok(JVStatusInput) + ) -> Result<JVStatusResult, CmdPrepareError> { + // Initialize a reader for the local workspace and a default result structure + let mut reader = LocalWorkspaceReader::default(); + let mut input = JVStatusResult::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?; + + // 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 + input.current_account = account; + input.current_sheet = sheet_name; + input.moved = analyzed.moved; + input.created = analyzed.created; + input.lost = analyzed.lost; + input.erased = analyzed.erased; + input.modified = analyzed.modified; + input.update_time = update_time; + input.now_time = now_time; + + Ok(input) } - async fn exec(args: JVStatusInput) -> Result<JVStatusOutput, CmdExecuteError> { - todo!() + async fn exec(input: JVStatusResult) -> Result<JVStatusResult, CmdExecuteError> { + Ok(input) // Analyze command, no needs execute } fn get_help_str() -> String { @@ -38,8 +115,8 @@ impl JVCommand<JVStatusArgument, JVStatusInput, JVStatusOutput, JVStatusRenderer pub struct JVStatusRenderer; -impl JVResultRenderer<JVStatusOutput> for JVStatusRenderer { - async fn render(data: &JVStatusOutput) -> Result<JVRenderResult, CmdRenderError> { +impl JVResultRenderer<JVStatusResult> for JVStatusRenderer { + async fn render(data: &JVStatusResult) -> Result<JVRenderResult, CmdRenderError> { todo!() } } diff --git a/src/cmd/cmds/template.rs b/src/cmd/cmds/template.rs index 8874121..1c56c29 100644 --- a/src/cmd/cmds/template.rs +++ b/src/cmd/cmds/template.rs @@ -27,7 +27,7 @@ impl JVCommand<JVUnknownArgument, JVUnknownInput, JVUnknownOutput, JVStatusRende todo!() } - async fn exec(_args: JVUnknownInput) -> Result<JVUnknownOutput, CmdExecuteError> { + async fn exec(_input: JVUnknownInput) -> Result<JVUnknownOutput, CmdExecuteError> { todo!() } diff --git a/src/cmd/errors.rs b/src/cmd/errors.rs index e1cf835..358d15a 100644 --- a/src/cmd/errors.rs +++ b/src/cmd/errors.rs @@ -1,3 +1,5 @@ +use just_enough_vcs::vcs::data::{member::MemberId, sheet::SheetName}; + #[derive(thiserror::Error, Debug)] pub enum CmdPrepareError { #[error("IO error: {0}")] @@ -5,6 +7,30 @@ pub enum CmdPrepareError { #[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 { @@ -44,6 +70,12 @@ pub enum CmdRenderError { #[error("{0}")] Error(String), + + #[error("Serialize failed, {0}")] + SerializeFailed(String), + + #[error("Renderer `{0}` not found")] + RendererNotFound(String), } impl CmdRenderError { diff --git a/src/cmd/processer.rs b/src/cmd/processer.rs index bc84b7d..f5fc2a6 100644 --- a/src/cmd/processer.rs +++ b/src/cmd/processer.rs @@ -6,6 +6,7 @@ use crate::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(" "); @@ -25,7 +26,7 @@ pub async fn jv_cmd_process( 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).await; + return jv_cmd_process_node(matched_prefix, trimmed_args, ctx, renderer_override).await; } _ => { // Multiple matching nodes found diff --git a/src/cmd/renderers.rs b/src/cmd/renderers.rs new file mode 100644 index 0000000..91fa88e --- /dev/null +++ b/src/cmd/renderers.rs @@ -0,0 +1 @@ +pub mod json_renderer; diff --git a/src/cmd/renderers/json_renderer.rs b/src/cmd/renderers/json_renderer.rs new file mode 100644 index 0000000..14c1f81 --- /dev/null +++ b/src/cmd/renderers/json_renderer.rs @@ -0,0 +1,44 @@ +use serde::Serialize; +use serde_json; + +use crate::{ + cmd::{ + errors::CmdRenderError, + renderer::{JVRenderResult, JVResultRenderer}, + }, + r_print, +}; + +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) + } +} + +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/utils.rs b/src/utils.rs index 1e63cbe..8c2e306 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -6,3 +6,4 @@ pub mod input; pub mod logger; pub mod push_version; pub mod socket_addr_helper; +pub mod workspace_reader; diff --git a/src/utils/workspace_reader.rs b/src/utils/workspace_reader.rs new file mode 100644 index 0000000..575ec64 --- /dev/null +++ b/src/utils/workspace_reader.rs @@ -0,0 +1,256 @@ +use std::{collections::HashMap, net::SocketAddr, path::PathBuf}; + +use just_enough_vcs::{ + utils::cfg_file::config::ConfigFile, + vcs::{ + data::{ + local::{ + LocalWorkspace, + latest_file_data::LatestFileData, + latest_info::LatestInfo, + local_sheet::{LocalSheet, LocalSheetData}, + workspace_analyzer::{AnalyzeResult, AnalyzeResultPure}, + workspace_config::LocalConfig, + }, + member::MemberId, + sheet::{SheetData, SheetName}, + vault::vault_config::VaultUuid, + }, + env::current_local_path, + }, +}; + +use crate::cmd::errors::CmdPrepareError; + +/// Temporarily enter a directory to execute a block of code, then return to the original directory +macro_rules! entry_dir { + ($path:expr, $block:block) => {{ + let original_dir = std::env::current_dir().unwrap(); + std::env::set_current_dir($path).unwrap(); + let result = $block; + std::env::set_current_dir(original_dir).unwrap(); + result + }}; +} + +#[derive(Default)] +pub struct LocalWorkspaceReader { + cached_sheet: HashMap<SheetName, SheetData>, + current_dir: Option<PathBuf>, + workspace_dir: Option<PathBuf>, + latest_file_data: HashMap<MemberId, LatestFileData>, + latest_info: Option<LatestInfo>, + local_config: Option<LocalConfig>, + local_sheet_data: HashMap<(MemberId, SheetName), LocalSheetData>, + local_workspace: Option<LocalWorkspace>, +} + +impl LocalWorkspaceReader { + // Get the current directory + pub fn current_dir(&mut self) -> Result<&PathBuf, CmdPrepareError> { + if self.current_dir.is_none() { + let current_dir = std::env::current_dir()?; + self.current_dir = Some(current_dir); + } + Ok(self.current_dir.as_ref().unwrap()) + } + + // Get the workspace directory + pub fn workspace_dir(&mut self) -> Result<&PathBuf, CmdPrepareError> { + if self.workspace_dir.is_none() { + let workspace_dir = current_local_path(); + self.workspace_dir = workspace_dir; + return match &self.workspace_dir { + Some(d) => Ok(d), + None => Err(CmdPrepareError::LocalWorkspaceNotFound), + }; + } + Ok(self.workspace_dir.as_ref().unwrap()) + } + + // Get the local configuration + pub async fn local_config(&mut self) -> Result<&LocalConfig, CmdPrepareError> { + if self.local_config.is_none() { + let workspace_dir = self.workspace_dir()?; + let local_config = entry_dir!(workspace_dir, { + LocalConfig::read() + .await + .map_err(|_| CmdPrepareError::LocalConfigNotFound)? + }); + self.local_config = Some(local_config) + } + Ok(self.local_config.as_ref().unwrap()) + } + + // Current account + pub async fn current_account(&mut self) -> Result<MemberId, CmdPrepareError> { + Ok(self.local_config().await?.current_account()) + } + + // Whether it is in host mode + pub async fn is_host_mode(&mut self) -> Result<bool, CmdPrepareError> { + Ok(self.local_config().await?.is_host_mode()) + } + + // Whether the workspace is stained + pub async fn workspace_stained(&mut self) -> Result<bool, CmdPrepareError> { + Ok(self.local_config().await?.stained()) + } + + // Stain UUID + pub async fn stained_uuid(&mut self) -> Result<Option<VaultUuid>, CmdPrepareError> { + Ok(self.local_config().await?.stained_uuid()) + } + + // Upstream address + pub async fn upstream_addr(&mut self) -> Result<SocketAddr, CmdPrepareError> { + Ok(self.local_config().await?.upstream_addr()) + } + + // Currently used sheet + pub async fn sheet_in_use(&mut self) -> Result<&Option<SheetName>, CmdPrepareError> { + Ok(self.local_config().await?.sheet_in_use()) + } + + // Get the sheet name in use, or error if none + pub async fn sheet_name(&mut self) -> Result<SheetName, CmdPrepareError> { + match self.local_config().await?.sheet_in_use() { + Some(name) => Ok(name.clone()), + None => Err(CmdPrepareError::NoSheetInUse), + } + } + + // Current draft folder + pub async fn current_draft_folder(&mut self) -> Result<Option<PathBuf>, CmdPrepareError> { + Ok(self.local_config().await?.current_draft_folder()) + } + + // Get the local workspace + pub async fn local_workspace(&mut self) -> Result<&LocalWorkspace, CmdPrepareError> { + if self.local_workspace.is_none() { + let workspace_dir = self.workspace_dir()?.clone(); + let local_config = self.local_config().await?.clone(); + let Some(local_workspace) = entry_dir!(&workspace_dir, { + LocalWorkspace::init_current_dir(local_config) + }) else { + return Err(CmdPrepareError::LocalWorkspaceNotFound); + }; + self.local_workspace = Some(local_workspace); + } + Ok(self.local_workspace.as_ref().unwrap()) + } + + // Get the latest information + pub async fn latest_info(&mut self) -> Result<&LatestInfo, CmdPrepareError> { + if self.latest_info.is_none() { + let local_dir = self.workspace_dir()?.clone(); + let local_config = self.local_config().await?.clone(); + let latest_info = entry_dir!(&local_dir, { + match LatestInfo::read_from(LatestInfo::latest_info_path( + &local_dir, + &local_config.current_account(), + )) + .await + { + Ok(info) => info, + Err(_) => { + return Err(CmdPrepareError::LatestInfoNotFound); + } + } + }); + self.latest_info = Some(latest_info); + } + Ok(self.latest_info.as_ref().unwrap()) + } + + // Get the latest file data + pub async fn latest_file_data( + &mut self, + account: &MemberId, + ) -> Result<&LatestFileData, CmdPrepareError> { + if !self.latest_file_data.contains_key(account) { + let local_dir = self.workspace_dir()?; + let latest_file_data_path = + match entry_dir!(&local_dir, { LatestFileData::data_path(account) }) { + Ok(p) => p, + Err(_) => return Err(CmdPrepareError::LatestFileDataNotExist(account.clone())), + }; + let latest_file_data = LatestFileData::read_from(&latest_file_data_path).await?; + self.latest_file_data + .insert(account.clone(), latest_file_data); + } + + Ok(self.latest_file_data.get(account).unwrap()) + } + + // Get the cached sheet + pub async fn cached_sheet( + &mut self, + sheet_name: &SheetName, + ) -> Result<&SheetData, CmdPrepareError> { + if !self.cached_sheet.contains_key(sheet_name) { + let workspace_dir = self.workspace_dir()?; + let cached_sheet = entry_dir!(&workspace_dir, { + match just_enough_vcs::vcs::data::local::cached_sheet::CachedSheet::cached_sheet_data(sheet_name).await { + Ok(data) => data, + Err(_) => return Err(CmdPrepareError::CachedSheetNotFound(sheet_name.clone())), + } + }); + self.cached_sheet.insert(sheet_name.clone(), cached_sheet); + } + + Ok(self.cached_sheet.get(sheet_name).unwrap()) + } + + // Get the local sheet data + pub async fn local_sheet_data( + &mut self, + account: &MemberId, + sheet_name: &SheetName, + ) -> Result<&LocalSheetData, CmdPrepareError> { + let key = (account.clone(), sheet_name.clone()); + if !self.local_sheet_data.contains_key(&key) { + let workspace_dir = self.workspace_dir()?.clone(); + let local_workspace = self.local_workspace().await?; + let path = entry_dir!(&workspace_dir, { + local_workspace.local_sheet_path(account, sheet_name) + }); + let local_sheet_data = match LocalSheetData::read_from(path).await { + Ok(data) => data, + Err(_) => { + return Err(CmdPrepareError::LocalSheetNotFound( + account.clone(), + sheet_name.clone(), + )); + } + }; + self.local_sheet_data.insert(key.clone(), local_sheet_data); + } + + Ok(self.local_sheet_data.get(&key).unwrap()) + } + + // Clone and get the local sheet + pub async fn local_sheet_cloned( + &mut self, + account: &MemberId, + sheet_name: &SheetName, + ) -> Result<LocalSheet<'_>, CmdPrepareError> { + let local_sheet_data = self.local_sheet_data(account, sheet_name).await?.clone(); + Ok(LocalSheet::new( + self.local_workspace().await?, + account.clone(), + sheet_name.clone(), + local_sheet_data, + )) + } + + // Analyze local status + pub async fn analyze_local_status(&mut self) -> Result<AnalyzeResultPure, CmdPrepareError> { + let Ok(analyzed) = AnalyzeResult::analyze_local_status(self.local_workspace().await?).await + else { + return Err(CmdPrepareError::LocalStatusAnalyzeFailed); + }; + Ok(analyzed.into()) + } +} diff --git a/templates/_registry.rs.template b/templates/_registry.rs.template index fd6f779..cac3c8e 100644 --- a/templates/_registry.rs.template +++ b/templates/_registry.rs.template @@ -6,13 +6,21 @@ use crate::cmd::errors::CmdProcessError; pub async fn jv_cmd_process_node( node: &str, args: Vec<String>, - ctx: JVCommandContext + ctx: JVCommandContext, + renderer_override: String ) -> Result<crate::cmd::renderer::JVRenderResult, crate::cmd::errors::CmdProcessError> { match node { // PROCESS // -- TEMPLATE START -- // Command `<<KEY>>` - "<<NODE_NAME>>" => return crate::<<COMMAND_TYPE>>::process(args, ctx).await, + "<<NODE_NAME>>" => { + return crate::<<COMMAND_TYPE>>::process_with_renderer_flag( + args, + ctx, + renderer_override + ) + .await; + } // -- TEMPLATE END -- _ => {} } diff --git a/templates/renderer_list.txt b/templates/renderer_list.txt new file mode 100644 index 0000000..cfc8105 --- /dev/null +++ b/templates/renderer_list.txt @@ -0,0 +1,16 @@ +match renderer_str { +// MATCH +// -- TEMPLATE START -- + "<<NAME>>" => { + Self::process_with_renderer::< + <<TYPE>>, + >(args, ctx) + .await + } +// -- TEMPLATE END -- + _ => { + return Err(CmdProcessError::Render(CmdRenderError::RendererNotFound( + renderer, + ))); + } +} |
