Parse editable installs (#564)

Parse `-e` for editable installs in `requirements.txt`.

Unlike all the other requirements, editable installs don't have the name
of the package specified.
This commit is contained in:
konsti 2023-12-06 18:21:15 +01:00 committed by GitHub
parent 3f4d7b7826
commit 366c389385
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 153 additions and 7 deletions

1
Cargo.lock generated
View File

@ -2947,6 +2947,7 @@ dependencies = [
"thiserror", "thiserror",
"tracing", "tracing",
"unscanny", "unscanny",
"url",
] ]
[[package]] [[package]]

View File

@ -1,5 +1,6 @@
[workspace] [workspace]
members = ["crates/*"] members = ["crates/*"]
exclude = ["scripts"]
resolver = "2" resolver = "2"
[workspace.package] [workspace.package]

View File

@ -24,6 +24,7 @@ serde = { workspace = true }
thiserror = { workspace = true } thiserror = { workspace = true }
tracing = { workspace = true } tracing = { workspace = true }
unscanny = { workspace = true } unscanny = { workspace = true }
url = { workspace = true }
[dev-dependencies] [dev-dependencies]
anyhow = { version = "1.0.75" } anyhow = { version = "1.0.75" }

View File

@ -10,7 +10,6 @@
//! * `-e` //! * `-e`
//! //!
//! Unsupported: //! Unsupported:
//! * `-e <path>`. TBD
//! * `<path>`. TBD //! * `<path>`. TBD
//! * `<archive_url>`. TBD //! * `<archive_url>`. TBD
//! * Options without a requirement, such as `--find-links` or `--index-url` //! * Options without a requirement, such as `--find-links` or `--index-url`
@ -44,6 +43,7 @@ use fs_err as fs;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use tracing::warn; use tracing::warn;
use unscanny::{Pattern, Scanner}; use unscanny::{Pattern, Scanner};
use url::Url;
use pep508_rs::{Pep508Error, Requirement}; use pep508_rs::{Pep508Error, Requirement};
@ -63,6 +63,14 @@ enum RequirementsTxtStatement {
}, },
/// PEP 508 requirement plus metadata /// PEP 508 requirement plus metadata
RequirementEntry(RequirementEntry), RequirementEntry(RequirementEntry),
/// `-e`
EditableRequirement(EditableRequirement),
}
#[derive(Debug, Deserialize, Clone, Eq, PartialEq, Serialize)]
pub enum EditableRequirement {
Path(PathBuf),
Url(Url),
} }
/// A [Requirement] with additional metadata from the requirements.txt, currently only hashes but in /// A [Requirement] with additional metadata from the requirements.txt, currently only hashes but in
@ -97,6 +105,8 @@ pub struct RequirementsTxt {
pub requirements: Vec<RequirementEntry>, pub requirements: Vec<RequirementEntry>,
/// Constraints included with `-c` /// Constraints included with `-c`
pub constraints: Vec<Requirement>, pub constraints: Vec<Requirement>,
/// Editables with `-e`
pub editable: Vec<EditableRequirement>,
} }
impl RequirementsTxt { impl RequirementsTxt {
@ -182,6 +192,9 @@ impl RequirementsTxt {
RequirementsTxtStatement::RequirementEntry(requirement_entry) => { RequirementsTxtStatement::RequirementEntry(requirement_entry) => {
data.requirements.push(requirement_entry); data.requirements.push(requirement_entry);
} }
RequirementsTxtStatement::EditableRequirement(editable) => {
data.editable.push(editable);
}
} }
} }
Ok(data) Ok(data)
@ -230,12 +243,13 @@ fn parse_entry(
end, end,
} }
} else if s.eat_if("-e") { } else if s.eat_if("-e") {
let (requirement, hashes) = parse_requirement_and_hashes(s, content)?; let path_or_url = parse_value(s, |c: char| !['\n', '\r'].contains(&c))?;
RequirementsTxtStatement::RequirementEntry(RequirementEntry { let editable_requirement = if let Ok(url) = Url::from_str(path_or_url) {
requirement, EditableRequirement::Url(url)
hashes, } else {
editable: true, EditableRequirement::Path(PathBuf::from(path_or_url))
}) };
RequirementsTxtStatement::EditableRequirement(editable_requirement)
} else if s.at(char::is_ascii_alphanumeric) { } else if s.at(char::is_ascii_alphanumeric) {
let (requirement, hashes) = parse_requirement_and_hashes(s, content)?; let (requirement, hashes) = parse_requirement_and_hashes(s, content)?;
RequirementsTxtStatement::RequirementEntry(RequirementEntry { RequirementsTxtStatement::RequirementEntry(RequirementEntry {
@ -505,6 +519,7 @@ mod test {
#[test_case(Path::new("poetry-with-hashes.txt"))] #[test_case(Path::new("poetry-with-hashes.txt"))]
#[test_case(Path::new("small.txt"))] #[test_case(Path::new("small.txt"))]
#[test_case(Path::new("whitespace.txt"))] #[test_case(Path::new("whitespace.txt"))]
#[test_case(Path::new("editable.txt"))]
fn line_endings(path: &Path) { fn line_endings(path: &Path) {
let working_dir = workspace_test_data_dir().join("requirements-txt"); let working_dir = workspace_test_data_dir().join("requirements-txt");
let requirements_txt = working_dir.join(path); let requirements_txt = working_dir.join(path);
@ -581,4 +596,28 @@ mod test {
fn workspace_test_data_dir() -> PathBuf { fn workspace_test_data_dir() -> PathBuf {
PathBuf::from("./test-data") PathBuf::from("./test-data")
} }
#[test]
fn invalid_editable() {
let working_dir = workspace_test_data_dir().join("requirements-txt");
let basic = working_dir.join("invalid-requirement");
let err = RequirementsTxt::parse(&basic, &working_dir).unwrap_err();
let errors = anyhow::Error::new(err)
.chain()
.map(ToString::to_string)
.collect::<Vec<_>>();
let expected = &[
format!(
"Couldn't parse requirement in {} position 0 to 15",
basic.display()
),
indoc! {"
Expected an alphanumeric character starting the extra name, found 'ö'
numpy[ö]==1.29
^"
}
.to_string(),
];
assert_eq!(errors, expected);
}
} }

View File

@ -208,4 +208,5 @@ RequirementsTxt {
}, },
], ],
constraints: [], constraints: [],
editable: [],
} }

View File

@ -99,4 +99,5 @@ RequirementsTxt {
marker: None, marker: None,
}, },
], ],
editable: [],
} }

