refactor: replace lazy_static with LazyLock
This commit is contained in:
@@ -103,7 +103,7 @@ Rust patterns, error handling, and anti-patterns are defined in `.claude/rules/r
|
||||
|
||||
- **anyhow::Result** everywhere, always `.context("description")?`
|
||||
- **No unwrap()** in production code
|
||||
- **lazy_static!** for all regex (never compile inside a function)
|
||||
- **`LazyLock` statics** for all regex (never compile on every function call)
|
||||
- **Fallback pattern**: if filter fails, execute raw command unchanged
|
||||
- **No async**: single-threaded by design (startup <10ms)
|
||||
- **Exit code propagation**: `std::process::exit(code)` on child failure
|
||||
|
||||
+1
-1
@@ -60,7 +60,7 @@ Every filter needs a fallback path. Every hook must handle malformed input grace
|
||||
|
||||
<10ms startup. No async runtime. No config file I/O on the critical path. If developers perceive any delay, they'll disable RTK. Speed is the difference between adoption and abandonment.
|
||||
|
||||
`lazy_static!` for all regex. No network calls. No disk reads in the hot path. Benchmark before/after with `hyperfine`.
|
||||
Use `LazyLock` statics for all regex. No network calls. No disk reads in the hot path. Benchmark before/after with `hyperfine`.
|
||||
|
||||
### Extensibility
|
||||
|
||||
|
||||
Generated
-7
@@ -672,12 +672,6 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||
|
||||
[[package]]
|
||||
name = "leb128fmt"
|
||||
version = "0.1.0"
|
||||
@@ -902,7 +896,6 @@ dependencies = [
|
||||
"flate2",
|
||||
"getrandom 0.4.2",
|
||||
"ignore",
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"quick-xml",
|
||||
"regex",
|
||||
|
||||
@@ -18,7 +18,6 @@ anyhow = "1.0"
|
||||
ignore = "0.4"
|
||||
walkdir = "2"
|
||||
regex = "1"
|
||||
lazy_static = "1.4"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = { version = "1", features = ["preserve_order"] }
|
||||
colored = "3"
|
||||
|
||||
@@ -13,7 +13,7 @@ Our goal is to keep the codebase consistent and easy to extend. PRs that deviate
|
||||
New to RTK? The fastest path to a mergeable first PR:
|
||||
|
||||
1. **Read the flow once.** Start at [`CONTRIBUTING.md`](../../CONTRIBUTING.md), then skim [`docs/contributing/TECHNICAL.md`](TECHNICAL.md) to see how a command flows from `main.rs` → a `*_cmd.rs` filter → tracking → stdout.
|
||||
2. **Look at a good example.** [`src/cmds/git/git.rs`](../../src/cmds/git/git.rs) is a representative filter — it shows the `run()` entry point, `lazy_static!` regex setup, filter helpers, and embedded tests all in one file.
|
||||
2. **Look at a good example.** [`src/cmds/git/git.rs`](../../src/cmds/git/git.rs) is a representative filter — it shows the `run()` entry point, `LazyLock` regex setup, filter helpers, and embedded tests all in one file.
|
||||
3. **Know the shared helpers before reimplementing.** Two files cover most of what you need:
|
||||
- [`src/core/runner.rs`](../../src/core/runner.rs) — command execution wrappers: `run_filtered()` (run a command, then apply your filter function), `run_passthrough()` (run unfiltered but tracked), `run_streamed()` (streaming filter).
|
||||
- [`src/core/utils.rs`](../../src/core/utils.rs) — shared utilities: `resolved_command()`, `strip_ansi()`, `truncate()`, `count_tokens()`, and more.
|
||||
|
||||
@@ -370,7 +370,7 @@ Declarative filters with an 8-stage pipeline: strip ANSI, regex replace, match o
|
||||
|
||||
Achieved through:
|
||||
- Zero async overhead (single-threaded, no tokio)
|
||||
- Lazy regex compilation (`lazy_static!`)
|
||||
- Lazy regex compilation (`LazyLock`)
|
||||
- Minimal allocations (borrow over clone)
|
||||
- No config file I/O on startup (loaded on-demand)
|
||||
|
||||
|
||||
@@ -13,9 +13,9 @@ use crate::core::utils::{
|
||||
};
|
||||
use crate::json_cmd;
|
||||
use anyhow::{Context, Result};
|
||||
use lazy_static::lazy_static;
|
||||
use regex::Regex;
|
||||
use serde_json::Value;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
const MAX_ITEMS: usize = CAP_LIST;
|
||||
const JSON_COMPRESS_DEPTH: usize = 4;
|
||||
@@ -1325,9 +1325,8 @@ fn filter_eks_cluster(json_str: &str) -> Option<FilterResult> {
|
||||
Some(FilterResult::new(text))
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref S3_TRANSFER_RE: Regex = Regex::new(r"^(upload|download|delete|copy|move):").unwrap();
|
||||
}
|
||||
static S3_TRANSFER_RE: LazyLock<Regex> =
|
||||
LazyLock::new(|| Regex::new(r"^(upload|download|delete|copy|move):").unwrap());
|
||||
|
||||
fn filter_sqs_messages(json_str: &str) -> Option<FilterResult> {
|
||||
let v: Value = serde_json::from_str(json_str).ok()?;
|
||||
|
||||
@@ -7,18 +7,18 @@ use crate::core::runner::{self, RunOptions};
|
||||
use crate::core::truncate::CAP_LIST;
|
||||
use crate::core::utils::resolved_command;
|
||||
use anyhow::Result;
|
||||
use lazy_static::lazy_static;
|
||||
use regex::Regex;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
const MAX_TABLE_ROWS: usize = CAP_LIST;
|
||||
const MAX_EXPANDED_RECORDS: usize = CAP_LIST;
|
||||
|
||||
lazy_static! {
|
||||
static ref EXPANDED_RECORD: Regex = Regex::new(r"-\[ RECORD \d+ \]-").unwrap();
|
||||
static ref SEPARATOR: Regex = Regex::new(r"^[-+]+$").unwrap();
|
||||
static ref ROW_COUNT: Regex = Regex::new(r"^\(\d+ rows?\)$").unwrap();
|
||||
static ref RECORD_HEADER: Regex = Regex::new(r"^-\[ RECORD (\d+) \]-").unwrap();
|
||||
}
|
||||
static EXPANDED_RECORD: LazyLock<Regex> =
|
||||
LazyLock::new(|| Regex::new(r"-\[ RECORD \d+ \]-").unwrap());
|
||||
static SEPARATOR: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"^[-+]+$").unwrap());
|
||||
static ROW_COUNT: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"^\(\d+ rows?\)$").unwrap());
|
||||
static RECORD_HEADER: LazyLock<Regex> =
|
||||
LazyLock::new(|| Regex::new(r"^-\[ RECORD (\d+) \]-").unwrap());
|
||||
|
||||
// Edge cases vs previous manual implementation:
|
||||
// - On failure: stderr is no longer eprinted on the success path (only on failure via early_exit)
|
||||
|
||||
+61
-50
@@ -3,11 +3,11 @@
|
||||
use crate::core::utils::strip_ansi;
|
||||
use anyhow::{Context, Result};
|
||||
use flate2::read::GzDecoder;
|
||||
use lazy_static::lazy_static;
|
||||
use regex::Regex;
|
||||
use std::collections::HashSet;
|
||||
use std::io::{Cursor, Read};
|
||||
use std::path::Path;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct BinlogIssue {
|
||||
@@ -52,63 +52,74 @@ pub struct RestoreSummary {
|
||||
pub duration_text: Option<String>,
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref ISSUE_RE: Regex = Regex::new(
|
||||
static ISSUE_RE: LazyLock<Regex> = LazyLock::new(|| {
|
||||
Regex::new(
|
||||
r"(?m)^\s*(?P<file>[^\r\n:(]+)\((?P<line>\d+),(?P<column>\d+)\):\s*(?P<kind>error|warning)\s*(?:(?P<code>[A-Za-z]+\d+)\s*:\s*)?(?P<msg>.*)$"
|
||||
)
|
||||
.expect("valid regex");
|
||||
static ref BUILD_SUMMARY_RE: Regex = Regex::new(r"(?mi)^\s*(?P<count>\d+)\s+(?P<kind>warning|error)\(s\)")
|
||||
.expect("valid regex");
|
||||
static ref ERROR_COUNT_RE: Regex =
|
||||
Regex::new(r"(?i)\b(?P<count>\d+)\s+error\(s\)").expect("valid regex");
|
||||
static ref WARNING_COUNT_RE: Regex =
|
||||
Regex::new(r"(?i)\b(?P<count>\d+)\s+warning\(s\)").expect("valid regex");
|
||||
static ref FALLBACK_ERROR_LINE_RE: Regex =
|
||||
Regex::new(r"(?mi)^.+\(\d+,\d+\):\s*error(?:\s+[A-Za-z]{2,}\d{3,})?(?:\s*:.*)?$")
|
||||
.expect("valid regex");
|
||||
static ref FALLBACK_WARNING_LINE_RE: Regex =
|
||||
Regex::new(r"(?mi)^.+\(\d+,\d+\):\s*warning(?:\s+[A-Za-z]{2,}\d{3,})?(?:\s*:.*)?$")
|
||||
.expect("valid regex");
|
||||
static ref DURATION_RE: Regex =
|
||||
Regex::new(r"(?m)^\s*Time Elapsed\s+(?P<duration>[^\r\n]+)$").expect("valid regex");
|
||||
static ref TEST_RESULT_RE: Regex = Regex::new(
|
||||
.expect("valid regex")
|
||||
});
|
||||
static BUILD_SUMMARY_RE: LazyLock<Regex> = LazyLock::new(|| {
|
||||
Regex::new(r"(?mi)^\s*(?P<count>\d+)\s+(?P<kind>warning|error)\(s\)").expect("valid regex")
|
||||
});
|
||||
static ERROR_COUNT_RE: LazyLock<Regex> =
|
||||
LazyLock::new(|| Regex::new(r"(?i)\b(?P<count>\d+)\s+error\(s\)").expect("valid regex"));
|
||||
static WARNING_COUNT_RE: LazyLock<Regex> =
|
||||
LazyLock::new(|| Regex::new(r"(?i)\b(?P<count>\d+)\s+warning\(s\)").expect("valid regex"));
|
||||
static FALLBACK_ERROR_LINE_RE: LazyLock<Regex> = LazyLock::new(|| {
|
||||
Regex::new(r"(?mi)^.+\(\d+,\d+\):\s*error(?:\s+[A-Za-z]{2,}\d{3,})?(?:\s*:.*)?$")
|
||||
.expect("valid regex")
|
||||
});
|
||||
static FALLBACK_WARNING_LINE_RE: LazyLock<Regex> = LazyLock::new(|| {
|
||||
Regex::new(r"(?mi)^.+\(\d+,\d+\):\s*warning(?:\s+[A-Za-z]{2,}\d{3,})?(?:\s*:.*)?$")
|
||||
.expect("valid regex")
|
||||
});
|
||||
static DURATION_RE: LazyLock<Regex> = LazyLock::new(|| {
|
||||
Regex::new(r"(?m)^\s*Time Elapsed\s+(?P<duration>[^\r\n]+)$").expect("valid regex")
|
||||
});
|
||||
static TEST_RESULT_RE: LazyLock<Regex> = LazyLock::new(|| {
|
||||
Regex::new(
|
||||
r"(?m)(?:Passed!|Failed!)\s*-\s*Failed:\s*(?P<failed>\d+),\s*Passed:\s*(?P<passed>\d+),\s*Skipped:\s*(?P<skipped>\d+),\s*Total:\s*(?P<total>\d+),\s*Duration:\s*(?P<duration>[^\r\n-]+)"
|
||||
)
|
||||
.expect("valid regex");
|
||||
static ref TEST_SUMMARY_RE: Regex = Regex::new(
|
||||
.expect("valid regex")
|
||||
});
|
||||
static TEST_SUMMARY_RE: LazyLock<Regex> = LazyLock::new(|| {
|
||||
Regex::new(
|
||||
r"(?mi)^\s*Test summary:\s*total:\s*(?P<total>\d+),\s*failed:\s*(?P<failed>\d+),\s*(?:succeeded|passed):\s*(?P<passed>\d+),\s*skipped:\s*(?P<skipped>\d+),\s*duration:\s*(?P<duration>[^\r\n]+)$"
|
||||
)
|
||||
.expect("valid regex");
|
||||
static ref FAILED_TEST_HEAD_RE: Regex = Regex::new(
|
||||
r"(?m)^\s*Failed\s+(?P<name>[^\r\n\[]+)\s+\[[^\]\r\n]+\]\s*$"
|
||||
)
|
||||
.expect("valid regex");
|
||||
static ref RESTORE_PROJECT_RE: Regex =
|
||||
Regex::new(r"(?m)^\s*Restored\s+.+\.csproj\s*\(").expect("valid regex");
|
||||
static ref RESTORE_DIAGNOSTIC_RE: Regex = Regex::new(
|
||||
.expect("valid regex")
|
||||
});
|
||||
static FAILED_TEST_HEAD_RE: LazyLock<Regex> = LazyLock::new(|| {
|
||||
Regex::new(r"(?m)^\s*Failed\s+(?P<name>[^\r\n\[]+)\s+\[[^\]\r\n]+\]\s*$").expect("valid regex")
|
||||
});
|
||||
static RESTORE_PROJECT_RE: LazyLock<Regex> =
|
||||
LazyLock::new(|| Regex::new(r"(?m)^\s*Restored\s+.+\.csproj\s*\(").expect("valid regex"));
|
||||
static RESTORE_DIAGNOSTIC_RE: LazyLock<Regex> = LazyLock::new(|| {
|
||||
Regex::new(
|
||||
r"(?mi)^\s*(?:(?P<file>.+?)\s+:\s+)?(?P<kind>warning|error)\s+(?P<code>[A-Za-z]{2,}\d{3,})\s*:\s*(?P<msg>.+)$"
|
||||
)
|
||||
.expect("valid regex");
|
||||
static ref PROJECT_PATH_RE: Regex =
|
||||
Regex::new(r"(?m)^\s*([A-Za-z]:)?[^\r\n]*\.csproj(?:\s|$)").expect("valid regex");
|
||||
static ref PRINTABLE_RUN_RE: Regex = Regex::new(r"[\x20-\x7E]{5,}").expect("valid regex");
|
||||
static ref DIAGNOSTIC_CODE_RE: Regex =
|
||||
Regex::new(r"^[A-Za-z]{2,}\d{3,}$").expect("valid regex");
|
||||
static ref SOURCE_FILE_RE: Regex = Regex::new(r"(?i)([A-Za-z]:)?[/\\][^\s]+\.(cs|vb|fs)")
|
||||
.expect("valid regex");
|
||||
static ref SENSITIVE_ENV_RE: Regex = {
|
||||
let keys = SENSITIVE_ENV_VARS
|
||||
.iter()
|
||||
.map(|key| regex::escape(key))
|
||||
.collect::<Vec<_>>()
|
||||
.join("|");
|
||||
Regex::new(&format!(
|
||||
r"(?P<prefix>\b(?:{})\s*(?:=|:)\s*)(?P<value>[^\s;]+)",
|
||||
keys
|
||||
))
|
||||
.expect("valid regex")
|
||||
};
|
||||
}
|
||||
.expect("valid regex")
|
||||
});
|
||||
static PROJECT_PATH_RE: LazyLock<Regex> = LazyLock::new(|| {
|
||||
Regex::new(r"(?m)^\s*([A-Za-z]:)?[^\r\n]*\.csproj(?:\s|$)").expect("valid regex")
|
||||
});
|
||||
static PRINTABLE_RUN_RE: LazyLock<Regex> =
|
||||
LazyLock::new(|| Regex::new(r"[\x20-\x7E]{5,}").expect("valid regex"));
|
||||
static DIAGNOSTIC_CODE_RE: LazyLock<Regex> =
|
||||
LazyLock::new(|| Regex::new(r"^[A-Za-z]{2,}\d{3,}$").expect("valid regex"));
|
||||
static SOURCE_FILE_RE: LazyLock<Regex> =
|
||||
LazyLock::new(|| Regex::new(r"(?i)([A-Za-z]:)?[/\\][^\s]+\.(cs|vb|fs)").expect("valid regex"));
|
||||
static SENSITIVE_ENV_RE: LazyLock<Regex> = LazyLock::new(|| {
|
||||
let keys = SENSITIVE_ENV_VARS
|
||||
.iter()
|
||||
.map(|key| regex::escape(key))
|
||||
.collect::<Vec<_>>()
|
||||
.join("|");
|
||||
Regex::new(&format!(
|
||||
r"(?P<prefix>\b(?:{})\s*(?:=|:)\s*)(?P<value>[^\s;]+)",
|
||||
keys
|
||||
))
|
||||
.expect("valid regex")
|
||||
});
|
||||
|
||||
const SENSITIVE_ENV_VARS: &[&str] = &[
|
||||
"PATH",
|
||||
|
||||
+9
-10
@@ -8,20 +8,19 @@ use crate::core::truncate::CAP_LIST;
|
||||
use crate::core::utils::{ok_confirmation, resolved_command, truncate};
|
||||
use crate::git;
|
||||
use anyhow::Result;
|
||||
use lazy_static::lazy_static;
|
||||
use regex::Regex;
|
||||
use serde_json::Value;
|
||||
use std::process::Command;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
lazy_static! {
|
||||
static ref HTML_COMMENT_RE: Regex = Regex::new(r"(?s)<!--.*?-->").unwrap();
|
||||
static ref BADGE_LINE_RE: Regex =
|
||||
Regex::new(r"(?m)^\s*\[!\[[^\]]*\]\([^)]*\)\]\([^)]*\)\s*$").unwrap();
|
||||
static ref IMAGE_ONLY_LINE_RE: Regex = Regex::new(r"(?m)^\s*!\[[^\]]*\]\([^)]*\)\s*$").unwrap();
|
||||
static ref HORIZONTAL_RULE_RE: Regex =
|
||||
Regex::new(r"(?m)^\s*(?:---+|\*\*\*+|___+)\s*$").unwrap();
|
||||
static ref MULTI_BLANK_RE: Regex = Regex::new(r"\n{3,}").unwrap();
|
||||
}
|
||||
static HTML_COMMENT_RE: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"(?s)<!--.*?-->").unwrap());
|
||||
static BADGE_LINE_RE: LazyLock<Regex> =
|
||||
LazyLock::new(|| Regex::new(r"(?m)^\s*\[!\[[^\]]*\]\([^)]*\)\]\([^)]*\)\s*$").unwrap());
|
||||
static IMAGE_ONLY_LINE_RE: LazyLock<Regex> =
|
||||
LazyLock::new(|| Regex::new(r"(?m)^\s*!\[[^\]]*\]\([^)]*\)\s*$").unwrap());
|
||||
static HORIZONTAL_RULE_RE: LazyLock<Regex> =
|
||||
LazyLock::new(|| Regex::new(r"(?m)^\s*(?:---+|\*\*\*+|___+)\s*$").unwrap());
|
||||
static MULTI_BLANK_RE: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"\n{3,}").unwrap());
|
||||
|
||||
/// Filter markdown body to remove noise while preserving meaningful content.
|
||||
/// Removes HTML comments, badge lines, image-only lines, horizontal rules,
|
||||
|
||||
+17
-16
@@ -15,26 +15,27 @@ use crate::core::runner::{self, RunOptions};
|
||||
use crate::core::truncate::{CAP_LIST, CAP_WARNINGS};
|
||||
use crate::core::utils::{ok_confirmation, resolved_command, strip_ansi, truncate};
|
||||
use anyhow::Result;
|
||||
use lazy_static::lazy_static;
|
||||
use regex::Regex;
|
||||
use serde_json::Value;
|
||||
use std::process::Command;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
lazy_static! {
|
||||
static ref HTML_COMMENT_RE: Regex = Regex::new(r"(?s)<!--.*?-->").unwrap();
|
||||
static ref BADGE_LINE_RE: Regex =
|
||||
Regex::new(r"(?m)^\s*\[!\[[^\]]*\]\([^)]*\)\]\([^)]*\)\s*$").unwrap();
|
||||
static ref IMAGE_ONLY_LINE_RE: Regex = Regex::new(r"(?m)^\s*!\[[^\]]*\]\([^)]*\)\s*$").unwrap();
|
||||
static ref HORIZONTAL_RULE_RE: Regex =
|
||||
Regex::new(r"(?m)^\s*(?:---+|\*\*\*+|___+)\s*$").unwrap();
|
||||
static ref MULTI_BLANK_RE: Regex = Regex::new(r"\n{3,}").unwrap();
|
||||
static ref MR_URL_RE: Regex = Regex::new(r"/-/merge_requests/(\d+)").unwrap();
|
||||
/// Match GitLab CI section markers: section_start/end:timestamp:name[0K
|
||||
static ref SECTION_MARKER_RE: Regex =
|
||||
Regex::new(r"section_(?:start|end):\d+:[a-z0-9_]+(?:\x1b\[0K|\[0K)*").unwrap();
|
||||
/// Match bare bracket ANSI-like codes without ESC prefix: [0K, [0;m, [36;1m, etc.
|
||||
static ref BARE_ANSI_RE: Regex = Regex::new(r"\[[\d;]+[A-Za-z]").unwrap();
|
||||
}
|
||||
static HTML_COMMENT_RE: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"(?s)<!--.*?-->").unwrap());
|
||||
static BADGE_LINE_RE: LazyLock<Regex> =
|
||||
LazyLock::new(|| Regex::new(r"(?m)^\s*\[!\[[^\]]*\]\([^)]*\)\]\([^)]*\)\s*$").unwrap());
|
||||
static IMAGE_ONLY_LINE_RE: LazyLock<Regex> =
|
||||
LazyLock::new(|| Regex::new(r"(?m)^\s*!\[[^\]]*\]\([^)]*\)\s*$").unwrap());
|
||||
static HORIZONTAL_RULE_RE: LazyLock<Regex> =
|
||||
LazyLock::new(|| Regex::new(r"(?m)^\s*(?:---+|\*\*\*+|___+)\s*$").unwrap());
|
||||
static MULTI_BLANK_RE: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"\n{3,}").unwrap());
|
||||
static MR_URL_RE: LazyLock<Regex> =
|
||||
LazyLock::new(|| Regex::new(r"/-/merge_requests/(\d+)").unwrap());
|
||||
/// Match GitLab CI section markers: section_start/end:timestamp:name[0K
|
||||
static SECTION_MARKER_RE: LazyLock<Regex> = LazyLock::new(|| {
|
||||
Regex::new(r"section_(?:start|end):\d+:[a-z0-9_]+(?:\x1b\[0K|\[0K)*").unwrap()
|
||||
});
|
||||
/// Match bare bracket ANSI-like codes without ESC prefix: [0K, [0;m, [36;1m, etc.
|
||||
static BARE_ANSI_RE: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"\[[\d;]+[A-Za-z]").unwrap());
|
||||
|
||||
/// Filter markdown body to remove noise while preserving meaningful content.
|
||||
/// Removes HTML comments, badge lines, image-only lines, horizontal rules,
|
||||
|
||||
+12
-11
@@ -5,21 +5,22 @@ use crate::core::tracking;
|
||||
use crate::core::truncate::{reduced, CAP_LIST};
|
||||
use crate::core::utils::{ok_confirmation, resolved_command, strip_ansi, truncate};
|
||||
use anyhow::{Context, Result};
|
||||
use lazy_static::lazy_static;
|
||||
use regex::Regex;
|
||||
use std::ffi::OsString;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
lazy_static! {
|
||||
static ref EMAIL_RE: Regex =
|
||||
Regex::new(r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b").unwrap();
|
||||
static ref BRANCH_NAME_RE: Regex = Regex::new(
|
||||
r#"(?:Created|Pushed|pushed|Deleted|deleted)\s+branch\s+[`"']?([a-zA-Z0-9/_.\-+@]+)"#
|
||||
static EMAIL_RE: LazyLock<Regex> =
|
||||
LazyLock::new(|| Regex::new(r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b").unwrap());
|
||||
static BRANCH_NAME_RE: LazyLock<Regex> = LazyLock::new(|| {
|
||||
Regex::new(
|
||||
r#"(?:Created|Pushed|pushed|Deleted|deleted)\s+branch\s+[`"']?([a-zA-Z0-9/_.\-+@]+)"#,
|
||||
)
|
||||
.unwrap();
|
||||
static ref PR_LINE_RE: Regex =
|
||||
Regex::new(r"(Created|Updated)\s+pull\s+request\s+#(\d+)\s+for\s+([^\s:]+)(?::\s*(\S+))?")
|
||||
.unwrap();
|
||||
}
|
||||
.unwrap()
|
||||
});
|
||||
static PR_LINE_RE: LazyLock<Regex> = LazyLock::new(|| {
|
||||
Regex::new(r"(Created|Updated)\s+pull\s+request\s+#(\d+)\s+for\s+([^\s:]+)(?::\s*(\S+))?")
|
||||
.unwrap()
|
||||
});
|
||||
|
||||
fn run_gt_filtered(
|
||||
subcmd: &[&str],
|
||||
|
||||
+8
-14
@@ -5,6 +5,7 @@ use crate::core::truncate::CAP_WARNINGS;
|
||||
use crate::core::utils::{resolved_command, strip_ansi, tool_exists, truncate};
|
||||
use anyhow::Result;
|
||||
use regex::Regex;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
pub fn run(args: &[String], verbose: u8) -> Result<i32> {
|
||||
// Try next directly first, fallback to npx if not found
|
||||
@@ -40,17 +41,11 @@ pub fn run(args: &[String], verbose: u8) -> Result<i32> {
|
||||
|
||||
/// Filter Next.js build output - extract routes, bundles, warnings
|
||||
fn filter_next_build(output: &str) -> String {
|
||||
lazy_static::lazy_static! {
|
||||
// Route line pattern: ○ /dashboard 1.2 kB 132 kB
|
||||
static ref ROUTE_PATTERN: Regex = Regex::new(
|
||||
r"^[○●◐λ✓]\s+(/[^\s]*)\s+(\d+(?:\.\d+)?)\s*(kB|B)"
|
||||
).unwrap();
|
||||
|
||||
// Bundle size pattern
|
||||
static ref BUNDLE_PATTERN: Regex = Regex::new(
|
||||
r"^[○●◐λ✓]\s+([\w/\-\.]+)\s+(\d+(?:\.\d+)?)\s*(kB|B)\s+(\d+(?:\.\d+)?)\s*(kB|B)"
|
||||
).unwrap();
|
||||
}
|
||||
// Bundle size pattern
|
||||
static BUNDLE_PATTERN: LazyLock<Regex> = LazyLock::new(|| {
|
||||
Regex::new(r"^[○●◐λ✓]\s+([\w/\-\.]+)\s+(\d+(?:\.\d+)?)\s*(kB|B)\s+(\d+(?:\.\d+)?)\s*(kB|B)")
|
||||
.unwrap()
|
||||
});
|
||||
|
||||
let mut routes_static = 0;
|
||||
let mut routes_dynamic = 0;
|
||||
@@ -173,9 +168,8 @@ fn filter_next_build(output: &str) -> String {
|
||||
|
||||
/// Extract time from build output (e.g., "Compiled in 34.2s")
|
||||
fn extract_time(line: &str) -> Option<String> {
|
||||
lazy_static::lazy_static! {
|
||||
static ref TIME_RE: Regex = Regex::new(r"(\d+(?:\.\d+)?)\s*(s|ms)").unwrap();
|
||||
}
|
||||
static TIME_RE: LazyLock<Regex> =
|
||||
LazyLock::new(|| Regex::new(r"(\d+(?:\.\d+)?)\s*(s|ms)").unwrap());
|
||||
|
||||
TIME_RE
|
||||
.captures(line)
|
||||
|
||||
@@ -6,6 +6,7 @@ use crate::core::utils::{detect_package_manager, resolved_command, strip_ansi};
|
||||
use anyhow::{Context, Result};
|
||||
use regex::Regex;
|
||||
use serde::Deserialize;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
use crate::parser::{
|
||||
emit_degradation_warning, emit_passthrough_warning, truncate_passthrough, FormatMode,
|
||||
@@ -163,14 +164,10 @@ fn collect_test_results(
|
||||
|
||||
/// Tier 2: Extract test statistics using regex (degraded mode)
|
||||
fn extract_playwright_regex(output: &str) -> Option<TestResult> {
|
||||
lazy_static::lazy_static! {
|
||||
static ref SUMMARY_RE: Regex = Regex::new(
|
||||
r"(\d+)\s+(passed|failed|flaky|skipped)"
|
||||
).unwrap();
|
||||
static ref DURATION_RE: Regex = Regex::new(
|
||||
r"\((\d+(?:\.\d+)?)(ms|s|m)\)"
|
||||
).unwrap();
|
||||
}
|
||||
static SUMMARY_RE: LazyLock<Regex> =
|
||||
LazyLock::new(|| Regex::new(r"(\d+)\s+(passed|failed|flaky|skipped)").unwrap());
|
||||
static DURATION_RE: LazyLock<Regex> =
|
||||
LazyLock::new(|| Regex::new(r"\((\d+(?:\.\d+)?)(ms|s|m)\)").unwrap());
|
||||
|
||||
let clean_output = strip_ansi(output);
|
||||
|
||||
@@ -219,11 +216,8 @@ fn extract_playwright_regex(output: &str) -> Option<TestResult> {
|
||||
|
||||
/// Extract failures using regex
|
||||
fn extract_failures_regex(output: &str) -> Vec<TestFailure> {
|
||||
lazy_static::lazy_static! {
|
||||
static ref TEST_PATTERN: Regex = Regex::new(
|
||||
r"[×✗]\s+.*?›\s+([^›]+\.spec\.[tj]sx?)"
|
||||
).unwrap();
|
||||
}
|
||||
static TEST_PATTERN: LazyLock<Regex> =
|
||||
LazyLock::new(|| Regex::new(r"[×✗]\s+.*?›\s+([^›]+\.spec\.[tj]sx?)").unwrap());
|
||||
|
||||
let mut failures = Vec::new();
|
||||
|
||||
|
||||
@@ -4,14 +4,13 @@ use crate::core::runner;
|
||||
use crate::core::stream::{BlockHandler, BlockStreamFilter};
|
||||
use crate::core::utils::{resolved_command, tool_exists, truncate};
|
||||
use anyhow::Result;
|
||||
use lazy_static::lazy_static;
|
||||
use regex::Regex;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::sync::LazyLock;
|
||||
|
||||
lazy_static! {
|
||||
static ref TSC_ERROR: Regex =
|
||||
Regex::new(r"^(.+?)\((\d+),(\d+)\):\s+(error|warning)\s+(TS\d+):\s+(.+)$").unwrap();
|
||||
}
|
||||
static TSC_ERROR: LazyLock<Regex> = LazyLock::new(|| {
|
||||
Regex::new(r"^(.+?)\((\d+),(\d+)\):\s+(error|warning)\s+(TS\d+):\s+(.+)$").unwrap()
|
||||
});
|
||||
|
||||
pub fn run(args: &[String], verbose: u8) -> Result<i32> {
|
||||
let tsc_exists = tool_exists("tsc");
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
use anyhow::{Context, Result};
|
||||
use regex::Regex;
|
||||
use serde::Deserialize;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
use crate::core::stream::exec_capture;
|
||||
use crate::core::tracking;
|
||||
@@ -116,17 +117,10 @@ fn extract_failures_from_json(json: &VitestJsonOutput) -> Vec<TestFailure> {
|
||||
|
||||
/// Tier 2: Extract test statistics using regex (degraded mode)
|
||||
fn extract_stats_regex(output: &str) -> Option<TestResult> {
|
||||
lazy_static::lazy_static! {
|
||||
static ref TEST_FILES_RE: Regex = Regex::new(
|
||||
r"Test Files\s+(?:(\d+)\s+failed\s+\|\s+)?(\d+)\s+passed"
|
||||
).unwrap();
|
||||
static ref TESTS_RE: Regex = Regex::new(
|
||||
r"Tests\s+(?:(\d+)\s+failed\s+\|\s+)?(\d+)\s+passed"
|
||||
).unwrap();
|
||||
static ref DURATION_RE: Regex = Regex::new(
|
||||
r"Duration\s+([\d.]+)(ms|s)"
|
||||
).unwrap();
|
||||
}
|
||||
static TESTS_RE: LazyLock<Regex> =
|
||||
LazyLock::new(|| Regex::new(r"Tests\s+(?:(\d+)\s+failed\s+\|\s+)?(\d+)\s+passed").unwrap());
|
||||
static DURATION_RE: LazyLock<Regex> =
|
||||
LazyLock::new(|| Regex::new(r"Duration\s+([\d.]+)(ms|s)").unwrap());
|
||||
|
||||
let clean_output = strip_ansi(output);
|
||||
|
||||
|
||||
+63
-58
@@ -3,20 +3,20 @@ use crate::core::stream::StreamFilter;
|
||||
use crate::core::truncate::CAP_LIST;
|
||||
use crate::core::utils::resolved_command;
|
||||
use anyhow::Result;
|
||||
use lazy_static::lazy_static;
|
||||
use regex::Regex;
|
||||
use std::ffi::OsString;
|
||||
use std::process::Command;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
// ── Shared regex patterns (used across multiple filters) ─────────────────────
|
||||
|
||||
lazy_static! {
|
||||
static ref TASK_LINE: Regex = Regex::new(r"^> Task :").unwrap();
|
||||
static ref TRY_SECTION: Regex =
|
||||
Regex::new(r"^\* Try:|^> Run with --|^> Get more help at").unwrap();
|
||||
static ref BUILD_STATUS: Regex = Regex::new(r"^BUILD (SUCCESSFUL|FAILED)").unwrap();
|
||||
static ref ACTIONABLE: Regex = Regex::new(r"^\d+ actionable tasks?").unwrap();
|
||||
}
|
||||
static TASK_LINE: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"^> Task :").unwrap());
|
||||
static TRY_SECTION: LazyLock<Regex> =
|
||||
LazyLock::new(|| Regex::new(r"^\* Try:|^> Run with --|^> Get more help at").unwrap());
|
||||
static BUILD_STATUS: LazyLock<Regex> =
|
||||
LazyLock::new(|| Regex::new(r"^BUILD (SUCCESSFUL|FAILED)").unwrap());
|
||||
static ACTIONABLE: LazyLock<Regex> =
|
||||
LazyLock::new(|| Regex::new(r"^\d+ actionable tasks?").unwrap());
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
enum GradlewTask {
|
||||
@@ -177,25 +177,27 @@ pub fn run(args: &[String], verbose: u8) -> Result<i32> {
|
||||
// ── Build filter predicate ────────────────────────────────────────────────────
|
||||
|
||||
fn filter_build_line(line: &str) -> bool {
|
||||
lazy_static! {
|
||||
static ref DAEMON_LINE: Regex = Regex::new(
|
||||
static DAEMON_LINE: LazyLock<Regex> = LazyLock::new(|| {
|
||||
Regex::new(
|
||||
r"^(Starting a Gradle Daemon|Daemon will be stopped|Reusing configuration cache|Calculating task graph|> Configure project|Deprecated Gradle features|You can use|For more on this|Configuration cache entry)"
|
||||
)
|
||||
.unwrap();
|
||||
static ref PROGRESS: Regex =
|
||||
Regex::new(r"^\s*\d+%|^Downloading|^Configuring|^Resolving|^\[Incubating\]|^Wrote HTML report|^class \S+ could not|^\[android-")
|
||||
.unwrap();
|
||||
static ref ERROR_LINE: Regex = Regex::new(
|
||||
.unwrap()
|
||||
});
|
||||
static PROGRESS: LazyLock<Regex> = LazyLock::new(|| {
|
||||
Regex::new(r"^\s*\d+%|^Downloading|^Configuring|^Resolving|^\[Incubating\]|^Wrote HTML report|^class \S+ could not|^\[android-")
|
||||
.unwrap()
|
||||
});
|
||||
static ERROR_LINE: LazyLock<Regex> = LazyLock::new(|| {
|
||||
Regex::new(
|
||||
r"(?i)(^FAILURE:|^\* What went wrong:|^\* Where:|> Could not|e: |error:|^Execution failed|Lint found \d+ error)"
|
||||
)
|
||||
.unwrap();
|
||||
// Compiler + gradle warnings: kotlinc emits "w: ", javac/gradle "warning:" or "Warning:"
|
||||
static ref WARN_LINE: Regex = Regex::new(
|
||||
r"^(w: |warning:|Warning:|WARNING:)"
|
||||
)
|
||||
.unwrap();
|
||||
static ref BUILD_SCAN: Regex = Regex::new(r"gradle\.com/s/|Publishing build scan").unwrap();
|
||||
}
|
||||
.unwrap()
|
||||
});
|
||||
// Compiler + gradle warnings: kotlinc emits "w: ", javac/gradle "warning:" or "Warning:"
|
||||
static WARN_LINE: LazyLock<Regex> =
|
||||
LazyLock::new(|| Regex::new(r"^(w: |warning:|Warning:|WARNING:)").unwrap());
|
||||
static BUILD_SCAN: LazyLock<Regex> =
|
||||
LazyLock::new(|| Regex::new(r"gradle\.com/s/|Publishing build scan").unwrap());
|
||||
|
||||
// Always strip these
|
||||
if TASK_LINE.is_match(line)
|
||||
@@ -228,14 +230,16 @@ fn is_framework_frame(trimmed: &str) -> bool {
|
||||
}
|
||||
|
||||
fn filter_test(output: &str) -> String {
|
||||
lazy_static! {
|
||||
static ref FAILED_LINE: Regex = Regex::new(r"FAILED$| FAILED ").unwrap();
|
||||
static ref PASSED_SKIPPED: Regex = Regex::new(r" PASSED$| SKIPPED$").unwrap();
|
||||
static ref SUMMARY_LINE: Regex = Regex::new(
|
||||
r"\d+ tests? completed|\d+ tests? failed|There were failing tests|See the report at"
|
||||
static FAILED_LINE: LazyLock<Regex> =
|
||||
LazyLock::new(|| Regex::new(r"FAILED$| FAILED ").unwrap());
|
||||
static PASSED_SKIPPED: LazyLock<Regex> =
|
||||
LazyLock::new(|| Regex::new(r" PASSED$| SKIPPED$").unwrap());
|
||||
static SUMMARY_LINE: LazyLock<Regex> = LazyLock::new(|| {
|
||||
Regex::new(
|
||||
r"\d+ tests? completed|\d+ tests? failed|There were failing tests|See the report at",
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
.unwrap()
|
||||
});
|
||||
|
||||
if output.is_empty() {
|
||||
return String::new();
|
||||
@@ -304,14 +308,16 @@ fn filter_test(output: &str) -> String {
|
||||
// ── Connected / instrumented test filter ─────────────────────────────────────
|
||||
|
||||
fn filter_connected(output: &str) -> String {
|
||||
lazy_static! {
|
||||
static ref INSTRUMENTATION_STATUS: Regex =
|
||||
Regex::new(r"^INSTRUMENTATION_STATUS[_CODE]*:").unwrap();
|
||||
static ref INSTRUMENTATION_RESULT: Regex = Regex::new(r"^INSTRUMENTATION_RESULT:").unwrap();
|
||||
static ref INSTRUMENTATION_CODE: Regex = Regex::new(r"^INSTRUMENTATION_CODE:").unwrap();
|
||||
static ref STARTING_TESTS: Regex = Regex::new(r"^Starting \d+ tests? on ").unwrap();
|
||||
static ref INSTALLING_APK: Regex = Regex::new(r"^Installing APK").unwrap();
|
||||
}
|
||||
static INSTRUMENTATION_STATUS: LazyLock<Regex> =
|
||||
LazyLock::new(|| Regex::new(r"^INSTRUMENTATION_STATUS[_CODE]*:").unwrap());
|
||||
static INSTRUMENTATION_RESULT: LazyLock<Regex> =
|
||||
LazyLock::new(|| Regex::new(r"^INSTRUMENTATION_RESULT:").unwrap());
|
||||
static INSTRUMENTATION_CODE: LazyLock<Regex> =
|
||||
LazyLock::new(|| Regex::new(r"^INSTRUMENTATION_CODE:").unwrap());
|
||||
static STARTING_TESTS: LazyLock<Regex> =
|
||||
LazyLock::new(|| Regex::new(r"^Starting \d+ tests? on ").unwrap());
|
||||
static INSTALLING_APK: LazyLock<Regex> =
|
||||
LazyLock::new(|| Regex::new(r"^Installing APK").unwrap());
|
||||
|
||||
if output.is_empty() {
|
||||
return String::new();
|
||||
@@ -352,26 +358,25 @@ fn filter_connected(output: &str) -> String {
|
||||
// ── Lint output filter ────────────────────────────────────────────────────────
|
||||
|
||||
fn filter_lint(output: &str) -> String {
|
||||
lazy_static! {
|
||||
// Android lint errors: src/main/java/Foo.kt:45: Error: message [IssueId]
|
||||
static ref ANDROID_LINT_ERROR: Regex =
|
||||
Regex::new(r"[^:]+:\d+:.*[Ee]rror:.*\[").unwrap();
|
||||
// Android lint warnings: src/main/java/Foo.kt:89: Warning: message [IssueId]
|
||||
static ref ANDROID_LINT_WARNING: Regex =
|
||||
Regex::new(r"[^:]+:\d+:.*[Ww]arning:.*\[").unwrap();
|
||||
// ktlint: file:line:col: Lint error > message
|
||||
static ref KTLINT_VIOLATION: Regex =
|
||||
Regex::new(r"[^:]+:\d+:\d+:.*[Ll]int").unwrap();
|
||||
// detekt: file:line:col: error - message
|
||||
static ref DETEKT_VIOLATION: Regex =
|
||||
Regex::new(r"[^:]+:\d+:\d+:.*error").unwrap();
|
||||
// Summary lines
|
||||
static ref SUMMARY_LINE: Regex =
|
||||
Regex::new(r"\d+ (issues?|errors?|warnings?)").unwrap();
|
||||
// Strip report path lines (too long)
|
||||
static ref REPORT_LINE: Regex =
|
||||
Regex::new(r"Wrote (HTML|XML|text) report|file://|/build/reports/lint").unwrap();
|
||||
}
|
||||
// Android lint errors: src/main/java/Foo.kt:45: Error: message [IssueId]
|
||||
static ANDROID_LINT_ERROR: LazyLock<Regex> =
|
||||
LazyLock::new(|| Regex::new(r"[^:]+:\d+:.*[Ee]rror:.*\[").unwrap());
|
||||
// Android lint warnings: src/main/java/Foo.kt:89: Warning: message [IssueId]
|
||||
static ANDROID_LINT_WARNING: LazyLock<Regex> =
|
||||
LazyLock::new(|| Regex::new(r"[^:]+:\d+:.*[Ww]arning:.*\[").unwrap());
|
||||
// ktlint: file:line:col: Lint error > message
|
||||
static KTLINT_VIOLATION: LazyLock<Regex> =
|
||||
LazyLock::new(|| Regex::new(r"[^:]+:\d+:\d+:.*[Ll]int").unwrap());
|
||||
// detekt: file:line:col: error - message
|
||||
static DETEKT_VIOLATION: LazyLock<Regex> =
|
||||
LazyLock::new(|| Regex::new(r"[^:]+:\d+:\d+:.*error").unwrap());
|
||||
// Summary lines
|
||||
static SUMMARY_LINE: LazyLock<Regex> =
|
||||
LazyLock::new(|| Regex::new(r"\d+ (issues?|errors?|warnings?)").unwrap());
|
||||
// Strip report path lines (too long)
|
||||
static REPORT_LINE: LazyLock<Regex> = LazyLock::new(|| {
|
||||
Regex::new(r"Wrote (HTML|XML|text) report|file://|/build/reports/lint").unwrap()
|
||||
});
|
||||
|
||||
if output.is_empty() {
|
||||
return String::new();
|
||||
|
||||
+38
-32
@@ -9,12 +9,12 @@ use crate::core::runner::{self, RunOptions};
|
||||
use crate::core::truncate::CAP_WARNINGS;
|
||||
use crate::core::utils::{resolved_command, strip_ansi};
|
||||
use anyhow::Result;
|
||||
use lazy_static::lazy_static;
|
||||
use regex::Regex;
|
||||
use std::collections::HashSet;
|
||||
use std::ffi::OsString;
|
||||
use std::path::Path;
|
||||
use std::process::Command;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
/// Cap on emitted failing test-class blocks and `[ERROR] Failures:` summary
|
||||
/// entries — test-failure cap class, same binding as pytest/rspec/rake/runner.
|
||||
@@ -22,46 +22,52 @@ const MAX_MVN_FAILING_CLASSES: usize = CAP_WARNINGS;
|
||||
|
||||
// ── Shared regex patterns ────────────────────────────────────────────────────
|
||||
|
||||
lazy_static! {
|
||||
/// `[INFO] Running com.example.app.FooTest`
|
||||
static ref RUNNING: Regex = Regex::new(r"^\[INFO\] Running ").unwrap();
|
||||
/// `[INFO] Running com.example.app.FooTest`
|
||||
static RUNNING: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"^\[INFO\] Running ").unwrap());
|
||||
|
||||
/// Surefire/Failsafe per-class close line. Captures `Failures` and `Errors`.
|
||||
/// Tolerates the optional `<<< FAILURE!` / `<<< ERROR!` marker (3.5.5 emits
|
||||
/// `<<< FAILURE!` even for errors-only classes — see
|
||||
/// `mvn_test_multifail_slice_raw.txt`; `ERROR!` accepted defensively for
|
||||
/// other Surefire versions; failure detection is via the captured counts,
|
||||
/// not the marker). Separator is `-` (Surefire 2.x) or `--` (Surefire 3.x).
|
||||
/// Prefix INFO/ERROR/WARNING (3.x emits WARNING for classes with only
|
||||
/// skipped tests).
|
||||
static ref CLOSE: Regex = Regex::new(
|
||||
/// Surefire/Failsafe per-class close line. Captures `Failures` and `Errors`.
|
||||
/// Tolerates the optional `<<< FAILURE!` / `<<< ERROR!` marker (3.5.5 emits
|
||||
/// `<<< FAILURE!` even for errors-only classes — see
|
||||
/// `mvn_test_multifail_slice_raw.txt`; `ERROR!` accepted defensively for
|
||||
/// other Surefire versions; failure detection is via the captured counts,
|
||||
/// not the marker). Separator is `-` (Surefire 2.x) or `--` (Surefire 3.x).
|
||||
/// Prefix INFO/ERROR/WARNING (3.x emits WARNING for classes with only
|
||||
/// skipped tests).
|
||||
static CLOSE: LazyLock<Regex> = LazyLock::new(|| {
|
||||
Regex::new(
|
||||
r"^\[(?:INFO|ERROR|WARNING)\] Tests run: \d+, Failures: (\d+), Errors: (\d+), Skipped: \d+, Time elapsed: [^ ]+ s(?:\s+<<<\s*(?:FAILURE|ERROR)!)?\s+--?\s+in (.+)$"
|
||||
).unwrap();
|
||||
).unwrap()
|
||||
});
|
||||
|
||||
/// Final BUILD footer.
|
||||
static ref BUILD_FOOT: Regex = Regex::new(r"^\[(?:INFO|ERROR)\] BUILD (?:SUCCESS|FAILURE)$").unwrap();
|
||||
/// Final BUILD footer.
|
||||
static BUILD_FOOT: LazyLock<Regex> =
|
||||
LazyLock::new(|| Regex::new(r"^\[(?:INFO|ERROR)\] BUILD (?:SUCCESS|FAILURE)$").unwrap());
|
||||
|
||||
/// `[INFO] Results:` separator before the aggregate.
|
||||
static ref RESULTS: Regex = Regex::new(r"^\[INFO\] Results:\s*$").unwrap();
|
||||
/// `[INFO] Results:` separator before the aggregate.
|
||||
static RESULTS: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"^\[INFO\] Results:\s*$").unwrap());
|
||||
|
||||
/// Aggregate counts line (no `Time elapsed`, no ` - in `).
|
||||
static ref AGG: Regex = Regex::new(
|
||||
r"^\[(?:INFO|ERROR)\] Tests run: \d+, Failures: \d+, Errors: \d+, Skipped: \d+\s*$"
|
||||
).unwrap();
|
||||
/// Aggregate counts line (no `Time elapsed`, no ` - in `).
|
||||
static AGG: LazyLock<Regex> = LazyLock::new(|| {
|
||||
Regex::new(r"^\[(?:INFO|ERROR)\] Tests run: \d+, Failures: \d+, Errors: \d+, Skipped: \d+\s*$")
|
||||
.unwrap()
|
||||
});
|
||||
|
||||
/// Plugin banner line: `[INFO] --- plugin:goal (id) @ module ---`.
|
||||
static ref PLUGIN_BANNER: Regex = Regex::new(r"^\[INFO\] --- .* @ .* ---$").unwrap();
|
||||
/// Plugin banner line: `[INFO] --- plugin:goal (id) @ module ---`.
|
||||
static PLUGIN_BANNER: LazyLock<Regex> =
|
||||
LazyLock::new(|| Regex::new(r"^\[INFO\] --- .* @ .* ---$").unwrap());
|
||||
|
||||
/// Module banner with project name in brackets.
|
||||
static ref MODULE_BANNER: Regex = Regex::new(r"^\[INFO\] -+< .+ >-+$").unwrap();
|
||||
/// Module banner with project name in brackets.
|
||||
static MODULE_BANNER: LazyLock<Regex> =
|
||||
LazyLock::new(|| Regex::new(r"^\[INFO\] -+< .+ >-+$").unwrap());
|
||||
|
||||
/// Reactor summary header that opens the per-module pass/fail block at
|
||||
/// the end of a multi-module build.
|
||||
static ref REACTOR_SUMMARY: Regex = Regex::new(r"^\[INFO\] Reactor Summary for ").unwrap();
|
||||
/// Reactor summary header that opens the per-module pass/fail block at
|
||||
/// the end of a multi-module build.
|
||||
static REACTOR_SUMMARY: LazyLock<Regex> =
|
||||
LazyLock::new(|| Regex::new(r"^\[INFO\] Reactor Summary for ").unwrap());
|
||||
|
||||
/// Compile-error coordinate substring to strip when deduping warnings/errors.
|
||||
static ref FILE_COORD: Regex = Regex::new(r"/[^:]+\.java:\[\d+,\d+\]").unwrap();
|
||||
}
|
||||
/// Compile-error coordinate substring to strip when deduping warnings/errors.
|
||||
static FILE_COORD: LazyLock<Regex> =
|
||||
LazyLock::new(|| Regex::new(r"/[^:]+\.java:\[\d+,\d+\]").unwrap());
|
||||
|
||||
// ── Quiet-mode detection ────────────────────────────────────────────────────
|
||||
|
||||
|
||||
@@ -2,17 +2,15 @@
|
||||
|
||||
use super::test_output::filter_test_runner_output;
|
||||
use super::utils::{strip_ansi_and_controls, PhpTestRunner};
|
||||
use lazy_static::lazy_static;
|
||||
use regex::Regex;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
lazy_static! {
|
||||
static ref BOX_CHARS_RE: Regex =
|
||||
Regex::new(r"[\u{2500}-\u{257F}\u{2580}-\u{259F}\u{25A0}-\u{25FF}\u{27A0}-\u{27BF}]+")
|
||||
.unwrap();
|
||||
static ref DOTS_RE: Regex = Regex::new(r"\.{3,}").unwrap();
|
||||
static ref MULTI_SPACE_RE: Regex = Regex::new(r"[ \t]{2,}").unwrap();
|
||||
static ref MULTI_BLANK_RE: Regex = Regex::new(r"\n{3,}").unwrap();
|
||||
}
|
||||
static BOX_CHARS_RE: LazyLock<Regex> = LazyLock::new(|| {
|
||||
Regex::new(r"[\u{2500}-\u{257F}\u{2580}-\u{259F}\u{25A0}-\u{25FF}\u{27A0}-\u{27BF}]+").unwrap()
|
||||
});
|
||||
static DOTS_RE: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"\.{3,}").unwrap());
|
||||
static MULTI_SPACE_RE: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"[ \t]{2,}").unwrap());
|
||||
static MULTI_BLANK_RE: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"\n{3,}").unwrap());
|
||||
|
||||
pub fn filter_artisan_output(output: &str) -> String {
|
||||
let mut cleaned = strip_ansi_and_controls(output);
|
||||
|
||||
@@ -8,18 +8,16 @@
|
||||
use super::utils::{php_tool_command, strip_ansi_and_controls};
|
||||
use crate::core::runner;
|
||||
use anyhow::Result;
|
||||
use lazy_static::lazy_static;
|
||||
use regex::Regex;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
const MAX_FAILURES_SHOWN: usize = 10;
|
||||
const MAX_DETAIL_LINES_PER_FAILURE: usize = 2;
|
||||
|
||||
lazy_static! {
|
||||
// PHPUnit prints each failure heading as "N) Class::method". Anchor to that
|
||||
// exact shape so detail lines that merely start with a digit and contain ')'
|
||||
// (e.g. "5 of 10 assertions passed in Foo::bar()") don't split a block.
|
||||
static ref FAILURE_HEADING_RE: Regex = Regex::new(r"^\d+\) \S").unwrap();
|
||||
}
|
||||
// PHPUnit prints each failure heading as "N) Class::method". Anchor to that
|
||||
// exact shape so detail lines that merely start with a digit and contain ')'
|
||||
// (e.g. "5 of 10 assertions passed in Foo::bar()") don't split a block.
|
||||
static FAILURE_HEADING_RE: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"^\d+\) \S").unwrap());
|
||||
|
||||
pub fn run(args: &[String], verbose: u8) -> Result<i32> {
|
||||
let mut cmd = php_tool_command("phpunit");
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
use crate::core::utils::{composer_tool_paths, resolve_binary, resolved_command};
|
||||
use lazy_static::lazy_static;
|
||||
use regex::Regex;
|
||||
use std::path::Path;
|
||||
use std::process::Command;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
lazy_static! {
|
||||
static ref ANSI_RE: Regex = Regex::new(r"\x1b\[[0-9;]*[A-Za-z]").unwrap();
|
||||
static ref CONTROL_RE: Regex = Regex::new(r"[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]").unwrap();
|
||||
}
|
||||
static ANSI_RE: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"\x1b\[[0-9;]*[A-Za-z]").unwrap());
|
||||
static CONTROL_RE: LazyLock<Regex> =
|
||||
LazyLock::new(|| Regex::new(r"[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]").unwrap());
|
||||
|
||||
pub fn php_tool_command(tool: &str) -> Command {
|
||||
for local_tool in composer_tool_paths(tool) {
|
||||
|
||||
@@ -5,6 +5,7 @@ use crate::core::utils::{resolved_command, strip_ansi, tool_exists, truncate};
|
||||
use anyhow::Result;
|
||||
use regex::Regex;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
pub fn run(args: &[String], verbose: u8) -> Result<i32> {
|
||||
let mut cmd = if tool_exists("mypy") {
|
||||
@@ -51,13 +52,11 @@ struct MypyError {
|
||||
}
|
||||
|
||||
pub fn filter_mypy_output(output: &str) -> String {
|
||||
lazy_static::lazy_static! {
|
||||
// file.py:12: error: Message [error-code]
|
||||
// file.py:12:5: error: Message [error-code]
|
||||
static ref MYPY_DIAG: Regex = Regex::new(
|
||||
r"^(.+?):(\d+)(?::\d+)?: (error|warning|note): (.+?)(?:\s+\[(.+)\])?$"
|
||||
).unwrap();
|
||||
}
|
||||
// file.py:12: error: Message [error-code]
|
||||
// file.py:12:5: error: Message [error-code]
|
||||
static MYPY_DIAG: LazyLock<Regex> = LazyLock::new(|| {
|
||||
Regex::new(r"^(.+?):(\d+)(?::\d+)?: (error|warning|note): (.+?)(?:\s+\[(.+)\])?$").unwrap()
|
||||
});
|
||||
|
||||
let lines: Vec<&str> = output.lines().collect();
|
||||
let mut errors: Vec<MypyError> = Vec::new();
|
||||
|
||||
@@ -13,15 +13,17 @@ use crate::core::tracking;
|
||||
use crate::core::truncate::{CAP_INVENTORY, CAP_WARNINGS};
|
||||
use crate::core::utils::{exit_code_from_status, resolved_command, strip_ansi, truncate};
|
||||
use anyhow::{Context, Result};
|
||||
use lazy_static::lazy_static;
|
||||
use regex::Regex;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
lazy_static! {
|
||||
static ref PYTHON_FRAME_RE: Regex = Regex::new(r#"^\s*File ".*", line \d+.*$"#).unwrap();
|
||||
static ref PYTHON_EXCEPTION_RE: Regex =
|
||||
Regex::new(r"^\s*[A-Za-z_][A-Za-z0-9_.]*(?:Error|Exception):").unwrap();
|
||||
static ref JS_FRAME_RE: Regex = Regex::new(r"^\s*at .+:\d+:\d+.*$").unwrap();
|
||||
static ref ERROR_START_PATTERNS: Vec<Regex> = vec![
|
||||
static PYTHON_FRAME_RE: LazyLock<Regex> =
|
||||
LazyLock::new(|| Regex::new(r#"^\s*File ".*", line \d+.*$"#).unwrap());
|
||||
static PYTHON_EXCEPTION_RE: LazyLock<Regex> =
|
||||
LazyLock::new(|| Regex::new(r"^\s*[A-Za-z_][A-Za-z0-9_.]*(?:Error|Exception):").unwrap());
|
||||
static JS_FRAME_RE: LazyLock<Regex> =
|
||||
LazyLock::new(|| Regex::new(r"^\s*at .+:\d+:\d+.*$").unwrap());
|
||||
static ERROR_START_PATTERNS: LazyLock<Vec<Regex>> = LazyLock::new(|| {
|
||||
vec![
|
||||
Regex::new(r"(?i)\berror\b").unwrap(),
|
||||
Regex::new(r"(?i)\bfailed\b").unwrap(),
|
||||
Regex::new(r"(?i)\bfailure\b").unwrap(),
|
||||
@@ -35,8 +37,8 @@ lazy_static! {
|
||||
Regex::new(r"^\s*Caused by:").unwrap(),
|
||||
Regex::new(r"^\s*note:").unwrap(),
|
||||
Regex::new(r"^\s*help:").unwrap(),
|
||||
];
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const MAX_TRACEBACK_FRAMES: usize = CAP_WARNINGS;
|
||||
const MAX_ERROR_CONTINUATION_LINES: usize = CAP_WARNINGS;
|
||||
|
||||
@@ -8,6 +8,7 @@ use crate::core::runner;
|
||||
use crate::core::truncate::CAP_WARNINGS;
|
||||
use crate::core::utils::{ruby_exec, strip_ansi};
|
||||
use anyhow::Result;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
const MAX_RAKE_FAILURES: usize = CAP_WARNINGS;
|
||||
|
||||
@@ -163,10 +164,9 @@ fn filter_minitest_output(output: &str) -> String {
|
||||
}
|
||||
|
||||
fn is_failure_header(line: &str) -> bool {
|
||||
lazy_static::lazy_static! {
|
||||
static ref RE_FAILURE: regex::Regex =
|
||||
regex::Regex::new(r"^\d+\)\s+(Failure|Error):$").unwrap();
|
||||
}
|
||||
static RE_FAILURE: LazyLock<regex::Regex> =
|
||||
LazyLock::new(|| regex::Regex::new(r"^\d+\)\s+(Failure|Error):$").unwrap());
|
||||
|
||||
RE_FAILURE.is_match(line)
|
||||
}
|
||||
|
||||
|
||||
+13
-11
@@ -9,25 +9,27 @@ use crate::core::runner;
|
||||
use crate::core::truncate::{reduced, CAP_WARNINGS};
|
||||
use crate::core::utils::{fallback_tail, ruby_exec, truncate};
|
||||
use anyhow::Result;
|
||||
use lazy_static::lazy_static;
|
||||
use regex::Regex;
|
||||
use serde::Deserialize;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
// rspec failures carry full backtraces — show fewer than a generic warning list.
|
||||
const MAX_RSPEC_FAILURES: usize = reduced(CAP_WARNINGS, 5);
|
||||
|
||||
// ── Noise-stripping regex patterns ──────────────────────────────────────────
|
||||
|
||||
lazy_static! {
|
||||
static ref RE_SPRING: Regex = Regex::new(r"(?i)running via spring preloader").unwrap();
|
||||
static ref RE_SIMPLECOV: Regex =
|
||||
Regex::new(r"(?i)(coverage report|simplecov|coverage/|\.simplecov|All Files.*Lines)")
|
||||
.unwrap();
|
||||
static ref RE_DEPRECATION: Regex = Regex::new(r"^DEPRECATION WARNING:").unwrap();
|
||||
static ref RE_FINISHED_IN: Regex = Regex::new(r"^Finished in \d").unwrap();
|
||||
static ref RE_SCREENSHOT: Regex = Regex::new(r"saved screenshot to (.+)").unwrap();
|
||||
static ref RE_RSPEC_SUMMARY: Regex = Regex::new(r"(\d+) examples?, (\d+) failures?").unwrap();
|
||||
}
|
||||
static RE_SPRING: LazyLock<Regex> =
|
||||
LazyLock::new(|| Regex::new(r"(?i)running via spring preloader").unwrap());
|
||||
static RE_SIMPLECOV: LazyLock<Regex> = LazyLock::new(|| {
|
||||
Regex::new(r"(?i)(coverage report|simplecov|coverage/|\.simplecov|All Files.*Lines)").unwrap()
|
||||
});
|
||||
static RE_DEPRECATION: LazyLock<Regex> =
|
||||
LazyLock::new(|| Regex::new(r"^DEPRECATION WARNING:").unwrap());
|
||||
static RE_FINISHED_IN: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"^Finished in \d").unwrap());
|
||||
static RE_SCREENSHOT: LazyLock<Regex> =
|
||||
LazyLock::new(|| Regex::new(r"saved screenshot to (.+)").unwrap());
|
||||
static RE_RSPEC_SUMMARY: LazyLock<Regex> =
|
||||
LazyLock::new(|| Regex::new(r"(\d+) examples?, (\d+) failures?").unwrap());
|
||||
|
||||
// ── JSON structures matching RSpec's --format json output ───────────────────
|
||||
|
||||
|
||||
@@ -3,15 +3,15 @@
|
||||
use crate::core::stream::StreamFilter;
|
||||
use crate::core::truncate::{CAP_LIST, CAP_WARNINGS};
|
||||
use anyhow::Result;
|
||||
use lazy_static::lazy_static;
|
||||
use regex::Regex;
|
||||
use std::process::Command;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
const MAX_RUNNER_FAILURES: usize = CAP_WARNINGS;
|
||||
const MAX_RUNNER_LINES: usize = CAP_LIST;
|
||||
|
||||
lazy_static! {
|
||||
static ref ERROR_PATTERNS: Vec<Regex> = vec![
|
||||
static ERROR_PATTERNS: LazyLock<Vec<Regex>> = LazyLock::new(|| {
|
||||
vec![
|
||||
// Generic errors
|
||||
Regex::new(r"(?i)^.*error[\s:\[].*$").unwrap(),
|
||||
Regex::new(r"(?i)^.*\berr\b.*$").unwrap(),
|
||||
@@ -31,8 +31,8 @@ lazy_static! {
|
||||
Regex::new(r"^\s*at .*:\d+:\d+.*$").unwrap(),
|
||||
// Go
|
||||
Regex::new(r"^.*\.go:\d+:.*$").unwrap(),
|
||||
];
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
struct ErrorStreamFilter {
|
||||
in_error_block: bool,
|
||||
|
||||
+36
-38
@@ -1,55 +1,53 @@
|
||||
use crate::core::runner::{self, RunOptions};
|
||||
use crate::core::utils::{resolved_command, truncate};
|
||||
use anyhow::Result;
|
||||
use lazy_static::lazy_static;
|
||||
use regex::Regex;
|
||||
use std::ffi::OsString;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
lazy_static! {
|
||||
/// Matches the ScalaTest summary line:
|
||||
/// Tests: succeeded N, failed N, canceled N, ignored N, pending N
|
||||
static ref TEST_SUMMARY_RE: Regex = Regex::new(
|
||||
r"Tests: succeeded (\d+), failed (\d+), canceled (\d+), ignored (\d+), pending (\d+)"
|
||||
).unwrap();
|
||||
/// Matches the ScalaTest summary line:
|
||||
/// Tests: succeeded N, failed N, canceled N, ignored N, pending N
|
||||
static TEST_SUMMARY_RE: LazyLock<Regex> = LazyLock::new(|| {
|
||||
Regex::new(
|
||||
r"Tests: succeeded (\d+), failed (\d+), canceled (\d+), ignored (\d+), pending (\d+)",
|
||||
)
|
||||
.unwrap()
|
||||
});
|
||||
|
||||
/// Matches the munit summary line (also used by discipline-munit / ZIO Test):
|
||||
/// [info] Passed: Total N, Failed N, Errors N, Passed N
|
||||
/// [info] Failed: Total N, Failed N, Errors N, Passed N
|
||||
static ref MUNIT_SUMMARY_RE: Regex = Regex::new(
|
||||
r"^\[info\] (?:Passed|Failed): Total \d+, Failed (\d+), Errors (\d+), Passed (\d+)"
|
||||
).unwrap();
|
||||
/// Matches the munit summary line (also used by discipline-munit / ZIO Test):
|
||||
/// [info] Passed: Total N, Failed N, Errors N, Passed N
|
||||
/// [info] Failed: Total N, Failed N, Errors N, Passed N
|
||||
static MUNIT_SUMMARY_RE: LazyLock<Regex> = LazyLock::new(|| {
|
||||
Regex::new(r"^\[info\] (?:Passed|Failed): Total \d+, Failed (\d+), Errors (\d+), Passed (\d+)")
|
||||
.unwrap()
|
||||
});
|
||||
|
||||
/// Matches suite count line:
|
||||
/// Suites: completed N, aborted N
|
||||
static ref SUITE_SUMMARY_RE: Regex = Regex::new(
|
||||
r"Suites: completed (\d+), aborted (\d+)"
|
||||
).unwrap();
|
||||
/// Matches suite count line:
|
||||
/// Suites: completed N, aborted N
|
||||
static SUITE_SUMMARY_RE: LazyLock<Regex> =
|
||||
LazyLock::new(|| Regex::new(r"Suites: completed (\d+), aborted (\d+)").unwrap());
|
||||
|
||||
/// Matches the "Run completed in" timing line
|
||||
static ref RUN_TIME_RE: Regex = Regex::new(
|
||||
r"Run completed in (\d+) seconds?"
|
||||
).unwrap();
|
||||
/// Matches the "Run completed in" timing line
|
||||
static RUN_TIME_RE: LazyLock<Regex> =
|
||||
LazyLock::new(|| Regex::new(r"Run completed in (\d+) seconds?").unwrap());
|
||||
|
||||
/// Matches [info] Compiling N Scala source(s)
|
||||
static ref COMPILE_COUNT_RE: Regex = Regex::new(
|
||||
r"\[info\] Compiling (\d+) Scala source"
|
||||
).unwrap();
|
||||
/// Matches [info] Compiling N Scala source(s)
|
||||
static COMPILE_COUNT_RE: LazyLock<Regex> =
|
||||
LazyLock::new(|| Regex::new(r"\[info\] Compiling (\d+) Scala source").unwrap());
|
||||
|
||||
/// Matches [success] Total time: Ns
|
||||
static ref SUCCESS_TIME_RE: Regex = Regex::new(
|
||||
r"\[success\] Total time: (\d+) s"
|
||||
).unwrap();
|
||||
/// Matches [success] Total time: Ns
|
||||
static SUCCESS_TIME_RE: LazyLock<Regex> =
|
||||
LazyLock::new(|| Regex::new(r"\[success\] Total time: (\d+) s").unwrap());
|
||||
|
||||
/// Matches [error] lines
|
||||
static ref ERROR_RE: Regex = Regex::new(
|
||||
r"^\[error\]"
|
||||
).unwrap();
|
||||
/// Matches [error] lines
|
||||
static ERROR_RE: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"^\[error\]").unwrap());
|
||||
|
||||
/// Lines that are SBT noise (loading, resolving, downloading, etc.)
|
||||
static ref NOISE_RE: Regex = Regex::new(
|
||||
/// Lines that are SBT noise (loading, resolving, downloading, etc.)
|
||||
static NOISE_RE: LazyLock<Regex> = LazyLock::new(|| {
|
||||
Regex::new(
|
||||
r"^\[info\] (welcome to sbt|loading |set current project|Updating |Resolved |Fetching |downloading |Done )"
|
||||
).unwrap();
|
||||
}
|
||||
).unwrap()
|
||||
});
|
||||
|
||||
/// Integration test subcommand patterns (sbt configuration/task notation).
|
||||
/// These produce ScalaTest output and should use the same filtering as `sbt test`.
|
||||
|
||||
+12
-12
@@ -4,23 +4,23 @@ use crate::core::guard::never_worse;
|
||||
use crate::core::tracking;
|
||||
use crate::core::truncate::{reduced, CAP_WARNINGS};
|
||||
use anyhow::Result;
|
||||
use lazy_static::lazy_static;
|
||||
use regex::Regex;
|
||||
use std::collections::HashMap;
|
||||
use std::fs;
|
||||
use std::io::{self, BufRead};
|
||||
use std::path::Path;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
lazy_static! {
|
||||
static ref TIMESTAMP_RE: Regex =
|
||||
Regex::new(r"^\d{4}[-/]\d{2}[-/]\d{2}[T ]\d{2}:\d{2}:\d{2}[.,]?\d*\s*").unwrap();
|
||||
static ref UUID_RE: Regex =
|
||||
Regex::new(r"[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}")
|
||||
.unwrap();
|
||||
static ref HEX_RE: Regex = Regex::new(r"0x[0-9a-fA-F]+").unwrap();
|
||||
static ref NUM_RE: Regex = Regex::new(r"\b\d{4,}\b").unwrap();
|
||||
static ref PATH_RE: Regex = Regex::new(r"/[\w./\-]+").unwrap();
|
||||
}
|
||||
static TIMESTAMP_RE: LazyLock<Regex> = LazyLock::new(|| {
|
||||
Regex::new(r"^\d{4}[-/]\d{2}[-/]\d{2}[T ]\d{2}:\d{2}:\d{2}[.,]?\d*\s*").unwrap()
|
||||
});
|
||||
static UUID_RE: LazyLock<Regex> = LazyLock::new(|| {
|
||||
Regex::new(r"[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}")
|
||||
.unwrap()
|
||||
});
|
||||
static HEX_RE: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"0x[0-9a-fA-F]+").unwrap());
|
||||
static NUM_RE: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"\b\d{4,}\b").unwrap());
|
||||
static PATH_RE: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"/[\w./\-]+").unwrap());
|
||||
|
||||
/// Filter and deduplicate log output
|
||||
pub fn run_file(file: &Path, verbose: u8) -> Result<()> {
|
||||
@@ -76,7 +76,7 @@ fn analyze_logs(content: &str) -> String {
|
||||
let mut unique_errors: Vec<String> = Vec::new();
|
||||
let mut unique_warnings: Vec<String> = Vec::new();
|
||||
|
||||
// Use module-level lazy_static regexes for normalization
|
||||
// Use module-level LazyLock regexes for normalization
|
||||
|
||||
for line in content.lines() {
|
||||
let line_lower = line.to_lowercase();
|
||||
|
||||
@@ -5,19 +5,19 @@ use crate::core::runner::{self, RunOptions};
|
||||
use crate::core::truncate::{reduced, CAP_WARNINGS};
|
||||
use crate::core::utils::resolved_command;
|
||||
use anyhow::Result;
|
||||
use lazy_static::lazy_static;
|
||||
use regex::Regex;
|
||||
use std::io::IsTerminal;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
lazy_static! {
|
||||
/// Matches the date+time portion in `ls -la` output, which serves as a
|
||||
/// stable anchor regardless of owner/group column width.
|
||||
/// E.g.: " Mar 31 16:18 " or " Dec 25 2024 "
|
||||
static ref LS_DATE_RE: Regex = Regex::new(
|
||||
/// Matches the date+time portion in `ls -la` output, which serves as a
|
||||
/// stable anchor regardless of owner/group column width.
|
||||
/// E.g.: " Mar 31 16:18 " or " Dec 25 2024 "
|
||||
static LS_DATE_RE: LazyLock<Regex> = LazyLock::new(|| {
|
||||
Regex::new(
|
||||
r"\s+(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s+\d{1,2}\s+(?:\d{4}|\d{2}:\d{2})\s+"
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
.unwrap()
|
||||
});
|
||||
|
||||
pub fn run(args: &[String], verbose: u8) -> Result<i32> {
|
||||
let show_all = args
|
||||
|
||||
@@ -17,6 +17,7 @@ use regex::Regex;
|
||||
use std::collections::HashMap;
|
||||
use std::io::IsTerminal;
|
||||
use std::process::Command;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
/// Short single-char flags that consume one following token (or inline remainder)
|
||||
/// as their value. `-e` is handled separately — its value goes to `patterns`.
|
||||
@@ -734,9 +735,9 @@ pub fn run(
|
||||
/// The `bool` in the tuple is `true` for match lines (`:` separator) and
|
||||
/// `false` for context lines (`-` separator, emitted by -A/-B/-C).
|
||||
fn parse_match_line(line: &str) -> Option<(String, usize, bool, &str)> {
|
||||
lazy_static::lazy_static! {
|
||||
static ref MATCH_LINE_RE: Regex = Regex::new(r"^([^\x00]+)\x00(\d+)([:-])(.*)$").unwrap();
|
||||
}
|
||||
static MATCH_LINE_RE: LazyLock<Regex> =
|
||||
LazyLock::new(|| Regex::new(r"^([^\x00]+)\x00(\d+)([:-])(.*)$").unwrap());
|
||||
|
||||
MATCH_LINE_RE.captures(line).and_then(|caps| {
|
||||
let file = caps.get(1)?.as_str().to_string();
|
||||
let line_num: usize = caps.get(2)?.as_str().parse().ok()?;
|
||||
|
||||
+1
-1
@@ -132,4 +132,4 @@ When the truncated output is a **flat list** and the hidden items start at a pre
|
||||
**Deviating from a cap.** A filter whose items are unusually verbose (multi-line entries, backtraces) may show fewer than its class cap. Use `truncate::reduced(cap, by)` rather than a bare `cap - by`: `reduced` returns `cap - by`, except when the reduction would empty the list (`by >= cap`), in which case it drops the deviation and uses the full `cap`. This guarantees a deviation can never hide every item, and — crucially — stays a `usize`-underflow-safe `const fn` once caps become runtime-configurable (a bare `CAP_WARNINGS - 5` would panic or wrap to "no truncation" if a user set `CAP_WARNINGS` below `5`). Never deviate with a bare literal or with `*`/`/` (those scale unboundedly). Each deviation needs a one-line comment stating why.
|
||||
|
||||
## Adding New Functionality
|
||||
Place new infrastructure code here if it meets **all** of these criteria: (1) it has no dependencies on command modules or hooks, (2) it is used by two or more other modules, and (3) it provides a general-purpose utility rather than command-specific logic. Follow the existing pattern of lazy-initialized resources (`lazy_static!` for regex, on-demand config loading) to preserve the <10ms startup target. Add `#[cfg(test)] mod tests` with unit tests in the same file.
|
||||
Place new infrastructure code here if it meets **all** of these criteria: (1) it has no dependencies on command modules or hooks, (2) it is used by two or more other modules, and (3) it provides a general-purpose utility rather than command-specific logic. Follow the existing pattern of lazy-initialized resources (`LazyLock` for regex, on-demand config loading) to preserve the <10ms startup target. Add `#[cfg(test)] mod tests` with unit tests in the same file.
|
||||
|
||||
+9
-12
@@ -1,8 +1,8 @@
|
||||
//! Strips comments and boilerplate from source code to save tokens.
|
||||
|
||||
use lazy_static::lazy_static;
|
||||
use regex::Regex;
|
||||
use std::str::FromStr;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum FilterLevel {
|
||||
@@ -155,10 +155,7 @@ impl FilterStrategy for NoFilter {
|
||||
|
||||
pub struct MinimalFilter;
|
||||
|
||||
lazy_static! {
|
||||
static ref MULTIPLE_BLANK_LINES: Regex = Regex::new(r"\n{3,}").unwrap();
|
||||
static ref TRAILING_WHITESPACE: Regex = Regex::new(r"[ \t]+$").unwrap();
|
||||
}
|
||||
static MULTIPLE_BLANK_LINES: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"\n{3,}").unwrap());
|
||||
|
||||
impl FilterStrategy for MinimalFilter {
|
||||
fn filter(&self, content: &str, lang: &Language) -> String {
|
||||
@@ -232,14 +229,14 @@ impl FilterStrategy for MinimalFilter {
|
||||
|
||||
pub struct AggressiveFilter;
|
||||
|
||||
lazy_static! {
|
||||
static ref IMPORT_PATTERN: Regex =
|
||||
Regex::new(r"^(use |import |from |require\(|#include)").unwrap();
|
||||
static ref FUNC_SIGNATURE: Regex = Regex::new(
|
||||
r"^(pub\s+)?(async\s+)?(fn|def|function|func|class|struct|enum|trait|interface|type)\s+\w+"
|
||||
static IMPORT_PATTERN: LazyLock<Regex> =
|
||||
LazyLock::new(|| Regex::new(r"^(use |import |from |require\(|#include)").unwrap());
|
||||
static FUNC_SIGNATURE: LazyLock<Regex> = LazyLock::new(|| {
|
||||
Regex::new(
|
||||
r"^(pub\s+)?(async\s+)?(fn|def|function|func|class|struct|enum|trait|interface|type)\s+\w+",
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
.unwrap()
|
||||
});
|
||||
|
||||
impl FilterStrategy for AggressiveFilter {
|
||||
fn filter(&self, content: &str, lang: &Language) -> String {
|
||||
|
||||
@@ -23,10 +23,10 @@
|
||||
/// 7. max_lines — absolute line cap
|
||||
/// 8. on_empty — message if result is empty
|
||||
use super::constants::RTK_META_COMMANDS;
|
||||
use lazy_static::lazy_static;
|
||||
use regex::{Regex, RegexSet};
|
||||
use serde::Deserialize;
|
||||
use std::collections::BTreeMap;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
// Built-in filters: concatenated from src/filters/*.toml by build.rs at compile time.
|
||||
const BUILTIN_TOML: &str = include_str!(concat!(env!("OUT_DIR"), "/builtin_filters.toml"));
|
||||
@@ -395,9 +395,7 @@ fn compile_filter(name: String, def: TomlFilterDef) -> Result<CompiledFilter, St
|
||||
// Singleton (lazy-loaded, one-time cost)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
lazy_static! {
|
||||
static ref REGISTRY: TomlFilterRegistry = TomlFilterRegistry::load();
|
||||
}
|
||||
static REGISTRY: LazyLock<TomlFilterRegistry> = LazyLock::new(TomlFilterRegistry::load);
|
||||
|
||||
pub fn toml_disabled() -> bool {
|
||||
std::env::var("RTK_NO_TOML").ok().as_deref() == Some("1")
|
||||
@@ -420,9 +418,7 @@ pub fn filter_parse_error(content: &str) -> Option<String> {
|
||||
.map(|e| e.to_string())
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref MATCH_SET: RegexSet = build_match_set();
|
||||
}
|
||||
static MATCH_SET: LazyLock<RegexSet> = LazyLock::new(build_match_set);
|
||||
|
||||
pub fn command_matches_filter(command: &str) -> bool {
|
||||
MATCH_SET.is_match(command)
|
||||
@@ -810,7 +806,7 @@ mod tests {
|
||||
use super::*;
|
||||
|
||||
// Helper: build a CompiledFilter from inline TOML for tests.
|
||||
// Never touches the lazy_static registry.
|
||||
// Never touches the lazy registry.
|
||||
fn make_filters(toml: &str) -> Vec<CompiledFilter> {
|
||||
TomlFilterRegistry::parse_and_compile(toml, "test").expect("test TOML should be valid")
|
||||
}
|
||||
|
||||
+4
-3
@@ -11,6 +11,7 @@ use serde_json::Value;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
use std::sync::LazyLock;
|
||||
use std::sync::OnceLock;
|
||||
|
||||
/// Truncates a string to `max_len` characters, appending `...` if needed.
|
||||
@@ -49,9 +50,9 @@ pub fn truncate(s: &str, max_len: usize) -> String {
|
||||
/// assert_eq!(strip_ansi(colored), "Error");
|
||||
/// ```
|
||||
pub fn strip_ansi(text: &str) -> String {
|
||||
lazy_static::lazy_static! {
|
||||
static ref ANSI_RE: Regex = Regex::new(r"\x1b\[[0-9;]*[a-zA-Z]").unwrap();
|
||||
}
|
||||
static ANSI_RE: LazyLock<Regex> =
|
||||
LazyLock::new(|| Regex::new(r"\x1b\[[0-9;]*[a-zA-Z]").unwrap());
|
||||
|
||||
ANSI_RE.replace_all(text, "").to_string()
|
||||
}
|
||||
|
||||
|
||||
@@ -70,4 +70,4 @@ Add an entry to `rules.rs`. Each rule has:
|
||||
- `category`, `savings_pct` — metadata for discover reports
|
||||
- `subcmd_savings`, `subcmd_status` — per-subcommand overrides
|
||||
|
||||
No other files need to change. The registry compiles the patterns at first use via `lazy_static`.
|
||||
No other files need to change. The registry compiles the patterns at first use via `LazyLock`.
|
||||
|
||||
+44
-40
@@ -1,9 +1,9 @@
|
||||
//! Matches shell commands against known RTK rewrite rules to decide how to handle them.
|
||||
|
||||
use crate::core::utils::composer_bin_dirs;
|
||||
use lazy_static::lazy_static;
|
||||
use regex::{Regex, RegexSet};
|
||||
use std::path::Path;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
use super::lexer::{shell_split, split_on_operators, tokenize, ParsedToken, PipeKind, TokenKind};
|
||||
use super::rules::{IGNORED_EXACT, IGNORED_PREFIXES, RULES};
|
||||
@@ -48,36 +48,42 @@ pub fn category_avg_tokens(category: &str, subcmd: &str) -> usize {
|
||||
}
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref REGEX_SET: RegexSet =
|
||||
RegexSet::new(RULES.iter().map(|r| r.pattern)).expect("invalid regex patterns");
|
||||
static ref COMPILED: Vec<Regex> = RULES
|
||||
static REGEX_SET: LazyLock<RegexSet> = LazyLock::new(|| {
|
||||
RegexSet::new(RULES.iter().map(|r| r.pattern)).expect("invalid regex patterns")
|
||||
});
|
||||
static COMPILED: LazyLock<Vec<Regex>> = LazyLock::new(|| {
|
||||
RULES
|
||||
.iter()
|
||||
.map(|r| Regex::new(r.pattern).expect("invalid regex"))
|
||||
.collect();
|
||||
static ref ENV_PREFIX: Regex = {
|
||||
let double_quoted = r#""(?:[^"\\]|\\.)*""#;
|
||||
let single_quoted = r#"'(?:[^'\\]|\\.)*'"#;
|
||||
let unquoted = r#"[^\s]*"#;
|
||||
let env_value = format!("(?:{}|{}|{})", double_quoted, single_quoted, unquoted);
|
||||
let env_assign = format!(r#"[A-Z_][A-Z0-9_]*={}"#, env_value);
|
||||
Regex::new(&format!(r#"^(?:sudo\s+|env\s+|{}\s+)+"#, env_assign)).unwrap()
|
||||
};
|
||||
// Git global options that appear before the subcommand: -C <path>, -c <key=val>,
|
||||
// --git-dir <dir>, --work-tree <dir>, and flag-only options (#163)
|
||||
static ref GIT_GLOBAL_OPT: Regex =
|
||||
Regex::new(r"^(?:(?:-C\s+\S+|-c\s+\S+|--git-dir(?:=\S+|\s+\S+)|--work-tree(?:=\S+|\s+\S+)|--no-pager|--no-optional-locks|--bare|--literal-pathspecs)\s+)+").unwrap();
|
||||
// Issue #1362: each capture expects a SINGLE file argument (`\S+$`). Multi-file
|
||||
// invocations like `head -3 a b c` fail to match so the segment is passed through
|
||||
// to the native `head`/`tail` binary — which already handles multi-file with
|
||||
// `==> name <==` banners that `rtk read --max-lines` cannot reproduce.
|
||||
static ref HEAD_N: Regex = Regex::new(r"^head\s+-(\d+)\s+(\S+)$").unwrap();
|
||||
static ref HEAD_LINES: Regex = Regex::new(r"^head\s+--lines=(\d+)\s+(\S+)$").unwrap();
|
||||
static ref TAIL_N: Regex = Regex::new(r"^tail\s+-(\d+)\s+(\S+)$").unwrap();
|
||||
static ref TAIL_N_SPACE: Regex = Regex::new(r"^tail\s+-n\s+(\d+)\s+(\S+)$").unwrap();
|
||||
static ref TAIL_LINES_EQ: Regex = Regex::new(r"^tail\s+--lines=(\d+)\s+(\S+)$").unwrap();
|
||||
static ref TAIL_LINES_SPACE: Regex = Regex::new(r"^tail\s+--lines\s+(\d+)\s+(\S+)$").unwrap();
|
||||
}
|
||||
.collect()
|
||||
});
|
||||
static ENV_PREFIX: LazyLock<Regex> = LazyLock::new(|| {
|
||||
let double_quoted = r#""(?:[^"\\]|\\.)*""#;
|
||||
let single_quoted = r#"'(?:[^'\\]|\\.)*'"#;
|
||||
let unquoted = r#"[^\s]*"#;
|
||||
let env_value = format!("(?:{}|{}|{})", double_quoted, single_quoted, unquoted);
|
||||
let env_assign = format!(r#"[A-Z_][A-Z0-9_]*={}"#, env_value);
|
||||
Regex::new(&format!(r#"^(?:sudo\s+|env\s+|{}\s+)+"#, env_assign)).unwrap()
|
||||
});
|
||||
// Git global options that appear before the subcommand: -C <path>, -c <key=val>,
|
||||
// --git-dir <dir>, --work-tree <dir>, and flag-only options (#163)
|
||||
static GIT_GLOBAL_OPT: LazyLock<Regex> = LazyLock::new(|| {
|
||||
Regex::new(r"^(?:(?:-C\s+\S+|-c\s+\S+|--git-dir(?:=\S+|\s+\S+)|--work-tree(?:=\S+|\s+\S+)|--no-pager|--no-optional-locks|--bare|--literal-pathspecs)\s+)+").unwrap()
|
||||
});
|
||||
// Issue #1362: each capture expects a SINGLE file argument (`\S+$`). Multi-file
|
||||
// invocations like `head -3 a b c` fail to match so the segment is passed through
|
||||
// to the native `head`/`tail` binary — which already handles multi-file with
|
||||
// `==> name <==` banners that `rtk read --max-lines` cannot reproduce.
|
||||
static HEAD_N: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"^head\s+-(\d+)\s+(\S+)$").unwrap());
|
||||
static HEAD_LINES: LazyLock<Regex> =
|
||||
LazyLock::new(|| Regex::new(r"^head\s+--lines=(\d+)\s+(\S+)$").unwrap());
|
||||
static TAIL_N: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"^tail\s+-(\d+)\s+(\S+)$").unwrap());
|
||||
static TAIL_N_SPACE: LazyLock<Regex> =
|
||||
LazyLock::new(|| Regex::new(r"^tail\s+-n\s+(\d+)\s+(\S+)$").unwrap());
|
||||
static TAIL_LINES_EQ: LazyLock<Regex> =
|
||||
LazyLock::new(|| Regex::new(r"^tail\s+--lines=(\d+)\s+(\S+)$").unwrap());
|
||||
static TAIL_LINES_SPACE: LazyLock<Regex> =
|
||||
LazyLock::new(|| Regex::new(r"^tail\s+--lines\s+(\d+)\s+(\S+)$").unwrap());
|
||||
|
||||
const GOLANGCI_GLOBAL_OPT_WITH_VALUE: &[&str] = &[
|
||||
"-c",
|
||||
@@ -519,17 +525,15 @@ fn strip_trailing_redirects(cmd: &str) -> (&str, &str) {
|
||||
(cmd_part, redir_part)
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
/// Matches a bash line-continuation: a backslash immediately followed by
|
||||
/// `\n` or `\r\n`, *plus* any horizontal whitespace on the line before AND
|
||||
/// after the break. This is what bash already collapses to a single space
|
||||
/// before executing the command — rtk's hook matcher needs to do the same
|
||||
/// so commands authored across multiple lines still hit the rewrite rules.
|
||||
/// Consuming the trailing whitespace prevents double spaces in cases like
|
||||
/// `git diff \<NL>HEAD~1`.
|
||||
static ref LINE_CONTINUATION_RE: Regex =
|
||||
Regex::new(r"(?m)[ \t\x0B\x0C]*\\\r?\n[ \t\x0B\x0C]*").unwrap();
|
||||
}
|
||||
/// Matches a bash line-continuation: a backslash immediately followed by
|
||||
/// `\n` or `\r\n`, *plus* any horizontal whitespace on the line before AND
|
||||
/// after the break. This is what bash already collapses to a single space
|
||||
/// before executing the command — rtk's hook matcher needs to do the same
|
||||
/// so commands authored across multiple lines still hit the rewrite rules.
|
||||
/// Consuming the trailing whitespace prevents double spaces in cases like
|
||||
/// `git diff \<NL>HEAD~1`.
|
||||
static LINE_CONTINUATION_RE: LazyLock<Regex> =
|
||||
LazyLock::new(|| Regex::new(r"(?m)[ \t\x0B\x0C]*\\\r?\n[ \t\x0B\x0C]*").unwrap());
|
||||
|
||||
/// Replace every bash line continuation with a single space, mirroring what
|
||||
/// bash does before dispatching the command. Returns a borrowed `&str` when the
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ Analyzes Claude Code session history to detect recurring CLI mistakes — comman
|
||||
|
||||
## Dependencies
|
||||
|
||||
- **Uses**: `discover::provider::ClaudeProvider` (session file discovery and command extraction), `lazy_static`/`regex` (error pattern matching), `serde_json` (JSON output)
|
||||
- **Uses**: `discover::provider::ClaudeProvider` (session file discovery and command extraction), `LazyLock`/`regex` (error pattern matching), `serde_json` (JSON output)
|
||||
- **Used by**: `src/main.rs` (routes `rtk learn` command)
|
||||
|
||||
## Detection Algorithm
|
||||
|
||||
+25
-19
@@ -1,7 +1,7 @@
|
||||
//! Pattern-matches CLI errors against known correction rules.
|
||||
|
||||
use lazy_static::lazy_static;
|
||||
use regex::Regex;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum ErrorType {
|
||||
@@ -48,32 +48,38 @@ pub struct CorrectionRule {
|
||||
pub example_error: String,
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref UNKNOWN_FLAG_RE: Regex = Regex::new(
|
||||
static UNKNOWN_FLAG_RE: LazyLock<Regex> = LazyLock::new(|| {
|
||||
Regex::new(
|
||||
r"(?i)(unexpected argument|unknown (option|flag)|unrecognized (option|flag)|invalid (option|flag))"
|
||||
).unwrap();
|
||||
).unwrap()
|
||||
});
|
||||
|
||||
static ref CMD_NOT_FOUND_RE: Regex = Regex::new(
|
||||
r"(?i)(command not found|not recognized as an internal|no such file or directory.*command)"
|
||||
).unwrap();
|
||||
static CMD_NOT_FOUND_RE: LazyLock<Regex> = LazyLock::new(|| {
|
||||
Regex::new(
|
||||
r"(?i)(command not found|not recognized as an internal|no such file or directory.*command)",
|
||||
)
|
||||
.unwrap()
|
||||
});
|
||||
|
||||
static ref WRONG_PATH_RE: Regex = Regex::new(
|
||||
r"(?i)(no such file or directory|cannot find the path|file not found)"
|
||||
).unwrap();
|
||||
static WRONG_PATH_RE: LazyLock<Regex> = LazyLock::new(|| {
|
||||
Regex::new(r"(?i)(no such file or directory|cannot find the path|file not found)").unwrap()
|
||||
});
|
||||
|
||||
static ref MISSING_ARG_RE: Regex = Regex::new(
|
||||
static MISSING_ARG_RE: LazyLock<Regex> = LazyLock::new(|| {
|
||||
Regex::new(
|
||||
r"(?i)(requires a value|requires an argument|missing (required )?argument|expected.*argument)"
|
||||
).unwrap();
|
||||
).unwrap()
|
||||
});
|
||||
|
||||
static ref PERMISSION_DENIED_RE: Regex = Regex::new(
|
||||
r"(?i)(permission denied|access denied|not permitted)"
|
||||
).unwrap();
|
||||
static PERMISSION_DENIED_RE: LazyLock<Regex> =
|
||||
LazyLock::new(|| Regex::new(r"(?i)(permission denied|access denied|not permitted)").unwrap());
|
||||
|
||||
// User rejection patterns - NOT actual errors
|
||||
static ref USER_REJECTION_RE: Regex = Regex::new(
|
||||
// User rejection patterns - NOT actual errors
|
||||
static USER_REJECTION_RE: LazyLock<Regex> = LazyLock::new(|| {
|
||||
Regex::new(
|
||||
r"(?i)(user (doesn't want|declined|rejected|cancelled)|operation (cancelled|aborted) by user)"
|
||||
).unwrap();
|
||||
}
|
||||
).unwrap()
|
||||
});
|
||||
|
||||
/// Filters out user rejections - requires actual error-indicating content
|
||||
pub fn is_command_error(is_error: bool, output: &str) -> bool {
|
||||
|
||||
Reference in New Issue
Block a user