use clap::{CommandFactory, FromArgMatches, Parser}; use git_testament::git_testament; use log::{debug, error, info}; use riki::error::RikiError; use riki::git::{git_dirty, git_whatchanged}; use riki::name::Name; use riki::pagespec; use riki::site::Site; use riki::time::parse_timestamp; use riki::util::{canonicalize, copy_file_from_source, get_mtime, mkdir, set_mtime, write}; use riki::version::Version; use std::error::Error; use std::path::{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<(), RikiError> { let env = env_logger::Env::new().filter(ENVLOG); env_logger::init_from_env(env); info!("riki starts"); let version = Version::new(&VERSION); let short = version.version()?; let long = version.long_version()?; { let argparser = Args::command(); let argparser = argparser.version(short.as_str()); let argparser = argparser.long_version(long.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()?, Command::Timestamps(cmd) => cmd.run()?, Command::ParseDate(cmd) => cmd.run()?, Command::PageSpec(cmd) => cmd.run()?, Command::Links(cmd) => cmd.run()?, } } info!("riki ends OK"); Ok(()) } /// 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), Timestamps(Timestamps), ParseDate(ParseDate), PageSpec(PageSpec), Links(Links), } /// 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<(), RikiError> { 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().count()); 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(); debug!("writing: {}", output.display()); write(&output, &format!("{}", htmlpage))?; set_mtime(&output, page.meta().mtime())?; } for file in site.files_only() { copy_file_from_source(file.source_path(), file.destination_path())?; } Ok(()) } } /// List source files in the site. #[derive(Parser)] struct List { /// Directory where source files are. srcdir: PathBuf, } impl List { fn run(&self) -> Result<(), RikiError> { let srcdir = canonicalize(&self.srcdir)?; let mut site = Site::new(&srcdir, &srcdir); site.scan()?; let mut names: Vec<&Name> = site.pages_and_files().collect(); names.sort_by_cached_key(|name| name.page_path()); for name in names { println!("{}", name); } Ok(()) } } /// Show the timestamp for each source file. #[derive(Parser)] struct Timestamps { /// Directory where source files are. srcdir: PathBuf, } impl Timestamps { fn run(&self) -> Result<(), RikiError> { let srcdir = canonicalize(&self.srcdir)?; let mut site = Site::new(&srcdir, &srcdir); site.scan()?; let mut names: Vec<&Name> = site.pages_and_files().collect(); names.sort_by_cached_key(|name| name.page_path()); let whatchanged = git_whatchanged(&srcdir)?; let dirty = git_dirty(&srcdir)?; println!(); for name in names { let relative = name.source_path().strip_prefix(&srcdir).unwrap(); if Self::is_dirty(relative, &dirty) { println!("dirty: {} {:?}", name, get_mtime(name.source_path())?); } else if let Some(timestamp) = whatchanged.get(relative) { println!("git: {} {:?}", name, timestamp); } } Ok(()) } fn is_dirty(filename: &Path, dirty: &[PathBuf]) -> bool { dirty.iter().any(|x| *x == filename) } } /// Parse a date string as the 'meta date' directive does. #[derive(Parser)] struct ParseDate { /// Date strings to parse. dates: Vec, } impl ParseDate { fn run(&self) -> Result<(), RikiError> { for date in self.dates.iter() { println!("input: {:?}", date); let parsed = parse_timestamp(date)?; println!("parse: {:?}", parsed); } Ok(()) } } /// List the pages that match a PageSpec expression. #[derive(Parser)] struct PageSpec { /// Directory where source files are. srcdir: PathBuf, /// Path to page that contains the PageSpec. path: PathBuf, /// PageSpec expression. pagespec: String, } impl PageSpec { fn run(&self) -> Result<(), RikiError> { let srcdir = canonicalize(&self.srcdir)?; let mut site = Site::new(&srcdir, &PathBuf::from("/tmp")); site.scan()?; site.process()?; let pagespec = pagespec::PageSpec::new(&self.path, &self.pagespec)?; debug!("pagespec: {:#?}", pagespec); for page in site.markdown_pages() { let path = page.meta().path(); debug!("consider markdown page {}", path.display()); if pagespec.matches(&site, path) { println!("{}", path.display()); } } for name in site.files_only() { let path = name.page_path(); debug!("consider file {}", path.display()); if pagespec.matches(&site, path) { println!("{}", path.display()); } } Ok(()) } } /// List links in the pages listed on the command line. #[derive(Parser)] struct Links { /// Directory where source files are. srcdir: PathBuf, /// Path to pages whose links are to be listed. pages: Vec, } impl Links { fn run(&self) -> Result<(), RikiError> { let srcdir = canonicalize(&self.srcdir)?; let mut site = Site::new(&srcdir, &PathBuf::from("/tmp")); site.scan()?; site.process()?; for path in self.pages.iter() { if let Some(page) = site.page(path) { println!("page: {:?}", page.meta().path().display()); for path in page.meta().links_to() { println!(" {}", path.display()); } } } Ok(()) } }