summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--Registry.toml24
-rw-r--r--build.rs120
-rw-r--r--src/bin/jvn.rs10
-rw-r--r--src/cmd.rs1
-rw-r--r--src/cmd/cmd_system.rs29
-rw-r--r--src/cmd/cmds/status.rs105
-rw-r--r--src/cmd/cmds/template.rs2
-rw-r--r--src/cmd/errors.rs32
-rw-r--r--src/cmd/processer.rs3
-rw-r--r--src/cmd/renderers.rs1
-rw-r--r--src/cmd/renderers/json_renderer.rs44
-rw-r--r--src/utils.rs1
-rw-r--r--src/utils/workspace_reader.rs256
-rw-r--r--templates/_registry.rs.template12
-rw-r--r--templates/renderer_list.txt16
16 files changed, 621 insertions, 36 deletions
diff --git a/.gitignore b/.gitignore
index 6630493..07a06bf 100644
--- a/.gitignore
+++ b/.gitignore
@@ -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"
diff --git a/build.rs b/build.rs
index 73c6cbd..7763be2 100644
--- a/build.rs
+++ b/build.rs
@@ -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 {
diff --git a/src/cmd.rs b/src/cmd.rs
index 5bab2c1..2c9cac6 100644
--- a/src/cmd.rs
+++ b/src/cmd.rs
@@ -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,
+ )));
+ }
+}