pyproject-toml-0.13.7/.cargo_vcs_info.json0000644000000001360000000000100141140ustar { "git": { "sha1": "02d274155edf0faf08f8600f0048199067fec26d" }, "path_in_vcs": "" }pyproject-toml-0.13.7/.github/renovate.json5000064400000000000000000000004241046102023000170470ustar 00000000000000{ "$schema": "https://docs.renovatebot.com/renovate-schema.json", "extends": [ "config:recommended", "helpers:pinGitHubActionDigests", ":configMigration", ":semanticCommitsDisabled" ], "schedule": [ "before 4am on the first day of the month" ] } pyproject-toml-0.13.7/.github/workflows/CI.yml000064400000000000000000000033561046102023000173260ustar 00000000000000on: push: branches: - main pull_request: name: CI jobs: lint-rust: name: Lint Rust runs-on: ubuntu-latest steps: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 - uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2 - run: rustup component add clippy rustfmt - name: Rustfmt run: cargo fmt --all -- --check - name: Clippy env: RUSTFLAGS: -D warnings run: cargo clippy --workspace - name: Clippy (all features) env: RUSTFLAGS: -D warnings run: cargo clippy --workspace --all-features - name: Check documentation env: RUSTDOCFLAGS: -D warnings run: cargo doc --workspace --all-features --no-deps --document-private-items test: name: Test Suite runs-on: ${{ matrix.os }} strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] steps: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 - uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2 - run: cargo test check-wasm: runs-on: ubuntu-latest steps: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 - uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2 - run: rustup target add wasm32-unknown-unknown - run: cargo check --target wasm32-unknown-unknown minimal-versions: runs-on: ubuntu-latest steps: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 - uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2 - run: cargo +nightly update -Z minimal-versions - run: cargo test pyproject-toml-0.13.7/.github/workflows/release.yml000064400000000000000000000005601046102023000204450ustar 00000000000000name: Release on: push: tags: - v* jobs: crates-io: runs-on: ubuntu-latest steps: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 - uses: dtolnay/rust-toolchain@stable - name: Push to crates.io env: CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} run: cargo publish pyproject-toml-0.13.7/.gitignore000064400000000000000000000000101046102023000146630ustar 00000000000000/target pyproject-toml-0.13.7/Cargo.lock0000644000000465270000000000100121050ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "aho-corasick" version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] [[package]] name = "boxcar" version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26c4925bc979b677330a8c7fe7a8c94af2dbb4a2d37b4a20a80d884400f46baa" [[package]] name = "console" version = "0.15.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8" dependencies = [ "encode_unicode", "libc", "once_cell", "windows-sys", ] [[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 = "either" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "encode_unicode" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" [[package]] name = "equivalent" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "form_urlencoded" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" dependencies = [ "percent-encoding", ] [[package]] name = "glob" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" [[package]] name = "hashbrown" version = "0.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" [[package]] name = "icu_collections" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" dependencies = [ "displaydoc", "potential_utf", "yoke", "zerofrom", "zerovec", ] [[package]] name = "icu_locale_core" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" dependencies = [ "displaydoc", "litemap", "tinystr", "writeable", "zerovec", ] [[package]] name = "icu_normalizer" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" dependencies = [ "displaydoc", "icu_collections", "icu_normalizer_data", "icu_properties", "icu_provider", "smallvec", "zerovec", ] [[package]] name = "icu_normalizer_data" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" [[package]] name = "icu_properties" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" dependencies = [ "displaydoc", "icu_collections", "icu_locale_core", "icu_properties_data", "icu_provider", "potential_utf", "zerotrie", "zerovec", ] [[package]] name = "icu_properties_data" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" [[package]] name = "icu_provider" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" dependencies = [ "displaydoc", "icu_locale_core", "stable_deref_trait", "tinystr", "writeable", "yoke", "zerofrom", "zerotrie", "zerovec", ] [[package]] name = "idna" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" 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 = "indexmap" version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2481980430f9f78649238835720ddccc57e52df14ffce1c6f37391d61b563e9" dependencies = [ "equivalent", "hashbrown", "serde", ] [[package]] name = "insta" version = "1.43.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46fdb647ebde000f43b5b53f773c30cf9b0cb4300453208713fa38b2c70935a0" dependencies = [ "console", "once_cell", "similar", ] [[package]] name = "itertools" version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" dependencies = [ "either", ] [[package]] name = "libc" version = "0.2.174" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" [[package]] name = "litemap" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" [[package]] name = "memchr" version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" [[package]] name = "once_cell" version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "pep440_rs" version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "31095ca1f396e3de32745f42b20deef7bc09077f918b085307e8eab6ddd8fb9c" dependencies = [ "once_cell", "serde", "tracing", "unicode-width", "unscanny", "version-ranges", ] [[package]] name = "pep508_rs" version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "faee7227064121fcadcd2ff788ea26f0d8f2bd23a0574da11eca23bc935bcc05" dependencies = [ "boxcar", "indexmap", "itertools", "once_cell", "pep440_rs", "regex", "rustc-hash", "serde", "smallvec", "thiserror 1.0.69", "tracing", "unicode-width", "url", "urlencoding", "version-ranges", ] [[package]] name = "percent-encoding" version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pin-project-lite" version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" [[package]] name = "potential_utf" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" dependencies = [ "zerovec", ] [[package]] name = "proc-macro2" version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" dependencies = [ "unicode-ident", ] [[package]] name = "pyproject-toml" version = "0.13.7" dependencies = [ "glob", "indexmap", "insta", "pep440_rs", "pep508_rs", "serde", "thiserror 2.0.16", "toml", ] [[package]] name = "quote" version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] [[package]] name = "regex" version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", "regex-automata", "regex-syntax", ] [[package]] name = "regex-automata" version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", "regex-syntax", ] [[package]] name = "regex-syntax" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "rustc-hash" version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" [[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 = "serde_spanned" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40734c41988f7306bb04f0ecf60ec0f3f1caa34290e4e8ea471dcd3346483b83" dependencies = [ "serde", ] [[package]] name = "similar" version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" [[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.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" [[package]] name = "syn" version = "2.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" 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 = "thiserror" version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ "thiserror-impl 1.0.69", ] [[package]] name = "thiserror" version = "2.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0" dependencies = [ "thiserror-impl 2.0.16", ] [[package]] name = "thiserror-impl" version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "thiserror-impl" version = "2.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "tinystr" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" dependencies = [ "displaydoc", "zerovec", ] [[package]] name = "toml" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75129e1dc5000bfbaa9fee9d1b21f974f9fbad9daec557a521ee6e080825f6e8" dependencies = [ "indexmap", "serde", "serde_spanned", "toml_datetime", "toml_parser", "toml_writer", "winnow", ] [[package]] name = "toml_datetime" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bade1c3e902f58d73d3f294cd7f20391c1cb2fbcb643b73566bc773971df91e3" dependencies = [ "serde", ] [[package]] name = "toml_parser" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b551886f449aa90d4fe2bdaa9f4a2577ad2dde302c61ecf262d80b116db95c10" dependencies = [ "winnow", ] [[package]] name = "toml_writer" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcc842091f2def52017664b53082ecbbeb5c7731092bad69d2c63050401dfd64" [[package]] name = "tracing" version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ "pin-project-lite", "tracing-attributes", "tracing-core", ] [[package]] name = "tracing-attributes" version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "tracing-core" version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" dependencies = [ "once_cell", ] [[package]] name = "unicode-ident" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] name = "unicode-width" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c" [[package]] name = "unscanny" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e9df2af067a7953e9c3831320f35c1cc0600c30d44d9f7a12b01db1cd88d6b47" [[package]] name = "url" version = "2.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" dependencies = [ "form_urlencoded", "idna", "percent-encoding", "serde", ] [[package]] name = "urlencoding" version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" [[package]] name = "utf8_iter" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "version-ranges" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8d079415ceb2be83fc355adbadafe401307d5c309c7e6ade6638e6f9f42f42d" dependencies = [ "smallvec", ] [[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 = "winnow" version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95" [[package]] name = "writeable" version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" [[package]] name = "yoke" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" dependencies = [ "serde", "stable_deref_trait", "yoke-derive", "zerofrom", ] [[package]] name = "yoke-derive" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" dependencies = [ "proc-macro2", "quote", "syn", "synstructure", ] [[package]] name = "zerofrom" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", "syn", "synstructure", ] [[package]] name = "zerotrie" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" dependencies = [ "displaydoc", "yoke", "zerofrom", ] [[package]] name = "zerovec" version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" dependencies = [ "yoke", "zerofrom", "zerovec-derive", ] [[package]] name = "zerovec-derive" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" dependencies = [ "proc-macro2", "quote", "syn", ] pyproject-toml-0.13.7/Cargo.toml0000644000000027640000000000100121230ustar # 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" rust-version = "1.74" name = "pyproject-toml" version = "0.13.7" build = false autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "pyproject.toml parser in Rust" readme = "README.md" keywords = [ "pyproject", "pep517", "pep518", "pep621", "pep639", ] license = "MIT" repository = "https://github.com/PyO3/pyproject-toml-rs.git" [features] pep639-glob = ["glob"] tracing = [ "pep440_rs/tracing", "pep508_rs/tracing", ] [lib] name = "pyproject_toml" path = "src/lib.rs" [dependencies.glob] version = "0.3.1" optional = true [dependencies.indexmap] version = "2.6.0" features = ["serde"] [dependencies.pep440_rs] version = "0.7.2" [dependencies.pep508_rs] version = "0.9.1" [dependencies.serde] version = "1.0.214" features = ["derive"] [dependencies.thiserror] version = "2.0.12" [dependencies.toml] version = "0.9.4" features = [ "parse", "serde", "std", ] default-features = false [dev-dependencies.insta] version = "1.41.0" pyproject-toml-0.13.7/Cargo.toml.orig000064400000000000000000000015741046102023000156020ustar 00000000000000[package] name = "pyproject-toml" version = "0.13.7" description = "pyproject.toml parser in Rust" edition = "2021" license = "MIT" keywords = ["pyproject", "pep517", "pep518", "pep621", "pep639"] readme = "README.md" repository = "https://github.com/PyO3/pyproject-toml-rs.git" rust-version = "1.74" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] glob = { version = "0.3.1", optional = true } indexmap = { version = "2.6.0", features = ["serde"] } pep440_rs = { version = "0.7.2" } pep508_rs = { version = "0.9.1" } serde = { version = "1.0.214", features = ["derive"] } thiserror = { version = "2.0.12" } toml = { version = "0.9.4", default-features = false, features = ["parse", "serde", "std"] } [features] tracing = ["pep440_rs/tracing", "pep508_rs/tracing"] pep639-glob = ["glob"] [dev-dependencies] insta = "1.41.0" pyproject-toml-0.13.7/Changelog.md000064400000000000000000000031741046102023000151220ustar 00000000000000# Changelog ## Unreleased ## 0.13.7 * Normalize extra names in optional dependencies ## 0.13.6 * Support resolving optional dependencies and dependency groups * Update toml to 0.9 ## 0.13.5 * Better PEP 639 `license-files` glob validation and error messages from uv * A public `check_pep639_glob` function for using the PEP 639 support with a different glob crate. ## 0.13.4 * Update pep440_rs to 0.7.2 * Update pep508_rs to 0.9.1 * Ensure wasm32-unknown-unknown support ## 0.13.3 * Make `license` and `license_files` public again * Add accessors for email / name on Contact * Add a method to resolve dependency groups into concrete lists of dependencies ## 0.13.2 * Make `Contact` definition strict ## 0.13.1 * Fix `Contact` definition ## 0.13.0 * Update to the provisional PEP 639. This is technically a breaking change, but only for fields previously in draft * Update pep440_rs to 0.7.1 * Update pep508_rs to 0.8.0 ## 0.12.0 * Support dependency groups (PEP 735) ## 0.11.0 * Update pep440_rs to 0.6.0 * Update pep508_rs to 0.6.0 ## 0.8.0 * The `build_system` table is now optional. There are many projects that use pyproject.toml for tool configuration without specifying a build backend, which this change reflects. ## 0.6.0 * Update to latest [PEP 639](https://peps.python.org/pep-0639) draft. The `license` key is now an enum that can either be an SPDX identifier or the previous table form, which accepting PEP 639 would deprecate. The previous implementation of a `project.license-expression` key in `pyproject.toml` has been [removed](https://peps.python.org/pep-0639/#define-a-new-top-level-license-expression-key). pyproject-toml-0.13.7/LICENSE000064400000000000000000000021061046102023000137100ustar 00000000000000MIT License Copyright (c) 2021-present PyO3 Project and Contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. pyproject-toml-0.13.7/README.md000064400000000000000000000032401046102023000141620ustar 00000000000000# pyproject-toml-rs [![Crates.io](https://img.shields.io/crates/v/pyproject-toml.svg)](https://crates.io/crates/pyproject-toml) [![docs.rs](https://docs.rs/pyproject-toml/badge.svg)](https://docs.rs/pyproject-toml/) `pyproject.toml` parser in Rust. ## Installation Add it to your ``Cargo.toml``: ```toml [dependencies] pyproject-toml = "0.8" ``` then you are good to go. If you are using Rust 2015 you have to add ``extern crate pyproject_toml`` to your crate root as well. ## Extended parsing If you want to add additional fields parsing, you can do it with [`serde`](https://github.com/serde-rs/serde)'s [`flatten`](https://serde.rs/field-attrs.html#flatten) feature and implement the [`Deref`](https://doc.rust-lang.org/std/ops/trait.Deref.html) trait, for example: ```rust use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, Debug, Clone)] pub struct PyProjectToml { #[serde(flatten)] inner: pyproject_toml::PyProjectToml, tool: Option, } #[derive(Serialize, Deserialize, Debug, Clone)] #[serde(rename_all = "kebab-case")] pub struct Tool { maturin: Option, } #[derive(Serialize, Deserialize, Debug, Clone)] #[serde(rename_all = "kebab-case")] pub struct ToolMaturin { sdist_include: Option>, } impl std::ops::Deref for PyProjectToml { type Target = pyproject_toml::PyProjectToml; fn deref(&self) -> &Self::Target { &self.inner } } impl PyProjectToml { pub fn new(content: &str) -> Result { toml::from_str(content) } } ``` ## License This work is released under the MIT license. A copy of the license is provided in the [LICENSE](./LICENSE) file. pyproject-toml-0.13.7/src/lib.rs000064400000000000000000000447721046102023000146250ustar 00000000000000#[cfg(feature = "pep639-glob")] mod pep639_glob; mod resolution; #[cfg(feature = "pep639-glob")] pub use pep639_glob::{check_pep639_glob, parse_pep639_glob, Pep639GlobError}; pub use resolution::ResolveError; use indexmap::IndexMap; use pep440_rs::{Version, VersionSpecifiers}; use pep508_rs::Requirement; use resolution::resolve; use serde::{Deserialize, Serialize}; use std::ops::Deref; use std::path::PathBuf; /// The `[build-system]` section of a pyproject.toml as specified in PEP 517 #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] #[serde(rename_all = "kebab-case")] pub struct BuildSystem { /// PEP 508 dependencies required to execute the build system pub requires: Vec, /// A string naming a Python object that will be used to perform the build pub build_backend: Option, /// Specify that their backend code is hosted in-tree, this key contains a list of directories pub backend_path: Option>, } /// A pyproject.toml as specified in PEP 517 #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] #[serde(rename_all = "kebab-case")] pub struct PyProjectToml { /// Build-related data pub build_system: Option, /// Project metadata pub project: Option, /// Dependency groups table pub dependency_groups: Option, } /// PEP 621 project metadata #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] #[serde(rename_all = "kebab-case")] pub struct Project { /// The name of the project // TODO: This should become a `PackageName` in the next breaking release. pub name: String, /// The version of the project as supported by PEP 440 pub version: Option, /// The summary description of the project pub description: Option, /// The full description of the project (i.e. the README) pub readme: Option, /// The Python version requirements of the project pub requires_python: Option, /// The license under which the project is distributed /// /// Supports both the current standard and the provisional PEP 639 pub license: Option, /// The paths to files containing licenses and other legal notices to be distributed with the /// project. /// /// Use `parse_pep639_glob` from the optional `pep639-glob` feature to find the matching files. /// /// Note that this doesn't check the PEP 639 rules for combining `license_files` and `license`. /// /// From the provisional PEP 639 pub license_files: Option>, /// The people or organizations considered to be the "authors" of the project pub authors: Option>, /// Similar to "authors" in that its exact meaning is open to interpretation pub maintainers: Option>, /// The keywords for the project pub keywords: Option>, /// Trove classifiers which apply to the project pub classifiers: Option>, /// A table of URLs where the key is the URL label and the value is the URL itself pub urls: Option>, /// Entry points pub entry_points: Option>>, /// Corresponds to the console_scripts group in the core metadata pub scripts: Option>, /// Corresponds to the gui_scripts group in the core metadata pub gui_scripts: Option>, /// Project dependencies pub dependencies: Option>, /// Optional dependencies // TODO: The `String` should become a `ExtraName` in the next breaking release. pub optional_dependencies: Option>>, /// Specifies which fields listed by PEP 621 were intentionally unspecified /// so another tool can/will provide such metadata dynamically. pub dynamic: Option>, } impl Project { /// Initializes the only field mandatory in PEP 621 (`name`) and leaves everything else empty pub fn new(name: String) -> Self { Self { name, version: None, description: None, readme: None, requires_python: None, license: None, license_files: None, authors: None, maintainers: None, keywords: None, classifiers: None, urls: None, entry_points: None, scripts: None, gui_scripts: None, dependencies: None, optional_dependencies: None, dynamic: None, } } } /// The full description of the project (i.e. the README). #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] #[serde(rename_all = "kebab-case")] #[serde(untagged)] pub enum ReadMe { /// Relative path to a text file containing the full description RelativePath(String), /// Detailed readme information #[serde(rename_all = "kebab-case")] Table { /// A relative path to a file containing the full description file: Option, /// Full description text: Option, /// The content-type of the full description content_type: Option, }, } /// The optional `project.license` key /// /// Specified in . #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] #[serde(untagged)] pub enum License { /// An SPDX Expression. /// /// Note that this doesn't check the validity of the SPDX expression or PEP 639 rules. /// /// From the provisional PEP 639. Spdx(String), Text { /// The full text of the license. text: String, }, File { /// The file containing the license text. file: PathBuf, }, } /// A `project.authors` or `project.maintainers` entry. /// /// Specified in /// . /// /// The entry is derived from the email format of `John Doe `. You need to /// provide at least name or email. #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] // deny_unknown_fields prevents using the name field when the email is not a string. #[serde( untagged, deny_unknown_fields, expecting = "a table with 'name' and/or 'email' keys" )] pub enum Contact { /// TODO(konsti): RFC 822 validation. NameEmail { name: String, email: String }, /// TODO(konsti): RFC 822 validation. Name { name: String }, /// TODO(konsti): RFC 822 validation. Email { email: String }, } impl Contact { /// Returns the name of the contact. pub fn name(&self) -> Option<&str> { match self { Contact::NameEmail { name, .. } | Contact::Name { name } => Some(name), Contact::Email { .. } => None, } } /// Returns the email of the contact. pub fn email(&self) -> Option<&str> { match self { Contact::NameEmail { email, .. } | Contact::Email { email } => Some(email), Contact::Name { .. } => None, } } } /// The `[dependency-groups]` section of pyproject.toml, as specified in PEP 735 #[derive(Serialize, Deserialize, Default, Debug, Clone, PartialEq, Eq)] #[serde(transparent)] // TODO: The `String` should become a `ExtraName` in the next breaking release. pub struct DependencyGroups(pub IndexMap>); impl Deref for DependencyGroups { type Target = IndexMap>; fn deref(&self) -> &Self::Target { &self.0 } } /// A specifier item in a Dependency Group #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] #[serde(rename_all = "kebab-case", untagged)] #[allow(clippy::large_enum_variant)] pub enum DependencyGroupSpecifier { /// PEP 508 requirement string String(Requirement), /// Include another dependency group #[serde(rename_all = "kebab-case")] Table { /// The name of the group to include include_group: String, }, } /// Optional dependencies and dependency groups resolved into flat lists of requirements that are /// not self-referential /// /// Note that `project.name` is required to resolve self-referential optional dependencies #[derive(Clone, Debug, Default, PartialEq, Eq)] pub struct ResolvedDependencies { pub optional_dependencies: IndexMap>, pub dependency_groups: IndexMap>, } impl PyProjectToml { /// Parse `pyproject.toml` content pub fn new(content: &str) -> Result { toml::de::from_str(content) } /// Resolve the optional dependencies (extras) and dependency groups into flat lists of /// requirements. /// /// This function will recursively resolve all optional dependency groups and dependency groups, /// including those that reference other groups. It will return an error if /// - there is a cycle in the groups, or /// - a group references another group that does not exist. /// /// Resolving self-referential optional dependencies requires `project.name` to be set. /// /// Note: This method makes no guarantee about the order of items and whether duplicates are /// removed or not. pub fn resolve(&self) -> Result { let self_reference_name = self.project.as_ref().map(|p| p.name.as_str()); let optional_dependencies = self .project .as_ref() .and_then(|p| p.optional_dependencies.as_ref()); let dependency_groups = self.dependency_groups.as_ref(); let resolved_dependencies = resolve( self_reference_name, optional_dependencies, dependency_groups, )?; Ok(resolved_dependencies) } } #[cfg(test)] mod tests { use super::{DependencyGroupSpecifier, License, PyProjectToml, ReadMe}; use pep440_rs::{Version, VersionSpecifiers}; use pep508_rs::Requirement; use std::path::PathBuf; use std::str::FromStr; #[test] fn test_parse_pyproject_toml() { let source = r#"[build-system] requires = ["maturin"] build-backend = "maturin" [project] name = "spam" version = "2020.0.0" description = "Lovely Spam! Wonderful Spam!" readme = "README.rst" requires-python = ">=3.8" license = {file = "LICENSE.txt"} keywords = ["egg", "bacon", "sausage", "tomatoes", "Lobster Thermidor"] authors = [ {email = "hi@pradyunsg.me"}, {name = "Tzu-Ping Chung"} ] maintainers = [ {name = "Brett Cannon", email = "brett@python.org"} ] classifiers = [ "Development Status :: 4 - Beta", "Programming Language :: Python" ] dependencies = [ "httpx", "gidgethub[httpx]>4.0.0", "django>2.1; os_name != 'nt'", "django>2.0; os_name == 'nt'" ] [project.optional-dependencies] test = [ "pytest < 5.0.0", "pytest-cov[all]" ] [project.urls] homepage = "example.com" documentation = "readthedocs.org" repository = "github.com" changelog = "github.com/me/spam/blob/master/CHANGELOG.md" [project.scripts] spam-cli = "spam:main_cli" [project.gui-scripts] spam-gui = "spam:main_gui" [project.entry-points."spam.magical"] tomatoes = "spam:main_tomatoes""#; let project_toml = PyProjectToml::new(source).unwrap(); let build_system = &project_toml.build_system.unwrap(); assert_eq!( build_system.requires, &[Requirement::from_str("maturin").unwrap()] ); assert_eq!(build_system.build_backend.as_deref(), Some("maturin")); let project = project_toml.project.as_ref().unwrap(); assert_eq!(project.name, "spam"); assert_eq!( project.version, Some(Version::from_str("2020.0.0").unwrap()) ); assert_eq!( project.description.as_deref(), Some("Lovely Spam! Wonderful Spam!") ); assert_eq!( project.readme, Some(ReadMe::RelativePath("README.rst".to_string())) ); assert_eq!( project.requires_python, Some(VersionSpecifiers::from_str(">=3.8").unwrap()) ); assert_eq!( project.license, Some(License::File { file: PathBuf::from("LICENSE.txt"), }) ); assert_eq!( project.keywords.as_ref().unwrap(), &["egg", "bacon", "sausage", "tomatoes", "Lobster Thermidor"] ); assert_eq!( project.scripts.as_ref().unwrap()["spam-cli"], "spam:main_cli" ); assert_eq!( project.gui_scripts.as_ref().unwrap()["spam-gui"], "spam:main_gui" ); } #[test] fn test_parse_pyproject_toml_license_expression() { let source = r#"[build-system] requires = ["maturin"] build-backend = "maturin" [project] name = "spam" license = "MIT OR BSD-3-Clause" "#; let project_toml = PyProjectToml::new(source).unwrap(); let project = project_toml.project.as_ref().unwrap(); assert_eq!( project.license, Some(License::Spdx("MIT OR BSD-3-Clause".to_owned())) ); } /// https://peps.python.org/pep-0639/ #[test] fn test_parse_pyproject_toml_license_paths() { let source = r#"[build-system] requires = ["maturin"] build-backend = "maturin" [project] name = "spam" license = "MIT AND (Apache-2.0 OR BSD-2-Clause)" license-files = [ "LICENSE", "setuptools/_vendor/LICENSE", "setuptools/_vendor/LICENSE.APACHE", "setuptools/_vendor/LICENSE.BSD", ] "#; let project_toml = PyProjectToml::new(source).unwrap(); let project = project_toml.project.as_ref().unwrap(); assert_eq!( project.license, Some(License::Spdx( "MIT AND (Apache-2.0 OR BSD-2-Clause)".to_owned() )) ); assert_eq!( project.license_files, Some(vec![ "LICENSE".to_owned(), "setuptools/_vendor/LICENSE".to_owned(), "setuptools/_vendor/LICENSE.APACHE".to_owned(), "setuptools/_vendor/LICENSE.BSD".to_owned() ]) ); } // https://peps.python.org/pep-0639/ #[test] fn test_parse_pyproject_toml_license_globs() { let source = r#"[build-system] requires = ["maturin"] build-backend = "maturin" [project] name = "spam" license = "MIT AND (Apache-2.0 OR BSD-2-Clause)" license-files = [ "LICENSE*", "setuptools/_vendor/LICENSE*", ] "#; let project_toml = PyProjectToml::new(source).unwrap(); let project = project_toml.project.as_ref().unwrap(); assert_eq!( project.license, Some(License::Spdx( "MIT AND (Apache-2.0 OR BSD-2-Clause)".to_owned() )) ); assert_eq!( project.license_files, Some(vec![ "LICENSE*".to_owned(), "setuptools/_vendor/LICENSE*".to_owned(), ]) ); } #[test] fn test_parse_pyproject_toml_default_license_files() { let source = r#"[build-system] requires = ["maturin"] build-backend = "maturin" [project] name = "spam" "#; let project_toml = PyProjectToml::new(source).unwrap(); let project = project_toml.project.as_ref().unwrap(); // Changed from the PEP 639 draft. assert_eq!(project.license_files.clone(), None); } #[test] fn test_parse_pyproject_toml_readme_content_type() { let source = r#"[build-system] requires = ["maturin"] build-backend = "maturin" [project] name = "spam" readme = {text = "ReadMe!", content-type = "text/plain"} "#; let project_toml = PyProjectToml::new(source).unwrap(); let project = project_toml.project.as_ref().unwrap(); assert_eq!( project.readme, Some(ReadMe::Table { file: None, text: Some("ReadMe!".to_string()), content_type: Some("text/plain".to_string()) }) ); } #[test] fn test_parse_pyproject_toml_dependency_groups() { let source = r#"[dependency-groups] alpha = ["beta", "gamma", "delta"] epsilon = ["eta<2.0", "theta==2024.09.01"] iota = [{include-group = "alpha"}] "#; let project_toml = PyProjectToml::new(source).unwrap(); let dependency_groups = project_toml.dependency_groups.as_ref().unwrap(); assert_eq!( dependency_groups["alpha"], vec![ DependencyGroupSpecifier::String(Requirement::from_str("beta").unwrap()), DependencyGroupSpecifier::String(Requirement::from_str("gamma").unwrap()), DependencyGroupSpecifier::String(Requirement::from_str("delta").unwrap(),) ] ); assert_eq!( dependency_groups["epsilon"], vec![ DependencyGroupSpecifier::String(Requirement::from_str("eta<2.0").unwrap()), DependencyGroupSpecifier::String( Requirement::from_str("theta==2024.09.01").unwrap() ) ] ); assert_eq!( dependency_groups["iota"], vec![DependencyGroupSpecifier::Table { include_group: "alpha".to_string() }] ); } #[test] fn invalid_email() { let source = r#" [project] name = "hello-world" version = "0.1.0" # Ensure that the spans from toml handle utf-8 correctly authors = [ { name = "Z͑ͫ̓ͪ̂ͫ̽͏̴̙̤̞͉͚̯̞̠͍A̴̵̜̰͔ͫ͗͢L̠ͨͧͩ͘G̴̻͈͍͔̹̑͗̎̅͛́Ǫ̵̹̻̝̳͂̌̌͘", email = 1 } ] "#; let err = PyProjectToml::new(source).unwrap_err(); assert_eq!( err.to_string(), "TOML parse error at line 6, column 11 | 6 | authors = [ | ^ a table with 'name' and/or 'email' keys " ); } #[test] fn test_contact_accessors() { let contact = super::Contact::NameEmail { name: "John Doe".to_string(), email: "john@example.com".to_string(), }; assert_eq!(contact.name(), Some("John Doe")); assert_eq!(contact.email(), Some("john@example.com")); let contact = super::Contact::Name { name: "John Doe".to_string(), }; assert_eq!(contact.name(), Some("John Doe")); assert_eq!(contact.email(), None); let contact = super::Contact::Email { email: "john@example.com".to_string(), }; assert_eq!(contact.name(), None); assert_eq!(contact.email(), Some("john@example.com")); } } pyproject-toml-0.13.7/src/pep639_glob.rs000064400000000000000000000202231046102023000160710ustar 00000000000000//! Implementation of PEP 639 cross-language restricted globs. use glob::{Pattern, PatternError}; use thiserror::Error; #[derive(Debug, Error)] pub enum Pep639GlobError { #[error(transparent)] PatternError(#[from] PatternError), #[error( "The parent directory operator (`..`) at position {pos} is not allowed in glob: `{glob}`" )] ParentDirectory { glob: String, pos: usize }, #[error("Invalid character `{invalid}` at position {pos} in glob: `{glob}`")] InvalidCharacter { glob: String, pos: usize, invalid: char, }, #[error("Only forward slashes are allowed as path separator, invalid character at position {pos} in glob: `{glob}`")] InvalidBackslash { glob: String, pos: usize }, #[error("Invalid character `{invalid}` in range at position {pos} in glob: `{glob}`")] InvalidCharacterRange { glob: String, pos: usize, invalid: char, }, #[error("Too many at stars at position {pos} in glob: `{glob}`")] TooManyStars { glob: String, pos: usize }, } /// Parse a PEP 639 `license-files` glob /// /// The syntax is more restricted than regular globbing in Python or Rust for platform independent /// results. Since [`glob::Pattern`] is a superset over this format, we can use it after validating /// that no unsupported features are in the string. /// /// From [PEP 639](https://peps.python.org/pep-0639/#add-license-files-key): /// /// > Its value is an array of strings which MUST contain valid glob patterns, /// > as specified below: /// > /// > - Alphanumeric characters, underscores (`_`), hyphens (`-`) and dots (`.`) /// > MUST be matched verbatim. /// > /// > - Special glob characters: `*`, `?`, `**` and character ranges: `[]` /// > containing only the verbatim matched characters MUST be supported. /// > Within `[...]`, the hyphen indicates a range (e.g. `a-z`). /// > Hyphens at the start or end are matched literally. /// > /// > - Path delimiters MUST be the forward slash character (`/`). /// > Patterns are relative to the directory containing `pyproject.toml`, /// > therefore the leading slash character MUST NOT be used. /// > /// > - Parent directory indicators (`..`) MUST NOT be used. /// > /// > Any characters or character sequences not covered by this specification are /// > invalid. Projects MUST NOT use such values. /// > Tools consuming this field MAY reject invalid values with an error. pub fn parse_pep639_glob(glob: &str) -> Result { check_pep639_glob(glob)?; Ok(Pattern::new(glob)?) } /// Check if a glob pattern is valid according to PEP 639 rules. /// /// See [parse_pep639_glob]. pub fn check_pep639_glob(glob: &str) -> Result<(), Pep639GlobError> { let mut chars = glob.chars().enumerate().peekable(); // A `..` is on a parent directory indicator at the start of the string or after a directory // separator. let mut start_or_slash = true; while let Some((pos, c)) = chars.next() { // `***` or `**literals` can be correctly represented with less stars. They are banned by // `glob`, they are allowed by `globset` and PEP 639 is ambiguous, so we're filtering them // out. if c == '*' { let mut star_run = 1; while let Some((_, c)) = chars.peek() { if *c == '*' { star_run += 1; chars.next(); } else { break; } } if star_run >= 3 { return Err(Pep639GlobError::TooManyStars { glob: glob.to_string(), // We don't update pos for the stars. pos, }); } else if star_run == 2 { if let Some((_, c)) = chars.peek() { if *c != '/' { return Err(Pep639GlobError::TooManyStars { glob: glob.to_string(), // We don't update pos for the stars. pos, }); } } } start_or_slash = false; } else if c.is_alphanumeric() || matches!(c, '_' | '-' | '?') { start_or_slash = false; } else if c == '.' { if start_or_slash && matches!(chars.peek(), Some((_, '.'))) { return Err(Pep639GlobError::ParentDirectory { pos, glob: glob.to_string(), }); } start_or_slash = false; } else if c == '/' { start_or_slash = true; } else if c == '[' { for (pos, c) in chars.by_ref() { if c.is_alphanumeric() || matches!(c, '_' | '-' | '.') { // Allowed. } else if c == ']' { break; } else { return Err(Pep639GlobError::InvalidCharacterRange { glob: glob.to_string(), pos, invalid: c, }); } } start_or_slash = false; } else if c == '\\' { return Err(Pep639GlobError::InvalidBackslash { glob: glob.to_string(), pos, }); } else { return Err(Pep639GlobError::InvalidCharacter { glob: glob.to_string(), pos, invalid: c, }); } } Ok(()) } #[cfg(test)] mod tests { use super::*; use insta::assert_snapshot; #[test] fn test_error() { let parse_err = |glob| parse_pep639_glob(glob).unwrap_err().to_string(); assert_snapshot!( parse_err(".."), @"The parent directory operator (`..`) at position 0 is not allowed in glob: `..`" ); assert_snapshot!( parse_err("licenses/.."), @"The parent directory operator (`..`) at position 9 is not allowed in glob: `licenses/..`" ); assert_snapshot!( parse_err("licenses/LICEN!E.txt"), @"Invalid character `!` at position 14 in glob: `licenses/LICEN!E.txt`" ); assert_snapshot!( parse_err("licenses/LICEN[!C]E.txt"), @"Invalid character `!` in range at position 15 in glob: `licenses/LICEN[!C]E.txt`" ); assert_snapshot!( parse_err("licenses/LICEN[C?]E.txt"), @"Invalid character `?` in range at position 16 in glob: `licenses/LICEN[C?]E.txt`" ); assert_snapshot!( parse_err("******"), @"Too many at stars at position 0 in glob: `******`" ); assert_snapshot!( parse_err("licenses/**license"), @"Too many at stars at position 9 in glob: `licenses/**license`" ); assert_snapshot!( parse_err("licenses/***/licenses.csv"), @"Too many at stars at position 9 in glob: `licenses/***/licenses.csv`" ); assert_snapshot!( parse_err(r"licenses\eula.txt"), @r"Only forward slashes are allowed as path separator, invalid character at position 8 in glob: `licenses\eula.txt`" ); assert_snapshot!( parse_err(r"**/@test"), @"Invalid character `@` at position 3 in glob: `**/@test`" ); // Backslashes are not allowed assert_snapshot!( parse_err(r"public domain/Gulliver\\'s Travels.txt"), @r"Invalid character ` ` at position 6 in glob: `public domain/Gulliver\\'s Travels.txt`" ); } #[test] fn test_valid() { let cases = [ "licenses/*.txt", "licenses/**/*.txt", "LICEN[CS]E.txt", "LICEN?E.txt", "[a-z].txt", "[a-z._-].txt", "*/**", "LICENSE..txt", "LICENSE_file-1.txt", // (google translate) "licenses/라이센스*.txt", "licenses/ライセンス*.txt", "licenses/执照*.txt", "src/**", ]; for case in cases { parse_pep639_glob(case).unwrap(); } } } pyproject-toml-0.13.7/src/resolution.rs000064400000000000000000000412561046102023000162540ustar 00000000000000use crate::{DependencyGroupSpecifier, DependencyGroups, ResolvedDependencies}; use indexmap::IndexMap; use pep508_rs::{ExtraName, Requirement}; use std::fmt::Display; use std::str::FromStr; use thiserror::Error; /// Normalize a group/extra name according to PEP 685. fn normalize_name(name: &str) -> String { ExtraName::from_str(name) .map(|extra| extra.to_string()) .unwrap_or_else(|_| name.to_string()) } #[derive(Debug, Error)] #[error(transparent)] pub struct ResolveError(#[from] ResolveErrorKind); #[derive(Debug, Error)] pub enum ResolveErrorKind { #[error("Failed to find optional dependency `{name}` included by {included_by}")] OptionalDependencyNotFound { name: String, included_by: Item }, #[error("Failed to find dependency group `{name}` included by {included_by}")] DependencyGroupNotFound { name: String, included_by: Item }, #[error("Cycles are not supported: {0}")] DependencyGroupCycle(Cycle), } /// A cycle in the recursion. #[derive(Debug)] pub struct Cycle(Vec); /// Display a cycle, e.g., `a -> b -> c -> a`. impl Display for Cycle { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let Some((first, rest)) = self.0.split_first() else { return Ok(()); }; write!(f, "{first}")?; for group in rest { write!(f, " -> {group}")?; } write!(f, " -> {first}")?; Ok(()) } } /// A reference to either an optional dependency or a dependency group. #[derive(Debug, Clone, Eq, PartialEq)] pub enum Item { Extra(String), Group(String), } impl Display for Item { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Item::Extra(extra) => write!(f, "extra:{extra}",), Item::Group(group) => { write!(f, "group:{group}") } } } } pub(crate) fn resolve( self_reference_name: Option<&str>, optional_dependencies: Option<&IndexMap>>, dependency_groups: Option<&DependencyGroups>, ) -> Result { let mut resolved_dependencies = ResolvedDependencies::default(); // Resolve optional dependencies, which may only reference optional dependencies. if let Some(optional_dependencies) = optional_dependencies { for extra in optional_dependencies.keys() { resolve_optional_dependency( extra, optional_dependencies, &mut resolved_dependencies, &mut Vec::new(), self_reference_name, )?; } } // Resolve dependency groups, which may reference dependency groups and optional dependencies. if let Some(dependency_groups) = dependency_groups { for group in dependency_groups.keys() { // It's a reference to other groups. Recurse into them resolve_dependency_group( group, optional_dependencies.unwrap_or(&IndexMap::default()), dependency_groups, &mut resolved_dependencies, &mut Vec::new(), self_reference_name, )?; } } Ok(resolved_dependencies) } /// Resolves a single optional dependency. fn resolve_optional_dependency( extra: &str, optional_dependencies: &IndexMap>, resolved: &mut ResolvedDependencies, parents: &mut Vec, project_name: Option<&str>, ) -> Result, ResolveError> { if let Some(requirements) = resolved.optional_dependencies.get(extra) { return Ok(requirements.clone()); } let normalized_extra = normalize_name(extra); // Find the key in optional_dependencies by comparing normalized versions // TODO: next breaking release remove this once Extra is added let unresolved_requirements = optional_dependencies .iter() .find(|(key, _)| normalize_name(key) == normalized_extra) .map(|(_, reqs)| reqs); let Some(unresolved_requirements) = unresolved_requirements else { let parent = parents .iter() .last() .expect("missing optional dependency must have parent") .clone(); return Err(ResolveErrorKind::OptionalDependencyNotFound { name: extra.to_string(), included_by: parent, } .into()); }; // Check for cycles let item = Item::Extra(extra.to_string()); if parents.contains(&item) { return Err(ResolveErrorKind::DependencyGroupCycle(Cycle(parents.clone())).into()); } parents.push(item); // Recurse into references, and add their resolved requirements to our own requirements. let mut resolved_requirements = Vec::with_capacity(unresolved_requirements.len()); for unresolved_requirement in unresolved_requirements.iter() { // TODO: This should become a `PackageName` in the next breaking release. if project_name .is_some_and(|project_name| project_name == unresolved_requirement.name.to_string()) { // Resolve each extra individually, as each refers to a different optional // dependency entry. for extra in &unresolved_requirement.extras { let extra_string = extra.to_string(); resolved_requirements.extend(resolve_optional_dependency( &extra_string, optional_dependencies, resolved, parents, project_name, )?); } } else { resolved_requirements.push(unresolved_requirement.clone()) } } resolved .optional_dependencies .insert(extra.to_string(), resolved_requirements.clone()); parents.pop(); Ok(resolved_requirements) } /// Resolves a single dependency group. fn resolve_dependency_group( dep_group: &String, optional_dependencies: &IndexMap>, dependency_groups: &DependencyGroups, resolved: &mut ResolvedDependencies, parents: &mut Vec, project_name: Option<&str>, ) -> Result, ResolveError> { if let Some(requirements) = resolved.dependency_groups.get(dep_group) { return Ok(requirements.clone()); } let Some(unresolved_requirements) = dependency_groups.get(dep_group) else { let parent = parents .iter() .last() .expect("missing optional dependency must have parent") .clone(); return Err(ResolveErrorKind::DependencyGroupNotFound { name: dep_group.to_string(), included_by: parent, } .into()); }; // Check for cycles let item = Item::Group(dep_group.to_string()); if parents.contains(&item) { return Err(ResolveErrorKind::DependencyGroupCycle(Cycle(parents.clone())).into()); } parents.push(item); // Otherwise, perform recursion, as required, on the dependency group's specifiers let mut resolved_requirements = Vec::with_capacity(unresolved_requirements.len()); for unresolved_requirement in unresolved_requirements.iter() { match unresolved_requirement { DependencyGroupSpecifier::String(spec) => { if project_name.is_some_and(|project_name| project_name == spec.name.to_string()) { for extra in &spec.extras { resolved_requirements.extend(resolve_optional_dependency( extra.as_ref(), optional_dependencies, resolved, parents, project_name, )?); } } else { resolved_requirements.push(spec.clone()) } } DependencyGroupSpecifier::Table { include_group } => { resolved_requirements.extend(resolve_dependency_group( include_group, optional_dependencies, dependency_groups, resolved, parents, project_name, )?); } } } // Add the resolved group to IndexMap resolved .dependency_groups .insert(dep_group.to_string(), resolved_requirements.clone()); parents.pop(); Ok(resolved_requirements) } #[cfg(test)] mod tests { use pep508_rs::Requirement; use std::str::FromStr; use crate::resolution::{resolve_optional_dependency, Item}; use crate::{PyProjectToml, ResolvedDependencies}; #[test] fn parse_pyproject_toml_optional_dependencies_resolve() { let source = r#"[project] name = "spam" [project.optional-dependencies] alpha = ["beta", "gamma", "delta"] epsilon = ["eta<2.0", "theta==2024.09.01"] iota = ["spam[alpha]"] "#; let pyproject_toml = PyProjectToml::new(source).unwrap(); let resolved_dependencies = pyproject_toml.resolve().unwrap(); assert_eq!( resolved_dependencies.optional_dependencies["iota"], vec![ Requirement::from_str("beta").unwrap(), Requirement::from_str("gamma").unwrap(), Requirement::from_str("delta").unwrap() ] ); } #[test] fn parse_pyproject_toml_optional_dependencies_cycle() { let source = r#" [project] name = "spam" [project.optional-dependencies] alpha = ["spam[iota]"] iota = ["spam[alpha]"] "#; let pyproject_toml = PyProjectToml::new(source).unwrap(); assert_eq!( pyproject_toml.resolve().unwrap_err().to_string(), "Cycles are not supported: extra:alpha -> extra:iota -> extra:alpha" ) } #[test] fn parse_pyproject_toml_optional_dependencies_missing_include() { let source = r#" [project] name = "spam" [project.optional-dependencies] iota = ["spam[alpha]"] "#; let pyproject_toml = PyProjectToml::new(source).unwrap(); assert_eq!( pyproject_toml.resolve().unwrap_err().to_string(), "Failed to find optional dependency `alpha` included by extra:iota" ) } #[test] fn parse_pyproject_toml_optional_dependencies_missing_top_level() { let source = r#" [project] name = "spam" [project.optional-dependencies] alpha = ["beta"] "#; let pyproject_toml = PyProjectToml::new(source).unwrap(); let mut resolved = ResolvedDependencies::default(); let err = resolve_optional_dependency( "foo", pyproject_toml .project .as_ref() .unwrap() .optional_dependencies .as_ref() .unwrap(), &mut resolved, &mut vec![Item::Extra("bar".to_string())], Some("spam"), ) .unwrap_err(); assert_eq!( err.to_string(), "Failed to find optional dependency `foo` included by extra:bar" ); } #[test] fn parse_pyproject_toml_dependency_groups_resolve() { let source = r#" [dependency-groups] alpha = ["beta", "gamma", "delta"] epsilon = ["eta<2.0", "theta==2024.09.01"] iota = [{include-group = "alpha"}] "#; let pyproject_toml = PyProjectToml::new(source).unwrap(); let resolved_dependencies = pyproject_toml.resolve().unwrap(); assert_eq!( resolved_dependencies.dependency_groups["iota"], vec![ Requirement::from_str("beta").unwrap(), Requirement::from_str("gamma").unwrap(), Requirement::from_str("delta").unwrap() ] ); } #[test] fn parse_pyproject_toml_dependency_groups_cycle() { let source = r#" [dependency-groups] alpha = [{include-group = "iota"}] iota = [{include-group = "alpha"}] "#; let pyproject_toml = PyProjectToml::new(source).unwrap(); assert_eq!( pyproject_toml.resolve().unwrap_err().to_string(), "Cycles are not supported: group:alpha -> group:iota -> group:alpha" ) } #[test] fn parse_pyproject_toml_dependency_groups_missing_include() { let source = r#" [dependency-groups] iota = [{include-group = "alpha"}] "#; let pyproject_toml = PyProjectToml::new(source).unwrap(); assert_eq!( pyproject_toml.resolve().unwrap_err().to_string(), "Failed to find dependency group `alpha` included by group:iota" ) } #[test] fn parse_pyproject_toml_dependency_groups_with_optional_dependencies() { let source = r#" [project] name = "spam" [project.optional-dependencies] test = ["pytest"] [dependency-groups] dev = ["spam[test]"] "#; let pyproject_toml = PyProjectToml::new(source).unwrap(); let resolved_dependencies = pyproject_toml.resolve().unwrap(); assert_eq!( resolved_dependencies.dependency_groups["dev"], vec![Requirement::from_str("pytest").unwrap()] ); } #[test] fn name_collision() { let source = r#" [project] name = "spam" [project.optional-dependencies] dev = ["pytest"] [dependency-groups] dev = ["ruff"] "#; let pyproject_toml = PyProjectToml::new(source).unwrap(); let resolved_dependencies = pyproject_toml.resolve().unwrap(); assert_eq!( resolved_dependencies.optional_dependencies["dev"], vec![Requirement::from_str("pytest").unwrap()] ); assert_eq!( resolved_dependencies.dependency_groups["dev"], vec![Requirement::from_str("ruff").unwrap()] ); } #[test] fn optional_dependencies_are_not_dependency_groups() { let source = r#" [project] name = "spam" [project.optional-dependencies] test = ["pytest"] [dependency-groups] dev = ["spam[test]"] "#; let pyproject_toml = PyProjectToml::new(source).unwrap(); let resolved_dependencies = pyproject_toml.resolve().unwrap(); assert!(resolved_dependencies .optional_dependencies .contains_key("test")); assert!(!resolved_dependencies.dependency_groups.contains_key("test")); assert!(resolved_dependencies.dependency_groups.contains_key("dev")); } #[test] fn mixed_resolution() { let source = r#" [project] name = "spam" [project.optional-dependencies] test = ["pytest"] numpy = ["numpy"] [dependency-groups] dev = ["spam[test]"] test = ["spam[numpy]"] "#; let pyproject_toml = PyProjectToml::new(source).unwrap(); let resolved_dependencies = pyproject_toml.resolve().unwrap(); assert_eq!( resolved_dependencies.dependency_groups["dev"], vec![Requirement::from_str("pytest").unwrap()] ); assert_eq!( resolved_dependencies.dependency_groups["test"], vec![Requirement::from_str("numpy").unwrap()] ); } #[test] fn optional_dependencies_with_underscores() { // Test that optional dependency group names with underscores are normalized // when referenced in extras. PEP 685 specifies that extras should be normalized // by replacing _, ., - with a single -. let source = r#" [project] name = "foo" [project.optional-dependencies] all = [ "foo[group-one]", "foo[group_two]", ] group_one = [ "anyio>=4.9.0", ] group-two = [ "trio>=0.31.0", ] "#; let pyproject_toml = PyProjectToml::new(source).unwrap(); let resolved_dependencies = pyproject_toml.resolve().unwrap(); // Both group-one and group_two should resolve correctly assert_eq!( resolved_dependencies.optional_dependencies["all"], vec![ Requirement::from_str("anyio>=4.9.0").unwrap(), Requirement::from_str("trio>=0.31.0").unwrap(), ] ); } }