summaryrefslogtreecommitdiffstats
path: root/vendor/gix-glob/src/pattern.rs
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 12:41:41 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 12:41:41 +0000
commit10ee2acdd26a7f1298c6f6d6b7af9b469fe29b87 (patch)
treebdffd5d80c26cf4a7a518281a204be1ace85b4c1 /vendor/gix-glob/src/pattern.rs
parentReleasing progress-linux version 1.70.0+dfsg1-9~progress7.99u1. (diff)
downloadrustc-10ee2acdd26a7f1298c6f6d6b7af9b469fe29b87.tar.xz
rustc-10ee2acdd26a7f1298c6f6d6b7af9b469fe29b87.zip
Merging upstream version 1.70.0+dfsg2.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vendor/gix-glob/src/pattern.rs')
-rw-r--r--vendor/gix-glob/src/pattern.rs162
1 files changed, 162 insertions, 0 deletions
diff --git a/vendor/gix-glob/src/pattern.rs b/vendor/gix-glob/src/pattern.rs
new file mode 100644
index 000000000..fa874b226
--- /dev/null
+++ b/vendor/gix-glob/src/pattern.rs
@@ -0,0 +1,162 @@
+use std::fmt;
+
+use bitflags::bitflags;
+use bstr::{BStr, ByteSlice};
+
+use crate::{pattern, wildmatch, Pattern};
+
+bitflags! {
+ /// Information about a [`Pattern`].
+ ///
+ /// Its main purpose is to accelerate pattern matching, or to negate the match result or to
+ /// keep special rules only applicable when matching paths.
+ ///
+ /// The mode is typically created when parsing the pattern by inspecting it and isn't typically handled by the user.
+ #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))]
+ pub struct Mode: u32 {
+ /// The pattern does not contain a sub-directory and - it doesn't contain slashes after removing the trailing one.
+ const NO_SUB_DIR = 1 << 0;
+ /// A pattern that is '*literal', meaning that it ends with what's given here
+ const ENDS_WITH = 1 << 1;
+ /// The pattern must match a directory, and not a file.
+ const MUST_BE_DIR = 1 << 2;
+ /// The pattern matches, but should be negated. Note that this mode has to be checked and applied by the caller.
+ const NEGATIVE = 1 << 3;
+ /// The pattern starts with a slash and thus matches only from the beginning.
+ const ABSOLUTE = 1 << 4;
+ }
+}
+
+/// Describes whether to match a path case sensitively or not.
+///
+/// Used in [Pattern::matches_repo_relative_path()].
+#[derive(Debug, PartialOrd, PartialEq, Copy, Clone, Hash, Ord, Eq)]
+pub enum Case {
+ /// The case affects the match
+ Sensitive,
+ /// Ignore the case of ascii characters.
+ Fold,
+}
+
+impl Default for Case {
+ fn default() -> Self {
+ Case::Sensitive
+ }
+}
+
+impl Pattern {
+ /// Parse the given `text` as pattern, or return `None` if `text` was empty.
+ pub fn from_bytes(text: &[u8]) -> Option<Self> {
+ crate::parse::pattern(text).map(|(text, mode, first_wildcard_pos)| Pattern {
+ text,
+ mode,
+ first_wildcard_pos,
+ })
+ }
+
+ /// Return true if a match is negated.
+ pub fn is_negative(&self) -> bool {
+ self.mode.contains(Mode::NEGATIVE)
+ }
+
+ /// Match the given `path` which takes slashes (and only slashes) literally, and is relative to the repository root.
+ /// Note that `path` is assumed to be relative to the repository.
+ ///
+ /// We may take various shortcuts which is when `basename_start_pos` and `is_dir` come into play.
+ /// `basename_start_pos` is the index at which the `path`'s basename starts.
+ ///
+ /// Lastly, `case` folding can be configured as well.
+ pub fn matches_repo_relative_path<'a>(
+ &self,
+ path: impl Into<&'a BStr>,
+ basename_start_pos: Option<usize>,
+ is_dir: Option<bool>,
+ case: Case,
+ ) -> bool {
+ let is_dir = is_dir.unwrap_or(false);
+ if !is_dir && self.mode.contains(pattern::Mode::MUST_BE_DIR) {
+ return false;
+ }
+
+ let flags = wildmatch::Mode::NO_MATCH_SLASH_LITERAL
+ | match case {
+ Case::Fold => wildmatch::Mode::IGNORE_CASE,
+ Case::Sensitive => wildmatch::Mode::empty(),
+ };
+ let path = path.into();
+ debug_assert_eq!(
+ basename_start_pos,
+ path.rfind_byte(b'/').map(|p| p + 1),
+ "BUG: invalid cached basename_start_pos provided"
+ );
+ debug_assert!(!path.starts_with(b"/"), "input path must be relative");
+
+ if self.mode.contains(pattern::Mode::NO_SUB_DIR) && !self.mode.contains(pattern::Mode::ABSOLUTE) {
+ let basename = &path[basename_start_pos.unwrap_or_default()..];
+ self.matches(basename, flags)
+ } else {
+ self.matches(path, flags)
+ }
+ }
+
+ /// See if `value` matches this pattern in the given `mode`.
+ ///
+ /// `mode` can identify `value` as path which won't match the slash character, and can match
+ /// strings with cases ignored as well. Note that the case folding performed here is ASCII only.
+ ///
+ /// Note that this method uses some shortcuts to accelerate simple patterns.
+ fn matches<'a>(&self, value: impl Into<&'a BStr>, mode: wildmatch::Mode) -> bool {
+ let value = value.into();
+ match self.first_wildcard_pos {
+ // "*literal" case, overrides starts-with
+ Some(pos) if self.mode.contains(pattern::Mode::ENDS_WITH) && !value.contains(&b'/') => {
+ let text = &self.text[pos + 1..];
+ if mode.contains(wildmatch::Mode::IGNORE_CASE) {
+ value
+ .len()
+ .checked_sub(text.len())
+ .map(|start| text.eq_ignore_ascii_case(&value[start..]))
+ .unwrap_or(false)
+ } else {
+ value.ends_with(text.as_ref())
+ }
+ }
+ Some(pos) => {
+ if mode.contains(wildmatch::Mode::IGNORE_CASE) {
+ if !value
+ .get(..pos)
+ .map_or(false, |value| value.eq_ignore_ascii_case(&self.text[..pos]))
+ {
+ return false;
+ }
+ } else if !value.starts_with(&self.text[..pos]) {
+ return false;
+ }
+ crate::wildmatch(self.text.as_bstr(), value, mode)
+ }
+ None => {
+ if mode.contains(wildmatch::Mode::IGNORE_CASE) {
+ self.text.eq_ignore_ascii_case(value)
+ } else {
+ self.text == value
+ }
+ }
+ }
+ }
+}
+
+impl fmt::Display for Pattern {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ if self.mode.contains(Mode::NEGATIVE) {
+ "!".fmt(f)?;
+ }
+ if self.mode.contains(Mode::ABSOLUTE) {
+ "/".fmt(f)?;
+ }
+ self.text.fmt(f)?;
+ if self.mode.contains(Mode::MUST_BE_DIR) {
+ "/".fmt(f)?;
+ }
+ Ok(())
+ }
+}