summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
author魏曹先生 <1992414357@qq.com>2026-01-22 14:24:50 +0800
committer魏曹先生 <1992414357@qq.com>2026-01-22 14:24:50 +0800
commit47bf9b75f6dd8c2d3c3f1fb947a16e0e055f49cf (patch)
treec767e331e68134e19caeeb03269525be7b4fe6e1 /src
parent53c26d656f975f93319dd432e409c1ea740ce06d (diff)
Add renderer system and implement status command
Diffstat (limited to 'src')
-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
11 files changed, 462 insertions, 22 deletions
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())
+ }
+}