use clap::Parser;
use crate::{LogDestinationConfig, LoggingConfig};
#[allow(missing_docs)]
#[derive(Parser, Debug)]
pub struct LogArgs {
#[arg(long, value_parser=parse_destination_config)]
#[clap(verbatim_doc_comment)]
pub log: Vec<Option<LogDestinationConfig>>,
}
fn parse_destination_config(input: &str) -> Result<Option<LogDestinationConfig>, String> {
crate::parser::parse_config_definition(input).map_err(|err| err.to_string())
}
impl LogArgs {
pub fn or_default(&self, default: LoggingConfig) -> LoggingConfig {
if self.log.is_empty() {
default
} else {
let destinations = self.log.iter().filter_map(|log| log.clone()).collect();
LoggingConfig::new(destinations)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
mod parse_destination_config {
use crate::LogDestination;
use super::*;
#[test]
fn empty_string() {
assert_eq!(
parse_destination_config(""),
Err(
"Invalid empty log destination. Choose stderr, syslog, file, or none"
.to_string()
)
);
}
#[test]
fn none() {
assert_eq!(parse_destination_config("none"), Ok(None));
}
#[test]
fn stderr() {
assert_eq!(
parse_destination_config("stderr"),
Ok(Some(LogDestinationConfig {
destination: LogDestination::Stderr,
level: None
}))
);
}
#[test]
fn stderr_with_level() {
assert_eq!(
parse_destination_config("DEBUG:stderr"),
Ok(Some(LogDestinationConfig {
destination: LogDestination::Stderr,
level: Some(log::LevelFilter::Debug)
}))
);
}
}
mod or_default {
use crate::LogDestination;
use super::*;
#[test]
fn no_flags_present_chooses_default() {
let args = LogArgs { log: vec![] };
let default = vec![LogDestinationConfig {
destination: LogDestination::Stderr,
level: Some(log::LevelFilter::Info),
}];
let parsed = args.or_default(LoggingConfig::new(default.clone()));
assert_eq!(default, parsed.destinations());
}
#[test]
fn none_flag_present() {
let args = LogArgs { log: vec![None] };
let parsed = args.or_default(LoggingConfig::new(vec![LogDestinationConfig {
destination: LogDestination::Stderr,
level: Some(log::LevelFilter::Info),
}]));
assert_eq!(parsed.destinations().len(), 0);
}
#[test]
fn one_flag_present() {
let destinations = vec![LogDestinationConfig {
destination: LogDestination::Stderr,
level: Some(log::LevelFilter::Info),
}];
let args = LogArgs {
log: destinations.iter().cloned().map(Some).collect(),
};
let parsed = args.or_default(LoggingConfig::new(vec![]));
assert_eq!(destinations, parsed.destinations());
}
#[test]
fn two_flags_present() {
let destinations = vec![
LogDestinationConfig {
destination: LogDestination::Stderr,
level: Some(log::LevelFilter::Info),
},
LogDestinationConfig {
destination: LogDestination::File(std::path::PathBuf::from("/tmp/logfile")),
level: Some(log::LevelFilter::Debug),
},
];
let args = LogArgs {
log: destinations.iter().cloned().map(Some).collect(),
};
let parsed = args.or_default(LoggingConfig::new(vec![]));
assert_eq!(destinations, parsed.destinations());
}
#[test]
fn two_flags_with_one_none_present() {
let first_flag = LogDestinationConfig {
destination: LogDestination::Stderr,
level: Some(log::LevelFilter::Info),
};
let destinations = vec![Some(first_flag.clone()), None];
let args = LogArgs { log: destinations };
let parsed = args.or_default(LoggingConfig::new(vec![]));
assert_eq!(vec![first_flag], parsed.destinations());
}
}
}