View File

@ -73,4 +73,5 @@ RequirementsTxt {
}, },
], ],
constraints: [], constraints: [],
editable: [],
} }

View File

@ -0,0 +1,58 @@
---
source: crates/requirements-txt/src/lib.rs
expression: actual
---
RequirementsTxt {
requirements: [
RequirementEntry {
requirement: Requirement {
name: PackageName(
"numpy",
),
extras: None,
version_or_url: None,
marker: None,
},
hashes: [],
editable: false,
},
RequirementEntry {
requirement: Requirement {
name: PackageName(
"pandas",
),
extras: Some(
[
ExtraName(
"tabulate",
),
],
),
version_or_url: Some(
Url(
Url {
scheme: "https",
cannot_be_a_base: false,
username: "",
password: None,
host: Some(
Domain(
"github.com",
),
),
port: None,
path: "/pandas-dev/pandas",
query: None,
fragment: None,
},
),
),
marker: None,
},
hashes: [],
editable: false,
},
],
constraints: [],
editable: [],
}

View File

@ -5,4 +5,5 @@ expression: actual
RequirementsTxt { RequirementsTxt {
requirements: [], requirements: [],
constraints: [], constraints: [],
editable: [],
} }

View File

@ -136,4 +136,5 @@ RequirementsTxt {
}, },
], ],
constraints: [], constraints: [],
editable: [],
} }

View File

@ -52,4 +52,5 @@ RequirementsTxt {
}, },
], ],
constraints: [], constraints: [],
editable: [],
} }

View File

@ -18,4 +18,5 @@ RequirementsTxt {
}, },
], ],
constraints: [], constraints: [],
editable: [],
} }

View File

@ -336,4 +336,5 @@ RequirementsTxt {
}, },
], ],
constraints: [], constraints: [],
editable: [],
} }

View File

@ -74,4 +74,5 @@ RequirementsTxt {
}, },
], ],
constraints: [], constraints: [],
editable: [],
} }

View File

@ -54,4 +54,5 @@ RequirementsTxt {
}, },
], ],
constraints: [], constraints: [],
editable: [],
} }

View File

@ -208,4 +208,5 @@ RequirementsTxt {
}, },
], ],
constraints: [], constraints: [],
editable: [],
} }

View File

@ -99,4 +99,5 @@ RequirementsTxt {
marker: None, marker: None,
}, },
], ],
editable: [],
} }

View File

@ -73,4 +73,5 @@ RequirementsTxt {
}, },
], ],
constraints: [], constraints: [],
editable: [],
} }

View File

@ -5,4 +5,5 @@ expression: actual
RequirementsTxt { RequirementsTxt {
requirements: [], requirements: [],
constraints: [], constraints: [],
editable: [],
} }

View File

@ -136,4 +136,5 @@ RequirementsTxt {
}, },
], ],
constraints: [], constraints: [],
editable: [],
} }

View File

@ -52,4 +52,5 @@ RequirementsTxt {
}, },
], ],
constraints: [], constraints: [],
editable: [],
} }

View File

@ -18,4 +18,5 @@ RequirementsTxt {
}, },
], ],
constraints: [], constraints: [],
editable: [],
} }

View File

@ -336,4 +336,5 @@ RequirementsTxt {
}, },
], ],
constraints: [], constraints: [],
editable: [],
} }

View File

@ -74,4 +74,5 @@ RequirementsTxt {
}, },
], ],
constraints: [], constraints: [],
editable: [],
} }

View File

@ -54,4 +54,5 @@ RequirementsTxt {
}, },
], ],
constraints: [], constraints: [],
editable: [],
} }

View File

@ -0,0 +1,25 @@
# #
# #
# #
# #
# #
# #
# #
##
#
numpy # #
\
pandas [tabulate] @ https://github.com/pandas-dev/pandas \
# üh
#
# 안녕
#

View File

@ -0,0 +1 @@
numpy[ö]==1.29