diff options
| -rw-r--r-- | Cargo.lock | 189 | ||||
| -rw-r--r-- | src/bin/jvii.rs | 8 | ||||
| -rw-r--r-- | src/bin/jvn.rs | 127 | ||||
| -rw-r--r-- | src/data.rs | 2 | ||||
| -rw-r--r-- | src/data/ipaddress_history.rs | 34 | ||||
| -rw-r--r-- | src/systems/cmd/errors.rs | 27 | ||||
| -rw-r--r-- | utils/src/legacy.rs | 9 | ||||
| -rw-r--r-- | utils/src/legacy/display.rs | 490 | ||||
| -rw-r--r-- | utils/src/legacy/env.rs | 104 | ||||
| -rw-r--r-- | utils/src/legacy/fs.rs | 40 | ||||
| -rw-r--r-- | utils/src/legacy/globber.rs | 292 | ||||
| -rw-r--r-- | utils/src/legacy/input.rs | 141 | ||||
| -rw-r--r-- | utils/src/legacy/levenshtein_distance.rs | 34 | ||||
| -rw-r--r-- | utils/src/legacy/logger.rs | 86 | ||||
| -rw-r--r-- | utils/src/legacy/push_version.rs | 30 | ||||
| -rw-r--r-- | utils/src/legacy/socket_addr_helper.rs | 194 | ||||
| -rw-r--r-- | utils/src/lib.rs | 3 |
17 files changed, 86 insertions, 1724 deletions
@@ -3,30 +3,6 @@ version = 4 [[package]] -name = "action_system" -version = "0.1.0" -dependencies = [ - "action_system_macros", - "serde", - "serde_json", - "tcp_connection", - "tokio", -] - -[[package]] -name = "action_system_macros" -version = "0.1.0" -dependencies = [ - "just_fmt", - "proc-macro2", - "quote", - "serde", - "serde_json", - "syn", - "tcp_connection", -] - -[[package]] name = "aes" version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -165,17 +141,6 @@ dependencies = [ ] [[package]] -name = "async-trait" -version = "0.1.89" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] name = "atty" version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -211,16 +176,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" [[package]] -name = "bincode2" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f49f6183038e081170ebbbadee6678966c7d54728938a3e7de7f4e780770318f" -dependencies = [ - "byteorder", - "serde", -] - -[[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -329,29 +284,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] -name = "cfg_file" -version = "0.1.0" -dependencies = [ - "async-trait", - "bincode2", - "cfg_file_derive", - "ron", - "serde", - "serde_json", - "serde_yaml", - "tokio", - "toml 0.9.8", -] - -[[package]] -name = "cfg_file_derive" -version = "0.1.0" -dependencies = [ - "quote", - "syn", -] - -[[package]] name = "chacha20" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -376,6 +308,16 @@ dependencies = [ ] [[package]] +name = "chunking_system" +version = "0.1.0" +dependencies = [ + "asset_system", + "just_fmt", + "size", + "tokio", +] + +[[package]] name = "cipher" version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1205,10 +1147,9 @@ dependencies = [ name = "just_enough_vcs" version = "0.0.0" dependencies = [ - "action_system", "asset_system", - "cfg_file", "chrono", + "chunking_system", "config_system", "constants", "data_struct", @@ -1222,8 +1163,6 @@ dependencies = [ "tcp_connection", "toml 0.9.8", "vault_system", - "vcs_actions", - "vcs_data", "vcs_docs", "workspace_system", ] @@ -1338,7 +1277,6 @@ checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" dependencies = [ "bitflags 2.9.4", "libc", - "redox_syscall", ] [[package]] @@ -1656,22 +1594,12 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ - "rand_chacha 0.3.1", + "rand_chacha", "rand_core 0.6.4", ] [[package]] name = "rand" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" -dependencies = [ - "rand_chacha 0.9.0", - "rand_core 0.9.3", -] - -[[package]] -name = "rand" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc266eb313df6c5c09c1c7b1fbe2510961e5bcd3add930c1e31f7ed9da0feff8" @@ -1692,16 +1620,6 @@ dependencies = [ ] [[package]] -name = "rand_chacha" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" -dependencies = [ - "ppv-lite86", - "rand_core 0.9.3", -] - -[[package]] name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1712,15 +1630,6 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" -dependencies = [ - "getrandom 0.3.4", -] - -[[package]] -name = "rand_core" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c8d0fd677905edcbeedbf2edb6494d676f0e98d54d5cf9bda0b061cb8fb8aba" @@ -2182,6 +2091,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" [[package]] +name = "size" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b6709c7b6754dca1311b3c73e79fcce40dd414c782c66d88e8823030093b02b" + +[[package]] name = "slab" version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2474,9 +2389,7 @@ version = "1.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" dependencies = [ - "getrandom 0.3.4", "js-sys", - "serde", "wasm-bindgen", ] @@ -2494,45 +2407,6 @@ dependencies = [ ] [[package]] -name = "vcs_actions" -version = "0.1.0" -dependencies = [ - "action_system", - "cfg_file", - "just_fmt", - "log", - "serde", - "serde_json", - "sha1_hash", - "tcp_connection", - "thiserror", - "tokio", - "vcs_data", -] - -[[package]] -name = "vcs_data" -version = "0.1.0" -dependencies = [ - "action_system", - "cfg_file", - "chrono", - "data_struct", - "dirs", - "just_fmt", - "rand 0.9.2", - "serde", - "sha1_hash", - "tcp_connection", - "tokio", - "uuid", - "vcs_docs", - "walkdir", - "whoami", - "winapi", -] - -[[package]] name = "vcs_docs" version = "0.1.0" @@ -2586,12 +2460,6 @@ dependencies = [ ] [[package]] -name = "wasite" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" - -[[package]] name = "wasm-bindgen" version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2685,27 +2553,6 @@ dependencies = [ ] [[package]] -name = "web-sys" -version = "0.3.81" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9367c417a924a74cae129e6a2ae3b47fabb1f8995595ab474029da749a8be120" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "whoami" -version = "1.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d4a4db5077702ca3015d3d02d74974948aba2ad9e12ab7df718ee64ccd7e97d" -dependencies = [ - "libredox", - "wasite", - "web-sys", -] - -[[package]] name = "winapi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" diff --git a/src/bin/jvii.rs b/src/bin/jvii.rs index fbd7139..6277619 100644 --- a/src/bin/jvii.rs +++ b/src/bin/jvii.rs @@ -5,9 +5,9 @@ use std::path::PathBuf; use std::time::Duration; use clap::Parser; -use cli_utils::legacy::display::display_width; -use cli_utils::legacy::display::md; -use cli_utils::legacy::env::current_locales; +use cli_utils::display::markdown::Markdown; +use cli_utils::display::str_width::display_width; +use cli_utils::env::locales::current_locales; use crossterm::{ QueueableCommand, cursor::MoveTo, @@ -309,7 +309,7 @@ impl Editor { } else { "".to_string() }, - md(t!("jvii.hints")) + t!("jvii.hints").to_string().markdown() ); stdout.queue(SetForegroundColor(Color::Black))?; diff --git a/src/bin/jvn.rs b/src/bin/jvn.rs index f35b845..0373552 100644 --- a/src/bin/jvn.rs +++ b/src/bin/jvn.rs @@ -5,8 +5,7 @@ use std::{ }; use cli_utils::{ - display::markdown::Markdown, - legacy::{display::md, env::current_locales, levenshtein_distance}, + display::markdown::Markdown, env::locales::current_locales, math::levenshtein_distance, }; use just_progress::{ progress, @@ -193,17 +192,27 @@ async fn main() { handle_render_error(cmd_render_error); } CmdProcessError::Error(error) => { - eprintln!("{}", md(t!("process_error.other", error = error))); + eprintln!( + "{}", + t!("process_error.other", error = error) + .to_string() + .markdown() + ); } CmdProcessError::NoNodeFound(node) => { - eprintln!("{}", md(t!("process_error.no_node_found", node = node))); + eprintln!( + "{}", + t!("process_error.no_node_found", node = node) + .to_string() + .markdown() + ); } CmdProcessError::NoMatchingCommand => { handle_no_matching_command_error(args); } CmdProcessError::ParseError(help) => { if help.trim().is_empty() { - eprintln!("{}", md(t!("process_error.parse_error"))); + eprintln!("{}", t!("process_error.parse_error").to_string().markdown()); } else { eprintln!("{}", help) } @@ -211,11 +220,16 @@ async fn main() { CmdProcessError::RendererOverrideButRequestHelp => { eprintln!( "{}", - md(t!("process_error.renderer_override_but_request_help")) + t!("process_error.renderer_override_but_request_help") + .to_string() + .markdown() ); } CmdProcessError::DowncastFailed => { - eprintln!("{}", md(t!("process_error.downcast_failed"))); + eprintln!( + "{}", + t!("process_error.downcast_failed").to_string().markdown() + ); } } } @@ -282,14 +296,21 @@ fn handle_no_matching_command_error(args: Vec<String>) { } } if similar_nodes.is_empty() { - eprintln!("{}", md(t!("process_error.no_matching_command"))); + eprintln!( + "{}", + t!("process_error.no_matching_command") + .to_string() + .markdown() + ); } else { eprintln!( "{}", - md(t!( + t!( "process_error.no_matching_command_but_similar", similars = similar_nodes[0] - )) + ) + .to_string() + .markdown() ); } } @@ -299,55 +320,19 @@ fn handle_prepare_error(cmd_prepare_error: CmdPrepareError) { CmdPrepareError::Io(error) => { eprintln!( "{}", - md(t!("prepare_error.io", error = display_io_error(error))) + t!("prepare_error.io", error = display_io_error(error)) + .to_string() + .markdown() ); } CmdPrepareError::Error(msg) => { - eprintln!("{}", md(t!("prepare_error.error", error = msg))); - } - CmdPrepareError::LocalWorkspaceNotFound => { - eprintln!("{}", md(t!("prepare_error.local_workspace_not_found"))); - } - CmdPrepareError::LocalConfigNotFound => { - eprintln!("{}", md(t!("prepare_error.local_config_not_found"))); - } - CmdPrepareError::LatestInfoNotFound => { - eprintln!("{}", md(t!("prepare_error.latest_info_not_found"))); - } - CmdPrepareError::LatestFileDataNotExist(member_id) => { eprintln!( "{}", - md(t!( - "prepare_error.latest_file_data_not_exist", - member_id = member_id - )) + t!("prepare_error.error", error = msg) + .to_string() + .markdown() ); } - CmdPrepareError::CachedSheetNotFound(sheet_name) => { - eprintln!( - "{}", - md(t!( - "prepare_error.cached_sheet_not_found", - sheet_name = sheet_name - )) - ); - } - CmdPrepareError::LocalSheetNotFound(member_id, sheet_name) => { - eprintln!( - "{}", - md(t!( - "prepare_error.local_sheet_not_found", - member_id = member_id, - sheet_name = sheet_name - )) - ); - } - CmdPrepareError::LocalStatusAnalyzeFailed => { - eprintln!("{}", md(t!("prepare_error.local_status_analyze_failed"))); - } - CmdPrepareError::NoSheetInUse => { - eprintln!("{}", md(t!("prepare_error.no_sheet_in_use"))); - } CmdPrepareError::EarlyOutput(_) => { // Early output is not an error // No additional handling needed, @@ -362,12 +347,19 @@ fn handle_execute_error(cmd_execute_error: CmdExecuteError) { CmdExecuteError::Io(error) => { eprintln!( "{}", - md(t!("execute_error.io", error = display_io_error(error))) + t!("execute_error.io", error = display_io_error(error)) + .to_string() + .markdown() ); } CmdExecuteError::Prepare(cmd_prepare_error) => handle_prepare_error(cmd_prepare_error), CmdExecuteError::Error(msg) => { - eprintln!("{}", md(t!("execute_error.error", error = msg))); + eprintln!( + "{}", + t!("execute_error.error", error = msg) + .to_string() + .markdown() + ); } } } @@ -377,37 +369,46 @@ fn handle_render_error(cmd_render_error: CmdRenderError) { CmdRenderError::Io(error) => { eprintln!( "{}", - md(t!("render_error.io", error = display_io_error(error))) + t!("render_error.io", error = display_io_error(error)) + .to_string() + .markdown() ); } CmdRenderError::Prepare(cmd_prepare_error) => handle_prepare_error(cmd_prepare_error), CmdRenderError::Execute(cmd_execute_error) => handle_execute_error(cmd_execute_error), CmdRenderError::Error(msg) => { - eprintln!("{}", md(t!("render_error.error", error = msg))); + eprintln!( + "{}", + t!("render_error.error", error = msg).to_string().markdown() + ); } CmdRenderError::SerializeFailed(error) => { eprintln!( "{}", - md(t!( - "render_error.serialize_failed", - error = error.to_string() - )) + t!("render_error.serialize_failed", error = error.to_string()) + .to_string() + .markdown() ); } CmdRenderError::RendererNotFound(renderer_name) => { eprintln!( "{}", - md(t!( + t!( "render_error.renderer_not_found", renderer_name = renderer_name - )) + ) + .to_string() + .markdown() ); } CmdRenderError::TypeMismatch { expected: _, actual: _, } => { - eprintln!("{}", md(t!("render_error.type_mismatch"))); + eprintln!( + "{}", + t!("render_error.type_mismatch").to_string().markdown() + ); } } } diff --git a/src/data.rs b/src/data.rs index d1c5d53..036fd98 100644 --- a/src/data.rs +++ b/src/data.rs @@ -1,4 +1,2 @@ #[allow(dead_code)] pub mod compile_info; - -pub mod ipaddress_history; diff --git a/src/data/ipaddress_history.rs b/src/data/ipaddress_history.rs deleted file mode 100644 index 142797d..0000000 --- a/src/data/ipaddress_history.rs +++ /dev/null @@ -1,34 +0,0 @@ -use just_enough_vcs::lib::env::current_cfg_dir; - -const IP_HISTORY_NAME: &str = "ip_history.txt"; - -pub struct IpAddressHistory { - pub recent_ip_address: Vec<String>, -} - -pub async fn get_recent_ip_address() -> Vec<String> { - if let Some(local) = current_cfg_dir() { - let path = local.join(IP_HISTORY_NAME); - match tokio::fs::read_to_string(path).await { - Ok(content) => content.lines().map(String::from).collect(), - Err(_) => Vec::new(), - } - } else { - Vec::new() - } -} - -pub async fn insert_recent_ip_address(ip: impl Into<String>) { - let ip = ip.into(); - if let Some(local) = current_cfg_dir() { - let path = local.join(IP_HISTORY_NAME); - let mut recent_ips = get_recent_ip_address().await; - recent_ips.retain(|existing_ip| existing_ip != &ip); - recent_ips.insert(0, ip); - if recent_ips.len() > 8 { - recent_ips.truncate(8); - } - let content = recent_ips.join("\n"); - let _ = tokio::fs::write(path, content).await; - } -} diff --git a/src/systems/cmd/errors.rs b/src/systems/cmd/errors.rs index efa12a9..4559651 100644 --- a/src/systems/cmd/errors.rs +++ b/src/systems/cmd/errors.rs @@ -1,5 +1,3 @@ -use just_enough_vcs::lib::data::{member::MemberId, sheet::SheetName}; - use crate::systems::cmd::cmd_system::AnyOutput; #[derive(thiserror::Error, Debug)] @@ -10,31 +8,6 @@ pub enum CmdPrepareError { #[error("{0}")] Error(String), - // Workspace Reader Errors - #[error("LocalWorkspace not found")] - LocalWorkspaceNotFound, - - #[error("LocalConfig not found")] - LocalConfigNotFound, - - #[error("LatestInfo not found")] - LatestInfoNotFound, - - #[error("LatestFileData of {0} not found")] - LatestFileDataNotExist(MemberId), - - #[error("CachedSheet `{0}` not found")] - CachedSheetNotFound(SheetName), - - #[error("LocalSheet `{0}/{1}` not found")] - LocalSheetNotFound(MemberId, SheetName), - - #[error("LocalStatusAnalyzeFailed")] - LocalStatusAnalyzeFailed, - - #[error("No sheet in use")] - NoSheetInUse, - #[error("Error occurred and returned early")] EarlyOutput(AnyOutput), } diff --git a/utils/src/legacy.rs b/utils/src/legacy.rs deleted file mode 100644 index 682c679..0000000 --- a/utils/src/legacy.rs +++ /dev/null @@ -1,9 +0,0 @@ -pub mod display; -pub mod env; -pub mod fs; -pub mod globber; -pub mod input; -pub mod levenshtein_distance; -pub mod logger; -pub mod push_version; -pub mod socket_addr_helper; diff --git a/utils/src/legacy/display.rs b/utils/src/legacy/display.rs deleted file mode 100644 index 57f4f5b..0000000 --- a/utils/src/legacy/display.rs +++ /dev/null @@ -1,490 +0,0 @@ -#![allow(clippy::all)] - -use colored::*; -use just_enough_vcs::lib::data::sheet::SheetMappingMetadata; -use std::{ - collections::{BTreeMap, HashMap, VecDeque}, - path::PathBuf, -}; - -pub struct SimpleTable { - items: Vec<String>, - line: Vec<Vec<String>>, - length: Vec<usize>, - padding: usize, -} - -impl SimpleTable { - /// Create a new Table - pub fn new(items: Vec<impl Into<String>>) -> Self { - Self::new_with_padding(items, 2) - } - - /// Create a new Table with padding - pub fn new_with_padding(items: Vec<impl Into<String>>, padding: usize) -> Self { - let items: Vec<String> = items.into_iter().map(|v| v.into()).collect(); - let mut length = Vec::with_capacity(items.len()); - - for item in &items { - length.push(display_width(item)); - } - - SimpleTable { - items, - padding, - line: Vec::new(), - length, - } - } - - /// Push a new row of items to the table - pub fn push_item(&mut self, items: Vec<impl Into<String>>) { - let items: Vec<String> = items.into_iter().map(|v| v.into()).collect(); - - let mut processed_items = Vec::with_capacity(self.items.len()); - - for i in 0..self.items.len() { - if i < items.len() { - processed_items.push(items[i].clone()); - } else { - processed_items.push(String::new()); - } - } - - for (i, d) in processed_items.iter().enumerate() { - let d_len = display_width(d); - if d_len > self.length[i] { - self.length[i] = d_len; - } - } - - self.line.push(processed_items); - } - - /// Insert a new row of items at the specified index - pub fn insert_item(&mut self, index: usize, items: Vec<impl Into<String>>) { - let items: Vec<String> = items.into_iter().map(|v| v.into()).collect(); - - let mut processed_items = Vec::with_capacity(self.items.len()); - - for i in 0..self.items.len() { - if i < items.len() { - processed_items.push(items[i].clone()); - } else { - processed_items.push(String::new()); - } - } - - for (i, d) in processed_items.iter().enumerate() { - let d_len = display_width(d); - if d_len > self.length[i] { - self.length[i] = d_len; - } - } - - self.line.insert(index, processed_items); - } - - /// Get the current maximum column widths - fn get_column_widths(&self) -> &[usize] { - &self.length - } -} - -impl std::fmt::Display for SimpleTable { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let column_widths = self.get_column_widths(); - - // Build the header row - let header: Vec<String> = self - .items - .iter() - .enumerate() - .map(|(i, item)| { - let target_width = column_widths[i] + self.padding; - let current_width = display_width(item); - let space_count = target_width - current_width; - let space = " ".repeat(space_count); - let result = format!("{}{}", item, space); - result - }) - .collect(); - writeln!(f, "{}", header.join(""))?; - - // Build each data row - for row in &self.line { - let formatted_row: Vec<String> = row - .iter() - .enumerate() - .map(|(i, cell)| { - let target_width = column_widths[i] + self.padding; - let current_width = display_width(cell); - let space_count = target_width - current_width; - let spaces = " ".repeat(space_count); - let result = format!("{}{}", cell, spaces); - result - }) - .collect(); - writeln!(f, "{}", formatted_row.join(""))?; - } - - Ok(()) - } -} - -pub fn display_width(s: &str) -> usize { - // Filter out ANSI escape sequences before calculating width - let filtered_bytes = strip_ansi_escapes::strip(s); - let filtered_str = match std::str::from_utf8(&filtered_bytes) { - Ok(s) => s, - Err(_) => s, // Fallback to original string if UTF-8 conversion fails - }; - - let mut width = 0; - for c in filtered_str.chars() { - if c.is_ascii() { - width += 1; - } else { - width += 2; - } - } - width -} - -/// Convert byte size to a human-readable string format -/// -/// Automatically selects the appropriate unit (B, KB, MB, GB, TB) based on the byte size -/// and formats it as a string with two decimal places -pub fn size_str(total_size: usize) -> String { - if total_size < 1024 { - format!("{} B", total_size) - } else if total_size < 1024 * 1024 { - format!("{:.2} KB", total_size as f64 / 1024.0) - } else if total_size < 1024 * 1024 * 1024 { - format!("{:.2} MB", total_size as f64 / (1024.0 * 1024.0)) - } else if total_size < 1024 * 1024 * 1024 * 1024 { - format!("{:.2} GB", total_size as f64 / (1024.0 * 1024.0 * 1024.0)) - } else { - format!( - "{:.2} TB", - total_size as f64 / (1024.0 * 1024.0 * 1024.0 * 1024.0) - ) - } -} - -// Convert the Markdown formatted text into a format supported by the command line -pub fn md(text: impl AsRef<str>) -> String { - let text = text.as_ref().trim(); - let mut result = String::new(); - let mut color_stack: VecDeque<String> = VecDeque::new(); - - let mut i = 0; - let chars: Vec<char> = text.chars().collect(); - - while i < chars.len() { - // Check for escape character \ - if chars[i] == '\\' && i + 1 < chars.len() { - let escaped_char = chars[i + 1]; - // Only escape specific characters - if matches!(escaped_char, '*' | '<' | '>' | '`') { - let mut escaped_text = escaped_char.to_string(); - - // Apply current color stack - for color in color_stack.iter().rev() { - escaped_text = apply_color(&escaped_text, color); - } - - result.push_str(&escaped_text); - i += 2; - continue; - } - } - - // Check for color tag start [[color]] - if i + 1 < chars.len() && chars[i] == '[' && chars[i + 1] == '[' { - let mut j = i + 2; - while j < chars.len() - && !(chars[j] == ']' && j + 1 < chars.len() && chars[j + 1] == ']') - { - j += 1; - } - - if j + 1 < chars.len() { - let tag_content: String = chars[i + 2..j].iter().collect(); - - // Check if it's a closing tag [[/]] - if tag_content == "/" { - color_stack.pop_back(); - i = j + 2; - continue; - } - - // It's a color tag - color_stack.push_back(tag_content.clone()); - i = j + 2; - continue; - } - } - - // Check for bold **text** - if i + 1 < chars.len() && chars[i] == '*' && chars[i + 1] == '*' { - let mut j = i + 2; - while j + 1 < chars.len() && !(chars[j] == '*' && chars[j + 1] == '*') { - j += 1; - } - - if j + 1 < chars.len() { - let bold_text: String = chars[i + 2..j].iter().collect(); - let mut formatted_text = bold_text.bold().to_string(); - - // Apply current color stack - for color in color_stack.iter().rev() { - formatted_text = apply_color(&formatted_text, color); - } - - result.push_str(&formatted_text); - i = j + 2; - continue; - } - } - - // Check for italic *text* - if chars[i] == '*' { - let mut j = i + 1; - while j < chars.len() && chars[j] != '*' { - j += 1; - } - - if j < chars.len() { - let italic_text: String = chars[i + 1..j].iter().collect(); - let mut formatted_text = italic_text.italic().to_string(); - - // Apply current color stack - for color in color_stack.iter().rev() { - formatted_text = apply_color(&formatted_text, color); - } - - result.push_str(&formatted_text); - i = j + 1; - continue; - } - } - - // Check for angle-bracketed content <text> - if chars[i] == '<' { - let mut j = i + 1; - while j < chars.len() && chars[j] != '>' { - j += 1; - } - - if j < chars.len() { - // Include the angle brackets in the output - let angle_text: String = chars[i..=j].iter().collect(); - let mut formatted_text = angle_text.cyan().to_string(); - - // Apply current color stack - for color in color_stack.iter().rev() { - formatted_text = apply_color(&formatted_text, color); - } - - result.push_str(&formatted_text); - i = j + 1; - continue; - } - } - - // Check for inline code `text` - if chars[i] == '`' { - let mut j = i + 1; - while j < chars.len() && chars[j] != '`' { - j += 1; - } - - if j < chars.len() { - // Include the backticks in the output - let code_text: String = chars[i..=j].iter().collect(); - let mut formatted_text = code_text.green().to_string(); - - // Apply current color stack - for color in color_stack.iter().rev() { - formatted_text = apply_color(&formatted_text, color); - } - - result.push_str(&formatted_text); - i = j + 1; - continue; - } - } - - // Regular character - let mut current_char = chars[i].to_string(); - - // Apply current color stack - for color in color_stack.iter().rev() { - current_char = apply_color(¤t_char, color); - } - - result.push_str(¤t_char); - i += 1; - } - - result -} - -// Helper function to apply color to text -fn apply_color(text: impl AsRef<str>, color_name: impl AsRef<str>) -> String { - let text = text.as_ref(); - let color_name = color_name.as_ref(); - match color_name { - // Normal colors - "black" => text.black().to_string(), - "red" => text.red().to_string(), - "green" => text.green().to_string(), - "yellow" => text.yellow().to_string(), - "blue" => text.blue().to_string(), - "magenta" => text.magenta().to_string(), - "cyan" => text.cyan().to_string(), - "white" => text.white().to_string(), - "bright_black" => text.bright_black().to_string(), - "bright_red" => text.bright_red().to_string(), - "bright_green" => text.bright_green().to_string(), - "bright_yellow" => text.bright_yellow().to_string(), - "bright_blue" => text.bright_blue().to_string(), - "bright_magenta" => text.bright_magenta().to_string(), - "bright_cyan" => text.bright_cyan().to_string(), - "bright_white" => text.bright_white().to_string(), - - // Short aliases for bright colors - "b_black" => text.bright_black().to_string(), - "b_red" => text.bright_red().to_string(), - "b_green" => text.bright_green().to_string(), - "b_yellow" => text.bright_yellow().to_string(), - "b_blue" => text.bright_blue().to_string(), - "b_magenta" => text.bright_magenta().to_string(), - "b_cyan" => text.bright_cyan().to_string(), - "b_white" => text.bright_white().to_string(), - - // Gray colors using truecolor - "gray" | "grey" => text.truecolor(128, 128, 128).to_string(), - "bright_gray" | "bright_grey" => text.truecolor(192, 192, 192).to_string(), - "b_gray" | "b_grey" => text.truecolor(192, 192, 192).to_string(), - - // Default to white if color not recognized - _ => 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); - } -} diff --git a/utils/src/legacy/env.rs b/utils/src/legacy/env.rs deleted file mode 100644 index b4ad089..0000000 --- a/utils/src/legacy/env.rs +++ /dev/null @@ -1,104 +0,0 @@ -use std::path::PathBuf; - -/// Returns the current locale string based on environment variables. -/// -/// The function checks for locale settings in the following order: -/// 1. JV_LANG environment variable -/// 2. APP_LANG environment variable -/// 3. LANG environment variable (extracts base language before dot and replaces underscores with hyphens) -/// 4. Defaults to "en" if no locale environment variables are found -/// -/// # Returns -/// A String containing the detected locale code -pub fn current_locales() -> String { - if let Ok(lang) = std::env::var("JV_LANG") { - return lang; - } - - if let Ok(lang) = std::env::var("APP_LANG") { - return lang; - } - - if let Ok(lang) = std::env::var("LANG") { - if let Some(base_lang) = lang.split('.').next() { - return base_lang.replace('_', "-"); - } - return lang; - } - - "en".to_string() -} - -/// Checks if auto update is enabled based on environment variables. -/// -/// The function checks the JV_AUTO_UPDATE environment variable and compares -/// its value (after trimming and converting to lowercase) against known -/// positive and negative values. -/// -/// # Returns -/// `true` if the value matches "yes", "y", or "true" -/// `false` if the value matches "no", "n", or "false", or if the variable is not set -pub fn enable_auto_update() -> bool { - if let Ok(auto_update) = std::env::var("JV_AUTO_UPDATE") { - let normalized = auto_update.trim().to_lowercase(); - match normalized.as_str() { - "yes" | "y" | "true" => return true, - "no" | "n" | "false" => return false, - _ => {} - } - } - false -} - -/// Gets the auto update expiration time based on environment variables. -/// -/// The function checks the JV_OUTDATED_MINUTES environment variable. -/// Requires JV_AUTO_UPDATE to be enabled. -/// Next time the `jv` command is used, if the content is outdated, `jv update` will be automatically executed. -/// -/// # Returns -/// - When the set number is < 0, timeout-based update is disabled -/// - When the set number = 0, update runs every time (not recommended) -/// - When the set number > 0, update according to the specified time -/// - If not set or conversion error occurs, the default is -1 -pub fn auto_update_outdate() -> i64 { - if !enable_auto_update() { - return -1; - } - - match std::env::var("JV_OUTDATED_MINUTES") { - Ok(value) => value.trim().parse::<i64>().unwrap_or(-1), - Err(_) => -1, - } -} - -/// Gets the default text editor based on environment variables. -/// -/// The function checks the JV_TEXT_EDITOR and EDITOR environment variables -/// and returns their values if they are set. If neither variable is set, -/// it returns "jvii" as the default editor. -/// -/// # Returns -/// A String containing the default text editor -pub async fn get_default_editor() -> String { - if let Ok(editor) = std::env::var("JV_TEXT_EDITOR") { - return editor; - } - - if let Ok(editor) = std::env::var("EDITOR") { - return editor; - } - - "jvii".to_string() -} - -/// Get temporary file path -pub fn current_tempfile_path(name: &str) -> Option<PathBuf> { - dirs::config_local_dir().map(|path| { - if cfg!(target_os = "linux") { - path.join("jvcs").join(".temp").join(name) - } else { - path.join("JustEnoughVCS").join(".temp").join(name) - } - }) -} diff --git a/utils/src/legacy/fs.rs b/utils/src/legacy/fs.rs deleted file mode 100644 index 0050cf1..0000000 --- a/utils/src/legacy/fs.rs +++ /dev/null @@ -1,40 +0,0 @@ -pub async fn move_across_partitions( - source_path: impl AsRef<std::path::Path>, - dest_path: impl AsRef<std::path::Path>, -) -> Result<(), std::io::Error> { - let source_path = source_path.as_ref(); - let dest_path = dest_path.as_ref(); - if !source_path.exists() { - return Err(std::io::Error::new( - std::io::ErrorKind::NotFound, - "Source file does not exist", - )); - } - - if let Ok(()) = std::fs::rename(source_path, dest_path) { - return Ok(()); - } - - std::fs::copy(source_path, dest_path)?; - std::fs::remove_file(source_path)?; - - Ok(()) -} - -pub async fn copy_across_partitions( - source_path: impl AsRef<std::path::Path>, - dest_path: impl AsRef<std::path::Path>, -) -> Result<(), std::io::Error> { - let source_path = source_path.as_ref(); - let dest_path = dest_path.as_ref(); - if !source_path.exists() { - return Err(std::io::Error::new( - std::io::ErrorKind::NotFound, - "Source file does not exist", - )); - } - - std::fs::copy(source_path, dest_path)?; - - Ok(()) -} diff --git a/utils/src/legacy/globber.rs b/utils/src/legacy/globber.rs deleted file mode 100644 index b6a3032..0000000 --- a/utils/src/legacy/globber.rs +++ /dev/null @@ -1,292 +0,0 @@ -use std::{io::Error, path::PathBuf, str::FromStr}; - -use just_fmt::fmt_path::fmt_path_str; - -use crate::legacy::globber::constants::{SPLIT_STR, get_base_dir_current}; - -pub struct Globber { - pattern: String, - base: PathBuf, - names: Vec<String>, -} - -#[allow(dead_code)] -impl Globber { - pub fn new(pattern: String, base: PathBuf) -> Self { - Self { - pattern, - base, - names: Vec::new(), - } - } - - pub fn names(&self) -> Vec<&String> { - self.names.iter().collect() - } - - pub fn base(&self) -> &PathBuf { - &self.base - } - - pub fn into_names(self) -> Vec<String> { - self.names - } - - pub fn paths(&self) -> Vec<PathBuf> { - self.names.iter().map(|n| self.base.join(n)).collect() - } - - pub fn glob<F>(mut self, get_names: F) -> Result<Self, std::io::Error> - where - F: Fn(PathBuf) -> Vec<GlobItem>, - { - let full_path = format!("{}{}{}", self.base.display(), SPLIT_STR, self.pattern); - - let (path, pattern) = if let Some(last_split) = full_path.rfind(SPLIT_STR) { - let (path_part, pattern_part) = full_path.split_at(last_split); - let mut path = path_part.to_string(); - if !path.ends_with(SPLIT_STR) { - path.push_str(SPLIT_STR); - } - let Ok(result) = fmt_path_str(&path) else { - return Err(Error::new( - std::io::ErrorKind::InvalidInput, - format!("Invalid path: \"{}\"", &path), - )); - }; - (result, pattern_part[SPLIT_STR.len()..].to_string()) - } else { - (String::default(), full_path) - }; - - self.base = match PathBuf::from_str(&path) { - Ok(r) => r, - Err(_) => { - return Err(Error::new( - std::io::ErrorKind::InvalidInput, - format!("Invalid path: \"{}\"", &path), - )); - } - }; - - let pattern = if pattern.is_empty() || pattern == "." { - "*".to_string() - } else if pattern.ends_with(SPLIT_STR) { - format!("{}*", pattern) - } else { - pattern - }; - - if !pattern.contains('*') && !pattern.contains('?') { - self.names = vec![pattern]; - return Ok(self); - } - - let mut collected = Vec::new(); - - collect_files(&path.into(), "./".to_string(), &mut collected, &get_names); - fn collect_files<F>( - base: &PathBuf, - current: String, - file_names: &mut Vec<String>, - get_names: &F, - ) where - F: Fn(PathBuf) -> Vec<GlobItem>, - { - let current_path = if current.is_empty() { - base.clone() - } else { - base.join(¤t) - }; - - let items = get_names(current_path); - for item in items { - match item { - GlobItem::File(file_name) => { - let relative_path = { - fmt_path_str(format!("{}{}{}", current, SPLIT_STR, file_name)) - .unwrap_or_default() - }; - file_names.push(relative_path) - } - GlobItem::Directory(dir_name) => { - let new_current = { - fmt_path_str(format!("{}{}{}", current, SPLIT_STR, dir_name)) - .unwrap_or_default() - }; - collect_files(base, new_current, file_names, get_names); - } - } - } - } - - self.names = collected - .iter() - .filter_map(|name| match_pattern(name, &pattern)) - .collect(); - - Ok(self) - } -} - -fn match_pattern(name: &str, pattern: &str) -> Option<String> { - if pattern.is_empty() { - return None; - } - - let name_chars: Vec<char> = name.chars().collect(); - let pattern_chars: Vec<char> = pattern.chars().collect(); - - let mut name_idx = 0; - let mut pattern_idx = 0; - let mut star_idx = -1; - let mut match_idx = -1; - - while name_idx < name_chars.len() { - if pattern_idx < pattern_chars.len() - && (pattern_chars[pattern_idx] == '?' - || pattern_chars[pattern_idx] == name_chars[name_idx]) - { - name_idx += 1; - pattern_idx += 1; - } else if pattern_idx < pattern_chars.len() && pattern_chars[pattern_idx] == '*' { - star_idx = pattern_idx as i32; - match_idx = name_idx as i32; - pattern_idx += 1; - } else if star_idx != -1 { - pattern_idx = (star_idx + 1) as usize; - match_idx += 1; - name_idx = match_idx as usize; - } else { - return None; - } - } - - while pattern_idx < pattern_chars.len() && pattern_chars[pattern_idx] == '*' { - pattern_idx += 1; - } - - if pattern_idx == pattern_chars.len() { - Some(name.to_string()) - } else { - None - } -} - -impl<T: AsRef<str>> From<T> for Globber { - fn from(pattern: T) -> Self { - let (base_dir, pattern) = get_base_dir_current(pattern.as_ref().to_string()); - Self::new(pattern, base_dir) - } -} - -#[derive(Debug, Clone)] -pub enum GlobItem { - File(String), - Directory(String), -} - -impl PartialEq for GlobItem { - fn eq(&self, other: &Self) -> bool { - match (self, other) { - (GlobItem::File(a), GlobItem::File(b)) => a == b, - (GlobItem::Directory(a), GlobItem::Directory(b)) => a == b, - _ => false, - } - } -} - -impl std::fmt::Display for GlobItem { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - GlobItem::File(name) => write!(f, "{}", name), - GlobItem::Directory(name) => write!(f, "{}", name), - } - } -} - -impl std::hash::Hash for GlobItem { - fn hash<H: std::hash::Hasher>(&self, state: &mut H) { - match self { - GlobItem::File(name) => { - state.write_u8(0); - name.hash(state); - } - GlobItem::Directory(name) => { - state.write_u8(1); - name.hash(state); - } - } - } -} - -impl Eq for GlobItem {} - -pub mod constants { - use std::{env::current_dir, path::PathBuf}; - - #[cfg(unix)] - pub(crate) const CURRENT_DIR_PREFIX: &str = "./"; - #[cfg(windows)] - pub(crate) const CURRENT_DIR_PREFIX: &str = ".\\"; - - #[cfg(unix)] - pub(crate) const USER_DIR_PREFIX: &str = "~"; - #[cfg(windows)] - pub(crate) const USER_DIR_PREFIX: &str = "~\\"; - - #[cfg(unix)] - pub(crate) const ROOT_DIR_PREFIX: &str = "/"; - #[cfg(windows)] - pub(crate) const ROOT_DIR_PREFIX: &str = "\\"; - - #[cfg(unix)] - pub(crate) const SPLIT_STR: &str = "/"; - #[cfg(windows)] - pub(crate) const SPLIT_STR: &str = "\\"; - - pub fn get_base_dir_current(input: String) -> (PathBuf, String) { - get_base_dir(input, current_dir().unwrap_or_default()) - } - - pub fn get_base_dir(input: String, current_dir: PathBuf) -> (PathBuf, String) { - if let Some(remaining) = input.strip_prefix(CURRENT_DIR_PREFIX) { - (current_dir, remaining.to_string()) - } else if let Some(remaining) = input.strip_prefix(USER_DIR_PREFIX) { - (dirs::home_dir().unwrap_or_default(), remaining.to_string()) - } else if let Some(remaining) = input.strip_prefix(ROOT_DIR_PREFIX) { - { - #[cfg(unix)] - { - (PathBuf::from(ROOT_DIR_PREFIX), remaining.to_string()) - } - #[cfg(windows)] - { - let current_drive = current_dir - .components() - .find_map(|comp| { - if let std::path::Component::Prefix(prefix_component) = comp { - Some(prefix_component) - } else { - None - } - }) - .and_then(|prefix_component| match prefix_component.kind() { - std::path::Prefix::Disk(drive_letter) - | std::path::Prefix::VerbatimDisk(drive_letter) => { - Some((drive_letter as char).to_string()) - } - _ => None, - }) - .unwrap_or_else(|| "C".to_string()); - ( - PathBuf::from(format!("{}:{}", current_drive, ROOT_DIR_PREFIX)), - remaining.to_string(), - ) - } - } - } else { - (current_dir, input) - } - } -} diff --git a/utils/src/legacy/input.rs b/utils/src/legacy/input.rs deleted file mode 100644 index 501ce69..0000000 --- a/utils/src/legacy/input.rs +++ /dev/null @@ -1,141 +0,0 @@ -use tokio::{fs, process::Command}; - -use crate::legacy::env::get_default_editor; - -/// Confirm the current operation -/// Waits for user input of 'y' or 'n' -pub async fn confirm_hint(text: impl Into<String>) -> bool { - use tokio::io::{self, AsyncBufReadExt, AsyncWriteExt, BufReader}; - - let prompt = text.into().trim().to_string(); - - let mut stdout = io::stdout(); - let mut stdin = BufReader::new(io::stdin()); - - stdout - .write_all(prompt.as_bytes()) - .await - .expect("Failed to write prompt"); - stdout.flush().await.expect("Failed to flush stdout"); - - let mut input = String::new(); - stdin - .read_line(&mut input) - .await - .expect("Failed to read input"); - - input.trim().eq_ignore_ascii_case("y") -} - -/// Confirm the current operation, or execute a closure if rejected -/// Waits for user input of 'y' or 'n' -/// If 'n' is entered, executes the provided closure and returns false -pub async fn confirm_hint_or<F>(text: impl Into<String>, on_reject: F) -> bool -where - F: FnOnce(), -{ - let confirmed = confirm_hint(text).await; - if !confirmed { - on_reject(); - } - confirmed -} - -/// Confirm the current operation, and execute a closure if confirmed -/// Waits for user input of 'y' or 'n' -/// If 'y' is entered, executes the provided closure and returns true -pub async fn confirm_hint_then<F>(text: impl Into<String>, on_confirm: F) -> bool -where - F: FnOnce(), -{ - let confirmed = confirm_hint(text).await; - if confirmed { - on_confirm(); - } - confirmed -} - -/// Input text using the system editor -/// Opens the system editor (from EDITOR environment variable) with default text in a cache file, -/// then reads back the modified content after the editor closes, removing comment lines -pub async fn input_with_editor( - default_text: impl AsRef<str>, - cache_file: impl AsRef<std::path::Path>, - comment_char: impl AsRef<str>, -) -> Result<String, std::io::Error> { - input_with_editor_cutsom( - default_text, - cache_file, - comment_char, - get_default_editor().await, - ) - .await -} - -pub async fn input_with_editor_cutsom( - default_text: impl AsRef<str>, - cache_file: impl AsRef<std::path::Path>, - comment_char: impl AsRef<str>, - editor: String, -) -> Result<String, std::io::Error> { - let cache_path = cache_file.as_ref(); - let default_content = default_text.as_ref(); - let comment_prefix = comment_char.as_ref(); - - // Write default text to cache file - fs::write(cache_path, default_content).await?; - - // Open editor with cache file - let status = Command::new(editor).arg(cache_path).status().await?; - - if !status.success() { - return Err(std::io::Error::other("Editor exited with non-zero status")); - } - - // Read the modified content - let content = fs::read_to_string(cache_path).await?; - - // Remove comment lines and trim - let processed_content: String = content - .lines() - .filter(|line| !line.trim().starts_with(comment_prefix)) - .collect::<Vec<&str>>() - .join("\n"); - - // Delete the cache file - let _ = fs::remove_file(cache_path).await; - - Ok(processed_content) -} - -/// Show text using the system pager (less) -/// Opens the system pager (less) with the given text content written to the specified file -/// If less is not found, directly outputs the content to stdout -pub async fn show_in_pager( - content: impl AsRef<str>, - cache_file: impl AsRef<std::path::Path>, -) -> Result<(), std::io::Error> { - let content_str = content.as_ref(); - let cache_path = cache_file.as_ref(); - - // Write content to cache file - fs::write(cache_path, content_str).await?; - - // Try to use less first - let status = Command::new("less").arg(cache_path).status().await; - - match status { - Ok(status) if status.success() => Ok(()), - _ => { - // If less failed, output directly to stdout - use tokio::io::{self, AsyncWriteExt}; - let mut stdout = io::stdout(); - stdout - .write_all(content_str.as_bytes()) - .await - .expect("Failed to write content"); - stdout.flush().await.expect("Failed to flush stdout"); - Ok(()) - } - } -} diff --git a/utils/src/legacy/levenshtein_distance.rs b/utils/src/legacy/levenshtein_distance.rs deleted file mode 100644 index 6bdb7e7..0000000 --- a/utils/src/legacy/levenshtein_distance.rs +++ /dev/null @@ -1,34 +0,0 @@ -use std::cmp::min; - -pub fn levenshtein_distance(a: &str, b: &str) -> usize { - let a_chars: Vec<char> = a.chars().collect(); - let b_chars: Vec<char> = b.chars().collect(); - let a_len = a_chars.len(); - let b_len = b_chars.len(); - - if a_len == 0 { - return b_len; - } - if b_len == 0 { - return a_len; - } - - let mut dp = vec![vec![0; b_len + 1]; a_len + 1]; - - for (i, row) in dp.iter_mut().enumerate() { - row[0] = i; - } - - for (j, cell) in dp[0].iter_mut().enumerate() { - *cell = j; - } - - for (i, a_char) in a_chars.iter().enumerate() { - for (j, b_char) in b_chars.iter().enumerate() { - let cost = if a_char == b_char { 0 } else { 1 }; - dp[i + 1][j + 1] = min(dp[i][j + 1] + 1, min(dp[i + 1][j] + 1, dp[i][j] + cost)); - } - } - - dp[a_len][b_len] -} diff --git a/utils/src/legacy/logger.rs b/utils/src/legacy/logger.rs deleted file mode 100644 index 7c18d30..0000000 --- a/utils/src/legacy/logger.rs +++ /dev/null @@ -1,86 +0,0 @@ -use std::path::Path; - -use colored::Colorize; -use env_logger::{Builder, Target}; -use just_enough_vcs::lib::data::vault::vault_config::LoggerLevel; -use just_fmt::fmt_path::fmt_path; -use log::{Level, LevelFilter}; - -pub fn build_env_logger(log_path: impl AsRef<Path>, logger_level: LoggerLevel) { - use std::io::{self, Write}; - - struct MultiWriter<A, B> { - a: A, - b: B, - } - - impl<A: Write, B: Write> MultiWriter<A, B> { - fn new(a: A, b: B) -> Self { - Self { a, b } - } - } - - impl<A: Write, B: Write> Write for MultiWriter<A, B> { - fn write(&mut self, buf: &[u8]) -> io::Result<usize> { - let _ = self.a.write(buf); - self.b.write(buf) - } - - fn flush(&mut self) -> io::Result<()> { - let _ = self.a.flush(); - self.b.flush() - } - } - - let log_path = { - let path = log_path.as_ref(); - let Ok(path) = fmt_path(path) else { - eprintln!( - "Build logger failed: {} is not a vaild path.", - path.display() - ); - return; - }; - path - }; - - let mut builder = Builder::new(); - - let log_format = |buf: &mut env_logger::fmt::Formatter, record: &log::Record| { - let now = chrono::Local::now(); - - let level_style = match record.level() { - Level::Error => record.args().to_string().red().bold(), - Level::Warn => record.args().to_string().yellow().bold(), - Level::Info => record.args().to_string().white(), - Level::Debug => record.args().to_string().white(), - Level::Trace => record.args().to_string().cyan(), - }; - - writeln!( - buf, - "{} {}", - now.format("%H:%M:%S") - .to_string() - .truecolor(105, 105, 105) - .bold(), - level_style - ) - }; - - let log_file = std::fs::File::create(log_path).expect("Failed to create log file"); - let combined_target = Target::Pipe(Box::new(MultiWriter::new(std::io::stdout(), log_file))); - - let level = match logger_level { - LoggerLevel::Debug => LevelFilter::Debug, - LoggerLevel::Trace => LevelFilter::Trace, - LoggerLevel::Info => LevelFilter::Info, - }; - - builder - .format(log_format) - .filter(None, level) - .filter_module("just_enough_vcs", level) - .target(combined_target) - .init(); -} diff --git a/utils/src/legacy/push_version.rs b/utils/src/legacy/push_version.rs deleted file mode 100644 index 6da9039..0000000 --- a/utils/src/legacy/push_version.rs +++ /dev/null @@ -1,30 +0,0 @@ -pub fn push_version(current_version: impl Into<String>) -> Option<String> { - let version_str = current_version.into(); - let parts: Vec<&str> = version_str.split('.').collect(); - - if parts.len() != 3 { - return None; - } - - let major: Result<u32, _> = parts[0].parse(); - let minor: Result<u32, _> = parts[1].parse(); - let patch: Result<u32, _> = parts[2].parse(); - - if let (Ok(mut major), Ok(mut minor), Ok(mut patch)) = (major, minor, patch) { - patch += 1; - - if patch > 99 { - patch = 0; - minor += 1; - - if minor > 99 { - minor = 0; - major += 1; - } - } - - Some(format!("{}.{}.{}", major, minor, patch)) - } else { - None - } -} diff --git a/utils/src/legacy/socket_addr_helper.rs b/utils/src/legacy/socket_addr_helper.rs deleted file mode 100644 index 29ccd9f..0000000 --- a/utils/src/legacy/socket_addr_helper.rs +++ /dev/null @@ -1,194 +0,0 @@ -use std::net::SocketAddr; -use tokio::net::lookup_host; - -/// Helper function to parse a string into a SocketAddr with optional default port -pub async fn get_socket_addr( - address_str: impl AsRef<str>, - default_port: u16, -) -> Result<SocketAddr, std::io::Error> { - let address = address_str.as_ref().trim(); - - // Return error if input is empty after trimming - if address.is_empty() { - return Err(std::io::Error::new( - std::io::ErrorKind::InvalidInput, - "Empty address string", - )); - } - - // Check if the address contains a port - if let Some((host, port_str)) = parse_host_and_port(address) { - let port = port_str.parse::<u16>().map_err(|e| { - std::io::Error::new( - std::io::ErrorKind::InvalidInput, - format!("Invalid port number '{}': {}", port_str, e), - ) - })?; - - return resolve_to_socket_addr(host, port).await; - } - - // No port specified, use default port - resolve_to_socket_addr(address, default_port).await -} - -/// Parse host and port from address string -fn parse_host_and_port(address: &str) -> Option<(&str, &str)> { - if address.starts_with('[') - && let Some(close_bracket) = address.find(']') - && close_bracket + 1 < address.len() - && address.as_bytes()[close_bracket + 1] == b':' - { - let host = &address[1..close_bracket]; - let port = &address[close_bracket + 2..]; - return Some((host, port)); - } - - // Handle IPv4 addresses and hostnames with ports - if let Some(colon_pos) = address.rfind(':') { - // Check if this is not part of an IPv6 address without brackets - if !address.contains('[') && !address.contains(']') { - let host = &address[..colon_pos]; - let port = &address[colon_pos + 1..]; - - // Basic validation to avoid false positives - if !host.is_empty() && !port.is_empty() && port.chars().all(|c| c.is_ascii_digit()) { - return Some((host, port)); - } - } - } - - None -} - -/// Resolve host to SocketAddr, handling both IP addresses and domain names -async fn resolve_to_socket_addr(host: &str, port: u16) -> Result<SocketAddr, std::io::Error> { - // First try to parse as IP address (IPv4 or IPv6) - if let Ok(ip_addr) = host.parse() { - return Ok(SocketAddr::new(ip_addr, port)); - } - - // If it's not a valid IP address, treat it as a domain name and perform DNS lookup - let lookup_addr = format!("{}:{}", host, port); - let mut addrs = lookup_host(&lookup_addr).await?; - - if let Some(addr) = addrs.next() { - Ok(addr) - } else { - Err(std::io::Error::new( - std::io::ErrorKind::NotFound, - format!("Could not resolve host '{}'", host), - )) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[tokio::test] - async fn test_ipv4_with_port() { - let result = get_socket_addr("127.0.0.1:8080", 80).await; - assert!(result.is_ok()); - let addr = result.unwrap(); - assert_eq!(addr.port(), 8080); - assert_eq!(addr.ip().to_string(), "127.0.0.1"); - } - - #[tokio::test] - async fn test_ipv4_without_port() { - let result = get_socket_addr("192.168.1.1", 443).await; - assert!(result.is_ok()); - let addr = result.unwrap(); - assert_eq!(addr.port(), 443); - assert_eq!(addr.ip().to_string(), "192.168.1.1"); - } - - #[tokio::test] - async fn test_ipv6_with_port() { - let result = get_socket_addr("[::1]:8080", 80).await; - assert!(result.is_ok()); - let addr = result.unwrap(); - assert_eq!(addr.port(), 8080); - assert_eq!(addr.ip().to_string(), "::1"); - } - - #[tokio::test] - async fn test_ipv6_without_port() { - let result = get_socket_addr("[::1]", 443).await; - assert!(result.is_ok()); - let addr = result.unwrap(); - assert_eq!(addr.port(), 443); - assert_eq!(addr.ip().to_string(), "::1"); - } - - #[tokio::test] - async fn test_invalid_port() { - let result = get_socket_addr("127.0.0.1:99999", 80).await; - assert!(result.is_err()); - } - - #[tokio::test] - async fn test_empty_string() { - let result = get_socket_addr("", 80).await; - assert!(result.is_err()); - } - - #[tokio::test] - async fn test_whitespace_trimming() { - let result = get_socket_addr(" 127.0.0.1:8080 ", 80).await; - assert!(result.is_ok()); - let addr = result.unwrap(); - assert_eq!(addr.port(), 8080); - } - - #[tokio::test] - async fn test_domain_name_with_port() { - // This test will only pass if localhost resolves - let result = get_socket_addr("localhost:8080", 80).await; - if result.is_ok() { - let addr = result.unwrap(); - assert_eq!(addr.port(), 8080); - // localhost should resolve to 127.0.0.1 or ::1 - assert!(addr.ip().is_loopback()); - } - } - - #[tokio::test] - async fn test_domain_name_without_port() { - // This test will only pass if localhost resolves - let result = get_socket_addr("localhost", 443).await; - if result.is_ok() { - let addr = result.unwrap(); - assert_eq!(addr.port(), 443); - // localhost should resolve to 127.0.0.1 or ::1 - assert!(addr.ip().is_loopback()); - } - } - - #[tokio::test] - async fn test_parse_host_and_port() { - // IPv4 with port - assert_eq!( - parse_host_and_port("192.168.1.1:8080"), - Some(("192.168.1.1", "8080")) - ); - - // IPv6 with port - assert_eq!(parse_host_and_port("[::1]:8080"), Some(("::1", "8080"))); - - // Hostname with port - assert_eq!( - parse_host_and_port("example.com:443"), - Some(("example.com", "443")) - ); - - // No port - assert_eq!(parse_host_and_port("192.168.1.1"), None); - assert_eq!(parse_host_and_port("example.com"), None); - - // Invalid cases - assert_eq!(parse_host_and_port(":"), None); - assert_eq!(parse_host_and_port("192.168.1.1:"), None); - } -} diff --git a/utils/src/lib.rs b/utils/src/lib.rs index f61179e..c76143e 100644 --- a/utils/src/lib.rs +++ b/utils/src/lib.rs @@ -3,6 +3,3 @@ pub mod env; pub mod input; pub mod macros; pub mod math; - -// Legacy -pub mod legacy; |
