From 7b7d4f576c75b0ebba6baa93a315f5339ed28748 Mon Sep 17 00:00:00 2001 From: kjuulh Date: Fri, 25 Jul 2025 23:40:09 +0200 Subject: [PATCH] feat: create noil --- .drone.yml | 2 + .gitignore | 2 + Cargo.lock | 1000 +++++++++++++++++++++++++++++++++ Cargo.toml | 16 + README.md | 2 + crates/noil/.gitignore | 1 + crates/noil/Cargo.toml | 20 + crates/noil/src/main.rs | 842 +++++++++++++++++++++++++++ cuddle.yaml | 17 + mise.toml | 14 + renovate.json | 3 + templates/docker-compose.yaml | 15 + 12 files changed, 1934 insertions(+) create mode 100644 .drone.yml create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 README.md create mode 100644 crates/noil/.gitignore create mode 100644 crates/noil/Cargo.toml create mode 100644 crates/noil/src/main.rs create mode 100644 cuddle.yaml create mode 100644 mise.toml create mode 100644 renovate.json create mode 100644 templates/docker-compose.yaml diff --git a/.drone.yml b/.drone.yml new file mode 100644 index 0000000..77bff5c --- /dev/null +++ b/.drone.yml @@ -0,0 +1,2 @@ +kind: template +load: cuddle-rust-cli-plan.yaml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9c4c004 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +target/ +.cuddle/ diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..1837da7 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1000 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "anstream" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is-terminal", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41ed9a86bf92ae6580e0a31281f65a1b1d867c0cc68d5346e2ae128dddfa6a7d" + +[[package]] +name = "anstyle-parse" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e765fd216e48e067936442276d1d57399e37bce53c264d6fefbe298080cb57ee" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +dependencies = [ + "windows-sys 0.48.0", +] + +[[package]] +name = "anstyle-wincon" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188" +dependencies = [ + "anstyle", + "windows-sys 0.48.0", +] + +[[package]] +name = "anyhow" +version = "1.0.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" + +[[package]] +name = "blake3" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3888aaa89e4b2a40fca9848e400f6a658a5a3978de7be858e209cafa8be9a4a0" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", +] + +[[package]] +name = "bstr" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "bytes" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" + +[[package]] +name = "cc" +version = "1.2.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "deec109607ca693028562ed836a5f1c4b8bd77755c4e132fc5ce11b0b6211ae7" +dependencies = [ + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clap" +version = "4.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80672091db20273a15cf9fdd4e47ed43b5091ec9841bf4c6145c9dfbbcae09ed" +dependencies = [ + "clap_builder", + "clap_derive", + "once_cell", +] + +[[package]] +name = "clap_builder" +version = "4.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1458a1df40e1e2afebb7ab60ce55c1fa8f431146205aa5f4887e0b111c27636" +dependencies = [ + "anstream", + "anstyle", + "bitflags 1.3.2", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8cd2b2a819ad6eec39e8f1d6b53001af1e5469f8c177579cdaeb313115b825f" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b" + +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + +[[package]] +name = "constant_time_eq" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + +[[package]] +name = "dotenvy" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" + +[[package]] +name = "errno" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "getrandom" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", +] + +[[package]] +name = "globset" +version = "0.4.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54a1028dfc5f5df5da8a56a73e6c153c9a9708ec57232470703592a3f18e49f5" +dependencies = [ + "aho-corasick", + "bstr", + "log", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "hermit-abi" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" +dependencies = [ + "libc", +] + +[[package]] +name = "hermit-abi" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" + +[[package]] +name = "ignore" +version = "0.4.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d89fd380afde86567dfba715db065673989d6253f42b88179abd3eae47bda4b" +dependencies = [ + "crossbeam-deque", + "globset", + "log", + "memchr", + "regex-automata", + "same-file", + "walkdir", + "winapi-util", +] + +[[package]] +name = "io-lifetimes" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" +dependencies = [ + "hermit-abi 0.3.1", + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "is-terminal" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f" +dependencies = [ + "hermit-abi 0.3.1", + "io-lifetimes", + "rustix", + "windows-sys 0.48.0", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.174" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" + +[[package]] +name = "linux-raw-sys" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" + +[[package]] +name = "lock_api" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" + +[[package]] +name = "memchr" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" + +[[package]] +name = "mio" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" +dependencies = [ + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys 0.48.0", +] + +[[package]] +name = "noil" +version = "0.1.0" +dependencies = [ + "anyhow", + "blake3", + "clap", + "dotenvy", + "ignore", + "pretty_assertions", + "rand", + "tokio", + "tracing", + "tracing-subscriber", + "walkdir", +] + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "num_cpus" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" +dependencies = [ + "hermit-abi 0.2.6", + "libc", +] + +[[package]] +name = "once_cell" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.48.0", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "pretty_assertions" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d" +dependencies = [ + "diff", + "yansi", +] + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom", +] + +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "rustix" +version = "0.37.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b96e891d04aa506a6d1f318d2771bcb1c7dfda84e126660ace067c9b474bb2c0" +dependencies = [ + "bitflags 1.3.2", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys 0.48.0", +] + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "sharded-slab" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +dependencies = [ + "libc", +] + +[[package]] +name = "smallvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" + +[[package]] +name = "socket2" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "syn" +version = "2.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thread_local" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "tokio" +version = "1.28.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94d7b1cfd2aa4011f2de74c2c4c63665e27a71006b0a192dcd2710272e73dfa2" +dependencies = [ + "autocfg", + "bytes", + "libc", + "mio", + "num_cpus", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.48.0", +] + +[[package]] +name = "tokio-macros" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing" +version = "0.1.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +dependencies = [ + "cfg-if", + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f57e3ca2a01450b1a921183a9c9cbfda207fd822cef4ccb00a65402cbba7a74" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +dependencies = [ + "nu-ansi-term", + "sharded-slab", + "smallvec", + "thread_local", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "unicode-ident" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" + +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[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.0", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.0", +] + +[[package]] +name = "windows-targets" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" +dependencies = [ + "windows_aarch64_gnullvm 0.48.0", + "windows_aarch64_msvc 0.48.0", + "windows_i686_gnu 0.48.0", + "windows_i686_msvc 0.48.0", + "windows_x86_64_gnu 0.48.0", + "windows_x86_64_gnullvm 0.48.0", + "windows_x86_64_msvc 0.48.0", +] + +[[package]] +name = "windows-targets" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "windows_aarch64_gnullvm 0.52.0", + "windows_aarch64_msvc 0.52.0", + "windows_i686_gnu 0.52.0", + "windows_i686_msvc 0.52.0", + "windows_x86_64_gnu 0.52.0", + "windows_x86_64_gnullvm 0.52.0", + "windows_x86_64_msvc 0.52.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" + +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags 2.9.1", +] + +[[package]] +name = "yansi" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" + +[[package]] +name = "zerocopy" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..80d8f21 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,16 @@ +[workspace] +members = ["crates/*"] +resolver = "2" + +[workspace.package] +version = "0.1.0" + +[workspace.dependencies] +noil = { path = "crates/noil" } + +anyhow = { version = "1" } +tokio = { version = "1", features = ["full"] } +tracing = { version = "0.1", features = ["log"] } +tracing-subscriber = { version = "0.3.18" } +clap = { version = "4", features = ["derive", "env"] } +dotenvy = { version = "0.15" } diff --git a/README.md b/README.md new file mode 100644 index 0000000..6928ab3 --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# noil + diff --git a/crates/noil/.gitignore b/crates/noil/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/crates/noil/.gitignore @@ -0,0 +1 @@ +/target diff --git a/crates/noil/Cargo.toml b/crates/noil/Cargo.toml new file mode 100644 index 0000000..59e07c4 --- /dev/null +++ b/crates/noil/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "noil" +edition = "2021" + +version.workspace = true + +[dependencies] +anyhow.workspace = true +tokio.workspace = true +tracing.workspace = true +tracing-subscriber.workspace = true +clap.workspace = true +dotenvy.workspace = true +walkdir = "2.5.0" +ignore = "0.4.23" +blake3 = "1.8.2" +rand = "0.9.2" + +[dev-dependencies] +pretty_assertions = "1.4.1" diff --git a/crates/noil/src/main.rs b/crates/noil/src/main.rs new file mode 100644 index 0000000..8fff82c --- /dev/null +++ b/crates/noil/src/main.rs @@ -0,0 +1,842 @@ +use std::{ + env::temp_dir, + fmt::{Display, Write}, + path::{Path, PathBuf}, +}; + +use anyhow::Context; +use clap::{Parser, Subcommand}; +use tokio::io::{AsyncReadExt, AsyncWriteExt}; + +#[derive(Parser)] +#[command(author, version, about, long_about = None)] +struct Command { + #[command(subcommand)] + command: Option, + + #[arg()] + path: Option, + + #[arg(long = "no-color", default_value = "false")] + no_color: bool, +} + +#[derive(Subcommand)] +enum Commands { + Edit { + #[arg()] + path: PathBuf, + }, + Apply {}, + Fmt {}, +} + +const ALPHABET: &[u8] = b"abcdefghijklmnopqrstuvwxyz0123456789"; +const ALPHABET_LEN: u32 = ALPHABET.len() as u32; + +fn encode_256bit_base36(input: &[u8; 32]) -> String { + let mut num = *input; + let mut output = Vec::with_capacity(52); // log_36(2^256) ≈ 50.7 + + while num.iter().any(|&b| b != 0) { + let mut rem: u32 = 0; + for byte in num.iter_mut() { + let acc = ((rem as u16) << 8) | *byte as u16; + *byte = (acc / ALPHABET_LEN as u16) as u8; + rem = (acc % ALPHABET_LEN as u16) as u32; + } + output.push(ALPHABET[rem as usize]); + } + + if output.is_empty() { + output.push(ALPHABET[0]); + } + + output.reverse(); + String::from_utf8(output).unwrap() +} + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + dotenvy::dotenv().ok(); + tracing_subscriber::fmt::init(); + + let cli = Command::parse(); + tracing::debug!("Starting cli"); + + match cli.command { + Some(Commands::Edit { path }) => { + let mut small_id = Vec::with_capacity(8); + for id in small_id.iter_mut() { + *id = ALPHABET[rand::random_range(0..(ALPHABET_LEN as u8)) as usize]; + } + let small_id = String::from_utf8_lossy(&small_id); + + let file_path = temp_dir() + .join("noil") + .join(small_id.to_string()) + .join("buf.noil"); + + if let Some(parent) = file_path.parent() { + tokio::fs::create_dir_all(parent) + .await + .context("failed to create temp dir file")?; + } + + let mut file = tokio::fs::File::create(&file_path) + .await + .context("create temp file for noil")?; + + let output = get_outputs(&path, true).await?; + file.write_all(output.as_bytes()).await?; + file.flush().await?; + + let editor = std::env::var("EDITOR").context("EDITOR not found in env")?; + + let mut cmd = tokio::process::Command::new(editor.trim()); + cmd.arg(&file_path); + + let mut process = cmd.spawn()?; + let status = process.wait().await.context("editor closed prematurely")?; + if !status.success() { + let code = status.code().unwrap_or(-1); + anyhow::bail!("editor exited: {code}"); + } + + let noil_content = tokio::fs::read_to_string(&file_path) + .await + .context("read noil file")?; + + write_changes(&noil_content).await?; + + todo!() + } + Some(Commands::Fmt {}) => { + let mut stdin = tokio::io::stdin(); + let mut buffer = Vec::new(); + + stdin.read_to_end(&mut buffer).await?; + + let input = String::from_utf8_lossy(&buffer); + + let output = format(&input)?; + + let mut stdout = tokio::io::stdout(); + stdout.write_all(output.as_bytes()).await?; + stdout.flush().await?; + } + Some(Commands::Apply {}) => { + let mut stdin = tokio::io::stdin(); + let mut buffer = Vec::new(); + + stdin.read_to_end(&mut buffer).await?; + + let input = String::from_utf8_lossy(&buffer); + + write_changes(&input).await?; + } + None => { + let path = match &cli.path { + Some(path) => path, + None => anyhow::bail!("a path is required if just using noil"), + }; + + let output = get_outputs(path, cli.no_color).await?; + + let mut stdout = tokio::io::stdout(); + stdout.write_all(output.as_bytes()).await?; + stdout.flush().await?; + } + } + + Ok(()) +} + +async fn write_changes(input: &str) -> anyhow::Result<()> { + let noil_index = parse(input).context("parse input")?; + + fn print_op(key: &str, index: Option<&str>, path: Option<&Path>) { + match index { + Some(index) => match path { + Some(path) => println!("OP: {key} ({index}) - {}", path.display()), + + None => println!("OP: {key} ({index})"), + }, + None => match path { + Some(path) => { + println!("OP: {key} - {}", path.display()) + } + None => println!("OP: {key}"), + }, + } + } + + println!("Changes: \n"); + for item in noil_index.files { + match item.entry.operation { + Operation::Add => print_op("ADD", None, Some(&item.path)), + Operation::Copy { index } => print_op("COPY", Some(&index), Some(&item.path)), + Operation::Delete { index } => print_op("DELETE", Some(&index), Some(&item.path)), + Operation::Move { index } => print_op("MOVE", Some(&index), Some(&item.path)), + _ => {} + } + } + print!("\nApply changes? (y/N): "); + println!(); + + Ok(()) +} + +async fn get_outputs(path: &Path, no_color: bool) -> anyhow::Result { + let mut paths = Vec::new(); + for entry in ignore::WalkBuilder::new(path) + .hidden(true) + .git_ignore(true) + .ignore(true) + .build() + { + let entry = entry?; + + let hash = blake3::hash(entry.path().to_string_lossy().as_bytes()); + + let hash_output = encode_256bit_base36(hash.as_bytes()); + + paths.push((hash_output, entry.into_path())); + } + + paths.sort_by_key(|(h, _p)| h.clone()); + + let hashes = paths.iter().map(|(h, _)| h.as_str()).collect::>(); + let (shortest_len, _global_prefixes, individual_prefixes) = shortest_unique_prefixes(&hashes); + + let mut paths = paths + .into_iter() + .enumerate() + .map(|(index, (_, p))| (&_global_prefixes[index], &individual_prefixes[index], p)) + .collect::>(); + + paths.sort_by_key(|(_, _h, p)| p.clone()); + + let mut lines = Vec::new(); + + for (prefix, individual_prefix, path) in paths { + let path_str = path.display().to_string(); + let mut line = String::new(); + write!( + &mut line, + "{}{} : {}{}", + { + if no_color { + prefix + } else if let Some(suffix) = prefix.strip_prefix(individual_prefix) { + &format!("*{individual_prefix}*{suffix}") + } else { + prefix + } + }, + " ".repeat(shortest_len - prefix.len()), + path_str, + { + if path.is_dir() && !path.to_string_lossy().trim_end().ends_with("/") { + "/" + } else { + "" + } + } + )?; + + lines.push(line); + } + + Ok(lines.join("\n")) +} + +fn format(input: &str) -> anyhow::Result { + let noil_index = parse(input).context("parse input")?; + + let max_op_len = noil_index + .files + .iter() + .map(|f| f.entry.operation.to_string().len()) + .max() + .unwrap_or_default(); + let max_prefix_len = noil_index + .files + .iter() + .map(|f| match &f.entry.operation { + Operation::Copy { index } + | Operation::Delete { index } + | Operation::Move { index } + | Operation::Existing { index } => index.len(), + Operation::Add => 0, + }) + .max() + .unwrap_or_default(); + + let mut output_buf = Vec::new(); + + for file in noil_index.files { + let mut line = String::new(); + let space = " "; + + // Write operation + let operation = file.entry.operation.to_string(); + if !operation.is_empty() { + let spaces = max_op_len - operation.len(); + line.write_str(&operation)?; + line.write_str(&space.repeat(spaces))?; + } else { + line.write_str(&space.repeat(max_op_len))?; + } + if max_op_len > 0 { + line.write_str(&space.repeat(3))?; + } + + // Write index + let index = match file.entry.operation { + Operation::Copy { index } + | Operation::Delete { index } + | Operation::Move { index } + | Operation::Existing { index } => Some(index), + Operation::Add => None, + }; + + if let Some(index) = index { + let spaces = max_prefix_len - index.len(); + line.write_str(&index)?; + line.write_str(&space.repeat(spaces))?; + } else { + line.write_str(&space.repeat(max_prefix_len))?; + } + if max_prefix_len > 0 { + line.write_str(&space.repeat(3))?; + } + + // Write divider + line.write_str(":")?; + line.write_str(&space.repeat(3))?; + + // Write path + line.write_str(&file.path.display().to_string())?; + + output_buf.push(line); + } + + let output = output_buf.join("\n"); + + Ok(output) +} + +#[cfg(test)] +mod test_format { + #[test] + fn can_format_complex_file() -> anyhow::Result<()> { + let input = r#" +C asdf : /Somasdlf +as : /bla/bla/bla +MOVE assdfasdf : /bla/bla/bla +RENAME asdf23 : /bla/bla/bla +a : /bla/bla/bla + +123 : /123123/1231 + "#; + + let expected = r#" +COPY asdf : /Somasdlf + as : /bla/bla/bla +MOVE assdfasdf : /bla/bla/bla +MOVE asdf23 : /bla/bla/bla + a : /bla/bla/bla + 123 : /123123/1231 + "# + .trim(); + + let output = super::format(input)?; + + pretty_assertions::assert_eq!(expected, &output); + + Ok(()) + } + + #[test] + fn can_format_no_op() -> anyhow::Result<()> { + let input = r#" +asdf : /Somasdlf +as : /bla/bla/bla + assdfasdf : /bla/bla/bla + asdf23 : /bla/bla/bla +a : /bla/bla/bla + +123 : /123123/1231 + "#; + + let expected = r#" +asdf : /Somasdlf +as : /bla/bla/bla +assdfasdf : /bla/bla/bla +asdf23 : /bla/bla/bla +a : /bla/bla/bla +123 : /123123/1231 + "# + .trim(); + + let output = super::format(input)?; + + pretty_assertions::assert_eq!(expected, &output); + + Ok(()) + } +} + +#[derive(Clone, PartialEq, Debug)] +pub struct Buffer { + files: Vec, +} + +#[derive(Clone, PartialEq, Debug)] +pub struct File { + path: PathBuf, + entry: FileEntry, +} + +#[derive(Clone, PartialEq, Debug)] +pub struct FileEntry { + raw_op: Option, + operation: Operation, +} + +#[derive(Clone, PartialEq, Debug)] +pub enum Operation { + Existing { index: String }, + Add, + Copy { index: String }, + Delete { index: String }, + Move { index: String }, +} + +impl Display for Operation { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let op = match self { + Operation::Existing { .. } => "", + Operation::Add => "ADD", + Operation::Copy { .. } => "COPY", + Operation::Delete { .. } => "DELETE", + Operation::Move { .. } => "MOVE", + }; + + f.write_str(op) + } +} + +impl FileEntry { + fn parse(file_entry: &str) -> anyhow::Result { + let items = file_entry.split(' ').collect::>(); + + // get left most non-empty + let Some(first) = items.first() else { + anyhow::bail!("not a valid file entry, doesn't contain anything"); + }; + + let Some(last) = items.last() else { + anyhow::bail!("not a valid file entry, doesn't contain anything"); + }; + + if first == last && !first.chars().any(|c| c.is_uppercase()) { + // We've got a raw index + + return Ok(Self { + raw_op: None, + operation: Operation::Existing { + index: first.to_string(), + }, + }); + } + + let index = last.to_string(); + + let op = match *first { + // ADD: first == last is sanity check there there is nothing else for this operation + "A" | "ADD" if first == last => Operation::Add {}, + // COPY: First cannot be equal last here, otherwise there is no index + "C" | "COPY" if first != last => Operation::Copy { index }, + // DELETE: + "D" | "DEL" | "DELETE" if first != last => Operation::Delete { index }, + // MOVE: + "M" | "MV" | "MOVE" | "RENAME" if first != last => Operation::Move { index }, + o => { + anyhow::bail!("operation: {} is not supported", o); + } + }; + + Ok(FileEntry { + raw_op: Some(first.to_string()), + operation: op, + }) + } +} + +#[cfg(test)] +mod test_2 { + use crate::{parse, Buffer, File, FileEntry}; + + #[test] + fn can_parse_item() -> anyhow::Result<()> { + let input = r#" +abc : /var/my +ecd : /var/my/path +"#; + + let output = parse(input)?; + + pretty_assertions::assert_eq!( + Buffer { + files: vec![ + File { + path: "/var/my".into(), + entry: FileEntry { + raw_op: None, + operation: crate::Operation::Existing { + index: "abc".into() + }, + }, + }, + File { + path: "/var/my/path".into(), + entry: FileEntry { + raw_op: None, + operation: crate::Operation::Existing { + index: "ecd".into() + } + } + } + ] + }, + output + ); + + Ok(()) + } + + #[test] + fn can_parse_item_add_operation() -> anyhow::Result<()> { + let input = r#" +abc : /var/my +ecd : /var/my/path +A : /var/my/path/new-path +ADD : /var/my/path/new-long-path +"#; + + let output = parse(input)?; + + pretty_assertions::assert_eq!( + Buffer { + files: vec![ + File { + path: "/var/my".into(), + entry: FileEntry { + raw_op: None, + operation: crate::Operation::Existing { + index: "abc".into() + } + }, + }, + File { + path: "/var/my/path".into(), + entry: FileEntry { + raw_op: None, + operation: crate::Operation::Existing { + index: "ecd".into() + } + }, + }, + File { + path: "/var/my/path/new-path".into(), + entry: FileEntry { + raw_op: Some("A".into()), + operation: crate::Operation::Add, + }, + }, + File { + path: "/var/my/path/new-long-path".into(), + entry: FileEntry { + raw_op: Some("ADD".into()), + operation: crate::Operation::Add, + } + } + ] + }, + output + ); + + Ok(()) + } + + #[test] + fn can_parse_item_copy_operation() -> anyhow::Result<()> { + let input = r#" +abc : /var/my +ecd : /var/my/path +C abc : /var/my/path/copy-into +COPY ecd : /var/my/path/copy-into-long +"#; + + let output = parse(input)?; + + pretty_assertions::assert_eq!( + Buffer { + files: vec![ + File { + path: "/var/my".into(), + entry: FileEntry { + raw_op: None, + operation: crate::Operation::Existing { + index: "abc".into() + } + }, + }, + File { + path: "/var/my/path".into(), + entry: FileEntry { + raw_op: None, + operation: crate::Operation::Existing { + index: "ecd".into() + } + }, + }, + File { + path: "/var/my/path/copy-into".into(), + entry: FileEntry { + raw_op: Some("C".into()), + operation: crate::Operation::Copy { + index: "abc".into() + }, + }, + }, + File { + path: "/var/my/path/copy-into-long".into(), + entry: FileEntry { + raw_op: Some("COPY".into()), + operation: crate::Operation::Copy { + index: "ecd".into() + }, + } + } + ] + }, + output + ); + + Ok(()) + } + #[test] + fn can_parse_item_delete_operation() -> anyhow::Result<()> { + let input = r#" +D abc : /var/my +DEL ecd : /var/my/path +DELETE ecd : /var/my/path +"#; + + let output = parse(input)?; + + pretty_assertions::assert_eq!( + Buffer { + files: vec![ + File { + path: "/var/my".into(), + entry: FileEntry { + raw_op: Some("D".into()), + operation: crate::Operation::Delete { + index: "abc".into() + } + }, + }, + File { + path: "/var/my/path".into(), + entry: FileEntry { + raw_op: Some("DEL".into()), + operation: crate::Operation::Delete { + index: "ecd".into() + } + }, + }, + File { + path: "/var/my/path".into(), + entry: FileEntry { + raw_op: Some("DELETE".into()), + operation: crate::Operation::Delete { + index: "ecd".into() + } + }, + }, + ] + }, + output + ); + + Ok(()) + } + + #[test] + fn can_parse_item_move_operation() -> anyhow::Result<()> { + let input = r#" +abc : /var/my +ecd : /var/my/path +M abc : /var/my/some-different-place +MV ecd : /var/my/some-different-place +MOVE ecd : /var/my/some-different-place +RENAME ecd : /var/my/some-different-place +"#; + + let output = parse(input)?; + + pretty_assertions::assert_eq!( + Buffer { + files: vec![ + File { + path: "/var/my".into(), + entry: FileEntry { + raw_op: None, + operation: crate::Operation::Existing { + index: "abc".into() + } + }, + }, + File { + path: "/var/my/path".into(), + entry: FileEntry { + raw_op: None, + operation: crate::Operation::Existing { + index: "ecd".into() + } + }, + }, + File { + path: "/var/my/some-different-place".into(), + entry: FileEntry { + raw_op: Some("M".into()), + operation: crate::Operation::Move { + index: "abc".into() + } + }, + }, + File { + path: "/var/my/some-different-place".into(), + entry: FileEntry { + raw_op: Some("MV".into()), + operation: crate::Operation::Move { + index: "ecd".into() + } + }, + }, + File { + path: "/var/my/some-different-place".into(), + entry: FileEntry { + raw_op: Some("MOVE".into()), + operation: crate::Operation::Move { + index: "ecd".into() + } + }, + }, + File { + path: "/var/my/some-different-place".into(), + entry: FileEntry { + raw_op: Some("RENAME".into()), + operation: crate::Operation::Move { + index: "ecd".into() + } + }, + }, + ] + }, + output + ); + + Ok(()) + } +} + +fn parse(input: &str) -> anyhow::Result { + let mut files = Vec::default(); + // We are keeping parsing simple. For each line take any non empty lines, the first part should be an index. This is where the magic happens, if it contains special tokens handle accordingly, the path always comes after a :. + for line in input.lines() { + if let Some((left, right)) = line.trim().rsplit_once(" : ") { + let path = PathBuf::from(right.trim()); + let file_entry = FileEntry::parse(left.trim())?; + + files.push(File { + path, + entry: file_entry, + }) + } + } + + Ok(Buffer { files }) +} + +fn shortest_unique_prefixes(values: &[&str]) -> (usize, Vec, Vec) { + if values.is_empty() { + return (0, Vec::new(), Vec::new()); + } + + let len = values[0].len(); + let mut global_prefix_len = 0; + let mut individual_prefixes = Vec::with_capacity(values.len()); + + // Helper to find shared prefix length + fn shared_prefix_len(a: &str, b: &str) -> usize { + a.chars() + .zip(b.chars()) + .take_while(|(ac, bc)| ac == bc) + .count() + } + + for i in 0..values.len() { + let cur = values[i]; + let mut max_shared = 0; + + if i > 0 { + max_shared = max_shared.max(shared_prefix_len(cur, values[i - 1])); + } + if i + 1 < values.len() { + max_shared = max_shared.max(shared_prefix_len(cur, values[i + 1])); + } + + // Add 1 to ensure uniqueness + let unique_len = (max_shared + 1).min(len); + individual_prefixes.push(cur[..unique_len].to_string()); + + // For global prefix: max shared between any two neighbors + if i + 1 < values.len() { + global_prefix_len = global_prefix_len.max(shared_prefix_len(cur, values[i + 1]) + 1); + } + } + + global_prefix_len = global_prefix_len.min(len); + let global_prefixes = values + .iter() + .map(|s| s[..global_prefix_len].to_string()) + .collect(); + + (global_prefix_len, global_prefixes, individual_prefixes) +} + +#[cfg(test)] +mod test { + use crate::shortest_unique_prefixes; + + #[test] + fn simple_shortest() { + let mut input = vec!["1ab", "3ab", "1ca"]; + let expected_len: usize = 2; + let expected_global: Vec = vec!["1a".into(), "1c".into(), "3a".into()]; + let expected_individual: Vec = vec!["1a".into(), "1c".into(), "3".into()]; + + input.sort(); + + let (len, global_prefixes, individual_prefixes) = shortest_unique_prefixes(&input); + + assert_eq!(expected_len, len); + assert_eq!(expected_global, global_prefixes); + assert_eq!(expected_individual, individual_prefixes); + } +} diff --git a/cuddle.yaml b/cuddle.yaml new file mode 100644 index 0000000..d4205c2 --- /dev/null +++ b/cuddle.yaml @@ -0,0 +1,17 @@ +# yaml-language-server: $schema=https://git.front.kjuulh.io/kjuulh/cuddle/raw/branch/main/schemas/base.json + +base: "git@git.front.kjuulh.io:kjuulh/cuddle-rust-cli-plan.git" + +vars: + service: "noil" + registry: kasperhermansen + +please: + project: + owner: kjuulh + repository: "noil" + branch: "main" + settings: + api_url: "https://git.front.kjuulh.io" + actions: + rust: diff --git a/mise.toml b/mise.toml new file mode 100644 index 0000000..5bf5302 --- /dev/null +++ b/mise.toml @@ -0,0 +1,14 @@ +[tasks."run:edit"] +run = """#!/usr/bin/env zsh + +set -e + +tmp=$(mktemp) +cargo run -- --no-color target/ > "$tmp" || return 1 +${EDITOR:-hx} "$tmp" +cargo run -- apply < "$tmp" +rm "$tmp" +""" + +[tasks.test] +run = "cargo nextest run" diff --git a/renovate.json b/renovate.json new file mode 100644 index 0000000..7190a60 --- /dev/null +++ b/renovate.json @@ -0,0 +1,3 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json" +} diff --git a/templates/docker-compose.yaml b/templates/docker-compose.yaml new file mode 100644 index 0000000..8fae72a --- /dev/null +++ b/templates/docker-compose.yaml @@ -0,0 +1,15 @@ +version: "3" +services: + crdb: + restart: 'always' + image: 'cockroachdb/cockroach:v23.1.14' + command: 'start-single-node --advertise-addr 0.0.0.0 --insecure' + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:8080/health?ready=1"] + interval: '10s' + timeout: '30s' + retries: 5 + start_period: '20s' + ports: + - 8080:8080 + - '26257:26257'