From 0cd19e64d4d255e45233255478ca3a0bd5c439ae Mon Sep 17 00:00:00 2001 From: 魏曹先生 <1992414357@qq.com> Date: Tue, 14 Oct 2025 17:17:48 +0800 Subject: feat: add internationalization support and new command-line tools - Add locale support with English and Chinese translations - Introduce new jv and jvv command-line tools - Replace jvc.rs with improved command structure - Add utility modules for language selection and markdown coloring - Update configuration and dependencies --- src/bin/jvv.rs | 434 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 434 insertions(+) create mode 100644 src/bin/jvv.rs (limited to 'src/bin/jvv.rs') diff --git a/src/bin/jvv.rs b/src/bin/jvv.rs new file mode 100644 index 0000000..828a093 --- /dev/null +++ b/src/bin/jvv.rs @@ -0,0 +1,434 @@ +use std::{env::set_current_dir, path::Path}; + +use clap::{Parser, Subcommand}; +use env_logger::{Builder, Target}; +use just_enough_vcs::{ + utils::{ + cfg_file::config::ConfigFile, + string_proc::{self, pascal_case}, + }, + vcs::{ + connection::action_service::server_entry, + constants::SERVER_FILE_VAULT, + current::current_vault_path, + data::{ + member::Member, + vault::{Vault, config::VaultConfig}, + }, + }, +}; +use just_enough_vcs_cli::utils::{lang_selector::current_locales, md_colored::md}; +use log::LevelFilter; +use rust_i18n::{set_locale, t}; +use tokio::fs::{self}; + +// Import i18n files +rust_i18n::i18n!("locales/help_docs", fallback = "en"); + +#[derive(Parser, Debug)] +#[command( + disable_help_flag = true, + disable_version_flag = true, + disable_help_subcommand = true, + help_template = "{all-args}" +)] +struct JustEnoughVcsVault { + #[command(subcommand)] + command: JustEnoughVcsCommand, +} + +#[derive(Subcommand, Debug)] +enum JustEnoughVcsCommand { + /// Get vault info in the current directory + Here(HereArgs), + + /// Create a new directory and initialize a vault + Create(CreateVaultArgs), + + /// Create a vault in the current directory + Init(InitVaultArgs), + + /// Member manage + #[command(subcommand)] + Member(MemberManage), + + /// Manage service + #[command(subcommand)] + Service(ServiceManage), +} + +#[derive(Subcommand, Debug)] +enum MemberManage { + /// Register a member to the vault + Register(MemberRegisterArgs), + + /// Remove a member from the vault + Remove(MemberRemoveArgs), + + /// List all members in the vault + List(MemberListArgs), + + /// Show help information + #[command(alias = "--help", alias = "-h")] + Help, +} + +#[derive(Subcommand, Debug)] +enum ServiceManage { + /// Listen connection at current vault + Listen(ListenArgs), + + /// Show help information + #[command(alias = "--help", alias = "-h")] + Help, +} + +#[derive(Parser, Debug)] +struct HereArgs { + /// Show help information + #[arg(short, long)] + help: bool, +} + +#[derive(Parser, Debug)] +struct CreateVaultArgs { + /// Show help information + #[arg(short, long)] + help: bool, + + /// Vault name to create + vault_name: String, +} + +#[derive(Parser, Debug)] +struct InitVaultArgs { + /// Show help information + #[arg(short, long)] + help: bool, +} + +#[derive(Parser, Debug)] +struct MemberRegisterArgs { + /// Show help information + #[arg(short, long)] + help: bool, + + /// Member name + member_name: String, +} + +#[derive(Parser, Debug)] +struct MemberRemoveArgs { + /// Show help information + #[arg(short, long)] + help: bool, + + /// Member name + member_name: String, +} + +#[derive(Parser, Debug)] +struct MemberListArgs { + /// Show help information + #[arg(short, long)] + help: bool, +} + +#[derive(Parser, Debug)] +struct ListenArgs { + /// Show help information + #[arg(short, long)] + help: bool, + + /// Disable logging + #[arg(short, long)] + no_log: bool, +} + +#[tokio::main] +async fn main() { + set_locale(¤t_locales()); + + let Ok(parser) = JustEnoughVcsVault::try_parse() else { + println!("{}", md(t!("jvv.help"))); + return; + }; + + match parser.command { + JustEnoughVcsCommand::Here(here_args) => { + if here_args.help { + println!("{}", md(t!("jvv.here"))); + return; + } + jvv_here(here_args).await; + } + JustEnoughVcsCommand::Create(create_vault_args) => { + if create_vault_args.help { + println!("{}", md(t!("jvv.create"))); + return; + } + jvv_create(create_vault_args).await; + } + JustEnoughVcsCommand::Init(init_vault_args) => { + if init_vault_args.help { + println!("{}", md(t!("jvv.init"))); + return; + } + jvv_init(init_vault_args).await; + } + JustEnoughVcsCommand::Member(member_manage) => { + let vault_cfg = VaultConfig::read() + .await + .expect(t!("jvv.fail.no_vault_here").trim()); + + let vault = match Vault::init_current_dir(vault_cfg) { + Some(vault) => vault, + None => { + eprintln!( + "{}", + t!("jvv.fail.jvcs", err = "Failed to initialize vault") + ); + return; + } + }; + + match member_manage { + MemberManage::Register(member_register_args) => { + if member_register_args.help { + println!("{}", md(t!("jvv.member"))); + return; + } + jvv_member_register(vault, member_register_args).await; + } + MemberManage::Remove(member_remove_args) => { + if member_remove_args.help { + println!("{}", md(t!("jvv.member"))); + return; + } + jvv_member_remove(vault, member_remove_args).await; + } + MemberManage::List(member_list_args) => { + if member_list_args.help { + println!("{}", md(t!("jvv.member"))); + return; + } + jvv_member_list(vault, member_list_args).await; + } + MemberManage::Help => { + println!("{}", md(t!("jvv.member"))); + return; + } + } + } + JustEnoughVcsCommand::Service(service_manage) => match service_manage { + ServiceManage::Listen(listen_args) => { + if listen_args.help { + println!("{}", md(t!("jvv.service"))); + return; + } + jvv_service_listen(listen_args).await; + } + ServiceManage::Help => { + println!("{}", md(t!("jvv.service"))); + return; + } + }, + } +} + +async fn jvv_here(_args: HereArgs) { + let Some(current_vault) = current_vault_path() else { + eprintln!("{}", t!("jvv.fail.here.cfg_not_found").trim()); + return; + }; + + // Read vault cfg + let vault_cfg_file = current_vault.join(SERVER_FILE_VAULT); + if !vault_cfg_file.exists() { + eprintln!("{}", t!("jvv.fail.here.cfg_not_found").trim()); + return; + } + + let vault_cfg = VaultConfig::read() + .await + .expect(t!("jvv.fail.here.cfg_not_found").trim()); + + // Get vault name + let vault_name = vault_cfg.vault_name(); + + // Success + println!("{}", md(t!("jvv.success.here.root", name = vault_name))) +} + +async fn jvv_init(_args: InitVaultArgs) { + let current_dir = std::env::current_dir().expect(t!("jvv.fail.std.current_dir").trim()); + if !current_dir.read_dir().unwrap().next().is_none() { + eprintln!("{}", t!("jvv.fail.init.not_empty")); + return; + } + + // Setup vault + let vault_name = current_dir + .file_name() + .expect(t!("jvv.fail.std.current_dir_name").trim()) + .to_string_lossy() + .to_string(); + let vault_name = pascal_case!(vault_name); + + if let Err(err) = Vault::setup_vault(current_dir.clone()).await { + eprintln!("{}", t!("jvv.fail.jvcs", err = err.to_string())); + return; + } + + // Read vault cfg + let mut vault_cfg = match VaultConfig::read().await { + Ok(cfg) => cfg, + Err(err) => { + eprintln!("{}", t!("jvv.fail.jvcs", err = err.to_string())); + return; + } + }; + + vault_cfg.change_name(vault_name); + if let Err(err) = VaultConfig::write(&vault_cfg).await { + eprintln!("{}", t!("jvv.fail.jvcs", err = err.to_string())); + return; + } + + // Success + println!( + "{}", + t!("jvv.success.init", name = current_dir.to_string_lossy()) + ) +} + +async fn jvv_create(args: CreateVaultArgs) { + let current_dir = std::env::current_dir().expect(t!("jvv.fail.std.current_dir").trim()); + let target_dir = current_dir.join(args.vault_name.clone()); + + // Create directory + if let Err(_) = fs::create_dir_all(&target_dir).await { + eprintln!( + "{}", + t!( + "jvv.fail.tokio.fs.create_dir", + dir = target_dir.to_string_lossy() + ) + ); + return; + } + + if !target_dir.read_dir().unwrap().next().is_none() { + eprintln!("{}", t!("jvv.fail.create.not_empty")); + return; + } + + // Setup vault + let vault_name = pascal_case!(args.vault_name); + if let Err(err) = Vault::setup_vault(target_dir.clone()).await { + eprintln!("{}", t!("jvv.fail.jvcs", err = err.to_string())); + return; + } + + // Enter target_dir + if let Err(_) = set_current_dir(&target_dir) { + eprintln!( + "{}", + t!( + "jvv.fail.std.set_current_dir", + dir = target_dir.to_string_lossy() + ) + ); + return; + } + + // Read vault cfg + let mut vault_cfg = match VaultConfig::read().await { + Ok(cfg) => cfg, + Err(err) => { + eprintln!("{}", t!("jvv.fail.jvcs", err = err.to_string())); + return; + } + }; + + vault_cfg.change_name(vault_name); + if let Err(err) = VaultConfig::write(&vault_cfg).await { + eprintln!("{}", t!("jvv.fail.jvcs", err = err.to_string())); + return; + } + + // Success + println!( + "{}", + t!("jvv.success.create", name = target_dir.to_string_lossy()) + ) +} + +async fn jvv_member_register(vault: Vault, args: MemberRegisterArgs) { + let register = vault + .register_member_to_vault(Member::new(args.member_name.clone())) + .await; + + if register.is_err() { + eprintln!("{}", t!("jvv.fail.member.register").trim()); + return; + } + + println!( + "{}", + t!("jvv.success.member.register", member = args.member_name) + ) +} + +async fn jvv_member_remove(vault: Vault, args: MemberRemoveArgs) { + let _ = vault.remove_member_from_vault(&args.member_name); + + println!( + "{}", + t!("jvv.success.member.remove", member = args.member_name) + ) +} + +async fn jvv_member_list(vault: Vault, _args: MemberListArgs) { + // Get id list + let ids = vault.member_ids().expect(t!("jvv.fail.member.list").trim()); + + // Print header + println!("{}", md(t!("jvv.success.member.list.header"))); + + // Print list + let mut i = 0; + for member in ids { + println!("{}. {}", i + 1, member); + i += 1; + } + + // Print footer + println!("{}", md(t!("jvv.success.member.list.footer", num = i))); +} + +async fn jvv_service_listen(args: ListenArgs) { + let Some(current_vault) = current_vault_path() else { + eprintln!("{}", t!("jvv.fail.here.cfg_not_found").trim()); + return; + }; + + if !args.no_log { + build_env_logger(current_vault.join("log.txt")); + } + + let _ = server_entry(current_vault).await; +} + +fn build_env_logger(log_path: impl AsRef) { + let mut builder = Builder::from_default_env(); + builder + .filter_level(LevelFilter::Info) + .target(Target::Stdout); + + let log_file = std::fs::File::create(log_path).expect("Failed to create log file"); + builder + .filter_module("just_enough_vcs", LevelFilter::Trace) + .target(Target::Pipe(Box::new(log_file))) + .init(); +} -- cgit