mirror of https://github.com/astral-sh/uv
Move portable glob parsing to struct (#13311)
Refactoring to make fixing #13280 easier.
This commit is contained in:
parent
3218e364ae
commit
9071e0eeac
|
|
@ -12,7 +12,7 @@ use version_ranges::Ranges;
|
|||
use walkdir::WalkDir;
|
||||
|
||||
use uv_fs::Simplified;
|
||||
use uv_globfilter::{parse_portable_glob, GlobDirFilter};
|
||||
use uv_globfilter::{GlobDirFilter, PortableGlobParser};
|
||||
use uv_normalize::{ExtraName, PackageName};
|
||||
use uv_pep440::{Version, VersionSpecifiers};
|
||||
use uv_pep508::{
|
||||
|
|
@ -395,10 +395,11 @@ impl PyProjectToml {
|
|||
let mut license_files = Vec::new();
|
||||
let mut license_globs_parsed = Vec::new();
|
||||
for license_glob in license_globs {
|
||||
let pep639_glob =
|
||||
parse_portable_glob(license_glob).map_err(|err| Error::PortableGlob {
|
||||
let pep639_glob = PortableGlobParser.parse(license_glob).map_err(|err| {
|
||||
Error::PortableGlob {
|
||||
field: license_glob.to_string(),
|
||||
source: err,
|
||||
}
|
||||
})?;
|
||||
license_globs_parsed.push(pep639_glob);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ use tar::{EntryType, Header};
|
|||
use tracing::{debug, trace};
|
||||
use uv_distribution_filename::{SourceDistExtension, SourceDistFilename};
|
||||
use uv_fs::Simplified;
|
||||
use uv_globfilter::{parse_portable_glob, GlobDirFilter};
|
||||
use uv_globfilter::{GlobDirFilter, PortableGlobParser};
|
||||
use uv_pypi_types::Identifier;
|
||||
use uv_warnings::warn_user_once;
|
||||
use walkdir::WalkDir;
|
||||
|
|
@ -88,7 +88,9 @@ fn source_dist_matcher(
|
|||
.to_string();
|
||||
includes.push(format!("{}/**", globset::escape(import_path)));
|
||||
for include in includes {
|
||||
let glob = parse_portable_glob(&include).map_err(|err| Error::PortableGlob {
|
||||
let glob = PortableGlobParser
|
||||
.parse(&include)
|
||||
.map_err(|err| Error::PortableGlob {
|
||||
field: "tool.uv.build-backend.source-include".to_string(),
|
||||
source: err,
|
||||
})?;
|
||||
|
|
@ -111,7 +113,9 @@ fn source_dist_matcher(
|
|||
// Include the license files
|
||||
for license_files in pyproject_toml.license_files_source_dist() {
|
||||
trace!("Including license files at: `{license_files}`");
|
||||
let glob = parse_portable_glob(license_files).map_err(|err| Error::PortableGlob {
|
||||
let glob = PortableGlobParser
|
||||
.parse(license_files)
|
||||
.map_err(|err| Error::PortableGlob {
|
||||
field: "project.license-files".to_string(),
|
||||
source: err,
|
||||
})?;
|
||||
|
|
@ -120,12 +124,11 @@ fn source_dist_matcher(
|
|||
|
||||
// Include the data files
|
||||
for (name, directory) in settings.data.iter() {
|
||||
let glob =
|
||||
parse_portable_glob(&format!("{}/**", globset::escape(directory))).map_err(|err| {
|
||||
Error::PortableGlob {
|
||||
let glob = PortableGlobParser
|
||||
.parse(&format!("{}/**", globset::escape(directory)))
|
||||
.map_err(|err| Error::PortableGlob {
|
||||
field: format!("tool.uv.build-backend.data.{name}"),
|
||||
source: err,
|
||||
}
|
||||
})?;
|
||||
trace!("Including data ({name}) at: `{directory}`");
|
||||
include_globs.push(glob);
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ use zip::{CompressionMethod, ZipWriter};
|
|||
|
||||
use uv_distribution_filename::WheelFilename;
|
||||
use uv_fs::Simplified;
|
||||
use uv_globfilter::{parse_portable_glob, GlobDirFilter};
|
||||
use uv_globfilter::{GlobDirFilter, PortableGlobParser};
|
||||
use uv_platform_tags::{AbiTag, LanguageTag, PlatformTag};
|
||||
use uv_pypi_types::Identifier;
|
||||
use uv_warnings::warn_user_once;
|
||||
|
|
@ -432,7 +432,9 @@ pub(crate) fn build_exclude_matcher(
|
|||
} else {
|
||||
format!("**/{exclude}").to_string()
|
||||
};
|
||||
let glob = parse_portable_glob(&exclude).map_err(|err| Error::PortableGlob {
|
||||
let glob = PortableGlobParser
|
||||
.parse(&exclude)
|
||||
.map_err(|err| Error::PortableGlob {
|
||||
field: "tool.uv.build-backend.*-exclude".to_string(),
|
||||
source: err,
|
||||
})?;
|
||||
|
|
@ -467,7 +469,7 @@ fn wheel_subdir_from_globs(
|
|||
src.user_display(),
|
||||
license_files
|
||||
);
|
||||
parse_portable_glob(license_files)
|
||||
PortableGlobParser.parse(license_files)
|
||||
})
|
||||
.collect::<Result<_, _>>()
|
||||
.map_err(|err| Error::PortableGlob {
|
||||
|
|
|
|||
|
|
@ -124,7 +124,7 @@ impl GlobDirFilter {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::glob_dir_filter::GlobDirFilter;
|
||||
use crate::portable_glob::parse_portable_glob;
|
||||
use crate::portable_glob::PortableGlobParser;
|
||||
use std::path::{Path, MAIN_SEPARATOR};
|
||||
use tempfile::tempdir;
|
||||
use walkdir::WalkDir;
|
||||
|
|
@ -152,7 +152,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn match_directory() {
|
||||
let patterns = PATTERNS.map(|pattern| parse_portable_glob(pattern).unwrap());
|
||||
let patterns = PATTERNS.map(|pattern| PortableGlobParser.parse(pattern).unwrap());
|
||||
let matcher = GlobDirFilter::from_globs(&patterns).unwrap();
|
||||
assert!(matcher.match_directory(&Path::new("path1").join("dir1")));
|
||||
assert!(matcher.match_directory(&Path::new("path2").join("dir2")));
|
||||
|
|
@ -170,7 +170,7 @@ mod tests {
|
|||
fs_err::create_dir_all(file.parent().unwrap()).unwrap();
|
||||
fs_err::File::create(file).unwrap();
|
||||
}
|
||||
let patterns = PATTERNS.map(|pattern| parse_portable_glob(pattern).unwrap());
|
||||
let patterns = PATTERNS.map(|pattern| PortableGlobParser.parse(pattern).unwrap());
|
||||
let matcher = GlobDirFilter::from_globs(&patterns).unwrap();
|
||||
|
||||
// Test the prefix filtering
|
||||
|
|
@ -228,7 +228,7 @@ mod tests {
|
|||
fs_err::create_dir_all(file.parent().unwrap()).unwrap();
|
||||
fs_err::File::create(file).unwrap();
|
||||
}
|
||||
let patterns = PATTERNS.map(|pattern| parse_portable_glob(pattern).unwrap());
|
||||
let patterns = PATTERNS.map(|pattern| PortableGlobParser.parse(pattern).unwrap());
|
||||
|
||||
let include_matcher = GlobDirFilter::from_globs(&patterns).unwrap();
|
||||
|
||||
|
|
|
|||
|
|
@ -7,4 +7,4 @@ mod glob_dir_filter;
|
|||
mod portable_glob;
|
||||
|
||||
pub use glob_dir_filter::GlobDirFilter;
|
||||
pub use portable_glob::{check_portable_glob, parse_portable_glob, PortableGlobError};
|
||||
pub use portable_glob::{PortableGlobError, PortableGlobParser};
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
use globset::GlobSetBuilder;
|
||||
use std::env::args;
|
||||
use tracing::trace;
|
||||
use uv_globfilter::{parse_portable_glob, GlobDirFilter};
|
||||
use uv_globfilter::{GlobDirFilter, PortableGlobParser};
|
||||
use walkdir::WalkDir;
|
||||
|
||||
fn main() {
|
||||
|
|
@ -12,7 +12,7 @@ fn main() {
|
|||
|
||||
let mut include_globs = Vec::new();
|
||||
for include in includes {
|
||||
let glob = parse_portable_glob(include).unwrap();
|
||||
let glob = PortableGlobParser.parse(include).unwrap();
|
||||
include_globs.push(glob.clone());
|
||||
}
|
||||
let include_matcher = GlobDirFilter::from_globs(&include_globs).unwrap();
|
||||
|
|
@ -25,7 +25,7 @@ fn main() {
|
|||
} else {
|
||||
format!("**/{exclude}").to_string()
|
||||
};
|
||||
let glob = parse_portable_glob(&exclude).unwrap();
|
||||
let glob = PortableGlobParser.parse(&exclude).unwrap();
|
||||
exclude_builder.add(glob);
|
||||
}
|
||||
// https://github.com/BurntSushi/ripgrep/discussions/2927
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
//! Cross-language glob syntax from [PEP 639](https://packaging.python.org/en/latest/specifications/glob-patterns/).
|
||||
//! Cross-language glob syntax from
|
||||
//! [PEP 639](https://packaging.python.org/en/latest/specifications/glob-patterns/).
|
||||
|
||||
use globset::{Glob, GlobBuilder};
|
||||
use thiserror::Error;
|
||||
|
|
@ -28,6 +29,12 @@ pub enum PortableGlobError {
|
|||
TooManyStars { glob: String, pos: usize },
|
||||
}
|
||||
|
||||
/// Cross-language glob parser with the glob syntax from
|
||||
/// [PEP 639](https://packaging.python.org/en/latest/specifications/glob-patterns/).
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
pub struct PortableGlobParser;
|
||||
|
||||
impl PortableGlobParser {
|
||||
/// Parse cross-language glob syntax from [PEP 639](https://packaging.python.org/en/latest/specifications/glob-patterns/):
|
||||
///
|
||||
/// - Alphanumeric characters, underscores (`_`), hyphens (`-`) and dots (`.`) are matched verbatim.
|
||||
|
|
@ -43,13 +50,13 @@ pub enum PortableGlobError {
|
|||
/// - Parent directory indicators (`..`) are not allowed.
|
||||
///
|
||||
/// These rules mean that matching the backslash (`\`) is forbidden, which avoid collisions with the windows path separator.
|
||||
pub fn parse_portable_glob(glob: &str) -> Result<Glob, PortableGlobError> {
|
||||
check_portable_glob(glob)?;
|
||||
pub fn parse(&self, glob: &str) -> Result<Glob, PortableGlobError> {
|
||||
self.check(glob)?;
|
||||
Ok(GlobBuilder::new(glob).literal_separator(true).build()?)
|
||||
}
|
||||
|
||||
/// See [`parse_portable_glob`].
|
||||
pub fn check_portable_glob(glob: &str) -> Result<(), PortableGlobError> {
|
||||
/// See [`Self::parse`].
|
||||
pub fn check(&self, glob: &str) -> Result<(), PortableGlobError> {
|
||||
let mut chars = glob.chars().enumerate().peekable();
|
||||
// A `..` is on a parent directory indicator at the start of the string or after a directory
|
||||
// separator.
|
||||
|
|
@ -122,6 +129,7 @@ pub fn check_portable_glob(glob: &str) -> Result<(), PortableGlobError> {
|
|||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
|
@ -130,7 +138,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_error() {
|
||||
let parse_err = |glob| parse_portable_glob(glob).unwrap_err().to_string();
|
||||
let parse_err = |glob| PortableGlobParser.parse(glob).unwrap_err().to_string();
|
||||
assert_snapshot!(
|
||||
parse_err(".."),
|
||||
@"The parent directory operator (`..`) at position 0 is not allowed in glob: `..`"
|
||||
|
|
@ -188,7 +196,7 @@ mod tests {
|
|||
"src/**",
|
||||
];
|
||||
for case in cases {
|
||||
parse_portable_glob(case).unwrap();
|
||||
PortableGlobParser.parse(case).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue