summaryrefslogtreecommitdiff
path: root/src/bin/subplot.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/bin/subplot.rs')
-rw-r--r--src/bin/subplot.rs200
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)