diff options
| -rw-r--r-- | Cargo.lock | 1 | ||||
| -rw-r--r-- | Cargo.toml | 1 | ||||
| -rw-r--r-- | resources/locales/jvn/en.yml | 247 | ||||
| -rw-r--r-- | resources/locales/jvn/zh-CN.yml | 247 | ||||
| -rw-r--r-- | src/bin/jvn.rs | 184 | ||||
| -rw-r--r-- | src/lib.rs | 3 | ||||
| -rw-r--r-- | src/macros.rs | 1 | ||||
| -rw-r--r-- | src/macros/command_flag.rs | 33 | ||||
| -rw-r--r-- | src/systems.rs | 1 | ||||
| -rw-r--r-- | src/systems/cmd/cmd_system.rs | 83 | ||||
| -rw-r--r-- | src/systems/cmd/processer.rs | 31 | ||||
| -rw-r--r-- | src/systems/debug.rs | 1 | ||||
| -rw-r--r-- | src/systems/debug/verbose_logger.rs | 43 | ||||
| -rw-r--r-- | templates/_override_renderer_dispatcher.rs.template | 4 |
14 files changed, 791 insertions, 89 deletions
@@ -1140,6 +1140,7 @@ dependencies = [ "cmd_system_macros", "colored", "crossterm", + "env_logger", "erased-serde", "just_enough_vcs", "just_fmt", @@ -76,6 +76,7 @@ chrono = "0.4" # Logging log = "0.4" +env_logger = "0.11" # Async tokio = { version = "1", features = ["full"] } diff --git a/resources/locales/jvn/en.yml b/resources/locales/jvn/en.yml index 801c735..a311eb3 100644 --- a/resources/locales/jvn/en.yml +++ b/resources/locales/jvn/en.yml @@ -45,7 +45,7 @@ process_error: prepare_error: io: | I/O error in preparation phase! - Error: %{error} + %{error} error: | Unknown error in preparation phase! @@ -85,7 +85,7 @@ prepare_error: execute_error: io: | I/O error in execution phase! - Error: %{error} + %{error} error: | Error in execution phase! @@ -94,7 +94,7 @@ execute_error: render_error: io: | I/O error in rendering phase! - Error: %{error} + %{error} error: | Error in rendering phase! @@ -113,3 +113,244 @@ render_error: Please use `jv -v -C` to get detailed version traceback and contact the developers. github: https://github.com/JustEnoughVCS/CommandLine/ + +io_error: + not_found: | + File or directory not found! + Please check if the path is correct or if the file exists. + %{raw_error} + + permission_denied: | + Permission denied! + Please check if you have sufficient permissions to access the file or directory. + %{raw_error} + + connection_refused: | + Connection refused! + Please check if the target address and port are correct and if the service is running. + %{raw_error} + + connection_reset: | + Connection reset! + The network connection was unexpectedly interrupted during transmission. + %{raw_error} + + host_unreachable: | + Host unreachable! + Please check your network connection and the status of the target host. + %{raw_error} + + network_unreachable: | + Network unreachable! + Please check your network connection and routing settings. + %{raw_error} + + connection_aborted: | + Connection aborted! + The network connection was interrupted during establishment. + %{raw_error} + + not_connected: | + Not connected to network! + Please check your network connection status. + %{raw_error} + + addr_in_use: | + Address already in use! + Please try a different address or port. + %{raw_error} + + addr_not_available: | + Address not available! + Please check if the address configuration is correct. + %{raw_error} + + network_down: | + Network is down! + Please check your network connection. + %{raw_error} + + broken_pipe: | + Broken pipe! + The communication pipe was interrupted during transmission. + %{raw_error} + + already_exists: | + File or directory already exists! + Please try a different name or delete the existing file. + %{raw_error} + + would_block: | + Operation would block! + The non-blocking operation could not be completed immediately. + %{raw_error} + + not_a_directory: | + Not a directory! + Please check if the path points to a directory. + %{raw_error} + + is_a_directory: | + Is a directory! + Please check if the path points to a file. + %{raw_error} + + directory_not_empty: | + Directory not empty! + Please empty the directory contents first. + %{raw_error} + + read_only_filesystem: | + Read-only filesystem! + Cannot write to a read-only filesystem. + %{raw_error} + + stale_network_file_handle: | + Stale network file handle! + Please re-establish the connection. + %{raw_error} + + invalid_input: | + Invalid input! + Please check if the input parameters are correct. + %{raw_error} + + invalid_data: | + Invalid data! + Please check the data format and content. + %{raw_error} + + timed_out: | + Operation timed out! + Please check your network connection or retry the operation. + %{raw_error} + + write_zero: | + Write zero bytes! + The write operation did not write any data. + %{raw_error} + + storage_full: | + Storage full! + Please free up disk space. + %{raw_error} + + not_seekable: | + File not seekable! + This file does not support seek operations. + %{raw_error} + + quota_exceeded: | + Quota exceeded! + Please check your disk quota settings. + %{raw_error} + + file_too_large: | + File too large! + Please check the file size limit. + %{raw_error} + + resource_busy: | + Resource busy! + Please try again later or release the occupied resource. + %{raw_error} + + executable_file_busy: | + Executable file busy! + Please wait for the program to finish running. + %{raw_error} + + deadlock: | + Deadlock! + Multiple operations are waiting for each other, preventing further execution. + %{raw_error} + + crosses_devices: | + Cross-device operation! + The operation involves multiple devices, which is not supported. + %{raw_error} + + too_many_links: | + Too many links! + The number of filesystem links exceeds the limit. + %{raw_error} + + invalid_filename: | + Invalid filename! + Please check if the filename meets system requirements. + %{raw_error} + + argument_list_too_long: | + Argument list too long! + Please reduce the number or length of arguments. + %{raw_error} + + interrupted: | + Operation interrupted! + The operation was interrupted by a signal. Please retry. + %{raw_error} + + unsupported: | + Operation not supported! + This operation is not supported by the current system or environment. + %{raw_error} + + unexpected_eof: | + Unexpected end of file! + Encountered an unexpected end-of-file marker while reading. + %{raw_error} + + out_of_memory: | + Out of memory! + System memory is insufficient to complete the operation. + %{raw_error} + + other: | + %{error} + +logger: + error: | + ERROR + warn: | + WARN + info: | + INFO + debug: | + DEBUG + trace: | + TRACE + +verbose: + setup_verbose: Starting `Verbose` mode + setup_i18n: Setting language to `%{lang}` + setup_renderer: Setting renderer to `%{renderer}` + no_error_logs: Disabling error output + help: Help flag entered, command will not be executed + confirmed: Confirmation flag entered, all commands will run without confirmation + + no_arguments: No command entered! Will output help information + + user_input: User input command `%{command}` + + process_success: Command executed successfully! + process_fail: Command execution failed! + + print_render_result: Result has been rendered + + cmd_process_start: Starting to search nodes to match command ... + cmd_match_no_node: Unable to find any matching command node + cmd_match_matched_single: Found matching command node `%{node}` + cmd_match_matched_multi: Matched multiple command nodes `%{nodes}`, selecting `${node} (most relevant)` + + cmd_process: Command execution starting! + render_with_specific_renderer: Result will be rendered with the default renderer + render_with_override_renderer: Result will be rendered with the override renderer (%{renderer}) + + cmd_process_parse: Command will be converted to `%{t}` + cmd_process_parse_failed: Failed to convert command to `%{t}`! + + cmd_process_prepare: Entering preparation phase, converting to `%{i}` and `%{c}` in parallel + cmd_process_prepare_failed: Preparation phase failed! + + cmd_process_exec: Entering execution phase + cmd_process_exec_failed: Execution phase failed! diff --git a/resources/locales/jvn/zh-CN.yml b/resources/locales/jvn/zh-CN.yml index 71953a9..7d2b52b 100644 --- a/resources/locales/jvn/zh-CN.yml +++ b/resources/locales/jvn/zh-CN.yml @@ -44,7 +44,7 @@ process_error: prepare_error: io: | 命令在准备阶段发生了 I/O 错误! - 错误信息:%{error} + %{error} error: | 命令在准备阶段发生未知错误! @@ -83,7 +83,7 @@ prepare_error: execute_error: io: | 命令在运行阶段发生了 I/O 错误! - 错误信息:%{error} + %{error} error: | 命令在运行阶段发生错误! @@ -92,7 +92,7 @@ execute_error: render_error: io: | 命令在渲染阶段发生了 I/O 错误! - 错误信息:%{error} + %{error} error: | 命令在渲染阶段发生错误! @@ -111,3 +111,244 @@ render_error: 请使用 `jv -v -C` 获得详细的版本追溯,并联系开发人员 github: https://github.com/JustEnoughVCS/CommandLine/ + +io_error: + not_found: | + 找不到文件或目录! + 请检查路径是否正确,或文件是否存在 + %{raw_error} + + permission_denied: | + 权限被拒绝! + 请检查您是否有足够的权限访问该文件或目录 + %{raw_error} + + connection_refused: | + 连接被拒绝! + 请检查目标地址和端口是否正确,以及服务是否正在运行 + %{raw_error} + + connection_reset: | + 连接被重置! + 网络连接在传输过程中被意外中断 + %{raw_error} + + host_unreachable: | + 主机不可达! + 请检查网络连接和目标主机状态 + %{raw_error} + + network_unreachable: | + 网络不可达! + 请检查网络连接和路由设置 + %{raw_error} + + connection_aborted: | + 连接被中止! + 网络连接在建立过程中被中断 + %{raw_error} + + not_connected: | + 未连接到网络! + 请检查网络连接状态 + %{raw_error} + + addr_in_use: | + 地址已被占用! + 请尝试使用其他地址或端口 + %{raw_error} + + addr_not_available: | + 地址不可用! + 请检查地址配置是否正确 + %{raw_error} + + network_down: | + 网络已断开! + 请检查网络连接 + %{raw_error} + + broken_pipe: | + 管道已损坏! + 通信管道在传输过程中被中断 + %{raw_error} + + already_exists: | + 文件或目录已存在! + 请尝试使用其他名称或删除现有文件 + %{raw_error} + + would_block: | + 操作将被阻塞! + 非阻塞操作无法立即完成 + %{raw_error} + + not_a_directory: | + 不是目录! + 请检查路径是否指向一个目录 + %{raw_error} + + is_a_directory: | + 是一个目录! + 请检查路径是否指向一个文件 + %{raw_error} + + directory_not_empty: | + 目录非空! + 请先清空目录内容 + %{raw_error} + + read_only_filesystem: | + 文件系统只读! + 无法写入只读文件系统 + %{raw_error} + + stale_network_file_handle: | + 网络文件句柄已失效! + 请重新建立连接 + %{raw_error} + + invalid_input: | + 输入无效! + 请检查输入参数是否正确 + %{raw_error} + + invalid_data: | + 数据无效! + 请检查数据格式和内容 + %{raw_error} + + timed_out: | + 操作超时! + 请检查网络连接或重试操作 + %{raw_error} + + write_zero: | + 写入零字节! + 写入操作未写入任何数据 + %{raw_error} + + storage_full: | + 存储空间已满! + 请清理磁盘空间 + %{raw_error} + + not_seekable: | + 文件不可寻址! + 该文件不支持寻址操作 + %{raw_error} + + quota_exceeded: | + 超出配额限制! + 请检查磁盘配额设置 + %{raw_error} + + file_too_large: | + 文件过大! + 请检查文件大小限制 + %{raw_error} + + resource_busy: | + 资源正忙! + 请稍后重试或释放占用资源 + %{raw_error} + + executable_file_busy: | + 可执行文件正忙! + 请等待程序结束运行 + %{raw_error} + + deadlock: | + 死锁! + 多个操作相互等待导致无法继续执行 + %{raw_error} + + crosses_devices: | + 跨设备操作! + 操作涉及多个设备,不支持此类操作 + %{raw_error} + + too_many_links: | + 链接过多! + 文件系统链接数量超出限制 + %{raw_error} + + invalid_filename: | + 文件名无效! + 请检查文件名是否符合系统要求 + %{raw_error} + + argument_list_too_long: | + 参数列表过长! + 请减少参数数量或长度 + %{raw_error} + + interrupted: | + 操作被中断! + 操作被信号中断,请重试 + %{raw_error} + + unsupported: | + 操作不支持! + 当前系统或环境不支持此操作 + %{raw_error} + + unexpected_eof: | + 意外的文件结束! + 在读取过程中遇到意外的文件结束符 + %{raw_error} + + out_of_memory: | + 内存不足! + 系统内存不足,无法完成操作 + %{raw_error} + + other: | + %{error} + +logger: + error: | + 错误 + warn: | + 警告 + info: | + 信息 + debug: | + 调试 + trace: | + 追踪 + +verbose: + setup_verbose: 启动 `Verbose` 模式 + setup_i18n: 设置语言为 `%{lang}` + setup_renderer: 设置渲染器为 `%{renderer}` + no_error_logs: 禁用错误输出 + help: 帮助符号已输入,将不会执行命令 + confirmed: 确认符号已输入,所有命令将无需确认即可运行 + + no_arguments: 无命令输入!将输出帮助信息 + + user_input: 用户输入命令:%{command} + + process_success: 命令执行成功! + process_fail: 命令执行失败! + + print_render_result: 结果已渲染完成: + + cmd_process_start: 开始搜寻节点以匹配命令 ... + cmd_match_no_node: 无法找到任何相匹配的命令节点 + cmd_match_matched_single: 找到相匹配的命令节点 `%{node}` + cmd_match_matched_multi: 匹配多个命令节点 `%{nodes}`,选择 `${node} (最相关)` + + cmd_process: 命令开始执行! + render_with_specific_renderer: 结果将用默认渲染器呈现 + render_with_override_renderer: 结果将用覆盖渲染器 (%{renderer}) 呈现 + + cmd_process_parse: 命令将转换为 `%{t}` + cmd_process_parse_failed: 命令转换为 `%{t}` 失败! + + cmd_process_prepare: 进入准备阶段,并行转换为 `%{i}` 和 `%{c}` + cmd_process_prepare_failed: 准备阶段失败! + + cmd_process_exec: 进入执行阶段 + cmd_process_exec_failed: 执行阶段失败! diff --git a/src/bin/jvn.rs b/src/bin/jvn.rs index 7ec1f4d..aa438d0 100644 --- a/src/bin/jvn.rs +++ b/src/bin/jvn.rs @@ -1,76 +1,87 @@ use std::process::exit; -use cli_utils::display::md; -use cli_utils::env::current_locales; -use cli_utils::levenshtein_distance::levenshtein_distance; -use just_enough_vcs_cli::systems::cmd::_commands::jv_cmd_nodes; -use just_enough_vcs_cli::systems::cmd::cmd_system::JVCommandContext; -use just_enough_vcs_cli::systems::cmd::errors::{CmdExecuteError, CmdPrepareError, CmdRenderError}; -use just_enough_vcs_cli::systems::cmd::{errors::CmdProcessError, processer::jv_cmd_process}; +use cli_utils::{display::md, env::current_locales, levenshtein_distance::levenshtein_distance}; +use just_enough_vcs_cli::{ + special_argument, special_flag, + systems::{ + cmd::{ + _commands::jv_cmd_nodes, + cmd_system::JVCommandContext, + errors::{CmdExecuteError, CmdPrepareError, CmdProcessError, CmdRenderError}, + processer::jv_cmd_process, + }, + debug::verbose_logger::init_verbose_logger, + }, +}; +use log::{LevelFilter, error, info, trace, warn}; use rust_i18n::{set_locale, t}; rust_i18n::i18n!("resources/locales/jvn", fallback = "en"); -macro_rules! special_flag { - ($args:expr, $flag:expr) => {{ - let flag = $flag; - let found = $args.iter().any(|arg| arg == flag); - $args.retain(|arg| arg != flag); - found - }}; -} - -macro_rules! special_argument { - ($args:expr, $flag:expr) => {{ - let flag = $flag; - let mut value: Option<String> = None; - let mut i = 0; - while i < $args.len() { - if $args[i] == flag { - if i + 1 < $args.len() { - value = Some($args[i + 1].clone()); - $args.remove(i + 1); - $args.remove(i); - } else { - value = None; - $args.remove(i); - } - break; - } - i += 1; - } - value - }}; -} - #[tokio::main] async fn main() { // Collect arguments let mut args: Vec<String> = std::env::args().skip(1).collect(); - // Init i18n + // Init colored + #[cfg(windows)] + colored::control::set_virtual_terminal(true).unwrap(); + + // Output control flags + let quiet = special_flag!(args, "--quiet") || special_flag!(args, "-q"); + let verbose = special_flag!(args, "--verbose") || special_flag!(args, "-V"); + let verbose_full = special_flag!(args, "--verbose-full"); + + // If `--verbose` or `--verbose-full` is enabled and `--quiet` is not enabled, turn on the logger + let filter = if (verbose || verbose_full) && !quiet { + let filter = if verbose_full { + LevelFilter::Trace + } else { + LevelFilter::Info + }; + Some(filter) + } else { + None + }; + init_verbose_logger(filter); + trace!("{}", t!("verbose.setup_verbose")); + + // I18n flags let lang = special_argument!(args, "--lang").unwrap_or(current_locales()); set_locale(&lang); + trace!("{}", t!("verbose.setup_i18n", lang = lang)); // Renderer let renderer_override = special_argument!(args, "--renderer").unwrap_or("default".to_string()); + trace!( + "{}", + t!("verbose.setup_renderer", renderer = renderer_override) + ); // Other flags let no_error_logs = special_flag!(args, "--no-error-logs"); - let quiet = special_flag!(args, "--quiet") || special_flag!(args, "-q"); let help = special_flag!(args, "--help") || special_flag!(args, "-h"); let confirmed = special_flag!(args, "--confirm") || special_flag!(args, "-C"); - // Init colored - #[cfg(windows)] - colored::control::set_virtual_terminal(true).unwrap(); + if no_error_logs { + trace!("{}", t!("verbose.no_error_logs")); + } + if help { + trace!("{}", t!("verbose.help")); + } + if confirmed { + trace!("{}", t!("verbose.confirmed")); + } // Handle help when no arguments provided if args.len() < 1 && help { + warn!("{}", t!("verbose.no_arguments")); eprintln!("{}", md(t!("help"))); exit(1); } + info!("{}", t!("verbose.user_input", command = args.join(" "))); + // Process commands let render_result = match jv_cmd_process( &args, @@ -79,8 +90,12 @@ async fn main() { ) .await { - Ok(result) => result, + Ok(result) => { + info!("{}", t!("verbose.process_success")); + result + } Err(e) => { + error!("{}", t!("verbose.process_fail")); if !no_error_logs { match e { CmdProcessError::Prepare(cmd_prepare_error) => { @@ -125,6 +140,7 @@ async fn main() { // Print if !quiet { + info!("{}", t!("verbose.print_render_result")); print!("{}", render_result); } } @@ -159,7 +175,10 @@ fn handle_no_matching_command_error(args: Vec<String>) { fn handle_prepare_error(cmd_prepare_error: CmdPrepareError) { match cmd_prepare_error { CmdPrepareError::Io(error) => { - eprintln!("{}", md(t!("prepare_error.io", error = error.to_string()))); + eprintln!( + "{}", + md(t!("prepare_error.io", error = display_io_error(error))) + ); } CmdPrepareError::Error(msg) => { eprintln!("{}", md(t!("prepare_error.error", error = msg))); @@ -213,7 +232,10 @@ fn handle_prepare_error(cmd_prepare_error: CmdPrepareError) { fn handle_execute_error(cmd_execute_error: CmdExecuteError) { match cmd_execute_error { CmdExecuteError::Io(error) => { - eprintln!("{}", md(t!("execute_error.io", error = error.to_string()))); + eprintln!( + "{}", + md(t!("execute_error.io", error = display_io_error(error))) + ); } CmdExecuteError::Prepare(cmd_prepare_error) => handle_prepare_error(cmd_prepare_error), CmdExecuteError::Error(msg) => { @@ -225,7 +247,10 @@ fn handle_execute_error(cmd_execute_error: CmdExecuteError) { fn handle_render_error(cmd_render_error: CmdRenderError) { match cmd_render_error { CmdRenderError::Io(error) => { - eprintln!("{}", md(t!("render_error.io", error = error.to_string()))); + eprintln!( + "{}", + md(t!("render_error.io", error = display_io_error(error))) + ); } CmdRenderError::Prepare(cmd_prepare_error) => handle_prepare_error(cmd_prepare_error), CmdRenderError::Execute(cmd_execute_error) => handle_execute_error(cmd_execute_error), @@ -258,3 +283,66 @@ fn handle_render_error(cmd_render_error: CmdRenderError) { } } } + +fn display_io_error(error: std::io::Error) -> std::borrow::Cow<'static, str> { + match error.kind() { + std::io::ErrorKind::NotFound => t!("io_error.not_found", raw_error = error), + std::io::ErrorKind::PermissionDenied => t!("io_error.permission_denied", raw_error = error), + std::io::ErrorKind::ConnectionRefused => { + t!("io_error.connection_refused", raw_error = error) + } + std::io::ErrorKind::ConnectionReset => t!("io_error.connection_reset", raw_error = error), + std::io::ErrorKind::HostUnreachable => t!("io_error.host_unreachable", raw_error = error), + std::io::ErrorKind::NetworkUnreachable => { + t!("io_error.network_unreachable", raw_error = error) + } + std::io::ErrorKind::ConnectionAborted => { + t!("io_error.connection_aborted", raw_error = error) + } + std::io::ErrorKind::NotConnected => t!("io_error.not_connected", raw_error = error), + std::io::ErrorKind::AddrInUse => t!("io_error.addr_in_use", raw_error = error), + std::io::ErrorKind::AddrNotAvailable => { + t!("io_error.addr_not_available", raw_error = error) + } + std::io::ErrorKind::NetworkDown => t!("io_error.network_down", raw_error = error), + std::io::ErrorKind::BrokenPipe => t!("io_error.broken_pipe", raw_error = error), + std::io::ErrorKind::AlreadyExists => t!("io_error.already_exists", raw_error = error), + std::io::ErrorKind::WouldBlock => t!("io_error.would_block", raw_error = error), + std::io::ErrorKind::NotADirectory => t!("io_error.not_a_directory", raw_error = error), + std::io::ErrorKind::IsADirectory => t!("io_error.is_a_directory", raw_error = error), + std::io::ErrorKind::DirectoryNotEmpty => { + t!("io_error.directory_not_empty", raw_error = error) + } + std::io::ErrorKind::ReadOnlyFilesystem => { + t!("io_error.read_only_filesystem", raw_error = error) + } + std::io::ErrorKind::StaleNetworkFileHandle => { + t!("io_error.stale_network_file_handle", raw_error = error) + } + std::io::ErrorKind::InvalidInput => t!("io_error.invalid_input", raw_error = error), + std::io::ErrorKind::InvalidData => t!("io_error.invalid_data", raw_error = error), + std::io::ErrorKind::TimedOut => t!("io_error.timed_out", raw_error = error), + std::io::ErrorKind::WriteZero => t!("io_error.write_zero", raw_error = error), + std::io::ErrorKind::StorageFull => t!("io_error.storage_full", raw_error = error), + std::io::ErrorKind::NotSeekable => t!("io_error.not_seekable", raw_error = error), + std::io::ErrorKind::QuotaExceeded => t!("io_error.quota_exceeded", raw_error = error), + std::io::ErrorKind::FileTooLarge => t!("io_error.file_too_large", raw_error = error), + std::io::ErrorKind::ResourceBusy => t!("io_error.resource_busy", raw_error = error), + std::io::ErrorKind::ExecutableFileBusy => { + t!("io_error.executable_file_busy", raw_error = error) + } + std::io::ErrorKind::Deadlock => t!("io_error.deadlock", raw_error = error), + std::io::ErrorKind::CrossesDevices => t!("io_error.crosses_devices", raw_error = error), + std::io::ErrorKind::TooManyLinks => t!("io_error.too_many_links", raw_error = error), + std::io::ErrorKind::InvalidFilename => t!("io_error.invalid_filename", raw_error = error), + std::io::ErrorKind::ArgumentListTooLong => { + t!("io_error.argument_list_too_long", raw_error = error) + } + std::io::ErrorKind::Interrupted => t!("io_error.interrupted", raw_error = error), + std::io::ErrorKind::Unsupported => t!("io_error.unsupported", raw_error = error), + std::io::ErrorKind::UnexpectedEof => t!("io_error.unexpected_eof", raw_error = error), + std::io::ErrorKind::OutOfMemory => t!("io_error.out_of_memory", raw_error = error), + std::io::ErrorKind::Other => t!("io_error.other", error = error.to_string()), + _ => t!("io_error.other", error = error.to_string()), + } +} @@ -13,3 +13,6 @@ pub mod systems; /// Commands pub mod cmds; + +/// Macros +pub mod macros; diff --git a/src/macros.rs b/src/macros.rs new file mode 100644 index 0000000..134b301 --- /dev/null +++ b/src/macros.rs @@ -0,0 +1 @@ +pub mod command_flag; diff --git a/src/macros/command_flag.rs b/src/macros/command_flag.rs new file mode 100644 index 0000000..894b3f4 --- /dev/null +++ b/src/macros/command_flag.rs @@ -0,0 +1,33 @@ +#[macro_export] +macro_rules! special_flag { + ($args:expr, $flag:expr) => {{ + let flag = $flag; + let found = $args.iter().any(|arg| arg == flag); + $args.retain(|arg| arg != flag); + found + }}; +} + +#[macro_export] +macro_rules! special_argument { + ($args:expr, $flag:expr) => {{ + let flag = $flag; + let mut value: Option<String> = None; + let mut i = 0; + while i < $args.len() { + if $args[i] == flag { + if i + 1 < $args.len() { + value = Some($args[i + 1].clone()); + $args.remove(i + 1); + $args.remove(i); + } else { + value = None; + $args.remove(i); + } + break; + } + i += 1; + } + value + }}; +} diff --git a/src/systems.rs b/src/systems.rs index 7f77ca5..2ca53be 100644 --- a/src/systems.rs +++ b/src/systems.rs @@ -1,2 +1,3 @@ pub mod cmd; +pub mod debug; pub mod render; diff --git a/src/systems/cmd/cmd_system.rs b/src/systems/cmd/cmd_system.rs index 1fe6e66..57aa15d 100644 --- a/src/systems/cmd/cmd_system.rs +++ b/src/systems/cmd/cmd_system.rs @@ -1,3 +1,6 @@ +use log::{error, info}; +use rust_i18n::t; + use crate::{ r_println, systems::{ @@ -6,11 +9,13 @@ use crate::{ }, }; use std::{ - any::{Any, TypeId}, + any::{Any, TypeId, type_name}, collections::HashMap, future::Future, }; +rust_i18n::i18n!("resources/locales/jvn", fallback = "en"); + pub struct JVCommandContext { pub help: bool, pub confirmed: bool, @@ -42,39 +47,17 @@ where return Err(CmdProcessError::RendererOverrideButRequestHelp); } + info!("{}", t!("verbose.cmd_process")); let (data, type_id) = Self::process(args, ctx).await?; - let renderer_override = renderer_override.as_str(); + let renderer = renderer_override.as_str(); // Serialize the data based on its concrete type - let render_result: Result<JVRenderResult, CmdRenderError> = if type_id - == std::any::TypeId::of::<crate::cmds::out::hex::JVHexOutput>() - { - let concrete_data = data - .downcast::<crate::cmds::out::hex::JVHexOutput>() - .map_err(|_| CmdProcessError::DowncastFailed)?; - include!("../render/_override_renderer_dispatcher.rs") - } else if type_id - == std::any::TypeId::of::<crate::cmds::out::mappings::JVMappingsOutput>() - { - let concrete_data = data - .downcast::<crate::cmds::out::mappings::JVMappingsOutput>() - .map_err(|_| CmdProcessError::DowncastFailed)?; - include!("../render/_override_renderer_dispatcher.rs") - } else if type_id == std::any::TypeId::of::<crate::cmds::out::status::JVStatusOutput>() - { - let concrete_data = data - .downcast::<crate::cmds::out::status::JVStatusOutput>() - .map_err(|_| CmdProcessError::DowncastFailed)?; - include!("../render/_override_renderer_dispatcher.rs") - } else { - return Err(CmdProcessError::NoMatchingCommand); - }; - - match render_result { - Ok(r) => Ok(r), - Err(e) => Err(CmdProcessError::Render(e)), - } + info!( + "{}", + t!("verbose.render_with_override_renderer", renderer = renderer) + ); + include!("../render/_override_renderer_entry.rs") } } @@ -93,7 +76,10 @@ where return Ok(r); } + info!("{}", t!("verbose.cmd_process")); let (data, id) = Self::process(args, ctx).await?; + + info!("{}", t!("verbose.render_with_specific_renderer")); match render(data, id).await { Ok(r) => Ok(r), Err(e) => Err(CmdProcessError::Render(e)), @@ -111,22 +97,53 @@ where full_args.extend(args); + info!( + "{}", + t!("verbose.cmd_process_parse", t = type_name::<Argument>()) + ); + let parsed_args = match Argument::try_parse_from(full_args) { Ok(args) => args, - Err(_) => return Err(CmdProcessError::ParseError(Self::get_help_str())), + Err(_) => { + error!( + "{}", + t!( + "verbose.cmd_process_parse_failed", + t = type_name::<Argument>() + ) + ); + return Err(CmdProcessError::ParseError(Self::get_help_str())); + } }; + info!( + "{}", + t!( + "verbose.cmd_process_prepare", + i = type_name::<Input>(), + c = type_name::<Collect>() + ) + ); + let (input, collect) = match tokio::try_join!( Self::prepare(&parsed_args, &ctx), Self::collect(&parsed_args, &ctx) ) { Ok((input, collect)) => (input, collect), - Err(e) => return Err(CmdProcessError::from(e)), + Err(e) => { + error!("{}", t!("verbose.cmd_process_prepare_failed")); + return Err(CmdProcessError::from(e)); + } }; + info!("{}", t!("verbose.cmd_process_exec")); + let data = match Self::exec(input, collect).await { Ok(output) => output, - Err(e) => return Err(CmdProcessError::from(e)), + Err(e) => { + error!("{}", t!("verbose.cmd_process_exec_failed")); + return Err(CmdProcessError::from(e)); + } }; Ok(data) diff --git a/src/systems/cmd/processer.rs b/src/systems/cmd/processer.rs index 196764a..0f68afb 100644 --- a/src/systems/cmd/processer.rs +++ b/src/systems/cmd/processer.rs @@ -1,13 +1,20 @@ +use log::{error, info, warn}; +use rust_i18n::t; + use crate::systems::cmd::_commands::{jv_cmd_nodes, jv_cmd_process_node}; use crate::systems::cmd::cmd_system::JVCommandContext; use crate::systems::cmd::errors::CmdProcessError; use crate::systems::render::renderer::JVRenderResult; +rust_i18n::i18n!("resources/locales/jvn", fallback = "en"); + pub async fn jv_cmd_process( args: &Vec<String>, ctx: JVCommandContext, renderer_override: String, ) -> Result<JVRenderResult, CmdProcessError> { + info!("{}", t!("verbose.cmd_process_start")); + let nodes = jv_cmd_nodes(); // Why add a space? @@ -27,10 +34,18 @@ pub async fn jv_cmd_process( match matching_nodes.len() { 0 => { // No matching node found + error!("{}", t!("verbose.cmd_match_no_node")); return Err(CmdProcessError::NoMatchingCommand); } 1 => { let matched_prefix = matching_nodes[0]; + + // DEBUG + info!( + "{}", + t!("verbose.cmd_match_matched_single", node = matched_prefix) + ); + let prefix_len = matched_prefix.split_whitespace().count(); let trimmed_args: Vec<String> = args.into_iter().cloned().skip(prefix_len).collect(); return jv_cmd_process_node(matched_prefix, trimmed_args, ctx, renderer_override).await; @@ -39,6 +54,22 @@ pub async fn jv_cmd_process( // Multiple matching nodes found // Find the node with the longest length (most specific match) let matched_prefix = matching_nodes.iter().max_by_key(|node| node.len()).unwrap(); + + // DEBUG + let nodes_str = matching_nodes + .iter() + .map(|s| s.as_str()) + .collect::<Vec<_>>() + .join(", "); + warn!( + "{}", + t!( + "verbose.cmd_match_matched_multi", + nodes = nodes_str, + node = matched_prefix + ) + ); + let prefix_len = matched_prefix.split_whitespace().count(); let trimmed_args: Vec<String> = args.into_iter().cloned().skip(prefix_len).collect(); return jv_cmd_process_node(matched_prefix, trimmed_args, ctx, renderer_override).await; diff --git a/src/systems/debug.rs b/src/systems/debug.rs new file mode 100644 index 0000000..be544e8 --- /dev/null +++ b/src/systems/debug.rs @@ -0,0 +1 @@ +pub mod verbose_logger; diff --git a/src/systems/debug/verbose_logger.rs b/src/systems/debug/verbose_logger.rs new file mode 100644 index 0000000..ceb8bd8 --- /dev/null +++ b/src/systems/debug/verbose_logger.rs @@ -0,0 +1,43 @@ +use chrono::Local; +use env_logger::Builder; +use log::Level; +use rust_i18n::t; +use std::io::Write; + +rust_i18n::i18n!("resources/locales/jvn", fallback = "en"); + +pub fn init_verbose_logger(level_filter: Option<log::LevelFilter>) { + let mut builder = match level_filter { + Some(f) => { + let mut b = Builder::new(); + b.filter_level(f); + b + } + None => return, + }; + + builder + .format(|buf, record| { + let now = Local::now(); + let timestamp = now.format("%y-%-m-%-d %H:%M:%S"); + let level = record.level(); + let args = record.args(); + + let (prefix, color_code) = match level { + Level::Error => (t!("logger.error").trim().to_string(), "\x1b[31m"), + Level::Warn => (t!("logger.warn").trim().to_string(), "\x1b[33m"), + Level::Info => (t!("logger.info").trim().to_string(), "\x1b[37m"), + Level::Debug => (t!("logger.debug").trim().to_string(), "\x1b[90m"), + Level::Trace => (t!("logger.trace").trim().to_string(), "\x1b[36m"), + }; + + let colored_prefix = if prefix.is_empty() { + String::new() + } else { + format!("{}[{}] {}: \x1b[0m", color_code, timestamp, prefix) + }; + + writeln!(buf, "{}{}", colored_prefix, args) + }) + .init(); +} diff --git a/templates/_override_renderer_dispatcher.rs.template b/templates/_override_renderer_dispatcher.rs.template index b22c957..80cabb6 100644 --- a/templates/_override_renderer_dispatcher.rs.template +++ b/templates/_override_renderer_dispatcher.rs.template @@ -1,5 +1,5 @@ // Auto generated by build.rs -match renderer_override { +match renderer { // MATCH // -- TEMPLATE START -- "<<NAME>>" => { @@ -8,7 +8,7 @@ match renderer_override { // -- TEMPLATE END -- _ => { return Err(CmdProcessError::Render(CmdRenderError::RendererNotFound( - renderer_override.to_string(), + renderer.to_string(), ))); } } |
