From abf49ddcb789cc31fd5595a142eff897f847cab4 Mon Sep 17 00:00:00 2001 From: Dan Johnson Date: Fri, 29 Aug 2025 11:53:46 -0700 Subject: [PATCH 1/2] Add load subcommand --- CHANGELOG.md | 3 ++ Cargo.lock | 16 ++++++ Cargo.toml | 1 + src/load.rs | 129 +++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 31 ++++++++++++ src/save.rs | 21 +++----- src/test_util.rs | 14 +++++ 7 files changed, 201 insertions(+), 14 deletions(-) create mode 100644 src/load.rs create mode 100644 src/test_util.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index c25d012..89b1f0e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ ## Unreleased +### Added +- New subcommand: `load`, which loads blueprint books saved with `save` + ## Version 0.1.5 ### Added diff --git a/Cargo.lock b/Cargo.lock index d5fe669..792bb2a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -267,6 +267,12 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + [[package]] name = "equivalent" version = "1.0.2" @@ -291,6 +297,7 @@ dependencies = [ "clap", "crossterm", "flate2", + "itertools", "jaq-core", "jaq-json", "jaq-std", @@ -394,6 +401,15 @@ version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.15" diff --git a/Cargo.toml b/Cargo.toml index 27d7702..c6127ff 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,7 @@ base64 = "0.22.1" clap = { version = "4.5.42", features = ["derive"] } crossterm = { version = "0.29.0", features = ["osc52"] } flate2 = { version = "1.1.2", default-features = false, features = ["zlib-rs"] } +itertools = "0.14.0" sanitize-filename = "0.6.0" serde = { version = "1.0.219", features = ["derive"] } serde_json = "1.0.142" diff --git a/src/load.rs b/src/load.rs new file mode 100644 index 0000000..fe8ddef --- /dev/null +++ b/src/load.rs @@ -0,0 +1,129 @@ +use std::{fs, path::Path}; + +use itertools::Itertools; + +fn load_file(path: &Path) -> serde_json::Value { + serde_json::from_str( + &fs::read_to_string(path) + .unwrap_or_else(|e| panic!("error reading {path:?}: {e}", path = &path)), + ) + .unwrap_or_else(|e| panic!("invalid json in {path:?}: {e}")) +} +pub fn load(path: &Path) -> serde_json::Value { + let files = match path + .read_dir() + .and_then(|files| files.collect::, _>>()) + { + Ok(files) => files, + Err(e) => match e.kind() { + std::io::ErrorKind::NotADirectory => return load_file(path), + _ => { + panic!("error loading {path:?}: {e}"); + } + }, + }; + let mut book_json: Option = None; + let mut entries = vec![]; + for file in files { + let path = file.path(); + let json = load(&path); + if file.file_name() == "book.json" { + book_json = Some(json) + } else { + let json_index: Option = json.get("index").map(|val| { + val.as_number() + .and_then(|num| num.as_u64()) + .unwrap_or_else(|| panic!("invalid index {val:?} in {path:?}")) + }); + let index = if let Some(filename) = file.file_name().to_str() + && let Some((first_word, _)) = filename.split_once(' ') + && let Ok(filename_index) = first_word.parse::() + { + if let Some(json_index) = json_index { + if filename_index != json_index { + eprintln!( + "WARN: {path:?} index mismatch; file contains index {json_index}, using index {filename_index} from filename" + ) + } + } else { + eprintln!( + "WARN: {path:?} missing index, assuming index {filename_index} from filename" + ) + } + filename_index + } else { + json_index.unwrap_or_else(|| { + panic!("error: {path:?} missing index, can't load into book") + }) + }; + + entries.push((path, index, json)); + } + } + let book_json_path = path.join("book.json"); + let Some(mut book_json) = book_json else { + panic!("{book_json_path:?} not found"); + }; + entries.sort_by_key(|(_path, index, _json)| *index); + for (l, r) in entries.iter().tuple_windows() { + if l.1 == r.1 { + panic!( + "error: {l:?} and {r:?} have same index {index}", + l = l.0, + r = r.0, + index = l.1 + ); + } + } + let blueprints = book_json + .get_mut("blueprint_book") + .unwrap_or_else(|| panic!("error: {book_json_path:?} not a blueprint book??")) + .get_mut("blueprints") + .unwrap_or_else(|| panic!("error: {book_json_path:?} missing empty blueprints array")) + .as_array_mut() + .unwrap_or_else(|| { + panic!("error: {book_json_path:?} blueprints wrong type (expected array)") + }); + if !blueprints.is_empty() { + panic!("error: {book_json_path:?} blueprints array not empty") + } + *blueprints = entries + .into_iter() + .map(|(_path, _index, json)| json) + .collect(); + book_json +} + +#[cfg(test)] +mod tests { + use std::str::FromStr as _; + + use crate::test_util::read_dir_unwrap; + + use super::*; + + // See also save tests which check round-tripping. + + #[test] + #[should_panic = "have same index 1"] + fn test_load_duplicate() { + let bp = "0eNrlUdtqg0AQ/ZUwz2uIJkYU8pIPCH0vRVadliV7sXsJDeK/d1SiLYS00Mc+ztk5lz3TQSUDtlZoX1bGnKHoFsRB8fxlHN5EbfQEO/GmuRwwzRVCAVWQ50hoh9ajhZ6B0A1+QBH3LwxQe+EFTtRxuJY6qIo2i5jdl2DQGkcsowcXUkrybJ0yuBIly9cpWTTCYj1txAmDVyGJOJnc3Gfx2rQt2shYJOX3wCVlIDjo2ihFCow2VMst94ZCwQGG3MFhOat6G3AAhUc1pJ2LYSB5hVQGHJ9WJ7JbxYRdiDRGS/dJvsvzNNum212WLNVsetb9+wNYPgJ/KP/novf3mNHY93K7EzqPzeo4oZy+dcHyJvAbl/iRy3e9zQO9/hMTWy6D"; + let dir = tempfile::tempdir().unwrap(); + let json = crate::blueprint::blueprint_to_json(bp); + let json = serde_json::Value::from_str(&json).expect("should contain valid json"); + crate::save::save(json.clone(), Some(dir.path())); + let files = read_dir_unwrap(dir.path()); + assert_eq!(files, &["Untitled"]); + let subfiles = read_dir_unwrap(&dir.path().join("Untitled")); + assert_eq!(subfiles, ["0 BP Name 1.json", "1 Nested Book", "book.json"]); + + std::fs::rename( + dir.path().join("Untitled").join("0 BP Name 1.json"), + dir.path().join("Untitled").join("1 BP Name 1.json"), + ) + .unwrap(); + + let loaded_json = load(&dir.path().join("Untitled")); + assert_eq!(loaded_json, json) + } +} diff --git a/src/main.rs b/src/main.rs index 0dc2a75..586eed8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,9 +1,13 @@ mod blueprint; mod json_walk; +mod load; mod save; +#[cfg(test)] +mod test_util; use std::fmt::Write; use std::io::{Read, stderr, stdin}; +use std::path::PathBuf; use std::str::FromStr; use blueprint::{blueprint_to_json, json_to_blueprint}; @@ -45,6 +49,16 @@ enum Commands { }, /// Saves blueprint as a .json file, or as a directory of json files if it's a blueprint book. Save { blueprint_string: Option }, + /// Loads previously-saved blueprints from the given file or directory. + Load { + file: PathBuf, + /// Sends the output to the clipboard + #[arg(long)] + to_clipboard: bool, + /// Add the current git commit to the description as a tag. + #[arg(long)] + stamp: bool, + }, } mod terminal; @@ -140,6 +154,23 @@ impl Commands { save::save(json, None); } + Commands::Load { + file, + to_clipboard, + stamp, + } => { + let json = load::load(&file); + if stamp { + unimplemented!(); + } + let bp = json_to_blueprint(json); + if to_clipboard { + crossterm::execute!(stderr(), CopyToClipboard::to_clipboard_from(bp)).unwrap(); + println!("blueprint copied to clipboard.") + } else { + println!("{bp}"); + } + } } } } diff --git a/src/save.rs b/src/save.rs index c6e1575..a3be4a9 100644 --- a/src/save.rs +++ b/src/save.rs @@ -205,20 +205,9 @@ pub fn save(mut json: serde_json::Value, dir: Option<&Path>) { #[cfg(test)] mod tests { - use std::{ffi::OsString, fs::read_dir, str::FromStr as _}; + use std::str::FromStr as _; - #[track_caller] - fn read_dir_unwrap(path: &Path) -> Vec { - let mut result = read_dir(path) - .unwrap_or_else(|e| panic!("error reading directory {path:?}: {e}")) - .map(|f| { - f.unwrap_or_else(|e| panic!("error while reading directory {path:?}: {e}")) - .file_name() - }) - .collect::>(); - result.sort(); - result - } + use crate::test_util::read_dir_unwrap; use super::*; #[test] @@ -234,6 +223,9 @@ mod tests { std::fs::read_to_string(dir.path().join("[icon=selector-combinator].json")).unwrap(); let written_json = serde_json::Value::from_str(&written_json).unwrap(); assert_eq!(written_json, json); + + let loaded_json = crate::load::load(&dir.path().join("[icon=selector-combinator].json")); + assert_eq!(loaded_json, json) } #[test] @@ -250,7 +242,8 @@ mod tests { let subsubfiles = read_dir_unwrap(&dir.path().join("Untitled/1 Nested Book")); assert_eq!(subsubfiles, ["6 [icon=bulk-inserter].json", "book.json"]); - // TODO: when load is implemented, test that it can load everything back properly. + let loaded_json = crate::load::load(&dir.path().join("Untitled")); + assert_eq!(loaded_json, json) } #[test] diff --git a/src/test_util.rs b/src/test_util.rs new file mode 100644 index 0000000..ef450b4 --- /dev/null +++ b/src/test_util.rs @@ -0,0 +1,14 @@ +use std::{ffi::OsString, fs::read_dir, path::Path}; + +#[track_caller] +pub(crate) fn read_dir_unwrap(path: &Path) -> Vec { + let mut result = read_dir(path) + .unwrap_or_else(|e| panic!("error reading directory {path:?}: {e}")) + .map(|f| { + f.unwrap_or_else(|e| panic!("error while reading directory {path:?}: {e}")) + .file_name() + }) + .collect::>(); + result.sort(); + result +} From 959a8746b595d1b7158ba4628f570f2e55666334 Mon Sep 17 00:00:00 2001 From: Dan Johnson Date: Fri, 29 Aug 2025 20:53:26 -0700 Subject: [PATCH 2/2] implement load --stamp --- Cargo.lock | 1199 ++++++++++++++++++++++++++++++++++++++++++++-- Cargo.toml | 1 + src/blueprint.rs | 95 ++++ src/load.rs | 17 + src/main.rs | 4 +- 5 files changed, 1286 insertions(+), 30 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 792bb2a..7c059a0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -82,6 +82,12 @@ dependencies = [ "windows-sys 0.60.2", ] +[[package]] +name = "arc-swap" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" + [[package]] name = "autocfg" version = "1.5.0" @@ -100,12 +106,38 @@ version = "2.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34efbcccd345379ca2868b2b2c9d3782e9cc58ba87bc7d79d5b53d9c9ae6f25d" +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bstr" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4" +dependencies = [ + "memchr", + "regex-automata", + "serde", +] + [[package]] name = "bumpalo" version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "cc" version = "1.2.34" @@ -173,6 +205,12 @@ version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" +[[package]] +name = "clru" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbd0f76e066e64fdc5631e3bb46381254deab9ef1158292f27c8c57e3bf3fe59" + [[package]] name = "colorchoice" version = "1.0.4" @@ -194,6 +232,15 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + [[package]] name = "crc32fast" version = "1.5.0" @@ -231,6 +278,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + [[package]] name = "derive_more" version = "2.0.1" @@ -252,6 +309,27 @@ dependencies = [ "syn", ] +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "document-features" version = "0.2.11" @@ -261,6 +339,12 @@ dependencies = [ "litrs", ] +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + [[package]] name = "dyn-clone" version = "1.0.20" @@ -297,6 +381,7 @@ dependencies = [ "clap", "crossterm", "flate2", + "gix", "itertools", "jaq-core", "jaq-json", @@ -308,6 +393,16 @@ dependencies = [ "zlib-rs", ] +[[package]] +name = "faster-hex" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7223ae2d2f179b803433d9c830478527e92b8117eab39460edae7f1614d9fb73" +dependencies = [ + "heapless", + "serde", +] + [[package]] name = "fastrand" version = "2.3.0" @@ -331,58 +426,745 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "getrandom" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasi 0.14.3+wasi-0.2.4", +] + +[[package]] +name = "gix" +version = "0.73.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "514c29cc879bdc0286b0cbc205585a49b252809eb86c69df4ce4f855ee75f635" +dependencies = [ + "gix-actor", + "gix-commitgraph", + "gix-config", + "gix-date", + "gix-diff", + "gix-discover", + "gix-features", + "gix-fs", + "gix-glob", + "gix-hash", + "gix-hashtable", + "gix-lock", + "gix-object", + "gix-odb", + "gix-pack", + "gix-path", + "gix-protocol", + "gix-ref", + "gix-refspec", + "gix-revision", + "gix-revwalk", + "gix-sec", + "gix-shallow", + "gix-tempfile", + "gix-trace", + "gix-traverse", + "gix-url", + "gix-utils", + "gix-validate", + "once_cell", + "smallvec", + "thiserror", +] + +[[package]] +name = "gix-actor" +version = "0.35.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d36dcf9efe32b51b12dfa33cedff8414926124e760a32f9e7a6b5580d280967" +dependencies = [ + "bstr", + "gix-date", + "gix-utils", + "itoa", + "thiserror", + "winnow", +] + +[[package]] +name = "gix-chunk" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b1f1d8764958699dc764e3f727cef280ff4d1bd92c107bbf8acd85b30c1bd6f" +dependencies = [ + "thiserror", +] + +[[package]] +name = "gix-command" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b31b65ca48a352ae86312b27a514a0c661935f96b481ac8b4371f65815eb196" +dependencies = [ + "bstr", + "gix-path", + "gix-quote", + "gix-trace", + "shell-words", +] + +[[package]] +name = "gix-commitgraph" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bb23121e952f43a5b07e3e80890336cb847297467a410475036242732980d06" +dependencies = [ + "bstr", + "gix-chunk", + "gix-hash", + "memmap2", + "thiserror", +] + +[[package]] +name = "gix-config" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfb898c5b695fd4acfc3c0ab638525a65545d47706064dcf7b5ead6cdb136c0" +dependencies = [ + "bstr", + "gix-config-value", + "gix-features", + "gix-glob", + "gix-path", + "gix-ref", + "gix-sec", + "memchr", + "once_cell", + "smallvec", + "thiserror", + "unicode-bom", + "winnow", +] + +[[package]] +name = "gix-config-value" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f012703eb67e263c6c1fc96649fec47694dd3e5d2a91abfc65e4a6a6dc85309" +dependencies = [ + "bitflags", + "bstr", + "gix-path", + "libc", + "thiserror", +] + +[[package]] +name = "gix-date" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "996b6b90bafb287330af92b274c3e64309dc78359221d8612d11cd10c8b9fe1c" +dependencies = [ + "bstr", + "itoa", + "jiff", + "smallvec", + "thiserror", +] + +[[package]] +name = "gix-diff" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de854852010d44a317f30c92d67a983e691c9478c8a3fb4117c1f48626bcdea8" +dependencies = [ + "bstr", + "gix-hash", + "gix-object", + "thiserror", +] + +[[package]] +name = "gix-discover" +version = "0.41.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffb180c91ca1a2cf53e828bb63d8d8f8fa7526f49b83b33d7f46cbeb5d79d30a" +dependencies = [ + "bstr", + "dunce", + "gix-fs", + "gix-hash", + "gix-path", + "gix-ref", + "gix-sec", + "thiserror", +] + +[[package]] +name = "gix-features" +version = "0.43.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd1543cd9b8abcbcebaa1a666a5c168ee2cda4dea50d3961ee0e6d1c42f81e5b" +dependencies = [ + "crc32fast", + "flate2", + "gix-path", + "gix-trace", + "gix-utils", + "libc", + "once_cell", + "prodash", + "thiserror", + "walkdir", +] + +[[package]] +name = "gix-fs" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a4d90307d064fa7230e0f87b03231be28f8ba63b913fc15346f489519d0c304" +dependencies = [ + "bstr", + "fastrand", + "gix-features", + "gix-path", + "gix-utils", + "thiserror", +] + +[[package]] +name = "gix-glob" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b947db8366823e7a750c254f6bb29e27e17f27e457bf336ba79b32423db62cd5" +dependencies = [ + "bitflags", + "bstr", + "gix-features", + "gix-path", +] + +[[package]] +name = "gix-hash" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "251fad79796a731a2a7664d9ea95ee29a9e99474de2769e152238d4fdb69d50e" +dependencies = [ + "faster-hex", + "gix-features", + "sha1-checked", + "thiserror", +] + +[[package]] +name = "gix-hashtable" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c35300b54896153e55d53f4180460931ccd69b7e8d2f6b9d6401122cdedc4f07" +dependencies = [ + "gix-hash", + "hashbrown", + "parking_lot", +] + +[[package]] +name = "gix-lock" +version = "18.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9fa71da90365668a621e184eb5b979904471af1b3b09b943a84bc50e8ad42ed" +dependencies = [ + "gix-tempfile", + "gix-utils", + "thiserror", +] + +[[package]] +name = "gix-object" +version = "0.50.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d69ce108ab67b65fbd4fb7e1331502429d78baeb2eee10008bdef55765397c07" +dependencies = [ + "bstr", + "gix-actor", + "gix-date", + "gix-features", + "gix-hash", + "gix-hashtable", + "gix-path", + "gix-utils", + "gix-validate", + "itoa", + "smallvec", + "thiserror", + "winnow", +] + +[[package]] +name = "gix-odb" +version = "0.70.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c9d7af10fda9df0bb4f7f9bd507963560b3c66cb15a5b825caf752e0eb109ac" +dependencies = [ + "arc-swap", + "gix-date", + "gix-features", + "gix-fs", + "gix-hash", + "gix-hashtable", + "gix-object", + "gix-pack", + "gix-path", + "gix-quote", + "parking_lot", + "tempfile", + "thiserror", +] + +[[package]] +name = "gix-pack" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8571df89bfca5abb49c3e3372393f7af7e6f8b8dbe2b96303593cef5b263019" +dependencies = [ + "clru", + "gix-chunk", + "gix-features", + "gix-hash", + "gix-hashtable", + "gix-object", + "gix-path", + "memmap2", + "smallvec", + "thiserror", +] + +[[package]] +name = "gix-packetline" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2592fbd36249a2fea11056f7055cc376301ef38d903d157de41998335bbf1f93" +dependencies = [ + "bstr", + "faster-hex", + "gix-trace", + "thiserror", +] + +[[package]] +name = "gix-path" +version = "0.10.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06d37034a4c67bbdda76f7bcd037b2f7bc0fba0c09a6662b19697a5716e7b2fd" +dependencies = [ + "bstr", + "gix-trace", + "gix-validate", + "home", + "once_cell", + "thiserror", +] + +[[package]] +name = "gix-protocol" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12b4b807c47ffcf7c1e5b8119585368a56449f3493da93b931e1d4239364e922" +dependencies = [ + "bstr", + "gix-date", + "gix-features", + "gix-hash", + "gix-ref", + "gix-shallow", + "gix-transport", + "gix-utils", + "maybe-async", + "thiserror", + "winnow", +] + +[[package]] +name = "gix-quote" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a375a75b4d663e8bafe3bf4940a18a23755644c13582fa326e99f8f987d83fd" +dependencies = [ + "bstr", + "gix-utils", + "thiserror", +] + +[[package]] +name = "gix-ref" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b966f578079a42f4a51413b17bce476544cca1cf605753466669082f94721758" +dependencies = [ + "gix-actor", + "gix-features", + "gix-fs", + "gix-hash", + "gix-lock", + "gix-object", + "gix-path", + "gix-tempfile", + "gix-utils", + "gix-validate", + "memmap2", + "thiserror", + "winnow", +] + +[[package]] +name = "gix-refspec" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d29cae1ae31108826e7156a5e60bffacab405f4413f5bc0375e19772cce0055" +dependencies = [ + "bstr", + "gix-hash", + "gix-revision", + "gix-validate", + "smallvec", + "thiserror", +] + +[[package]] +name = "gix-revision" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f651f2b1742f760bb8161d6743229206e962b73d9c33c41f4e4aefa6586cbd3d" +dependencies = [ + "bstr", + "gix-commitgraph", + "gix-date", + "gix-hash", + "gix-object", + "gix-revwalk", + "thiserror", +] + +[[package]] +name = "gix-revwalk" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06e74f91709729e099af6721bd0fa7d62f243f2005085152301ca5cdd86ec02c" +dependencies = [ + "gix-commitgraph", + "gix-date", + "gix-hash", + "gix-hashtable", + "gix-object", + "smallvec", + "thiserror", +] + +[[package]] +name = "gix-sec" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09f7053ed7c66633b56c57bc6ed3377be3166eaf3dc2df9f1c5ec446df6fdf2c" +dependencies = [ + "bitflags", + "gix-path", + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "gix-shallow" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d936745103243ae4c510f19e0760ce73fb0f08096588fdbe0f0d7fb7ce8944b7" +dependencies = [ + "bstr", + "gix-hash", + "gix-lock", + "thiserror", +] + +[[package]] +name = "gix-tempfile" +version = "18.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "666c0041bcdedf5fa05e9bef663c897debab24b7dc1741605742412d1d47da57" +dependencies = [ + "gix-fs", + "libc", + "once_cell", + "parking_lot", + "tempfile", +] + +[[package]] +name = "gix-trace" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2ccaf54b0b1743a695b482ca0ab9d7603744d8d10b2e5d1a332fef337bee658" + +[[package]] +name = "gix-transport" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f7cc0179fc89d53c54e1f9ce51229494864ab4bf136132d69db1b011741ca3" +dependencies = [ + "bstr", + "gix-command", + "gix-features", + "gix-packetline", + "gix-quote", + "gix-sec", + "gix-url", + "thiserror", +] + +[[package]] +name = "gix-traverse" +version = "0.47.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7cdc82509d792ba0ad815f86f6b469c7afe10f94362e96c4494525a6601bdd5" +dependencies = [ + "bitflags", + "gix-commitgraph", + "gix-date", + "gix-hash", + "gix-hashtable", + "gix-object", + "gix-revwalk", + "smallvec", + "thiserror", +] + +[[package]] +name = "gix-url" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b76a9d266254ad287ffd44467cd88e7868799b08f4d52e02d942b93e514d16f" +dependencies = [ + "bstr", + "gix-features", + "gix-path", + "percent-encoding", + "thiserror", + "url", +] + +[[package]] +name = "gix-utils" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5351af2b172caf41a3728eb4455326d84e0d70fe26fc4de74ab0bd37df4191c5" +dependencies = [ + "fastrand", + "unicode-normalization", +] + +[[package]] +name = "gix-validate" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77b9e00cacde5b51388d28ed746c493b18a6add1f19b5e01d686b3b9ece66d4d" +dependencies = [ + "bstr", + "thiserror", +] + +[[package]] +name = "hash32" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" +dependencies = [ + "byteorder", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" + +[[package]] +name = "heapless" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" +dependencies = [ + "hash32", + "stable_deref_trait", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hifijson" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a7763b98ba8a24f59e698bf9ab197e7676c640d6455d1580b4ce7dc560f0f0d" + +[[package]] +name = "home" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" dependencies = [ - "cfg-if", - "libc", - "r-efi", - "wasi 0.14.3+wasi-0.2.4", + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", ] [[package]] -name = "hashbrown" -version = "0.15.5" +name = "icu_normalizer_data" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" [[package]] -name = "heck" -version = "0.5.0" +name = "icu_properties" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "potential_utf", + "zerotrie", + "zerovec", +] [[package]] -name = "hifijson" -version = "0.2.3" +name = "icu_properties_data" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a7763b98ba8a24f59e698bf9ab197e7676c640d6455d1580b4ce7dc560f0f0d" +checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" [[package]] -name = "iana-time-zone" -version = "0.1.63" +name = "icu_provider" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" +checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "log", - "wasm-bindgen", - "windows-core", + "displaydoc", + "icu_locale_core", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", ] [[package]] -name = "iana-time-zone-haiku" -version = "0.1.2" +name = "idna" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" dependencies = [ - "cc", + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", ] [[package]] @@ -457,6 +1239,47 @@ dependencies = [ "urlencoding", ] +[[package]] +name = "jiff" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be1f93b8b1eb69c77f24bbb0afdf66f54b632ee39af40ca21c4365a1d7347e49" +dependencies = [ + "jiff-static", + "jiff-tzdb-platform", + "log", + "portable-atomic", + "portable-atomic-util", + "serde", + "windows-sys 0.59.0", +] + +[[package]] +name = "jiff-static" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "jiff-tzdb" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1283705eb0a21404d2bfd6eef2a7593d240bc42a0bdb39db0ad6fa2ec026524" + +[[package]] +name = "jiff-tzdb-platform" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "875a5a69ac2bab1a891711cf5eccbec1ce0341ea805560dcd90b7a2e925132e8" +dependencies = [ + "jiff-tzdb", +] + [[package]] name = "js-sys" version = "0.3.77" @@ -494,6 +1317,12 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" +[[package]] +name = "litemap" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" + [[package]] name = "litrs" version = "0.4.2" @@ -516,12 +1345,32 @@ version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" +[[package]] +name = "maybe-async" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cf92c10c7e361d6b99666ec1c6f9805b0bea2c3bd8c78dc6fe98ac5bd78db11" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "memchr" version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" +[[package]] +name = "memmap2" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843a98750cd611cc2965a8213b53b43e715f13c37a9e096c6408e69990961db7" +dependencies = [ + "libc", +] + [[package]] name = "miniz_oxide" version = "0.8.9" @@ -587,6 +1436,36 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "portable-atomic" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" + +[[package]] +name = "portable-atomic-util" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "potential_utf" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84df19adbe5b5a0782edcab45899906947ab039ccf4573713735ee7de1e6b08a" +dependencies = [ + "zerovec", +] + [[package]] name = "proc-macro2" version = "1.0.101" @@ -596,6 +1475,15 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "prodash" +version = "30.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6efc566849d3d9d737c5cb06cc50e48950ebe3d3f9d70631490fff3a07b139" +dependencies = [ + "parking_lot", +] + [[package]] name = "quote" version = "1.0.40" @@ -680,6 +1568,15 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +[[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 = "sanitize-filename" version = "0.6.0" @@ -727,6 +1624,33 @@ dependencies = [ "serde", ] +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha1-checked" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89f599ac0c323ebb1c6082821a54962b839832b03984598375bff3975b804423" +dependencies = [ + "digest", + "sha1", +] + +[[package]] +name = "shell-words" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" + [[package]] name = "shlex" version = "1.3.0" @@ -769,6 +1693,12 @@ version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + [[package]] name = "strsim" version = "0.11.1" @@ -786,6 +1716,17 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "tempfile" version = "3.21.0" @@ -799,36 +1740,136 @@ dependencies = [ "windows-sys 0.60.2", ] +[[package]] +name = "thiserror" +version = "2.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tinystr" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + [[package]] name = "typed-arena" version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a" +[[package]] +name = "typenum" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" + +[[package]] +name = "unicode-bom" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7eec5d1121208364f6793f7d2e222bf75a915c19557537745b195b253dd64217" + [[package]] name = "unicode-ident" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +[[package]] +name = "unicode-normalization" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" +dependencies = [ + "tinyvec", +] + [[package]] name = "unicode-segmentation" version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" +[[package]] +name = "url" +version = "2.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + [[package]] name = "urlencoding" version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + [[package]] name = "utf8parse" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[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.1+wasi-snapshot-preview1" @@ -918,6 +1959,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0978bf7171b3d90bac376700cb56d606feb40f251a475a5d6634613564460b22" +dependencies = [ + "windows-sys 0.60.2", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" @@ -1130,12 +2180,105 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" +[[package]] +name = "winnow" +version = "0.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" +dependencies = [ + "memchr", +] + [[package]] name = "wit-bindgen" version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "052283831dbae3d879dc7f51f3d92703a316ca49f91540417d38591826127814" +[[package]] +name = "writeable" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" + +[[package]] +name = "yoke" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerotrie" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "zlib-rs" version = "0.5.1" diff --git a/Cargo.toml b/Cargo.toml index c6127ff..2fd989c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,7 @@ base64 = "0.22.1" clap = { version = "4.5.42", features = ["derive"] } crossterm = { version = "0.29.0", features = ["osc52"] } flate2 = { version = "1.1.2", default-features = false, features = ["zlib-rs"] } +gix = { version = "0.73.0", default-features = false } itertools = "0.14.0" sanitize-filename = "0.6.0" serde = { version = "1.0.219", features = ["derive"] } diff --git a/src/blueprint.rs b/src/blueprint.rs index e799662..b003ee5 100644 --- a/src/blueprint.rs +++ b/src/blueprint.rs @@ -2,8 +2,10 @@ pub(crate) mod count_entities; pub(crate) mod upgrade_quality; use std::{ + borrow::Borrow, fmt::{Debug, Display}, io::Read, + mem, }; use base64::{Engine, prelude::BASE64_STANDARD}; @@ -43,6 +45,80 @@ pub fn json_to_blueprint(value: serde_json::Value) -> String { result } +pub(crate) enum BlueprintType> { + Blueprint(T), + BlueprintBook(T), + UpgradePlanner(T), + DeconstructionPlanner(T), +} + +impl BlueprintType<&mut serde_json::Value> { + pub fn new(json: &mut serde_json::Value) -> BlueprintType<&mut serde_json::Value> { + let json = match json { + serde_json::Value::Object(json) => json, + _ => panic!("blueprint entry should be an object, got {json:?}"), + }; + + if json.contains_key("blueprint") { + BlueprintType::Blueprint(&mut json["blueprint"]) + } else if json.contains_key("blueprint_book") { + BlueprintType::BlueprintBook(&mut json["blueprint_book"]) + } else if json.contains_key("upgrade_planner") { + BlueprintType::UpgradePlanner(&mut json["upgrade_planner"]) + } else if json.contains_key("deconstruction_planner") { + BlueprintType::DeconstructionPlanner(&mut json["deconstruction_planner"]) + } else { + panic!("blueprint has unknown type: {:?}", json.keys()); + } + } + + fn any_mut(&mut self) -> &mut serde_json::Value { + match self { + BlueprintType::Blueprint(value) => value, + BlueprintType::BlueprintBook(value) => value, + BlueprintType::UpgradePlanner(value) => value, + BlueprintType::DeconstructionPlanner(value) => value, + } + } + + pub(crate) fn set_tag_in_description(&mut self, tag: &str, value: &str) { + let description = self + .any_mut() + .as_object_mut() + .expect("should be a json object") + .entry("description") + .or_insert_with(|| json!("")); + let serde_json::Value::String(description) = description else { + panic!("expected description to be a string") + }; + *description = set_tag_in_string(mem::take(description), tag, value); + } +} + +fn set_tag_in_string(description: String, tag: &str, value: &str) -> String { + let start_offset = if description.starts_with(&format!("{tag}:")) { + 0 + } else if let Some(index) = description.find(&format!("\n{tag}:")) { + index + "\n".len() + } else { + // couldn't find tag, add it to a new line at the end + let mut new_description = description; + if !new_description.is_empty() { + new_description.push('\n'); + } + new_description.push_str(&format!("{tag}: {value}")); + return new_description; + }; + let old_description = description; + let prefix = &old_description[..start_offset]; + let suffix = if let Some(end_index) = &old_description[start_offset..].find("\n") { + &old_description[start_offset + end_index..] + } else { + "" + }; + format!("{prefix}{tag}: {value}{suffix}") +} + pub fn make_constant_combinator_json(signals: Vec<((String, Quality), i64)>) -> serde_json::Value { const COMBINATOR: &str = "0eNqNkNEOgjAMRf+lz8MIEYH9ijFmw6pNRkdGMRKyf3fDF5+Mj23uPbe3K1g34xiIBfQK1HueQJ9WmOjOxuWdLCOCBhIcQAGbIU9ZJ4al6P1giY34AFEB8RVfoMt4VoAsJIQf3DYsF54HiyEJfoIUjH5KXs85P/GK+ljvagUL6OpY7uqUdKWA/UdyUJkiwbuLxYd5UkIk342cYPirjQTD0+iDFBad5CK9n/NHyu9KMbfajPrrawqeKWU7pGrLQ9NVTVt1zb7tYnwDHB11ag=="; let json = blueprint_to_json(COMBINATOR); @@ -119,3 +195,22 @@ impl Quality { }) } } + +#[cfg(test)] +mod tests { + use crate::blueprint::set_tag_in_string; + + #[test] + fn test_set_tag() { + assert_eq!( + set_tag_in_string("foo bar".into(), "t", "val"), + "foo bar\nt: val" + ); + + // Empty string considered to be empty + assert_eq!(set_tag_in_string("".into(), "t", "val"), "t: val"); + + // Existing newlines are considered intentional + assert_eq!(set_tag_in_string("\n".into(), "t", "val"), "\n\nt: val"); + } +} diff --git a/src/load.rs b/src/load.rs index fe8ddef..58796a6 100644 --- a/src/load.rs +++ b/src/load.rs @@ -2,6 +2,8 @@ use std::{fs, path::Path}; use itertools::Itertools; +use crate::blueprint; + fn load_file(path: &Path) -> serde_json::Value { serde_json::from_str( &fs::read_to_string(path) @@ -94,6 +96,21 @@ pub fn load(path: &Path) -> serde_json::Value { book_json } +pub(crate) fn stamp(json: &mut serde_json::Value, path: &Path) { + let dir = if path.is_dir() { + path + } else { + path.parent().unwrap_or(Path::new(".")) + }; + let repo = + gix::discover(dir).unwrap_or_else(|e| panic!("error: couldn't find git repository: {e}")); + let id = repo + .head_id() + .unwrap_or_else(|e| panic!("error: couldn't get HEAD commit: {e}")); + let mut bp = blueprint::BlueprintType::new(json); + bp.set_tag_in_description("last_commit", &format!("{id}")); +} + #[cfg(test)] mod tests { use std::str::FromStr as _; diff --git a/src/main.rs b/src/main.rs index 586eed8..0b337e0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -159,9 +159,9 @@ impl Commands { to_clipboard, stamp, } => { - let json = load::load(&file); + let mut json = load::load(&file); if stamp { - unimplemented!(); + load::stamp(&mut json, &file); } let bp = json_to_blueprint(json); if to_clipboard {