You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

311 lines
10 KiB

use clap::{self, Arg, Command};
fn add_input(cmd: Command) -> Command {
cmd.arg(
Arg::new("input")
.help("Input file")
.default_value("-")
.long_help("If not present or a single dash, standard input will be used"),
)
}
fn add_input_as_option(cmd: Command) -> Command {
cmd.arg(
Arg::new("input")
.long("input")
.default_value("-")
.long_help("If not present or a single dash, standard input will be used")
.takes_value(true),
)
}
fn add_min_max(cmd: Command) -> Command {
cmd.arg(
Arg::new("max")
.long("max")
.short('M')
.allow_hyphen_values(true)
.help("Filter out values bigger than this")
.takes_value(true),
)
.arg(
Arg::new("min")
.long("min")
.short('m')
.allow_hyphen_values(true)
.help("Filter out values smaller than this")
.takes_value(true),
)
}
fn add_regex(cmd: Command) -> Command {
const LONG_RE_ABOUT: &str = "\
A regular expression used for capturing the values to be plotted inside input
lines.
By default this will use a capture group named `value`. If not present, it will
use first capture group.
If no regex is used, the whole input lines will be matched.
Examples of regex are ' 200 \\d+ ([0-9.]+)' (where there is one anonymous capture
group) and 'a(a)? (?P<value>[0-9.]+)' (where there are two capture groups, and
the named one will be used).
";
cmd.arg(
Arg::new("regex")
.long("regex")
.short('R')
.help("Use a regex to capture input values")
.long_help(LONG_RE_ABOUT)
.takes_value(true),
)
}
fn add_non_capturing_regex(cmd: Command) -> Command {
cmd.arg(
Arg::new("regex")
.long("regex")
.short('R')
.help("Filter out lines where regex is not present")
.takes_value(true),
)
}
fn add_width(cmd: Command) -> Command {
cmd.arg(
Arg::new("width")
.long("width")
.short('w')
.help("Use this many characters as terminal width")
.default_value("110")
.takes_value(true),
)
}
fn add_intervals(cmd: Command) -> Command {
cmd.arg(
Arg::new("intervals")
.long("intervals")
.short('i')
.help("Use no more than this amount of buckets to classify data")
.default_value("20")
.takes_value(true),
)
}
fn add_precision(cmd: Command) -> Command {
cmd.arg(
Arg::new("precision")
.long("precision")
.short('p')
.help("Show that number of decimals (if omitted, 'human' units will be used)")
.default_value("-1")
.takes_value(true),
)
}
pub fn get_app() -> Command<'static> {
let mut hist = Command::new("hist")
.version(clap::crate_version!())
.about("Plot an histogram from input values");
hist = add_input(add_regex(add_width(add_min_max(add_precision(
add_intervals(hist),
)))));
let mut plot = Command::new("plot")
.version(clap::crate_version!())
.about("Plot an 2d x-y graph where y-values are averages of input values")
.arg(
Arg::new("height")
.long("height")
.short('H')
.help("Use that many `rows` for the plot")
.default_value("40")
.takes_value(true),
);
plot = add_input(add_regex(add_width(add_min_max(add_precision(plot)))));
let mut matches = Command::new("matches")
.version(clap::crate_version!())
.allow_missing_positional(true)
.about("Plot barchar with counts of occurences of matches params");
matches = add_input_as_option(add_width(matches)).arg(
Arg::new("match")
.help("Count maches for those strings")
.required(true)
.takes_value(true)
.multiple_occurrences(true),
);
let mut timehist =
Command::new("timehist")
.version(clap::crate_version!())
.about("Plot histogram with amount of matches over time")
.arg(
Arg::new("format")
.long("format")
.short('f')
.help("Use this string formatting")
.takes_value(true),
)
.arg(
Arg::new("duration")
.long("duration")
.help("Cap the time interval at that duration (example: '3h 5min')")
.takes_value(true),
)
.arg(Arg::new("early-stop").long("early-stop").help(
"If duration flag is used, assume monotonic times and stop as soon as possible",
));
timehist = add_input(add_width(add_non_capturing_regex(add_intervals(timehist))));
let mut splittimehist = Command::new("split-timehist")
.version(clap::crate_version!())
.about("Plot histogram of with amount of matches over time, split per match type")
.arg(
Arg::new("format")
.long("format")
.short('f')
.help("Use this string formatting")
.takes_value(true),
);
splittimehist = add_input_as_option(add_width(add_intervals(splittimehist))).arg(
Arg::new("match")
.help("Count maches for those strings")
.required(true)
.takes_value(true)
.multiple_occurrences(true),
);
let mut common_terms = Command::new("common-terms")
.version(clap::crate_version!())
.about("Plot histogram with most common terms in input lines");
common_terms = add_input(add_regex(add_width(common_terms))).arg(
Arg::new("lines")
.long("lines")
.short('l')
.help("Display that many lines, sorting by most frequent")
.default_value("10")
.takes_value(true),
);
Command::new("lowcharts")
.author(clap::crate_authors!())
.version(clap::crate_version!())
.about(clap::crate_description!())
.max_term_width(100)
.subcommand_required(true)
.arg(
Arg::new("color")
.short('c')
.long("color")
.help("Use colors in the output")
.possible_values(&["auto", "no", "yes"])
.default_value("auto")
.takes_value(true),
)
.arg(
Arg::new("verbose")
.short('v')
.long("verbose")
.help("Be more verbose")
.takes_value(false),
)
.subcommand(hist)
.subcommand(plot)
.subcommand(matches)
.subcommand(timehist)
.subcommand(splittimehist)
.subcommand(common_terms)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn hist_subcommand_arg_parsing() {
let arg_vec = vec!["lowcharts", "--verbose", "hist", "foo"];
let m = get_app().get_matches_from(arg_vec);
assert!(m.is_present("verbose"));
let sub_m = m.subcommand_matches("hist").unwrap();
assert_eq!("foo", sub_m.value_of("input").unwrap());
assert!(sub_m.value_of("max").is_none());
assert!(sub_m.value_of("min").is_none());
assert!(sub_m.value_of("regex").is_none());
assert_eq!("110", sub_m.value_of("width").unwrap());
assert_eq!("20", sub_m.value_of("intervals").unwrap());
}
#[test]
fn plot_subcommand_arg_parsing() {
let arg_vec = vec![
"lowcharts",
"plot",
"--max",
"1.1",
"-m",
"0.9",
"--height",
"11",
];
let m = get_app().get_matches_from(arg_vec);
assert!(!m.is_present("verbose"));
let sub_m = m.subcommand_matches("plot").unwrap();
assert_eq!("-", sub_m.value_of("input").unwrap());
assert_eq!("1.1", sub_m.value_of("max").unwrap());
assert_eq!("0.9", sub_m.value_of("min").unwrap());
assert_eq!("11", sub_m.value_of("height").unwrap());
}
#[test]
fn matches_subcommand_arg_parsing() {
let arg_vec = vec!["lowcharts", "matches", "A", "B", "C"];
let m = get_app().get_matches_from(arg_vec);
let sub_m = m.subcommand_matches("matches").unwrap();
assert_eq!("-", sub_m.value_of("input").unwrap());
assert_eq!(
vec!["A", "B", "C"],
sub_m.values_of("match").unwrap().collect::<Vec<&str>>()
);
let arg_vec = vec!["lowcharts", "matches", "A", "--input", "B", "C"];
let m = get_app().get_matches_from(arg_vec);
let sub_m = m.subcommand_matches("matches").unwrap();
assert_eq!("B", sub_m.value_of("input").unwrap());
assert_eq!(
vec!["A", "C"],
sub_m.values_of("match").unwrap().collect::<Vec<&str>>()
);
}
#[test]
fn timehist_subcommand_arg_parsing() {
let arg_vec = vec!["lowcharts", "timehist", "--regex", "foo", "some"];
let m = get_app().get_matches_from(arg_vec);
let sub_m = m.subcommand_matches("timehist").unwrap();
assert_eq!("some", sub_m.value_of("input").unwrap());
assert_eq!("foo", sub_m.value_of("regex").unwrap());
}
#[test]
fn splittimehist_subcommand_arg_parsing() {
let arg_vec = vec!["lowcharts", "split-timehist", "foo", "bar"];
let m = get_app().get_matches_from(arg_vec);
let sub_m = m.subcommand_matches("split-timehist").unwrap();
assert_eq!(
vec!["foo", "bar"],
sub_m.values_of("match").unwrap().collect::<Vec<&str>>()
);
}
#[test]
fn terms_subcommand_arg_parsing() {
let arg_vec = vec!["lowcharts", "common-terms", "--regex", "foo", "some"];
let m = get_app().get_matches_from(arg_vec);
let sub_m = m.subcommand_matches("common-terms").unwrap();
assert_eq!("some", sub_m.value_of("input").unwrap());
assert_eq!("foo", sub_m.value_of("regex").unwrap());
}
}