From b2f6b92e3b12dff91282e0b6a4fd39b7f7f2c62a Mon Sep 17 00:00:00 2001 From: konsti Date: Mon, 6 May 2024 12:13:27 +0200 Subject: [PATCH] Move pep508-rs `Cursor` into its own module (#3401) --- crates/pep508-rs/src/cursor.rs | 141 ++++++++++++++++++++++++++++++++ crates/pep508-rs/src/lib.rs | 141 ++------------------------------ crates/pep508-rs/src/marker.rs | 9 +- crates/pep508-rs/src/unnamed.rs | 3 +- 4 files changed, 155 insertions(+), 139 deletions(-) create mode 100644 crates/pep508-rs/src/cursor.rs diff --git a/crates/pep508-rs/src/cursor.rs b/crates/pep508-rs/src/cursor.rs new file mode 100644 index 000000000..64014aeea --- /dev/null +++ b/crates/pep508-rs/src/cursor.rs @@ -0,0 +1,141 @@ +use crate::{Pep508Error, Pep508ErrorSource}; +use std::fmt::{Display, Formatter}; +use std::str::Chars; + +/// A [`Cursor`] over a string. +#[derive(Debug, Clone)] +pub struct Cursor<'a> { + input: &'a str, + chars: Chars<'a>, + pos: usize, +} + +impl<'a> Cursor<'a> { + /// Convert from `&str`. + pub fn new(input: &'a str) -> Self { + Self { + input, + chars: input.chars(), + pos: 0, + } + } + + /// Returns a new cursor starting at the given position. + pub fn at(self, pos: usize) -> Self { + Self { + input: self.input, + chars: self.input[pos..].chars(), + pos, + } + } + + /// Returns the current byte position of the cursor. + pub(crate) fn pos(&self) -> usize { + self.pos + } + + /// Returns a slice over the input string. + pub(crate) fn slice(&self, start: usize, len: usize) -> &str { + &self.input[start..start + len] + } + + /// Peeks the next character and position from the input stream without consuming it. + pub(crate) fn peek(&self) -> Option<(usize, char)> { + self.chars.clone().next().map(|char| (self.pos, char)) + } + + /// Peeks the next character from the input stream without consuming it. + pub(crate) fn peek_char(&self) -> Option { + self.chars.clone().next() + } + + /// Eats the next character from the input stream if it matches the given token. + pub(crate) fn eat_char(&mut self, token: char) -> Option { + let (start_pos, peek_char) = self.peek()?; + if peek_char == token { + self.next(); + Some(start_pos) + } else { + None + } + } + + /// Consumes whitespace from the cursor. + pub(crate) fn eat_whitespace(&mut self) { + while let Some(char) = self.peek_char() { + if char.is_whitespace() { + self.next(); + } else { + return; + } + } + } + + /// Returns the next character and position from the input stream and consumes it. + pub(crate) fn next(&mut self) -> Option<(usize, char)> { + let pos = self.pos; + let char = self.chars.next()?; + self.pos += char.len_utf8(); + Some((pos, char)) + } + + pub(crate) fn remaining(&self) -> usize { + self.chars.clone().count() + } + + /// Peeks over the cursor as long as the condition is met, without consuming it. + pub(crate) fn peek_while(&mut self, condition: impl Fn(char) -> bool) -> (usize, usize) { + let peeker = self.chars.clone(); + let start = self.pos(); + let len = peeker.take_while(|c| condition(*c)).count(); + (start, len) + } + + /// Consumes characters from the cursor as long as the condition is met. + pub(crate) fn take_while(&mut self, condition: impl Fn(char) -> bool) -> (usize, usize) { + let start = self.pos(); + let mut len = 0; + while let Some(char) = self.peek_char() { + if !condition(char) { + break; + } + + self.next(); + len += char.len_utf8(); + } + (start, len) + } + + /// Consumes characters from the cursor, raising an error if it doesn't match the given token. + pub(crate) fn next_expect_char( + &mut self, + expected: char, + span_start: usize, + ) -> Result<(), Pep508Error> { + match self.next() { + None => Err(Pep508Error { + message: Pep508ErrorSource::String(format!( + "Expected '{expected}', found end of dependency specification" + )), + start: span_start, + len: 1, + input: self.to_string(), + }), + Some((_, value)) if value == expected => Ok(()), + Some((pos, other)) => Err(Pep508Error { + message: Pep508ErrorSource::String(format!( + "Expected '{expected}', found '{other}'" + )), + start: pos, + len: other.len_utf8(), + input: self.to_string(), + }), + } + } +} + +impl Display for Cursor<'_> { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.input) + } +} diff --git a/crates/pep508-rs/src/lib.rs b/crates/pep508-rs/src/lib.rs index cf9eb0e06..89d63d7a6 100644 --- a/crates/pep508-rs/src/lib.rs +++ b/crates/pep508-rs/src/lib.rs @@ -23,8 +23,9 @@ use std::fmt::{Display, Formatter}; #[cfg(feature = "pyo3")] use std::hash::{Hash, Hasher}; use std::path::Path; -use std::str::{Chars, FromStr}; +use std::str::FromStr; +use cursor::Cursor; #[cfg(feature = "pyo3")] use pep440_rs::PyVersion; #[cfg(feature = "pyo3")] @@ -49,6 +50,7 @@ pub use unnamed::UnnamedRequirement; pub use uv_normalize::{ExtraName, InvalidNameError, PackageName}; pub use verbatim_url::{expand_env_vars, split_scheme, strip_host, Scheme, VerbatimUrl}; +mod cursor; mod marker; #[cfg(feature = "non-pep508-extensions")] mod unnamed; @@ -479,136 +481,6 @@ impl<'a> From<&'a VersionOrUrl> for VersionOrUrlRef<'a> { } } -/// A [`Cursor`] over a string. -#[derive(Debug, Clone)] -pub struct Cursor<'a> { - input: &'a str, - chars: Chars<'a>, - pos: usize, -} - -impl<'a> Cursor<'a> { - /// Convert from `&str`. - pub fn new(input: &'a str) -> Self { - Self { - input, - chars: input.chars(), - pos: 0, - } - } - - /// Returns a new cursor starting at the given position. - pub fn at(self, pos: usize) -> Self { - Self { - input: self.input, - chars: self.input[pos..].chars(), - pos, - } - } - - /// Returns the current byte position of the cursor. - fn pos(&self) -> usize { - self.pos - } - - /// Returns a slice over the input string. - fn slice(&self, start: usize, len: usize) -> &str { - &self.input[start..start + len] - } - - /// Peeks the next character and position from the input stream without consuming it. - fn peek(&self) -> Option<(usize, char)> { - self.chars.clone().next().map(|char| (self.pos, char)) - } - - /// Peeks the next character from the input stream without consuming it. - fn peek_char(&self) -> Option { - self.chars.clone().next() - } - - /// Eats the next character from the input stream if it matches the given token. - fn eat_char(&mut self, token: char) -> Option { - let (start_pos, peek_char) = self.peek()?; - if peek_char == token { - self.next(); - Some(start_pos) - } else { - None - } - } - - /// Consumes whitespace from the cursor. - fn eat_whitespace(&mut self) { - while let Some(char) = self.peek_char() { - if char.is_whitespace() { - self.next(); - } else { - return; - } - } - } - - /// Returns the next character and position from the input stream and consumes it. - fn next(&mut self) -> Option<(usize, char)> { - let pos = self.pos; - let char = self.chars.next()?; - self.pos += char.len_utf8(); - Some((pos, char)) - } - - /// Peeks over the cursor as long as the condition is met, without consuming it. - fn peek_while(&mut self, condition: impl Fn(char) -> bool) -> (usize, usize) { - let peeker = self.chars.clone(); - let start = self.pos(); - let len = peeker.take_while(|c| condition(*c)).count(); - (start, len) - } - - /// Consumes characters from the cursor as long as the condition is met. - fn take_while(&mut self, condition: impl Fn(char) -> bool) -> (usize, usize) { - let start = self.pos(); - let mut len = 0; - while let Some(char) = self.peek_char() { - if !condition(char) { - break; - } - - self.next(); - len += char.len_utf8(); - } - (start, len) - } - - /// Consumes characters from the cursor, raising an error if it doesn't match the given token. - fn next_expect_char(&mut self, expected: char, span_start: usize) -> Result<(), Pep508Error> { - match self.next() { - None => Err(Pep508Error { - message: Pep508ErrorSource::String(format!( - "Expected '{expected}', found end of dependency specification" - )), - start: span_start, - len: 1, - input: self.to_string(), - }), - Some((_, value)) if value == expected => Ok(()), - Some((pos, other)) => Err(Pep508Error { - message: Pep508ErrorSource::String(format!( - "Expected '{expected}', found '{other}'" - )), - start: pos, - len: other.len_utf8(), - input: self.to_string(), - }), - } - } -} - -impl Display for Cursor<'_> { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.input) - } -} - fn parse_name(cursor: &mut Cursor) -> Result { // https://peps.python.org/pep-0508/#names // ^([A-Z0-9]|[A-Z0-9][A-Z0-9._-]*[A-Z0-9])$ with re.IGNORECASE @@ -1258,7 +1130,7 @@ fn parse_pep508_requirement( } }; - let requirement_end = cursor.pos; + let requirement_end = cursor.pos(); // wsp* cursor.eat_whitespace(); @@ -1319,7 +1191,7 @@ fn parse_unnamed_requirement( // Parse the URL itself, along with any extras. let (url, extras) = parse_unnamed_url(cursor, working_dir)?; - let requirement_end = cursor.pos; + let requirement_end = cursor.pos(); // wsp* cursor.eat_whitespace(); @@ -1403,12 +1275,13 @@ mod tests { use pep440_rs::{Operator, Version, VersionPattern, VersionSpecifier}; use uv_normalize::{ExtraName, InvalidNameError, PackageName}; + use crate::cursor::Cursor; use crate::marker::{ parse_markers_impl, MarkerExpression, MarkerOperator, MarkerTree, MarkerValue, MarkerValueString, MarkerValueVersion, }; use crate::unnamed::UnnamedRequirement; - use crate::{Cursor, Pep508Error, Requirement, VerbatimUrl, VersionOrUrl}; + use crate::{Pep508Error, Requirement, VerbatimUrl, VersionOrUrl}; fn parse_pepe508_err(input: &str) -> String { Requirement::from_str(input).unwrap_err().to_string() diff --git a/crates/pep508-rs/src/marker.rs b/crates/pep508-rs/src/marker.rs index b2fd48adc..5112b3281 100644 --- a/crates/pep508-rs/src/marker.rs +++ b/crates/pep508-rs/src/marker.rs @@ -9,7 +9,8 @@ //! outcomes. This implementation tries to carefully validate everything and emit warnings whenever //! bogus comparisons with unintended semantics are made. -use crate::{Cursor, Pep508Error, Pep508ErrorSource}; +use crate::cursor::Cursor; +use crate::{Pep508Error, Pep508ErrorSource}; use pep440_rs::{Version, VersionPattern, VersionSpecifier}; #[cfg(feature = "pyo3")] use pyo3::{ @@ -908,7 +909,7 @@ impl FromStr for MarkerExpression { "Unexpected character '{unexpected}', expected end of input" )), start: pos, - len: chars.chars.clone().count(), + len: chars.remaining(), input: chars.to_string(), }); } @@ -1417,7 +1418,7 @@ fn parse_marker_op( } /// ```text -/// marker = marker_or +/// marker = marker_or^ /// ``` pub(crate) fn parse_markers_impl(cursor: &mut Cursor) -> Result { let marker = parse_marker_or(cursor)?; @@ -1430,7 +1431,7 @@ pub(crate) fn parse_markers_impl(cursor: &mut Cursor) -> Result