use std::fs;
use std::path::{Path, PathBuf};
pub(crate) fn run(name: Option<String>, force: bool, out: &str) -> Result<(), String> {
match name {
None => list_templates(),
Some(n) => copy_template(&n, force, Path::new(out)),
}
}
fn list_templates() -> Result<(), String> {
let names = rustio_admin::embedded_template_names();
println!("{} embedded templates:", names.len());
for n in &names {
println!(" {n}");
}
println!();
println!("Copy one with `rustio override <name>` — drops it into");
println!("`./templates/<name>` so a `RUSTIO_TEMPLATE_DIR=./templates`");
println!("run picks it up instead of the embedded default.");
Ok(())
}
fn copy_template(name: &str, force: bool, out_root: &Path) -> Result<(), String> {
let body = rustio_admin::embedded_template_source(name).ok_or_else(|| {
format!("unknown template `{name}`. Run `rustio override` (no args) for the full list.")
})?;
if name.contains("..") || name.starts_with('/') {
return Err(format!(
"refusing to write template `{name}` — path contains parent or absolute segment"
));
}
let dest: PathBuf = out_root.join(name);
if dest.exists() && !force {
return Err(format!(
"{} already exists. Pass --force to overwrite, or move the existing file first.",
dest.display()
));
}
if let Some(parent) = dest.parent() {
fs::create_dir_all(parent)
.map_err(|e| format!("create directory {}: {e}", parent.display()))?;
}
fs::write(&dest, body).map_err(|e| format!("write {}: {e}", dest.display()))?;
println!("Copied {name} → {} ({} bytes)", dest.display(), body.len());
println!();
println!("Edit the file, then run your project with");
println!(" RUSTIO_TEMPLATE_DIR={} cargo run", out_root.display());
println!("to pick up the override.");
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use std::env;
#[test]
fn unknown_template_returns_clear_error() {
let tmp = env::temp_dir().join("rustio-cli-override-tests-unknown");
let _ = fs::remove_dir_all(&tmp);
let err = copy_template("admin/does-not-exist.html", false, &tmp).unwrap_err();
assert!(err.contains("unknown template"), "got: {err}");
assert!(!tmp.exists());
}
#[test]
fn refuses_path_traversal() {
let tmp = env::temp_dir().join("rustio-cli-override-tests-traversal");
let _ = fs::remove_dir_all(&tmp);
let err = copy_template("admin/../etc/passwd.html", false, &tmp).unwrap_err();
assert!(
err.contains("unknown template") || err.contains("parent or absolute"),
"got: {err}"
);
assert!(!tmp.exists());
}
#[test]
fn copies_known_template_and_writes_correct_bytes() {
let tmp = env::temp_dir().join("rustio-cli-override-tests-copy");
let _ = fs::remove_dir_all(&tmp);
let names = rustio_admin::embedded_template_names();
let target = names
.first()
.copied()
.expect("at least one embedded template");
copy_template(target, false, &tmp).expect("copy succeeds");
let dest = tmp.join(target);
assert!(dest.is_file(), "destination {} not written", dest.display());
let body = fs::read_to_string(&dest).unwrap();
let expected = rustio_admin::embedded_template_source(target).unwrap();
assert_eq!(body, expected, "written body diverges from embedded source");
let _ = fs::remove_dir_all(&tmp);
}
#[test]
fn refuses_to_clobber_without_force() {
let tmp = env::temp_dir().join("rustio-cli-override-tests-clobber");
let _ = fs::remove_dir_all(&tmp);
let names = rustio_admin::embedded_template_names();
let target = names.first().copied().unwrap();
copy_template(target, false, &tmp).unwrap();
let err = copy_template(target, false, &tmp).unwrap_err();
assert!(err.contains("already exists"), "got: {err}");
copy_template(target, true, &tmp).expect("force overrides existing file");
let _ = fs::remove_dir_all(&tmp);
}
}