use crate::config::ScriptStageType;
use anyhow::Result;
use clap::{ArgMatches, Parser};
#[derive(clap::ValueEnum, Clone, Debug)]
pub enum InputFormat {
Auto,
Jsonl,
Line,
Logfmt,
Syslog,
Cef,
Csv,
Tsv,
Csvnh,
Tsvnh,
Apache,
Nginx,
Cols,
Docker,
}
#[derive(clap::ValueEnum, Clone, Debug, Default)]
pub enum OutputFormat {
Jsonl,
#[default]
Default,
Logfmt,
Csv,
Tsv,
Csvnh,
Tsvnh,
Hide,
Null,
}
#[derive(clap::ValueEnum, Clone, Debug)]
pub enum FileOrder {
None,
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>")]
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(
long = "file-order",
value_enum,
default_value = "none",
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 = "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 = "error-report-file", help_heading = "Error Handling")]
pub error_report_file: Option<String>,
#[arg(long = "strict", help_heading = "Error Handling")]
pub strict: bool,
#[arg(short = 'v', long = "verbose", help_heading = "Error Handling")]
pub verbose: bool,
#[arg(short = 'q', long = "quiet", help_heading = "Error Handling")]
pub quiet: bool,
#[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 = 'c', long = "core", help_heading = "Output Options")]
pub core: bool,
#[arg(short = 'b', long = "brief", help_heading = "Output Options")]
pub brief: 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 = "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(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 = "metrics-file", help_heading = "Metrics and Stats")]
pub metrics_file: Option<String>,
#[arg(long = "no-section-headers", help_heading = "Output Format Control")]
pub no_section_headers: bool,
#[arg(short = 'a', long = "alias", help_heading = "Configuration Options")]
pub alias: Vec<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,
}
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())
}
}