use crate::config::ScriptStageType;
use anyhow::Result;
use clap::{ArgMatches, Parser};
#[derive(clap::ValueEnum, Clone, Debug)]
pub enum InputFormat {
Auto,
Json,
Line,
Logfmt,
Syslog,
Cef,
Csv,
Tsv,
Csvnh,
Tsvnh,
Combined,
}
#[derive(clap::ValueEnum, Clone, Debug, Default)]
pub enum OutputFormat {
Json,
#[default]
Default,
Logfmt,
Csv,
Tsv,
Csvnh,
Tsvnh,
None,
}
#[derive(clap::ValueEnum, Clone, Debug)]
pub enum FileOrder {
Cli,
Name,
Mtime,
}
#[derive(Parser)]
#[command(name = "kelora")]
#[command(about = "A command-line log analysis tool with embedded Rhai scripting")]
#[command(
long_about = "A command-line log analysis tool with embedded Rhai scripting\n\nMODES:\n (default) Sequential processing - best for streaming/interactive use\n --parallel Parallel processing - best for high-throughput batch analysis"
)]
#[command(author = "Dirk Loss <mail@dirk-loss.de>")]
#[command(version)]
#[command(args_override_self = true)]
pub struct Cli {
pub files: Vec<String>,
#[arg(
short = 'f',
long = "format",
value_enum,
default_value = "line",
help_heading = "Input Options"
)]
pub format: InputFormat,
#[arg(short = 'j', help_heading = "Input Options", conflicts_with = "format")]
pub json_input: bool,
#[arg(
long = "file-order",
value_enum,
default_value = "cli",
help_heading = "Input Options"
)]
pub file_order: FileOrder,
#[arg(long = "skip-lines", help_heading = "Input Options")]
pub skip_lines: Option<usize>,
#[arg(long = "ignore-lines", help_heading = "Input Options")]
pub ignore_lines: Option<String>,
#[arg(long = "ts-field", help_heading = "Input Options")]
pub ts_field: Option<String>,
#[arg(long = "ts-format", help_heading = "Input Options")]
pub ts_format: Option<String>,
#[arg(long = "input-tz", help_heading = "Input Options")]
pub input_tz: Option<String>,
#[arg(short = 'M', long = "multiline", help_heading = "Input Options")]
pub multiline: Option<String>,
#[arg(long = "extract-prefix", help_heading = "Input Options")]
pub extract_prefix: Option<String>,
#[arg(
long = "prefix-sep",
default_value = "|",
help_heading = "Input Options"
)]
pub prefix_sep: String,
#[arg(long = "begin", help_heading = "Processing Options")]
pub begin: Option<String>,
#[arg(long = "filter", help_heading = "Processing Options")]
pub filters: Vec<String>,
#[arg(short = 'e', long = "exec", help_heading = "Processing Options")]
pub execs: Vec<String>,
#[arg(short = 'E', long = "exec-file", help_heading = "Processing Options")]
pub exec_files: Vec<String>,
#[arg(long = "end", help_heading = "Processing Options")]
pub end: Option<String>,
#[arg(long = "window", help_heading = "Processing Options")]
pub window_size: Option<usize>,
#[arg(long = "strict", help_heading = "Error Handling")]
pub strict: bool,
#[arg(
long = "no-strict",
help_heading = "Error Handling",
overrides_with = "strict"
)]
pub no_strict: bool,
#[arg(short = 'v', long = "verbose", action = clap::ArgAction::Count, help_heading = "Error Handling")]
pub verbose: u8,
#[arg(short = 'q', long = "quiet", action = clap::ArgAction::Count, help_heading = "Error Handling")]
pub quiet: u8,
#[arg(
short = 'l',
long = "levels",
value_delimiter = ',',
help_heading = "Filtering Options"
)]
pub levels: Vec<String>,
#[arg(
short = 'L',
long = "exclude-levels",
value_delimiter = ',',
help_heading = "Filtering Options"
)]
pub exclude_levels: Vec<String>,
#[arg(
short = 'k',
long = "keys",
value_delimiter = ',',
help_heading = "Filtering Options"
)]
pub keys: Vec<String>,
#[arg(
short = 'K',
long = "exclude-keys",
value_delimiter = ',',
help_heading = "Filtering Options"
)]
pub exclude_keys: Vec<String>,
#[arg(long = "since", help_heading = "Filtering Options")]
pub since: Option<String>,
#[arg(long = "until", help_heading = "Filtering Options")]
pub until: Option<String>,
#[arg(long = "take", help_heading = "Filtering Options")]
pub take: Option<usize>,
#[arg(
short = 'F',
long = "output-format",
value_enum,
default_value = "default",
help_heading = "Output Options"
)]
pub output_format: OutputFormat,
#[arg(
short = 'J',
help_heading = "Output Options",
conflicts_with = "output_format"
)]
pub json_output: bool,
#[arg(short = 'c', long = "core", help_heading = "Output Options")]
pub core: bool,
#[arg(short = 'b', long = "brief", help_heading = "Output Options")]
pub brief: bool,
#[arg(long = "wrap", help_heading = "Output Options")]
pub wrap: bool,
#[arg(
long = "no-wrap",
help_heading = "Output Options",
overrides_with = "wrap"
)]
pub no_wrap: bool,
#[arg(short = 'o', long = "output-file", help_heading = "Output Options")]
pub output_file: Option<String>,
#[arg(long = "pretty-ts", help_heading = "Output Options")]
pub pretty_ts: Option<String>,
#[arg(short = 'z', help_heading = "Output Options")]
pub format_timestamps_local: bool,
#[arg(short = 'Z', help_heading = "Output Options")]
pub format_timestamps_utc: bool,
#[arg(long = "parallel", help_heading = "Performance Options")]
pub parallel: bool,
#[arg(
long = "no-parallel",
help_heading = "Performance Options",
overrides_with = "parallel"
)]
pub no_parallel: bool,
#[arg(
long = "threads",
default_value_t = 0,
help_heading = "Performance Options"
)]
pub threads: usize,
#[arg(long = "batch-size", help_heading = "Performance Options")]
pub batch_size: Option<usize>,
#[arg(
long = "batch-timeout",
default_value_t = 200,
help_heading = "Performance Options"
)]
pub batch_timeout: u64,
#[arg(long = "unordered", help_heading = "Performance Options")]
pub no_preserve_order: bool,
#[arg(long = "force-color", help_heading = "Display Options")]
pub force_color: bool,
#[arg(long = "no-color", help_heading = "Display Options")]
pub no_color: bool,
#[arg(long = "no-emoji", help_heading = "Display Options")]
pub no_emoji: bool,
#[arg(short = 's', long = "stats", help_heading = "Metrics and Stats")]
pub stats: bool,
#[arg(
long = "no-stats",
help_heading = "Metrics and Stats",
overrides_with = "stats"
)]
pub no_stats: bool,
#[arg(short = 'S', long = "stats-only", help_heading = "Metrics and Stats")]
pub stats_only: bool,
#[arg(short = 'm', long = "metrics", help_heading = "Metrics and Stats")]
pub metrics: bool,
#[arg(
long = "no-metrics",
help_heading = "Metrics and Stats",
overrides_with = "metrics"
)]
pub no_metrics: bool,
#[arg(long = "metrics-file", help_heading = "Metrics and Stats")]
pub metrics_file: Option<String>,
#[arg(short = 'a', long = "alias", help_heading = "Configuration Options")]
pub alias: Vec<String>,
#[arg(long = "config-file", help_heading = "Configuration Options")]
pub config_file: Option<String>,
#[arg(long = "show-config", help_heading = "Configuration Options")]
pub show_config: bool,
#[arg(long = "ignore-config", help_heading = "Configuration Options")]
pub ignore_config: bool,
#[arg(long = "help-rhai", help_heading = "Help Options")]
pub help_rhai: bool,
#[arg(long = "help-functions", help_heading = "Help Options")]
pub help_functions: bool,
#[arg(long = "help-time", help_heading = "Help Options")]
pub help_time: bool,
#[arg(long = "help-multiline", help_heading = "Help Options")]
pub help_multiline: bool,
}
impl Cli {
pub fn resolve_boolean_flags(&mut self) {
if self.no_stats {
self.stats = false;
}
if self.no_parallel {
self.parallel = false;
}
if self.no_metrics {
self.metrics = false;
}
if self.no_strict {
self.strict = false;
}
}
}
impl Cli {
pub fn get_ordered_script_stages(&self, matches: &ArgMatches) -> Result<Vec<ScriptStageType>> {
let mut stages_with_indices = Vec::new();
if let Some(filter_indices) = matches.indices_of("filters") {
let filter_values: Vec<&String> =
matches.get_many::<String>("filters").unwrap().collect();
for (pos, index) in filter_indices.enumerate() {
stages_with_indices
.push((index, ScriptStageType::Filter(filter_values[pos].clone())));
}
}
if let Some(exec_indices) = matches.indices_of("execs") {
let exec_values: Vec<&String> = matches.get_many::<String>("execs").unwrap().collect();
for (pos, index) in exec_indices.enumerate() {
stages_with_indices.push((index, ScriptStageType::Exec(exec_values[pos].clone())));
}
}
if let Some(exec_file_indices) = matches.indices_of("exec_files") {
let exec_file_values: Vec<&String> =
matches.get_many::<String>("exec_files").unwrap().collect();
for (pos, index) in exec_file_indices.enumerate() {
let file_path = &exec_file_values[pos];
let script_content = std::fs::read_to_string(file_path).map_err(|e| {
anyhow::anyhow!("Failed to read exec file '{}': {}", file_path, e)
})?;
stages_with_indices.push((index, ScriptStageType::Exec(script_content)));
}
}
stages_with_indices.sort_by_key(|(index, _)| *index);
Ok(stages_with_indices
.into_iter()
.map(|(_, stage)| stage)
.collect())
}
}