mirror of
https://github.com/astral-sh/uv.git
synced 2025-11-22 12:35:36 +00:00
Some checks failed
CI / build binary | freebsd (push) Has been cancelled
CI / Determine changes (push) Has been cancelled
CI / typos (push) Has been cancelled
CI / mkdocs (push) Has been cancelled
CI / lint (push) Has been cancelled
CI / cargo shear (push) Has been cancelled
CI / check windows trampoline | i686 (push) Has been cancelled
CI / check windows trampoline | x86_64 (push) Has been cancelled
CI / test windows trampoline | i686 (push) Has been cancelled
CI / test windows trampoline | x86_64 (push) Has been cancelled
CI / build binary | linux (push) Has been cancelled
CI / build binary | macos aarch64 (push) Has been cancelled
CI / build binary | macos x86_64 (push) Has been cancelled
CI / build binary | windows (push) Has been cancelled
CI / cargo build (msrv) (push) Has been cancelled
CI / ecosystem test | prefecthq/prefect (push) Has been cancelled
CI / ecosystem test | pallets/flask (push) Has been cancelled
CI / integration test | conda on ubuntu (push) Has been cancelled
CI / integration test | free-threaded on linux (push) Has been cancelled
CI / integration test | github actions (push) Has been cancelled
CI / integration test | determine publish changes (push) Has been cancelled
CI / integration test | uv publish (push) Has been cancelled
CI / check cache | ubuntu (push) Has been cancelled
CI / check cache | macos aarch64 (push) Has been cancelled
CI / check system | python on debian (push) Has been cancelled
CI / check system | python on fedora (push) Has been cancelled
CI / check system | python on ubuntu (push) Has been cancelled
CI / check system | python on opensuse (push) Has been cancelled
CI / check system | python on rocky linux 8 (push) Has been cancelled
CI / check system | python on rocky linux 9 (push) Has been cancelled
CI / check system | pypy on ubuntu (push) Has been cancelled
CI / check system | pyston (push) Has been cancelled
CI / check system | alpine (push) Has been cancelled
CI / check system | python on macos aarch64 (push) Has been cancelled
CI / check system | homebrew python on macos aarch64 (push) Has been cancelled
CI / check system | python on macos x86_64 (push) Has been cancelled
CI / check system | python3.10 on windows (push) Has been cancelled
CI / check system | python3.10 on windows x86 (push) Has been cancelled
CI / check system | python3.13 on windows (push) Has been cancelled
CI / check system | python3.12 via chocolatey (push) Has been cancelled
CI / check system | python3.9 via pyenv (push) Has been cancelled
CI / check system | python3.13 (push) Has been cancelled
CI / cargo clippy | ubuntu (push) Has been cancelled
CI / cargo clippy | windows (push) Has been cancelled
CI / cargo dev generate-all (push) Has been cancelled
CI / cargo test | ubuntu (push) Has been cancelled
CI / cargo test | macos (push) Has been cancelled
CI / cargo test | windows (push) Has been cancelled
CI / check windows trampoline | aarch64 (push) Has been cancelled
CI / integration test | free-threaded on windows (push) Has been cancelled
CI / integration test | pypy on ubuntu (push) Has been cancelled
CI / integration test | pypy on windows (push) Has been cancelled
CI / integration test | graalpy on ubuntu (push) Has been cancelled
CI / integration test | graalpy on windows (push) Has been cancelled
CI / check system | conda3.11 on linux (push) Has been cancelled
CI / check system | conda3.8 on linux (push) Has been cancelled
CI / check system | conda3.11 on macos (push) Has been cancelled
CI / check system | conda3.8 on macos (push) Has been cancelled
CI / check system | conda3.11 on windows (push) Has been cancelled
CI / check system | conda3.8 on windows (push) Has been cancelled
CI / check system | amazonlinux (push) Has been cancelled
CI / check system | embedded python3.10 on windows (push) Has been cancelled
CI / benchmarks (push) Has been cancelled
125 lines
3.8 KiB
Rust
125 lines
3.8 KiB
Rust
use std::str::FromStr;
|
|
|
|
/// A comma-separated string of requirements, e.g., `"flask,anyio"`, that takes extras into account
|
|
/// (i.e., treats `"psycopg[binary,pool]"` as a single requirement).
|
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
pub struct CommaSeparatedRequirements(Vec<String>);
|
|
|
|
impl IntoIterator for CommaSeparatedRequirements {
|
|
type Item = String;
|
|
type IntoIter = std::vec::IntoIter<Self::Item>;
|
|
|
|
fn into_iter(self) -> Self::IntoIter {
|
|
self.0.into_iter()
|
|
}
|
|
}
|
|
|
|
impl FromStr for CommaSeparatedRequirements {
|
|
type Err = String;
|
|
|
|
fn from_str(input: &str) -> Result<Self, Self::Err> {
|
|
// Split on commas _outside_ of brackets.
|
|
let mut requirements = Vec::new();
|
|
let mut depth = 0usize;
|
|
let mut start = 0usize;
|
|
for (i, c) in input.char_indices() {
|
|
match c {
|
|
'[' => {
|
|
depth = depth.saturating_add(1);
|
|
}
|
|
']' => {
|
|
depth = depth.saturating_sub(1);
|
|
}
|
|
',' if depth == 0 => {
|
|
// If the next character is a version identifier, skip the comma, as in:
|
|
// `requests>=2.1,<3`.
|
|
if let Some(c) = input
|
|
.get(i + ','.len_utf8()..)
|
|
.and_then(|s| s.chars().find(|c| !c.is_whitespace()))
|
|
{
|
|
if matches!(c, '!' | '=' | '<' | '>' | '~') {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
let requirement = input[start..i].trim().to_string();
|
|
if !requirement.is_empty() {
|
|
requirements.push(requirement);
|
|
}
|
|
start = i + ','.len_utf8();
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
let requirement = input[start..].trim().to_string();
|
|
if !requirement.is_empty() {
|
|
requirements.push(requirement);
|
|
}
|
|
Ok(Self(requirements))
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::CommaSeparatedRequirements;
|
|
use std::str::FromStr;
|
|
|
|
#[test]
|
|
fn single() {
|
|
assert_eq!(
|
|
CommaSeparatedRequirements::from_str("flask").unwrap(),
|
|
CommaSeparatedRequirements(vec!["flask".to_string()])
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn double() {
|
|
assert_eq!(
|
|
CommaSeparatedRequirements::from_str("flask,anyio").unwrap(),
|
|
CommaSeparatedRequirements(vec!["flask".to_string(), "anyio".to_string()])
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn empty() {
|
|
assert_eq!(
|
|
CommaSeparatedRequirements::from_str("flask,,anyio").unwrap(),
|
|
CommaSeparatedRequirements(vec!["flask".to_string(), "anyio".to_string()])
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn single_extras() {
|
|
assert_eq!(
|
|
CommaSeparatedRequirements::from_str("psycopg[binary,pool]").unwrap(),
|
|
CommaSeparatedRequirements(vec!["psycopg[binary,pool]".to_string()])
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn double_extras() {
|
|
assert_eq!(
|
|
CommaSeparatedRequirements::from_str("psycopg[binary,pool], flask").unwrap(),
|
|
CommaSeparatedRequirements(vec![
|
|
"psycopg[binary,pool]".to_string(),
|
|
"flask".to_string()
|
|
])
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn single_specifiers() {
|
|
assert_eq!(
|
|
CommaSeparatedRequirements::from_str("requests>=2.1,<3").unwrap(),
|
|
CommaSeparatedRequirements(vec!["requests>=2.1,<3".to_string()])
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn double_specifiers() {
|
|
assert_eq!(
|
|
CommaSeparatedRequirements::from_str("requests>=2.1,<3, flask").unwrap(),
|
|
CommaSeparatedRequirements(vec!["requests>=2.1,<3".to_string(), "flask".to_string()])
|
|
);
|
|
}
|
|
}
|