use crate::output::Color;
use clap::{ArgAction, Args, Command};
use nextest_runner::{
pager::PagedOutput,
platform::Platform,
user_config::{
EarlyUserConfig, UserConfigLocation,
elements::{PagerSetting, PaginateSetting, UiConfig},
},
write_str::WriteStr,
};
use std::io::{IsTerminal, Write};
use tracing::debug;
#[derive(Debug)]
pub struct EarlySetup {
build_target_platform: Platform,
pub early_args: EarlyArgs,
}
impl EarlySetup {
pub fn new(cli_args: &[String], app: &Command) -> Self {
let build_target_platform =
Platform::build_target().expect("nextest is built for a supported platform");
let early_args = extract_early_args(cli_args, app);
Self {
build_target_platform,
early_args,
}
}
}
#[derive(Clone, Debug, Default, Args)]
pub struct EarlyArgs {
#[arg(
long,
value_enum,
default_value_t,
hide_possible_values = true,
global = true,
value_name = "WHEN",
env = "CARGO_TERM_COLOR"
)]
pub color: Color,
#[arg(long, global = true)]
pub no_pager: bool,
#[arg(
long,
global = true,
value_name = "PATH",
env = "NEXTEST_USER_CONFIG_FILE",
help_heading = "Config options"
)]
pub user_config_file: Option<String>,
}
impl EarlyArgs {
pub fn user_config_location(&self) -> UserConfigLocation<'_> {
UserConfigLocation::from_cli_or_env(self.user_config_file.as_deref())
}
pub fn resolve_pager(&self, resolved_ui: &UiConfig) -> (PagerSetting, PaginateSetting) {
if self.no_pager {
return (resolved_ui.pager.clone(), PaginateSetting::Never);
}
(resolved_ui.pager.clone(), resolved_ui.paginate)
}
}
fn extract_early_args(args: &[String], app: &Command) -> EarlyArgs {
let early_cmd = app
.clone()
.disable_version_flag(true)
.disable_help_flag(true)
.arg(
clap::Arg::new("help")
.short('h')
.long("help")
.global(true)
.action(ArgAction::Count),
)
.ignore_errors(true);
match early_cmd.try_get_matches_from(args) {
Ok(matches) => {
let no_pager = matches
.try_get_one::<bool>("no_pager")
.ok()
.flatten()
.copied()
.unwrap_or(false);
let color = matches
.try_get_one::<Color>("color")
.ok()
.flatten()
.copied()
.unwrap_or_default();
let user_config_file = matches
.try_get_one::<String>("user_config_file")
.ok()
.flatten()
.cloned();
EarlyArgs {
color,
no_pager,
user_config_file,
}
}
Err(_) => {
EarlyArgs::default()
}
}
}
pub fn handle_clap_error(err: clap::Error, early_setup: &EarlySetup) -> i32 {
use clap::error::ErrorKind;
match err.kind() {
ErrorKind::DisplayHelp | ErrorKind::DisplayHelpOnMissingArgumentOrSubcommand => {
handle_help_output(
err,
&early_setup.early_args,
&early_setup.build_target_platform,
);
0
}
ErrorKind::DisplayVersion => {
let _ = err.print();
0
}
_ => {
let _ = err.print();
err.exit_code()
}
}
}
fn handle_help_output(err: clap::Error, early_args: &EarlyArgs, host_platform: &Platform) {
let should_colorize = early_args
.color
.should_colorize(supports_color::Stream::Stdout);
let help_text = if should_colorize {
err.render().ansi().to_string()
} else {
err.render().to_string()
};
let should_page = !early_args.no_pager && std::io::stdout().is_terminal();
if !should_page {
let _ = std::io::stdout().write_all(help_text.as_bytes());
return;
}
let early_config =
EarlyUserConfig::for_platform(host_platform, early_args.user_config_location());
if early_config.paginate == PaginateSetting::Never {
let _ = std::io::stdout().write_all(help_text.as_bytes());
return;
}
let mut paged = PagedOutput::request_pager(
&early_config.pager,
early_config.paginate,
&early_config.streampager,
);
if let Err(error) = paged.write_str(&help_text) {
debug!("failed to write to pager: {error}, falling back to stdout");
let _ = std::io::stdout().write_all(help_text.as_bytes());
return;
}
if let Err(error) = paged.write_str_flush() {
debug!("failed to flush pager: {error}");
}
paged.finalize();
}
#[cfg(test)]
mod tests {
use super::*;
use crate::dispatch::app::CargoNextestApp;
use clap::CommandFactory;
fn cmd() -> Command {
CargoNextestApp::command()
}
fn args(s: &str) -> Vec<String> {
s.split_whitespace().map(Into::into).collect()
}
#[test]
fn test_extract_early_args() {
unsafe {
std::env::remove_var("CARGO_TERM_COLOR");
}
let early = extract_early_args(&[], &cmd());
assert!(!early.no_pager);
assert!(matches!(early.color, Color::Auto));
let early = extract_early_args(&args("cargo nextest --no-pager run"), &cmd());
assert!(early.no_pager);
let early = extract_early_args(&args("cargo nextest --color=always"), &cmd());
assert!(matches!(early.color, Color::Always));
let early = extract_early_args(&args("cargo nextest --color=never"), &cmd());
assert!(matches!(early.color, Color::Never));
let early = extract_early_args(&args("cargo nextest --color always"), &cmd());
assert!(matches!(early.color, Color::Always));
let early = extract_early_args(&args("cargo nextest --no-pager --color=never run"), &cmd());
assert!(early.no_pager);
assert!(matches!(early.color, Color::Never));
let early = extract_early_args(&args("cargo nextest run -- --no-pager"), &cmd());
assert!(!early.no_pager, "--no-pager after -- should be ignored");
let early = extract_early_args(&args("cargo nextest --no-pager run -- test_name"), &cmd());
assert!(early.no_pager, "--no-pager before -- should work");
let early = extract_early_args(&args("cargo nextest run -- --color=never"), &cmd());
assert!(
matches!(early.color, Color::Auto),
"--color after -- should be ignored"
);
}
}