diff options
| author | 魏曹先生 <1992414357@qq.com> | 2026-03-12 14:28:08 +0800 |
|---|---|---|
| committer | 魏曹先生 <1992414357@qq.com> | 2026-03-12 14:28:08 +0800 |
| commit | 0a95bae451c1847f4f0b9601e60959f4e8e6b669 (patch) | |
| tree | 9e1cfad4f86a73176a4d738b28e7732b66fe5f97 | |
| parent | 8564c8f2177dec0c2c0c031d156347fa6b4485bc (diff) | |
Refactor display utilities
34 files changed, 1187 insertions, 985 deletions
@@ -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" @@ -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(¤t_char, color); - } - - result.push_str(¤t_char); - i += 1; - } - - result -} - -// Helper function to apply color to text -fn apply_color(text: impl AsRef<str>, color_name: impl AsRef<str>) -> String { - let text = text.as_ref(); - let color_name = color_name.as_ref(); - match color_name { - // Normal colors - "black" => text.black().to_string(), - "red" => text.red().to_string(), - "green" => text.green().to_string(), - "yellow" => text.yellow().to_string(), - "blue" => text.blue().to_string(), - "magenta" => text.magenta().to_string(), - "cyan" => text.cyan().to_string(), - "white" => text.white().to_string(), - "bright_black" => text.bright_black().to_string(), - "bright_red" => text.bright_red().to_string(), - "bright_green" => text.bright_green().to_string(), - "bright_yellow" => text.bright_yellow().to_string(), - "bright_blue" => text.bright_blue().to_string(), - "bright_magenta" => text.bright_magenta().to_string(), - "bright_cyan" => text.bright_cyan().to_string(), - "bright_white" => text.bright_white().to_string(), - - // Short aliases for bright colors - "b_black" => text.bright_black().to_string(), - "b_red" => text.bright_red().to_string(), - "b_green" => text.bright_green().to_string(), - "b_yellow" => text.bright_yellow().to_string(), - "b_blue" => text.bright_blue().to_string(), - "b_magenta" => text.bright_magenta().to_string(), - "b_cyan" => text.bright_cyan().to_string(), - "b_white" => text.bright_white().to_string(), - - // Gray colors using truecolor - "gray" | "grey" => text.truecolor(128, 128, 128).to_string(), - "bright_gray" | "bright_grey" => text.truecolor(192, 192, 192).to_string(), - "b_gray" | "b_grey" => text.truecolor(192, 192, 192).to_string(), - - // Default to white if color not recognized - _ => text.to_string(), - } -} - -/// Render a HashMap of PathBuf to SheetMappingMetadata as a tree string. -pub fn render_share_path_tree(paths: &HashMap<PathBuf, SheetMappingMetadata>) -> String { - if paths.is_empty() { - return String::new(); - } - - // Collect all path components into a tree structure - let mut root = TreeNode::new("".to_string()); - - for (path, metadata) in paths { - let mut current = &mut root; - let components: Vec<String> = path - .components() - .filter_map(|comp| match comp { - std::path::Component::Normal(s) => s.to_str().map(|s| s.to_string()), - _ => None, - }) - .collect(); - - for (i, comp) in components.iter().enumerate() { - let is_leaf = i == components.len() - 1; - let child = current - .children - .entry(comp.clone()) - .or_insert_with(|| TreeNode::new(comp.clone())); - - // If this is the leaf node, store the metadata - if is_leaf { - child.metadata = Some((metadata.id.clone(), metadata.version.clone())); - } - - current = child; - } - } - - // Convert tree to string representation - let mut result = String::new(); - let is_root = true; - let prefix = String::new(); - let last_stack = vec![true]; // Root is always "last" - - add_tree_node_to_string(&root, &mut result, is_root, &prefix, &last_stack); - - result -} - -/// Internal tree node structure for building the path tree -#[derive(Debug)] -struct TreeNode { - name: String, - children: BTreeMap<String, TreeNode>, // Use BTreeMap for sorted output - metadata: Option<(String, String)>, // Store (id, version) for leaf nodes -} - -impl TreeNode { - fn new(name: String) -> Self { - Self { - name, - children: BTreeMap::new(), - metadata: None, - } - } -} - -/// Recursively add tree node to string representation -fn add_tree_node_to_string( - node: &TreeNode, - result: &mut String, - is_root: bool, - prefix: &str, - last_stack: &[bool], -) { - if !is_root { - // Add the tree prefix for this node - for &is_last in &last_stack[1..] { - if is_last { - result.push_str(" "); - } else { - result.push_str("│ "); - } - } - - // Add the connector for this node - if let Some(&is_last) = last_stack.last() { - if is_last { - result.push_str("└── "); - } else { - result.push_str("├── "); - } - } - - // Add node name - result.push_str(&node.name); - - // Add metadata for leaf nodes - if let Some((id, version)) = &node.metadata { - // Truncate id to first 11 characters - let truncated_id = if id.len() > 11 { &id[..11] } else { id }; - result.push_str(&format!(" [{}|{}]", truncated_id, version)); - } - - result.push('\n'); - } - - // Process children - let child_count = node.children.len(); - for (i, (_, child)) in node.children.iter().enumerate() { - let is_last_child = i == child_count - 1; - let mut new_last_stack = last_stack.to_vec(); - new_last_stack.push(is_last_child); - - add_tree_node_to_string(child, result, false, prefix, &new_last_stack); - } -} +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(¤t_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(¤t_char, color); + } + + result.push_str(¤t_char); + i += 1; + } + + result +} + +// Helper function to apply color to text +fn apply_color(text: impl AsRef<str>, color_name: impl AsRef<str>) -> String { + let text = text.as_ref(); + let color_name = color_name.as_ref(); + match color_name { + // Normal colors + "black" => text.black().to_string(), + "red" => text.red().to_string(), + "green" => text.green().to_string(), + "yellow" => text.yellow().to_string(), + "blue" => text.blue().to_string(), + "magenta" => text.magenta().to_string(), + "cyan" => text.cyan().to_string(), + "white" => text.white().to_string(), + "bright_black" => text.bright_black().to_string(), + "bright_red" => text.bright_red().to_string(), + "bright_green" => text.bright_green().to_string(), + "bright_yellow" => text.bright_yellow().to_string(), + "bright_blue" => text.bright_blue().to_string(), + "bright_magenta" => text.bright_magenta().to_string(), + "bright_cyan" => text.bright_cyan().to_string(), + "bright_white" => text.bright_white().to_string(), + + // Short aliases for bright colors + "b_black" => text.bright_black().to_string(), + "b_red" => text.bright_red().to_string(), + "b_green" => text.bright_green().to_string(), + "b_yellow" => text.bright_yellow().to_string(), + "b_blue" => text.bright_blue().to_string(), + "b_magenta" => text.bright_magenta().to_string(), + "b_cyan" => text.bright_cyan().to_string(), + "b_white" => text.bright_white().to_string(), + + // Gray colors using truecolor + "gray" | "grey" => text.truecolor(128, 128, 128).to_string(), + "bright_gray" | "bright_grey" => text.truecolor(192, 192, 192).to_string(), + "b_gray" | "b_grey" => text.truecolor(192, 192, 192).to_string(), + + // Default to white if color not recognized + _ => text.to_string(), + } +} + +/// Render a HashMap of PathBuf to SheetMappingMetadata as a tree string. +pub fn render_share_path_tree(paths: &HashMap<PathBuf, SheetMappingMetadata>) -> String { + if paths.is_empty() { + return String::new(); + } + + // Collect all path components into a tree structure + let mut root = TreeNode::new("".to_string()); + + for (path, metadata) in paths { + let mut current = &mut root; + let components: Vec<String> = path + .components() + .filter_map(|comp| match comp { + std::path::Component::Normal(s) => s.to_str().map(|s| s.to_string()), + _ => None, + }) + .collect(); + + for (i, comp) in components.iter().enumerate() { + let is_leaf = i == components.len() - 1; + let child = current + .children + .entry(comp.clone()) + .or_insert_with(|| TreeNode::new(comp.clone())); + + // If this is the leaf node, store the metadata + if is_leaf { + child.metadata = Some((metadata.id.clone(), metadata.version.clone())); + } + + current = child; + } + } + + // Convert tree to string representation + let mut result = String::new(); + let is_root = true; + let prefix = String::new(); + let last_stack = vec![true]; // Root is always "last" + + add_tree_node_to_string(&root, &mut result, is_root, &prefix, &last_stack); + + result +} + +/// Internal tree node structure for building the path tree +#[derive(Debug)] +struct TreeNode { + name: String, + children: BTreeMap<String, TreeNode>, // Use BTreeMap for sorted output + metadata: Option<(String, String)>, // Store (id, version) for leaf nodes +} + +impl TreeNode { + fn new(name: String) -> Self { + Self { + name, + children: BTreeMap::new(), + metadata: None, + } + } +} + +/// Recursively add tree node to string representation +fn add_tree_node_to_string( + node: &TreeNode, + result: &mut String, + is_root: bool, + prefix: &str, + last_stack: &[bool], +) { + if !is_root { + // Add the tree prefix for this node + for &is_last in &last_stack[1..] { + if is_last { + result.push_str(" "); + } else { + result.push_str("│ "); + } + } + + // Add the connector for this node + if let Some(&is_last) = last_stack.last() { + if is_last { + result.push_str("└── "); + } else { + result.push_str("├── "); + } + } + + // Add node name + result.push_str(&node.name); + + // Add metadata for leaf nodes + if let Some((id, version)) = &node.metadata { + // Truncate id to first 11 characters + let truncated_id = if id.len() > 11 { &id[..11] } else { id }; + result.push_str(&format!(" [{}|{}]", truncated_id, version)); + } + + result.push('\n'); + } + + // Process children + let child_count = node.children.len(); + for (i, (_, child)) in node.children.iter().enumerate() { + let is_last_child = i == child_count - 1; + let mut new_last_stack = last_stack.to_vec(); + new_last_stack.push(is_last_child); + + add_tree_node_to_string(child, result, false, prefix, &new_last_stack); + } +} diff --git a/utils/src/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] +} |
