diff options
author | Daniel Silverstone <dsilvers+gitlab@digital-scurf.org> | 2023-11-11 10:49:14 +0000 |
---|---|---|
committer | Daniel Silverstone <dsilvers+gitlab@digital-scurf.org> | 2023-11-11 10:49:14 +0000 |
commit | 16521f5e92c06c978c148aa69bd5223eb126aea5 (patch) | |
tree | 9b060a977b768ebc7ce42338fd82076f815c1d6b /src | |
parent | dccf8d75321fca24caa8efd3a6eb41bc7af15f2f (diff) | |
parent | 6a8a210de12bb9e79d9d610a7ca091567dc9550a (diff) | |
download | subplot-16521f5e92c06c978c148aa69bd5223eb126aea5.tar.gz |
Merge branch 'liw/libdoc' into 'main'
feat: generate library documentation from bindings files
Closes #341
See merge request subplot/subplot!362
Diffstat (limited to 'src')
-rw-r--r-- | src/bin/subplot.rs | 89 | ||||
-rw-r--r-- | src/bindings.rs | 51 |
2 files changed, 125 insertions, 15 deletions
diff --git a/src/bin/subplot.rs b/src/bin/subplot.rs index 6c118d4..40f0f55 100644 --- a/src/bin/subplot.rs +++ b/src/bin/subplot.rs @@ -6,8 +6,8 @@ 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, - Warnings, + codegen, load_document, resource, Binding, Bindings, Document, EmbeddedFile, MarkupOpts, Style, + SubplotError, Warnings, }; use time::{format_description::FormatItem, macros::format_description, OffsetDateTime}; @@ -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(), } } } @@ -371,6 +375,87 @@ 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>, +} + +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())?; + + 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) -> 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)); + } + 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())); + } + } + } + md + } +} + fn load_linted_doc( filename: &Path, style: Style, diff --git a/src/bindings.rs b/src/bindings.rs index b629992..5fe5887 100644 --- a/src/bindings.rs +++ b/src/bindings.rs @@ -166,6 +166,7 @@ pub struct Binding { regex: Regex, impls: HashMap<String, Arc<BindingImpl>>, types: HashMap<String, CaptureType>, + doc: Option<String>, } impl Binding { @@ -175,6 +176,7 @@ impl Binding { pattern: &str, case_sensitive: bool, mut types: HashMap<String, CaptureType>, + doc: Option<String>, ) -> Result<Binding, SubplotError> { let regex = RegexBuilder::new(&format!("^{pattern}$")) .case_insensitive(!case_sensitive) @@ -193,6 +195,7 @@ impl Binding { regex, impls: HashMap::new(), types, + doc, }) } @@ -214,6 +217,11 @@ impl Binding { &self.pattern } + /// Return documentation string for binding, if any. + pub fn doc(&self) -> Option<&str> { + self.doc.as_deref() + } + /// Retrieve a particular implementation by name pub fn step_impl(&self, template: &str) -> Option<Arc<BindingImpl>> { self.impls.get(template).cloned() @@ -319,7 +327,7 @@ mod test_binding { #[test] fn creates_new() { - let b = Binding::new(StepKind::Given, "I am Tomjon", false, HashMap::new()).unwrap(); + let b = Binding::new(StepKind::Given, "I am Tomjon", false, HashMap::new(), None).unwrap(); assert_eq!(b.kind(), StepKind::Given); assert!(b.regex().is_match("I am Tomjon")); assert!(!b.regex().is_match("I am Tomjon of Lancre")); @@ -328,19 +336,20 @@ mod test_binding { #[test] fn equal() { - let a = Binding::new(StepKind::Given, "I am Tomjon", false, HashMap::new()).unwrap(); - let b = Binding::new(StepKind::Given, "I am Tomjon", false, HashMap::new()).unwrap(); + let a = Binding::new(StepKind::Given, "I am Tomjon", false, HashMap::new(), None).unwrap(); + let b = Binding::new(StepKind::Given, "I am Tomjon", false, HashMap::new(), None).unwrap(); assert_eq!(a, b); } #[test] fn not_equal() { - let a = Binding::new(StepKind::Given, "I am Tomjon", false, HashMap::new()).unwrap(); + let a = Binding::new(StepKind::Given, "I am Tomjon", false, HashMap::new(), None).unwrap(); let b = Binding::new( StepKind::Given, "I am Tomjon of Lancre", false, HashMap::new(), + None, ) .unwrap(); assert_ne!(a, b); @@ -349,21 +358,21 @@ mod test_binding { #[test] fn does_not_match_with_wrong_kind() { let step = ScenarioStep::new(StepKind::Given, "given", "yo", Location::Unknown); - let b = Binding::new(StepKind::When, "yo", false, HashMap::new()).unwrap(); + let b = Binding::new(StepKind::When, "yo", false, HashMap::new(), None).unwrap(); assert!(b.match_with_step("", &step).is_none()); } #[test] fn does_not_match_with_wrong_text() { let step = ScenarioStep::new(StepKind::Given, "given", "foo", Location::Unknown); - let b = Binding::new(StepKind::Given, "bar", false, HashMap::new()).unwrap(); + let b = Binding::new(StepKind::Given, "bar", false, HashMap::new(), None).unwrap(); assert!(b.match_with_step("", &step).is_none()); } #[test] fn match_with_fixed_pattern() { let step = ScenarioStep::new(StepKind::Given, "given", "foo", Location::Unknown); - let b = Binding::new(StepKind::Given, "foo", false, HashMap::new()).unwrap(); + let b = Binding::new(StepKind::Given, "foo", false, HashMap::new(), None).unwrap(); let m = b.match_with_step("", &step).unwrap(); assert_eq!(m.kind(), StepKind::Given); let mut parts = m.parts(); @@ -385,6 +394,7 @@ mod test_binding { r"I am (?P<who>\S+), I am", false, HashMap::new(), + None, ) .unwrap(); let m = b.match_with_step("", &step).unwrap(); @@ -399,9 +409,9 @@ mod test_binding { #[test] fn case_sensitive_mismatch() { let step = ScenarioStep::new(StepKind::Given, "given", "I am Tomjon", Location::Unknown); - let b = Binding::new(StepKind::Given, r"i am tomjon", false, HashMap::new()).unwrap(); + let b = Binding::new(StepKind::Given, r"i am tomjon", false, HashMap::new(), None).unwrap(); assert!(b.match_with_step("", &step).is_some()); - let b = Binding::new(StepKind::Given, r"i am tomjon", true, HashMap::new()).unwrap(); + let b = Binding::new(StepKind::Given, r"i am tomjon", true, HashMap::new(), None).unwrap(); assert!(b.match_with_step("", &step).is_none()); } } @@ -445,6 +455,7 @@ struct ParsedBinding { case_sensitive: bool, #[serde(default)] types: HashMap<String, CaptureType>, + doc: Option<String>, } #[derive(Debug, Deserialize)] @@ -578,7 +589,13 @@ fn from_hashmap(parsed: &ParsedBinding) -> Result<Binding, SubplotError> { trace!("Successfully acquired binding"); - let mut ret = Binding::new(kind, &pattern, parsed.case_sensitive, types)?; + let mut ret = Binding::new( + kind, + &pattern, + parsed.case_sensitive, + types, + parsed.doc.clone(), + )?; trace!("Binding parsed OK"); for (template, pimpl) in &parsed.impls { ret.add_impl(template, &pimpl.function, pimpl.cleanup.as_deref()); @@ -612,6 +629,7 @@ mod test_bindings { r"I am (?P<name>\S+)", false, HashMap::new(), + None, ) .unwrap(); let mut bindings = Bindings::new(); @@ -694,7 +712,8 @@ mod test_bindings { #[test] fn does_not_find_match_for_unmatching_kind() { let step = ScenarioStep::new(StepKind::Given, "given", "I am Tomjon", Location::Unknown); - let binding = Binding::new(StepKind::When, r"I am Tomjon", false, HashMap::new()).unwrap(); + let binding = + Binding::new(StepKind::When, r"I am Tomjon", false, HashMap::new(), None).unwrap(); let mut bindings = Bindings::new(); bindings.add(binding); assert!(matches!( @@ -711,6 +730,7 @@ mod test_bindings { r"I am Tomjon of Lancre", false, HashMap::new(), + None, ) .unwrap(); let mut bindings = Bindings::new(); @@ -725,7 +745,9 @@ mod test_bindings { fn two_matching_bindings() { let step = ScenarioStep::new(StepKind::Given, "given", "I am Tomjon", Location::Unknown); let mut bindings = Bindings::default(); - bindings.add(Binding::new(StepKind::Given, r"I am Tomjon", false, HashMap::new()).unwrap()); + bindings.add( + Binding::new(StepKind::Given, r"I am Tomjon", false, HashMap::new(), None).unwrap(), + ); bindings.add( Binding::new( StepKind::Given, @@ -733,6 +755,7 @@ mod test_bindings { .unwrap(), false, HashMap::new(), + None, ) .unwrap(), ); @@ -745,7 +768,8 @@ mod test_bindings { #[test] fn finds_match_for_fixed_string_pattern() { let step = ScenarioStep::new(StepKind::Given, "given", "I am Tomjon", Location::Unknown); - let binding = Binding::new(StepKind::Given, r"I am Tomjon", false, HashMap::new()).unwrap(); + let binding = + Binding::new(StepKind::Given, r"I am Tomjon", false, HashMap::new(), None).unwrap(); let mut bindings = Bindings::new(); bindings.add(binding); let m = bindings.find("", &step).unwrap(); @@ -767,6 +791,7 @@ mod test_bindings { r"I am (?P<name>\S+)", false, HashMap::new(), + None, ) .unwrap(); let mut bindings = Bindings::new(); |