1pub mod config;
2pub mod error;
3pub mod output;
4pub mod parse;
5pub mod search;
6pub mod trace;
7pub mod tree;
8
9use std::path::PathBuf;
10
11pub use config::default_patterns;
13pub use error::{Result, SearchError};
14pub use output::TreeFormatter;
15pub use parse::{KeyExtractor, TranslationEntry, YamlParser};
16pub use search::{CodeReference, Match, PatternMatcher, TextSearcher};
17pub use trace::{
18 CallExtractor, CallGraphBuilder, CallNode, CallTree, FunctionDef, FunctionFinder,
19 TraceDirection,
20};
21pub use tree::{Location, NodeType, ReferenceTree, ReferenceTreeBuilder, TreeNode};
22
23#[derive(Debug, Clone)]
25pub struct TraceQuery {
26 pub function_name: String,
27 pub direction: TraceDirection,
28 pub max_depth: usize,
29 pub base_dir: Option<PathBuf>,
30}
31
32impl TraceQuery {
33 pub fn new(function_name: String, direction: TraceDirection, max_depth: usize) -> Self {
34 Self {
35 function_name,
36 direction,
37 max_depth,
38 base_dir: None,
39 }
40 }
41
42 pub fn with_base_dir(mut self, base_dir: PathBuf) -> Self {
43 self.base_dir = Some(base_dir);
44 self
45 }
46}
47
48#[derive(Debug, Clone)]
50pub struct SearchQuery {
51 pub text: String,
52 pub case_sensitive: bool,
53 pub base_dir: Option<PathBuf>,
54}
55
56impl SearchQuery {
57 pub fn new(text: String) -> Self {
58 Self {
59 text,
60 case_sensitive: false,
61 base_dir: None,
62 }
63 }
64
65 pub fn with_case_sensitive(mut self, case_sensitive: bool) -> Self {
66 self.case_sensitive = case_sensitive;
67 self
68 }
69
70 pub fn with_base_dir(mut self, base_dir: PathBuf) -> Self {
71 self.base_dir = Some(base_dir);
72 self
73 }
74}
75
76#[derive(Debug)]
78pub struct SearchResult {
79 pub query: String,
80 pub translation_entries: Vec<TranslationEntry>,
81 pub code_references: Vec<CodeReference>,
82}
83
84pub fn run_search(query: SearchQuery) -> Result<SearchResult> {
92 let base_dir = query
94 .base_dir
95 .clone()
96 .unwrap_or_else(|| std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")));
97
98 let extractor = KeyExtractor::new();
100 let translation_entries = extractor.extract(&base_dir, &query.text)?;
101
102 if translation_entries.is_empty() {
103 return Ok(SearchResult {
104 query: query.text,
105 translation_entries: vec![],
106 code_references: vec![],
107 });
108 }
109
110 let matcher = PatternMatcher::new(base_dir);
113 let mut all_code_refs = Vec::new();
114
115 for entry in &translation_entries {
116 let key_variations = generate_partial_keys(&entry.key);
118
119 for key in &key_variations {
121 let code_refs = matcher.find_usages(key)?;
122 all_code_refs.extend(code_refs);
123 }
124 }
125
126 all_code_refs.sort_by(|a, b| a.file.cmp(&b.file).then(a.line.cmp(&b.line)));
128 all_code_refs.dedup_by(|a, b| a.file == b.file && a.line == b.line);
129
130 Ok(SearchResult {
131 query: query.text,
132 translation_entries,
133 code_references: all_code_refs,
134 })
135}
136
137pub fn run_trace(query: TraceQuery) -> Result<Option<CallTree>> {
150 let base_dir = query
151 .base_dir
152 .clone()
153 .unwrap_or_else(|| std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")));
154
155 let finder = FunctionFinder::new(base_dir.clone());
156 if let Some(start_fn) = finder.find_function(&query.function_name) {
157 let extractor = CallExtractor::new(base_dir);
158 let builder = CallGraphBuilder::new(query.direction, query.max_depth, &finder, &extractor);
159 builder.build_trace(&start_fn)
160 } else {
161 Ok(None)
162 }
163}
164
165pub fn filter_translation_files(matches: &[Match]) -> Vec<PathBuf> {
167 matches
168 .iter()
169 .filter(|m| {
170 let path = m.file.to_string_lossy();
171 path.ends_with(".yml") || path.ends_with(".yaml")
172 })
173 .map(|m| m.file.clone())
174 .collect()
175}
176
177pub fn generate_partial_keys(full_key: &str) -> Vec<String> {
184 let mut keys = Vec::new();
185
186 keys.push(full_key.to_string());
188
189 let segments: Vec<&str> = full_key.split('.').collect();
190
191 if segments.len() >= 2 {
193 if segments.len() > 1 {
196 let without_first = segments[1..].join(".");
197 keys.push(without_first);
198 }
199
200 if segments.len() > 1 {
203 let without_last = segments[..segments.len() - 1].join(".");
204 keys.push(without_last);
205 }
206 }
207
208 keys
209}