uv/crates/uv-cli/src/comma.rs
Charlie Marsh c306e46e1d
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
Remove trailing commas before brackets (#10740)
2025-01-18 19:56:46 +00:00

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()])
);
}
}