use std::path::Path;
use std::time::Duration;
use crate::build::build_sites;
use crate::cli::{OutArgs, SiteArgs};
use crate::manifest;
use crate::scan;
use crate::session::Session;
use crate::write::{describe, write_sites};
const POLL: Duration = Duration::from_millis(500);
pub fn build(site: &SiteArgs, out: &OutArgs, force: bool) -> Result<(), String> {
let session = Session::open()?;
report(&session.warnings);
if !force {
manifest::check_destination(&out.out).map_err(|e| {
format!("{e}\n (use --force to write into it anyway, or --out to name another)")
})?;
}
let sites = build_sites(&session, site.site.as_deref(), site.base_url.as_deref())?;
for built in &sites {
report(&built.warnings);
}
let written = write_sites(&sites, &out.out, site.site.is_some())?;
for built in &sites {
println!("✓ {}", describe(built));
}
println!();
println!(
"✓ Wrote {} file{} and {} attachment{} to {}",
written.files,
crate::build::plural(written.files),
written.attachments,
crate::build::plural(written.attachments),
out.out.display()
);
if written.pruned > 0 {
println!(
" removed {} file{} the last build left behind",
written.pruned,
crate::build::plural(written.pruned)
);
}
if site.base_url.is_none() {
println!(" (no --base-url, so no sitemap, robots.txt or feeds were generated)");
}
Ok(())
}
pub fn watch(site: &SiteArgs, out: &OutArgs, force: bool) -> Result<(), String> {
build(site, out, force)?;
let root = Session::open()?.root_dir;
let skip = scan::build_dir_within(&root, &out.out);
let mut fingerprint = scan::fingerprint(&root, skip.as_deref());
println!();
println!(" Watching {}. Press Ctrl-C to stop.", root.display());
loop {
std::thread::sleep(POLL);
let next = scan::fingerprint(&root, skip.as_deref());
if next == fingerprint {
continue;
}
match rebuild(site, out) {
Ok(summary) => println!(" rebuilt — {summary}"),
Err(e) => eprintln!("✗ Rebuild failed: {e}"),
}
fingerprint = scan::fingerprint(&root, skip.as_deref());
}
}
fn rebuild(site: &SiteArgs, out: &OutArgs) -> Result<String, String> {
let session = Session::open()?;
report(&session.warnings);
let sites = build_sites(&session, site.site.as_deref(), site.base_url.as_deref())?;
for built in &sites {
report(&built.warnings);
}
let written = write_sites(&sites, &out.out, site.site.is_some())?;
let pages: usize = sites.iter().map(|s| s.pages).sum();
Ok(format!(
"{} page{}{}",
pages,
crate::build::plural(pages),
match written.pruned {
0 => String::new(),
n => format!(", {n} stale file{} removed", crate::build::plural(n)),
}
))
}
pub fn clean(out: &OutArgs, force: bool) -> Result<(), String> {
let dest = &out.out;
if !dest.exists() {
println!("✓ Nothing to clean — {} does not exist", dest.display());
return Ok(());
}
if force {
std::fs::remove_dir_all(dest).map_err(|e| format!("{}: {e}", dest.display()))?;
println!("✓ Removed {}", dest.display());
return Ok(());
}
let Some(mut wrote) = manifest::read(dest).map_err(|e| format!("{}: {e}", dest.display()))?
else {
return Err(format!(
"{} holds no record of a plates build, so nothing here knows what it may \
delete\n (use --force to remove the whole directory)",
dest.display()
));
};
wrote.insert(manifest::MANIFEST.to_string());
let removed = manifest::remove(dest, &wrote).map_err(|e| format!("{}: {e}", dest.display()))?;
prune_if_empty(dest);
println!(
"✓ Removed {removed} file{} from {}",
crate::build::plural(removed),
dest.display()
);
Ok(())
}
fn prune_if_empty(dest: &Path) {
if std::fs::read_dir(dest).is_ok_and(|mut d| d.next().is_none()) {
let _ = std::fs::remove_dir(dest);
}
}
pub fn report(warnings: &[String]) {
for warning in warnings {
eprintln!("⚠ {warning}");
}
}