summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/cmds/status.rs98
-rw-r--r--src/inputs.rs1
-rw-r--r--src/inputs/status.rs43
-rw-r--r--src/lib.rs2
-rw-r--r--src/outputs/status.rs46
-rw-r--r--src/renderers/status.rs252
-rw-r--r--src/systems/cmd/renderer.rs4
-rw-r--r--src/utils/display.rs24
-rw-r--r--src/utils/workspace_reader.rs121
9 files changed, 540 insertions, 51 deletions
diff --git a/src/cmds/status.rs b/src/cmds/status.rs
index 8279e50..54e033e 100644
--- a/src/cmds/status.rs
+++ b/src/cmds/status.rs
@@ -1,8 +1,11 @@
use std::time::SystemTime;
+use just_enough_vcs::vcs::constants::VAULT_HOST_NAME;
+
use crate::{
arguments::status::JVStatusArgument,
- outputs::status::JVStatusResult,
+ inputs::status::JVStatusInput,
+ outputs::status::{JVStatusOutput, JVStatusWrongModifyReason},
renderers::status::JVStatusRenderer,
systems::cmd::{
cmd_system::{JVCommand, JVCommandContext},
@@ -13,16 +16,16 @@ use crate::{
pub struct JVStatusCommand;
-impl JVCommand<JVStatusArgument, JVStatusResult, JVStatusResult, JVStatusRenderer>
+impl JVCommand<JVStatusArgument, JVStatusInput, JVStatusOutput, JVStatusRenderer>
for JVStatusCommand
{
async fn prepare(
_args: JVStatusArgument,
_ctx: JVCommandContext,
- ) -> Result<JVStatusResult, CmdPrepareError> {
+ ) -> Result<JVStatusInput, CmdPrepareError> {
// Initialize a reader for the local workspace and a default result structure
let mut reader = LocalWorkspaceReader::default();
- let mut input = JVStatusResult::default();
+ let mut input = JVStatusInput::default();
// Analyze the current status of the local workspace
// (detects changes like created, modified, moved, etc.)
@@ -34,6 +37,16 @@ impl JVCommand<JVStatusArgument, JVStatusResult, JVStatusResult, JVStatusRendere
// 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()
@@ -47,19 +60,82 @@ impl JVCommand<JVStatusArgument, JVStatusResult, JVStatusResult, JVStatusRendere
// 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.is_host_mode = is_host_mode;
+ input.in_ref_sheet = is_ref_sheet;
+ input.analyzed_result = analyzed;
input.update_time = update_time;
input.now_time = now_time;
+ input.latest_file_data = latest_file_data;
Ok(input)
}
- async fn exec(input: JVStatusResult) -> Result<JVStatusResult, CmdExecuteError> {
- Ok(input) // Analyze command, no needs execute
+ async fn exec(mut input: JVStatusInput) -> Result<JVStatusOutput, CmdExecuteError> {
+ let latest_file_data = &input.latest_file_data;
+
+ // Calculate whether modifications are correc
+ let modified = &input.analyzed_result.modified;
+ for item in modified {
+ // Get mapping
+ let Ok(mapping) = input.local_sheet_data.mapping_data(&item) 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 {
+ input.wrong_modified_items.insert(
+ item.clone(),
+ JVStatusWrongModifyReason::BaseVersionMismatch {
+ base_version,
+ latest_version,
+ },
+ );
+ continue;
+ }
+ }
+
+ // Check edit right (only check when current is not HOST)
+ if input.current_account != VAULT_HOST_NAME {
+ let holder = latest_file_data.file_holder(mapping.mapping_vfid());
+ if holder.is_none() {
+ input
+ .wrong_modified_items
+ .insert(item.clone(), JVStatusWrongModifyReason::NoHolder);
+ continue;
+ }
+
+ let holder = holder.cloned().unwrap();
+ if &input.current_account != &holder {
+ input.wrong_modified_items.insert(
+ item.clone(),
+ JVStatusWrongModifyReason::ModifiedButNotHeld { holder: holder },
+ );
+ }
+ }
+ }
+
+ let output = JVStatusOutput {
+ current_account: input.current_account,
+ current_sheet: input.current_sheet,
+ is_host_mode: input.is_host_mode,
+ in_ref_sheet: input.in_ref_sheet,
+ analyzed_result: input.analyzed_result,
+ wrong_modified_items: input.wrong_modified_items,
+ update_time: input.update_time,
+ now_time: input.now_time,
+ };
+
+ Ok(output)
}
fn get_help_str() -> String {
diff --git a/src/inputs.rs b/src/inputs.rs
index e69de29..822c729 100644
--- a/src/inputs.rs
+++ b/src/inputs.rs
@@ -0,0 +1 @@
+pub mod status;
diff --git a/src/inputs/status.rs b/src/inputs/status.rs
new file mode 100644
index 0000000..eb3b504
--- /dev/null
+++ b/src/inputs/status.rs
@@ -0,0 +1,43 @@
+use std::{collections::HashMap, time::SystemTime};
+
+use just_enough_vcs::vcs::data::{
+ local::{
+ latest_file_data::LatestFileData,
+ local_sheet::LocalSheetData,
+ workspace_analyzer::{AnalyzeResultPure, ModifiedRelativePathBuf},
+ },
+ member::MemberId,
+ sheet::SheetName,
+};
+
+use crate::outputs::status::JVStatusWrongModifyReason;
+
+pub struct JVStatusInput {
+ pub current_account: MemberId,
+ pub current_sheet: SheetName,
+ pub is_host_mode: bool,
+ pub in_ref_sheet: bool,
+ pub analyzed_result: AnalyzeResultPure,
+ pub latest_file_data: LatestFileData,
+ pub local_sheet_data: LocalSheetData,
+ pub wrong_modified_items: HashMap<ModifiedRelativePathBuf, JVStatusWrongModifyReason>,
+ pub update_time: SystemTime,
+ pub now_time: SystemTime,
+}
+
+impl Default for JVStatusInput {
+ fn default() -> Self {
+ Self {
+ current_account: MemberId::default(),
+ current_sheet: SheetName::default(),
+ is_host_mode: false,
+ in_ref_sheet: false,
+ analyzed_result: AnalyzeResultPure::default(),
+ latest_file_data: LatestFileData::default(),
+ local_sheet_data: LocalSheetData::default(),
+ wrong_modified_items: HashMap::new(),
+ update_time: SystemTime::now(),
+ now_time: SystemTime::now(),
+ }
+ }
+}
diff --git a/src/lib.rs b/src/lib.rs
index 2e44cbe..dc8ff05 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,3 +1,5 @@
+rust_i18n::i18n!("resources/locales/jvn", fallback = "en");
+
// --- LIBS ---
/// Utils
diff --git a/src/outputs/status.rs b/src/outputs/status.rs
index f9ce875..2b8d9c6 100644
--- a/src/outputs/status.rs
+++ b/src/outputs/status.rs
@@ -1,43 +1,45 @@
-use std::{
- collections::{HashMap, HashSet},
- path::PathBuf,
- time::SystemTime,
-};
+use std::{collections::HashMap, time::SystemTime};
use just_enough_vcs::vcs::data::{
- local::workspace_analyzer::{
- CreatedRelativePathBuf, FromRelativePathBuf, LostRelativePathBuf, ModifiedRelativePathBuf,
- ToRelativePathBuf,
- },
+ local::workspace_analyzer::{AnalyzeResultPure, ModifiedRelativePathBuf},
member::MemberId,
sheet::SheetName,
- vault::virtual_file::VirtualFileId,
};
use serde::Serialize;
#[derive(Serialize)]
-pub struct JVStatusResult {
+pub struct JVStatusOutput {
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 is_host_mode: bool,
+ pub in_ref_sheet: bool,
+ pub analyzed_result: AnalyzeResultPure,
+ pub wrong_modified_items: HashMap<ModifiedRelativePathBuf, JVStatusWrongModifyReason>,
pub update_time: SystemTime,
pub now_time: SystemTime,
}
-impl Default for JVStatusResult {
+#[derive(Serialize)]
+pub enum JVStatusWrongModifyReason {
+ BaseVersionMismatch {
+ base_version: String,
+ latest_version: String,
+ },
+ ModifiedButNotHeld {
+ holder: String,
+ },
+ NoHolder,
+}
+
+impl Default for JVStatusOutput {
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(),
+ is_host_mode: false,
+ in_ref_sheet: false,
+ analyzed_result: AnalyzeResultPure::default(),
+ wrong_modified_items: HashMap::new(),
update_time: SystemTime::now(),
now_time: SystemTime::now(),
}
diff --git a/src/renderers/status.rs b/src/renderers/status.rs
index 0bf1c5c..d51252e 100644
--- a/src/renderers/status.rs
+++ b/src/renderers/status.rs
@@ -1,18 +1,262 @@
+use rust_i18n::t;
+
use crate::{
- outputs::status::JVStatusResult,
+ outputs::status::{JVStatusOutput, JVStatusWrongModifyReason},
r_println,
systems::cmd::{
errors::CmdRenderError,
renderer::{JVRenderResult, JVResultRenderer},
},
+ utils::{
+ display::{SimpleTable, md},
+ env::auto_update_outdate,
+ },
};
pub struct JVStatusRenderer;
-impl JVResultRenderer<JVStatusResult> for JVStatusRenderer {
- async fn render(_data: &JVStatusResult) -> Result<JVRenderResult, CmdRenderError> {
+enum Mode {
+ StructuralChangesMode,
+ ContentChangesMode,
+ Clean,
+}
+
+impl JVResultRenderer<JVStatusOutput> for JVStatusRenderer {
+ async fn render(data: &JVStatusOutput) -> Result<JVRenderResult, CmdRenderError> {
let mut r = JVRenderResult::default();
- r_println!(r, "Nothing");
+
+ // Render Header
+ render_header(&mut r, data);
+
+ // Render Info and Mode
+ render_info_and_mode(&mut r, data);
+
+ // Render Hint
+ render_hint(&mut r, data);
+
Ok(r)
}
}
+
+fn render_header(r: &mut JVRenderResult, data: &JVStatusOutput) {
+ let account = &data.current_account;
+ let sheet = &data.current_sheet;
+ r_println!(
+ r,
+ "{}",
+ md(t!("status.header", account = account, sheet = sheet))
+ );
+}
+
+fn render_info_and_mode(r: &mut JVRenderResult, data: &JVStatusOutput) {
+ let mut info_erased = String::default();
+ let mut info_moved = String::default();
+ let mut info_lost = String::default();
+ let mut info_created = String::default();
+ let mut info_modified = String::default();
+
+ // Collect erased items
+ if !data.analyzed_result.erased.is_empty() {
+ info_erased.push_str(format!("{}\n", md(t!("status.info_display.erased.header"))).as_str());
+ for erased in data.analyzed_result.erased.iter() {
+ info_erased.push_str(
+ format!(
+ "{}\n",
+ md(t!(
+ "status.info_display.erased.item",
+ item = erased.display()
+ ))
+ )
+ .as_str(),
+ );
+ }
+ }
+
+ // Collect moved items
+ if !data.analyzed_result.moved.is_empty() {
+ let mut table = SimpleTable::new(vec![
+ format!("{}", md(t!("status.info_display.moved.header"))),
+ "".to_string(),
+ ]);
+ for (_, (from, to)) in data.analyzed_result.moved.iter() {
+ table.push_item(vec![
+ format!(
+ "{}",
+ md(t!("status.info_display.moved.left", left = from.display()))
+ ),
+ format!(
+ "{}",
+ md(t!("status.info_display.moved.right", right = to.display()))
+ ),
+ ]);
+ }
+ info_moved.push_str(table.to_string().as_str());
+ }
+
+ // Collect lost items
+ if !data.analyzed_result.lost.is_empty() {
+ info_lost.push_str(format!("{}\n", md(t!("status.info_display.lost.header"))).as_str());
+ for lost in data.analyzed_result.lost.iter() {
+ info_lost.push_str(
+ format!(
+ "{}\n",
+ md(t!("status.info_display.lost.item", item = lost.display()))
+ )
+ .as_str(),
+ );
+ }
+ }
+
+ // Collect created items
+ if !data.analyzed_result.created.is_empty() {
+ info_created
+ .push_str(format!("{}\n", md(t!("status.info_display.created.header"))).as_str());
+ for created in data.analyzed_result.created.iter() {
+ info_created.push_str(
+ format!(
+ "{}\n",
+ md(t!(
+ "status.info_display.created.item",
+ item = created.display()
+ ))
+ )
+ .as_str(),
+ );
+ }
+ }
+
+ // Collect modified items
+ if !data.analyzed_result.modified.is_empty() {
+ info_modified
+ .push_str(format!("{}\n", md(t!("status.info_display.modified.header"))).as_str());
+ for modified in data.analyzed_result.modified.iter() {
+ if let Some(reason) = data.wrong_modified_items.get(modified) {
+ let reason_str = match reason {
+ JVStatusWrongModifyReason::BaseVersionMismatch {
+ base_version,
+ latest_version,
+ } => md(t!(
+ "status.info_display.modified.reason.base_version_mismatch",
+ base_version = base_version,
+ latest_version = latest_version
+ )),
+ JVStatusWrongModifyReason::ModifiedButNotHeld { holder } => md(t!(
+ "status.info_display.modified.reason.modified_but_not_held",
+ holder = holder
+ )),
+ JVStatusWrongModifyReason::NoHolder => {
+ md(t!("status.info_display.modified.reason.no_holder"))
+ }
+ };
+ info_modified.push_str(
+ format!(
+ "{}\n",
+ md(t!(
+ "status.info_display.modified.item_wrong",
+ item = modified.display(),
+ reason = reason_str
+ ))
+ )
+ .as_str(),
+ );
+ continue;
+ }
+ info_modified.push_str(
+ format!(
+ "{}\n",
+ md(t!(
+ "status.info_display.modified.item",
+ item = modified.display()
+ ))
+ )
+ .as_str(),
+ );
+ }
+ }
+
+ let structural_info = vec![info_erased, info_moved, info_lost].join("\n");
+ let content_info = vec![info_created, info_modified].join("\n");
+
+ let mode = get_mode(data);
+ match mode {
+ Mode::StructuralChangesMode => {
+ r_println!(
+ r,
+ "{}",
+ md(t!("status.current_mode.structural", info = structural_info))
+ );
+ }
+ Mode::ContentChangesMode => {
+ r_println!(
+ r,
+ "{}",
+ md(t!("status.current_mode.content", info = content_info))
+ );
+ }
+ Mode::Clean => r_println!(r, "{}", md(t!("status.current_mode.clean"))),
+ }
+}
+
+fn render_hint(r: &mut JVRenderResult, data: &JVStatusOutput) {
+ // Outdate Hint
+ let update_time = &data.update_time;
+ let now_time = &data.now_time;
+ let duration_minutes: i64 = (now_time
+ .duration_since(*update_time)
+ .unwrap_or_default()
+ .as_secs()
+ / 60) as i64;
+ let outdate_minutes = auto_update_outdate();
+
+ // Outdated
+ if duration_minutes > outdate_minutes {
+ let hours = duration_minutes / 60;
+ let minutes = duration_minutes % 60;
+ let seconds = (now_time
+ .duration_since(*update_time)
+ .unwrap_or_default()
+ .as_secs()
+ % 60) as i64;
+
+ r_println!(
+ r,
+ "{}",
+ md(t!(
+ "status.hints.outdate",
+ h = hours,
+ m = minutes,
+ s = seconds
+ ))
+ );
+ }
+
+ let in_ref_sheet = &data.in_ref_sheet;
+ let is_host_mode = &data.is_host_mode;
+
+ // Readonly
+ if *in_ref_sheet && !is_host_mode {
+ r_println!(r, "{}", md(t!("status.hints.readonly")));
+ }
+
+ // Host
+ if *is_host_mode {
+ r_println!(r, "{}", md(t!("status.hints.host")));
+ }
+}
+
+fn get_mode(data: &JVStatusOutput) -> Mode {
+ let analyzed = &data.analyzed_result;
+
+ // If there are any lost, moved, or erased items, use structural changes mode
+ if !analyzed.moved.is_empty() || !analyzed.lost.is_empty() || !analyzed.erased.is_empty() {
+ Mode::StructuralChangesMode
+ }
+ // Otherwise, if there are any created or modified items, use content changes mode
+ else if !analyzed.created.is_empty() || !analyzed.modified.is_empty() {
+ Mode::ContentChangesMode
+ }
+ // Otherwise, it's clean
+ else {
+ Mode::Clean
+ }
+}
diff --git a/src/systems/cmd/renderer.rs b/src/systems/cmd/renderer.rs
index bdd702d..1849ee9 100644
--- a/src/systems/cmd/renderer.rs
+++ b/src/systems/cmd/renderer.rs
@@ -42,13 +42,13 @@ impl JVRenderResult {
#[macro_export]
macro_rules! r_print {
($result:expr, $($arg:tt)*) => {
- $result.print(&format!($($arg)*));
+ $result.print(&format!($($arg)*))
};
}
#[macro_export]
macro_rules! r_println {
($result:expr, $($arg:tt)*) => {
- $result.println(&format!($($arg)*));
+ $result.println(&format!($($arg)*))
};
}
diff --git a/src/utils/display.rs b/src/utils/display.rs
index f0532f3..835313b 100644
--- a/src/utils/display.rs
+++ b/src/utils/display.rs
@@ -268,17 +268,17 @@ pub fn md(text: impl AsRef<str>) -> String {
}
}
- // Check for inline code `text`
- if chars[i] == '`' {
+ // Check for angle-bracketed content <text>
+ if chars[i] == '<' {
let mut j = i + 1;
- while j < chars.len() && chars[j] != '`' {
+ while j < chars.len() && chars[j] != '>' {
j += 1;
}
if j < chars.len() {
- // Include the backticks in the output
- let code_text: String = chars[i..=j].iter().collect();
- let mut formatted_text = code_text.green().to_string();
+ // Include the angle brackets in the output
+ let angle_text: String = chars[i..=j].iter().collect();
+ let mut formatted_text = angle_text.cyan().to_string();
// Apply current color stack
for color in color_stack.iter().rev() {
@@ -291,17 +291,17 @@ pub fn md(text: impl AsRef<str>) -> String {
}
}
- // Check for angle-bracketed content <text>
- if chars[i] == '<' {
+ // Check for inline code `text`
+ if chars[i] == '`' {
let mut j = i + 1;
- while j < chars.len() && chars[j] != '>' {
+ while j < chars.len() && chars[j] != '`' {
j += 1;
}
if j < chars.len() {
- // Include the angle brackets in the output
- let angle_text: String = chars[i..=j].iter().collect();
- let mut formatted_text = angle_text.cyan().to_string();
+ // Include the backticks in the output
+ let code_text: String = chars[i..=j].iter().collect();
+ let mut formatted_text = code_text.green().to_string();
// Apply current color stack
for color in color_stack.iter().rev() {
diff --git a/src/utils/workspace_reader.rs b/src/utils/workspace_reader.rs
index a3bc754..c001330 100644
--- a/src/utils/workspace_reader.rs
+++ b/src/utils/workspace_reader.rs
@@ -253,4 +253,125 @@ impl LocalWorkspaceReader {
};
Ok(analyzed.into())
}
+
+ // Pop the local configuration (take ownership if cached)
+ pub async fn pop_local_config(&mut self) -> Result<LocalConfig, CmdPrepareError> {
+ if let Some(local_config) = self.local_config.take() {
+ Ok(local_config)
+ } else {
+ let workspace_dir = self.workspace_dir()?;
+ let local_config = entry_dir!(workspace_dir, {
+ LocalConfig::read()
+ .await
+ .map_err(|_| CmdPrepareError::LocalConfigNotFound)?
+ });
+ Ok(local_config)
+ }
+ }
+
+ // Pop the local workspace (take ownership if cached)
+ pub async fn pop_local_workspace(&mut self) -> Result<LocalWorkspace, CmdPrepareError> {
+ if let Some(local_workspace) = self.local_workspace.take() {
+ Ok(local_workspace)
+ } else {
+ 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);
+ };
+ Ok(local_workspace)
+ }
+ }
+
+ // Pop the latest information (take ownership if cached)
+ pub async fn pop_latest_info(&mut self) -> Result<LatestInfo, CmdPrepareError> {
+ if let Some(latest_info) = self.latest_info.take() {
+ Ok(latest_info)
+ } else {
+ 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);
+ }
+ }
+ });
+ Ok(latest_info)
+ }
+ }
+
+ // Pop the latest file data for a specific account (take ownership if cached)
+ pub async fn pop_latest_file_data(
+ &mut self,
+ account: &MemberId,
+ ) -> Result<LatestFileData, CmdPrepareError> {
+ if let Some(latest_file_data) = self.latest_file_data.remove(account) {
+ Ok(latest_file_data)
+ } else {
+ 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?;
+ Ok(latest_file_data)
+ }
+ }
+
+ // Pop the cached sheet for a specific sheet name (take ownership if cached)
+ pub async fn pop_cached_sheet(
+ &mut self,
+ sheet_name: &SheetName,
+ ) -> Result<SheetData, CmdPrepareError> {
+ if let Some(cached_sheet) = self.cached_sheet.remove(sheet_name) {
+ Ok(cached_sheet)
+ } else {
+ 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())),
+ }
+ });
+ Ok(cached_sheet)
+ }
+ }
+
+ // Pop the local sheet data for a specific account and sheet name (take ownership if cached)
+ pub async fn pop_local_sheet_data(
+ &mut self,
+ account: &MemberId,
+ sheet_name: &SheetName,
+ ) -> Result<LocalSheetData, CmdPrepareError> {
+ let key = (account.clone(), sheet_name.clone());
+ if let Some(local_sheet_data) = self.local_sheet_data.remove(&key) {
+ Ok(local_sheet_data)
+ } else {
+ 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(),
+ ));
+ }
+ };
+ Ok(local_sheet_data)
+ }
+ }
}