From 0a81c16df535853c68d5c833a213a81c2a1a9388 Mon Sep 17 00:00:00 2001 From: Lars Wirzenius Date: Wed, 10 May 2023 07:42:52 +0300 Subject: refactor: move document metadata out of Markdown struct It's _document_ metadata, not metadata for a specific markdown file. It belongs in Document, not Markdown. Make it so. Sponsored-by: author --- src/doc.rs | 49 ++++++++++++++++++++++++++++++++++++++++----- src/md.rs | 62 +++++---------------------------------------------------- src/metadata.rs | 7 +++++++ 3 files changed, 56 insertions(+), 62 deletions(-) diff --git a/src/doc.rs b/src/doc.rs index 459b0e7..0b0b9e7 100644 --- a/src/doc.rs +++ b/src/doc.rs @@ -1,8 +1,8 @@ use crate::bindings::CaptureType; use crate::generate_test_program; use crate::get_basedir_from; -use crate::html::Element; use crate::html::HtmlPage; +use crate::html::{Content, Element, ElementTag}; use crate::md::Markdown; use crate::EmbeddedFile; use crate::EmbeddedFiles; @@ -128,8 +128,7 @@ impl Document { let mdfile = meta.markdown(); let mdfile = basedir.join(mdfile); - let mut md = Markdown::load_file(mdfile.as_path())?; - md.set_metadata(&meta); + let md = Markdown::load_file(mdfile.as_path())?; let doc = Self::from_ast(basedir, filename.into(), &meta, md, style, template)?; @@ -144,12 +143,52 @@ impl Document { title.push_child(crate::html::Content::Text(self.meta().title().into())); head.push_child(crate::html::Content::Elt(title)); - self.md.set_date(date.into()); + self.meta.set_date(date.into()); - let page = HtmlPage::new(head, self.md.to_html()); + let mut body = self.typeset_meta(); + body.push_child(Content::Elt(self.md.root_element().clone())); + let page = HtmlPage::new(head, body); page.serialize().unwrap() // FIXME } + fn typeset_meta(&self) -> Element { + let mut div = Element::new(ElementTag::Div); + div.push_child(Content::Elt(Self::title(self.meta.title()))); + if let Some(authors) = self.meta.authors() { + div.push_child(Content::Elt(Self::authors(authors))); + } + if let Some(date) = self.meta.date() { + div.push_child(Content::Elt(Self::date(date))); + } + div + } + + fn title(title: &str) -> Element { + let mut e = Element::new(ElementTag::H1); + e.push_child(Content::Text(title.into())); + e + } + + fn authors(authors: &[String]) -> Element { + let mut list = Element::new(ElementTag::P); + list.push_child(Content::Text("By: ".into())); + let mut first = true; + for a in authors { + if !first { + list.push_child(Content::Text(", ".into())); + } + list.push_child(Content::Text(a.into())); + first = false; + } + list + } + + fn date(date: &str) -> Element { + let mut e = Element::new(ElementTag::P); + e.push_child(Content::Text(date.into())); + e + } + /// Return the document's metadata. pub fn meta(&self) -> &Metadata { &self.meta diff --git a/src/md.rs b/src/md.rs index 9ad30bc..5e56198 100644 --- a/src/md.rs +++ b/src/md.rs @@ -3,7 +3,7 @@ use crate::{ html::{parse, Attribute, Content, Element, ElementTag}, parse_scenario_snippet, Bindings, EmbeddedFile, EmbeddedFiles, Scenario, ScenarioStep, Style, - SubplotError, Warnings, YamlMetadata, + SubplotError, Warnings, }; use log::trace; use std::collections::HashSet; @@ -13,7 +13,6 @@ use std::path::{Path, PathBuf}; #[derive(Debug)] pub struct Markdown { html: Element, - meta: Option, } impl Markdown { @@ -32,63 +31,12 @@ impl Markdown { } fn new(html: Element) -> Self { - Self { html, meta: None } + Self { html } } - /// Set document metadata from subplot. - pub fn set_metadata(&mut self, meta: &YamlMetadata) { - self.meta = Some(meta.clone()); - } - - /// Set date. - pub fn set_date(&mut self, date: String) { - if let Some(meta) = &mut self.meta { - meta.set_date(date); - } - } - - /// Return parsed HTML of the markdown. - pub fn to_html(&self) -> Element { - if let Some(meta) = &self.meta { - let mut div = Element::new(ElementTag::Div); - div.push_child(Content::Elt(Self::title(meta.title()))); - if let Some(authors) = meta.authors() { - div.push_child(Content::Elt(Self::authors(authors))); - } - if let Some(date) = meta.date() { - div.push_child(Content::Elt(Self::date(date))); - } - div.push_child(Content::Elt(self.html.clone())); - div - } else { - self.html.clone() - } - } - - fn title(title: &str) -> Element { - let mut e = Element::new(ElementTag::H1); - e.push_child(Content::Text(title.into())); - e - } - - fn authors(authors: &[String]) -> Element { - let mut list = Element::new(ElementTag::P); - list.push_child(Content::Text("By: ".into())); - let mut first = true; - for a in authors { - if !first { - list.push_child(Content::Text(", ".into())); - } - list.push_child(Content::Text(a.into())); - first = false; - } - list - } - - fn date(date: &str) -> Element { - let mut e = Element::new(ElementTag::P); - e.push_child(Content::Text(date.into())); - e + /// Return root element of markdown. + pub fn root_element(&self) -> &Element { + &self.html } /// Find included images. diff --git a/src/metadata.rs b/src/metadata.rs index 9f25621..d569b40 100644 --- a/src/metadata.rs +++ b/src/metadata.rs @@ -176,6 +176,7 @@ pub struct Metadata { basedir: PathBuf, title: String, date: Option, + authors: Option>, markdown_filename: PathBuf, bindings_filenames: Vec, bindings: Bindings, @@ -234,6 +235,7 @@ impl Metadata { basedir: basedir.as_ref().to_path_buf(), title: yaml.title().into(), date: yaml.date().map(|s| s.into()), + authors: yaml.authors().map(|a| a.into()), markdown_filename: yaml.markdown().into(), bindings_filenames, bindings, @@ -261,6 +263,11 @@ impl Metadata { self.date = Some(date); } + /// Authors. + pub fn authors(&self) -> Option<&[String]> { + self.authors.as_deref() + } + /// Return base dir for all relative filenames. pub fn basedir(&self) -> &Path { &self.basedir -- cgit v1.2.1 From ace211d2c2e9c775872c4a23087763afff5a5bb2 Mon Sep 17 00:00:00 2001 From: Lars Wirzenius Date: Wed, 10 May 2023 09:10:25 +0300 Subject: feat! allow multiple markdown files for a subplot Sponsored-by: author --- src/bin/subplot.rs | 14 ++++++++-- src/doc.rs | 68 ++++++++++++++++++++++++++++++++++++------------ src/metadata.rs | 16 ++++++------ subplot-build/src/lib.rs | 4 ++- subplot.md | 38 +++++++++++++++++++++++++++ 5 files changed, 112 insertions(+), 28 deletions(-) diff --git a/src/bin/subplot.rs b/src/bin/subplot.rs index 41d8894..8da6c2b 100644 --- a/src/bin/subplot.rs +++ b/src/bin/subplot.rs @@ -264,8 +264,18 @@ 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)?) + let mut newest = None; + for filename in doc.meta().markdown_filenames() { + let mtime = Self::mtime(filename)?; + if let Some(so_far) = newest { + if mtime > so_far { + newest = Some(mtime); + } + } else { + newest = Some(mtime); + } + } + Self::mtime_formatted(newest.unwrap()) }; if Self::need_output(&mut doc, self.template.as_deref(), &self.output) { diff --git a/src/doc.rs b/src/doc.rs index 0b0b9e7..640968c 100644 --- a/src/doc.rs +++ b/src/doc.rs @@ -66,7 +66,7 @@ static KNOWN_BLOCK_CLASSES: &[&str] = &["numberLines", "noNumberLines"]; #[derive(Debug)] pub struct Document { subplot: PathBuf, - md: Markdown, + markdowns: Vec, meta: Metadata, files: EmbeddedFiles, style: Style, @@ -75,14 +75,14 @@ pub struct Document { impl Document { fn new( subplot: PathBuf, - md: Markdown, + markdowns: Vec, meta: Metadata, files: EmbeddedFiles, style: Style, ) -> Document { let doc = Document { subplot, - md, + markdowns, meta, files, style, @@ -95,7 +95,7 @@ impl Document { basedir: P, subplot: PathBuf, yamlmeta: &YamlMetadata, - md: Markdown, + markdowns: Vec, style: Style, template: Option<&str>, ) -> Result @@ -104,12 +104,22 @@ impl Document { { let meta = Metadata::from_yaml_metadata(basedir, yamlmeta, template)?; trace!("metadata from YAML: {:#?}", meta); - let files = md.embedded_files(); - let doc = Document::new(subplot, md, meta, files, style); + let files = Self::all_files(&markdowns); + let doc = Document::new(subplot, markdowns, meta, files, style); trace!("Loaded from JSON OK"); Ok(doc) } + fn all_files(markdowns: &[Markdown]) -> EmbeddedFiles { + let mut files = EmbeddedFiles::default(); + for md in markdowns { + for file in md.embedded_files().files() { + files.push(file.clone()); + } + } + files + } + /// Construct a Document from a named file. pub fn from_file( basedir: &Path, @@ -126,11 +136,13 @@ impl Document { let meta = load_metadata_from_yaml_file(filename)?; trace!("metadata from YAML file: {:#?}", meta); - let mdfile = meta.markdown(); - let mdfile = basedir.join(mdfile); - let md = Markdown::load_file(mdfile.as_path())?; + let mut markdowns = vec![]; + for filename in meta.markdowns() { + let filename = basedir.join(filename); + markdowns.push(Markdown::load_file(&filename)?); + } - let doc = Self::from_ast(basedir, filename.into(), &meta, md, style, template)?; + let doc = Self::from_ast(basedir, filename.into(), &meta, markdowns, style, template)?; trace!("Loaded document OK"); Ok(doc) @@ -146,7 +158,9 @@ impl Document { self.meta.set_date(date.into()); let mut body = self.typeset_meta(); - body.push_child(Content::Elt(self.md.root_element().clone())); + for md in self.markdowns.iter() { + body.push_child(Content::Elt(md.root_element().clone())); + } let page = HtmlPage::new(head, body); page.serialize().unwrap() // FIXME } @@ -230,10 +244,14 @@ impl Document { names.push(PathBuf::from(x)) } - names.push(self.meta().markdown_filename().to_path_buf()); + for name in self.meta().markdown_filenames() { + names.push(name.into()); + } - let mut images = self.md.images(); - names.append(&mut images); + for md in self.markdowns.iter() { + let mut images = md.images(); + names.append(&mut images); + } names } @@ -276,7 +294,7 @@ impl Document { /// Check that all the block classes in the document are known fn check_block_classes(&self) -> Result<(), SubplotError> { - let classes_in_doc = self.md.block_classes(); + let classes_in_doc = self.all_block_classes(); // Build the set of known good classes let mut known_classes: HashSet = HashSet::new(); @@ -299,6 +317,16 @@ impl Document { } } + fn all_block_classes(&self) -> HashSet { + let mut set = HashSet::new(); + for md in self.markdowns.iter() { + for class in md.block_classes() { + set.insert(class); + } + } + set + } + /// Check that all named files (in matched steps) are actually present in the /// document. pub fn check_named_files_exist( @@ -402,12 +430,18 @@ impl Document { /// Typeset a Subplot document. pub fn typeset(&mut self, warnings: &mut Warnings) { - warnings.push_all(self.md.typeset(self.style.clone(), self.meta.bindings())); + for md in self.markdowns.iter_mut() { + warnings.push_all(md.typeset(self.style.clone(), self.meta.bindings())); + } } /// Return all scenarios in a document. pub fn scenarios(&self) -> Result, SubplotError> { - self.md.scenarios() + let mut scenarios = vec![]; + for md in self.markdowns.iter() { + scenarios.append(&mut md.scenarios()?); + } + Ok(scenarios) } /// Return matched scenarios in a document. diff --git a/src/metadata.rs b/src/metadata.rs index d569b40..e4d3d53 100644 --- a/src/metadata.rs +++ b/src/metadata.rs @@ -63,9 +63,9 @@ impl YamlMetadata { Ok(meta) } - /// Name of file with the Markdown for the subplot document. - pub fn markdown(&self) -> &Path { - &self.markdowns[0] + /// Names of files with the Markdown for the subplot document. + pub fn markdowns(&self) -> &[PathBuf] { + &self.markdowns } /// Title. @@ -177,7 +177,7 @@ pub struct Metadata { title: String, date: Option, authors: Option>, - markdown_filename: PathBuf, + markdown_filenames: Vec, bindings_filenames: Vec, bindings: Bindings, impls: HashMap, @@ -236,7 +236,7 @@ impl Metadata { title: yaml.title().into(), date: yaml.date().map(|s| s.into()), authors: yaml.authors().map(|a| a.into()), - markdown_filename: yaml.markdown().into(), + markdown_filenames: yaml.markdowns().into(), bindings_filenames, bindings, impls, @@ -273,9 +273,9 @@ impl Metadata { &self.basedir } - /// Return filename of the markdown file. - pub fn markdown_filename(&self) -> &Path { - &self.markdown_filename + /// Return filenames of the markdown files. + pub fn markdown_filenames(&self) -> &[PathBuf] { + &self.markdown_filenames } /// Return filename where bindings are specified. diff --git a/subplot-build/src/lib.rs b/subplot-build/src/lib.rs index 7f194c5..20d3e08 100644 --- a/subplot-build/src/lib.rs +++ b/subplot-build/src/lib.rs @@ -54,7 +54,9 @@ where // re-running. let base_path = get_basedir_from(filename); let meta = output.doc.meta(); - buildrs_deps(&base_path, Some(meta.markdown_filename())); + for filename in meta.markdown_filenames() { + buildrs_deps(&base_path, Some(filename.as_path())); + } buildrs_deps(&base_path, meta.bindings_filenames()); let docimpl = output .doc diff --git a/subplot.md b/subplot.md index 05edeae..8556afd 100644 --- a/subplot.md +++ b/subplot.md @@ -2685,6 +2685,44 @@ bibliography: [foo.bib, bar.bib] ~~~ +### Multiple markdown files + +This scenario tests that the `markdowns` field in metadata can specify +more than one markdown file. + +~~~scenario +given file multimd.subplot +given file md1.md +given file md2.md +given an installed subplot +when I run subplot docgen multimd.subplot -o multimd.html +when I run cat multimd.html +then file multimd.html exists +and file multimd.html contains "The Fabulous Title" +and file multimd.html contains "First markdown file." +and file multimd.html contains "Second markdown file." +~~~ + +~~~{#multimd.subplot .file .yaml .numberLines} +title: The Fabulous Title +authors: +- Alfred Pennyworth +- Geoffrey Butler +date: WIP +markdowns: +- md1.md +- md2.md +~~~ + +~~~{#md1.md .file .markdown .numberLines} +First markdown file. +~~~ + +~~~{#md2.md .file .markdown .numberLines} +Second markdown file. +~~~ + + ## Embedded files Subplot allows data files to be embedded in the input document. This -- cgit v1.2.1