mod aggregate;
mod dedup;
mod extract;
mod group;
mod lua;
mod match_output;
mod parse;
mod replace;
pub mod section;
mod skip;
mod template;
use crate::config::types::{FilterConfig, OutputBranch};
use crate::runner::CommandResult;
use self::section::SectionMap;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FilterResult {
pub output: String,
}
pub fn apply(config: &FilterConfig, result: &CommandResult, args: &[String]) -> FilterResult {
if let Some(rule) = match_output::find_matching_rule(&config.match_output, &result.combined) {
let output = match_output::render_output(&rule.output, &rule.contains, &result.combined);
return FilterResult { output };
}
let replace_buf: Vec<String>;
let raw_lines: Vec<&str> = if config.replace.is_empty() {
replace_buf = vec![];
let _ = &replace_buf; result.combined.lines().collect()
} else {
let initial: Vec<&str> = result.combined.lines().collect();
replace_buf = replace::apply_replace(&config.replace, &initial);
replace_buf.iter().map(String::as_str).collect()
};
let lines = skip::apply_skip(&config.skip, &raw_lines);
let lines = skip::apply_keep(&config.keep, &lines);
let lines = if config.dedup {
dedup::apply_dedup(&lines, config.dedup_window)
} else {
lines
};
if let Some(ref script_cfg) = config.lua_script {
let pre_filtered = lines.join("\n");
match lua::run_lua_script(script_cfg, &pre_filtered, result.exit_code, args) {
Ok(Some(output)) => return FilterResult { output },
Ok(None) => {} Err(e) => eprintln!("[tokf] lua script error: {e:#}"),
}
}
if let Some(ref parse_config) = config.parse {
let parse_result = parse::run_parse(parse_config, &lines);
let output_config = config.output.clone().unwrap_or_default();
let output = parse::render_output(&output_config, &parse_result);
return FilterResult { output };
}
let has_sections = !config.section.is_empty();
let sections = if has_sections {
let raw_lines: Vec<&str> = result.combined.lines().collect();
section::collect_sections(&config.section, &raw_lines)
} else {
SectionMap::new()
};
let branch = select_branch(config, result.exit_code);
let pre_filtered = lines.join("\n");
let output = branch.map_or_else(
|| apply_fallback(config, &pre_filtered),
|b| {
apply_branch(b, &pre_filtered, §ions, has_sections)
.unwrap_or_else(|| apply_fallback(config, &pre_filtered))
},
);
FilterResult { output }
}
const fn select_branch(config: &FilterConfig, exit_code: i32) -> Option<&OutputBranch> {
if exit_code == 0 {
config.on_success.as_ref()
} else {
config.on_failure.as_ref()
}
}
fn apply_branch(
branch: &OutputBranch,
combined: &str,
sections: &SectionMap,
has_sections: bool,
) -> Option<String> {
let vars = branch
.aggregate
.as_ref()
.map_or_else(std::collections::HashMap::new, |agg_rule| {
aggregate::run_aggregate(agg_rule, sections)
});
if let Some(ref output_tmpl) = branch.output {
if has_sections {
let any_collected = sections
.values()
.any(|s| !s.lines.is_empty() || !s.blocks.is_empty());
if !any_collected && vars.is_empty() {
return None; }
}
let mut vars = vars;
vars.insert("output".to_string(), combined.to_string());
return Some(template::render_template(output_tmpl, &vars, sections));
}
let mut lines: Vec<&str> = combined.lines().collect();
if let Some(tail) = branch.tail
&& lines.len() > tail
{
lines = lines.split_off(lines.len() - tail);
}
if let Some(head) = branch.head {
lines.truncate(head);
}
lines = skip::apply_skip(&branch.skip, &lines);
if let Some(ref rule) = branch.extract {
return Some(extract::apply_extract(rule, &lines));
}
Some(lines.join("\n"))
}
fn apply_fallback(config: &FilterConfig, combined: &str) -> String {
if let Some(ref fb) = config.fallback
&& let Some(tail) = fb.tail
{
let lines: Vec<&str> = combined.lines().collect();
if lines.len() > tail {
return lines[lines.len() - tail..].join("\n");
}
}
combined.to_string()
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests;