use std::path::PathBuf;
use std::time::Duration;
use clap::Parser;
use clap::Subcommand;
use clap::ValueEnum;
use scrut::config::DocumentConfig;
use scrut::config::OutputStreamControl;
use scrut::config::TestCaseConfig;
use scrut::escaping::Escaper;
use scrut::parsers::parser::ParserType;
#[derive(Debug, Subcommand)]
pub(crate) enum Commands {
Create(super::create::Args),
Test(super::test::Args),
Update(super::update::Args),
}
impl Commands {
pub(crate) fn run(&self) -> anyhow::Result<()> {
match &self {
Commands::Create(cmd) => cmd.run(),
Commands::Test(cmd) => cmd.run(),
Commands::Update(cmd) => cmd.run(),
}
}
}
#[derive(Debug, Clone, ValueEnum)]
pub enum ScrutRenderer {
Auto,
Pretty,
Diff,
Json,
Yaml,
}
#[derive(Parser, Debug)]
pub(crate) struct GlobalParameters {
#[clap(long, short = 'C', global = true)]
pub(crate) cram_compat: bool,
#[clap(long, overrides_with = "no_combine_output", global = true)]
pub(crate) combine_output: bool,
#[clap(long, overrides_with = "combine_output", global = true)]
pub(crate) no_combine_output: bool,
#[clap(long, overrides_with = "no_keep_output_crlf", global = true)]
pub(crate) keep_output_crlf: bool,
#[clap(long, overrides_with = "keep_output_crlf", global = true)]
pub(crate) no_keep_output_crlf: bool,
#[clap(long, short = 'e', global = true)]
pub(crate) escaping: Option<Escaper>,
#[clap(long, short, global = true)]
pub(crate) shell: Option<PathBuf>,
#[clap(long, short, global = true)]
pub(crate) work_directory: Option<PathBuf>,
#[clap(long, conflicts_with = "work_directory", global = true)]
pub(crate) keep_temporary_directories: bool,
#[clap(long, global = true)]
pub(crate) timeout_seconds: Option<u64>,
#[clap(long, alias = "no-colour", global = true)]
pub(crate) no_color: bool,
#[cfg(feature = "logging")]
#[clap(long, global = true, value_enum, default_value_t = logging::LogLevel::default())]
pub(crate) log_level: logging::LogLevel,
}
#[cfg(feature = "logging")]
impl GlobalParameters {
pub fn init_logging(&self) -> anyhow::Result<()> {
logging::init_logging(&self.log_level, self.no_color)
}
}
#[derive(Parser, Debug, Default)]
pub(crate) struct GlobalSharedParameters {
#[clap(from_global)]
pub(crate) cram_compat: bool,
#[clap(from_global)]
pub(crate) combine_output: bool,
#[clap(from_global)]
pub(crate) no_combine_output: bool,
#[clap(from_global)]
pub(crate) keep_output_crlf: bool,
#[clap(from_global)]
pub(crate) no_keep_output_crlf: bool,
#[clap(from_global)]
pub(crate) shell: Option<PathBuf>,
#[clap(from_global)]
pub(crate) work_directory: Option<PathBuf>,
#[clap(from_global)]
pub(crate) keep_temporary_directories: bool,
#[clap(from_global)]
pub(crate) escaping: Option<Escaper>,
#[clap(from_global)]
pub(crate) timeout_seconds: Option<u64>,
#[clap(from_global)]
pub(crate) no_color: bool,
#[cfg(feature = "logging")]
#[clap(from_global)]
pub(crate) log_level: logging::LogLevel,
}
impl GlobalSharedParameters {
pub(crate) fn to_document_config(&self) -> DocumentConfig {
let mut config = DocumentConfig::empty();
if let Some(ref value) = self.shell {
config.shell = Some(value.clone())
}
if let Some(value) = self.timeout_seconds {
config.total_timeout = Some(Duration::from_secs(value))
}
config
}
pub(crate) fn to_testcase_config(&self) -> TestCaseConfig {
let mut config = TestCaseConfig::empty();
if self.no_combine_output {
config.output_stream = Some(OutputStreamControl::Stdout)
} else if self.combine_output {
config.output_stream = Some(OutputStreamControl::Combined)
}
if self.no_keep_output_crlf {
config.keep_crlf = Some(false)
} else if self.keep_output_crlf {
config.keep_crlf = Some(true)
}
config
}
pub(crate) fn output_escaping(&self, parser: Option<ParserType>) -> Escaper {
self.escaping
.to_owned()
.unwrap_or_else(|| match parser.unwrap_or(ParserType::Markdown) {
ParserType::Markdown => Escaper::Unicode,
ParserType::Cram => Escaper::Ascii,
})
}
}
#[cfg(feature = "logging")]
mod logging {
use std::env;
use std::fmt::Display;
use std::fmt::Formatter;
use std::io;
use anyhow::Context;
use clap::ValueEnum;
use tracing_subscriber::EnvFilter;
use tracing_subscriber::FmtSubscriber;
#[derive(Debug, Clone, ValueEnum, Default)]
pub enum LogLevel {
Trace,
Debug,
Info,
#[default]
Warn,
Error,
}
impl Display for LogLevel {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
match self {
Self::Trace => "trace",
Self::Debug => "debug",
Self::Info => "info",
Self::Warn => "warn",
Self::Error => "error",
}
)
}
}
pub fn init_logging(cmd_log_level: &LogLevel, no_color: bool) -> anyhow::Result<()> {
let log_level = env::var(EnvFilter::DEFAULT_ENV)
.ok()
.unwrap_or_else(|| cmd_log_level.to_string());
let filter = EnvFilter::builder()
.parse(&log_level)
.with_context(|| format!("invalid log level `{log_level}` provided"))?;
FmtSubscriber::builder()
.with_ansi(!no_color && console::colors_enabled())
.with_writer(io::stderr)
.with_env_filter(filter)
.init();
Ok(())
}
}
#[cfg(test)]
mod tests {
use scrut::config::DocumentConfig;
use scrut::config::OutputStreamControl;
use scrut::config::TestCaseConfig;
use super::GlobalSharedParameters;
#[test]
fn test_to_document_config() {
let tests = vec![
(GlobalSharedParameters::default(), DocumentConfig::empty()),
(
GlobalSharedParameters {
shell: Some("other-shell".into()),
..Default::default()
},
DocumentConfig {
shell: Some("other-shell".into()),
..DocumentConfig::empty()
},
),
];
for (params, expected) in tests {
assert_eq!(params.to_document_config(), expected);
}
}
#[test]
fn test_to_testcase_config() {
let tests = vec![
(GlobalSharedParameters::default(), TestCaseConfig::empty()),
(
GlobalSharedParameters {
no_combine_output: true,
..Default::default()
},
TestCaseConfig {
output_stream: Some(OutputStreamControl::Stdout),
..TestCaseConfig::empty()
},
),
(
GlobalSharedParameters {
combine_output: true,
..Default::default()
},
TestCaseConfig {
output_stream: Some(OutputStreamControl::Combined),
..TestCaseConfig::empty()
},
),
(
GlobalSharedParameters {
no_keep_output_crlf: true,
..Default::default()
},
TestCaseConfig {
keep_crlf: Some(false),
..TestCaseConfig::empty()
},
),
(
GlobalSharedParameters {
keep_output_crlf: true,
..Default::default()
},
TestCaseConfig {
keep_crlf: Some(true),
..TestCaseConfig::empty()
},
),
];
for (idx, (params, expected)) in tests.into_iter().enumerate() {
assert_eq!(
params.to_testcase_config(),
expected,
"test case #{}",
idx + 1
);
}
}
}