diff options
Diffstat (limited to 'src/bin/subplot.rs')
-rw-r--r-- | src/bin/subplot.rs | 200 |
1 files changed, 139 insertions, 61 deletions
diff --git a/src/bin/subplot.rs b/src/bin/subplot.rs index b86c613..8dc8973 100644 --- a/src/bin/subplot.rs +++ b/src/bin/subplot.rs @@ -6,13 +6,13 @@ use anyhow::Result; use env_logger::fmt::Color; use log::{debug, error, info, trace, warn}; use subplot::{ - codegen, load_document, resource, Document, EmbeddedFile, MarkupOpts, Style, SubplotError, + codegen, load_document, resource, Binding, Bindings, Document, EmbeddedFile, MarkupOpts, Style, + SubplotError, Warnings, }; use time::{format_description::FormatItem, macros::format_description, OffsetDateTime}; use clap::{CommandFactory, FromArgMatches, Parser}; use std::convert::TryFrom; -use std::ffi::OsString; use std::fs::{self, write}; use std::io::Write; use std::path::{Path, PathBuf}; @@ -58,6 +58,8 @@ enum Cmd { Codegen(Codegen), #[clap(hide = true)] Resources(Resources), + #[clap(hide = true)] + Libdocgen(Libdocgen), } impl Cmd { @@ -68,6 +70,7 @@ impl Cmd { Cmd::Docgen(d) => d.run(), Cmd::Codegen(c) => c.run(), Cmd::Resources(r) => r.run(), + Cmd::Libdocgen(r) => r.run(), } } @@ -78,6 +81,7 @@ impl Cmd { Cmd::Docgen(d) => d.doc_path(), Cmd::Codegen(c) => c.doc_path(), Cmd::Resources(r) => r.doc_path(), + Cmd::Libdocgen(r) => r.doc_path(), } } } @@ -88,7 +92,7 @@ fn long_version() -> Result<String> { 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)?; + writeln!(ret, "Built from branch: {branch}")?; } else { writeln!(ret, "Branch information is missing.")?; } @@ -159,7 +163,7 @@ impl Extract { let doc = load_linted_doc(&self.filename, Style::default(), None, self.merciful)?; let files: Vec<&EmbeddedFile> = if self.embedded.is_empty() { - doc.files() + doc.embedded_files() .iter() .map(Result::Ok) .collect::<Result<Vec<_>>>() @@ -221,7 +225,7 @@ impl Metadata { #[derive(Debug, Parser)] /// Typeset subplot document /// -/// Process a subplot document and typeset it using Pandoc. +/// Process a subplot document and typeset it. struct Docgen { /// Allow warnings in document? #[clap(long)] @@ -251,14 +255,9 @@ impl Docgen { } fn run(&self) -> Result<()> { - let mut style = Style::default(); - if self.output.extension() == Some(&OsString::from("pdf")) { - trace!("PDF output chosen"); - style.typeset_links_as_notes(); - } + let style = Style::default(); let mut doc = load_linted_doc(&self.input, style, self.template.as_deref(), self.merciful)?; - let mut pandoc = pandoc::new(); // Metadata date from command line or file mtime. However, we // can't set it directly, since we don't want to override the date // in the actual document, if given, so we only set @@ -269,32 +268,29 @@ impl Docgen { } else if let Some(date) = doc.meta().date() { date.to_string() } else { - let filename = doc.meta().basedir().join(doc.meta().markdown_filename()); - Self::mtime_formatted(Self::mtime(&filename)?) - }; - pandoc.add_option(pandoc::PandocOption::Meta("date".to_string(), Some(date))); - pandoc.add_option(pandoc::PandocOption::TableOfContents); - pandoc.add_option(pandoc::PandocOption::Standalone); - pandoc.add_option(pandoc::PandocOption::NumberSections); - - if Self::need_output(&mut doc, self.template.as_deref(), &self.output) { - doc.typeset(); - pandoc.set_input_format(pandoc::InputFormat::Json, vec![]); - pandoc.set_input(pandoc::InputKind::Pipe(doc.ast()?)); - pandoc.set_output(pandoc::OutputKind::File(self.output.clone())); - - debug!("Executing pandoc to produce {}", self.output.display()); - let r = pandoc.execute(); - if let Err(pandoc::PandocError::Err(output)) = r { - let code = output.status.code().unwrap_or(127); - let stderr = String::from_utf8_lossy(&output.stderr); - error!("Failed to execute Pandoc: exit code {}", code); - error!("{}", stderr.strip_suffix('\n').unwrap()); - - return Err(anyhow::Error::msg("Pandoc failed")); + let mut newest = None; + let basedir = if let Some(basedir) = self.input.parent() { + basedir.to_path_buf() + } else { + return Err(SubplotError::BasedirError(self.input.clone()).into()); + }; + for filename in doc.meta().markdown_filenames() { + let filename = basedir.join(filename); + let mtime = Self::mtime(&filename)?; + if let Some(so_far) = newest { + if mtime > so_far { + newest = Some(mtime); + } + } else { + newest = Some(mtime); + } } - r?; - } + Self::mtime_formatted(newest.unwrap()) + }; + + doc.typeset(&mut Warnings::default(), self.template.as_deref())?; + std::fs::write(&self.output, doc.to_html(&date)?) + .map_err(|e| SubplotError::WriteFile(self.output.clone(), e))?; Ok(()) } @@ -316,24 +312,6 @@ impl Docgen { let time = OffsetDateTime::from_unix_timestamp(secs).unwrap(); time.format(DATE_FORMAT).unwrap() } - - fn need_output(doc: &mut subplot::Document, template: Option<&str>, output: &Path) -> bool { - let output = match Self::mtime(output) { - Err(_) => return true, - Ok(ts) => ts, - }; - - for filename in doc.sources(template) { - let source = match Self::mtime(&filename) { - Err(_) => return true, - Ok(ts) => ts, - }; - if source >= output { - return true; - } - } - false - } } #[derive(Debug, Parser)] @@ -397,13 +375,105 @@ impl Codegen { } } +#[derive(Debug, Parser)] +/// Generate test suites from Subplot documents +/// +/// This reads a subplot document, extracts the scenarios, and writes out a test +/// program capable of running the scenarios in the subplot document. +struct Libdocgen { + // Bindings file to read. + input: PathBuf, + + // Output document filename + #[clap(name = "FILE", long = "output", short = 'o')] + output: PathBuf, + + /// The template to use from the document. + /// + /// If not specified, subplot will try and find a unique template name from the document + #[clap(name = "TEMPLATE", long = "template", short = 't')] + template: Option<String>, + + /// Be merciful by allowing bindings to not have documentation. + #[clap(long)] + merciful: bool, +} + +impl Libdocgen { + fn doc_path(&self) -> Option<&Path> { + None + } + + fn run(&self) -> Result<()> { + debug!("libdocgen starts"); + + let mut bindings = Bindings::new(); + bindings.add_from_file(&self.input, None)?; + // println!("{:#?}", bindings); + + let mut doc = LibDoc::new(&self.input); + for b in bindings.bindings() { + // println!("{} {}", b.kind(), b.pattern()); + doc.push_binding(b); + } + + std::fs::write(&self.output, doc.to_markdown(self.merciful)?)?; + + debug!("libdogen ends successfully"); + Ok(()) + } +} + +struct LibDoc { + filename: PathBuf, + bindings: Vec<Binding>, +} + +impl LibDoc { + fn new(filename: &Path) -> Self { + Self { + filename: filename.into(), + bindings: vec![], + } + } + + fn push_binding(&mut self, binding: &Binding) { + self.bindings.push(binding.clone()); + } + + fn to_markdown(&self, merciful: bool) -> Result<String> { + let mut md = String::new(); + md.push_str(&format!("# Library `{}`\n\n", self.filename.display())); + for b in self.bindings.iter() { + md.push_str(&format!("\n## {} `{}`\n", b.kind(), b.pattern())); + if let Some(doc) = b.doc() { + md.push_str(&format!("\n{}\n", doc)); + } else if !merciful { + return Err(SubplotError::NoBindingDoc( + self.filename.clone(), + b.kind(), + b.pattern().into(), + ) + .into()); + } + if b.types().count() > 0 { + md.push_str("\nCaptures:\n\n"); + for (name, cap_type) in b.types() { + md.push_str(&format!("- `{}`: {}\n", name, cap_type.as_str())); + } + } + } + Ok(md) + } +} + fn load_linted_doc( filename: &Path, style: Style, template: Option<&str>, merciful: bool, ) -> Result<Document, SubplotError> { - let mut doc = load_document(filename, style, None)?; + let doc = load_document(filename, style, None)?; trace!("Got doc, now linting it"); doc.lint()?; trace!("Doc linted ok"); @@ -422,16 +492,24 @@ fn load_linted_doc( }; let template = template.to_string(); trace!("Template: {:#?}", template); - doc.check_named_files_exist(&template)?; - doc.check_matched_steps_have_impl(&template); - doc.check_embedded_files_are_used(&template)?; + let mut warnings = Warnings::default(); + doc.check_bindings(&mut warnings)?; + doc.check_named_code_blocks_have_appropriate_class(&mut warnings)?; + doc.check_named_files_exist(&template, &mut warnings)?; + if !template.is_empty() { + // We have a template, let's check we have implementations + doc.check_matched_steps_have_impl(&template, &mut warnings); + } else { + trace!("No template found, so cannot check impl presence"); + } + doc.check_embedded_files_are_used(&template, &mut warnings)?; - for w in doc.warnings() { + for w in warnings.warnings() { warn!("{}", w); } - if !doc.warnings().is_empty() && !merciful { - return Err(SubplotError::Warnings(doc.warnings().len())); + if !warnings.is_empty() && !merciful { + return Err(SubplotError::Warnings(warnings.len())); } Ok(doc) |