pub(crate) const DEFAULT_BANNER_ART: &str = trusty_common::banner::TRUSTY_SPLASH_ART;
pub(crate) struct BannerEnv {
override_file: Option<std::ffi::OsString>,
home: Option<std::ffi::OsString>,
}
impl BannerEnv {
pub(crate) fn from_process() -> Self {
Self {
override_file: std::env::var_os("TRUSTY_MPM_BANNER_FILE"),
home: std::env::var_os("HOME"),
}
}
#[cfg(test)]
fn new(override_file: Option<&std::path::Path>, home: Option<&std::path::Path>) -> Self {
Self {
override_file: override_file.map(|p| p.as_os_str().to_os_string()),
home: home.map(|p| p.as_os_str().to_os_string()),
}
}
fn home_banner_path(&self) -> Option<std::path::PathBuf> {
self.home.as_ref().map(|h| {
std::path::PathBuf::from(h)
.join(".trusty-mpm")
.join("banner.txt")
})
}
fn banner_file_path(&self) -> Option<std::path::PathBuf> {
if let Some(path) = self.override_file.as_ref()
&& !path.is_empty()
{
return Some(std::path::PathBuf::from(path));
}
self.home_banner_path()
}
}
pub(crate) fn write_default_if_absent_in(env: &BannerEnv) {
let Some(path) = env.home_banner_path() else {
return;
};
if let Some(parent) = path.parent()
&& std::fs::create_dir_all(parent).is_err()
{
return;
}
if let Ok(mut f) = std::fs::OpenOptions::new()
.write(true)
.create_new(true)
.open(&path)
{
use std::io::Write as _;
let _ = f.write_all(DEFAULT_BANNER_ART.as_bytes());
}
}
fn refresh_if_legacy(path: &std::path::Path, trimmed_content: &str) -> bool {
let is_known_legacy = super::legacy::KNOWN_LEGACY_DEFAULTS
.iter()
.any(|legacy| legacy.trim() == trimmed_content);
if !is_known_legacy {
return false;
}
match std::fs::write(path, DEFAULT_BANNER_ART) {
Ok(()) => true,
Err(e) => {
tracing::debug!(
"failed to refresh legacy banner seed {}: {e}",
path.display()
);
false
}
}
}
pub(crate) fn load_banner_art() -> String {
load_banner_art_in(&BannerEnv::from_process())
}
pub(crate) fn load_banner_art_in(env: &BannerEnv) -> String {
let Some(path) = env.banner_file_path() else {
return DEFAULT_BANNER_ART.to_string();
};
match std::fs::read_to_string(&path) {
Ok(content) if !content.trim().is_empty() => {
if refresh_if_legacy(&path, content.trim()) {
DEFAULT_BANNER_ART.to_string()
} else {
content
}
}
Ok(_) => {
tracing::debug!(
"banner file is empty, using embedded default: {}",
path.display()
);
DEFAULT_BANNER_ART.to_string()
}
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
write_default_if_absent_in(env);
DEFAULT_BANNER_ART.to_string()
}
Err(e) => {
tracing::debug!(
"failed to read banner file {}: {e}, using embedded default",
path.display()
);
DEFAULT_BANNER_ART.to_string()
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn home_only(home: &std::path::Path) -> BannerEnv {
BannerEnv::new(None, Some(home))
}
#[test]
fn banner_source_embedded_fallback_is_nonempty() {
assert!(!DEFAULT_BANNER_ART.trim().is_empty());
assert!(
DEFAULT_BANNER_ART.contains('█'),
"default art should contain full-block chars"
);
}
#[test]
fn banner_source_override_file_used() {
let dir = tempfile::tempdir().unwrap();
let file = dir.path().join("banner.txt");
std::fs::write(&file, "CUSTOM ART\n").unwrap();
let art = load_banner_art_in(&BannerEnv::new(Some(&file), None));
assert_eq!(art.trim(), "CUSTOM ART");
}
#[test]
fn banner_source_missing_falls_back() {
let dir = tempfile::tempdir().unwrap();
let file = dir.path().join("no-such.txt");
let fake_home = dir.path().join("home");
std::fs::create_dir_all(&fake_home).unwrap();
let art = load_banner_art_in(&BannerEnv::new(Some(&file), Some(&fake_home)));
let seeded = std::fs::read_to_string(fake_home.join(".trusty-mpm").join("banner.txt")).ok();
assert_eq!(art, DEFAULT_BANNER_ART);
assert_eq!(
seeded.as_deref(),
Some(DEFAULT_BANNER_ART),
"the NotFound arm seeds <home>/.trusty-mpm/banner.txt — it must land in \
THIS test's sandbox, never the ambient home (#4407)"
);
}
#[test]
fn banner_source_empty_falls_back() {
let dir = tempfile::tempdir().unwrap();
let file = dir.path().join("empty.txt");
std::fs::write(&file, " \n \n").unwrap();
let art = load_banner_art_in(&BannerEnv::new(Some(&file), None));
assert_eq!(art, DEFAULT_BANNER_ART);
}
#[test]
fn banner_source_empty_override_value_falls_through_to_home() {
let dir = tempfile::tempdir().unwrap();
let fake_home = dir.path().join("home");
std::fs::create_dir_all(fake_home.join(".trusty-mpm")).unwrap();
std::fs::write(
fake_home.join(".trusty-mpm").join("banner.txt"),
"HOME ART\n",
)
.unwrap();
let env = BannerEnv {
override_file: Some(std::ffi::OsString::new()),
home: Some(fake_home.as_os_str().to_os_string()),
};
assert_eq!(load_banner_art_in(&env).trim(), "HOME ART");
}
#[test]
fn banner_source_env_override_takes_precedence() {
let dir = tempfile::tempdir().unwrap();
let env_file = dir.path().join("env-banner.txt");
std::fs::write(&env_file, "ENV ART\n").unwrap();
let fake_home = dir.path().join("home");
std::fs::create_dir_all(fake_home.join(".trusty-mpm")).unwrap();
std::fs::write(
fake_home.join(".trusty-mpm").join("banner.txt"),
"HOME ART\n",
)
.unwrap();
let art = load_banner_art_in(&BannerEnv::new(Some(&env_file), Some(&fake_home)));
assert_eq!(art.trim(), "ENV ART");
}
#[test]
fn banner_source_first_run_writes_default() {
let dir = tempfile::tempdir().unwrap();
let fake_home = dir.path().join("fresh-home");
std::fs::create_dir_all(&fake_home).unwrap();
write_default_if_absent_in(&home_only(&fake_home));
let written = std::fs::read_to_string(fake_home.join(".trusty-mpm").join("banner.txt"))
.expect("default banner.txt should be written on first run");
assert_eq!(written, DEFAULT_BANNER_ART);
}
#[test]
fn banner_source_first_run_no_overwrite() {
let dir = tempfile::tempdir().unwrap();
let fake_home = dir.path().join("existing-home");
let banner_dir = fake_home.join(".trusty-mpm");
std::fs::create_dir_all(&banner_dir).unwrap();
let banner_path = banner_dir.join("banner.txt");
std::fs::write(&banner_path, "KEEP ME\n").unwrap();
write_default_if_absent_in(&home_only(&fake_home));
assert_eq!(std::fs::read_to_string(&banner_path).unwrap(), "KEEP ME\n");
}
#[test]
fn banner_source_no_home_is_a_noop() {
write_default_if_absent_in(&BannerEnv::new(None, None));
assert_eq!(
load_banner_art_in(&BannerEnv::new(None, None)),
DEFAULT_BANNER_ART
);
}
#[test]
fn banner_source_refresh_on_legacy_match() {
let dir = tempfile::tempdir().unwrap();
let file = dir.path().join("banner.txt");
std::fs::write(&file, super::super::legacy::LEGACY_PRE_1907).unwrap();
let art = load_banner_art_in(&BannerEnv::new(Some(&file), None));
let on_disk_after = std::fs::read_to_string(&file).unwrap();
assert_eq!(
art, DEFAULT_BANNER_ART,
"load_banner_art must return the refreshed current default"
);
assert_eq!(
on_disk_after, DEFAULT_BANNER_ART,
"the on-disk legacy seed file must be rewritten to the current default"
);
}
#[test]
fn banner_source_refresh_does_not_touch_custom_content() {
let dir = tempfile::tempdir().unwrap();
let file = dir.path().join("banner.txt");
std::fs::write(&file, "MY CUSTOM ROBOT ART\n").unwrap();
let art = load_banner_art_in(&BannerEnv::new(Some(&file), None));
let on_disk_after = std::fs::read_to_string(&file).unwrap();
assert_eq!(
art.trim(),
"MY CUSTOM ROBOT ART",
"custom content must be returned unchanged"
);
assert_eq!(
on_disk_after, "MY CUSTOM ROBOT ART\n",
"custom content on disk must never be rewritten"
);
}
#[test]
fn banner_source_refresh_is_noop_on_current_default() {
let dir = tempfile::tempdir().unwrap();
let file = dir.path().join("banner.txt");
std::fs::write(&file, DEFAULT_BANNER_ART).unwrap();
let art = load_banner_art_in(&BannerEnv::new(Some(&file), None));
assert_eq!(art, DEFAULT_BANNER_ART);
}
}