tokf_common/config/types.rs
1#![allow(dead_code)]
2
3use std::collections::BTreeMap;
4
5use serde::{Deserialize, Serialize};
6
7/// A command pattern — either a single string or a list of alternatives.
8///
9/// ```toml
10/// command = "git push" # Single
11/// command = ["pnpm test", "npm test"] # Multiple: any variant
12/// command = "npm run *" # Wildcard: * matches one word
13/// ```
14#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
15#[serde(untagged)]
16pub enum CommandPattern {
17 Single(String),
18 Multiple(Vec<String>),
19}
20
21impl CommandPattern {
22 /// All pattern strings for this command.
23 pub fn patterns(&self) -> &[String] {
24 match self {
25 Self::Single(s) => std::slice::from_ref(s),
26 Self::Multiple(v) => v,
27 }
28 }
29
30 /// Canonical (first) pattern string, used for display and dedup.
31 pub fn first(&self) -> &str {
32 match self {
33 Self::Single(s) => s.as_str(),
34 Self::Multiple(v) => v.first().map_or("", String::as_str),
35 }
36 }
37}
38
39impl Default for CommandPattern {
40 fn default() -> Self {
41 Self::Single(String::new())
42 }
43}
44
45/// Top-level filter configuration, deserialized from a `.toml` file.
46// FilterConfig has many independent boolean flags that map directly to TOML keys.
47// Grouping them into enums would not improve clarity here.
48#[allow(clippy::struct_excessive_bools)]
49#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
50pub struct FilterConfig {
51 /// The command this filter applies to (e.g. "git push").
52 pub command: CommandPattern,
53
54 /// Optional override command to run instead of the matched command prefix.
55 ///
56 /// Use `{args}` to interpolate the user-supplied arguments that appear
57 /// **after** the matched pattern words. Example:
58 /// ```toml
59 /// run = "git log --oneline --no-decorate -n 20 {args}"
60 /// ```
61 ///
62 /// # Transparent global flags and `run`
63 ///
64 /// When the runtime matches a command like `git -C /repo log`, the global
65 /// flags (`-C /repo`) are skipped during pattern matching but are **not**
66 /// included in `{args}`. `{args}` only contains arguments that appear
67 /// *after* the fully-matched pattern (`log` in this case), so the override
68 /// command receives `{args} = []` and the `-C /repo` flags are silently
69 /// dropped.
70 ///
71 /// If your override command must honour such flags, include them explicitly
72 /// in the `run` value or omit `run` to let tokf reconstruct the command
73 /// from `command_args[..words_consumed]` (which *does* preserve the flags).
74 pub run: Option<String>,
75
76 /// Patterns for lines to skip (applied before section parsing).
77 #[serde(default)]
78 pub skip: Vec<String>,
79
80 /// Patterns for lines to keep (inverse of skip).
81 #[serde(default)]
82 pub keep: Vec<String>,
83
84 /// Pipeline steps to run before filtering.
85 #[serde(default)]
86 pub step: Vec<Step>,
87
88 /// Extract a single value from the output.
89 pub extract: Option<ExtractRule>,
90
91 /// Whole-output matchers checked before any line processing.
92 #[serde(default)]
93 pub match_output: Vec<MatchOutputRule>,
94
95 /// State-machine sections for collecting lines into named groups.
96 #[serde(default)]
97 pub section: Vec<Section>,
98
99 /// Branch taken when the command exits 0.
100 pub on_success: Option<OutputBranch>,
101
102 /// Branch taken when the command exits non-zero.
103 pub on_failure: Option<OutputBranch>,
104
105 /// Structured parsing rules (branch line, file grouping).
106 pub parse: Option<ParseConfig>,
107
108 /// Output formatting configuration.
109 pub output: Option<OutputConfig>,
110
111 /// Fallback behavior when no other rule matches.
112 pub fallback: Option<FallbackConfig>,
113
114 /// Per-line regex replacement steps, applied before skip/keep.
115 #[serde(default)]
116 pub replace: Vec<ReplaceRule>,
117
118 /// Collapse consecutive identical lines (or within a sliding window).
119 #[serde(default)]
120 pub dedup: bool,
121
122 /// Window size for dedup (default: consecutive only).
123 pub dedup_window: Option<usize>,
124
125 /// Strip ANSI escape sequences before skip/keep pattern matching.
126 #[serde(default)]
127 pub strip_ansi: bool,
128
129 /// Trim leading/trailing whitespace from each line before skip/keep matching.
130 #[serde(default)]
131 pub trim_lines: bool,
132
133 /// Remove all blank lines from the final output.
134 #[serde(default)]
135 pub strip_empty_lines: bool,
136
137 /// Collapse consecutive blank lines into one in the final output.
138 #[serde(default)]
139 pub collapse_empty_lines: bool,
140
141 /// Optional Lua/Luau script escape hatch.
142 #[serde(default)]
143 pub lua_script: Option<ScriptConfig>,
144
145 /// Chunk processing: split output into repeating structural blocks.
146 #[serde(default)]
147 pub chunk: Vec<ChunkConfig>,
148
149 /// Variant entries for context-aware filter delegation.
150 #[serde(default)]
151 pub variant: Vec<Variant>,
152
153 /// When true, append a hint line after the filtered output telling the reader
154 /// how to retrieve the full, unfiltered output from history.
155 ///
156 /// Example hint appended to output:
157 /// ```text
158 /// [tokf] output filtered — to see what was omitted: `tokf history show --raw 42`
159 /// ```
160 ///
161 /// This is useful for LLM consumers that need to know complete output is
162 /// available even though tokf has compressed it.
163 #[serde(default)]
164 pub show_history_hint: bool,
165}
166
167/// A pipeline step that runs a sub-command and captures its output.
168#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
169pub struct Step {
170 /// Command to run.
171 pub run: String,
172
173 /// Name to bind the output to in the template context.
174 #[serde(rename = "as")]
175 pub as_name: Option<String>,
176
177 /// Whether this step is part of a pipeline. Reserved for Phase 2+; unused by
178 /// current filter configs.
179 pub pipeline: Option<bool>,
180}
181
182/// Extracts a value from text using a regex pattern and formats it.
183#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
184pub struct ExtractRule {
185 /// Regex pattern with capture groups.
186 pub pattern: String,
187
188 /// Output template using `{1}`, `{2}`, etc. for captures.
189 pub output: String,
190}
191
192/// Matches against the full output and short-circuits with a fixed message.
193#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
194pub struct MatchOutputRule {
195 /// Substring to search for in the combined output.
196 pub contains: String,
197
198 /// Output to emit if the substring is found.
199 pub output: String,
200}
201
202/// A state-machine section that collects lines between enter/exit markers.
203#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
204pub struct Section {
205 /// Name of this section (for diagnostics/debugging).
206 pub name: Option<String>,
207
208 /// Regex that activates this section.
209 pub enter: Option<String>,
210
211 /// Regex that deactivates this section.
212 pub exit: Option<String>,
213
214 /// Regex that individual lines must match to be collected.
215 #[serde(rename = "match")]
216 pub match_pattern: Option<String>,
217
218 /// Regex to split collected content into blocks.
219 pub split_on: Option<String>,
220
221 /// Variable name for the collected lines/blocks.
222 pub collect_as: Option<String>,
223}
224
225/// Output branch for success/failure exit codes.
226#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
227pub struct OutputBranch {
228 /// Template string for the output.
229 pub output: Option<String>,
230
231 /// Aggregation rule for collected sections (singular shorthand).
232 pub aggregate: Option<AggregateRule>,
233
234 /// Multiple aggregation rules for collected sections.
235 #[serde(default)]
236 pub aggregates: Vec<AggregateRule>,
237
238 /// Number of lines to keep from the tail.
239 pub tail: Option<usize>,
240
241 /// Number of lines to keep from the head.
242 pub head: Option<usize>,
243
244 /// Patterns for lines to skip within this branch.
245 #[serde(default)]
246 pub skip: Vec<String>,
247
248 /// Extract rule applied within this branch.
249 pub extract: Option<ExtractRule>,
250}
251
252/// Aggregates values from a collected section using regex extraction.
253#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
254pub struct AggregateRule {
255 /// Name of the collected section to aggregate from.
256 pub from: String,
257
258 /// Regex pattern to extract numeric values.
259 pub pattern: String,
260
261 /// Name for the summed value.
262 pub sum: Option<String>,
263
264 /// Name for the count of matching entries.
265 pub count_as: Option<String>,
266}
267
268/// Configuration for splitting output into repeating structural blocks.
269///
270/// Chunks split output at delimiter lines, extract structured data from
271/// each block, and collect the results as a structured collection for
272/// template rendering.
273#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
274pub struct ChunkConfig {
275 /// Regex that marks the start of each chunk.
276 pub split_on: String,
277
278 /// Whether the splitting line is included in the chunk (default: true).
279 #[serde(default = "default_true")]
280 pub include_split_line: bool,
281
282 /// Variable name for the structured collection in templates.
283 pub collect_as: String,
284
285 /// Extract a named field from the split (header) line.
286 pub extract: Option<ChunkExtract>,
287
288 /// Per-chunk body line extractions (first match per rule wins).
289 #[serde(default)]
290 pub body_extract: Vec<ChunkBodyExtract>,
291
292 /// Per-chunk aggregate rules (run within each chunk's lines).
293 #[serde(default)]
294 pub aggregate: Vec<ChunkAggregateRule>,
295
296 /// Field name to group chunks by (merging numeric fields).
297 pub group_by: Option<String>,
298
299 /// When set alongside `group_by`, preserve each group's original items
300 /// as a nested collection under this name instead of discarding them.
301 pub children_as: Option<String>,
302}
303
304const fn default_true() -> bool {
305 true
306}
307
308/// Per-chunk aggregation rule. Unlike branch-level `AggregateRule`, this does
309/// not need a `from` field because it always operates on the chunk's own lines.
310#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
311pub struct ChunkAggregateRule {
312 /// Regex pattern to extract numeric values.
313 pub pattern: String,
314
315 /// Name for the summed value.
316 pub sum: Option<String>,
317
318 /// Name for the count of matching entries.
319 pub count_as: Option<String>,
320}
321
322/// Extract a named field from a line within a chunk (header or body).
323#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
324pub struct ChunkFieldExtract {
325 /// Regex pattern with a capture group.
326 pub pattern: String,
327
328 /// Variable name for the captured value.
329 #[serde(rename = "as")]
330 pub as_name: String,
331
332 /// When true, if this field is not extracted from a chunk, it inherits
333 /// the value from the most recent chunk that did extract it.
334 #[serde(default)]
335 pub carry_forward: bool,
336}
337
338/// Backward-compatible alias for header extraction.
339pub type ChunkExtract = ChunkFieldExtract;
340
341/// Backward-compatible alias for body-line extraction.
342pub type ChunkBodyExtract = ChunkFieldExtract;
343
344/// Structured parsing configuration for status-like outputs.
345#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
346pub struct ParseConfig {
347 /// Rule for extracting the branch name from the first line.
348 pub branch: Option<LineExtract>,
349
350 /// Rule for grouping file entries by status code.
351 pub group: Option<GroupConfig>,
352}
353
354/// Extracts a value from a specific line number.
355#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
356pub struct LineExtract {
357 /// 1-based line number to extract from.
358 pub line: usize,
359
360 /// Regex pattern with capture groups.
361 pub pattern: String,
362
363 /// Output template using `{1}`, `{2}`, etc. for captures.
364 pub output: String,
365}
366
367/// Groups lines by a key pattern and maps keys to human labels.
368#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
369pub struct GroupConfig {
370 /// Rule for extracting the group key from each line.
371 pub key: ExtractRule,
372
373 /// Map from raw key to human-readable label.
374 #[serde(default)]
375 pub labels: BTreeMap<String, String>,
376}
377
378/// Output formatting configuration for the final rendered result.
379#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
380pub struct OutputConfig {
381 /// Top-level output format template.
382 pub format: Option<String>,
383
384 /// Format template for each group count line.
385 pub group_counts_format: Option<String>,
386
387 /// Message to emit when there are no items to report.
388 pub empty: Option<String>,
389}
390
391/// Fallback behavior when no specific rule matches.
392#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
393pub struct FallbackConfig {
394 /// Number of lines to keep from the tail as a last resort.
395 pub tail: Option<usize>,
396}
397
398/// One per-line regex replacement step.
399///
400/// Pattern is applied to each line; on match, the line is replaced with the
401/// interpolated output template. Capture groups use `{1}`, `{2}`, … syntax.
402/// Multiple rules run in order.
403#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
404pub struct ReplaceRule {
405 pub pattern: String,
406 pub output: String,
407}
408
409/// Supported scripting languages for the `[lua_script]` escape hatch.
410#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
411#[serde(rename_all = "lowercase")]
412pub enum ScriptLang {
413 Luau,
414}
415
416/// Lua/Luau script escape hatch configuration.
417/// Exactly one of `file` or `source` must be set.
418/// `file` paths resolve relative to the current working directory.
419#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
420pub struct ScriptConfig {
421 pub lang: ScriptLang,
422 /// Path to a `.luau` file (resolved relative to CWD).
423 pub file: Option<String>,
424 /// Inline Luau source.
425 pub source: Option<String>,
426}
427
428/// Detection criteria for a filter variant.
429#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
430pub struct VariantDetect {
431 /// File paths to check in CWD (pre-execution detection).
432 #[serde(default)]
433 pub files: Vec<String>,
434 /// Regex pattern to match against command output (post-execution fallback).
435 pub output_pattern: Option<String>,
436}
437
438/// A variant entry that delegates to a specialized child filter.
439#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
440pub struct Variant {
441 /// Human-readable name for this variant.
442 pub name: String,
443 /// Detection criteria (file-based and/or output-pattern).
444 pub detect: VariantDetect,
445 /// Filter name to delegate to (relative path without `.toml`).
446 pub filter: String,
447}