1use regex::Regex;
7use serde_json::Value;
8use std::cell::RefCell;
9
10thread_local! {
11 static CACHED_RE: RefCell<Option<(String, Regex)>> = const { RefCell::new(None) };
12}
13
14fn get_or_compile_regex(pattern: &str) -> Result<Regex, regex::Error> {
15 CACHED_RE.with(|cell| {
16 let mut cache = cell.borrow_mut();
17 if let Some((ref cached_pat, ref re)) = *cache {
18 if cached_pat == pattern {
19 return Ok(re.clone());
20 }
21 }
22 let re = Regex::new(pattern)?;
23 *cache = Some((pattern.to_string(), re.clone()));
24 Ok(re)
25 })
26}
27
28#[derive(Debug, Clone)]
30pub struct JsonGrepMatch {
31 pub field: String,
33 pub lines: Vec<usize>,
35 pub context_start: usize,
36 pub context_end: usize,
37 pub content: String,
38}
39
40pub fn ripgrep_json_fields(
43 json_str: &str,
44 pattern: &str,
45 context: usize,
46) -> Result<Vec<JsonGrepMatch>, String> {
47 let regex = get_or_compile_regex(pattern).map_err(|e| format!("Invalid regex: {}", e))?;
48 let data: Value = serde_json::from_str(json_str).map_err(|e| format!("Invalid JSON: {}", e))?;
49 Ok(grep_value(&data, ®ex, context, ""))
50}
51
52fn grep_value(data: &Value, regex: &Regex, context: usize, path: &str) -> Vec<JsonGrepMatch> {
53 match data {
54 Value::String(s) => {
55 let text = s.replace("\r\n", "\n");
56 let text_lines: Vec<&str> = text.split('\n').collect();
57 grep_lines_internal(&text_lines, regex, context, path)
58 }
59 Value::Object(map) => {
60 let mut matches = Vec::new();
61 for (key, val) in map {
62 let child = if path.is_empty() {
63 key.clone()
64 } else {
65 format!("{}.{}", path, key)
66 };
67 matches.extend(grep_value(val, regex, context, &child));
68 }
69 matches
70 }
71 Value::Array(arr) => {
72 let mut matches = Vec::new();
73 for (i, item) in arr.iter().enumerate() {
74 let child = format!("{}[{}]", path, i);
75 matches.extend(grep_value(item, regex, context, &child));
76 }
77 matches
78 }
79 _ => Vec::new(),
80 }
81}
82
83fn grep_lines_internal(
84 text_lines: &[&str],
85 regex: &Regex,
86 context: usize,
87 field: &str,
88) -> Vec<JsonGrepMatch> {
89 let mut raw: Vec<(usize, usize, usize)> = Vec::new();
90 for (idx, line) in text_lines.iter().enumerate() {
91 if regex.is_match(line) {
92 let start = idx.saturating_sub(context);
93 let end = (idx + context + 1).min(text_lines.len());
94 raw.push((idx + 1, start, end));
95 }
96 }
97
98 struct Group {
99 lines: Vec<usize>,
100 start: usize,
101 end: usize,
102 }
103 let mut groups: Vec<Group> = Vec::new();
104 for (hit_line, start, end) in raw {
105 if let Some(last) = groups.last_mut() {
106 if start <= last.end {
107 last.lines.push(hit_line);
108 last.end = last.end.max(end);
109 continue;
110 }
111 }
112 groups.push(Group {
113 lines: vec![hit_line],
114 start,
115 end,
116 });
117 }
118
119 groups
120 .into_iter()
121 .map(|g| {
122 let content = text_lines[g.start..g.end].join("\n");
123 JsonGrepMatch {
124 field: field.to_string(),
125 lines: g.lines,
126 context_start: g.start + 1,
127 context_end: g.end,
128 content,
129 }
130 })
131 .collect()
132}