summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--locales/help_docs/en.yml82
-rw-r--r--locales/help_docs/zh-CN.yml83
-rw-r--r--scripts/completions/bash/completion_jv.sh36
-rw-r--r--scripts/completions/powershell/completion_jv.ps135
-rw-r--r--src/bin/jv.rs477
-rw-r--r--src/utils/display.rs121
6 files changed, 799 insertions, 35 deletions
diff --git a/locales/help_docs/en.yml b/locales/help_docs/en.yml
index 1f55143..be88c60 100644
--- a/locales/help_docs/en.yml
+++ b/locales/help_docs/en.yml
@@ -450,15 +450,15 @@ jv:
**Usage**:
jv share <FILE> <SHEET> <DESCRIPTION> - Share mapping to other sheets
jv share <SHARE_ID> - Import share to current sheet
- jv share <REF_SHEET> <FILE> - Import mapping from other reference sheet
- jv shares - View incoming shares
+ jv share list - View incoming shares
+ jv share see - View share details
**Tip**: The import command can use the following parameters
- --only-remote - Only import mapping into the sheet, do not modify local structure
- --strict - Strict import mode, reject all conflicts, this is the default scheme
- --skip - Skip conflicting items
+ --safe - Safe import, reject all conflicts, this is the default scheme
+ --skip - Skip all conflicting items
--overwrite - Force overwrite conflicting mappings, dangerous operation
+ --reject - Reject this share
**Sharing** is the simplest way to give file visibility to others
@@ -577,6 +577,14 @@ jv:
from_core: |
**Error**: `%{err}` (This error is from core call)
+ share:
+ share_id_not_exist: |
+ The share `%{id}` does not exist.
+
+ invalid_target_sheet: |
+ The sheet `%{sheet}` you specified does not exist in your context.
+ If you are sure it exists, please use `jv update` to update the workspace.
+
sheet:
align:
no_direction: |
@@ -911,6 +919,22 @@ jv:
description: DESCRIPTION
description_current: Editing ...
+ share:
+ list:
+ headers:
+ id: ID
+ sharer: SHARER
+ description: DESCRIPTION
+ file_count: COUNT
+ footer: Use `jv share see <ID>` to view the specific content of the share
+
+ content: |
+ %{share_id}
+ FROM: %{sharer}
+ %{description}
+ MAPPINGS:
+ %{mappings}
+
status:
struct_changes_display: |
Viewing sheet %{sheet_name} (%{h}h %{m}min %{s}secs ago).
@@ -1032,6 +1056,54 @@ jv:
Error syncing upstream information to local: Local path %{path} already exists, but a move operation needs to move an item here.
Please try moving the item to a different path, then run `jv update` again
+ share:
+ share_mapping:
+ success: |
+ Successfully shared visibility of %{file_nums} files to `%{to_sheet}`
+ The holder of that sheet, `%{to_sheet_holder}`, will see your share after performing an update
+
+ target_sheet_not_found: |
+ The sheet `%{to_sheet}` you specified does not exist.
+ You can use `jv sheet list --all` to list all sheets
+
+ target_is_self: |
+ You cannot share your own mapping to yourself
+
+ mapping_not_found: |
+ In your share, a mapping was found that is not recognized by the upstream!
+ Mapping: %{mapping}
+
+ Please confirm your local mapping is aligned with the upstream. You can use `jv align` to check the status
+
+ unknown: |
+ Unknown result!
+
+ merge_shares:
+ success: |
+ Successfully merged share `%{share_id}` into your sheet `%{sheet}`
+ Upstream information has changed, please use `jv update` to sync to the latest information
+
+ success_reject:
+ Rejected share `%{share_id}`
+
+ has_conflicts: |
+ Conflicts occurred when merging structure from share `%{share_id}` into your sheet!
+ Because the share contains mappings that overlap with your sheet!
+ You can use `jv share %{share_id} --skip`
+ or use `jv share %{share_id} --overwrite`
+ to select the merge mode
+
+ edit_not_allowed: |
+ Upstream prevented you from modifying this sheet!
+ Because you do not have edit rights for this sheet
+
+ share_id_not_found: |
+ Cannot find the share `%{share_id}` you provided in the upstream
+ You can use `jv share list` to list all shares after `jv update`
+
+ merge_failed: |
+ Merge failed: %{error}
+
sheet:
make:
success: |
diff --git a/locales/help_docs/zh-CN.yml b/locales/help_docs/zh-CN.yml
index 3f5f438..50441ff 100644
--- a/locales/help_docs/zh-CN.yml
+++ b/locales/help_docs/zh-CN.yml
@@ -441,15 +441,15 @@ jv:
**用法**:
jv share <文件> <表> <描述> - 分享映射到其他表
jv share <分享ID> - 将分享导入到当前表
- jv share <REF表> <文件> - 从其他参考表中导入映射
- jv shares - 查看传入的分享
+ jv share list - 查看传入的分享
+ jv share see - 查看分享的详情
**提示**:import 命令可使用如下参数
- --only-remote - 只在表中导入映射,不修改本地结构
- --strict - 严格的导入模式,拒绝所有冲突,这是默认的方案
- --skip - 跳过冲突项
+ --safe - 安全导入,拒绝所有冲突,这是默认的方案
+ --skip - 跳过所有冲突项
--overwrite - 强制覆盖冲突的映射,危险的操作
+ --reject - 拒绝该分享
**分享** 是将文件可见性交由其他人的最简途径
@@ -567,6 +567,14 @@ jv:
from_core: |
**错误**:`%{err}`(该错误来自核心调用)
+ share:
+ share_id_not_exist: |
+ 您给出的分享 `%{id}` 不存在
+
+ invalid_target_sheet: |
+ 您所给出的表 `%{sheet}` 在您的上下文中并不存在
+ 若您确定它存在,请使用 `jv update` 更新工作区
+
sheet:
align:
no_direction: |
@@ -904,6 +912,22 @@ jv:
description: 描述
description_current: 正在编辑中 ...
+ share:
+ list:
+ headers:
+ id: 分享ID
+ sharer: 分享者
+ description: 描述
+ file_count: 文件数
+ footer: 使用 `jv share see <ID>` 查看分享的具体内容
+
+ content: |
+ %{share_id}
+ 来自: %{sharer}
+ %{description}
+ 映射:
+ %{mappings}
+
status:
struct_changes_display: |
表 %{sheet_name} 的状态基于 %{h} 小时 %{m} 分钟 %{s} 秒前
@@ -1023,6 +1047,55 @@ jv:
在同步上游信息至本地时发生了错误:本地已存在 %{path},但是某个移动项需要移动到此处。
请尝试移动该项至其他路径,再重新输入 `jv update`
+ share:
+ share_mapping:
+ success: |
+ 成功将 %{file_nums} 个文件的可见性分享至 `%{to_sheet}`
+ 该表的持有者 `%{to_sheet_holder}` 在执行更新后即可看到您的分享
+
+ target_sheet_not_found: |
+ 您所指定的 `%{to_sheet}` 不存在,
+ 您可以使用 `jv sheet list --all` 列出所有的表
+
+ target_is_self: |
+ 您不能将自己的映射分享给自己
+
+ mapping_not_found: |
+ 在您的分享中,找到并未被上游所承认的映射!
+ 映射:%{mapping}
+
+ 请确认您的本地映射和上游是否对齐,您可以使用 `jv align` 查看状态
+
+ unknown: |
+ 未知的结果!
+
+ merge_shares:
+ success: |
+ 成功将分享 `%{share_id}` 合入您的表 `%{sheet}`
+ 上游信息已变更,请使用 `jv update` 同步至最新信息
+
+ success_reject:
+ 已拒绝接受分享 `%{share_id}`
+
+ has_conflicts: |
+ 从分享 `%{share_id}` 合并结构到您的表时发生冲突!
+ 因为分享中存在和您表中重合的映射!
+ 您可以使用 `jv share %{share_id} --skip`
+ 或使用 `jv share %{share_id} --overwrite`
+ 来选择合并模式
+
+ edit_not_allowed: |
+ 上游阻止了您修改此表!
+ 因为您没有该表的编辑权
+
+ share_id_not_found: |
+ 在上游中无法找到您给出的分享 `%{share_id}`
+ 您可以在 `jv update` 后使用
+ `jv share list` 来列出所有的分享
+
+ merge_failed: |
+ 合并失败:%{error}
+
sheet:
make:
success: |
diff --git a/scripts/completions/bash/completion_jv.sh b/scripts/completions/bash/completion_jv.sh
index 4023e42..364df9d 100644
--- a/scripts/completions/bash/completion_jv.sh
+++ b/scripts/completions/bash/completion_jv.sh
@@ -18,7 +18,7 @@ _jv_completion() {
local base_commands="create init direct unstain account update \
sheet status here move mv docs exit use sheets accounts \
as make drop track hold throw login \
- jump align info"
+ jump align info share"
# Subcommands - Account
local account_commands="list as add remove movekey mvkey mvk genpub help"
@@ -157,6 +157,40 @@ _jv_completion() {
return 0
fi
+ # Completion share
+ if [[ "$subcmd" == "share" ]]; then
+ if [[ $cword -eq 2 ]]; then
+ # First parameter: list, see, jv share list --raw results, or files
+ local share_list
+ share_list=$($cmd share list --raw 2>/dev/null)
+ local first_param_options="list see $share_list"
+ COMPREPLY=($(compgen -W "$first_param_options" -f -- "$cur"))
+ elif [[ $cword -eq 3 ]]; then
+ # Second parameter: depends on first parameter
+ local first_param="${words[2]}"
+
+ if [[ "$first_param" == "list" ]]; then
+ # list -> nothing
+ COMPREPLY=()
+ elif [[ "$first_param" == "see" ]]; then
+ # see -> jv share list --raw results
+ local share_list
+ share_list=$($cmd share list --raw 2>/dev/null)
+ COMPREPLY=($(compgen -W "$share_list" -- "$cur"))
+ elif [[ "$first_param" == *"@"* ]]; then
+ # Contains "@" (shareid) -> show options
+ COMPREPLY=($(compgen -W "--safe --overwrite --skip --reject" -- "$cur"))
+ else
+ # File input -> show jv sheet list --all --raw results
+ local all_sheets
+ all_sheets=$($cmd sheet list --all --raw 2>/dev/null)
+ COMPREPLY=($(compgen -W "$all_sheets" -- "$cur"))
+ fi
+ fi
+ # Third parameter: no completion
+ return 0
+ fi
+
# Completion login
if [[ "$subcmd" == "login" ]]; then
if [[ $cword -eq 2 ]]; then
diff --git a/scripts/completions/powershell/completion_jv.ps1 b/scripts/completions/powershell/completion_jv.ps1
index 0c854e3..84ba01a 100644
--- a/scripts/completions/powershell/completion_jv.ps1
+++ b/scripts/completions/powershell/completion_jv.ps1
@@ -16,7 +16,7 @@ Register-ArgumentCompleter -Native -CommandName jv -ScriptBlock {
"create", "init", "direct", "unstain", "account", "update",
"sheet", "status", "here", "move", "mv", "docs", "exit", "use", "sheets", "accounts",
"as", "make", "drop", "track", "hold", "throw", "login",
- "jump", "align", "info"
+ "jump", "align", "info", "share"
)
# Account subcommands
@@ -175,6 +175,39 @@ Register-ArgumentCompleter -Native -CommandName jv -ScriptBlock {
return @()
}
+ # Completion for share command
+ if ($subcmd -eq "share") {
+ if ($currentIndex -eq 2) {
+ # First parameter: list, see, jv share list --raw results, or files in current directory
+ $staticOptions = @("list", "see")
+ $shareList = & $cmd share list --raw 2>$null
+ $files = Get-ChildItem -Name -File -Path "." 2>$null
+ $completions = $staticOptions + $shareList + $files
+ return $completions | Where-Object { $_ -like "$wordToComplete*" }
+ } elseif ($currentIndex -eq 3) {
+ # Second parameter: depends on the first parameter
+ $firstParam = $words[2]
+ if ($firstParam -eq "list") {
+ # list -> nothing
+ return @()
+ } elseif ($firstParam -eq "see") {
+ # see -> jv share list --raw results
+ $shareList = & $cmd share list --raw 2>$null
+ return $shareList | Where-Object { $_ -like "$wordToComplete*" }
+ } elseif ($firstParam -like "*@*") {
+ # Contains "@" (shareid) -> show options
+ $options = @("--safe", "--overwrite", "--skip", "--reject")
+ return $options | Where-Object { $_ -like "$wordToComplete*" }
+ } else {
+ # Otherwise, assume it's a file -> show jv sheet list --all --raw results
+ $allSheets = & $cmd sheet list --all --raw 2>$null
+ return $allSheets | Where-Object { $_ -like "$wordToComplete*" }
+ }
+ }
+ # Third parameter: no completion
+ return @()
+ }
+
# Aliases completion
switch ($subcmd) {
"as" {
diff --git a/src/bin/jv.rs b/src/bin/jv.rs
index b4c7c91..8b73366 100644
--- a/src/bin/jv.rs
+++ b/src/bin/jv.rs
@@ -21,8 +21,11 @@ use just_enough_vcs::{
},
sheet_actions::{
DropSheetActionResult, EditMappingActionArguments, EditMappingActionResult,
- EditMappingOperations, InvalidMoveReason, MakeSheetActionResult, OperationArgument,
- proc_drop_sheet_action, proc_edit_mapping_action, proc_make_sheet_action,
+ EditMappingOperations, InvalidMoveReason, MakeSheetActionResult,
+ MergeShareMappingActionResult, MergeShareMappingArguments, OperationArgument,
+ ShareMappingActionResult, ShareMappingArguments, proc_drop_sheet_action,
+ proc_edit_mapping_action, proc_make_sheet_action, proc_merge_share_mapping_action,
+ proc_share_mapping_action,
},
track_action::{
CreateTaskResult, NextVersion, SyncTaskResult, TrackFileActionArguments,
@@ -36,7 +39,7 @@ use just_enough_vcs::{
},
constants::{
CLIENT_FILE_TODOLIST, CLIENT_FILE_WORKSPACE, CLIENT_FOLDER_WORKSPACE_ROOT_NAME,
- CLIENT_PATH_WORKSPACE_ROOT, PORT,
+ CLIENT_PATH_WORKSPACE_ROOT, PORT, VAULT_HOST_NAME,
},
current::{correct_current_dir, current_cfg_dir, current_local_path},
data::{
@@ -53,7 +56,10 @@ use just_enough_vcs::{
member::{Member, MemberId},
sheet::{SheetData, SheetMappingMetadata},
user::UserDirectory,
- vault::virtual_file::{VirtualFileId, VirtualFileVersion},
+ vault::{
+ sheet_share::{Share, ShareMergeMode},
+ virtual_file::{VirtualFileId, VirtualFileVersion},
+ },
},
docs::{ASCII_YIZI, document, documents},
},
@@ -81,7 +87,7 @@ use just_enough_vcs_cli::{
ipaddress_history::{get_recent_ip_address, insert_recent_ip_address},
},
utils::{
- display::{SimpleTable, display_width, md, size_str},
+ display::{SimpleTable, display_width, md, render_share_path_tree, size_str},
env::{auto_update_outdate, current_locales, enable_auto_update},
fs::move_across_partitions,
globber::{GlobItem, Globber},
@@ -171,7 +177,7 @@ enum JustEnoughVcsWorkspaceCommand {
Move(MoveMappingArgs),
/// Share file visibility to other sheets
- Share(ShareFileArgs),
+ Share(ShareMappingArgs),
/// Sync information from upstream vault
#[command(alias = "u")]
@@ -639,7 +645,7 @@ struct MoveMappingArgs {
}
#[derive(Parser, Debug)]
-struct ShareFileArgs {
+struct ShareMappingArgs {
/// Show help information
#[arg(short, long)]
help: bool,
@@ -652,6 +658,26 @@ struct ShareFileArgs {
/// Arguments 3
args3: Option<String>,
+
+ /// Safe merge
+ #[arg(short = 's', long)]
+ safe: bool,
+
+ /// Skip all conflicting mappings
+ #[arg(short = 'S', long)]
+ skip: bool,
+
+ /// Overwrite all conflicting mappings
+ #[arg(short = 'o', long)]
+ overwrite: bool,
+
+ /// Reject this share
+ #[arg(short = 'R', long)]
+ reject: bool,
+
+ /// Show raw output
+ #[arg(short = 'r', long)]
+ raw: bool,
}
#[derive(Parser, Debug)]
@@ -4267,17 +4293,28 @@ async fn proc_mapping_edit(
}
}
-async fn jv_share(args: ShareFileArgs) {
+async fn jv_share(args: ShareMappingArgs) {
// Import share mode
- if let (Some(import_id), None, None) = (&args.args1, &args.args2, &args.args3) {
- share_accept(import_id).await;
+ if let (Some(args1), None, None) = (&args.args1, &args.args2, &args.args3) {
+ // List mode
+ if args1.trim() == "list" || args1.trim() == "ls" {
+ share_list(args).await;
+ return;
+ }
+
+ share_accept(args1.to_string(), args).await;
return;
}
// Pull mode
- if let (Some(from_sheet), Some(import_pattern), None) = (&args.args1, &args.args2, &args.args3)
- {
- share_in(from_sheet, import_pattern).await;
+ if let (Some(args1), Some(args2), None) = (&args.args1, &args.args2, &args.args3) {
+ // See mode
+ if args1.trim() == "see" {
+ share_see(args2.to_string()).await;
+ return;
+ }
+
+ share_in(args1.to_string(), args2.to_string(), args).await;
return;
}
@@ -4285,26 +4322,422 @@ async fn jv_share(args: ShareFileArgs) {
if let (Some(share_pattern), Some(to_sheet), Some(description)) =
(&args.args1, &args.args2, &args.args3)
{
- share_out(share_pattern, to_sheet, description).await;
+ share_out(
+ share_pattern.to_string(),
+ to_sheet.to_string(),
+ description.to_string(),
+ args,
+ )
+ .await;
return;
}
println!("{}", md(t!("jv.share")));
}
-async fn share_accept(_import_id: &str) {
- // TODO: Implement import share logic
- eprintln!("share_accept not implemented yet");
+async fn share_list(args: ShareMappingArgs) {
+ let _ = correct_current_dir();
+
+ let Some(local_dir) = current_local_path() else {
+ eprintln!("{}", t!("jv.fail.workspace_not_found").trim());
+ return;
+ };
+
+ let local_config = match precheck().await {
+ Some(config) => config,
+ None => return,
+ };
+
+ let sheet_name = local_config.sheet_in_use().clone().unwrap_or_default();
+
+ let Ok(latest_info) = LatestInfo::read_from(LatestInfo::latest_info_path(
+ &local_dir,
+ &local_config.current_account(),
+ ))
+ .await
+ else {
+ eprintln!(
+ "{}",
+ md(t!(
+ "jv.fail.cfg_not_found.latest_info",
+ account = &local_config.current_account()
+ ))
+ );
+ return;
+ };
+
+ if let Some(shares) = latest_info.shares_in_my_sheets.get(&sheet_name) {
+ // Sort
+ let mut sorted_shares: BTreeMap<String, &Share> = BTreeMap::new();
+ for (id, share) in shares {
+ sorted_shares.insert(id.clone(), share);
+ }
+
+ if !args.raw {
+ // Create table and insert information
+ let mut table = SimpleTable::new(vec![
+ t!("jv.success.share.list.headers.id"),
+ t!("jv.success.share.list.headers.sharer"),
+ t!("jv.success.share.list.headers.description"),
+ t!("jv.success.share.list.headers.file_count"),
+ ]);
+ for (id, share) in sorted_shares {
+ table.insert_item(
+ 0,
+ vec![
+ id.to_string(),
+ share.sharer.to_string(),
+ truncate_first_line(share.description.to_string()),
+ share.mappings.len().to_string(),
+ ],
+ );
+ }
+
+ // Render
+ println!("{}", table);
+ println!("{}", md(t!("jv.success.share.list.footer")));
+ } else {
+ sorted_shares
+ .iter()
+ .for_each(|share| println!("{}", share.0));
+ }
+ }
+}
+
+async fn share_see(share_id: String) {
+ let _ = correct_current_dir();
+
+ let Some(local_dir) = current_local_path() else {
+ eprintln!("{}", t!("jv.fail.workspace_not_found").trim());
+ return;
+ };
+
+ let local_config = match precheck().await {
+ Some(config) => config,
+ None => return,
+ };
+
+ let sheet_name = local_config.sheet_in_use().clone().unwrap_or_default();
+
+ let Ok(latest_info) = LatestInfo::read_from(LatestInfo::latest_info_path(
+ &local_dir,
+ &local_config.current_account(),
+ ))
+ .await
+ else {
+ eprintln!(
+ "{}",
+ md(t!(
+ "jv.fail.cfg_not_found.latest_info",
+ account = &local_config.current_account()
+ ))
+ );
+ return;
+ };
+
+ if let Some(shares) = latest_info.shares_in_my_sheets.get(&sheet_name) {
+ if let Some(share) = shares.get(&share_id) {
+ println!(
+ "{}",
+ md(t!(
+ "jv.success.share.content",
+ share_id = share_id,
+ sharer = share.sharer,
+ description = share.description,
+ mappings = render_share_path_tree(&share.mappings)
+ ))
+ );
+ }
+ }
+}
+
+async fn share_accept(import_id: String, args: ShareMappingArgs) {
+ let _ = correct_current_dir();
+
+ let Some(local_dir) = current_local_path() else {
+ eprintln!("{}", t!("jv.fail.workspace_not_found").trim());
+ return;
+ };
+
+ let local_config = match precheck().await {
+ Some(config) => config,
+ None => return,
+ };
+
+ let sheet_name = local_config.sheet_in_use().clone().unwrap_or_default();
+
+ let Ok(latest_info) = LatestInfo::read_from(LatestInfo::latest_info_path(
+ &local_dir,
+ &local_config.current_account(),
+ ))
+ .await
+ else {
+ eprintln!(
+ "{}",
+ md(t!(
+ "jv.fail.cfg_not_found.latest_info",
+ account = &local_config.current_account()
+ ))
+ );
+ return;
+ };
+
+ let contains_share = if let Some(share_ids) = latest_info.shares_in_my_sheets.get(&sheet_name) {
+ share_ids.contains_key(&import_id)
+ } else {
+ false
+ };
+
+ if !contains_share {
+ eprintln!(
+ "{}",
+ md(t!("jv.fail.share.share_id_not_exist", id = &import_id))
+ );
+ return;
+ }
+
+ let (pool, ctx, _output) = match build_pool_and_ctx(&local_config).await {
+ Some(result) => result,
+ None => return,
+ };
+
+ let share_merge_mode = {
+ if args.safe {
+ ShareMergeMode::Safe
+ } else if args.skip {
+ ShareMergeMode::Skip
+ } else if args.overwrite {
+ ShareMergeMode::Overwrite
+ } else if args.reject {
+ ShareMergeMode::RejectAll
+ } else {
+ ShareMergeMode::Safe
+ }
+ };
+
+ match proc_merge_share_mapping_action(
+ &pool,
+ ctx,
+ MergeShareMappingArguments {
+ share_id: import_id.clone(),
+ share_merge_mode,
+ },
+ )
+ .await
+ {
+ Ok(r) => match r {
+ MergeShareMappingActionResult::Success => {
+ if args.reject {
+ println!(
+ "{}",
+ md(t!(
+ "jv.result.share.merge_shares.success_reject",
+ share_id = &import_id
+ ))
+ );
+ } else {
+ println!(
+ "{}",
+ md(t!(
+ "jv.result.share.merge_shares.success",
+ share_id = &import_id,
+ sheet = &sheet_name
+ ))
+ );
+ }
+ }
+ MergeShareMappingActionResult::HasConflicts => {
+ eprintln!(
+ "{}",
+ md(t!(
+ "jv.result.share.merge_shares.has_conflicts",
+ share_id = &import_id
+ ))
+ );
+ }
+ MergeShareMappingActionResult::AuthorizeFailed(e) => {
+ eprintln!("{}", md(t!("jv.result.common.authroize_failed", err = e)));
+ }
+ MergeShareMappingActionResult::EditNotAllowed => {
+ eprintln!(
+ "{}",
+ md(t!("jv.result.share.merge_shares.edit_not_allowed"))
+ );
+ }
+ MergeShareMappingActionResult::ShareIdNotFound(share_id) => {
+ eprintln!(
+ "{}",
+ md(t!(
+ "jv.result.share.merge_shares.share_id_not_found",
+ share_id = share_id
+ ))
+ );
+ }
+ MergeShareMappingActionResult::MergeFails(error) => {
+ eprintln!(
+ "{}",
+ md(t!(
+ "jv.result.share.merge_shares.merge_failed",
+ error = error
+ ))
+ );
+ }
+ MergeShareMappingActionResult::Unknown => {
+ eprintln!("{}", md(t!("jv.result.share.merge_shares.unknown")));
+ }
+ },
+ Err(e) => handle_err(e),
+ }
}
-async fn share_in(_from_sheet: &str, _import_pattern: &str) {
+async fn share_in(_from_sheet: String, _import_pattern: String, _args: ShareMappingArgs) {
// TODO: Implement pull mode logic
- eprintln!("share_in not implemented yet");
}
-async fn share_out(_share_pattern: &str, _to_sheet: &str, _description: &str) {
- // TODO: Implement share mode logic
- eprintln!("share_out not implemented yet");
+async fn share_out(
+ share_pattern: String,
+ to_sheet: String,
+ description: String,
+ _args: ShareMappingArgs,
+) {
+ let shared_files = {
+ let local_dir = match current_local_path() {
+ Some(dir) => dir,
+ None => {
+ eprintln!("{}", t!("jv.fail.workspace_not_found").trim());
+ return;
+ }
+ };
+ let files = glob(share_pattern, &local_dir).await;
+ files
+ .iter()
+ .filter_map(|f| PathBuf::from_str(f.0).ok())
+ .collect::<Vec<_>>()
+ };
+
+ let _ = correct_current_dir();
+
+ let Some(local_dir) = current_local_path() else {
+ eprintln!("{}", t!("jv.fail.workspace_not_found").trim());
+ return;
+ };
+
+ let local_config = match precheck().await {
+ Some(config) => config,
+ None => return,
+ };
+
+ let Ok(latest_info) = LatestInfo::read_from(LatestInfo::latest_info_path(
+ &local_dir,
+ &local_config.current_account(),
+ ))
+ .await
+ else {
+ eprintln!(
+ "{}",
+ md(t!(
+ "jv.fail.cfg_not_found.latest_info",
+ account = &local_config.current_account()
+ ))
+ );
+ return;
+ };
+
+ // Pre-check if the sheet exists
+ let contains_in_my_sheet = latest_info.visible_sheets.contains(&to_sheet);
+ let contains_in_other_sheet = latest_info
+ .invisible_sheets
+ .iter()
+ .find(|info| info.sheet_name == to_sheet)
+ .is_some();
+ if !contains_in_my_sheet && !contains_in_other_sheet {
+ eprintln!(
+ "{}",
+ md(t!("jv.fail.share.invalid_target_sheet", sheet = &to_sheet))
+ );
+ return;
+ }
+
+ let (pool, ctx, _output) = match build_pool_and_ctx(&local_config).await {
+ Some(result) => result,
+ None => return,
+ };
+
+ let to_sheet_holder = {
+ if latest_info.reference_sheets.contains(&to_sheet) {
+ VAULT_HOST_NAME.to_string()
+ } else if latest_info.visible_sheets.contains(&to_sheet) {
+ local_config.current_account()
+ } else {
+ let mut holder = String::new();
+ for info in &latest_info.invisible_sheets {
+ if info.sheet_name == to_sheet {
+ holder = info.holder_name.as_ref().cloned().unwrap_or_default();
+ break;
+ }
+ }
+ holder
+ }
+ };
+
+ match proc_share_mapping_action(
+ &pool,
+ ctx,
+ ShareMappingArguments {
+ mappings: shared_files.clone(),
+ description,
+
+ // Since the Action internally checks the current sheet,
+ // there's no need to fill in from_sheet here.
+ // This is prepared for pull operations.
+ from_sheet: None,
+ to_sheet: to_sheet.clone(),
+ },
+ )
+ .await
+ {
+ Ok(r) => match r {
+ ShareMappingActionResult::Success => {
+ println!(
+ "{}",
+ md(t!(
+ "jv.result.share.share_mapping.success",
+ file_nums = shared_files.len(),
+ to_sheet = to_sheet,
+ to_sheet_holder = to_sheet_holder
+ ))
+ );
+ }
+ ShareMappingActionResult::AuthorizeFailed(e) => {
+ eprintln!("{}", md(t!("jv.result.common.authroize_failed", err = e)));
+ }
+ ShareMappingActionResult::TargetSheetNotFound(sheet) => {
+ eprintln!(
+ "{}",
+ md(t!(
+ "jv.result.share.share_mapping.target_sheet_not_found",
+ to_sheet = sheet
+ ))
+ );
+ }
+ ShareMappingActionResult::TargetIsSelf => {
+ eprintln!("{}", md(t!("jv.result.share.share_mapping.target_is_self")));
+ }
+ ShareMappingActionResult::MappingNotFound(path_buf) => {
+ eprintln!(
+ "{}",
+ md(t!(
+ "jv.result.share.share_mapping.mapping_not_found",
+ mapping = path_buf.display()
+ ))
+ );
+ }
+ ShareMappingActionResult::Unknown => {
+ eprintln!("{}", md(t!("jv.result.share.share_mapping.unknown")));
+ }
+ },
+ Err(e) => handle_err(e),
+ }
}
async fn jv_account_add(user_dir: UserDirectory, args: AccountAddArgs) {
diff --git a/src/utils/display.rs b/src/utils/display.rs
index 4610f4f..f0532f3 100644
--- a/src/utils/display.rs
+++ b/src/utils/display.rs
@@ -1,5 +1,9 @@
use colored::*;
-use std::collections::VecDeque;
+use just_enough_vcs::vcs::data::sheet::SheetMappingMetadata;
+use std::{
+ collections::{BTreeMap, HashMap, VecDeque},
+ path::PathBuf,
+};
pub struct SimpleTable {
items: Vec<String>,
@@ -365,3 +369,118 @@ fn apply_color(text: &str, color_name: &str) -> String {
_ => text.to_string(),
}
}
+
+/// Render a HashMap of PathBuf to SheetMappingMetadata as a tree string.
+pub fn render_share_path_tree(paths: &HashMap<PathBuf, SheetMappingMetadata>) -> String {
+ if paths.is_empty() {
+ return String::new();
+ }
+
+ // Collect all path components into a tree structure
+ let mut root = TreeNode::new("".to_string());
+
+ for (path, metadata) in paths {
+ let mut current = &mut root;
+ let components: Vec<String> = path
+ .components()
+ .filter_map(|comp| match comp {
+ std::path::Component::Normal(s) => s.to_str().map(|s| s.to_string()),
+ _ => None,
+ })
+ .collect();
+
+ for (i, comp) in components.iter().enumerate() {
+ let is_leaf = i == components.len() - 1;
+ let child = current
+ .children
+ .entry(comp.clone())
+ .or_insert_with(|| TreeNode::new(comp.clone()));
+
+ // If this is the leaf node, store the metadata
+ if is_leaf {
+ child.metadata = Some((metadata.id.clone(), metadata.version.clone()));
+ }
+
+ current = child;
+ }
+ }
+
+ // Convert tree to string representation
+ let mut result = String::new();
+ let is_root = true;
+ let prefix = String::new();
+ let last_stack = vec![true]; // Root is always "last"
+
+ add_tree_node_to_string(&root, &mut result, is_root, &prefix, &last_stack);
+
+ result
+}
+
+/// Internal tree node structure for building the path tree
+#[derive(Debug)]
+struct TreeNode {
+ name: String,
+ children: BTreeMap<String, TreeNode>, // Use BTreeMap for sorted output
+ metadata: Option<(String, String)>, // Store (id, version) for leaf nodes
+}
+
+impl TreeNode {
+ fn new(name: String) -> Self {
+ Self {
+ name,
+ children: BTreeMap::new(),
+ metadata: None,
+ }
+ }
+}
+
+/// Recursively add tree node to string representation
+fn add_tree_node_to_string(
+ node: &TreeNode,
+ result: &mut String,
+ is_root: bool,
+ prefix: &str,
+ last_stack: &[bool],
+) {
+ if !is_root {
+ // Add the tree prefix for this node
+ for &is_last in &last_stack[1..] {
+ if is_last {
+ result.push_str(" ");
+ } else {
+ result.push_str("│ ");
+ }
+ }
+
+ // Add the connector for this node
+ if let Some(&is_last) = last_stack.last() {
+ if is_last {
+ result.push_str("└── ");
+ } else {
+ result.push_str("├── ");
+ }
+ }
+
+ // Add node name
+ result.push_str(&node.name);
+
+ // Add metadata for leaf nodes
+ if let Some((id, version)) = &node.metadata {
+ // Truncate id to first 11 characters
+ let truncated_id = if id.len() > 11 { &id[..11] } else { id };
+ result.push_str(&format!(" [{}|{}]", truncated_id, version));
+ }
+
+ result.push('\n');
+ }
+
+ // Process children
+ let child_count = node.children.len();
+ for (i, (_, child)) in node.children.iter().enumerate() {
+ let is_last_child = i == child_count - 1;
+ let mut new_last_stack = last_stack.to_vec();
+ new_last_stack.push(is_last_child);
+
+ add_tree_node_to_string(child, result, false, prefix, &new_last_stack);
+ }
+}