pyproject-toml-0.7.0/.cargo_vcs_info.json0000644000000001360000000000100140300ustar { "git": { "sha1": "9043ba0d4ecc2a7f783faf714a477abf72f8a7fd" }, "path_in_vcs": "" }pyproject-toml-0.7.0/.github/dependabot.yml000064400000000000000000000006721046102023000170150ustar 00000000000000# To get started with Dependabot version updates, you'll need to specify which # package ecosystems to update and where the package manifests are located. # Please see the documentation for all configuration options: # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates version: 2 updates: - package-ecosystem: "github-actions" directory: "/" schedule: interval: "monthly" pyproject-toml-0.7.0/.github/workflows/CI.yml000064400000000000000000000013231046102023000172320ustar 00000000000000on: push: branches: - main pull_request: name: CI jobs: check: name: Check runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: dtolnay/rust-toolchain@stable - run: cargo check test: name: Test Suite runs-on: ${{ matrix.os }} strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] steps: - uses: actions/checkout@v3 - uses: dtolnay/rust-toolchain@stable - run: cargo test fmt: name: Rustfmt runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: dtolnay/rust-toolchain@stable with: components: rustfmt - run: cargo fmt --all -- --check pyproject-toml-0.7.0/.gitignore000064400000000000000000000000231046102023000146030ustar 00000000000000/target Cargo.lock pyproject-toml-0.7.0/Cargo.toml0000644000000021550000000000100120310ustar # 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.64" name = "pyproject-toml" version = "0.7.0" 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" [dependencies.indexmap] version = "2.0.0" features = ["serde"] [dependencies.pep440_rs] version = "0.3.6" features = ["serde"] [dependencies.pep508_rs] version = "0.2.1" features = ["serde"] [dependencies.serde] version = "1.0.125" features = ["derive"] [dependencies.toml] version = "0.7.0" features = ["parse"] default-features = false pyproject-toml-0.7.0/Cargo.toml.orig000064400000000000000000000013111046102023000155030ustar 00000000000000[package] name = "pyproject-toml" version = "0.7.0" 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.64" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] indexmap = { version = "2.0.0", features = ["serde"] } serde = { version = "1.0.125", features = ["derive"] } toml = { version = "0.7.0", default-features = false, features = ["parse"] } pep508_rs = { version = "0.2.1", features = ["serde"] } pep440_rs = { version = "0.3.6", features = ["serde"] } pyproject-toml-0.7.0/Changelog.md000064400000000000000000000006441046102023000150350ustar 00000000000000# Changelog ## 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.7.0/LICENSE000064400000000000000000000021061046102023000136240ustar 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.7.0/README.md000064400000000000000000000034751046102023000141100ustar 00000000000000# pyproject-toml-rs [![GitHub Actions](https://github.com/PyO3/pyproject-toml-rs/workflows/CI/badge.svg)](https://github.com/PyO3/pyproject-toml-rs/actions?query=workflow%3ACI) [![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.7" ``` 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.7.0/src/lib.rs000064400000000000000000000304631046102023000145310ustar 00000000000000use indexmap::IndexMap; use pep440_rs::{Version, VersionSpecifiers}; use pep508_rs::Requirement; use serde::{Deserialize, Serialize}; /// 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: BuildSystem, /// Project metadata pub project: 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 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, /// License pub license: Option, /// License Files (PEP 639) - https://peps.python.org/pep-0639/#add-license-files-key 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 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, }, } /// License #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] #[serde(untagged)] pub enum License { /// A SPDX license expression, according to PEP 639 String(String), /// A PEP 621 license table. Note that accepting PEP 639 will deprecate this table Table { /// A relative file path to the file which contains the license for the project file: Option, /// The license content of the project text: Option, }, } /// License-Files #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] pub enum LicenseFiles { /// List of file paths describing `License-File` output #[serde(rename = "paths")] Paths(Option>), /// List of glob patterns describing `License-File` output #[serde(rename = "globs")] Globs(Option>), } /// Default value specified by PEP 639 impl Default for LicenseFiles { fn default() -> Self { LicenseFiles::Globs(Some(vec![ "LICEN[CS]E*".to_owned(), "COPYING*".to_owned(), "NOTICE*".to_owned(), "AUTHORS*".to_owned(), ])) } } /// Project people contact information #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] #[serde(expecting = "a table with 'name' and 'email' keys")] pub struct Contact { /// A valid email name pub name: Option, /// A valid email address pub email: Option, } impl PyProjectToml { /// Parse `pyproject.toml` content pub fn new(content: &str) -> Result { toml::de::from_str(content) } } #[cfg(test)] mod tests { use super::{License, LicenseFiles, PyProjectToml, ReadMe}; use pep440_rs::{Version, VersionSpecifiers}; use pep508_rs::Requirement; 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; 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::Table { file: Some("LICENSE.txt".to_owned()), text: None }) ); 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::String("MIT OR BSD-3-Clause".to_owned())) ); } /// https://peps.python.org/pep-0639/#advanced-example #[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.paths = [ "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::String( "MIT AND (Apache-2.0 OR BSD-2-Clause)".to_owned() )) ); assert_eq!( project.license_files, Some(LicenseFiles::Paths(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/#advanced-example #[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.globs = [ "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::String( "MIT AND (Apache-2.0 OR BSD-2-Clause)".to_owned() )) ); assert_eq!( project.license_files, Some(LicenseFiles::Globs(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(); assert_eq!( project.license_files.clone().unwrap_or_default(), LicenseFiles::Globs(Some(vec![ "LICEN[CS]E*".to_owned(), "COPYING*".to_owned(), "NOTICE*".to_owned(), "AUTHORS*".to_owned(), ])) ); } #[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()) }) ); } }