use std::path::{Path, PathBuf};
use std::process::Command;
fn repository() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR"))
.join("../../..")
.canonicalize()
.expect("the repository root resolves")
}
const DECLARED: &str = "
identifier_schemes.module_page_id:
pattern: \"{namespace}-MOD-{slug}\"
namespace: HW
allocation: minted-once
kinds.module_page:
is_a: governed_document
purpose: rationale
voice: declarative
lifecycle: standard
language: ste_house
identifier: {scheme: module_page_id}
shelves.module_pages:
title: The module pages
path: docs/modules/*/README.md
homogeneous: true
kind: module_page
identifier_schemes.corpus_index_id:
pattern: \"{namespace}-IDX-{slug}\"
namespace: HW
allocation: minted-once
kinds.corpus_index:
is_a: governed_document
purpose: rationale
voice: declarative
lifecycle: standard
language: ste_house
identifier: {scheme: corpus_index_id}
shelves.corpus_index:
title: The corpus index
path: docs/INDEX.md
homogeneous: true
kind: corpus_index
";
struct Root {
at: PathBuf,
}
impl Root {
fn new(label: &str) -> Root {
let at = std::env::temp_dir().join(format!(
"headwater-cli-fixed-name-shelf-{}-{label}",
std::process::id()
));
let _ = std::fs::remove_dir_all(&at);
std::fs::create_dir_all(&at).expect("the root is made");
let repository = repository();
copy(
&repository.join(headwater_resolve::package::PACKAGES),
&at.join(headwater_resolve::package::PACKAGES),
);
copy(
&repository.join("docs/taxonomies"),
&at.join("docs/taxonomies"),
);
std::fs::copy(
repository.join(".headwater/taxonomy.yml"),
at.join(".headwater/taxonomy.yml"),
)
.expect("the declaration copies");
let overlay = std::fs::read_to_string(repository.join(".headwater/overlay.yml"))
.expect("the overlay reads");
assert!(
overlay.contains("\nadd:\n"),
"the overlay opens its additions with `add:`, where the scratch kinds go"
);
std::fs::write(
at.join(".headwater/overlay.yml"),
overlay.replacen("\nadd:\n", &format!("\nadd:\n{DECLARED}\n"), 1),
)
.expect("the overlay writes");
let root = Root { at };
let resolved = root.run(&["taxonomy", "resolve"]);
assert_eq!(
resolved.code,
Some(0),
"the fixture resolves\n{}{}",
resolved.out,
resolved.err
);
root
}
fn run(&self, arguments: &[&str]) -> Ran {
let output = Command::new(env!("CARGO_BIN_EXE_headwater"))
.args(arguments)
.arg("--root")
.arg(&self.at)
.output()
.expect("the binary runs");
Ran {
code: output.status.code(),
out: String::from_utf8_lossy(&output.stdout).into_owned(),
err: String::from_utf8_lossy(&output.stderr).into_owned(),
}
}
fn has(&self, path: &str) -> bool {
self.at.join(path).exists()
}
}
impl Drop for Root {
fn drop(&mut self) {
let _ = std::fs::remove_dir_all(&self.at);
}
}
#[derive(Debug)]
struct Ran {
code: Option<i32>,
out: String,
err: String,
}
fn copy(from: &Path, to: &Path) {
std::fs::create_dir_all(to).expect("the directory is there");
for entry in std::fs::read_dir(from).expect("the fixture directory reads") {
let entry = entry.expect("the entry reads");
let target = to.join(entry.file_name());
match entry.file_type().expect("the file type reads").is_dir() {
true => copy(&entry.path(), &target),
false => {
std::fs::copy(entry.path(), &target).expect("the fixture copies");
}
}
}
}
#[test]
fn a_fixed_name_under_a_glob_with_no_directory_names_the_flag() {
let root = Root::new("no-directory");
let ran = root.run(&["new", "module_page", "--title", "Alpha"]);
assert_eq!(ran.code, Some(1), "{ran:?}");
assert!(ran.err.contains("docs/modules/*/README.md"), "{ran:?}");
assert!(ran.err.contains("--directory"), "{ran:?}");
assert!(!root.has("docs/modules"), "nothing was written: {ran:?}");
}
#[test]
fn a_fixed_name_under_a_glob_lands_where_the_caller_names_and_reads_back() {
let root = Root::new("named");
let ran = root.run(&[
"new",
"module_page",
"--title",
"Alpha",
"--directory",
"docs/modules/alpha",
]);
assert_eq!(ran.code, Some(0), "{ran:?}");
assert!(root.has("docs/modules/alpha/README.md"), "{ran:?}");
let explained = root.run(&["explain", "docs/modules/alpha/README.md"]);
assert_eq!(explained.code, Some(0), "{explained:?}");
assert!(
explained.out.contains("module_page"),
"the written file is read back as its kind\n{explained:?}"
);
}
#[test]
fn a_directory_off_the_shelf_is_refused_and_nothing_is_written() {
let root = Root::new("off-shelf");
let ran = root.run(&[
"new",
"module_page",
"--title",
"Alpha",
"--directory",
"docs/other/alpha",
]);
assert_eq!(ran.code, Some(1), "{ran:?}");
assert!(ran.err.contains("docs/modules/*/README.md"), "{ran:?}");
assert!(
!ran.err.contains("defect"),
"not blamed on the scaffolder: {ran:?}"
);
assert!(!root.has("docs/other"), "nothing was written: {ran:?}");
}
#[test]
fn a_single_file_shelf_is_written_once_and_never_overwritten() {
let root = Root::new("single");
let first = root.run(&["new", "corpus_index", "--title", "The index"]);
assert_eq!(first.code, Some(0), "{first:?}");
let written = std::fs::read(root.at.join("docs/INDEX.md")).expect("the index was written");
let second = root.run(&["new", "corpus_index", "--title", "Another index"]);
assert_eq!(second.code, Some(1), "{second:?}");
assert!(second.err.contains("docs/INDEX.md"), "{second:?}");
assert_eq!(
std::fs::read(root.at.join("docs/INDEX.md")).expect("the index reads"),
written,
"the second run left the file alone"
);
}
#[test]
fn a_directory_with_a_glob_character_is_refused_and_nothing_is_written() {
for (label, directory, segment) in [
("glob-star", "docs/modules/*", "`*`"),
("glob-question", "docs/modules/a?b", "`a?b`"),
("glob-bracket", "docs/modules/[ab]", "`[ab]`"),
] {
let root = Root::new(label);
let ran = root.run(&[
"new",
"module_page",
"--title",
"Alpha",
"--directory",
directory,
]);
assert_eq!(ran.code, Some(1), "{directory}: {ran:?}");
assert!(
ran.err.contains(&format!("the segment {segment}")),
"{directory}: the refusal names the segment\n{ran:?}"
);
assert!(ran.err.contains("docs/modules/*/README.md"), "{ran:?}");
assert!(
!ran.err.contains("defect"),
"not blamed on the scaffolder: {ran:?}"
);
assert!(!root.has("docs/modules"), "nothing was written: {ran:?}");
}
}
#[test]
fn a_directory_for_a_shelf_that_decides_it_is_refused_and_nothing_is_written() {
let root = Root::new("not-asked");
let ran = root.run(&[
"new",
"corpus_index",
"--title",
"The index",
"--directory",
"docs",
]);
assert_eq!(ran.code, Some(1), "{ran:?}");
assert!(ran.err.contains("--directory"), "{ran:?}");
assert!(ran.err.contains("docs/INDEX.md"), "{ran:?}");
assert!(ran.err.contains("decides the directory"), "{ran:?}");
assert!(!root.has("docs/INDEX.md"), "nothing was written: {ran:?}");
}
#[test]
fn a_directory_that_climbs_out_is_refused_and_nothing_is_written() {
let root = Root::new("climbs-out");
let ran = root.run(&[
"new",
"module_page",
"--title",
"Alpha",
"--directory",
"docs/modules/..",
]);
assert_eq!(ran.code, Some(1), "{ran:?}");
assert!(
ran.err
.contains("a `.`, `..` or empty segment names no directory"),
"the refusal names the `..` segment as its cause\n{ran:?}"
);
assert!(!root.has("docs/README.md"), "nothing was written: {ran:?}");
assert!(!root.has("docs/modules"), "nothing was written: {ran:?}");
}