debian-control-0.2.14/.cargo_vcs_info.json0000644000000001540000000000100140200ustar { "git": { "sha1": "b7616efb2a1c6b382149b639f541165dac8b405b" }, "path_in_vcs": "debian-control" }debian-control-0.2.14/Cargo.lock0000644000000540500000000000100117770ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 4 [[package]] name = "aho-corasick" version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" dependencies = [ "memchr", ] [[package]] name = "android_system_properties" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" dependencies = [ "libc", ] [[package]] name = "autocfg" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "bumpalo" version = "3.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" [[package]] name = "cc" version = "1.2.49" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90583009037521a116abf44494efecd645ba48b6622457080f080b85544e2215" dependencies = [ "find-msvc-tools", "shlex", ] [[package]] name = "cfg-if" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "chrono" version = "0.4.42" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" dependencies = [ "iana-time-zone", "js-sys", "num-traits", "wasm-bindgen", "windows-link", ] [[package]] name = "core-foundation-sys" version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "countme" version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7704b5fdd17b18ae31c4c1da5a2e0305a2bf17b5249300a9ee9ed7b72114c636" [[package]] name = "deb822-derive" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e83ef29a094bcb2b7dd0f609ace7f5a34ef9a62e0731ebd350637640320a3b15" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "deb822-fast" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "114c474fa4cd5d6d24bb5e68b36fa4ef70f5b830e3cc14a9b66a12e71a15aeb9" dependencies = [ "deb822-derive", ] [[package]] name = "deb822-lossless" version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4b7d08154e599ffe6652ff71969763c70a93e2acc3cd9c274e576028fddd4f1" dependencies = [ "pyo3", "regex", "rowan", "serde", ] [[package]] name = "debian-control" version = "0.2.14" dependencies = [ "chrono", "deb822-fast", "deb822-lossless", "debversion", "pyo3", "regex", "rowan", "serde", "serde_json", "url", ] [[package]] name = "debversion" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4f5cc9ce1d5067bee8060dd75208525dd0133ffea0b2960fef64ab85d58c4c5" dependencies = [ "chrono", "lazy-regex", "num-bigint", ] [[package]] name = "displaydoc" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "find-msvc-tools" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" [[package]] name = "form_urlencoded" version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" dependencies = [ "percent-encoding", ] [[package]] name = "hashbrown" version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" [[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "iana-time-zone" version = "0.1.64" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", "log", "wasm-bindgen", "windows-core", ] [[package]] name = "iana-time-zone-haiku" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" dependencies = [ "cc", ] [[package]] name = "icu_collections" version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" dependencies = [ "displaydoc", "potential_utf", "yoke", "zerofrom", "zerovec", ] [[package]] name = "icu_locale_core" version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" dependencies = [ "displaydoc", "litemap", "tinystr", "writeable", "zerovec", ] [[package]] name = "icu_normalizer" version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" dependencies = [ "icu_collections", "icu_normalizer_data", "icu_properties", "icu_provider", "smallvec", "zerovec", ] [[package]] name = "icu_normalizer_data" version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" [[package]] name = "icu_properties" version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" dependencies = [ "icu_collections", "icu_locale_core", "icu_properties_data", "icu_provider", "zerotrie", "zerovec", ] [[package]] name = "icu_properties_data" version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" [[package]] name = "icu_provider" version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" dependencies = [ "displaydoc", "icu_locale_core", "writeable", "yoke", "zerofrom", "zerotrie", "zerovec", ] [[package]] name = "idna" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" dependencies = [ "idna_adapter", "smallvec", "utf8_iter", ] [[package]] name = "idna_adapter" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" dependencies = [ "icu_normalizer", "icu_properties", ] [[package]] name = "indoc" version = "2.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706" dependencies = [ "rustversion", ] [[package]] name = "itoa" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "js-sys" version = "0.3.83" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" dependencies = [ "once_cell", "wasm-bindgen", ] [[package]] name = "lazy-regex" version = "3.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "191898e17ddee19e60bccb3945aa02339e81edd4a8c50e21fd4d48cdecda7b29" dependencies = [ "lazy-regex-proc_macros", "once_cell", "regex", ] [[package]] name = "lazy-regex-proc_macros" version = "3.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c35dc8b0da83d1a9507e12122c80dea71a9c7c613014347392483a83ea593e04" dependencies = [ "proc-macro2", "quote", "regex", "syn", ] [[package]] name = "libc" version = "0.2.178" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" [[package]] name = "litemap" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" [[package]] name = "log" version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "memchr" version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" [[package]] name = "memoffset" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" dependencies = [ "autocfg", ] [[package]] name = "num-bigint" version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ "num-integer", "num-traits", ] [[package]] name = "num-integer" version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" dependencies = [ "num-traits", ] [[package]] name = "num-traits" version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] [[package]] name = "once_cell" version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "percent-encoding" version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "portable-atomic" version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" [[package]] name = "potential_utf" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" dependencies = [ "zerovec", ] [[package]] name = "proc-macro2" version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" dependencies = [ "unicode-ident", ] [[package]] name = "pyo3" version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab53c047fcd1a1d2a8820fe84f05d6be69e9526be40cb03b73f86b6b03e6d87d" dependencies = [ "indoc", "libc", "memoffset", "once_cell", "portable-atomic", "pyo3-build-config", "pyo3-ffi", "pyo3-macros", "unindent", ] [[package]] name = "pyo3-build-config" version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b455933107de8642b4487ed26d912c2d899dec6114884214a0b3bb3be9261ea6" dependencies = [ "target-lexicon", ] [[package]] name = "pyo3-ffi" version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c85c9cbfaddf651b1221594209aed57e9e5cff63c4d11d1feead529b872a089" dependencies = [ "libc", "pyo3-build-config", ] [[package]] name = "pyo3-macros" version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0a5b10c9bf9888125d917fb4d2ca2d25c8df94c7ab5a52e13313a07e050a3b02" dependencies = [ "proc-macro2", "pyo3-macros-backend", "quote", "syn", ] [[package]] name = "pyo3-macros-backend" version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03b51720d314836e53327f5871d4c0cfb4fb37cc2c4a11cc71907a86342c40f9" dependencies = [ "heck", "proc-macro2", "pyo3-build-config", "quote", "syn", ] [[package]] name = "quote" version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" dependencies = [ "proc-macro2", ] [[package]] name = "regex" version = "1.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" dependencies = [ "aho-corasick", "memchr", "regex-automata", "regex-syntax", ] [[package]] name = "regex-automata" version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" dependencies = [ "aho-corasick", "memchr", "regex-syntax", ] [[package]] name = "regex-syntax" version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" [[package]] name = "rowan" version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "417a3a9f582e349834051b8a10c8d71ca88da4211e4093528e36b9845f6b5f21" dependencies = [ "countme", "hashbrown", "rustc-hash", "text-size", ] [[package]] name = "rustc-hash" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustversion" version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "ryu" version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" [[package]] name = "serde" version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ "serde_core", "serde_derive", ] [[package]] name = "serde_core" version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "serde_json" version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" dependencies = [ "itoa", "memchr", "ryu", "serde", "serde_core", ] [[package]] name = "shlex" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "smallvec" version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "stable_deref_trait" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] name = "syn" version = "2.0.111" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "synstructure" version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "target-lexicon" version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df7f62577c25e07834649fc3b39fafdc597c0a3527dc1c60129201ccfcbaa50c" [[package]] name = "text-size" version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f18aa187839b2bdb1ad2fa35ead8c4c2976b64e4363c386d45ac0f7ee85c9233" [[package]] name = "tinystr" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" dependencies = [ "displaydoc", "zerovec", ] [[package]] name = "unicode-ident" version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" [[package]] name = "unindent" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7264e107f553ccae879d21fbea1d6724ac785e8c3bfc762137959b5802826ef3" [[package]] name = "url" version = "2.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" dependencies = [ "form_urlencoded", "idna", "percent-encoding", "serde", ] [[package]] name = "utf8_iter" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "wasm-bindgen" version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" dependencies = [ "cfg-if", "once_cell", "rustversion", "wasm-bindgen-macro", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" dependencies = [ "quote", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" dependencies = [ "bumpalo", "proc-macro2", "quote", "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" dependencies = [ "unicode-ident", ] [[package]] name = "windows-core" version = "0.62.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" dependencies = [ "windows-implement", "windows-interface", "windows-link", "windows-result", "windows-strings", ] [[package]] name = "windows-implement" version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "windows-interface" version = "0.59.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "windows-link" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] name = "windows-result" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" dependencies = [ "windows-link", ] [[package]] name = "windows-strings" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" dependencies = [ "windows-link", ] [[package]] name = "writeable" version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" [[package]] name = "yoke" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" dependencies = [ "stable_deref_trait", "yoke-derive", "zerofrom", ] [[package]] name = "yoke-derive" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" dependencies = [ "proc-macro2", "quote", "syn", "synstructure", ] [[package]] name = "zerofrom" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", "syn", "synstructure", ] [[package]] name = "zerotrie" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" dependencies = [ "displaydoc", "yoke", "zerofrom", ] [[package]] name = "zerovec" version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" dependencies = [ "yoke", "zerofrom", "zerovec-derive", ] [[package]] name = "zerovec-derive" version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" dependencies = [ "proc-macro2", "quote", "syn", ] debian-control-0.2.14/Cargo.toml0000644000000041370000000000100120230ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" name = "debian-control" version = "0.2.14" authors = ["Jelmer Vernooij "] build = false autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "A parser for Debian control files" homepage = "https://github.com/jelmer/deb822-lossless" readme = "README.md" keywords = [ "debian", "deb822", "rfc822", "lossless", "edit", ] categories = ["parser-implementations"] license = "Apache-2.0" repository = "https://github.com/jelmer/deb822-lossless" [badges.maintenance] status = "actively-maintained" [features] chrono = ["dep:chrono"] default = [ "chrono", "lossless", ] lossless = [ "dep:deb822-lossless", "dep:rowan", ] python-debian = [ "dep:pyo3", "deb822-lossless/python-debian", ] serde = ["dep:serde"] [lib] name = "debian_control" path = "src/lib.rs" [[example]] name = "create-file" path = "examples/create-file.rs" required-features = ["lossless"] [[example]] name = "incremental_parse" path = "examples/incremental_parse.rs" required-features = ["lossless"] [dependencies.chrono] version = ">=0.4, <0.5" optional = true [dependencies.deb822-fast] version = ">=0.2.0, <0.3" features = ["derive"] [dependencies.deb822-lossless] version = ">=0.5.1, <0.6" optional = true [dependencies.debversion] version = ">=0.4.7, <0.6" [dependencies.pyo3] version = ">=0.27, <0.28" optional = true [dependencies.regex] version = ">=1, <2" [dependencies.rowan] version = "0.16" optional = true [dependencies.serde] version = "1" optional = true [dependencies.url] version = ">=2, <3" [dev-dependencies.serde_json] version = "1.0.145" debian-control-0.2.14/Cargo.toml.orig000064400000000000000000000023511046102023000155000ustar 00000000000000[package] name = "debian-control" authors = ["Jelmer Vernooij "] edition = "2021" version = "0.2.14" license = "Apache-2.0" description = "A parser for Debian control files" repository = { workspace = true } homepage = { workspace = true } keywords = ["debian", "deb822", "rfc822", "lossless", "edit"] categories = ["parser-implementations"] [dependencies] rowan = { version = "0.16", optional = true } debversion = ">=0.4.7, <0.6" regex = ">=1, <2" deb822-lossless = { version = ">=0.5.1, <0.6", path = "../deb822-lossless", optional = true } deb822-fast = { path = "../deb822-fast", features = ["derive"], version = ">=0.2.0, <0.3" } url = ">=2, <3" pyo3 = { workspace = true, optional = true } chrono = { version = ">=0.4, <0.5", optional = true } serde = { version = "1", optional = true } [features] default = ["chrono", "lossless"] python-debian = ["dep:pyo3", "deb822-lossless/python-debian"] chrono = ["dep:chrono"] serde = ["dep:serde"] lossless = ["dep:deb822-lossless", "dep:rowan"] [[example]] name = "create-file" required-features = ["lossless"] [[example]] name = "incremental_parse" required-features = ["lossless"] [dev-dependencies] serde_json = "1.0.145" [badges] maintenance = { status = "actively-maintained" } debian-control-0.2.14/README.md000064400000000000000000000013551046102023000140730ustar 00000000000000# Lossless parser for Debian Control files This crate provides a parser for Debian control files. It is lossless, meaning that it will preserve the original formatting of the file. It also provides a way to serialize the parsed data back to a string. ```rust use debian_control::{Control, Priority}; use std::fs::File; let mut control = Control::new(); let mut source = control.add_source("hello"); source.set_section("rust"); let mut binary = control.add_binary("hello"); binary.set_architecture("amd64"); binary.set_priority(Priority::Optional); binary.set_description("Hello, world!"); assert_eq!(control.to_string(), r#"Source: hello Section: rust Package: hello Architecture: amd64 Priority: optional Description: Hello, world! "#); ``` debian-control-0.2.14/examples/create-file.rs000064400000000000000000000007011046102023000171520ustar 00000000000000use debian_control::fields::Priority; use debian_control::lossless::Control; pub fn main() { let mut control = Control::new(); let mut source = control.add_source("hello"); source.set_section(Some("rust")); let mut binary = control.add_binary("hello"); binary.set_architecture(Some("amd64")); binary.set_priority(Some(Priority::Optional)); binary.set_description(Some("Hello, world!")); println!("{}", control); } debian-control-0.2.14/examples/incremental_parse.rs000064400000000000000000000034411046102023000204710ustar 00000000000000use deb822_lossless::TextRange; use debian_control::lossless::Control; fn main() { // Example control file content let control_text = r#"Source: example-package Maintainer: John Doe Build-Depends: debhelper (>= 12) Package: example-binary Architecture: any Depends: ${shlibs:Depends}, ${misc:Depends} Description: Example package This is an example package with a multi-line description. "#; // Parse the control file let control: Control = control_text.parse().unwrap(); // Simulate a change in the range where "Architecture: any" is located // In a real LSP, this would come from the editor let change_start = control_text.find("Architecture:").unwrap(); let change_end = change_start + "Architecture: any".len(); let change_range = TextRange::new((change_start as u32).into(), (change_end as u32).into()); println!("Checking fields in range {}..{}", change_start, change_end); println!("Fields affected by the change:"); // Use the new range-based API to find affected fields for entry in control.fields_in_range(change_range) { if let Some(key) = entry.key() { println!(" - Field: {}", key); let value = entry.value(); println!(" Value: {}", value.trim()); } } // Also demonstrate checking if specific paragraphs overlap if let Some(source) = control.source() { println!( "\nSource paragraph overlaps with change: {}", source.overlaps_range(change_range) ); } for binary in control.binaries() { if let Some(name) = binary.name() { println!( "Binary '{}' overlaps with change: {}", name, binary.overlaps_range(change_range) ); } } } debian-control-0.2.14/src/fields.rs000064400000000000000000000541051046102023000152200ustar 00000000000000//! Fields for the control file use std::str::FromStr; /// Priority of a package #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)] pub enum Priority { /// Required Required, /// Important Important, /// Standard Standard, /// Optional Optional, /// Extra Extra, /// Source /// /// Note: This priority is not officially documented in Debian policy, /// but is commonly used to indicate source packages. /// /// While packages generally follow the priority values defined in policy, for source packages /// the archive-management software (such as dak, the /// Debian Archive Kit) may set "Priority: source". Source, } impl std::fmt::Display for Priority { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { f.write_str(match self { Priority::Required => "required", Priority::Important => "important", Priority::Standard => "standard", Priority::Optional => "optional", Priority::Extra => "extra", Priority::Source => "source", }) } } impl std::str::FromStr for Priority { type Err = String; fn from_str(s: &str) -> Result { match s { "required" => Ok(Priority::Required), "important" => Ok(Priority::Important), "standard" => Ok(Priority::Standard), "optional" => Ok(Priority::Optional), "extra" => Ok(Priority::Extra), "source" => Ok(Priority::Source), _ => Err(format!("Invalid priority: {}", s)), } } } /// A checksum of a file pub trait Checksum { /// Filename fn filename(&self) -> &str; /// Size of the file, in bytes fn size(&self) -> usize; } /// SHA1 checksum #[derive(Debug, Clone, PartialEq, Eq, Hash, Default, PartialOrd, Ord)] pub struct Sha1Checksum { /// SHA1 checksum pub sha1: String, /// Size of the file, in bytes pub size: usize, /// Filename pub filename: String, } impl Checksum for Sha1Checksum { fn filename(&self) -> &str { &self.filename } fn size(&self) -> usize { self.size } } impl std::fmt::Display for Sha1Checksum { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "{} {} {}", self.sha1, self.size, self.filename) } } impl std::str::FromStr for Sha1Checksum { type Err = String; fn from_str(s: &str) -> Result { let mut parts = s.split_whitespace(); let sha1 = parts.next().ok_or_else(|| "Missing sha1".to_string())?; let size = parts .next() .ok_or_else(|| "Missing size".to_string())? .parse() .map_err(|e: std::num::ParseIntError| e.to_string())?; let filename = parts .next() .ok_or_else(|| "Missing filename".to_string())? .to_string(); Ok(Self { sha1: sha1.to_string(), size, filename, }) } } /// SHA-256 checksum #[derive(Debug, Clone, PartialEq, Eq, Hash, Default, PartialOrd, Ord)] pub struct Sha256Checksum { /// SHA-256 checksum pub sha256: String, /// Size of the file, in bytes pub size: usize, /// Filename pub filename: String, } impl Checksum for Sha256Checksum { fn filename(&self) -> &str { &self.filename } fn size(&self) -> usize { self.size } } impl std::fmt::Display for Sha256Checksum { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "{} {} {}", self.sha256, self.size, self.filename) } } impl std::str::FromStr for Sha256Checksum { type Err = String; fn from_str(s: &str) -> Result { let mut parts = s.split_whitespace(); let sha256 = parts.next().ok_or_else(|| "Missing sha256".to_string())?; let size = parts .next() .ok_or_else(|| "Missing size".to_string())? .parse() .map_err(|e: std::num::ParseIntError| e.to_string())?; let filename = parts .next() .ok_or_else(|| "Missing filename".to_string())? .to_string(); Ok(Self { sha256: sha256.to_string(), size, filename, }) } } /// SHA-512 checksum #[derive(Debug, Clone, PartialEq, Eq, Hash, Default, PartialOrd, Ord)] pub struct Sha512Checksum { /// SHA-512 checksum pub sha512: String, /// Size of the file, in bytes pub size: usize, /// Filename pub filename: String, } impl Checksum for Sha512Checksum { fn filename(&self) -> &str { &self.filename } fn size(&self) -> usize { self.size } } impl std::fmt::Display for Sha512Checksum { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "{} {} {}", self.sha512, self.size, self.filename) } } impl std::str::FromStr for Sha512Checksum { type Err = String; fn from_str(s: &str) -> Result { let mut parts = s.split_whitespace(); let sha512 = parts.next().ok_or_else(|| "Missing sha512".to_string())?; let size = parts .next() .ok_or_else(|| "Missing size".to_string())? .parse() .map_err(|e: std::num::ParseIntError| e.to_string())?; let filename = parts .next() .ok_or_else(|| "Missing filename".to_string())? .to_string(); Ok(Self { sha512: sha512.to_string(), size, filename, }) } } /// An MD5 checksum of a file #[derive(Debug, Clone, PartialEq, Eq, Hash, Default, PartialOrd, Ord)] pub struct Md5Checksum { /// The MD5 checksum pub md5sum: String, /// The size of the file pub size: usize, /// The filename pub filename: String, } impl std::fmt::Display for Md5Checksum { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "{} {} {}", self.md5sum, self.size, self.filename) } } impl std::str::FromStr for Md5Checksum { type Err = (); fn from_str(s: &str) -> Result { let mut parts = s.split_whitespace(); let md5sum = parts.next().ok_or(())?; let size = parts.next().ok_or(())?.parse().map_err(|_| ())?; let filename = parts.next().ok_or(())?.to_string(); Ok(Self { md5sum: md5sum.to_string(), size, filename, }) } } impl Checksum for Md5Checksum { fn filename(&self) -> &str { &self.filename } fn size(&self) -> usize { self.size } } /// A package list entry #[derive(Debug, Clone, PartialEq, Eq)] pub struct PackageListEntry { /// Package name pub package: String, /// Package type pub package_type: String, /// Section pub section: String, /// Priority pub priority: Priority, /// Extra fields pub extra: std::collections::HashMap, } impl PackageListEntry { /// Create a new package list entry pub fn new(package: &str, package_type: &str, section: &str, priority: Priority) -> Self { Self { package: package.to_string(), package_type: package_type.to_string(), section: section.to_string(), priority, extra: std::collections::HashMap::new(), } } } impl std::fmt::Display for PackageListEntry { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!( f, "{} {} {} {}", self.package, self.package_type, self.section, self.priority )?; for (k, v) in &self.extra { write!(f, " {}={}", k, v)?; } Ok(()) } } impl std::str::FromStr for PackageListEntry { type Err = String; fn from_str(s: &str) -> Result { let mut parts = s.split_whitespace(); let package = parts .next() .ok_or_else(|| "Missing package".to_string())? .to_string(); let package_type = parts .next() .ok_or_else(|| "Missing package type".to_string())? .to_string(); let section = parts .next() .ok_or_else(|| "Missing section".to_string())? .to_string(); let priority = parts .next() .ok_or_else(|| "Missing priority".to_string())? .parse()?; let mut extra = std::collections::HashMap::new(); for part in parts { let mut kv = part.split('='); let k = kv .next() .ok_or_else(|| "Missing key".to_string())? .to_string(); let v = kv .next() .ok_or_else(|| "Missing value".to_string())? .to_string(); extra.insert(k, v); } Ok(Self { package, package_type, section, priority, extra, }) } } /// Urgency of a particular package version #[derive(Debug, Clone, PartialEq, Eq, Hash, Default, PartialOrd, Ord)] pub enum Urgency { /// Low #[default] Low, /// Medium Medium, /// High High, /// Emergency Emergency, /// Critical Critical, } impl std::fmt::Display for Urgency { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { match self { Urgency::Low => f.write_str("low"), Urgency::Medium => f.write_str("medium"), Urgency::High => f.write_str("high"), Urgency::Emergency => f.write_str("emergency"), Urgency::Critical => f.write_str("critical"), } } } impl FromStr for Urgency { type Err = String; fn from_str(s: &str) -> Result { match s.to_lowercase().as_str() { "low" => Ok(Urgency::Low), "medium" => Ok(Urgency::Medium), "high" => Ok(Urgency::High), "emergency" => Ok(Urgency::Emergency), "critical" => Ok(Urgency::Critical), _ => Err(format!("invalid urgency: {}", s)), } } } /// Multi-arch policy #[derive(PartialEq, Eq, Debug, Default, Clone)] pub enum MultiArch { /// Indicates that the package is identical across all architectures. The package can satisfy dependencies for other architectures. Same, /// The package can be installed alongside the same package of other architectures. It doesn't provide files that conflict with other architectures. Foreign, /// The package is only for its native architecture and cannot satisfy dependencies for other architectures. #[default] No, /// Similar to "foreign", but the package manager may choose not to install it for foreign architectures if a native package is available. Allowed, } impl std::str::FromStr for MultiArch { type Err = String; fn from_str(s: &str) -> Result { match s { "same" => Ok(MultiArch::Same), "foreign" => Ok(MultiArch::Foreign), "no" => Ok(MultiArch::No), "allowed" => Ok(MultiArch::Allowed), _ => Err(format!("Invalid multiarch: {}", s)), } } } impl std::fmt::Display for MultiArch { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { f.write_str(match self { MultiArch::Same => "same", MultiArch::Foreign => "foreign", MultiArch::No => "no", MultiArch::Allowed => "allowed", }) } } /// Format a Debian package description according to Debian policy. /// /// Package descriptions consist of a short description (synopsis) and a long description. /// The long description lines are indented with a single space, and empty lines are /// represented as " ." (space followed by a period). /// /// # Arguments /// /// * `short` - The short description (synopsis), typically one line /// * `long` - The long description, can be multiple lines /// /// # Returns /// /// A formatted description string suitable for use in a Debian control file. /// /// # Examples /// /// ``` /// use debian_control::fields::format_description; /// /// let formatted = format_description("A great package", "This package does amazing things.\nIt is very useful."); /// assert_eq!(formatted, "A great package\n This package does amazing things.\n It is very useful."); /// /// // Empty lines become " ." /// let with_empty = format_description("Summary", "First paragraph.\n\nSecond paragraph."); /// assert_eq!(with_empty, "Summary\n First paragraph.\n .\n Second paragraph."); /// ``` pub fn format_description(short: &str, long: &str) -> String { let mut result = short.to_string(); for line in long.lines() { result.push('\n'); if line.trim().is_empty() { result.push_str(" ."); } else { result.push(' '); result.push_str(line); } } result } /// Standards-Version field value /// /// Represents a Debian standards version as a tuple of up to 4 components. /// Commonly used versions include "3.9.8", "4.6.2", etc. /// /// # Examples /// /// ``` /// use debian_control::fields::StandardsVersion; /// use std::str::FromStr; /// /// let version = StandardsVersion::from_str("4.6.2").unwrap(); /// assert_eq!(version.major(), 4); /// assert_eq!(version.minor(), 6); /// assert_eq!(version.patch(), 2); /// assert_eq!(version.to_string(), "4.6.2"); /// /// // Versions can be compared /// let v1 = StandardsVersion::from_str("4.6.2").unwrap(); /// let v2 = StandardsVersion::from_str("4.5.1").unwrap(); /// assert!(v1 > v2); /// ``` #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct StandardsVersion { major: u8, minor: u8, patch: u8, micro: u8, } impl StandardsVersion { /// Create a new standards version pub fn new(major: u8, minor: u8, patch: u8, micro: u8) -> Self { Self { major, minor, patch, micro, } } /// Get the major version component pub fn major(&self) -> u8 { self.major } /// Get the minor version component pub fn minor(&self) -> u8 { self.minor } /// Get the patch version component pub fn patch(&self) -> u8 { self.patch } /// Get the micro version component pub fn micro(&self) -> u8 { self.micro } /// Convert to a tuple (major, minor, patch, micro) pub fn as_tuple(&self) -> (u8, u8, u8, u8) { (self.major, self.minor, self.patch, self.micro) } } impl std::fmt::Display for StandardsVersion { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { if self.micro != 0 { write!( f, "{}.{}.{}.{}", self.major, self.minor, self.patch, self.micro ) } else if self.patch != 0 { write!(f, "{}.{}.{}", self.major, self.minor, self.patch) } else if self.minor != 0 { write!(f, "{}.{}", self.major, self.minor) } else { write!(f, "{}", self.major) } } } impl std::str::FromStr for StandardsVersion { type Err = String; fn from_str(s: &str) -> Result { let parts: Vec<&str> = s.split('.').collect(); if parts.is_empty() || parts.len() > 4 { return Err(format!( "Invalid standards version format: {} (expected 1-4 dot-separated components)", s )); } let major = parts[0] .parse() .map_err(|_| format!("Invalid major version: {}", parts[0]))?; let minor = if parts.len() > 1 { parts[1] .parse() .map_err(|_| format!("Invalid minor version: {}", parts[1]))? } else { 0 }; let patch = if parts.len() > 2 { parts[2] .parse() .map_err(|_| format!("Invalid patch version: {}", parts[2]))? } else { 0 }; let micro = if parts.len() > 3 { parts[3] .parse() .map_err(|_| format!("Invalid micro version: {}", parts[3]))? } else { 0 }; Ok(Self { major, minor, patch, micro, }) } } impl PartialOrd for StandardsVersion { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } impl Ord for StandardsVersion { fn cmp(&self, other: &Self) -> std::cmp::Ordering { self.as_tuple().cmp(&other.as_tuple()) } } #[cfg(test)] mod tests { use super::*; #[test] fn test_sha1_checksum_filename() { let checksum = Sha1Checksum { sha1: "abc123".to_string(), size: 1234, filename: "test.deb".to_string(), }; assert_eq!(checksum.filename(), "test.deb".to_string()); } #[test] fn test_md5_checksum_filename() { let checksum = Md5Checksum { md5sum: "abc123".to_string(), size: 1234, filename: "test.deb".to_string(), }; assert_eq!(checksum.filename(), "test.deb".to_string()); } #[test] fn test_sha256_checksum_filename() { let checksum = Sha256Checksum { sha256: "abc123".to_string(), size: 1234, filename: "test.deb".to_string(), }; assert_eq!(checksum.filename(), "test.deb".to_string()); } #[test] fn test_sha512_checksum_filename() { let checksum = Sha512Checksum { sha512: "abc123".to_string(), size: 1234, filename: "test.deb".to_string(), }; assert_eq!(checksum.filename(), "test.deb".to_string()); } #[test] fn test_format_description_basic() { let formatted = format_description( "A great package", "This package does amazing things.\nIt is very useful.", ); assert_eq!( formatted, "A great package\n This package does amazing things.\n It is very useful." ); } #[test] fn test_format_description_empty_lines() { let formatted = format_description("Summary", "First paragraph.\n\nSecond paragraph."); assert_eq!( formatted, "Summary\n First paragraph.\n .\n Second paragraph." ); } #[test] fn test_format_description_short_only() { let formatted = format_description("Short description", ""); assert_eq!(formatted, "Short description"); } #[test] fn test_format_description_multiple_empty_lines() { let formatted = format_description("Test", "Line 1\n\n\nLine 2"); assert_eq!(formatted, "Test\n Line 1\n .\n .\n Line 2"); } #[test] fn test_format_description_whitespace_only_line() { let formatted = format_description("Test", "Line 1\n \nLine 2"); assert_eq!(formatted, "Test\n Line 1\n .\n Line 2"); } #[test] fn test_format_description_complex() { let long_desc = "This is a test package.\n\nIt has multiple paragraphs.\n\nAnd even lists:\n - Item 1\n - Item 2"; let formatted = format_description("Test package", long_desc); assert_eq!( formatted, "Test package\n This is a test package.\n .\n It has multiple paragraphs.\n .\n And even lists:\n - Item 1\n - Item 2" ); } #[test] fn test_standards_version_parse() { let v = "4.6.2".parse::().unwrap(); assert_eq!(v.major(), 4); assert_eq!(v.minor(), 6); assert_eq!(v.patch(), 2); assert_eq!(v.micro(), 0); assert_eq!(v.as_tuple(), (4, 6, 2, 0)); } #[test] fn test_standards_version_parse_two_components() { let v = "3.9".parse::().unwrap(); assert_eq!(v.major(), 3); assert_eq!(v.minor(), 9); assert_eq!(v.patch(), 0); assert_eq!(v.micro(), 0); } #[test] fn test_standards_version_parse_four_components() { let v = "4.6.2.1".parse::().unwrap(); assert_eq!(v.major(), 4); assert_eq!(v.minor(), 6); assert_eq!(v.patch(), 2); assert_eq!(v.micro(), 1); } #[test] fn test_standards_version_parse_single_component() { let v = "4".parse::().unwrap(); assert_eq!(v.major(), 4); assert_eq!(v.minor(), 0); assert_eq!(v.patch(), 0); assert_eq!(v.micro(), 0); } #[test] fn test_standards_version_display() { let v = StandardsVersion::new(4, 6, 2, 0); assert_eq!(v.to_string(), "4.6.2"); let v = StandardsVersion::new(3, 9, 8, 0); assert_eq!(v.to_string(), "3.9.8"); let v = StandardsVersion::new(4, 6, 2, 1); assert_eq!(v.to_string(), "4.6.2.1"); let v = StandardsVersion::new(3, 9, 0, 0); assert_eq!(v.to_string(), "3.9"); let v = StandardsVersion::new(4, 0, 0, 0); assert_eq!(v.to_string(), "4"); } #[test] fn test_standards_version_comparison() { let v1 = "4.6.2".parse::().unwrap(); let v2 = "4.5.1".parse::().unwrap(); assert!(v1 > v2); let v3 = "4.6.2".parse::().unwrap(); assert_eq!(v1, v3); let v4 = "3.9.8".parse::().unwrap(); assert!(v1 > v4); let v5 = "4.6.2.1".parse::().unwrap(); assert!(v5 > v1); } #[test] fn test_standards_version_roundtrip() { let versions = vec!["4.6.2", "3.9.8", "4.6.2.1", "3.9", "4"]; for version_str in versions { let v = version_str.parse::().unwrap(); assert_eq!(v.to_string(), version_str); } } #[test] fn test_standards_version_invalid() { assert!("".parse::().is_err()); assert!("a.b.c".parse::().is_err()); assert!("1.2.3.4.5".parse::().is_err()); assert!("1.2.3.-1".parse::().is_err()); } } debian-control-0.2.14/src/lib.rs000064400000000000000000000103401046102023000145110ustar 00000000000000#![deny(missing_docs)] // Until we drop support for PyO3 0.22, allow use of deprecated functions. #![allow(deprecated)] //! Parser for Debian control files. //! //! This crate provides a parser for Debian control files. //! //! # Example //! //! ```rust //! use debian_control::lossy::Control; //! use debian_control::fields::Priority; //! use std::fs::File; //! //! let mut control = Control::new(); //! let mut source = &mut control.source; //! source.name = "hello".to_string(); //! source.section = Some("rust".to_string()); //! //! let mut binary = control.add_binary("hello"); //! binary.architecture = Some("amd64".to_string()); //! binary.priority = Some(Priority::Optional); //! binary.description = Some("Hello, world!".to_string()); //! //! assert_eq!(control.to_string(), r#"Source: hello //! Section: rust //! //! Package: hello //! Architecture: amd64 //! Priority: optional //! Description: Hello, world! //! "#); //! ``` //! //! See the ``lossless`` module for a parser that preserves all comments and formatting, and //! as well as allowing inline errors. pub mod lossy; #[cfg(feature = "lossless")] pub use lossless::control::{Binary, Control, Source}; pub mod fields; pub use fields::*; #[cfg(feature = "lossless")] pub mod lossless; #[cfg(feature = "lossless")] pub use lossless::apt; #[cfg(feature = "lossless")] pub use lossless::changes; #[cfg(feature = "lossless")] pub use lossless::control; #[cfg(feature = "lossless")] pub mod pgp; pub mod relations; pub mod vcs; use std::borrow::Cow; /// Error type for parsing an identity string. #[derive(Debug, PartialEq)] pub enum ParseIdentityError { /// No email address found. NoEmail, } impl std::fmt::Display for ParseIdentityError { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { match self { ParseIdentityError::NoEmail => write!(f, "No email found"), } } } impl std::error::Error for ParseIdentityError {} /// Parse an identity string into a name and an email address. /// /// The input string should be in the format `Name `. If the email is missing, an error is /// returned. /// /// # Example /// ``` /// use debian_control::parse_identity; /// assert_eq!(parse_identity("Joe Example "), Ok(("Joe Example", "joe@example.com"))); /// ``` /// /// # Arguments /// * `s` - The input string. /// /// # Returns /// A tuple with the name and the email address. pub fn parse_identity(s: &str) -> Result<(&str, &str), ParseIdentityError> { // Support Name and email, but ensure email contains an "@". if let Some((name, email)) = s.split_once('<') { if let Some(email) = email.strip_suffix('>') { Ok((name.trim(), email.trim())) } else { Err(ParseIdentityError::NoEmail) } } else if s.contains('@') { Ok(("", s.trim())) } else { Err(ParseIdentityError::NoEmail) } } /// A trait for looking up versions of packages. pub trait VersionLookup { /// Look up the version of a package. fn lookup_version<'a>( &'a self, package: &'_ str, ) -> Option>; } impl VersionLookup for std::collections::HashMap { fn lookup_version<'a>(&'a self, package: &str) -> Option> { self.get(package).map(Cow::Borrowed) } } impl VersionLookup for F where F: Fn(&str) -> Option, { fn lookup_version<'a>(&'a self, name: &str) -> Option> { self(name).map(Cow::Owned) } } impl VersionLookup for (String, debversion::Version) { fn lookup_version<'a>(&'a self, name: &str) -> Option> { if name == self.0 { Some(Cow::Borrowed(&self.1)) } else { None } } } #[cfg(test)] mod tests { use super::*; #[test] fn test_parse_identity() { assert_eq!( parse_identity("Joe Example "), Ok(("Joe Example", "joe@example.com")) ); assert_eq!( parse_identity("joe@example.com"), Ok(("", "joe@example.com")) ); assert_eq!(parse_identity("somebody"), Err(ParseIdentityError::NoEmail)); } } debian-control-0.2.14/src/lossless/apt.rs000064400000000000000000001365751046102023000164210ustar 00000000000000//! APT package manager files use crate::fields::{ Md5Checksum, MultiArch, Priority, Sha1Checksum, Sha256Checksum, Sha512Checksum, }; use crate::lossless::relations::Relations; use rowan::ast::AstNode; /// A source package in the APT package manager. #[derive(Debug, Clone, PartialEq, Eq)] pub struct Source(deb822_lossless::Paragraph); #[cfg(feature = "python-debian")] impl<'py> pyo3::IntoPyObject<'py> for Source { type Target = pyo3::PyAny; type Output = pyo3::Bound<'py, Self::Target>; type Error = pyo3::PyErr; fn into_pyobject(self, py: pyo3::Python<'py>) -> Result { use pyo3::prelude::*; let d = self.0.into_pyobject(py)?; let m = py.import("debian.deb822")?; let cls = m.getattr("Sources")?; cls.call1((d,)) } } #[cfg(feature = "python-debian")] impl<'a, 'py> pyo3::IntoPyObject<'py> for &'a Source { type Target = pyo3::PyAny; type Output = pyo3::Bound<'py, Self::Target>; type Error = pyo3::PyErr; fn into_pyobject(self, py: pyo3::Python<'py>) -> Result { use pyo3::prelude::*; let d = (&self.0).into_pyobject(py)?; let m = py.import("debian.deb822")?; let cls = m.getattr("Sources")?; cls.call1((d,)) } } #[cfg(feature = "python-debian")] impl<'py> pyo3::FromPyObject<'_, 'py> for Source { type Error = pyo3::PyErr; fn extract(ob: pyo3::Borrowed<'_, 'py, pyo3::PyAny>) -> Result { Ok(Source(ob.extract()?)) } } impl From for Source { fn from(paragraph: deb822_lossless::Paragraph) -> Self { Self(paragraph) } } impl Default for Source { fn default() -> Self { Self(deb822_lossless::Paragraph::new()) } } impl Source { /// Create a new source package pub fn new() -> Self { Self(deb822_lossless::Paragraph::new()) } /// Parse source package text, returning a Parse result /// /// Note: This expects a single paragraph, not a full deb822 document pub fn parse(text: &str) -> deb822_lossless::Parse { // We need to transform Parse to Parse // Since Source wraps a single Paragraph, we need custom logic let deb822_parse = deb822_lossless::Deb822::parse(text); // For now, we'll use the same green node but the cast will extract the first paragraph let green = deb822_parse.green().clone(); let mut errors = deb822_parse.errors().to_vec(); // Check if there's exactly one paragraph if errors.is_empty() { let deb822 = deb822_parse.tree(); let paragraph_count = deb822.paragraphs().count(); if paragraph_count == 0 { errors.push("No paragraphs found".to_string()); } else if paragraph_count > 1 { errors.push("Multiple paragraphs found, expected one".to_string()); } } deb822_lossless::Parse::new(green, errors) } /// Get the source name pub fn package(&self) -> Option { self.0.get("Package").map(|s| s.to_string()) } /// Set the package name pub fn set_package(&mut self, package: &str) { self.0.set("Package", package); } /// Get the version of the package pub fn version(&self) -> Option { self.0.get("Version").map(|s| s.parse().unwrap()) } /// Set the version of the package pub fn set_version(&mut self, version: debversion::Version) { self.0.set("Version", &version.to_string()); } /// Get the maintainer of the package pub fn maintainer(&self) -> Option { self.0.get("Maintainer").map(|s| s.to_string()) } /// Set the maintainer of the package pub fn set_maintainer(&mut self, maintainer: &str) { self.0.set("Maintainer", maintainer); } /// Get the uploaders of the package pub fn uploaders(&self) -> Option> { self.0.get("Uploaders").map(|s| { s.split(',') .map(|s| s.trim().to_string()) .collect::>() }) } /// Set the uploaders of the package pub fn set_uploaders(&mut self, uploaders: Vec) { self.0.set("Uploaders", &uploaders.join(", ")); } /// Get the standards version of the package pub fn standards_version(&self) -> Option { self.0.get("Standards-Version").map(|s| s.to_string()) } /// Set the standards version of the package pub fn set_standards_version(&mut self, version: &str) { self.0.set("Standards-Version", version); } /// Get the source format of the package pub fn format(&self) -> Option { self.0.get("Format").map(|s| s.to_string()) } /// Set the format of the package pub fn set_format(&mut self, format: &str) { self.0.set("Format", format); } /// Get the Vcs-Browser field pub fn vcs_browser(&self) -> Option { self.0.get("Vcs-Browser").map(|s| s.to_string()) } /// Set the Vcs-Browser field pub fn set_vcs_browser(&mut self, url: &str) { self.0.set("Vcs-Browser", url); } /// Get the Vcs-Git field pub fn vcs_git(&self) -> Option { self.0.get("Vcs-Git").map(|s| s.to_string()) } /// Set the Vcs-Git field pub fn set_vcs_git(&mut self, url: &str) { self.0.set("Vcs-Git", url); } /// Get the Vcs-Svn field pub fn vcs_svn(&self) -> Option { self.0.get("Vcs-Svn").map(|s| s.to_string()) } /// Set the Vcs-Svn field pub fn set_vcs_svn(&mut self, url: &str) { self.0.set("Vcs-Svn", url); } /// Get the Vcs-Hg field pub fn vcs_hg(&self) -> Option { self.0.get("Vcs-Hg").map(|s| s.to_string()) } /// Set the Vcs-Hg field pub fn set_vcs_hg(&mut self, url: &str) { self.0.set("Vcs-Hg", url); } /// Get the Vcs-Bzr field pub fn vcs_bzr(&self) -> Option { self.0.get("Vcs-Bzr").map(|s| s.to_string()) } /// Set the Vcs-Bzr field pub fn set_vcs_bzr(&mut self, url: &str) { self.0.set("Vcs-Bzr", url); } /// Get the Vcs-Arch field pub fn vcs_arch(&self) -> Option { self.0.get("Vcs-Arch").map(|s| s.to_string()) } /// Set the Vcs-Arch field pub fn set_vcs_arch(&mut self, url: &str) { self.0.set("Vcs-Arch", url); } /// Get the Vcs-Svk field pub fn vcs_svk(&self) -> Option { self.0.get("Vcs-Svk").map(|s| s.to_string()) } /// Set the Svk VCS pub fn set_vcs_svk(&mut self, url: &str) { self.0.set("Vcs-Svk", url); } /// Get the Darcs VCS pub fn vcs_darcs(&self) -> Option { self.0.get("Vcs-Darcs").map(|s| s.to_string()) } /// Set the Darcs VCS pub fn set_vcs_darcs(&mut self, url: &str) { self.0.set("Vcs-Darcs", url); } /// Get the Mtn VCS pub fn vcs_mtn(&self) -> Option { self.0.get("Vcs-Mtn").map(|s| s.to_string()) } /// Set the Mtn VCS pub fn set_vcs_mtn(&mut self, url: &str) { self.0.set("Vcs-Mtn", url); } /// Get the Cvs VCS pub fn vcs_cvs(&self) -> Option { self.0.get("Vcs-Cvs").map(|s| s.to_string()) } /// Set the Cvs VCS pub fn set_vcs_cvs(&mut self, url: &str) { self.0.set("Vcs-Cvs", url); } /// Get the build depends pub fn build_depends(&self) -> Option { self.0.get("Build-Depends").map(|s| s.parse().unwrap()) } /// Set the build depends pub fn set_build_depends(&mut self, relations: Relations) { self.0.set("Build-Depends", relations.to_string().as_str()); } /// Get the arch-independent build depends pub fn build_depends_indep(&self) -> Option { self.0 .get("Build-Depends-Indep") .map(|s| s.parse().unwrap()) } /// Set the arch-independent build depends pub fn set_build_depends_indep(&mut self, relations: Relations) { self.0.set("Build-Depends-Indep", &relations.to_string()); } /// Get the arch-dependent build depends pub fn build_depends_arch(&self) -> Option { self.0.get("Build-Depends-Arch").map(|s| s.parse().unwrap()) } /// Set the arch-dependent build depends pub fn set_build_depends_arch(&mut self, relations: Relations) { self.0.set("Build-Depends-Arch", &relations.to_string()); } /// Get the build conflicts pub fn build_conflicts(&self) -> Option { self.0.get("Build-Conflicts").map(|s| s.parse().unwrap()) } /// Set the build conflicts pub fn set_build_conflicts(&mut self, relations: Relations) { self.0.set("Build-Conflicts", &relations.to_string()); } /// Get the build conflicts indep pub fn build_conflicts_indep(&self) -> Option { self.0 .get("Build-Conflicts-Indep") .map(|s| s.parse().unwrap()) } /// Set the build conflicts indep pub fn set_build_conflicts_indep(&mut self, relations: Relations) { self.0.set("Build-Conflicts-Indep", &relations.to_string()); } /// Get the build conflicts arch pub fn build_conflicts_arch(&self) -> Option { self.0 .get("Build-Conflicts-Arch") .map(|s| s.parse().unwrap()) } /// Set the build conflicts arch pub fn set_build_conflicts_arch(&mut self, relations: Relations) { self.0.set("Build-Conflicts-Arch", &relations.to_string()); } /// Get the binary relations pub fn binary(&self) -> Option { self.0.get("Binary").map(|s| s.parse().unwrap()) } /// Set the binary relations pub fn set_binary(&mut self, relations: Relations) { self.0.set("Binary", &relations.to_string()); } /// Get the homepage of the package. pub fn homepage(&self) -> Option { self.0.get("Homepage").map(|s| s.to_string()) } /// Set the homepage of the package. pub fn set_homepage(&mut self, url: &str) { self.0.set("Homepage", url); } /// Get the section of the package. pub fn section(&self) -> Option { self.0.get("Section").map(|s| s.to_string()) } /// Set the section of the package. pub fn set_section(&mut self, section: &str) { self.0.set("Section", section); } /// Get the priority of the package. pub fn priority(&self) -> Option { self.0.get("Priority").and_then(|v| v.parse().ok()) } /// Set the priority of the package. pub fn set_priority(&mut self, priority: Priority) { self.0.set("Priority", priority.to_string().as_str()); } /// The architecture of the package. pub fn architecture(&self) -> Option { self.0.get("Architecture") } /// Set the architecture of the package. pub fn set_architecture(&mut self, arch: &str) { self.0.set("Architecture", arch); } /// Get the directory pub fn directory(&self) -> Option { self.0.get("Directory").map(|s| s.to_string()) } /// Set the directory pub fn set_directory(&mut self, dir: &str) { self.0.set("Directory", dir); } /// Get the test suite pub fn testsuite(&self) -> Option { self.0.get("Testsuite").map(|s| s.to_string()) } /// Set the testsuite pub fn set_testsuite(&mut self, testsuite: &str) { self.0.set("Testsuite", testsuite); } /// Get the files pub fn files(&self) -> Vec { self.0 .get("Files") .map(|s| { s.lines() .map(|line| line.parse().unwrap()) .collect::>() }) .unwrap_or_default() } /// Set the files pub fn set_files(&mut self, files: Vec) { self.0.set( "Files", &files .iter() .map(|f| f.to_string()) .collect::>() .join("\n"), ); } /// Get the SHA1 checksums pub fn checksums_sha1(&self) -> Vec { self.0 .get("Checksums-Sha1") .map(|s| { s.lines() .map(|line| line.parse().unwrap()) .collect::>() }) .unwrap_or_default() } /// Set the SHA1 checksums pub fn set_checksums_sha1(&mut self, checksums: Vec) { self.0.set( "Checksums-Sha1", &checksums .iter() .map(|c| c.to_string()) .collect::>() .join("\n"), ); } /// Get the SHA256 checksums pub fn checksums_sha256(&self) -> Vec { self.0 .get("Checksums-Sha256") .map(|s| { s.lines() .map(|line| line.parse().unwrap()) .collect::>() }) .unwrap_or_default() } /// Set the SHA256 checksums pub fn set_checksums_sha256(&mut self, checksums: Vec) { self.0.set( "Checksums-Sha256", &checksums .iter() .map(|c| c.to_string()) .collect::>() .join("\n"), ); } /// Get the SHA512 checksums pub fn checksums_sha512(&self) -> Vec { self.0 .get("Checksums-Sha512") .map(|s| { s.lines() .map(|line| line.parse().unwrap()) .collect::>() }) .unwrap_or_default() } /// Set the SHA512 checksums pub fn set_checksums_sha512(&mut self, checksums: Vec) { self.0.set( "Checksums-Sha512", &checksums .iter() .map(|c| c.to_string()) .collect::>() .join("\n"), ); } } impl std::str::FromStr for Source { type Err = deb822_lossless::ParseError; fn from_str(s: &str) -> Result { Source::parse(s).to_result() } } impl AstNode for Source { type Language = deb822_lossless::Lang; fn can_cast(kind: ::Kind) -> bool { // Source can be cast from either a Paragraph or a Deb822 (taking first paragraph) deb822_lossless::Paragraph::can_cast(kind) || deb822_lossless::Deb822::can_cast(kind) } fn cast(syntax: rowan::SyntaxNode) -> Option { // Try to cast as Paragraph first if let Some(para) = deb822_lossless::Paragraph::cast(syntax.clone()) { Some(Source(para)) } else if let Some(deb822) = deb822_lossless::Deb822::cast(syntax) { // If it's a Deb822, take the first paragraph deb822.paragraphs().next().map(Source) } else { None } } fn syntax(&self) -> &rowan::SyntaxNode { self.0.syntax() } } /// A package in the APT package manager. #[derive(Debug, Clone, PartialEq, Eq)] pub struct Package(deb822_lossless::Paragraph); #[cfg(feature = "python-debian")] impl<'py> pyo3::IntoPyObject<'py> for Package { type Target = pyo3::PyAny; type Output = pyo3::Bound<'py, Self::Target>; type Error = pyo3::PyErr; fn into_pyobject(self, py: pyo3::Python<'py>) -> Result { use pyo3::prelude::*; let d = self.0.into_pyobject(py)?; let m = py.import("debian.deb822")?; let cls = m.getattr("Packages")?; cls.call1((d,)) } } #[cfg(feature = "python-debian")] impl<'a, 'py> pyo3::IntoPyObject<'py> for &'a Package { type Target = pyo3::PyAny; type Output = pyo3::Bound<'py, Self::Target>; type Error = pyo3::PyErr; fn into_pyobject(self, py: pyo3::Python<'py>) -> Result { use pyo3::prelude::*; let d = (&self.0).into_pyobject(py)?; let m = py.import("debian.deb822")?; let cls = m.getattr("Packages")?; cls.call1((d,)) } } #[cfg(feature = "python-debian")] impl<'py> pyo3::FromPyObject<'_, 'py> for Package { type Error = pyo3::PyErr; fn extract(ob: pyo3::Borrowed<'_, 'py, pyo3::PyAny>) -> Result { Ok(Package(ob.extract()?)) } } impl Package { /// Create a new package. pub fn new(paragraph: deb822_lossless::Paragraph) -> Self { Self(paragraph) } /// Parse package text, returning a Parse result /// /// Note: This expects a single paragraph, not a full deb822 document pub fn parse(text: &str) -> deb822_lossless::Parse { let deb822_parse = deb822_lossless::Deb822::parse(text); let green = deb822_parse.green().clone(); let mut errors = deb822_parse.errors().to_vec(); // Check if there's exactly one paragraph if errors.is_empty() { let deb822 = deb822_parse.tree(); let paragraph_count = deb822.paragraphs().count(); if paragraph_count == 0 { errors.push("No paragraphs found".to_string()); } else if paragraph_count > 1 { errors.push("Multiple paragraphs found, expected one".to_string()); } } deb822_lossless::Parse::new(green, errors) } /// Get the name of the package. pub fn name(&self) -> Option { self.0.get("Package").map(|s| s.to_string()) } /// Set the name of the package. pub fn set_name(&mut self, name: &str) { self.0.set("Package", name); } /// Get the version of the package. pub fn version(&self) -> Option { self.0.get("Version").map(|s| s.parse().unwrap()) } /// Set the version of the package. pub fn set_version(&mut self, version: debversion::Version) { self.0.set("Version", &version.to_string()); } /// Get the installed size of the package in bytes. pub fn installed_size(&self) -> Option { self.0.get("Installed-Size").map(|s| s.parse().unwrap()) } /// Set the installed size of the package in bytes. pub fn set_installed_size(&mut self, size: usize) { self.0.set("Installed-Size", &size.to_string()); } /// Get the maintainer of the package. pub fn maintainer(&self) -> Option { self.0.get("Maintainer").map(|s| s.to_string()) } /// Set the maintainer of the package. pub fn set_maintainer(&mut self, maintainer: &str) { self.0.set("Maintainer", maintainer); } /// Get the architecture of the package. pub fn architecture(&self) -> Option { self.0.get("Architecture").map(|s| s.to_string()) } /// Set the architecture of the package. pub fn set_architecture(&mut self, arch: &str) { self.0.set("Architecture", arch); } /// Get the packages that this package depends on. pub fn depends(&self) -> Option { self.0.get("Depends").map(|s| s.parse().unwrap()) } /// Set the packages that this package depends on. pub fn set_depends(&mut self, relations: Relations) { self.0.set("Depends", &relations.to_string()); } /// Get the packages that this package suggests. pub fn recommends(&self) -> Option { self.0.get("Recommends").map(|s| s.parse().unwrap()) } /// Set the packages that this package recommends. pub fn set_recommends(&mut self, relations: Relations) { self.0.set("Recommends", &relations.to_string()); } /// Get the packages that this package suggests. pub fn suggests(&self) -> Option { self.0.get("Suggests").map(|s| s.parse().unwrap()) } /// Set the packages that this package suggests. pub fn set_suggests(&mut self, relations: Relations) { self.0.set("Suggests", &relations.to_string()); } /// Get the packages that this package enhances. pub fn enhances(&self) -> Option { self.0.get("Enhances").map(|s| s.parse().unwrap()) } /// Set the packages that this package enhances. pub fn set_enhances(&mut self, relations: Relations) { self.0.set("Enhances", &relations.to_string()); } /// Get the relations that this package pre-depends on. pub fn pre_depends(&self) -> Option { self.0.get("Pre-Depends").map(|s| s.parse().unwrap()) } /// Set the relations that this package pre-depends on. pub fn set_pre_depends(&mut self, relations: Relations) { self.0.set("Pre-Depends", &relations.to_string()); } /// Get the relations that this package breaks. pub fn breaks(&self) -> Option { self.0.get("Breaks").map(|s| s.parse().unwrap()) } /// Set the relations that this package breaks. pub fn set_breaks(&mut self, relations: Relations) { self.0.set("Breaks", &relations.to_string()); } /// Get the relations that this package conflicts with. pub fn conflicts(&self) -> Option { self.0.get("Conflicts").map(|s| s.parse().unwrap()) } /// Set the relations that this package conflicts with. pub fn set_conflicts(&mut self, relations: Relations) { self.0.set("Conflicts", &relations.to_string()); } /// Get the relations that this package replaces. pub fn replaces(&self) -> Option { self.0.get("Replaces").map(|s| s.parse().unwrap()) } /// Set the relations that this package replaces. pub fn set_replaces(&mut self, relations: Relations) { self.0.set("Replaces", &relations.to_string()); } /// Get the relations that this package provides. pub fn provides(&self) -> Option { self.0.get("Provides").map(|s| s.parse().unwrap()) } /// Set the relations that the package provides. pub fn set_provides(&mut self, relations: Relations) { self.0.set("Provides", &relations.to_string()); } /// Get the section of the package. pub fn section(&self) -> Option { self.0.get("Section").map(|s| s.to_string()) } /// Set the section of the package. pub fn set_section(&mut self, section: &str) { self.0.set("Section", section); } /// Get the priority of the package. pub fn priority(&self) -> Option { self.0.get("Priority").and_then(|v| v.parse().ok()) } /// Set the priority of the package. pub fn set_priority(&mut self, priority: Priority) { self.0.set("Priority", priority.to_string().as_str()); } /// Get the description of the package. pub fn description(&self) -> Option { self.0.get_multiline("Description") } /// Set the description of the package. pub fn set_description(&mut self, description: &str) { self.0.set("Description", description); } /// Get the upstream homepage of the package. pub fn homepage(&self) -> Option { self.0.get("Homepage").map(|s| s.parse().unwrap()) } /// Set the upstream homepage of the package. pub fn set_homepage(&mut self, url: &url::Url) { self.0.set("Homepage", url.as_ref()); } /// Get the source of the package. pub fn source(&self) -> Option { self.0.get("Source").map(|s| s.to_string()) } /// Set the source of the package. pub fn set_source(&mut self, source: &str) { self.0.set("Source", source); } /// Get the MD5 checksum of the description. pub fn description_md5(&self) -> Option { self.0.get("Description-md5").map(|s| s.to_string()) } /// Set the MD5 checksum of the description. pub fn set_description_md5(&mut self, md5: &str) { self.0.set("Description-md5", md5); } /// Get the tags of the package. pub fn tags(&self, tag: &str) -> Option> { self.0 .get(tag) .map(|s| s.split(',').map(|s| s.trim().to_string()).collect()) } /// Set the tags of the package. pub fn set_tags(&mut self, tag: &str, tags: Vec) { self.0.set(tag, &tags.join(", ")); } /// Get the filename of the package. pub fn filename(&self) -> Option { self.0.get("Filename").map(|s| s.to_string()) } /// Set the filename of the package. pub fn set_filename(&mut self, filename: &str) { self.0.set("Filename", filename); } /// Get the size of the package. pub fn size(&self) -> Option { self.0.get("Size").map(|s| s.parse().unwrap()) } /// Set the size of the package. pub fn set_size(&mut self, size: usize) { self.0.set("Size", &size.to_string()); } /// Get the MD5 checksum. pub fn md5sum(&self) -> Option { self.0.get("MD5sum").map(|s| s.to_string()) } /// Set the MD5 checksum. pub fn set_md5sum(&mut self, md5sum: &str) { self.0.set("MD5sum", md5sum); } /// Get the SHA256 checksum. pub fn sha256(&self) -> Option { self.0.get("SHA256").map(|s| s.to_string()) } /// Set the SHA256 checksum. pub fn set_sha256(&mut self, sha256: &str) { self.0.set("SHA256", sha256); } /// Get the multi-arch field. pub fn multi_arch(&self) -> Option { self.0.get("Multi-Arch").map(|s| s.parse().unwrap()) } /// Set the multi-arch field. pub fn set_multi_arch(&mut self, arch: MultiArch) { self.0.set("Multi-Arch", arch.to_string().as_str()); } } impl std::str::FromStr for Package { type Err = deb822_lossless::ParseError; fn from_str(s: &str) -> Result { Package::parse(s).to_result() } } impl AstNode for Package { type Language = deb822_lossless::Lang; fn can_cast(kind: ::Kind) -> bool { deb822_lossless::Paragraph::can_cast(kind) || deb822_lossless::Deb822::can_cast(kind) } fn cast(syntax: rowan::SyntaxNode) -> Option { if let Some(para) = deb822_lossless::Paragraph::cast(syntax.clone()) { Some(Package(para)) } else if let Some(deb822) = deb822_lossless::Deb822::cast(syntax) { deb822.paragraphs().next().map(Package) } else { None } } fn syntax(&self) -> &rowan::SyntaxNode { self.0.syntax() } } /// A release in the APT package manager. pub struct Release(deb822_lossless::Paragraph); #[cfg(feature = "python-debian")] impl<'py> pyo3::IntoPyObject<'py> for Release { type Target = pyo3::PyAny; type Output = pyo3::Bound<'py, Self::Target>; type Error = pyo3::PyErr; fn into_pyobject(self, py: pyo3::Python<'py>) -> Result { use pyo3::prelude::*; let d = self.0.into_pyobject(py)?; let m = py.import("debian.deb822")?; let cls = m.getattr("Release")?; cls.call1((d,)) } } #[cfg(feature = "python-debian")] impl<'a, 'py> pyo3::IntoPyObject<'py> for &'a Release { type Target = pyo3::PyAny; type Output = pyo3::Bound<'py, Self::Target>; type Error = pyo3::PyErr; fn into_pyobject(self, py: pyo3::Python<'py>) -> Result { use pyo3::prelude::*; let d = (&self.0).into_pyobject(py)?; let m = py.import("debian.deb822")?; let cls = m.getattr("Release")?; cls.call1((d,)) } } #[cfg(feature = "python-debian")] impl<'py> pyo3::FromPyObject<'_, 'py> for Release { type Error = pyo3::PyErr; fn extract(ob: pyo3::Borrowed<'_, 'py, pyo3::PyAny>) -> Result { Ok(Release(ob.extract()?)) } } impl Release { /// Create a new release pub fn new(paragraph: deb822_lossless::Paragraph) -> Self { Self(paragraph) } /// Parse release text, returning a Parse result /// /// Note: This expects a single paragraph, not a full deb822 document pub fn parse(text: &str) -> deb822_lossless::Parse { let deb822_parse = deb822_lossless::Deb822::parse(text); let green = deb822_parse.green().clone(); let mut errors = deb822_parse.errors().to_vec(); // Check if there's exactly one paragraph if errors.is_empty() { let deb822 = deb822_parse.tree(); let paragraph_count = deb822.paragraphs().count(); if paragraph_count == 0 { errors.push("No paragraphs found".to_string()); } else if paragraph_count > 1 { errors.push("Multiple paragraphs found, expected one".to_string()); } } deb822_lossless::Parse::new(green, errors) } /// Get the origin of the release pub fn origin(&self) -> Option { self.0.get("Origin").map(|s| s.to_string()) } /// Set the origin of the release pub fn set_origin(&mut self, origin: &str) { self.0.set("Origin", origin); } /// Get the label of the release pub fn label(&self) -> Option { self.0.get("Label").map(|s| s.to_string()) } /// Set the label of the release pub fn set_label(&mut self, label: &str) { self.0.set("Label", label); } /// Get the suite of the release pub fn suite(&self) -> Option { self.0.get("Suite").map(|s| s.to_string()) } /// Set the suite of the release pub fn set_suite(&mut self, suite: &str) { self.0.set("Suite", suite); } /// Get the codename of the release pub fn codename(&self) -> Option { self.0.get("Codename").map(|s| s.to_string()) } /// Set the codename of the release pub fn set_codename(&mut self, codename: &str) { self.0.set("Codename", codename); } /// Get the URLs at which the changelogs can be found pub fn changelogs(&self) -> Option> { self.0.get("Changelogs").map(|s| { s.split(',') .map(|s| s.trim().to_string()) .collect::>() }) } /// Set the URLs at which the changelogs can be found pub fn set_changelogs(&mut self, changelogs: Vec) { self.0.set("Changelogs", &changelogs.join(", ")); } #[cfg(feature = "chrono")] /// Get the date of the release pub fn date(&self) -> Option> { self.0 .get("Date") .as_ref() .map(|s| chrono::DateTime::parse_from_rfc2822(s).unwrap()) } #[cfg(feature = "chrono")] /// Set the date of the release pub fn set_date(&mut self, date: chrono::DateTime) { self.0.set("Date", date.to_rfc2822().as_str()); } #[cfg(feature = "chrono")] /// Get the date until the release is valid pub fn valid_until(&self) -> Option> { self.0 .get("Valid-Until") .as_ref() .map(|s| chrono::DateTime::parse_from_rfc2822(s).unwrap()) } #[cfg(feature = "chrono")] /// Set the date until the release is valid pub fn set_valid_until(&mut self, date: chrono::DateTime) { self.0.set("Valid-Until", date.to_rfc2822().as_str()); } /// Get whether acquire by hash is enabled pub fn acquire_by_hash(&self) -> bool { self.0 .get("Acquire-By-Hash") .map(|s| s == "yes") .unwrap_or(false) } /// Set whether acquire by hash is enabled pub fn set_acquire_by_hash(&mut self, acquire_by_hash: bool) { self.0.set( "Acquire-By-Hash", if acquire_by_hash { "yes" } else { "no" }, ); } /// Get whether the release has no support for architecture all pub fn no_support_for_architecture_all(&self) -> bool { self.0 .get("No-Support-For-Architecture-All") .map(|s| s == "yes") .unwrap_or(false) } /// Set whether the release has no support for architecture all pub fn set_no_support_for_architecture_all(&mut self, no_support_for_architecture_all: bool) { self.0.set( "No-Support-For-Architecture-All", if no_support_for_architecture_all { "yes" } else { "no" }, ); } /// Get the architectures pub fn architectures(&self) -> Option> { self.0.get("Architectures").map(|s| { s.split_whitespace() .map(|s| s.trim().to_string()) .collect::>() }) } /// Set the architectures pub fn set_architectures(&mut self, architectures: Vec) { self.0.set("Architectures", &architectures.join(" ")); } /// Get the components pub fn components(&self) -> Option> { self.0.get("Components").map(|s| { s.split_whitespace() .map(|s| s.trim().to_string()) .collect::>() }) } /// Set the components pub fn set_components(&mut self, components: Vec) { self.0.set("Components", &components.join(" ")); } /// Get the description pub fn description(&self) -> Option { self.0.get_multiline("Description") } /// Set the description pub fn set_description(&mut self, description: &str) { self.0.set("Description", description); } /// Get the MD5 checksums pub fn checksums_md5(&self) -> Vec { self.0 .get("MD5Sum") .map(|s| { s.lines() .map(|line| line.parse().unwrap()) .collect::>() }) .unwrap_or_default() } /// Set the MD5 checksums pub fn set_checksums_md5(&mut self, files: Vec) { self.0.set( "MD5Sum", &files .iter() .map(|f| f.to_string()) .collect::>() .join("\n"), ); } /// Get the SHA1 checksums pub fn checksums_sha1(&self) -> Vec { self.0 .get("SHA1") .map(|s| { s.lines() .map(|line| line.parse().unwrap()) .collect::>() }) .unwrap_or_default() } /// Set the SHA1 checksums pub fn set_checksums_sha1(&mut self, checksums: Vec) { self.0.set( "SHA1", &checksums .iter() .map(|c| c.to_string()) .collect::>() .join("\n"), ); } /// Get the SHA256 checksums pub fn checksums_sha256(&self) -> Vec { self.0 .get("SHA256") .map(|s| { s.lines() .map(|line| line.parse().unwrap()) .collect::>() }) .unwrap_or_default() } /// Set the SHA256 checksums pub fn set_checksums_sha256(&mut self, checksums: Vec) { self.0.set( "SHA256", &checksums .iter() .map(|c| c.to_string()) .collect::>() .join("\n"), ); } /// Get the SHA512 checksums pub fn checksums_sha512(&self) -> Vec { self.0 .get("SHA512") .map(|s| { s.lines() .map(|line| line.parse().unwrap()) .collect::>() }) .unwrap_or_default() } /// Set the SHA512 checksums pub fn set_checksums_sha512(&mut self, checksums: Vec) { self.0.set( "SHA512", &checksums .iter() .map(|c| c.to_string()) .collect::>() .join("\n"), ); } } impl std::str::FromStr for Release { type Err = deb822_lossless::ParseError; fn from_str(s: &str) -> Result { Release::parse(s).to_result() } } impl AstNode for Release { type Language = deb822_lossless::Lang; fn can_cast(kind: ::Kind) -> bool { deb822_lossless::Paragraph::can_cast(kind) || deb822_lossless::Deb822::can_cast(kind) } fn cast(syntax: rowan::SyntaxNode) -> Option { if let Some(para) = deb822_lossless::Paragraph::cast(syntax.clone()) { Some(Release(para)) } else if let Some(deb822) = deb822_lossless::Deb822::cast(syntax) { deb822.paragraphs().next().map(Release) } else { None } } fn syntax(&self) -> &rowan::SyntaxNode { self.0.syntax() } } #[cfg(test)] mod tests { use super::*; use crate::fields::PackageListEntry; #[test] fn test_parse_package_list() { let s = "package1 binary section standard extra1=foo extra2=bar"; let p: PackageListEntry = s.parse().unwrap(); assert_eq!(p.package, "package1"); assert_eq!(p.package_type, "binary"); assert_eq!(p.section, "section"); assert_eq!(p.priority, super::Priority::Standard); assert_eq!(p.extra.get("extra1"), Some(&"foo".to_string())); assert_eq!(p.extra.get("extra2"), Some(&"bar".to_string())); } #[test] fn test_parse_package_list_no_extra() { let s = "package1 binary section standard"; let p: PackageListEntry = s.parse().unwrap(); assert_eq!(p.package, "package1"); assert_eq!(p.package_type, "binary"); assert_eq!(p.section, "section"); assert_eq!(p.priority, super::Priority::Standard); assert!(p.extra.is_empty()); } #[test] fn test_files() { let s = "md5sum 1234 filename"; let f: super::Md5Checksum = s.parse().unwrap(); assert_eq!(f.md5sum, "md5sum"); assert_eq!(f.size, 1234); assert_eq!(f.filename, "filename"); } #[test] fn test_sha1_checksum() { let s = "sha1 1234 filename"; let f: super::Sha1Checksum = s.parse().unwrap(); assert_eq!(f.sha1, "sha1"); assert_eq!(f.size, 1234); assert_eq!(f.filename, "filename"); } #[test] fn test_sha256_checksum() { let s = "sha256 1234 filename"; let f: super::Sha256Checksum = s.parse().unwrap(); assert_eq!(f.sha256, "sha256"); assert_eq!(f.size, 1234); assert_eq!(f.filename, "filename"); } #[test] fn test_sha512_checksum() { let s = "sha512 1234 filename"; let f: super::Sha512Checksum = s.parse().unwrap(); assert_eq!(f.sha512, "sha512"); assert_eq!(f.size, 1234); assert_eq!(f.filename, "filename"); } #[test] fn test_source() { let s = r#"Package: foo Version: 1.0 Maintainer: John Doe Uploaders: Jane Doe Standards-Version: 3.9.8 Format: 3.0 (quilt) Vcs-Browser: https://example.com/foo Vcs-Git: https://example.com/foo.git Build-Depends: debhelper (>= 9) Build-Depends-Indep: python Build-Depends-Arch: gcc Build-Conflicts: bar Build-Conflicts-Indep: python Build-Conflicts-Arch: gcc Binary: foo, bar Homepage: https://example.com/foo Section: devel Priority: optional Architecture: any Directory: pool/main/f/foo Files: 25dcf3b4b6b3b3b3b3b3b3b3b3b3b3b3 1234 foo_1.0.tar.gz Checksums-Sha1: b72b5fae3b3b3b3b3b3b3b3b3b3b3b3 1234 foo_1.0.tar.gz "#; let p: super::Source = s.parse().unwrap(); assert_eq!(p.package(), Some("foo".to_string())); assert_eq!(p.version(), Some("1.0".parse().unwrap())); assert_eq!( p.maintainer(), Some("John Doe ".to_string()) ); assert_eq!( p.uploaders(), Some(vec!["Jane Doe ".to_string()]) ); assert_eq!(p.standards_version(), Some("3.9.8".to_string())); assert_eq!(p.format(), Some("3.0 (quilt)".to_string())); assert_eq!(p.vcs_browser(), Some("https://example.com/foo".to_string())); assert_eq!(p.vcs_git(), Some("https://example.com/foo.git".to_string())); assert_eq!( p.build_depends_indep().map(|x| x.to_string()), Some("python".parse().unwrap()) ); assert_eq!(p.build_depends(), Some("debhelper (>= 9)".parse().unwrap())); assert_eq!(p.build_depends_arch(), Some("gcc".parse().unwrap())); assert_eq!(p.build_conflicts(), Some("bar".parse().unwrap())); assert_eq!(p.build_conflicts_indep(), Some("python".parse().unwrap())); assert_eq!(p.build_conflicts_arch(), Some("gcc".parse().unwrap())); assert_eq!(p.binary(), Some("foo, bar".parse().unwrap())); assert_eq!(p.homepage(), Some("https://example.com/foo".to_string())); assert_eq!(p.section(), Some("devel".to_string())); assert_eq!(p.priority(), Some(super::Priority::Optional)); assert_eq!(p.architecture(), Some("any".to_string())); assert_eq!(p.directory(), Some("pool/main/f/foo".to_string())); assert_eq!(p.files().len(), 1); assert_eq!( p.files()[0].md5sum, "25dcf3b4b6b3b3b3b3b3b3b3b3b3b3b3".to_string() ); assert_eq!(p.files()[0].size, 1234); assert_eq!(p.files()[0].filename, "foo_1.0.tar.gz".to_string()); assert_eq!(p.checksums_sha1().len(), 1); assert_eq!( p.checksums_sha1()[0].sha1, "b72b5fae3b3b3b3b3b3b3b3b3b3b3b3".to_string() ); } #[test] fn test_package() { let s = r#"Package: foo Version: 1.0 Source: bar Maintainer: John Doe Architecture: any Depends: bar Recommends: baz Suggests: qux Enhances: quux Pre-Depends: quuz Breaks: corge Conflicts: grault Replaces: garply Provides: waldo Section: devel Priority: optional Description: Foo is a bar Homepage: https://example.com/foo Description-md5: 1234 Tags: foo, bar Filename: pool/main/f/foo/foo_1.0.deb Size: 1234 Installed-Size: 1234 MD5sum: 1234 SHA256: 1234 Multi-Arch: same "#; let p: super::Package = s.parse().unwrap(); assert_eq!(p.name(), Some("foo".to_string())); assert_eq!(p.version(), Some("1.0".parse().unwrap())); assert_eq!(p.source(), Some("bar".to_string())); assert_eq!( p.maintainer(), Some("John Doe ".to_string()) ); assert_eq!(p.architecture(), Some("any".to_string())); assert_eq!(p.depends(), Some("bar".parse().unwrap())); assert_eq!(p.recommends(), Some("baz".parse().unwrap())); assert_eq!(p.suggests(), Some("qux".parse().unwrap())); assert_eq!(p.enhances(), Some("quux".parse().unwrap())); assert_eq!(p.pre_depends(), Some("quuz".parse().unwrap())); assert_eq!(p.breaks(), Some("corge".parse().unwrap())); assert_eq!(p.conflicts(), Some("grault".parse().unwrap())); assert_eq!(p.replaces(), Some("garply".parse().unwrap())); assert_eq!(p.provides(), Some("waldo".parse().unwrap())); assert_eq!(p.section(), Some("devel".to_string())); assert_eq!(p.priority(), Some(super::Priority::Optional)); assert_eq!(p.description(), Some("Foo is a bar".to_string())); assert_eq!( p.homepage(), Some(url::Url::parse("https://example.com/foo").unwrap()) ); assert_eq!(p.description_md5(), Some("1234".to_string())); assert_eq!( p.tags("Tags"), Some(vec!["foo".to_string(), "bar".to_string()]) ); assert_eq!( p.filename(), Some("pool/main/f/foo/foo_1.0.deb".to_string()) ); assert_eq!(p.size(), Some(1234)); assert_eq!(p.installed_size(), Some(1234)); assert_eq!(p.md5sum(), Some("1234".to_string())); assert_eq!(p.sha256(), Some("1234".to_string())); assert_eq!(p.multi_arch(), Some(MultiArch::Same)); } #[test] fn test_release() { let s = include_str!("../testdata/Release"); let release: super::Release = s.parse().unwrap(); assert_eq!(release.origin(), Some("Debian".to_string())); assert_eq!(release.label(), Some("Debian".to_string())); assert_eq!(release.suite(), Some("testing".to_string())); assert_eq!( release.architectures(), vec![ "all".to_string(), "amd64".to_string(), "arm64".to_string(), "armel".to_string(), "armhf".to_string() ] .into() ); assert_eq!( release.components(), vec![ "main".to_string(), "contrib".to_string(), "non-free-firmware".to_string(), "non-free".to_string() ] .into() ); assert_eq!( release.description(), Some("Debian x.y Testing distribution - Not Released".to_string()) ); assert_eq!(318, release.checksums_md5().len()); } #[test] fn test_source_vcs_arch() { let s = r#"Package: foo Version: 1.0 Vcs-Arch: https://example.com/arch/repo "#; let p: super::Source = s.parse().unwrap(); assert_eq!( p.vcs_arch(), Some("https://example.com/arch/repo".to_string()) ); } #[test] fn test_source_vcs_cvs() { let s = r#"Package: foo Version: 1.0 Vcs-Cvs: :pserver:anoncvs@example.com:/cvs/repo "#; let p: super::Source = s.parse().unwrap(); assert_eq!( p.vcs_cvs(), Some(":pserver:anoncvs@example.com:/cvs/repo".to_string()) ); } #[test] fn test_source_set_priority() { let s = r#"Package: foo Version: 1.0 Priority: optional "#; let mut p: super::Source = s.parse().unwrap(); p.set_priority(super::Priority::Required); assert_eq!(p.priority(), Some(super::Priority::Required)); } #[test] fn test_release_set_suite() { let s = r#"Origin: Debian Label: Debian Suite: testing "#; let mut release: super::Release = s.parse().unwrap(); release.set_suite("unstable"); assert_eq!(release.suite(), Some("unstable".to_string())); } #[test] fn test_release_codename() { let s = r#"Origin: Debian Label: Debian Suite: testing Codename: trixie "#; let release: super::Release = s.parse().unwrap(); assert_eq!(release.codename(), Some("trixie".to_string())); } #[test] fn test_release_set_checksums_sha512() { let s = r#"Origin: Debian Label: Debian Suite: testing "#; let mut release: super::Release = s.parse().unwrap(); let checksums = vec![super::Sha512Checksum { sha512: "abc123".to_string(), size: 1234, filename: "test.deb".to_string(), }]; release.set_checksums_sha512(checksums); assert_eq!(release.checksums_sha512().len(), 1); } #[test] fn test_package_set_replaces() { let s = r#"Package: foo Version: 1.0 "#; let mut p: super::Package = s.parse().unwrap(); let relations: Relations = "bar (>= 1.0.0)".parse().unwrap(); p.set_replaces(relations); assert_eq!(p.replaces(), Some("bar (>= 1.0.0)".parse().unwrap())); } } debian-control-0.2.14/src/lossless/buildinfo.rs000064400000000000000000000243131046102023000175720ustar 00000000000000//! Parser for Debian buildinfo files //! //! The buildinfo file format is a Debian-specific format that is used to store //! information about the build environment of a package. See for //! more information. use crate::fields::{Md5Checksum, Sha1Checksum, Sha256Checksum}; use crate::lossless::relations::Relations; use rowan::ast::AstNode; /// A buildinfo file pub struct Buildinfo(deb822_lossless::Paragraph); impl From for Buildinfo { fn from(paragraph: deb822_lossless::Paragraph) -> Self { Self(paragraph) } } impl Default for Buildinfo { fn default() -> Self { let mut para = deb822_lossless::Paragraph::new(); para.set("Format", "1.0"); Self(para) } } impl Buildinfo { /// Create a new source package pub fn new() -> Self { Self(deb822_lossless::Paragraph::new()) } /// Parse buildinfo text, returning a Parse result /// /// Note: This expects a single paragraph, not a full deb822 document pub fn parse(text: &str) -> deb822_lossless::Parse { let deb822_parse = deb822_lossless::Deb822::parse(text); let green = deb822_parse.green().clone(); let mut errors = deb822_parse.errors().to_vec(); // Check if there's exactly one paragraph if errors.is_empty() { let deb822 = deb822_parse.tree(); let paragraph_count = deb822.paragraphs().count(); if paragraph_count == 0 { errors.push("No paragraphs found".to_string()); } else if paragraph_count > 1 { errors.push("Multiple paragraphs found, expected one".to_string()); } } deb822_lossless::Parse::new(green, errors) } /// Get the source name pub fn source(&self) -> Option { self.0.get("Source").map(|s| s.to_string()) } /// Set the package name pub fn set_source(&mut self, package: &str) { self.0.set("Source", package); } /// Get the binary package names pub fn binaries(&self) -> Option> { self.0.get("Binary").map(|s| { s.split(' ') .map(|s| s.trim().to_string()) .collect::>() }) } /// Set the binary package names pub fn set_binaries(&mut self, binaries: Vec) { self.0.set("Binary", &binaries.join(" ")); } /// Get the version of the package pub fn version(&self) -> Option { self.0.get("Version").map(|s| s.parse().unwrap()) } /// Set the version of the package pub fn set_version(&mut self, version: debversion::Version) { self.0.set("Version", &version.to_string()); } /// Get the build architecture pub fn build_architecture(&self) -> Option { self.0.get("Build-Architecture").map(|s| s.to_string()) } /// Set the build architecture pub fn set_build_architecture(&mut self, arch: &str) { self.0.set("Build-Architecture", arch); } /// Get the architecture pub fn architecture(&self) -> Option { self.0.get("Architecture").map(|s| s.to_string()) } /// Set the architecture pub fn set_architecture(&mut self, arch: &str) { self.0.set("Architecture", arch); } /// Get Sha256 checksums pub fn checksums_sha256(&self) -> Vec { self.0 .get("Checksums-Sha256") .map(|s| { s.lines() .map(|line| line.parse().unwrap()) .collect::>() }) .unwrap_or_default() } /// Set Sha256 checksums pub fn set_checksums_sha256(&mut self, checksums: Vec) { self.0.set( "Checksums-Sha256", &checksums .iter() .map(|c| c.to_string()) .collect::>() .join("\n"), ); } /// Get SHA1 checksums pub fn checksums_sha1(&self) -> Vec { self.0 .get("Checksums-Sha1") .map(|s| { s.lines() .map(|line| line.parse().unwrap()) .collect::>() }) .unwrap_or_default() } /// Set SHA1 checksums pub fn set_checksums_sha1(&mut self, checksums: Vec) { self.0.set( "Checksums-Sha1", &checksums .iter() .map(|c| c.to_string()) .collect::>() .join("\n"), ); } /// Get MD5 checksums pub fn checksums_md5(&self) -> Vec { self.0 .get("Checksums-Md5") .map(|s| { s.lines() .map(|line| line.parse().unwrap()) .collect::>() }) .unwrap_or_default() } /// Set MD5 checksums pub fn set_checksums_md5(&mut self, checksums: Vec) { self.0.set( "Checksums-Md5", &checksums .iter() .map(|c| c.to_string()) .collect::>() .join("\n"), ); } /// Get the build origin pub fn build_origin(&self) -> Option { self.0.get("Build-Origin").map(|s| s.to_string()) } /// Set the build origin pub fn set_build_origin(&mut self, origin: &str) { self.0.set("Build-Origin", origin); } /// Date on which the package was built pub fn build_date(&self) -> Option { self.0.get("Build-Date").map(|s| s.to_string()) } /// Set the build date pub fn set_build_date(&mut self, date: &str) { self.0.set("Build-Date", date); } /// Get the build tainted by field list pub fn build_tainted_by(&self) -> Option> { self.0 .get("Build-Tainted-By") .map(|s| s.split(' ').map(|s| s.to_string()).collect()) } /// Set the build tainted by field list pub fn set_build_tainted_by(&mut self, tainted_by: Vec) { self.0.set("Build-Tainted-By", &tainted_by.join(" ")); } /// Get the source format of the package pub fn format(&self) -> Option { self.0.get("Format").map(|s| s.to_string()) } /// Set the format of the package pub fn set_format(&mut self, format: &str) { self.0.set("Format", format); } /// Get the build path pub fn build_path(&self) -> Option { self.0.get("Build-Path").map(|s| s.to_string()) } /// Set the build path pub fn set_build_path(&mut self, path: &str) { self.0.set("Build-Path", path); } /// Get the build environment pub fn environment(&self) -> Option> { self.0.get("Environment").map(|s| { s.lines() .map(|line| { let (key, value) = line.split_once('=').unwrap(); (key.to_string(), value.to_string()) }) .collect() }) } /// Set the build environment pub fn set_environment(&mut self, env: std::collections::HashMap) { let mut s = String::new(); for (key, value) in env { if !s.is_empty() { s.push('\n'); } s.push_str(&format!("{}={}", key, value)); } self.0.set("Environment", &s); } /// Get the list of installed build depends pub fn installed_build_depends(&self) -> Option { self.0 .get("Installed-Build-Depends") .map(|s| s.parse().unwrap()) } /// Set the list of installed build depends pub fn set_installed_build_depends(&mut self, depends: Relations) { self.0.set("Installed-Build-Depends", &depends.to_string()); } } impl std::str::FromStr for Buildinfo { type Err = deb822_lossless::ParseError; fn from_str(s: &str) -> Result { Buildinfo::parse(s).to_result() } } impl AstNode for Buildinfo { type Language = deb822_lossless::Lang; fn can_cast(kind: ::Kind) -> bool { deb822_lossless::Paragraph::can_cast(kind) || deb822_lossless::Deb822::can_cast(kind) } fn cast(syntax: rowan::SyntaxNode) -> Option { if let Some(para) = deb822_lossless::Paragraph::cast(syntax.clone()) { Some(Buildinfo(para)) } else if let Some(deb822) = deb822_lossless::Deb822::cast(syntax) { deb822.paragraphs().next().map(Buildinfo) } else { None } } fn syntax(&self) -> &rowan::SyntaxNode { self.0.syntax() } } #[cfg(test)] mod tests { use super::*; #[test] fn test_parse() { let s = include_str!("../../testdata/ruff.buildinfo"); let buildinfo: Buildinfo = s.parse().unwrap(); assert_eq!(buildinfo.format(), Some("1.0".to_string())); } #[test] fn test_set_environment() { let s = include_str!("../../testdata/ruff.buildinfo"); let mut buildinfo: Buildinfo = s.parse().unwrap(); let mut env = std::collections::HashMap::new(); env.insert("TEST_VAR".to_string(), "test_value".to_string()); env.insert("ANOTHER_VAR".to_string(), "another_value".to_string()); buildinfo.set_environment(env); let env_field = buildinfo.0.get("Environment").unwrap(); assert!(env_field.contains("TEST_VAR=test_value")); assert!(env_field.contains("ANOTHER_VAR=another_value")); } #[test] fn test_set_build_path() { let s = include_str!("../../testdata/ruff.buildinfo"); let mut buildinfo: Buildinfo = s.parse().unwrap(); buildinfo.set_build_path("/tmp/build/path"); assert_eq!(buildinfo.build_path(), Some("/tmp/build/path".to_string())); } #[test] fn test_build_origin() { let s = include_str!("../../testdata/ruff.buildinfo"); let buildinfo: Buildinfo = s.parse().unwrap(); assert_eq!(buildinfo.build_origin(), Some("Debian".to_string())); } } debian-control-0.2.14/src/lossless/changes.rs000064400000000000000000000344171046102023000172350ustar 00000000000000//! Changes files use rowan::ast::AstNode; /// Changes file #[derive(Debug, Clone, PartialEq, Eq)] pub struct Changes(deb822_lossless::Paragraph); /// Errors that can occur when parsing a Changes file. #[derive(Debug)] pub enum ParseError { /// An error occurred while parsing a Deb822 file. Deb822(deb822_lossless::Error), /// No paragraphs were found in the file. NoParagraphs, /// Multiple paragraphs were found in the file. MultipleParagraphs, } impl std::fmt::Display for ParseError { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { match self { Self::Deb822(e) => write!(f, "{}", e), Self::NoParagraphs => write!(f, "no paragraphs found"), Self::MultipleParagraphs => write!(f, "multiple paragraphs found"), } } } impl std::error::Error for ParseError {} impl From for ParseError { fn from(e: deb822_lossless::Error) -> Self { Self::Deb822(e) } } /// A file in a source package. #[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct File { /// MD5 checksum of the file. pub md5sum: String, /// Size of the file in bytes. pub size: usize, /// Section the file belongs to. pub section: String, /// Priority of the file. pub priority: crate::Priority, /// Filename of the file. pub filename: String, } impl std::fmt::Display for File { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!( f, "{} {} {} {} {}", self.md5sum, self.size, self.section, self.priority, self.filename ) } } impl std::str::FromStr for File { type Err = (); fn from_str(s: &str) -> Result { let mut parts = s.split_whitespace(); let md5sum = parts.next().ok_or(())?; let size = parts.next().ok_or(())?.parse().map_err(|_| ())?; let section = parts.next().ok_or(())?.to_string(); let priority = parts.next().ok_or(())?.parse().map_err(|_| ())?; let filename = parts.next().ok_or(())?.to_string(); Ok(Self { md5sum: md5sum.to_string(), size, section, priority, filename, }) } } impl Changes { /// Parse changes text, returning a Parse result /// /// Note: This expects a single paragraph, not a full deb822 document pub fn parse(text: &str) -> deb822_lossless::Parse { let deb822_parse = deb822_lossless::Deb822::parse(text); let green = deb822_parse.green().clone(); let mut errors = deb822_parse.errors().to_vec(); // Check if there's exactly one paragraph if errors.is_empty() { let deb822 = deb822_parse.tree(); let paragraph_count = deb822.paragraphs().count(); if paragraph_count == 0 { errors.push("No paragraphs found".to_string()); } else if paragraph_count > 1 { errors.push("Multiple paragraphs found, expected one".to_string()); } } deb822_lossless::Parse::new(green, errors) } /// Returns the format of the Changes file. pub fn format(&self) -> Option { self.0.get("Format").map(|s| s.to_string()) } /// Set the format of the Changes file. pub fn set_format(&mut self, value: &str) { self.0.set("Format", value); } /// Returns the name of the source package. pub fn source(&self) -> Option { self.0.get("Source").map(|s| s.to_string()) } /// Returns the list of binary packages generated by the source package. pub fn binary(&self) -> Option> { self.0 .get("Binary") .map(|s| s.split_whitespace().map(|s| s.to_string()).collect()) } /// Returns the architecture the source package is intended for. pub fn architecture(&self) -> Option> { self.0 .get("Architecture") .map(|s| s.split_whitespace().map(|s| s.to_string()).collect()) } /// Returns the version of the source package. pub fn version(&self) -> Option { self.0.get("Version").map(|s| s.parse().unwrap()) } /// Returns the distribution the source package is intended for. pub fn distribution(&self) -> Option { self.0.get("Distribution").map(|s| s.to_string()) } /// Returns the urgency of the source package. pub fn urgency(&self) -> Option { self.0.get("Urgency").map(|s| s.parse().unwrap()) } /// Returns the name and email address of the person who maintains the package. pub fn maintainer(&self) -> Option { self.0.get("Maintainer").map(|s| s.to_string()) } /// Returns the name and email address of the person who uploaded the package. pub fn changed_by(&self) -> Option { self.0.get("Changed-By").map(|s| s.to_string()) } /// Returns the description of the source package. pub fn description(&self) -> Option { self.0.get_multiline("Description") } /// Returns the SHA-1 checksums of the files in the source package. pub fn checksums_sha1(&self) -> Option> { self.0 .get("Checksums-Sha1") .map(|s| s.lines().map(|line| line.parse().unwrap()).collect()) } /// Returns the SHA-256 checksums of the files in the source package. pub fn checksums_sha256(&self) -> Option> { self.0 .get("Checksums-Sha256") .map(|s| s.lines().map(|line| line.parse().unwrap()).collect()) } /// Returns the list of files in the source package. pub fn files(&self) -> Option> { self.0 .get("Files") .map(|s| s.lines().map(|line| line.parse().unwrap()).collect()) } /// Returns the path to the pool directory for the source package. pub fn get_pool_path(&self) -> Option { let files = self.files()?; let section = &files.first().unwrap().section; let section = if let Some((section, _subsection)) = section.split_once('/') { section } else { "main" }; let source = self.source()?; let subdir = if source.starts_with("lib") { "lib".to_string() } else { source[..1].to_lowercase() }; Some(format!("pool/{}/{}/{}", section, subdir, source)) } /// Create a new Changes file. pub fn new() -> Self { let mut slf = Self(deb822_lossless::Paragraph::new()); slf.set_format("1.8"); slf } /// Read a Changes file from a file. pub fn from_file>(path: P) -> Result { let deb822 = deb822_lossless::Deb822::from_file(path)?; let mut paras = deb822.paragraphs(); let para = match paras.next() { Some(para) => para, None => return Err(ParseError::NoParagraphs), }; if paras.next().is_some() { return Err(ParseError::MultipleParagraphs); } Ok(Self(para)) } /// Read a Changes file from a file, allowing syntax errors. pub fn from_file_relaxed>( path: P, ) -> Result<(Self, Vec), std::io::Error> { let (mut deb822, mut errors) = deb822_lossless::Deb822::from_file_relaxed(path)?; let mut paras = deb822.paragraphs(); let para = match paras.next() { Some(para) => para, None => deb822.add_paragraph(), }; if paras.next().is_some() { errors.push("multiple paragraphs found".to_string()); } Ok((Self(para), errors)) } /// Read a Changes file from a reader. pub fn read(mut r: R) -> Result { let deb822 = deb822_lossless::Deb822::read(&mut r)?; let mut paras = deb822.paragraphs(); let para = match paras.next() { Some(para) => para, None => return Err(ParseError::NoParagraphs), }; if paras.next().is_some() { return Err(ParseError::MultipleParagraphs); } Ok(Self(para)) } /// Read a Changes file from a reader, allowing syntax errors. pub fn read_relaxed( mut r: R, ) -> Result<(Self, Vec), deb822_lossless::Error> { let (mut deb822, mut errors) = deb822_lossless::Deb822::read_relaxed(&mut r)?; let mut paras = deb822.paragraphs(); let para = match paras.next() { Some(para) => para, None => deb822.add_paragraph(), }; if paras.next().is_some() { errors.push("multiple paragraphs found".to_string()); } Ok((Self(para), errors)) } } impl Default for Changes { fn default() -> Self { Self::new() } } #[cfg(feature = "python-debian")] impl<'py> pyo3::IntoPyObject<'py> for Changes { type Target = pyo3::PyAny; type Output = pyo3::Bound<'py, Self::Target>; type Error = pyo3::PyErr; fn into_pyobject(self, py: pyo3::Python<'py>) -> Result { self.0.into_pyobject(py) } } #[cfg(feature = "python-debian")] impl<'a, 'py> pyo3::IntoPyObject<'py> for &'a Changes { type Target = pyo3::PyAny; type Output = pyo3::Bound<'py, Self::Target>; type Error = pyo3::PyErr; fn into_pyobject(self, py: pyo3::Python<'py>) -> Result { (&self.0).into_pyobject(py) } } #[cfg(feature = "python-debian")] impl<'py> pyo3::FromPyObject<'_, 'py> for Changes { type Error = pyo3::PyErr; fn extract(ob: pyo3::Borrowed<'_, 'py, pyo3::PyAny>) -> Result { Ok(Changes(ob.extract()?)) } } impl AstNode for Changes { type Language = deb822_lossless::Lang; fn can_cast(kind: ::Kind) -> bool { deb822_lossless::Paragraph::can_cast(kind) || deb822_lossless::Deb822::can_cast(kind) } fn cast(syntax: rowan::SyntaxNode) -> Option { if let Some(para) = deb822_lossless::Paragraph::cast(syntax.clone()) { Some(Changes(para)) } else if let Some(deb822) = deb822_lossless::Deb822::cast(syntax) { deb822.paragraphs().next().map(Changes) } else { None } } fn syntax(&self) -> &rowan::SyntaxNode { self.0.syntax() } } #[cfg(test)] mod tests { #[test] fn test_new() { let changes = super::Changes::new(); assert_eq!(changes.format(), Some("1.8".to_string())); } #[test] fn test_parse() { let changes = r#"Format: 1.8 Date: Fri, 08 Sep 2023 18:23:59 +0100 Source: buildlog-consultant Binary: python3-buildlog-consultant Architecture: all Version: 0.0.34-1 Distribution: unstable Urgency: medium Maintainer: Jelmer Vernooij Changed-By: Jelmer Vernooij Description: python3-buildlog-consultant - build log parser and analyser Changes: buildlog-consultant (0.0.34-1) UNRELEASED; urgency=medium . * New upstream release. * Update standards version to 4.6.2, no changes needed. Checksums-Sha1: f1657e628254428ad74542e82c253a181894e8d0 17153 buildlog-consultant_0.0.34-1_amd64.buildinfo b44493c05d014bcd59180942d0125b20ddf45d03 2550812 python3-buildlog-consultant_0.0.34-1_all.deb Checksums-Sha256: 342a5782bf6a4f282d9002f726d2cac9c689c7e0fa7f61a1b0ecbf4da7916bdb 17153 buildlog-consultant_0.0.34-1_amd64.buildinfo 7f7e5df81ee23fbbe89015edb37e04f4bb40672fa6e9b1afd4fd698e57db78fd 2550812 python3-buildlog-consultant_0.0.34-1_all.deb Files: aa83112b0f8774a573bcf0b7b5cc12cc 17153 python optional buildlog-consultant_0.0.34-1_amd64.buildinfo a55858b90fe0ca728c89c1a1132b45c5 2550812 python optional python3-buildlog-consultant_0.0.34-1_all.deb "#; let changes = super::Changes::read(changes.as_bytes()).unwrap(); assert_eq!(changes.format(), Some("1.8".to_string())); assert_eq!(changes.source(), Some("buildlog-consultant".to_string())); assert_eq!( changes.binary(), Some(vec!["python3-buildlog-consultant".to_string()]) ); assert_eq!(changes.architecture(), Some(vec!["all".to_string()])); assert_eq!(changes.version(), Some("0.0.34-1".parse().unwrap())); assert_eq!(changes.distribution(), Some("unstable".to_string())); assert_eq!(changes.urgency(), Some(crate::fields::Urgency::Medium)); assert_eq!( changes.maintainer(), Some("Jelmer Vernooij ".to_string()) ); assert_eq!( changes.changed_by(), Some("Jelmer Vernooij ".to_string()) ); assert_eq!( changes.description(), Some("python3-buildlog-consultant - build log parser and analyser".to_string()) ); assert_eq!( changes.checksums_sha1(), Some(vec![ "f1657e628254428ad74542e82c253a181894e8d0 17153 buildlog-consultant_0.0.34-1_amd64.buildinfo".parse().unwrap(), "b44493c05d014bcd59180942d0125b20ddf45d03 2550812 python3-buildlog-consultant_0.0.34-1_all.deb".parse().unwrap() ]) ); assert_eq!( changes.checksums_sha256(), Some(vec![ "342a5782bf6a4f282d9002f726d2cac9c689c7e0fa7f61a1b0ecbf4da7916bdb 17153 buildlog-consultant_0.0.34-1_amd64.buildinfo" .parse() .unwrap(), "7f7e5df81ee23fbbe89015edb37e04f4bb40672fa6e9b1afd4fd698e57db78fd 2550812 python3-buildlog-consultant_0.0.34-1_all.deb" .parse() .unwrap() ]) ); assert_eq!( changes.files(), Some(vec![ "aa83112b0f8774a573bcf0b7b5cc12cc 17153 python optional buildlog-consultant_0.0.34-1_amd64.buildinfo".parse().unwrap(), "a55858b90fe0ca728c89c1a1132b45c5 2550812 python optional python3-buildlog-consultant_0.0.34-1_all.deb".parse().unwrap() ]) ); assert_eq!( changes.get_pool_path(), Some("pool/main/b/buildlog-consultant".to_string()) ); } } debian-control-0.2.14/src/lossless/control.rs000064400000000000000000002320631046102023000173020ustar 00000000000000//! This module provides a lossless representation of a Debian control file. //! //! # Example //! ```rust //! use debian_control::lossless::Control; //! use debian_control::relations::VersionConstraint; //! let input = r###"Source: dulwich //! ## Comments are preserved //! Maintainer: Jelmer Vernooij //! Build-Depends: python3, debhelper-compat (= 12) //! //! Package: python3-dulwich //! Architecture: amd64 //! Description: Pure-python git implementation //! "###; //! //! let mut control: Control = input.parse().unwrap(); //! //! // Bump debhelper-compat //! let source = control.source().unwrap(); //! let bd = source.build_depends().unwrap(); //! //! // Get entry with index 1 in Build-Depends, then set the version //! let entry = bd.get_entry(1).unwrap(); //! let mut debhelper = entry.relations().next().unwrap(); //! assert_eq!(debhelper.name(), "debhelper-compat"); //! debhelper.set_version(Some((VersionConstraint::Equal, "13".parse().unwrap()))); //! //! assert_eq!(source.to_string(), r###"Source: dulwich //! ## Comments are preserved //! Maintainer: Jelmer Vernooij //! Build-Depends: python3, debhelper-compat (= 12) //! "###); //! ``` use crate::fields::{MultiArch, Priority}; use crate::lossless::relations::Relations; use deb822_lossless::{Deb822, Paragraph}; use rowan::ast::AstNode; /// Parsing mode for Relations fields #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum ParseMode { /// Strict parsing - fail on syntax errors Strict, /// Relaxed parsing - accept syntax errors Relaxed, /// Allow substvars like ${misc:Depends} Substvar, } /// Canonical field order for source paragraphs in debian/control files pub const SOURCE_FIELD_ORDER: &[&str] = &[ "Source", "Section", "Priority", "Maintainer", "Uploaders", "Build-Depends", "Build-Depends-Indep", "Build-Depends-Arch", "Build-Conflicts", "Build-Conflicts-Indep", "Build-Conflicts-Arch", "Standards-Version", "Vcs-Browser", "Vcs-Git", "Vcs-Svn", "Vcs-Bzr", "Vcs-Hg", "Vcs-Darcs", "Vcs-Cvs", "Vcs-Arch", "Vcs-Mtn", "Homepage", "Rules-Requires-Root", "Testsuite", "Testsuite-Triggers", ]; /// Canonical field order for binary packages in debian/control files pub const BINARY_FIELD_ORDER: &[&str] = &[ "Package", "Architecture", "Section", "Priority", "Multi-Arch", "Essential", "Build-Profiles", "Built-Using", "Pre-Depends", "Depends", "Recommends", "Suggests", "Enhances", "Conflicts", "Breaks", "Replaces", "Provides", "Description", ]; fn format_field(name: &str, value: &str) -> String { match name { "Uploaders" => value .split(',') .map(|s| s.trim().to_string()) .collect::>() .join(",\n"), "Build-Depends" | "Build-Depends-Indep" | "Build-Depends-Arch" | "Build-Conflicts" | "Build-Conflicts-Indep" | "Build-Conflics-Arch" | "Depends" | "Recommends" | "Suggests" | "Enhances" | "Pre-Depends" | "Breaks" => { let relations: Relations = value.parse().unwrap(); let relations = relations.wrap_and_sort(); relations.to_string() } _ => value.to_string(), } } /// A Debian control file #[derive(Debug, Clone, PartialEq, Eq)] pub struct Control { deb822: Deb822, parse_mode: ParseMode, } impl Control { /// Create a new control file with strict parsing pub fn new() -> Self { Control { deb822: Deb822::new(), parse_mode: ParseMode::Strict, } } /// Create a new control file with the specified parse mode pub fn new_with_mode(parse_mode: ParseMode) -> Self { Control { deb822: Deb822::new(), parse_mode, } } /// Get the parse mode for this control file pub fn parse_mode(&self) -> ParseMode { self.parse_mode } /// Return the underlying deb822 object, mutable pub fn as_mut_deb822(&mut self) -> &mut Deb822 { &mut self.deb822 } /// Return the underlying deb822 object pub fn as_deb822(&self) -> &Deb822 { &self.deb822 } /// Parse control file text, returning a Parse result pub fn parse(text: &str) -> deb822_lossless::Parse { let deb822_parse = Deb822::parse(text); // Transform Parse to Parse let green = deb822_parse.green().clone(); let errors = deb822_parse.errors().to_vec(); let positioned_errors = deb822_parse.positioned_errors().to_vec(); deb822_lossless::Parse::new_with_positioned_errors(green, errors, positioned_errors) } /// Return the source package pub fn source(&self) -> Option { let parse_mode = self.parse_mode; self.deb822 .paragraphs() .find(|p| p.get("Source").is_some()) .map(|paragraph| Source { paragraph, parse_mode, }) } /// Iterate over all binary packages pub fn binaries(&self) -> impl Iterator + '_ { let parse_mode = self.parse_mode; self.deb822 .paragraphs() .filter(|p| p.get("Package").is_some()) .map(move |paragraph| Binary { paragraph, parse_mode, }) } /// Add a new source package /// /// # Arguments /// * `name` - The name of the source package /// /// # Returns /// The newly created source package /// /// # Example /// ```rust /// use debian_control::lossless::control::Control; /// let mut control = Control::new(); /// let source = control.add_source("foo"); /// assert_eq!(source.name(), Some("foo".to_owned())); /// ``` pub fn add_source(&mut self, name: &str) -> Source { let mut p = self.deb822.add_paragraph(); p.set("Source", name); self.source().unwrap() } /// Add new binary package /// /// # Arguments /// * `name` - The name of the binary package /// /// # Returns /// The newly created binary package /// /// # Example /// ```rust /// use debian_control::lossless::control::Control; /// let mut control = Control::new(); /// let binary = control.add_binary("foo"); /// assert_eq!(binary.name(), Some("foo".to_owned())); /// ``` pub fn add_binary(&mut self, name: &str) -> Binary { let mut p = self.deb822.add_paragraph(); p.set("Package", name); Binary { paragraph: p, parse_mode: ParseMode::Strict, } } /// Remove a binary package paragraph by name /// /// # Arguments /// * `name` - The name of the binary package to remove /// /// # Returns /// `true` if a binary paragraph with the given name was found and removed, `false` otherwise /// /// # Example /// ```rust /// use debian_control::lossless::control::Control; /// let mut control = Control::new(); /// control.add_binary("foo"); /// assert_eq!(control.binaries().count(), 1); /// assert!(control.remove_binary("foo")); /// assert_eq!(control.binaries().count(), 0); /// ``` pub fn remove_binary(&mut self, name: &str) -> bool { let index = self .deb822 .paragraphs() .position(|p| p.get("Package").as_deref() == Some(name)); if let Some(index) = index { self.deb822.remove_paragraph(index); true } else { false } } /// Read a control file from a file pub fn from_file>(path: P) -> Result { Ok(Control { deb822: Deb822::from_file(path)?, parse_mode: ParseMode::Strict, }) } /// Read a control file from a file, allowing syntax errors pub fn from_file_relaxed>( path: P, ) -> Result<(Self, Vec), std::io::Error> { let (deb822, errors) = Deb822::from_file_relaxed(path)?; Ok(( Control { deb822, parse_mode: ParseMode::Relaxed, }, errors, )) } /// Read a control file from a reader pub fn read(mut r: R) -> Result { Ok(Control { deb822: Deb822::read(&mut r)?, parse_mode: ParseMode::Strict, }) } /// Read a control file from a reader, allowing syntax errors pub fn read_relaxed( mut r: R, ) -> Result<(Self, Vec), deb822_lossless::Error> { let (deb822, errors) = Deb822::read_relaxed(&mut r)?; Ok(( Control { deb822, parse_mode: ParseMode::Relaxed, }, errors, )) } /// Wrap and sort the control file /// /// # Arguments /// * `indentation` - The indentation to use /// * `immediate_empty_line` - Whether to add an empty line at the start of multi-line fields /// * `max_line_length_one_liner` - The maximum line length for one-liner fields pub fn wrap_and_sort( &mut self, indentation: deb822_lossless::Indentation, immediate_empty_line: bool, max_line_length_one_liner: Option, ) { let sort_paragraphs = |a: &Paragraph, b: &Paragraph| -> std::cmp::Ordering { // Sort Source before Package let a_is_source = a.get("Source").is_some(); let b_is_source = b.get("Source").is_some(); if a_is_source && !b_is_source { return std::cmp::Ordering::Less; } else if !a_is_source && b_is_source { return std::cmp::Ordering::Greater; } else if a_is_source && b_is_source { return a.get("Source").cmp(&b.get("Source")); } a.get("Package").cmp(&b.get("Package")) }; let wrap_paragraph = |p: &Paragraph| -> Paragraph { // TODO: Add Source/Package specific wrapping // TODO: Add support for wrapping and sorting fields p.wrap_and_sort( indentation, immediate_empty_line, max_line_length_one_liner, None, Some(&format_field), ) }; self.deb822 = self .deb822 .wrap_and_sort(Some(&sort_paragraphs), Some(&wrap_paragraph)); } /// Sort binary package paragraphs alphabetically by package name. /// /// This method reorders the binary package paragraphs in alphabetical order /// based on their Package field value. The source paragraph always remains first. /// /// # Arguments /// * `keep_first` - If true, keeps the first binary package in place and only /// sorts the remaining binary packages. If false, sorts all binary packages. /// /// # Example /// ```rust /// use debian_control::lossless::Control; /// /// let input = r#"Source: foo /// /// Package: libfoo /// Architecture: all /// /// Package: libbar /// Architecture: all /// "#; /// /// let mut control: Control = input.parse().unwrap(); /// control.sort_binaries(false); /// /// // Binary packages are now sorted: libbar comes before libfoo /// let binaries: Vec<_> = control.binaries().collect(); /// assert_eq!(binaries[0].name(), Some("libbar".to_string())); /// assert_eq!(binaries[1].name(), Some("libfoo".to_string())); /// ``` pub fn sort_binaries(&mut self, keep_first: bool) { let mut paragraphs: Vec<_> = self.deb822.paragraphs().collect(); if paragraphs.len() <= 1 { return; // Only source paragraph, nothing to sort } // Find the index where binary packages start (after source) let source_idx = paragraphs.iter().position(|p| p.get("Source").is_some()); let binary_start = source_idx.map(|i| i + 1).unwrap_or(0); // Determine where to start sorting let sort_start = if keep_first && paragraphs.len() > binary_start + 1 { binary_start + 1 } else { binary_start }; if sort_start >= paragraphs.len() { return; // Nothing to sort } // Sort binary packages by package name paragraphs[sort_start..].sort_by(|a, b| { let a_name = a.get("Package"); let b_name = b.get("Package"); a_name.cmp(&b_name) }); // Rebuild the Deb822 with sorted paragraphs let sort_paragraphs = |a: &Paragraph, b: &Paragraph| -> std::cmp::Ordering { let a_pos = paragraphs.iter().position(|p| p == a); let b_pos = paragraphs.iter().position(|p| p == b); a_pos.cmp(&b_pos) }; self.deb822 = self.deb822.wrap_and_sort(Some(&sort_paragraphs), None); } /// Iterate over fields that overlap with the given range /// /// This method returns all fields (entries) from all paragraphs that have any overlap /// with the specified text range. This is useful for incremental parsing in LSP contexts /// where you only want to process fields that were affected by a text change. /// /// # Arguments /// * `range` - The text range to check for overlaps /// /// # Returns /// An iterator over all Entry items that overlap with the given range /// /// # Example /// ```rust /// use debian_control::lossless::Control; /// use deb822_lossless::TextRange; /// /// let control_text = "Source: foo\nMaintainer: test@example.com\n\nPackage: bar\nArchitecture: all\n"; /// let control: Control = control_text.parse().unwrap(); /// /// // Get fields in a specific range (e.g., where a change occurred) /// let change_range = TextRange::new(20.into(), 40.into()); /// for entry in control.fields_in_range(change_range) { /// if let Some(key) = entry.key() { /// println!("Field {} was in the changed range", key); /// } /// } /// ``` pub fn fields_in_range( &self, range: rowan::TextRange, ) -> impl Iterator + '_ { self.deb822 .paragraphs() .flat_map(move |p| p.entries().collect::>()) .filter(move |entry| { let entry_range = entry.syntax().text_range(); // Check if ranges overlap entry_range.start() < range.end() && range.start() < entry_range.end() }) } } impl From for Deb822 { fn from(c: Control) -> Self { c.deb822 } } impl From for Control { fn from(d: Deb822) -> Self { Control { deb822: d, parse_mode: ParseMode::Strict, } } } impl Default for Control { fn default() -> Self { Self::new() } } impl std::str::FromStr for Control { type Err = deb822_lossless::ParseError; fn from_str(s: &str) -> Result { Control::parse(s).to_result() } } /// A source package paragraph #[derive(Debug, Clone, PartialEq, Eq)] pub struct Source { paragraph: Paragraph, parse_mode: ParseMode, } impl From for Paragraph { fn from(s: Source) -> Self { s.paragraph } } impl From for Source { fn from(p: Paragraph) -> Self { Source { paragraph: p, parse_mode: ParseMode::Strict, } } } impl std::fmt::Display for Source { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { self.paragraph.fmt(f) } } impl Source { /// Parse a relations field according to the parse mode fn parse_relations(&self, s: &str) -> Relations { match self.parse_mode { ParseMode::Strict => s.parse().unwrap(), ParseMode::Relaxed => Relations::parse_relaxed(s, false).0, ParseMode::Substvar => Relations::parse_relaxed(s, true).0, } } /// The name of the source package. pub fn name(&self) -> Option { self.paragraph.get("Source") } /// Wrap and sort the control file paragraph pub fn wrap_and_sort( &mut self, indentation: deb822_lossless::Indentation, immediate_empty_line: bool, max_line_length_one_liner: Option, ) { self.paragraph = self.paragraph.wrap_and_sort( indentation, immediate_empty_line, max_line_length_one_liner, None, Some(&format_field), ); } /// Return the underlying deb822 paragraph, mutable pub fn as_mut_deb822(&mut self) -> &mut Paragraph { &mut self.paragraph } /// Return the underlying deb822 paragraph pub fn as_deb822(&self) -> &Paragraph { &self.paragraph } /// Set the name of the source package. pub fn set_name(&mut self, name: &str) { self.set("Source", name); } /// The default section of the packages built from this source package. pub fn section(&self) -> Option { self.paragraph.get("Section") } /// Set the section of the source package pub fn set_section(&mut self, section: Option<&str>) { if let Some(section) = section { self.set("Section", section); } else { self.paragraph.remove("Section"); } } /// The default priority of the packages built from this source package. pub fn priority(&self) -> Option { self.paragraph.get("Priority").and_then(|v| v.parse().ok()) } /// Set the priority of the source package pub fn set_priority(&mut self, priority: Option) { if let Some(priority) = priority { self.set("Priority", priority.to_string().as_str()); } else { self.paragraph.remove("Priority"); } } /// The maintainer of the package. pub fn maintainer(&self) -> Option { self.paragraph.get("Maintainer") } /// Set the maintainer of the package pub fn set_maintainer(&mut self, maintainer: &str) { self.set("Maintainer", maintainer); } /// The build dependencies of the package. pub fn build_depends(&self) -> Option { self.paragraph .get("Build-Depends") .map(|s| self.parse_relations(&s)) } /// Set the Build-Depends field pub fn set_build_depends(&mut self, relations: &Relations) { self.set("Build-Depends", relations.to_string().as_str()); } /// Return the Build-Depends-Indep field pub fn build_depends_indep(&self) -> Option { self.paragraph .get("Build-Depends-Indep") .map(|s| self.parse_relations(&s)) } /// Return the Build-Depends-Arch field pub fn build_depends_arch(&self) -> Option { self.paragraph .get("Build-Depends-Arch") .map(|s| self.parse_relations(&s)) } /// The build conflicts of the package. pub fn build_conflicts(&self) -> Option { self.paragraph .get("Build-Conflicts") .map(|s| self.parse_relations(&s)) } /// Return the Build-Conflicts-Indep field pub fn build_conflicts_indep(&self) -> Option { self.paragraph .get("Build-Conflicts-Indep") .map(|s| self.parse_relations(&s)) } /// Return the Build-Conflicts-Arch field pub fn build_conflicts_arch(&self) -> Option { self.paragraph .get("Build-Conflicts-Arch") .map(|s| self.parse_relations(&s)) } /// Return the standards version pub fn standards_version(&self) -> Option { self.paragraph.get("Standards-Version") } /// Set the Standards-Version field pub fn set_standards_version(&mut self, version: &str) { self.set("Standards-Version", version); } /// Return the upstrea mHomepage pub fn homepage(&self) -> Option { self.paragraph.get("Homepage").and_then(|s| s.parse().ok()) } /// Set the Homepage field pub fn set_homepage(&mut self, homepage: &url::Url) { self.set("Homepage", homepage.to_string().as_str()); } /// Return the Vcs-Git field pub fn vcs_git(&self) -> Option { self.paragraph.get("Vcs-Git") } /// Set the Vcs-Git field pub fn set_vcs_git(&mut self, url: &str) { self.set("Vcs-Git", url); } /// Return the Vcs-Browser field pub fn vcs_svn(&self) -> Option { self.paragraph.get("Vcs-Svn").map(|s| s.to_string()) } /// Set the Vcs-Svn field pub fn set_vcs_svn(&mut self, url: &str) { self.set("Vcs-Svn", url); } /// Return the Vcs-Bzr field pub fn vcs_bzr(&self) -> Option { self.paragraph.get("Vcs-Bzr").map(|s| s.to_string()) } /// Set the Vcs-Bzr field pub fn set_vcs_bzr(&mut self, url: &str) { self.set("Vcs-Bzr", url); } /// Return the Vcs-Arch field pub fn vcs_arch(&self) -> Option { self.paragraph.get("Vcs-Arch").map(|s| s.to_string()) } /// Set the Vcs-Arch field pub fn set_vcs_arch(&mut self, url: &str) { self.set("Vcs-Arch", url); } /// Return the Vcs-Svk field pub fn vcs_svk(&self) -> Option { self.paragraph.get("Vcs-Svk").map(|s| s.to_string()) } /// Set the Vcs-Svk field pub fn set_vcs_svk(&mut self, url: &str) { self.set("Vcs-Svk", url); } /// Return the Vcs-Darcs field pub fn vcs_darcs(&self) -> Option { self.paragraph.get("Vcs-Darcs").map(|s| s.to_string()) } /// Set the Vcs-Darcs field pub fn set_vcs_darcs(&mut self, url: &str) { self.set("Vcs-Darcs", url); } /// Return the Vcs-Mtn field pub fn vcs_mtn(&self) -> Option { self.paragraph.get("Vcs-Mtn").map(|s| s.to_string()) } /// Set the Vcs-Mtn field pub fn set_vcs_mtn(&mut self, url: &str) { self.set("Vcs-Mtn", url); } /// Return the Vcs-Cvs field pub fn vcs_cvs(&self) -> Option { self.paragraph.get("Vcs-Cvs").map(|s| s.to_string()) } /// Set the Vcs-Cvs field pub fn set_vcs_cvs(&mut self, url: &str) { self.set("Vcs-Cvs", url); } /// Return the Vcs-Hg field pub fn vcs_hg(&self) -> Option { self.paragraph.get("Vcs-Hg").map(|s| s.to_string()) } /// Set the Vcs-Hg field pub fn set_vcs_hg(&mut self, url: &str) { self.set("Vcs-Hg", url); } /// Set a field in the source paragraph, using canonical field ordering for source packages pub fn set(&mut self, key: &str, value: &str) { self.paragraph .set_with_field_order(key, value, SOURCE_FIELD_ORDER); } /// Retrieve a field pub fn get(&self, key: &str) -> Option { self.paragraph.get(key) } /// Return the Vcs-Browser field pub fn vcs_browser(&self) -> Option { self.paragraph.get("Vcs-Browser") } /// Return the Vcs used by the package pub fn vcs(&self) -> Option { for (name, value) in self.paragraph.items() { if name.starts_with("Vcs-") && name != "Vcs-Browser" { return crate::vcs::Vcs::from_field(&name, &value).ok(); } } None } /// Set the Vcs-Browser field pub fn set_vcs_browser(&mut self, url: Option<&str>) { if let Some(url) = url { self.set("Vcs-Browser", url); } else { self.paragraph.remove("Vcs-Browser"); } } /// Return the Uploaders field pub fn uploaders(&self) -> Option> { self.paragraph .get("Uploaders") .map(|s| s.split(',').map(|s| s.trim().to_owned()).collect()) } /// Set the uploaders field pub fn set_uploaders(&mut self, uploaders: &[&str]) { self.set( "Uploaders", uploaders .iter() .map(|s| s.to_string()) .collect::>() .join(", ") .as_str(), ); } /// Return the architecture field pub fn architecture(&self) -> Option { self.paragraph.get("Architecture") } /// Set the architecture field pub fn set_architecture(&mut self, arch: Option<&str>) { if let Some(arch) = arch { self.set("Architecture", arch); } else { self.paragraph.remove("Architecture"); } } /// Return the Rules-Requires-Root field pub fn rules_requires_root(&self) -> Option { self.paragraph .get("Rules-Requires-Root") .map(|s| match s.to_lowercase().as_str() { "yes" => true, "no" => false, _ => panic!("invalid Rules-Requires-Root value"), }) } /// Set the Rules-Requires-Root field pub fn set_rules_requires_root(&mut self, requires_root: bool) { self.set( "Rules-Requires-Root", if requires_root { "yes" } else { "no" }, ); } /// Return the Testsuite field pub fn testsuite(&self) -> Option { self.paragraph.get("Testsuite") } /// Set the Testsuite field pub fn set_testsuite(&mut self, testsuite: &str) { self.set("Testsuite", testsuite); } /// Check if this source paragraph's range overlaps with the given range /// /// # Arguments /// * `range` - The text range to check for overlap /// /// # Returns /// `true` if the paragraph overlaps with the given range, `false` otherwise pub fn overlaps_range(&self, range: rowan::TextRange) -> bool { let para_range = self.paragraph.syntax().text_range(); para_range.start() < range.end() && range.start() < para_range.end() } /// Get fields in this source paragraph that overlap with the given range /// /// # Arguments /// * `range` - The text range to check for overlaps /// /// # Returns /// An iterator over Entry items that overlap with the given range pub fn fields_in_range( &self, range: rowan::TextRange, ) -> impl Iterator + '_ { self.paragraph.entries().filter(move |entry| { let entry_range = entry.syntax().text_range(); entry_range.start() < range.end() && range.start() < entry_range.end() }) } } #[cfg(feature = "python-debian")] impl<'py> pyo3::IntoPyObject<'py> for Source { type Target = pyo3::PyAny; type Output = pyo3::Bound<'py, Self::Target>; type Error = pyo3::PyErr; fn into_pyobject(self, py: pyo3::Python<'py>) -> Result { self.paragraph.into_pyobject(py) } } #[cfg(feature = "python-debian")] impl<'a, 'py> pyo3::IntoPyObject<'py> for &'a Source { type Target = pyo3::PyAny; type Output = pyo3::Bound<'py, Self::Target>; type Error = pyo3::PyErr; fn into_pyobject(self, py: pyo3::Python<'py>) -> Result { (&self.paragraph).into_pyobject(py) } } #[cfg(feature = "python-debian")] impl<'py> pyo3::FromPyObject<'_, 'py> for Source { type Error = pyo3::PyErr; fn extract(ob: pyo3::Borrowed<'_, 'py, pyo3::PyAny>) -> Result { Ok(Source { paragraph: ob.extract()?, parse_mode: ParseMode::Strict, }) } } impl std::fmt::Display for Control { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { self.deb822.fmt(f) } } impl AstNode for Control { type Language = deb822_lossless::Lang; fn can_cast(kind: ::Kind) -> bool { Deb822::can_cast(kind) } fn cast(syntax: rowan::SyntaxNode) -> Option { Deb822::cast(syntax).map(|deb822| Control { deb822, parse_mode: ParseMode::Strict, }) } fn syntax(&self) -> &rowan::SyntaxNode { self.deb822.syntax() } } /// A binary package paragraph #[derive(Debug, Clone, PartialEq, Eq)] pub struct Binary { paragraph: Paragraph, parse_mode: ParseMode, } impl From for Paragraph { fn from(b: Binary) -> Self { b.paragraph } } impl From for Binary { fn from(p: Paragraph) -> Self { Binary { paragraph: p, parse_mode: ParseMode::Strict, } } } #[cfg(feature = "python-debian")] impl<'py> pyo3::IntoPyObject<'py> for Binary { type Target = pyo3::PyAny; type Output = pyo3::Bound<'py, Self::Target>; type Error = pyo3::PyErr; fn into_pyobject(self, py: pyo3::Python<'py>) -> Result { self.paragraph.into_pyobject(py) } } #[cfg(feature = "python-debian")] impl<'a, 'py> pyo3::IntoPyObject<'py> for &'a Binary { type Target = pyo3::PyAny; type Output = pyo3::Bound<'py, Self::Target>; type Error = pyo3::PyErr; fn into_pyobject(self, py: pyo3::Python<'py>) -> Result { (&self.paragraph).into_pyobject(py) } } #[cfg(feature = "python-debian")] impl<'py> pyo3::FromPyObject<'_, 'py> for Binary { type Error = pyo3::PyErr; fn extract(ob: pyo3::Borrowed<'_, 'py, pyo3::PyAny>) -> Result { Ok(Binary { paragraph: ob.extract()?, parse_mode: ParseMode::Strict, }) } } impl Default for Binary { fn default() -> Self { Self::new() } } impl std::fmt::Display for Binary { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { self.paragraph.fmt(f) } } impl Binary { /// Parse a relations field according to the parse mode fn parse_relations(&self, s: &str) -> Relations { match self.parse_mode { ParseMode::Strict => s.parse().unwrap(), ParseMode::Relaxed => Relations::parse_relaxed(s, false).0, ParseMode::Substvar => Relations::parse_relaxed(s, true).0, } } /// Create a new binary package control file pub fn new() -> Self { Binary { paragraph: Paragraph::new(), parse_mode: ParseMode::Strict, } } /// Return the underlying deb822 paragraph, mutable pub fn as_mut_deb822(&mut self) -> &mut Paragraph { &mut self.paragraph } /// Return the underlying deb822 paragraph pub fn as_deb822(&self) -> &Paragraph { &self.paragraph } /// Wrap and sort the control file pub fn wrap_and_sort( &mut self, indentation: deb822_lossless::Indentation, immediate_empty_line: bool, max_line_length_one_liner: Option, ) { self.paragraph = self.paragraph.wrap_and_sort( indentation, immediate_empty_line, max_line_length_one_liner, None, Some(&format_field), ); } /// The name of the package. pub fn name(&self) -> Option { self.paragraph.get("Package") } /// Set the name of the package pub fn set_name(&mut self, name: &str) { self.set("Package", name); } /// The section of the package. pub fn section(&self) -> Option { self.paragraph.get("Section") } /// Set the section pub fn set_section(&mut self, section: Option<&str>) { if let Some(section) = section { self.set("Section", section); } else { self.paragraph.remove("Section"); } } /// The priority of the package. pub fn priority(&self) -> Option { self.paragraph.get("Priority").and_then(|v| v.parse().ok()) } /// Set the priority of the package pub fn set_priority(&mut self, priority: Option) { if let Some(priority) = priority { self.set("Priority", priority.to_string().as_str()); } else { self.paragraph.remove("Priority"); } } /// The architecture of the package. pub fn architecture(&self) -> Option { self.paragraph.get("Architecture") } /// Set the architecture of the package pub fn set_architecture(&mut self, arch: Option<&str>) { if let Some(arch) = arch { self.set("Architecture", arch); } else { self.paragraph.remove("Architecture"); } } /// The dependencies of the package. pub fn depends(&self) -> Option { self.paragraph .get("Depends") .map(|s| self.parse_relations(&s)) } /// Set the Depends field pub fn set_depends(&mut self, depends: Option<&Relations>) { if let Some(depends) = depends { self.set("Depends", depends.to_string().as_str()); } else { self.paragraph.remove("Depends"); } } /// The package that this package recommends pub fn recommends(&self) -> Option { self.paragraph .get("Recommends") .map(|s| self.parse_relations(&s)) } /// Set the Recommends field pub fn set_recommends(&mut self, recommends: Option<&Relations>) { if let Some(recommends) = recommends { self.set("Recommends", recommends.to_string().as_str()); } else { self.paragraph.remove("Recommends"); } } /// Packages that this package suggests pub fn suggests(&self) -> Option { self.paragraph .get("Suggests") .map(|s| self.parse_relations(&s)) } /// Set the Suggests field pub fn set_suggests(&mut self, suggests: Option<&Relations>) { if let Some(suggests) = suggests { self.set("Suggests", suggests.to_string().as_str()); } else { self.paragraph.remove("Suggests"); } } /// The package that this package enhances pub fn enhances(&self) -> Option { self.paragraph .get("Enhances") .map(|s| self.parse_relations(&s)) } /// Set the Enhances field pub fn set_enhances(&mut self, enhances: Option<&Relations>) { if let Some(enhances) = enhances { self.set("Enhances", enhances.to_string().as_str()); } else { self.paragraph.remove("Enhances"); } } /// The package that this package pre-depends on pub fn pre_depends(&self) -> Option { self.paragraph .get("Pre-Depends") .map(|s| self.parse_relations(&s)) } /// Set the Pre-Depends field pub fn set_pre_depends(&mut self, pre_depends: Option<&Relations>) { if let Some(pre_depends) = pre_depends { self.set("Pre-Depends", pre_depends.to_string().as_str()); } else { self.paragraph.remove("Pre-Depends"); } } /// The package that this package breaks pub fn breaks(&self) -> Option { self.paragraph .get("Breaks") .map(|s| self.parse_relations(&s)) } /// Set the Breaks field pub fn set_breaks(&mut self, breaks: Option<&Relations>) { if let Some(breaks) = breaks { self.set("Breaks", breaks.to_string().as_str()); } else { self.paragraph.remove("Breaks"); } } /// The package that this package conflicts with pub fn conflicts(&self) -> Option { self.paragraph .get("Conflicts") .map(|s| self.parse_relations(&s)) } /// Set the Conflicts field pub fn set_conflicts(&mut self, conflicts: Option<&Relations>) { if let Some(conflicts) = conflicts { self.set("Conflicts", conflicts.to_string().as_str()); } else { self.paragraph.remove("Conflicts"); } } /// The package that this package replaces pub fn replaces(&self) -> Option { self.paragraph .get("Replaces") .map(|s| self.parse_relations(&s)) } /// Set the Replaces field pub fn set_replaces(&mut self, replaces: Option<&Relations>) { if let Some(replaces) = replaces { self.set("Replaces", replaces.to_string().as_str()); } else { self.paragraph.remove("Replaces"); } } /// Return the Provides field pub fn provides(&self) -> Option { self.paragraph .get("Provides") .map(|s| self.parse_relations(&s)) } /// Set the Provides field pub fn set_provides(&mut self, provides: Option<&Relations>) { if let Some(provides) = provides { self.set("Provides", provides.to_string().as_str()); } else { self.paragraph.remove("Provides"); } } /// Return the Built-Using field pub fn built_using(&self) -> Option { self.paragraph .get("Built-Using") .map(|s| self.parse_relations(&s)) } /// Set the Built-Using field pub fn set_built_using(&mut self, built_using: Option<&Relations>) { if let Some(built_using) = built_using { self.set("Built-Using", built_using.to_string().as_str()); } else { self.paragraph.remove("Built-Using"); } } /// The Multi-Arch field pub fn multi_arch(&self) -> Option { self.paragraph.get("Multi-Arch").map(|s| s.parse().unwrap()) } /// Set the Multi-Arch field pub fn set_multi_arch(&mut self, multi_arch: Option) { if let Some(multi_arch) = multi_arch { self.set("Multi-Arch", multi_arch.to_string().as_str()); } else { self.paragraph.remove("Multi-Arch"); } } /// Whether the package is essential pub fn essential(&self) -> bool { self.paragraph .get("Essential") .map(|s| s == "yes") .unwrap_or(false) } /// Set whether the package is essential pub fn set_essential(&mut self, essential: bool) { if essential { self.set("Essential", "yes"); } else { self.paragraph.remove("Essential"); } } /// Binary package description pub fn description(&self) -> Option { self.paragraph.get_multiline("Description") } /// Set the binary package description pub fn set_description(&mut self, description: Option<&str>) { if let Some(description) = description { self.paragraph.set_with_indent_pattern( "Description", description, Some(&deb822_lossless::IndentPattern::Fixed(1)), Some(BINARY_FIELD_ORDER), ); } else { self.paragraph.remove("Description"); } } /// Return the upstream homepage pub fn homepage(&self) -> Option { self.paragraph.get("Homepage").and_then(|s| s.parse().ok()) } /// Set the upstream homepage pub fn set_homepage(&mut self, url: &url::Url) { self.set("Homepage", url.as_str()); } /// Set a field in the binary paragraph, using canonical field ordering for binary packages pub fn set(&mut self, key: &str, value: &str) { self.paragraph .set_with_field_order(key, value, BINARY_FIELD_ORDER); } /// Retrieve a field pub fn get(&self, key: &str) -> Option { self.paragraph.get(key) } /// Check if this binary paragraph's range overlaps with the given range /// /// # Arguments /// * `range` - The text range to check for overlap /// /// # Returns /// `true` if the paragraph overlaps with the given range, `false` otherwise pub fn overlaps_range(&self, range: rowan::TextRange) -> bool { let para_range = self.paragraph.syntax().text_range(); para_range.start() < range.end() && range.start() < para_range.end() } /// Get fields in this binary paragraph that overlap with the given range /// /// # Arguments /// * `range` - The text range to check for overlaps /// /// # Returns /// An iterator over Entry items that overlap with the given range pub fn fields_in_range( &self, range: rowan::TextRange, ) -> impl Iterator + '_ { self.paragraph.entries().filter(move |entry| { let entry_range = entry.syntax().text_range(); entry_range.start() < range.end() && range.start() < entry_range.end() }) } } #[cfg(test)] mod tests { use super::*; use crate::relations::VersionConstraint; #[test] fn test_source_set_field_ordering() { let mut control = Control::new(); let mut source = control.add_source("mypackage"); // Add fields in random order source.set("Homepage", "https://example.com"); source.set("Build-Depends", "debhelper"); source.set("Standards-Version", "4.5.0"); source.set("Maintainer", "Test "); // Convert to string and check field order let output = source.to_string(); let lines: Vec<&str> = output.lines().collect(); // Source should be first assert!(lines[0].starts_with("Source:")); // Find the positions of each field let maintainer_pos = lines .iter() .position(|l| l.starts_with("Maintainer:")) .unwrap(); let build_depends_pos = lines .iter() .position(|l| l.starts_with("Build-Depends:")) .unwrap(); let standards_pos = lines .iter() .position(|l| l.starts_with("Standards-Version:")) .unwrap(); let homepage_pos = lines .iter() .position(|l| l.starts_with("Homepage:")) .unwrap(); // Check ordering according to SOURCE_FIELD_ORDER assert!(maintainer_pos < build_depends_pos); assert!(build_depends_pos < standards_pos); assert!(standards_pos < homepage_pos); } #[test] fn test_binary_set_field_ordering() { let mut control = Control::new(); let mut binary = control.add_binary("mypackage"); // Add fields in random order binary.set("Description", "A test package"); binary.set("Architecture", "amd64"); binary.set("Depends", "libc6"); binary.set("Section", "utils"); // Convert to string and check field order let output = binary.to_string(); let lines: Vec<&str> = output.lines().collect(); // Package should be first assert!(lines[0].starts_with("Package:")); // Find the positions of each field let arch_pos = lines .iter() .position(|l| l.starts_with("Architecture:")) .unwrap(); let section_pos = lines .iter() .position(|l| l.starts_with("Section:")) .unwrap(); let depends_pos = lines .iter() .position(|l| l.starts_with("Depends:")) .unwrap(); let desc_pos = lines .iter() .position(|l| l.starts_with("Description:")) .unwrap(); // Check ordering according to BINARY_FIELD_ORDER assert!(arch_pos < section_pos); assert!(section_pos < depends_pos); assert!(depends_pos < desc_pos); } #[test] fn test_source_specific_set_methods_use_field_ordering() { let mut control = Control::new(); let mut source = control.add_source("mypackage"); // Use specific set_* methods in random order source.set_homepage(&"https://example.com".parse().unwrap()); source.set_maintainer("Test "); source.set_standards_version("4.5.0"); source.set_vcs_git("https://github.com/example/repo"); // Convert to string and check field order let output = source.to_string(); let lines: Vec<&str> = output.lines().collect(); // Find the positions of each field let source_pos = lines.iter().position(|l| l.starts_with("Source:")).unwrap(); let maintainer_pos = lines .iter() .position(|l| l.starts_with("Maintainer:")) .unwrap(); let standards_pos = lines .iter() .position(|l| l.starts_with("Standards-Version:")) .unwrap(); let vcs_git_pos = lines .iter() .position(|l| l.starts_with("Vcs-Git:")) .unwrap(); let homepage_pos = lines .iter() .position(|l| l.starts_with("Homepage:")) .unwrap(); // Check ordering according to SOURCE_FIELD_ORDER assert!(source_pos < maintainer_pos); assert!(maintainer_pos < standards_pos); assert!(standards_pos < vcs_git_pos); assert!(vcs_git_pos < homepage_pos); } #[test] fn test_binary_specific_set_methods_use_field_ordering() { let mut control = Control::new(); let mut binary = control.add_binary("mypackage"); // Use specific set_* methods in random order binary.set_description(Some("A test package")); binary.set_architecture(Some("amd64")); let depends = "libc6".parse().unwrap(); binary.set_depends(Some(&depends)); binary.set_section(Some("utils")); binary.set_priority(Some(Priority::Optional)); // Convert to string and check field order let output = binary.to_string(); let lines: Vec<&str> = output.lines().collect(); // Find the positions of each field let package_pos = lines .iter() .position(|l| l.starts_with("Package:")) .unwrap(); let arch_pos = lines .iter() .position(|l| l.starts_with("Architecture:")) .unwrap(); let section_pos = lines .iter() .position(|l| l.starts_with("Section:")) .unwrap(); let priority_pos = lines .iter() .position(|l| l.starts_with("Priority:")) .unwrap(); let depends_pos = lines .iter() .position(|l| l.starts_with("Depends:")) .unwrap(); let desc_pos = lines .iter() .position(|l| l.starts_with("Description:")) .unwrap(); // Check ordering according to BINARY_FIELD_ORDER assert!(package_pos < arch_pos); assert!(arch_pos < section_pos); assert!(section_pos < priority_pos); assert!(priority_pos < depends_pos); assert!(depends_pos < desc_pos); } #[test] fn test_parse() { let control: Control = r#"Source: foo Section: libs Priority: optional Build-Depends: bar (>= 1.0.0), baz (>= 1.0.0) Homepage: https://example.com "# .parse() .unwrap(); let source = control.source().unwrap(); assert_eq!(source.name(), Some("foo".to_owned())); assert_eq!(source.section(), Some("libs".to_owned())); assert_eq!(source.priority(), Some(super::Priority::Optional)); assert_eq!( source.homepage(), Some("https://example.com".parse().unwrap()) ); let bd = source.build_depends().unwrap(); let entries = bd.entries().collect::>(); assert_eq!(entries.len(), 2); let rel = entries[0].relations().collect::>().pop().unwrap(); assert_eq!(rel.name(), "bar"); assert_eq!( rel.version(), Some(( VersionConstraint::GreaterThanEqual, "1.0.0".parse().unwrap() )) ); let rel = entries[1].relations().collect::>().pop().unwrap(); assert_eq!(rel.name(), "baz"); assert_eq!( rel.version(), Some(( VersionConstraint::GreaterThanEqual, "1.0.0".parse().unwrap() )) ); } #[test] fn test_description() { let control: Control = r#"Source: foo Package: foo Description: this is the short description And the longer one . is on the next lines "# .parse() .unwrap(); let binary = control.binaries().next().unwrap(); assert_eq!( binary.description(), Some( "this is the short description\nAnd the longer one\n.\nis on the next lines" .to_owned() ) ); } #[test] fn test_set_description_on_package_without_description() { let control: Control = r#"Source: foo Package: foo Architecture: amd64 "# .parse() .unwrap(); let mut binary = control.binaries().next().unwrap(); // Set description on a binary that doesn't have one binary.set_description(Some( "Short description\nLonger description\n.\nAnother line", )); let output = binary.to_string(); // Check that the description was set assert_eq!( binary.description(), Some("Short description\nLonger description\n.\nAnother line".to_owned()) ); // Verify the output format has exactly one space indent assert_eq!( output, "Package: foo\nArchitecture: amd64\nDescription: Short description\n Longer description\n .\n Another line\n" ); } #[test] fn test_as_mut_deb822() { let mut control = Control::new(); let deb822 = control.as_mut_deb822(); let mut p = deb822.add_paragraph(); p.set("Source", "foo"); assert_eq!(control.source().unwrap().name(), Some("foo".to_owned())); } #[test] fn test_as_deb822() { let control = Control::new(); let _deb822: &Deb822 = control.as_deb822(); } #[test] fn test_set_depends() { let mut control = Control::new(); let mut binary = control.add_binary("foo"); let relations: Relations = "bar (>= 1.0.0)".parse().unwrap(); binary.set_depends(Some(&relations)); } #[test] fn test_wrap_and_sort() { let mut control: Control = r#"Package: blah Section: libs Package: foo Description: this is a bar blah "# .parse() .unwrap(); control.wrap_and_sort(deb822_lossless::Indentation::Spaces(2), false, None); let expected = r#"Package: blah Section: libs Package: foo Description: this is a bar blah "# .to_owned(); assert_eq!(control.to_string(), expected); } #[test] fn test_wrap_and_sort_source() { let mut control: Control = r#"Source: blah Depends: foo, bar (<= 1.0.0) "# .parse() .unwrap(); control.wrap_and_sort(deb822_lossless::Indentation::Spaces(2), true, None); let expected = r#"Source: blah Depends: bar (<= 1.0.0), foo "# .to_owned(); assert_eq!(control.to_string(), expected); } #[test] fn test_source_wrap_and_sort() { let control: Control = r#"Source: blah Build-Depends: foo, bar (>= 1.0.0) "# .parse() .unwrap(); let mut source = control.source().unwrap(); source.wrap_and_sort(deb822_lossless::Indentation::Spaces(2), true, None); // The actual behavior - the method modifies the source in-place // but doesn't automatically affect the overall control structure // So we just test that the method executes without error assert!(source.build_depends().is_some()); } #[test] fn test_binary_set_breaks() { let mut control = Control::new(); let mut binary = control.add_binary("foo"); let relations: Relations = "bar (>= 1.0.0)".parse().unwrap(); binary.set_breaks(Some(&relations)); assert!(binary.breaks().is_some()); } #[test] fn test_binary_set_pre_depends() { let mut control = Control::new(); let mut binary = control.add_binary("foo"); let relations: Relations = "bar (>= 1.0.0)".parse().unwrap(); binary.set_pre_depends(Some(&relations)); assert!(binary.pre_depends().is_some()); } #[test] fn test_binary_set_provides() { let mut control = Control::new(); let mut binary = control.add_binary("foo"); let relations: Relations = "bar (>= 1.0.0)".parse().unwrap(); binary.set_provides(Some(&relations)); assert!(binary.provides().is_some()); } #[test] fn test_source_build_conflicts() { let control: Control = r#"Source: blah Build-Conflicts: foo, bar (>= 1.0.0) "# .parse() .unwrap(); let source = control.source().unwrap(); let conflicts = source.build_conflicts(); assert!(conflicts.is_some()); } #[test] fn test_source_vcs_svn() { let control: Control = r#"Source: blah Vcs-Svn: https://example.com/svn/repo "# .parse() .unwrap(); let source = control.source().unwrap(); assert_eq!( source.vcs_svn(), Some("https://example.com/svn/repo".to_string()) ); } #[test] fn test_control_from_conversion() { let deb822_data = r#"Source: test Section: libs "#; let deb822: Deb822 = deb822_data.parse().unwrap(); let control = Control::from(deb822); assert!(control.source().is_some()); } #[test] fn test_fields_in_range() { let control_text = r#"Source: test-package Maintainer: Test User Build-Depends: debhelper (>= 12) Package: test-binary Architecture: any Depends: ${shlibs:Depends} Description: Test package This is a test package "#; let control: Control = control_text.parse().unwrap(); // Test range that covers only the Source field let source_start = 0; let source_end = "Source: test-package".len(); let source_range = rowan::TextRange::new((source_start as u32).into(), (source_end as u32).into()); let fields: Vec<_> = control.fields_in_range(source_range).collect(); assert_eq!(fields.len(), 1); assert_eq!(fields[0].key(), Some("Source".to_string())); // Test range that covers multiple fields in source paragraph let maintainer_start = control_text.find("Maintainer:").unwrap(); let build_depends_end = control_text .find("Build-Depends: debhelper (>= 12)") .unwrap() + "Build-Depends: debhelper (>= 12)".len(); let multi_range = rowan::TextRange::new( (maintainer_start as u32).into(), (build_depends_end as u32).into(), ); let fields: Vec<_> = control.fields_in_range(multi_range).collect(); assert_eq!(fields.len(), 2); assert_eq!(fields[0].key(), Some("Maintainer".to_string())); assert_eq!(fields[1].key(), Some("Build-Depends".to_string())); // Test range that spans across paragraphs let cross_para_start = control_text.find("Build-Depends:").unwrap(); let cross_para_end = control_text.find("Architecture: any").unwrap() + "Architecture: any".len(); let cross_range = rowan::TextRange::new( (cross_para_start as u32).into(), (cross_para_end as u32).into(), ); let fields: Vec<_> = control.fields_in_range(cross_range).collect(); assert_eq!(fields.len(), 3); // Build-Depends, Package, Architecture assert_eq!(fields[0].key(), Some("Build-Depends".to_string())); assert_eq!(fields[1].key(), Some("Package".to_string())); assert_eq!(fields[2].key(), Some("Architecture".to_string())); // Test empty range (should return no fields) let empty_range = rowan::TextRange::new(1000.into(), 1001.into()); let fields: Vec<_> = control.fields_in_range(empty_range).collect(); assert_eq!(fields.len(), 0); } #[test] fn test_source_overlaps_range() { let control_text = r#"Source: test-package Maintainer: Test User Package: test-binary Architecture: any "#; let control: Control = control_text.parse().unwrap(); let source = control.source().unwrap(); // Test range that overlaps with source paragraph let overlap_range = rowan::TextRange::new(10.into(), 30.into()); assert!(source.overlaps_range(overlap_range)); // Test range that doesn't overlap with source paragraph let binary_start = control_text.find("Package:").unwrap(); let no_overlap_range = rowan::TextRange::new( (binary_start as u32).into(), ((binary_start + 20) as u32).into(), ); assert!(!source.overlaps_range(no_overlap_range)); // Test range that starts before and ends within source paragraph let partial_overlap = rowan::TextRange::new(0.into(), 15.into()); assert!(source.overlaps_range(partial_overlap)); } #[test] fn test_source_fields_in_range() { let control_text = r#"Source: test-package Maintainer: Test User Build-Depends: debhelper (>= 12) Package: test-binary "#; let control: Control = control_text.parse().unwrap(); let source = control.source().unwrap(); // Test range covering Maintainer field let maintainer_start = control_text.find("Maintainer:").unwrap(); let maintainer_end = maintainer_start + "Maintainer: Test User ".len(); let maintainer_range = rowan::TextRange::new( (maintainer_start as u32).into(), (maintainer_end as u32).into(), ); let fields: Vec<_> = source.fields_in_range(maintainer_range).collect(); assert_eq!(fields.len(), 1); assert_eq!(fields[0].key(), Some("Maintainer".to_string())); // Test range covering multiple fields let all_source_range = rowan::TextRange::new(0.into(), 100.into()); let fields: Vec<_> = source.fields_in_range(all_source_range).collect(); assert_eq!(fields.len(), 3); // Source, Maintainer, Build-Depends } #[test] fn test_binary_overlaps_range() { let control_text = r#"Source: test-package Package: test-binary Architecture: any Depends: ${shlibs:Depends} "#; let control: Control = control_text.parse().unwrap(); let binary = control.binaries().next().unwrap(); // Test range that overlaps with binary paragraph let package_start = control_text.find("Package:").unwrap(); let overlap_range = rowan::TextRange::new( (package_start as u32).into(), ((package_start + 30) as u32).into(), ); assert!(binary.overlaps_range(overlap_range)); // Test range before binary paragraph let no_overlap_range = rowan::TextRange::new(0.into(), 10.into()); assert!(!binary.overlaps_range(no_overlap_range)); } #[test] fn test_binary_fields_in_range() { let control_text = r#"Source: test-package Package: test-binary Architecture: any Depends: ${shlibs:Depends} Description: Test binary This is a test binary package "#; let control: Control = control_text.parse().unwrap(); let binary = control.binaries().next().unwrap(); // Test range covering Architecture and Depends let arch_start = control_text.find("Architecture:").unwrap(); let depends_end = control_text.find("Depends: ${shlibs:Depends}").unwrap() + "Depends: ${shlibs:Depends}".len(); let range = rowan::TextRange::new((arch_start as u32).into(), (depends_end as u32).into()); let fields: Vec<_> = binary.fields_in_range(range).collect(); assert_eq!(fields.len(), 2); assert_eq!(fields[0].key(), Some("Architecture".to_string())); assert_eq!(fields[1].key(), Some("Depends".to_string())); // Test partial overlap with Description field let desc_start = control_text.find("Description:").unwrap(); let partial_range = rowan::TextRange::new( ((desc_start + 5) as u32).into(), ((desc_start + 15) as u32).into(), ); let fields: Vec<_> = binary.fields_in_range(partial_range).collect(); assert_eq!(fields.len(), 1); assert_eq!(fields[0].key(), Some("Description".to_string())); } #[test] fn test_incremental_parsing_use_case() { // This test simulates a real LSP use case where only changed fields are processed let control_text = r#"Source: example Maintainer: John Doe Standards-Version: 4.6.0 Build-Depends: debhelper-compat (= 13) Package: example-bin Architecture: all Depends: ${misc:Depends} Description: Example package This is an example. "#; let control: Control = control_text.parse().unwrap(); // Simulate a change to Standards-Version field let change_start = control_text.find("Standards-Version:").unwrap(); let change_end = change_start + "Standards-Version: 4.6.0".len(); let change_range = rowan::TextRange::new((change_start as u32).into(), (change_end as u32).into()); // Only process fields in the changed range let affected_fields: Vec<_> = control.fields_in_range(change_range).collect(); assert_eq!(affected_fields.len(), 1); assert_eq!( affected_fields[0].key(), Some("Standards-Version".to_string()) ); // Verify that we're not processing unrelated fields for entry in &affected_fields { let key = entry.key().unwrap(); assert_ne!(key, "Maintainer"); assert_ne!(key, "Build-Depends"); assert_ne!(key, "Architecture"); } } #[test] fn test_positioned_parse_errors() { // Test case from the requirements document let input = "Invalid: field\nBroken field without colon"; let parsed = Control::parse(input); // Should have positioned errors accessible let positioned_errors = parsed.positioned_errors(); assert!( !positioned_errors.is_empty(), "Should have positioned errors" ); // Test that we can access error properties for error in positioned_errors { let start_offset: u32 = error.range.start().into(); let end_offset: u32 = error.range.end().into(); // Verify we have meaningful error messages assert!(!error.message.is_empty()); // Verify ranges are valid assert!(start_offset <= end_offset); assert!(end_offset <= input.len() as u32); // Error should have a code assert!(error.code.is_some()); println!( "Error at {:?}: {} (code: {:?})", error.range, error.message, error.code ); } // Should also be able to get string errors for backward compatibility let string_errors = parsed.errors(); assert!(!string_errors.is_empty()); assert_eq!(string_errors.len(), positioned_errors.len()); } #[test] fn test_sort_binaries_basic() { let input = r#"Source: foo Package: libfoo Architecture: all Package: libbar Architecture: all "#; let mut control: Control = input.parse().unwrap(); control.sort_binaries(false); let binaries: Vec<_> = control.binaries().collect(); assert_eq!(binaries.len(), 2); assert_eq!(binaries[0].name(), Some("libbar".to_string())); assert_eq!(binaries[1].name(), Some("libfoo".to_string())); } #[test] fn test_sort_binaries_keep_first() { let input = r#"Source: foo Package: zzz-first Architecture: all Package: libbar Architecture: all Package: libaaa Architecture: all "#; let mut control: Control = input.parse().unwrap(); control.sort_binaries(true); let binaries: Vec<_> = control.binaries().collect(); assert_eq!(binaries.len(), 3); // First binary should remain in place assert_eq!(binaries[0].name(), Some("zzz-first".to_string())); // The rest should be sorted assert_eq!(binaries[1].name(), Some("libaaa".to_string())); assert_eq!(binaries[2].name(), Some("libbar".to_string())); } #[test] fn test_sort_binaries_already_sorted() { let input = r#"Source: foo Package: aaa Architecture: all Package: bbb Architecture: all Package: ccc Architecture: all "#; let mut control: Control = input.parse().unwrap(); control.sort_binaries(false); let binaries: Vec<_> = control.binaries().collect(); assert_eq!(binaries.len(), 3); assert_eq!(binaries[0].name(), Some("aaa".to_string())); assert_eq!(binaries[1].name(), Some("bbb".to_string())); assert_eq!(binaries[2].name(), Some("ccc".to_string())); } #[test] fn test_sort_binaries_no_binaries() { let input = r#"Source: foo Maintainer: test@example.com "#; let mut control: Control = input.parse().unwrap(); control.sort_binaries(false); // Should not crash, just do nothing assert_eq!(control.binaries().count(), 0); } #[test] fn test_sort_binaries_one_binary() { let input = r#"Source: foo Package: bar Architecture: all "#; let mut control: Control = input.parse().unwrap(); control.sort_binaries(false); let binaries: Vec<_> = control.binaries().collect(); assert_eq!(binaries.len(), 1); assert_eq!(binaries[0].name(), Some("bar".to_string())); } #[test] fn test_sort_binaries_preserves_fields() { let input = r#"Source: foo Package: zzz Architecture: any Depends: libc6 Description: ZZZ package Package: aaa Architecture: all Depends: ${misc:Depends} Description: AAA package "#; let mut control: Control = input.parse().unwrap(); control.sort_binaries(false); let binaries: Vec<_> = control.binaries().collect(); assert_eq!(binaries.len(), 2); // First binary should be aaa assert_eq!(binaries[0].name(), Some("aaa".to_string())); assert_eq!(binaries[0].architecture(), Some("all".to_string())); assert_eq!(binaries[0].description(), Some("AAA package".to_string())); // Second binary should be zzz assert_eq!(binaries[1].name(), Some("zzz".to_string())); assert_eq!(binaries[1].architecture(), Some("any".to_string())); assert_eq!(binaries[1].description(), Some("ZZZ package".to_string())); } #[test] fn test_remove_binary_basic() { let mut control = Control::new(); control.add_binary("foo"); assert_eq!(control.binaries().count(), 1); assert!(control.remove_binary("foo")); assert_eq!(control.binaries().count(), 0); } #[test] fn test_remove_binary_nonexistent() { let mut control = Control::new(); control.add_binary("foo"); assert!(!control.remove_binary("bar")); assert_eq!(control.binaries().count(), 1); } #[test] fn test_remove_binary_multiple() { let mut control = Control::new(); control.add_binary("foo"); control.add_binary("bar"); control.add_binary("baz"); assert_eq!(control.binaries().count(), 3); assert!(control.remove_binary("bar")); assert_eq!(control.binaries().count(), 2); let names: Vec<_> = control.binaries().map(|b| b.name().unwrap()).collect(); assert_eq!(names, vec!["foo", "baz"]); } #[test] fn test_remove_binary_preserves_source() { let input = r#"Source: mypackage Package: foo Architecture: all Package: bar Architecture: all "#; let mut control: Control = input.parse().unwrap(); assert!(control.source().is_some()); assert_eq!(control.binaries().count(), 2); assert!(control.remove_binary("foo")); // Source should still be present assert!(control.source().is_some()); assert_eq!( control.source().unwrap().name(), Some("mypackage".to_string()) ); // Only bar should remain assert_eq!(control.binaries().count(), 1); assert_eq!( control.binaries().next().unwrap().name(), Some("bar".to_string()) ); } #[test] fn test_remove_binary_from_parsed() { let input = r#"Source: test Package: test-bin Architecture: any Depends: libc6 Description: Test binary Package: test-lib Architecture: all Description: Test library "#; let mut control: Control = input.parse().unwrap(); assert_eq!(control.binaries().count(), 2); assert!(control.remove_binary("test-bin")); let output = control.to_string(); assert!(!output.contains("test-bin")); assert!(output.contains("test-lib")); assert!(output.contains("Source: test")); } #[test] fn test_build_depends_preserves_indentation_after_removal() { let input = r#"Source: acpi-support Section: admin Priority: optional Maintainer: Debian Acpi Team Build-Depends: debhelper (>= 10), quilt (>= 0.40), libsystemd-dev [linux-any], dh-systemd (>= 1.5), pkg-config "#; let control: Control = input.parse().unwrap(); let mut source = control.source().unwrap(); // Get the Build-Depends let mut build_depends = source.build_depends().unwrap(); // Find and remove dh-systemd entry let mut to_remove = Vec::new(); for (idx, entry) in build_depends.entries().enumerate() { for relation in entry.relations() { if relation.name() == "dh-systemd" { to_remove.push(idx); break; } } } for idx in to_remove.into_iter().rev() { build_depends.remove_entry(idx); } // Set it back source.set_build_depends(&build_depends); let output = source.to_string(); // The indentation should be preserved (4 spaces on the continuation line) assert!( output.contains("Build-Depends: debhelper (>= 10), quilt (>= 0.40),\n libsystemd-dev [linux-any], pkg-config"), "Expected 4-space indentation to be preserved, but got:\n{}", output ); } #[test] fn test_build_depends_direct_string_set_loses_indentation() { let input = r#"Source: acpi-support Section: admin Priority: optional Maintainer: Debian Acpi Team Build-Depends: debhelper (>= 10), quilt (>= 0.40), libsystemd-dev [linux-any], dh-systemd (>= 1.5), pkg-config "#; let control: Control = input.parse().unwrap(); let mut source = control.source().unwrap(); // Get the Build-Depends as Relations let mut build_depends = source.build_depends().unwrap(); // Find and remove dh-systemd entry let mut to_remove = Vec::new(); for (idx, entry) in build_depends.entries().enumerate() { for relation in entry.relations() { if relation.name() == "dh-systemd" { to_remove.push(idx); break; } } } for idx in to_remove.into_iter().rev() { build_depends.remove_entry(idx); } // Set it back using the string representation - this is what might cause the bug source.set("Build-Depends", &build_depends.to_string()); let output = source.to_string(); println!("Output with string set:"); println!("{}", output); // Check if indentation is preserved // This test documents the current behavior - it may fail if indentation is lost assert!( output.contains("Build-Depends: debhelper (>= 10), quilt (>= 0.40),\n libsystemd-dev [linux-any], pkg-config"), "Expected 4-space indentation to be preserved, but got:\n{}", output ); } #[test] fn test_parse_mode_strict_default() { let control = Control::new(); assert_eq!(control.parse_mode(), ParseMode::Strict); let control: Control = "Source: test\n".parse().unwrap(); assert_eq!(control.parse_mode(), ParseMode::Strict); } #[test] fn test_parse_mode_new_with_mode() { let control_relaxed = Control::new_with_mode(ParseMode::Relaxed); assert_eq!(control_relaxed.parse_mode(), ParseMode::Relaxed); let control_substvar = Control::new_with_mode(ParseMode::Substvar); assert_eq!(control_substvar.parse_mode(), ParseMode::Substvar); } #[test] fn test_relaxed_mode_handles_broken_relations() { let input = r#"Source: test-package Build-Depends: debhelper, @@@broken@@@, python3 Package: test-pkg Depends: libfoo, %%%invalid%%%, libbar "#; let (control, _errors) = Control::read_relaxed(input.as_bytes()).unwrap(); assert_eq!(control.parse_mode(), ParseMode::Relaxed); // These should not panic even with broken syntax if let Some(source) = control.source() { let bd = source.build_depends(); assert!(bd.is_some()); let relations = bd.unwrap(); // Should have parsed the valid parts in relaxed mode assert!(relations.len() >= 2); // at least debhelper and python3 } for binary in control.binaries() { let deps = binary.depends(); assert!(deps.is_some()); let relations = deps.unwrap(); // Should have parsed the valid parts assert!(relations.len() >= 2); // at least libfoo and libbar } } #[test] fn test_substvar_mode_via_parse() { // Parse normally to get valid structure, but then we'd need substvar mode // Actually, we can't test this properly without the ability to set mode on parsed content // So let's just test that read_relaxed with substvars works let input = r#"Source: test-package Build-Depends: debhelper, ${misc:Depends} Package: test-pkg Depends: ${shlibs:Depends}, libfoo "#; // This will parse in relaxed mode, which also allows substvars to some degree let (control, _errors) = Control::read_relaxed(input.as_bytes()).unwrap(); if let Some(source) = control.source() { // Should parse without panic even with substvars let bd = source.build_depends(); assert!(bd.is_some()); } for binary in control.binaries() { let deps = binary.depends(); assert!(deps.is_some()); } } #[test] #[should_panic] fn test_strict_mode_panics_on_broken_syntax() { let input = r#"Source: test-package Build-Depends: debhelper, @@@broken@@@ "#; // Strict mode (default) should panic on invalid syntax let control: Control = input.parse().unwrap(); if let Some(source) = control.source() { // This should panic when trying to parse the broken Build-Depends let _ = source.build_depends(); } } #[test] fn test_from_file_relaxed_sets_relaxed_mode() { let input = r#"Source: test-package Maintainer: Test "#; let (control, _errors) = Control::read_relaxed(input.as_bytes()).unwrap(); assert_eq!(control.parse_mode(), ParseMode::Relaxed); } #[test] fn test_parse_mode_propagates_to_paragraphs() { let input = r#"Source: test-package Build-Depends: debhelper, @@@invalid@@@, python3 Package: test-pkg Depends: libfoo, %%%bad%%%, libbar "#; // Parse in relaxed mode let (control, _) = Control::read_relaxed(input.as_bytes()).unwrap(); // The source and binary paragraphs should inherit relaxed mode // and not panic when parsing relations if let Some(source) = control.source() { assert!(source.build_depends().is_some()); } for binary in control.binaries() { assert!(binary.depends().is_some()); } } #[test] fn test_preserves_final_newline() { // Test that the final newline is preserved when writing control files let input_with_newline = "Source: test-package\nMaintainer: Test \n\nPackage: test-pkg\nArchitecture: any\n"; let control: Control = input_with_newline.parse().unwrap(); let output = control.to_string(); assert_eq!(output, input_with_newline); } #[test] fn test_preserves_no_final_newline() { // Test that absence of final newline is also preserved (even though it's not POSIX-compliant) let input_without_newline = "Source: test-package\nMaintainer: Test \n\nPackage: test-pkg\nArchitecture: any"; let control: Control = input_without_newline.parse().unwrap(); let output = control.to_string(); assert_eq!(output, input_without_newline); } #[test] fn test_final_newline_after_modifications() { // Test that final newline is preserved even after modifications let input = "Source: test-package\nMaintainer: Test \n\nPackage: test-pkg\nArchitecture: any\n"; let control: Control = input.parse().unwrap(); // Make a modification let mut source = control.source().unwrap(); source.set_section(Some("utils")); let output = control.to_string(); let expected = "Source: test-package\nSection: utils\nMaintainer: Test \n\nPackage: test-pkg\nArchitecture: any\n"; assert_eq!(output, expected); } } debian-control-0.2.14/src/lossless/mod.rs000064400000000000000000000017361046102023000164020ustar 00000000000000//! Lossless parser for various Debian control files //! //! This library provides a parser for various Debian control files, such as `control`, `changes`, //! and apt `Release`, `Packages`, and `Sources` files. The parser is lossless, meaning that it //! preserves all formatting as well as any possible errors in the files. //! //! # Example with positioned errors //! //! ``` //! use debian_control::lossless::{Control, PositionedParseError}; //! //! let input = "Invalid: field\nBroken field without colon"; //! let parsed = Control::parse(input); //! //! // Access positioned errors for precise error reporting //! for error in parsed.positioned_errors() { //! println!("Error at {:?}: {}", error.range, error.message); //! // Use error.range for IDE/language server integration //! } //! ``` pub mod apt; pub mod buildinfo; pub mod changes; pub mod control; pub mod relations; pub use control::*; pub use deb822_lossless::{Parse, PositionedParseError}; pub use relations::*; debian-control-0.2.14/src/lossless/relations.rs000064400000000000000000005266151046102023000176330ustar 00000000000000//! Parser for relationship fields like `Depends`, `Recommends`, etc. //! //! # Example //! ``` //! use debian_control::lossless::relations::{Relations, Relation}; //! use debian_control::relations::VersionConstraint; //! //! let mut relations: Relations = r"python3-dulwich (>= 0.19.0), python3-requests, python3-urllib3 (<< 1.26.0)".parse().unwrap(); //! assert_eq!(relations.to_string(), "python3-dulwich (>= 0.19.0), python3-requests, python3-urllib3 (<< 1.26.0)"); //! assert!(relations.satisfied_by(|name: &str| -> Option { //! match name { //! "python3-dulwich" => Some("0.19.0".parse().unwrap()), //! "python3-requests" => Some("2.25.1".parse().unwrap()), //! "python3-urllib3" => Some("1.25.11".parse().unwrap()), //! _ => None //! }})); //! relations.remove_entry(1); //! relations.get_entry(0).unwrap().get_relation(0).unwrap().set_archqual("amd64"); //! assert_eq!(relations.to_string(), "python3-dulwich:amd64 (>= 0.19.0), python3-urllib3 (<< 1.26.0)"); //! ``` use crate::relations::SyntaxKind::{self, *}; use crate::relations::{BuildProfile, VersionConstraint}; use debversion::Version; use rowan::{Direction, NodeOrToken}; use std::collections::HashSet; /// Error type for parsing relations fields #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct ParseError(Vec); impl std::fmt::Display for ParseError { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { for err in &self.0 { writeln!(f, "{}", err)?; } Ok(()) } } impl std::error::Error for ParseError {} /// Second, implementing the `Language` trait teaches rowan to convert between /// these two SyntaxKind types, allowing for a nicer SyntaxNode API where /// "kinds" are values from our `enum SyntaxKind`, instead of plain u16 values. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] enum Lang {} impl rowan::Language for Lang { type Kind = SyntaxKind; fn kind_from_raw(raw: rowan::SyntaxKind) -> Self::Kind { unsafe { std::mem::transmute::(raw.0) } } fn kind_to_raw(kind: Self::Kind) -> rowan::SyntaxKind { kind.into() } } /// GreenNode is an immutable tree, which is cheap to change, /// but doesn't contain offsets and parent pointers. use rowan::{GreenNode, GreenToken}; /// You can construct GreenNodes by hand, but a builder /// is helpful for top-down parsers: it maintains a stack /// of currently in-progress nodes use rowan::GreenNodeBuilder; /// The parse results are stored as a "green tree". /// We'll discuss working with the results later struct Parse { green_node: GreenNode, #[allow(unused)] errors: Vec, } fn parse(text: &str, allow_substvar: bool) -> Parse { struct Parser { /// input tokens, including whitespace, /// in *reverse* order. tokens: Vec<(SyntaxKind, String)>, /// the in-progress tree. builder: GreenNodeBuilder<'static>, /// the list of syntax errors we've accumulated /// so far. errors: Vec, /// whether to allow substvars allow_substvar: bool, } impl Parser { fn parse_substvar(&mut self) { self.builder.start_node(SyntaxKind::SUBSTVAR.into()); self.bump(); if self.current() != Some(L_CURLY) { self.error(format!("expected {{ but got {:?}", self.current()).to_string()); } else { self.bump(); } loop { match self.current() { Some(IDENT) | Some(COLON) => { self.bump(); } Some(R_CURLY) => { break; } e => { self.error(format!("expected identifier or : but got {:?}", e).to_string()); } } } if self.current() != Some(R_CURLY) { self.error(format!("expected }} but got {:?}", self.current()).to_string()); } else { self.bump(); } self.builder.finish_node(); } fn parse_entry(&mut self) { self.skip_ws(); self.builder.start_node(SyntaxKind::ENTRY.into()); loop { self.parse_relation(); match self.peek_past_ws() { Some(COMMA) => { break; } Some(PIPE) => { self.skip_ws(); self.bump(); self.skip_ws(); } None => { self.skip_ws(); break; } _ => { self.skip_ws(); self.builder.start_node(SyntaxKind::ERROR.into()); match self.tokens.pop() { Some((k, t)) => { self.builder.token(k.into(), t.as_str()); self.errors .push(format!("Expected comma or pipe, not {:?}", (k, t))); } None => { self.errors .push("Expected comma or pipe, got end of file".to_string()); } } self.builder.finish_node(); } } } self.builder.finish_node(); } fn error(&mut self, error: String) { self.errors.push(error); self.builder.start_node(SyntaxKind::ERROR.into()); if self.current().is_some() { self.bump(); } self.builder.finish_node(); } fn parse_relation(&mut self) { self.builder.start_node(SyntaxKind::RELATION.into()); if self.current() == Some(IDENT) { self.bump(); } else { self.error("Expected package name".to_string()); } match self.peek_past_ws() { Some(COLON) => { self.skip_ws(); self.builder.start_node(ARCHQUAL.into()); self.bump(); self.skip_ws(); if self.current() == Some(IDENT) { self.bump(); } else { self.error("Expected architecture name".to_string()); } self.builder.finish_node(); self.skip_ws(); } Some(PIPE) | Some(COMMA) => {} None | Some(L_PARENS) | Some(L_BRACKET) | Some(L_ANGLE) => { self.skip_ws(); } e => { self.skip_ws(); self.error(format!( "Expected ':' or '|' or '[' or '<' or ',' but got {:?}", e )); } } if self.peek_past_ws() == Some(L_PARENS) { self.skip_ws(); self.builder.start_node(VERSION.into()); self.bump(); self.skip_ws(); self.builder.start_node(CONSTRAINT.into()); while self.current() == Some(L_ANGLE) || self.current() == Some(R_ANGLE) || self.current() == Some(EQUAL) { self.bump(); } self.builder.finish_node(); self.skip_ws(); // Read IDENT and COLON tokens until we see R_PARENS // This handles version strings with epochs (e.g., "1:2.3.2-2~") while matches!(self.current(), Some(IDENT) | Some(COLON)) { self.bump(); } if self.current() == Some(R_PARENS) { self.bump(); } else { self.error("Expected ')'".to_string()); } self.builder.finish_node(); } if self.peek_past_ws() == Some(L_BRACKET) { self.skip_ws(); self.builder.start_node(ARCHITECTURES.into()); self.bump(); loop { self.skip_ws(); match self.current() { Some(NOT) => { self.bump(); } Some(IDENT) => { self.bump(); } Some(R_BRACKET) => { self.bump(); break; } _ => { self.error("Expected architecture name or '!' or ']'".to_string()); } } } self.builder.finish_node(); } while self.peek_past_ws() == Some(L_ANGLE) { self.skip_ws(); self.builder.start_node(PROFILES.into()); self.bump(); loop { self.skip_ws(); match self.current() { Some(IDENT) => { self.bump(); } Some(NOT) => { self.bump(); self.skip_ws(); if self.current() == Some(IDENT) { self.bump(); } else { self.error("Expected profile".to_string()); } } Some(R_ANGLE) => { self.bump(); break; } None => { self.error("Expected profile or '>'".to_string()); break; } _ => { self.error("Expected profile or '!' or '>'".to_string()); } } } self.builder.finish_node(); } self.builder.finish_node(); } fn parse(mut self) -> Parse { self.builder.start_node(SyntaxKind::ROOT.into()); self.skip_ws(); while self.current().is_some() { match self.current() { Some(IDENT) => self.parse_entry(), Some(DOLLAR) => { if self.allow_substvar { self.parse_substvar() } else { self.error("Substvars are not allowed".to_string()); } } Some(COMMA) => { // Empty entry, but that's okay - probably? } Some(c) => { self.error(format!("expected $ or identifier but got {:?}", c)); } None => { self.error("expected identifier but got end of file".to_string()); } } self.skip_ws(); match self.current() { Some(COMMA) => { self.bump(); } None => { break; } c => { self.error(format!("expected comma or end of file but got {:?}", c)); } } self.skip_ws(); } self.builder.finish_node(); // Turn the builder into a GreenNode Parse { green_node: self.builder.finish(), errors: self.errors, } } /// Advance one token, adding it to the current branch of the tree builder. fn bump(&mut self) { let (kind, text) = self.tokens.pop().unwrap(); self.builder.token(kind.into(), text.as_str()); } /// Peek at the first unprocessed token fn current(&self) -> Option { self.tokens.last().map(|(kind, _)| *kind) } fn skip_ws(&mut self) { while self.current() == Some(WHITESPACE) || self.current() == Some(NEWLINE) { self.bump() } } fn peek_past_ws(&self) -> Option { let mut i = self.tokens.len(); while i > 0 { i -= 1; match self.tokens[i].0 { WHITESPACE | NEWLINE => {} _ => return Some(self.tokens[i].0), } } None } } let mut tokens = crate::relations::lex(text); tokens.reverse(); Parser { tokens, builder: GreenNodeBuilder::new(), errors: Vec::new(), allow_substvar, } .parse() } // To work with the parse results we need a view into the // green tree - the Syntax tree. // It is also immutable, like a GreenNode, // but it contains parent pointers, offsets, and // has identity semantics. type SyntaxNode = rowan::SyntaxNode; #[allow(unused)] type SyntaxToken = rowan::SyntaxToken; #[allow(unused)] type SyntaxElement = rowan::NodeOrToken; impl Parse { fn root_mut(&self) -> Relations { Relations::cast(SyntaxNode::new_root_mut(self.green_node.clone())).unwrap() } } macro_rules! ast_node { ($ast:ident, $kind:ident) => { /// A node in the syntax tree representing a $ast #[repr(transparent)] pub struct $ast(SyntaxNode); impl $ast { #[allow(unused)] fn cast(node: SyntaxNode) -> Option { if node.kind() == $kind { Some(Self(node)) } else { None } } } impl std::fmt::Display for $ast { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str(&self.0.text().to_string()) } } }; } ast_node!(Relations, ROOT); ast_node!(Entry, ENTRY); ast_node!(Relation, RELATION); ast_node!(Substvar, SUBSTVAR); impl PartialEq for Relations { fn eq(&self, other: &Self) -> bool { self.entries().collect::>() == other.entries().collect::>() } } impl PartialEq for Entry { fn eq(&self, other: &Self) -> bool { self.relations().collect::>() == other.relations().collect::>() } } impl PartialEq for Relation { fn eq(&self, other: &Self) -> bool { self.name() == other.name() && self.version() == other.version() && self.archqual() == other.archqual() && self.architectures().map(|x| x.collect::>()) == other.architectures().map(|x| x.collect::>()) && self.profiles().eq(other.profiles()) } } #[cfg(feature = "serde")] impl serde::Serialize for Relations { fn serialize(&self, serializer: S) -> Result { let rep = self.to_string(); serializer.serialize_str(&rep) } } #[cfg(feature = "serde")] impl<'de> serde::Deserialize<'de> for Relations { fn deserialize>(deserializer: D) -> Result { let s = String::deserialize(deserializer)?; let relations = s.parse().map_err(serde::de::Error::custom)?; Ok(relations) } } impl std::fmt::Debug for Relations { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let mut s = f.debug_struct("Relations"); for entry in self.entries() { s.field("entry", &entry); } s.finish() } } impl std::fmt::Debug for Entry { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let mut s = f.debug_struct("Entry"); for relation in self.relations() { s.field("relation", &relation); } s.finish() } } #[cfg(feature = "serde")] impl serde::Serialize for Entry { fn serialize(&self, serializer: S) -> Result { let rep = self.to_string(); serializer.serialize_str(&rep) } } #[cfg(feature = "serde")] impl<'de> serde::Deserialize<'de> for Entry { fn deserialize>(deserializer: D) -> Result { let s = String::deserialize(deserializer)?; let entry = s.parse().map_err(serde::de::Error::custom)?; Ok(entry) } } impl std::fmt::Debug for Relation { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let mut s = f.debug_struct("Relation"); s.field("name", &self.name()); if let Some((vc, version)) = self.version() { s.field("version", &vc); s.field("version", &version); } s.finish() } } #[cfg(feature = "serde")] impl serde::Serialize for Relation { fn serialize(&self, serializer: S) -> Result { let rep = self.to_string(); serializer.serialize_str(&rep) } } #[cfg(feature = "serde")] impl<'de> serde::Deserialize<'de> for Relation { fn deserialize>(deserializer: D) -> Result { let s = String::deserialize(deserializer)?; let relation = s.parse().map_err(serde::de::Error::custom)?; Ok(relation) } } impl Default for Relations { fn default() -> Self { Self::new() } } /// Check if a package name should be treated as special for sorting purposes. /// /// Special names include substitution variables (like `${misc:Depends}`) /// and template variables (like `@cdbs@`). fn is_special_package_name(name: &str) -> bool { // Substitution variables like ${misc:Depends} if name.starts_with("${") && name.ends_with('}') { return true; } // Template variables like @cdbs@ if name.starts_with('@') && name.ends_with('@') { return true; } false } /// Trait for defining sorting order of package relations. pub trait SortingOrder { /// Compare two package names for sorting. /// /// Returns true if `name1` should come before `name2`. fn lt(&self, name1: &str, name2: &str) -> bool; /// Check if a package name should be ignored for sorting purposes. fn ignore(&self, name: &str) -> bool; } /// Default sorting order (lexicographical with special items last). #[derive(Debug, Clone, Copy, Default)] pub struct DefaultSortingOrder; impl SortingOrder for DefaultSortingOrder { fn lt(&self, name1: &str, name2: &str) -> bool { let special1 = is_special_package_name(name1); let special2 = is_special_package_name(name2); // Special items always come last if special1 && !special2 { return false; } if !special1 && special2 { return true; } if special1 && special2 { // Both special - maintain original order return false; } // Both are regular packages, use alphabetical order name1 < name2 } fn ignore(&self, name: &str) -> bool { is_special_package_name(name) } } /// Sorting order matching wrap-and-sort behavior. /// /// This sorting order matches the behavior of the devscripts wrap-and-sort tool. /// It sorts packages into three groups: /// 1. Build-system packages (debhelper-compat, cdbs, etc.) - sorted first /// 2. Regular packages starting with [a-z0-9] - sorted in the middle /// 3. Substvars and other special packages - sorted last /// /// Within each group, packages are sorted lexicographically. #[derive(Debug, Clone, Copy, Default)] pub struct WrapAndSortOrder; impl WrapAndSortOrder { /// Build systems that should be sorted first, matching wrap-and-sort const BUILD_SYSTEMS: &'static [&'static str] = &[ "cdbs", "debhelper-compat", "debhelper", "debputy", "dpkg-build-api", "dpkg-dev", ]; fn get_sort_key<'a>(&self, name: &'a str) -> (i32, &'a str) { // Check if it's a build system (including dh-* packages) if Self::BUILD_SYSTEMS.contains(&name) || name.starts_with("dh-") { return (-1, name); } // Check if it starts with a regular character if name .chars() .next() .is_some_and(|c| c.is_ascii_lowercase() || c.is_ascii_digit()) { return (0, name); } // Special packages (substvars, etc.) go last (1, name) } } impl SortingOrder for WrapAndSortOrder { fn lt(&self, name1: &str, name2: &str) -> bool { self.get_sort_key(name1) < self.get_sort_key(name2) } fn ignore(&self, _name: &str) -> bool { // wrap-and-sort doesn't ignore any packages - it sorts everything false } } impl Relations { /// Create a new relations field pub fn new() -> Self { Self::from(vec![]) } /// Wrap and sort this relations field #[must_use] pub fn wrap_and_sort(self) -> Self { let mut entries = self .entries() .map(|e| e.wrap_and_sort()) .collect::>(); entries.sort(); // TODO: preserve comments Self::from(entries) } /// Iterate over the entries in this relations field pub fn entries(&self) -> impl Iterator + '_ { self.0.children().filter_map(Entry::cast) } /// Iterate over the entries in this relations field pub fn iter(&self) -> impl Iterator + '_ { self.entries() } /// Remove the entry at the given index pub fn get_entry(&self, idx: usize) -> Option { self.entries().nth(idx) } /// Remove the entry at the given index pub fn remove_entry(&mut self, idx: usize) -> Entry { let mut entry = self.get_entry(idx).unwrap(); entry.remove(); entry } /// Helper to collect all consecutive WHITESPACE/NEWLINE tokens starting from a node fn collect_whitespace(start: Option>) -> String { let mut pattern = String::new(); let mut current = start; while let Some(token) = current { if matches!(token.kind(), WHITESPACE | NEWLINE) { if let NodeOrToken::Token(t) = &token { pattern.push_str(t.text()); } current = token.next_sibling_or_token(); } else { break; } } pattern } /// Helper to convert a NodeOrToken to its green equivalent fn to_green(node: &NodeOrToken) -> NodeOrToken { match node { NodeOrToken::Node(n) => NodeOrToken::Node(n.green().into()), NodeOrToken::Token(t) => NodeOrToken::Token(t.green().to_owned()), } } /// Helper to check if a token is whitespace fn is_whitespace_token(token: &GreenToken) -> bool { token.kind() == rowan::SyntaxKind(WHITESPACE as u16) || token.kind() == rowan::SyntaxKind(NEWLINE as u16) } /// Helper to strip trailing whitespace tokens from a list of green children fn strip_trailing_ws_from_children( mut children: Vec>, ) -> Vec> { while let Some(last) = children.last() { if let NodeOrToken::Token(t) = last { if Self::is_whitespace_token(t) { children.pop(); } else { break; } } else { break; } } children } /// Helper to strip trailing whitespace from a RELATION node's children fn strip_relation_trailing_ws(relation: &SyntaxNode) -> GreenNode { let children: Vec<_> = relation .children_with_tokens() .map(|c| Self::to_green(&c)) .collect(); let stripped = Self::strip_trailing_ws_from_children(children); GreenNode::new(relation.kind().into(), stripped) } /// Helper to build nodes for insertion with odd syntax fn build_odd_syntax_nodes( before_ws: &str, after_ws: &str, ) -> Vec> { [ (!before_ws.is_empty()) .then(|| NodeOrToken::Token(GreenToken::new(WHITESPACE.into(), before_ws))), Some(NodeOrToken::Token(GreenToken::new(COMMA.into(), ","))), (!after_ws.is_empty()) .then(|| NodeOrToken::Token(GreenToken::new(WHITESPACE.into(), after_ws))), ] .into_iter() .flatten() .collect() } /// Detect if relations use odd syntax (whitespace before comma) and return whitespace parts fn detect_odd_syntax(&self) -> Option<(String, String)> { for entry_node in self.entries() { let mut node = entry_node.0.next_sibling_or_token()?; // Skip whitespace tokens and collect their text let mut before = String::new(); while matches!(node.kind(), WHITESPACE | NEWLINE) { if let NodeOrToken::Token(t) = &node { before.push_str(t.text()); } node = node.next_sibling_or_token()?; } // Check if we found a comma after whitespace if node.kind() == COMMA && !before.is_empty() { let after = Self::collect_whitespace(node.next_sibling_or_token()); return Some((before, after)); } } None } /// Detect the most common whitespace pattern after commas in the relations. /// /// This matches debmutate's behavior of analyzing existing whitespace /// patterns to preserve formatting when adding entries. /// /// # Arguments /// * `default` - The default whitespace pattern to use if no pattern is detected fn detect_whitespace_pattern(&self, default: &str) -> String { use std::collections::HashMap; let entries: Vec<_> = self.entries().collect(); let num_entries = entries.len(); if num_entries == 0 { // Check if there are any substvars if self.substvars().next().is_some() { // Has substvars but no entries - use default spacing return default.to_string(); } return String::from(""); // Truly empty - first entry gets no prefix } if num_entries == 1 { // Single entry - check if there's a pattern after it if let Some(node) = entries[0].0.next_sibling_or_token() { if node.kind() == COMMA { let pattern = Self::collect_whitespace(node.next_sibling_or_token()); if !pattern.is_empty() { return pattern; } } } return default.to_string(); // Use default for single entry with no pattern } // Count whitespace patterns after commas (excluding the last entry) let mut whitespace_counts: HashMap = HashMap::new(); for (i, entry) in entries.iter().enumerate() { if i == num_entries - 1 { break; // Skip the last entry } // Look for comma and whitespace after this entry if let Some(mut node) = entry.0.next_sibling_or_token() { // Skip any whitespace/newlines before the comma (odd syntax) while matches!(node.kind(), WHITESPACE | NEWLINE) { if let Some(next) = node.next_sibling_or_token() { node = next; } else { break; } } // Found comma, collect all whitespace/newlines after it if node.kind() == COMMA { let pattern = Self::collect_whitespace(node.next_sibling_or_token()); if !pattern.is_empty() { *whitespace_counts.entry(pattern).or_insert(0) += 1; } } } } // If there's exactly one pattern, use it if whitespace_counts.len() == 1 { if let Some((ws, _)) = whitespace_counts.iter().next() { return ws.clone(); } } // Multiple patterns - use the most common if let Some((ws, _)) = whitespace_counts.iter().max_by_key(|(_, count)| *count) { return ws.clone(); } // Use the provided default default.to_string() } /// Insert a new entry at the given index /// /// # Arguments /// * `idx` - The index to insert at /// * `entry` - The entry to insert /// * `default_sep` - Optional default separator to use if no pattern is detected (defaults to " ") pub fn insert_with_separator(&mut self, idx: usize, entry: Entry, default_sep: Option<&str>) { let is_empty = self.entries().next().is_none(); let whitespace = self.detect_whitespace_pattern(default_sep.unwrap_or(" ")); // Strip trailing whitespace first self.strip_trailing_whitespace(); // Detect odd syntax (whitespace before comma) let odd_syntax = self.detect_odd_syntax(); let (position, new_children) = if let Some(current_entry) = self.entries().nth(idx) { let to_insert = if idx == 0 && is_empty { vec![entry.0.green().into()] } else if let Some((before_ws, after_ws)) = &odd_syntax { let mut nodes = vec![entry.0.green().into()]; nodes.extend(Self::build_odd_syntax_nodes(before_ws, after_ws)); nodes } else { vec![ entry.0.green().into(), NodeOrToken::Token(GreenToken::new(COMMA.into(), ",")), NodeOrToken::Token(GreenToken::new(WHITESPACE.into(), whitespace.as_str())), ] }; (current_entry.0.index(), to_insert) } else { let child_count = self.0.children_with_tokens().count(); let to_insert = if idx == 0 { vec![entry.0.green().into()] } else if let Some((before_ws, after_ws)) = &odd_syntax { let mut nodes = Self::build_odd_syntax_nodes(before_ws, after_ws); nodes.push(entry.0.green().into()); nodes } else { vec![ NodeOrToken::Token(GreenToken::new(COMMA.into(), ",")), NodeOrToken::Token(GreenToken::new(WHITESPACE.into(), whitespace.as_str())), entry.0.green().into(), ] }; (child_count, to_insert) }; // We can safely replace the root here since Relations is a root node self.0 = SyntaxNode::new_root_mut( self.0.replace_with( self.0 .green() .splice_children(position..position, new_children), ), ); } /// Insert a new entry at the given index with default separator pub fn insert(&mut self, idx: usize, entry: Entry) { self.insert_with_separator(idx, entry, None); } /// Helper to recursively strip trailing whitespace from an ENTRY node fn strip_entry_trailing_ws(entry: &SyntaxNode) -> GreenNode { let mut children: Vec<_> = entry .children_with_tokens() .map(|c| Self::to_green(&c)) .collect(); // Strip trailing whitespace from the last RELATION if present if let Some(NodeOrToken::Node(last)) = children.last() { if last.kind() == rowan::SyntaxKind(RELATION as u16) { // Replace last child with stripped version let relation_node = entry.children().last().unwrap(); children.pop(); children.push(NodeOrToken::Node(Self::strip_relation_trailing_ws( &relation_node, ))); } } // Strip trailing whitespace tokens at entry level let stripped = Self::strip_trailing_ws_from_children(children); GreenNode::new(ENTRY.into(), stripped) } fn strip_trailing_whitespace(&mut self) { let mut children: Vec<_> = self .0 .children_with_tokens() .map(|c| Self::to_green(&c)) .collect(); // Strip trailing whitespace from the last ENTRY if present if let Some(NodeOrToken::Node(last)) = children.last() { if last.kind() == rowan::SyntaxKind(ENTRY as u16) { let last_entry = self.0.children().last().unwrap(); children.pop(); children.push(NodeOrToken::Node(Self::strip_entry_trailing_ws( &last_entry, ))); } } // Strip trailing whitespace tokens at root level let stripped = Self::strip_trailing_ws_from_children(children); let nc = self.0.children_with_tokens().count(); self.0 = SyntaxNode::new_root_mut( self.0 .replace_with(self.0.green().splice_children(0..nc, stripped)), ); } /// Replace the entry at the given index pub fn replace(&mut self, idx: usize, entry: Entry) { let current_entry = self.get_entry(idx).unwrap(); self.0.splice_children( current_entry.0.index()..current_entry.0.index() + 1, vec![entry.0.into()], ); } /// Push a new entry to the relations field pub fn push(&mut self, entry: Entry) { let pos = self.entries().count(); self.insert(pos, entry); } /// Return the names of substvars in this relations field pub fn substvars(&self) -> impl Iterator + '_ { self.0 .children() .filter_map(Substvar::cast) .map(|s| s.to_string()) } /// Parse a relations field from a string, allowing syntax errors pub fn parse_relaxed(s: &str, allow_substvar: bool) -> (Relations, Vec) { let parse = parse(s, allow_substvar); (parse.root_mut(), parse.errors) } /// Check if this relations field is satisfied by the given package versions. pub fn satisfied_by(&self, package_version: impl crate::VersionLookup + Copy) -> bool { self.entries().all(|e| e.satisfied_by(package_version)) } /// Check if this relations field is empty pub fn is_empty(&self) -> bool { self.entries().count() == 0 } /// Get the number of entries in this relations field pub fn len(&self) -> usize { self.entries().count() } /// Ensure that a package has at least a minimum version constraint. /// /// If the package already exists with a version constraint that satisfies /// the minimum version, it is left unchanged. Otherwise, the constraint /// is updated or added. /// /// # Arguments /// * `package` - The package name /// * `minimum_version` - The minimum version required /// /// # Example /// ``` /// use debian_control::lossless::relations::Relations; /// /// let mut relations: Relations = "debhelper (>= 9)".parse().unwrap(); /// relations.ensure_minimum_version("debhelper", &"12".parse().unwrap()); /// assert_eq!(relations.to_string(), "debhelper (>= 12)"); /// /// let mut relations: Relations = "python3".parse().unwrap(); /// relations.ensure_minimum_version("debhelper", &"12".parse().unwrap()); /// assert!(relations.to_string().contains("debhelper (>= 12)")); /// ``` pub fn ensure_minimum_version(&mut self, package: &str, minimum_version: &Version) { let mut found = false; let mut obsolete_indices = vec![]; let mut update_idx = None; let entries: Vec<_> = self.entries().collect(); for (idx, entry) in entries.iter().enumerate() { let relations: Vec<_> = entry.relations().collect(); // Check if this entry has multiple alternatives with our package let names: Vec<_> = relations.iter().map(|r| r.name()).collect(); if names.len() > 1 && names.contains(&package.to_string()) { // This is a complex alternative relation, mark for removal if obsolete let is_obsolete = relations.iter().any(|r| { if r.name() != package { return false; } if let Some((vc, ver)) = r.version() { matches!(vc, VersionConstraint::GreaterThan if &ver < minimum_version) || matches!(vc, VersionConstraint::GreaterThanEqual if &ver <= minimum_version) } else { false } }); if is_obsolete { obsolete_indices.push(idx); } continue; } // Single package entry if names.len() == 1 && names[0] == package { found = true; let relation = relations.into_iter().next().unwrap(); // Check if update is needed let should_update = if let Some((vc, ver)) = relation.version() { match vc { VersionConstraint::GreaterThanEqual | VersionConstraint::GreaterThan => { &ver < minimum_version } _ => false, } } else { true }; if should_update { update_idx = Some(idx); } break; } } // Perform updates after iteration if let Some(idx) = update_idx { let relation = Relation::new( package, Some((VersionConstraint::GreaterThanEqual, minimum_version.clone())), ); // Get the existing entry and replace its relation to preserve formatting let mut entry = self.get_entry(idx).unwrap(); entry.replace(0, relation); self.replace(idx, entry); } // Remove obsolete entries for idx in obsolete_indices.into_iter().rev() { self.remove_entry(idx); } // Add if not found if !found { let relation = Relation::new( package, Some((VersionConstraint::GreaterThanEqual, minimum_version.clone())), ); self.push(Entry::from(relation)); } } /// Ensure that a package has an exact version constraint. /// /// # Arguments /// * `package` - The package name /// * `version` - The exact version required /// /// # Example /// ``` /// use debian_control::lossless::relations::Relations; /// /// let mut relations: Relations = "debhelper (>= 9)".parse().unwrap(); /// relations.ensure_exact_version("debhelper", &"12".parse().unwrap()); /// assert_eq!(relations.to_string(), "debhelper (= 12)"); /// ``` pub fn ensure_exact_version(&mut self, package: &str, version: &Version) { let mut found = false; let mut update_idx = None; let entries: Vec<_> = self.entries().collect(); for (idx, entry) in entries.iter().enumerate() { let relations: Vec<_> = entry.relations().collect(); let names: Vec<_> = relations.iter().map(|r| r.name()).collect(); if names.len() > 1 && names[0] == package { panic!("Complex rule for {}, aborting", package); } if names.len() == 1 && names[0] == package { found = true; let relation = relations.into_iter().next().unwrap(); let should_update = if let Some((vc, ver)) = relation.version() { vc != VersionConstraint::Equal || &ver != version } else { true }; if should_update { update_idx = Some(idx); } break; } } // Perform update after iteration if let Some(idx) = update_idx { let relation = Relation::new(package, Some((VersionConstraint::Equal, version.clone()))); // Get the existing entry and replace its relation to preserve formatting let mut entry = self.get_entry(idx).unwrap(); entry.replace(0, relation); self.replace(idx, entry); } if !found { let relation = Relation::new(package, Some((VersionConstraint::Equal, version.clone()))); self.push(Entry::from(relation)); } } /// Ensure that a package dependency exists, without specifying a version. /// /// If the package already exists (with or without a version constraint), /// the relations field is left unchanged. Otherwise, the package is added /// without a version constraint. /// /// # Arguments /// * `package` - The package name /// /// # Example /// ``` /// use debian_control::lossless::relations::Relations; /// /// let mut relations: Relations = "python3".parse().unwrap(); /// relations.ensure_some_version("debhelper"); /// assert!(relations.to_string().contains("debhelper")); /// ``` pub fn ensure_some_version(&mut self, package: &str) { for entry in self.entries() { let relations: Vec<_> = entry.relations().collect(); let names: Vec<_> = relations.iter().map(|r| r.name()).collect(); if names.len() > 1 && names[0] == package { panic!("Complex rule for {}, aborting", package); } if names.len() == 1 && names[0] == package { // Package already exists, don't modify return; } } // Package not found, add it let relation = Relation::simple(package); self.push(Entry::from(relation)); } /// Ensure that a relation exists in the dependencies. /// /// This function checks if the provided entry is already satisfied by an /// existing entry. If it is, no changes are made. If an existing entry is /// weaker than the new entry (i.e., the new entry implies the existing one), /// the existing entry is replaced with the new one. Otherwise, the new entry /// is added. /// /// # Arguments /// * `new_entry` - The entry to ensure exists /// /// # Returns /// `true` if the entry was added or replaced, `false` if it was already satisfied /// /// # Example /// ``` /// use debian_control::lossless::relations::{Relations, Entry}; /// /// let mut relations: Relations = "python3".parse().unwrap(); /// let new_entry: Entry = "debhelper (>= 12)".parse().unwrap(); /// let added = relations.ensure_relation(new_entry); /// assert!(added); /// assert!(relations.to_string().contains("debhelper (>= 12)")); /// ``` pub fn ensure_relation(&mut self, new_entry: Entry) -> bool { let mut to_replace: Vec = Vec::new(); let mut to_remove: Vec = Vec::new(); let mut already_satisfied = false; // Check existing entries for (idx, existing_entry) in self.entries().enumerate() { if new_entry.is_implied_by(&existing_entry) { // The new entry is already satisfied by an existing entry already_satisfied = true; break; } if existing_entry.is_implied_by(&new_entry) { // The new entry implies the existing one (is stronger) // We should replace/remove the weaker existing entry if to_replace.is_empty() { to_replace.push(idx); } else { to_remove.push(idx); } } } if already_satisfied { return false; } // Remove weaker entries in reverse order for idx in to_remove.into_iter().rev() { self.remove_entry(idx); } // Replace or add the entry if let Some(&idx) = to_replace.first() { self.replace(idx, new_entry); } else { self.add_dependency(new_entry, None); } true } /// Ensure that a substitution variable is present in the relations. /// /// If the substvar already exists, it is left unchanged. Otherwise, it is added /// at the end of the relations list. /// /// # Arguments /// * `substvar` - The substitution variable (e.g., "${misc:Depends}") /// /// # Returns /// `Ok(())` on success, or `Err` with an error message if parsing fails /// /// # Example /// ``` /// use debian_control::lossless::relations::Relations; /// /// let mut relations: Relations = "python3".parse().unwrap(); /// relations.ensure_substvar("${misc:Depends}").unwrap(); /// assert_eq!(relations.to_string(), "python3, ${misc:Depends}"); /// ``` pub fn ensure_substvar(&mut self, substvar: &str) -> Result<(), String> { // Check if the substvar already exists for existing in self.substvars() { if existing.trim() == substvar.trim() { return Ok(()); } } // Parse the substvar let (parsed, errors) = Relations::parse_relaxed(substvar, true); if !errors.is_empty() { return Err(errors.join("\n")); } // Detect whitespace pattern to preserve formatting let whitespace = self.detect_whitespace_pattern(" "); // Find the substvar node and inject it for substvar_node in parsed.0.children().filter(|n| n.kind() == SUBSTVAR) { let has_content = self.entries().next().is_some() || self.substvars().next().is_some(); let mut builder = GreenNodeBuilder::new(); builder.start_node(ROOT.into()); // Copy existing content for child in self.0.children_with_tokens() { match child { NodeOrToken::Node(n) => inject(&mut builder, n), NodeOrToken::Token(t) => builder.token(t.kind().into(), t.text()), } } // Add separator if needed, using detected whitespace pattern if has_content { builder.token(COMMA.into(), ","); builder.token(WHITESPACE.into(), whitespace.as_str()); } // Inject the substvar node inject(&mut builder, substvar_node); builder.finish_node(); self.0 = SyntaxNode::new_root_mut(builder.finish()); } Ok(()) } /// Remove a substitution variable from the relations. /// /// If the substvar exists, it is removed along with its surrounding separators. /// If the substvar does not exist, this is a no-op. /// /// # Arguments /// * `substvar` - The substitution variable to remove (e.g., "${misc:Depends}") /// /// # Example /// ``` /// use debian_control::lossless::relations::Relations; /// /// let (mut relations, _) = Relations::parse_relaxed("python3, ${misc:Depends}", true); /// relations.drop_substvar("${misc:Depends}"); /// assert_eq!(relations.to_string(), "python3"); /// ``` pub fn drop_substvar(&mut self, substvar: &str) { // Find all substvar nodes that match the given string let substvars_to_remove: Vec<_> = self .0 .children() .filter_map(Substvar::cast) .filter(|s| s.to_string().trim() == substvar.trim()) .collect(); for substvar_node in substvars_to_remove { // Determine if this is the first substvar (no previous ENTRY or SUBSTVAR siblings) let is_first = !substvar_node .0 .siblings(Direction::Prev) .skip(1) .any(|n| n.kind() == ENTRY || n.kind() == SUBSTVAR); let mut removed_comma = false; // Remove whitespace and comma after the substvar while let Some(n) = substvar_node.0.next_sibling_or_token() { if n.kind() == WHITESPACE || n.kind() == NEWLINE { n.detach(); } else if n.kind() == COMMA { n.detach(); removed_comma = true; break; } else { break; } } // If not first, remove preceding whitespace and comma if !is_first { while let Some(n) = substvar_node.0.prev_sibling_or_token() { if n.kind() == WHITESPACE || n.kind() == NEWLINE { n.detach(); } else if !removed_comma && n.kind() == COMMA { n.detach(); break; } else { break; } } } else { // If first and we didn't remove a comma after, clean up any leading whitespace while let Some(n) = substvar_node.0.next_sibling_or_token() { if n.kind() == WHITESPACE || n.kind() == NEWLINE { n.detach(); } else { break; } } } // Finally, detach the substvar node itself substvar_node.0.detach(); } } /// Filter entries based on a predicate function. /// /// # Arguments /// * `keep` - A function that returns true for entries to keep /// /// # Example /// ``` /// use debian_control::lossless::relations::Relations; /// /// let mut relations: Relations = "python3, debhelper, rustc".parse().unwrap(); /// relations.filter_entries(|entry| { /// entry.relations().any(|r| r.name().starts_with("python")) /// }); /// assert_eq!(relations.to_string(), "python3"); /// ``` pub fn filter_entries(&mut self, keep: F) where F: Fn(&Entry) -> bool, { let indices_to_remove: Vec<_> = self .entries() .enumerate() .filter_map(|(idx, entry)| if keep(&entry) { None } else { Some(idx) }) .collect(); // Remove in reverse order to maintain correct indices for idx in indices_to_remove.into_iter().rev() { self.remove_entry(idx); } } /// Check whether the relations are sorted according to a given sorting order. /// /// # Arguments /// * `sorting_order` - The sorting order to check against /// /// # Example /// ``` /// use debian_control::lossless::relations::{Relations, WrapAndSortOrder}; /// /// let relations: Relations = "debhelper, python3, rustc".parse().unwrap(); /// assert!(relations.is_sorted(&WrapAndSortOrder)); /// /// let relations: Relations = "rustc, debhelper, python3".parse().unwrap(); /// assert!(!relations.is_sorted(&WrapAndSortOrder)); /// ``` pub fn is_sorted(&self, sorting_order: &impl SortingOrder) -> bool { let mut last_name: Option = None; for entry in self.entries() { // Skip empty entries let mut relations = entry.relations(); let Some(relation) = relations.next() else { continue; }; let name = relation.name(); // Skip items that should be ignored if sorting_order.ignore(&name) { continue; } // Check if this breaks the sort order if let Some(ref last) = last_name { if sorting_order.lt(&name, last) { return false; } } last_name = Some(name); } true } /// Find the position to insert an entry while maintaining sort order. /// /// This method detects the current sorting order and returns the appropriate /// insertion position. If there are fewer than 2 entries, it defaults to /// WrapAndSortOrder. If no sorting order is detected, it returns the end position. /// /// # Arguments /// * `entry` - The entry to insert /// /// # Returns /// The index where the entry should be inserted fn find_insert_position(&self, entry: &Entry) -> usize { // Get the package name from the first relation in the entry let Some(relation) = entry.relations().next() else { // Empty entry, just append at the end return self.len(); }; let package_name = relation.name(); // Count non-empty entries let count = self.entries().filter(|e| !e.is_empty()).count(); // If there are less than 2 items, default to WrapAndSortOrder let sorting_order: Box = if count < 2 { Box::new(WrapAndSortOrder) } else { // Try to detect which sorting order is being used // Try WrapAndSortOrder first, then DefaultSortingOrder if self.is_sorted(&WrapAndSortOrder) { Box::new(WrapAndSortOrder) } else if self.is_sorted(&DefaultSortingOrder) { Box::new(DefaultSortingOrder) } else { // No sorting order detected, just append at the end return self.len(); } }; // If adding a special item that should be ignored by this sort order, append at the end if sorting_order.ignore(&package_name) { return self.len(); } // Insert in sorted order among regular items let mut position = 0; for (idx, existing_entry) in self.entries().enumerate() { let mut existing_relations = existing_entry.relations(); let Some(existing_relation) = existing_relations.next() else { // Empty entry, skip position += 1; continue; }; let existing_name = existing_relation.name(); // Skip special items when finding insertion position if sorting_order.ignore(&existing_name) { position += 1; continue; } // Compare with regular items only if sorting_order.lt(&package_name, &existing_name) { return idx; } position += 1; } position } /// Drop a dependency from the relations by package name. /// /// # Arguments /// * `package` - The package name to remove /// /// # Returns /// `true` if the package was found and removed, `false` otherwise /// /// # Example /// ``` /// use debian_control::lossless::relations::Relations; /// /// let mut relations: Relations = "python3, debhelper, rustc".parse().unwrap(); /// assert!(relations.drop_dependency("debhelper")); /// assert_eq!(relations.to_string(), "python3, rustc"); /// assert!(!relations.drop_dependency("nonexistent")); /// ``` pub fn drop_dependency(&mut self, package: &str) -> bool { let indices_to_remove: Vec<_> = self .entries() .enumerate() .filter_map(|(idx, entry)| { let relations: Vec<_> = entry.relations().collect(); let names: Vec<_> = relations.iter().map(|r| r.name()).collect(); if names == vec![package] { Some(idx) } else { None } }) .collect(); let found = !indices_to_remove.is_empty(); // Remove in reverse order to maintain correct indices for idx in indices_to_remove.into_iter().rev() { self.remove_entry(idx); } found } /// Add a dependency at a specific position or auto-detect the position. /// /// If `position` is `None`, the position is automatically determined based /// on the detected sorting order. If a sorting order is detected, the entry /// is inserted in the appropriate position to maintain that order. Otherwise, /// it is appended at the end. /// /// # Arguments /// * `entry` - The entry to add /// * `position` - Optional position to insert at /// /// # Example /// ``` /// use debian_control::lossless::relations::{Relations, Relation, Entry}; /// /// let mut relations: Relations = "python3, rustc".parse().unwrap(); /// let entry = Entry::from(Relation::simple("debhelper")); /// relations.add_dependency(entry, None); /// // debhelper is inserted in sorted order (if order is detected) /// ``` pub fn add_dependency(&mut self, entry: Entry, position: Option) { let pos = position.unwrap_or_else(|| self.find_insert_position(&entry)); self.insert(pos, entry); } /// Get the entry containing a specific package. /// /// This returns the first entry that contains exactly one relation with the /// specified package name (no alternatives). /// /// # Arguments /// * `package` - The package name to search for /// /// # Returns /// A tuple of (index, Entry) if found /// /// # Errors /// Returns `Err` with a message if the package is found in a complex rule (with alternatives) /// /// # Example /// ``` /// use debian_control::lossless::relations::Relations; /// /// let relations: Relations = "python3, debhelper (>= 12), rustc".parse().unwrap(); /// let (idx, entry) = relations.get_relation("debhelper").unwrap(); /// assert_eq!(idx, 1); /// assert_eq!(entry.to_string(), "debhelper (>= 12)"); /// ``` pub fn get_relation(&self, package: &str) -> Result<(usize, Entry), String> { for (idx, entry) in self.entries().enumerate() { let relations: Vec<_> = entry.relations().collect(); let names: Vec<_> = relations.iter().map(|r| r.name()).collect(); if names.len() > 1 && names.contains(&package.to_string()) { return Err(format!("Complex rule for {}, aborting", package)); } if names.len() == 1 && names[0] == package { return Ok((idx, entry)); } } Err(format!("Package {} not found", package)) } /// Iterate over all entries containing a specific package. /// /// # Arguments /// * `package` - The package name to search for /// /// # Returns /// An iterator over tuples of (index, Entry) /// /// # Example /// ``` /// use debian_control::lossless::relations::Relations; /// /// let relations: Relations = "python3 | python3-minimal, python3-dev".parse().unwrap(); /// let entries: Vec<_> = relations.iter_relations_for("python3").collect(); /// assert_eq!(entries.len(), 1); /// ``` pub fn iter_relations_for(&self, package: &str) -> impl Iterator + '_ { let package = package.to_string(); self.entries().enumerate().filter(move |(_, entry)| { let names: Vec<_> = entry.relations().map(|r| r.name()).collect(); names.contains(&package) }) } /// Check whether a package exists in the relations. /// /// # Arguments /// * `package` - The package name to search for /// /// # Returns /// `true` if the package is found, `false` otherwise /// /// # Example /// ``` /// use debian_control::lossless::relations::Relations; /// /// let relations: Relations = "python3, debhelper, rustc".parse().unwrap(); /// assert!(relations.has_relation("debhelper")); /// assert!(!relations.has_relation("nonexistent")); /// ``` pub fn has_relation(&self, package: &str) -> bool { self.entries() .any(|entry| entry.relations().any(|r| r.name() == package)) } } impl From> for Relations { fn from(entries: Vec) -> Self { let mut builder = GreenNodeBuilder::new(); builder.start_node(ROOT.into()); for (i, entry) in entries.into_iter().enumerate() { if i > 0 { builder.token(COMMA.into(), ","); builder.token(WHITESPACE.into(), " "); } inject(&mut builder, entry.0); } builder.finish_node(); Relations(SyntaxNode::new_root_mut(builder.finish())) } } impl From for Relations { fn from(entry: Entry) -> Self { Self::from(vec![entry]) } } impl Default for Entry { fn default() -> Self { Self::new() } } impl PartialOrd for Entry { fn partial_cmp(&self, other: &Self) -> Option { let mut rels_a = self.relations(); let mut rels_b = other.relations(); while let (Some(a), Some(b)) = (rels_a.next(), rels_b.next()) { match a.cmp(&b) { std::cmp::Ordering::Equal => continue, x => return Some(x), } } if rels_a.next().is_some() { return Some(std::cmp::Ordering::Greater); } if rels_b.next().is_some() { return Some(std::cmp::Ordering::Less); } Some(std::cmp::Ordering::Equal) } } impl Eq for Entry {} impl Ord for Entry { fn cmp(&self, other: &Self) -> std::cmp::Ordering { self.partial_cmp(other).unwrap() } } impl Entry { /// Create a new entry pub fn new() -> Self { let mut builder = GreenNodeBuilder::new(); builder.start_node(SyntaxKind::ENTRY.into()); builder.finish_node(); Entry(SyntaxNode::new_root_mut(builder.finish())) } /// Replace the relation at the given index pub fn replace(&mut self, idx: usize, relation: Relation) { let current_relation = self.get_relation(idx).unwrap(); let old_root = current_relation.0; let new_root = relation.0; // Preserve white the current relation has let mut prev = new_root.first_child_or_token(); let mut new_head_len = 0; // First, strip off any whitespace from the new relation while let Some(p) = prev { if p.kind() == WHITESPACE || p.kind() == NEWLINE { new_head_len += 1; prev = p.next_sibling_or_token(); } else { break; } } let mut new_tail_len = 0; let mut next = new_root.last_child_or_token(); while let Some(n) = next { if n.kind() == WHITESPACE || n.kind() == NEWLINE { new_tail_len += 1; next = n.prev_sibling_or_token(); } else { break; } } // Then, inherit the whitespace from the old relation let mut prev = old_root.first_child_or_token(); let mut old_head = vec![]; while let Some(p) = prev { if p.kind() == WHITESPACE || p.kind() == NEWLINE { old_head.push(p.clone()); prev = p.next_sibling_or_token(); } else { break; } } let mut old_tail = vec![]; let mut next = old_root.last_child_or_token(); while let Some(n) = next { if n.kind() == WHITESPACE || n.kind() == NEWLINE { old_tail.push(n.clone()); next = n.prev_sibling_or_token(); } else { break; } } new_root.splice_children(0..new_head_len, old_head); let tail_pos = new_root.children_with_tokens().count() - new_tail_len; new_root.splice_children( tail_pos - new_tail_len..tail_pos, old_tail.into_iter().rev(), ); let index = old_root.index(); self.0 .splice_children(index..index + 1, vec![new_root.into()]); } /// Wrap and sort the relations in this entry #[must_use] pub fn wrap_and_sort(&self) -> Self { let mut relations = self .relations() .map(|r| r.wrap_and_sort()) .collect::>(); // TODO: preserve comments relations.sort(); Self::from(relations) } /// Iterate over the relations in this entry pub fn relations(&self) -> impl Iterator + '_ { self.0.children().filter_map(Relation::cast) } /// Iterate over the relations in this entry pub fn iter(&self) -> impl Iterator + '_ { self.relations() } /// Get the relation at the given index pub fn get_relation(&self, idx: usize) -> Option { self.relations().nth(idx) } /// Remove the relation at the given index /// /// # Arguments /// * `idx` - The index of the relation to remove /// /// # Example /// ``` /// use debian_control::lossless::relations::{Relation,Entry}; /// let mut entry: Entry = r"python3-dulwich (>= 0.19.0) | python3-requests".parse().unwrap(); /// entry.remove_relation(1); /// assert_eq!(entry.to_string(), "python3-dulwich (>= 0.19.0)"); /// ``` pub fn remove_relation(&self, idx: usize) -> Relation { let mut relation = self.get_relation(idx).unwrap(); relation.remove(); relation } /// Check if this entry is satisfied by the given package versions. /// /// # Arguments /// * `package_version` - A function that returns the version of a package. /// /// # Example /// ``` /// use debian_control::lossless::relations::{Relation,Entry}; /// let entry = Entry::from(vec!["samba (>= 2.0)".parse::().unwrap()]); /// assert!(entry.satisfied_by(|name: &str| -> Option { /// match name { /// "samba" => Some("2.0".parse().unwrap()), /// _ => None /// }})); /// ``` pub fn satisfied_by(&self, package_version: impl crate::VersionLookup + Copy) -> bool { self.relations().any(|r| { let actual = package_version.lookup_version(r.name().as_str()); if let Some((vc, version)) = r.version() { if let Some(actual) = actual { match vc { VersionConstraint::GreaterThanEqual => *actual >= version, VersionConstraint::LessThanEqual => *actual <= version, VersionConstraint::Equal => *actual == version, VersionConstraint::GreaterThan => *actual > version, VersionConstraint::LessThan => *actual < version, } } else { false } } else { actual.is_some() } }) } /// Remove this entry /// /// # Example /// ``` /// use debian_control::lossless::relations::{Relations,Entry}; /// let mut relations: Relations = r"python3-dulwich (>= 0.19.0), python3-urllib3 (<< 1.26.0)".parse().unwrap(); /// let mut entry = relations.get_entry(0).unwrap(); /// entry.remove(); /// assert_eq!(relations.to_string(), "python3-urllib3 (<< 1.26.0)"); /// ``` pub fn remove(&mut self) { let mut removed_comma = false; let is_first = !self .0 .siblings(Direction::Prev) .skip(1) .any(|n| n.kind() == ENTRY); while let Some(n) = self.0.next_sibling_or_token() { if n.kind() == WHITESPACE || n.kind() == NEWLINE { n.detach(); } else if n.kind() == COMMA { n.detach(); removed_comma = true; break; } else { panic!("Unexpected node: {:?}", n); } } if !is_first { while let Some(n) = self.0.prev_sibling_or_token() { if n.kind() == WHITESPACE || n.kind() == NEWLINE { n.detach(); } else if !removed_comma && n.kind() == COMMA { n.detach(); break; } else { break; } } } else { while let Some(n) = self.0.next_sibling_or_token() { if n.kind() == WHITESPACE || n.kind() == NEWLINE { n.detach(); } else { break; } } } self.0.detach(); } /// Check if this entry is empty pub fn is_empty(&self) -> bool { self.relations().count() == 0 } /// Get the number of relations in this entry pub fn len(&self) -> usize { self.relations().count() } /// Push a new relation to the entry /// /// # Arguments /// * `relation` - The relation to push /// /// # Example /// ``` /// use debian_control::lossless::relations::{Relation,Entry}; /// let mut entry: Entry = "samba (>= 2.0)".parse().unwrap(); /// entry.push("python3-requests".parse().unwrap()); /// assert_eq!(entry.to_string(), "samba (>= 2.0) | python3-requests"); /// ``` pub fn push(&mut self, relation: Relation) { let is_empty = !self .0 .children_with_tokens() .any(|n| n.kind() == PIPE || n.kind() == RELATION); let (position, new_children) = if let Some(current_relation) = self.relations().last() { let to_insert: Vec> = if is_empty { vec![relation.0.green().into()] } else { vec![ NodeOrToken::Token(GreenToken::new(WHITESPACE.into(), " ")), NodeOrToken::Token(GreenToken::new(PIPE.into(), "|")), NodeOrToken::Token(GreenToken::new(WHITESPACE.into(), " ")), relation.0.green().into(), ] }; (current_relation.0.index() + 1, to_insert) } else { let child_count = self.0.children_with_tokens().count(); ( child_count, if is_empty { vec![relation.0.green().into()] } else { vec![ NodeOrToken::Token(GreenToken::new(PIPE.into(), "|")), NodeOrToken::Token(GreenToken::new(WHITESPACE.into(), " ")), relation.0.green().into(), ] }, ) }; let new_root = SyntaxNode::new_root_mut( self.0.replace_with( self.0 .green() .splice_children(position..position, new_children), ), ); if let Some(parent) = self.0.parent() { parent.splice_children(self.0.index()..self.0.index() + 1, vec![new_root.into()]); self.0 = parent .children_with_tokens() .nth(self.0.index()) .unwrap() .clone() .into_node() .unwrap(); } else { self.0 = new_root; } } /// Check if this entry (OR-group) is implied by another entry. /// /// An entry is implied by another if any of the relations in this entry /// is implied by any relation in the outer entry. This follows the semantics /// of OR-groups in Debian dependencies. /// /// For example: /// - `pkg >= 1.0` is implied by `pkg >= 1.5 | libc6` (first relation matches) /// - `pkg1 | pkg2` is implied by `pkg1` (pkg1 satisfies the requirement) /// /// # Arguments /// * `outer` - The outer entry that may imply this entry /// /// # Returns /// `true` if this entry is implied by `outer`, `false` otherwise /// /// # Example /// ``` /// use debian_control::lossless::relations::Entry; /// /// let inner: Entry = "pkg (>= 1.0)".parse().unwrap(); /// let outer: Entry = "pkg (>= 1.5) | libc6".parse().unwrap(); /// assert!(inner.is_implied_by(&outer)); /// ``` pub fn is_implied_by(&self, outer: &Entry) -> bool { // If entries are identical, they imply each other if self == outer { return true; } // Check if any relation in inner is implied by any relation in outer for inner_rel in self.relations() { for outer_rel in outer.relations() { if inner_rel.is_implied_by(&outer_rel) { return true; } } } false } } fn inject(builder: &mut GreenNodeBuilder, node: SyntaxNode) { builder.start_node(node.kind().into()); for child in node.children_with_tokens() { match child { rowan::NodeOrToken::Node(child) => { inject(builder, child); } rowan::NodeOrToken::Token(token) => { builder.token(token.kind().into(), token.text()); } } } builder.finish_node(); } impl From> for Entry { fn from(relations: Vec) -> Self { let mut builder = GreenNodeBuilder::new(); builder.start_node(SyntaxKind::ENTRY.into()); for (i, relation) in relations.into_iter().enumerate() { if i > 0 { builder.token(WHITESPACE.into(), " "); builder.token(COMMA.into(), "|"); builder.token(WHITESPACE.into(), " "); } inject(&mut builder, relation.0); } builder.finish_node(); Entry(SyntaxNode::new_root_mut(builder.finish())) } } impl From for Entry { fn from(relation: Relation) -> Self { Self::from(vec![relation]) } } /// Helper function to tokenize a version string, handling epochs /// Version strings like "1:2.3.2-2~" need to be split into: IDENT("1"), COLON, IDENT("2.3.2-2~") fn tokenize_version(builder: &mut GreenNodeBuilder, version: &Version) { let version_str = version.to_string(); // Split on the first colon (if any) to handle epochs if let Some(colon_pos) = version_str.find(':') { // Epoch part (before colon) builder.token(IDENT.into(), &version_str[..colon_pos]); builder.token(COLON.into(), ":"); // Version part (after colon) builder.token(IDENT.into(), &version_str[colon_pos + 1..]); } else { // No epoch, just a regular version builder.token(IDENT.into(), version_str.as_str()); } } impl Relation { /// Create a new relation /// /// # Arguments /// * `name` - The name of the package /// * `version_constraint` - The version constraint and version to use /// /// # Example /// ``` /// use debian_control::lossless::relations::{Relation}; /// use debian_control::relations::VersionConstraint; /// let relation = Relation::new("samba", Some((VersionConstraint::GreaterThanEqual, "2.0".parse().unwrap()))); /// assert_eq!(relation.to_string(), "samba (>= 2.0)"); /// ``` pub fn new(name: &str, version_constraint: Option<(VersionConstraint, Version)>) -> Self { let mut builder = GreenNodeBuilder::new(); builder.start_node(SyntaxKind::RELATION.into()); builder.token(IDENT.into(), name); if let Some((vc, version)) = version_constraint { builder.token(WHITESPACE.into(), " "); builder.start_node(SyntaxKind::VERSION.into()); builder.token(L_PARENS.into(), "("); builder.start_node(SyntaxKind::CONSTRAINT.into()); for c in vc.to_string().chars() { builder.token( match c { '>' => R_ANGLE.into(), '<' => L_ANGLE.into(), '=' => EQUAL.into(), _ => unreachable!(), }, c.to_string().as_str(), ); } builder.finish_node(); builder.token(WHITESPACE.into(), " "); tokenize_version(&mut builder, &version); builder.token(R_PARENS.into(), ")"); builder.finish_node(); } builder.finish_node(); Relation(SyntaxNode::new_root_mut(builder.finish())) } /// Wrap and sort this relation /// /// # Example /// ``` /// use debian_control::lossless::relations::Relation; /// let relation = " samba ( >= 2.0) ".parse::().unwrap(); /// assert_eq!(relation.wrap_and_sort().to_string(), "samba (>= 2.0)"); /// ``` #[must_use] pub fn wrap_and_sort(&self) -> Self { let mut builder = GreenNodeBuilder::new(); builder.start_node(SyntaxKind::RELATION.into()); builder.token(IDENT.into(), self.name().as_str()); if let Some(archqual) = self.archqual() { builder.token(COLON.into(), ":"); builder.token(IDENT.into(), archqual.as_str()); } if let Some((vc, version)) = self.version() { builder.token(WHITESPACE.into(), " "); builder.start_node(SyntaxKind::VERSION.into()); builder.token(L_PARENS.into(), "("); builder.start_node(SyntaxKind::CONSTRAINT.into()); builder.token( match vc { VersionConstraint::GreaterThanEqual => R_ANGLE.into(), VersionConstraint::LessThanEqual => L_ANGLE.into(), VersionConstraint::Equal => EQUAL.into(), VersionConstraint::GreaterThan => R_ANGLE.into(), VersionConstraint::LessThan => L_ANGLE.into(), }, vc.to_string().as_str(), ); builder.finish_node(); builder.token(WHITESPACE.into(), " "); tokenize_version(&mut builder, &version); builder.token(R_PARENS.into(), ")"); builder.finish_node(); } if let Some(architectures) = self.architectures() { builder.token(WHITESPACE.into(), " "); builder.start_node(ARCHITECTURES.into()); builder.token(L_BRACKET.into(), "["); for (i, arch) in architectures.enumerate() { if i > 0 { builder.token(WHITESPACE.into(), " "); } builder.token(IDENT.into(), arch.as_str()); } builder.token(R_BRACKET.into(), "]"); builder.finish_node(); } for profiles in self.profiles() { builder.token(WHITESPACE.into(), " "); builder.start_node(PROFILES.into()); builder.token(L_ANGLE.into(), "<"); for (i, profile) in profiles.into_iter().enumerate() { if i > 0 { builder.token(WHITESPACE.into(), " "); } match profile { BuildProfile::Disabled(name) => { builder.token(NOT.into(), "!"); builder.token(IDENT.into(), name.as_str()); } BuildProfile::Enabled(name) => { builder.token(IDENT.into(), name.as_str()); } } } builder.token(R_ANGLE.into(), ">"); builder.finish_node(); } builder.finish_node(); Relation(SyntaxNode::new_root_mut(builder.finish())) } /// Create a new simple relation, without any version constraints. /// /// # Example /// ``` /// use debian_control::lossless::relations::Relation; /// let relation = Relation::simple("samba"); /// assert_eq!(relation.to_string(), "samba"); /// ``` pub fn simple(name: &str) -> Self { Self::new(name, None) } /// Remove the version constraint from the relation. /// /// # Example /// ``` /// use debian_control::lossless::relations::{Relation}; /// use debian_control::relations::VersionConstraint; /// let mut relation = Relation::new("samba", Some((VersionConstraint::GreaterThanEqual, "2.0".parse().unwrap()))); /// relation.drop_constraint(); /// assert_eq!(relation.to_string(), "samba"); /// ``` pub fn drop_constraint(&mut self) -> bool { let version_token = self.0.children().find(|n| n.kind() == VERSION); if let Some(version_token) = version_token { // Remove any whitespace before the version token while let Some(prev) = version_token.prev_sibling_or_token() { if prev.kind() == WHITESPACE || prev.kind() == NEWLINE { prev.detach(); } else { break; } } version_token.detach(); return true; } false } /// Return the name of the package in the relation. /// /// # Example /// ``` /// use debian_control::lossless::relations::Relation; /// let relation = Relation::simple("samba"); /// assert_eq!(relation.name(), "samba"); /// ``` pub fn name(&self) -> String { self.0 .children_with_tokens() .find_map(|it| match it { SyntaxElement::Token(token) if token.kind() == IDENT => Some(token), _ => None, }) .unwrap() .text() .to_string() } /// Return the archqual /// /// # Example /// ``` /// use debian_control::lossless::relations::Relation; /// let relation: Relation = "samba:any".parse().unwrap(); /// assert_eq!(relation.archqual(), Some("any".to_string())); /// ``` pub fn archqual(&self) -> Option { let archqual = self.0.children().find(|n| n.kind() == ARCHQUAL); let node = if let Some(archqual) = archqual { archqual.children_with_tokens().find_map(|it| match it { SyntaxElement::Token(token) if token.kind() == IDENT => Some(token), _ => None, }) } else { None }; node.map(|n| n.text().to_string()) } /// Set the architecture qualifier for this relation. /// /// # Example /// ``` /// use debian_control::lossless::relations::Relation; /// let mut relation = Relation::simple("samba"); /// relation.set_archqual("any"); /// assert_eq!(relation.to_string(), "samba:any"); /// ``` pub fn set_archqual(&mut self, archqual: &str) { let mut builder = GreenNodeBuilder::new(); builder.start_node(ARCHQUAL.into()); builder.token(COLON.into(), ":"); builder.token(IDENT.into(), archqual); builder.finish_node(); let node_archqual = self.0.children().find(|n| n.kind() == ARCHQUAL); if let Some(node_archqual) = node_archqual { self.0.splice_children( node_archqual.index()..node_archqual.index() + 1, vec![SyntaxNode::new_root_mut(builder.finish()).into()], ); } else { let name_node = self.0.children_with_tokens().find(|n| n.kind() == IDENT); let idx = if let Some(name_node) = name_node { name_node.index() + 1 } else { 0 }; self.0.splice_children( idx..idx, vec![SyntaxNode::new_root_mut(builder.finish()).into()], ); } } /// Return the version constraint and the version it is constrained to. pub fn version(&self) -> Option<(VersionConstraint, Version)> { let vc = self.0.children().find(|n| n.kind() == VERSION); let vc = vc.as_ref()?; let constraint = vc.children().find(|n| n.kind() == CONSTRAINT); // Collect all IDENT and COLON tokens to handle versions with epochs (e.g., "1:2.3.2-2~") let version_str: String = vc .children_with_tokens() .filter_map(|it| match it { SyntaxElement::Token(token) if token.kind() == IDENT || token.kind() == COLON => { Some(token.text().to_string()) } _ => None, }) .collect(); if let Some(constraint) = constraint { if !version_str.is_empty() { let vc: VersionConstraint = constraint.to_string().parse().unwrap(); Some((vc, version_str.parse().unwrap())) } else { None } } else { None } } /// Set the version constraint for this relation /// /// # Example /// ``` /// use debian_control::lossless::relations::{Relation}; /// use debian_control::relations::VersionConstraint; /// let mut relation = Relation::simple("samba"); /// relation.set_version(Some((VersionConstraint::GreaterThanEqual, "2.0".parse().unwrap()))); /// assert_eq!(relation.to_string(), "samba (>= 2.0)"); /// ``` pub fn set_version(&mut self, version_constraint: Option<(VersionConstraint, Version)>) { let current_version = self.0.children().find(|n| n.kind() == VERSION); if let Some((vc, version)) = version_constraint { let mut builder = GreenNodeBuilder::new(); builder.start_node(VERSION.into()); builder.token(L_PARENS.into(), "("); builder.start_node(CONSTRAINT.into()); match vc { VersionConstraint::GreaterThanEqual => { builder.token(R_ANGLE.into(), ">"); builder.token(EQUAL.into(), "="); } VersionConstraint::LessThanEqual => { builder.token(L_ANGLE.into(), "<"); builder.token(EQUAL.into(), "="); } VersionConstraint::Equal => { builder.token(EQUAL.into(), "="); } VersionConstraint::GreaterThan => { builder.token(R_ANGLE.into(), ">"); } VersionConstraint::LessThan => { builder.token(L_ANGLE.into(), "<"); } } builder.finish_node(); // CONSTRAINT builder.token(WHITESPACE.into(), " "); tokenize_version(&mut builder, &version); builder.token(R_PARENS.into(), ")"); builder.finish_node(); // VERSION if let Some(current_version) = current_version { self.0.splice_children( current_version.index()..current_version.index() + 1, vec![SyntaxNode::new_root_mut(builder.finish()).into()], ); } else { let name_node = self.0.children_with_tokens().find(|n| n.kind() == IDENT); let idx = if let Some(name_node) = name_node { name_node.index() + 1 } else { 0 }; let new_children = vec![ GreenToken::new(WHITESPACE.into(), " ").into(), builder.finish().into(), ]; let new_root = SyntaxNode::new_root_mut( self.0.green().splice_children(idx..idx, new_children), ); if let Some(parent) = self.0.parent() { parent .splice_children(self.0.index()..self.0.index() + 1, vec![new_root.into()]); self.0 = parent .children_with_tokens() .nth(self.0.index()) .unwrap() .clone() .into_node() .unwrap(); } else { self.0 = new_root; } } } else if let Some(current_version) = current_version { // Remove any whitespace before the version token while let Some(prev) = current_version.prev_sibling_or_token() { if prev.kind() == WHITESPACE || prev.kind() == NEWLINE { prev.detach(); } else { break; } } current_version.detach(); } } /// Return an iterator over the architectures for this relation /// /// # Example /// ``` /// use debian_control::lossless::relations::Relation; /// let relation: Relation = "samba [amd64]".parse().unwrap(); /// assert_eq!(relation.architectures().unwrap().collect::>(), vec!["amd64".to_string()]); /// ``` pub fn architectures(&self) -> Option + '_> { let architectures = self.0.children().find(|n| n.kind() == ARCHITECTURES)?; Some(architectures.children_with_tokens().filter_map(|node| { let token = node.as_token()?; if token.kind() == IDENT { Some(token.text().to_string()) } else { None } })) } /// Returns an iterator over the build profiles for this relation /// /// # Example /// ``` /// use debian_control::lossless::relations::{Relation}; /// use debian_control::relations::{BuildProfile}; /// let relation: Relation = "samba ".parse().unwrap(); /// assert_eq!(relation.profiles().collect::>(), vec![vec![BuildProfile::Disabled("nocheck".to_string())]]); /// ``` pub fn profiles(&self) -> impl Iterator> + '_ { let profiles = self.0.children().filter(|n| n.kind() == PROFILES); profiles.map(|profile| { // iterate over nodes separated by whitespace tokens let mut ret = vec![]; let mut current = vec![]; for token in profile.children_with_tokens() { match token.kind() { WHITESPACE | NEWLINE => { if !current.is_empty() { ret.push(current.join("").parse::().unwrap()); current = vec![]; } } L_ANGLE | R_ANGLE => {} _ => { current.push(token.to_string()); } } } if !current.is_empty() { ret.push(current.concat().parse().unwrap()); } ret }) } /// Remove this relation /// /// # Example /// ``` /// use debian_control::lossless::relations::{Relation,Entry}; /// let mut entry: Entry = r"python3-dulwich (>= 0.19.0) | python3-urllib3 (<< 1.26.0)".parse().unwrap(); /// let mut relation = entry.get_relation(0).unwrap(); /// relation.remove(); /// assert_eq!(entry.to_string(), "python3-urllib3 (<< 1.26.0)"); /// ``` pub fn remove(&mut self) { let is_first = !self .0 .siblings(Direction::Prev) .skip(1) .any(|n| n.kind() == RELATION); if !is_first { // Not the first item in the list. Remove whitespace backwards to the previous // pipe, the pipe and any whitespace until the previous relation while let Some(n) = self.0.prev_sibling_or_token() { if n.kind() == WHITESPACE || n.kind() == NEWLINE { n.detach(); } else if n.kind() == PIPE { n.detach(); break; } else { break; } } while let Some(n) = self.0.prev_sibling_or_token() { if n.kind() == WHITESPACE || n.kind() == NEWLINE { n.detach(); } else { break; } } } else { // First item in the list. Remove whitespace up to the pipe, the pipe and anything // before the next relation while let Some(n) = self.0.next_sibling_or_token() { if n.kind() == WHITESPACE || n.kind() == NEWLINE { n.detach(); } else if n.kind() == PIPE { n.detach(); break; } else { panic!("Unexpected node: {:?}", n); } } while let Some(n) = self.0.next_sibling_or_token() { if n.kind() == WHITESPACE || n.kind() == NEWLINE { n.detach(); } else { break; } } } // If this was the last relation in the entry, remove the entire entry if let Some(mut parent) = self.0.parent().and_then(Entry::cast) { if parent.is_empty() { parent.remove(); } else { self.0.detach(); } } else { self.0.detach(); } } /// Set the architectures for this relation /// /// # Example /// ``` /// use debian_control::lossless::relations::Relation; /// let mut relation = Relation::simple("samba"); /// relation.set_architectures(vec!["amd64", "i386"].into_iter()); /// assert_eq!(relation.to_string(), "samba [amd64 i386]"); /// ``` pub fn set_architectures<'a>(&mut self, architectures: impl Iterator) { let mut builder = GreenNodeBuilder::new(); builder.start_node(ARCHITECTURES.into()); builder.token(L_BRACKET.into(), "["); for (i, arch) in architectures.enumerate() { if i > 0 { builder.token(WHITESPACE.into(), " "); } builder.token(IDENT.into(), arch); } builder.token(R_BRACKET.into(), "]"); builder.finish_node(); let node_architectures = self.0.children().find(|n| n.kind() == ARCHITECTURES); if let Some(node_architectures) = node_architectures { let new_root = SyntaxNode::new_root_mut(builder.finish()); self.0.splice_children( node_architectures.index()..node_architectures.index() + 1, vec![new_root.into()], ); } else { let profiles = self.0.children().find(|n| n.kind() == PROFILES); let idx = if let Some(profiles) = profiles { profiles.index() } else { self.0.children_with_tokens().count() }; let new_root = SyntaxNode::new_root(self.0.green().splice_children( idx..idx, vec![ GreenToken::new(WHITESPACE.into(), " ").into(), builder.finish().into(), ], )); if let Some(parent) = self.0.parent() { parent.splice_children(self.0.index()..self.0.index() + 1, vec![new_root.into()]); self.0 = parent .children_with_tokens() .nth(self.0.index()) .unwrap() .clone() .into_node() .unwrap(); } else { self.0 = new_root; } } } /// Add a build profile to this relation /// /// # Example /// ``` /// use debian_control::lossless::relations::Relation; /// use debian_control::relations::BuildProfile; /// let mut relation = Relation::simple("samba"); /// relation.add_profile(&[BuildProfile::Disabled("nocheck".to_string())]); /// assert_eq!(relation.to_string(), "samba "); /// ``` pub fn add_profile(&mut self, profile: &[BuildProfile]) { let mut builder = GreenNodeBuilder::new(); builder.start_node(PROFILES.into()); builder.token(L_ANGLE.into(), "<"); for (i, profile) in profile.iter().enumerate() { if i > 0 { builder.token(WHITESPACE.into(), " "); } match profile { BuildProfile::Disabled(name) => { builder.token(NOT.into(), "!"); builder.token(IDENT.into(), name.as_str()); } BuildProfile::Enabled(name) => { builder.token(IDENT.into(), name.as_str()); } } } builder.token(R_ANGLE.into(), ">"); builder.finish_node(); let node_profiles = self.0.children().find(|n| n.kind() == PROFILES); if let Some(node_profiles) = node_profiles { let new_root = SyntaxNode::new_root_mut(builder.finish()); self.0.splice_children( node_profiles.index()..node_profiles.index() + 1, vec![new_root.into()], ); } else { let idx = self.0.children_with_tokens().count(); let new_root = SyntaxNode::new_root(self.0.green().splice_children( idx..idx, vec![ GreenToken::new(WHITESPACE.into(), " ").into(), builder.finish().into(), ], )); if let Some(parent) = self.0.parent() { parent.splice_children(self.0.index()..self.0.index() + 1, vec![new_root.into()]); self.0 = parent .children_with_tokens() .nth(self.0.index()) .unwrap() .clone() .into_node() .unwrap(); } else { self.0 = new_root; } } } /// Build a new relation pub fn build(name: &str) -> RelationBuilder { RelationBuilder::new(name) } /// Check if this relation is implied by another relation. /// /// A relation is implied by another if the outer relation is more restrictive /// or equal to this relation. For example: /// - `pkg >= 1.0` is implied by `pkg >= 1.5` (outer is more restrictive) /// - `pkg >= 1.0` is implied by `pkg = 1.5` (outer is more restrictive) /// - `pkg` (no version) is implied by any versioned constraint on `pkg` /// /// # Arguments /// * `outer` - The outer relation that may imply this relation /// /// # Returns /// `true` if this relation is implied by `outer`, `false` otherwise /// /// # Example /// ``` /// use debian_control::lossless::relations::Relation; /// use debian_control::relations::VersionConstraint; /// /// let inner = Relation::new("pkg", Some((VersionConstraint::GreaterThanEqual, "1.0".parse().unwrap()))); /// let outer = Relation::new("pkg", Some((VersionConstraint::GreaterThanEqual, "1.5".parse().unwrap()))); /// assert!(inner.is_implied_by(&outer)); /// /// let inner2 = Relation::new("pkg", None); /// assert!(inner2.is_implied_by(&outer)); /// ``` pub fn is_implied_by(&self, outer: &Relation) -> bool { if self.name() != outer.name() { return false; } let inner_version = self.version(); let outer_version = outer.version(); // No version constraint on inner means it's always implied if inner_version.is_none() { return true; } // If versions are identical, they imply each other if inner_version == outer_version { return true; } // Inner has version but outer doesn't - not implied if outer_version.is_none() { return false; } let (inner_constraint, inner_ver) = inner_version.unwrap(); let (outer_constraint, outer_ver) = outer_version.unwrap(); use VersionConstraint::*; match inner_constraint { GreaterThanEqual => match outer_constraint { GreaterThan => outer_ver > inner_ver, GreaterThanEqual | Equal => outer_ver >= inner_ver, LessThan | LessThanEqual => false, }, Equal => match outer_constraint { Equal => outer_ver == inner_ver, _ => false, }, LessThan => match outer_constraint { LessThan => outer_ver <= inner_ver, LessThanEqual | Equal => outer_ver < inner_ver, GreaterThan | GreaterThanEqual => false, }, LessThanEqual => match outer_constraint { LessThanEqual | Equal | LessThan => outer_ver <= inner_ver, GreaterThan | GreaterThanEqual => false, }, GreaterThan => match outer_constraint { GreaterThan => outer_ver >= inner_ver, Equal | GreaterThanEqual => outer_ver > inner_ver, LessThan | LessThanEqual => false, }, } } } /// A builder for creating a `Relation` /// /// # Example /// ``` /// use debian_control::lossless::relations::{Relation}; /// use debian_control::relations::VersionConstraint; /// let relation = Relation::build("samba") /// .version_constraint(VersionConstraint::GreaterThanEqual, "2.0".parse().unwrap()) /// .archqual("any") /// .architectures(vec!["amd64".to_string(), "i386".to_string()]) /// .build(); /// assert_eq!(relation.to_string(), "samba:any (>= 2.0) [amd64 i386]"); /// ``` pub struct RelationBuilder { name: String, version_constraint: Option<(VersionConstraint, Version)>, archqual: Option, architectures: Option>, profiles: Vec>, } impl RelationBuilder { /// Create a new `RelationBuilder` with the given package name fn new(name: &str) -> Self { Self { name: name.to_string(), version_constraint: None, archqual: None, architectures: None, profiles: vec![], } } /// Set the version constraint for this relation pub fn version_constraint(mut self, vc: VersionConstraint, version: Version) -> Self { self.version_constraint = Some((vc, version)); self } /// Set the architecture qualifier for this relation pub fn archqual(mut self, archqual: &str) -> Self { self.archqual = Some(archqual.to_string()); self } /// Set the architectures for this relation pub fn architectures(mut self, architectures: Vec) -> Self { self.architectures = Some(architectures); self } /// Set the build profiles for this relation pub fn profiles(mut self, profiles: Vec>) -> Self { self.profiles = profiles; self } /// Add a build profile to this relation pub fn add_profile(mut self, profile: Vec) -> Self { self.profiles.push(profile); self } /// Build the `Relation` pub fn build(self) -> Relation { let mut relation = Relation::new(&self.name, self.version_constraint); if let Some(archqual) = &self.archqual { relation.set_archqual(archqual); } if let Some(architectures) = &self.architectures { relation.set_architectures(architectures.iter().map(|s| s.as_str())); } for profile in &self.profiles { relation.add_profile(profile); } relation } } impl PartialOrd for Relation { fn partial_cmp(&self, other: &Self) -> Option { // Compare by name first, then by version let name_cmp = self.name().cmp(&other.name()); if name_cmp != std::cmp::Ordering::Equal { return Some(name_cmp); } let self_version = self.version(); let other_version = other.version(); match (self_version, other_version) { (Some((self_vc, self_version)), Some((other_vc, other_version))) => { let vc_cmp = self_vc.cmp(&other_vc); if vc_cmp != std::cmp::Ordering::Equal { return Some(vc_cmp); } Some(self_version.cmp(&other_version)) } (Some(_), None) => Some(std::cmp::Ordering::Greater), (None, Some(_)) => Some(std::cmp::Ordering::Less), (None, None) => Some(std::cmp::Ordering::Equal), } } } impl Eq for Relation {} impl Ord for Relation { fn cmp(&self, other: &Self) -> std::cmp::Ordering { self.partial_cmp(other).unwrap() } } impl std::str::FromStr for Relations { type Err = String; fn from_str(s: &str) -> Result { let parse = parse(s, false); if parse.errors.is_empty() { Ok(parse.root_mut()) } else { Err(parse.errors.join("\n")) } } } impl std::str::FromStr for Entry { type Err = String; fn from_str(s: &str) -> Result { let root: Relations = s.parse()?; let mut entries = root.entries(); let entry = if let Some(entry) = entries.next() { entry } else { return Err("No entry found".to_string()); }; if entries.next().is_some() { return Err("Multiple entries found".to_string()); } Ok(entry) } } impl std::str::FromStr for Relation { type Err = String; fn from_str(s: &str) -> Result { let entry: Entry = s.parse()?; let mut relations = entry.relations(); let relation = if let Some(relation) = relations.next() { relation } else { return Err("No relation found".to_string()); }; if relations.next().is_some() { return Err("Multiple relations found".to_string()); } Ok(relation) } } impl From for Relation { fn from(relation: crate::lossy::Relation) -> Self { let mut builder = Relation::build(&relation.name); if let Some((vc, version)) = relation.version { builder = builder.version_constraint(vc, version); } if let Some(archqual) = relation.archqual { builder = builder.archqual(&archqual); } if let Some(architectures) = relation.architectures { builder = builder.architectures(architectures); } builder = builder.profiles(relation.profiles); builder.build() } } impl From for crate::lossy::Relation { fn from(relation: Relation) -> Self { crate::lossy::Relation { name: relation.name(), version: relation.version(), archqual: relation.archqual(), architectures: relation.architectures().map(|a| a.collect()), profiles: relation.profiles().collect(), } } } impl From for Vec { fn from(entry: Entry) -> Self { entry.relations().map(|r| r.into()).collect() } } impl From> for Entry { fn from(relations: Vec) -> Self { let relations: Vec = relations.into_iter().map(|r| r.into()).collect(); Entry::from(relations) } } #[cfg(test)] mod tests { use super::*; #[test] fn test_parse() { let input = "python3-dulwich"; let parsed: Relations = input.parse().unwrap(); assert_eq!(parsed.to_string(), input); assert_eq!(parsed.entries().count(), 1); let entry = parsed.entries().next().unwrap(); assert_eq!(entry.to_string(), "python3-dulwich"); assert_eq!(entry.relations().count(), 1); let relation = entry.relations().next().unwrap(); assert_eq!(relation.to_string(), "python3-dulwich"); assert_eq!(relation.version(), None); let input = "python3-dulwich (>= 0.20.21)"; let parsed: Relations = input.parse().unwrap(); assert_eq!(parsed.to_string(), input); assert_eq!(parsed.entries().count(), 1); let entry = parsed.entries().next().unwrap(); assert_eq!(entry.to_string(), "python3-dulwich (>= 0.20.21)"); assert_eq!(entry.relations().count(), 1); let relation = entry.relations().next().unwrap(); assert_eq!(relation.to_string(), "python3-dulwich (>= 0.20.21)"); assert_eq!( relation.version(), Some(( VersionConstraint::GreaterThanEqual, "0.20.21".parse().unwrap() )) ); } #[test] fn test_multiple() { let input = "python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)"; let parsed: Relations = input.parse().unwrap(); assert_eq!(parsed.to_string(), input); assert_eq!(parsed.entries().count(), 2); let entry = parsed.entries().next().unwrap(); assert_eq!(entry.to_string(), "python3-dulwich (>= 0.20.21)"); assert_eq!(entry.relations().count(), 1); let relation = entry.relations().next().unwrap(); assert_eq!(relation.to_string(), "python3-dulwich (>= 0.20.21)"); assert_eq!( relation.version(), Some(( VersionConstraint::GreaterThanEqual, "0.20.21".parse().unwrap() )) ); let entry = parsed.entries().nth(1).unwrap(); assert_eq!(entry.to_string(), "python3-dulwich (<< 0.21)"); assert_eq!(entry.relations().count(), 1); let relation = entry.relations().next().unwrap(); assert_eq!(relation.to_string(), "python3-dulwich (<< 0.21)"); assert_eq!( relation.version(), Some((VersionConstraint::LessThan, "0.21".parse().unwrap())) ); } #[test] fn test_architectures() { let input = "python3-dulwich [amd64 arm64 armhf i386 mips mips64el mipsel ppc64el s390x]"; let parsed: Relations = input.parse().unwrap(); assert_eq!(parsed.to_string(), input); assert_eq!(parsed.entries().count(), 1); let entry = parsed.entries().next().unwrap(); assert_eq!( entry.to_string(), "python3-dulwich [amd64 arm64 armhf i386 mips mips64el mipsel ppc64el s390x]" ); assert_eq!(entry.relations().count(), 1); let relation = entry.relations().next().unwrap(); assert_eq!( relation.to_string(), "python3-dulwich [amd64 arm64 armhf i386 mips mips64el mipsel ppc64el s390x]" ); assert_eq!(relation.version(), None); assert_eq!( relation.architectures().unwrap().collect::>(), vec![ "amd64", "arm64", "armhf", "i386", "mips", "mips64el", "mipsel", "ppc64el", "s390x" ] .into_iter() .map(|s| s.to_string()) .collect::>() ); } #[test] fn test_profiles() { let input = "foo (>= 1.0) [i386 arm] , bar"; let parsed: Relations = input.parse().unwrap(); assert_eq!(parsed.to_string(), input); assert_eq!(parsed.entries().count(), 2); let entry = parsed.entries().next().unwrap(); assert_eq!( entry.to_string(), "foo (>= 1.0) [i386 arm] " ); assert_eq!(entry.relations().count(), 1); let relation = entry.relations().next().unwrap(); assert_eq!( relation.to_string(), "foo (>= 1.0) [i386 arm] " ); assert_eq!( relation.version(), Some((VersionConstraint::GreaterThanEqual, "1.0".parse().unwrap())) ); assert_eq!( relation.architectures().unwrap().collect::>(), vec!["i386", "arm"] .into_iter() .map(|s| s.to_string()) .collect::>() ); assert_eq!( relation.profiles().collect::>(), vec![ vec![BuildProfile::Disabled("nocheck".to_string())], vec![BuildProfile::Disabled("cross".to_string())] ] ); } #[test] fn test_substvar() { let input = "${shlibs:Depends}"; let (parsed, errors) = Relations::parse_relaxed(input, true); assert_eq!(errors, Vec::::new()); assert_eq!(parsed.to_string(), input); assert_eq!(parsed.entries().count(), 0); assert_eq!( parsed.substvars().collect::>(), vec!["${shlibs:Depends}"] ); } #[test] fn test_new() { let r = Relation::new( "samba", Some((VersionConstraint::GreaterThanEqual, "2.0".parse().unwrap())), ); assert_eq!(r.to_string(), "samba (>= 2.0)"); } #[test] fn test_drop_constraint() { let mut r = Relation::new( "samba", Some((VersionConstraint::GreaterThanEqual, "2.0".parse().unwrap())), ); r.drop_constraint(); assert_eq!(r.to_string(), "samba"); } #[test] fn test_simple() { let r = Relation::simple("samba"); assert_eq!(r.to_string(), "samba"); } #[test] fn test_remove_first_entry() { let mut rels: Relations = r#"python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)"# .parse() .unwrap(); let removed = rels.remove_entry(0); assert_eq!(removed.to_string(), "python3-dulwich (>= 0.20.21)"); assert_eq!(rels.to_string(), "python3-dulwich (<< 0.21)"); } #[test] fn test_remove_last_entry() { let mut rels: Relations = r#"python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)"# .parse() .unwrap(); rels.remove_entry(1); assert_eq!(rels.to_string(), "python3-dulwich (>= 0.20.21)"); } #[test] fn test_remove_middle() { let mut rels: Relations = r#"python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21), python3-dulwich (<< 0.22)"# .parse() .unwrap(); rels.remove_entry(1); assert_eq!( rels.to_string(), "python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.22)" ); } #[test] fn test_remove_added() { let mut rels: Relations = r#"python3-dulwich (>= 0.20.21)"#.parse().unwrap(); let entry = Entry::from(vec![Relation::simple("python3-dulwich")]); rels.push(entry); rels.remove_entry(1); assert_eq!(rels.to_string(), "python3-dulwich (>= 0.20.21)"); } #[test] fn test_push() { let mut rels: Relations = r#"python3-dulwich (>= 0.20.21)"#.parse().unwrap(); let entry = Entry::from(vec![Relation::simple("python3-dulwich")]); rels.push(entry); assert_eq!( rels.to_string(), "python3-dulwich (>= 0.20.21), python3-dulwich" ); } #[test] fn test_insert_with_custom_separator() { let mut rels: Relations = "python3".parse().unwrap(); let entry = Entry::from(vec![Relation::simple("debhelper")]); rels.insert_with_separator(1, entry, Some("\n ")); assert_eq!(rels.to_string(), "python3,\n debhelper"); } #[test] fn test_insert_with_wrap_and_sort_separator() { let mut rels: Relations = "python3".parse().unwrap(); let entry = Entry::from(vec![Relation::simple("rustc")]); // Simulate wrap-and-sort -a style with field name "Depends: " (9 chars) rels.insert_with_separator(1, entry, Some("\n ")); assert_eq!(rels.to_string(), "python3,\n rustc"); } #[test] fn test_push_from_empty() { let mut rels: Relations = "".parse().unwrap(); let entry = Entry::from(vec![Relation::simple("python3-dulwich")]); rels.push(entry); assert_eq!(rels.to_string(), "python3-dulwich"); } #[test] fn test_insert() { let mut rels: Relations = r#"python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)"# .parse() .unwrap(); let entry = Entry::from(vec![Relation::simple("python3-dulwich")]); rels.insert(1, entry); assert_eq!( rels.to_string(), "python3-dulwich (>= 0.20.21), python3-dulwich, python3-dulwich (<< 0.21)" ); } #[test] fn test_insert_at_start() { let mut rels: Relations = r#"python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)"# .parse() .unwrap(); let entry = Entry::from(vec![Relation::simple("python3-dulwich")]); rels.insert(0, entry); assert_eq!( rels.to_string(), "python3-dulwich, python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)" ); } #[test] fn test_insert_after_error() { let (mut rels, errors) = Relations::parse_relaxed("@foo@, debhelper (>= 1.0)", false); assert_eq!( errors, vec![ "expected $ or identifier but got ERROR", "expected comma or end of file but got Some(IDENT)", "expected $ or identifier but got ERROR" ] ); let entry = Entry::from(vec![Relation::simple("bar")]); rels.push(entry); assert_eq!(rels.to_string(), "@foo@, debhelper (>= 1.0), bar"); } #[test] fn test_insert_before_error() { let (mut rels, errors) = Relations::parse_relaxed("debhelper (>= 1.0), @foo@, bla", false); assert_eq!( errors, vec![ "expected $ or identifier but got ERROR", "expected comma or end of file but got Some(IDENT)", "expected $ or identifier but got ERROR" ] ); let entry = Entry::from(vec![Relation::simple("bar")]); rels.insert(0, entry); assert_eq!(rels.to_string(), "bar, debhelper (>= 1.0), @foo@, bla"); } #[test] fn test_replace() { let mut rels: Relations = r#"python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)"# .parse() .unwrap(); let entry = Entry::from(vec![Relation::simple("python3-dulwich")]); rels.replace(1, entry); assert_eq!( rels.to_string(), "python3-dulwich (>= 0.20.21), python3-dulwich" ); } #[test] fn test_relation_from_entries() { let entries = vec![ Entry::from(vec![Relation::simple("python3-dulwich")]), Entry::from(vec![Relation::simple("python3-breezy")]), ]; let rels: Relations = entries.into(); assert_eq!(rels.entries().count(), 2); assert_eq!(rels.to_string(), "python3-dulwich, python3-breezy"); } #[test] fn test_entry_from_relations() { let relations = vec![ Relation::simple("python3-dulwich"), Relation::simple("python3-breezy"), ]; let entry: Entry = relations.into(); assert_eq!(entry.relations().count(), 2); assert_eq!(entry.to_string(), "python3-dulwich | python3-breezy"); } #[test] fn test_parse_entry() { let parsed: Entry = "python3-dulwich (>= 0.20.21) | bar".parse().unwrap(); assert_eq!(parsed.to_string(), "python3-dulwich (>= 0.20.21) | bar"); assert_eq!(parsed.relations().count(), 2); assert_eq!( "foo, bar".parse::().unwrap_err(), "Multiple entries found" ); assert_eq!("".parse::().unwrap_err(), "No entry found"); } #[test] fn test_parse_relation() { let parsed: Relation = "python3-dulwich (>= 0.20.21)".parse().unwrap(); assert_eq!(parsed.to_string(), "python3-dulwich (>= 0.20.21)"); assert_eq!( parsed.version(), Some(( VersionConstraint::GreaterThanEqual, "0.20.21".parse().unwrap() )) ); assert_eq!( "foo | bar".parse::().unwrap_err(), "Multiple relations found" ); assert_eq!("".parse::().unwrap_err(), "No entry found"); } #[test] fn test_special() { let parsed: Relation = "librust-breezyshim+dirty-tracker-dev:amd64 (>= 0.1.138-~~)" .parse() .unwrap(); assert_eq!( parsed.to_string(), "librust-breezyshim+dirty-tracker-dev:amd64 (>= 0.1.138-~~)" ); assert_eq!( parsed.version(), Some(( VersionConstraint::GreaterThanEqual, "0.1.138-~~".parse().unwrap() )) ); assert_eq!(parsed.archqual(), Some("amd64".to_string())); assert_eq!(parsed.name(), "librust-breezyshim+dirty-tracker-dev"); } #[test] fn test_relations_satisfied_by() { let rels: Relations = "python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)" .parse() .unwrap(); let satisfied = |name: &str| -> Option { match name { "python3-dulwich" => Some("0.20.21".parse().unwrap()), _ => None, } }; assert!(rels.satisfied_by(satisfied)); let satisfied = |name: &str| match name { "python3-dulwich" => Some("0.21".parse().unwrap()), _ => None, }; assert!(!rels.satisfied_by(satisfied)); let satisfied = |name: &str| match name { "python3-dulwich" => Some("0.20.20".parse().unwrap()), _ => None, }; assert!(!rels.satisfied_by(satisfied)); } #[test] fn test_entry_satisfied_by() { let entry: Entry = "python3-dulwich (>= 0.20.21) | python3-dulwich (<< 0.18)" .parse() .unwrap(); let satisfied = |name: &str| -> Option { match name { "python3-dulwich" => Some("0.20.21".parse().unwrap()), _ => None, } }; assert!(entry.satisfied_by(satisfied)); let satisfied = |name: &str| -> Option { match name { "python3-dulwich" => Some("0.18".parse().unwrap()), _ => None, } }; assert!(!entry.satisfied_by(satisfied)); } #[test] fn test_wrap_and_sort_relation() { let relation: Relation = " python3-dulwich (>= 11) [ amd64 ] < lala>" .parse() .unwrap(); let wrapped = relation.wrap_and_sort(); assert_eq!( wrapped.to_string(), "python3-dulwich (>= 11) [amd64] " ); } #[test] fn test_wrap_and_sort_relations() { let entry: Relations = "python3-dulwich (>= 0.20.21) | bar, \n\n\n\npython3-dulwich (<< 0.21)" .parse() .unwrap(); let wrapped = entry.wrap_and_sort(); assert_eq!( wrapped.to_string(), "bar | python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)" ); } #[cfg(feature = "serde")] #[test] fn test_serialize_relations() { let relations: Relations = "python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)" .parse() .unwrap(); let serialized = serde_json::to_string(&relations).unwrap(); assert_eq!( serialized, r#""python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)""# ); } #[cfg(feature = "serde")] #[test] fn test_deserialize_relations() { let relations: Relations = "python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)" .parse() .unwrap(); let serialized = serde_json::to_string(&relations).unwrap(); let deserialized: Relations = serde_json::from_str(&serialized).unwrap(); assert_eq!(deserialized.to_string(), relations.to_string()); } #[cfg(feature = "serde")] #[test] fn test_serialize_relation() { let relation: Relation = "python3-dulwich (>= 0.20.21)".parse().unwrap(); let serialized = serde_json::to_string(&relation).unwrap(); assert_eq!(serialized, r#""python3-dulwich (>= 0.20.21)""#); } #[cfg(feature = "serde")] #[test] fn test_deserialize_relation() { let relation: Relation = "python3-dulwich (>= 0.20.21)".parse().unwrap(); let serialized = serde_json::to_string(&relation).unwrap(); let deserialized: Relation = serde_json::from_str(&serialized).unwrap(); assert_eq!(deserialized.to_string(), relation.to_string()); } #[cfg(feature = "serde")] #[test] fn test_serialize_entry() { let entry: Entry = "python3-dulwich (>= 0.20.21) | python3-dulwich (<< 0.18)" .parse() .unwrap(); let serialized = serde_json::to_string(&entry).unwrap(); assert_eq!( serialized, r#""python3-dulwich (>= 0.20.21) | python3-dulwich (<< 0.18)""# ); } #[cfg(feature = "serde")] #[test] fn test_deserialize_entry() { let entry: Entry = "python3-dulwich (>= 0.20.21) | python3-dulwich (<< 0.18)" .parse() .unwrap(); let serialized = serde_json::to_string(&entry).unwrap(); let deserialized: Entry = serde_json::from_str(&serialized).unwrap(); assert_eq!(deserialized.to_string(), entry.to_string()); } #[test] fn test_remove_first_relation() { let entry: Entry = "python3-dulwich (>= 0.20.21) | python3-dulwich (<< 0.18)" .parse() .unwrap(); let mut rel = entry.relations().next().unwrap(); rel.remove(); assert_eq!(entry.to_string(), "python3-dulwich (<< 0.18)"); } #[test] fn test_remove_last_relation() { let entry: Entry = "python3-dulwich (>= 0.20.21) | python3-dulwich (<< 0.18)" .parse() .unwrap(); let mut rel = entry.relations().nth(1).unwrap(); rel.remove(); assert_eq!(entry.to_string(), "python3-dulwich (>= 0.20.21)"); } #[test] fn test_remove_only_relation() { let entry: Entry = "python3-dulwich (>= 0.20.21)".parse().unwrap(); let mut rel = entry.relations().next().unwrap(); rel.remove(); assert_eq!(entry.to_string(), ""); } #[test] fn test_relations_is_empty() { let entry: Relations = "python3-dulwich (>= 0.20.21)".parse().unwrap(); assert!(!entry.is_empty()); assert_eq!(1, entry.len()); let mut rel = entry.entries().next().unwrap(); rel.remove(); assert!(entry.is_empty()); assert_eq!(0, entry.len()); } #[test] fn test_entry_is_empty() { let entry: Entry = "python3-dulwich (>= 0.20.21) | python3-dulwich (<< 0.18)" .parse() .unwrap(); assert!(!entry.is_empty()); assert_eq!(2, entry.len()); let mut rel = entry.relations().next().unwrap(); rel.remove(); assert!(!entry.is_empty()); assert_eq!(1, entry.len()); let mut rel = entry.relations().next().unwrap(); rel.remove(); assert!(entry.is_empty()); assert_eq!(0, entry.len()); } #[test] fn test_relation_set_version() { let mut rel: Relation = "samba".parse().unwrap(); rel.set_version(None); assert_eq!("samba", rel.to_string()); rel.set_version(Some(( VersionConstraint::GreaterThanEqual, "2.0".parse().unwrap(), ))); assert_eq!("samba (>= 2.0)", rel.to_string()); rel.set_version(None); assert_eq!("samba", rel.to_string()); rel.set_version(Some(( VersionConstraint::GreaterThanEqual, "2.0".parse().unwrap(), ))); rel.set_version(Some(( VersionConstraint::GreaterThanEqual, "1.1".parse().unwrap(), ))); assert_eq!("samba (>= 1.1)", rel.to_string()); } #[test] fn test_replace_relation() { let mut entry: Entry = "python3-dulwich (>= 0.20.21) | python3-dulwich (<< 0.18)" .parse() .unwrap(); let new_rel = Relation::simple("python3-breezy"); entry.replace(0, new_rel); assert_eq!( entry.to_string(), "python3-breezy | python3-dulwich (<< 0.18)" ); } #[test] fn test_entry_push_relation() { let relations: Relations = "python3-dulwich (>= 0.20.21)".parse().unwrap(); let new_rel = Relation::simple("python3-breezy"); let mut entry = relations.entries().next().unwrap(); entry.push(new_rel); assert_eq!( entry.to_string(), "python3-dulwich (>= 0.20.21) | python3-breezy" ); assert_eq!( relations.to_string(), "python3-dulwich (>= 0.20.21) | python3-breezy" ); } #[test] fn test_relations_remove_empty_entry() { let (mut relations, errors) = Relations::parse_relaxed("foo, , bar, ", false); assert_eq!(errors, Vec::::new()); assert_eq!(relations.to_string(), "foo, , bar, "); assert_eq!(relations.len(), 2); assert_eq!( relations.entries().next().unwrap().to_string(), "foo".to_string() ); assert_eq!( relations.entries().nth(1).unwrap().to_string(), "bar".to_string() ); relations.remove_entry(1); assert_eq!(relations.to_string(), "foo, , "); } #[test] fn test_entry_remove_relation() { let entry: Entry = "python3-dulwich | samba".parse().unwrap(); let removed = entry.remove_relation(0); assert_eq!(removed.to_string(), "python3-dulwich"); assert_eq!(entry.to_string(), "samba"); } #[test] fn test_wrap_and_sort_removes_empty_entries() { let relations: Relations = "foo, , bar, ".parse().unwrap(); let wrapped = relations.wrap_and_sort(); assert_eq!(wrapped.to_string(), "bar, foo"); } #[test] fn test_set_archqual() { let entry: Entry = "python3-dulwich | samba".parse().unwrap(); let mut rel = entry.relations().next().unwrap(); rel.set_archqual("amd64"); assert_eq!(rel.to_string(), "python3-dulwich:amd64"); assert_eq!(rel.archqual(), Some("amd64".to_string())); assert_eq!(entry.to_string(), "python3-dulwich:amd64 | samba"); rel.set_archqual("i386"); assert_eq!(rel.to_string(), "python3-dulwich:i386"); assert_eq!(rel.archqual(), Some("i386".to_string())); assert_eq!(entry.to_string(), "python3-dulwich:i386 | samba"); } #[test] fn test_set_architectures() { let mut relation = Relation::simple("samba"); relation.set_architectures(vec!["amd64", "i386"].into_iter()); assert_eq!(relation.to_string(), "samba [amd64 i386]"); } #[test] fn test_relation_builder_no_architectures() { // Test that building a relation without architectures doesn't add empty brackets let relation = Relation::build("debhelper").build(); assert_eq!(relation.to_string(), "debhelper"); } #[test] fn test_relation_builder_with_architectures() { // Test that building a relation with architectures works correctly let relation = Relation::build("samba") .architectures(vec!["amd64".to_string(), "i386".to_string()]) .build(); assert_eq!(relation.to_string(), "samba [amd64 i386]"); } #[test] fn test_ensure_minimum_version_add_new() { let mut relations: Relations = "python3".parse().unwrap(); relations.ensure_minimum_version("debhelper", &"12".parse().unwrap()); assert_eq!(relations.to_string(), "python3, debhelper (>= 12)"); } #[test] fn test_ensure_minimum_version_update() { let mut relations: Relations = "debhelper (>= 9)".parse().unwrap(); relations.ensure_minimum_version("debhelper", &"12".parse().unwrap()); assert_eq!(relations.to_string(), "debhelper (>= 12)"); } #[test] fn test_ensure_minimum_version_no_change() { let mut relations: Relations = "debhelper (>= 13)".parse().unwrap(); relations.ensure_minimum_version("debhelper", &"12".parse().unwrap()); assert_eq!(relations.to_string(), "debhelper (>= 13)"); } #[test] fn test_ensure_minimum_version_no_version() { let mut relations: Relations = "debhelper".parse().unwrap(); relations.ensure_minimum_version("debhelper", &"12".parse().unwrap()); assert_eq!(relations.to_string(), "debhelper (>= 12)"); } #[test] fn test_ensure_minimum_version_preserves_newline() { // Test that newline after the field name is preserved // This is the format often used in Debian control files: // Build-Depends: // debhelper (>= 9), // pkg-config let input = "\n debhelper (>= 9),\n pkg-config,\n uuid-dev"; let mut relations: Relations = input.parse().unwrap(); relations.ensure_minimum_version("debhelper", &"12~".parse().unwrap()); let result = relations.to_string(); // The newline before the first entry should be preserved assert!( result.starts_with('\n'), "Expected result to start with newline, got: {:?}", result ); assert_eq!(result, "\n debhelper (>= 12~),\n pkg-config,\n uuid-dev"); } #[test] fn test_ensure_minimum_version_preserves_newline_in_control() { // Test the full scenario from the bug report use crate::lossless::Control; use std::str::FromStr; let input = r#"Source: f2fs-tools Section: admin Priority: optional Maintainer: Test Build-Depends: debhelper (>= 9), pkg-config, uuid-dev Package: f2fs-tools Description: test "#; let control = Control::from_str(input).unwrap(); let mut source = control.source().unwrap(); let mut build_depends = source.build_depends().unwrap(); let version = Version::from_str("12~").unwrap(); build_depends.ensure_minimum_version("debhelper", &version); source.set_build_depends(&build_depends); let output = control.to_string(); // Check that the formatting is preserved - the newline after "Build-Depends:" should still be there assert!( output.contains("Build-Depends:\n debhelper (>= 12~)"), "Expected 'Build-Depends:\\n debhelper (>= 12~)' but got:\n{}", output ); } #[test] fn test_ensure_exact_version_add_new() { let mut relations: Relations = "python3".parse().unwrap(); relations.ensure_exact_version("debhelper", &"12".parse().unwrap()); assert_eq!(relations.to_string(), "python3, debhelper (= 12)"); } #[test] fn test_ensure_exact_version_update() { let mut relations: Relations = "debhelper (>= 9)".parse().unwrap(); relations.ensure_exact_version("debhelper", &"12".parse().unwrap()); assert_eq!(relations.to_string(), "debhelper (= 12)"); } #[test] fn test_ensure_exact_version_no_change() { let mut relations: Relations = "debhelper (= 12)".parse().unwrap(); relations.ensure_exact_version("debhelper", &"12".parse().unwrap()); assert_eq!(relations.to_string(), "debhelper (= 12)"); } #[test] fn test_ensure_some_version_add_new() { let mut relations: Relations = "python3".parse().unwrap(); relations.ensure_some_version("debhelper"); assert_eq!(relations.to_string(), "python3, debhelper"); } #[test] fn test_ensure_some_version_exists_with_version() { let mut relations: Relations = "debhelper (>= 12)".parse().unwrap(); relations.ensure_some_version("debhelper"); assert_eq!(relations.to_string(), "debhelper (>= 12)"); } #[test] fn test_ensure_some_version_exists_no_version() { let mut relations: Relations = "debhelper".parse().unwrap(); relations.ensure_some_version("debhelper"); assert_eq!(relations.to_string(), "debhelper"); } #[test] fn test_ensure_substvar() { let mut relations: Relations = "python3".parse().unwrap(); relations.ensure_substvar("${misc:Depends}").unwrap(); assert_eq!(relations.to_string(), "python3, ${misc:Depends}"); } #[test] fn test_ensure_substvar_already_exists() { let (mut relations, _) = Relations::parse_relaxed("python3, ${misc:Depends}", true); relations.ensure_substvar("${misc:Depends}").unwrap(); assert_eq!(relations.to_string(), "python3, ${misc:Depends}"); } #[test] fn test_ensure_substvar_empty_relations() { let mut relations: Relations = Relations::new(); relations.ensure_substvar("${misc:Depends}").unwrap(); assert_eq!(relations.to_string(), "${misc:Depends}"); } #[test] fn test_ensure_substvar_preserves_whitespace() { // Test with non-standard whitespace (multiple spaces) let (mut relations, _) = Relations::parse_relaxed("python3, rustc", false); relations.ensure_substvar("${misc:Depends}").unwrap(); // Should preserve the double-space pattern assert_eq!(relations.to_string(), "python3, rustc, ${misc:Depends}"); } #[test] fn test_ensure_substvar_to_existing_substvar() { // Test adding a substvar to existing substvar (no entries) // This reproduces the bug where space after comma is lost let (mut relations, _) = Relations::parse_relaxed("${shlibs:Depends}", true); relations.ensure_substvar("${misc:Depends}").unwrap(); // Should have a space after the comma assert_eq!(relations.to_string(), "${shlibs:Depends}, ${misc:Depends}"); } #[test] fn test_drop_substvar_basic() { let (mut relations, _) = Relations::parse_relaxed("python3, ${misc:Depends}", true); relations.drop_substvar("${misc:Depends}"); assert_eq!(relations.to_string(), "python3"); } #[test] fn test_drop_substvar_first_position() { let (mut relations, _) = Relations::parse_relaxed("${misc:Depends}, python3", true); relations.drop_substvar("${misc:Depends}"); assert_eq!(relations.to_string(), "python3"); } #[test] fn test_drop_substvar_middle_position() { let (mut relations, _) = Relations::parse_relaxed("python3, ${misc:Depends}, rustc", true); relations.drop_substvar("${misc:Depends}"); assert_eq!(relations.to_string(), "python3, rustc"); } #[test] fn test_drop_substvar_only_substvar() { let (mut relations, _) = Relations::parse_relaxed("${misc:Depends}", true); relations.drop_substvar("${misc:Depends}"); assert_eq!(relations.to_string(), ""); } #[test] fn test_drop_substvar_not_exists() { let (mut relations, _) = Relations::parse_relaxed("python3, rustc", false); relations.drop_substvar("${misc:Depends}"); assert_eq!(relations.to_string(), "python3, rustc"); } #[test] fn test_drop_substvar_multiple_substvars() { let (mut relations, _) = Relations::parse_relaxed("python3, ${misc:Depends}, ${shlibs:Depends}", true); relations.drop_substvar("${misc:Depends}"); assert_eq!(relations.to_string(), "python3, ${shlibs:Depends}"); } #[test] fn test_drop_substvar_preserves_whitespace() { let (mut relations, _) = Relations::parse_relaxed("python3, ${misc:Depends}", true); relations.drop_substvar("${misc:Depends}"); assert_eq!(relations.to_string(), "python3"); } #[test] fn test_filter_entries_basic() { let mut relations: Relations = "python3, debhelper, rustc".parse().unwrap(); relations.filter_entries(|entry| entry.relations().any(|r| r.name().starts_with("python"))); assert_eq!(relations.to_string(), "python3"); } #[test] fn test_filter_entries_keep_all() { let mut relations: Relations = "python3, debhelper".parse().unwrap(); relations.filter_entries(|_| true); assert_eq!(relations.to_string(), "python3, debhelper"); } #[test] fn test_filter_entries_remove_all() { let mut relations: Relations = "python3, debhelper".parse().unwrap(); relations.filter_entries(|_| false); assert_eq!(relations.to_string(), ""); } #[test] fn test_filter_entries_keep_middle() { let mut relations: Relations = "aaa, bbb, ccc".parse().unwrap(); relations.filter_entries(|entry| entry.relations().any(|r| r.name() == "bbb")); assert_eq!(relations.to_string(), "bbb"); } // Tests for new convenience methods #[test] fn test_is_sorted_wrap_and_sort_order() { // Sorted according to WrapAndSortOrder let relations: Relations = "debhelper, python3, rustc".parse().unwrap(); assert!(relations.is_sorted(&WrapAndSortOrder)); // Not sorted let relations: Relations = "rustc, debhelper, python3".parse().unwrap(); assert!(!relations.is_sorted(&WrapAndSortOrder)); // Build systems first (sorted alphabetically within their group) let (relations, _) = Relations::parse_relaxed("cdbs, debhelper-compat, python3, ${misc:Depends}", true); assert!(relations.is_sorted(&WrapAndSortOrder)); } #[test] fn test_is_sorted_default_order() { // Sorted alphabetically let relations: Relations = "aaa, bbb, ccc".parse().unwrap(); assert!(relations.is_sorted(&DefaultSortingOrder)); // Not sorted let relations: Relations = "ccc, aaa, bbb".parse().unwrap(); assert!(!relations.is_sorted(&DefaultSortingOrder)); // Special items at end let (relations, _) = Relations::parse_relaxed("aaa, bbb, ${misc:Depends}", true); assert!(relations.is_sorted(&DefaultSortingOrder)); } #[test] fn test_is_sorted_with_substvars() { // Substvars should be ignored by DefaultSortingOrder let (relations, _) = Relations::parse_relaxed("python3, ${misc:Depends}, rustc", true); // This is considered sorted because ${misc:Depends} is ignored assert!(relations.is_sorted(&DefaultSortingOrder)); } #[test] fn test_drop_dependency_exists() { let mut relations: Relations = "python3, debhelper, rustc".parse().unwrap(); assert!(relations.drop_dependency("debhelper")); assert_eq!(relations.to_string(), "python3, rustc"); } #[test] fn test_drop_dependency_not_exists() { let mut relations: Relations = "python3, rustc".parse().unwrap(); assert!(!relations.drop_dependency("nonexistent")); assert_eq!(relations.to_string(), "python3, rustc"); } #[test] fn test_drop_dependency_only_item() { let mut relations: Relations = "python3".parse().unwrap(); assert!(relations.drop_dependency("python3")); assert_eq!(relations.to_string(), ""); } #[test] fn test_add_dependency_to_empty() { let mut relations: Relations = "".parse().unwrap(); let entry = Entry::from(Relation::simple("python3")); relations.add_dependency(entry, None); assert_eq!(relations.to_string(), "python3"); } #[test] fn test_add_dependency_sorted_position() { let mut relations: Relations = "debhelper, rustc".parse().unwrap(); let entry = Entry::from(Relation::simple("python3")); relations.add_dependency(entry, None); // Should be inserted in sorted position assert_eq!(relations.to_string(), "debhelper, python3, rustc"); } #[test] fn test_add_dependency_explicit_position() { let mut relations: Relations = "python3, rustc".parse().unwrap(); let entry = Entry::from(Relation::simple("debhelper")); relations.add_dependency(entry, Some(0)); assert_eq!(relations.to_string(), "debhelper, python3, rustc"); } #[test] fn test_add_dependency_build_system_first() { let mut relations: Relations = "python3, rustc".parse().unwrap(); let entry = Entry::from(Relation::simple("debhelper-compat")); relations.add_dependency(entry, None); // debhelper-compat should be inserted first (build system) assert_eq!(relations.to_string(), "debhelper-compat, python3, rustc"); } #[test] fn test_add_dependency_at_end() { let mut relations: Relations = "debhelper, python3".parse().unwrap(); let entry = Entry::from(Relation::simple("zzz-package")); relations.add_dependency(entry, None); // Should be added at the end (alphabetically after python3) assert_eq!(relations.to_string(), "debhelper, python3, zzz-package"); } #[test] fn test_add_dependency_to_single_entry() { // Regression test: ensure comma is added when inserting into single-entry Relations let mut relations: Relations = "python3-dulwich".parse().unwrap(); let entry: Entry = "debhelper-compat (= 12)".parse().unwrap(); relations.add_dependency(entry, None); // Should insert with comma separator assert_eq!( relations.to_string(), "debhelper-compat (= 12), python3-dulwich" ); } #[test] fn test_get_relation_exists() { let relations: Relations = "python3, debhelper (>= 12), rustc".parse().unwrap(); let result = relations.get_relation("debhelper"); assert!(result.is_ok()); let (idx, entry) = result.unwrap(); assert_eq!(idx, 1); assert_eq!(entry.to_string(), "debhelper (>= 12)"); } #[test] fn test_get_relation_not_exists() { let relations: Relations = "python3, rustc".parse().unwrap(); let result = relations.get_relation("nonexistent"); assert_eq!(result, Err("Package nonexistent not found".to_string())); } #[test] fn test_get_relation_complex_rule() { let relations: Relations = "python3 | python3-minimal, rustc".parse().unwrap(); let result = relations.get_relation("python3"); assert_eq!( result, Err("Complex rule for python3, aborting".to_string()) ); } #[test] fn test_iter_relations_for_simple() { let relations: Relations = "python3, debhelper, python3-dev".parse().unwrap(); let entries: Vec<_> = relations.iter_relations_for("python3").collect(); assert_eq!(entries.len(), 1); assert_eq!(entries[0].0, 0); assert_eq!(entries[0].1.to_string(), "python3"); } #[test] fn test_iter_relations_for_alternatives() { let relations: Relations = "python3 | python3-minimal, python3-dev".parse().unwrap(); let entries: Vec<_> = relations.iter_relations_for("python3").collect(); // Should find both the alternative entry and python3-dev is not included assert_eq!(entries.len(), 1); assert_eq!(entries[0].0, 0); } #[test] fn test_iter_relations_for_not_found() { let relations: Relations = "python3, rustc".parse().unwrap(); let entries: Vec<_> = relations.iter_relations_for("debhelper").collect(); assert_eq!(entries.len(), 0); } #[test] fn test_has_relation_exists() { let relations: Relations = "python3, debhelper, rustc".parse().unwrap(); assert!(relations.has_relation("debhelper")); assert!(relations.has_relation("python3")); assert!(relations.has_relation("rustc")); } #[test] fn test_has_relation_not_exists() { let relations: Relations = "python3, rustc".parse().unwrap(); assert!(!relations.has_relation("debhelper")); } #[test] fn test_has_relation_in_alternative() { let relations: Relations = "python3 | python3-minimal".parse().unwrap(); assert!(relations.has_relation("python3")); assert!(relations.has_relation("python3-minimal")); } #[test] fn test_sorting_order_wrap_and_sort_build_systems() { let order = WrapAndSortOrder; // Build systems should come before regular packages assert!(order.lt("debhelper", "python3")); assert!(order.lt("debhelper-compat", "rustc")); assert!(order.lt("cdbs", "aaa")); assert!(order.lt("dh-python", "python3")); } #[test] fn test_sorting_order_wrap_and_sort_regular_packages() { let order = WrapAndSortOrder; // Regular packages sorted alphabetically assert!(order.lt("aaa", "bbb")); assert!(order.lt("python3", "rustc")); assert!(!order.lt("rustc", "python3")); } #[test] fn test_sorting_order_wrap_and_sort_substvars() { let order = WrapAndSortOrder; // Substvars should come after regular packages assert!(order.lt("python3", "${misc:Depends}")); assert!(!order.lt("${misc:Depends}", "python3")); // But wrap-and-sort doesn't ignore them assert!(!order.ignore("${misc:Depends}")); } #[test] fn test_sorting_order_default_special_items() { let order = DefaultSortingOrder; // Special items should come after regular items assert!(order.lt("python3", "${misc:Depends}")); assert!(order.lt("aaa", "@cdbs@")); // And should be ignored assert!(order.ignore("${misc:Depends}")); assert!(order.ignore("@cdbs@")); assert!(!order.ignore("python3")); } #[test] fn test_is_special_package_name() { assert!(is_special_package_name("${misc:Depends}")); assert!(is_special_package_name("${shlibs:Depends}")); assert!(is_special_package_name("@cdbs@")); assert!(!is_special_package_name("python3")); assert!(!is_special_package_name("debhelper")); } #[test] fn test_add_dependency_with_explicit_position() { // Test that add_dependency works with explicit position and preserves whitespace let mut relations: Relations = "python3, rustc".parse().unwrap(); let entry = Entry::from(Relation::simple("debhelper")); relations.add_dependency(entry, Some(1)); // Should preserve the 2-space pattern from the original assert_eq!(relations.to_string(), "python3, debhelper, rustc"); } #[test] fn test_whitespace_detection_single_space() { let mut relations: Relations = "python3, rustc".parse().unwrap(); let entry = Entry::from(Relation::simple("debhelper")); relations.add_dependency(entry, Some(1)); assert_eq!(relations.to_string(), "python3, debhelper, rustc"); } #[test] fn test_whitespace_detection_multiple_spaces() { let mut relations: Relations = "python3, rustc, gcc".parse().unwrap(); let entry = Entry::from(Relation::simple("debhelper")); relations.add_dependency(entry, Some(1)); // Should detect and use the 2-space pattern assert_eq!(relations.to_string(), "python3, debhelper, rustc, gcc"); } #[test] fn test_whitespace_detection_mixed_patterns() { // When patterns differ, use the most common one let mut relations: Relations = "a, b, c, d, e".parse().unwrap(); let entry = Entry::from(Relation::simple("x")); relations.push(entry); // Three single-space (after a, b, d), one double-space (after c) // Should use single space as it's most common assert_eq!(relations.to_string(), "a, b, c, d, e, x"); } #[test] fn test_whitespace_detection_newlines() { let mut relations: Relations = "python3,\n rustc".parse().unwrap(); let entry = Entry::from(Relation::simple("debhelper")); relations.add_dependency(entry, Some(1)); // Detects full pattern including newline assert_eq!(relations.to_string(), "python3,\n debhelper,\n rustc"); } #[test] fn test_append_with_newline_no_trailing() { let mut relations: Relations = "foo,\n bar".parse().unwrap(); let entry = Entry::from(Relation::simple("blah")); relations.add_dependency(entry, None); assert_eq!(relations.to_string(), "foo,\n bar,\n blah"); } #[test] fn test_append_with_trailing_newline() { let mut relations: Relations = "foo,\n bar\n".parse().unwrap(); let entry = Entry::from(Relation::simple("blah")); relations.add_dependency(entry, None); assert_eq!(relations.to_string(), "foo,\n bar,\n blah"); } #[test] fn test_append_with_4_space_indent() { let mut relations: Relations = "foo,\n bar".parse().unwrap(); let entry = Entry::from(Relation::simple("blah")); relations.add_dependency(entry, None); assert_eq!(relations.to_string(), "foo,\n bar,\n blah"); } #[test] fn test_append_with_4_space_and_trailing_newline() { let mut relations: Relations = "foo,\n bar\n".parse().unwrap(); let entry = Entry::from(Relation::simple("blah")); relations.add_dependency(entry, None); assert_eq!(relations.to_string(), "foo,\n bar,\n blah"); } #[test] fn test_odd_syntax_append_no_trailing() { let mut relations: Relations = "\n foo\n , bar".parse().unwrap(); let entry = Entry::from(Relation::simple("blah")); relations.add_dependency(entry, None); assert_eq!(relations.to_string(), "\n foo\n , bar\n , blah"); } #[test] fn test_odd_syntax_append_with_trailing() { let mut relations: Relations = "\n foo\n , bar\n".parse().unwrap(); let entry = Entry::from(Relation::simple("blah")); relations.add_dependency(entry, None); assert_eq!(relations.to_string(), "\n foo\n , bar\n , blah"); } #[test] fn test_insert_at_1_no_trailing() { let mut relations: Relations = "foo,\n bar".parse().unwrap(); let entry = Entry::from(Relation::simple("blah")); relations.add_dependency(entry, Some(1)); assert_eq!(relations.to_string(), "foo,\n blah,\n bar"); } #[test] fn test_insert_at_1_with_trailing() { let mut relations: Relations = "foo,\n bar\n".parse().unwrap(); let entry = Entry::from(Relation::simple("blah")); relations.add_dependency(entry, Some(1)); assert_eq!(relations.to_string(), "foo,\n blah,\n bar"); } #[test] fn test_odd_syntax_insert_at_1() { let mut relations: Relations = "\n foo\n , bar\n".parse().unwrap(); let entry = Entry::from(Relation::simple("blah")); relations.add_dependency(entry, Some(1)); assert_eq!(relations.to_string(), "\n foo\n , blah\n , bar"); } #[test] fn test_relations_preserves_exact_whitespace() { // Test that Relations preserves exact whitespace from input let input = "debhelper (>= 10), quilt (>= 0.40),\n libsystemd-dev [linux-any], pkg-config"; let relations: Relations = input.parse().unwrap(); // The whitespace should be preserved in the syntax tree assert_eq!( relations.to_string(), input, "Relations should preserve exact whitespace from input" ); } #[test] fn test_remove_entry_preserves_indentation() { // Test that removing an entry preserves the indentation pattern let input = "debhelper (>= 10), quilt (>= 0.40),\n libsystemd-dev [linux-any], dh-systemd (>= 1.5), pkg-config"; let mut relations: Relations = input.parse().unwrap(); // Find and remove dh-systemd entry (index 2) let mut to_remove = Vec::new(); for (idx, entry) in relations.entries().enumerate() { for relation in entry.relations() { if relation.name() == "dh-systemd" { to_remove.push(idx); break; } } } for idx in to_remove.into_iter().rev() { relations.remove_entry(idx); } let output = relations.to_string(); println!("After removal: '{}'", output); // The 4-space indentation should be preserved assert!( output.contains("\n libsystemd-dev"), "Expected 4-space indentation to be preserved, but got:\n'{}'", output ); } #[test] fn test_relation_is_implied_by_same_package() { // Same package name with compatible version constraints let inner = Relation::new( "pkg", Some((VersionConstraint::GreaterThanEqual, "1.0".parse().unwrap())), ); let outer = Relation::new( "pkg", Some((VersionConstraint::GreaterThanEqual, "1.5".parse().unwrap())), ); assert!(inner.is_implied_by(&outer)); } #[test] fn test_relation_is_implied_by_different_package() { // Different package names should not imply let inner = Relation::new("pkg1", None); let outer = Relation::new("pkg2", None); assert!(!inner.is_implied_by(&outer)); } #[test] fn test_relation_is_implied_by_no_version() { // No version constraint is implied by any version let inner = Relation::new("pkg", None); let outer = Relation::new( "pkg", Some((VersionConstraint::GreaterThanEqual, "1.0".parse().unwrap())), ); assert!(inner.is_implied_by(&outer)); } #[test] fn test_relation_is_implied_by_identical() { // Identical relations imply each other let inner = Relation::new( "pkg", Some((VersionConstraint::Equal, "1.0".parse().unwrap())), ); let outer = Relation::new( "pkg", Some((VersionConstraint::Equal, "1.0".parse().unwrap())), ); assert!(inner.is_implied_by(&outer)); assert!(outer.is_implied_by(&inner)); } #[test] fn test_relation_is_implied_by_greater_than_equal() { // pkg >= 1.0 is implied by pkg >= 2.0 let inner = Relation::new( "pkg", Some((VersionConstraint::GreaterThanEqual, "1.0".parse().unwrap())), ); let outer = Relation::new( "pkg", Some((VersionConstraint::GreaterThanEqual, "2.0".parse().unwrap())), ); assert!(inner.is_implied_by(&outer)); assert!(!outer.is_implied_by(&inner)); // pkg >= 1.0 is implied by pkg = 2.0 let outer = Relation::new( "pkg", Some((VersionConstraint::Equal, "2.0".parse().unwrap())), ); assert!(inner.is_implied_by(&outer)); // pkg >= 1.0 is implied by pkg >> 1.5 let outer = Relation::new( "pkg", Some((VersionConstraint::GreaterThan, "1.5".parse().unwrap())), ); assert!(inner.is_implied_by(&outer)); // pkg >= 3.0 is NOT implied by pkg >> 3.0 (>> 3.0 doesn't include 3.0 itself) let inner = Relation::new( "pkg", Some((VersionConstraint::GreaterThanEqual, "3.0".parse().unwrap())), ); let outer = Relation::new( "pkg", Some((VersionConstraint::GreaterThan, "3.0".parse().unwrap())), ); assert!(!inner.is_implied_by(&outer)); } #[test] fn test_relation_is_implied_by_less_than_equal() { // pkg <= 2.0 is implied by pkg <= 1.0 let inner = Relation::new( "pkg", Some((VersionConstraint::LessThanEqual, "2.0".parse().unwrap())), ); let outer = Relation::new( "pkg", Some((VersionConstraint::LessThanEqual, "1.0".parse().unwrap())), ); assert!(inner.is_implied_by(&outer)); assert!(!outer.is_implied_by(&inner)); // pkg <= 2.0 is implied by pkg = 1.0 let outer = Relation::new( "pkg", Some((VersionConstraint::Equal, "1.0".parse().unwrap())), ); assert!(inner.is_implied_by(&outer)); // pkg <= 2.0 is implied by pkg << 1.5 let outer = Relation::new( "pkg", Some((VersionConstraint::LessThan, "1.5".parse().unwrap())), ); assert!(inner.is_implied_by(&outer)); } #[test] fn test_relation_is_implied_by_equal() { // pkg = 1.0 is only implied by pkg = 1.0 let inner = Relation::new( "pkg", Some((VersionConstraint::Equal, "1.0".parse().unwrap())), ); let outer = Relation::new( "pkg", Some((VersionConstraint::Equal, "1.0".parse().unwrap())), ); assert!(inner.is_implied_by(&outer)); // Not implied by different version let outer = Relation::new( "pkg", Some((VersionConstraint::Equal, "2.0".parse().unwrap())), ); assert!(!inner.is_implied_by(&outer)); // Not implied by >= constraint let outer = Relation::new( "pkg", Some((VersionConstraint::GreaterThanEqual, "1.0".parse().unwrap())), ); assert!(!inner.is_implied_by(&outer)); } #[test] fn test_relation_is_implied_by_greater_than() { // pkg >> 1.0 is implied by pkg >> 2.0 let inner = Relation::new( "pkg", Some((VersionConstraint::GreaterThan, "1.0".parse().unwrap())), ); let outer = Relation::new( "pkg", Some((VersionConstraint::GreaterThan, "2.0".parse().unwrap())), ); assert!(inner.is_implied_by(&outer)); // pkg >> 1.0 is implied by pkg = 2.0 let outer = Relation::new( "pkg", Some((VersionConstraint::Equal, "2.0".parse().unwrap())), ); assert!(inner.is_implied_by(&outer)); // pkg >> 1.0 is implied by pkg >= 1.5 (strictly greater) let outer = Relation::new( "pkg", Some((VersionConstraint::GreaterThanEqual, "1.5".parse().unwrap())), ); assert!(inner.is_implied_by(&outer)); // pkg >> 1.0 is NOT implied by pkg >= 1.0 (could be equal) let outer = Relation::new( "pkg", Some((VersionConstraint::GreaterThanEqual, "1.0".parse().unwrap())), ); assert!(!inner.is_implied_by(&outer)); } #[test] fn test_relation_is_implied_by_less_than() { // pkg << 2.0 is implied by pkg << 1.0 let inner = Relation::new( "pkg", Some((VersionConstraint::LessThan, "2.0".parse().unwrap())), ); let outer = Relation::new( "pkg", Some((VersionConstraint::LessThan, "1.0".parse().unwrap())), ); assert!(inner.is_implied_by(&outer)); // pkg << 2.0 is implied by pkg = 1.0 let outer = Relation::new( "pkg", Some((VersionConstraint::Equal, "1.0".parse().unwrap())), ); assert!(inner.is_implied_by(&outer)); // pkg << 2.0 is implied by pkg <= 1.5 (strictly less) let outer = Relation::new( "pkg", Some((VersionConstraint::LessThanEqual, "1.5".parse().unwrap())), ); assert!(inner.is_implied_by(&outer)); } #[test] fn test_relation_is_implied_by_incompatible_constraints() { // >= and <= are incompatible let inner = Relation::new( "pkg", Some((VersionConstraint::GreaterThanEqual, "1.0".parse().unwrap())), ); let outer = Relation::new( "pkg", Some((VersionConstraint::LessThanEqual, "2.0".parse().unwrap())), ); assert!(!inner.is_implied_by(&outer)); assert!(!outer.is_implied_by(&inner)); } #[test] fn test_entry_is_implied_by_identical() { let inner: Entry = "pkg (>= 1.0)".parse().unwrap(); let outer: Entry = "pkg (>= 1.0)".parse().unwrap(); assert!(inner.is_implied_by(&outer)); } #[test] fn test_entry_is_implied_by_or_group() { // "pkg >= 1.0" is implied by "pkg >= 1.5 | libc6" let inner: Entry = "pkg (>= 1.0)".parse().unwrap(); let outer: Entry = "pkg (>= 1.5) | libc6".parse().unwrap(); assert!(inner.is_implied_by(&outer)); } #[test] fn test_entry_is_implied_by_simple_or() { // "pkg1 | pkg2" is implied by "pkg1" (first alternative satisfies) let inner: Entry = "pkg1 | pkg2".parse().unwrap(); let outer: Entry = "pkg1".parse().unwrap(); assert!(inner.is_implied_by(&outer)); // Also implied by "pkg2" let outer: Entry = "pkg2".parse().unwrap(); assert!(inner.is_implied_by(&outer)); } #[test] fn test_entry_is_implied_by_not_implied() { // "pkg >= 2.0" is NOT implied by "pkg >= 1.0" let inner: Entry = "pkg (>= 2.0)".parse().unwrap(); let outer: Entry = "pkg (>= 1.0)".parse().unwrap(); assert!(!inner.is_implied_by(&outer)); } #[test] fn test_entry_is_implied_by_different_packages() { let inner: Entry = "pkg1".parse().unwrap(); let outer: Entry = "pkg2".parse().unwrap(); assert!(!inner.is_implied_by(&outer)); } #[test] fn test_entry_is_implied_by_complex_or() { // "pkg1 | pkg2" is implied by "pkg1 | pkg2" (identical) let inner: Entry = "pkg1 | pkg2".parse().unwrap(); let outer: Entry = "pkg1 | pkg2".parse().unwrap(); assert!(inner.is_implied_by(&outer)); // "pkg1 | pkg2" is implied by "pkg1 | pkg2 | pkg3" (one matches) let outer: Entry = "pkg1 | pkg2 | pkg3".parse().unwrap(); assert!(inner.is_implied_by(&outer)); } #[test] fn test_parse_version_with_epoch() { // Test parsing version strings with epoch (e.g., "1:2.3.2-2~") // The colon should be treated as part of the version, not as a delimiter let input = "amule-dbg (<< 1:2.3.2-2~)"; let parsed: Relations = input.parse().unwrap(); assert_eq!(parsed.to_string(), input); assert_eq!(parsed.entries().count(), 1); let entry = parsed.entries().next().unwrap(); assert_eq!(entry.to_string(), "amule-dbg (<< 1:2.3.2-2~)"); assert_eq!(entry.relations().count(), 1); let relation = entry.relations().next().unwrap(); assert_eq!(relation.name(), "amule-dbg"); assert_eq!(relation.to_string(), "amule-dbg (<< 1:2.3.2-2~)"); assert_eq!( relation.version(), Some((VersionConstraint::LessThan, "1:2.3.2-2~".parse().unwrap())) ); } #[test] fn test_ensure_relation_add_new() { // Test adding a new relation that doesn't exist yet let mut relations: Relations = "python3".parse().unwrap(); let new_entry: Entry = "debhelper (>= 12)".parse().unwrap(); let added = relations.ensure_relation(new_entry); assert!(added); // debhelper is inserted in sorted position (alphabetically before python3) assert_eq!(relations.to_string(), "debhelper (>= 12), python3"); } #[test] fn test_ensure_relation_already_satisfied() { // Test that a relation is not added if it's already satisfied by a stronger constraint let mut relations: Relations = "debhelper (>= 13)".parse().unwrap(); let new_entry: Entry = "debhelper (>= 12)".parse().unwrap(); let added = relations.ensure_relation(new_entry); assert!(!added); assert_eq!(relations.to_string(), "debhelper (>= 13)"); } #[test] fn test_ensure_relation_replace_weaker() { // Test that a weaker relation is replaced with a stronger one let mut relations: Relations = "debhelper (>= 11)".parse().unwrap(); let new_entry: Entry = "debhelper (>= 13)".parse().unwrap(); let added = relations.ensure_relation(new_entry); assert!(added); assert_eq!(relations.to_string(), "debhelper (>= 13)"); } #[test] fn test_ensure_relation_replace_multiple_weaker() { // Test that multiple weaker relations are replaced/removed when a stronger one is added let mut relations: Relations = "debhelper (>= 11), debhelper (>= 10), python3" .parse() .unwrap(); let new_entry: Entry = "debhelper (>= 13)".parse().unwrap(); let added = relations.ensure_relation(new_entry); assert!(added); assert_eq!(relations.to_string(), "debhelper (>= 13), python3"); } #[test] fn test_ensure_relation_identical_entry() { // Test that an identical entry is not added again let mut relations: Relations = "debhelper (>= 12)".parse().unwrap(); let new_entry: Entry = "debhelper (>= 12)".parse().unwrap(); let added = relations.ensure_relation(new_entry); assert!(!added); assert_eq!(relations.to_string(), "debhelper (>= 12)"); } #[test] fn test_ensure_relation_no_version_constraint() { // Test that a relation without version constraint is added let mut relations: Relations = "python3".parse().unwrap(); let new_entry: Entry = "debhelper".parse().unwrap(); let added = relations.ensure_relation(new_entry); assert!(added); // debhelper is inserted in sorted position (alphabetically before python3) assert_eq!(relations.to_string(), "debhelper, python3"); } #[test] fn test_ensure_relation_strengthen_unversioned() { // Test that a versioned constraint replaces an unversioned one // An unversioned dependency is weaker than a versioned one let mut relations: Relations = "debhelper".parse().unwrap(); let new_entry: Entry = "debhelper (>= 12)".parse().unwrap(); let added = relations.ensure_relation(new_entry); assert!(added); assert_eq!(relations.to_string(), "debhelper (>= 12)"); } #[test] fn test_ensure_relation_versioned_implies_unversioned() { // Test that an unversioned dependency is already satisfied by a versioned one // A versioned dependency is stronger and implies the unversioned one let mut relations: Relations = "debhelper (>= 12)".parse().unwrap(); let new_entry: Entry = "debhelper".parse().unwrap(); let added = relations.ensure_relation(new_entry); assert!(!added); assert_eq!(relations.to_string(), "debhelper (>= 12)"); } #[test] fn test_ensure_relation_preserves_whitespace() { // Test that whitespace is preserved when adding a new relation let mut relations: Relations = "python3, rustc".parse().unwrap(); let new_entry: Entry = "debhelper (>= 12)".parse().unwrap(); let added = relations.ensure_relation(new_entry); assert!(added); // debhelper is inserted in sorted position (alphabetically before python3 and rustc) assert_eq!(relations.to_string(), "debhelper (>= 12), python3, rustc"); } #[test] fn test_ensure_relation_empty_relations() { // Test adding to empty relations let mut relations: Relations = Relations::new(); let new_entry: Entry = "debhelper (>= 12)".parse().unwrap(); let added = relations.ensure_relation(new_entry); assert!(added); assert_eq!(relations.to_string(), "debhelper (>= 12)"); } #[test] fn test_ensure_relation_alternative_dependencies() { // Test with alternative dependencies (|) let mut relations: Relations = "python3 | python3-minimal".parse().unwrap(); let new_entry: Entry = "debhelper (>= 12)".parse().unwrap(); let added = relations.ensure_relation(new_entry); assert!(added); // debhelper is inserted in sorted position (alphabetically before python3) assert_eq!( relations.to_string(), "debhelper (>= 12), python3 | python3-minimal" ); } #[test] fn test_ensure_relation_replace_in_middle() { // Test that replacing a weaker entry in the middle preserves order let mut relations: Relations = "python3, debhelper (>= 11), rustc".parse().unwrap(); let new_entry: Entry = "debhelper (>= 13)".parse().unwrap(); let added = relations.ensure_relation(new_entry); assert!(added); assert_eq!(relations.to_string(), "python3, debhelper (>= 13), rustc"); } #[test] fn test_ensure_relation_with_different_package() { // Test that adding a different package doesn't affect existing ones let mut relations: Relations = "python3, debhelper (>= 12)".parse().unwrap(); let new_entry: Entry = "rustc".parse().unwrap(); let added = relations.ensure_relation(new_entry); assert!(added); assert_eq!(relations.to_string(), "python3, debhelper (>= 12), rustc"); } } debian-control-0.2.14/src/lossy/apt.rs000064400000000000000000000313421046102023000157050ustar 00000000000000//! APT related structures use crate::lossy::{Relations, SourceRelation}; use deb822_fast::{FromDeb822, FromDeb822Paragraph, ToDeb822, ToDeb822Paragraph}; fn deserialize_yesno(s: &str) -> Result { match s { "yes" => Ok(true), "no" => Ok(false), _ => Err(format!("invalid value for yesno: {}", s)), } } fn serialize_yesno(b: &bool) -> String { if *b { "yes".to_string() } else { "no".to_string() } } fn deserialize_components(value: &str) -> Result, String> { Ok(value.split_whitespace().map(|s| s.to_string()).collect()) } fn join_whitespace(components: &[String]) -> String { components.join(" ") } fn deserialize_architectures(value: &str) -> Result, String> { Ok(value.split_whitespace().map(|s| s.to_string()).collect()) } #[derive(Debug, Clone, PartialEq, Eq, ToDeb822, FromDeb822)] /// A Release file pub struct Release { #[deb822(field = "Codename")] /// The codename of the release pub codename: String, #[deb822( field = "Components", deserialize_with = deserialize_components, serialize_with = join_whitespace )] /// Components supported by the release pub components: Vec, #[deb822( field = "Architectures", deserialize_with = deserialize_architectures, serialize_with = join_whitespace )] /// Architectures supported by the release pub architectures: Vec, #[deb822(field = "Description")] /// Description of the release pub description: String, #[deb822(field = "Origin")] /// Origin of the release pub origin: String, #[deb822(field = "Label")] /// Label of the release pub label: String, #[deb822(field = "Suite")] /// Suite of the release pub suite: String, #[deb822(field = "Version")] /// Version of the release pub version: String, #[deb822(field = "Date")] /// Date the release was published pub date: String, #[deb822(field = "NotAutomatic", deserialize_with = deserialize_yesno, serialize_with = serialize_yesno)] /// Whether the release is not automatic pub not_automatic: bool, #[deb822(field = "ButAutomaticUpgrades", deserialize_with = deserialize_yesno, serialize_with = serialize_yesno)] /// Indicates if packages retrieved from this release should be automatically upgraded pub but_automatic_upgrades: bool, #[deb822(field = "Acquire-By-Hash", deserialize_with = deserialize_yesno, serialize_with = serialize_yesno)] /// Whether packages files can be acquired by hash pub acquire_by_hash: bool, } fn deserialize_binaries(value: &str) -> Result, String> { Ok(value.split_whitespace().map(|s| s.to_string()).collect()) } fn join_lines(components: &[String]) -> String { components.join("\n") } fn deserialize_package_list(value: &str) -> Result, String> { Ok(value.split('\n').map(|s| s.to_string()).collect()) } #[derive(Debug, Clone, PartialEq, Eq, ToDeb822, FromDeb822)] /// A source pub struct Source { #[deb822(field = "Directory")] /// The directory of the source pub directory: String, #[deb822(field = "Description")] /// Description of the source pub description: Option, #[deb822(field = "Version")] /// Version of the source pub version: debversion::Version, #[deb822(field = "Package")] /// Package of the source pub package: String, #[deb822(field = "Binary", deserialize_with = deserialize_binaries, serialize_with = join_whitespace)] /// Binaries of the source pub binaries: Option>, #[deb822(field = "Maintainer")] /// Maintainer of the source pub maintainer: Option, #[deb822(field = "Build-Depends")] /// Build dependencies of the source pub build_depends: Option, #[deb822(field = "Build-Depends-Indep")] /// Build dependencies independent of the architecture of the source pub build_depends_indep: Option, #[deb822(field = "Build-Conflicts")] /// Build conflicts of the source pub build_conflicts: Option, #[deb822(field = "Build-Conflicts-Indep")] /// Build conflicts independent of the architecture of the source pub build_conflicts_indep: Option, #[deb822(field = "Standards-Version")] /// Standards version of the source pub standards_version: Option, #[deb822(field = "Homepage")] /// Homepage of the source pub homepage: Option, #[deb822(field = "Autobuild")] /// Whether the source should be autobuilt pub autobuild: Option, #[deb822(field = "Testsuite")] /// Testsuite of the source pub testsuite: Option, #[deb822(field = "Vcs-Browser")] /// VCS browser of the source pub vcs_browser: Option, #[deb822(field = "Vcs-Git")] /// VCS Git of the source pub vcs_git: Option, #[deb822(field = "Vcs-Bzr")] /// VCS Bzr of the source pub vcs_bzr: Option, #[deb822(field = "Vcs-Hg")] /// VCS Hg of the source pub vcs_hg: Option, #[deb822(field = "Vcs-Svn")] /// VCS SVN of the source pub vcs_svn: Option, #[deb822(field = "Vcs-Darcs")] /// VCS Darcs of the source pub vcs_darcs: Option, #[deb822(field = "Vcs-Cvs")] /// VCS CVS of the source pub vcs_cvs: Option, #[deb822(field = "Vcs-Arch")] /// VCS Arch of the source pub vcs_arch: Option, #[deb822(field = "Vcs-Mtn")] /// VCS Mtn of the source pub vcs_mtn: Option, #[deb822(field = "Priority")] /// Priority of the source pub priority: Option, #[deb822(field = "Section")] /// Section of the source pub section: Option, #[deb822(field = "Format")] /// Format of the source pub format: Option, #[deb822(field = "Package-List", deserialize_with = deserialize_package_list, serialize_with = join_lines)] /// Package list of the source pub package_list: Vec, } impl std::str::FromStr for Source { type Err = String; fn from_str(s: &str) -> Result { let para = s .parse::() .map_err(|e| e.to_string())?; FromDeb822Paragraph::from_paragraph(¶) } } impl std::fmt::Display for Source { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { let para: deb822_fast::Paragraph = self.to_paragraph(); write!(f, "{}", para) } } /// A package #[derive(Debug, Clone, PartialEq, Eq, ToDeb822, FromDeb822)] pub struct Package { /// The name of the package #[deb822(field = "Package")] pub name: String, /// The version of the package #[deb822(field = "Version")] pub version: debversion::Version, /// The name and version of the source package, if different from `name` #[deb822(field = "Source")] pub source: Option, /// The architecture of the package #[deb822(field = "Architecture")] pub architecture: String, /// The maintainer of the package #[deb822(field = "Maintainer")] pub maintainer: Option, /// The installed size of the package #[deb822(field = "Installed-Size")] pub installed_size: Option, /// Dependencies #[deb822(field = "Depends")] pub depends: Option, /// Pre-Depends #[deb822(field = "Pre-Depends")] pub pre_depends: Option, /// Recommends #[deb822(field = "Recommends")] pub recommends: Option, /// Suggests #[deb822(field = "Suggests")] pub suggests: Option, /// Enhances #[deb822(field = "Enhances")] pub enhances: Option, /// Breaks #[deb822(field = "Breaks")] pub breaks: Option, /// Conflicts #[deb822(field = "Conflicts")] pub conflicts: Option, /// Provides #[deb822(field = "Provides")] pub provides: Option, /// Replaces #[deb822(field = "Replaces")] pub replaces: Option, /// Built-Using #[deb822(field = "Built-Using")] pub built_using: Option, /// Description #[deb822(field = "Description")] pub description: Option, /// Homepage #[deb822(field = "Homepage")] pub homepage: Option, /// Priority #[deb822(field = "Priority")] pub priority: Option, /// Section #[deb822(field = "Section")] pub section: Option, /// Essential #[deb822(field = "Essential", deserialize_with = deserialize_yesno, serialize_with = serialize_yesno)] pub essential: Option, /// Tag #[deb822(field = "Tag")] pub tag: Option, /// Size #[deb822(field = "Size")] pub size: Option, /// MD5sum #[deb822(field = "MD5sum")] pub md5sum: Option, /// SHA256 #[deb822(field = "SHA256")] pub sha256: Option, /// Description (MD5) #[deb822(field = "Description-MD5")] pub description_md5: Option, } impl std::str::FromStr for Package { type Err = String; fn from_str(s: &str) -> Result { let para = s .parse::() .map_err(|e| e.to_string())?; FromDeb822Paragraph::from_paragraph(¶) } } impl std::fmt::Display for Package { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { let para: deb822_fast::Paragraph = self.to_paragraph(); write!(f, "{}", para) } } #[cfg(test)] mod tests { use super::*; use deb822_fast::Paragraph; use deb822_fast::ToDeb822Paragraph; #[test] fn test_release() { let release = Release { codename: "focal".to_string(), components: vec!["main".to_string(), "restricted".to_string()], architectures: vec!["amd64".to_string(), "arm64".to_string()], description: "Ubuntu 20.04 LTS".to_string(), origin: "Ubuntu".to_string(), label: "Ubuntu".to_string(), suite: "focal".to_string(), version: "20.04".to_string(), date: "Thu, 23 Apr 2020 17:19:19 UTC".to_string(), not_automatic: false, but_automatic_upgrades: true, acquire_by_hash: true, }; let deb822 = r#"Codename: focal Components: main restricted Architectures: amd64 arm64 Description: Ubuntu 20.04 LTS Origin: Ubuntu Label: Ubuntu Suite: focal Version: 20.04 Date: Thu, 23 Apr 2020 17:19:19 UTC NotAutomatic: no ButAutomaticUpgrades: yes Acquire-By-Hash: yes "#; let para = deb822.parse::().unwrap(); let release: deb822_fast::Paragraph = release.to_paragraph(); assert_eq!(release, para); } #[test] fn test_package() { let package = r#"Package: apt Version: 2.1.10 Architecture: amd64 Maintainer: APT Development Team Installed-Size: 3524 Depends: libc6 (>= 2.14), libgcc1 Pre-Depends: dpkg (>= 1.15.6) Recommends: gnupg Suggests: apt-doc, aptitude | synaptic | wajig "#; let package: Package = package.parse().unwrap(); assert_eq!(package.name, "apt"); assert_eq!(package.version, "2.1.10".parse().unwrap()); assert_eq!(package.architecture, "amd64"); } #[test] fn test_package_essential() { let package = r#"Package: base-files Version: 11.1 Architecture: amd64 Essential: yes "#; let package: Package = package.parse().unwrap(); assert_eq!(package.name, "base-files"); assert_eq!(package.essential, Some(true)); } #[test] fn test_package_essential_no() { let package = r#"Package: apt Version: 2.1.10 Architecture: amd64 Essential: no "#; let package: Package = package.parse().unwrap(); assert_eq!(package.name, "apt"); assert_eq!(package.essential, Some(false)); } #[test] fn test_package_with_different_source() { let package = r#"Package: apt Source: not-apt (1.1.5) Version: 2.1.10 Architecture: amd64 Maintainer: APT Development Team Installed-Size: 3524 Depends: libc6 (>= 2.14), libgcc1 Pre-Depends: dpkg (>= 1.15.6) Recommends: gnupg Suggests: apt-doc, aptitude | synaptic | wajig "#; let package: Package = package.parse().unwrap(); assert_eq!(package.name, "apt"); assert_eq!(package.version, "2.1.10".parse().unwrap()); assert_eq!(package.architecture, "amd64"); assert_eq!( package.source, Some(SourceRelation { name: "not-apt".to_string(), version: Some("1.1.5".parse().unwrap()) }) ); } } debian-control-0.2.14/src/lossy/buildinfo.rs000064400000000000000000000113051046102023000170710ustar 00000000000000//! Parser for Debian buildinfo files //! //! The buildinfo file format is a Debian-specific format that is used to store //! information about the build environment of a package. See for //! more information. use crate::lossy::relations::Relations; use deb822_fast::FromDeb822Paragraph; use deb822_fast::{FromDeb822, ToDeb822}; use std::collections::HashMap; use std::path::PathBuf; fn deserialize_env(s: &str) -> Result, String> { let mut env = HashMap::new(); for line in s.lines() { if line.trim().is_empty() { continue; } let (key, value) = match line.split_once("=") { Some((key, value)) => { if value.starts_with('"') && value.ends_with('"') { let value = value[1..value.len() - 1].to_string(); (key, value) } else { (key, value.to_string()) } } None => { // If there is no '=', then the line is invalid return Err("Invalid environment variable".to_string()); } }; env.insert(key.to_string(), value.to_string()); } Ok(env) } fn serialize_env(env: &HashMap) -> String { let mut s = String::new(); for (key, value) in env { s.push_str(&format!("{}={}\n", key, value)); } s } fn deserialize_version(s: &str) -> Result { s.parse().map_err(|e: debversion::ParseError| e.to_string()) } fn serialize_version(version: &debversion::Version) -> String { version.to_string() } fn serialize_pathbuf(path: &std::path::Path) -> String { path.display().to_string() } fn deserialize_pathbuf(s: &str) -> Result { Ok(PathBuf::from(s)) } #[derive(FromDeb822, ToDeb822)] /// The buildinfo file. pub struct Buildinfo { #[deb822(field = "Format")] /// The format of the buildinfo file. format: String, #[deb822(field = "Build-Architecture")] /// The architecture the package is built on. build_architecture: String, #[deb822(field = "Source")] /// The name of the source package. source: String, #[deb822(field = "Binary")] /// Folded list of binary packages built from the source package. binary: Option, #[deb822(field = "Architecture")] /// The architecture the package is built for. architecture: String, #[deb822( field = "Version", deserialize_with = deserialize_version, serialize_with = serialize_version )] /// The version number of a package. version: debversion::Version, #[deb822(field = "Binary-Only-Changes")] binary_only_changes: Option, #[deb822(field = "Checksums-Sha256")] /// The SHA256 checksums of the files in the package. // TODO: Parse properly checksums_sha256: Option, #[deb822(field = "Checksums-Sha1")] /// The SHA1 checksums of the files in the package. // TODO: Parse properly checksums_sha1: Option, #[deb822(field = "Checksums-Md5")] /// The MD5 checksums of the files in the package. // TODO: Parse properly checksums_md5: Option, #[deb822(field = "Build-Origin")] /// The origin of the build. build_origin: Option, #[deb822(field = "Build-Date")] /// The date the package was built. build_date: Option, #[deb822(field = "Build-Tainted-By")] /// The reason the build is tainted. build_tainted_by: Option, #[deb822(field = "Build-Path", deserialize_with = deserialize_pathbuf, serialize_with = serialize_pathbuf)] /// Absolute path of the directory in which the package was built. build_path: Option, #[deb822( field = "Environment", deserialize_with = deserialize_env, serialize_with = serialize_env )] /// Environment variables used during the build. environment: Option>, #[deb822(field = "Installed-Build-Depends")] /// The packages that this package depends on during build. installed_build_depends: Option, } impl std::str::FromStr for Buildinfo { type Err = String; fn from_str(s: &str) -> Result { let para: deb822_fast::Paragraph = s.parse().map_err(|e: deb822_fast::Error| e.to_string())?; Self::from_paragraph(¶) } } #[cfg(test)] mod tests { use super::*; use std::str::FromStr; #[test] fn test_buildinfo() { let input = include_str!("../../testdata/ruff.buildinfo"); let buildinfo = Buildinfo::from_str(input).unwrap(); assert_eq!(buildinfo.format, "1.0"); } } debian-control-0.2.14/src/lossy/control.rs000064400000000000000000000227011046102023000166000ustar 00000000000000use crate::fields::Priority; use crate::lossy::relations::Relations; use deb822_fast::{FromDeb822, ToDeb822}; use deb822_fast::{FromDeb822Paragraph, ToDeb822Paragraph}; fn deserialize_yesno(s: &str) -> Result { match s { "yes" => Ok(true), "no" => Ok(false), _ => Err(format!("invalid value for yesno: {}", s)), } } fn serialize_yesno(b: &bool) -> String { if *b { "yes".to_string() } else { "no".to_string() } } /// The source package. #[derive(FromDeb822, ToDeb822, Default, Clone, PartialEq)] pub struct Source { #[deb822(field = "Source")] /// The name of the source package. pub name: String, #[deb822(field = "Build-Depends")] /// The packages that this package depends on during build. pub build_depends: Option, #[deb822(field = "Build-Depends-Indep")] /// The packages that this package depends on during build. pub build_depends_indep: Option, #[deb822(field = "Build-Depends-Arch")] /// The packages that this package depends on during build. pub build_depends_arch: Option, #[deb822(field = "Build-Conflicts")] /// The packages that this package conflicts with during build. pub build_conflicts: Option, #[deb822(field = "Build-Conflicts-Indep")] /// The packages that this package conflicts with during build. pub build_conflicts_indep: Option, #[deb822(field = "Build-Conflicts-Arch")] /// The packages that this package conflicts with during build. pub build_conflicts_arch: Option, #[deb822(field = "Standards-Version")] /// The version of the Debian Policy Manual that the package complies with. pub standards_version: Option, #[deb822(field = "Homepage")] /// The homepage of the package. pub homepage: Option, #[deb822(field = "Section")] /// The section of the package. pub section: Option, #[deb822(field = "Priority")] /// The priority of the package. pub priority: Option, #[deb822(field = "Maintainer")] /// The maintainer of the package. pub maintainer: Option, #[deb822(field = "Uploaders")] /// The uploaders of the package. pub uploaders: Option, #[deb822(field = "Architecture")] /// The architecture the package is built for. pub architecture: Option, #[deb822(field = "Rules-Requires-Root", deserialize_with = deserialize_yesno, serialize_with = serialize_yesno)] /// Whether the package's build rules require root. pub rules_requires_root: Option, #[deb822(field = "Testsuite")] /// The name of the test suite. pub testsuite: Option, #[deb822(field = "Vcs-Git")] /// The URL of the Git repository. pub vcs_git: Option, #[deb822(field = "Vcs-Browser")] /// The URL to the web interface of the VCS. pub vcs_browser: Option, } impl std::fmt::Display for Source { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { let para: deb822_fast::Paragraph = self.to_paragraph(); write!(f, "{}", para)?; Ok(()) } } /// A binary package. #[derive(FromDeb822, ToDeb822, Default, Clone, PartialEq)] pub struct Binary { #[deb822(field = "Package")] /// The name of the package. pub name: String, #[deb822(field = "Depends")] /// The packages that this package depends on. pub depends: Option, #[deb822(field = "Recommends")] /// The packages that this package recommends. pub recommends: Option, #[deb822(field = "Suggests")] /// The packages that this package suggests. pub suggests: Option, #[deb822(field = "Enhances")] /// The packages that this package enhances. pub enhances: Option, #[deb822(field = "Pre-Depends")] /// The packages that this package depends on before it is installed. pub pre_depends: Option, #[deb822(field = "Breaks")] /// The packages that this package breaks. pub breaks: Option, #[deb822(field = "Conflicts")] /// The packages that this package conflicts with. pub conflicts: Option, #[deb822(field = "Replaces")] /// The packages that this package replaces. pub replaces: Option, #[deb822(field = "Provides")] /// The packages that this package provides. pub provides: Option, #[deb822(field = "Built-Using")] /// The packages that this package is built using. pub built_using: Option, #[deb822(field = "Architecture")] /// The architecture the package is built for. pub architecture: Option, #[deb822(field = "Section")] /// The section of the package. pub section: Option, #[deb822(field = "Priority")] /// The priority of the package. pub priority: Option, #[deb822(field = "Multi-Arch")] /// The multi-arch field. pub multi_arch: Option, #[deb822(field = "Essential", deserialize_with = deserialize_yesno, serialize_with = serialize_yesno)] /// Whether the package is essential. pub essential: Option, #[deb822(field = "Description")] /// The description of the package. The first line is the short description, and the rest is the long description. pub description: Option, } impl std::fmt::Display for Binary { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { let para: deb822_fast::Paragraph = self.to_paragraph(); write!(f, "{}", para)?; Ok(()) } } /// A control file. #[derive(Clone, PartialEq)] pub struct Control { /// The source package. pub source: Source, /// The binary packages. pub binaries: Vec, } impl std::fmt::Display for Control { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "{}", self.source)?; for binary in &self.binaries { f.write_str("\n")?; write!(f, "{}", binary)?; } Ok(()) } } impl std::str::FromStr for Control { type Err = String; fn from_str(s: &str) -> Result { let deb822: deb822_fast::Deb822 = s.parse().map_err(|e| format!("parse error: {}", e))?; let mut source: Option = None; let mut binaries: Vec = Vec::new(); for para in deb822.iter() { if para.get("Package").is_some() { let binary: Binary = Binary::from_paragraph(para)?; binaries.push(binary); } else if para.get("Source").is_some() { if source.is_some() { return Err("more than one source paragraph".to_string()); } source = Some(Source::from_paragraph(para)?); } else { return Err("paragraph without Source or Package field".to_string()); } } Ok(Control { source: source.ok_or_else(|| "no source paragraph".to_string())?, binaries, }) } } impl Default for Control { fn default() -> Self { Self::new() } } impl Control { /// Create a new control file. pub fn new() -> Self { Self { source: Source::default(), binaries: Vec::new(), } } /// Add a new binary package to the control file. pub fn add_binary(&mut self, name: &str) -> &mut Binary { let binary = Binary { name: name.to_string(), ..Default::default() }; self.binaries.push(binary); self.binaries.last_mut().unwrap() } } #[cfg(test)] mod tests { use super::*; use crate::relations::VersionConstraint; #[test] fn test_parse() { let control: Control = r#"Source: foo Section: libs Priority: optional Build-Depends: bar (>= 1.0.0), baz (>= 1.0.0) Homepage: https://example.com "# .parse() .unwrap(); let source = &control.source; assert_eq!(source.name, "foo".to_owned()); assert_eq!(source.section, Some("libs".to_owned())); assert_eq!(source.priority, Some(super::Priority::Optional)); assert_eq!( source.homepage, Some("https://example.com".parse().unwrap()) ); let bd = source.build_depends.as_ref().unwrap(); let mut entries = bd.iter().collect::>(); assert_eq!(entries.len(), 2); let rel = entries[0].pop().unwrap(); assert_eq!(rel.name, "bar"); assert_eq!( rel.version, Some(( VersionConstraint::GreaterThanEqual, "1.0.0".parse().unwrap() )) ); let rel = entries[1].pop().unwrap(); assert_eq!(rel.name, "baz"); assert_eq!( rel.version, Some(( VersionConstraint::GreaterThanEqual, "1.0.0".parse().unwrap() )) ); } #[test] fn test_description() { let control: Control = r#"Source: foo Package: foo Description: this is the short description And the longer one . is on the next lines "# .parse() .unwrap(); let binary = &control.binaries[0]; assert_eq!( binary.description, Some( "this is the short description\nAnd the longer one\n.\nis on the next lines" .to_owned() ) ); } } debian-control-0.2.14/src/lossy/ftpmaster.rs000064400000000000000000000027731046102023000171340ustar 00000000000000//! FTP-master-related files use deb822_fast::{FromDeb822, FromDeb822Paragraph, ToDeb822}; fn serialize_list(list: &Vec) -> String { list.join("\n") } fn deserialize_list(list: &str) -> Result, String> { Ok(list.lines().map(|s| s.to_string()).collect()) } #[derive(Debug, Clone, FromDeb822, ToDeb822)] /// A removal file pub struct Removal { #[deb822(field = "Date")] /// The date of the removal pub date: String, #[deb822(field = "Suite")] /// The suite from which the package was removed pub suite: Option, #[deb822(field = "Ftpmaster")] /// The FTP-master who performed the removal pub ftpmaster: String, #[deb822(field = "Sources", serialize_with = serialize_list, deserialize_with = deserialize_list)] /// The sources that were removed pub sources: Option>, #[deb822(field = "Binaries", serialize_with = serialize_list, deserialize_with = deserialize_list)] /// The binaries that were removed pub binaries: Option>, #[deb822(field = "Reason")] /// The reason for the removal pub reason: String, #[deb822(field = "Bug")] /// The bug number associated with the removal pub bug: Option, } impl std::str::FromStr for Removal { type Err = String; fn from_str(s: &str) -> Result { let paragraph = deb822_fast::Paragraph::from_str(s).map_err(|e| e.to_string())?; Self::from_paragraph(¶graph).map_err(|e| e.to_string()) } } debian-control-0.2.14/src/lossy/mod.rs000064400000000000000000000002441046102023000156750ustar 00000000000000//! Lossy parsing of Debian control files pub mod apt; pub mod buildinfo; mod control; pub use control::*; pub mod ftpmaster; mod relations; pub use relations::*; debian-control-0.2.14/src/lossy/relations.rs000064400000000000000000000637431046102023000171330ustar 00000000000000//! Parser for relationship fields like `Depends`, `Recommends`, etc. //! //! # Example //! ``` //! use debian_control::lossy::{Relations, Relation}; //! use debian_control::relations::VersionConstraint; //! //! let mut relations: Relations = r"python3-dulwich (>= 0.19.0), python3-requests, python3-urllib3 (<< 1.26.0)".parse().unwrap(); //! assert_eq!(relations.to_string(), "python3-dulwich (>= 0.19.0), python3-requests, python3-urllib3 (<< 1.26.0)"); //! assert!(relations.satisfied_by(|name: &str| -> Option { //! match name { //! "python3-dulwich" => Some("0.19.0".parse().unwrap()), //! "python3-requests" => Some("2.25.1".parse().unwrap()), //! "python3-urllib3" => Some("1.25.11".parse().unwrap()), //! _ => None //! }})); //! relations.remove(1); //! relations[0][0].archqual = Some("amd64".to_string()); //! assert_eq!(relations.to_string(), "python3-dulwich:amd64 (>= 0.19.0), python3-urllib3 (<< 1.26.0)"); //! ``` use std::iter::Peekable; use crate::relations::SyntaxKind::*; use crate::relations::{lex, BuildProfile, SyntaxKind, VersionConstraint}; /// A relation entry in a relationship field. #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Relation { /// Package name. pub name: String, /// Architecture qualifier. pub archqual: Option, /// Architectures that this relation is only valid for. pub architectures: Option>, /// Version constraint and version. pub version: Option<(VersionConstraint, debversion::Version)>, /// Build profiles that this relation is only valid for. pub profiles: Vec>, } impl Default for Relation { fn default() -> Self { Self::new() } } impl Relation { /// Create an empty relation. pub fn new() -> Self { Self { name: String::new(), archqual: None, architectures: None, version: None, profiles: Vec::new(), } } /// Build a new relation pub fn build(name: &str) -> RelationBuilder { RelationBuilder::new(name) } /// Check if this entry is satisfied by the given package versions. /// /// # Arguments /// * `package_version` - A function that returns the version of a package. /// /// # Example /// ``` /// use debian_control::lossy::Relation; /// let entry: Relation = "samba (>= 2.0)".parse().unwrap(); /// assert!(entry.satisfied_by(|name: &str| -> Option { /// match name { /// "samba" => Some("2.0".parse().unwrap()), /// _ => None /// }})); /// ``` pub fn satisfied_by(&self, package_version: impl crate::VersionLookup) -> bool { let actual = package_version.lookup_version(self.name.as_str()); if let Some((vc, version)) = &self.version { if let Some(actual) = actual { match vc { VersionConstraint::GreaterThanEqual => actual.as_ref() >= version, VersionConstraint::LessThanEqual => actual.as_ref() <= version, VersionConstraint::Equal => actual.as_ref() == version, VersionConstraint::GreaterThan => actual.as_ref() > version, VersionConstraint::LessThan => actual.as_ref() < version, } } else { false } } else { actual.is_some() } } } /// A builder for a relation entry in a relationship field. pub struct RelationBuilder { name: String, archqual: Option, architectures: Option>, version: Option<(VersionConstraint, debversion::Version)>, profiles: Vec>, } impl RelationBuilder { /// Create a new relation builder. pub fn new(name: &str) -> Self { Self { name: name.to_string(), archqual: None, architectures: None, version: None, profiles: Vec::new(), } } /// Set the architecture qualifier. pub fn archqual(mut self, archqual: &str) -> Self { self.archqual = Some(archqual.to_string()); self } /// Set the architectures that this relation is only valid for. pub fn architectures(mut self, architectures: Vec<&str>) -> Self { self.architectures = Some(architectures.into_iter().map(|s| s.to_string()).collect()); self } /// Set the version constraint and version. pub fn version(mut self, constraint: VersionConstraint, version: &str) -> Self { self.version = Some((constraint, version.parse().unwrap())); self } /// Add a build profile that this relation is only valid for. pub fn profile(mut self, profile: Vec) -> Self { self.profiles.push(profile); self } /// Build the relation. pub fn build(self) -> Relation { Relation { name: self.name, archqual: self.archqual, architectures: self.architectures, version: self.version, profiles: self.profiles, } } } impl std::fmt::Display for Relation { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "{}", self.name)?; if let Some(archqual) = &self.archqual { write!(f, ":{}", archqual)?; } if let Some((constraint, version)) = &self.version { write!(f, " ({} {})", constraint, version)?; } if let Some(archs) = &self.architectures { write!(f, " [{}]", archs.join(" "))?; } for profile in &self.profiles { write!(f, " <")?; for (i, profile) in profile.iter().enumerate() { if i > 0 { write!(f, ", ")?; } write!(f, "{}", profile)?; } write!(f, ">")?; } Ok(()) } } #[cfg(feature = "serde")] impl<'de> serde::Deserialize<'de> for Relation { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { let s = String::deserialize(deserializer)?; s.parse().map_err(serde::de::Error::custom) } } #[cfg(feature = "serde")] impl serde::Serialize for Relation { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { self.to_string().serialize(serializer) } } /// A collection of relation entries in a relationship field. #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Relations(pub Vec>); impl std::ops::Index for Relations { type Output = Vec; fn index(&self, index: usize) -> &Self::Output { &self.0[index] } } impl std::ops::IndexMut for Relations { fn index_mut(&mut self, index: usize) -> &mut Self::Output { &mut self.0[index] } } impl FromIterator> for Relations { fn from_iter>>(iter: I) -> Self { Self(iter.into_iter().collect()) } } impl Default for Relations { fn default() -> Self { Self::new() } } impl Relations { /// Create an empty relations. pub fn new() -> Self { Self(Vec::new()) } /// Remove an entry from the relations. pub fn remove(&mut self, index: usize) { self.0.remove(index); } /// Iterate over the entries in the relations. pub fn iter(&self) -> impl Iterator> { self.0.iter().map(|entry| entry.iter().collect()) } /// Number of entries in the relations. pub fn len(&self) -> usize { self.0.len() } /// Check if the relations are empty. pub fn is_empty(&self) -> bool { self.0.is_empty() } /// Check if the relations are satisfied by the given package versions. pub fn satisfied_by(&self, package_version: impl crate::VersionLookup + Copy) -> bool { self.0 .iter() .all(|e| e.iter().any(|r| r.satisfied_by(package_version))) } } impl FromIterator for Relations { fn from_iter>(iter: I) -> Self { Self(iter.into_iter().map(|r| vec![r]).collect()) } } impl std::fmt::Display for Relations { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { for (i, entry) in self.0.iter().enumerate() { if i > 0 { f.write_str(", ")?; } for (j, relation) in entry.iter().enumerate() { if j > 0 { f.write_str(" | ")?; } write!(f, "{}", relation)?; } } Ok(()) } } impl std::str::FromStr for Relation { type Err = String; fn from_str(s: &str) -> Result { let tokens = lex(s); let mut tokens = tokens.into_iter().peekable(); fn eat_whitespace(tokens: &mut Peekable>) { while let Some((WHITESPACE, _)) = tokens.peek() { tokens.next(); } } let name = match tokens.next() { Some((IDENT, name)) => name, _ => return Err("Expected package name".to_string()), }; eat_whitespace(&mut tokens); let archqual = if let Some((COLON, _)) = tokens.peek() { tokens.next(); match tokens.next() { Some((IDENT, s)) => Some(s), _ => return Err("Expected architecture qualifier".to_string()), } } else { None }; eat_whitespace(&mut tokens); let version = if let Some((L_PARENS, _)) = tokens.peek() { tokens.next(); eat_whitespace(&mut tokens); let mut constraint = String::new(); while let Some((kind, t)) = tokens.peek() { match kind { EQUAL | L_ANGLE | R_ANGLE => { constraint.push_str(t); tokens.next(); } _ => break, } } let constraint = constraint.parse()?; eat_whitespace(&mut tokens); // Read IDENT and COLON tokens until we see R_PARENS let mut version_string = String::new(); while let Some((kind, s)) = tokens.peek() { match kind { R_PARENS => break, IDENT | COLON => version_string.push_str(s), n => return Err(format!("Unexpected token: {:?}", n)), } tokens.next(); } let version = version_string .parse() .map_err(|e: debversion::ParseError| e.to_string())?; eat_whitespace(&mut tokens); if let Some((R_PARENS, _)) = tokens.next() { } else { return Err(format!("Expected ')', found {:?}", tokens.next())); } Some((constraint, version)) } else { None }; eat_whitespace(&mut tokens); let architectures = if let Some((L_BRACKET, _)) = tokens.peek() { tokens.next(); let mut archs = Vec::new(); loop { match tokens.next() { Some((IDENT, s)) => archs.push(s), Some((WHITESPACE, _)) => {} Some((R_BRACKET, _)) => break, _ => return Err("Expected architecture name".to_string()), } } Some(archs) } else { None }; eat_whitespace(&mut tokens); let mut profiles = Vec::new(); while let Some((L_ANGLE, _)) = tokens.peek() { tokens.next(); loop { let mut profile = Vec::new(); loop { match tokens.next() { Some((NOT, _)) => { let profile_name = match tokens.next() { Some((IDENT, s)) => s, _ => return Err("Expected profile name".to_string()), }; profile.push(BuildProfile::Disabled(profile_name)); } Some((IDENT, s)) => profile.push(BuildProfile::Enabled(s)), Some((WHITESPACE, _)) => {} _ => return Err("Expected profile name".to_string()), } if let Some((COMMA, _)) = tokens.peek() { tokens.next(); } else { break; } } profiles.push(profile); if let Some((R_ANGLE, _)) = tokens.next() { eat_whitespace(&mut tokens); break; } } } eat_whitespace(&mut tokens); if let Some((kind, _)) = tokens.next() { return Err(format!("Unexpected token: {:?}", kind)); } Ok(Relation { name, archqual, architectures, version, profiles, }) } } impl std::str::FromStr for Relations { type Err = String; fn from_str(s: &str) -> Result { let mut relations = Vec::new(); if s.is_empty() { return Ok(Relations(relations)); } for entry in s.split(',') { let entry = entry.trim(); if entry.is_empty() { // Ignore empty entries. continue; } let entry_relations = entry.split('|').map(|relation| { let relation = relation.trim(); if relation.is_empty() { return Err("Empty relation".to_string()); } relation.parse() }); relations.push(entry_relations.collect::, _>>()?); } Ok(Relations(relations)) } } #[cfg(feature = "serde")] impl<'de> serde::Deserialize<'de> for Relations { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { let s = String::deserialize(deserializer)?; s.parse().map_err(serde::de::Error::custom) } } #[cfg(feature = "serde")] impl serde::Serialize for Relations { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { self.to_string().serialize(serializer) } } /// An Package to Source relation #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct SourceRelation { /// Source package name pub name: String, /// Source package version (if different from binary package one) pub version: Option, } impl std::fmt::Display for SourceRelation { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "{name}", name = self.name)?; if let Some(ref version) = self.version { write!(f, " (= {version})")?; } Ok(()) } } impl std::str::FromStr for SourceRelation { type Err = String; fn from_str(s: &str) -> Result { let tokens = lex(s); let mut tokens = tokens.into_iter().peekable(); fn eat_whitespace(tokens: &mut Peekable>) { while let Some((WHITESPACE, _)) = tokens.peek() { tokens.next(); } } let name = match tokens.next() { Some((IDENT, name)) => name, _ => return Err("Expected package name".to_string()), }; eat_whitespace(&mut tokens); match tokens.next() { None => { return Ok(Self { name, version: None, }); } Some((L_PARENS, _)) => {} _ => return Err("Unexpected token after package name".to_string()), }; let mut version_str = "".to_string(); for (token, value) in tokens.by_ref() { if token != R_PARENS { version_str.push_str(&value); } else { break; } } let version: debversion::Version = version_str .parse() .map_err(|err| format!("Failed to parse version: {err}"))?; eat_whitespace(&mut tokens); if tokens.next().is_some() { return Err("Unexpected tokens after version".to_string()); } Ok(Self { name, version: Some(version), }) } } #[cfg(feature = "serde")] impl<'de> serde::Deserialize<'de> for SourceRelation { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { let s = String::deserialize(deserializer)?; s.parse().map_err(serde::de::Error::custom) } } #[cfg(feature = "serde")] impl serde::Serialize for SourceRelation { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { self.to_string().serialize(serializer) } } #[cfg(test)] mod tests { use super::*; #[test] fn test_parse() { let input = "python3-dulwich"; let parsed: Relations = input.parse().unwrap(); assert_eq!(parsed.to_string(), input); assert_eq!(parsed.len(), 1); let entry = &parsed[0]; assert_eq!(entry.len(), 1); let relation = &entry[0]; assert_eq!(relation.to_string(), "python3-dulwich"); assert_eq!(relation.version, None); let input = "python3-dulwich (>= 0.20.21)"; let parsed: Relations = input.parse().unwrap(); assert_eq!(parsed.to_string(), input); assert_eq!(parsed.len(), 1); let entry = &parsed[0]; assert_eq!(entry.len(), 1); let relation = &entry[0]; assert_eq!(relation.to_string(), "python3-dulwich (>= 0.20.21)"); assert_eq!( relation.version, Some(( VersionConstraint::GreaterThanEqual, "0.20.21".parse().unwrap() )) ); } #[test] fn test_multiple() { let input = "python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)"; let parsed: Relations = input.parse().unwrap(); assert_eq!(parsed.to_string(), input); assert_eq!(parsed.len(), 2); let entry = &parsed[0]; assert_eq!(entry.len(), 1); let relation = &entry[0]; assert_eq!(relation.to_string(), "python3-dulwich (>= 0.20.21)"); assert_eq!( relation.version, Some(( VersionConstraint::GreaterThanEqual, "0.20.21".parse().unwrap() )) ); let entry = &parsed[1]; assert_eq!(entry.len(), 1); let relation = &entry[0]; assert_eq!(relation.to_string(), "python3-dulwich (<< 0.21)"); assert_eq!( relation.version, Some((VersionConstraint::LessThan, "0.21".parse().unwrap())) ); } #[test] fn test_architectures() { let input = "python3-dulwich [amd64 arm64 armhf i386 mips mips64el mipsel ppc64el s390x]"; let parsed: Relations = input.parse().unwrap(); assert_eq!(parsed.to_string(), input); assert_eq!(parsed.len(), 1); let entry = &parsed[0]; assert_eq!( entry[0].to_string(), "python3-dulwich [amd64 arm64 armhf i386 mips mips64el mipsel ppc64el s390x]" ); assert_eq!(entry.len(), 1); let relation = &entry[0]; assert_eq!( relation.to_string(), "python3-dulwich [amd64 arm64 armhf i386 mips mips64el mipsel ppc64el s390x]" ); assert_eq!(relation.version, None); assert_eq!( relation.architectures.as_ref().unwrap(), &vec![ "amd64", "arm64", "armhf", "i386", "mips", "mips64el", "mipsel", "ppc64el", "s390x" ] .into_iter() .map(|s| s.to_string()) .collect::>() ); } #[test] fn test_profiles() { let input = "foo (>= 1.0) [i386 arm] , bar"; let parsed: Relations = input.parse().unwrap(); assert_eq!(parsed.to_string(), input); assert_eq!(parsed.iter().count(), 2); let entry = parsed.iter().next().unwrap(); assert_eq!( entry[0].to_string(), "foo (>= 1.0) [i386 arm] " ); assert_eq!(entry.len(), 1); let relation = entry[0]; assert_eq!( relation.to_string(), "foo (>= 1.0) [i386 arm] " ); assert_eq!( relation.version, Some((VersionConstraint::GreaterThanEqual, "1.0".parse().unwrap())) ); assert_eq!( relation.architectures.as_ref().unwrap(), &["i386", "arm"] .into_iter() .map(|s| s.to_string()) .collect::>() ); assert_eq!( relation.profiles, vec![ vec![BuildProfile::Disabled("nocheck".to_string())], vec![BuildProfile::Disabled("cross".to_string())] ] ); } #[cfg(feature = "serde")] #[test] fn test_serde_relations() { let input = "python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)"; let parsed: Relations = input.parse().unwrap(); let serialized = serde_json::to_string(&parsed).unwrap(); assert_eq!( serialized, r#""python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)""# ); let deserialized: Relations = serde_json::from_str(&serialized).unwrap(); assert_eq!(deserialized, parsed); } #[cfg(feature = "serde")] #[test] fn test_serde_relation() { let input = "python3-dulwich (>= 0.20.21)"; let parsed: Relation = input.parse().unwrap(); let serialized = serde_json::to_string(&parsed).unwrap(); assert_eq!(serialized, r#""python3-dulwich (>= 0.20.21)""#); let deserialized: Relation = serde_json::from_str(&serialized).unwrap(); assert_eq!(deserialized, parsed); } #[test] fn test_relations_is_empty() { let input = "python3-dulwich (>= 0.20.21)"; let parsed: Relations = input.parse().unwrap(); assert!(!parsed.is_empty()); let input = ""; let parsed: Relations = input.parse().unwrap(); assert!(parsed.is_empty()); } #[test] fn test_relations_len() { let input = "python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)"; let parsed: Relations = input.parse().unwrap(); assert_eq!(parsed.len(), 2); } #[test] fn test_relations_remove() { let input = "python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)"; let mut parsed: Relations = input.parse().unwrap(); parsed.remove(1); assert_eq!(parsed.len(), 1); assert_eq!(parsed.to_string(), "python3-dulwich (>= 0.20.21)"); } #[test] fn test_relations_satisfied_by() { let input = "python3-dulwich (>= 0.20.21), python3-dulwich (<< 0.21)"; let parsed: Relations = input.parse().unwrap(); assert!( parsed.satisfied_by(|name: &str| -> Option { match name { "python3-dulwich" => Some("0.20.21".parse().unwrap()), _ => None, } }) ); assert!( !parsed.satisfied_by(|name: &str| -> Option { match name { "python3-dulwich" => Some("0.21".parse().unwrap()), _ => None, } }) ); } #[test] fn test_relation_satisfied_by() { let input = "python3-dulwich (>= 0.20.21)"; let parsed: Relation = input.parse().unwrap(); assert!( parsed.satisfied_by(|name: &str| -> Option { match name { "python3-dulwich" => Some("0.20.21".parse().unwrap()), _ => None, } }) ); assert!( !parsed.satisfied_by(|name: &str| -> Option { match name { "python3-dulwich" => Some("0.20.20".parse().unwrap()), _ => None, } }) ); } #[test] fn test_relations_from_iter() { let relation1 = Relation::build("python3-dulwich") .version(VersionConstraint::GreaterThanEqual, "0.19.0") .build(); let relation2 = Relation::build("python3-requests").build(); let relations: Relations = vec![relation1, relation2].into_iter().collect(); assert_eq!( relations.to_string(), "python3-dulwich (>= 0.19.0), python3-requests" ); } #[test] fn test_source_relation_without_version() { let input = "some-package"; let parsed: SourceRelation = input.parse().unwrap(); assert_eq!(parsed.name, input); assert!(parsed.version.is_none()); } #[test] fn test_source_relation_with_valid_version() { let input = "some-package (1.2.3+dfsg1-5~bpo13+1)"; let parsed: SourceRelation = input.parse().unwrap(); assert_eq!(parsed.name, "some-package"); let expected_version = debversion::Version { epoch: None, upstream_version: "1.2.3+dfsg1".to_string(), debian_revision: Some("5~bpo13+1".to_string()), }; assert_eq!(parsed.version, Some(expected_version)); } #[test] fn test_source_relation_with_invalid_version() { let input = "some-package (1.2_@!.3+dfsg1-5~bpo13+1)"; let attempted_parse: Result = input.parse(); assert!(attempted_parse.is_err()) } } debian-control-0.2.14/src/pgp.rs000064400000000000000000000165521046102023000145440ustar 00000000000000//! PGP signature parsing. /// Error during PGP signature parsing. #[derive(Debug, PartialEq, Eq)] pub enum Error { /// Missing PGP signature. MissingPgpSignature, /// Payload missing in the signed message. MissingPayload, /// Truncated PGP signature. TruncatedPgpSignature, /// Junk after PGP signature. JunkAfterPgpSignature, } impl std::fmt::Display for Error { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { match self { Error::MissingPgpSignature => write!(f, "missing PGP signature"), Error::TruncatedPgpSignature => write!(f, "truncated PGP signature"), Error::JunkAfterPgpSignature => write!(f, "junk after PGP signature"), Error::MissingPayload => write!(f, "missing payload"), } } } impl std::error::Error for Error {} /// Strip a PGP signature from a signed message. /// /// This function takes a signed message and returns the payload and the PGP signature. /// If the input is not a signed message, the function returns the input as the payload and `None` /// as the signature. /// /// # Arguments /// * `input` - The signed message. /// /// # Errors /// This function returns an error if the input is a signed message but the payload, PGP signature, /// or the PGP signature metadata is missing, or if there is junk after the PGP signature. /// The error indicates the reason for the failure. /// /// # Returns /// A tuple containing the payload and the PGP signature, if present. /// /// # Examples /// ``` /// let input = "-----BEGIN PGP SIGNED MESSAGE----- /// Hash: SHA256 /// /// Hello, world! /// -----BEGIN PGP SIGNATURE----- /// iQIzBAEBCAAdFiEEpyNohvPMyq0Uiif4DphATThvodkFAmbJ6swACgkQDphATThv /// odkUiw//VDVOwHGRVxpvyIjSvH0AMQmANOvolJ5EoCu1I5UG2x98UPiMV5oTNv1r /// ... /// =olY7 /// -----END PGP SIGNATURE----- /// "; /// let (output, signature) = debian_control::pgp::strip_pgp_signature(input).unwrap(); /// assert_eq!(output, "Hello, world!\n"); /// assert_eq!(signature.unwrap().len(), 136); /// ``` pub fn strip_pgp_signature(input: &str) -> Result<(String, Option), Error> { let mut lines = input.lines(); let first_line = if let Some(line) = lines.next() { line } else { return Ok((input.to_string(), None)); }; if first_line != "-----BEGIN PGP SIGNED MESSAGE-----" { return Ok((input.to_string(), None)); } // Read the metadata let mut metadata = String::new(); loop { let line = lines.next().ok_or(Error::MissingPayload)?; if line.is_empty() { break; } use std::fmt::Write; writeln!(&mut metadata, "{}", line).unwrap(); } let mut payload = String::new(); loop { let line = lines.next().ok_or(Error::MissingPgpSignature)?; if line == "-----BEGIN PGP SIGNATURE-----" { break; } use std::fmt::Write; writeln!(&mut payload, "{}", line).unwrap(); } let mut signature = String::new(); loop { let line = lines.next().ok_or(Error::TruncatedPgpSignature)?; if line == "-----END PGP SIGNATURE-----" { break; } signature.push_str(line); } if let Some(_line) = lines.next() { return Err(Error::JunkAfterPgpSignature); } Ok((payload, Some(signature))) } #[cfg(test)] mod tests { #[test] fn test_strip_pgp_wrapper() { let input = include_str!("testdata/InRelease"); let (output, signature) = super::strip_pgp_signature(input).unwrap(); assert_eq!( output, r###"Origin: Debian Label: Debian Suite: experimental Codename: rc-buggy Changelogs: https://metadata.ftp-master.debian.org/changelogs/@CHANGEPATH@_changelog Date: Sat, 24 Aug 2024 14:13:49 UTC Valid-Until: Sat, 31 Aug 2024 14:13:49 UTC NotAutomatic: yes Acquire-By-Hash: yes No-Support-for-Architecture-all: Packages Architectures: all amd64 arm64 armel armhf i386 mips64el ppc64el riscv64 s390x Components: main contrib non-free-firmware non-free Description: Experimental packages - not released; use at your own risk. "### ); assert_eq!( signature.as_deref(), Some( r###"iQIzBAEBCAAdFiEEpyNohvPMyq0Uiif4DphATThvodkFAmbJ6swACgkQDphATThv odkUiw//VDVOwHGRVxpvyIjSvH0AMQmANOvolJ5EoCu1I5UG2x98UPiMV5oTNv1r B79A3nb+FL2toeuHUJBN3G1WNg6xeH0vD43hGcxhCgVn6NADogv8pBEpyynn1qC0 iketp6kEiHvGMpEj4JqOUEcq2Mafq2TTf9zEqYuTr8NqL9hC/pG8YqPKT3rhPdc3 /D4/0dTT7L+wqLgVTjjNFNcmKU1ywvaWLF5b0VktZ1W6xIqnZYfHyP0iMqolrGqF +NG+igpsMuLI6JtoqoE+yKtWlaQi7pY7VB+OFroywNxEobzPgwdqz0r8pdJq8S4V CMXJqoY18KHdVd4qbU9yGr6qkqopHdMMcpvV9X1UG5xDKUb2OrdbBYttKFGuIuuM S6ZzM+26bztVXLzSra/w7gn7Qm1GluT+EncYrleAAgUvruCRrLFptDpHMAuKWKs5 OyNLh1ZUe/TrkmYGhehsVEBNmG+/HnzS7VKzLfpANHLAXthoEF9Lzqe0lPETa9NZ rSF/EfQwh8omsaBDfighU46fZJwKGSWOIz69jXrQ6YV9hBI/frUDHQUkMLBwjnVo 8hvr0s6/8hHRwNlLRW3XQuwL+wiz0qyk6u6RRudglqSyN1FwIAtTsGkERWN82au2 DY6KLpnfN7/0bIueDUWCP40Dib+eW5Y0/Z536WhNbp8C/OIKeVyJAjMEAQEIAB0W IQRMtQGQIHtHWKP3Onlu0Oe4JkPhMQUCZsnq6QAKCRBu0Oe4JkPhMUJsD/0ZTGIM oI9bzhP6NadhiNNruxLQfq/+fVx/oJbyOJy4IaYPOE0JVeqzZv/wFL/XOVXw6Gg2 V/SHe0cT+iuwdKd+8oMEYaOHQUeU8RhAguypeTdizZef3YjIL+2n4v0mLeq/jMHO a6Hyd09eUQrHedmcgViwQYOX/9/oqls0j3OGtyx1gmpIsmCxJtqsWNsXEcBLaNlm xSAp5YYa5USenFAph4VlR2sG+VdJrG/wtCj8TuDtJCA4tOML3JB5zwgnzfpLMVU6 l+WFKkSzl0f/dlMUYRtRoU9ccpWpyajMs968QsOp0lKLZ5Kq98fSXqOzKriDimpv 4WSmlLRptRgKL0J/Nc1eYRVEPnu+tBsitLdip52SLrqYcbCOErtxOLMIIbbC2HiR Q0lYYgky2TwO8bbCWhTyQIznldnSRhNE1STf5bctphNeWQE6zRFmMGyHh9pQYVNF KkmCbzHcv6EbUOp7Q7c5D/mijN8On/h9TEYU6EbbrQ1AEc+IulXukzlaLCMKJ0Tx XqsogWqW/nbOxTdudMn+qjd7gVsLtNIDKA42Csyac5Hwl9YDqgicyOMGBY88gocV 8fDXnyUhX5Es35AgO25Sh8CbISC29479o4/MdZXCGMIJEocjPx46Dy+hP1sIcFyp KYQwHDLf3TLHWF9z0lvGFYSAq1H8gOwchDISGA== =olY7 "### .replace('\n', "") .as_ref() ) ); } #[test] fn test_strip_pgp_no_pgp_signature() { let input = "Hello, world!"; let (output, signature) = super::strip_pgp_signature(input).unwrap(); assert_eq!(output, input); assert_eq!(signature, None); } #[test] fn test_strip_pgp_missing_payload() { let input = r###"-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA256 "###; let err = super::strip_pgp_signature(input).unwrap_err(); assert_eq!(err, super::Error::MissingPayload); } #[test] fn test_strip_pgp_missing_pgp_signature() { let input = r###"-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA256 Hello, world! "###; let err = super::strip_pgp_signature(input).unwrap_err(); assert_eq!(err, super::Error::MissingPgpSignature); } #[test] fn test_strip_pgp_truncated_pgp_signature() { let input = r###"-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA256 Hello, world! -----BEGIN PGP SIGNATURE----- B79A3nb+FL2toeuHUJBN3G1WNg6xeH0vD43hGcxhCgVn6NADogv8pBEpyynn1qC0 "###; let err = super::strip_pgp_signature(input).unwrap_err(); assert_eq!(err, super::Error::TruncatedPgpSignature); } #[test] fn test_strip_pgp_junk_after_pgp_signature() { let input = r###"-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA256 Hello, world! -----BEGIN PGP SIGNATURE----- B79A3nb+FL2toeuHUJBN3G1WNg6xeH0vD43hGcxhCgVn6NADogv8pBEpyynn1qC0 -----END PGP SIGNATURE----- Junk after PGP signature "###; let err = super::strip_pgp_signature(input).unwrap_err(); assert_eq!(err, super::Error::JunkAfterPgpSignature); } } debian-control-0.2.14/src/relations.rs000064400000000000000000000165161046102023000157560ustar 00000000000000//! Parsing of Debian relations strings. use std::iter::Peekable; use std::str::Chars; /// Build profile for a package. #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum BuildProfile { /// A build profile that is enabled. Enabled(String), /// A build profile that is disabled. Disabled(String), } impl std::fmt::Display for BuildProfile { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { match self { BuildProfile::Enabled(s) => f.write_str(s), BuildProfile::Disabled(s) => write!(f, "!{}", s), } } } impl std::str::FromStr for BuildProfile { type Err = String; fn from_str(s: &str) -> Result { if let Some(s) = s.strip_prefix('!') { Ok(BuildProfile::Disabled(s.to_string())) } else { Ok(BuildProfile::Enabled(s.to_string())) } } } /// Constraint on a Debian package version. #[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] pub enum VersionConstraint { /// << LessThan, // << /// <= LessThanEqual, // <= /// = Equal, // = /// >> GreaterThan, // >> /// >= GreaterThanEqual, // >= } impl std::str::FromStr for VersionConstraint { type Err = String; fn from_str(s: &str) -> Result { match s { ">=" => Ok(VersionConstraint::GreaterThanEqual), "<=" => Ok(VersionConstraint::LessThanEqual), "=" => Ok(VersionConstraint::Equal), ">>" => Ok(VersionConstraint::GreaterThan), "<<" => Ok(VersionConstraint::LessThan), _ => Err(format!("Invalid version constraint: {}", s)), } } } impl std::fmt::Display for VersionConstraint { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { match self { VersionConstraint::GreaterThanEqual => f.write_str(">="), VersionConstraint::LessThanEqual => f.write_str("<="), VersionConstraint::Equal => f.write_str("="), VersionConstraint::GreaterThan => f.write_str(">>"), VersionConstraint::LessThan => f.write_str("<<"), } } } /// Let's start with defining all kinds of tokens and /// composite nodes. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] #[allow(non_camel_case_types)] #[repr(u16)] #[allow(missing_docs)] pub enum SyntaxKind { IDENT = 0, // package name COLON, // : PIPE, COMMA, // , L_PARENS, // ( R_PARENS, // ) L_BRACKET, // [ R_BRACKET, // ] NOT, // ! L_ANGLE, // < R_ANGLE, // > EQUAL, // = WHITESPACE, // whitespace NEWLINE, // newline DOLLAR, // $ L_CURLY, R_CURLY, ERROR, // as well as errors // composite nodes ROOT, // The entire file ENTRY, // A single entry RELATION, // An alternative in a dependency ARCHQUAL, // An architecture qualifier VERSION, // A version constraint CONSTRAINT, // (">=", "<=", "=", ">>", "<<") ARCHITECTURES, PROFILES, SUBSTVAR, } /// Convert our `SyntaxKind` into the rowan `SyntaxKind`. #[cfg(feature = "lossless")] impl From for rowan::SyntaxKind { fn from(kind: SyntaxKind) -> Self { Self(kind as u16) } } /// A lexer for relations strings. pub struct Lexer<'a> { input: Peekable>, } impl<'a> Lexer<'a> { /// Create a new lexer for the given input. pub fn new(input: &'a str) -> Self { Lexer { input: input.chars().peekable(), } } fn is_whitespace(c: char) -> bool { c == ' ' || c == '\t' || c == '\r' } fn is_valid_ident_char(c: char) -> bool { c.is_ascii_alphanumeric() || c == '-' || c == '.' || c == '+' || c == '~' } fn read_while(&mut self, predicate: F) -> String where F: Fn(char) -> bool, { let mut result = String::new(); while let Some(&c) = self.input.peek() { if predicate(c) { result.push(c); self.input.next(); } else { break; } } result } fn next_token(&mut self) -> Option<(SyntaxKind, String)> { if let Some(&c) = self.input.peek() { match c { ':' => { self.input.next(); Some((SyntaxKind::COLON, c.to_string())) } '|' => { self.input.next(); Some((SyntaxKind::PIPE, c.to_string())) } ',' => { self.input.next(); Some((SyntaxKind::COMMA, c.to_string())) } '(' => { self.input.next(); Some((SyntaxKind::L_PARENS, c.to_string())) } ')' => { self.input.next(); Some((SyntaxKind::R_PARENS, c.to_string())) } '[' => { self.input.next(); Some((SyntaxKind::L_BRACKET, c.to_string())) } ']' => { self.input.next(); Some((SyntaxKind::R_BRACKET, c.to_string())) } '!' => { self.input.next(); Some((SyntaxKind::NOT, c.to_string())) } '$' => { self.input.next(); Some((SyntaxKind::DOLLAR, c.to_string())) } '{' => { self.input.next(); Some((SyntaxKind::L_CURLY, c.to_string())) } '}' => { self.input.next(); Some((SyntaxKind::R_CURLY, c.to_string())) } '<' => { self.input.next(); Some((SyntaxKind::L_ANGLE, c.to_string())) } '>' => { self.input.next(); Some((SyntaxKind::R_ANGLE, c.to_string())) } '=' => { self.input.next(); Some((SyntaxKind::EQUAL, c.to_string())) } '\n' => { self.input.next(); Some((SyntaxKind::NEWLINE, c.to_string())) } _ if Self::is_whitespace(c) => { let whitespace = self.read_while(Self::is_whitespace); Some((SyntaxKind::WHITESPACE, whitespace)) } // TODO: separate handling for package names and versions? _ if Self::is_valid_ident_char(c) => { let key = self.read_while(Self::is_valid_ident_char); Some((SyntaxKind::IDENT, key)) } _ => { self.input.next(); Some((SyntaxKind::ERROR, c.to_string())) } } } else { None } } } impl Iterator for Lexer<'_> { type Item = (SyntaxKind, String); fn next(&mut self) -> Option { self.next_token() } } pub(crate) fn lex(input: &str) -> Vec<(SyntaxKind, String)> { let mut lexer = Lexer::new(input); lexer.by_ref().collect::>() } debian-control-0.2.14/src/testdata/InRelease000064400000000000000000000041671046102023000170120ustar 00000000000000-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA256 Origin: Debian Label: Debian Suite: experimental Codename: rc-buggy Changelogs: https://metadata.ftp-master.debian.org/changelogs/@CHANGEPATH@_changelog Date: Sat, 24 Aug 2024 14:13:49 UTC Valid-Until: Sat, 31 Aug 2024 14:13:49 UTC NotAutomatic: yes Acquire-By-Hash: yes No-Support-for-Architecture-all: Packages Architectures: all amd64 arm64 armel armhf i386 mips64el ppc64el riscv64 s390x Components: main contrib non-free-firmware non-free Description: Experimental packages - not released; use at your own risk. -----BEGIN PGP SIGNATURE----- iQIzBAEBCAAdFiEEpyNohvPMyq0Uiif4DphATThvodkFAmbJ6swACgkQDphATThv odkUiw//VDVOwHGRVxpvyIjSvH0AMQmANOvolJ5EoCu1I5UG2x98UPiMV5oTNv1r B79A3nb+FL2toeuHUJBN3G1WNg6xeH0vD43hGcxhCgVn6NADogv8pBEpyynn1qC0 iketp6kEiHvGMpEj4JqOUEcq2Mafq2TTf9zEqYuTr8NqL9hC/pG8YqPKT3rhPdc3 /D4/0dTT7L+wqLgVTjjNFNcmKU1ywvaWLF5b0VktZ1W6xIqnZYfHyP0iMqolrGqF +NG+igpsMuLI6JtoqoE+yKtWlaQi7pY7VB+OFroywNxEobzPgwdqz0r8pdJq8S4V CMXJqoY18KHdVd4qbU9yGr6qkqopHdMMcpvV9X1UG5xDKUb2OrdbBYttKFGuIuuM S6ZzM+26bztVXLzSra/w7gn7Qm1GluT+EncYrleAAgUvruCRrLFptDpHMAuKWKs5 OyNLh1ZUe/TrkmYGhehsVEBNmG+/HnzS7VKzLfpANHLAXthoEF9Lzqe0lPETa9NZ rSF/EfQwh8omsaBDfighU46fZJwKGSWOIz69jXrQ6YV9hBI/frUDHQUkMLBwjnVo 8hvr0s6/8hHRwNlLRW3XQuwL+wiz0qyk6u6RRudglqSyN1FwIAtTsGkERWN82au2 DY6KLpnfN7/0bIueDUWCP40Dib+eW5Y0/Z536WhNbp8C/OIKeVyJAjMEAQEIAB0W IQRMtQGQIHtHWKP3Onlu0Oe4JkPhMQUCZsnq6QAKCRBu0Oe4JkPhMUJsD/0ZTGIM oI9bzhP6NadhiNNruxLQfq/+fVx/oJbyOJy4IaYPOE0JVeqzZv/wFL/XOVXw6Gg2 V/SHe0cT+iuwdKd+8oMEYaOHQUeU8RhAguypeTdizZef3YjIL+2n4v0mLeq/jMHO a6Hyd09eUQrHedmcgViwQYOX/9/oqls0j3OGtyx1gmpIsmCxJtqsWNsXEcBLaNlm xSAp5YYa5USenFAph4VlR2sG+VdJrG/wtCj8TuDtJCA4tOML3JB5zwgnzfpLMVU6 l+WFKkSzl0f/dlMUYRtRoU9ccpWpyajMs968QsOp0lKLZ5Kq98fSXqOzKriDimpv 4WSmlLRptRgKL0J/Nc1eYRVEPnu+tBsitLdip52SLrqYcbCOErtxOLMIIbbC2HiR Q0lYYgky2TwO8bbCWhTyQIznldnSRhNE1STf5bctphNeWQE6zRFmMGyHh9pQYVNF KkmCbzHcv6EbUOp7Q7c5D/mijN8On/h9TEYU6EbbrQ1AEc+IulXukzlaLCMKJ0Tx XqsogWqW/nbOxTdudMn+qjd7gVsLtNIDKA42Csyac5Hwl9YDqgicyOMGBY88gocV 8fDXnyUhX5Es35AgO25Sh8CbISC29479o4/MdZXCGMIJEocjPx46Dy+hP1sIcFyp KYQwHDLf3TLHWF9z0lvGFYSAq1H8gOwchDISGA== =olY7 -----END PGP SIGNATURE----- debian-control-0.2.14/src/testdata/Release000064400000000000000000002406771046102023000165330ustar 00000000000000Origin: Debian Label: Debian Suite: testing Codename: trixie Changelogs: https://metadata.ftp-master.debian.org/changelogs/@CHANGEPATH@_changelog Date: Sat, 24 Aug 2024 14:13:49 UTC Valid-Until: Sat, 31 Aug 2024 14:13:49 UTC Acquire-By-Hash: yes No-Support-for-Architecture-all: Packages Architectures: all amd64 arm64 armel armhf Components: main contrib non-free-firmware non-free Description: Debian x.y Testing distribution - Not Released MD5Sum: b0b85eb959fdabeddfbf4fc52ce4da61 2080465 contrib/Contents-all d23c658438dea75e65bbf4a42085915c 27796 contrib/Contents-all.diff/Index fcbef407bbd6c89c9a0412ab0cd25d20 141476 contrib/Contents-all.gz d27c91a7d13ce624f5ddcbdebe5e7a6e 1948483 contrib/Contents-amd64 2fb841238891e0159aaa50c6abeffaab 27796 contrib/Contents-amd64.diff/Index a77d3091d8a29f53e98145c1b459eff8 115711 contrib/Contents-amd64.gz 4dee9181f0c9dfe945c7b43b184993f7 526340 contrib/Contents-arm64 dae4d446b4e8410fbe685728a83c8bfd 27796 contrib/Contents-arm64.diff/Index 9af9d1508a36aff28ac534569a943a01 42154 contrib/Contents-arm64.gz 36da1e4b619068dbe4d61a07229695a3 442301 contrib/Contents-armel 4a7f299080196b7359cbc22de53a3212 27796 contrib/Contents-armel.diff/Index 1014af277e0beb788bfc0724f8535960 34910 contrib/Contents-armel.gz 29646c4ec3ae516f58e246f0fe671d0d 452469 contrib/Contents-armhf 7ffe75d5b53f5d69c0e5a1d7809d8d6d 27796 contrib/Contents-armhf.diff/Index 1e7424a9c5d09fbe1f3d4e4e7c0e1298 36681 contrib/Contents-armhf.gz 2b83c1b0f589c4be30fc55d9a322597b 8742457 contrib/Contents-source 113c3d904cec530023ff3a3bcd9d0a19 27796 contrib/Contents-source.diff/Index b3758eda2055740c7c68e603d97a6f7e 622471 contrib/Contents-source.gz d41d8cd98f00b204e9800998ecf8427e 0 contrib/Contents-udeb-all 4a4dd3598707603b3f76a2378a4504aa 20 contrib/Contents-udeb-all.gz 03caf2691b7e177ac494199b3f080d09 93 contrib/Contents-udeb-amd64 444dfae8750010e40628e9f369bbeaed 68 contrib/Contents-udeb-amd64.gz 03caf2691b7e177ac494199b3f080d09 93 contrib/Contents-udeb-arm64 444dfae8750010e40628e9f369bbeaed 68 contrib/Contents-udeb-arm64.gz 03caf2691b7e177ac494199b3f080d09 93 contrib/Contents-udeb-armel 444dfae8750010e40628e9f369bbeaed 68 contrib/Contents-udeb-armel.gz 03caf2691b7e177ac494199b3f080d09 93 contrib/Contents-udeb-armhf 444dfae8750010e40628e9f369bbeaed 68 contrib/Contents-udeb-armhf.gz 6d6e2b279637538314f1d808e679e4a6 93642 contrib/binary-all/Packages afbc3b9908000f739ad498934edf7cc6 27796 contrib/binary-all/Packages.diff/Index 00d14f6d7ce7ee79d3328d46d13f9e95 27671 contrib/binary-all/Packages.gz 9edf4c3bb6fad01521093d30dade9cf8 24232 contrib/binary-all/Packages.xz 32dbe4e5d48f3b2a39ad5c1278fcb2ca 104 contrib/binary-all/Release 4bfe7a334e16b328cfb8cdf31b335cb8 222946 contrib/binary-amd64/Packages 72d1b39534af110fbbe72e7f2ed8dc1b 27796 contrib/binary-amd64/Packages.diff/Index bcd1074dcd0184d92ca839d480d021fd 61963 contrib/binary-amd64/Packages.gz 171d08776f4d169bf94f9019aba35fce 51560 contrib/binary-amd64/Packages.xz d4e4d726264e766566682aebe40ed5c8 106 contrib/binary-amd64/Release ecd619aeea278284e9c79f94866261ea 192951 contrib/binary-arm64/Packages 3d475e0756245db9ac8ac18897ddb90a 27796 contrib/binary-arm64/Packages.diff/Index 74c90521ea2485b8a74ae7f146746831 54362 contrib/binary-arm64/Packages.gz 857fb92192bf1cab24b24324c2359a6b 45580 contrib/binary-arm64/Packages.xz 934d88ee7bb19fd4ec69140722bffca9 106 contrib/binary-arm64/Release 029be45bd7ab824189adf082645bbd32 155543 contrib/binary-armel/Packages dfd8833f398bd710174e437eaae62a20 27796 contrib/binary-armel/Packages.diff/Index 1e19f476f2383eaa421501849652ebcd 44789 contrib/binary-armel/Packages.gz ba825783810f2e369012e6c5f2665992 37928 contrib/binary-armel/Packages.xz 701829e43e173aeaf907ebaa47b6262d 106 contrib/binary-armel/Release ba53d6d2df8fd9d8f253d92f3cca9dde 171104 contrib/binary-armhf/Packages af4fbb7bca0316b281d1a792da4164a9 27796 contrib/binary-armhf/Packages.diff/Index aaf1a2cfb07259f2fd3ef7483c9935b9 48612 contrib/binary-armhf/Packages.gz 51a6f75e78df5357300576f196221afc 41108 contrib/binary-armhf/Packages.xz 4a983008704063e5bb24cb5ae9a10750 106 contrib/binary-armhf/Release d2e27860ac9a025e3ea525d737fad986 107 contrib/source/Release 691460f93780d1387168ddad9d3ffb85 202905 contrib/source/Sources 7ffe1a75815308e22829c26e89104cdd 27796 contrib/source/Sources.diff/Index 636fd4f26d042cd827691097fecec495 58475 contrib/source/Sources.gz da8de87927682e9471c8e32a30f5689b 49324 contrib/source/Sources.xz 71971cdbbfdcd1167c6b8a18437f696d 551603921 main/Contents-all ad7d5077b5473798233344fa4cb3edfb 28026 main/Contents-all.diff/Index 898822ecf5a2aea1529f1e84516ec18d 35441693 main/Contents-all.gz a0b29faf7dfa63ad565e497765e87285 154336934 main/Contents-amd64 09cde7066117c4e5fa58a8150f3e9ecd 28024 main/Contents-amd64.diff/Index 96f374b73d9b14ad582867ff31aa4467 11504796 main/Contents-amd64.gz 34cb636c52f0dcf840ee2fb9cd92f3fd 150173808 main/Contents-arm64 3d04ea69010ea110180d6c771cb416c2 28024 main/Contents-arm64.diff/Index 9f1f935f05259db67b2d13c56b883197 11239982 main/Contents-arm64.gz 132e25963c571bdf206af841c6186d6d 114932206 main/Contents-armel 328522319be40dd3101b8d8e87cc8294 28024 main/Contents-armel.diff/Index 01fbd45e20ca3719bd34270a9aedef9e 9291435 main/Contents-armel.gz 902bd16af8192bc174917c5cf3e689e7 121561633 main/Contents-armhf 65fb288dc0ab6e568e9ad64839bea7a0 28024 main/Contents-armhf.diff/Index ffcad2692fcae0687fdbbb3f9a70de97 9743390 main/Contents-armhf.gz 2254c58718c24b2b6698d5fe90949a3f 832318686 main/Contents-source 40377baf42f144fc2c1c344f9610b449 28062 main/Contents-source.diff/Index c3e3df12f60ece88858ba1e6a3150b61 85139645 main/Contents-source.gz 0718e4cb0e188574e28136cf68bedf3b 155710 main/Contents-udeb-all ea69b878ac03672d9b226f5f751e3104 13562 main/Contents-udeb-all.gz 86a2686d48e014ab67654f7a7a8facdb 399316 main/Contents-udeb-amd64 f4d3451c915ac28b5090e82bd99ff6e7 30359 main/Contents-udeb-amd64.gz c02f594eb22ded127c60ccb7ec00d15b 532859 main/Contents-udeb-arm64 b0b9edd3f30f987e512d64402e95f1a4 39721 main/Contents-udeb-arm64.gz 36fd248f9dee6581749a6599ee4d4cb0 257183 main/Contents-udeb-armel 10c8dc86829e251cf464bd789664e65e 20322 main/Contents-udeb-armel.gz 541e3610c52ce16c8c26c83af5126eca 486749 main/Contents-udeb-armhf 2046dd581129f7cd7731618eda232d1c 36801 main/Contents-udeb-armhf.gz feaa82cbaa9a18ea06dec4d4818427d4 23411580 main/binary-all/Packages 20a09bf8c632b613a2defe06f021c1e3 27910 main/binary-all/Packages.diff/Index 8b92fe9489b3f9234b36603c162cc75d 5884441 main/binary-all/Packages.gz b5fc0ebbefff85a3ece69de30ff83556 4369564 main/binary-all/Packages.xz c8bfdb5a00c15cbbf7e8e413bee667fb 101 main/binary-all/Release 703fb0977dd7a5b28b174854f8194727 53593082 main/binary-amd64/Packages ae7ed5236ac5ca904ce25819f4a0fd98 27910 main/binary-amd64/Packages.diff/Index 50aa5607ab8e2e3b54a068c2f82f570b 12583046 main/binary-amd64/Packages.gz 3021114c55b13945ecdba956ee158f66 9183000 main/binary-amd64/Packages.xz 74ccfb2ce9d4955e4cff92dede081800 103 main/binary-amd64/Release 988aa1d5c14be74168d437fdef54bb58 52966151 main/binary-arm64/Packages d5d9239447e1679b85eedd32f7a458c7 27910 main/binary-arm64/Packages.diff/Index 6bc325969ae0e529cef7720f475f564b 12468227 main/binary-arm64/Packages.gz f04e8179e9c395db27c10294037095d0 9094512 main/binary-arm64/Packages.xz bc92927f1da7b92a0785a9f8e389bea6 103 main/binary-arm64/Release 1e8835ae29ce23115cb2532675c44c06 50171040 main/binary-armel/Packages ceed65e5463b31edf0e4c618714d0df7 27910 main/binary-armel/Packages.diff/Index f75605e8935c798692cc923784e57cd8 11990954 main/binary-armel/Packages.gz b762f833c192ce9ff93a80cf6f5ffc13 8714948 main/binary-armel/Packages.xz 0e79663d207a38384ddaf12b9beae94a 103 main/binary-armel/Release 183d87521ddbc5aff77cc2cacabf1007 50538157 main/binary-armhf/Packages 3b19c8c28f84c6b5a0d9cd45d398d61b 27910 main/binary-armhf/Packages.diff/Index 509e023d9650689eff5ef29371f1e39c 12066777 main/binary-armhf/Packages.gz d7784436dbafec6a53368b5f7d43ec11 8776908 main/binary-armhf/Packages.xz ce2c73c4733777c72eb7d4665e475392 103 main/binary-armhf/Release af46311522d80111fad434006158c514 58561 main/installer-amd64/20230607/images/MD5SUMS 0e1cad0ea39fd6c7be1a690d129bc2ae 78509 main/installer-amd64/20230607/images/SHA256SUMS af46311522d80111fad434006158c514 58561 main/installer-amd64/current/images/MD5SUMS 0e1cad0ea39fd6c7be1a690d129bc2ae 78509 main/installer-amd64/current/images/SHA256SUMS 3cee35c49b3e7cbb2e3d55102a1bb3ed 92659 main/installer-arm64/20230607/images/MD5SUMS c78be9d5ad82fceae09de129e49c9ec1 126815 main/installer-arm64/20230607/images/SHA256SUMS 3cee35c49b3e7cbb2e3d55102a1bb3ed 92659 main/installer-arm64/current/images/MD5SUMS c78be9d5ad82fceae09de129e49c9ec1 126815 main/installer-arm64/current/images/SHA256SUMS 15a5f3eec9d6cd2810ae28ae0e2acf7f 20336 main/installer-armel/20230607/images/MD5SUMS 53fcb32c4daff97231a17e1207713525 28412 main/installer-armel/20230607/images/SHA256SUMS 15a5f3eec9d6cd2810ae28ae0e2acf7f 20336 main/installer-armel/current/images/MD5SUMS 53fcb32c4daff97231a17e1207713525 28412 main/installer-armel/current/images/SHA256SUMS 1af6f21e61b4ff2ce7e2d7c18485b8b8 76534 main/installer-armhf/20230607/images/MD5SUMS d50a94e3a83c64c2bdded0aa73edd5b9 110082 main/installer-armhf/20230607/images/SHA256SUMS 1af6f21e61b4ff2ce7e2d7c18485b8b8 76534 main/installer-armhf/current/images/MD5SUMS d50a94e3a83c64c2bdded0aa73edd5b9 110082 main/installer-armhf/current/images/SHA256SUMS 65792a893ced0c1a566d984477351ad9 104 main/source/Release b5587eb663a0825a036c46f1ee910fa2 54636355 main/source/Sources 7f6755adc63953f46fa4fd0ebe14a11e 27910 main/source/Sources.diff/Index db1a8f1fbbd749ea473fdf5dacc26151 13382715 main/source/Sources.gz 4ad81df41b24b3f2bde26d43115b692d 9973020 main/source/Sources.xz 9194ae35cc2a6faf08014f9386ca1c4a 424664 non-free-firmware/Contents-all 3867d2cce75131d4c0f2eedf3c90abf7 10084 non-free-firmware/Contents-all.diff/Index baf9571676af5fc3146b941172114f9d 23291 non-free-firmware/Contents-all.gz 0ad1f8d555bed69e998c0700398c04ec 15889 non-free-firmware/Contents-amd64 339ddbcc351c9ba548b3d65b3288f7da 9100 non-free-firmware/Contents-amd64.diff/Index 22ab6747a3315048e127057811475758 1093 non-free-firmware/Contents-amd64.gz cbe2fc21f271a7fd14bd10d3ea45546d 1769 non-free-firmware/Contents-arm64 13ae7afe6c8bcc6ae096090a3dbf209d 5164 non-free-firmware/Contents-arm64.diff/Index 4540231021109d7d9cd23a202c303ed5 290 non-free-firmware/Contents-arm64.gz d41d8cd98f00b204e9800998ecf8427e 0 non-free-firmware/Contents-armel 4a4dd3598707603b3f76a2378a4504aa 20 non-free-firmware/Contents-armel.gz d41d8cd98f00b204e9800998ecf8427e 0 non-free-firmware/Contents-armhf 4a4dd3598707603b3f76a2378a4504aa 20 non-free-firmware/Contents-armhf.gz e390b76350fa9728d9e0562201bbdd64 221869 non-free-firmware/Contents-source f179efc96d9fc4b141ea99f2a4128356 19432 non-free-firmware/Contents-source.diff/Index f11c550f5caf63e694dd55f805660529 24074 non-free-firmware/Contents-source.gz 7b5784fff5eddb6cbb72a37c300ed5b8 29021 non-free-firmware/binary-all/Packages 20c693f7d60df0dd49a893b7c5e568f9 12052 non-free-firmware/binary-all/Packages.diff/Index 93be2e954e17c8526c7963dd239c22b0 7060 non-free-firmware/binary-all/Packages.gz 63be38ea1280bcab5769caeb55aceb60 6316 non-free-firmware/binary-all/Packages.xz bf9e4dd084f3474a59918da941bee054 114 non-free-firmware/binary-all/Release 565e6e1db4e615ef271077b171d769c1 31865 non-free-firmware/binary-amd64/Packages b48af2b96b4a25390794f53841a622ec 27796 non-free-firmware/binary-amd64/Packages.diff/Index 06d94fa809897693c43372402ec7b47c 7903 non-free-firmware/binary-amd64/Packages.gz 6eddf506b8b008028606b2ec3f43e653 7036 non-free-firmware/binary-amd64/Packages.xz dd9f569c11cbf63d072cdc576aa138b8 116 non-free-firmware/binary-amd64/Release 7058b83aa78391ca980b93055eb79656 30519 non-free-firmware/binary-arm64/Packages ab47a07e0ca186d6895f93978c57b7a4 22384 non-free-firmware/binary-arm64/Packages.diff/Index 67e8904fe75afd6446995198cfa4ac38 7429 non-free-firmware/binary-arm64/Packages.gz a6ed0ac98bb0ab93980b9545c779cc0f 6644 non-free-firmware/binary-arm64/Packages.xz ad03005cd870d2862c1e9e6fa474aa00 116 non-free-firmware/binary-arm64/Release 7b5784fff5eddb6cbb72a37c300ed5b8 29021 non-free-firmware/binary-armel/Packages 20c693f7d60df0dd49a893b7c5e568f9 12052 non-free-firmware/binary-armel/Packages.diff/Index 93be2e954e17c8526c7963dd239c22b0 7060 non-free-firmware/binary-armel/Packages.gz 63be38ea1280bcab5769caeb55aceb60 6316 non-free-firmware/binary-armel/Packages.xz 1df8ad56c60b9c3cc1ac4c896304f92a 116 non-free-firmware/binary-armel/Release 7b5784fff5eddb6cbb72a37c300ed5b8 29021 non-free-firmware/binary-armhf/Packages 20c693f7d60df0dd49a893b7c5e568f9 12052 non-free-firmware/binary-armhf/Packages.diff/Index 93be2e954e17c8526c7963dd239c22b0 7060 non-free-firmware/binary-armhf/Packages.gz 63be38ea1280bcab5769caeb55aceb60 6316 non-free-firmware/binary-armhf/Packages.xz 7d0c10c39c65de664d0e1d71c6abddf6 116 non-free-firmware/binary-armhf/Release d41d8cd98f00b204e9800998ecf8427e 0 non-free-firmware/debian-installer/binary-all/Packages 4a4dd3598707603b3f76a2378a4504aa 20 non-free-firmware/debian-installer/binary-all/Packages.gz 8dc5aea5b03dff8595f096f9e368e888 32 non-free-firmware/debian-installer/binary-all/Packages.xz bf9e4dd084f3474a59918da941bee054 114 non-free-firmware/debian-installer/binary-all/Release d41d8cd98f00b204e9800998ecf8427e 0 non-free-firmware/debian-installer/binary-amd64/Packages 4a4dd3598707603b3f76a2378a4504aa 20 non-free-firmware/debian-installer/binary-amd64/Packages.gz 8dc5aea5b03dff8595f096f9e368e888 32 non-free-firmware/debian-installer/binary-amd64/Packages.xz dd9f569c11cbf63d072cdc576aa138b8 116 non-free-firmware/debian-installer/binary-amd64/Release d41d8cd98f00b204e9800998ecf8427e 0 non-free-firmware/debian-installer/binary-arm64/Packages 4a4dd3598707603b3f76a2378a4504aa 20 non-free-firmware/debian-installer/binary-arm64/Packages.gz 8dc5aea5b03dff8595f096f9e368e888 32 non-free-firmware/debian-installer/binary-arm64/Packages.xz ad03005cd870d2862c1e9e6fa474aa00 116 non-free-firmware/debian-installer/binary-arm64/Release d41d8cd98f00b204e9800998ecf8427e 0 non-free-firmware/debian-installer/binary-armel/Packages 4a4dd3598707603b3f76a2378a4504aa 20 non-free-firmware/debian-installer/binary-armel/Packages.gz 8dc5aea5b03dff8595f096f9e368e888 32 non-free-firmware/debian-installer/binary-armel/Packages.xz 1df8ad56c60b9c3cc1ac4c896304f92a 116 non-free-firmware/debian-installer/binary-armel/Release d41d8cd98f00b204e9800998ecf8427e 0 non-free-firmware/debian-installer/binary-armhf/Packages 4a4dd3598707603b3f76a2378a4504aa 20 non-free-firmware/debian-installer/binary-armhf/Packages.gz 8dc5aea5b03dff8595f096f9e368e888 32 non-free-firmware/debian-installer/binary-armhf/Packages.xz 7d0c10c39c65de664d0e1d71c6abddf6 116 non-free-firmware/debian-installer/binary-armhf/Release eace55fa4ef2ee63ca7f4bff2ac308f9 378149 non-free-firmware/dep11/Components-amd64.yml 64f49bef8f400ffcc4253ed2e464cf6e 34379 non-free-firmware/dep11/Components-amd64.yml.gz 49c8424385788c400da3e18d4ae85c16 20616 non-free-firmware/dep11/Components-amd64.yml.xz 6bf760751071c509db8511713249727b 378149 non-free-firmware/dep11/Components-arm64.yml 3832bd91318c06d7d4f639e0a0b12ad0 34357 non-free-firmware/dep11/Components-arm64.yml.gz 3141a4fee4a54dbb851d29492dbdcbce 20796 non-free-firmware/dep11/Components-arm64.yml.xz 36b0033fc8c088461236cc7fc764c9a5 378149 non-free-firmware/dep11/Components-armel.yml 8a6032e7ec43561089a6ff5301605aab 34377 non-free-firmware/dep11/Components-armel.yml.gz f67641654b2911e2f9954f1db4deffdb 20944 non-free-firmware/dep11/Components-armel.yml.xz 6f4fb51eb636b6b724734ab34a124eb1 378149 non-free-firmware/dep11/Components-armhf.yml cb62aab07ae41d073970f284fed36a48 34513 non-free-firmware/dep11/Components-armhf.yml.gz 0ee6769a04c36174e889bd3f531e6538 20888 non-free-firmware/dep11/Components-armhf.yml.xz 0f343b0931126a20f133d67c2b018a3b 1024 non-free-firmware/dep11/icons-128x128.tar 31f6566d35ccd604be46ed5b1f813cdf 29 non-free-firmware/dep11/icons-128x128.tar.gz 0f343b0931126a20f133d67c2b018a3b 1024 non-free-firmware/dep11/icons-48x48.tar 31f6566d35ccd604be46ed5b1f813cdf 29 non-free-firmware/dep11/icons-48x48.tar.gz 0f343b0931126a20f133d67c2b018a3b 1024 non-free-firmware/dep11/icons-64x64.tar 31f6566d35ccd604be46ed5b1f813cdf 29 non-free-firmware/dep11/icons-64x64.tar.gz 42c33c4447b52487eddad5f5a4132ce6 15168 non-free-firmware/i18n/Translation-en de86d61a1193eea7695bee850e7dd0b4 5656 non-free-firmware/i18n/Translation-en.diff/Index cac34d82abed4c60c037176a3fc99cfc 4796 non-free-firmware/i18n/Translation-en.xz fab8478e9f9133fcc852ea9291e1390e 117 non-free-firmware/source/Release 32eafdfd99ac12f7c8339206bce7305e 31740 non-free-firmware/source/Sources 810518504c9ce60ddf061b86d2d6a672 27796 non-free-firmware/source/Sources.diff/Index f83099b550293dba07f40ab1ae424380 7539 non-free-firmware/source/Sources.gz d86c21d19fb2af08cb186b59e30fdff2 6948 non-free-firmware/source/Sources.xz 0c7a1f7fd5dbdbab8923e9788c1a0797 14898506 non-free/Contents-all fb51881b5113a213adffe71d1f61e91a 27910 non-free/Contents-all.diff/Index b50780b56fb7afef6a46d0e398fd04d9 785223 non-free/Contents-all.gz b7a61d8d3de75fb7ff17d6cdd9707ac4 985673 non-free/Contents-amd64 8d3b69d9b94e8eb71e4108e05523fcd9 27796 non-free/Contents-amd64.diff/Index 6542bd23232023810aaebf4f20f34186 75457 non-free/Contents-amd64.gz 51cce61c4e99d0dcbbf048cd1b7ba056 567043 non-free/Contents-arm64 67cc6012300f8d07e73dd5d2a1408a62 27796 non-free/Contents-arm64.diff/Index 7f6dfb55dd0988846c5c39f1826b7e83 44801 non-free/Contents-arm64.gz 76a2d1c13219689e32173a9979af007f 149034 non-free/Contents-armel b0bccdd7d349284b63829c0f44147c84 15988 non-free/Contents-armel.diff/Index a9890d541dbf10dc4d7403580e12ab50 14071 non-free/Contents-armel.gz 8c594023568f447990d0211b350480fe 158838 non-free/Contents-armhf c62b1b514aaf37de06b658673c257cd6 17956 non-free/Contents-armhf.diff/Index 060715b01b41bbcfbb6eaca6675f4b13 15250 non-free/Contents-armhf.gz 98390144abf7171985526e40ae949aa1 9738587 non-free/Contents-source d122eb5cfdd23978aaf9f3ee3511e38d 27796 non-free/Contents-source.diff/Index 9aea42426b4e661696a9800be725a3b5 979973 non-free/Contents-source.gz d41d8cd98f00b204e9800998ecf8427e 0 non-free/Contents-udeb-all 4a4dd3598707603b3f76a2378a4504aa 20 non-free/Contents-udeb-all.gz 13ae2f90a0b256587e69711ac552df4b 480 non-free/Contents-udeb-amd64 8ab034f4f486119a4e67766ff31caff9 114 non-free/Contents-udeb-amd64.gz 13ae2f90a0b256587e69711ac552df4b 480 non-free/Contents-udeb-arm64 8ab034f4f486119a4e67766ff31caff9 114 non-free/Contents-udeb-arm64.gz 13ae2f90a0b256587e69711ac552df4b 480 non-free/Contents-udeb-armel 8ab034f4f486119a4e67766ff31caff9 114 non-free/Contents-udeb-armel.gz 13ae2f90a0b256587e69711ac552df4b 480 non-free/Contents-udeb-armhf 8ab034f4f486119a4e67766ff31caff9 114 non-free/Contents-udeb-armhf.gz 5381d1928d7a47c9a651aac0f935aac1 220739 non-free/binary-all/Packages a83d9ad4e5c1e8a7fe1675ca84e80d29 27796 non-free/binary-all/Packages.diff/Index 7d2dad370feaad46debd2f64011983a8 52221 non-free/binary-all/Packages.gz 956a4265400bc2e7103ce82693c16919 43344 non-free/binary-all/Packages.xz 9fa14c811434e4228f7ef9efbbc6988e 105 non-free/binary-all/Release 016016d73fd38021b41018fa44deaf64 597497 non-free/binary-amd64/Packages bee087e1e21b940da7258ebf06a4bb9e 27796 non-free/binary-amd64/Packages.diff/Index 39833d498e9c80bfaaf444eba0e7d829 122578 non-free/binary-amd64/Packages.gz 050790746859a3241f6c45db66072b9c 98068 non-free/binary-amd64/Packages.xz ea5134d6f572ea57607f020ae67c1976 107 non-free/binary-amd64/Release fcb634483d3ff0e934e47fd0319e4c94 410729 non-free/binary-arm64/Packages 1d9fe04a9355e1e5f0bfb8c27d68647f 27796 non-free/binary-arm64/Packages.diff/Index dd54d77b57b97dc97826e97af8298f85 93016 non-free/binary-arm64/Packages.gz fbe21d693dc95c645ec6877b74c73038 75384 non-free/binary-arm64/Packages.xz b9d6c5c210379f7e7904e009ce1c28a8 107 non-free/binary-arm64/Release 621de080530f0f45c8d4f1e8cb72d583 270311 non-free/binary-armel/Packages f033ae6ea0c17fc5dc21dfe52996b5ff 27796 non-free/binary-armel/Packages.diff/Index 4641fb223b8aa28694b8a4d3724c186f 66160 non-free/binary-armel/Packages.gz 8691a1ee9be4c61bcff261d0aad4f684 54356 non-free/binary-armel/Packages.xz 36e00cdfe67c582bd447ccfca0a38c95 107 non-free/binary-armel/Release bdfe1332e129dab24173a4c18ff34e8b 278052 non-free/binary-armhf/Packages 5379ecc9963f43f11507754f01f1b8a5 27796 non-free/binary-armhf/Packages.diff/Index 82e4fc7d85e118b8571f3a8e91637f17 67863 non-free/binary-armhf/Packages.gz 6d5a70cb7a5e59874dcba62f5f3f07db 55888 non-free/binary-armhf/Packages.xz 1b5ddc7fd9b1bdf839046c4bad8e8093 107 non-free/binary-armhf/Release d41d8cd98f00b204e9800998ecf8427e 0 non-free/debian-installer/binary-all/Packages 4a4dd3598707603b3f76a2378a4504aa 20 non-free/debian-installer/binary-all/Packages.gz 8dc5aea5b03dff8595f096f9e368e888 32 non-free/debian-installer/binary-all/Packages.xz 9fa14c811434e4228f7ef9efbbc6988e 105 non-free/debian-installer/binary-all/Release 17dacb3319bd2f218eb5b6a127555b07 607 non-free/debian-installer/binary-amd64/Packages ee33ccab888693948dda49bfa9a0aa9d 413 non-free/debian-installer/binary-amd64/Packages.gz 627490e1f8e42eb77a5e7b39329a94f7 492 non-free/debian-installer/binary-amd64/Packages.xz ea5134d6f572ea57607f020ae67c1976 107 non-free/debian-installer/binary-amd64/Release 8299d446d16cc444c9ca372afe967304 607 non-free/debian-installer/binary-arm64/Packages 5b718942489a9b79b94a3c4cd1556d80 412 non-free/debian-installer/binary-arm64/Packages.gz aaa3eb73bd884cb7069d3c3da6b55049 488 non-free/debian-installer/binary-arm64/Packages.xz b9d6c5c210379f7e7904e009ce1c28a8 107 non-free/debian-installer/binary-arm64/Release ce4029302c4e0b3318a3948839286c94 607 non-free/debian-installer/binary-armel/Packages 4d4422d4f1c8e29b77e9bfc9b9ca04a4 411 non-free/debian-installer/binary-armel/Packages.gz 88b8d2e8c31683fec8042856bd6d67e1 492 non-free/debian-installer/binary-armel/Packages.xz 36e00cdfe67c582bd447ccfca0a38c95 107 non-free/debian-installer/binary-armel/Release 47d3f9d9edd558f520adaf9ae9f13794 607 non-free/debian-installer/binary-armhf/Packages 49718d429ae8180c30fcdd5d52de088e 412 non-free/debian-installer/binary-armhf/Packages.gz 466ba67dbc928098e38fd0f3f645261b 492 non-free/debian-installer/binary-armhf/Packages.xz 1b5ddc7fd9b1bdf839046c4bad8e8093 107 non-free/debian-installer/binary-armhf/Release 780772dbdf224f83bd47d5b5cf512fc2 11715 non-free/dep11/Components-amd64.yml b968d2b7a1f8effb609f4337a57888ce 3610 non-free/dep11/Components-amd64.yml.gz ded115094818816848824557e55c5c28 3492 non-free/dep11/Components-amd64.yml.xz 5533021347d4ae918fbb52ccdb3847b0 5893 non-free/dep11/Components-arm64.yml ec7f1f9487a9f657b3a67f00b08734b2 1786 non-free/dep11/Components-arm64.yml.gz fb9f2bccb176eb1cea6ec9cb2f2a363d 1844 non-free/dep11/Components-arm64.yml.xz 56797f6b44f0c4b16ded71e3672748dd 5893 non-free/dep11/Components-armel.yml 2623ddddb2d10a980b476e63090a7888 1785 non-free/dep11/Components-armel.yml.gz 176cd435d531a2559ebf2c55247c0edb 1852 non-free/dep11/Components-armel.yml.xz 1c8d1417aad04386714eadf1195654d6 5893 non-free/dep11/Components-armhf.yml 3d383d2b409bee40e760d7be4acfb1df 1785 non-free/dep11/Components-armhf.yml.gz 654f2781c7f898a5d07fa24ef4087cd2 1856 non-free/dep11/Components-armhf.yml.xz 8566e95d97a49bac96c80d3dbdac3933 7168 non-free/dep11/icons-128x128.tar 73abee5384891168658eeced4a858a50 2167 non-free/dep11/icons-128x128.tar.gz 7ba249df54febd7ff941a127b4a2460b 3072 non-free/dep11/icons-48x48.tar ed228b786f9cb3606d65b4db8d907142 578 non-free/dep11/icons-48x48.tar.gz 5bb8a5c8362b808dd96014cfb9432691 17920 non-free/dep11/icons-64x64.tar d2e2f78fa5a0c4e2c91012a21ad30506 12030 non-free/dep11/icons-64x64.tar.gz 395ead6c28cd3dccd666b852279538ca 420788 non-free/i18n/Translation-en e9c97e2e1137793ba3904f1641fc0aee 27796 non-free/i18n/Translation-en.diff/Index 22facf077dc33c6169391c5021ed4f75 67232 non-free/i18n/Translation-en.xz dfef19158c8765956e1f2c612b661e03 108 non-free/source/Release cdb1f251d9ef073b8ce9340e4cd38ba9 336581 non-free/source/Sources 2c26891786dc414788f368712690710b 27796 non-free/source/Sources.diff/Index b91f4298c4d4ef1787b6f62329159b8e 90508 non-free/source/Sources.gz 625b4e89c677374fbeb983e53e28bfef 74912 non-free/source/Sources.xz SHA256: 5d16e0c307216f55ed79ba479d6ae724131093a64834cf22992877e911b99a04 2080465 contrib/Contents-all 58cd1b5ae2df58dd55992bd8ab137b066ac423229e7471a2f981546e2b2411da 27796 contrib/Contents-all.diff/Index 5c3e40d4fa29cbc2a9e717e4a3c02e5bc6fb7b01e91c6330ca33f217075d4778 141476 contrib/Contents-all.gz e78714c19e33b69a5d6d8f3df7d3d60f2a506ea5b5c5cf94beadb3b2bf99ebcd 1948483 contrib/Contents-amd64 dae8f0998246e1aa983dcf9c539187fb9bd0f318304f309eda9970af1f0d4678 27796 contrib/Contents-amd64.diff/Index 1ed6166c0507c8c9b480ae828fdc795e5baa9fbf5bec625f7b10cbf3d92c4dc1 115711 contrib/Contents-amd64.gz 4f71a18b95d112e448bc7476e3e2fa8bd7c9338e8233019f1213b5bc336d16db 526340 contrib/Contents-arm64 f7a9d0c9c265990e81890f0671de5ad2bff73f25060f0bf86ffcaf51ede383c9 27796 contrib/Contents-arm64.diff/Index 47d37fcbe570e18276200e78c1c8d16f8d791d1cd4fbae368bca97f3e9f7b839 42154 contrib/Contents-arm64.gz f63d6b2b52d0059b8ebb0f11b3b0fb6f012d90d011068bfdfb0b0e46e297bea8 442301 contrib/Contents-armel f9c3977609cbe2a9cc6fe40154b8996f6ed18c0d76b4c34dc8c57cea07167a54 27796 contrib/Contents-armel.diff/Index c538e15f8052037e3ca5b849fe7deb6798b65aaa2972b054d7766d23148f621e 34910 contrib/Contents-armel.gz 20061c547f1f987e475235644b3aef09f176b50c9e8e29c560ac759c28c78e7f 452469 contrib/Contents-armhf 877c042908bfac2fcf8be9c42dc22f47a5a7390511d048b882e9c4a8e3f745b6 27796 contrib/Contents-armhf.diff/Index 971face04d23a57b2665d495ead9c2db55606db0e7abce05d699523ed9bee4b6 36681 contrib/Contents-armhf.gz b20649282476b8c8abbb9fda4335092391baf81025f96a1ebbd272d700e538a4 8742457 contrib/Contents-source cc0279dd8204c32ba62bd7aad4444d5621f5421807600face5917b9bd9badedb 27796 contrib/Contents-source.diff/Index 89e1b441e8528862e5a69607da3b4e9ce67615725f675e71af87fe2b9c2d9c89 622471 contrib/Contents-source.gz e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/Contents-udeb-all f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/Contents-udeb-all.gz 7e37c13b45e8502eeb61994430a03aa4d1dc1da9330a2fafbd35ce5a480a2d54 93 contrib/Contents-udeb-amd64 ee4e2117297557383a81e9f37e0083632e182172fd1131005163a4b31af3c9ef 68 contrib/Contents-udeb-amd64.gz 7e37c13b45e8502eeb61994430a03aa4d1dc1da9330a2fafbd35ce5a480a2d54 93 contrib/Contents-udeb-arm64 ee4e2117297557383a81e9f37e0083632e182172fd1131005163a4b31af3c9ef 68 contrib/Contents-udeb-arm64.gz 7e37c13b45e8502eeb61994430a03aa4d1dc1da9330a2fafbd35ce5a480a2d54 93 contrib/Contents-udeb-armel ee4e2117297557383a81e9f37e0083632e182172fd1131005163a4b31af3c9ef 68 contrib/Contents-udeb-armel.gz 7e37c13b45e8502eeb61994430a03aa4d1dc1da9330a2fafbd35ce5a480a2d54 93 contrib/Contents-udeb-armhf ee4e2117297557383a81e9f37e0083632e182172fd1131005163a4b31af3c9ef 68 contrib/Contents-udeb-armhf.gz 5a6775a423d494fdf1df6ed1caa266a148e20417bf828a1d5ca61e031d1c0964 93642 contrib/binary-all/Packages 5340bc5d83c9103d6a7044b83d166df6983865b0893a2b68197b4b1bb9c94d66 27796 contrib/binary-all/Packages.diff/Index 08611fbac77aa09237320cb13f174fd6b1aa9ae06298fc2e287ccaceb43d033f 27671 contrib/binary-all/Packages.gz 6664844e872bc3f69c19727a29f138c7d13fe945294f89c087407bdf36172361 24232 contrib/binary-all/Packages.xz 0e971b6961e1dc1143f096c8bef874e122e2463839e17a38b6636dbff99d4f37 104 contrib/binary-all/Release 69b3ad9e04abb03a1054d994a35667941172a9d77e58249cc0e66a719436dc65 222946 contrib/binary-amd64/Packages 677be89198aceb5796530f748b4e6580df999c57fa65eb07cf34fa5480f675b7 27796 contrib/binary-amd64/Packages.diff/Index a608b3d490c1108d3f980384fe9c4a6507c33b8de4da11c9ad6fda43fa810820 61963 contrib/binary-amd64/Packages.gz 2202aa8e73e33b3eca391d107b139022a93afcdc20c802a15e150a8ac04ad327 51560 contrib/binary-amd64/Packages.xz 7e9be4d2fff8226af72974921fd846c814e2c745b8a4ef2c2d39b75b4a2a0ff6 106 contrib/binary-amd64/Release 58385364db9b55090160b4f21d18475f5456506f399d6783888eb50774c446c6 192951 contrib/binary-arm64/Packages b9b12490828b7486239ad22c4ce9cee0862043221126ad92df5599b9161715b7 27796 contrib/binary-arm64/Packages.diff/Index b84be1e7b2be3c36b89c4f291e68ede9b59c521f53e706cdf78f39ce826a6f5b 54362 contrib/binary-arm64/Packages.gz 97f91101755ad3f436fb38ce6d6a13a5e1a4109fb327722dac80677b5f1e2ce9 45580 contrib/binary-arm64/Packages.xz 24ede283b92c2211aeff8251b8b436907d6a1d21b92e167973c13c86e14d654b 106 contrib/binary-arm64/Release e997723d130588a42c23654314c4ab8a3a0b7f9dfba2da0a3e6e54e464501ca5 155543 contrib/binary-armel/Packages 9616421ac1492066723f95f54c2086fb5dca173770b97d9478250be5b4f8a6fe 27796 contrib/binary-armel/Packages.diff/Index 3d7d7e58f11265728ca2e152d00dc7d2b07f150ec7acac9b254d05b5b9404cfc 44789 contrib/binary-armel/Packages.gz 844a32c2914a2dfaec2d2090869277f6d7219f2961ea104448a0e8e8f84917dd 37928 contrib/binary-armel/Packages.xz ba25dae7a686bf5a5c76ccf849b22ca6edc40a991919cb56e6bc3534a7d44890 106 contrib/binary-armel/Release d134e181578a22fc3922b944e79477ea5291180cf47b2d333f4585ac9dbe1f9d 171104 contrib/binary-armhf/Packages 06795f5b53cc6aa4a20994f7cf3e16b0ebb9eedad2934b8e10cb84e95ba791b7 27796 contrib/binary-armhf/Packages.diff/Index ca85896afe82be7f3c7ca2d082446d32c5163656fa17bfca64a9b201c7f30218 48612 contrib/binary-armhf/Packages.gz 907310e0d119644db8625fef0a77a50a2b5fc301c64f65316e8e20c842166a1a 41108 contrib/binary-armhf/Packages.xz 1ffbc272438b15074afaaf2a44e0a5ecbfe1fdfbdc4a0eb06c9ac08f0fd6f229 106 contrib/binary-armhf/Release e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/debian-installer/binary-all/Packages f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 contrib/debian-installer/binary-all/Packages.gz 0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 contrib/debian-installer/binary-all/Packages.xz 0e971b6961e1dc1143f096c8bef874e122e2463839e17a38b6636dbff99d4f37 104 contrib/debian-installer/binary-all/Release 8a0cf75b847f5d616b9d8c8070d0c2e23ef41baa91747a4d17d9435451959e10 526 contrib/debian-installer/binary-amd64/Packages a31a153aca13f370d40f00f08583fe87915272d962eccdd80780cced4bec543f 373 contrib/debian-installer/binary-amd64/Packages.gz 70fcd0ee50d8ae68b17c1b6972ba43e3b80ea8df06141fadd73043035faba3af 448 contrib/debian-installer/binary-amd64/Packages.xz 7e9be4d2fff8226af72974921fd846c814e2c745b8a4ef2c2d39b75b4a2a0ff6 106 contrib/debian-installer/binary-amd64/Release e3125ef6890d722ac5917c161b17a940d509a2ddb33fc6e4016f48202b766674 526 contrib/debian-installer/binary-arm64/Packages 7d7e9366418daf48575a28d92ce02e91bae8b16e4a1ba731c443f54eb806585c 374 contrib/debian-installer/binary-arm64/Packages.gz 9446b92a9714b3032f2e445ded1f0b9cd85f3b3b24b11a766452af6afe928a98 448 contrib/debian-installer/binary-arm64/Packages.xz 24ede283b92c2211aeff8251b8b436907d6a1d21b92e167973c13c86e14d654b 106 contrib/debian-installer/binary-arm64/Release cb2131dd12592444e2019c920df6d0f257484890a108515eb4c7f4987cb82f83 526 contrib/debian-installer/binary-armel/Packages a68ad99d2ad71629e4f9b1311c260e29abe48bf8a5688f4818b70ed97e5081f2 376 contrib/debian-installer/binary-armel/Packages.gz c7d2a5c574d60a5e7712cba0ec7695269b0d599b4cc2f7518b3c0372c9b1e370 448 contrib/debian-installer/binary-armel/Packages.xz ba25dae7a686bf5a5c76ccf849b22ca6edc40a991919cb56e6bc3534a7d44890 106 contrib/debian-installer/binary-armel/Release a3a9c672e4626d35430999ffdabc95edee92e51f51432f6375746468bd948090 526 contrib/debian-installer/binary-armhf/Packages 038e7fb6f0c00f8aac289dea82df082eb56bd8f7ea89a02124bdaeb4e8261464 377 contrib/debian-installer/binary-armhf/Packages.gz 44b15144afd09ed42750dc8582316b5d9c4f003fe77c7721d5ddee19613bccca 452 contrib/debian-installer/binary-armhf/Packages.xz 1ffbc272438b15074afaaf2a44e0a5ecbfe1fdfbdc4a0eb06c9ac08f0fd6f229 106 contrib/debian-installer/binary-armhf/Release e2f78a7dadff9be609a0a66b1cb742fd474a6af97a102bd325499b6cad0fed2d 142026 contrib/dep11/Components-amd64.yml fed86dd3f7ea8e20645aca2fb946ac3d323add50d72f1dcd6a212374bf062c1b 29361 contrib/dep11/Components-amd64.yml.gz 65c528df08c0ec429d6981936911aee017fd262066480dfa1dfc192a65b55f4f 25012 contrib/dep11/Components-amd64.yml.xz 562b826647570b87872e161d79d2d4c678d9452160f722d504ffc0775a7bfc74 131904 contrib/dep11/Components-arm64.yml 3a94e6f226a0c124c691d9f3477bfa5f52c0da388f4ccfd7feace8555c3cb5f7 27115 contrib/dep11/Components-arm64.yml.gz bc00b59fcecb5f6c42d711179d2d592b14245928e0011b549e94bfefad4b4464 23256 contrib/dep11/Components-arm64.yml.xz 5a9622610483bbc9c8703849c9f84f63050563b4badac4c6e9625a72f2d9c631 131904 contrib/dep11/Components-armel.yml 93675fb6e8750d8d03d3d1e00fb42561bfede53a25d77274780b2f44c66e920b 26993 contrib/dep11/Components-armel.yml.gz c314f24874bd4e7fadab6e3ba42a16c07cb46b9a62e1d7dd736525a5d5232580 23212 contrib/dep11/Components-armel.yml.xz 60016e8f68ffd1fbac02ccab5b8515ef491e4a5f6dedbed286e7300f1e3840fb 131904 contrib/dep11/Components-armhf.yml cd9aa9cb0673973c06258e5598535229d2d9a77c53043124dfc55ce06e36ec25 26938 contrib/dep11/Components-armhf.yml.gz 133fad96d623e6778446c830871337716fb238140b183d74668bff4edbff1bce 23264 contrib/dep11/Components-armhf.yml.xz 80823bc610dd120a38fb33742d392053f280a9ce0bc008bd7920aa71eb43a501 305664 contrib/dep11/icons-128x128.tar 5d8123673f055e6f6b237da103fb37691129eeeaae9f3e209482e4eb944a9957 241186 contrib/dep11/icons-128x128.tar.gz b3c123dbc67d795108ae87af609a9d30d28c1694ff65ca21ae07569b017b05e7 93696 contrib/dep11/icons-48x48.tar 1b605ebe7da7025ffe82a18c612a4ed9236da721005281d7f849c366ee7a55fd 57124 contrib/dep11/icons-48x48.tar.gz 7c6be9a76f88b84211c55f62a471055fa6a67b7eca1dc9e3531aa44f16892a0b 164864 contrib/dep11/icons-64x64.tar 904592c0900befbaa03ea8e192ca5551d49960f0a9b3b4dc1d825fa613db1df9 120322 contrib/dep11/icons-64x64.tar.gz 0a7b25068eadecf95a7d68902c7abb1855958a8c092905bb237ea5550d011fa5 185090 contrib/i18n/Translation-en e40a7a43b9084eedf7313c6ae4b5c8fe7bf6deacb27479fbd2c8055b0ed2844d 27796 contrib/i18n/Translation-en.diff/Index 9d80d821ae877cfd7326efe09d8a832a4ee0e93d0fb9b23efb45818c6ead2b30 47604 contrib/i18n/Translation-en.xz 877dd77e1c38444346a1279efbb0dd5c8b86299ef9a8b4401de0216f66788444 107 contrib/source/Release d14267011636ac5b2e673f24d82e3e944ca1385441c796fcdebeb3391d7bfc6c 202905 contrib/source/Sources 357eb3a6c0f239f7b0abd243aa9678caddc60f4720a445223955b13e35c5f596 27796 contrib/source/Sources.diff/Index 81cc3aede3d3b3fc833e03073eaea2a0a95e7486eb31a0c982aff5c2e60a314a 58475 contrib/source/Sources.gz 630becee17e0fca72ea54365a092c9c188db692ab39f8c08d458fb91b43c7ec4 49324 contrib/source/Sources.xz fff22684d2404b1a9d379afb715893adec748a6aa9085a4e9f57bd0256cf004a 551603921 main/Contents-all e4b49b43795e0bd1d1873de639bb743c690556c49268c3844620572560339e14 28026 main/Contents-all.diff/Index e0367ce91bd17710c975d3720badd005a63c9380189dd08685328f1174a6caaa 35441693 main/Contents-all.gz 307f1713a24ba97f67e3e1615c4394cf499d93d9881ce616928d45a6ada46615 154336934 main/Contents-amd64 82f2452031582817a08603892d342e3c921ff512c712260471c62fd2f30dab3b 28024 main/Contents-amd64.diff/Index 29324633d64c5d9ea3bb86438d60038e49f255ae330acbe67161f164720c66fe 11504796 main/Contents-amd64.gz cfe533491a6c2a698b4a451420fa529074239caacc6c9f9b6713eea6b6d1ecd7 150173808 main/Contents-arm64 1433af0ce8d52c0ade336159ebb91b17594ec6acece17196af362322ae90fbda 28024 main/Contents-arm64.diff/Index 181f22a3355ec1e156a23c162aa759763dc3a77684532ef672da8b49c4cf44ad 11239982 main/Contents-arm64.gz 9bd256f26bccd1b40d095716ddc5b5c74aec7ad9ff747cf26c0d4c1375b6f94f 114932206 main/Contents-armel 123589ecaad7f87ba511962fdef7b9e6cac99f198cad219c9e2139755bd4fbf2 28024 main/Contents-armel.diff/Index 16c303b012e10c05c80d567e7b30fc6a6191700c4adfaa8e445e0d6ec6df729b 9291435 main/Contents-armel.gz 2debd526bf8d9df011905815c92a63502eac7840da64a0c84632fc05dfea8d1f 121561633 main/Contents-armhf 81ad94e265737d03c037f63fdb260ff5da8f869dfe1ccd86223324b27936d801 28024 main/Contents-armhf.diff/Index 74c5a3f64c077d09e6e80722a363c6a69fca771a9450212774fe0ccb51da48bc 9743390 main/Contents-armhf.gz 7350c903c753b8d1375d892d9e59dcaa2109c2f4ffddb4b6297678a73b221ee6 28062 main/Contents-source.diff/Index ce3b5eb095e01749ba339061c66f0da4fa5fadc65c4ae9aa2fc5b7e4834de9d3 85139645 main/Contents-source.gz e35e0caa6b4c100956a988ed9ad329b2d58c7e914eecdcf8b8bab22a35732048 155710 main/Contents-udeb-all e302456361961fc38beeb74082a80d7ec815cc16d45033f1f1c08fc60d777487 13562 main/Contents-udeb-all.gz f918d7d96329322180ec1a1bbbffa1e3ebd6a03bfc63eb3e2cd129fd7f7333f7 399316 main/Contents-udeb-amd64 b59d5ed83c99af81335501b19b2c828207ed3e1993e12906932ca4f65234c248 30359 main/Contents-udeb-amd64.gz 21cf822c136108bf8b09174b70833b16b99b96b1ad38da10e28dd5af11b8d52e 532859 main/Contents-udeb-arm64 1bdcffccaa62903e33fe255ec808c094b41451734a5c97a60c1e66b931265179 39721 main/Contents-udeb-arm64.gz dea5c77665c4392189977d087ce31de3acd4e74f1ae571eb024a44a2c98d97eb 257183 main/Contents-udeb-armel 31634fcc731712c59da9c6828ab9d8c99bebc22497897d14517bbdffbf413ddb 20322 main/Contents-udeb-armel.gz 257814382555189f798ee99bddd0a93d69642e841d82dde6b96528b0653acd42 486749 main/Contents-udeb-armhf d57bfe801be75374ce6816e01a6f4864b8fa2fbaaa69548b8b2d0c59039e79dd 36801 main/Contents-udeb-armhf.gz e18b9f5374b3ed4db1685f08622f1f5f1d604b299bd4ec6da84c26236da3a618 23411580 main/binary-all/Packages 7bd33b25868e8f0a8cdedc335a76d87067676e64ac808351d4521783f7d56646 27910 main/binary-all/Packages.diff/Index efd145f3a9eb666919169d7436b614e0eebdbafb4489ddda7d2954a7d61eacd8 5884441 main/binary-all/Packages.gz 1ad9520a5d7809a75ebaf777a0699b3d636eb0b410d9ee8272743c7981029875 4369564 main/binary-all/Packages.xz 72d0382efbdf0aaf86a59cf49e56a0e74eb1a9b6c840755c43d6346d9e6956ae 101 main/binary-all/Release b128f5378971205e918cffb50cc0a4b1b3f5fe10d159426507e6d4b2dde48b63 53593082 main/binary-amd64/Packages efb2df8b84f00d999383568c870ee8f3ba964d9162e0d4180c08d869946c28ef 27910 main/binary-amd64/Packages.diff/Index 7d75862c17a364e4d6cebae445cc3b381c7cce224952dd5b1ffc7a025d75f077 12583046 main/binary-amd64/Packages.gz 4bb18a56653c9fb2c98a83d3937ce8f19dd3c42504403b37ca610597cdc21c2b 9183000 main/binary-amd64/Packages.xz 440e6f0db6250a47bbb7d38357cdf8a1775084c2cd05acec6a4dd66488ceee3e 103 main/binary-amd64/Release 0ac5f71e343a38c7255a69cd6af5b75bc58c11582e33b08f4f0553132a50f870 52966151 main/binary-arm64/Packages eef031e57b0334073b218166c8d869ccef26129b02bb707809304d18fbdd084a 27910 main/binary-arm64/Packages.diff/Index bb12fe40ba28f183cbe17e6d354b0995d01bbc3e4f0edec6d743ae0af27f2965 12468227 main/binary-arm64/Packages.gz f6c786df9e0feb6cf9a5237ab75fa8044eab59a58de6776cac3476a2cba4a223 9094512 main/binary-arm64/Packages.xz b6d6f4b091589152e43700244b399d9ffed70c1b504d7f8a3bd6bd080769909c 103 main/binary-arm64/Release fd9c72af849b47a595ced0480f0ee34714f24cc72da1b00dc16d0f1acc637edd 50171040 main/binary-armel/Packages 460fc2fba52c17f1f5537588d33f57d3028034f7d83c609f654411aa44f30827 27910 main/binary-armel/Packages.diff/Index a3323e182a9a2f7bd9111bec87f69ff0c8429631f0bd74e20a0b687163d8f1e7 11990954 main/binary-armel/Packages.gz 99cc99593c31a79502b820e97a0018994f37127b004c44bba6517d9b1f097864 8714948 main/binary-armel/Packages.xz 587089c52210058730739708b6426cffaf7a5ffd60b62238ab559704620b1c08 103 main/binary-armel/Release 056667abc16ab72415045ad6a3ca1b90bb5252bc945a18454dc9bea713a6ece4 50538157 main/binary-armhf/Packages 6adb991eeb392cef1e19afe1ab661f33c82ee5e827143c542e7030a9c1bb2bee 27910 main/binary-armhf/Packages.diff/Index 6976d391def59937a3c5dc2257257c31ac38aeb8d3797c8a5f7fb70f86920e84 12066777 main/binary-armhf/Packages.gz 35aa727076c251dee0b8a2601074a0c114f8af021ba481c93e70d1fd3ad6a152 8776908 main/binary-armhf/Packages.xz e9b747c8856b1737731d70a4ec5212a3b1a7f7d762085448b297c51e47ce84f9 103 main/binary-armhf/Release 142597d1a7ce2b5a0979be547c5b9716963e086fbbef65aa5ea7d701f4312ca2 60289 main/debian-installer/binary-all/Packages b1755036e8371f37310d51cdf80647fbe98632695d168a7e91a23bc4d7f1e2ed 16308 main/debian-installer/binary-all/Packages.gz 57ff978d8bdffe83328a97dbacb9a4dfe707f23553413d4cbd76c996707ccc94 14396 main/debian-installer/binary-all/Packages.xz 72d0382efbdf0aaf86a59cf49e56a0e74eb1a9b6c840755c43d6346d9e6956ae 101 main/debian-installer/binary-all/Release 8d504b50d888424c227e42d4b07829e0db6016d72895aff3ac1ca767c34bcba0 229600 main/debian-installer/binary-amd64/Packages 6ab73425252872b827792c81e362504c9c6c9b059cbb123cdd00e7a742599897 61747 main/debian-installer/binary-amd64/Packages.gz ed668e6b883815cc9e8ba7843a7e7bc3727940e82c193984d27d094d9f4814d8 50572 main/debian-installer/binary-amd64/Packages.xz 440e6f0db6250a47bbb7d38357cdf8a1775084c2cd05acec6a4dd66488ceee3e 103 main/debian-installer/binary-amd64/Release 5dad9a5e542b1c6a708cf67049bb3d0d6bc1f99686703c73ee2388e8209a28be 223211 main/debian-installer/binary-arm64/Packages 9ccce599971d75edf3e96c68c227d2fdef2369b529de28300ef884de89ef5eed 59827 main/debian-installer/binary-arm64/Packages.gz 9c5e743795d46925224b06511c40879c0d2dd6bdebabefc7b20aab7bcb731da0 49580 main/debian-installer/binary-arm64/Packages.xz b6d6f4b091589152e43700244b399d9ffed70c1b504d7f8a3bd6bd080769909c 103 main/debian-installer/binary-arm64/Release 27f8faf6764b90e7bd2b5e1b7cfe7d15996f4171b3310dfcb3a944aa3ee42b35 192908 main/debian-installer/binary-armel/Packages 4552bdde5fec7731cf00dfcc89d3a0888e3a639782ea5ca6ac1a7b4a8662532b 54107 main/debian-installer/binary-armel/Packages.gz 525acee5dadee74d5da6fc1d6f9f0ba2bd567e871e09d9f3421a25dca57e0e22 44772 main/debian-installer/binary-armel/Packages.xz 587089c52210058730739708b6426cffaf7a5ffd60b62238ab559704620b1c08 103 main/debian-installer/binary-armel/Release 11a568c3800246c7a6999d6c836416b0c7ecee321a4549bfa557bfec6f5333b1 222069 main/debian-installer/binary-armhf/Packages 11046f6ab8d8bb81171035d241c070079dd3339af1ba226dba017bd27053c54b 60236 main/debian-installer/binary-armhf/Packages.gz 7fb306248962cb0201451d939a28c41f885b69eac873c470cde8e0642060d732 49724 main/debian-installer/binary-armhf/Packages.xz e9b747c8856b1737731d70a4ec5212a3b1a7f7d762085448b297c51e47ce84f9 103 main/debian-installer/binary-armhf/Release 937776b97fd17ad438d60191b7da132f4e64f551713f0f3ca17d8334f8c1f012 20502391 main/dep11/Components-amd64.yml d7643cb3c769b1a3696228ca6bf9c0e002b1ce8ec9f83cd373c4ac23b7297dfe 7023567 main/dep11/Components-amd64.yml.gz 462226509b4292e2497461dd064927d0fb994b47b8d12e8bd9e8ec470fc61080 4551520 main/dep11/Components-amd64.yml.xz eccc6ee2405873c3d4d18f2414531edaa36bbc2d8f15508da75d99ff06aa4b17 20458415 main/dep11/Components-arm64.yml f66906e4ce63eb8e899c2499170e8ed04687a1c0498a1072277eedf19872ad48 7006947 main/dep11/Components-arm64.yml.gz 6fdf023e9a46a83e3ce86cb34c54dcdb500f26007108ddea11831c86a28b0c7b 4546080 main/dep11/Components-arm64.yml.xz e25594a6c5be7164c5b4529713a3936415fcc2bcb2b65cde3dea9e218c96bd23 19647206 main/dep11/Components-armel.yml cf6194ec0417eb34de4674d9253fb3692ba60dd5a33b2d3c2c131c936b266f0c 6730949 main/dep11/Components-armel.yml.gz d19e1c20f7c5714d159ecee8207ae74c440f6037d1d75723884cab69a84f0a54 4368956 main/dep11/Components-armel.yml.xz 55d9a276203bea7ff27ada88cdb2ba8f9d27a4e4782d0a8f88a9ce18730f30e8 20193770 main/dep11/Components-armhf.yml 216e9d2a4f7a51d5580438f003bf20799b3021c34fc0fbabed22a1067c409973 6926970 main/dep11/Components-armhf.yml.gz 4c0cd71b721a00ddc69a44dd37eff40deb06cbff5a6a2cdd828056c7eda3a4aa 4487052 main/dep11/Components-armhf.yml.xz d1fa05147bf392a1645751aaab92b12bf5cdb5ce70afed6583db5513261d1940 12782592 main/dep11/icons-128x128.tar e28ee60357c6f274a28bd76c36c59875acd73e8918d7029bafd0561c1d3d82d9 11041869 main/dep11/icons-128x128.tar.gz a667b00658a42cbe5f61f9f2671543bc33678bcf9a0f01393d46fa65b1911c72 4970496 main/dep11/icons-48x48.tar 9f35d22243adc9ed8bb04f81e083904cffcb00d1649b651aec0b07d9cb289794 3501105 main/dep11/icons-48x48.tar.gz 1f2de15819d1b2ad8e86100a25fb5d6e1dc09357be7bb23cd7d26e735465abf0 9037312 main/dep11/icons-64x64.tar ec71519c23c89a1725ab6e03590a73bd0a5df304d8350bce0f0b8efdf23f6fdf 6917055 main/dep11/icons-64x64.tar.gz 292129c83602fa69c2b7e079c6db299bfd9ca0a9c17ae5923b88575d51d115ac 3005 main/i18n/Translation-ar cd3bfa11589dbe5e61b0a56d6dbd8e30007844fafd3c0023b9d4802e32850da5 1176 main/i18n/Translation-ar.bz2 f7473d4f6f29dca7f018faf5143900fc9b9afe23a459487c7d1ea19b85d47979 1228 main/i18n/Translation-ar.diff/Index e061ee16e4478c39875bc3d977fdd5f880a71a3ea97c9f5119ac127a4305579a 6191 main/i18n/Translation-ca ed06627194c667d774188bcf0d9b859625ec60d2098238ee3c1cd5e1c147c4f7 2673 main/i18n/Translation-ca.bz2 a6386dcfcc78287b90b6a543a7870f873ff2e2a09420b5cf55cd87e2ac615b42 997160 main/i18n/Translation-cs 6ee311f9da92580cdc1c9d87577bf71c7ff25ed324d5518ff3f6693e6d18bb7d 265164 main/i18n/Translation-cs.bz2 2aacf87b1f4357c2b7d40b04b96c77ff66057b88ad726932b62d9e9e7c682358 27796 main/i18n/Translation-cs.diff/Index c69b583d4eeb8dc7ae5f1e7bf2258b551c5a2eb1cd958c238b4850f39bd6bfdd 25724198 main/i18n/Translation-da 411ded53b0bc9d1dca40479530ccbd4829d7408ecf45d4b5f22c0f46a752d026 5379154 main/i18n/Translation-da.bz2 9737ae1b9ad54d9b49943e3d2a752c3178dfd962cf1bb6ee32562dfe31c81935 27910 main/i18n/Translation-da.diff/Index cde2e938600f2d374fd03ce83335206b970ed42f1724f780d15b1ed3f163ccea 7857864 main/i18n/Translation-de ba6d8ca4f56cefc3ef6cc44563b363523921746d06b9b797eb433bb80cdfcc0e 1689555 main/i18n/Translation-de.bz2 731f225ad5a1b0a2a224ce12c50c1d7e35a669a9f5cb23e1673a51c15907f0ba 27796 main/i18n/Translation-de.diff/Index 284169348b8bd4e0de4cc5641eeb05577e80d2bd736452e454976c052cf3cbe2 1347 main/i18n/Translation-de_DE 481a435ad350105b74c4972859c44f447b7a8b5edea0d42f6dd635792e00a461 830 main/i18n/Translation-de_DE.bz2 9f3b3bc0da0653f0ac8484024a7f77aeda681474907f3a94b8a0a0933775d14d 6257 main/i18n/Translation-el 807de361285151534654b83681415016d443e4abd1a7ba36e1e78b4ac337b973 1835 main/i18n/Translation-el.bz2 6d629004fc613dc045bf232a4f6c91529771fcf0c612c14f9aed167d3d4e7fc5 33221551 main/i18n/Translation-en 29aefe8d2faba84e030c88ca4081360cb254947598737f26f471c3a44d52170b 27910 main/i18n/Translation-en.diff/Index 78242ab2b4a28b7d053edba1e4b77bf789198fc6fa6b95f89033c4405dc67960 6119904 main/i18n/Translation-en.xz abccaeb24d409c21b94883b74785053d0f8fad3e94449078ebe92af38861bc5a 2261 main/i18n/Translation-eo 747ab457a83de3b107e25b9cc5536aea2f19e0fe1f08d5357475acea0d788fae 1196 main/i18n/Translation-eo.bz2 4bfff883e83853ddeef6f0e0dd8223d63c029a87db5ff05609cd66c4bb07e996 1188528 main/i18n/Translation-es 9da765fc60e25b76868e4263b8d4a8df0a65f4a89d98740c19d05b1ccdb13a86 274837 main/i18n/Translation-es.bz2 46a8b19753259ae083d55e2d2b528f1de30e54ba638c271894c28b9e6139501f 27796 main/i18n/Translation-es.diff/Index f78cb04d9dfe8f72e820d30b8eb2e83dac64ef26ba57e84ebf99f67eeba7397e 7582 main/i18n/Translation-eu 7c0540c325065b216de4710d3176c2011da9723732c77d3ae5af8c1254145ed0 3118 main/i18n/Translation-eu.bz2 b34ef48841bdda9281fe2f19d30f8528ac226e1e138cc200f5102d33b25b8623 1228 main/i18n/Translation-eu.diff/Index 1624cd51b51a780eaaf3b9ed2562f900e5315dd7d9ee786280397a84c08322b9 255516 main/i18n/Translation-fi c6630bfd3f85a7b49daf8cfdb80ef2a22241c5a4be7e98a6baa16f047f5a2815 67831 main/i18n/Translation-fi.bz2 9822547daf06842991fe891054b2617a861efff84c602dd052f0a5569fb1c611 27796 main/i18n/Translation-fi.diff/Index a7f00d2f4470bd47e46c2d8f52bc40a07dd5ddc67504f8098cd4987e55339be5 14321629 main/i18n/Translation-fr d92feff84af8f4bff0f736079a6974baadc2f83e04d1997365ab928794909b88 2881213 main/i18n/Translation-fr.bz2 8f5b1af077e3149f2b5d1950d5a9b07799747a2f17acec1dabc568a829e0b419 27910 main/i18n/Translation-fr.diff/Index ce1a70b1000909a09166e30d574c717f3d60ba173bb65ad65e768374dc73232d 1427 main/i18n/Translation-gl fa1eb924fc1473b81f7790ccd909de1dc274f4f266df8af544261f03e1d21079 824 main/i18n/Translation-gl.bz2 f19a00e1b60dc704794da231599c0996daf308174755d58d9d4297c39066563a 34093 main/i18n/Translation-hr 2335f30d9d91a626ce1aa388392003e89530bde116ba126b90f88b710274b5c1 4341 main/i18n/Translation-hr.bz2 3cb2ef619e7d875161d3591ad463725376fa298ba14d34c14a2696eb2afb2d2f 5656 main/i18n/Translation-hr.diff/Index c7d77b32543e3a15dcc0b2cd3ff97be494df858483db7076261100f3b6479c8b 55713 main/i18n/Translation-hu 5e0d60580a33f9203fc93a921d33d464aa7fbfadd6a8c851fa48ed076177990a 19261 main/i18n/Translation-hu.bz2 b17749ec1639238ddf781dd304f11990cdcfcb6899d25015b4ad625123b86cca 10576 main/i18n/Translation-hu.diff/Index c3e3d51b1aca0489b91f204afca10830b8d58e571c10a80202243b5b93eb4f66 3352 main/i18n/Translation-id 210703f8aed2e5878063be79391ae413e91d24a0acd45684d7652a5130a8fe41 1517 main/i18n/Translation-id.bz2 e9020ba1ade9e7f33ee7402e4f749f8540557b1773c5991f094f54aa2c609ccf 1228 main/i18n/Translation-id.diff/Index a90cccc008a62166f1825e074b784730fe245607630b54b37b89e398bac28285 27210140 main/i18n/Translation-it a929066e08d12ab7affa5cf9aee3901f423b21653ca50d8da1d46aa8afb82d24 5233545 main/i18n/Translation-it.bz2 2c54e3d97acd11f4e63cef7bc5d0a575cba8b438e8e4b0e637c15a8719ed6e52 27910 main/i18n/Translation-it.diff/Index 609e48c2f00b5c821d1a3dd61d588bbf718c1ba6d5978242bd3861c03af8d2be 4061723 main/i18n/Translation-ja d2c5bca604a0a6d6daaa0135c16ea2971e3d5036cc057a6fd656b1d9df9e40c5 696024 main/i18n/Translation-ja.bz2 c82a8352e6545421c70d27d9e60d8681fd0a5a07134a49a9da1c8b2e64809a0f 27796 main/i18n/Translation-ja.diff/Index b4d899c568ccb1a4ed95220cb16ea71a871db1d12f2a3f65f431af8738d07fca 10499 main/i18n/Translation-km 046506424d849db2b7ec9832f9cedb204b82c068da9c924b9ed33e4b736ddf8f 2153 main/i18n/Translation-km.bz2 9cc010bc7a17e3470147489077f2bd79401bb199680094164c74e05d6f42a0ae 1228 main/i18n/Translation-km.diff/Index e26e450281f2123f1e9f244611a3b00dd96dea57b7cece027e0958d67c9ec550 2762844 main/i18n/Translation-ko f45fb20d866af71e14dbfba84c383d408fd539cfcc8aefab38ad4c7af569b6bf 612492 main/i18n/Translation-ko.bz2 64037bd5ea0392864df6b2aacf6c4c5b204941780d66d6cc769617cebb99edc5 27796 main/i18n/Translation-ko.diff/Index e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/i18n/Translation-ml d3dda84eb03b9738d118eb2be78e246106900493c0ae07819ad60815134a8058 14 main/i18n/Translation-ml.bz2 16be336bba03786450a43321709eca2fce7fa7b50a135a97da71e16eb5e7d60b 1193 main/i18n/Translation-nb fdec5fc00fe2d0e3c7730462f95273492d278eb8a6957c1b437969833366c217 738 main/i18n/Translation-nb.bz2 16881132cf5f20a4254f1936ed5ee1a64675d381b53ebb945790b3d7d577e976 164194 main/i18n/Translation-nl 9846d686ee3a56b63c6517c48c2ee15f106839f0d6b4c90aa87b481fc184628b 41037 main/i18n/Translation-nl.bz2 4c7ea76b3f8f4c3237b78cfe3f56a629bbc822d5b1cc0ba7a7b0d2ee6d09a5b3 27796 main/i18n/Translation-nl.diff/Index 4c29f863edc8cf2cb2a32fd0ef48577ae821b2acbe0c295cb66edd7566e02493 2788050 main/i18n/Translation-pl fc9f8ff0821db67ace529174dd4a80d2f26f3ee4588e63eabf21f4afeac19120 643116 main/i18n/Translation-pl.bz2 1e9c742afd33e3110bb32b9f9bb76dfb1b0b1f61f75f9c1d0087fe1c11519aa7 27796 main/i18n/Translation-pl.diff/Index 9d771ca0eee97779dca4e523e085eb3957a9dd7db9fed96e655129ba6f73f100 946075 main/i18n/Translation-pt 40600a6494777baadf28860b93c1f284c451a7ec29a67b5ef65a13239d230c1c 232288 main/i18n/Translation-pt.bz2 80296015448f66ee729bab18c364fd919594c65b82480b4f3f40c046d1086dc2 27796 main/i18n/Translation-pt.diff/Index 23c3b2559ec6de2697088e24485714a2039ba998f6e4499f00761b81de06837f 336 main/i18n/Translation-pt_AO a2a19c81386819c63f502f9b9778fbfd1dcd4938efd6a17660b3d9aeffc92ece 272 main/i18n/Translation-pt_AO.bz2 5a5023744e9bdae5a2a2c1bc8d2b6ff6eea1af4baae6db683d29ad8458edc587 4080960 main/i18n/Translation-pt_BR 0925038e2449998e7a64235044df17d7d47a041160c68f0c43a1de7750ebfb5d 970585 main/i18n/Translation-pt_BR.bz2 8cc74f28d5c01f85048e7f8bb9160c9d5e2b603bd4a4a202f246c5a141d74017 27796 main/i18n/Translation-pt_BR.diff/Index c90708ca8975ced4acf4be98a4ac1f5c8092fd826b4d928e35c3650e705553d4 1717 main/i18n/Translation-ro 35f2449dba7bd93e0aece908f4c4de53cc864a48c8f7aeaa5a64f67384e1bcda 982 main/i18n/Translation-ro.bz2 32afdd77edcd67097fe6b50974067d6220be991a02beb3d2a37e9ad691fb0686 2953988 main/i18n/Translation-ru 103ffd37259456fcfeaca5013a31a3a690b93a4c280273dc72c7288bdc902aa4 474345 main/i18n/Translation-ru.bz2 6f5b1d3322c579b31f3646af6bc3851903ceafdbfd38ddcc531c0578d772b3e3 27796 main/i18n/Translation-ru.diff/Index 1dc7b4579ba6db1a5baf442ebf56127f66b9b38d570aa60ba54ff62030366d5e 5441319 main/i18n/Translation-sk f4543ebd5ee07ddc415ef6c278a82d6a6537e0fe8e3a607dd40eb9d66a3eab37 1150783 main/i18n/Translation-sk.bz2 f8f80b6986a659fbbec96936326b7686aa89b3a2864882f1bc4ce28da5a1814a 27796 main/i18n/Translation-sk.diff/Index 0b3dfcf4bd2fa3e676c48101f18f9f66332c9c8d98b65e1f86b2c739801b8033 297825 main/i18n/Translation-sr 9febec44c9637de7d1edb402f09984b41a249a6cfc3f41a4e33f7ecfcbd873e8 49802 main/i18n/Translation-sr.bz2 e8f0ffdf768e9765f8e36699533c034f3fb3314ed58e7b875f0e663d853ece21 27796 main/i18n/Translation-sr.diff/Index 340be8a51255b7eefc3cc86199d8c909fafb0d4d1e7ec34f6eb8bb7ecbd4a73f 78017 main/i18n/Translation-sv 52823b82a756e0709afe0348c2716089bc14e273efd982bec02165290aacff4b 25030 main/i18n/Translation-sv.bz2 2bb77b70019e801312627e857e572f289f89adf02941e87cb5104c9000e03aba 14512 main/i18n/Translation-sv.diff/Index aa7e2de78da8eeaad9a260fbbc17a5b92d30317809933fc4e13cf7130c12c723 14884 main/i18n/Translation-tr a618b4efa21e5d7f35b0d093a3dcce47772a37c92a76d97cb02aa00cb8ef3ec9 5520 main/i18n/Translation-tr.bz2 4ff546643cf2b48048d6d218f38dcc28212e6a4e8586fa2e72c5f2e4ce4a8593 4180 main/i18n/Translation-tr.diff/Index 951cee68c692643e45dac742f0ec8c47222afc47a2d2c2de8cdc74db0c703efa 3392641 main/i18n/Translation-uk b0e68152ac06301bd4742a44af288a8e8de16515e8658b4ca4a931858c3e4ac1 505704 main/i18n/Translation-uk.bz2 885f3fd62596c978fd0d9b8d33b8691cf167764c396a752f2edf35c9493ebe02 27796 main/i18n/Translation-uk.diff/Index 66e6907338988b2b3dae532b80297580e418b8f91276cdab148b58904f94125d 18784 main/i18n/Translation-vi 5552b1e64d0441872553ce5092c2a425dc60b0abbef6b0ca4bd4f980994d8775 5759 main/i18n/Translation-vi.bz2 339a108a94a27094c5efb98d8a303eae4676cf6fffa098c3227bec1124320ddc 736 main/i18n/Translation-vi.diff/Index 7133134d1b1b6c869b4b700fed9778e93a0b774391402ad3399e9ff46984efff 2007 main/i18n/Translation-zh 8cbeadbbcec613b8476f8e2aa40b15772909109e10a83317c111fcf7c28d0219 1215 main/i18n/Translation-zh.bz2 45ace25e50416def3f19678e1a25241f969f3e5d33df6a4dfa0dc19effcd3082 523044 main/i18n/Translation-zh_CN 58219ecb58e4f00ee9508ef12bcbfc5a0caaf037a1cf1e244c8f1364480fd995 123328 main/i18n/Translation-zh_CN.bz2 ff3ab388427bc38babde07ef82c60b21190bef5e21d603cceb13cb4a3b0e3b85 27796 main/i18n/Translation-zh_CN.diff/Index 5c72d7d612156780cdd4438162bb1ab01d5e6c324f90f62c22cc33aa275a2f14 33003 main/i18n/Translation-zh_TW d1d86ecf354a67a5cf7bd1945b763eb7d279f73d7988805edb80a504c1bd86bc 12432 main/i18n/Translation-zh_TW.bz2 f1e7b8308de71a4a68b685360762bbab14b3d68df70f31b872f9e017bd8c9f9d 6148 main/i18n/Translation-zh_TW.diff/Index 143ad35e650711c8ba02f2b645eab4ae6eddec0f807b375b65702ac77daaafeb 58561 main/installer-amd64/20230607/images/MD5SUMS fbf9b5a60c8315abbfdf94935bdfce3b16089639f0c2c2a3aca5e1e94d8b8503 78509 main/installer-amd64/20230607/images/SHA256SUMS 143ad35e650711c8ba02f2b645eab4ae6eddec0f807b375b65702ac77daaafeb 58561 main/installer-amd64/current/images/MD5SUMS fbf9b5a60c8315abbfdf94935bdfce3b16089639f0c2c2a3aca5e1e94d8b8503 78509 main/installer-amd64/current/images/SHA256SUMS 75cde65e6a7597ac52e76eaf8ec95bb1f70628c841ebe33877d979530ed54a87 92659 main/installer-arm64/20230607/images/MD5SUMS 4b0884bd001ca61df06cee22fd49fd20a998443dd5ff4c728a9ae5e3725c1989 126815 main/installer-arm64/20230607/images/SHA256SUMS 75cde65e6a7597ac52e76eaf8ec95bb1f70628c841ebe33877d979530ed54a87 92659 main/installer-arm64/current/images/MD5SUMS 4b0884bd001ca61df06cee22fd49fd20a998443dd5ff4c728a9ae5e3725c1989 126815 main/installer-arm64/current/images/SHA256SUMS 67620ff3d7a2b7e29e55e414013087308a79eba1818a4e8f2321792a2e4f3329 20336 main/installer-armel/20230607/images/MD5SUMS 4029ae03d65bbd9ccffdcfa4d8669afe8fe99a22a666aafd3670aa2e0673264f 28412 main/installer-armel/20230607/images/SHA256SUMS 67620ff3d7a2b7e29e55e414013087308a79eba1818a4e8f2321792a2e4f3329 20336 main/installer-armel/current/images/MD5SUMS 4029ae03d65bbd9ccffdcfa4d8669afe8fe99a22a666aafd3670aa2e0673264f 28412 main/installer-armel/current/images/SHA256SUMS 1211a06e2ac07005b31aaac92ebb3d3a54781980bd61ed061e9746f226668ad7 76534 main/installer-armhf/20230607/images/MD5SUMS 35d878d792be7b149c14454603c00053697f928c795d2bc4ed133d4159eb8bc9 110082 main/installer-armhf/20230607/images/SHA256SUMS 1211a06e2ac07005b31aaac92ebb3d3a54781980bd61ed061e9746f226668ad7 76534 main/installer-armhf/current/images/MD5SUMS 35d878d792be7b149c14454603c00053697f928c795d2bc4ed133d4159eb8bc9 110082 main/installer-armhf/current/images/SHA256SUMS 4c1cf3f297d9fdf0ab6f833a3d0ab68cf4c9c6115cc548c4978fc03302026327 104 main/source/Release feef53b7356da17401a3df531137754eeec105a7f23c4dcebd70f85f9479f35e 54636355 main/source/Sources 459a0ddfef32df8ddb9b898f4d5c78dff668ac2776579edbd25ec83024fbbf52 27910 main/source/Sources.diff/Index 0009ddfbbd21f5e9683d141a88675685cf95f27ea5c61d2bff5933f8c012abf9 13382715 main/source/Sources.gz 2e21e136912c227138135facb49b979125c94f0b34a2c91130fdb2a670280418 9973020 main/source/Sources.xz 5d4071730617ee6a26218a52c8d442be32f9bab67b39d52a36806c293cb237cf 424664 non-free-firmware/Contents-all 9418af052fd887b0b78787734bbc0ad3c1b81b9da957f6aa3ac3225b0422b0a6 10084 non-free-firmware/Contents-all.diff/Index 224e319501f863fe8b09289fba5881fdaeacaf57fa92acac80d00827a89b4c14 23291 non-free-firmware/Contents-all.gz a8c79e619795bd48b4b146cba06990bd870f85e46c4ccfd4ba927c919d670d8b 15889 non-free-firmware/Contents-amd64 053274f1ccaed1517ed55e0fd8f9270ed785bc6239e0bcf9b2684260b90d3abd 9100 non-free-firmware/Contents-amd64.diff/Index d1c2cb071de53e31cde8ab309c03f450bab769a34f64d6d7141fe27abfd3a97e 1093 non-free-firmware/Contents-amd64.gz 6b80614b68a8b6b808ebd95735e0dcc6f17cc8e21cad33051ee7b3cbc03f3ede 1769 non-free-firmware/Contents-arm64 a1378bea1f88b18483f9450a36b3418a4805287d438e21cc6be38d3aede99fa3 5164 non-free-firmware/Contents-arm64.diff/Index c8a9404ee2f6e362e703070337e8418a9bdde35fbba6f0203bacd789bfc043d7 290 non-free-firmware/Contents-arm64.gz e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free-firmware/Contents-armel f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free-firmware/Contents-armel.gz e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free-firmware/Contents-armhf f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free-firmware/Contents-armhf.gz 60da67bc32f5b3d2fab9b4e1aacde5149f153588e32bd934338f3e0d5c719681 221869 non-free-firmware/Contents-source 575032fc00107ce1355fe60112a34eefd048b83f51b1c2bd6a87f13b0fe25c36 19432 non-free-firmware/Contents-source.diff/Index da210e3af8a951bc40619ec88a2a54e34f4dc1bfb9356e8de2ca8809c7e56f55 24074 non-free-firmware/Contents-source.gz e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free-firmware/Contents-udeb-all f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free-firmware/Contents-udeb-all.gz e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free-firmware/Contents-udeb-amd64 f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free-firmware/Contents-udeb-amd64.gz e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free-firmware/Contents-udeb-arm64 f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free-firmware/Contents-udeb-arm64.gz e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free-firmware/Contents-udeb-armel f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free-firmware/Contents-udeb-armel.gz e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free-firmware/Contents-udeb-armhf f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free-firmware/Contents-udeb-armhf.gz 97e3a95a41f4935dca3599b7ec18981106dda1d77bb347dd0f7559cff94d2ea5 29021 non-free-firmware/binary-all/Packages 386ec8a3c43dcf86d67647a10196aca896b33d6ebaa3a73babcfdbc512f0d410 12052 non-free-firmware/binary-all/Packages.diff/Index 00a31b363da5bd10eed786f6f543ee016ba5658e112b07c7a047e423b2972286 7060 non-free-firmware/binary-all/Packages.gz 3942406c130ab1dbc5cbf9025dea87347855bb20ec3b625021307209ea5815bd 6316 non-free-firmware/binary-all/Packages.xz a8e28440f28ff05df7b33c792f11fa96a3fb9582d520d453bfe3f89281efe5a9 114 non-free-firmware/binary-all/Release 28ce1615d0b130d7f741a63e683cad51faba5c59c70144bfeb4a038ec754855e 31865 non-free-firmware/binary-amd64/Packages d025dc33d9333e614aae49997f3b1c76731cd5de7294eee821000edc9f70ab85 27796 non-free-firmware/binary-amd64/Packages.diff/Index 0c49aad41caa0a4a5497fe191321032c9860da4f25259baaf118b9be87757729 7903 non-free-firmware/binary-amd64/Packages.gz 4d8ed598695f83efba704c91dda0d88cfc9a6a4eab41b68fff85b43084b2567d 7036 non-free-firmware/binary-amd64/Packages.xz 9ae2322ebb93ccb5f042a9f12ac522275da0842ca29f38335520f9e3fc6d8c02 116 non-free-firmware/binary-amd64/Release b1f49e6eb1e4764878d29da8b6ff4fae03ee0ea9f991de27caddf9e023dcd072 30519 non-free-firmware/binary-arm64/Packages a39e24285d2ef74c1487a64848d02b0348100502e47fe11cb501d3a29a75cac1 22384 non-free-firmware/binary-arm64/Packages.diff/Index 553b7e3fb8dbb7188e6c93b0f6640b8c1a70ce4fcaba9b2e40f2602e170044de 7429 non-free-firmware/binary-arm64/Packages.gz 7929124377b079abb7ee8de1274a6dc188682ede86989b8962298a051cb723aa 6644 non-free-firmware/binary-arm64/Packages.xz 5d0478bbd0a2e311360a35551900cbfc8aeee2a62e3422224a22fc3f8bd2f07a 116 non-free-firmware/binary-arm64/Release 97e3a95a41f4935dca3599b7ec18981106dda1d77bb347dd0f7559cff94d2ea5 29021 non-free-firmware/binary-armel/Packages 386ec8a3c43dcf86d67647a10196aca896b33d6ebaa3a73babcfdbc512f0d410 12052 non-free-firmware/binary-armel/Packages.diff/Index 00a31b363da5bd10eed786f6f543ee016ba5658e112b07c7a047e423b2972286 7060 non-free-firmware/binary-armel/Packages.gz 3942406c130ab1dbc5cbf9025dea87347855bb20ec3b625021307209ea5815bd 6316 non-free-firmware/binary-armel/Packages.xz 1fb3bee08bb47f5afa62ed7fcaaffe46c15e74bc02070673f7d0037a70a7d61c 116 non-free-firmware/binary-armel/Release 97e3a95a41f4935dca3599b7ec18981106dda1d77bb347dd0f7559cff94d2ea5 29021 non-free-firmware/binary-armhf/Packages 386ec8a3c43dcf86d67647a10196aca896b33d6ebaa3a73babcfdbc512f0d410 12052 non-free-firmware/binary-armhf/Packages.diff/Index 00a31b363da5bd10eed786f6f543ee016ba5658e112b07c7a047e423b2972286 7060 non-free-firmware/binary-armhf/Packages.gz 3942406c130ab1dbc5cbf9025dea87347855bb20ec3b625021307209ea5815bd 6316 non-free-firmware/binary-armhf/Packages.xz a7964a32acad4d5be8eb82d559bfb760768888fff495bc02cd0ee61a91ac8fa5 116 non-free-firmware/binary-armhf/Release e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free-firmware/debian-installer/binary-all/Packages f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free-firmware/debian-installer/binary-all/Packages.gz 0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free-firmware/debian-installer/binary-all/Packages.xz a8e28440f28ff05df7b33c792f11fa96a3fb9582d520d453bfe3f89281efe5a9 114 non-free-firmware/debian-installer/binary-all/Release e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free-firmware/debian-installer/binary-amd64/Packages f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free-firmware/debian-installer/binary-amd64/Packages.gz 0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free-firmware/debian-installer/binary-amd64/Packages.xz 9ae2322ebb93ccb5f042a9f12ac522275da0842ca29f38335520f9e3fc6d8c02 116 non-free-firmware/debian-installer/binary-amd64/Release e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free-firmware/debian-installer/binary-arm64/Packages f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free-firmware/debian-installer/binary-arm64/Packages.gz 0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free-firmware/debian-installer/binary-arm64/Packages.xz 5d0478bbd0a2e311360a35551900cbfc8aeee2a62e3422224a22fc3f8bd2f07a 116 non-free-firmware/debian-installer/binary-arm64/Release e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free-firmware/debian-installer/binary-armel/Packages f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free-firmware/debian-installer/binary-armel/Packages.gz 0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free-firmware/debian-installer/binary-armel/Packages.xz 1fb3bee08bb47f5afa62ed7fcaaffe46c15e74bc02070673f7d0037a70a7d61c 116 non-free-firmware/debian-installer/binary-armel/Release e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free-firmware/debian-installer/binary-armhf/Packages f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free-firmware/debian-installer/binary-armhf/Packages.gz 0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free-firmware/debian-installer/binary-armhf/Packages.xz a7964a32acad4d5be8eb82d559bfb760768888fff495bc02cd0ee61a91ac8fa5 116 non-free-firmware/debian-installer/binary-armhf/Release 9f1af6ba9c84a38a6f224dedc4262de98fada2a567395c755636444c196f2425 378149 non-free-firmware/dep11/Components-amd64.yml eeb5fc70bcff5564e64656c5295fd3fee6548d1cfad9010b1f4f2cce24187085 34379 non-free-firmware/dep11/Components-amd64.yml.gz 5da2635e92f20cb0bdd2bd4aaab4e280473c3d6c3a7bc06855a809ce3422c346 20616 non-free-firmware/dep11/Components-amd64.yml.xz f39605fb004c4fceeb4b5107f8884bfc3d0b965f0ea1541bac8ca3c734d79c53 378149 non-free-firmware/dep11/Components-arm64.yml 216ff50681e72a2d6a9342f9c0f26bd941863a33b412030c4471cb52efd71287 34357 non-free-firmware/dep11/Components-arm64.yml.gz 1e99427c5f6463222bdcbff7ad71b2b30773f730b844baf1db0a4443cbfadd17 20796 non-free-firmware/dep11/Components-arm64.yml.xz 734c85d71c3284a05bb305a3147be1ae813b649b072bb8d13b9f30abeace27fe 378149 non-free-firmware/dep11/Components-armel.yml df5c1127084edb7c5fbeede373e86ee66970a1219df12febd37880eddbd2b544 34377 non-free-firmware/dep11/Components-armel.yml.gz b70e285cf07a52b0d2bf1b8395a7d27e4335fb7e6362ccebeeae926fe3e0e78c 20944 non-free-firmware/dep11/Components-armel.yml.xz 1c9e79c126ec5b0a9eae9d5ae0f5dde7a5dd1ca510116d8049e1852d907d4459 378149 non-free-firmware/dep11/Components-armhf.yml d6c8c8d2c2017611f92c66ecbe83de043aa63cfec0160cfe3086135ad7acba06 34513 non-free-firmware/dep11/Components-armhf.yml.gz 0e3de38992594155a27193311e025df4d836732bdf0510aa6340be626acacfe6 20888 non-free-firmware/dep11/Components-armhf.yml.xz 5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef 1024 non-free-firmware/dep11/icons-128x128.tar 7989bb311baa38ef545250282aa065d23281c46dfb8faabe4c653487bdbded5c 29 non-free-firmware/dep11/icons-128x128.tar.gz 5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef 1024 non-free-firmware/dep11/icons-48x48.tar 7989bb311baa38ef545250282aa065d23281c46dfb8faabe4c653487bdbded5c 29 non-free-firmware/dep11/icons-48x48.tar.gz 5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef 1024 non-free-firmware/dep11/icons-64x64.tar 7989bb311baa38ef545250282aa065d23281c46dfb8faabe4c653487bdbded5c 29 non-free-firmware/dep11/icons-64x64.tar.gz c2faf83556c688649c4e155c7b0bee75499063310f98a974f98b43804c511ea5 15168 non-free-firmware/i18n/Translation-en c8d4bc91102533dcbaa659cba0a210ad20c9096ad8a20f4c246e37dc27141a31 5656 non-free-firmware/i18n/Translation-en.diff/Index d530bbb84930e13ae46e347955d575afe5b776c8180d5a4285c0a71fee618247 4796 non-free-firmware/i18n/Translation-en.xz 7338e3808e4fee29ef2b70e1e65604d6d6513b6b000d6e80b2d656e8047a59f6 117 non-free-firmware/source/Release ebf754e90a1c0474321798f98222c0b00c258b962e7a47bc317729f66815820c 31740 non-free-firmware/source/Sources c0e363f63ef242b21a79b81e68579bdb949c5f192574cec9dc3609f5134edeba 27796 non-free-firmware/source/Sources.diff/Index 09c89008e7a471e8969ce5763b9f12a8bc358d24a54d17837ff6fafd9b23745d 7539 non-free-firmware/source/Sources.gz 78f8e0386cc392213ae5377ee446a723634efe50ec3c3e9f33d0c22cd8bd1c45 6948 non-free-firmware/source/Sources.xz 91c63857b46ace7720d0895e60a65ab74fbcf585f0d5171b9ce36b2ec2aceaee 14898506 non-free/Contents-all 22f8bba1ddf807ca4e1ee67c0326645461c63add2d7d6ad9d93b6d53817569d0 27910 non-free/Contents-all.diff/Index 142d80e58d13b9d6e2ec7279e8970b5ad97d778e2fd464bdc688725780f85ad6 785223 non-free/Contents-all.gz 99a4219387827e8e4a6ac30c92b52a10aee675957ac9f0e9cf8796de35050d1d 985673 non-free/Contents-amd64 51a0cc1458b71fbc50fd9b1586890bec7c76bb6eefe31447f85aac7ce34ce949 27796 non-free/Contents-amd64.diff/Index d3f806d9fdce18954051c0897740e251260f24d9428f432a2523addb8812b16c 75457 non-free/Contents-amd64.gz a20d44e8cc8cfcf8c5476bbd13c596ee776343a20e639bd5b451023a46a6cb3e 567043 non-free/Contents-arm64 031af7d9dd7c51b034a62acb510e9571ff80ceb91bdd31754cc05ebb13cf3f6e 27796 non-free/Contents-arm64.diff/Index e22b22f453947a1cd1bd2ce08ea0d431a6b104fdf97df7cd2b95bc0ed9ca2746 44801 non-free/Contents-arm64.gz 19971fabb1d37dc42f3b3dcb2cc2cde63487dbf6217c476f488552fe5a20bbd1 149034 non-free/Contents-armel 885ac7be1c9fd314a3de288bf8bbe6e9a709890bdff31b41d566f590185131d4 15988 non-free/Contents-armel.diff/Index c1060eb5af8b3f219a96ddb901c72360cd09263576e4e7bc56eec65ac793545d 14071 non-free/Contents-armel.gz 5a14c05698b8589d90f9bbe9887101eaf581cc67a28ef1ce057cec73afb3c854 158838 non-free/Contents-armhf 7ce1de92ad28a9d6c19012717aaab6b032237d4e9fc60ea93dd2066ca3289447 17956 non-free/Contents-armhf.diff/Index 1cc45d3572b112784a297839bc50bda97a8e3db66a053d0387f75fdfa68eee9a 15250 non-free/Contents-armhf.gz 7c4f9850a14d372a4343d93f93433eb89a31371ba6ba40af9ef0230ed31b1593 9738587 non-free/Contents-source bbf19ab32a6747147aaca2d3ea74a4a4343b3bbe856fac5e32c419464d5ff12b 27796 non-free/Contents-source.diff/Index 240b3fb4570ea908d5f852eb1a0d45aa7069661a871e0c8908c96da68c1b3872 979973 non-free/Contents-source.gz e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/Contents-udeb-all f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/Contents-udeb-all.gz 53e97ea5da70d8e815d9bf8ca8a61a437bd6ff88f4651b6c255df04295b422a3 480 non-free/Contents-udeb-amd64 b7f7488fee92b44e9cf035a9118d9bd4a6ea63e6d340ba673b4a8fea49c1bed0 114 non-free/Contents-udeb-amd64.gz 53e97ea5da70d8e815d9bf8ca8a61a437bd6ff88f4651b6c255df04295b422a3 480 non-free/Contents-udeb-arm64 b7f7488fee92b44e9cf035a9118d9bd4a6ea63e6d340ba673b4a8fea49c1bed0 114 non-free/Contents-udeb-arm64.gz 53e97ea5da70d8e815d9bf8ca8a61a437bd6ff88f4651b6c255df04295b422a3 480 non-free/Contents-udeb-armel b7f7488fee92b44e9cf035a9118d9bd4a6ea63e6d340ba673b4a8fea49c1bed0 114 non-free/Contents-udeb-armel.gz 53e97ea5da70d8e815d9bf8ca8a61a437bd6ff88f4651b6c255df04295b422a3 480 non-free/Contents-udeb-armhf b7f7488fee92b44e9cf035a9118d9bd4a6ea63e6d340ba673b4a8fea49c1bed0 114 non-free/Contents-udeb-armhf.gz b92ea40c0d23157e1834fefb038a4f3d5e208148a9c5d1d32aa7683097dbaff3 220739 non-free/binary-all/Packages 33221085c59ae874a8d999f5358472a0c6421ed55fe14707fbae54fb572513ac 27796 non-free/binary-all/Packages.diff/Index 3c36ebafb0f0b283973a756bcb8cee7b17e7a97cdf41489ea555b14ab714ee11 52221 non-free/binary-all/Packages.gz 2ce35f11a6bd7c79d412d8072ff5d96b66b849aee7eb17f01891d5735c18574a 43344 non-free/binary-all/Packages.xz 99c9bba8428e4c59a626a2ff61b8061ceb448d7a59658d92e906425c4206e849 105 non-free/binary-all/Release c653c773fd498e40488c7670f3364f52ae535312a8bb1657324d78b3841f6631 597497 non-free/binary-amd64/Packages a954a81bd80c97bb71496c73f4bd21556a85a22b801d2b7e4e3eb8b53d8becda 27796 non-free/binary-amd64/Packages.diff/Index cc0bad244dadad77d970bdace6529b94b50ddc1e1dfd1786b80a6490494b0fd9 122578 non-free/binary-amd64/Packages.gz 7b2d4702cd5f47108644481974f84ea3c0f28d32f7b29dd00fba8b5d84ff8064 98068 non-free/binary-amd64/Packages.xz bdab55d88a0b0acd7ee89a668a1044b69d1b57313834d8e6767b815e39900d54 107 non-free/binary-amd64/Release bd1ce9769625118b282db8aa22f03453a9ec195789d19d46f089f6c128917442 410729 non-free/binary-arm64/Packages d9882c43cf2daeb57b7cb3b30d9f06c805a778d1ffa4db9ac08c961a535f82f0 27796 non-free/binary-arm64/Packages.diff/Index 939b97acc1edede52667e6cb674ca4c1f1e59cfbaec860b0bc6ff844f07338e3 93016 non-free/binary-arm64/Packages.gz 926b6f1e2e2cd757cf6546785443acde32f73cc7fe3eaabc82b3fb6fcc082ae4 75384 non-free/binary-arm64/Packages.xz d2f86895e67badf476812567224994c1736e4d895a299c5c6c794a6f8f6afad2 107 non-free/binary-arm64/Release 745403b4004bab1011822418c5f0fe6f32a3a5629395dc004bb68ed521c9682b 270311 non-free/binary-armel/Packages 9085a833bb5c7ee97d7fe81c184d7f4f6303570f4e18701eb0c1408092d8ea6e 27796 non-free/binary-armel/Packages.diff/Index b84d270c62a66eab7a488338c4e6664ab91ee0962649fdce82389ca92634f30c 66160 non-free/binary-armel/Packages.gz 225335d8082721ac63458619a8576c4fad7a1e5e01aecf8190fc74dc0ba6a312 54356 non-free/binary-armel/Packages.xz 870250d2289eefb55c5d6742c2bfbf35c8049819f18cccfb547ae930154ac422 107 non-free/binary-armel/Release 8ebc1a931a0f4d918c665d172f9f76b4a93f6d392b07ece14974b069cdafdab7 278052 non-free/binary-armhf/Packages 802e35ac2a14fc428def634a23133ff6ce8175e514f988f83c48fce93ee23fb1 27796 non-free/binary-armhf/Packages.diff/Index 7000f17d9fa0d205a50e1e9fd20d78aef52ca6766c756e223f3e3fd5a7b8015c 67863 non-free/binary-armhf/Packages.gz 9347685a23ccaf548bf2e50f1909dea49084a2b8e295aa5aafc24323bbf0de0e 55888 non-free/binary-armhf/Packages.xz adf1dde66d1c282ef8d22cdfe5915ed3b255c5747347dd99171ef5133cb56ad9 107 non-free/binary-armhf/Release e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 non-free/debian-installer/binary-all/Packages f61f27bd17de546264aa58f40f3aafaac7021e0ef69c17f6b1b4cd7664a037ec 20 non-free/debian-installer/binary-all/Packages.gz 0040f94d11d0039505328a90b2ff48968db873e9e7967307631bf40ef5679275 32 non-free/debian-installer/binary-all/Packages.xz 99c9bba8428e4c59a626a2ff61b8061ceb448d7a59658d92e906425c4206e849 105 non-free/debian-installer/binary-all/Release e15f9550a04f337aaca0db2295ec8dae7b333bd6d9fe85e818d933ff6f8acd27 607 non-free/debian-installer/binary-amd64/Packages 87eb4085fa1e71ef4f53c9ed3b5a415dc37395141d888ee4e8848a63cc94e4a5 413 non-free/debian-installer/binary-amd64/Packages.gz 0efa00fc748e86d01c0b52a88defc7584886c6818072a7e0c5e598206f2fc1fd 492 non-free/debian-installer/binary-amd64/Packages.xz bdab55d88a0b0acd7ee89a668a1044b69d1b57313834d8e6767b815e39900d54 107 non-free/debian-installer/binary-amd64/Release ed5c887687bb4929955f7b93c47cf4e7d6caff2c9b765a48cc3002ce3faccb11 607 non-free/debian-installer/binary-arm64/Packages 1999c2cdfcc520fe3af7aec93defc2d02b62832df40e798731be0f5ea3f9a894 412 non-free/debian-installer/binary-arm64/Packages.gz 94dfd6b6edb70f510be11f1748c2bbdf11840d45fa25c2bfeafb2e058f402b44 488 non-free/debian-installer/binary-arm64/Packages.xz d2f86895e67badf476812567224994c1736e4d895a299c5c6c794a6f8f6afad2 107 non-free/debian-installer/binary-arm64/Release 9a4f9dedeccc422cbac74e875b27e9db029378a1d03c9a5e953bdb55e84fa9bd 607 non-free/debian-installer/binary-armel/Packages c8239018ed5d50d881bd3b5d6a879e53d2620ca59a68a3aac141e7159c794e15 411 non-free/debian-installer/binary-armel/Packages.gz 387962963c407195614eda41d610a61176224c97ee96368ce58bf611090d6f85 492 non-free/debian-installer/binary-armel/Packages.xz 870250d2289eefb55c5d6742c2bfbf35c8049819f18cccfb547ae930154ac422 107 non-free/debian-installer/binary-armel/Release 4111491a78e2be778ee86cba9542d7382ee522013247ffca3ade4e56b38bafe5 607 non-free/debian-installer/binary-armhf/Packages 2de9422d927494fa486943539b7ed1fd41678cbd385ae3a4383d2f089d7da57d 412 non-free/debian-installer/binary-armhf/Packages.gz d1cb0e08fa424389ecd207c419619f6b3d8e8f834e5b7147b99290a18d1f1a2a 492 non-free/debian-installer/binary-armhf/Packages.xz adf1dde66d1c282ef8d22cdfe5915ed3b255c5747347dd99171ef5133cb56ad9 107 non-free/debian-installer/binary-armhf/Release 96da9bcf2c06bbfab310500a1c476ce444f8a318441fb127c15343b553911608 11715 non-free/dep11/Components-amd64.yml ef5058416560ae2f5984963735d16563fe2f0d7ab1e1aaf19e64ce703a0224ee 3610 non-free/dep11/Components-amd64.yml.gz 17086dfd821cadf471851484705aca8f5c0653ed5ca988e5d01acde96adfea94 3492 non-free/dep11/Components-amd64.yml.xz c69f4bbfc4b1ace4034894e0e9f90a36e4e02e599b5be6414bf1a98f565d4e66 5893 non-free/dep11/Components-arm64.yml 298297d9e178a5467984ddd1a8e425b59d98b3c9274135f4c9cd332751e6d787 1786 non-free/dep11/Components-arm64.yml.gz 3248095356555a16336d774367bfadfdf969230a6062efa581e8195a595cd640 1844 non-free/dep11/Components-arm64.yml.xz ee8c7d3af7ac3fd110449d2dbb75ef04fe2a4efae034fbb5b5762a3e784a8339 5893 non-free/dep11/Components-armel.yml c82b21c9f91d8537bb66b72495a16e9d05ca44b1a71f2925bd885427ce556c3b 1785 non-free/dep11/Components-armel.yml.gz abc790ab0899a56b11aeba4c84639b4c3be52a06353c86cfa57082582a08e726 1852 non-free/dep11/Components-armel.yml.xz 0a7327a21bfc19534ba47103033c8a6e5f81f467fc03b878c5cc59223c199244 5893 non-free/dep11/Components-armhf.yml 626ee28366d36be0e88ddfe46420832736b4920a41df04763406f1eefc9605bd 1785 non-free/dep11/Components-armhf.yml.gz 8708ee62012cc8e4b3d969ed66bfce1e5f5dc3768fe2dc86fc13b2df7703502b 1856 non-free/dep11/Components-armhf.yml.xz d38600d75d6cdf282e37f9acdade6cba278f583fbc32a6ac994d6e8bfe4a751e 7168 non-free/dep11/icons-128x128.tar 2f1c57c68df2bd2c31a6c86c61b857e3ed367efc7a419e62a7e6e5fe60f1b039 2167 non-free/dep11/icons-128x128.tar.gz 925885b4612bc30eeec0c37fe8f99ffdcb19e4a8af35a64436da074c7306d130 3072 non-free/dep11/icons-48x48.tar 3a9aa93874c7df26641c8e6b879fa54e2dcea90bdbc922f8fe38cd7b6b26b1df 578 non-free/dep11/icons-48x48.tar.gz 2228c3a53742503986e6e7435b4357970094e019a691d6f3d027ff7267f22bdd 17920 non-free/dep11/icons-64x64.tar 2cd7f6a2e9e53a4d3121d00c8d98a815148d300bbb4a1887c0a52d3c935aa618 12030 non-free/dep11/icons-64x64.tar.gz 1fa87cb885789abb60f5505628b2bc129637c193ba406332939ffe4bc450d5c8 420788 non-free/i18n/Translation-en 4a48748a5c2532e2e7ab8ceb287f74de4b404f8e6f677157f3c1071ae195a8fe 27796 non-free/i18n/Translation-en.diff/Index 52ead5815ba59be68a8b4312dd14b2959e704298e68bc0a4687a3ffecb8fb0ca 67232 non-free/i18n/Translation-en.xz 5817d1b66355cca8704653e473eab6c4e0eec884914abd452a801787789d5509 108 non-free/source/Release f17cd43097b8a101b6354ad4896ecca478167e83e20cf57bdff159b174bcc2dd 336581 non-free/source/Sources 1e24b7b092ab916227f73a40585f4af18de40ecac802588b44bb484921beb710 27796 non-free/source/Sources.diff/Index ba1490e724a764ee6fa3b3c701331571608facfdea15252846b4e7e8f9878d4e 90508 non-free/source/Sources.gz 22ca387b16b8b54c9fbec0c0ea5b03fa0b5e9b52811f63aca89b07d6d6ce4b5a 74912 non-free/source/Sources.xz debian-control-0.2.14/src/vcs.rs000064400000000000000000000204711046102023000145440ustar 00000000000000//! Version Control System information use regex::Regex; use std::str::FromStr; /// Parsed VCS information #[derive(Debug, Clone, PartialEq, Eq)] pub struct ParsedVcs { /// URL of the repository pub repo_url: String, /// Name of the branch, if not the default branch pub branch: Option, /// Subpath within the repository pub subpath: Option, } impl FromStr for ParsedVcs { type Err = &'static str; fn from_str(s: &str) -> Result { let s = s.trim(); let mut subpath: Option = None; let branch: Option; let repo_url: String; let re = Regex::new(r" \[([^] ]+)\]").unwrap(); let remaining = if let Some(m) = re.find(s) { let substr = &m.as_str()[2..m.as_str().len() - 1]; subpath = Some(substr.to_string()); format!("{}{}", &s[..m.start()], &s[m.end()..]) } else { s.to_string() }; if let Some(index) = remaining.find(" -b ") { let (url, branch_str) = remaining.split_at(index); branch = Some(branch_str[4..].to_string()); repo_url = url.to_string(); } else { branch = None; repo_url = remaining; } Ok(ParsedVcs { repo_url, branch, subpath, }) } } impl std::fmt::Display for ParsedVcs { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { f.write_str(&self.repo_url)?; if let Some(branch) = &self.branch { write!(f, " -b {}", branch)?; } if let Some(subpath) = &self.subpath { write!(f, " [{}]", subpath)?; } Ok(()) } } /// Version Control System information #[derive(Debug, Clone)] pub enum Vcs { /// Git repository Git { /// URL of the repository repo_url: String, /// Name of the branch, if not the default branch branch: Option, /// Subpath within the repository subpath: Option, }, /// Bazaar branch Bzr { /// URL of the repository repo_url: String, /// Subpath within the repository subpath: Option, }, /// Mercurial repository Hg { /// URL of the repository repo_url: String, }, /// Subversion repository Svn { /// URL of the repository, including branch path and subpath url: String, }, /// CVS repository Cvs { /// Root of the CVS repository root: String, /// Module within the CVS repository module: Option, }, } impl Vcs { /// Parse a VCS field /// /// # Arguments /// * `name` - Name of the VCS /// * `value` - Value of the VCS field pub fn from_field(name: &str, value: &str) -> Result { match name { "Git" => { let parsed_vcs: ParsedVcs = value.parse::().map_err(|e| e.to_string())?; Ok(Vcs::Git { repo_url: parsed_vcs.repo_url, branch: parsed_vcs.branch, subpath: parsed_vcs.subpath, }) } "Bzr" => { let parsed_vcs: ParsedVcs = value.parse::().map_err(|e| e.to_string())?; if parsed_vcs.branch.is_some() { return Err("Invalid branch value for Vcs-Bzr".to_string()); } Ok(Vcs::Bzr { repo_url: parsed_vcs.repo_url, subpath: parsed_vcs.subpath, }) } "Hg" => Ok(Vcs::Hg { repo_url: value.to_string(), }), "Svn" => Ok(Vcs::Svn { url: value.to_string(), }), "Cvs" => { if let Some((root, module)) = value.split_once(' ') { Ok(Vcs::Cvs { root: root.to_string(), module: Some(module.to_string()), }) } else { Ok(Vcs::Cvs { root: value.to_string(), module: None, }) } } n => Err(format!("Unknown VCS: {}", n)), } } /// Convert the VCS information to a field /// /// Returns a tuple with the name of the VCS and the value of the field pub fn to_field(&self) -> (&str, String) { match self { Vcs::Git { repo_url, branch, subpath, } => ( "Git", ParsedVcs { repo_url: repo_url.clone(), branch: branch.clone(), subpath: subpath.clone(), } .to_string(), ), Vcs::Bzr { repo_url, subpath } => ( "Bzr", match subpath { Some(subpath) => format!("{} [{}]", repo_url, subpath), None => repo_url.clone(), }, ), Vcs::Hg { repo_url } => ("Hg", repo_url.clone()), Vcs::Svn { url } => ("Svn", url.clone()), Vcs::Cvs { root, module } => ( "Cvs", match module { Some(module) => format!("{} {}", root, module), None => root.clone(), }, ), } } /// Extract the subpath from the VCS information pub fn subpath(&self) -> Option<&str> { match self { Vcs::Git { subpath, .. } => subpath.as_deref(), Vcs::Bzr { subpath, .. } => subpath.as_deref(), _ => None, } } /// Convert the VCS information to a URL that is usable by Breezy pub fn to_branch_url(&self) -> Option { match self { Vcs::Git { repo_url, branch, subpath: _, // TODO: Proper URL encoding } => Some(format!("{},branch={}", repo_url, branch.as_ref().unwrap())), Vcs::Bzr { repo_url, subpath: _, } => Some(repo_url.to_string()), Vcs::Hg { repo_url } => Some(repo_url.to_string()), Vcs::Svn { url } => Some(url.to_string()), _ => None, } } } #[cfg(test)] mod test { use super::*; #[test] fn test_vcs_info() { let vcs_info = ParsedVcs::from_str("https://github.com/jelmer/example").unwrap(); assert_eq!(vcs_info.repo_url, "https://github.com/jelmer/example"); assert_eq!(vcs_info.branch, None); assert_eq!(vcs_info.subpath, None); } #[test] fn test_vcs_info_with_branch() { let vcs_info = ParsedVcs::from_str("https://github.com/jelmer/example -b branch").unwrap(); assert_eq!(vcs_info.repo_url, "https://github.com/jelmer/example"); assert_eq!(vcs_info.branch, Some("branch".to_string())); assert_eq!(vcs_info.subpath, None); } #[test] fn test_vcs_info_with_subpath() { let vcs_info = ParsedVcs::from_str("https://github.com/jelmer/example [subpath]").unwrap(); assert_eq!(vcs_info.repo_url, "https://github.com/jelmer/example"); assert_eq!(vcs_info.branch, None); assert_eq!(vcs_info.subpath, Some("subpath".to_string())); } #[test] fn test_vcs_info_with_branch_and_subpath() { let vcs_info = ParsedVcs::from_str("https://github.com/jelmer/example -b branch [subpath]").unwrap(); assert_eq!(vcs_info.repo_url, "https://github.com/jelmer/example"); assert_eq!(vcs_info.branch, Some("branch".to_string())); assert_eq!(vcs_info.subpath, Some("subpath".to_string())); } #[test] fn test_eq() { let vcs_info1 = ParsedVcs::from_str("https://github.com/jelmer/example -b branch [subpath]").unwrap(); let vcs_info2 = ParsedVcs::from_str("https://github.com/jelmer/example -b branch [subpath]").unwrap(); let vcs_info3 = ParsedVcs::from_str("https://example.com/jelmer/example -b branch [subpath]").unwrap(); assert_eq!(vcs_info1, vcs_info2); assert_ne!(vcs_info1, vcs_info3); } } debian-control-0.2.14/testdata/ruff.buildinfo000064400000000000000000000774201046102023000172720ustar 00000000000000Format: 1.0 Source: ruff Architecture: source Version: 0.0.291+dfsg1-2 Checksums-Md5: 9c94edc7ca07a64be4d4843bb3870bf8 2807 ruff_0.0.291+dfsg1-2.dsc Checksums-Sha1: 6e243c1325f425f002f88429c654f0566bdd818c 2807 ruff_0.0.291+dfsg1-2.dsc Checksums-Sha256: 99b0e3f419a9f2dad7d734dd7535b97d563fb0952940a739ea94300ab2d34964 2807 ruff_0.0.291+dfsg1-2.dsc Build-Origin: Debian Build-Architecture: amd64 Build-Date: Wed, 08 Nov 2023 09:35:44 +0000 Build-Tainted-By: merged-usr-via-aliased-dirs usr-local-has-configs usr-local-has-includes usr-local-has-libraries usr-local-has-programs Installed-Build-Depends: autoconf (= 2.71-3), automake (= 1:1.16.5-1.3), autopoint (= 0.21-13), autotools-dev (= 20220109.1), base-files (= 13), base-passwd (= 3.6.2), bash (= 5.2.15-2+b6), binutils (= 2.41-6), binutils-common (= 2.41-6), binutils-mingw-w64-i686 (= 2.41-4+11+nmu1), binutils-mingw-w64-x86-64 (= 2.41-4+11+nmu1), binutils-x86-64-linux-gnu (= 2.41-6), bsdextrautils (= 2.39.2-5), bsdutils (= 1:2.39.2-5), build-essential (= 12.10), bzip2 (= 1.0.8-5+b1), cargo (= 0.66.0+ds1-1), clang (= 1:16.0-57), clang-13 (= 1:13.0.1-13), clang-14 (= 1:14.0.6-16), clang-16 (= 1:16.0.6-17), cmake (= 3.27.7-1), cmake-data (= 3.27.7-1), coreutils (= 9.1-1), cpp (= 4:13.2.0-1), cpp-10 (= 10.5.0-2), cpp-11 (= 11.4.0-5), cpp-12 (= 12.3.0-11), cpp-13 (= 13.2.0-6), dash (= 0.5.12-6), debconf (= 1.5.82), debhelper (= 13.11.7), debianutils (= 5.14), dh-autoreconf (= 20), dh-python (= 6.20231107), dh-strip-nondeterminism (= 1.13.1-1), diffutils (= 1:3.10-1), dpkg (= 1.22.1), dpkg-dev (= 1.22.1), dwz (= 0.15-1), file (= 1:5.45-2), findutils (= 4.9.0-5), fontconfig-config (= 2.14.2-6), fonts-dejavu-core (= 2.37-8), fonts-dejavu-mono (= 2.37-8), fonts-freefont-ttf (= 20211204+svn4273-2), fonts-liberation (= 1:2.1.5-3), fonts-noto-core (= 20201225-2), fonts-noto-mono (= 20201225-2), fonts-texgyre (= 20180621-6), fonts-urw-base35 (= 20200910-7), g++ (= 4:13.2.0-1), g++-13 (= 13.2.0-6), gawk (= 1:5.2.1-2), gcc (= 4:13.2.0-1), gcc-10 (= 10.5.0-2), gcc-10-base (= 10.5.0-2), gcc-11 (= 11.4.0-5), gcc-11-base (= 11.4.0-5), gcc-12 (= 12.3.0-11), gcc-12-base (= 12.3.0-11), gcc-13 (= 13.2.0-6), gcc-13-base (= 13.2.0-6), gettext (= 0.21-13+b1), gettext-base (= 0.21-13+b1), grep (= 3.11-3), groff-base (= 1.23.0-3), gzip (= 1.12-1), hostname (= 3.23+nmu1), init-system-helpers (= 1.65.2), install-info (= 7.1-1), intltool-debian (= 0.35.0+20060710.6), lib32gcc-s1 (= 13.2.0-6), lib32stdc++6 (= 13.2.0-6), libacl1 (= 2.3.1-3), libarchive-zip-perl (= 1.68+git20210106.2385b67-1~jan+nus3), libarchive13 (= 3.7.2-1), libasan6 (= 11.4.0-5), libasan8 (= 13.2.0-6), libatomic1 (= 13.2.0-6), libattr1 (= 1:2.5.1-4), libaudit-common (= 1:3.1.1-1), libaudit1 (= 1:3.1.1-1), libbinutils (= 2.41-6), libblkid1 (= 2.39.2-5), libbrotli-dev (= 1.1.0-1), libbrotli1 (= 1.1.0-1), libbsd0 (= 0.11.7-4), libbz2-1.0 (= 1.0.8-5+b1), libbz2-dev (= 1.0.8-5+b1), libc-bin (= 2.37-12), libc-dev-bin (= 2.37-12), libc6 (= 2.37-12), libc6-dev (= 2.37-12), libc6-i386 (= 2.37-12), libcap-ng0 (= 0.8.3-1+b3), libcap2 (= 1:2.66-4), libcc1-0 (= 13.2.0-6), libclang-16-dev (= 1:16.0.6-17), libclang-common-13-dev (= 1:13.0.1-13), libclang-common-14-dev (= 1:14.0.6-16), libclang-common-16-dev (= 1:16.0.6-17), libclang-cpp13 (= 1:13.0.1-13), libclang-cpp14 (= 1:14.0.6-16), libclang-cpp16 (= 1:16.0.6-17), libclang-dev (= 1:16.0-57), libclang1-13 (= 1:13.0.1-13), libclang1-14 (= 1:14.0.6-16), libclang1-16 (= 1:16.0.6-17), libcom-err2 (= 1.47.0-2+b1), libcrypt-dev (= 1:4.4.36-2), libcrypt1 (= 1:4.4.36-2), libctf-nobfd0 (= 2.41-6), libctf0 (= 2.41-6), libcurl3-gnutls (= 8.4.0-2), libcurl4 (= 8.4.0-2), libdb5.3 (= 5.3.28+dfsg2-3), libdebconfclient0 (= 0.271), libdebhelper-perl (= 13.11.7), libdpkg-perl (= 1.22.1), libedit2 (= 3.1-20230828-1), libelf1 (= 0.189-4), libexpat1 (= 2.5.0-2), libexpat1-dev (= 2.5.0-2), libffi8 (= 3.4.4-1), libfile-find-rule-perl (= 0.34+git20200222.1.b3e8a91-1~jan+nus1), libfile-stripnondeterminism-perl (= 1.13.1-1), libfontconfig-dev (= 2.14.2-6), libfontconfig1 (= 2.14.2-6), libfontenc1 (= 1:1.1.4-1), libfreetype-dev (= 2.13.2+dfsg-1), libfreetype6 (= 2.13.2+dfsg-1), libgc1 (= 1:8.2.4-1), libgcc-10-dev (= 10.5.0-2), libgcc-11-dev (= 11.4.0-5), libgcc-12-dev (= 12.3.0-11), libgcc-13-dev (= 13.2.0-6), libgcc-s1 (= 13.2.0-6), libgcrypt20 (= 1.10.2-3), libgdbm-compat4 (= 1.23-3), libgdbm6 (= 1.23-3), libgit2-1.5 (= 1.5.1+ds-1), libgmp10 (= 2:6.3.0+dfsg-2), libgnutls30 (= 3.8.1-4+b1), libgomp1 (= 13.2.0-6), libgpg-error0 (= 1.47-2), libgprofng0 (= 2.41-6), libgssapi-krb5-2 (= 1.20.1-5), libhogweed6 (= 3.9.1-2), libhttp-parser2.9 (= 2.9.4-6), libhwasan0 (= 13.2.0-6), libicu72 (= 72.1-4), libidn2-0 (= 2.3.4-1+b1), libisl23 (= 0.26-3), libitm1 (= 13.2.0-6), libjansson4 (= 2.14-2), libjs-jquery (= 3.6.1+dfsg+~3.5.14-1), libjs-sphinxdoc (= 7.2.6-2), libjs-underscore (= 1.13.4~dfsg+~1.11.4-3), libjsoncpp25 (= 1.9.5-6), libk5crypto3 (= 1.20.1-5), libkeyutils1 (= 1.6.3-2), libkrb5-3 (= 1.20.1-5), libkrb5support0 (= 1.20.1-5), libldap-2.5-0 (= 2.5.13+dfsg-5), libllvm13 (= 1:13.0.1-13), libllvm14 (= 1:14.0.6-16), libllvm16 (= 1:16.0.6-17), liblsan0 (= 13.2.0-6), liblz4-1 (= 1.9.4-1), liblzma5 (= 5.4.4-0.1), libmagic-mgc (= 1:5.45-2), libmagic1 (= 1:5.45-2), libmbedcrypto7 (= 2.28.5-1), libmbedtls14 (= 2.28.5-1), libmbedx509-1 (= 2.28.5-1), libmd0 (= 1.1.0-1), libmimalloc-dev (= 2.1.2+ds-2), libmimalloc2.0 (= 2.1.2+ds-2), libmount1 (= 2.39.2-5), libmpc3 (= 1.3.1-1), libmpfr6 (= 4.2.1-1), libncursesw6 (= 6.4+20231016-1), libnettle8 (= 3.9.1-2), libnghttp2-14 (= 1.58.0-1), libnsl-dev (= 1.3.0-3), libnsl2 (= 1.3.0-3), libnumber-compare-perl (= 0.03+git20200222.1.251c2bb-1~jan+nus1), libobjc-13-dev (= 13.2.0-6), libobjc4 (= 13.2.0-6), libp11-kit0 (= 0.25.0-5), libpam-modules (= 1.5.2-9.1), libpam-modules-bin (= 1.5.2-9.1), libpam-runtime (= 1.5.2-9.1), libpam0g (= 1.5.2-9.1), libpcre2-8-0 (= 10.42-4), libperl5.36 (= 5.36.0-9), libpfm4 (= 4.13.0+git15-gefd10fb-2), libpipeline1 (= 1.5.7-1), libpkgconf3 (= 1.9.3-1~jan+nur1), libpng-dev (= 1.6.40-2), libpng16-16 (= 1.6.40-2), libproc2-0 (= 2:4.0.4-2), libpsl5 (= 0.21.2-1+b1), libpython3-all-dev (= 3.11.4-5+b1), libpython3-dev (= 3.11.4-5+b1), libpython3-stdlib (= 3.11.4-5+b1), libpython3.11 (= 3.11.6-3), libpython3.11-dev (= 3.11.6-3), libpython3.11-minimal (= 3.11.6-3), libpython3.11-stdlib (= 3.11.6-3), libquadmath0 (= 13.2.0-6), libreadline8 (= 8.2-1.3), librhash0 (= 1.4.3-3), librtmp1 (= 2.4+20151223.gitfa8646d.1-2+b2), librust-ab-glyph-dev (= 0.2.21-2), librust-ab-glyph-rasterizer+libm-dev (= 0.1.7-1+b1), librust-ab-glyph-rasterizer-dev (= 0.1.7-1+b1), librust-addr2line-dev (= 0.20.0-1), librust-adler-dev (= 1.0.2-2), librust-ahash-0.7-dev (= 0.7.6-13), librust-aho-corasick-dev (= 1.1.1-3), librust-alloc-no-stdlib-dev (= 2.0.4-1), librust-alloc-stdlib-dev (= 0.2.2-1), librust-annotate-snippets-dev (= 0.9.1-1+b1), librust-ansi-term-dev (= 0.12.1-1), librust-anstream-dev (= 0.6.4-1), librust-anstyle-dev (= 1.0.1-1+b1), librust-anstyle-parse-dev (= 0.2.1-1+b1), librust-anstyle-query-dev (= 1.0.0-1+b1), librust-anyhow-dev (= 1.0.72-1), librust-arbitrary-dev (= 1.3.0-1), librust-arc-swap-dev (= 1.6.0-2), librust-argfile-dev (= 0.1.6-1+b1), librust-arrayvec-dev (= 0.7.2-2), librust-ascii-canvas-dev (= 2.0.0-2), librust-ascii-dev (= 1.0.0-2), librust-assert-cmd-dev (= 2.0.12-1), librust-async-attributes-dev (= 1.1.2-5), librust-async-channel-dev (= 1.9.0-2), librust-async-executor-dev (= 1.5.4-2), librust-async-fs-dev (= 1.6.0-4), librust-async-global-executor-dev (= 2.3.1-1), librust-async-io-dev (= 1.13.0-4), librust-async-lock-dev (= 2.8.0-1), librust-async-net-dev (= 1.8.0-1), librust-async-process-dev (= 1.7.0-3), librust-async-std-dev (= 1.12.0-15), librust-async-task-dev (= 4.5.0-1), librust-atomic-dev (= 0.5.1-4), librust-atomic-polyfill-dev (= 1.0.2-1+b1), librust-atomic-waker-dev (= 1.1.1-2), librust-atty-dev (= 0.2.14-2), librust-autocfg-dev (= 1.1.0-1), librust-backtrace-dev (= 0.3.68-2), librust-base64-dev (= 0.21.2-1), librust-bigdecimal-dev (= 0.3.0-1), librust-bincode-dev (= 1.3.3-1), librust-bindgen-dev (= 0.66.1-3), librust-bit-set+std-dev (= 0.5.2-1), librust-bit-set-dev (= 0.5.2-1), librust-bit-vec-dev (= 0.6.3-1), librust-bitflags-1-dev (= 1.3.2-5+b1), librust-bitflags-dev (= 2.4.0-1), librust-bitvec-dev (= 1.0.1-1+b1), librust-blobby-dev (= 0.3.1-1), librust-block-buffer-dev (= 0.10.2-2), librust-blocking-dev (= 1.3.1-2), librust-brotli-decompressor-dev (= 2.3.4-1), librust-bstr-dev (= 1.7.0-2), librust-bumpalo-dev (= 3.12.0-1), librust-bytecheck-derive-dev (= 0.6.11-1), librust-bytecheck-dev (= 0.6.11-1), librust-bytemuck-dev (= 1.12.1-1), librust-byteorder-dev (= 1.4.3-2), librust-bytes-dev (= 1.4.0-1), librust-cachedir-dev (= 0.3.0-1+b1), librust-camino-dev (= 1.1.6-1), librust-cast-dev (= 0.3.0-1), librust-cc-dev (= 1.0.83-1), librust-cexpr-dev (= 0.6.0-2), librust-cfg-if-0.1-dev (= 0.1.10-2), librust-cfg-if-dev (= 1.0.0-1), librust-chic-dev (= 1.2.2-1+b1), librust-chrono-dev (= 0.4.31-1), librust-chunked-transfer-dev (= 1.4.0-1+b1), librust-clang-sys+libloading-dev (= 1.3.0-1), librust-clang-sys-dev (= 1.3.0-1), librust-clap-2-dev (= 2.34.0-3), librust-clap-builder-dev (= 4.4.6-1), librust-clap-derive-dev (= 4.4.2-1), librust-clap-dev (= 4.4.6-1), librust-clap-lex-dev (= 0.5.0-1), librust-clearscreen-dev (= 2.0.1-1+b1), librust-cmake-dev (= 0.1.45-1), librust-color-quant-dev (= 1.1.0-1), librust-colorchoice-dev (= 1.0.0-1+b1), librust-colored-dev (= 2.0.0-1), librust-compiler-builtins+core-dev (= 0.1.101-1), librust-compiler-builtins+rustc-dep-of-std-dev (= 0.1.101-1), librust-compiler-builtins-dev (= 0.1.101-1), librust-concurrent-queue-dev (= 2.3.0-1), librust-configparser-dev (= 3.0.2-2), librust-console-dev (= 0.15.2-2), librust-console-error-panic-hook-dev (= 0.1.7-1), librust-console-log-dev (= 1.0.0-1+b1), librust-const-cstr-dev (= 0.3.0-1+b1), librust-const-oid-dev (= 0.9.3-1+b1), librust-const-random-dev (= 0.1.13-1), librust-const-random-macro-dev (= 0.1.13-1), librust-cookie-dev (= 0.16.2-2), librust-cookie-store-dev (= 0.19.1-3), librust-countme-dev (= 3.0.1-1+b1), librust-cpp-demangle-dev (= 0.4.0-1), librust-cpufeatures-dev (= 0.2.4-1), librust-crc32fast-dev (= 1.3.2-2), librust-criterion-0.3-dev (= 0.3.6-8), librust-criterion-plot-dev (= 0.4.5-1), librust-critical-section-dev (= 1.1.1-1+b1), librust-crossbeam-channel-dev (= 0.5.6-1), librust-crossbeam-deque-dev (= 0.8.1-1), librust-crossbeam-epoch+std-dev (= 0.9.13-1), librust-crossbeam-epoch-dev (= 0.9.13-1), librust-crossbeam-utils-dev (= 0.8.12-1), librust-crunchy-dev (= 0.2.2-1+b1), librust-crypto-common-dev (= 0.1.6-1), librust-csv-core-dev (= 0.1.10-1), librust-csv-dev (= 1.2.2-1), librust-ctor-dev (= 0.1.26-1), librust-cty-dev (= 0.2.1-1+b1), librust-darling+suggestions-dev (= 0.20.3-1), librust-darling-core+strsim-dev (= 0.20.3-1), librust-darling-core-dev (= 0.20.3-1), librust-darling-dev (= 0.20.3-1), librust-darling-macro-dev (= 0.20.3-1), librust-dashmap-dev (= 5.4.0-1), librust-derive-arbitrary-dev (= 1.3.1-1), librust-diff-dev (= 0.1.12-1), librust-difflib-dev (= 0.4.0-1+b1), librust-digest-dev (= 0.10.7-2), librust-dirs-dev (= 5.0.1-1), librust-dirs-next-dev (= 2.0.0-1), librust-dirs-sys-dev (= 0.4.1-1), librust-dirs-sys-next-dev (= 0.1.1-1+b1), librust-dlib-dev (= 0.5.0-1), librust-doc-comment-dev (= 0.3.3-1), librust-drop-bomb-dev (= 0.1.5-1+b1), librust-dyn-clone-dev (= 1.0.2-1+b1), librust-either-dev (= 1.9.0-1), librust-ena-dev (= 0.14.0-2), librust-encode-unicode-dev (= 0.3.6-1), librust-encoding-rs-dev (= 0.8.31-2), librust-enumset-derive-dev (= 0.8.1-1), librust-enumset-dev (= 1.1.2-1), librust-env-logger-0.7+default-dev (= 0.7.1-4), librust-env-logger-0.7-dev (= 0.7.1-4), librust-env-logger-dev (= 0.10.0-2), librust-erased-serde-dev (= 0.3.23-1), librust-errno-dev (= 0.3.1-1), librust-error-chain-dev (= 0.12.4-1), librust-event-listener-dev (= 2.5.3-4), librust-eyre+default-dev (= 0.6.8-1+b1), librust-eyre-dev (= 0.6.8-1+b1), librust-fallible-iterator-dev (= 0.2.0-2), librust-fastrand-dev (= 1.8.0-1), librust-fern-dev (= 0.6.1-1), librust-filetime-dev (= 0.2.22-1), librust-fixedbitset-dev (= 0.4.2-1), librust-flate2-dev (= 1.0.27-2), librust-float-cmp-dev (= 0.9.0-1), librust-float-ord-dev (= 0.3.2-1), librust-fnv-dev (= 1.0.7-1), librust-font-kit-dev (= 0.11.0-2), librust-foreign-types-0.3-dev (= 0.3.2-1+b2), librust-foreign-types-shared-0.1-dev (= 0.1.1-1+b2), librust-form-urlencoded-dev (= 1.2.0-1), librust-freetype-dev (= 0.7.0-4), librust-freetype-sys-dev (= 0.13.1-1), librust-fs-err-dev (= 2.9.0-1+b1), librust-funty-dev (= 2.0.0-1+b1), librust-futures-channel-dev (= 0.3.28-1), librust-futures-core-dev (= 0.3.28-1), librust-futures-dev (= 0.3.28-1), librust-futures-executor-dev (= 0.3.28-1), librust-futures-io-dev (= 0.3.28-1), librust-futures-lite-dev (= 1.12.0-1+b1), librust-futures-macro-dev (= 0.3.28-1), librust-futures-sink-dev (= 0.3.28-1), librust-futures-task-dev (= 0.3.28-1), librust-futures-util-dev (= 0.3.28-1), librust-generic-array-dev (= 0.14.7-1), librust-getrandom-dev (= 0.2.10-1), librust-ghost-dev (= 0.1.5-1+b1), librust-gif-dev (= 0.11.3-1), librust-gimli-dev (= 0.27.3-1), librust-glob-dev (= 0.3.1-1), librust-globset-dev (= 0.4.13-1), librust-half-dev (= 1.6.0-2), librust-hashbrown-dev (= 0.12.3-1), librust-heck-dev (= 0.4.1-1), librust-hex-dev (= 0.4.3-1+b2), librust-hexf-parse-dev (= 0.2.1-1+b1), librust-hkdf-dev (= 0.12.3-1), librust-hmac-dev (= 0.12.1-1), librust-hostname-dev (= 0.3.1-1), librust-http-dev (= 0.2.9-1), librust-humantime-dev (= 2.1.0-1+b1), librust-iana-time-zone-dev (= 0.1.53-1+b1), librust-ident-case-dev (= 1.0.1-1), librust-idna-dev (= 0.4.0-1), librust-ignore-dev (= 0.4.20-1), librust-image-dev (= 0.24.7-2), librust-imperative-dev (= 1.0.5-1), librust-indenter-dev (= 0.3.3-1+b1), librust-indexmap-dev (= 1.9.3-1), librust-indoc-dev (= 2.0.3-1), librust-inflector-dev (= 0.11.4-1+b1), librust-inotify-dev (= 0.9.6-1+b1), librust-inotify-sys-dev (= 0.1.5-1), librust-insta-cmd-dev (= 0.4.0-1+b1), librust-insta-dev (= 1.21.0-1), librust-inventory-dev (= 0.3.3-1~jan+nur1), librust-is-macro-dev (= 0.3.0-1+b1), librust-is-terminal-dev (= 0.4.9-2), librust-itertools-dev (= 0.10.5-1), librust-itoa-dev (= 1.0.9-1), librust-joinery-dev (= 3.1.0-1+b1), librust-jpeg-decoder-dev (= 0.3.0-1), librust-js-sys-dev (= 0.3.64-1), librust-kstring-dev (= 2.0.0-1), librust-kv-log-macro-dev (= 1.0.8-3), librust-lalrpop-dev (= 0.20.0-2), librust-lalrpop-util-dev (= 0.20.0-1), librust-lazy-static-dev (= 1.4.0-2), librust-lazycell-dev (= 1.3.0-3), librust-lexical-parse-float-dev (= 0.8.5-1+b1), librust-lexical-parse-integer-dev (= 0.8.6-1+b1), librust-lexical-util-dev (= 0.8.5-1+b1), librust-libc-dev (= 0.2.149-1), librust-libcst-derive-dev (= 0.1.0-1+b1), librust-libcst-dev (= 0.1.0-1), librust-libloading-dev (= 0.7.4-1), librust-libm-dev (= 0.2.7-1), librust-libmimalloc-sys-dev (= 0.1.25-1+b1), librust-libwebp-sys-dev (= 0.9.4-1), librust-libz-sys+default-dev (= 1.1.8-2), librust-libz-sys+libc-dev (= 1.1.8-2), librust-libz-sys-dev (= 1.1.8-2), librust-linked-hash-map-dev (= 0.5.6-1), librust-linux-raw-sys-dev (= 0.4.9-1), librust-lock-api-dev (= 0.4.9-1), librust-log-dev (= 0.4.20-2), librust-lru-dev (= 0.7.8-1+b1), librust-lz4-flex-dev (= 0.11.1-1+b1), librust-match-cfg-dev (= 0.1.0-4), librust-matches-dev (= 0.1.8-1), librust-md-5-dev (= 0.10.6-1), librust-md5-asm-dev (= 0.5.0-2), librust-memchr-dev (= 2.6.4-3), librust-memmap2-dev (= 0.5.10-2), librust-memoffset-dev (= 0.6.5-1), librust-mimalloc-dev (= 0.1.29-1+b1), librust-minimal-lexical-dev (= 0.2.1-2), librust-miniz-oxide-dev (= 0.7.1-1), librust-mio-dev (= 0.8.8-1), librust-native-tls-dev (= 0.2.11-2), librust-natord-dev (= 1.0.9-1+b1), librust-new-debug-unreachable-dev (= 1.0.4-1), librust-nix-dev (= 0.26.2-1), librust-no-panic-dev (= 0.1.13-1), librust-nohash-hasher-dev (= 0.2.0-1+b1), librust-nom+std-dev (= 7.1.3-1), librust-nom-dev (= 7.1.3-1), librust-normalize-line-endings-dev (= 0.3.0-1+b2), librust-notify-dev (= 5.2.0-1), librust-nu-ansi-term-dev (= 0.48.0-2), librust-num-bigint-dev (= 0.4.3-2), librust-num-complex-dev (= 0.4.0-2), librust-num-cpus-dev (= 1.16.0-1), librust-num-integer+i128-dev (= 0.1.44-1), librust-num-integer+std-dev (= 0.1.44-1), librust-num-integer-dev (= 0.1.44-1), librust-num-rational-dev (= 0.4.1-2), librust-num-threads-dev (= 0.1.6-1+b1), librust-num-traits-dev (= 0.2.15-1), librust-object-dev (= 0.31.1-1), librust-once-cell-dev (= 1.18.0-1), librust-oorandom-dev (= 11.1.3-1+b2), librust-openssl-dev (= 0.10.57-1), librust-openssl-macros-dev (= 0.1.0-1+b1), librust-openssl-probe-dev (= 0.1.2-1+b1), librust-openssl-sys-dev (= 0.9.93-1), librust-option-ext-dev (= 0.2.0-1+b1), librust-os-str-bytes-dev (= 6.0.0-1+b1), librust-owned-ttf-parser-dev (= 0.19.0-1), librust-owning-ref-dev (= 0.4.1-1), librust-parking-dev (= 2.0.0-1+b1), librust-parking-lot-core-dev (= 0.9.6-1), librust-parking-lot-dev (= 0.12.1-1), librust-paste-dev (= 1.0.7-1), librust-path-absolutize-dev (= 3.1.1-1+b1), librust-path-dedot-dev (= 3.1.1-1+b1), librust-pathdiff-dev (= 0.2.1-1+b1), librust-pathfinder-geometry-dev (= 0.5.1-1+b1), librust-pathfinder-simd-dev (= 0.5.1-1+b1), librust-peeking-take-while-dev (= 0.1.2-1+b1), librust-peg-dev (= 0.8.2-1), librust-peg-macros-dev (= 0.8.2-1), librust-peg-runtime-dev (= 0.8.2-1), librust-pep440-rs-dev (= 0.3.12-1), librust-pep508-rs-dev (= 0.2.1-2), librust-percent-encoding-dev (= 2.3.0-1), librust-pest-derive-dev (= 2.7.4-1), librust-pest-dev (= 2.7.4-1), librust-pest-generator-dev (= 2.7.4-1), librust-pest-meta-dev (= 2.7.4-1), librust-petgraph-dev (= 0.6.4-1), librust-phf+phf-macros-dev (= 0.11.2-1), librust-phf+std-dev (= 0.11.2-1), librust-phf-codegen-dev (= 0.11.2-1), librust-phf-dev (= 0.11.2-1), librust-phf-generator-dev (= 0.11.2-1), librust-phf-macros-dev (= 0.11.2-1), librust-phf-shared-dev (= 0.11.2-1), librust-pico-args-dev (= 0.4.2-1+b1), librust-pin-project-lite-dev (= 0.2.13-1), librust-pin-utils-dev (= 0.1.0-1), librust-pkg-config-dev (= 0.3.25-2), librust-plotters-backend-dev (= 0.3.5-1), librust-plotters-bitmap-dev (= 0.3.3-3), librust-plotters-dev (= 0.3.5-2), librust-plotters-svg-dev (= 0.3.5-1), librust-pmutil-dev (= 0.6.1-1+b1), librust-png-dev (= 0.17.7-3), librust-polling-dev (= 2.8.0-1), librust-portable-atomic-dev (= 1.4.3-2), librust-ppv-lite86-dev (= 0.2.16-1), librust-precomputed-hash-dev (= 0.1.1-1+b1), librust-predicates-core-dev (= 1.0.6-1), librust-predicates-dev (= 3.0.3-1), librust-predicates-tree-dev (= 1.0.7-1), librust-prettyplease-dev (= 0.2.6-1+b1), librust-print-bytes-dev (= 0.5.0-1+b1), librust-proc-macro-crate-dev (= 1.2.1-1), librust-proc-macro-error-attr-dev (= 1.0.4-1), librust-proc-macro-error-dev (= 1.0.4-1), librust-proc-macro-hack-dev (= 0.5.19-1), librust-proc-macro2-dev (= 1.0.65-1), librust-proptest+bit-set-dev (= 1.0.0-3), librust-proptest+default-dev (= 1.0.0-3), librust-proptest+fork-dev (= 1.0.0-3), librust-proptest+lazy-static-dev (= 1.0.0-3), librust-proptest+quick-error-dev (= 1.0.0-3), librust-proptest+regex-syntax-dev (= 1.0.0-3), librust-proptest+rusty-fork-dev (= 1.0.0-3), librust-proptest+std-dev (= 1.0.0-3), librust-proptest+tempfile-dev (= 1.0.0-3), librust-proptest+timeout-dev (= 1.0.0-3), librust-proptest-dev (= 1.0.0-3), librust-psl-types-dev (= 2.0.11-1), librust-ptr-meta-derive-dev (= 0.1.4-1+b1), librust-ptr-meta-dev (= 0.1.4-1+b1), librust-publicsuffix-dev (= 2.2.3-2), librust-pyo3-build-config+python3-dll-a-dev (= 0.19.0-1), librust-pyo3-build-config-dev (= 0.19.0-1), librust-pyo3-dev (= 0.19.0-3), librust-pyo3-ffi-dev (= 0.19.0-1), librust-pyo3-log-dev (= 0.8.3-1), librust-pyo3-macros+abi3-dev (= 0.19.0-1), librust-pyo3-macros-backend-dev (= 0.19.0-1), librust-pyo3-macros-dev (= 0.19.0-1), librust-pyproject-toml-dev (= 0.7.0-1), librust-python3-dll-a-dev (= 0.2.6-1), librust-qoi-dev (= 0.4.1-2), librust-quick-error-dev (= 2.0.1-1), librust-quick-junit-dev (= 0.3.3-1+b1), librust-quick-xml-dev (= 0.27.1-3), librust-quickcheck-dev (= 1.0.3-3), librust-quote-dev (= 1.0.30-1), librust-radium-dev (= 1.0.0-1+b1), librust-rand-chacha-dev (= 0.3.1-2), librust-rand-core+getrandom-dev (= 0.6.4-1), librust-rand-core+serde-dev (= 0.6.4-1), librust-rand-core+std-dev (= 0.6.4-1), librust-rand-core-dev (= 0.6.4-1), librust-rand-dev (= 0.8.5-1), librust-rand-xorshift-dev (= 0.3.0-2), librust-rayon-core-dev (= 1.11.0-1), librust-rayon-dev (= 1.7.0-1), librust-redox-syscall-dev (= 0.2.16-1), librust-regex-automata-dev (= 0.4.3-1), librust-regex-dev (= 1.10.2-1), librust-regex-syntax-dev (= 0.8.2-1), librust-rend-dev (= 0.4.0-1), librust-ring-dev (= 0.17.5-1), librust-rkyv-derive-dev (= 0.7.42-1), librust-rkyv-dev (= 0.7.42-1), librust-ron-dev (= 0.7.1-2), librust-rust-decimal-dev (= 1.23.1-1), librust-rust-stemmers-dev (= 1.2.0-1), librust-rustc-demangle-dev (= 0.1.21-1), librust-rustc-hash-dev (= 1.1.0-1), librust-rustc-serialize-dev (= 0.3.24-1+b1), librust-rustc-std-workspace-core-dev (= 1.0.0-1+b1), librust-rustc-version-dev (= 0.4.0-1+b1), librust-rustix-dev (= 0.38.21-1), librust-rustls-dev (= 0.21.8-3.1), librust-rustls-native-certs-dev (= 0.6.3-3.1), librust-rustls-pemfile-dev (= 1.0.3-1), librust-rustls-webpki-dev (= 0.101.7-2.1), librust-rustpython-ast-dev (= 0.2.0-1+b1), librust-rustpython-common-dev (= 0.2.0-1+b1), librust-rustpython-compiler-core-dev (= 0.2.0-1+b1), librust-rustpython-parser-dev (= 0.2.0-2), librust-rustversion-dev (= 1.0.14-1), librust-rusty-fork+wait-timeout-dev (= 0.3.0-1), librust-rusty-fork-dev (= 0.3.0-1), librust-ruzstd-dev (= 0.4.0-2), librust-ryu-dev (= 1.0.2-1), librust-same-file-dev (= 1.0.6-1), librust-schannel-dev (= 0.1.19-1), librust-schemars-derive-dev (= 0.8.12-1+b1), librust-schemars-dev (= 0.8.12-1+b1), librust-scopeguard-dev (= 1.1.0-1), librust-sct-dev (= 0.7.1-2), librust-seahash-dev (= 4.1.0-1+b1), librust-semver-dev (= 1.0.18-1), librust-serde+serde-derive-dev (= 1.0.171-1), librust-serde-cbor-dev (= 0.11.2-1), librust-serde-derive-dev (= 1.0.171-1), librust-serde-derive-internals-dev (= 0.26.0-1+b1), librust-serde-dev (= 1.0.171-1), librust-serde-fmt-dev (= 1.0.3-2), librust-serde-json-dev (= 1.0.103-1), librust-serde-spanned-dev (= 0.6.3-1+b1), librust-serde-test-dev (= 1.0.171-1), librust-serde-with-dev (= 3.3.0-1+b1), librust-serde-with-macros-dev (= 3.3.0-1+b1), librust-sha1-asm-dev (= 0.5.1-2), librust-sha1-dev (= 0.10.5-1), librust-sha2-asm-dev (= 0.6.2-2), librust-sha2-dev (= 0.10.7-1), librust-sharded-slab-dev (= 0.1.4-2), librust-shellexpand-dev (= 3.1.0-3), librust-shlex-dev (= 1.1.0-1), librust-signal-hook-dev (= 0.3.15-1), librust-signal-hook-registry-dev (= 1.4.0-1), librust-simdutf8-dev (= 0.1.4-4), librust-similar-dev (= 2.2.1-2), librust-siphasher-dev (= 0.3.10-1), librust-slab-dev (= 0.4.4-1+b1), librust-slog-dev (= 2.5.2-1), librust-smallvec-dev (= 1.11.0-1), librust-smawk-dev (= 0.3.1-2), librust-smol-dev (= 1.3.0-4), librust-smol-str-dev (= 0.2.0-1+b1), librust-socket2-dev (= 0.5.5-1), librust-socks-dev (= 0.3.4-4), librust-spin-dev (= 0.9.8-1), librust-stable-deref-trait-dev (= 1.2.0-1), librust-static-assertions-dev (= 1.1.0-1), librust-string-cache-dev (= 0.8.7-1), librust-strsim-dev (= 0.10.0-1), librust-strum-dev (= 0.24.1-2), librust-strum-macros-dev (= 0.24.3-1), librust-subtle+default-dev (= 2.4.1-1), librust-subtle-dev (= 2.4.1-1), librust-sval-buffer-dev (= 2.6.1-1+b1), librust-sval-derive-dev (= 2.6.1-2), librust-sval-dev (= 2.6.1-2), librust-sval-dynamic-dev (= 2.6.1-1+b1), librust-sval-fmt-dev (= 2.6.1-1+b1), librust-sval-ref-dev (= 2.6.1-1+b1), librust-sval-serde-dev (= 2.6.1-1+b1), librust-syn-1-dev (= 1.0.109-2), librust-syn-dev (= 2.0.26-1), librust-synstructure+proc-macro-dev (= 0.12.3-2), librust-synstructure-dev (= 0.12.3-2), librust-syslog-dev (= 6.0.1-2), librust-tap-dev (= 1.0.1-1+b1), librust-target-lexicon-dev (= 0.12.9-1), librust-tempfile-dev (= 3.8.1-1), librust-term-dev (= 0.5.2-5), librust-term-size-dev (= 0.3.1-2), librust-termcolor-dev (= 1.3.0-1), librust-terminal-size-dev (= 0.2.6-3), librust-terminfo-dev (= 0.8.0-1+b1), librust-termtree-dev (= 0.4.1-1), librust-test-case-dev (= 3.2.1-1), librust-textwrap-dev (= 0.16.0-2), librust-thiserror-core-dev (= 1.0.38-2), librust-thiserror-core-impl-dev (= 1.0.38-1+b1), librust-thiserror-dev (= 1.0.49-1), librust-thiserror-impl-dev (= 1.0.49-1), librust-thread-id-dev (= 4.0.0-1), librust-thread-local-dev (= 1.1.4-1), librust-tiff-dev (= 0.9.0-1), librust-tikv-jemalloc-sys-dev (= 0.5.4-1), librust-tikv-jemallocator-dev (= 0.5.4-1), librust-time-core-dev (= 0.1.1-1+b1), librust-time-dev (= 0.3.23-2), librust-time-macros-dev (= 0.2.10-1), librust-tiny-keccak-dev (= 2.0.2-1+b2), librust-tinytemplate-dev (= 1.2.1-1), librust-tinyvec+tinyvec-macros-dev (= 1.6.0-2), librust-tinyvec-dev (= 1.6.0-2), librust-tinyvec-macros-dev (= 0.1.0-1+b1), librust-titlecase-dev (= 2.2.1-1+b1), librust-tokio-dev (= 1.33.0-1), librust-tokio-macros-dev (= 2.1.0-1), librust-toml-0.5-dev (= 0.5.11-2+b1), librust-toml-datetime-dev (= 0.6.3-1+b1), librust-toml-dev (= 0.7.6-1), librust-toml-edit-dev (= 0.19.14-1), librust-tracing-attributes-dev (= 0.1.26-1), librust-tracing-core-dev (= 0.1.30-1), librust-tracing-dev (= 0.1.37-1), librust-tracing-log-dev (= 0.1.3-2), librust-tracing-serde-dev (= 0.1.3-2), librust-tracing-subscriber-dev (= 0.3.17-2), librust-traitobject-dev (= 0.1.0-1+b1), librust-ttf-parser+default-dev (= 0.19.1-2), librust-ttf-parser-dev (= 0.19.1-2), librust-twox-hash-dev (= 1.6.3-1+b1), librust-typed-arena-dev (= 2.0.1-1), librust-typemap-dev (= 0.3.3-2), librust-typenum-dev (= 1.16.0-2), librust-ucd-trie-dev (= 0.1.5-1), librust-unic-char-property-dev (= 0.9.0-1+b1), librust-unic-char-range-dev (= 0.9.0-1+b1), librust-unic-common-dev (= 0.9.0-2), librust-unic-emoji-char-dev (= 0.9.0-1+b1), librust-unic-ucd-category-dev (= 0.9.0-1+b1), librust-unic-ucd-ident-dev (= 0.9.0-1+b1), librust-unic-ucd-version-dev (= 0.9.0-1+b1), librust-unicase-dev (= 2.6.0-1), librust-unicode-bidi-dev (= 0.3.13-1), librust-unicode-ident-dev (= 1.0.12-1), librust-unicode-linebreak-dev (= 0.1.4-1), librust-unicode-names2-dev (= 0.6.0-1+b1), librust-unicode-normalization-dev (= 0.1.22-1), librust-unicode-segmentation-dev (= 1.9.0-1), librust-unicode-width-dev (= 0.1.11-1), librust-unicode-xid-dev (= 0.2.4-1), librust-unindent-dev (= 0.2.1-1), librust-uniquote-dev (= 3.3.0-1), librust-unsafe-any-dev (= 0.4.2-2), librust-untrusted-dev (= 0.9.0-2), librust-ureq-dev (= 2.8.0-2), librust-url+serde-dev (= 2.4.0-2), librust-url-dev (= 2.4.0-2), librust-utf8parse-dev (= 0.2.1-1), librust-uuid-dev (= 1.4.1-1), librust-valuable-derive-dev (= 0.1.0-1+b1), librust-valuable-dev (= 0.1.0-4), librust-value-bag-dev (= 1.4.1-3), librust-value-bag-serde1-dev (= 1.4.1-2), librust-value-bag-sval2-dev (= 1.4.1-3), librust-vcpkg-dev (= 0.2.8-1), librust-vec-map-dev (= 0.8.1-2+b1), librust-version-check-dev (= 0.9.4-1), librust-volatile-0.3-dev (= 0.3.0-1+b1), librust-wait-timeout-dev (= 0.2.0-1), librust-waker-fn-dev (= 1.1.0-1+b1), librust-walkdir-dev (= 2.4.0-1), librust-wasm-bindgen+default-dev (= 0.2.87-1), librust-wasm-bindgen+spans-dev (= 0.2.87-1), librust-wasm-bindgen-backend-dev (= 0.2.87-1), librust-wasm-bindgen-dev (= 0.2.87-1), librust-wasm-bindgen-macro+spans-dev (= 0.2.87-1), librust-wasm-bindgen-macro-dev (= 0.2.87-1), librust-wasm-bindgen-macro-support+spans-dev (= 0.2.87-1), librust-wasm-bindgen-macro-support-dev (= 0.2.87-1), librust-wasm-bindgen-shared-dev (= 0.2.87-1), librust-web-sys-dev (= 0.3.64-1), librust-webp-dev (= 0.2.6-1), librust-weezl-dev (= 0.1.5-1+b1), librust-which-dev (= 4.2.5-1), librust-widestring-dev (= 1.0.2-1), librust-wild-dev (= 2.1.0-1), librust-winapi-dev (= 0.3.9-1+b1), librust-winapi-i686-pc-windows-gnu-dev (= 0.4.0-1+b1), librust-winapi-util-dev (= 0.1.5-1), librust-winapi-x86-64-pc-windows-gnu-dev (= 0.4.0-1+b1), librust-winnow-dev (= 0.5.15-1), librust-wyz-dev (= 0.5.1-1+b1), librust-yaml-rust-dev (= 0.4.5-1+b1), librust-yansi-term-dev (= 0.1.2-1+b1), librust-yeslogic-fontconfig-sys-dev (= 3.0.1-1+b1), librust-zeroize-derive-dev (= 1.3.3-1), librust-zeroize-dev (= 1.6.0-1), libsasl2-2 (= 2.1.28+dfsg1-3), libsasl2-modules-db (= 2.1.28+dfsg1-3), libseccomp2 (= 2.5.4-2), libselinux1 (= 3.5-1), libsframe1 (= 2.41-6), libsharpyuv-dev (= 1.3.2-0.3), libsharpyuv0 (= 1.3.2-0.3), libsigsegv2 (= 2.14-1), libsmartcols1 (= 2.39.2-5), libsqlite3-0 (= 3.44.0-1), libssh2-1 (= 1.11.0-2), libssl-dev (= 3.0.12-1), libssl3 (= 3.0.12-1), libstd-rust-1.70 (= 1.70.0+dfsg1-1), libstd-rust-dev (= 1.70.0+dfsg1-1), libstdc++-13-dev (= 13.2.0-6), libstdc++6 (= 13.2.0-6), libsub-override-perl (= 0.09+git20210306.1.9af7488-1~jan+nus2), libsystemd0 (= 254.5-1), libtasn1-6 (= 4.19.0-3), libtext-glob-perl (= 0.11+git20200516.1.f0b200d-1~jan+nus1), libtinfo6 (= 6.4+20231016-1), libtirpc-common (= 1.3.3+ds-1), libtirpc-dev (= 1.3.3+ds-1), libtirpc3 (= 1.3.3+ds-1), libtool (= 2.4.7-7), libtsan0 (= 11.4.0-5), libtsan2 (= 13.2.0-6), libubsan1 (= 13.2.0-6), libuchardet0 (= 0.0.7+git20210127.6f38ab9-1~jan+nus1), libudev1 (= 254.5-1), libunistring5 (= 1.1-2), libuuid1 (= 2.39.2-5), libuv1 (= 1.46.0-2), libwebp-dev (= 1.3.2-0.3), libwebp7 (= 1.3.2-0.3), libwebpdecoder3 (= 1.3.2-0.3), libwebpdemux2 (= 1.3.2-0.3), libwebpmux3 (= 1.3.2-0.3), libxml2 (= 2.9.14+dfsg-1.3), libyaml-0-2 (= 0.2.5+git20200622.acd6f6f-1~jan+nus1), libz3-4 (= 4.8.12-3.1), libzstd1 (= 1.5.5+dfsg2-2), linux-libc-dev (= 6.5.10-1), llvm (= 1:16.0-57), llvm-13-linker-tools (= 1:13.0.1-13), llvm-14-linker-tools (= 1:14.0.6-16), llvm-16 (= 1:16.0.6-17), llvm-16-linker-tools (= 1:16.0.6-17), llvm-16-runtime (= 1:16.0.6-17), llvm-runtime (= 1:16.0-57), login (= 1:4.13+dfsg1-3), lsb-base (= 11.6), m4 (= 1.4.19-4), mailcap (= 3.70+nmu1), make (= 4.3-4.1), man-db (= 2.12.0-1), mawk (= 1.3.4.20230808-1), media-types (= 10.1.0), mime-support (= 3.66), ncurses-base (= 6.4+20231016-1), ncurses-bin (= 6.4+20231016-1), patch (= 2.7.6-7), perl (= 5.36.0-9), perl-base (= 5.36.0-9), perl-modules-5.36 (= 5.36.0-9), pkgconf (= 1.9.3-1~jan+nur1), pkgconf-bin (= 1.9.3-1~jan+nur1), po-debconf (= 1.0.21+nmu1), procps (= 2:4.0.4-2), pybuild-plugin-pyproject (= 6.20231107), python3 (= 3.11.4-5+b1), python3-all (= 3.11.4-5+b1), python3-all-dev (= 3.11.4-5+b1), python3-build (= 0.10.0-1), python3-dev (= 3.11.4-5+b1), python3-distutils (= 3.11.5-1), python3-importlib-metadata (= 6.6.0-1~jan+nur1), python3-installer (= 0.7.0+dfsg1-2), python3-lib2to3 (= 3.11.5-1), python3-libcst (= 1.0.1-2), python3-maturin (= 1.1.0-1+b1), python3-minimal (= 3.11.4-5+b1), python3-mypy-extensions (= 1.0.0-1), python3-packaging (= 23.2-1), python3-pkg-resources (= 68.1.2-2), python3-pyproject-hooks (= 1.0.0-2), python3-setuptools (= 68.1.2-2), python3-toml (= 0.10.2-1), python3-tomli (= 2.0.1-2), python3-typing-extensions (= 4.7.1-2), python3-typing-inspect (= 0.9.0-1), python3-wheel (= 0.41.2-1), python3-yaml (= 6.0.1-1), python3-zipp (= 3.16.2-1~jan+nur1), python3.11 (= 3.11.6-3), python3.11-dev (= 3.11.6-3), python3.11-minimal (= 3.11.6-3), readline-common (= 8.2-1.3), rpcsvc-proto (= 1.4.3-1), rustc (= 1.70.0+dfsg1-1), sed (= 4.9-1), sensible-utils (= 0.0.20), sysvinit-utils (= 3.08-3), tar (= 1.34+dfsg-1.2), tzdata (= 2023c-10), usrmerge (= 38), util-linux (= 2.39.2-5), uuid-dev (= 2.39.2-5), x11-common (= 1:7.7+23), xfonts-encodings (= 1:1.0.4-2.2), xfonts-utils (= 1:7.7+6), xz-utils (= 5.4.4-0.1), zlib1g (= 1:1.2.13.dfsg-3), zlib1g-dev (= 1:1.2.13.dfsg-3) Environment: DEB_BUILD_OPTIONS="parallel=32" LANG="en_GB.UTF-8" SOURCE_DATE_EPOCH="1699435918"