use crate::config::Config; use crate::entities::{ Description, DynEntity, EntitiesManager, EntitiesManagerComponents, Usr, }; use crate::pandoc::into_pandoc; use anyhow::{ensure, Context, Result}; use rayon::Scope; use thiserror::Error; use std::collections::HashMap; use std::io::Write; use std::path::Path; const DEFAULT_CSS: &[u8] = include_bytes!("../res/style.css"); pub(crate) fn generate(base_dir: &Path, manager: EntitiesManager, config: &Config) -> Result<()> { let EntitiesManagerComponents { toplevel_entities, descriptions, } = manager.decompose(); let md_output_dir = base_dir.join("markdown"); let html_output_dir = base_dir.join("html"); std::fs::create_dir_all(&md_output_dir) .context("Failed to create the markdown output directory")?; std::fs::create_dir_all(&html_output_dir) .context("Failed to create the HTML output directory")?; let mut css_tempfile = tempfile::Builder::new() .prefix("style") .suffix(".css") .tempfile()?; css_tempfile.write_all(DEFAULT_CSS)?; let css_path = css_tempfile.path(); debug!("Generated temporary file with CSS at: {:?}", css_path); rayon::scope(|scope| { for (usr, entity) in &toplevel_entities { generate_recursively( &usr, entity.as_ref(), scope, &descriptions, &md_output_dir, &html_output_dir, css_path, ); } }); Ok(()) } fn generate_recursively<'a>( usr: &'a Usr, entity: &'a DynEntity, pool: &Scope<'a>, descriptions: &'a HashMap, md_output_dir: &'a Path, html_output_dir: &'a Path, css_path: &'a Path, ) { pool.spawn(move |pool| { trace!("Trying to generate {}", usr.0); let leftovers = generate_single( &usr, entity, descriptions.get(&usr).unwrap(), &descriptions, &md_output_dir, &html_output_dir, &css_path, ) .unwrap(); for (usr, entity) in leftovers { generate_recursively( usr, entity, pool, descriptions, md_output_dir, html_output_dir, css_path, ); } }); } fn generate_single<'e>( usr: &Usr, entity: &'e DynEntity, description: &Description, descriptions: &HashMap, md_output_dir: impl AsRef, html_output_dir: impl AsRef, css_path: impl AsRef, ) -> Result> { use std::io::prelude::*; use std::process::{Command, Stdio}; let md_output_file = md_output_dir.as_ref().join(&usr.0); let html_output_file = html_output_dir.as_ref().join(&usr.0); let mut command = Command::new("pandoc"); command .stdin(Stdio::piped()) .stdout(Stdio::piped()) .stderr(Stdio::piped()) .args(&[ "--from=json", "--to=markdown", "--output", md_output_file .to_str() .context("Entity name is not valid UTF-8")?, ]); debug!("Launching command: {:?}", command); let mut pandoc = command .spawn() .context("Failed to execute Pandoc command")?; let (pandoc_ast, leftovers) = into_pandoc(entity, description, descriptions); if log_enabled!(log::Level::Trace) { let json = serde_json::to_string(&pandoc_ast).context("Failed to convert Pandoc AST to JSON")?; trace!("Sending json: {}", json); write!( pandoc.stdin.as_mut().expect("Pandoc stdin not captured"), "{}", &json, ) .context("Failed to convert Pandoc AST to JSON")?; } else { serde_json::to_writer( pandoc.stdin.as_mut().expect("Pandoc stdin not captured"), &pandoc_ast, ) .context("Failed to convert Pandoc AST to JSON")?; } let command_str = format!("{:?}", pandoc); let output = pandoc .wait_with_output() .expect("Pandoc command wasn't running"); ensure!( output.status.success(), CommandError { command: format!("{:?}", command_str), status: output.status, stderr: String::from_utf8(output.stderr).expect("Pandoc outputted invalid UTF-8"), } ); let mut command = Command::new("pandoc"); command .stdin(Stdio::piped()) .stdout(Stdio::piped()) .stderr(Stdio::piped()) .args(&[ "--from=markdown", "--to=html", "--css", css_path .as_ref() .to_str() .context("CSS path is not valid UTF-8")?, "--standalone", "--self-contained", md_output_file .to_str() .context("Entity name is not valid UTF-8")?, "--output", html_output_file .to_str() .context("Entity name is not valid UTF-8")?, ]); let command_str = format!("{:?}", command); debug!("Launching command: {}", command_str); let output = command .output() .context("Failed to execute Pandoc command")?; ensure!( output.status.success(), CommandError { command: command_str, status: output.status, stderr: String::from_utf8(output.stderr).expect("Pandoc outputted invalid UTF-8"), } ); Ok(leftovers) } #[derive(Debug, Clone, Error)] #[error( "Command {} returned status {:?} and stderr {:?}", command, status, stderr )] struct CommandError { command: String, status: std::process::ExitStatus, stderr: String, }