--- a/Cargo.lock +++ b/Cargo.lock @@ -3,6 +3,12 @@ version = 4 [[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] name = "ahash" version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -109,7 +115,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -120,7 +126,7 @@ dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -139,18 +145,61 @@ ] [[package]] +name = "arc-swap" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a3a1fd6f75306b68087b831f025c712524bcb19aad54e557b1129cfa0a2b207" +dependencies = [ + "rustversion", +] + +[[package]] name = "arraydeque" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d902e3d592a523def97af8f317b08ce16b7ab854c1985a0c671e6f15cebc236" [[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] name = "autocfg" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] +name = "aws-lc-rs" +version = "1.16.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec6fb3fe69024a75fa7e1bfb48aa6cf59706a101658ea01bfd33b2b248a038f" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f50037ee5e1e41e7b8f9d161680a725bd1626cb6f8c7e901f91f942850852fe7" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", +] + +[[package]] name = "base64" version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -164,6 +213,15 @@ [[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 = "block-buffer" version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdd35008169921d80bc60d3d0ab416eecb028c4cd653352907921d95084790be" @@ -178,6 +236,7 @@ checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab" dependencies = [ "memchr", + "regex-automata", "serde", ] @@ -191,6 +250,33 @@ ] [[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" + +[[package]] +name = "bytesize" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bd91ee7b2422bcb158d90ef4d14f75ef67f340943fc4149891dcce8f8b972a3" + +[[package]] +name = "camino" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e629a66d692cb9ff1a1c664e41771b3dcaf961985a9774c0eb0bd1b51cf60a48" +dependencies = [ + "serde_core", +] + +[[package]] name = "cast" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -203,6 +289,8 @@ checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d" dependencies = [ "find-msvc-tools", + "jobserver", + "libc", "shlex", ] @@ -295,12 +383,40 @@ checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" [[package]] +name = "clru" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "197fd99cb113a8d5d9b6376f3aa817f32c1078f2343b714fff7d2ca44fdf67d5" +dependencies = [ + "hashbrown 0.16.1", +] + +[[package]] +name = "cmake" +version = "0.1.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" +dependencies = [ + "cc", +] + +[[package]] name = "colorchoice" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" [[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] name = "console" version = "0.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -308,7 +424,7 @@ dependencies = [ "encode_unicode", "libc", - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -328,6 +444,22 @@ checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" [[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] name = "countme" version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -335,6 +467,15 @@ [[package]] name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "cpufeatures" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" @@ -343,6 +484,15 @@ ] [[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] name = "criterion" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -378,6 +528,15 @@ ] [[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] name = "crossbeam-deque" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -410,6 +569,16 @@ [[package]] name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "crypto-common" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77727bb15fa921304124b128af125e7e3b968275d1b108b379190264f4423710" @@ -418,17 +587,62 @@ ] [[package]] +name = "dashmap" +version = "6.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer 0.10.4", + "crypto-common 0.1.7", +] + +[[package]] name = "digest" version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" dependencies = [ - "block-buffer", + "block-buffer 0.12.0", "const-oid", - "crypto-common", + "crypto-common 0.2.1", ] [[package]] +name = "directories" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16f5094c54661b38d03bd7e50df373292118db60b585c08a411c6d840017fe7d" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.61.2", +] + +[[package]] name = "displaydoc" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -461,6 +675,12 @@ checksum = "9bda8e21c04aca2ae33ffc2fd8c23134f3cac46db123ba97bd9d3f3b8a4a85e1" [[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] name = "educe" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -535,7 +755,7 @@ checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -550,18 +770,49 @@ ] [[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.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" [[package]] +name = "filetime" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f98844151eee8917efc50bd9e8318cb963ae8b297431495d3f758616ea5c57db" +dependencies = [ + "cfg-if", + "libc", + "libredox", +] + +[[package]] name = "find-msvc-tools" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" [[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "miniz_oxide", + "zlib-rs", +] + +[[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -589,7 +840,85 @@ ] [[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + +[[package]] +name = "futures-channel" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-io" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" + +[[package]] +name = "futures-sink" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", +] + +[[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.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" @@ -616,6 +945,940 @@ ] [[package]] +name = "gix" +version = "0.83.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce52001b946a6249d5d0d3011df0a042ac3f8a4d013460db6476577b0b9c567" +dependencies = [ + "gix-actor", + "gix-archive", + "gix-attributes", + "gix-blame", + "gix-command", + "gix-commitgraph", + "gix-config", + "gix-credentials", + "gix-date", + "gix-diff", + "gix-dir", + "gix-discover", + "gix-error", + "gix-features", + "gix-filter", + "gix-fs", + "gix-glob", + "gix-hash", + "gix-hashtable", + "gix-ignore", + "gix-index", + "gix-lock", + "gix-mailmap", + "gix-merge", + "gix-negotiate", + "gix-object", + "gix-odb", + "gix-pack", + "gix-path", + "gix-pathspec", + "gix-prompt", + "gix-protocol", + "gix-ref", + "gix-refspec", + "gix-revision", + "gix-revwalk", + "gix-sec", + "gix-shallow", + "gix-status", + "gix-submodule", + "gix-tempfile", + "gix-trace", + "gix-transport", + "gix-traverse", + "gix-url", + "gix-utils", + "gix-validate", + "gix-worktree", + "gix-worktree-state", + "gix-worktree-stream", + "nonempty", + "parking_lot", + "regex", + "signal-hook", + "smallvec", + "thiserror", +] + +[[package]] +name = "gix-actor" +version = "0.41.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "272916673b83714734b15d4ef3c8b5f1ccddb15fea8ff548430b97c1ab7b7ed8" +dependencies = [ + "bstr", + "gix-date", + "gix-error", +] + +[[package]] +name = "gix-archive" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a20ec244b733338d4cb60e5e05eac700dab7fcc689647b1d1daa9396b119342" +dependencies = [ + "bstr", + "gix-date", + "gix-error", + "gix-object", + "gix-worktree-stream", +] + +[[package]] +name = "gix-attributes" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe17c5a1c0b6f2ef1476aa1d3222ea50cdff67608016613a58bfc3e078046000" +dependencies = [ + "bstr", + "gix-glob", + "gix-path", + "gix-quote", + "gix-trace", + "kstring", + "smallvec", + "thiserror", + "unicode-bom", +] + +[[package]] +name = "gix-bitmap" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ecbfc77ec6852294e341ecc305a490b59f2813e6ca42d79efda5099dcab1894" +dependencies = [ + "gix-error", +] + +[[package]] +name = "gix-blame" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14dab9a942ab54a9661ded7397c3bf927274e7afa94494db0d75cfcbde02ca0a" +dependencies = [ + "gix-commitgraph", + "gix-date", + "gix-diff", + "gix-error", + "gix-hash", + "gix-object", + "gix-revwalk", + "gix-trace", + "gix-traverse", + "gix-worktree", + "smallvec", + "thiserror", +] + +[[package]] +name = "gix-chunk" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edf288be9b60fe7231de03771faa292be1493d84786f68727e33ad1f91764320" +dependencies = [ + "gix-error", +] + +[[package]] +name = "gix-command" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86335306511abe43d75c866d4b1f3d90932fe202edcd43e1314036333e7384d8" +dependencies = [ + "bstr", + "gix-path", + "gix-quote", + "gix-trace", + "shell-words", +] + +[[package]] +name = "gix-commitgraph" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe3b5aa0f24e19028c261d229aeeedafcaaa52ebd71021cc15184620fc9d32eb" +dependencies = [ + "bstr", + "gix-chunk", + "gix-error", + "gix-hash", + "memmap2", + "nonempty", +] + +[[package]] +name = "gix-config" +version = "0.56.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c01848aebd21c67f6ba41f1de8efd46ae96df21f001954a3c9e1517e514d410" +dependencies = [ + "bstr", + "gix-config-value", + "gix-features", + "gix-glob", + "gix-path", + "gix-ref", + "gix-sec", + "smallvec", + "thiserror", + "unicode-bom", +] + +[[package]] +name = "gix-config-value" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13b39ed39ee4c10a3b157f9fb94bac8098d9f8e56201f0cf7dee6c187416c4b2" +dependencies = [ + "bitflags", + "bstr", + "gix-path", + "libc", + "thiserror", +] + +[[package]] +name = "gix-credentials" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65ca11598b70811d7b16ff90945a6e57dfe521e85b744e51636965fe39cc8f60" +dependencies = [ + "bstr", + "gix-command", + "gix-config-value", + "gix-date", + "gix-path", + "gix-prompt", + "gix-sec", + "gix-trace", + "gix-url", + "thiserror", +] + +[[package]] +name = "gix-date" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b94cdae4eb4b0f4136e3d9b3aa2d2cd03cfb5bb9b636b31263aea2df86d41543" +dependencies = [ + "bstr", + "gix-error", + "itoa", + "jiff", + "smallvec", +] + +[[package]] +name = "gix-diff" +version = "0.63.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc08e0fa1a91ff5f24affeab052f198056645e1de004910bde7b82b50ea5982a" +dependencies = [ + "bstr", + "gix-attributes", + "gix-command", + "gix-filter", + "gix-fs", + "gix-hash", + "gix-imara-diff", + "gix-index", + "gix-object", + "gix-path", + "gix-pathspec", + "gix-tempfile", + "gix-trace", + "gix-traverse", + "gix-worktree", + "thiserror", +] + +[[package]] +name = "gix-dir" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a0fc06e9e1e430cbf0a313666976d90f822f461a6525320427aa9b8af5236c" +dependencies = [ + "bstr", + "gix-discover", + "gix-fs", + "gix-ignore", + "gix-index", + "gix-object", + "gix-path", + "gix-pathspec", + "gix-trace", + "gix-utils", + "gix-worktree", + "thiserror", +] + +[[package]] +name = "gix-discover" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17852e6a501e688a1702b24ebe5b3761d4719455bc869fd29f38b0b859bcad34" +dependencies = [ + "bstr", + "dunce", + "gix-fs", + "gix-path", + "gix-ref", + "gix-sec", + "thiserror", +] + +[[package]] +name = "gix-error" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e207b971746ab724fccdfced2e4e19e854744611904a0195d3aa8fda8a110613" +dependencies = [ + "bstr", +] + +[[package]] +name = "gix-features" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af375693ad5333d0a2c66b4c5b2cbe9ccc38e34f8e8bf24e4ae42c12307fdc4f" +dependencies = [ + "bytes", + "bytesize", + "crc32fast", + "crossbeam-channel", + "gix-path", + "gix-trace", + "gix-utils", + "libc", + "once_cell", + "parking_lot", + "prodash", + "thiserror", + "walkdir", + "zlib-rs", +] + +[[package]] +name = "gix-filter" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dac917dbe9653c9b615d248db91907a365bd779750c9e1b457a9d9fdeece3a08" +dependencies = [ + "bstr", + "encoding_rs", + "gix-attributes", + "gix-command", + "gix-hash", + "gix-object", + "gix-packetline", + "gix-path", + "gix-quote", + "gix-trace", + "gix-utils", + "smallvec", + "thiserror", +] + +[[package]] +name = "gix-fs" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e1967daac9848757c47c2aef0c57bcadc1a897347f559778249bf286a536c86" +dependencies = [ + "bstr", + "fastrand", + "gix-features", + "gix-path", + "gix-utils", + "thiserror", +] + +[[package]] +name = "gix-glob" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08bf29249a069bf2507f5964f80997f37b134d320ea348d66527726b9be2c38c" +dependencies = [ + "bitflags", + "bstr", + "gix-features", + "gix-path", +] + +[[package]] +name = "gix-hash" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcf70d1e252337eed16360f8b8ebb71865ece58eab7954b39ce38b420de703d2" +dependencies = [ + "faster-hex", + "gix-features", + "sha1-checked", + "thiserror", +] + +[[package]] +name = "gix-hashtable" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d33b455e07b3c16d3b2eeebc7b38d2dafcbf8a653de1138ef55d4c2a1fd0b08b" +dependencies = [ + "gix-hash", + "hashbrown 0.16.1", + "parking_lot", +] + +[[package]] +name = "gix-ignore" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bb13fbbeeafee943e52b61fcc88dfddf6a452fcaf0c4d0cdc8f218fa25bbec5" +dependencies = [ + "bstr", + "gix-glob", + "gix-path", + "gix-trace", + "unicode-bom", +] + +[[package]] +name = "gix-imara-diff" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39eb0623e15e4cb83c02ce6a959e48fadd1ae3b715b36b5acc01816e01388c82" +dependencies = [ + "bstr", + "hashbrown 0.16.1", +] + +[[package]] +name = "gix-index" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54c3ef97ad08121e4327a6226bd63fed6b9e3c6b976d48bddd4356d9d41191db" +dependencies = [ + "bitflags", + "bstr", + "filetime", + "fnv", + "gix-bitmap", + "gix-features", + "gix-fs", + "gix-hash", + "gix-lock", + "gix-object", + "gix-traverse", + "gix-utils", + "gix-validate", + "hashbrown 0.16.1", + "itoa", + "libc", + "memmap2", + "rustix", + "smallvec", + "thiserror", +] + +[[package]] +name = "gix-lock" +version = "23.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09b3bc074e5723027b482dcd9ab99d95804a53742f6de812d0172fbba4a186c1" +dependencies = [ + "gix-tempfile", + "gix-utils", + "thiserror", +] + +[[package]] +name = "gix-mailmap" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "023d3a6561cbebe45b89e0764d48928ad970667076f16fa5889e6f86d8432086" +dependencies = [ + "bstr", + "gix-actor", + "gix-date", + "gix-error", +] + +[[package]] +name = "gix-merge" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74bbcdcc52b70a32f0a151b024dff9d0fcf56ee48f00d9503e735af9d99ea881" +dependencies = [ + "bstr", + "gix-command", + "gix-diff", + "gix-filter", + "gix-fs", + "gix-hash", + "gix-imara-diff", + "gix-index", + "gix-object", + "gix-path", + "gix-quote", + "gix-revision", + "gix-revwalk", + "gix-tempfile", + "gix-trace", + "gix-worktree", + "nonempty", + "thiserror", +] + +[[package]] +name = "gix-negotiate" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "103d42bfade1b8a96ca5005933127bdad461ce588d92422b2c2daa3ff20d780c" +dependencies = [ + "bitflags", + "gix-commitgraph", + "gix-date", + "gix-hash", + "gix-object", + "gix-revwalk", +] + +[[package]] +name = "gix-object" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a38075a95d7cc5df8afd38e72c617026c1456952207a4120a7f55a3fbf93b4d7" +dependencies = [ + "bstr", + "gix-actor", + "gix-date", + "gix-features", + "gix-hash", + "gix-hashtable", + "gix-utils", + "gix-validate", + "itoa", + "smallvec", + "thiserror", +] + +[[package]] +name = "gix-odb" +version = "0.80.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aeeda12a9663120418735ecdc1250d06eeab0be75700e47b3402a981331716ba" +dependencies = [ + "arc-swap", + "gix-features", + "gix-fs", + "gix-hash", + "gix-hashtable", + "gix-object", + "gix-pack", + "gix-path", + "gix-quote", + "memmap2", + "parking_lot", + "tempfile", + "thiserror", +] + +[[package]] +name = "gix-pack" +version = "0.70.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf02e6f5c8f07a069c9ea5245f40d9b14856ada4086091dc99941b49002b4fa" +dependencies = [ + "clru", + "gix-chunk", + "gix-error", + "gix-features", + "gix-hash", + "gix-hashtable", + "gix-object", + "gix-path", + "gix-tempfile", + "memmap2", + "parking_lot", + "smallvec", + "thiserror", + "uluru", +] + +[[package]] +name = "gix-packetline" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "362246df440ee691699f0664cbf7006a6ece477db6734222be95e4198e5656e6" +dependencies = [ + "bstr", + "faster-hex", + "gix-trace", + "thiserror", +] + +[[package]] +name = "gix-path" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "671a6059e8a4c1b7f406e24716499cefa3926e060876fb1959ef225efeee346e" +dependencies = [ + "bstr", + "gix-trace", + "gix-validate", + "thiserror", +] + +[[package]] +name = "gix-pathspec" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a84a4f083dd70fb49f4377e13afa6d90df2daaa1c705c49d6ff1331fc7e8855" +dependencies = [ + "bitflags", + "bstr", + "gix-attributes", + "gix-config-value", + "gix-glob", + "gix-path", + "thiserror", +] + +[[package]] +name = "gix-prompt" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e041a626c64cb69e4117fcdf80da8d0e454fba3b1f420412792d191f52251aee" +dependencies = [ + "gix-command", + "gix-config-value", + "parking_lot", + "rustix", + "thiserror", +] + +[[package]] +name = "gix-protocol" +version = "0.61.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa4bee82db63ec635996b96efae71cf467c155fa3f34a556184373224a26c4fd" +dependencies = [ + "bstr", + "gix-credentials", + "gix-date", + "gix-features", + "gix-hash", + "gix-lock", + "gix-negotiate", + "gix-object", + "gix-ref", + "gix-refspec", + "gix-revwalk", + "gix-shallow", + "gix-trace", + "gix-transport", + "gix-utils", + "maybe-async", + "nonempty", + "thiserror", +] + +[[package]] +name = "gix-quote" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e97b73791a64bc0fa7dd2c5b3e551136115f97750b876ed1c952c7a7dbaf8be" +dependencies = [ + "bstr", + "gix-error", + "gix-utils", +] + +[[package]] +name = "gix-ref" +version = "0.63.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8ba9cc15f558b274c99349b83130f5ec83459660828fde9718bbbb43a726167" +dependencies = [ + "gix-actor", + "gix-features", + "gix-fs", + "gix-hash", + "gix-lock", + "gix-object", + "gix-path", + "gix-tempfile", + "gix-utils", + "gix-validate", + "memmap2", + "thiserror", +] + +[[package]] +name = "gix-refspec" +version = "0.41.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61755b27d57edc8940a1b1593c8c61548ca8e4c02da1ed8d5bfeda9eb2a6b761" +dependencies = [ + "bstr", + "gix-error", + "gix-glob", + "gix-hash", + "gix-revision", + "gix-validate", + "smallvec", + "thiserror", +] + +[[package]] +name = "gix-revision" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fb5288fac706d3ea3e4e2ba9ec38b78743b8c02f422e18cb342299cfd6ab7e8" +dependencies = [ + "bitflags", + "bstr", + "gix-commitgraph", + "gix-date", + "gix-error", + "gix-hash", + "gix-hashtable", + "gix-object", + "gix-revwalk", + "gix-trace", + "nonempty", +] + +[[package]] +name = "gix-revwalk" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "313813706b073a12ff7f9b2896bf3e6504cdac7cfbc97b1920114724705069f0" +dependencies = [ + "gix-commitgraph", + "gix-date", + "gix-error", + "gix-hash", + "gix-hashtable", + "gix-object", + "smallvec", + "thiserror", +] + +[[package]] +name = "gix-sec" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5a3a2d3e504a238136751e646a6c028252286a0ea64ea9974bf0498633407c6" +dependencies = [ + "bitflags", + "gix-path", + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "gix-shallow" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29187305521bfacf4aefd284ab28dbfa9fb74abd39a5e63dd313b1baa5808c27" +dependencies = [ + "bstr", + "gix-hash", + "gix-lock", + "nonempty", + "thiserror", +] + +[[package]] +name = "gix-status" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68c6d2a8c521ffa205fe7e268c82e6d1378ba37cd826ca10ab6129fdc29a4b65" +dependencies = [ + "bstr", + "filetime", + "gix-diff", + "gix-dir", + "gix-features", + "gix-filter", + "gix-fs", + "gix-hash", + "gix-index", + "gix-object", + "gix-path", + "gix-pathspec", + "gix-worktree", + "portable-atomic", + "thiserror", +] + +[[package]] +name = "gix-submodule" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fd5fc8692890bd71a596e540fd4c364f8460eaa82c4eaaedebde6e1e3eb4d91" +dependencies = [ + "bstr", + "gix-config", + "gix-path", + "gix-pathspec", + "gix-refspec", + "gix-url", + "thiserror", +] + +[[package]] +name = "gix-tempfile" +version = "23.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "691ea1e31435c7e7d4d04705ec9d1c0d9482c46b2acf512bc723939d8f0af7fb" +dependencies = [ + "dashmap", + "gix-fs", + "libc", + "parking_lot", + "signal-hook", + "signal-hook-registry", + "tempfile", +] + +[[package]] +name = "gix-trace" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f23569e55f2ffaf958617353b9734a7d52a7c19c439eeaa5e3efc217fd2270e" + +[[package]] +name = "gix-transport" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffd6a5c676b92d4ead5f5a2b2935024415dec69edc997b6090ca9cac010a3018" +dependencies = [ + "base64", + "bstr", + "gix-command", + "gix-credentials", + "gix-features", + "gix-packetline", + "gix-quote", + "gix-sec", + "gix-url", + "reqwest", + "thiserror", +] + +[[package]] +name = "gix-traverse" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a14b7052c0786676c03e71fcfde7d7f0f8e8316e642b5cec6bb3998719b2ce5c" +dependencies = [ + "bitflags", + "gix-commitgraph", + "gix-date", + "gix-hash", + "gix-hashtable", + "gix-object", + "gix-revwalk", + "smallvec", + "thiserror", +] + +[[package]] +name = "gix-url" +version = "0.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35842d099e813f6f6bba529e88d4670572149c3df79b7a412952259887721ece" +dependencies = [ + "bstr", + "gix-path", + "percent-encoding", + "thiserror", +] + +[[package]] +name = "gix-utils" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e477b4f07a6e8da4ba791c53c858102959703c60d70f199932010d5b94adb2c" +dependencies = [ + "bstr", + "fastrand", + "unicode-normalization", +] + +[[package]] +name = "gix-validate" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e26ac2602b43eadfdca0560b81d3341944162a3c9f64ccdeef8fc501ad80dad5" +dependencies = [ + "bstr", +] + +[[package]] +name = "gix-worktree" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d69955eb5e2910832f88d041964b809eee01dadd579237e0b55efec58fd406fd" +dependencies = [ + "bstr", + "gix-attributes", + "gix-fs", + "gix-glob", + "gix-hash", + "gix-ignore", + "gix-index", + "gix-object", + "gix-path", + "gix-validate", +] + +[[package]] +name = "gix-worktree-state" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a96dccbcf9e8fe0291c55f06e08da93ebb2e691c1311276f541eefcc6d70800" +dependencies = [ + "bstr", + "gix-features", + "gix-filter", + "gix-fs", + "gix-index", + "gix-object", + "gix-path", + "gix-worktree", + "io-close", + "thiserror", +] + +[[package]] +name = "gix-worktree-stream" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8444b8ed4662e1a0c97f3eceda29630001a1bbb2632201e50312623e594213" +dependencies = [ + "gix-attributes", + "gix-error", + "gix-features", + "gix-filter", + "gix-fs", + "gix-hash", + "gix-object", + "gix-path", + "gix-traverse", + "parking_lot", +] + +[[package]] name = "globset" version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -639,6 +1902,25 @@ ] [[package]] +name = "h2" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "171fefbc92fe4a4de27e0698d6a5b392d6a0e333506bc49133760b3bcf948733" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] name = "half" version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -650,6 +1932,15 @@ ] [[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.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -668,6 +1959,17 @@ [[package]] name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash 0.2.0", +] + +[[package]] +name = "hashbrown" version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" @@ -678,6 +1980,16 @@ ] [[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.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -723,6 +2035,51 @@ ] [[package]] +name = "http" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "human_format" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaec953f16e5bcf6b8a3cb3aa959b17e5577dbd2693e94554c462c08be22624b" + +[[package]] name = "hybrid-array" version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -732,6 +2089,65 @@ ] [[package]] +name = "hyper" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "h2", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "base64", + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + +[[package]] name = "icu_collections" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -876,6 +2292,22 @@ ] [[package]] +name = "io-close" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cadcf447f06744f8ce713d2d6239bb5bde2c357a452397a9ed90c625da390bc" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "ipnet" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" + +[[package]] name = "is_terminal_polyfill" version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -906,6 +2338,118 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] +name = "jiff" +version = "0.2.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f00b5dbd620d61dfdcb6007c9c1f6054ebd75319f163d886a9055cec1155073d" +dependencies = [ + "jiff-static", + "jiff-tzdb-platform", + "log", + "portable-atomic", + "portable-atomic-util", + "serde_core", + "windows-sys 0.61.2", +] + +[[package]] +name = "jiff-static" +version = "0.2.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e000de030ff8022ea1da3f466fbb0f3a809f5e51ed31f6dd931c35181ad8e6d7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "jiff-tzdb" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c900ef84826f1338a557697dc8fc601df9ca9af4ac137c7fb61d4c6f2dfd3076" + +[[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 = "jni" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" +dependencies = [ + "cfg-if", + "combine", + "jni-macros", + "jni-sys", + "log", + "simd_cesu8", + "thiserror", + "walkdir", + "windows-link", +] + +[[package]] +name = "jni-macros" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "simd_cesu8", + "syn", +] + +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.4", + "libc", +] + +[[package]] +name = "jrb" +version = "0.5.0-pre98" +dependencies = [ + "clap", + "jrsonnet-pkg", + "serde", + "serde_json", + "tracing", + "tracing-subscriber", +] + +[[package]] name = "jrsonnet" version = "0.5.0-pre98" dependencies = [ @@ -1078,6 +2622,23 @@ ] [[package]] +name = "jrsonnet-pkg" +version = "0.5.0-pre98" +dependencies = [ + "camino", + "directories", + "gix", + "peg", + "reqwest", + "serde", + "serde_json", + "thiserror", + "tracing", + "url", + "zip", +] + +[[package]] name = "jrsonnet-rowan-parser" version = "0.5.0-pre98" dependencies = [ @@ -1109,7 +2670,7 @@ "serde", "serde-saphyr", "serde_json", - "sha1", + "sha1 0.11.0", "sha2", "sha3", ] @@ -1158,10 +2719,25 @@ checksum = "9e24a010dd405bd7ed803e5253182815b41bf2e6a80cc3bfc066658e03a198aa" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.3.0", +] + +[[package]] +name = "kstring" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "558bf9508a558512042d3095138b1f7b8fe90c5467d94f9f1da28b3731c5dbd1" +dependencies = [ + "static_assertions", ] [[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] name = "leb128fmt" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1185,6 +2761,18 @@ ] [[package]] +name = "libredox" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c" +dependencies = [ + "bitflags", + "libc", + "plain", + "redox_syscall 0.7.5", +] + +[[package]] name = "linux-raw-sys" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1197,6 +2785,15 @@ checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" [[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] name = "log" version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1244,6 +2841,32 @@ ] [[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + +[[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 = "md5" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1256,6 +2879,15 @@ checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] +name = "memmap2" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "714098028fe011992e1c3962653c96b2d578c4b4bce9036e15ff220319b1e0e3" +dependencies = [ + "libc", +] + +[[package]] name = "mimalloc-sys" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1275,6 +2907,33 @@ ] [[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "mio" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] name = "nix" version = "0.31.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1287,6 +2946,21 @@ ] [[package]] +name = "nonempty" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9737e026353e5cd0736f98eddae28665118eb6f6600902a7f50db585621fecb6" + +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] name = "num-bigint" version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1343,6 +3017,18 @@ checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" [[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] name = "ouroboros" version = "0.18.5" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1377,6 +3063,29 @@ ] [[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.5.18", + "smallvec", + "windows-link", +] + +[[package]] name = "pathdiff" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1416,6 +3125,18 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "plain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" + +[[package]] name = "plotters" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1444,6 +3165,21 @@ ] [[package]] +name = "portable-atomic" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" + +[[package]] +name = "portable-atomic-util" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618" +dependencies = [ + "portable-atomic", +] + +[[package]] name = "potential_utf" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1494,6 +3230,17 @@ ] [[package]] +name = "prodash" +version = "31.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "962200e2d7d551451297d9fdce85138374019ada198e30ea9ede38034e27604c" +dependencies = [ + "bytesize", + "human_format", + "parking_lot", +] + +[[package]] name = "psm" version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1504,6 +3251,62 @@ ] [[package]] +name = "quinn" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash 2.1.2", + "rustls", + "socket2", + "thiserror", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" +dependencies = [ + "aws-lc-rs", + "bytes", + "getrandom 0.3.4", + "lru-slab", + "rand", + "ring", + "rustc-hash 2.1.2", + "rustls", + "rustls-pki-types", + "slab", + "thiserror", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.60.2", +] + +[[package]] name = "quote" version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1592,6 +3395,35 @@ ] [[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_syscall" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4666a1a60d8412eab19d94f6d13dcc9cea0a5ef4fdf6a5db306537413c661b1b" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_users" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" +dependencies = [ + "getrandom 0.2.17", + "libredox", + "thiserror", +] + +[[package]] name = "regex" version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1621,6 +3453,60 @@ checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" [[package]] +name = "reqwest" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62e0021ea2c22aed41653bc7e1419abb2c97e038ff2c33d0e1309e49a97deec0" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "js-sys", + "log", + "mime", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-pki-types", + "rustls-platform-verifier", + "sync_wrapper", + "tokio", + "tokio-rustls", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] name = "rowan" version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1645,6 +3531,15 @@ checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" [[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] name = "rustix" version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1654,10 +3549,85 @@ "errno", "libc", "linux-raw-sys", - "windows-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls" +version = "0.23.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b" +dependencies = [ + "aws-lc-rs", + "once_cell", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pki-types" +version = "1.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9" +dependencies = [ + "web-time", + "zeroize", ] [[package]] +name = "rustls-platform-verifier" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0" +dependencies = [ + "core-foundation", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework", + "security-framework-sys", + "webpki-root-certs", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls-platform-verifier-android" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" + +[[package]] +name = "rustls-webpki" +version = "0.103.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" +dependencies = [ + "aws-lc-rs", + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] name = "rustversion" version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1673,6 +3643,44 @@ ] [[package]] +name = "schannel" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "security-framework" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] name = "semver" version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1740,13 +3748,34 @@ [[package]] name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "digest 0.10.7", +] + +[[package]] +name = "sha1" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214" dependencies = [ "cfg-if", - "cpufeatures", - "digest", + "cpufeatures 0.3.0", + "digest 0.11.3", +] + +[[package]] +name = "sha1-checked" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89f599ac0c323ebb1c6082821a54962b839832b03984598375bff3975b804423" +dependencies = [ + "digest 0.10.7", + "sha1 0.10.6", ] [[package]] @@ -1756,8 +3785,8 @@ checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" dependencies = [ "cfg-if", - "cpufeatures", - "digest", + "cpufeatures 0.3.0", + "digest 0.11.3", ] [[package]] @@ -1766,29 +3795,102 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be176f1a57ce4e3d31c1a166222d9768de5954f811601fb7ca06fc8203905ce1" dependencies = [ - "digest", + "digest 0.11.3", "keccak", ] [[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shell-words" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc6fe69c597f9c37bfeeeeeb33da3530379845f10be461a66d16d03eca2ded77" + +[[package]] name = "shlex" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] +name = "signal-hook" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a0c28ca5908dbdbcd52e6fdaa00358ab88637f8ab33e1f188dd510eb44b53d" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "simd-adler32" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" + +[[package]] +name = "simd_cesu8" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94f90157bb87cddf702797c5dadfa0be7d266cdf49e22da2fcaa32eff75b2c33" +dependencies = [ + "rustc_version", + "simdutf8", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + +[[package]] name = "similar" version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" [[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] name = "smallvec" version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] +name = "socket2" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] name = "stable_deref_trait" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1804,7 +3906,7 @@ "cfg-if", "libc", "psm", - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -1835,6 +3937,12 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] name = "syn" version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1857,6 +3965,15 @@ ] [[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] name = "synstructure" version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1877,7 +3994,7 @@ "getrandom 0.4.2", "once_cell", "rustix", - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -1921,6 +4038,15 @@ ] [[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + +[[package]] name = "tinystr" version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1941,18 +4067,203 @@ ] [[package]] +name = "tinyvec" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" +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 = "tokio" +version = "1.52.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "110a78583f19d5cdb2c5ccf321d1290344e71313c6c37d43520d386027d18386" +dependencies = [ + "bytes", + "libc", + "mio", + "pin-project-lite", + "socket2", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28f0d049ccfaa566e14e9663d304d8577427b368cb4710a20528690287a738b" +dependencies = [ + "bitflags", + "bytes", + "futures-util", + "http", + "http-body", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", + "url", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +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.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex-automata", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "typed-path" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e28f89b80c87b8fb0cf04ab448d5dd0dd0ade2f8891bae878de66a75a28600e" + +[[package]] name = "typenum" version = "1.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" [[package]] +name = "uluru" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c8a2469e56e6e5095c82ccd3afb98dad95f7af7929aab6d8ba8d6e0f73657da" +dependencies = [ + "arrayvec", +] + +[[package]] name = "ungrammar" version = "1.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3e5df347f0bf3ec1d670aad6ca5c6a1859cd9ea61d2113125794654ccced68f" [[package]] +name = "unicode-bom" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7eec5d1121208364f6793f7d2e222bf75a915c19557537745b195b253dd64217" + +[[package]] name = "unicode-box-drawing" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1965,6 +4276,15 @@ checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] +name = "unicode-normalization" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" +dependencies = [ + "tinyvec", +] + +[[package]] name = "unicode-width" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1977,6 +4297,12 @@ checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] name = "url" version = "2.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2001,6 +4327,12 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] name = "version_check" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2026,6 +4358,21 @@ ] [[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] name = "wasip2" version = "1.0.3+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2146,6 +4493,25 @@ ] [[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-root-certs" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31141ce3fc3e300ae89b78c0dd67f9708061d1d2eda54b8209346fd6be9a92c" +dependencies = [ + "rustls-pki-types", +] + +[[package]] name = "winapi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2167,7 +4533,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -2184,6 +4550,24 @@ [[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.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-sys" version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" @@ -2192,6 +4576,135 @@ ] [[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] name = "wit-bindgen" version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2392,6 +4905,12 @@ ] [[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" + +[[package]] name = "zerotrie" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2425,7 +4944,39 @@ ] [[package]] +name = "zip" +version = "8.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d04a6b5381502aa6087c94c669499eb1602eb9c5e8198e534de571f7154809b" +dependencies = [ + "crc32fast", + "flate2", + "indexmap", + "memchr", + "typed-path", + "zopfli", +] + +[[package]] +name = "zlib-rs" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3be3d40e40a133f9c916ee3f9f4fa2d9d63435b5fbe1bfc6d9dae0aa0ada1513" + +[[package]] name = "zmij" version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" + +[[package]] +name = "zopfli" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f05cd8797d63865425ff89b5c4a48804f35ba0ce8d125800027ad6017d2b5249" +dependencies = [ + "bumpalo", + "crc32fast", + "log", + "simd-adler32", +] --- a/Cargo.toml +++ b/Cargo.toml @@ -30,6 +30,7 @@ jrsonnet-types = { path = "./crates/jrsonnet-types", version = "0.5.0-pre98" } jrsonnet-formatter = { path = "./crates/jrsonnet-formatter", version = "0.5.0-pre98" } jrsonnet-lexer = { path = "./crates/jrsonnet-lexer", version = "0.5.0-pre98" } +jrsonnet-pkg = { path = "./crates/jrsonnet-pkg", version = "0.5.0-pre98" } jrsonnet-gcmodule = { version = "0.5.0" } # Diagnostics. # hi-doc is my library, which handles text formatting very well, but isn't polished enough yet @@ -116,6 +117,21 @@ console_error_panic_hook = "0.1" getrandom = "0.3.4" +# Bundler +tracing = "0.1.44" +tracing-subscriber = { version = "0.3.23", features = ["env-filter"] } +reqwest = { version = "0.13", features = [ + "blocking", + "rustls", +], default-features = false } +zip = { version = "8", default-features = false, features = ["deflate"] } +directories = "6.0.0" +gix = { version = "0.83.0", features = [ + "blocking-network-client", + "blocking-http-transport-reqwest-rust-tls", +] } +camino = { version = "1.2.2", features = ["serde1"] } + [workspace.lints.rust] unsafe_op_in_unsafe_fn = "deny" --- /dev/null +++ b/cmds/jrb/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "jrb" +description = "jsonnet package manager" +authors.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true +version.workspace = true + +[lints] +workspace = true + +[dependencies] +jrsonnet-pkg.workspace = true + +clap = { workspace = true, features = ["derive"] } +serde = { workspace = true } +serde_json.workspace = true +tracing.workspace = true +tracing-subscriber.workspace = true --- /dev/null +++ b/cmds/jrb/src/main.rs @@ -0,0 +1,222 @@ +use std::{ + path::{Path, PathBuf}, + process::exit, +}; + +use clap::{Parser, Subcommand}; +use jrsonnet_pkg::{ + install, + jsonnet_bundler::{GitSource, JsonnetFile}, +}; +use tracing::{error, info, warn}; + +#[derive(Parser)] +#[clap(about = "A jsonnet package manager")] +struct Opts { + /// The directory used to cache packages in. + #[clap(long, default_value = "vendor")] + jsonnetpkg_home: PathBuf, + #[clap(subcommand)] + command: Command, +} + +#[derive(Subcommand)] +enum Command { + /// Initialize a new empty jsonnetfile + Init, + /// Install new dependencies. Existing ones are silently skipped + Install { + /// Package URIs to install + uris: Vec, + /// Show what would be done without making changes + #[clap(long)] + dry_run: bool, + }, + /// Update all or specific dependencies + Update { + /// Package URIs to update (all if empty) + uris: Vec, + /// Show what would be done without making changes + #[clap(long)] + dry_run: bool, + }, + /// Remove dependencies by name + Remove { + /// Dependency names (matched against both canonical and legacy names) + names: Vec, + /// Show what would be removed without making changes + #[clap(long)] + dry_run: bool, + }, +} + +const MANIFEST: &str = "jsonnetfile.json"; +const LOCKFILE: &str = "jsonnetfile.lock.json"; + +fn load_manifest() -> JsonnetFile { + let path = Path::new(MANIFEST); + if path.exists() { + JsonnetFile::load(path).unwrap_or_else(|e| { + error!("failed to load {MANIFEST}: {e}"); + exit(1); + }) + } else { + JsonnetFile { + version: 1, + dependencies: Vec::new(), + legacy_imports: true, + } + } +} + +fn save_json(path: &Path, value: &impl serde::Serialize) { + let json = serde_json::to_string_pretty(value).expect("serialization failed"); + std::fs::write(path, format!("{json}\n")).unwrap_or_else(|e| { + error!("failed to write {}: {e}", path.display()); + exit(1); + }); +} + +fn load_lockfile() -> Option { + let path = Path::new(LOCKFILE); + if path.exists() { + Some(JsonnetFile::load(path).unwrap_or_else(|e| { + error!("failed to load {LOCKFILE}: {e}"); + exit(1); + })) + } else { + None + } +} + +fn do_install( + manifest: &JsonnetFile, + lock: Option<&JsonnetFile>, + vendor_dir: &Path, + dry_run: bool, +) { + let new_lock = install::install(manifest, lock, vendor_dir, dry_run).unwrap_or_else(|e| { + error!("install failed: {e}"); + exit(1); + }); + if !dry_run { + save_json(Path::new(LOCKFILE), &new_lock); + } +} + +fn main() { + tracing_subscriber::fmt().init(); + + let opts = Opts::parse(); + + match opts.command { + Command::Init => { + let path = Path::new(MANIFEST); + if path.exists() { + warn!("{MANIFEST} already exists"); + exit(1); + } + let jf = JsonnetFile { + version: 1, + dependencies: Vec::new(), + legacy_imports: true, + }; + save_json(path, &jf); + } + Command::Install { uris, dry_run } => { + let mut manifest = load_manifest(); + + for uri in &uris { + let dep = GitSource::parse(uri).unwrap_or_else(|| { + eprintln!("failed to parse URI: {uri}"); + exit(1); + }); + let is_new = !manifest.dependencies.iter().any(|d| { + std::mem::discriminant(&d.source) == std::mem::discriminant(&dep.source) + && d.canonical_name() == dep.canonical_name() + }); + if is_new { + manifest.dependencies.push(dep); + } + } + + if !uris.is_empty() { + save_json(Path::new(MANIFEST), &manifest); + } + + let lock = load_lockfile(); + do_install(&manifest, lock.as_ref(), &opts.jsonnetpkg_home, dry_run); + } + Command::Update { uris, dry_run } => { + let mut manifest = load_manifest(); + + if !uris.is_empty() { + for uri in &uris { + let dep = GitSource::parse(uri).unwrap_or_else(|| { + eprintln!("failed to parse URI: {uri}"); + exit(1); + }); + if let Some(existing) = manifest + .dependencies + .iter_mut() + .find(|d| d.canonical_name() == dep.canonical_name()) + { + *existing = dep; + } else { + manifest.dependencies.push(dep); + } + } + save_json(Path::new(MANIFEST), &manifest); + } + + do_install(&manifest, None, &opts.jsonnetpkg_home, dry_run); + } + Command::Remove { names, dry_run } => { + let mut manifest = load_manifest(); + + let matched: Vec<_> = manifest + .dependencies + .iter() + .filter(|dep| { + names.iter().any(|name| { + dep.canonical_name() == *name || dep.legacy_link_name() == *name + }) + }) + .cloned() + .collect::>(); + + if matched.is_empty() { + eprintln!("no matching dependencies found"); + exit(1); + } + + for dep in &matched { + let canonical = dep.canonical_name(); + let dir = opts.jsonnetpkg_home.join(&canonical); + let legacy = dep.legacy_link_name(); + let link = opts.jsonnetpkg_home.join(&legacy); + if dry_run { + info!("would remove: {canonical} ({})", dir.display()); + } else { + info!("removing: {canonical}"); + if dir.exists() { + let _ = std::fs::remove_dir_all(&dir); + } + if link.symlink_metadata().is_ok() { + let _ = std::fs::remove_file(&link); + } + } + } + + if !dry_run { + manifest.dependencies.retain(|dep| { + !names.iter().any(|name| { + dep.canonical_name() == *name || dep.legacy_link_name() == *name + }) + }); + save_json(Path::new(MANIFEST), &manifest); + save_json(Path::new(LOCKFILE), &manifest); + } + } + } +} --- /dev/null +++ b/crates/jrsonnet-pkg/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "jrsonnet-pkg" +description = "jsonnet-bundler jsonnetfile parser and installer" +authors.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true +version.workspace = true + +[lints] +workspace = true + +[dependencies] +serde = { workspace = true, features = ["derive"] } +serde_json.workspace = true +thiserror.workspace = true +tracing.workspace = true + +# Source url parser +peg.workspace = true + +# Gix for git repos, reqwest + zip for github +gix.workspace = true +reqwest.workspace = true +zip.workspace = true +url.workspace = true +camino.workspace = true + +# Global cache dir +directories.workspace = true --- /dev/null +++ b/crates/jrsonnet-pkg/src/install/accessor.rs @@ -0,0 +1,132 @@ +use std::{ + fs::File, + io::{self, Read}, + result, + str::FromStr as _, + sync::Mutex, +}; + +use tracing::warn; +use zip::{ZipArchive, result::ZipError}; + +use crate::jsonnet_bundler::{SubDir, SubDirEscapeError}; + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error(transparent)] + Zip(#[from] ZipError), + #[error("invalid prefixed archive")] + ZipInvalidPrefix, + #[error("zip io: {0}")] + ZipIo(io::Error), + #[error("subdir not found: {0}")] + SubDirNotFound(SubDir), + #[error(transparent)] + SubdirEscape(#[from] SubDirEscapeError), +} +type Result = result::Result; + +pub trait SourceAccessor {} + +pub struct ZipFileAccessor { + archive: Mutex>, + // Github archives have top-level directory with repo name + prefix: SubDir, +} + +impl ZipFileAccessor { + pub fn new_prefixed(file: File) -> Result { + let archive = ZipArchive::new(file)?; + let prefix = archive.name_for_index(0).ok_or(Error::ZipInvalidPrefix)?; + + Ok(Self { + prefix: SubDir::from_str(prefix)?, + archive: Mutex::new(archive), + }) + } + /// Read a file from inside the archive's logical root (after stripping the + /// github-style `-/` prefix). + #[allow(clippy::significant_drop_tightening, reason = "false-positive")] + pub fn read(&self, name: &SubDir) -> Result>> { + let prefixed = self + .prefix + .join(name) + .expect("prefix and name are both subdirs"); + let mut archive = self.archive.lock().expect("not poisoned"); + let mut v = match archive.by_name(prefixed.as_str()) { + Ok(v) => v, + Err(ZipError::FileNotFound) => return Ok(None), + Err(e) => return Err(e.into()), + }; + if !v.is_file() { + return Ok(None); + } + let mut out = Vec::new(); + v.read_to_end(&mut out).map_err(Error::ZipIo)?; + Ok(Some(out)) + } + #[allow(clippy::significant_drop_tightening, reason = "false-positive")] + pub fn iter( + &self, + subdir: &SubDir, + cb: &mut dyn FnMut(SubDir, AccessorEntry) -> Result<(), E>, + ) -> Result<(), E> + where + E: From, + { + let mut archive = self.archive.lock().expect("not poisoned"); + let len = archive.len(); + + let mut found = false; + for i in 0..len { + let mut entry = archive.by_index(i).map_err(Error::from)?; + let raw = entry.name(); + let Ok(full_name) = SubDir::from_str(raw) else { + warn!("invalid zip entry name: {raw}"); + continue; + }; + // Peel off the github-archive top-level `-/` prefix. + let Some(in_repo) = full_name.strip_prefix(&self.prefix) else { + continue; + }; + let Some(name) = in_repo.strip_prefix(subdir) else { + continue; + }; + found = true; + if name.is_empty() && entry.is_dir() { + continue; + } + + cb( + name.clone(), + if entry.is_dir() { + AccessorEntry::Dir + } else if entry.is_file() { + let mut data = Vec::new(); + entry.read_to_end(&mut data).map_err(Error::ZipIo)?; + AccessorEntry::File(data) + } else { + // TODO: Symlinks? + panic!("unknown accessor entry type: {name:?}") + }, + )?; + } + + if !found { + return Err(Error::SubDirNotFound(subdir.clone()).into()); + } + + Ok(()) + } + pub fn len(&self) -> usize { + self.archive.lock().expect("not poisoned").len() + } + pub fn is_empty(&self) -> bool { + self.len() == 0 + } +} + +pub enum AccessorEntry { + Dir, + File(Vec), +} --- /dev/null +++ b/crates/jrsonnet-pkg/src/install/git.rs @@ -0,0 +1,212 @@ +#![allow(clippy::result_large_err)] + +use std::{collections::HashSet, fs, path::Path}; + +use gix::{ + bstr::{self, ByteSlice}, + interrupt, progress, + remote::{self, ref_map}, +}; +use tracing::info; + +use super::{Error, LocalExtraction, ResolveResult, Result, VendorSource, cache_dir}; +use crate::jsonnet_bundler::{Dependency, GitSource, JsonnetFile, Source, SubDir}; + +fn repo_cache_path(remote: &GitSource) -> Result { + Ok(cache_dir("git")?.join(&remote.host).join(&remote.repo)) +} + +fn ensure_repo(remote: &GitSource) -> Result { + let cache_path = repo_cache_path(remote)?; + + if cache_path.exists() { + if let Ok(repo) = gix::open(&cache_path) { + fetch_remote(&repo, &remote.remote())?; + return Ok(repo); + } + fs::remove_dir_all(&cache_path).map_err(|e| Error::Io(cache_path.clone(), e))?; + } + + fs::create_dir_all(cache_path.parent().expect("has parent")) + .map_err(|e| Error::Io(cache_path.clone(), e))?; + + let mut clone = gix::prepare_clone_bare(remote.remote(), &cache_path)?; + let (repo, _) = clone.fetch_only(progress::Discard, &interrupt::IS_INTERRUPTED)?; + fetch_remote(&repo, &remote.remote())?; + + Ok(repo) +} + +fn fetch_remote(repo: &gix::Repository, remote: &str) -> Result<(), Error> { + repo.remote_at(remote)? + .with_refspecs(["+refs/*:refs/*"], remote::Direction::Fetch)? + .connect(remote::Direction::Fetch)? + .prepare_fetch(progress::Discard, ref_map::Options::default())? + .receive(progress::Discard, &interrupt::IS_INTERRUPTED)?; + Ok(()) +} + +fn extract_tree( + repo: &gix::Repository, + tree: &gix::Tree<'_>, + subdir: &SubDir, + dest: &Path, +) -> Result<(), Error> { + let target_tree; + let tree = if subdir.is_empty() { + tree + } else { + let mut t = tree.clone(); + let entry = t + .peel_to_entry_by_path(subdir.as_path().as_std_path())? + .ok_or_else(|| Error::SubdirNotFound(subdir.to_string()))?; + target_tree = entry.object()?.into_tree(); + &target_tree + }; + + let files = tree.traverse().breadthfirst.files()?; + + for entry in &files { + if !entry.mode.is_blob() { + continue; + } + let rel_path = entry + .filepath + .to_str() + .map_err(|_| Error::InvalidPath(entry.filepath.to_string()))?; + let file_path = dest.join(rel_path); + + if let Some(parent) = file_path.parent() { + fs::create_dir_all(parent).map_err(|e| Error::Io(parent.to_owned(), e))?; + } + + let blob = repo.find_object(entry.oid)?; + fs::write(&file_path, &blob.data).map_err(|e| Error::Io(file_path, e))?; + } + + Ok(()) +} + +fn resolve_version<'r>(repo: &'r gix::Repository, version: &str) -> Result> { + let spec: &bstr::BStr = version.into(); + if let Ok(id) = repo.rev_parse_single(spec) { + return Ok(id); + } + for prefix in ["refs/heads/", "refs/tags/"] { + let refname = format!("{prefix}{version}"); + if let Ok(r) = repo.find_reference(&refname) { + return Ok(r.into_fully_peeled_id()?); + } + } + Ok(repo.rev_parse_single(spec)?) +} + +fn read_blob_at_path( + repo: &gix::Repository, + tree: &gix::Tree<'_>, + path: &SubDir, +) -> Option> { + let mut t = tree.clone(); + let entry = t + .peel_to_entry_by_path(path.as_path().as_std_path()) + .ok()??; + let blob = repo.find_object(entry.oid()).ok()?; + Some(blob.data.clone()) +} + +fn collect_tree_deps( + repo: &gix::Repository, + tree: &gix::Tree<'_>, + dir: &SubDir, + git_deps: &mut Vec, + local_extractions: &mut Vec, + visited: &mut HashSet, +) { + if !visited.insert(dir.clone()) { + return; + } + + let manifest_path = dir + .join("jsonnetfile.json") + .expect("appending a literal filename keeps it within parent"); + let Some(data) = read_blob_at_path(repo, tree, &manifest_path) else { + return; + }; + let Ok(manifest) = serde_json::from_slice::(&data) else { + return; + }; + + for dep in manifest.dependencies { + match &dep.source { + Source::Git(_) => git_deps.push(dep), + Source::Local(local) => { + let Ok(child_dir) = local.resolve_under(dir) else { + info!("local source {local} escapes its package; skipping"); + continue; + }; + let name = child_dir + .file_name() + .map_or_else(|| local.to_string(), str::to_owned); + local_extractions.push(LocalExtraction { + tree_path: child_dir.clone(), + name, + }); + collect_tree_deps(repo, tree, &child_dir, git_deps, local_extractions, visited); + } + } + } +} + +pub(super) fn resolve( + git_source: &GitSource, + version: Option<&str>, +) -> Result { + info!("fetching via git: {}", git_source.remote()); + let repo = ensure_repo(git_source)?; + let id = match version { + Some(v) => resolve_version(&repo, v)?, + None => repo.head_id()?, + }; + let commit = repo.find_object(id)?.into_commit(); + let tree = commit.tree()?; + + let mut transitive_git_deps = Vec::new(); + let mut local_extractions = Vec::new(); + let mut visited = HashSet::new(); + collect_tree_deps( + &repo, + &tree, + &git_source.subdir, + &mut transitive_git_deps, + &mut local_extractions, + &mut visited, + ); + + let repo_path = repo_cache_path(git_source)?; + let sha = id.to_string(); + + Ok(ResolveResult { + version: sha.clone(), + transitive_git_deps, + local_extractions, + source: VendorSource::GitTree { + repo_path, + commit_sha: sha, + subdir: git_source.subdir.clone(), + }, + }) +} + +pub(super) fn extract( + repo_path: &Path, + commit_sha: &str, + subdir: &SubDir, + dest: &Path, +) -> Result<(), Error> { + let repo = gix::open(repo_path)?; + let spec: &bstr::BStr = commit_sha.into(); + let id = repo.rev_parse_single(spec)?; + let commit = repo.find_object(id)?.into_commit(); + let tree = commit.tree()?; + extract_tree(&repo, &tree, subdir, dest) +} --- /dev/null +++ b/crates/jrsonnet-pkg/src/install/github.rs @@ -0,0 +1,190 @@ +#![allow(clippy::result_large_err)] + +use std::{ + collections::HashSet, + fs::{self, File}, + io::Write as _, + path::{Path, PathBuf}, +}; + +use reqwest::{blocking::Response, header}; +use tracing::{debug, info}; + +use super::{ + Error, LocalExtraction, ResolveResult, Result, VendorSource, + accessor::{AccessorEntry, ZipFileAccessor}, +}; +use crate::{ + install::{PKG_USER_AGENT, cache_dir}, + jsonnet_bundler::{Dependency, GitSource, JsonnetFile, Source, SubDir}, +}; + +fn is_sha(s: &str) -> bool { + s.len() == 40 && s.bytes().all(|b| b.is_ascii_hexdigit()) +} + +fn commit_cache_path(source: &GitSource, sha: &str) -> Result { + Ok(cache_dir("github")? + .join(source.plain_repo_name()) + .join(format!("{sha}.zip"))) +} + +fn resolve_sha(source: &GitSource, version: &str) -> Result { + let url = format!( + "https://api.github.com/repos/{}/commits/{}", + source.plain_repo_name(), + version + ); + let response = reqwest::blocking::Client::new() + .get(&url) + .header(header::ACCEPT, "application/vnd.github.sha") + .header(header::USER_AGENT, PKG_USER_AGENT) + .send() + .and_then(Response::error_for_status)?; + let sha = response.text()?; + Ok(sha.trim().to_owned()) +} + +fn fetch_zip(source: &GitSource, sha: &str) -> Result { + let cached = commit_cache_path(source, sha)?; + if cached.exists() { + debug!("using cached archive {}", cached.display()); + return Ok(ZipFileAccessor::new_prefixed( + File::open(&cached).map_err(|e| Error::Io(cached.clone(), e))?, + )?); + } + + let url = format!( + "https://github.com/{}/archive/{}.zip", + source.plain_repo_name(), + sha + ); + info!("downloading {url}"); + + let bytes = reqwest::blocking::Client::new() + .get(&url) + .header(header::USER_AGENT, PKG_USER_AGENT) + .send() + .and_then(Response::error_for_status)? + .bytes()?; + + if let Some(parent) = cached.parent() { + fs::create_dir_all(parent).map_err(|e| Error::Io(parent.to_owned(), e))?; + } + let mut downloaded = File::create_new(&cached).map_err(|e| Error::Io(cached.clone(), e))?; + downloaded + .write_all(&bytes) + .map_err(|e| Error::Io(cached.clone(), e))?; + + Ok(ZipFileAccessor::new_prefixed(downloaded)?) +} + +fn open_cached_zip(zip_path: &Path) -> Result { + Ok(ZipFileAccessor::new_prefixed( + File::open(zip_path).map_err(|e| Error::Io(zip_path.to_owned(), e))?, + )?) +} + +fn extract_subdir(archive: &ZipFileAccessor, subdir: &SubDir, dest: &Path) -> Result<()> { + archive.iter(subdir, &mut |name, entry| { + let target = dest.join(name); + match entry { + AccessorEntry::Dir => { + fs::create_dir_all(&target).map_err(|e| Error::Io(target, e))?; + } + AccessorEntry::File(data) => { + if let Some(parent) = target.parent() { + fs::create_dir_all(parent).map_err(|e| Error::Io(parent.to_owned(), e))?; + } + fs::write(&target, &data).map_err(|e| Error::Io(target, e))?; + } + } + Ok(()) + }) +} + +fn collect_archive_deps( + archive: &ZipFileAccessor, + dir: &SubDir, + git_deps: &mut Vec, + local_extractions: &mut Vec, + visited: &mut HashSet, +) -> Result<()> { + if !visited.insert(dir.clone()) { + return Ok(()); + } + + let manifest_path = dir + .join("jsonnetfile.json") + .expect("appending a literal filename keeps it within parent"); + + let Some(data) = archive.read(&manifest_path)? else { + return Ok(()); + }; + let Ok(manifest) = serde_json::from_slice::(&data) else { + return Ok(()); + }; + + for dep in manifest.dependencies { + match &dep.source { + Source::Git(_) => git_deps.push(dep), + Source::Local(local) => { + let Ok(child_dir) = local.resolve_under(dir) else { + tracing::info!("local source {local} escapes its package; skipping"); + continue; + }; + let name = child_dir + .file_name() + .map_or_else(|| local.to_string(), str::to_owned); + local_extractions.push(LocalExtraction { + tree_path: child_dir.clone(), + name, + }); + collect_archive_deps(archive, &child_dir, git_deps, local_extractions, visited)?; + } + } + } + Ok(()) +} + +pub(super) fn resolve(source: &GitSource, version: Option<&str>) -> Result { + let version_str = version.unwrap_or("HEAD"); + let sha = if is_sha(version_str) { + version_str.to_owned() + } else { + let resolved = resolve_sha(source, version_str)?; + info!("resolved {version_str} to {resolved}"); + resolved + }; + + let archive = fetch_zip(source, &sha)?; + + let mut transitive_git_deps = Vec::new(); + let mut local_extractions = Vec::new(); + let mut visited = HashSet::new(); + collect_archive_deps( + &archive, + &source.subdir, + &mut transitive_git_deps, + &mut local_extractions, + &mut visited, + )?; + + let zip_path = commit_cache_path(source, &sha)?; + + Ok(ResolveResult { + version: sha.clone(), + transitive_git_deps, + local_extractions, + source: VendorSource::GithubZip { + zip_path, + commit_sha: sha, + subdir: source.subdir.clone(), + }, + }) +} + +pub(super) fn extract(zip_path: &Path, subdir: &SubDir, dest: &Path) -> Result<()> { + let archive = open_cached_zip(zip_path)?; + extract_subdir(&archive, subdir, dest) +} --- /dev/null +++ b/crates/jrsonnet-pkg/src/install/mod.rs @@ -0,0 +1,406 @@ +#![allow(clippy::result_large_err)] + +pub mod accessor; +mod git; +mod github; + +use std::{ + collections::{BTreeMap, HashSet}, + fs, + path::{Path, PathBuf}, + result, +}; + +use camino::Utf8PathBuf; +use tracing::info; + +use crate::jsonnet_bundler::{Dependency, GitScheme, GitSource, JsonnetFile, Source, SubDir}; + +pub const PKG_USER_AGENT: &str = "jrsonnet-pkg (https://delta.rocks/jrsonnet)"; + +pub fn cache_dir(subdir: &str) -> Result { + Ok(directories::ProjectDirs::from("rocks", "delta", "jrsonnet") + .ok_or(Error::XdgUnavailable)? + .cache_dir() + .join(subdir)) +} + +pub(crate) struct LocalExtraction { + /// Path inside the parent repo's tree where this local source lives. + pub tree_path: SubDir, + pub name: String, +} + +pub(crate) struct ResolveResult { + pub version: String, + pub transitive_git_deps: Vec, + pub local_extractions: Vec, + pub source: VendorSource, +} + +const VERSION_FILE: &str = ".version"; + +/// How to populate a vendor path. +pub enum VendorSource { + GitTree { + repo_path: PathBuf, + commit_sha: String, + subdir: SubDir, + }, + GithubZip { + zip_path: PathBuf, + commit_sha: String, + subdir: SubDir, + }, + Symlink(Utf8PathBuf), +} + +impl VendorSource { + fn with_subdir(&self, new_subdir: SubDir) -> Self { + match self { + VendorSource::GitTree { + repo_path, + commit_sha, + .. + } => VendorSource::GitTree { + repo_path: repo_path.clone(), + commit_sha: commit_sha.clone(), + subdir: new_subdir, + }, + VendorSource::GithubZip { + zip_path, + commit_sha, + .. + } => VendorSource::GithubZip { + zip_path: zip_path.clone(), + commit_sha: commit_sha.clone(), + subdir: new_subdir, + }, + VendorSource::Symlink(target) => VendorSource::Symlink(target.clone()), + } + } +} + +pub struct InstallPlan { + pub lock: JsonnetFile, + /// vendor-relative path -> how to obtain it. + pub entries: BTreeMap, +} + +pub fn install( + manifest: &JsonnetFile, + lock: Option<&JsonnetFile>, + vendor_dir: &Path, + dry_run: bool, +) -> Result { + let plan = resolve(manifest, lock)?; + execute(&plan, vendor_dir, dry_run)?; + Ok(plan.lock) +} + +pub fn resolve(manifest: &JsonnetFile, lock: Option<&JsonnetFile>) -> Result { + let mut plan = InstallPlan { + lock: JsonnetFile { + version: manifest.version, + dependencies: Vec::new(), + legacy_imports: manifest.legacy_imports, + }, + entries: BTreeMap::new(), + }; + let mut installed = HashSet::new(); + + resolve_deps( + &manifest.dependencies, + lock, + manifest.legacy_imports, + &mut plan, + &mut installed, + )?; + + Ok(plan) +} + +fn is_up_to_date(dest: &Path, version: &str) -> bool { + fs::read_to_string(dest.join(VERSION_FILE)).is_ok_and(|v| v.trim() == version) +} + +fn write_version(dest: &Path, version: &str) -> Result<(), Error> { + fs::write(dest.join(VERSION_FILE), format!("{version}\n")) + .map_err(|e| Error::Io(dest.join(VERSION_FILE), e)) +} + +pub fn execute(plan: &InstallPlan, vendor_dir: &Path, dry_run: bool) -> Result<(), Error> { + if !dry_run { + for (path, source) in &plan.entries { + let dest = vendor_dir.join(path); + match source { + VendorSource::GitTree { + repo_path, + commit_sha, + subdir, + } => { + if is_up_to_date(&dest, commit_sha) { + continue; + } + info!("extract {path}"); + if dest.exists() { + fs::remove_dir_all(&dest).map_err(|e| Error::Io(dest.clone(), e))?; + } + fs::create_dir_all(&dest).map_err(|e| Error::Io(dest.clone(), e))?; + git::extract(repo_path, commit_sha, subdir, &dest)?; + write_version(&dest, commit_sha)?; + } + VendorSource::GithubZip { + zip_path, + commit_sha, + subdir, + } => { + if is_up_to_date(&dest, commit_sha) { + continue; + } + info!("extract {path}"); + if dest.exists() { + fs::remove_dir_all(&dest).map_err(|e| Error::Io(dest.clone(), e))?; + } + fs::create_dir_all(&dest).map_err(|e| Error::Io(dest.clone(), e))?; + github::extract(zip_path, subdir, &dest)?; + write_version(&dest, commit_sha)?; + } + VendorSource::Symlink(_) => {} + } + } + for (path, source) in &plan.entries { + if let VendorSource::Symlink(target) = source { + let dest = vendor_dir.join(path); + if dest + .symlink_metadata() + .is_ok_and(|m| m.file_type().is_symlink()) + { + if fs::read_link(&dest).is_ok_and(|t| t == target.as_std_path()) { + continue; + } + fs::remove_file(&dest).map_err(|e| Error::Io(dest.clone(), e))?; + } + info!("symlink {path} -> {target}"); + std::os::unix::fs::symlink(target.as_std_path(), &dest) + .map_err(|e| Error::Io(dest.clone(), e))?; + } + } + } + prune(plan, vendor_dir, dry_run)?; + Ok(()) +} + +fn prune(plan: &InstallPlan, vendor_dir: &Path, dry_run: bool) -> Result<(), Error> { + if !vendor_dir.is_dir() { + return Ok(()); + } + prune_recursive(plan, vendor_dir, vendor_dir, dry_run) +} + +fn prune_recursive( + plan: &InstallPlan, + vendor_dir: &Path, + dir: &Path, + dry_run: bool, +) -> Result<(), Error> { + let entries = fs::read_dir(dir).map_err(|e| Error::Io(dir.to_owned(), e))?; + for entry in entries { + let entry = entry.map_err(|e| Error::Io(dir.to_owned(), e))?; + let path = entry.path(); + let rel = path + .strip_prefix(vendor_dir) + .expect("path is under vendor_dir"); + let Ok(rel) = Utf8PathBuf::try_from(rel.to_owned()) else { + info!("prune (non-utf8) {}", rel.display()); + continue; + }; + + if plan.entries.contains_key(&rel) { + continue; + } + + let ft = entry.file_type().map_err(|e| Error::Io(path.clone(), e))?; + if ft.is_symlink() { + info!("prune {rel}"); + if !dry_run { + fs::remove_file(&path).map_err(|e| Error::Io(path, e))?; + } + } else if ft.is_dir() { + let prefix: Utf8PathBuf = format!("{rel}/").into(); + let has_descendants = plan + .entries + .range(prefix.clone()..) + .next() + .is_some_and(|(k, _)| k.starts_with(&prefix)); + if has_descendants { + prune_recursive(plan, vendor_dir, &path, dry_run)?; + } else { + info!("prune {rel}"); + if !dry_run { + fs::remove_dir_all(&path).map_err(|e| Error::Io(path, e))?; + } + } + } else { + info!("prune {rel}"); + if !dry_run { + fs::remove_file(&path).map_err(|e| Error::Io(path, e))?; + } + } + } + + if !dry_run + && dir != vendor_dir + && let Ok(mut entries) = fs::read_dir(dir) + && entries.next().is_none() + { + let _ = fs::remove_dir(dir); + } + + Ok(()) +} + +fn resolve_one(git_source: &GitSource, version: Option<&str>) -> Result { + if git_source.host == "github.com" && git_source.scheme == GitScheme::Https { + match github::resolve(git_source, version) { + Ok(result) => return Ok(result), + Err(e) => { + info!("github archive failed ({e}), falling back to git"); + } + } + } + git::resolve(git_source, version) +} + +fn locked_version<'a>(dep: &Dependency, lock: Option<&'a JsonnetFile>) -> Option<&'a str> { + let lock = lock?; + let key = dep.canonical_name(); + lock.dependencies + .iter() + .find(|d| d.canonical_name() == key) + .and_then(|d| d.version.as_deref()) +} + +fn resolve_deps( + deps: &[Dependency], + lock: Option<&JsonnetFile>, + legacy_imports: bool, + plan: &mut InstallPlan, + installed: &mut HashSet, +) -> Result<(), Error> { + for dep in deps { + let Source::Git(git_source) = &dep.source else { + continue; + }; + + let canonical = dep.canonical_name(); + if !installed.insert(canonical.clone()) { + continue; + } + + let version = locked_version(dep, lock).or(dep.version.as_deref()); + + info!( + "resolving {canonical} (version: {})", + version.unwrap_or("") + ); + + let result = resolve_one(git_source, version)?; + + plan.lock.dependencies.push(Dependency { + source: dep.source.clone(), + version: Some(result.version), + sum: dep.sum.clone(), + name: dep.name.clone(), + single: dep.single, + }); + + let mut repo_base = Utf8PathBuf::from(git_source.host.as_str()); + repo_base.push(git_source.plain_repo_name()); + + // Legacy symlink for the dep. Skipped if `legacyImports: false`, unless + // the user explicitly set `dep.name` (which is always honored). + if legacy_imports || dep.name.is_some() { + let legacy = Utf8PathBuf::from(dep.legacy_link_name()); + if legacy != canonical { + plan.entries + .insert(legacy, VendorSource::Symlink(canonical.clone())); + } + } + + for extraction in &result.local_extractions { + let extraction_canonical = repo_base.join(&extraction.tree_path); + plan.entries.insert( + extraction_canonical.clone(), + result.source.with_subdir(extraction.tree_path.clone()), + ); + if legacy_imports { + let extraction_name = Utf8PathBuf::from(&extraction.name); + if extraction_name != extraction_canonical { + plan.entries + .insert(extraction_name, VendorSource::Symlink(extraction_canonical)); + } + } + } + + // Main entry (after local extractions used with_subdir) + plan.entries.insert(canonical, result.source); + + resolve_deps( + &result.transitive_git_deps, + lock, + legacy_imports, + plan, + installed, + )?; + } + + Ok(()) +} + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("io error for {0}: {1}")] + Io(PathBuf, std::io::Error), + #[error("failed to discover xdg directories")] + XdgUnavailable, + #[error("git clone failed: {0}")] + GitClone(#[from] gix::clone::Error), + #[error(transparent)] + GitRemote(#[from] gix::remote::init::Error), + #[error(transparent)] + GitConnect(#[from] gix::remote::connect::Error), + #[error(transparent)] + GitFetchPrepare(#[from] gix::remote::fetch::prepare::Error), + #[error(transparent)] + GitRemoteFetch(#[from] gix::remote::fetch::Error), + #[error(transparent)] + GitCloneFetch(#[from] gix::clone::fetch::Error), + #[error(transparent)] + GitFindObject(#[from] gix::object::find::existing::Error), + #[error(transparent)] + GitTraverse(#[from] gix::traverse::tree::breadthfirst::Error), + #[error(transparent)] + GitHead(#[from] gix::reference::head_id::Error), + #[error(transparent)] + GitCommit(#[from] gix::object::commit::Error), + #[error(transparent)] + GitRevparse(#[from] gix::revision::spec::parse::single::Error), + #[error(transparent)] + GitRefspec(#[from] gix::refspec::parse::Error), + #[error(transparent)] + GitPeel(#[from] gix::reference::peel::Error), + #[error(transparent)] + GitOpen(#[from] gix::open::Error), + #[error("http error: {0}")] + Http(#[from] reqwest::Error), + #[error("zip error: {0}")] + Zip(Box), + #[error(transparent)] + Accessor(#[from] accessor::Error), + #[error("unknown subdir: {0}")] + SubdirNotFound(String), + #[error("invalid path in tree: {0}")] + InvalidPath(String), +} +pub(crate) type Result = result::Result; --- /dev/null +++ b/crates/jrsonnet-pkg/src/jsonnet_bundler.rs @@ -0,0 +1,883 @@ +use std::{fmt, path::Path, str::FromStr}; + +use camino::{Utf8Component, Utf8Path, Utf8PathBuf}; +use serde::{Deserialize, Serialize, de}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct JsonnetFile { + pub version: u32, + #[serde(default)] + pub dependencies: Vec, + #[serde(default = "legacy_imports_default", rename = "legacyImports")] + pub legacy_imports: bool, +} + +fn legacy_imports_default() -> bool { + true +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Dependency { + pub source: Source, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub version: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub sum: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub name: Option, + #[serde(default, skip_serializing_if = "is_false")] + pub single: bool, +} + +#[allow(clippy::trivially_copy_pass_by_ref, reason = "serde")] +fn is_false(v: &bool) -> bool { + !v +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum Source { + Git(GitSource), + Local(LocalSource), +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum GitScheme { + Https, + Ssh, +} + +/// Wrapper over `Utf8PathBuf`, ensuring it can't escape to either an absolute +/// path or a parent directory. +#[derive(Debug, Clone, Default, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub struct SubDir(Utf8PathBuf); + +#[derive(Debug, thiserror::Error)] +#[error("subdir attempted to escape")] +pub struct SubDirEscapeError; + +impl FromStr for SubDir { + type Err = SubDirEscapeError; + fn from_str(s: &str) -> Result { + Self::try_from(Utf8PathBuf::from(s)) + } +} +impl TryFrom for SubDir { + type Error = SubDirEscapeError; + + fn try_from(buf: Utf8PathBuf) -> Result { + for ele in buf.components() { + match ele { + Utf8Component::Prefix(_) | Utf8Component::RootDir | Utf8Component::ParentDir => { + return Err(SubDirEscapeError); + } + Utf8Component::CurDir | Utf8Component::Normal(_) => {} + } + } + Ok(Self(buf)) + } +} + +impl SubDir { + pub fn empty() -> Self { + Self(Utf8PathBuf::new()) + } + pub fn as_str(&self) -> &str { + self.0.as_str() + } + pub fn as_path(&self) -> &Utf8Path { + &self.0 + } + pub fn into_inner(self) -> Utf8PathBuf { + self.0 + } + pub fn join(&self, other: impl AsRef) -> Result { + SubDir::try_from(self.0.join(other)) + } + pub fn strip_prefix(&self, prefix: &SubDir) -> Option { + Some( + SubDir::try_from(self.0.strip_prefix(&prefix.0).ok()?.to_owned()) + .expect("stripping would not result in escape"), + ) + } + pub fn is_empty(&self) -> bool { + self.0.as_str().is_empty() + } + pub fn file_name(&self) -> Option<&str> { + self.0.file_name() + } + /// Strip a trailing `.git` extension, if any. + #[must_use] + pub fn without_git_suffix(&self) -> SubDir { + let mut p = self.0.clone(); + if p.extension() == Some("git") { + p.set_extension(""); + } + SubDir(p) + } +} +impl AsRef for SubDir { + fn as_ref(&self) -> &Utf8Path { + &self.0 + } +} +impl AsRef for SubDir { + fn as_ref(&self) -> &Path { + self.0.as_ref() + } +} +impl AsRef for SubDir { + fn as_ref(&self) -> &str { + self.0.as_str() + } +} +impl fmt::Display for SubDir { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) + } +} +impl PartialEq for SubDir { + fn eq(&self, other: &str) -> bool { + self.0.as_str() == other + } +} +impl PartialEq<&str> for SubDir { + fn eq(&self, other: &&str) -> bool { + self.0.as_str() == *other + } +} + +/// Wrapper over `String`, guaranteeing the value is a valid host: only ASCII +/// alphanumerics, dashes and dots, with at least one segment. +#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub struct Hostname(String); + +#[derive(Debug, thiserror::Error)] +#[error("invalid hostname")] +pub struct InvalidHostnameError; + +impl FromStr for Hostname { + type Err = InvalidHostnameError; + + fn from_str(s: &str) -> Result { + if s.is_empty() || s == "." || s == ".." { + return Err(InvalidHostnameError); + } + for seg in s.split('.') { + if seg.is_empty() { + return Err(InvalidHostnameError); + } + if !seg.bytes().all(|b| b.is_ascii_alphanumeric() || b == b'-') { + return Err(InvalidHostnameError); + } + } + Ok(Self(s.to_owned())) + } +} + +impl Hostname { + pub fn as_str(&self) -> &str { + &self.0 + } +} +impl AsRef for Hostname { + fn as_ref(&self) -> &str { + &self.0 + } +} +impl AsRef for Hostname { + fn as_ref(&self) -> &Path { + self.0.as_ref() + } +} +impl AsRef for Hostname { + fn as_ref(&self) -> &Utf8Path { + self.0.as_str().into() + } +} +impl fmt::Display for Hostname { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(&self.0) + } +} +impl PartialEq for Hostname { + fn eq(&self, other: &str) -> bool { + self.0 == other + } +} +impl PartialEq<&str> for Hostname { + fn eq(&self, other: &&str) -> bool { + self.0 == *other + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct GitSource { + pub scheme: GitScheme, + pub host: Hostname, + /// Repo path relative to host: `user/repo[.git]` (or with subgroups). + pub repo: SubDir, + /// Subdirectory within the repo. Empty means the repo root. + pub subdir: SubDir, +} + +/// A relative path that may climb out of its package via `..` parts, but only +/// at the head - once you go down (`SubDir` portion) you can't go back up. +/// +/// The total upward count is bounded only at resolution time, against the +/// containing package's depth. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct LocalSource { + pub ups: usize, + pub dir: SubDir, +} + +impl FromStr for LocalSource { + // Technically incorrect, as it only rejects mid-path ../'s... + type Err = SubDirEscapeError; + + fn from_str(s: &str) -> Result { + let mut ups = 0usize; + let mut rest = s; + loop { + if let Some(r) = rest.strip_prefix("./") { + rest = r; + } else if rest == "." { + rest = ""; + break; + } else if let Some(r) = rest.strip_prefix("../") { + ups = ups.checked_add(1).expect("can't be longer than s length"); + rest = r; + } else if rest == ".." { + ups = ups.checked_add(1).expect("can't be longer than s length"); + rest = ""; + break; + } else { + break; + } + } + Ok(Self { + ups, + dir: SubDir::from_str(rest)?, + }) + } +} + +impl fmt::Display for LocalSource { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut out = String::with_capacity(self.ups * 3 + self.dir.as_str().len()); + for _ in 0..self.ups { + out.push_str("../"); + } + out.push_str(self.dir.as_str()); + if out.is_empty() { + out.push('.'); + } else if out.ends_with('/') { + out.pop(); + } + // TODO: I didn't finish + f.write_str(&out) + } +} + +impl LocalSource { + pub fn resolve_under(&self, parent: &SubDir) -> Result { + let mut comps: Vec<&str> = parent.as_path().components().map(|c| c.as_str()).collect(); + if self.ups > comps.len() { + return Err(SubDirEscapeError); + } + comps.truncate(comps.len() - self.ups); + let mut buf = Utf8PathBuf::from_iter(comps); + buf.push(self.dir.as_path()); + SubDir::try_from(buf) + } +} + +impl Serialize for LocalSource { + fn serialize(&self, ser: S) -> Result { + #[derive(Serialize)] + struct JsonLocal<'a> { + directory: &'a str, + } + let rendered = self.to_string(); + JsonLocal { + directory: &rendered, + } + .serialize(ser) + } +} + +impl<'de> Deserialize<'de> for LocalSource { + fn deserialize>(de: D) -> Result { + #[derive(Deserialize)] + struct JsonLocal { + directory: String, + } + let j = JsonLocal::deserialize(de)?; + LocalSource::from_str(&j.directory) + .map_err(|e| de::Error::custom(format!("invalid local path {:?}: {e}", j.directory))) + } +} + +impl GitSource { + /// Repo path with the trailing `.git` (if any) stripped. + pub fn plain_repo_name(&self) -> SubDir { + self.repo.without_git_suffix() + } + + /// Canonical install path: `host/user/repo[/subdir]`. + pub fn name(&self) -> SubDir { + let mut p = Utf8PathBuf::from(self.host.as_str()); + p.push(self.plain_repo_name()); + if !self.subdir.is_empty() { + p.push(self.subdir.as_path()); + } + SubDir::try_from(p).expect("host + subdirs is a valid SubDir") + } + + /// Last path component of `repo[/subdir]`, used as the legacy symlink name. + pub fn legacy_name(&self) -> String { + self.name() + .file_name() + .expect("name has at least one component") + .to_owned() + } + + /// Git remote URL for cloning. + pub fn remote(&self) -> String { + let host = self.host.as_str(); + let repo = self.repo.as_str(); + match self.scheme { + GitScheme::Ssh => format!("ssh://git@{host}/{repo}"), + GitScheme::Https => format!("https://{host}/{repo}"), + } + } + + /// Parse a URI like `github.com/user/repo/subdir@version` into a + /// `Dependency`. + pub fn parse(uri: &str) -> Option { + git_uri::parse(uri).ok() + } +} + +peg::parser! { + grammar git_uri() for str { + rule host_segment() = ['a'..='z' | 'A'..='Z' | '0'..='9' | '-']+; + rule host() -> Hostname + = s:$(host_segment()++".") + { Hostname::from_str(s).expect("grammar restricted to valid host chars") } + + // User/repo path segments. `~` is allowed for Bitbucket personal repos. + rule path_segment() = ['a'..='z' | 'A'..='Z' | '0'..='9' | '_' | '-' | '~']+; + // Subdir segments allow dots (e.g. `ksonnet.beta.3`). + rule subdir_segment() = ['a'..='z' | 'A'..='Z' | '0'..='9' | '_' | '-' | '.']+; + + // `user[/group...]/repo.git` + rule repo_dotgit() -> SubDir + = s:$(path_segment()++"/" ".git") + { SubDir::from_str(s).expect("grammar restricted to subpath chars") } + // `user/repo` (exactly two segments, no `.git`) + rule repo_simple() -> SubDir + = s:$(path_segment() "/" path_segment()) + { SubDir::from_str(s).expect("grammar restricted to subpath chars") } + + // Subdir starts with `/`. May be empty. + rule subdir() -> SubDir + = "/" s:$(subdir_segment() ** "/") "/"? + { SubDir::from_str(s).expect("grammar restricted to subdir chars") } + / { SubDir::empty() } + + rule version() -> &'input str + = "@" v:$([_]+) { v } + + + // git@host:path.git[/subdir][@version] (SCP style) + rule scp_uri() -> Dependency + = "git@" h:host() ":" repo:repo_dotgit() subdir:subdir() + v:version()? + { + make_dep(GitScheme::Ssh, h, repo, subdir, v) + } + + // ssh://git@host/path.git[/subdir][@version] + rule ssh_uri() -> Dependency + = "ssh://git@" h:host() "/" repo:repo_dotgit() subdir:subdir() + v:version()? + { + make_dep(GitScheme::Ssh, h, repo, subdir, v) + } + + // [https://]host/path.git[/subdir][@version] + rule https_dotgit() -> Dependency + = "https://"? h:host() "/" repo:repo_dotgit() subdir:subdir() + v:version()? + { + make_dep(GitScheme::Https, h, repo, subdir, v) + } + + // [https://]host/user/repo[/subdir[/...]][@version] + rule https_simple() -> Dependency + = "https://"? h:host() "/" repo:repo_simple() subdir:subdir() + v:version()? + { + make_dep(GitScheme::Https, h, repo, subdir, v) + } + + pub rule parse() -> Dependency + = ssh_uri() / scp_uri() / https_dotgit() / https_simple() + } +} + +fn make_dep( + scheme: GitScheme, + host: Hostname, + repo: SubDir, + subdir: SubDir, + version: Option<&str>, +) -> Dependency { + Dependency { + source: Source::Git(GitSource { + scheme, + host, + repo, + subdir, + }), + version: version.map(str::to_owned), + sum: None, + name: None, + single: false, + } +} + +impl Dependency { + /// Canonical install path for deduplication and vendor extraction. + pub fn canonical_name(&self) -> Utf8PathBuf { + match &self.source { + Source::Git(git) => git.name().into_inner(), + Source::Local(local) => Utf8PathBuf::from(local.to_string()), + } + } + + /// Legacy symlink name: `dep.name` override, or last path component. + pub fn legacy_link_name(&self) -> String { + if let Some(name) = &self.name { + return name.clone(); + } + match &self.source { + Source::Git(git) => git.legacy_name(), + Source::Local(local) => local + .dir + .file_name() + .map_or_else(|| local.to_string(), str::to_owned), + } + } +} + +impl Serialize for GitSource { + fn serialize(&self, serializer: S) -> Result { + #[derive(Serialize)] + struct JsonGit<'a> { + remote: String, + #[serde(skip_serializing_if = "str::is_empty")] + subdir: &'a str, + } + JsonGit { + remote: self.remote(), + subdir: self.subdir.as_str(), + } + .serialize(serializer) + } +} + +impl<'de> Deserialize<'de> for GitSource { + fn deserialize>(deserializer: D) -> Result { + #[derive(Deserialize)] + struct JsonGit { + remote: String, + #[serde(default)] + subdir: String, + } + let j = JsonGit::deserialize(deserializer)?; + + let parsed = GitSource::parse(&j.remote) + .ok_or_else(|| de::Error::custom(format!("unable to parse git url {:?}", j.remote)))?; + let Source::Git(mut gs) = parsed.source else { + unreachable!() + }; + + if !j.subdir.is_empty() { + gs.subdir = SubDir::from_str(j.subdir.trim_start_matches('/')) + .map_err(|e| de::Error::custom(format!("invalid subdir {:?}: {e}", j.subdir)))?; + } + + Ok(gs) + } +} + +impl JsonnetFile { + pub fn load(path: &Path) -> Result { + let data = std::fs::read(path).map_err(|e| Error::Io(path.to_owned(), e))?; + serde_json::from_slice(&data).map_err(Error::Json) + } +} + +#[derive(Debug)] +pub enum Error { + Io(std::path::PathBuf, std::io::Error), + Json(serde_json::Error), +} +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Error::Io(path, e) => write!(f, "{}: {e}", path.display()), + Error::Json(e) => write!(f, "{e}"), + } + } +} +impl std::error::Error for Error {} + +#[cfg(test)] +mod tests { + use super::*; + + fn host(s: &str) -> Hostname { + Hostname::from_str(s).expect("test host") + } + fn sd(s: &str) -> SubDir { + SubDir::from_str(s).expect("test subdir") + } + + #[test] + fn parse_basic() { + let input = r#"{ + "version": 1, + "dependencies": [ + { + "source": { + "git": { + "remote": "https://github.com/grafana/jsonnet-libs.git", + "subdir": "grafana-builder" + } + }, + "version": "54865853ebc1f901964e25a2e7a0e4d2cb6b9648", + "sum": "ELsYwK+kGdzX1mee2Yy+/b2mdO4Y503BOCDkFzwmGbE=" + } + ], + "legacyImports": false + }"#; + + let jf: JsonnetFile = serde_json::from_str(input).unwrap(); + assert_eq!(jf.version, 1); + assert!(!jf.legacy_imports); + assert_eq!(jf.dependencies.len(), 1); + + let dep = &jf.dependencies[0]; + let Source::Git(git) = &dep.source else { + panic!("expected git source"); + }; + assert_eq!(git.host, "github.com"); + assert_eq!(git.repo, "grafana/jsonnet-libs.git"); + assert_eq!(git.subdir, "grafana-builder"); + assert_eq!( + git.name(), + "github.com/grafana/jsonnet-libs/grafana-builder" + ); + assert_eq!(git.legacy_name(), "grafana-builder"); + assert_eq!(git.remote(), "https://github.com/grafana/jsonnet-libs.git"); + assert_eq!( + dep.version.as_deref(), + Some("54865853ebc1f901964e25a2e7a0e4d2cb6b9648") + ); + } + + #[test] + fn parse_local_source() { + let input = r#"{ + "version": 1, + "dependencies": [ + { + "source": { + "local": { "directory": "../shared-lib" } + }, + "version": "" + } + ] + }"#; + + let jf: JsonnetFile = serde_json::from_str(input).unwrap(); + let dep = &jf.dependencies[0]; + let Source::Local(local) = &dep.source else { + panic!("expected local source"); + }; + assert_eq!(local.ups, 1); + assert_eq!(local.dir, "shared-lib"); + assert_eq!(local.to_string(), "../shared-lib"); + assert!(jf.legacy_imports); + } + + #[test] + fn parse_uri_github_slug() { + let dep = GitSource::parse("github.com/ksonnet/ksonnet-lib/ksonnet.beta.3").unwrap(); + let Source::Git(gs) = &dep.source else { + panic!() + }; + assert_eq!(gs.scheme, GitScheme::Https); + assert_eq!(gs.host, "github.com"); + assert_eq!(gs.repo, "ksonnet/ksonnet-lib"); + assert_eq!(gs.subdir, "ksonnet.beta.3"); + assert_eq!(dep.version, None); + assert_eq!(gs.remote(), "https://github.com/ksonnet/ksonnet-lib"); + } + + #[test] + fn parse_uri_ssh() { + let dep = GitSource::parse("ssh://git@example.com/user/repo.git/foobar@v1").unwrap(); + let Source::Git(gs) = &dep.source else { + panic!() + }; + assert_eq!(gs.scheme, GitScheme::Ssh); + assert_eq!(gs.host, "example.com"); + assert_eq!(gs.repo, "user/repo.git"); + assert_eq!(gs.subdir, "foobar"); + assert_eq!(dep.version.as_deref(), Some("v1")); + assert_eq!(gs.remote(), "ssh://git@example.com/user/repo.git"); + } + + #[test] + fn parse_uri_scp() { + let dep = GitSource::parse("git@my.host:user/repo.git/foobar@v1").unwrap(); + let Source::Git(gs) = &dep.source else { + panic!() + }; + assert_eq!(gs.scheme, GitScheme::Ssh); + assert_eq!(gs.host, "my.host"); + assert_eq!(gs.subdir, "foobar"); + assert_eq!(dep.version.as_deref(), Some("v1")); + assert_eq!(gs.remote(), "ssh://git@my.host/user/repo.git"); + } + + #[test] + fn parse_uri_https_explicit() { + let dep = GitSource::parse("https://example.com/foo/bar").unwrap(); + let Source::Git(gs) = &dep.source else { + panic!() + }; + assert_eq!(gs.scheme, GitScheme::Https); + assert_eq!(gs.host, "example.com"); + assert_eq!(gs.repo, "foo/bar"); + assert_eq!(gs.subdir, ""); + assert_eq!(gs.remote(), "https://example.com/foo/bar"); + } + + #[test] + fn parse_uri_no_scheme() { + let dep = GitSource::parse("example.com/foo/bar").unwrap(); + let Source::Git(gs) = &dep.source else { + panic!() + }; + assert_eq!(gs.scheme, GitScheme::Https); + assert_eq!(gs.host, "example.com"); + assert_eq!(gs.remote(), "https://example.com/foo/bar"); + } + + #[test] + fn parse_uri_path_and_version() { + let dep = GitSource::parse("example.com/foo/bar/baz@bat").unwrap(); + let Source::Git(gs) = &dep.source else { + panic!() + }; + assert_eq!(gs.repo, "foo/bar"); + assert_eq!(gs.subdir, "baz"); + assert_eq!(dep.version.as_deref(), Some("bat")); + } + + #[test] + fn parse_uri_version_only() { + let dep = GitSource::parse("example.com/foo/bar@baz").unwrap(); + let Source::Git(gs) = &dep.source else { + panic!() + }; + assert_eq!(gs.repo, "foo/bar"); + assert_eq!(gs.subdir, ""); + assert_eq!(dep.version.as_deref(), Some("baz")); + } + + #[test] + fn parse_uri_deep_path() { + let dep = GitSource::parse("example.com/foo/bar/baz/bat").unwrap(); + let Source::Git(gs) = &dep.source else { + panic!() + }; + assert_eq!(gs.repo, "foo/bar"); + assert_eq!(gs.subdir, "baz/bat"); + } + + #[test] + fn parse_uri_subgroups() { + let dep = GitSource::parse("example.com/group/subgroup/repository.git").unwrap(); + let Source::Git(gs) = &dep.source else { + panic!() + }; + assert_eq!(gs.repo, "group/subgroup/repository.git"); + assert_eq!(gs.plain_repo_name(), "group/subgroup/repository"); + assert_eq!(gs.subdir, ""); + assert_eq!( + gs.remote(), + "https://example.com/group/subgroup/repository.git" + ); + } + + #[test] + fn parse_uri_subgroup_subdir() { + let dep = GitSource::parse("example.com/group/subgroup/repository.git/subdir").unwrap(); + let Source::Git(gs) = &dep.source else { + panic!() + }; + assert_eq!(gs.plain_repo_name(), "group/subgroup/repository"); + assert_eq!(gs.subdir, "subdir"); + } + + #[test] + fn parse_uri_bitbucket_personal() { + let dep = GitSource::parse("bitbucket.org/~user/repository.git").unwrap(); + let Source::Git(gs) = &dep.source else { + panic!() + }; + assert_eq!(gs.host, "bitbucket.org"); + assert_eq!(gs.repo, "~user/repository.git"); + assert_eq!(gs.remote(), "https://bitbucket.org/~user/repository.git"); + } + + #[test] + fn name_with_subdir() { + let gs = GitSource { + scheme: GitScheme::Https, + host: host("github.com"), + repo: sd("ksonnet/ksonnet-lib"), + subdir: sd("ksonnet.beta.3"), + }; + assert_eq!(gs.name(), "github.com/ksonnet/ksonnet-lib/ksonnet.beta.3"); + assert_eq!(gs.legacy_name(), "ksonnet.beta.3"); + } + + #[test] + fn name_without_subdir() { + let gs = GitSource { + scheme: GitScheme::Https, + host: host("github.com"), + repo: sd("user/repo"), + subdir: SubDir::empty(), + }; + assert_eq!(gs.name(), "github.com/user/repo"); + assert_eq!(gs.legacy_name(), "repo"); + } + + #[test] + fn defaults() { + let input = r#"{ "version": 1 }"#; + let jf: JsonnetFile = serde_json::from_str(input).unwrap(); + assert!(jf.dependencies.is_empty()); + assert!(jf.legacy_imports); + } + + #[test] + fn roundtrip() { + let jf = JsonnetFile { + version: 1, + dependencies: vec![Dependency { + source: Source::Git(GitSource { + scheme: GitScheme::Https, + host: host("github.com"), + repo: sd("user/repo"), + subdir: sd("lib"), + }), + version: Some("main".into()), + sum: None, + name: None, + single: false, + }], + legacy_imports: false, + }; + let json = serde_json::to_string_pretty(&jf).unwrap(); + let parsed: JsonnetFile = serde_json::from_str(&json).unwrap(); + assert_eq!(parsed.dependencies.len(), 1); + let Source::Git(gs) = &parsed.dependencies[0].source else { + panic!() + }; + assert_eq!(gs.host, "github.com"); + assert_eq!(gs.repo, "user/repo"); + assert_eq!(gs.subdir, "lib"); + } + + #[test] + fn hostname_rejects_slash() { + assert!(Hostname::from_str("foo/bar").is_err()); + assert!(Hostname::from_str("").is_err()); + assert!(Hostname::from_str(".").is_err()); + assert!(Hostname::from_str("..").is_err()); + assert!(Hostname::from_str(".foo").is_err()); + assert!(Hostname::from_str("foo.").is_err()); + assert!(Hostname::from_str("foo..bar").is_err()); + assert!(Hostname::from_str("foo bar").is_err()); + assert!(Hostname::from_str("foo.bar").is_ok()); + } + + #[test] + fn subdir_rejects_escape() { + assert!(SubDir::from_str("../foo").is_err()); + assert!(SubDir::from_str("/foo").is_err()); + assert!(SubDir::from_str("foo/../bar").is_err()); + assert!(SubDir::from_str("foo/bar").is_ok()); + assert!(SubDir::from_str("").is_ok()); + } + + #[test] + fn local_source_parse() { + let l = LocalSource::from_str("../shared-lib").unwrap(); + assert_eq!(l.ups, 1); + assert_eq!(l.dir, "shared-lib"); + + let l = LocalSource::from_str("../../foo/bar").unwrap(); + assert_eq!(l.ups, 2); + assert_eq!(l.dir, "foo/bar"); + + let l = LocalSource::from_str("./foo").unwrap(); + assert_eq!(l.ups, 0); + assert_eq!(l.dir, "foo"); + + let l = LocalSource::from_str(".").unwrap(); + assert_eq!(l.ups, 0); + assert!(l.dir.is_empty()); + + let l = LocalSource::from_str("..").unwrap(); + assert_eq!(l.ups, 1); + assert!(l.dir.is_empty()); + + // Mid-path `..` is rejected. + assert!(LocalSource::from_str("foo/../bar").is_err()); + // Absolute path is rejected. + assert!(LocalSource::from_str("/foo").is_err()); + } + + #[test] + fn local_source_render_roundtrip() { + for s in ["../shared-lib", "../../foo/bar", "foo", "."] { + assert_eq!(LocalSource::from_str(s).unwrap().to_string(), s); + } + } + + #[test] + fn local_source_resolve_under() { + // `../foo` from `pkg/sub` lands at `pkg/foo`. + let l = LocalSource::from_str("../foo").unwrap(); + assert_eq!(l.resolve_under(&sd("pkg/sub")).unwrap(), "pkg/foo"); + + // Plain `foo` from `pkg/sub` lands at `pkg/sub/foo`. + let l = LocalSource::from_str("foo").unwrap(); + assert_eq!(l.resolve_under(&sd("pkg/sub")).unwrap(), "pkg/sub/foo"); + + // Too many `..` escapes the parent. + let l = LocalSource::from_str("../../../foo").unwrap(); + assert!(l.resolve_under(&sd("pkg")).is_err()); + } +} --- /dev/null +++ b/crates/jrsonnet-pkg/src/lib.rs @@ -0,0 +1,2 @@ +pub mod install; +pub mod jsonnet_bundler;