use clap::{CommandFactory, FromArgMatches, Parser}; use git_testament::{git_testament, render_testament, GitModification}; use log::{debug, error, info}; use riki::error::SiteError; use riki::site::Site; use riki::util::{canonicalize, copy_file_from_source, mkdir, set_mtime}; use std::error::Error; use std::path::PathBuf; const ENVLOG: &str = "RIKI_LOG"; git_testament!(VERSION); fn main() { if let Err(err) = real_main() { error!("ERROR: {}", err); let mut source = err.source(); while source.is_some() { let s = source.unwrap(); error!(" caused by: {}", s); source = source.unwrap().source(); } std::process::exit(1); } } fn real_main() -> Result<(), SiteError> { if std::env::var(ENVLOG).is_err() { std::env::set_var(ENVLOG, "warn"); } pretty_env_logger::init_custom_env(ENVLOG); info!("riki starts"); let version = version().map_err(SiteError::Format)?; let long_version = long_version().map_err(SiteError::Format)?; { let argparser = Args::command(); let argparser = argparser.version(version.as_str()); let argparser = argparser.long_version(long_version.as_str()); let args = argparser.get_matches(); let args = Args::from_arg_matches(&args).unwrap(); match args.command { Command::Build(cmd) => cmd.run()?, Command::List(cmd) => cmd.run()?, } } info!("riki ends OK"); Ok(()) } fn version() -> Result { use std::fmt::Write as _; let mut ret = String::new(); writeln!(ret, "{}", render_testament!(VERSION))?; writeln!( ret, "{} {}", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION") )?; Ok(ret) } fn long_version() -> Result { use std::fmt::Write as _; let mut ret = String::new(); writeln!(ret, "{}", render_testament!(VERSION))?; writeln!(ret, "Crate version: {}", env!("CARGO_PKG_VERSION"))?; if let Some(branch) = VERSION.branch_name { writeln!(ret, "Built from branch: {}", branch)?; } else { writeln!(ret, "Branch information is missing.")?; } writeln!(ret, "Commit info: {}", VERSION.commit)?; if VERSION.modifications.is_empty() { writeln!(ret, "Working tree is clean")?; } else { use GitModification::*; for fmod in VERSION.modifications { match fmod { Added(f) => writeln!(ret, "Added: {}", String::from_utf8_lossy(f))?, Removed(f) => writeln!(ret, "Removed: {}", String::from_utf8_lossy(f))?, Modified(f) => writeln!(ret, "Modified: {}", String::from_utf8_lossy(f))?, Untracked(f) => writeln!(ret, "Untracked: {}", String::from_utf8_lossy(f))?, } } } Ok(ret) } /// Static site generator. /// /// Riki generates a static web site from markdown files. It mostly /// follows wikitext and other syntax and semantics of the ikiwiki /// software (see http://ikiwiki.info/) for input files. #[derive(Parser)] struct Args { #[clap(subcommand)] command: Command, } #[derive(Parser)] enum Command { Build(Build), List(List), } /// Build the site. #[derive(Parser)] struct Build { /// Should the output only have the HTML BODY element or the full /// page? #[clap(long)] plain_body: bool, /// Directory where source files are. srcdir: PathBuf, /// Directore where output files get put. This directory must not /// exist before the program is run. destdir: PathBuf, } impl Build { fn run(&self) -> Result<(), SiteError> { let srcdir = canonicalize(&self.srcdir)?; debug!("srcdir={}", srcdir.display()); mkdir(&self.destdir)?; let destdir = canonicalize(&self.destdir)?; debug!("destdir={}", destdir.display()); let mut site = Site::new(&srcdir, &destdir); site.scan()?; site.process()?; debug!("markdown file count: {}", site.markdown_pages().len()); for page in site.markdown_pages() { let htmlpage = if self.plain_body { page.body_to_html()? } else { page.to_html()? }; let output = page.meta().destination_filename(&destdir); debug!("writing: {}", output.display()); htmlpage.write(&output)?; set_mtime(&output, page.meta().mtime())?; } for file in site.files() { let input = site.input_filename(file)?; let output = site.output_filename(file)?; copy_file_from_source(&input, &output)?; } Ok(()) } } /// List source files in the site. #[derive(Parser)] struct List { /// Directory where source files are. srcdir: PathBuf, } impl List { fn run(&self) -> Result<(), SiteError> { let srcdir = canonicalize(&self.srcdir)?; let mut site = Site::new(&srcdir, &srcdir); site.scan()?; let mut filenames = site.included_files().to_vec(); filenames.sort_unstable(); for filename in filenames { println!("{}", filename.display()); } Ok(()) } }