Skip to main content

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}