use std::{
env,
fs::{self, File},
path::PathBuf,
sync::{Arc, Barrier},
time::Instant,
};
use rayon::iter::{IntoParallelIterator, ParallelIterator};
use crate::{LiveServer, raw};
macro_rules! usage {
(@show $cmd:ident) => {
print!(concat!(
"usage: {cmd} help\n",
" show this usage menu\n",
" {cmd} run <input dir> <output dir>\n",
" generate the blog, writing it to the output directory\n",
" {cmd} serve <input dir> [<port>]\n",
" serve the blog on the given port (default: 8080)\n"
), cmd=$cmd);
};
($cmd:ident) => {{
$crate::cli::usage!(@show $cmd);
return;
}};
($cmd:ident; $msg:literal$(, $arg:expr)* $(,)?) => {{
$crate::cli::usage!(@show $cmd);
println!(concat!("\nERROR: ", $msg), $($arg),*);
return;
}};
}
pub(super) use usage;
pub fn run(cmd: &str, mut args: env::Args, cfg: raw::Config, start: Instant) {
let (in_dir, out_dir) = match (args.next(), args.next(), args.next()) {
(Some(i), Some(o), None) => (PathBuf::from(i), PathBuf::from(o)),
(None, _, _) => usage!(cmd; "bad `run`: missing <input dir>"),
(_, None, _) => usage!(cmd; "bad `run`: missing <output dir>"),
(_, _, Some(_)) => usage!(cmd; "bad `run`: extra argument"),
};
if !in_dir.is_dir() {
usage!(cmd; "bad `run`: <input dir> is not a directory");
}
if !out_dir.is_dir() {
fs::create_dir_all(&out_dir).expect("failed to create <output dir>");
}
let blog = cfg.build(&in_dir).expect("loading blog metadata failed");
let servables = blog.servables();
println!("blog loaded in {}us w/ {} urls", start.elapsed().as_micros(), servables.len());
let start = Instant::now();
servables.into_par_iter().for_each(|servable| {
let out_path = out_dir.join(servable.filepath());
if let Some(parent) = out_path.parent()
&& let Err(e) = fs::create_dir_all(parent)
{
eprintln!("ERROR: {:?}: creating {parent:?}: {e}", servable.url());
return;
}
if let Err(e) = File::create(&out_path).and_then(|mut out| servable.render(&mut out)) {
eprintln!("ERROR: rendering {:?} ({out_path:?}: {e}", servable.url());
std::process::exit(1); }
});
println!("blog rendered in {}us", start.elapsed().as_micros());
}
pub fn serve(cmd: &str, mut args: env::Args, cfg: raw::Config, start: Instant) {
let (in_dir, port) = match (args.next(), args.next(), args.next()) {
(Some(i), Some(p), None) => match p.parse() {
Ok(p) => (i, p),
Err(e) => usage!(cmd; "bad `serve`: bad <port>: {}", e),
},
(Some(i), None, _) => (i, 8080),
(None, _, _) => usage!(cmd; "bad `serve`: missing <input dir>"),
(_, _, Some(_)) => usage!(cmd; "bad `serve`: extra argument"),
};
let waiter = Arc::new(Barrier::new(2));
ctrlc::set_handler({
let mut waiter = Some(waiter.clone());
move || {
if let Some(w) = waiter.take() {
println!(" shutting down...");
w.wait();
} else {
println!(" please wait, we're stopping!");
}
}
})
.expect("failed to set ctrl+c handler");
println!("serving {in_dir:?} at http://localhost:{port}/");
let server = LiveServer::static_config(cfg, in_dir);
let bg = server.listen_background(port).expect("failed to start server");
println!("server started after {}us", start.elapsed().as_micros());
waiter.wait();
let start = Instant::now();
println!("stopping the server...");
bg.stop();
println!("server stopped in {}us. goodbye!", start.elapsed().as_micros());
}