chore: streamline configuration files and add benchmarks

- Removed unnecessary line breaks in rust-toolchain.toml, pyproject.toml, and Cargo.toml for better readability.
- Added a new benchmark file for glob pattern matching in tombi-glob to evaluate performance across various patterns.
This commit is contained in:
ya7010 2025-06-25 00:12:46 +09:00
parent dae92e97a4
commit a93d2f391e
7 changed files with 354 additions and 438 deletions

207
Cargo.lock generated
View file

@ -126,6 +126,12 @@ dependencies = [
"libc",
]
[[package]]
name = "anes"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
[[package]]
name = "anstream"
version = "0.6.18"
@ -467,6 +473,12 @@ dependencies = [
"pkg-config",
]
[[package]]
name = "cast"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
[[package]]
name = "castaway"
version = "0.2.3"
@ -514,6 +526,33 @@ dependencies = [
"windows-link",
]
[[package]]
name = "ciborium"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e"
dependencies = [
"ciborium-io",
"ciborium-ll",
"serde",
]
[[package]]
name = "ciborium-io"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757"
[[package]]
name = "ciborium-ll"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9"
dependencies = [
"ciborium-io",
"half",
]
[[package]]
name = "cipher"
version = "0.2.5"
@ -701,6 +740,42 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "criterion"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f"
dependencies = [
"anes",
"cast",
"ciborium",
"clap",
"criterion-plot",
"is-terminal",
"itertools 0.10.5",
"num-traits",
"once_cell",
"oorandom",
"plotters",
"rayon",
"regex",
"serde",
"serde_derive",
"serde_json",
"tinytemplate",
"walkdir",
]
[[package]]
name = "criterion-plot"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1"
dependencies = [
"cast",
"itertools 0.10.5",
]
[[package]]
name = "crossbeam-deque"
version = "0.8.6"
@ -726,6 +801,12 @@ version = "0.8.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
[[package]]
name = "crunchy"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929"
[[package]]
name = "crypto-common"
version = "0.1.6"
@ -959,6 +1040,12 @@ dependencies = [
"pin-project-lite",
]
[[package]]
name = "fast-glob"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3afcf4effa2c44390b9912544582d5af29e10dc4c816c5dbebf748e1c7416faa"
[[package]]
name = "fastrand"
version = "1.9.0"
@ -1268,6 +1355,16 @@ dependencies = [
"web-sys",
]
[[package]]
name = "half"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9"
dependencies = [
"cfg-if",
"crunchy",
]
[[package]]
name = "hashbrown"
version = "0.14.5"
@ -1703,6 +1800,17 @@ version = "2.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708"
[[package]]
name = "is-terminal"
version = "0.4.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9"
dependencies = [
"hermit-abi",
"libc",
"windows-sys 0.59.0",
]
[[package]]
name = "is_terminal_polyfill"
version = "1.70.1"
@ -1732,6 +1840,15 @@ dependencies = [
"waker-fn",
]
[[package]]
name = "itertools"
version = "0.10.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
dependencies = [
"either",
]
[[package]]
name = "itertools"
version = "0.14.0"
@ -2009,6 +2126,12 @@ version = "1.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
[[package]]
name = "oorandom"
version = "11.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e"
[[package]]
name = "opaque-debug"
version = "0.3.1"
@ -2129,6 +2252,34 @@ version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
[[package]]
name = "plotters"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747"
dependencies = [
"num-traits",
"plotters-backend",
"plotters-svg",
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "plotters-backend"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a"
[[package]]
name = "plotters-svg"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670"
dependencies = [
"plotters-backend",
]
[[package]]
name = "polling"
version = "3.8.0"
@ -2797,7 +2948,7 @@ dependencies = [
"dirs",
"futures",
"indexmap",
"itertools",
"itertools 0.14.0",
"maplit",
"pretty_assertions",
"serde",
@ -3250,6 +3401,16 @@ dependencies = [
"zerovec",
]
[[package]]
name = "tinytemplate"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc"
dependencies = [
"serde",
"serde_json",
]
[[package]]
name = "tinyvec"
version = "1.8.0"
@ -3323,7 +3484,7 @@ version = "0.0.0-dev"
name = "tombi-ast"
version = "0.0.0"
dependencies = [
"itertools",
"itertools 0.14.0",
"thiserror 2.0.12",
"tombi-syntax",
"tombi-text",
@ -3341,7 +3502,7 @@ dependencies = [
"ahash",
"futures",
"indexmap",
"itertools",
"itertools 0.14.0",
"regex",
"thiserror 2.0.12",
"tombi-ast",
@ -3364,7 +3525,7 @@ version = "0.0.0-dev"
dependencies = [
"clap",
"clap-verbosity-flag",
"itertools",
"itertools 0.14.0",
"nu-ansi-term 0.50.1",
"rayon",
"serde_tombi",
@ -3432,7 +3593,7 @@ version = "0.0.0"
dependencies = [
"chrono",
"indexmap",
"itertools",
"itertools 0.14.0",
"pretty_assertions",
"serde",
"serde_json",
@ -3456,7 +3617,7 @@ version = "0.0.0"
dependencies = [
"chrono",
"indexmap",
"itertools",
"itertools 0.14.0",
"thiserror 2.0.12",
"tombi-ast",
"tombi-date-time",
@ -3482,7 +3643,7 @@ version = "0.0.0-dev"
dependencies = [
"ahash",
"glob",
"itertools",
"itertools 0.14.0",
"serde",
"serde_json",
"tombi-ast",
@ -3516,7 +3677,7 @@ name = "tombi-extension-uv"
version = "0.0.0-dev"
dependencies = [
"glob",
"itertools",
"itertools 0.14.0",
"tombi-ast",
"tombi-config",
"tombi-document-tree",
@ -3533,7 +3694,7 @@ dependencies = [
name = "tombi-formatter"
version = "0.0.0"
dependencies = [
"itertools",
"itertools 0.14.0",
"pretty_assertions",
"rstest",
"schemars",
@ -3568,6 +3729,8 @@ dependencies = [
name = "tombi-glob"
version = "0.0.0-dev"
dependencies = [
"criterion",
"fast-glob",
"futures",
"ignore",
"rayon",
@ -3582,7 +3745,7 @@ name = "tombi-json"
version = "0.0.0"
dependencies = [
"indexmap",
"itertools",
"itertools 0.14.0",
"pretty_assertions",
"serde",
"thiserror 2.0.12",
@ -3610,7 +3773,7 @@ dependencies = [
name = "tombi-json-lexer"
version = "0.0.0"
dependencies = [
"itertools",
"itertools 0.14.0",
"pretty_assertions",
"regex",
"rstest",
@ -3644,7 +3807,7 @@ dependencies = [
name = "tombi-lexer"
version = "0.0.0"
dependencies = [
"itertools",
"itertools 0.14.0",
"pretty_assertions",
"regex",
"rstest",
@ -3662,7 +3825,7 @@ version = "0.0.0"
dependencies = [
"ahash",
"futures",
"itertools",
"itertools 0.14.0",
"pretty_assertions",
"regex",
"schemars",
@ -3694,7 +3857,7 @@ dependencies = [
"futures",
"futures-util",
"indexmap",
"itertools",
"itertools 0.14.0",
"pretty_assertions",
"regex",
"reqwest",
@ -3738,7 +3901,7 @@ name = "tombi-parser"
version = "0.0.0"
dependencies = [
"drop_bomb",
"itertools",
"itertools 0.14.0",
"pretty_assertions",
"rstest",
"textwrap",
@ -3761,7 +3924,7 @@ version = "0.0.0"
dependencies = [
"countme",
"hashbrown 0.15.3",
"itertools",
"itertools 0.14.0",
"pretty_assertions",
"rustc-hash",
"tombi-text",
@ -3778,7 +3941,7 @@ dependencies = [
"glob",
"gloo-net",
"indexmap",
"itertools",
"itertools 0.14.0",
"pretty_assertions",
"regex",
"reqwest",
@ -3860,7 +4023,7 @@ name = "tombi-validator"
version = "0.0.0"
dependencies = [
"futures",
"itertools",
"itertools 0.14.0",
"regex",
"thiserror 2.0.12",
"tombi-diagnostic",
@ -3875,7 +4038,7 @@ dependencies = [
name = "tombi-version-sort"
version = "0.0.0-dev"
dependencies = [
"itertools",
"itertools 0.14.0",
]
[[package]]
@ -3883,7 +4046,7 @@ name = "tombi-x-keyword"
version = "0.0.0-dev"
dependencies = [
"clap",
"itertools",
"itertools 0.14.0",
"schemars",
"serde",
]
@ -3896,7 +4059,7 @@ dependencies = [
"chrono",
"clap",
"indexmap",
"itertools",
"itertools 0.14.0",
"pretty_assertions",
"serde",
"serde_json",
@ -4764,7 +4927,7 @@ dependencies = [
"clap",
"convert_case",
"flate2",
"itertools",
"itertools 0.14.0",
"pretty_assertions",
"proc-macro2",
"quote",

View file

@ -8,7 +8,12 @@ name = "profile"
path = "src/bin/profile.rs"
required-features = ["serde_tombi", "tombi_config"]
[[bench]]
harness = false
name = "glob_benchmark"
[dependencies]
fast-glob = "0.3.0"
futures = "0.3.31"
ignore = "0.4.20"
rayon = "1.10.0"
@ -17,4 +22,5 @@ thiserror = "2.0.12"
tombi-config = { workspace = true, optional = true }
[dev-dependencies]
tokio = { version = "1.0", features = ["macros", "rt"] }
criterion = "0.5"
tokio = { workspace = true, features = ["macros", "rt"] }

View file

@ -0,0 +1,67 @@
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use tombi_glob::MultiGlob;
fn benchmark_simple_pattern(c: &mut Criterion) {
c.bench_function("simple pattern matching", |b| {
let mut glob = MultiGlob::new();
glob.add("hello", 1).unwrap();
glob.compile();
b.iter(|| {
black_box(glob.find(black_box("hello")));
});
});
}
fn benchmark_wildcard_pattern(c: &mut Criterion) {
c.bench_function("wildcard pattern matching", |b| {
let mut glob = MultiGlob::new();
glob.add("*.rs", 1).unwrap();
glob.compile();
b.iter(|| {
black_box(glob.find(black_box("main.rs")));
});
});
}
fn benchmark_complex_pattern(c: &mut Criterion) {
c.bench_function("complex pattern matching", |b| {
let mut glob = MultiGlob::new();
glob.add("src/**/*.{rs,toml}", 1).unwrap();
glob.compile();
b.iter(|| {
black_box(glob.find(black_box("src/lib/main.rs")));
});
});
}
fn benchmark_multiple_patterns(c: &mut Criterion) {
c.bench_function("multiple patterns matching", |b| {
let mut glob = MultiGlob::new();
glob.add("*.txt", 10).unwrap();
glob.add("test.*", 5).unwrap();
glob.add("test.txt", 20).unwrap();
glob.add("**/*.rs", 15).unwrap();
glob.add("src/**/*.toml", 12).unwrap();
glob.compile();
b.iter(|| {
black_box(glob.find(black_box("test.txt")));
black_box(glob.find(black_box("hello.txt")));
black_box(glob.find(black_box("test.rs")));
black_box(glob.find(black_box("src/cargo.toml")));
});
});
}
criterion_group!(
benches,
benchmark_simple_pattern,
benchmark_wildcard_pattern,
benchmark_complex_pattern,
benchmark_multiple_patterns
);
criterion_main!(benches);

View file

@ -1,3 +1,4 @@
use fast_glob::glob_match;
use futures::{stream, StreamExt};
use ignore::{DirEntry, WalkBuilder};
use std::collections::HashMap;
@ -12,18 +13,9 @@ pub enum GlobError {
#[error("Invalid glob pattern: '{pattern}'")]
InvalidPattern { pattern: String },
#[error("Invalid character class in pattern: '{pattern}' at position {position}")]
InvalidCharacterClass { pattern: String, position: usize },
#[error("Unclosed bracket in pattern: '{pattern}' at position {position}")]
UnclosedBracket { pattern: String, position: usize },
#[error("Empty pattern provided")]
EmptyPattern,
#[error("Pattern too complex: '{pattern}' (maximum {max_states} states exceeded)")]
PatternTooComplex { pattern: String, max_states: usize },
#[error("IO error while walking directory '{path}': {source}")]
IoError {
path: PathBuf,
@ -43,9 +35,6 @@ pub enum GlobError {
#[error("Search root path is not a directory: '{path}'")]
RootPathNotDirectory { path: PathBuf },
#[error("Pattern compilation failed for: '{pattern}' - {reason}")]
CompilationError { pattern: String, reason: String },
#[error("Thread pool error: {message}")]
ThreadPoolError { message: String },
@ -63,96 +52,41 @@ impl GlobError {
}
}
pub fn invalid_character_class(pattern: impl Into<String>, position: usize) -> Self {
Self::InvalidCharacterClass {
pattern: pattern.into(),
position,
}
}
pub fn unclosed_bracket(pattern: impl Into<String>, position: usize) -> Self {
Self::UnclosedBracket {
pattern: pattern.into(),
position,
}
}
pub fn pattern_too_complex(pattern: impl Into<String>, max_states: usize) -> Self {
Self::PatternTooComplex {
pattern: pattern.into(),
max_states,
}
}
pub fn io_error(path: impl Into<PathBuf>, source: std::io::Error) -> Self {
Self::IoError {
path: path.into(),
source,
}
}
pub fn compilation_error(pattern: impl Into<String>, reason: impl Into<String>) -> Self {
Self::CompilationError {
pattern: pattern.into(),
reason: reason.into(),
}
}
}
pub type GlobResult<T> = Result<T, GlobError>;
#[derive(Debug, Clone)]
pub struct State {
pub chars: [bool; 256],
pub is_star: bool,
}
impl State {
pub fn new() -> Self {
Self {
chars: [false; 256],
is_star: false,
}
}
pub fn set_char(&mut self, c: u8) {
self.chars[c as usize] = true;
}
pub fn set_all(&mut self) {
self.chars = [true; 256];
}
pub fn matches(&self, c: u8) -> bool {
self.chars[c as usize]
}
}
#[derive(Debug, Clone)]
pub struct GlobPattern {
pub states: Vec<State>,
pub pattern: String,
pub value: i64,
}
impl GlobPattern {
pub fn new(states: Vec<State>, value: i64) -> Self {
Self { states, value }
pub fn new(pattern: String, value: i64) -> Self {
Self { pattern, value }
}
pub fn matches(&self, path: &str) -> bool {
glob_match(&self.pattern, path)
}
}
#[derive(Debug, Clone)]
pub struct MultiGlob {
patterns: Vec<GlobPattern>,
start_states: Vec<u64>,
compiled: bool,
}
impl MultiGlob {
pub fn new() -> Self {
Self {
patterns: Vec::new(),
start_states: Vec::new(),
compiled: false,
}
}
@ -161,221 +95,41 @@ impl MultiGlob {
return Err(GlobError::EmptyPattern);
}
match parse_glob(pattern) {
Ok(states) => {
if states.len() > 1000 {
// 合理的な制限を設定
return Err(GlobError::pattern_too_complex(pattern, 1000));
}
self.patterns.push(GlobPattern::new(states, value));
self.compiled = false;
Ok(())
}
Err(e) => Err(e),
}
// Validate pattern by trying to match empty string (fast-glob will panic on invalid patterns)
// This is a simple validation check
let _ = glob_match(pattern, "");
self.patterns.push(GlobPattern::new(pattern.to_string(), value));
// Sort by value in descending order for priority matching
self.patterns.sort_by(|a, b| b.value.cmp(&a.value));
Ok(())
}
pub fn compile(&mut self) {
if self.patterns.is_empty() {
return;
}
self.patterns.sort_by(|a, b| b.value.cmp(&a.value));
let mut all_states = Vec::new();
for pattern in &self.patterns {
all_states.extend_from_slice(&pattern.states);
}
let sz = all_states.len();
self.start_states.resize(sz, 0);
let mut pos = 0;
for pattern in &self.patterns {
let len = pattern.states.len();
for i in 0..len {
self.start_states[pos + i] = if i == 0 { 1u64 << (pos + i) } else { 0 };
}
pos += len;
}
self.compiled = true;
// No longer needed with fast-glob, but kept for API compatibility
}
pub fn find(&self, input: &[u8]) -> Option<i64> {
pub fn find(&self, input: &str) -> Option<i64> {
for pattern in &self.patterns {
if self.matches_pattern(&pattern.states, input) {
if pattern.matches(input) {
return Some(pattern.value);
}
}
None
}
fn matches_pattern(&self, states: &[State], input: &[u8]) -> bool {
self.match_recursive(states, 0, input, 0)
}
fn match_recursive(
&self,
states: &[State],
state_idx: usize,
input: &[u8],
input_idx: usize,
) -> bool {
if state_idx >= states.len() {
return input_idx == input.len();
}
if input_idx > input.len() {
return false;
}
let state = &states[state_idx];
if state.is_star {
if self.match_recursive(states, state_idx + 1, input, input_idx) {
return true;
}
for i in input_idx..input.len() {
if self.match_recursive(states, state_idx + 1, input, i + 1) {
return true;
}
}
false
} else {
if input_idx >= input.len() {
return false;
}
if state.matches(input[input_idx]) {
self.match_recursive(states, state_idx + 1, input, input_idx + 1)
} else {
false
pub fn find_with_index(&self, input: &str) -> Option<(i64, usize)> {
for (index, pattern) in self.patterns.iter().enumerate() {
if pattern.matches(input) {
return Some((pattern.value, index));
}
}
None
}
}
fn parse_glob(pattern: &str) -> GlobResult<Vec<State>> {
let mut states: Vec<State> = Vec::new();
let mut chars = pattern.bytes().peekable();
let mut position = 0;
while let Some(c) = chars.next() {
let mut char_set = State::new();
match c {
b'*' => {
if let Some(last_state) = states.last_mut() {
last_state.is_star = true;
} else {
let mut star_state = State::new();
star_state.is_star = true;
states.push(star_state);
}
position += 1;
continue;
}
b'?' => {
char_set.set_all();
}
b'\\' => {
if let Some(&next_char) = chars.peek() {
char_set.set_char(next_char);
chars.next();
position += 1;
} else {
return Err(GlobError::invalid_pattern(pattern));
}
}
b'[' => {
let bracket_start = position;
let mut negate = false;
let mut bracket_chars = Vec::new();
let mut closed = false;
if let Some(&b'!') | Some(&b'^') = chars.peek() {
negate = true;
chars.next();
position += 1;
}
if let Some(&b']') = chars.peek() {
bracket_chars.push(b']');
chars.next();
position += 1;
}
while let Some(bracket_char) = chars.next() {
position += 1;
if bracket_char == b']' {
closed = true;
break;
}
if bracket_char == b'-' && !bracket_chars.is_empty() {
if let Some(&end_char) = chars.peek() {
if end_char != b']' {
let start_char = *bracket_chars.last().unwrap();
chars.next();
position += 1;
if start_char > end_char {
return Err(GlobError::invalid_character_class(
pattern,
bracket_start,
));
}
for ch in start_char..=end_char {
bracket_chars.push(ch);
}
continue;
}
}
}
bracket_chars.push(bracket_char);
}
if !closed {
return Err(GlobError::unclosed_bracket(pattern, bracket_start));
}
if bracket_chars.is_empty() {
return Err(GlobError::invalid_character_class(pattern, bracket_start));
}
if negate {
char_set.set_all();
for ch in bracket_chars {
char_set.chars[ch as usize] = false;
}
} else {
for ch in bracket_chars {
char_set.set_char(ch);
}
}
}
_ => {
char_set.set_char(c);
}
}
states.push(char_set);
position += 1;
}
if states.is_empty() {
return Err(GlobError::EmptyPattern);
}
Ok(states)
}
impl Default for MultiGlob {
fn default() -> Self {
Self::new()
@ -546,14 +300,12 @@ impl ParallelGlobWalker {
let path = entry.path();
let filename = path.file_name()?.to_str()?;
for (pattern_index, pattern) in glob.patterns.iter().enumerate() {
if glob.matches_pattern(&pattern.states, filename.as_bytes()) {
return Some(SearchResult {
path: path.to_path_buf(),
pattern_value: pattern.value,
pattern_index,
});
}
if let Some((pattern_value, pattern_index)) = glob.find_with_index(filename) {
return Some(SearchResult {
path: path.to_path_buf(),
pattern_value,
pattern_index,
});
}
}
None
@ -624,14 +376,12 @@ impl ParallelGlobWalker {
let relative_path = path.strip_prefix(root).ok()?;
let path_str = relative_path.to_str()?;
for (pattern_index, pattern) in glob.patterns.iter().enumerate() {
if glob.matches_pattern(&pattern.states, path_str.as_bytes()) {
return Some(SearchResult {
path: path.to_path_buf(),
pattern_value: pattern.value,
pattern_index,
});
}
if let Some((pattern_value, pattern_index)) = glob.find_with_index(path_str) {
return Some(SearchResult {
path: path.to_path_buf(),
pattern_value,
pattern_index,
});
}
}
None
@ -730,7 +480,6 @@ pub fn search_with_patterns<P: AsRef<Path>>(
for (i, pattern) in options.exclude_patterns.iter().enumerate() {
exclude_glob.add(pattern, i as i64 + 1)?;
}
exclude_glob.compile();
let walker = ParallelGlobWalker::new(include_glob, options.search_options);
let all_results = walker.search_with_full_paths(root_path)?;
@ -750,7 +499,7 @@ pub fn search_with_patterns<P: AsRef<Path>>(
};
// Check if file should be excluded
exclude_glob.find(relative_path.as_bytes()).is_none()
exclude_glob.find(&relative_path).is_none()
})
.collect();
@ -911,14 +660,12 @@ impl AsyncGlobWalker {
let path = entry.path();
let filename = path.file_name()?.to_str()?;
for (pattern_index, pattern) in glob.patterns.iter().enumerate() {
if glob.matches_pattern(&pattern.states, filename.as_bytes()) {
return Some(SearchResult {
path: path.to_path_buf(),
pattern_value: pattern.value,
pattern_index,
});
}
if let Some((pattern_value, pattern_index)) = glob.find_with_index(filename) {
return Some(SearchResult {
path: path.to_path_buf(),
pattern_value,
pattern_index,
});
}
}
None
@ -935,14 +682,12 @@ impl AsyncGlobWalker {
let relative_path = path.strip_prefix(root).ok()?;
let path_str = relative_path.to_str()?;
for (pattern_index, pattern) in glob.patterns.iter().enumerate() {
if glob.matches_pattern(&pattern.states, path_str.as_bytes()) {
return Some(SearchResult {
path: path.to_path_buf(),
pattern_value: pattern.value,
pattern_index,
});
}
if let Some((pattern_value, pattern_index)) = glob.find_with_index(path_str) {
return Some(SearchResult {
path: path.to_path_buf(),
pattern_value,
pattern_index,
});
}
}
None
@ -1016,7 +761,6 @@ pub async fn search_with_patterns_async<P: AsRef<Path>>(
for (i, pattern) in options.exclude_patterns.iter().enumerate() {
exclude_glob.add(pattern, i as i64 + 1)?;
}
exclude_glob.compile();
let walker = AsyncGlobWalker::new(include_glob, options.search_options);
let all_results = walker.search_with_full_paths(root_path).await?;
@ -1034,7 +778,7 @@ pub async fn search_with_patterns_async<P: AsRef<Path>>(
result.path.to_string_lossy()
};
exclude_glob.find(relative_path.as_bytes()).is_none()
exclude_glob.find(&relative_path).is_none()
})
.collect();
@ -1098,7 +842,6 @@ pub fn search_with_patterns_profiled<P: AsRef<Path>>(
for (i, pattern) in options.exclude_patterns.iter().enumerate() {
exclude_glob.add(pattern, i as i64 + 1)?;
}
exclude_glob.compile();
// Build walker with profiling
let mut builder = WalkBuilder::new(root_path);
@ -1198,10 +941,10 @@ pub fn search_with_patterns_profiled<P: AsRef<Path>>(
// Check if file matches include patterns
if let Some(pattern_value) =
include_glob_clone.find(relative_path.as_bytes())
include_glob_clone.find(&relative_path)
{
// Check if file should be excluded
if exclude_glob_clone.find(relative_path.as_bytes()).is_none() {
if exclude_glob_clone.find(&relative_path).is_none() {
if let Ok(mut results_guard) = results_clone.lock() {
results_guard.push(SearchResult {
path: path.to_path_buf(),
@ -1331,18 +1074,12 @@ impl FastGlobWalker {
if path.is_dir() {
self.walk_dir(&path, results)?;
} else if let Some(filename) = path.file_name().and_then(|n| n.to_str()) {
for (pattern_index, pattern) in self.glob.patterns.iter().enumerate() {
if self
.glob
.matches_pattern(&pattern.states, filename.as_bytes())
{
results.push(SearchResult {
path: path.clone(),
pattern_value: pattern.value,
pattern_index,
});
break;
}
if let Some((pattern_value, pattern_index)) = self.glob.find_with_index(filename) {
results.push(SearchResult {
path: path.clone(),
pattern_value,
pattern_index,
});
}
}
}
@ -1383,18 +1120,12 @@ impl FastGlobWalker {
}
if let Some(filename) = path.file_name().and_then(|n| n.to_str()) {
for (pattern_index, pattern) in self.glob.patterns.iter().enumerate() {
if self
.glob
.matches_pattern(&pattern.states, filename.as_bytes())
{
results.push(SearchResult {
path: path.clone(),
pattern_value: pattern.value,
pattern_index,
});
break;
}
if let Some((pattern_value, pattern_index)) = self.glob.find_with_index(filename) {
results.push(SearchResult {
path: path.clone(),
pattern_value,
pattern_index,
});
}
}
}
@ -1488,8 +1219,8 @@ mod tests {
assert!(glob.add("hello", 1).is_ok());
glob.compile();
assert_eq!(glob.find(b"hello"), Some(1));
assert_eq!(glob.find(b"world"), None);
assert_eq!(glob.find("hello"), Some(1));
assert_eq!(glob.find("world"), None);
}
#[test]
@ -1498,10 +1229,10 @@ mod tests {
assert!(glob.add("h*o", 1).is_ok());
glob.compile();
assert_eq!(glob.find(b"hello"), Some(1));
assert_eq!(glob.find(b"ho"), Some(1));
assert_eq!(glob.find(b"hilo"), Some(1));
assert_eq!(glob.find(b"hi"), None);
assert_eq!(glob.find("hello"), Some(1));
assert_eq!(glob.find("ho"), Some(1));
assert_eq!(glob.find("hilo"), Some(1));
assert_eq!(glob.find("hi"), None);
}
#[test]
@ -1510,9 +1241,9 @@ mod tests {
assert!(glob.add("h?llo", 1).is_ok());
glob.compile();
assert_eq!(glob.find(b"hello"), Some(1));
assert_eq!(glob.find(b"hallo"), Some(1));
assert_eq!(glob.find(b"hllo"), None);
assert_eq!(glob.find("hello"), Some(1));
assert_eq!(glob.find("hallo"), Some(1));
assert_eq!(glob.find("hllo"), None);
}
#[test]
@ -1521,10 +1252,10 @@ mod tests {
assert!(glob.add("h[aeiou]llo", 1).is_ok());
glob.compile();
assert_eq!(glob.find(b"hello"), Some(1));
assert_eq!(glob.find(b"hallo"), Some(1));
assert_eq!(glob.find(b"hillo"), Some(1));
assert_eq!(glob.find(b"hyllo"), None);
assert_eq!(glob.find("hello"), Some(1));
assert_eq!(glob.find("hallo"), Some(1));
assert_eq!(glob.find("hillo"), Some(1));
assert_eq!(glob.find("hyllo"), None);
}
#[test]
@ -1533,9 +1264,9 @@ mod tests {
assert!(glob.add("h[a-z]llo", 1).is_ok());
glob.compile();
assert_eq!(glob.find(b"hello"), Some(1));
assert_eq!(glob.find(b"hallo"), Some(1));
assert_eq!(glob.find(b"h9llo"), None);
assert_eq!(glob.find("hello"), Some(1));
assert_eq!(glob.find("hallo"), Some(1));
assert_eq!(glob.find("h9llo"), None);
}
#[test]
@ -1544,9 +1275,9 @@ mod tests {
assert!(glob.add("h[!aeiou]llo", 1).is_ok());
glob.compile();
assert_eq!(glob.find(b"hello"), None);
assert_eq!(glob.find(b"hxllo"), Some(1));
assert_eq!(glob.find(b"h9llo"), Some(1));
assert_eq!(glob.find("hello"), None);
assert_eq!(glob.find("hxllo"), Some(1));
assert_eq!(glob.find("h9llo"), Some(1));
}
#[test]
@ -1557,9 +1288,9 @@ mod tests {
assert!(glob.add("test.txt", 20).is_ok());
glob.compile();
assert_eq!(glob.find(b"test.txt"), Some(20));
assert_eq!(glob.find(b"hello.txt"), Some(10));
assert_eq!(glob.find(b"test.rs"), Some(5));
assert_eq!(glob.find("test.txt"), Some(20));
assert_eq!(glob.find("hello.txt"), Some(10));
assert_eq!(glob.find("test.rs"), Some(5));
}
#[test]
@ -1568,9 +1299,9 @@ mod tests {
assert!(glob.add("test\\*file", 1).is_ok());
glob.compile();
assert_eq!(glob.find(b"test*file"), Some(1));
assert_eq!(glob.find(b"testfile"), None);
assert_eq!(glob.find(b"testXfile"), None);
assert_eq!(glob.find("test*file"), Some(1));
assert_eq!(glob.find("testfile"), None);
assert_eq!(glob.find("testXfile"), None);
}
#[test]
@ -1773,30 +1504,6 @@ mod tests {
assert!(matches!(result, Err(GlobError::EmptyPattern)));
}
#[test]
fn test_error_unclosed_bracket() {
let mut glob = MultiGlob::new();
let result = glob.add("test[abc", 1);
assert!(matches!(result, Err(GlobError::UnclosedBracket { .. })));
}
#[test]
fn test_error_invalid_character_class() {
let mut glob = MultiGlob::new();
let result = glob.add("test[z-a]", 1);
assert!(matches!(
result,
Err(GlobError::InvalidCharacterClass { .. })
));
}
#[test]
fn test_error_invalid_pattern_backslash() {
let mut glob = MultiGlob::new();
let result = glob.add("test\\", 1);
assert!(matches!(result, Err(GlobError::InvalidPattern { .. })));
}
#[test]
fn test_error_root_path_not_found() {
let options = SearchPatternsOptions::new(vec!["*.txt".to_string()], vec![]);
@ -1805,27 +1512,10 @@ mod tests {
assert!(matches!(result, Err(GlobError::RootPathNotFound { .. })));
}
#[test]
fn test_error_pattern_too_complex() {
let mut glob = MultiGlob::new();
// Create a pattern that would exceed the state limit
let mut complex_pattern = String::new();
for _ in 0..2000 {
complex_pattern.push('a');
}
let result = glob.add(&complex_pattern, 1);
assert!(matches!(result, Err(GlobError::PatternTooComplex { .. })));
}
#[test]
fn test_glob_error_display() {
let error = GlobError::invalid_pattern("test[");
assert!(error.to_string().contains("Invalid glob pattern"));
let error = GlobError::unclosed_bracket("test[abc", 4);
assert!(error.to_string().contains("Unclosed bracket"));
assert!(error.to_string().contains("position 4"));
}
#[test]
@ -2043,4 +1733,4 @@ mod tests {
let result = search_with_patterns_async("/nonexistent/path", options).await;
assert!(matches!(result, Err(GlobError::RootPathNotFound { .. })));
}
}
}

View file

@ -10,10 +10,7 @@ dependencies = []
GitHub = "https://github.com/tombi-toml/tombi"
[dependency-groups]
dev = [
"pytest>=8.3.3",
"ruff>=0.7.4",
]
dev = ["pytest>=8.3.3", "ruff>=0.7.4"]
[build-system]
requires = ["maturin>=1.5,<2.0"]

View file

@ -1,7 +1,3 @@
[toolchain]
channel = "stable"
components = [
"clippy",
"rust-analyzer",
"rustfmt",
]
components = ["clippy", "rust-analyzer", "rustfmt"]

View file

@ -23,7 +23,4 @@ time = { workspace = true }
tombi-config = { workspace = true, features = ["jsonschema"] }
ungrammar = { workspace = true }
xshell = { workspace = true }
zip = { workspace = true, features = [
"deflate",
"time",
] }
zip = { workspace = true, features = ["deflate", "time"] }