difftreelog
feat jrb
in: master
11 files changed
Cargo.lockdiffbeforeafterboth255 packageslockfile v4
Might be heavy and slow!
ahash
0.8.12crates.io↘ 5↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75used byaho-corasick
1.1.4crates.io↘ 1↖ 3sourceregistry+https://github.com/rust-lang/crates.io-indexchecksumddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301depends onaliasable
0.1.3crates.io↘ 0↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fdused byalloca
0.4.0crates.io↘ 1↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksume5a7d05ea6aea7e9e64d25b9156ba2fee3fdd659e34e41063cd2fc7cd020d7f4depends onused byallocator-api2
0.2.21crates.io↘ 0↖ 3sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923anes
0.1.6crates.io↘ 0↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299used byannotate-snippets
0.12.15crates.io↘ 3↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum92570a3f9c98e7e84df84b71d0965ac99b1871fcd75a3773a3bd1bad13f64cf7used byannotated-string
0.3.0crates.io↘ 3↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum298ed730801db3c02f2edba003c9420a0f57ea48d37fdc5601c536113668c059used byanstream
1.0.0crates.io↘ 7↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28ddepends onused byanstyle
1.0.14crates.io↘ 0↖ 4sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000anstyle-parse
1.0.0crates.io↘ 1↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130edepends onused byanstyle-query
1.1.5crates.io↘ 1↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadcdepends onused byanstyle-wincon
3.0.11crates.io↘ 3↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747dused byanyhow
1.0.102crates.io↘ 0↖ 10sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842car_archive_writer
0.5.1crates.io↘ 1↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum7eb93bbb63b9c227414f6eb3a0adfddca591a8ce1e9b60661bb08969b87e340bdepends onused byarraydeque
0.5.1crates.io↘ 0↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum7d902e3d592a523def97af8f317b08ce16b7ab854c1985a0c671e6f15cebc236used byautocfg
1.5.0crates.io↘ 0↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksumc08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8used bybase64
0.22.1crates.io↘ 0↖ 2sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6bitflags
2.11.1crates.io↘ 0↖ 4sourceregistry+https://github.com/rust-lang/crates.io-indexchecksumc4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3block-buffer
0.12.0crates.io↘ 1↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksumcdd35008169921d80bc60d3d0ab416eecb028c4cd653352907921d95084790bedepends onused bybstr
1.12.1crates.io↘ 2↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cabdepends onused bybumpalo
3.20.2crates.io↘ 1↖ 2sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcbdepends oncast
0.3.0crates.io↘ 0↖ 2sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5cc
1.2.61crates.io↘ 2↖ 4sourceregistry+https://github.com/rust-lang/crates.io-indexchecksumd16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6ddepends oncfg_aliases
0.2.1crates.io↘ 0↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724used bycfg-if
1.0.4crates.io↘ 0↖ 13sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801ciborium
0.2.2crates.io↘ 3↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0eused byciborium-io
0.2.2crates.io↘ 0↖ 2sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757ciborium-ll
0.2.2crates.io↘ 2↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9depends onused byclap
4.6.1crates.io↘ 2↖ 7sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51depends onclap_builder
4.6.0crates.io↘ 4↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069fused byclap_complete
4.6.3crates.io↘ 1↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum660c0520455b1013b9bcb0393d5f643d7e4454fb69c915b8d6d2aa0e9a45acc3depends onused byclap_derive
4.6.1crates.io↘ 4↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksumf2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9used byclap_lex
1.1.0crates.io↘ 0↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksumc8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9used bycolorchoice
1.0.5crates.io↘ 0↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570used byconsole
0.16.3crates.io↘ 3↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksumd64e8af5551369d19cf50138de61f1c42074ab970f74e99be916646777f8fc87used byconsole_error_panic_hook
0.1.7crates.io↘ 2↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksuma06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbcdepends onused byconst-oid
0.10.2crates.io↘ 0↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksuma6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55cused bycountme
3.0.1crates.io↘ 0↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum7704b5fdd17b18ae31c4c1da5a2e0305a2bf17b5249300a9ee9ed7b72114c636used bycpufeatures
0.3.0crates.io↘ 1↖ 3sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201depends oncriterion
0.8.2crates.io↘ 17↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum950046b2aa2492f9a536f5f4f9a3de7b9e2476e575e05bd6c333371add4d98f3depends onused bycriterion-plot
0.8.2crates.io↘ 2↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksumd8d80a2f4f5b554395e47b5d8305bc3d27813bacb73493eb1001e8f76dae29eadepends onused bycrossbeam-deque
0.8.6crates.io↘ 2↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51used bycrossbeam-epoch
0.9.18crates.io↘ 1↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420edepends onused bycrossbeam-utils
0.8.21crates.io↘ 0↖ 3sourceregistry+https://github.com/rust-lang/crates.io-indexchecksumd0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28crypto-common
0.2.1crates.io↘ 1↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum77727bb15fa921304124b128af125e7e3b968275d1b108b379190264f4423710depends onused bydigest
0.11.3crates.io↘ 3↖ 3sourceregistry+https://github.com/rust-lang/crates.io-indexchecksumf1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2displaydoc
0.2.5crates.io↘ 3↖ 5sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0depends ondprint-core
0.67.4crates.io↘ 7↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum2c1d827947704a9495f705d6aeed270fa21a67f825f22902c28f38dc3af7a9aedepends ondrop_bomb
0.1.5crates.io↘ 0↖ 2sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum9bda8e21c04aca2ae33ffc2fd8c23134f3cac46db123ba97bd9d3f3b8a4a85e1educe
0.6.0crates.io↘ 4↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum1d7bc049e1bd8cdeb31b68bbd586a9464ecf9f3944af3958a7a9d0f8b9799417either
1.15.0crates.io↘ 0↖ 3sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719encode_unicode
1.0.0crates.io↘ 0↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0used byencoding_rs
0.8.35crates.io↘ 1↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3depends onused byencoding_rs_io
0.1.7crates.io↘ 1↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum1cc3c5651fb62ab8aa3103998dade57efdd028544bd300516baa31840c252a83depends onused byenum-ordinalize
4.3.2crates.io↘ 1↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum4a1091a7bb1f8f2c4b28f1fe2cef4980ca2d410a3d727d67ecc3178c9b0800f0depends onused byenum-ordinalize-derive
4.3.2crates.io↘ 3↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum8ca9601fb2d62598ee17836250842873a413586e5d7ed88b356e38ddbb0ec631depends onused byequivalent
1.0.2crates.io↘ 0↖ 3sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0ferrno
0.3.14crates.io↘ 2↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efebdepends onused byextension-trait
1.0.2crates.io↘ 3↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksumdd65f1b59dd22d680c7a626cc4a000c1e03d241c51c3e034d2bc9f1e90734f9bdepends onused byfastrand
2.4.1crates.io↘ 0↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6used byfind-msvc-tools
0.1.9crates.io↘ 0↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582used byfnv
1.0.7crates.io↘ 0↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1used byfoldhash
0.1.5crates.io↘ 0↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksumd9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2used byfoldhash
0.2.0crates.io↘ 0↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdbused byform_urlencoded
1.2.2crates.io↘ 1↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksumcb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcfdepends onused bygetrandom
0.3.4crates.io↘ 6↖ 4sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fdgetrandom
0.4.2crates.io↘ 5↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555depends onused byglobset
0.4.18crates.io↘ 5↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum52dfc19153a48bde0cbd630453615c8151bce3a5adfac7a0aebfbf0a1e1f57e3used bygranit-parser
0.0.2crates.io↘ 2↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksumb7e736dfe3881c53a7dce0685eb18202d0d9fe6911782f9870946eb9ee89d778depends onused byhalf
2.7.1crates.io↘ 3↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84bdepends onused byhashbrown
0.14.5crates.io↘ 0↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksume5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1used byhashbrown
0.15.5crates.io↘ 3↖ 2sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1hashbrown
0.17.0crates.io↘ 3↖ 3sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51heck
0.4.1crates.io↘ 0↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8used byheck
0.5.0crates.io↘ 0↖ 3sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55eahex
0.4.3crates.io↘ 0↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70used byhi-doc
0.3.0crates.io↘ 9↖ 6sourceregistry+https://github.com/rust-lang/crates.io-indexchecksumf70fb920ba34768415fb239d7d607486083bfc38ad35e3f1d558697f9f646f11depends onhi-doc-jumprope
1.2.1crates.io↘ 2↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum236c25809a9c0a0249b3488feb57744e12aa64e4f3db851980eab303719c7bdddepends onused byhybrid-array
0.4.11crates.io↘ 1↖ 2sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum08d46837a0ed51fe95bd3b05de33cd64a1ee88fc797477ca48446872504507c5depends onicu_collections
2.2.0crates.io↘ 6↖ 2sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191cicu_locale_core
2.2.0crates.io↘ 5↖ 2sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29icu_normalizer
2.2.0crates.io↘ 6↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksumc56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4depends onused byicu_normalizer_data
2.2.0crates.io↘ 0↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksumda3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38used byicu_properties
2.2.0crates.io↘ 6↖ 2sourceregistry+https://github.com/rust-lang/crates.io-indexchecksumbee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70dedepends onicu_properties_data
2.2.0crates.io↘ 0↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14used byicu_provider
2.2.0crates.io↘ 7↖ 2sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421depends onid-arena
2.3.0crates.io↘ 0↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954used byidna
1.1.0crates.io↘ 3↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4deused byidna_adapter
1.2.2crates.io↘ 2↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksumcb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714depends onused byindexmap
2.14.0crates.io↘ 4↖ 7sourceregistry+https://github.com/rust-lang/crates.io-indexchecksumd466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9indoc
2.0.7crates.io↘ 1↖ 3sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706depends oninsta
1.47.2crates.io↘ 6↖ 7sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum7b4a6248eb93a4401ed2f37dfe8ea592d3cf05b7cf4f8efa867b6895af7e094eis_terminal_polyfill
1.70.2crates.io↘ 0↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksuma6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695used byitertools
0.13.0crates.io↘ 1↖ 2sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186depends onitertools
0.14.0crates.io↘ 1↖ 3sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285depends onitoa
1.0.18crates.io↘ 0↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682used byjrsonnet-cli
0.5.0-pre98workspace↘ 5↖ 2jrsonnet-deps
0.5.0-pre98workspace↘ 5↖ 0jrsonnet-evaluator
0.5.0-pre98workspace↘ 24↖ 7depends on- anyhow
1.0.102 - drop_bomb
0.1.5 - educe
0.6.0 - hi-doc
0.3.0 - insta
1.47.2 - jrsonnet-gcmodule
0.5.0 - jrsonnet-interner
0.5.0-pre98 - jrsonnet-ir
0.5.0-pre98 - jrsonnet-ir-parser
0.5.0-pre98 - jrsonnet-macros
0.5.0-pre98 - jrsonnet-peg-parser
0.5.0-pre98 - jrsonnet-types
0.5.0-pre98 - num-bigint
0.4.6 - pathdiff
0.2.3 - rustc-hash
2.1.2 - rustversion
1.0.22 - serde
1.0.228 - smallvec
1.15.1 - stacker
0.1.24 - static_assertions
1.1.0 - strip-ansi-escapes
0.2.1 - strsim
0.11.1 - thiserror
2.0.18 - wasm-bindgen
0.2.106
- anyhow
jrsonnet-fmt
0.5.0-pre98workspace↘ 6↖ 0jrsonnet-formatter
0.5.0-pre98workspace↘ 6↖ 2jrsonnet-gcmodule
0.5.0crates.io↘ 1↖ 12sourceregistry+https://github.com/rust-lang/crates.io-indexchecksumf65a6fdef5568ea2e3459dcad15979d7613f968cbacf722e1138ffe83d28ee24depends onjrsonnet-gcmodule-derive
0.5.0crates.io↘ 3↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum829a23aa96f1afb78bbc9722e323ced7a0b75d9b89cec6bfbc709cf484e8a0a4depends onused byjrsonnet-interner
0.5.0-pre98workspace↘ 3↖ 3jrsonnet-ir
0.5.0-pre98workspace↘ 7↖ 7depends onjrsonnet-ir-parser
0.5.0-pre98workspace↘ 4↖ 2jrsonnet-lexer
0.5.0-pre98workspace↘ 1↖ 3jrsonnet-macros
0.5.0-pre98workspace↘ 4↖ 2jrsonnet-peg-parser
0.5.0-pre98workspace↘ 4↖ 1jrsonnet-rowan-parser
0.5.0-pre98workspace↘ 9↖ 1jrsonnet-stdlib
0.5.0-pre98workspace↘ 16↖ 4depends onjrsonnet-types
0.5.0-pre98workspace↘ 2↖ 2jrsonnet-web
0.5.0-pre98workspace↘ 13↖ 0js-sys
0.3.83crates.io↘ 2↖ 4sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8depends onkeccak
0.2.0crates.io↘ 2↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum9e24a010dd405bd7ed803e5253182815b41bf2e6a80cc3bfc066658e03a198aadepends onused byleb128fmt
0.1.0crates.io↘ 0↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2used bylibc
0.2.186crates.io↘ 0↖ 10sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66libjsonnet
0.5.0-pre98workspace↘ 5↖ 0linux-raw-sys
0.12.1crates.io↘ 0↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53used bylitemap
0.8.2crates.io↘ 0↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0used bylog
0.4.29crates.io↘ 0↖ 3sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897logos
0.16.1crates.io↘ 1↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksumeb2c55a318a87600ea870ff8c2012148b44bf18b74fad48d0f835c38c7d07c5fdepends onused bylogos-codegen
0.16.1crates.io↘ 6↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum58b3ffaa284e1350d017a57d04ada118c4583cf260c8fb01e0fe28a2e9cf8970used bylogos-derive
0.16.1crates.io↘ 1↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum52d3a9855747c17eaf4383823f135220716ab49bea5fbea7dd42cc9a92f8aa31depends onused bylru
0.18.0crates.io↘ 1↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum8a860605968fce16869fd239cf4237a82f3ac470723415db603b0e8b6c8d4fb9depends onused bymd5
0.8.0crates.io↘ 0↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksumae960838283323069879657ca3de837e9f7bbb4c7bf6ea7f1b290d5e9476d2e0used bymemchr
2.8.0crates.io↘ 0↖ 8sourceregistry+https://github.com/rust-lang/crates.io-indexchecksumf8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79mimalloc-sys
0.1.6crates.io↘ 2↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum4aa3cefb626f6ae3d0b2f71c5378c89d2b1d4d7bc246b0ca9a7ee61a4daad291depends onused bymimallocator
0.1.3crates.io↘ 1↖ 2sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum2d44fe4ebf6b538fcf39d9975c2b90bb3232d1ba8e8bffeacd004f27b20c577adepends onnix
0.31.2crates.io↘ 4↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum5d6d0705320c1e6ba1d912b5e37cf18071b6c2e9b7fa8215a1e8a7651966f5d3used bynum-bigint
0.4.6crates.io↘ 3↖ 2sourceregistry+https://github.com/rust-lang/crates.io-indexchecksuma5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9num-integer
0.1.46crates.io↘ 1↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858fdepends onused bynum-traits
0.2.19crates.io↘ 1↖ 7sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841depends onobject
0.37.3crates.io↘ 1↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksumff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fedepends onused byonce_cell
1.21.4crates.io↘ 0↖ 6sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50once_cell_polyfill
1.70.2crates.io↘ 0↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4feused byoorandom
11.1.5crates.io↘ 0↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksumd6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41eused byouroboros
0.18.5crates.io↘ 3↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum1e0f050db9c44b97a94723127e6be766ac5c340c48f2c4bb3ffa11713744be59used byouroboros_macro
0.18.5crates.io↘ 5↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum3c7028bdd3d43083f6d8d4d5187680d0d3560d54df4cc9d752005268b41e64d0used bypage_size
0.6.0crates.io↘ 2↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum30d5b2194ed13191c1999ae0704b7839fb18384fa22e49b57eeaa97d79ce40dadepends onused bypathdiff
0.2.3crates.io↘ 0↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksumdf94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3peg
0.8.6crates.io↘ 2↖ 3sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum0aad070be5b63aa72103f2fcdd70a83adbd5e90112ce5b574171ff1c65501773depends onpeg-macros
0.8.6crates.io↘ 3↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksumddd8ef6825cae95355031ae26a99b616a2a21f22ba2de0197c43dfb05acbe7eeused bypeg-runtime
0.8.6crates.io↘ 0↖ 2sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum7011d97b484a5ebdc4b1fdb3b12d5e4bbbea56e9d22b688f2e79e04b65a7d8a6used bypercent-encoding
2.3.2crates.io↘ 0↖ 2sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220used byplotters
0.3.7crates.io↘ 5↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747used byplotters-backend
0.3.7crates.io↘ 0↖ 2sourceregistry+https://github.com/rust-lang/crates.io-indexchecksumdf42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172aplotters-svg
0.3.7crates.io↘ 1↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670depends onused bypotential_utf
0.1.5crates.io↘ 1↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564depends onused byppv-lite86
0.2.21crates.io↘ 1↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9depends onused byprettyplease
0.2.37crates.io↘ 2↖ 2sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62bdepends onproc-macro2
1.0.106crates.io↘ 1↖ 25sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934depends onused by- clap_derive
4.6.1 - displaydoc
0.2.5 - educe
0.6.0 - enum-ordinalize-derive
4.3.2 - extension-trait
1.0.2 - jrsonnet-gcmodule-derive
0.5.0 - jrsonnet-macros
0.5.0-pre98 - logos-codegen
0.16.1 - ouroboros_macro
0.18.5 - peg-macros
0.8.6 - prettyplease
0.2.37 - proc-macro2-diagnostics
0.10.1 - quote
1.0.45 - serde_derive
1.0.228 - syn
2.0.117 - syn-dissect-closure
0.1.0 - synstructure
0.13.2 - thiserror-impl
2.0.18 - wasm-bindgen-macro-support
0.2.106 - wit-bindgen-rust-macro
0.51.0 - xtask
0.1.0 - yoke-derive
0.8.2 - zerocopy-derive
0.8.48 - zerofrom-derive
0.1.7 - zerovec-derive
0.11.3
- clap_derive
proc-macro2-diagnostics
0.10.1crates.io↘ 5↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksumaf066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8used bypsm
0.1.31crates.io↘ 2↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum645dbe486e346d9b5de3ef16ede18c26e6c70ad97418f4874b8b1889d6e761eadepends onused byquote
1.0.45crates.io↘ 1↖ 24sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924depends onused by- clap_derive
4.6.1 - displaydoc
0.2.5 - educe
0.6.0 - enum-ordinalize-derive
4.3.2 - extension-trait
1.0.2 - jrsonnet-gcmodule-derive
0.5.0 - jrsonnet-macros
0.5.0-pre98 - logos-codegen
0.16.1 - ouroboros_macro
0.18.5 - peg-macros
0.8.6 - proc-macro2-diagnostics
0.10.1 - serde_derive
1.0.228 - syn
2.0.117 - syn-dissect-closure
0.1.0 - synstructure
0.13.2 - thiserror-impl
2.0.18 - wasm-bindgen-macro
0.2.106 - wasm-bindgen-macro-support
0.2.106 - wit-bindgen-rust-macro
0.51.0 - xtask
0.1.0 - yoke-derive
0.8.2 - zerocopy-derive
0.8.48 - zerofrom-derive
0.1.7 - zerovec-derive
0.11.3
- clap_derive
r-efi
5.3.0crates.io↘ 0↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0fused byr-efi
6.0.0crates.io↘ 0↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksumf8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bfused byrand
0.9.4crates.io↘ 2↖ 3sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76eadepends onrand_chacha
0.9.0crates.io↘ 2↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksumd3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cbdepends onused byrand_core
0.9.5crates.io↘ 1↖ 2sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83cdepends onused byrandom_color
1.1.0crates.io↘ 1↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksumd635c5e80ae160390ac62ca027d2d06c94c1dc69e5c0a12f1e3a53664dc84966depends onused byrange-map
0.2.0crates.io↘ 1↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum12a5a2d6c7039059af621472a4389be1215a816df61aa4d531cfe85264aee95fdepends onused byrayon
1.12.0crates.io↘ 2↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksumfb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926ddepends onused byrayon-core
1.13.0crates.io↘ 2↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91used byregex
1.12.3crates.io↘ 4↖ 2sourceregistry+https://github.com/rust-lang/crates.io-indexchecksume10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276regex-automata
0.4.14crates.io↘ 3↖ 3sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8fregex-syntax
0.8.10crates.io↘ 0↖ 4sourceregistry+https://github.com/rust-lang/crates.io-indexchecksumdc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0arowan
0.16.1crates.io↘ 4↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum417a3a9f582e349834051b8a10c8d71ca88da4211e4093528e36b9845f6b5f21rustc-hash
1.1.0crates.io↘ 0↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2used byrustc-hash
2.1.2crates.io↘ 0↖ 5sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dberustix
1.1.4crates.io↘ 5↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksumb6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190used byrustversion
1.0.22crates.io↘ 0↖ 3sourceregistry+https://github.com/rust-lang/crates.io-indexchecksumb39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46dsame-file
1.0.6crates.io↘ 1↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502depends onused bysemver
1.0.28crates.io↘ 0↖ 2sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cdserde
1.0.228crates.io↘ 2↖ 16sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9edepends onserde_core
1.0.228crates.io↘ 1↖ 3sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67addepends onserde_derive
1.0.228crates.io↘ 3↖ 4sourceregistry+https://github.com/rust-lang/crates.io-indexchecksumd540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79depends onserde_json
1.0.149crates.io↘ 5↖ 7sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86serde-saphyr
0.0.26crates.io↘ 9↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksumdcc7fe48e34d02a97bc8e6253b8b91e5a47fe2c47eaacb5149cefbb69922eaf0depends onused bysha1
0.11.0crates.io↘ 3↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksumaacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214depends onused bysha2
0.11.0crates.io↘ 3↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4depends onused bysha3
0.11.0crates.io↘ 2↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksumbe176f1a57ce4e3d31c1a166222d9768de5954f811601fb7ca06fc8203905ce1depends onused bysimilar
2.7.0crates.io↘ 0↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksumbbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daaused bysmallvec
1.15.1crates.io↘ 0↖ 6sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03stable_deref_trait
1.2.1crates.io↘ 0↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596used bystacker
0.1.24crates.io↘ 5↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum640c8cdd92b6b12f5bcb1803ca3bbf5ab96e5e6b6b96b9ab77dabe9e880b3190static_assertions
1.1.0crates.io↘ 0↖ 3sourceregistry+https://github.com/rust-lang/crates.io-indexchecksuma2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543fstr_indices
0.4.4crates.io↘ 0↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksumd08889ec5408683408db66ad89e0e1f93dff55c73a4ccc71c427d5b277ee47e6used bystrip-ansi-escapes
0.2.1crates.io↘ 1↖ 2sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum2a8f8038e7e7969abb3f1b7c2a811225e9296da208539e0f79c5251d6cac0025depends onstrsim
0.11.1crates.io↘ 0↖ 2sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4fsyn
2.0.117crates.io↘ 3↖ 22sourceregistry+https://github.com/rust-lang/crates.io-indexchecksume665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99used by- clap_derive
4.6.1 - displaydoc
0.2.5 - educe
0.6.0 - enum-ordinalize-derive
4.3.2 - extension-trait
1.0.2 - jrsonnet-gcmodule-derive
0.5.0 - jrsonnet-macros
0.5.0-pre98 - logos-codegen
0.16.1 - ouroboros_macro
0.18.5 - prettyplease
0.2.37 - proc-macro2-diagnostics
0.10.1 - serde_derive
1.0.228 - syn-dissect-closure
0.1.0 - synstructure
0.13.2 - thiserror-impl
2.0.18 - wasm-bindgen-macro-support
0.2.106 - wit-bindgen-rust
0.51.0 - wit-bindgen-rust-macro
0.51.0 - yoke-derive
0.8.2 - zerocopy-derive
0.8.48 - zerofrom-derive
0.1.7 - zerovec-derive
0.11.3
- clap_derive
syn-dissect-closure
0.1.0crates.io↘ 3↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum343bae741672e4b94421cbe93f9794ba9a061434272f7e3a29ff43be26be3ac9depends onused bysynstructure
0.13.2crates.io↘ 3↖ 2sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2depends ontempfile
3.27.0crates.io↘ 5↖ 2sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bdtext-size
1.1.1crates.io↘ 0↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksumf18aa187839b2bdb1ad2fa35ead8c4c2976b64e4363c386d45ac0f7ee85c9233used bythiserror
2.0.18crates.io↘ 1↖ 5sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4depends onthiserror-impl
2.0.18crates.io↘ 3↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksumebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5depends onused bytinystr
0.8.3crates.io↘ 2↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksumc8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96ddepends onused bytinytemplate
1.2.1crates.io↘ 2↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksumbe4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bcdepends onused bytypenum
1.20.0crates.io↘ 0↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43deused byungrammar
1.16.1crates.io↘ 0↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksuma3e5df347f0bf3ec1d670aad6ca5c6a1859cd9ea61d2113125794654ccced68fused byunicode-box-drawing
0.3.0crates.io↘ 0↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum2a1f97719cf40224391201fc11e7f5b0cc0ba21416367cfc914e2d45af4e42efused byunicode-ident
1.0.24crates.io↘ 0↖ 3sourceregistry+https://github.com/rust-lang/crates.io-indexchecksume6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75unicode-width
0.2.2crates.io↘ 0↖ 2sourceregistry+https://github.com/rust-lang/crates.io-indexchecksumb4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254unicode-xid
0.2.6crates.io↘ 0↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksumebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853used byurl
2.5.8crates.io↘ 4↖ 2sourceregistry+https://github.com/rust-lang/crates.io-indexchecksumff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eedutf8_iter
1.0.4crates.io↘ 0↖ 2sourceregistry+https://github.com/rust-lang/crates.io-indexchecksumb6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6beutf8parse
0.2.2crates.io↘ 0↖ 2sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821version_check
0.9.5crates.io↘ 0↖ 2sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105avte
0.14.1crates.io↘ 1↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum231fdcd7ef3037e8330d8e17e61011a2c244126acc0a982f4040ac3f9f0bc077depends onused bywalkdir
2.5.0crates.io↘ 2↖ 2sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4bdepends onused bywasip2
1.0.3+wasi-0.2.9crates.io↘ 1↖ 2sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6depends onused bywasip3
0.4.0+wasi-0.3.0-rc-2026-01-06crates.io↘ 1↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5depends onused bywasm-bindgen
0.2.106crates.io↘ 5↖ 8sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fddepends onwasm-bindgen-futures
0.4.56crates.io↘ 5↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum836d9622d604feee9e5de25ac10e3ea5f2d65b41eac0d9ce72eb5deae707ce7cused bywasm-bindgen-macro
0.2.106crates.io↘ 2↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3used bywasm-bindgen-macro-support
0.2.106crates.io↘ 5↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksumcefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40used bywasm-encoder
0.244.0crates.io↘ 2↖ 2sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319depends onwasm-metadata
0.244.0crates.io↘ 4↖ 2sourceregistry+https://github.com/rust-lang/crates.io-indexchecksumbb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909wasmparser
0.244.0crates.io↘ 4↖ 4sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028feweb-sys
0.3.83crates.io↘ 2↖ 2sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum9b32828d774c412041098d182a8b38b16ea816958e07cf40eec2bc080ae137acdepends onwinapi
0.3.9crates.io↘ 2↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419used bywinapi-i686-pc-windows-gnu
0.4.0crates.io↘ 0↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksumac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6used bywinapi-util
0.1.11crates.io↘ 1↖ 2sourceregistry+https://github.com/rust-lang/crates.io-indexchecksumc2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22depends onused bywinapi-x86_64-pc-windows-gnu
0.4.0crates.io↘ 0↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183fused bywindows-link
0.2.1crates.io↘ 0↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksumf0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5used bywindows-sys
0.61.2crates.io↘ 1↖ 8sourceregistry+https://github.com/rust-lang/crates.io-indexchecksumae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fcdepends onwit-bindgen
0.51.0crates.io↘ 1↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksumd7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5depends onwit-bindgen
0.57.1crates.io↘ 0↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536eused bywit-bindgen-core
0.51.0crates.io↘ 3↖ 2sourceregistry+https://github.com/rust-lang/crates.io-indexchecksumea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dcdepends onwit-bindgen-rust
0.51.0crates.io↘ 8↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksumb7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21depends onused bywit-bindgen-rust-macro
0.51.0crates.io↘ 7↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17adepends onused bywit-component
0.244.0crates.io↘ 11↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2depends onused bywit-parser
0.244.0crates.io↘ 10↖ 2sourceregistry+https://github.com/rust-lang/crates.io-indexchecksumecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736depends onwriteable
0.6.3crates.io↘ 0↖ 2sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4xshell
0.2.7crates.io↘ 1↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum9e7290c623014758632efe00737145b6867b66292c42167f2ec381eb566a373ddepends onused byxshell-macros
0.2.7crates.io↘ 0↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum32ac00cd3f8ec9c1d33fb3e7958a82df6989c42d747bd326c822b1d625283547used byxtask
0.1.0workspace↘ 9↖ 0yansi
1.0.1crates.io↘ 0↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksumcfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049yoke
0.8.2crates.io↘ 3↖ 4sourceregistry+https://github.com/rust-lang/crates.io-indexchecksumabe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9cayoke-derive
0.8.2crates.io↘ 4↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksumde844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858eused byzerocopy
0.8.48crates.io↘ 1↖ 3sourceregistry+https://github.com/rust-lang/crates.io-indexchecksumeed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9depends onzerocopy-derive
0.8.48crates.io↘ 3↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4depends onused byzerofrom
0.1.7crates.io↘ 1↖ 5sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4dfdepends onzerofrom-derive
0.1.7crates.io↘ 4↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1used byzerotrie
0.2.4crates.io↘ 3↖ 2sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bfdepends onzerovec
0.11.6crates.io↘ 3↖ 7sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239zerovec-derive
0.11.3crates.io↘ 3↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksum625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555depends onused byzmij
1.0.21crates.io↘ 0↖ 1sourceregistry+https://github.com/rust-lang/crates.io-indexchecksumb8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaaused by
Cargo.tomldiffbeforeafterboth--- 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"
cmds/jrb/Cargo.tomldiffbeforeafterboth--- /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
cmds/jrb/src/main.rsdiffbeforeafterboth--- /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<String>,
+ /// 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<String>,
+ /// 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<String>,
+ /// 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<JsonnetFile> {
+ 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::<Vec<_>>();
+
+ 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);
+ }
+ }
+ }
+}
crates/jrsonnet-pkg/Cargo.tomldiffbeforeafterboth--- /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
crates/jrsonnet-pkg/src/install/accessor.rsdiffbeforeafterboth--- /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<T, E = Error> = result::Result<T, E>;
+
+pub trait SourceAccessor {}
+
+pub struct ZipFileAccessor {
+ archive: Mutex<ZipArchive<File>>,
+ // Github archives have top-level directory with repo name
+ prefix: SubDir,
+}
+
+impl ZipFileAccessor {
+ pub fn new_prefixed(file: File) -> Result<Self> {
+ 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 `<repo>-<sha>/` prefix).
+ #[allow(clippy::significant_drop_tightening, reason = "false-positive")]
+ pub fn read(&self, name: &SubDir) -> Result<Option<Vec<u8>>> {
+ 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<E>(
+ &self,
+ subdir: &SubDir,
+ cb: &mut dyn FnMut(SubDir, AccessorEntry) -> Result<(), E>,
+ ) -> Result<(), E>
+ where
+ E: From<Error>,
+ {
+ 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 `<repo>-<sha>/` 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<u8>),
+}
crates/jrsonnet-pkg/src/install/git.rsdiffbeforeafterboth--- /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<std::path::PathBuf> {
+ Ok(cache_dir("git")?.join(&remote.host).join(&remote.repo))
+}
+
+fn ensure_repo(remote: &GitSource) -> Result<gix::Repository> {
+ 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<gix::Id<'r>> {
+ 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<Vec<u8>> {
+ 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<Dependency>,
+ local_extractions: &mut Vec<LocalExtraction>,
+ visited: &mut HashSet<SubDir>,
+) {
+ 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::<JsonnetFile>(&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<ResolveResult, Error> {
+ 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)
+}
crates/jrsonnet-pkg/src/install/github.rsdiffbeforeafterboth--- /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<PathBuf> {
+ Ok(cache_dir("github")?
+ .join(source.plain_repo_name())
+ .join(format!("{sha}.zip")))
+}
+
+fn resolve_sha(source: &GitSource, version: &str) -> Result<String> {
+ 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<ZipFileAccessor> {
+ 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<ZipFileAccessor> {
+ 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<Dependency>,
+ local_extractions: &mut Vec<LocalExtraction>,
+ visited: &mut HashSet<SubDir>,
+) -> 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::<JsonnetFile>(&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<ResolveResult> {
+ 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)
+}
crates/jrsonnet-pkg/src/install/mod.rsdiffbeforeafterboth--- /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<std::path::PathBuf> {
+ 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<Dependency>,
+ pub local_extractions: Vec<LocalExtraction>,
+ 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<Utf8PathBuf, VendorSource>,
+}
+
+pub fn install(
+ manifest: &JsonnetFile,
+ lock: Option<&JsonnetFile>,
+ vendor_dir: &Path,
+ dry_run: bool,
+) -> Result<JsonnetFile, Error> {
+ let plan = resolve(manifest, lock)?;
+ execute(&plan, vendor_dir, dry_run)?;
+ Ok(plan.lock)
+}
+
+pub fn resolve(manifest: &JsonnetFile, lock: Option<&JsonnetFile>) -> Result<InstallPlan, Error> {
+ 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<ResolveResult, Error> {
+ 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<Utf8PathBuf>,
+) -> 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("<TBD>")
+ );
+
+ 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<zip::result::ZipError>),
+ #[error(transparent)]
+ Accessor(#[from] accessor::Error),
+ #[error("unknown subdir: {0}")]
+ SubdirNotFound(String),
+ #[error("invalid path in tree: {0}")]
+ InvalidPath(String),
+}
+pub(crate) type Result<T, E = Error> = result::Result<T, E>;
crates/jrsonnet-pkg/src/jsonnet_bundler.rsdiffbeforeafterboth--- /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<Dependency>,
+ #[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<String>,
+ #[serde(default, skip_serializing_if = "Option::is_none")]
+ pub sum: Option<String>,
+ #[serde(default, skip_serializing_if = "Option::is_none")]
+ pub name: Option<String>,
+ #[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, Self::Err> {
+ Self::try_from(Utf8PathBuf::from(s))
+ }
+}
+impl TryFrom<Utf8PathBuf> for SubDir {
+ type Error = SubDirEscapeError;
+
+ fn try_from(buf: Utf8PathBuf) -> Result<Self, Self::Error> {
+ 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<Utf8Path>) -> Result<SubDir, SubDirEscapeError> {
+ SubDir::try_from(self.0.join(other))
+ }
+ pub fn strip_prefix(&self, prefix: &SubDir) -> Option<SubDir> {
+ 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<Utf8Path> for SubDir {
+ fn as_ref(&self) -> &Utf8Path {
+ &self.0
+ }
+}
+impl AsRef<Path> for SubDir {
+ fn as_ref(&self) -> &Path {
+ self.0.as_ref()
+ }
+}
+impl AsRef<str> 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<str> 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<Self, Self::Err> {
+ 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<str> for Hostname {
+ fn as_ref(&self) -> &str {
+ &self.0
+ }
+}
+impl AsRef<Path> for Hostname {
+ fn as_ref(&self) -> &Path {
+ self.0.as_ref()
+ }
+}
+impl AsRef<Utf8Path> 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<str> 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<Self, Self::Err> {
+ 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<SubDir, SubDirEscapeError> {
+ 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<S: serde::Serializer>(&self, ser: S) -> Result<S::Ok, S::Error> {
+ #[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<D: serde::Deserializer<'de>>(de: D) -> Result<Self, D::Error> {
+ #[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<Dependency> {
+ 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<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
+ #[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<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
+ #[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<Self, Error> {
+ 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());
+ }
+}
crates/jrsonnet-pkg/src/lib.rsdiffbeforeafterboth--- /dev/null
+++ b/crates/jrsonnet-pkg/src/lib.rs
@@ -0,0 +1,2 @@
+pub mod install;
+pub mod jsonnet_bundler;