diff options
| author | 魏曹先生 <1992414357@qq.com> | 2025-10-30 09:48:59 +0800 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-10-30 09:48:59 +0800 |
| commit | 699adaa270dd698dd4e94eb83de2768058d838d0 (patch) | |
| tree | c2269fa15f7374435a18db6acf0780b871603538 /src/bin/jv.rs | |
| parent | f8e0fd6f4f38917a60207142761b21ac6a949cf3 (diff) | |
| parent | 3a3f40b2abbaa47063cdc3aeb0149e3d02276c1e (diff) | |
Merge branch 'main' into docs
Diffstat (limited to 'src/bin/jv.rs')
| -rw-r--r-- | src/bin/jv.rs | 524 |
1 files changed, 517 insertions, 7 deletions
diff --git a/src/bin/jv.rs b/src/bin/jv.rs index 9530203..95176b5 100644 --- a/src/bin/jv.rs +++ b/src/bin/jv.rs @@ -1,6 +1,30 @@ +use std::{env::current_dir, net::SocketAddr, path::PathBuf, process::exit}; + +use just_enough_vcs::{ + system::action_system::action::ActionContext, + utils::{cfg_file::config::ConfigFile, tcp_connection::instance::ConnectionInstance}, + vcs::{ + actions::local_actions::SetUpstreamVaultActionResult, + constants::PORT, + current::current_local_path, + data::{ + local::{LocalWorkspace, config::LocalConfig}, + member::Member, + user::UserDirectory, + }, + }, +}; + use clap::{Parser, Subcommand, arg, command}; -use just_enough_vcs_cli::utils::{lang_selector::current_locales, md_colored::md}; +use just_enough_vcs::{ + utils::tcp_connection::error::TcpTargetError, + vcs::{actions::local_actions::proc_set_upstream_vault_action, registry::client_registry}, +}; +use just_enough_vcs_cli::utils::{ + input::confirm_hint_or, lang_selector::current_locales, md_colored::md, socket_addr_helper, +}; use rust_i18n::{set_locale, t}; +use tokio::{fs, net::TcpSocket}; // Import i18n files rust_i18n::i18n!("locales", fallback = "en"); @@ -20,9 +44,12 @@ struct JustEnoughVcsWorkspace { #[derive(Subcommand, Debug)] enum JustEnoughVcsWorkspaceCommand { + #[command(alias = "--help", alias = "-h")] + Help, + // Member management /// Manage your local accounts - #[command(subcommand)] + #[command(subcommand, alias = "acc")] Account(AccountManage), /// Create an empty workspace @@ -84,6 +111,25 @@ enum AccountManage { /// Show help information #[command(alias = "--help", alias = "-h")] Help, + + /// Register a member to this computer + #[command(alias = "+")] + Add(AccountAddArgs), + + /// Remove a account from this computer + #[command(alias = "rm", alias = "-")] + Remove(AccountRemoveArgs), + + /// List all accounts in this computer + #[command(alias = "ls")] + List(AccountListArgs), + + /// Set current local workspace account + As(SetLocalWorkspaceAccountArgs), + + /// Move private key file to account + #[command(alias = "mvkey", alias = "mvk")] + MoveKey(MoveKeyToAccountArgs), } #[derive(Subcommand, Debug)] @@ -98,6 +144,13 @@ struct CreateWorkspaceArgs { /// Show help information #[arg(short, long)] help: bool, + + /// Workspace directory path + path: PathBuf, + + /// Force create, ignore files in the directory + #[arg(short, long)] + force: bool, } #[derive(Parser, Debug)] @@ -105,6 +158,10 @@ struct InitWorkspaceArgs { /// Show help information #[arg(short, long)] help: bool, + + /// Force create, ignore files in the directory + #[arg(short, long)] + force: bool, } #[derive(Parser, Debug)] @@ -115,6 +172,56 @@ struct HereArgs { } #[derive(Parser, Debug)] +struct AccountAddArgs { + /// Show help information + #[arg(short, long)] + help: bool, + + /// Account name + account_name: String, +} + +#[derive(Parser, Debug)] +struct AccountRemoveArgs { + /// Show help information + #[arg(short, long)] + help: bool, + + /// Account name + account_name: String, +} + +#[derive(Parser, Debug)] +struct AccountListArgs { + /// Show help information + #[arg(short, long)] + help: bool, +} + +#[derive(Parser, Debug)] +struct SetLocalWorkspaceAccountArgs { + /// Show help information + #[arg(short, long)] + help: bool, + + /// Account name + account_name: String, +} + +#[derive(Parser, Debug)] +struct MoveKeyToAccountArgs { + /// Show help information + #[arg(short, long)] + help: bool, + + /// Account name + account_name: String, + + /// Private key file path + key_path: PathBuf, +} + +#[derive(Parser, Debug)] struct TrackFileArgs { /// Show help information #[arg(short, long)] @@ -161,6 +268,13 @@ struct DirectArgs { /// Show help information #[arg(short, long)] help: bool, + + /// Upstream vault address + upstream: String, + + /// Whether to skip confirmation + #[arg(short = 'C', long)] + confirm: bool, } #[derive(Parser, Debug)] @@ -168,6 +282,10 @@ struct UnstainArgs { /// Show help information #[arg(short, long)] help: bool, + + /// Whether to skip confirmation + #[arg(short = 'C', long)] + confirm: bool, } #[derive(Parser, Debug)] @@ -187,33 +305,85 @@ async fn main() { colored::control::set_virtual_terminal(true).unwrap(); let Ok(parser) = JustEnoughVcsWorkspace::try_parse() else { - println!("{}", md(t!("jv.help"))); + println!("{}", md(t!("jv.fail.parse.parser_failed"))); return; }; match parser.command { - JustEnoughVcsWorkspaceCommand::Account(account_manage) => match account_manage { - AccountManage::Help => { - println!("{}", md(t!("jv.account"))); + JustEnoughVcsWorkspaceCommand::Help => { + println!("{}", md(t!("jv.help"))); + } + + JustEnoughVcsWorkspaceCommand::Account(account_manage) => { + let user_dir = match UserDirectory::current_doc_dir() { + Some(dir) => dir, + None => { + eprintln!("{}", t!("jv.fail.account.no_user_dir")); + return; + } + }; + + match account_manage { + AccountManage::Help => { + println!("{}", md(t!("jv.account"))); + } + AccountManage::Add(account_add_args) => { + if account_add_args.help { + println!("{}", md(t!("jv.account"))); + return; + } + jv_account_add(user_dir, account_add_args).await; + } + AccountManage::Remove(account_remove_args) => { + if account_remove_args.help { + println!("{}", md(t!("jv.account"))); + return; + } + jv_account_remove(user_dir, account_remove_args).await; + } + AccountManage::List(account_list_args) => { + if account_list_args.help { + println!("{}", md(t!("jv.account"))); + return; + } + jv_account_list(user_dir, account_list_args).await; + } + AccountManage::As(set_local_workspace_account_args) => { + if set_local_workspace_account_args.help { + println!("{}", md(t!("jv.account"))); + return; + } + jv_account_as(user_dir, set_local_workspace_account_args).await; + } + AccountManage::MoveKey(move_key_to_account_args) => { + if move_key_to_account_args.help { + println!("{}", md(t!("jv.account"))); + return; + } + jv_account_move_key(user_dir, move_key_to_account_args).await; + } } - }, + } JustEnoughVcsWorkspaceCommand::Create(create_workspace_args) => { if create_workspace_args.help { println!("{}", md(t!("jv.create"))); return; } + jv_create(create_workspace_args).await; } JustEnoughVcsWorkspaceCommand::Init(init_workspace_args) => { if init_workspace_args.help { println!("{}", md(t!("jv.init"))); return; } + jv_init(init_workspace_args).await; } JustEnoughVcsWorkspaceCommand::Here(here_args) => { if here_args.help { println!("{}", md(t!("jv.here"))); return; } + jv_here(here_args).await; } JustEnoughVcsWorkspaceCommand::Sheet(sheet_manage) => match sheet_manage { SheetManage::Help => { @@ -226,54 +396,394 @@ async fn main() { println!("{}", md(t!("jv.track"))); return; } + jv_track(track_file_args).await; } JustEnoughVcsWorkspaceCommand::Hold(hold_file_args) => { if hold_file_args.help { println!("{}", md(t!("jv.hold"))); return; } + jv_hold(hold_file_args).await; } JustEnoughVcsWorkspaceCommand::Throw(throw_file_args) => { if throw_file_args.help { println!("{}", md(t!("jv.throw"))); return; } + jv_throw(throw_file_args).await; } JustEnoughVcsWorkspaceCommand::Move(move_file_args) => { if move_file_args.help { println!("{}", md(t!("jv.move"))); return; } + jv_move(move_file_args).await; } JustEnoughVcsWorkspaceCommand::Export(export_file_args) => { if export_file_args.help { println!("{}", md(t!("jv.export"))); return; } + jv_export(export_file_args).await; } JustEnoughVcsWorkspaceCommand::Import(import_file_args) => { if import_file_args.help { println!("{}", md(t!("jv.import"))); return; } + jv_import(import_file_args).await; } JustEnoughVcsWorkspaceCommand::Direct(direct_args) => { if direct_args.help { println!("{}", md(t!("jv.direct"))); return; } + jv_direct(direct_args).await; } JustEnoughVcsWorkspaceCommand::Unstain(unstain_args) => { if unstain_args.help { println!("{}", md(t!("jv.unstain"))); return; } + jv_unstain(unstain_args).await; } JustEnoughVcsWorkspaceCommand::Docs(docs_args) => { if docs_args.help { println!("{}", md(t!("jv.docs"))); return; } + jv_docs(docs_args).await; + } + } +} + +async fn jv_create(args: CreateWorkspaceArgs) { + let path = args.path; + + if !args.force && path.exists() && !is_directory_empty(&path).await { + eprintln!("{}", t!("jv.fail.init_create_dir_not_empty").trim()); + return; + } + + match LocalWorkspace::setup_local_workspace(path).await { + Ok(_) => { + println!("{}", t!("jv.success.create")); + } + Err(e) => { + eprintln!("{}", t!("jv.fail.create", error = e.to_string())); + } + } +} + +async fn jv_init(args: InitWorkspaceArgs) { + let path = match current_dir() { + Ok(path) => path, + Err(e) => { + eprintln!("{}", t!("jv.fail.get_current_dir", error = e.to_string())); + return; + } + }; + + if !args.force && path.exists() && !is_directory_empty(&path).await { + eprintln!("{}", t!("jv.fail.init_create_dir_not_empty").trim()); + return; + } + + match LocalWorkspace::setup_local_workspace(path).await { + Ok(_) => { + println!("{}", t!("jv.success.init")); + } + Err(e) => { + eprintln!("{}", t!("jv.fail.init", error = e.to_string())); + } + } +} + +async fn is_directory_empty(path: &PathBuf) -> bool { + match fs::read_dir(path).await { + Ok(mut entries) => entries.next_entry().await.unwrap().is_none(), + Err(_) => false, + } +} + +async fn jv_here(_args: HereArgs) { + todo!() +} + +async fn jv_track(_args: TrackFileArgs) { + todo!() +} + +async fn jv_hold(_args: HoldFileArgs) { + todo!() +} + +async fn jv_throw(_args: ThrowFileArgs) { + todo!() +} + +async fn jv_move(_args: MoveFileArgs) { + todo!() +} + +async fn jv_export(_args: ExportFileArgs) { + todo!() +} + +async fn jv_import(_args: ImportFileArgs) { + todo!() +} + +async fn jv_account_add(user_dir: UserDirectory, args: AccountAddArgs) { + let member = Member::new(args.account_name.clone()); + + match user_dir.register_account(member).await { + Ok(_) => { + println!( + "{}", + t!("jv.success.account.add", account = args.account_name) + ); + } + Err(_) => { + eprintln!("{}", t!("jv.fail.account.add", account = args.account_name)); + } + } +} + +async fn jv_account_remove(user_dir: UserDirectory, args: AccountRemoveArgs) { + match user_dir.remove_account(&args.account_name) { + Ok(_) => { + println!( + "{}", + t!("jv.success.account.remove", account = args.account_name) + ); + } + Err(_) => { + eprintln!( + "{}", + t!("jv.fail.account.remove", account = args.account_name) + ); + } + } +} + +async fn jv_account_list(user_dir: UserDirectory, _args: AccountListArgs) { + match user_dir.account_ids() { + Ok(account_ids) => { + println!( + "{}", + md(t!( + "jv.success.account.list.header", + num = account_ids.len() + )) + ); + + let mut i = 0; + for account_id in account_ids { + println!("{}. {} {}", i + 1, &account_id, { + if user_dir.has_private_key(&account_id) { + t!("jv.success.account.list.status_has_key") + } else { + std::borrow::Cow::Borrowed("") + } + }); + i += 1; + } + } + Err(_) => { + eprintln!("{}", t!("jv.fail.account.list")); } } } + +async fn jv_account_as(user_dir: UserDirectory, args: SetLocalWorkspaceAccountArgs) { + // Account exist + let Ok(member) = user_dir.account(&args.account_name).await else { + eprintln!( + "{}", + t!("jv.fail.account.not_found", account = args.account_name) + ); + return; + }; + + let Some(_local_dir) = current_local_path() else { + eprintln!("{}", t!("jv.fail.workspace_not_found").trim()); + return; + }; + + let Ok(mut local_cfg) = LocalConfig::read().await else { + eprintln!("{}", md(t!("jv.fail.read_cfg"))); + return; + }; + + local_cfg.set_current_account(member.id()); + + let Ok(_) = LocalConfig::write(&local_cfg).await else { + eprintln!("{}", t!("jv.fail.write_cfg").trim()); + return; + }; + + println!( + "{}", + t!("jv.success.account.as", account = member.id()).trim() + ); +} + +async fn jv_account_move_key(user_dir: UserDirectory, args: MoveKeyToAccountArgs) { + // Key file exist + if !args.key_path.exists() { + eprintln!( + "{}", + t!("jv.fail.path_not_found", path = args.key_path.display()) + ); + return; + } + + // Account exist + let Ok(_member) = user_dir.account(&args.account_name).await else { + eprintln!( + "{}", + t!("jv.fail.account.not_found", account = args.account_name) + ); + return; + }; + + // Rename key file + match fs::rename( + args.key_path, + user_dir.account_private_key_path(&args.account_name), + ) + .await + { + Ok(_) => println!("{}", t!("jv.success.account.move_key")), + Err(_) => eprintln!("{}", t!("jv.fail.account.move_key")), + } +} + +async fn jv_direct(args: DirectArgs) { + if !args.confirm { + println!( + "{}", + t!("jv.confirm.direct", upstream = args.upstream).trim() + ); + confirm_hint_or(t!("common.confirm"), || exit(1)).await; + } + + let pool = client_registry::client_action_pool(); + let upstream = match socket_addr_helper::get_socket_addr(&args.upstream, PORT).await { + Ok(result) => result, + Err(e) => { + eprintln!( + "{}", + md(t!( + "jv.fail.parse.str_to_sockaddr", + str = &args.upstream.trim(), + err = e + )) + ); + return; + } + }; + + let Some(instance) = connect(upstream).await else { + // Since connect() function already printed error messages, we only handle the return here + return; + }; + + let ctx = ActionContext::local().insert_instance(instance); + + match proc_set_upstream_vault_action(&pool, ctx, upstream).await { + Err(e) => handle_err(e), + Ok(result) => match result { + SetUpstreamVaultActionResult::DirectedAndStained => { + println!( + "{}", + md(t!( + "jv.result.direct.directed_and_stained", + upstream = upstream + )) + ) + } + SetUpstreamVaultActionResult::AlreadyStained => { + eprintln!("{}", md(t!("jv.result.direct.already_stained"))) + } + SetUpstreamVaultActionResult::AuthorizeFailed(e) => { + println!( + "{}", + md(t!("jv.result.direct.directed_and_stained", err = e)) + ) + } + }, + }; +} + +async fn jv_unstain(args: UnstainArgs) { + let Some(_local_dir) = current_local_path() else { + eprintln!("{}", t!("jv.fail.workspace_not_found").trim()); + return; + }; + + let Ok(mut local_cfg) = LocalConfig::read().await else { + eprintln!("{}", md(t!("jv.fail.read_cfg"))); + return; + }; + + if !local_cfg.stained() { + eprintln!("{}", md(t!("jv.fail.unstain"))); + return; + } + + if !args.confirm { + println!( + "{}", + md(t!("jv.warn.unstain", upstream = local_cfg.vault_addr())) + ); + confirm_hint_or(t!("common.confirm"), || exit(1)).await; + } + + local_cfg.unstain(); + + let Ok(_) = LocalConfig::write(&local_cfg).await else { + eprintln!("{}", t!("jv.fail.write_cfg").trim()); + return; + }; + + println!("{}", md(t!("jv.success.unstain"))); +} + +async fn jv_docs(_args: DocsArgs) { + todo!() +} + +pub fn handle_err(err: TcpTargetError) { + eprintln!("{}", md(t!("jv.fail.from_just_version_control", err = err))) +} + +async fn connect(upstream: SocketAddr) -> Option<ConnectionInstance> { + // Create Socket + let socket = if upstream.is_ipv4() { + match TcpSocket::new_v4() { + Ok(socket) => socket, + Err(_) => { + eprintln!("{}", t!("jv.fail.create_socket").trim()); + return None; + } + } + } else { + match TcpSocket::new_v6() { + Ok(socket) => socket, + Err(_) => { + eprintln!("{}", t!("jv.fail.create_socket").trim()); + return None; + } + } + }; + + // Connect + let Ok(stream) = socket.connect(upstream).await else { + eprintln!("{}", t!("jv.fail.connection_failed").trim()); + return None; + }; + + Some(ConnectionInstance::from(stream)) +} |
