use std::ffi::OsString;
use clap::ColorChoice;
use cli::GlobalArgs;
use crate::adopted::{home_before, style_before};
use crate::home::resolve_paths;
use crate::{cli, style};
pub(crate) fn interpreters_from_config(
shep_toml: Option<&str>,
) -> std::collections::BTreeMap<String, String> {
shep_core::config::DaemonConfig::load(shep_toml, &|_| None)
.map(|cfg| cfg.interpreters)
.unwrap_or_default()
}
pub(crate) fn style_from_config(shep_toml: Option<&str>) -> Option<style::StyleLevel> {
shep_core::config::DaemonConfig::load(shep_toml, &|_| None)
.ok()?
.style
.level
.and_then(|raw| style::StyleLevel::parse(&raw))
}
pub(crate) fn resolve_style(global: &GlobalArgs) -> (style::StyleLevel, style::StyleSource) {
resolve_style_with_env(global, std::env::var("SHEP_STYLE").ok().as_deref())
}
fn resolve_style_with_env(
global: &GlobalArgs,
shep_style: Option<&str>,
) -> (style::StyleLevel, style::StyleSource) {
let config_text = resolve_paths(global)
.ok()
.and_then(|paths| std::fs::read_to_string(paths.daemon_config).ok());
style::resolve(
global.style,
shep_style,
style_from_config(config_text.as_deref()),
)
}
pub(crate) fn help_colour_choice(argv: &[OsString], shep_style: Option<&str>) -> ColorChoice {
let rest = argv.get(1..).unwrap_or_default();
let global = GlobalArgs {
host: None,
home: home_before(rest),
format: cli::Format::Table,
quiet: false,
style: style_before(rest),
};
match resolve_style_with_env(&global, shep_style).0 {
style::StyleLevel::Bare => ColorChoice::Never,
style::StyleLevel::Full | style::StyleLevel::Plain => ColorChoice::Auto,
}
}
#[cfg_attr(windows, allow(dead_code))]
pub(crate) fn style_write_is_overridden(source: style::StyleSource) -> bool {
matches!(source, style::StyleSource::Flag | style::StyleSource::Env)
}
pub(crate) fn must_render_bare(stdout_is_terminal: bool, fmt: cli::Format) -> bool {
!stdout_is_terminal || fmt == cli::Format::Json
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn must_render_bare_is_true_exactly_for_a_piped_stdout_or_a_json_format() {
assert!(
!must_render_bare(true, cli::Format::Table),
"a real terminal asking for a table gets to render one"
);
assert!(
must_render_bare(false, cli::Format::Table),
"piped stdout must render bare even under --format table"
);
assert!(
must_render_bare(true, cli::Format::Json),
"--format json must render bare even at a real terminal"
);
assert!(must_render_bare(false, cli::Format::Json));
}
#[test]
fn style_from_config_reads_the_level_and_is_lenient_about_everything_else() {
assert_eq!(
style_from_config(Some("[style]\nlevel = \"plain\"\n")),
Some(style::StyleLevel::Plain)
);
assert_eq!(style_from_config(None), None, "no file at all");
assert_eq!(style_from_config(Some("")), None, "an empty file");
assert_eq!(
style_from_config(Some("[style")),
None,
"a file that will not parse"
);
assert_eq!(
style_from_config(Some("[daemon]\nlog_level = \"info\"\n")),
None,
"a config with no [style] table at all"
);
assert_eq!(
style_from_config(Some("[style]\nlevel = \"loud\"\n")),
None,
"a level this build does not recognise"
);
}
#[test]
fn style_from_config_trims_the_same_way_shep_style_does() {
for raw in ["full", " full ", "\tfull\n", "FULL", " FuLl "] {
assert_eq!(
style_from_config(Some(&format!("[style]\nlevel = {raw:?}\n"))),
Some(style::StyleLevel::Full),
"shep.toml's own level must accept {raw:?} exactly as \
$SHEP_STYLE would"
);
assert_eq!(
style::resolve(None, Some(raw), None),
(style::StyleLevel::Full, style::StyleSource::Env),
"$SHEP_STYLE must accept {raw:?}"
);
}
}
#[test]
fn resolve_style_reads_the_flag_and_the_real_shep_toml_it_names() {
let dir = tempfile::tempdir().unwrap();
std::fs::write(dir.path().join("shep.toml"), "[style]\nlevel = \"plain\"\n").unwrap();
let global = cli::GlobalArgs {
host: None,
home: Some(dir.path().to_path_buf()),
format: cli::Format::Table,
quiet: false,
style: None,
};
assert_eq!(
resolve_style(&global),
(style::StyleLevel::Plain, style::StyleSource::Config),
"with no flag, shep.toml's own level answers"
);
let global = cli::GlobalArgs {
style: Some(style::StyleLevel::Bare),
..global
};
assert_eq!(
resolve_style(&global),
(style::StyleLevel::Bare, style::StyleSource::Flag),
"the flag wins over the very shep.toml that set plain above"
);
}
fn argv(tokens: &[&str]) -> Vec<OsString> {
tokens.iter().map(OsString::from).collect()
}
fn empty_home() -> (tempfile::TempDir, String) {
let dir = tempfile::tempdir().unwrap();
let flag = format!("--home={}", dir.path().display());
(dir, flag)
}
#[test]
fn help_colour_is_never_for_a_separate_style_flag_of_bare() {
let (_home, home) = empty_home();
let tokens = argv(&["shep", &home, "--style", "bare", "--help"]);
assert_eq!(help_colour_choice(&tokens, None), ColorChoice::Never);
}
#[test]
fn help_colour_is_never_for_an_equals_style_flag_of_bare() {
let (_home, home) = empty_home();
let tokens = argv(&["shep", &home, "--style=bare", "--help"]);
assert_eq!(help_colour_choice(&tokens, None), ColorChoice::Never);
}
#[test]
fn help_colour_is_never_for_a_shep_style_of_bare() {
let (_home, home) = empty_home();
let tokens = argv(&["shep", &home, "--help"]);
assert_eq!(
help_colour_choice(&tokens, Some("bare")),
ColorChoice::Never
);
}
#[test]
fn help_colour_is_never_for_a_shep_toml_level_of_bare() {
let dir = tempfile::tempdir().unwrap();
std::fs::write(dir.path().join("shep.toml"), "[style]\nlevel = \"bare\"\n").unwrap();
let home = format!("--home={}", dir.path().display());
let tokens = argv(&["shep", &home, "--help"]);
assert_eq!(help_colour_choice(&tokens, None), ColorChoice::Never);
}
#[test]
fn help_colour_is_auto_for_every_other_level_and_for_no_choice() {
let (_home, home) = empty_home();
for level in ["full", "plain"] {
let tokens = argv(&["shep", &home, "--style", level, "--help"]);
assert_eq!(help_colour_choice(&tokens, None), ColorChoice::Auto);
}
let tokens = argv(&["shep", &home, "--help"]);
assert_eq!(help_colour_choice(&tokens, None), ColorChoice::Auto);
}
#[test]
fn help_colour_lets_an_unparseable_style_flag_fall_through() {
let (_home, home) = empty_home();
let tokens = argv(&["shep", &home, "--style", "loud", "--help"]);
assert_eq!(
help_colour_choice(&tokens, Some("bare")),
ColorChoice::Never,
"$SHEP_STYLE answers when --style names no level"
);
}
#[test]
fn help_colour_lets_the_flag_beat_the_environment() {
let (_home, home) = empty_home();
let tokens = argv(&["shep", &home, "--style=full", "--help"]);
assert_eq!(help_colour_choice(&tokens, Some("bare")), ColorChoice::Auto);
}
#[test]
fn style_write_is_overridden_only_by_flag_or_env() {
assert!(style_write_is_overridden(style::StyleSource::Flag));
assert!(style_write_is_overridden(style::StyleSource::Env));
assert!(!style_write_is_overridden(style::StyleSource::Config));
assert!(!style_write_is_overridden(style::StyleSource::Default));
}
}