use clap::{Parser, ValueEnum};
use std::path::PathBuf;
#[derive(Debug, Parser, Clone)]
#[command(
name = "grafatui",
version,
about = "Grafana-like Prometheus charts in your terminal"
)]
pub(crate) struct Args {
#[arg(long)]
pub(crate) prometheus_url: Option<String>,
#[arg(long, value_name = "DURATION")]
pub(crate) range: Option<String>,
#[arg(long, value_name = "DURATION")]
pub(crate) step: Option<String>,
#[arg(long, value_name = "FILE")]
pub(crate) grafana_json: Option<PathBuf>,
#[arg(
long,
value_name = "FILE",
conflicts_with_all = [
"annotations_command",
"annotations_command_arg",
"annotations_command_timeout"
]
)]
pub(crate) annotations_file: Option<PathBuf>,
#[arg(long, value_name = "PROGRAM", conflicts_with = "annotations_file")]
pub(crate) annotations_command: Option<String>,
#[arg(
long,
value_name = "ARG",
requires = "annotations_command",
allow_hyphen_values = true
)]
pub(crate) annotations_command_arg: Vec<String>,
#[arg(long, value_name = "DURATION", requires = "annotations_command")]
pub(crate) annotations_command_timeout: Option<String>,
#[arg(long)]
pub(crate) validate: bool,
#[arg(long, requires = "validate")]
pub(crate) strict: bool,
#[arg(long, value_enum, default_value = "text", requires = "validate")]
pub(crate) format: ValidateFormat,
#[arg(long, default_value = "250")]
pub(crate) tick_rate: u64,
#[arg(long, value_name = "MS")]
pub(crate) refresh_rate: Option<u64>,
#[arg(long, value_name = "EXPR")]
pub(crate) query: Vec<String>,
#[arg(long, value_parser = parse_key_val::<String, String>, value_name = "KEY=VALUE")]
pub(crate) var: Vec<(String, String)>,
#[arg(long, value_name = "NAME")]
pub(crate) theme: Option<String>,
#[arg(long, value_name = "MARKER")]
pub(crate) threshold_marker: Option<String>,
#[arg(long, value_name = "COLOR")]
pub(crate) autogrid_color: Option<String>,
#[arg(long, value_name = "DIR")]
pub(crate) export_dir: Option<PathBuf>,
#[arg(long, value_enum, value_name = "FORMAT")]
pub(crate) export_format: Option<crate::export::ExportFormat>,
#[arg(long, value_name = "COUNT")]
pub(crate) record_max_frames: Option<usize>,
#[arg(long, value_name = "FILE")]
pub(crate) config: Option<PathBuf>,
#[command(subcommand)]
pub(crate) command: Option<Commands>,
}
#[derive(Debug, clap::Subcommand, Clone)]
pub(crate) enum Commands {
Completions {
shell: clap_complete::Shell,
},
Man,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, ValueEnum)]
pub(crate) enum ValidateFormat {
#[default]
Text,
Json,
}
pub(crate) fn parse_key_val<T, U>(
s: &str,
) -> Result<(T, U), Box<dyn std::error::Error + Send + Sync + 'static>>
where
T: std::str::FromStr,
T::Err: std::error::Error + Send + Sync + 'static,
U: std::str::FromStr,
U::Err: std::error::Error + Send + Sync + 'static,
{
let pos = s
.find('=')
.ok_or_else(|| format!("invalid KEY=value: no `=` found in `{}`", s))?;
Ok((s[..pos].parse()?, s[pos + 1..].parse()?))
}
#[cfg(test)]
mod tests {
use super::*;
use clap::Parser;
#[test]
fn test_parse_validate_with_grafana_json() {
let args = Args::parse_from(["grafatui", "--validate", "--grafana-json", "dashboard.json"]);
assert!(args.validate);
assert_eq!(args.grafana_json, Some(PathBuf::from("dashboard.json")));
}
#[test]
fn test_parse_validate_strict_and_json_format() {
let args = Args::parse_from([
"grafatui",
"--validate",
"--strict",
"--format",
"json",
"--grafana-json",
"dashboard.json",
]);
assert!(args.validate);
assert!(args.strict);
assert_eq!(args.format, crate::cli::ValidateFormat::Json);
}
#[test]
fn test_parse_annotations_file() {
let args = Args::parse_from(["grafatui", "--annotations-file", "events.jsonl"]);
assert_eq!(args.annotations_file, Some(PathBuf::from("events.jsonl")));
}
#[test]
fn parses_annotation_command_with_ordered_hyphen_arguments() {
let args = Args::try_parse_from([
"grafatui",
"--annotations-command",
"./provider",
"--annotations-command-arg=--environment",
"--annotations-command-arg=prod",
"--annotations-command-timeout",
"750ms",
])
.unwrap();
assert_eq!(args.annotations_command.as_deref(), Some("./provider"));
assert_eq!(args.annotations_command_arg, ["--environment", "prod"]);
assert_eq!(args.annotations_command_timeout.as_deref(), Some("750ms"));
}
#[test]
fn rejects_partial_or_conflicting_annotation_command_flags() {
assert!(Args::try_parse_from(["grafatui", "--annotations-command-arg=x"]).is_err());
assert!(
Args::try_parse_from([
"grafatui",
"--annotations-file",
"events.jsonl",
"--annotations-command",
"./provider"
])
.is_err()
);
}
}