Update SearchQueryCondition to support logical or and contains types; also update non-regex types to escape regex

pull/137/head
Chip Senkbeil 2 years ago
parent 193bb6d237
commit a8107aed3a
No known key found for this signature in database
GPG Key ID: 35EF1F8EC72A4131

@ -7,8 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [Unreleased]
### Added
- New `contains` and `or` types for `SearchQueryCondition`
### Changed ### Changed
- `SearchQueryCondition` now escapes regex for all types except `regex`
- Removed `min_depth` option from search - Removed `min_depth` option from search
- Updated search to properly use binary detection, filter out common ignore - Updated search to properly use binary detection, filter out common ignore
file patterns, and execute in parallel via the `ignore` crate and `num_cpus` file patterns, and execute in parallel via the `ignore` crate and `num_cpus`

1
Cargo.lock generated

@ -772,6 +772,7 @@ dependencies = [
"portable-pty", "portable-pty",
"predicates", "predicates",
"rand 0.8.5", "rand 0.8.5",
"regex",
"rstest", "rstest",
"schemars", "schemars",
"serde", "serde",

@ -30,6 +30,7 @@ num_cpus = "1.13.1"
once_cell = "1.13.0" once_cell = "1.13.0"
portable-pty = "0.7.0" portable-pty = "0.7.0"
rand = { version = "0.8.5", features = ["getrandom"] } rand = { version = "0.8.5", features = ["getrandom"] }
regex = "1.1"
serde = { version = "1.0.142", features = ["derive"] } serde = { version = "1.0.142", features = ["derive"] }
serde_bytes = "0.11.7" serde_bytes = "0.11.7"
serde_json = "1.0.83" serde_json = "1.0.83"

@ -70,20 +70,33 @@ impl SearchQueryTarget {
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[serde(rename_all = "snake_case", deny_unknown_fields, tag = "type")] #[serde(rename_all = "snake_case", deny_unknown_fields, tag = "type")]
pub enum SearchQueryCondition { pub enum SearchQueryCondition {
/// Begins with some text /// Text is found anywhere (all regex patterns are escaped)
Contains { value: String },
/// Begins with some text (all regex patterns are escaped)
EndsWith { value: String }, EndsWith { value: String },
/// Matches some text exactly /// Matches some text exactly (all regex patterns are escaped)
Equals { value: String }, Equals { value: String },
/// Any of the conditions match
Or { value: Vec<SearchQueryCondition> },
/// Matches some regex /// Matches some regex
Regex { value: String }, Regex { value: String },
/// Begins with some text /// Begins with some text (all regex patterns are escaped)
StartsWith { value: String }, StartsWith { value: String },
} }
impl SearchQueryCondition { impl SearchQueryCondition {
/// Creates a new instance with `Contains` variant
pub fn contains(value: impl Into<String>) -> Self {
Self::Contains {
value: value.into(),
}
}
/// Creates a new instance with `EndsWith` variant /// Creates a new instance with `EndsWith` variant
pub fn ends_with(value: impl Into<String>) -> Self { pub fn ends_with(value: impl Into<String>) -> Self {
Self::EndsWith { Self::EndsWith {
@ -98,6 +111,17 @@ impl SearchQueryCondition {
} }
} }
/// Creates a new instance with `Or` variant
pub fn or<I, C>(value: I) -> Self
where
I: IntoIterator<Item = C>,
C: Into<SearchQueryCondition>,
{
Self::Or {
value: value.into_iter().map(|s| s.into()).collect(),
}
}
/// Creates a new instance with `Regex` variant /// Creates a new instance with `Regex` variant
pub fn regex(value: impl Into<String>) -> Self { pub fn regex(value: impl Into<String>) -> Self {
Self::Regex { Self::Regex {
@ -115,10 +139,21 @@ impl SearchQueryCondition {
/// Converts the condition in a regex string /// Converts the condition in a regex string
pub fn to_regex_string(&self) -> String { pub fn to_regex_string(&self) -> String {
match self { match self {
Self::EndsWith { value } => format!(r"{value}$"), Self::Contains { value } => regex::escape(value),
Self::Equals { value } => format!(r"^{value}$"), Self::EndsWith { value } => format!(r"{}$", regex::escape(value)),
Self::Equals { value } => format!(r"^{}$", regex::escape(value)),
Self::Regex { value } => value.to_string(), Self::Regex { value } => value.to_string(),
Self::StartsWith { value } => format!(r"^{value}"), Self::StartsWith { value } => format!(r"^{}", regex::escape(value)),
Self::Or { value } => {
let mut s = String::new();
for (i, condition) in value.iter().enumerate() {
if i > 0 {
s.push('|');
}
s.push_str(&condition.to_regex_string());
}
s
}
} }
} }
} }
@ -349,3 +384,45 @@ impl SearchQueryMatchData {
schemars::schema_for!(SearchQueryMatchData) schemars::schema_for!(SearchQueryMatchData)
} }
} }
#[cfg(test)]
mod tests {
use super::*;
mod search_query_condition {
use super::*;
#[test]
fn to_regex_string_should_convert_to_appropriate_regex_and_escape_as_needed() {
assert_eq!(
SearchQueryCondition::contains("t^es$t").to_regex_string(),
r"t\^es\$t"
);
assert_eq!(
SearchQueryCondition::ends_with("t^es$t").to_regex_string(),
r"t\^es\$t$"
);
assert_eq!(
SearchQueryCondition::equals("t^es$t").to_regex_string(),
r"^t\^es\$t$"
);
assert_eq!(
SearchQueryCondition::or([
SearchQueryCondition::contains("t^es$t"),
SearchQueryCondition::equals("t^es$t"),
SearchQueryCondition::regex("^test$"),
])
.to_regex_string(),
r"t\^es\$t|^t\^es\$t$|^test$"
);
assert_eq!(
SearchQueryCondition::regex("test").to_regex_string(),
"test"
);
assert_eq!(
SearchQueryCondition::starts_with("t^es$t").to_regex_string(),
r"^t\^es\$t"
);
}
}
}

Loading…
Cancel
Save