summaryrefslogtreecommitdiff
path: root/src/bin/jv.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/bin/jv.rs')
-rw-r--r--src/bin/jv.rs524
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))
+}