Repository: reubeno/brush
Pull request #281 (2024-11-30 06:25:36)

fix: better handle escape chars in pattern bracket exprs

github-actions[bot]: ## Test Results
    2 files      9 suites   1m 12s ⏱️
  527 tests   527 ✅ 0 💤 0 ❌
1 042 runs  1 042 ✅ 0 💤 0 ❌

Results for commit 1ccaac7b.

[test-results]:data:application/gzip;base64,H4sIALixSmcC/12MSw7CIBQAr9KwdgHUSp+XMfCAhNgWw2fVeHeBtNq6nJlkVmLdZCK5d/zSkZhdagAFdA4yOb8UFDWWkmobuNjpETPiv3q6V1H0K6x000mYEHzYTMhLfTJ65Rvtz5P6PRsflo2PR/Tz7FIBwhClRKGQGwoIPTAu5Wj4CGChV2LQDG4KNHl/AHiPHxgGAQAA

github-actions[bot]: # Performance Benchmark Report
| Benchmark name                         | Baseline (μs) |  Test/PR (μs) |    Delta (μs) | Delta %         |
| -------------------------------------- | ------------- | ------------- | ------------- | --------------- |
| `expand_one_string                   ` | `    3.43 μs` | `    3.43 μs` | `    0.00 μs` | `⚪  Unchanged` |
| `instantiate_shell                   ` | `   60.76 μs` | `   60.92 μs` | `    0.16 μs` | `⚪  Unchanged` |
| `instantiate_shell_with_init_scripts ` | `30046.13 μs` | `30464.64 μs` | `  418.51 μs` | `⚪  Unchanged` |
| `parse_bash_completion               ` | ` 2817.80 μs` | ` 2861.85 μs` | `   44.05 μs` | `🟠 +1.56%    ` |
| `parse_sample_script                 ` | `    4.20 μs` | `    4.39 μs` | `    0.19 μs` | `⚪  Unchanged` |
| `run_echo_builtin_command            ` | `   90.70 μs` | `   92.73 μs` | `    2.02 μs` | `🟠 +2.23%    ` |
| `run_one_builtin_command             ` | `  107.35 μs` | `  109.92 μs` | `    2.57 μs` | `⚪  Unchanged` |
| `run_one_external_command            ` | ` 1993.90 μs` | ` 1965.70 μs` | `  -28.20 μs` | `🟢 -1.41%    ` |
| `run_one_external_command_directly   ` | ` 1008.31 μs` | ` 1009.07 μs` | `    0.76 μs` | `⚪  Unchanged` |

# Code Coverage Report: Only Changed Files listed

| Package | Base Coverage | New Coverage | Difference |
| ------- | ------------- | ------------ | ---------- |
| brush-core/src/expansion.rs | 🟢 96.53% | 🟢 96.26% | 🔴 -0.27% |
| brush-core/src/patterns.rs | 🟢 97.78% | 🟢 97.92% | 🟢 0.14% |
| brush-core/src/regex.rs | 🟢 97.67% | 🟢 100% | 🟢 2.33% |
| **Overall Coverage** | **🟢 74.57%** | **🟢 74.58%** | **🟢 0.01%** |

_Minimum allowed coverage is_ `70%`_, this run produced_ `74.58%`
<!-- Sticky Pull Request Comment -->

Patch:
diff --git a/brush-core/src/patterns.rs b/brush-core/src/patterns.rs
index c636cfb..36913ab 100644
--- a/brush-core/src/patterns.rs
+++ b/brush-core/src/patterns.rs
@@ -297,25 +297,17 @@ impl Pattern {
                     current_pattern.push_str(s);
                 }
                 PatternPiece::Literal(s) => {
-                    if !current_pattern.is_empty() {
-                        let regex_piece = pattern_to_regex_str(
-                            current_pattern.as_str(),
-                            self.enable_extended_globbing,
-                        )?;
-                        regex_str.push_str(regex_piece.as_str());
-                        current_pattern = String::new();
+                    for c in s.chars() {
+                        current_pattern.push('\\');
+                        current_pattern.push(c);
                     }
-
-                    regex_str.push_str(escape_for_regex(s).as_str());
                 }
             }
         }
 
-        if !current_pattern.is_empty() {
-            let regex_piece =
-                pattern_to_regex_str(current_pattern.as_str(), self.enable_extended_globbing)?;
-            regex_str.push_str(regex_piece.as_str());
-        }
+        let regex_piece =
+            pattern_to_regex_str(current_pattern.as_str(), self.enable_extended_globbing)?;
+        regex_str.push_str(regex_piece.as_str());
 
         if strict_suffix_match {
             regex_str.push('$');
@@ -361,26 +353,10 @@ fn requires_expansion(s: &str) -> bool {
     s.contains(['*', '?', '[', ']', '(', ')'])
 }
 
-fn escape_for_regex(s: &str) -> String {
-    let mut escaped = String::new();
-    for c in s.chars() {
-        if brush_parser::pattern::regex_char_needs_escaping(c) {
-            escaped.push('\\');
-        }
-        escaped.push(c);
-    }
-    escaped
-}
-
 fn pattern_to_regex_str(
     pattern: &str,
     enable_extended_globbing: bool,
 ) -> Result<String, error::Error> {
-    // TODO: pattern matching with **
-    if pattern.contains("**") {
-        return error::unimp("pattern matching with '**' pattern");
-    }
-
     Ok(brush_parser::pattern::pattern_to_regex_str(
         pattern,
         enable_extended_globbing,
diff --git a/brush-parser/src/pattern.rs b/brush-parser/src/pattern.rs
index 39d277a..b8803b6 100644
--- a/brush-parser/src/pattern.rs
+++ b/brush-parser/src/pattern.rs
@@ -155,6 +155,18 @@ mod tests {
     use super::*;
     use anyhow::Result;
 
+    #[test]
+    fn test_bracket_exprs() -> Result<()> {
+        assert_eq!(pattern_to_regex_str("[a-z]", true)?, "[a-z]");
+        assert_eq!(pattern_to_regex_str("[abc]", true)?, "[abc]");
+        assert_eq!(pattern_to_regex_str(r"[\(]", true)?, r"[\(]");
+        assert_eq!(pattern_to_regex_str(r"[(]", true)?, "[(]");
+        assert_eq!(pattern_to_regex_str("[[:digit:]]", true)?, "[[:digit:]]");
+        assert_eq!(pattern_to_regex_str(r"[-(),!]*", true)?, r"[-(),!].*");
+        assert_eq!(pattern_to_regex_str(r"[-\(\),\!]*", true)?, r"[-\(\),\!].*");
+        Ok(())
+    }
+
     #[test]
     fn test_extended_glob() -> Result<()> {
         assert_eq!(


Test patch:
diff --git a/brush-shell/tests/cases/patterns.yaml b/brush-shell/tests/cases/patterns.yaml
index 2f7790c..a049fb4 100644
--- a/brush-shell/tests/cases/patterns.yaml
+++ b/brush-shell/tests/cases/patterns.yaml
@@ -267,3 +267,10 @@ cases:
 
       myfunc abc
       myfunc "*"
+
+  - name: "Pattern matching: character sets"
+    stdin: |
+      [[ "x" == [a-z] ]]       && echo "1. Matched"
+      [[ "x" == [xyz] ]]       && echo "2. Matched"
+      [[ "1" == [[:digit:]] ]] && echo "3. Matched"
+      [[ "(" == [\(]  ]]       && echo "4. Matched"