summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock316
-rw-r--r--Cargo.toml73
-rw-r--r--macros/cmd_system_macros/Cargo.toml6
-rw-r--r--macros/render_system_macros/Cargo.toml6
-rw-r--r--src/bin/jv.rs10
-rw-r--r--src/bin/jvii.rs6
-rw-r--r--src/bin/jvn.rs4
-rw-r--r--src/bin/jvv.rs2
-rw-r--r--src/cmds/arg/storage_build.rs12
-rw-r--r--src/cmds/arg/storage_write.rs33
-rw-r--r--src/cmds/cmd/sheetedit.rs10
-rw-r--r--src/cmds/cmd/storage_build.rs70
-rw-r--r--src/cmds/cmd/storage_write.rs110
-rw-r--r--src/cmds/in/storage_rw.rs11
-rw-r--r--src/cmds/renderer/mappings_pretty.rs8
-rw-r--r--tools/build_helper/Cargo.toml9
-rw-r--r--utils/Cargo.toml24
-rw-r--r--utils/src/display.rs490
-rw-r--r--utils/src/display/colorful.rs275
-rw-r--r--utils/src/display/table.rs143
-rw-r--r--utils/src/legacy.rs9
-rw-r--r--utils/src/legacy/display.rs488
-rw-r--r--utils/src/legacy/env.rs (renamed from utils/src/env.rs)0
-rw-r--r--utils/src/legacy/fs.rs (renamed from utils/src/fs.rs)0
-rw-r--r--utils/src/legacy/globber.rs (renamed from utils/src/globber.rs)2
-rw-r--r--utils/src/legacy/input.rs (renamed from utils/src/input.rs)2
-rw-r--r--utils/src/legacy/levenshtein_distance.rs (renamed from utils/src/levenshtein_distance.rs)0
-rw-r--r--utils/src/legacy/logger.rs (renamed from utils/src/logger.rs)0
-rw-r--r--utils/src/legacy/push_version.rs (renamed from utils/src/push_version.rs)0
-rw-r--r--utils/src/legacy/socket_addr_helper.rs (renamed from utils/src/socket_addr_helper.rs)0
-rw-r--r--utils/src/lib.rs14
-rw-r--r--utils/src/macros.rs (renamed from utils/src/lazy_macros.rs)0
-rw-r--r--utils/src/math.rs1
-rw-r--r--utils/src/math/levenshtein_distance.rs38
34 files changed, 1187 insertions, 985 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 0224d56..afc5e63 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -159,7 +159,7 @@ dependencies = [
"asset_macros",
"constants",
"just_fmt",
- "thiserror 1.0.69",
+ "thiserror",
"tokio",
"winapi",
]
@@ -429,6 +429,7 @@ version = "0.1.0-dev"
dependencies = [
"chrono",
"colored",
+ "crossterm",
"dirs",
"env_logger",
"just_enough_vcs",
@@ -455,11 +456,22 @@ checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
[[package]]
name = "colored"
-version = "3.0.0"
+version = "3.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fde0e0ec90c9dfb3b4b1a0891a7dcd0e2bffde2f7efed5fe7c9bb00e5bfb915e"
+checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34"
+dependencies = [
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "config_system"
+version = "0.1.0"
dependencies = [
- "windows-sys 0.59.0",
+ "serde",
+ "serde_json",
+ "serde_yaml",
+ "thiserror",
+ "toml 0.9.8",
]
[[package]]
@@ -494,6 +506,15 @@ dependencies = [
]
[[package]]
+name = "convert_case"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9"
+dependencies = [
+ "unicode-segmentation",
+]
+
+[[package]]
name = "core-foundation-sys"
version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -559,15 +580,17 @@ checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
[[package]]
name = "crossterm"
-version = "0.27.0"
+version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df"
+checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b"
dependencies = [
"bitflags 2.9.4",
"crossterm_winapi",
- "libc",
- "mio 0.8.11",
+ "derive_more",
+ "document-features",
+ "mio",
"parking_lot",
+ "rustix",
"signal-hook",
"signal-hook-mio",
"winapi",
@@ -648,6 +671,28 @@ dependencies = [
]
[[package]]
+name = "derive_more"
+version = "2.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134"
+dependencies = [
+ "derive_more-impl",
+]
+
+[[package]]
+name = "derive_more-impl"
+version = "2.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb"
+dependencies = [
+ "convert_case",
+ "proc-macro2",
+ "quote",
+ "rustc_version",
+ "syn",
+]
+
+[[package]]
name = "digest"
version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -691,6 +736,15 @@ dependencies = [
]
[[package]]
+name = "document-features"
+version = "0.2.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61"
+dependencies = [
+ "litrs",
+]
+
+[[package]]
name = "ed25519"
version = "3.0.0-rc.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -748,14 +802,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
-name = "erased-serde"
-version = "0.4.9"
+name = "errno"
+version = "0.3.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "89e8918065695684b2b0702da20382d5ae6065cf3327bc2d6436bd49a71ce9f3"
+checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
dependencies = [
- "serde",
- "serde_core",
- "typeid",
+ "libc",
+ "windows-sys 0.61.2",
]
[[package]]
@@ -777,6 +830,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
[[package]]
+name = "framework"
+version = "0.1.0"
+dependencies = [
+ "just_fmt",
+ "space_macro",
+ "thiserror",
+ "tokio",
+]
+
+[[package]]
name = "futures"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -964,12 +1027,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
-name = "hex"
-version = "0.4.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
-
-[[package]]
name = "hex_display"
version = "0.1.0"
@@ -1122,18 +1179,21 @@ dependencies = [
"asset_system",
"cfg_file",
"chrono",
+ "config_system",
"constants",
"data_struct",
+ "framework",
"hex_display",
"jvlib",
"sha1_hash",
"sheet_system",
- "storage_system",
"tcp_connection",
"toml 0.9.8",
+ "vault_system",
"vcs_actions",
"vcs_data",
"vcs_docs",
+ "workspace_system",
]
[[package]]
@@ -1147,7 +1207,6 @@ dependencies = [
"colored",
"crossterm",
"env_logger",
- "erased-serde",
"just_enough_vcs",
"just_fmt",
"just_progress",
@@ -1160,7 +1219,7 @@ dependencies = [
"serde",
"serde_json",
"serde_yaml",
- "thiserror 2.0.17",
+ "thiserror",
"tokio",
"toml 0.9.8",
"walkdir",
@@ -1237,6 +1296,18 @@ dependencies = [
]
[[package]]
+name = "linux-raw-sys"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039"
+
+[[package]]
+name = "litrs"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092"
+
+[[package]]
name = "lock_api"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1268,23 +1339,12 @@ dependencies = [
[[package]]
name = "mio"
-version = "0.8.11"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c"
-dependencies = [
- "libc",
- "log",
- "wasi",
- "windows-sys 0.48.0",
-]
-
-[[package]]
-name = "mio"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69d83b0086dc8ecf3ce9ae2874b2d1290252e2a30720bea58a5c6639b0092873"
dependencies = [
"libc",
+ "log",
"wasi",
"windows-sys 0.61.2",
]
@@ -1621,7 +1681,7 @@ checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac"
dependencies = [
"getrandom 0.2.16",
"libredox",
- "thiserror 2.0.17",
+ "thiserror",
]
[[package]]
@@ -1796,6 +1856,19 @@ dependencies = [
]
[[package]]
+name = "rustix"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34"
+dependencies = [
+ "bitflags 2.9.4",
+ "errno",
+ "libc",
+ "linux-raw-sys",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
name = "rustversion"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1976,7 +2049,7 @@ dependencies = [
"serde",
"sha2 0.10.9",
"sheet_system_macros",
- "thiserror 1.0.69",
+ "thiserror",
"tokio",
]
@@ -2012,7 +2085,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b75a19a7a740b25bc7944bdee6172368f988763b744e3d4dfe753f6b4ece40cc"
dependencies = [
"libc",
- "mio 0.8.11",
+ "mio",
"signal-hook",
]
@@ -2070,6 +2143,16 @@ dependencies = [
]
[[package]]
+name = "space_macro"
+version = "0.1.0"
+dependencies = [
+ "just_fmt",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
name = "spin"
version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2092,18 +2175,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
[[package]]
-name = "storage_system"
-version = "0.1.0"
-dependencies = [
- "blake3",
- "hex",
- "log",
- "memmap2",
- "thiserror 1.0.69",
- "tokio",
-]
-
-[[package]]
name = "strip-ansi-escapes"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2150,38 +2221,18 @@ dependencies = [
"rsa",
"serde",
"serde_json",
- "thiserror 2.0.17",
+ "thiserror",
"tokio",
"uuid",
]
[[package]]
name = "thiserror"
-version = "1.0.69"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
-dependencies = [
- "thiserror-impl 1.0.69",
-]
-
-[[package]]
-name = "thiserror"
version = "2.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8"
dependencies = [
- "thiserror-impl 2.0.17",
-]
-
-[[package]]
-name = "thiserror-impl"
-version = "1.0.69"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
+ "thiserror-impl",
]
[[package]]
@@ -2197,13 +2248,13 @@ dependencies = [
[[package]]
name = "tokio"
-version = "1.48.0"
+version = "1.50.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408"
+checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d"
dependencies = [
"bytes",
"libc",
- "mio 1.1.0",
+ "mio",
"parking_lot",
"pin-project-lite",
"signal-hook-registry",
@@ -2315,12 +2366,6 @@ dependencies = [
]
[[package]]
-name = "typeid"
-version = "1.0.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c"
-
-[[package]]
name = "typenum"
version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2333,6 +2378,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d"
[[package]]
+name = "unicode-segmentation"
+version = "1.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
+
+[[package]]
name = "unicode-xid"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2369,6 +2420,19 @@ dependencies = [
]
[[package]]
+name = "vault_system"
+version = "0.1.0"
+dependencies = [
+ "asset_system",
+ "config_system",
+ "constants",
+ "framework",
+ "serde",
+ "thiserror",
+ "tokio",
+]
+
+[[package]]
name = "vcs_actions"
version = "0.1.0"
dependencies = [
@@ -2380,7 +2444,7 @@ dependencies = [
"serde_json",
"sha1_hash",
"tcp_connection",
- "thiserror 2.0.17",
+ "thiserror",
"tokio",
"vcs_data",
]
@@ -2672,15 +2736,6 @@ dependencies = [
[[package]]
name = "windows-sys"
-version = "0.48.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
-dependencies = [
- "windows-targets 0.48.5",
-]
-
-[[package]]
-name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
@@ -2690,15 +2745,6 @@ dependencies = [
[[package]]
name = "windows-sys"
-version = "0.59.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
-dependencies = [
- "windows-targets 0.52.6",
-]
-
-[[package]]
-name = "windows-sys"
version = "0.60.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
@@ -2717,21 +2763,6 @@ dependencies = [
[[package]]
name = "windows-targets"
-version = "0.48.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
-dependencies = [
- "windows_aarch64_gnullvm 0.48.5",
- "windows_aarch64_msvc 0.48.5",
- "windows_i686_gnu 0.48.5",
- "windows_i686_msvc 0.48.5",
- "windows_x86_64_gnu 0.48.5",
- "windows_x86_64_gnullvm 0.48.5",
- "windows_x86_64_msvc 0.48.5",
-]
-
-[[package]]
-name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
@@ -2765,12 +2796,6 @@ dependencies = [
[[package]]
name = "windows_aarch64_gnullvm"
-version = "0.48.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
-
-[[package]]
-name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
@@ -2783,12 +2808,6 @@ checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
[[package]]
name = "windows_aarch64_msvc"
-version = "0.48.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
-
-[[package]]
-name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
@@ -2801,12 +2820,6 @@ checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
[[package]]
name = "windows_i686_gnu"
-version = "0.48.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
-
-[[package]]
-name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
@@ -2831,12 +2844,6 @@ checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
[[package]]
name = "windows_i686_msvc"
-version = "0.48.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
-
-[[package]]
-name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
@@ -2849,12 +2856,6 @@ checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
[[package]]
name = "windows_x86_64_gnu"
-version = "0.48.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
-
-[[package]]
-name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
@@ -2867,12 +2868,6 @@ checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
[[package]]
name = "windows_x86_64_gnullvm"
-version = "0.48.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
-
-[[package]]
-name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
@@ -2885,12 +2880,6 @@ checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
[[package]]
name = "windows_x86_64_msvc"
-version = "0.48.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
-
-[[package]]
-name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
@@ -3005,6 +2994,19 @@ dependencies = [
]
[[package]]
+name = "workspace_system"
+version = "0.1.0"
+dependencies = [
+ "asset_system",
+ "config_system",
+ "constants",
+ "framework",
+ "serde",
+ "thiserror",
+ "tokio",
+]
+
+[[package]]
name = "zerocopy"
version = "0.8.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/Cargo.toml b/Cargo.toml
index 2b2aa7e..e6c061f 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -10,7 +10,7 @@ members = [
"utils/",
"tools/build_helper",
"macros/render_system_macros",
- "macros/cmd_system_macros"
+ "macros/cmd_system_macros",
]
[workspace.package]
@@ -44,56 +44,39 @@ toml = "0.9"
regex = "1.12"
just_template = "0.1.0"
+[workspace.dependencies]
+proc-macro2 = "1.0"
+quote = "1.0"
+syn = { version = "2.0", features = ["full", "extra-traits"] }
+just_fmt = "0.1.2"
+crossterm = "0.29"
+colored = "3.1"
+log = "0.4"
+env_logger = "0.11"
+thiserror = "2"
+serde = { version = "1", features = ["derive"] }
+
[dependencies]
-# Just Enough VCS
+cli_utils = { path = "utils" }
+cmd_system_macros = { path = "macros/cmd_system_macros" }
just_enough_vcs = { path = "../VersionControl", features = ["all"] }
-
-# RenderSystem Macros
render_system_macros = { path = "macros/render_system_macros" }
-# CommandSystem Macros
-cmd_system_macros = { path = "macros/cmd_system_macros" }
+crossterm.workspace = true
+env_logger.workspace = true
+just_fmt.workspace = true
+log.workspace = true
+serde.workspace = true
+thiserror.workspace = true
-# CommandLine Utilities
-cli_utils = { path = "utils" }
-
-# Error
-thiserror = "2.0.17"
-
-# Serialize
-# What the heck, why does this crate use kebab-case instead of snake_case ????
-erased_serde = { package = "erased-serde", version = "0.4" }
-serde = { version = "1", features = ["derive"] }
-serde_json = "1"
-serde_yaml = "0.9"
-ron = "0.11.0"
-toml = "0.9"
-
-# Command Line
-clap = { version = "4.5", features = ["derive"] }
-
-# Time
chrono = "0.4"
-
-# Logging
-log = "0.4"
-env_logger = "0.11"
-
-# Async
-tokio = { version = "1", features = ["full"] }
-
-# Display
-colored = "3.0"
+clap = { version = "4.5", features = ["derive"] }
+colored.workspace = true
just_progress = "0.1.1"
-
-# Terminal
-crossterm = "0.27"
-
-# i18n
+ron = "0.11.0"
rust-i18n = "3"
-
-# File & Directory
+serde_json = "1"
+serde_yaml = "0.9"
+tokio = { version = "1", features = ["full"] }
+toml = "0.9"
walkdir = "2.5.0"
-
-# String format
-just_fmt = "0.1.2"
diff --git a/macros/cmd_system_macros/Cargo.toml b/macros/cmd_system_macros/Cargo.toml
index 4a91064..3f9e0b7 100644
--- a/macros/cmd_system_macros/Cargo.toml
+++ b/macros/cmd_system_macros/Cargo.toml
@@ -7,6 +7,6 @@ edition = "2024"
proc-macro = true
[dependencies]
-proc-macro2 = "1.0"
-quote = "1.0"
-syn = { version = "2.0", features = ["full", "extra-traits", "visit"] }
+proc-macro2.workspace = true
+quote.workspace = true
+syn = { workspace = true, features = ["visit"] }
diff --git a/macros/render_system_macros/Cargo.toml b/macros/render_system_macros/Cargo.toml
index df435db..28f084d 100644
--- a/macros/render_system_macros/Cargo.toml
+++ b/macros/render_system_macros/Cargo.toml
@@ -7,6 +7,6 @@ edition = "2024"
proc-macro = true
[dependencies]
-proc-macro2 = "1.0"
-quote = "1.0"
-syn = { version = "2.0", features = ["full", "extra-traits"] }
+proc-macro2.workspace = true
+quote.workspace = true
+syn.workspace = true
diff --git a/src/bin/jv.rs b/src/bin/jv.rs
index c3dc4ca..0df6b32 100644
--- a/src/bin/jv.rs
+++ b/src/bin/jv.rs
@@ -21,7 +21,6 @@
// The new implementation is located in `jvn.rs`, please refer to it.
//
-use cli_utils::input::input_with_editor;
use colored::Colorize;
use just_enough_vcs::{
data::compile_info::CoreCompileInfo,
@@ -113,14 +112,13 @@ use std::{
};
use clap::{Parser, Subcommand};
-use cli_utils::{
+use cli_utils::legacy::{
display::{SimpleTable, display_width, md, render_share_path_tree, size_str},
env::{auto_update_outdate, current_locales, enable_auto_update},
fs::move_across_partitions,
globber::{GlobItem, Globber},
- input::{confirm_hint, confirm_hint_or, show_in_pager},
- push_version::push_version,
- socket_addr_helper,
+ input::{confirm_hint, confirm_hint_or, input_with_editor, show_in_pager},
+ push_version, socket_addr_helper,
};
use just_enough_vcs::utils::tcp_connection::error::TcpTargetError;
use just_enough_vcs_cli::{
@@ -4035,7 +4033,7 @@ async fn start_update_editor(
for item in files {
let path = item.0.display().to_string();
let base_ver = item.1.to_string();
- let next_ver = push_version(&base_ver).unwrap_or(" ".to_string());
+ let next_ver = push_version::push_version(&base_ver).unwrap_or(" ".to_string());
table.push_item(vec![
path,
base_ver,
diff --git a/src/bin/jvii.rs b/src/bin/jvii.rs
index 2f9cb6d..fbc3ed9 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::display::display_width;
-use cli_utils::display::md;
-use cli_utils::env::current_locales;
+use cli_utils::legacy::display::display_width;
+use cli_utils::legacy::display::md;
+use cli_utils::legacy::env::current_locales;
use crossterm::{
QueueableCommand,
cursor::MoveTo,
diff --git a/src/bin/jvn.rs b/src/bin/jvn.rs
index f2c5cf6..f45c78b 100644
--- a/src/bin/jvn.rs
+++ b/src/bin/jvn.rs
@@ -1,6 +1,6 @@
use std::{ops::Deref, process::exit};
-use cli_utils::{display::md, env::current_locales, levenshtein_distance::levenshtein_distance};
+use cli_utils::legacy::{display::md, env::current_locales, levenshtein_distance};
use just_enough_vcs_cli::{
special_argument, special_flag,
systems::{
@@ -197,7 +197,7 @@ fn handle_no_matching_command_error(args: Vec<String>) {
continue;
}
let args_str = args[..node_len].join(" ");
- let distance = levenshtein_distance(args_str.as_str(), node.as_str());
+ let distance = levenshtein_distance::levenshtein_distance(args_str.as_str(), node.as_str());
if distance <= 2 {
similar_nodes.push(node);
}
diff --git a/src/bin/jvv.rs b/src/bin/jvv.rs
index 335e374..885f3e0 100644
--- a/src/bin/jvv.rs
+++ b/src/bin/jvv.rs
@@ -22,7 +22,7 @@
//
use clap::{Parser, Subcommand};
-use cli_utils::{
+use cli_utils::legacy::{
display::{md, size_str},
env::current_locales,
logger::build_env_logger,
diff --git a/src/cmds/arg/storage_build.rs b/src/cmds/arg/storage_build.rs
deleted file mode 100644
index 5d57b97..0000000
--- a/src/cmds/arg/storage_build.rs
+++ /dev/null
@@ -1,12 +0,0 @@
-use std::path::PathBuf;
-
-use clap::Parser;
-
-#[derive(Parser, Debug)]
-pub struct JVStorageBuildArgument {
- pub index_file: PathBuf,
- pub storage: PathBuf,
-
- #[arg(short = 'o', long = "output")]
- pub output_file: Option<PathBuf>,
-}
diff --git a/src/cmds/arg/storage_write.rs b/src/cmds/arg/storage_write.rs
deleted file mode 100644
index e00dfdf..0000000
--- a/src/cmds/arg/storage_write.rs
+++ /dev/null
@@ -1,33 +0,0 @@
-use std::path::PathBuf;
-
-use clap::Parser;
-
-#[derive(Parser, Debug)]
-pub struct JVStorageWriteArgument {
- pub file: PathBuf,
- pub storage: PathBuf,
-
- #[arg(short = 'o', long = "output")]
- pub output_index: Option<PathBuf>,
-
- #[arg(long = "line")]
- pub line_chunking: bool,
-
- #[arg(long = "cdc", default_value_t = 0)]
- pub cdc_chunking: u32,
-
- #[arg(long = "fixed", default_value_t = 0)]
- pub fixed_chunking: u32,
-
- #[arg(long)]
- pub b: bool,
-
- #[arg(long)]
- pub kb: bool, // default chunk size unit
-
- #[arg(long)]
- pub mb: bool,
-
- #[arg(long)]
- pub gb: bool,
-}
diff --git a/src/cmds/cmd/sheetedit.rs b/src/cmds/cmd/sheetedit.rs
index 3e61642..74b0bbe 100644
--- a/src/cmds/cmd/sheetedit.rs
+++ b/src/cmds/cmd/sheetedit.rs
@@ -10,7 +10,9 @@ use crate::{
},
};
use cli_utils::{
- display::SimpleTable, env::get_default_editor, input::input_with_editor_cutsom, string_vec,
+ display::table::Table,
+ legacy::{env::get_default_editor, input::input_with_editor_cutsom},
+ string_vec,
};
use cmd_system_macros::exec;
use just_enough_vcs::system::sheet_system::{mapping::LocalMapping, sheet::SheetData};
@@ -96,7 +98,7 @@ fn render_pretty_mappings(mappings: &Vec<LocalMapping>) -> String {
t!("sheetedit.forward")
];
- let mut simple_table = SimpleTable::new(header);
+ let mut table = Table::new(header);
for mapping in mappings {
let mapping_str = mapping
@@ -105,7 +107,7 @@ fn render_pretty_mappings(mappings: &Vec<LocalMapping>) -> String {
.into_iter()
.map(|s| s.to_string())
.collect::<Vec<String>>();
- simple_table.push_item(vec![
+ table.push_item(vec![
format!(
" {} ",
mapping_str.get(0).unwrap_or(&String::default())
@@ -116,7 +118,7 @@ fn render_pretty_mappings(mappings: &Vec<LocalMapping>) -> String {
format!("{} ", mapping_str.get(4).unwrap_or(&String::default())), // Forward
]);
}
- simple_table.to_string()
+ table.to_string()
}
crate::command_template!();
diff --git a/src/cmds/cmd/storage_build.rs b/src/cmds/cmd/storage_build.rs
deleted file mode 100644
index 8e4d39c..0000000
--- a/src/cmds/cmd/storage_build.rs
+++ /dev/null
@@ -1,70 +0,0 @@
-use crate::{
- cmd_output,
- cmds::{
- arg::storage_build::JVStorageBuildArgument, collect::empty::JVEmptyCollect,
- r#in::storage_rw::JVStorageRWInput, out::none::JVNoneOutput,
- },
- systems::cmd::{
- cmd_system::JVCommandContext,
- errors::{CmdExecuteError, CmdPrepareError},
- },
-};
-use cli_utils::display::md;
-use cmd_system_macros::exec;
-use just_enough_vcs::system::storage_system::{error::StorageIOError, store::build_file};
-use rust_i18n::t;
-use std::any::TypeId;
-
-pub struct JVStorageBuildCommand;
-type Cmd = JVStorageBuildCommand;
-type Arg = JVStorageBuildArgument;
-type In = JVStorageRWInput;
-type Collect = JVEmptyCollect;
-
-fn help_str() -> String {
- todo!()
-}
-
-async fn prepare(args: &Arg, _ctx: &JVCommandContext) -> Result<In, CmdPrepareError> {
- let output_file = match &args.output_file {
- Some(v) => v.clone(),
- None => args.index_file.clone().with_extension("unknown"),
- };
-
- let (input, storage, output) = just_enough_vcs::system::storage_system::store::precheck(
- args.index_file.clone(),
- args.storage.clone(),
- output_file,
- )
- .await?;
-
- Ok(JVStorageRWInput {
- input,
- storage,
- output,
- chunking_policy: None,
- })
-}
-
-async fn collect(_args: &Arg, _ctx: &JVCommandContext) -> Result<Collect, CmdPrepareError> {
- Ok(Collect {})
-}
-
-#[exec]
-async fn exec(
- input: In,
- _collect: Collect,
-) -> Result<(Box<dyn std::any::Any + Send + 'static>, TypeId), CmdExecuteError> {
- build_file(input.input, input.storage, input.output)
- .await
- .map_err(|e| match e {
- StorageIOError::IOErr(error) => CmdExecuteError::Io(error),
- StorageIOError::HashTooShort => {
- CmdExecuteError::Error(md(t!("storage_write.hash_too_short")).to_string())
- }
- })?;
-
- cmd_output!(JVNoneOutput => JVNoneOutput {})
-}
-
-crate::command_template!();
diff --git a/src/cmds/cmd/storage_write.rs b/src/cmds/cmd/storage_write.rs
deleted file mode 100644
index 8c864a8..0000000
--- a/src/cmds/cmd/storage_write.rs
+++ /dev/null
@@ -1,110 +0,0 @@
-use crate::{
- cmd_output,
- cmds::{
- arg::storage_write::JVStorageWriteArgument, collect::empty::JVEmptyCollect,
- r#in::storage_rw::JVStorageRWInput, out::none::JVNoneOutput,
- },
- systems::cmd::{
- cmd_system::JVCommandContext,
- errors::{CmdExecuteError, CmdPrepareError},
- },
-};
-use cli_utils::display::md;
-use cmd_system_macros::exec;
-use just_enough_vcs::system::{
- constants::vault::values::vault_value_index_file_suffix,
- storage_system::{
- error::StorageIOError,
- store::{ChunkingPolicy, StorageConfig, write_file},
- },
-};
-use rust_i18n::t;
-use std::any::TypeId;
-
-pub struct JVStorageWriteCommand;
-type Cmd = JVStorageWriteCommand;
-type Arg = JVStorageWriteArgument;
-type In = JVStorageRWInput;
-type Collect = JVEmptyCollect;
-
-fn help_str() -> String {
- todo!()
-}
-
-async fn prepare(args: &Arg, _ctx: &JVCommandContext) -> Result<In, CmdPrepareError> {
- let output_path = match &args.output_index {
- Some(v) => v.clone(),
- None => args
- .file
- .clone()
- .with_extension(vault_value_index_file_suffix()),
- };
-
- // Default to using kb as the unit
- let scale = if args.gb {
- 1024 * 1024 * 1024
- } else if args.mb {
- 1024 * 1024
- } else if args.b {
- 1
- } else {
- 1024
- };
-
- let (input, storage, output) = just_enough_vcs::system::storage_system::store::precheck(
- args.file.clone(),
- args.storage.clone(),
- output_path,
- )
- .await?;
-
- let chunking_policy: ChunkingPolicy = if args.cdc_chunking > 0 {
- ChunkingPolicy::Cdc(args.cdc_chunking * scale)
- } else if args.fixed_chunking > 0 {
- ChunkingPolicy::FixedSize(args.fixed_chunking * scale)
- } else if args.line_chunking {
- ChunkingPolicy::Line
- } else {
- return Err(CmdPrepareError::Error(md(t!(
- "storage_write.unknown_chunking_policy"
- ))));
- };
-
- Ok(JVStorageRWInput {
- input,
- storage,
- output,
- chunking_policy: Some(chunking_policy),
- })
-}
-
-async fn collect(_args: &Arg, _ctx: &JVCommandContext) -> Result<Collect, CmdPrepareError> {
- Ok(JVEmptyCollect {})
-}
-
-#[exec]
-async fn exec(
- input: In,
- _collect: Collect,
-) -> Result<(Box<dyn std::any::Any + Send + 'static>, TypeId), CmdExecuteError> {
- // There is no chance to return None in the Prepare phase, so unwrap is safe here
- let chunking_policy = input.chunking_policy.unwrap();
-
- write_file(
- input.input,
- input.storage,
- input.output,
- &StorageConfig { chunking_policy },
- )
- .await
- .map_err(|e| match e {
- StorageIOError::IOErr(error) => CmdExecuteError::Io(error),
- StorageIOError::HashTooShort => {
- CmdExecuteError::Error(md(t!("storage_write.hash_too_short")).to_string())
- }
- })?;
-
- cmd_output!(JVNoneOutput => JVNoneOutput {})
-}
-
-crate::command_template!();
diff --git a/src/cmds/in/storage_rw.rs b/src/cmds/in/storage_rw.rs
deleted file mode 100644
index 596c1f9..0000000
--- a/src/cmds/in/storage_rw.rs
+++ /dev/null
@@ -1,11 +0,0 @@
-use std::path::PathBuf;
-
-use just_enough_vcs::system::storage_system::store::ChunkingPolicy;
-
-pub struct JVStorageRWInput {
- pub input: PathBuf,
- pub storage: PathBuf,
- pub output: PathBuf,
-
- pub chunking_policy: Option<ChunkingPolicy>,
-}
diff --git a/src/cmds/renderer/mappings_pretty.rs b/src/cmds/renderer/mappings_pretty.rs
index dad4d95..6431302 100644
--- a/src/cmds/renderer/mappings_pretty.rs
+++ b/src/cmds/renderer/mappings_pretty.rs
@@ -1,4 +1,4 @@
-use cli_utils::{display::SimpleTable, string_vec};
+use cli_utils::{display::table::Table, string_vec};
use colored::Colorize;
use just_enough_vcs::system::sheet_system::mapping::LocalMapping;
use render_system_macros::result_renderer;
@@ -29,7 +29,7 @@ fn render_pretty_mappings(mappings: &Vec<LocalMapping>) -> String {
"|"
];
- let mut simple_table = SimpleTable::new(header);
+ let mut table = Table::new(header);
let mut i = 1;
for mapping in mappings {
@@ -39,7 +39,7 @@ fn render_pretty_mappings(mappings: &Vec<LocalMapping>) -> String {
.into_iter()
.map(|s| s.to_string())
.collect::<Vec<String>>();
- simple_table.push_item(vec![
+ table.push_item(vec![
// Number
format!("{}", i).bold().to_string(),
// Mapping
@@ -89,5 +89,5 @@ fn render_pretty_mappings(mappings: &Vec<LocalMapping>) -> String {
i += 1;
}
- simple_table.to_string()
+ table.to_string()
}
diff --git a/tools/build_helper/Cargo.toml b/tools/build_helper/Cargo.toml
index deadd4c..7682795 100644
--- a/tools/build_helper/Cargo.toml
+++ b/tools/build_helper/Cargo.toml
@@ -4,10 +4,7 @@ edition = "2024"
version = "0.0.1"
[dependencies]
+colored.workspace = true
+serde.workspace = true
-# Serialization
-serde = { version = "1.0.226", features = ["derive"] }
-toml = { version = "0.9.7", features = ["serde"] }
-
-# Colored
-colored = "3.0.0"
+toml = { version = "0.9", features = ["serde"] }
diff --git a/utils/Cargo.toml b/utils/Cargo.toml
index c055c07..e4cc3a0 100644
--- a/utils/Cargo.toml
+++ b/utils/Cargo.toml
@@ -4,23 +4,15 @@ edition = "2024"
version.workspace = true
[dependencies]
-# Just Enough VCS
just_enough_vcs = { path = "../../VersionControl", features = ["all"] }
-# Display
-colored = "3.0"
-strip-ansi-escapes = "0.2.1"
-just_fmt = "0.1.2"
-
-# Async
-tokio = { version = "1", features = ["full"] }
+colored.workspace = true
+crossterm.workspace = true
+env_logger.workspace = true
+just_fmt.workspace = true
+log.workspace = true
-# Logging
-log = "0.4"
-env_logger = "0.11"
-
-# File & Directory
-dirs = "6.0.0"
-
-# Time
chrono = "0.4"
+dirs = "6.0.0"
+strip-ansi-escapes = "0.2.1"
+tokio = { version = "1", features = ["fs", "io-std", "net"] }
diff --git a/utils/src/display.rs b/utils/src/display.rs
index fc94d90..a9c48e8 100644
--- a/utils/src/display.rs
+++ b/utils/src/display.rs
@@ -1,488 +1,2 @@
-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);
- }
-}
+pub mod colorful;
+pub mod table;
diff --git a/utils/src/display/colorful.rs b/utils/src/display/colorful.rs
new file mode 100644
index 0000000..7daa6f2
--- /dev/null
+++ b/utils/src/display/colorful.rs
@@ -0,0 +1,275 @@
+use std::collections::VecDeque;
+
+use crossterm::style::Stylize;
+
+/// Trait for adding markdown formatting to strings
+pub trait Colorful {
+ fn colorful(&self) -> String;
+}
+
+impl Colorful for &str {
+ fn colorful(&self) -> String {
+ colorful(self)
+ }
+}
+
+impl Colorful for String {
+ fn colorful(&self) -> String {
+ colorful(self)
+ }
+}
+
+/// Converts a string to colored/formatted text with ANSI escape codes.
+///
+/// Supported syntax:
+/// - Bold: `**text**`
+/// - Italic: `*text*`
+/// - Underline: `_text_`
+/// - Angle-bracketed content: `<text>` (displayed as cyan)
+/// - Inline code: `` `text` `` (displayed as green)
+/// - Color tags: `[[color_name]]` and `[[/]]` to close color
+/// - Escape characters: `\*`, `\<`, `\>`, `` \` ``, `\_` for literal characters
+///
+/// Color tags support the following color names:
+/// Color tags support the following color names:
+///
+/// | Type | Color Names |
+/// |-----------------------|-----------------------------------------------------------------------------|
+/// | Standard colors | `black`, `red`, `green`, `yellow`, `blue`, `magenta`, `cyan`, `white` |
+/// | Bright colors | `bright_black` |
+/// | | `bright_red` |
+/// | | `bright_green` |
+/// | | `bright_yellow` |
+/// | | `bright_blue` |
+/// | | `bright_magenta` |
+/// | | `bright_cyan` |
+/// | | `bright_white` |
+/// | Bright color shorthands | `b_black` |
+/// | | `b_red` |
+/// | | `b_green` |
+/// | | `b_yellow` |
+/// | | `b_blue` |
+/// | | `b_magenta` |
+/// | | `b_cyan` |
+/// | | `b_white` |
+/// | Gray colors | `gray`/`grey` |
+/// | | `bright_gray`/`bright_grey` |
+/// | | `b_gray`/`b_grey` |
+///
+/// Color tags can be nested, `[[/]]` will close the most recently opened color tag.
+///
+/// # Arguments
+/// * `text` - The text to format, can be any type that implements `AsRef<str>`
+///
+/// # Returns
+/// Returns a `String` containing ANSI escape codes that can display colored/formatted text in ANSI-supported terminals.
+///
+/// # Examples
+/// ```
+/// use testing::fmt::colorful;
+///
+/// let formatted = colorful("Hello **world**!");
+/// println!("{}", formatted);
+///
+/// let colored = colorful("[[red]]Red text[[/]] and normal text");
+/// println!("{}", colored);
+///
+/// let nested = colorful("[[blue]]Blue [[green]]Green[[/]] Blue[[/]] normal");
+/// println!("{}", nested);
+/// ```
+pub fn colorful(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 chars: Vec<char> = text.chars().collect();
+ let mut i = 0;
+
+ 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_color_stack(&mut escaped_text, &color_stack);
+ result.push_str(&escaped_text);
+ i += 2;
+ continue;
+ }
+ }
+
+ // Check for color tag start [[color]]
+ if i + 1 < chars.len() && chars[i] == '[' && chars[i + 1] == '[' {
+ if let Some(end) = find_tag_end(&chars, i) {
+ let tag_content: String = chars[i + 2..end].iter().collect();
+
+ // Check if it's a closing tag [[/]]
+ if tag_content == "/" {
+ color_stack.pop_back();
+ } else {
+ // It's a color tag
+ color_stack.push_back(tag_content.clone());
+ }
+ i = end + 2;
+ continue;
+ }
+ }
+
+ // Check for bold **text**
+ if i + 1 < chars.len() && chars[i] == '*' && chars[i + 1] == '*' {
+ if let Some(end) = find_matching(&chars, i + 2, "**") {
+ let bold_text: String = chars[i + 2..end].iter().collect();
+ let mut formatted_text = bold_text.bold().to_string();
+ apply_color_stack(&mut formatted_text, &color_stack);
+ result.push_str(&formatted_text);
+ i = end + 2;
+ continue;
+ }
+ }
+
+ // Check for italic *text*
+ if chars[i] == '*' {
+ if let Some(end) = find_matching(&chars, i + 1, "*") {
+ let italic_text: String = chars[i + 1..end].iter().collect();
+ let mut formatted_text = italic_text.italic().to_string();
+ apply_color_stack(&mut formatted_text, &color_stack);
+ result.push_str(&formatted_text);
+ i = end + 1;
+ continue;
+ }
+ }
+
+ // Check for underline _text_
+ if chars[i] == '_' {
+ if let Some(end) = find_matching(&chars, i + 1, "_") {
+ let underline_text: String = chars[i + 1..end].iter().collect();
+ let mut formatted_text = format!("\x1b[4m{}\x1b[0m", underline_text);
+ apply_color_stack(&mut formatted_text, &color_stack);
+ result.push_str(&formatted_text);
+ i = end + 1;
+ continue;
+ }
+ }
+
+ // Check for angle-bracketed content <text>
+ if chars[i] == '<' {
+ if let Some(end) = find_matching(&chars, i + 1, ">") {
+ // Include the angle brackets in the output
+ let angle_text: String = chars[i..=end].iter().collect();
+ let mut formatted_text = angle_text.cyan().to_string();
+ apply_color_stack(&mut formatted_text, &color_stack);
+ result.push_str(&formatted_text);
+ i = end + 1;
+ continue;
+ }
+ }
+
+ // Check for inline code `text`
+ if chars[i] == '`' {
+ if let Some(end) = find_matching(&chars, i + 1, "`") {
+ // Include the backticks in the output
+ let code_text: String = chars[i..=end].iter().collect();
+ let mut formatted_text = code_text.green().to_string();
+ apply_color_stack(&mut formatted_text, &color_stack);
+ result.push_str(&formatted_text);
+ i = end + 1;
+ continue;
+ }
+ }
+
+ // Regular character
+ let mut current_char = chars[i].to_string();
+ apply_color_stack(&mut current_char, &color_stack);
+ result.push_str(&current_char);
+ i += 1;
+ }
+
+ result
+}
+
+// Helper function to find matching delimiter
+fn find_matching(chars: &[char], start: usize, delimiter: &str) -> Option<usize> {
+ let delim_chars: Vec<char> = delimiter.chars().collect();
+ let delim_len = delim_chars.len();
+
+ let mut j = start;
+ while j < chars.len() {
+ if delim_len == 1 {
+ if chars[j] == delim_chars[0] {
+ return Some(j);
+ }
+ } else if j + 1 < chars.len()
+ && chars[j] == delim_chars[0]
+ && chars[j + 1] == delim_chars[1]
+ {
+ return Some(j);
+ }
+ j += 1;
+ }
+ None
+}
+
+// Helper function to find color tag end
+fn find_tag_end(chars: &[char], start: usize) -> Option<usize> {
+ let mut j = start + 2;
+ while j + 1 < chars.len() {
+ if chars[j] == ']' && chars[j + 1] == ']' {
+ return Some(j);
+ }
+ j += 1;
+ }
+ None
+}
+
+// Helper function to apply color stack to text
+fn apply_color_stack(text: &mut String, color_stack: &VecDeque<String>) {
+ let mut result = text.clone();
+ for color in color_stack.iter().rev() {
+ result = apply_color(&result, color);
+ }
+ *text = 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.dark_grey().to_string(),
+ "red" => text.dark_red().to_string(),
+ "green" => text.dark_green().to_string(),
+ "yellow" => text.dark_yellow().to_string(),
+ "blue" => text.dark_blue().to_string(),
+ "magenta" => text.dark_magenta().to_string(),
+ "cyan" => text.dark_cyan().to_string(),
+ "white" => text.white().to_string(),
+ "bright_black" => text.black().to_string(),
+ "bright_red" => text.red().to_string(),
+ "bright_green" => text.green().to_string(),
+ "bright_yellow" => text.yellow().to_string(),
+ "bright_blue" => text.blue().to_string(),
+ "bright_magenta" => text.magenta().to_string(),
+ "bright_cyan" => text.cyan().to_string(),
+ "bright_white" => text.white().to_string(),
+
+ // Short aliases for bright colors
+ "b_black" => text.black().to_string(),
+ "b_red" => text.red().to_string(),
+ "b_green" => text.green().to_string(),
+ "b_yellow" => text.yellow().to_string(),
+ "b_blue" => text.blue().to_string(),
+ "b_magenta" => text.magenta().to_string(),
+ "b_cyan" => text.cyan().to_string(),
+ "b_white" => text.white().to_string(),
+
+ // Gray colors using truecolor
+ "gray" | "grey" => text.grey().to_string(),
+ "bright_gray" | "bright_grey" => text.white().to_string(),
+ "b_gray" | "b_grey" => text.white().to_string(),
+
+ // Default to white if color not recognized
+ _ => text.to_string(),
+ }
+}
diff --git a/utils/src/display/table.rs b/utils/src/display/table.rs
new file mode 100644
index 0000000..ae745d8
--- /dev/null
+++ b/utils/src/display/table.rs
@@ -0,0 +1,143 @@
+pub struct Table {
+ items: Vec<String>,
+ line: Vec<Vec<String>>,
+ length: Vec<usize>,
+ padding: usize,
+}
+
+impl Table {
+ /// 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));
+ }
+
+ Table {
+ 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 Table {
+ 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
+}
diff --git a/utils/src/legacy.rs b/utils/src/legacy.rs
new file mode 100644
index 0000000..682c679
--- /dev/null
+++ b/utils/src/legacy.rs
@@ -0,0 +1,9 @@
+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
new file mode 100644
index 0000000..fc94d90
--- /dev/null
+++ b/utils/src/legacy/display.rs
@@ -0,0 +1,488 @@
+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/env.rs b/utils/src/legacy/env.rs
index 1834cd3..1834cd3 100644
--- a/utils/src/env.rs
+++ b/utils/src/legacy/env.rs
diff --git a/utils/src/fs.rs b/utils/src/legacy/fs.rs
index 0050cf1..0050cf1 100644
--- a/utils/src/fs.rs
+++ b/utils/src/legacy/fs.rs
diff --git a/utils/src/globber.rs b/utils/src/legacy/globber.rs
index 7021898..4d722db 100644
--- a/utils/src/globber.rs
+++ b/utils/src/legacy/globber.rs
@@ -2,7 +2,7 @@ use std::{io::Error, path::PathBuf, str::FromStr};
use just_fmt::fmt_path::fmt_path_str;
-use crate::globber::constants::{SPLIT_STR, get_base_dir_current};
+use crate::legacy::globber::constants::{SPLIT_STR, get_base_dir_current};
pub struct Globber {
pattern: String,
diff --git a/utils/src/input.rs b/utils/src/legacy/input.rs
index bc67d90..95d53cb 100644
--- a/utils/src/input.rs
+++ b/utils/src/legacy/input.rs
@@ -1,6 +1,6 @@
use tokio::{fs, process::Command};
-use crate::env::get_default_editor;
+use crate::legacy::env::get_default_editor;
/// Confirm the current operation
/// Waits for user input of 'y' or 'n'
diff --git a/utils/src/levenshtein_distance.rs b/utils/src/legacy/levenshtein_distance.rs
index 6bdb7e7..6bdb7e7 100644
--- a/utils/src/levenshtein_distance.rs
+++ b/utils/src/legacy/levenshtein_distance.rs
diff --git a/utils/src/logger.rs b/utils/src/legacy/logger.rs
index 1bc96c1..1bc96c1 100644
--- a/utils/src/logger.rs
+++ b/utils/src/legacy/logger.rs
diff --git a/utils/src/push_version.rs b/utils/src/legacy/push_version.rs
index 6da9039..6da9039 100644
--- a/utils/src/push_version.rs
+++ b/utils/src/legacy/push_version.rs
diff --git a/utils/src/socket_addr_helper.rs b/utils/src/legacy/socket_addr_helper.rs
index 29ccd9f..29ccd9f 100644
--- a/utils/src/socket_addr_helper.rs
+++ b/utils/src/legacy/socket_addr_helper.rs
diff --git a/utils/src/lib.rs b/utils/src/lib.rs
index ef56189..ca2be9c 100644
--- a/utils/src/lib.rs
+++ b/utils/src/lib.rs
@@ -1,10 +1,6 @@
pub mod display;
-pub mod env;
-pub mod fs;
-pub mod globber;
-pub mod input;
-pub mod lazy_macros;
-pub mod levenshtein_distance;
-pub mod logger;
-pub mod push_version;
-pub mod socket_addr_helper;
+pub mod macros;
+pub mod math;
+
+// Legacy
+pub mod legacy;
diff --git a/utils/src/lazy_macros.rs b/utils/src/macros.rs
index f1cb75e..f1cb75e 100644
--- a/utils/src/lazy_macros.rs
+++ b/utils/src/macros.rs
diff --git a/utils/src/math.rs b/utils/src/math.rs
new file mode 100644
index 0000000..42a44a1
--- /dev/null
+++ b/utils/src/math.rs
@@ -0,0 +1 @@
+pub mod levenshtein_distance;
diff --git a/utils/src/math/levenshtein_distance.rs b/utils/src/math/levenshtein_distance.rs
new file mode 100644
index 0000000..98caa20
--- /dev/null
+++ b/utils/src/math/levenshtein_distance.rs
@@ -0,0 +1,38 @@
+use std::cmp::min;
+
+pub fn levenshtein_distance(a: &str, b: &str) -> usize {
+ let a_len = a.chars().count();
+ let b_len = b.chars().count();
+
+ if a_len == 0 {
+ return b_len;
+ }
+ if b_len == 0 {
+ return a_len;
+ }
+
+ let mut prev_row: Vec<usize> = (0..=b_len).collect();
+ let mut curr_row = vec![0; b_len + 1];
+
+ let mut a_chars = a.chars();
+
+ for i in 1..=a_len {
+ let a_char = a_chars.next().unwrap();
+ curr_row[0] = i;
+
+ let mut b_chars = b.chars();
+ for j in 1..=b_len {
+ let b_char = b_chars.next().unwrap();
+
+ let cost = if a_char == b_char { 0 } else { 1 };
+ curr_row[j] = min(
+ prev_row[j] + 1,
+ min(curr_row[j - 1] + 1, prev_row[j - 1] + cost),
+ );
+ }
+
+ std::mem::swap(&mut prev_row, &mut curr_row);
+ }
+
+ prev_row[b_len]
+}