version-ranges-0.1.2/.cargo_vcs_info.json0000644000000001540000000000100137760ustar { "git": { "sha1": "a8ed6afe8ab0d090280889eeb3bbb72bcc90b5d4" }, "path_in_vcs": "version-ranges" }version-ranges-0.1.2/Cargo.lock0000644000000302670000000000100117610ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 4 [[package]] name = "autocfg" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "base64" version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "bit-set" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" dependencies = [ "bit-vec", ] [[package]] name = "bit-vec" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" [[package]] name = "bitflags" version = "2.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" dependencies = [ "serde", ] [[package]] name = "byteorder" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "errno" version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", "windows-sys", ] [[package]] name = "fastrand" version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "getrandom" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "libc", "wasi 0.11.0+wasi-snapshot-preview1", ] [[package]] name = "getrandom" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" dependencies = [ "cfg-if", "libc", "r-efi", "wasi 0.14.4+wasi-0.2.4", ] [[package]] name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" version = "0.2.176" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "58f929b4d672ea937a23a1ab494143d968337a5f47e56d0815df1e0890ddf174" [[package]] name = "linux-raw-sys" version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[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.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "ppv-lite86" version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" dependencies = [ "zerocopy", ] [[package]] name = "proc-macro2" version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" dependencies = [ "unicode-ident", ] [[package]] name = "proptest" version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2bb0be07becd10686a0bb407298fb425360a5c44a663774406340c59a22de4ce" dependencies = [ "bit-set", "bit-vec", "bitflags", "lazy_static", "num-traits", "rand", "rand_chacha", "rand_xorshift", "regex-syntax", "rusty-fork", "tempfile", "unarray", ] [[package]] name = "quick-error" version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quote" version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" dependencies = [ "proc-macro2", ] [[package]] name = "r-efi" version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "rand" version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ "rand_chacha", "rand_core", ] [[package]] name = "rand_chacha" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", "rand_core", ] [[package]] name = "rand_core" version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ "getrandom 0.3.3", ] [[package]] name = "rand_xorshift" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a" dependencies = [ "rand_core", ] [[package]] name = "regex-syntax" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "ron" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db09040cc89e461f1a265139777a2bde7f8d8c67c4936f700c63ce3e2904d468" dependencies = [ "base64", "bitflags", "serde", "serde_derive", "unicode-ident", ] [[package]] name = "rustix" version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ "bitflags", "errno", "libc", "linux-raw-sys", "windows-sys", ] [[package]] name = "rusty-fork" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" dependencies = [ "fnv", "quick-error", "tempfile", "wait-timeout", ] [[package]] name = "serde" version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "smallvec" version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" dependencies = [ "serde", ] [[package]] name = "syn" version = "2.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "tempfile" version = "3.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a8a559c81686f576e8cd0290cd2a24a2a9ad80c98b3478856500fcbd7acd704" dependencies = [ "cfg-if", "fastrand", "getrandom 0.2.15", "once_cell", "rustix", "windows-sys", ] [[package]] name = "unarray" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" [[package]] name = "unicode-ident" version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" [[package]] name = "version-ranges" version = "0.1.2" dependencies = [ "proptest", "ron", "serde", "smallvec", ] [[package]] name = "wait-timeout" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" dependencies = [ "libc", ] [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasi" version = "0.14.4+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88a5f4a424faf49c3c2c344f166f0662341d470ea185e939657aaff130f0ec4a" dependencies = [ "wit-bindgen", ] [[package]] name = "windows-sys" version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ "windows-targets", ] [[package]] name = "windows-targets" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", "windows_i686_gnullvm", "windows_i686_msvc", "windows_x86_64_gnu", "windows_x86_64_gnullvm", "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "wit-bindgen" version = "0.45.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c573471f125075647d03df72e026074b7203790d41351cd6edc96f46bcccd36" [[package]] name = "zerocopy" version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ "byteorder", "zerocopy-derive", ] [[package]] name = "zerocopy-derive" version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", "syn", ] version-ranges-0.1.2/Cargo.toml0000644000000026120000000000100117750ustar # 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 = "version-ranges" version = "0.1.2" build = false include = [ "LICENSE", "README.md", "number-line-ranges.svg", "src/**", ] autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "Performance-optimized type for generic version ranges and operations on them." readme = "README.md" keywords = [ "version", "pubgrub", "selector", "ranges", ] license = "MPL-2.0" repository = "https://github.com/pubgrub-rs/pubgrub" resolver = "3" [features] serde = [ "dep:serde", "smallvec/serde", ] [lib] name = "version_ranges" path = "src/lib.rs" [dependencies.proptest] version = "1.6.0" optional = true [dependencies.serde] version = "1.0.219" features = ["derive"] optional = true [dependencies.smallvec] version = "1.14.0" features = ["union"] [dev-dependencies.proptest] version = "1.6.0" [dev-dependencies.ron] version = "0.11.0" version-ranges-0.1.2/Cargo.toml.orig000064400000000000000000000012271046102023000154570ustar 00000000000000[package] name = "version-ranges" version = "0.1.2" description = "Performance-optimized type for generic version ranges and operations on them." edition = "2021" repository = "https://github.com/pubgrub-rs/pubgrub" license = "MPL-2.0" keywords = ["version", "pubgrub", "selector", "ranges"] include = ["LICENSE", "README.md", "number-line-ranges.svg", "src/**"] [dependencies] proptest = { version = "1.6.0", optional = true } serde = { version = "1.0.219", features = ["derive"], optional = true } smallvec = { version = "1.14.0", features = ["union"] } [features] serde = ["dep:serde", "smallvec/serde"] [dev-dependencies] proptest = "1.6.0" ron = "0.11.0" version-ranges-0.1.2/LICENSE000064400000000000000000000405251046102023000136010ustar 00000000000000Mozilla Public License Version 2.0 ================================== 1. Definitions -------------- 1.1. "Contributor" means each individual or legal entity that creates, contributes to the creation of, or owns Covered Software. 1.2. "Contributor Version" means the combination of the Contributions of others (if any) used by a Contributor and that particular Contributor's Contribution. 1.3. "Contribution" means Covered Software of a particular Contributor. 1.4. "Covered Software" means Source Code Form to which the initial Contributor has attached the notice in Exhibit A, the Executable Form of such Source Code Form, and Modifications of such Source Code Form, in each case including portions thereof. 1.5. "Incompatible With Secondary Licenses" means (a) that the initial Contributor has attached the notice described in Exhibit B to the Covered Software; or (b) that the Covered Software was made available under the terms of version 1.1 or earlier of the License, but not also under the terms of a Secondary License. 1.6. "Executable Form" means any form of the work other than Source Code Form. 1.7. "Larger Work" means a work that combines Covered Software with other material, in a separate file or files, that is not Covered Software. 1.8. "License" means this document. 1.9. "Licensable" means having the right to grant, to the maximum extent possible, whether at the time of the initial grant or subsequently, any and all of the rights conveyed by this License. 1.10. "Modifications" means any of the following: (a) any file in Source Code Form that results from an addition to, deletion from, or modification of the contents of Covered Software; or (b) any new file in Source Code Form that contains any Covered Software. 1.11. "Patent Claims" of a Contributor means any patent claim(s), including without limitation, method, process, and apparatus claims, in any patent Licensable by such Contributor that would be infringed, but for the grant of the License, by the making, using, selling, offering for sale, having made, import, or transfer of either its Contributions or its Contributor Version. 1.12. "Secondary License" means either the GNU General Public License, Version 2.0, the GNU Lesser General Public License, Version 2.1, the GNU Affero General Public License, Version 3.0, or any later versions of those licenses. 1.13. "Source Code Form" means the form of the work preferred for making modifications. 1.14. "You" (or "Your") means an individual or a legal entity exercising rights under this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with You. For purposes of this definition, "control" means (a) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (b) ownership of more than fifty percent (50%) of the outstanding shares or beneficial ownership of such entity. 2. License Grants and Conditions -------------------------------- 2.1. Grants Each Contributor hereby grants You a world-wide, royalty-free, non-exclusive license: (a) under intellectual property rights (other than patent or trademark) Licensable by such Contributor to use, reproduce, make available, modify, display, perform, distribute, and otherwise exploit its Contributions, either on an unmodified basis, with Modifications, or as part of a Larger Work; and (b) under Patent Claims of such Contributor to make, use, sell, offer for sale, have made, import, and otherwise transfer either its Contributions or its Contributor Version. 2.2. Effective Date The licenses granted in Section 2.1 with respect to any Contribution become effective for each Contribution on the date the Contributor first distributes such Contribution. 2.3. Limitations on Grant Scope The licenses granted in this Section 2 are the only rights granted under this License. No additional rights or licenses will be implied from the distribution or licensing of Covered Software under this License. Notwithstanding Section 2.1(b) above, no patent license is granted by a Contributor: (a) for any code that a Contributor has removed from Covered Software; or (b) for infringements caused by: (i) Your and any other third party's modifications of Covered Software, or (ii) the combination of its Contributions with other software (except as part of its Contributor Version); or (c) under Patent Claims infringed by Covered Software in the absence of its Contributions. This License does not grant any rights in the trademarks, service marks, or logos of any Contributor (except as may be necessary to comply with the notice requirements in Section 3.4). 2.4. Subsequent Licenses No Contributor makes additional grants as a result of Your choice to distribute the Covered Software under a subsequent version of this License (see Section 10.2) or under the terms of a Secondary License (if permitted under the terms of Section 3.3). 2.5. Representation Each Contributor represents that the Contributor believes its Contributions are its original creation(s) or it has sufficient rights to grant the rights to its Contributions conveyed by this License. 2.6. Fair Use This License is not intended to limit any rights You have under applicable copyright doctrines of fair use, fair dealing, or other equivalents. 2.7. Conditions Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in Section 2.1. 3. Responsibilities ------------------- 3.1. Distribution of Source Form All distribution of Covered Software in Source Code Form, including any Modifications that You create or to which You contribute, must be under the terms of this License. You must inform recipients that the Source Code Form of the Covered Software is governed by the terms of this License, and how they can obtain a copy of this License. You may not attempt to alter or restrict the recipients' rights in the Source Code Form. 3.2. Distribution of Executable Form If You distribute Covered Software in Executable Form then: (a) such Covered Software must also be made available in Source Code Form, as described in Section 3.1, and You must inform recipients of the Executable Form how they can obtain a copy of such Source Code Form by reasonable means in a timely manner, at a charge no more than the cost of distribution to the recipient; and (b) You may distribute such Executable Form under the terms of this License, or sublicense it under different terms, provided that the license for the Executable Form does not attempt to limit or alter the recipients' rights in the Source Code Form under this License. 3.3. Distribution of a Larger Work You may create and distribute a Larger Work under terms of Your choice, provided that You also comply with the requirements of this License for the Covered Software. If the Larger Work is a combination of Covered Software with a work governed by one or more Secondary Licenses, and the Covered Software is not Incompatible With Secondary Licenses, this License permits You to additionally distribute such Covered Software under the terms of such Secondary License(s), so that the recipient of the Larger Work may, at their option, further distribute the Covered Software under the terms of either this License or such Secondary License(s). 3.4. Notices You may not remove or alter the substance of any license notices (including copyright notices, patent notices, disclaimers of warranty, or limitations of liability) contained within the Source Code Form of the Covered Software, except that You may alter any license notices to the extent required to remedy known factual inaccuracies. 3.5. Application of Additional Terms You may choose to offer, and to charge a fee for, warranty, support, indemnity or liability obligations to one or more recipients of Covered Software. However, You may do so only on Your own behalf, and not on behalf of any Contributor. You must make it absolutely clear that any such warranty, support, indemnity, or liability obligation is offered by You alone, and You hereby agree to indemnify every Contributor for any liability incurred by such Contributor as a result of warranty, support, indemnity or liability terms You offer. You may include additional disclaimers of warranty and limitations of liability specific to any jurisdiction. 4. Inability to Comply Due to Statute or Regulation --------------------------------------------------- If it is impossible for You to comply with any of the terms of this License with respect to some or all of the Covered Software due to statute, judicial order, or regulation then You must: (a) comply with the terms of this License to the maximum extent possible; and (b) describe the limitations and the code they affect. Such description must be placed in a text file included with all distributions of the Covered Software under this License. Except to the extent prohibited by statute or regulation, such description must be sufficiently detailed for a recipient of ordinary skill to be able to understand it. 5. Termination -------------- 5.1. The rights granted under this License will terminate automatically if You fail to comply with any of its terms. However, if You become compliant, then the rights granted under this License from a particular Contributor are reinstated (a) provisionally, unless and until such Contributor explicitly and finally terminates Your grants, and (b) on an ongoing basis, if such Contributor fails to notify You of the non-compliance by some reasonable means prior to 60 days after You have come back into compliance. Moreover, Your grants from a particular Contributor are reinstated on an ongoing basis if such Contributor notifies You of the non-compliance by some reasonable means, this is the first time You have received notice of non-compliance with this License from such Contributor, and You become compliant prior to 30 days after Your receipt of the notice. 5.2. If You initiate litigation against any entity by asserting a patent infringement claim (excluding declaratory judgment actions, counter-claims, and cross-claims) alleging that a Contributor Version directly or indirectly infringes any patent, then the rights granted to You by any and all Contributors for the Covered Software under Section 2.1 of this License shall terminate. 5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user license agreements (excluding distributors and resellers) which have been validly granted by You or Your distributors under this License prior to termination shall survive termination. ************************************************************************ * * * 6. Disclaimer of Warranty * * ------------------------- * * * * Covered Software is provided under this License on an "as is" * * basis, without warranty of any kind, either expressed, implied, or * * statutory, including, without limitation, warranties that the * * Covered Software is free of defects, merchantable, fit for a * * particular purpose or non-infringing. The entire risk as to the * * quality and performance of the Covered Software is with You. * * Should any Covered Software prove defective in any respect, You * * (not any Contributor) assume the cost of any necessary servicing, * * repair, or correction. This disclaimer of warranty constitutes an * * essential part of this License. No use of any Covered Software is * * authorized under this License except under this disclaimer. * * * ************************************************************************ ************************************************************************ * * * 7. Limitation of Liability * * -------------------------- * * * * Under no circumstances and under no legal theory, whether tort * * (including negligence), contract, or otherwise, shall any * * Contributor, or anyone who distributes Covered Software as * * permitted above, be liable to You for any direct, indirect, * * special, incidental, or consequential damages of any character * * including, without limitation, damages for lost profits, loss of * * goodwill, work stoppage, computer failure or malfunction, or any * * and all other commercial damages or losses, even if such party * * shall have been informed of the possibility of such damages. This * * limitation of liability shall not apply to liability for death or * * personal injury resulting from such party's negligence to the * * extent applicable law prohibits such limitation. Some * * jurisdictions do not allow the exclusion or limitation of * * incidental or consequential damages, so this exclusion and * * limitation may not apply to You. * * * ************************************************************************ 8. Litigation ------------- Any litigation relating to this License may be brought only in the courts of a jurisdiction where the defendant maintains its principal place of business and such litigation shall be governed by laws of that jurisdiction, without reference to its conflict-of-law provisions. Nothing in this Section shall prevent a party's ability to bring cross-claims or counter-claims. 9. Miscellaneous ---------------- This License represents the complete agreement concerning the subject matter hereof. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. Any law or regulation which provides that the language of a contract shall be construed against the drafter shall not be used to construe this License against a Contributor. 10. Versions of the License --------------------------- 10.1. New Versions Mozilla Foundation is the license steward. Except as provided in Section 10.3, no one other than the license steward has the right to modify or publish new versions of this License. Each version will be given a distinguishing version number. 10.2. Effect of New Versions You may distribute the Covered Software under the terms of the version of the License under which You originally received the Covered Software, or under the terms of any subsequent version published by the license steward. 10.3. Modified Versions If you create software not governed by this License, and you want to create a new license for such software, you may create and use a modified version of this License if you rename the license and remove any references to the name of the license steward (except to note that such modified license differs from this License). 10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses If You choose to distribute Source Code Form that is Incompatible With Secondary Licenses under the terms of this version of the License, the notice described in Exhibit B of this License must be attached. Exhibit A - Source Code Form License Notice ------------------------------------------- This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. You may add additional accurate notices of copyright ownership. Exhibit B - "Incompatible With Secondary Licenses" Notice --------------------------------------------------------- This Source Code Form is "Incompatible With Secondary Licenses", as defined by the Mozilla Public License, v. 2.0. version-ranges-0.1.2/README.md000064400000000000000000000044351046102023000140530ustar 00000000000000# Ranges [![crates.io](https://img.shields.io/crates/v/version-ranges.svg?logo=rust)](https://crates.io/crates/version-ranges) [![docs.rs](https://img.shields.io/badge/docs.rs-version-ranges)](https://docs.rs/version-ranges) This crate contains a performance-optimized type for generic version ranges and operations on them. `Ranges` can represent version selectors such as `(>=1.5.1, <2) OR (==3.1) OR (>4)`. Internally, it is an ordered list of contiguous intervals (segments) with inclusive, exclusive or open-ended ends, similar to a `Vec<(Bound, Bound)>`. You can construct a basic range from one of the following build blocks. All other ranges are concatenation, union, and complement of these basic ranges. - `Ranges::empty()`: No version - `Ranges::full()`: All versions - `Ranges::singleton(v)`: Only the version v exactly - `Ranges::higher_than(v)`: All versions `v <= versions` - `Ranges::strictly_higher_than(v)`: All versions `v < versions` - `Ranges::lower_than(v)`: All versions `versions <= v` - `Ranges::strictly_lower_than(v)`: All versions `versions < v` - `Ranges::between(v1, v2)`: All versions `v1 <= versions < v2` The optimized operations include `complement`, `contains`, `contains_many`, `intersection`, `is_disjoint`, `subset_of` and `union`. `Ranges` is generic over any type that implements `Ord` + `Clone` and can represent all kinds of slices with ordered coordinates, not just version ranges. While built as a performance-critical piece of [pubgrub](https://github.com/pubgrub-rs/pubgrub), it can be adopted for other domains, too. ![A number line and a sample range on it](number-line-ranges.svg) You can imagine a `Ranges` as slices over a number line. Note that there are limitations to the equality implementation: Given a `Ranges`, the segments `(Unbounded, Included(42u32))` and `(Included(0), Included(42u32))` as well as `(Included(1), Included(5))` and `(Included(1), Included(3)) + (Included(4), Included(5))` are reported as unequal, even though the match the same versions: We can't tell that there isn't a version between `0` and `-inf` or `3` and `4` respectively. ## Optional features * `serde`: serialization and deserialization for the version range, given that the version type also supports it. * `proptest`: Exports are proptest strategy for `Ranges`. version-ranges-0.1.2/number-line-ranges.svg000064400000000000000000000066051046102023000170100ustar 00000000000000 version-ranges-0.1.2/src/lib.rs000064400000000000000000001612401046102023000144750ustar 00000000000000// SPDX-License-Identifier: MPL-2.0 //! This crate contains a performance-optimized type for generic version ranges and operations on //! them. //! //! [`Ranges`] can represent version selectors such as `(>=1, <2) OR (==3) OR (>4)`. Internally, //! it is an ordered list of contiguous intervals (segments) with inclusive, exclusive or open-ended //! ends, similar to a `Vec<(Bound, Bound)>`. //! //! You can construct a basic range from one of the following build blocks. All other ranges are //! concatenation, union, and complement of these basic ranges. //! - [empty()](Ranges::empty): No version //! - [full()](Ranges::full): All versions //! - [singleton(v)](Ranges::singleton): Only the version v exactly //! - [higher_than(v)](Ranges::higher_than): All versions `v <= versions` //! - [strictly_higher_than(v)](Ranges::strictly_higher_than): All versions `v < versions` //! - [lower_than(v)](Ranges::lower_than): All versions `versions <= v` //! - [strictly_lower_than(v)](Ranges::strictly_lower_than): All versions `versions < v` //! - [between(v1, v2)](Ranges::between): All versions `v1 <= versions < v2` //! //! [`Ranges`] is generic over any type that implements [`Ord`] + [`Clone`] and can represent all //! kinds of slices with ordered coordinates, not just version ranges. While built as a //! performance-critical piece of [pubgrub](https://github.com/pubgrub-rs/pubgrub), it can be //! adopted for other domains, too. //! //! Note that there are limitations to the equality implementation: Given a `Ranges`, //! the segments `(Unbounded, Included(42u32))` and `(Included(0), Included(42u32))` as well as //! `(Included(1), Included(5))` and `(Included(1), Included(3)) + (Included(4), Included(5))` //! are reported as unequal, even though the match the same versions: We can't tell that there isn't //! a version between `0` and `-inf` or `3` and `4` respectively. //! //! ## Optional features //! //! * `serde`: serialization and deserialization for the version range, given that the version type //! also supports it. //! * `proptest`: Exports are proptest strategy for [`Ranges`]. use std::borrow::Borrow; use std::cmp::Ordering; use std::fmt::{Debug, Display, Formatter}; use std::ops::Bound::{self, Excluded, Included, Unbounded}; use std::ops::RangeBounds; #[cfg(any(feature = "proptest", test))] use proptest::prelude::*; use smallvec::{smallvec, SmallVec}; /// Ranges represents multiple intervals of a continuous range of monotone increasing values. /// /// Internally, [`Ranges`] are an ordered list of segments, where segment is a bounds pair. /// /// Invariants: /// 1. The segments are sorted, from lowest to highest (through `Ord`). /// 2. Each segment contains at least one version (start < end). /// 3. There is at least one version between two segments. /// /// These ensure that equivalent instances have an identical representation, which is important /// for `Eq` and `Hash`. Note that this representation cannot strictly guaranty equality of /// [`Ranges`] with equality of its representation without also knowing the nature of the underlying /// versions. In particular, if the version space is discrete, different representations, using /// different types of bounds (exclusive/inclusive) may correspond to the same set of existing /// versions. It is a tradeoff we acknowledge, but which makes representations of continuous version /// sets more accessible, to better handle features like pre-releases and other types of version /// modifiers. For example, `[(Included(3u32), Excluded(7u32))]` and /// `[(Included(3u32), Included(6u32))]` refer to the same version set, since there is no version /// between 6 and 7, which this crate doesn't know about. #[derive(Debug, Clone, Eq, PartialEq, Hash)] #[cfg_attr(feature = "serde", derive(serde::Serialize))] #[cfg_attr(feature = "serde", serde(transparent))] pub struct Ranges { /// Profiling in showed /// that a single stack entry is the most efficient. This is most likely due to `Interval` /// being large. segments: SmallVec<[Interval; 1]>, } // TODO: Replace the tuple type with a custom enum inlining the bounds to reduce the type's size. type Interval = (Bound, Bound); impl Ranges { /// Empty set of versions. pub fn empty() -> Self { Self { segments: SmallVec::new(), } } /// Set of all possible versions pub fn full() -> Self { Self { segments: smallvec![(Unbounded, Unbounded)], } } /// Set of all versions higher or equal to some version pub fn higher_than(v: impl Into) -> Self { Self { segments: smallvec![(Included(v.into()), Unbounded)], } } /// Set of all versions higher to some version pub fn strictly_higher_than(v: impl Into) -> Self { Self { segments: smallvec![(Excluded(v.into()), Unbounded)], } } /// Set of all versions lower to some version pub fn strictly_lower_than(v: impl Into) -> Self { Self { segments: smallvec![(Unbounded, Excluded(v.into()))], } } /// Set of all versions lower or equal to some version pub fn lower_than(v: impl Into) -> Self { Self { segments: smallvec![(Unbounded, Included(v.into()))], } } /// Set of versions greater or equal to `v1` but less than `v2`. pub fn between(v1: impl Into, v2: impl Into) -> Self { Self { segments: smallvec![(Included(v1.into()), Excluded(v2.into()))], } } /// Whether the set is empty, i.e. it has not ranges pub fn is_empty(&self) -> bool { self.segments.is_empty() } } impl Ranges { /// Set containing exactly one version pub fn singleton(v: impl Into) -> Self { let v = v.into(); Self { segments: smallvec![(Included(v.clone()), Included(v))], } } /// Returns the complement, which contains everything not included in `self`. pub fn complement(&self) -> Self { match self.segments.first() { // Complement of ∅ is ∞ None => Self::full(), // Complement of ∞ is ∅ Some((Unbounded, Unbounded)) => Self::empty(), // First high bound is +∞ Some((Included(v), Unbounded)) => Self::strictly_lower_than(v.clone()), Some((Excluded(v), Unbounded)) => Self::lower_than(v.clone()), Some((Unbounded, Included(v))) => { Self::negate_segments(Excluded(v.clone()), &self.segments[1..]) } Some((Unbounded, Excluded(v))) => { Self::negate_segments(Included(v.clone()), &self.segments[1..]) } Some((Included(_), Included(_))) | Some((Included(_), Excluded(_))) | Some((Excluded(_), Included(_))) | Some((Excluded(_), Excluded(_))) => Self::negate_segments(Unbounded, &self.segments), } } /// Helper function performing the negation of intervals in segments. fn negate_segments(start: Bound, segments: &[Interval]) -> Self { let mut complement_segments = SmallVec::new(); let mut start = start; for (v1, v2) in segments { complement_segments.push(( start, match v1 { Included(v) => Excluded(v.clone()), Excluded(v) => Included(v.clone()), Unbounded => unreachable!(), }, )); start = match v2 { Included(v) => Excluded(v.clone()), Excluded(v) => Included(v.clone()), Unbounded => Unbounded, } } if !matches!(start, Unbounded) { complement_segments.push((start, Unbounded)); } Self { segments: complement_segments, } } } impl Ranges { /// If self contains exactly a single version, return it, otherwise, return [None]. pub fn as_singleton(&self) -> Option<&V> { match self.segments.as_slice() { [(Included(v1), Included(v2))] => { if v1 == v2 { Some(v1) } else { None } } _ => None, } } /// Convert to something that can be used with /// [BTreeMap::range](std::collections::BTreeMap::range). /// All versions contained in self, will be in the output, /// but there may be versions in the output that are not contained in self. /// Returns None if the range is empty. pub fn bounding_range(&self) -> Option<(Bound<&V>, Bound<&V>)> { self.segments.first().map(|(start, _)| { let end = self .segments .last() .expect("if there is a first element, there must be a last element"); (start.as_ref(), end.1.as_ref()) }) } /// Returns true if self contains the specified value. pub fn contains(&self, version: &Q) -> bool where V: Borrow, Q: ?Sized + PartialOrd, { self.segments .binary_search_by(|segment| { // We have to reverse because we need the segment wrt to the version, while // within bounds tells us the version wrt to the segment. within_bounds(version, segment).reverse() }) // An equal interval is one that contains the version .is_ok() } /// Returns true if self contains the specified values. /// /// The `versions` iterator must be sorted. /// Functionally equivalent to `versions.map(|v| self.contains(v))`. /// Except it runs in `O(size_of_range + len_of_versions)` not `O(size_of_range * len_of_versions)` pub fn contains_many<'s, I, BV>(&'s self, versions: I) -> impl Iterator + 's where I: Iterator + 's, BV: Borrow + 's, { #[cfg(debug_assertions)] let mut last: Option = None; versions.scan(0, move |i, v| { #[cfg(debug_assertions)] { if let Some(l) = last.as_ref() { assert!( l.borrow() <= v.borrow(), "`contains_many` `versions` argument incorrectly sorted" ); } } while let Some(segment) = self.segments.get(*i) { match within_bounds(v.borrow(), segment) { Ordering::Less => return Some(false), Ordering::Equal => return Some(true), Ordering::Greater => *i += 1, } } #[cfg(debug_assertions)] { last = Some(v); } Some(false) }) } /// Construct a simple range from anything that impls [RangeBounds] like `v1..v2`. pub fn from_range_bounds(bounds: R) -> Self where R: RangeBounds, IV: Clone + Into, { let start = match bounds.start_bound() { Included(v) => Included(v.clone().into()), Excluded(v) => Excluded(v.clone().into()), Unbounded => Unbounded, }; let end = match bounds.end_bound() { Included(v) => Included(v.clone().into()), Excluded(v) => Excluded(v.clone().into()), Unbounded => Unbounded, }; if valid_segment(&start, &end) { Self { segments: smallvec![(start, end)], } } else { Self::empty() } } /// See [`Ranges`] for the invariants checked. fn check_invariants(self) -> Self { if cfg!(debug_assertions) { for p in self.segments.as_slice().windows(2) { assert!(end_before_start_with_gap(&p[0].1, &p[1].0)); } for (s, e) in self.segments.iter() { assert!(valid_segment(s, e)); } } self } } /// Implementing `PartialOrd` for start `Bound` of an interval. /// /// Legend: `∞` is unbounded, `[1,2]` is `>=1,<=2`, `]1,2[` is `>1,<2`. /// /// ```text /// left: ∞-------] /// right: [-----] /// left is smaller, since it starts earlier. /// /// left: [-----] /// right: ]-----] /// left is smaller, since it starts earlier. /// ``` fn cmp_bounds_start(left: Bound<&V>, right: Bound<&V>) -> Option { Some(match (left, right) { // left: ∞----- // right: ∞----- (Unbounded, Unbounded) => Ordering::Equal, // left: [--- // right: ∞----- (Included(_left), Unbounded) => Ordering::Greater, // left: ]--- // right: ∞----- (Excluded(_left), Unbounded) => Ordering::Greater, // left: ∞----- // right: [--- (Unbounded, Included(_right)) => Ordering::Less, // left: [----- OR [----- OR [----- // right: [--- OR [----- OR [--- (Included(left), Included(right)) => left.partial_cmp(right)?, (Excluded(left), Included(right)) => match left.partial_cmp(right)? { // left: ]----- // right: [--- Ordering::Less => Ordering::Less, // left: ]----- // right: [--- Ordering::Equal => Ordering::Greater, // left: ]--- // right: [----- Ordering::Greater => Ordering::Greater, }, // left: ∞----- // right: ]--- (Unbounded, Excluded(_right)) => Ordering::Less, (Included(left), Excluded(right)) => match left.partial_cmp(right)? { // left: [----- // right: ]--- Ordering::Less => Ordering::Less, // left: [----- // right: ]--- Ordering::Equal => Ordering::Less, // left: [--- // right: ]----- Ordering::Greater => Ordering::Greater, }, // left: ]----- OR ]----- OR ]--- // right: ]--- OR ]----- OR ]----- (Excluded(left), Excluded(right)) => left.partial_cmp(right)?, }) } /// Implementing `PartialOrd` for end `Bound` of an interval. /// /// We flip the unbounded ranges from `-∞` to `∞`, while `V`-valued bounds checks remain the same. /// /// Legend: `∞` is unbounded, `[1,2]` is `>=1,<=2`, `]1,2[` is `>1,<2`. /// /// ```text /// left: [--------∞ /// right: [-----] /// left is greater, since it starts earlier. /// /// left: [-----[ /// right: [-----] /// left is smaller, since it ends earlier. /// ``` fn cmp_bounds_end(left: Bound<&V>, right: Bound<&V>) -> Option { Some(match (left, right) { // left: -----∞ // right: -----∞ (Unbounded, Unbounded) => Ordering::Equal, // left: ---] // right: -----∞ (Included(_left), Unbounded) => Ordering::Less, // left: ---[ // right: -----∞ (Excluded(_left), Unbounded) => Ordering::Less, // left: -----∞ // right: ---] (Unbounded, Included(_right)) => Ordering::Greater, // left: -----] OR -----] OR ---] // right: ---] OR -----] OR -----] (Included(left), Included(right)) => left.partial_cmp(right)?, (Excluded(left), Included(right)) => match left.partial_cmp(right)? { // left: ---[ // right: -----] Ordering::Less => Ordering::Less, // left: -----[ // right: -----] Ordering::Equal => Ordering::Less, // left: -----[ // right: ---] Ordering::Greater => Ordering::Greater, }, (Unbounded, Excluded(_right)) => Ordering::Greater, (Included(left), Excluded(right)) => match left.partial_cmp(right)? { // left: ---] // right: -----[ Ordering::Less => Ordering::Less, // left: -----] // right: -----[ Ordering::Equal => Ordering::Greater, // left: -----] // right: ---[ Ordering::Greater => Ordering::Greater, }, // left: -----[ OR -----[ OR ---[ // right: ---[ OR -----[ OR -----[ (Excluded(left), Excluded(right)) => left.partial_cmp(right)?, }) } impl PartialOrd for Ranges { /// A simple ordering scheme where we zip the segments and compare all bounds in order. If all /// bounds are equal, the longer range is considered greater. (And if all zipped bounds are /// equal and we have the same number of segments, the ranges are equal). fn partial_cmp(&self, other: &Self) -> Option { for (left, right) in self.segments.iter().zip(other.segments.iter()) { let start_cmp = cmp_bounds_start(left.start_bound(), right.start_bound())?; if start_cmp != Ordering::Equal { return Some(start_cmp); } let end_cmp = cmp_bounds_end(left.end_bound(), right.end_bound())?; if end_cmp != Ordering::Equal { return Some(end_cmp); } } Some(self.segments.len().cmp(&other.segments.len())) } } impl Ord for Ranges { fn cmp(&self, other: &Self) -> Ordering { self.partial_cmp(other) .expect("PartialOrd must be `Some(Ordering)` for types that implement `Ord`") } } /// The ordering of the version wrt to the interval. /// ```text /// |-------| /// ^ ^ ^ /// less equal greater /// ``` fn within_bounds(version: &Q, segment: &Interval) -> Ordering where V: Borrow, Q: ?Sized + PartialOrd, { let below_lower_bound = match segment { (Excluded(start), _) => version <= start.borrow(), (Included(start), _) => version < start.borrow(), (Unbounded, _) => false, }; if below_lower_bound { return Ordering::Less; } let below_upper_bound = match segment { (_, Unbounded) => true, (_, Included(end)) => version <= end.borrow(), (_, Excluded(end)) => version < end.borrow(), }; if below_upper_bound { return Ordering::Equal; } Ordering::Greater } /// A valid segment is one where at least one version fits between start and end fn valid_segment(start: &Bound, end: &Bound) -> bool { match (start, end) { // Singleton interval are allowed (Included(s), Included(e)) => s <= e, (Included(s), Excluded(e)) => s < e, (Excluded(s), Included(e)) => s < e, (Excluded(s), Excluded(e)) => s < e, (Unbounded, _) | (_, Unbounded) => true, } } /// The end of one interval is before the start of the next one, so they can't be concatenated /// into a single interval. The `union` method calling with both intervals and then the intervals /// switched. If either is true, the intervals are separate in the union and if both are false, they /// are merged. /// ```text /// True for these two: /// |----| /// |-----| /// ^ end ^ start /// False for these two: /// |----| /// |-----| /// Here it depends: If they both exclude the position they share, there is a version in between /// them that blocks concatenation /// |----| /// |-----| /// ``` fn end_before_start_with_gap(end: &Bound, start: &Bound) -> bool { match (end, start) { (_, Unbounded) => false, (Unbounded, _) => false, (Included(left), Included(right)) => left < right, (Included(left), Excluded(right)) => left < right, (Excluded(left), Included(right)) => left < right, (Excluded(left), Excluded(right)) => left <= right, } } fn left_start_is_smaller(left: Bound, right: Bound) -> bool { match (left, right) { (Unbounded, _) => true, (_, Unbounded) => false, (Included(l), Included(r)) => l <= r, (Excluded(l), Excluded(r)) => l <= r, (Included(l), Excluded(r)) => l <= r, (Excluded(l), Included(r)) => l < r, } } fn left_end_is_smaller(left: Bound, right: Bound) -> bool { match (left, right) { (_, Unbounded) => true, (Unbounded, _) => false, (Included(l), Included(r)) => l <= r, (Excluded(l), Excluded(r)) => l <= r, (Excluded(l), Included(r)) => l <= r, (Included(l), Excluded(r)) => l < r, } } /// Group adjacent versions locations. /// /// ```text /// [None, 3, 6, 7, None] -> [(3, 7)] /// [3, 6, 7, None] -> [(None, 7)] /// [3, 6, 7] -> [(None, None)] /// [None, 1, 4, 7, None, None, None, 8, None, 9] -> [(1, 7), (8, 8), (9, None)] /// ``` fn group_adjacent_locations( mut locations: impl Iterator>, ) -> impl Iterator, Option)> { // If the first version matched, then the lower bound of that segment is not needed let mut seg = locations.next().flatten().map(|ver| (None, Some(ver))); std::iter::from_fn(move || { for ver in locations.by_ref() { if let Some(ver) = ver { // As long as were still matching versions, we keep merging into the currently matching segment seg = Some((seg.map_or(Some(ver), |(s, _)| s), Some(ver))); } else { // If we have found a version that doesn't match, then right the merge segment and prepare for a new one. if seg.is_some() { return seg.take(); } } } // If the last version matched, then write out the merged segment but the upper bound is not needed. seg.take().map(|(s, _)| (s, None)) }) } impl Ranges { /// Computes the union of this `Ranges` and another. pub fn union(&self, other: &Self) -> Self { let mut output = SmallVec::new(); let mut accumulator: Option<(&Bound<_>, &Bound<_>)> = None; let mut left_iter = self.segments.iter().peekable(); let mut right_iter = other.segments.iter().peekable(); loop { let smaller_interval = match (left_iter.peek(), right_iter.peek()) { (Some((left_start, left_end)), Some((right_start, right_end))) => { if left_start_is_smaller(left_start.as_ref(), right_start.as_ref()) { left_iter.next(); (left_start, left_end) } else { right_iter.next(); (right_start, right_end) } } (Some((left_start, left_end)), None) => { left_iter.next(); (left_start, left_end) } (None, Some((right_start, right_end))) => { right_iter.next(); (right_start, right_end) } (None, None) => break, }; if let Some(accumulator_) = accumulator { if end_before_start_with_gap(accumulator_.1, smaller_interval.0) { output.push((accumulator_.0.clone(), accumulator_.1.clone())); accumulator = Some(smaller_interval); } else { let accumulator_end = match (accumulator_.1, smaller_interval.1) { (_, Unbounded) | (Unbounded, _) => &Unbounded, (Included(l), Excluded(r) | Included(r)) if l == r => accumulator_.1, (Included(l) | Excluded(l), Included(r) | Excluded(r)) => { if l > r { accumulator_.1 } else { smaller_interval.1 } } }; accumulator = Some((accumulator_.0, accumulator_end)); } } else { accumulator = Some(smaller_interval) } } if let Some(accumulator) = accumulator { output.push((accumulator.0.clone(), accumulator.1.clone())); } Self { segments: output }.check_invariants() } /// Computes the intersection of two sets of versions. pub fn intersection(&self, other: &Self) -> Self { let mut output = SmallVec::new(); let mut left_iter = self.segments.iter().peekable(); let mut right_iter = other.segments.iter().peekable(); // By the definition of intersection any point that is matched by the output // must have a segment in each of the inputs that it matches. // Therefore, every segment in the output must be the intersection of a segment from each of the inputs. // It would be correct to do the "O(n^2)" thing, by computing the intersection of every segment from one input // with every segment of the other input, and sorting the result. // We can avoid the sorting by generating our candidate segments with an increasing `end` value. while let Some(((left_start, left_end), (right_start, right_end))) = left_iter.peek().zip(right_iter.peek()) { // The next smallest `end` value is going to come from one of the inputs. let left_end_is_smaller = left_end_is_smaller(left_end.as_ref(), right_end.as_ref()); // Now that we are processing `end` we will never have to process any segment smaller than that. // We can ensure that the input that `end` came from is larger than `end` by advancing it one step. // `end` is the smaller available input, so we know the other input is already larger than `end`. // Note: We can call `other_iter.next_if( == end)`, but the ends lining up is rare enough that // it does not end up being faster in practice. let (other_start, end) = if left_end_is_smaller { left_iter.next(); (right_start, left_end) } else { right_iter.next(); (left_start, right_end) }; // `start` will either come from the input `end` came from or the other input, whichever one is larger. // The intersection is invalid if `start` > `end`. // But, we already know that the segments in our input are valid. // So we do not need to check if the `start` from the input `end` came from is smaller than `end`. // If the `other_start` is larger than end, then the intersection will be invalid. if !valid_segment(other_start, end) { // Note: We can call `this_iter.next_if(!valid_segment(other_start, this_end))` in a loop. // But the checks make it slower for the benchmarked inputs. continue; } let start = match (left_start, right_start) { (Included(l), Included(r)) => Included(std::cmp::max(l, r)), (Excluded(l), Excluded(r)) => Excluded(std::cmp::max(l, r)), (Included(i), Excluded(e)) | (Excluded(e), Included(i)) => { if i <= e { Excluded(e) } else { Included(i) } } (s, Unbounded) | (Unbounded, s) => s.as_ref(), }; // Now we clone and push a new segment. // By dealing with references until now we ensure that NO cloning happens when we reject the segment. output.push((start.cloned(), end.clone())) } Self { segments: output }.check_invariants() } /// Return true if there can be no `V` so that `V` is contained in both `self` and `other`. /// /// Note that we don't know that set of all existing `V`s here, so we only check if the segments /// are disjoint, not if no version is contained in both. pub fn is_disjoint(&self, other: &Self) -> bool { // The operation is symmetric let mut left_iter = self.segments.iter().peekable(); let mut right_iter = other.segments.iter().peekable(); while let Some((left, right)) = left_iter.peek().zip(right_iter.peek()) { if !valid_segment(&right.start_bound(), &left.end_bound()) { left_iter.next(); } else if !valid_segment(&left.start_bound(), &right.end_bound()) { right_iter.next(); } else { return false; } } // The remaining element(s) can't intersect anymore true } /// Return true if any `V` that is contained in `self` is also contained in `other`. /// /// Note that we don't know that set of all existing `V`s here, so we only check if all /// segments `self` are contained in a segment of `other`. pub fn subset_of(&self, other: &Self) -> bool { let mut containing_iter = other.segments.iter(); let mut subset_iter = self.segments.iter(); let Some(mut containing_elem) = containing_iter.next() else { // As long as we have subset elements, we need containing elements return subset_iter.next().is_none(); }; for subset_elem in subset_iter { // Check if the current containing element ends before the subset element. // There needs to be another containing element for our subset element in this case. while !valid_segment(&subset_elem.start_bound(), &containing_elem.end_bound()) { if let Some(containing_elem_) = containing_iter.next() { containing_elem = containing_elem_; } else { return false; }; } let start_contained = left_start_is_smaller(containing_elem.start_bound(), subset_elem.start_bound()); if !start_contained { // The start element is not contained return false; } let end_contained = left_end_is_smaller(subset_elem.end_bound(), containing_elem.end_bound()); if !end_contained { // The end element is not contained return false; } } true } /// Returns a simpler representation that contains the same versions. /// /// For every one of the Versions provided in versions the existing range and the simplified range will agree on whether it is contained. /// The simplified version may include or exclude versions that are not in versions as the implementation wishes. /// /// If none of the versions are contained in the original than the range will be returned unmodified. /// If the range includes a single version, it will be returned unmodified. /// If all the versions are contained in the original than the range will be simplified to `full`. /// /// If the given versions are not sorted the correctness of this function is not guaranteed. pub fn simplify<'s, I, BV>(&self, versions: I) -> Self where I: Iterator + 's, BV: Borrow + 's, { // Do not simplify singletons if self.as_singleton().is_some() { return self.clone(); } #[cfg(debug_assertions)] let mut last: Option = None; // Return the segment index in the range for each version in the range, None otherwise let version_locations = versions.scan(0, move |i, v| { #[cfg(debug_assertions)] { if let Some(l) = last.as_ref() { assert!( l.borrow() <= v.borrow(), "`simplify` `versions` argument incorrectly sorted" ); } } while let Some(segment) = self.segments.get(*i) { match within_bounds(v.borrow(), segment) { Ordering::Less => return Some(None), Ordering::Equal => return Some(Some(*i)), Ordering::Greater => *i += 1, } } #[cfg(debug_assertions)] { last = Some(v); } Some(None) }); let mut kept_segments = group_adjacent_locations(version_locations).peekable(); // Do not return null sets if kept_segments.peek().is_none() { return self.clone(); } self.keep_segments(kept_segments) } /// Create a new range with a subset of segments at given location bounds. /// /// Each new segment is constructed from a pair of segments, taking the /// start of the first and the end of the second. fn keep_segments( &self, kept_segments: impl Iterator, Option)>, ) -> Ranges { let mut segments = SmallVec::new(); for (s, e) in kept_segments { segments.push(( s.map_or(Unbounded, |s| self.segments[s].0.clone()), e.map_or(Unbounded, |e| self.segments[e].1.clone()), )); } Self { segments }.check_invariants() } /// Iterate over the parts of the range. pub fn iter(&self) -> impl Iterator, &Bound)> { self.segments.iter().map(|(start, end)| (start, end)) } } // Newtype to avoid leaking our internal representation. pub struct RangesIter(smallvec::IntoIter<[Interval; 1]>); impl Iterator for RangesIter { type Item = Interval; fn next(&mut self) -> Option { self.0.next() } fn size_hint(&self) -> (usize, Option) { (self.0.len(), Some(self.0.len())) } } impl ExactSizeIterator for RangesIter {} impl DoubleEndedIterator for RangesIter { fn next_back(&mut self) -> Option { self.0.next_back() } } impl IntoIterator for Ranges { type Item = (Bound, Bound); // Newtype to avoid leaking our internal representation. type IntoIter = RangesIter; fn into_iter(self) -> Self::IntoIter { RangesIter(self.segments.into_iter()) } } impl FromIterator<(Bound, Bound)> for Ranges { /// Constructor from arbitrary, unsorted and potentially overlapping ranges. /// /// This is equivalent, but faster, to computing the [`Ranges::union`] of the /// [`Ranges::from_range_bounds`] of each segment. fn from_iter, Bound)>>(iter: T) -> Self { // We have three constraints we need to fulfil: // 1. The segments are sorted, from lowest to highest (through `Ord`): By sorting. // 2. Each segment contains at least one version (start < end): By skipping invalid // segments. // 3. There is at least one version between two segments: By merging overlapping elements. // // Technically, the implementation has a O(n²) worst case complexity since we're inserting // and removing. This has two motivations: One is that we don't have any performance // critical usages of this method as of this writing, so we have no real world benchmark. // The other is that we get the elements from an iterator, so to avoid moving elements // around we would first need to build a different, sorted collection with extra // allocation(s), before we could build our real segments. --Konsti // For this implementation, we choose to only build a single smallvec and insert or remove // in it, instead of e.g. collecting the segments into a sorted datastructure first and then // construction the second smallvec without shifting. let mut segments: SmallVec<[Interval; 1]> = SmallVec::new(); for segment in iter { if !valid_segment(&segment.start_bound(), &segment.end_bound()) { continue; } // Find where to insert the new segment let insertion_point = segments.partition_point(|elem: &Interval| { cmp_bounds_start(elem.start_bound(), segment.start_bound()) .unwrap() .is_lt() }); // Is it overlapping with the previous segment? let previous_overlapping = insertion_point > 0 && !end_before_start_with_gap( &segments[insertion_point - 1].end_bound(), &segment.start_bound(), ); // Is it overlapping with the following segment? We'll check if there's more than one // overlap later. let next_overlapping = insertion_point < segments.len() && !end_before_start_with_gap( &segment.end_bound(), &segments[insertion_point].start_bound(), ); match (previous_overlapping, next_overlapping) { (true, true) => { // previous: |------| // segment: |------| // following: |------| // final: |---------------| // // OR // // previous: |------| // segment: |-----------| // following: |----| // final: |---------------| // // OR // // previous: |------| // segment: |----------------| // following: |----| |------| // final: |------------------------| // We merge all three segments into one, which is effectively removing one of // two previously inserted and changing the bounds on the other. // Remove all elements covered by the final element let mut following = segments.remove(insertion_point); while insertion_point < segments.len() && !end_before_start_with_gap( &segment.end_bound(), &segments[insertion_point].start_bound(), ) { following = segments.remove(insertion_point); } // Set end to max(segment.end, .end) if cmp_bounds_end(segment.end_bound(), following.end_bound()) .unwrap() .is_lt() { segments[insertion_point - 1].1 = following.1; } else { segments[insertion_point - 1].1 = segment.1; } } (true, false) => { // previous: |------| // segment: |------| // following: |------| // // OR // // previous: |----------| // segment: |---| // following: |------| // // final: |----------| |------| // We can reuse the existing element by extending it. // Set end to max(segment.end, .end) if cmp_bounds_end( segments[insertion_point - 1].end_bound(), segment.end_bound(), ) .unwrap() .is_lt() { segments[insertion_point - 1].1 = segment.1; } } (false, true) => { // previous: |------| // segment: |------| // following: |------| // final: |------| |----------| // // OR // // previous: |------| // segment: |----------| // following: |---| // final: |------| |----------| // // OR // // previous: |------| // segment: |------------| // following: |---| |------| // // final: |------| |-----------------| // We can reuse the existing element by extending it. // Remove all fully covered segments so the next element is the last one that // overlaps. while insertion_point + 1 < segments.len() && !end_before_start_with_gap( &segment.end_bound(), &segments[insertion_point + 1].start_bound(), ) { // We know that the one after also overlaps, so we can drop the current // following. segments.remove(insertion_point); } // Set end to max(segment.end, .end) if cmp_bounds_end(segments[insertion_point].end_bound(), segment.end_bound()) .unwrap() .is_lt() { segments[insertion_point].1 = segment.1; } segments[insertion_point].0 = segment.0; } (false, false) => { // previous: |------| // segment: |------| // following: |------| // // final: |------| |------| |------| // This line is O(n), which makes the algorithm O(n²), but it should be good // enough for now. segments.insert(insertion_point, segment); } } } Self { segments }.check_invariants() } } // REPORT ###################################################################### impl Display for Ranges { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { if self.segments.is_empty() { write!(f, "∅")?; } else { for (idx, segment) in self.segments.iter().enumerate() { if idx > 0 { write!(f, " | ")?; } match segment { (Unbounded, Unbounded) => write!(f, "*")?, (Unbounded, Included(v)) => write!(f, "<={v}")?, (Unbounded, Excluded(v)) => write!(f, "<{v}")?, (Included(v), Unbounded) => write!(f, ">={v}")?, (Included(v), Included(b)) => { if v == b { write!(f, "{v}")? } else { write!(f, ">={v}, <={b}")? } } (Included(v), Excluded(b)) => write!(f, ">={v}, <{b}")?, (Excluded(v), Unbounded) => write!(f, ">{v}")?, (Excluded(v), Included(b)) => write!(f, ">{v}, <={b}")?, (Excluded(v), Excluded(b)) => write!(f, ">{v}, <{b}")?, }; } } Ok(()) } } // SERIALIZATION ############################################################### #[cfg(feature = "serde")] impl<'de, V: serde::Deserialize<'de>> serde::Deserialize<'de> for Ranges { fn deserialize>(deserializer: D) -> Result { // This enables conversion from the "old" discrete implementation of `Ranges` to the new // bounded one. // // Serialization is always performed in the new format. #[derive(serde::Deserialize)] #[serde(untagged)] enum EitherInterval { B(Bound, Bound), D(V, Option), } let bounds: SmallVec<[EitherInterval; 2]> = serde::Deserialize::deserialize(deserializer)?; let mut segments = SmallVec::new(); for i in bounds { match i { EitherInterval::B(l, r) => segments.push((l, r)), EitherInterval::D(l, Some(r)) => segments.push((Included(l), Excluded(r))), EitherInterval::D(l, None) => segments.push((Included(l), Unbounded)), } } Ok(Ranges { segments }) } } /// Generate version sets from a random vector of deltas between randomly inclusive or exclusive /// bounds. #[cfg(any(feature = "proptest", test))] pub fn proptest_strategy() -> impl Strategy> { ( any::(), prop::collection::vec(any::<(u32, bool)>(), 0..10), ) .prop_map(|(start_unbounded, deltas)| { let mut start = if start_unbounded { Some(Unbounded) } else { None }; let mut largest: u32 = 0; let mut last_bound_was_inclusive = false; let mut segments = SmallVec::new(); for (delta, inclusive) in deltas { // Add the offset to the current bound largest = match largest.checked_add(delta) { Some(s) => s, None => { // Skip this offset, if it would result in a too large bound. continue; } }; let current_bound = if inclusive { Included(largest) } else { Excluded(largest) }; // If we already have a start bound, the next offset defines the complete range. // If we don't have a start bound, we have to generate one. if let Some(start_bound) = start.take() { // If the delta from the start bound is 0, the only authorized configuration is // Included(x), Included(x) if delta == 0 && !(matches!(start_bound, Included(_)) && inclusive) { start = Some(start_bound); continue; } last_bound_was_inclusive = inclusive; segments.push((start_bound, current_bound)); } else { // If the delta from the end bound of the last range is 0 and // any of the last ending or current starting bound is inclusive, // we skip the delta because they basically overlap. if delta == 0 && (last_bound_was_inclusive || inclusive) { continue; } start = Some(current_bound); } } // If we still have a start bound, but didn't have enough deltas to complete another // segment, we add an unbounded upperbound. if let Some(start_bound) = start { segments.push((start_bound, Unbounded)); } Ranges { segments }.check_invariants() }) } #[cfg(test)] pub mod tests { use proptest::prelude::*; use super::*; fn version_strat() -> impl Strategy { any::() } proptest! { // Testing serde ---------------------------------- #[cfg(feature = "serde")] #[test] fn serde_round_trip(range in proptest_strategy()) { let s = ron::ser::to_string(&range).unwrap(); let r = ron::de::from_str(&s).unwrap(); assert_eq!(range, r); } // Testing negate ---------------------------------- #[test] fn negate_is_different(range in proptest_strategy()) { assert_ne!(range.complement(), range); } #[test] fn double_negate_is_identity(range in proptest_strategy()) { assert_eq!(range.complement().complement(), range); } #[test] fn negate_contains_opposite(range in proptest_strategy(), version in version_strat()) { assert_ne!(range.contains(&version), range.complement().contains(&version)); } // Testing intersection ---------------------------- #[test] fn intersection_is_symmetric(r1 in proptest_strategy(), r2 in proptest_strategy()) { assert_eq!(r1.intersection(&r2), r2.intersection(&r1)); } #[test] fn intersection_with_any_is_identity(range in proptest_strategy()) { assert_eq!(Ranges::full().intersection(&range), range); } #[test] fn intersection_with_none_is_none(range in proptest_strategy()) { assert_eq!(Ranges::empty().intersection(&range), Ranges::empty()); } #[test] fn intersection_is_idempotent(r1 in proptest_strategy(), r2 in proptest_strategy()) { assert_eq!(r1.intersection(&r2).intersection(&r2), r1.intersection(&r2)); } #[test] fn intersection_is_associative(r1 in proptest_strategy(), r2 in proptest_strategy(), r3 in proptest_strategy()) { assert_eq!(r1.intersection(&r2).intersection(&r3), r1.intersection(&r2.intersection(&r3))); } #[test] fn intesection_of_complements_is_none(range in proptest_strategy()) { assert_eq!(range.complement().intersection(&range), Ranges::empty()); } #[test] fn intesection_contains_both(r1 in proptest_strategy(), r2 in proptest_strategy(), version in version_strat()) { assert_eq!(r1.intersection(&r2).contains(&version), r1.contains(&version) && r2.contains(&version)); } // Testing union ----------------------------------- #[test] fn union_of_complements_is_any(range in proptest_strategy()) { assert_eq!(range.complement().union(&range), Ranges::full()); } #[test] fn union_contains_either(r1 in proptest_strategy(), r2 in proptest_strategy(), version in version_strat()) { assert_eq!(r1.union(&r2).contains(&version), r1.contains(&version) || r2.contains(&version)); } #[test] fn is_disjoint_through_intersection(r1 in proptest_strategy(), r2 in proptest_strategy()) { let disjoint_def = r1.intersection(&r2) == Ranges::empty(); assert_eq!(r1.is_disjoint(&r2), disjoint_def); } #[test] fn subset_of_through_intersection(r1 in proptest_strategy(), r2 in proptest_strategy()) { let disjoint_def = r1.intersection(&r2) == r1; assert_eq!(r1.subset_of(&r2), disjoint_def); } #[test] fn union_through_intersection(r1 in proptest_strategy(), r2 in proptest_strategy()) { let union_def = r1 .complement() .intersection(&r2.complement()) .complement() .check_invariants(); assert_eq!(r1.union(&r2), union_def); } // Testing contains -------------------------------- #[test] fn always_contains_exact(version in version_strat()) { assert!(Ranges::::singleton(version).contains(&version)); } #[test] fn contains_negation(range in proptest_strategy(), version in version_strat()) { assert_ne!(range.contains(&version), range.complement().contains(&version)); } #[test] fn contains_intersection(range in proptest_strategy(), version in version_strat()) { assert_eq!(range.contains(&version), range.intersection(&Ranges::singleton(version)) != Ranges::empty()); } #[test] fn contains_bounding_range(range in proptest_strategy(), version in version_strat()) { if range.contains(&version) { assert!(range.bounding_range().map(|b| b.contains(&version)).unwrap_or(false)); } } #[test] fn from_range_bounds(range in any::<(Bound, Bound)>(), version in version_strat()) { let rv: Ranges<_> = Ranges::::from_range_bounds(range); assert_eq!(range.contains(&version), rv.contains(&version)); } #[test] fn from_range_bounds_round_trip(range in any::<(Bound, Bound)>()) { let rv: Ranges = Ranges::from_range_bounds(range); let rv2: Ranges = rv.bounding_range().map(Ranges::from_range_bounds::<_, u32>).unwrap_or_else(Ranges::empty); assert_eq!(rv, rv2); } #[test] fn contains(range in proptest_strategy(), versions in proptest::collection::vec(version_strat(), ..30)) { for v in versions { assert_eq!(range.contains(&v), range.segments.iter().any(|s| RangeBounds::contains(s, &v))); } } #[test] fn contains_many(range in proptest_strategy(), mut versions in proptest::collection::vec(version_strat(), ..30)) { versions.sort(); assert_eq!(versions.len(), range.contains_many(versions.iter()).count()); for (a, b) in versions.iter().zip(range.contains_many(versions.iter())) { assert_eq!(range.contains(a), b); } } #[test] fn simplify(range in proptest_strategy(), mut versions in proptest::collection::vec(version_strat(), ..30)) { versions.sort(); let simp = range.simplify(versions.iter()); for v in versions { assert_eq!(range.contains(&v), simp.contains(&v)); } assert!(simp.segments.len() <= range.segments.len()) } #[test] fn from_iter_valid(segments in proptest::collection::vec(any::<(Bound, Bound)>(), ..30)) { let mut expected = Ranges::empty(); for segment in &segments { expected = expected.union(&Ranges::from_range_bounds(*segment)); } let actual = Ranges::from_iter(segments.clone()); assert_eq!(expected, actual, "{segments:?}"); } } #[test] fn contains_many_can_take_owned() { let range: Ranges = Ranges::singleton(1); let versions = vec![1, 2, 3]; // Check that iter can be a Cow assert_eq!( range.contains_many(versions.iter()).count(), range .contains_many(versions.iter().map(std::borrow::Cow::Borrowed)) .count() ); // Check that iter can be a V assert_eq!( range.contains_many(versions.iter()).count(), range.contains_many(versions.into_iter()).count() ); } #[test] fn contains_can_take_owned() { let range: Ranges> = Ranges::singleton(1); let version = 1; assert_eq!(range.contains(&Box::new(version)), range.contains(&version)); let range: Ranges = Ranges::singleton(1.to_string()); let version = 1.to_string(); assert_eq!(range.contains(&version), range.contains("1")); } #[test] fn simplify_can_take_owned() { let range: Ranges = Ranges::singleton(1); let versions = vec![1, 2, 3]; // Check that iter can be a Cow assert_eq!( range.simplify(versions.iter()), range.simplify(versions.iter().map(std::borrow::Cow::Borrowed)) ); // Check that iter can be a V assert_eq!( range.simplify(versions.iter()), range.simplify(versions.into_iter()) ); } #[test] fn version_ord() { let versions: &[Ranges] = &[ Ranges::strictly_lower_than(1u32), Ranges::lower_than(1u32), Ranges::singleton(1u32), Ranges::between(1u32, 3u32), Ranges::higher_than(1u32), Ranges::strictly_higher_than(1u32), Ranges::singleton(2u32), Ranges::singleton(2u32).union(&Ranges::singleton(3u32)), Ranges::singleton(2u32) .union(&Ranges::singleton(3u32)) .union(&Ranges::singleton(4u32)), Ranges::singleton(2u32).union(&Ranges::singleton(4u32)), Ranges::singleton(3u32), ]; let mut versions_sorted = versions.to_vec(); versions_sorted.sort(); assert_eq!(versions_sorted, versions); // Check that the sorting isn't just stable because we're returning equal. let mut version_reverse_sorted = versions.to_vec(); version_reverse_sorted.reverse(); version_reverse_sorted.sort(); assert_eq!(version_reverse_sorted, versions); } }