summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock189
-rw-r--r--src/bin/jvii.rs8
-rw-r--r--src/bin/jvn.rs127
-rw-r--r--src/data.rs2
-rw-r--r--src/data/ipaddress_history.rs34
-rw-r--r--src/systems/cmd/errors.rs27
-rw-r--r--utils/src/legacy.rs9
-rw-r--r--utils/src/legacy/display.rs490
-rw-r--r--utils/src/legacy/env.rs104
-rw-r--r--utils/src/legacy/fs.rs40
-rw-r--r--utils/src/legacy/globber.rs292
-rw-r--r--utils/src/legacy/input.rs141
-rw-r--r--utils/src/legacy/levenshtein_distance.rs34
-rw-r--r--utils/src/legacy/logger.rs86
-rw-r--r--utils/src/legacy/push_version.rs30
-rw-r--r--utils/src/legacy/socket_addr_helper.rs194
-rw-r--r--utils/src/lib.rs3
17 files changed, 86 insertions, 1724 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 28a2838..5e52d59 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -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(&current_char, color);
- }
-
- result.push_str(&current_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(&current)
- };
-
- 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;