pub(crate) const DEFAULT_BANNER_ART: &str = include_str!("image.txt");
fn home_banner_path() -> Option<std::path::PathBuf> {
std::env::var_os("HOME").map(|h| {
std::path::PathBuf::from(h)
.join(".trusty-mpm")
.join("banner.txt")
})
}
fn banner_file_path() -> Option<std::path::PathBuf> {
if let Ok(path) = std::env::var("TRUSTY_MPM_BANNER_FILE")
&& !path.is_empty()
{
return Some(std::path::PathBuf::from(path));
}
home_banner_path()
}
pub(crate) fn write_default_if_absent() {
let Some(path) = 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 {
let Some(path) = 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();
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::*;
static ENV_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
fn temp_dir() -> std::path::PathBuf {
let base = std::env::temp_dir().join(format!(
"tm-banner-test-{}",
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.subsec_nanos())
.unwrap_or(0)
));
std::fs::create_dir_all(&base).expect("create temp dir");
base
}
#[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 _g = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let dir = temp_dir();
let file = dir.join("banner.txt");
std::fs::write(&file, "CUSTOM ART\n").unwrap();
let old = std::env::var_os("TRUSTY_MPM_BANNER_FILE");
unsafe { std::env::set_var("TRUSTY_MPM_BANNER_FILE", &file) };
let art = load_banner_art();
unsafe {
match old {
Some(v) => std::env::set_var("TRUSTY_MPM_BANNER_FILE", v),
None => std::env::remove_var("TRUSTY_MPM_BANNER_FILE"),
}
}
std::fs::remove_dir_all(&dir).ok();
assert_eq!(art.trim(), "CUSTOM ART");
}
#[test]
fn banner_source_missing_falls_back() {
let _g = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let dir = temp_dir();
let file = dir.join("no-such.txt");
let old = std::env::var_os("TRUSTY_MPM_BANNER_FILE");
unsafe { std::env::set_var("TRUSTY_MPM_BANNER_FILE", &file) };
let art = load_banner_art();
unsafe {
match old {
Some(v) => std::env::set_var("TRUSTY_MPM_BANNER_FILE", v),
None => std::env::remove_var("TRUSTY_MPM_BANNER_FILE"),
}
}
std::fs::remove_dir_all(&dir).ok();
assert_eq!(art, DEFAULT_BANNER_ART);
}
#[test]
fn banner_source_empty_falls_back() {
let _g = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let dir = temp_dir();
let file = dir.join("empty.txt");
std::fs::write(&file, " \n \n").unwrap();
let old = std::env::var_os("TRUSTY_MPM_BANNER_FILE");
unsafe { std::env::set_var("TRUSTY_MPM_BANNER_FILE", &file) };
let art = load_banner_art();
unsafe {
match old {
Some(v) => std::env::set_var("TRUSTY_MPM_BANNER_FILE", v),
None => std::env::remove_var("TRUSTY_MPM_BANNER_FILE"),
}
}
std::fs::remove_dir_all(&dir).ok();
assert_eq!(art, DEFAULT_BANNER_ART);
}
#[test]
fn banner_source_env_override_takes_precedence() {
let _g = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let dir = temp_dir();
let env_file = dir.join("env-banner.txt");
std::fs::write(&env_file, "ENV ART\n").unwrap();
let fake_home = dir.join("home");
std::fs::create_dir_all(fake_home.join(".trusty-mpm")).unwrap();
let home_file = fake_home.join(".trusty-mpm").join("banner.txt");
std::fs::write(&home_file, "HOME ART\n").unwrap();
let old_env = std::env::var_os("TRUSTY_MPM_BANNER_FILE");
let old_home = std::env::var_os("HOME");
unsafe {
std::env::set_var("TRUSTY_MPM_BANNER_FILE", &env_file);
std::env::set_var("HOME", &fake_home);
}
let art = load_banner_art();
unsafe {
match old_env {
Some(v) => std::env::set_var("TRUSTY_MPM_BANNER_FILE", v),
None => std::env::remove_var("TRUSTY_MPM_BANNER_FILE"),
}
match old_home {
Some(v) => std::env::set_var("HOME", v),
None => std::env::remove_var("HOME"),
}
}
std::fs::remove_dir_all(&dir).ok();
assert_eq!(art.trim(), "ENV ART");
}
#[test]
fn banner_source_first_run_writes_default() {
let _g = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let fake_home = temp_dir().join("fresh-home");
std::fs::create_dir_all(&fake_home).unwrap();
let old_home = std::env::var_os("HOME");
let old_env = std::env::var_os("TRUSTY_MPM_BANNER_FILE");
unsafe {
std::env::set_var("HOME", &fake_home);
std::env::remove_var("TRUSTY_MPM_BANNER_FILE");
}
write_default_if_absent();
let written_path = fake_home.join(".trusty-mpm").join("banner.txt");
let written = std::fs::read_to_string(&written_path).ok();
unsafe {
match old_home {
Some(v) => std::env::set_var("HOME", v),
None => std::env::remove_var("HOME"),
}
match old_env {
Some(v) => std::env::set_var("TRUSTY_MPM_BANNER_FILE", v),
None => std::env::remove_var("TRUSTY_MPM_BANNER_FILE"),
}
}
std::fs::remove_dir_all(fake_home.parent().unwrap()).ok();
assert!(
written.is_some(),
"default banner.txt should be written on first run"
);
assert_eq!(written.unwrap(), DEFAULT_BANNER_ART);
}
#[test]
fn banner_source_first_run_no_overwrite() {
let _g = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let fake_home = temp_dir().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();
let old_home = std::env::var_os("HOME");
let old_env = std::env::var_os("TRUSTY_MPM_BANNER_FILE");
unsafe {
std::env::set_var("HOME", &fake_home);
std::env::remove_var("TRUSTY_MPM_BANNER_FILE");
}
write_default_if_absent();
let content = std::fs::read_to_string(&banner_path).unwrap();
unsafe {
match old_home {
Some(v) => std::env::set_var("HOME", v),
None => std::env::remove_var("HOME"),
}
match old_env {
Some(v) => std::env::set_var("TRUSTY_MPM_BANNER_FILE", v),
None => std::env::remove_var("TRUSTY_MPM_BANNER_FILE"),
}
}
std::fs::remove_dir_all(fake_home.parent().unwrap()).ok();
assert_eq!(content, "KEEP ME\n");
}
#[test]
fn banner_source_refresh_on_legacy_match() {
let _g = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let dir = temp_dir();
let file = dir.join("banner.txt");
std::fs::write(&file, super::super::legacy::LEGACY_PRE_1907).unwrap();
let old = std::env::var_os("TRUSTY_MPM_BANNER_FILE");
unsafe { std::env::set_var("TRUSTY_MPM_BANNER_FILE", &file) };
let art = load_banner_art();
let on_disk_after = std::fs::read_to_string(&file).unwrap();
unsafe {
match old {
Some(v) => std::env::set_var("TRUSTY_MPM_BANNER_FILE", v),
None => std::env::remove_var("TRUSTY_MPM_BANNER_FILE"),
}
}
std::fs::remove_dir_all(&dir).ok();
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 _g = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let dir = temp_dir();
let file = dir.join("banner.txt");
std::fs::write(&file, "MY CUSTOM ROBOT ART\n").unwrap();
let old = std::env::var_os("TRUSTY_MPM_BANNER_FILE");
unsafe { std::env::set_var("TRUSTY_MPM_BANNER_FILE", &file) };
let art = load_banner_art();
let on_disk_after = std::fs::read_to_string(&file).unwrap();
unsafe {
match old {
Some(v) => std::env::set_var("TRUSTY_MPM_BANNER_FILE", v),
None => std::env::remove_var("TRUSTY_MPM_BANNER_FILE"),
}
}
std::fs::remove_dir_all(&dir).ok();
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 _g = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let dir = temp_dir();
let file = dir.join("banner.txt");
std::fs::write(&file, DEFAULT_BANNER_ART).unwrap();
let old = std::env::var_os("TRUSTY_MPM_BANNER_FILE");
unsafe { std::env::set_var("TRUSTY_MPM_BANNER_FILE", &file) };
let art = load_banner_art();
unsafe {
match old {
Some(v) => std::env::set_var("TRUSTY_MPM_BANNER_FILE", v),
None => std::env::remove_var("TRUSTY_MPM_BANNER_FILE"),
}
}
std::fs::remove_dir_all(&dir).ok();
assert_eq!(art, DEFAULT_BANNER_ART);
}
}