summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLars Wirzenius <liw@liw.fi>2022-11-09 13:58:22 +0000
committerLars Wirzenius <liw@liw.fi>2022-11-09 13:58:22 +0000
commitafcea855723136707a791167e7823b5dfdc0fd8a (patch)
tree1041e81264145d66a5b168b82c546234b36d80cc
parent4b270f450b7a2ae1341ea22b8b373ba2cd8aa96c (diff)
parent8f17b2a721d2369691ff2e3d4950a436aa9533ab (diff)
downloadriki-afcea855723136707a791167e7823b5dfdc0fd8a.tar.gz
Merge branch 'liw/toc' into 'main'
add rudimentary ToC; cleanups; refactoring See merge request larswirzenius/riki!67
-rw-r--r--riki.md20
-rw-r--r--src/directive/calendar.rs19
-rw-r--r--src/directive/format.rs19
-rw-r--r--src/directive/graph.rs20
-rw-r--r--src/directive/img.rs201
-rw-r--r--src/directive/inline.rs35
-rw-r--r--src/directive/map.rs19
-rw-r--r--src/directive/meta.rs50
-rw-r--r--src/directive/mod.rs50
-rw-r--r--src/directive/pagestats.rs19
-rw-r--r--src/directive/shortcut.rs33
-rw-r--r--src/directive/sidebar.rs19
-rw-r--r--src/directive/table.rs23
-rw-r--r--src/directive/tag.rs25
-rw-r--r--src/directive/toc.rs83
-rw-r--r--src/directive/traillink.rs19
-rw-r--r--src/error.rs3
-rw-r--r--src/html.rs8
-rw-r--r--src/page.rs23
-rw-r--r--src/site.rs2
-rw-r--r--src/util.rs4
-rw-r--r--src/wikitext.rs16
22 files changed, 411 insertions, 299 deletions
diff --git a/riki.md b/riki.md
index 4c78f11..980e5b4 100644
--- a/riki.md
+++ b/riki.md
@@ -677,6 +677,26 @@ goodbye | cruel world
"""]]
~~~
+### `toc`
+
+_Requirement: the `toc` directive creates a table of contents._
+
+~~~scenario
+given an installed riki
+given file site/index.mdwn from toc
+when I run riki build site output
+when I run cat output/index.html
+then file output/index.html contains "<li>Introduction</li>"
+then file output/index.html contains "<li>Acknowledgements</li>"
+~~~
+
+~~~{#toc .file .markdown}
+[[!toc]]
+
+# Introduction
+## Acknowledgements
+~~~
+
## Source file tree
### Listing source files
diff --git a/src/directive/calendar.rs b/src/directive/calendar.rs
index a11cc52..f29aaf9 100644
--- a/src/directive/calendar.rs
+++ b/src/directive/calendar.rs
@@ -1,3 +1,4 @@
+use crate::directive::{DirectiveImplementation, Processed};
use crate::error::SiteError;
use crate::page::PageMeta;
use crate::site::Site;
@@ -6,9 +7,9 @@ use crate::wikitext::ParsedDirective;
#[derive(Debug, Default, Eq, PartialEq)]
pub struct Calendar {}
-impl Calendar {
- pub const REQUIRED: &'static [&'static str] = &[];
- pub const ALLOWED: &'static [&'static str] = &[
+impl DirectiveImplementation for Calendar {
+ const REQUIRED: &'static [&'static str] = &[];
+ const ALLOWED: &'static [&'static str] = &[
"type",
"pages",
"year",
@@ -17,15 +18,13 @@ impl Calendar {
"months_per_row",
"archivebase",
];
- pub const ALLOW_ANY_UNNAMED: bool = true;
+ const ALLOW_ANY_UNNAMED: bool = true;
- pub fn process(&self, _site: &Site, _meta: &mut PageMeta) -> Result<String, SiteError> {
- Err(SiteError::UnimplementedDirective("calendar".into()))
+ fn from_parsed(_: &ParsedDirective) -> Self {
+ Self::default()
}
-}
-impl From<&ParsedDirective> for Calendar {
- fn from(_: &ParsedDirective) -> Self {
- Calendar::default()
+ fn process(&self, _site: &Site, _meta: &mut PageMeta) -> Result<Processed, SiteError> {
+ Err(SiteError::UnimplementedDirective("calendar".into()))
}
}
diff --git a/src/directive/format.rs b/src/directive/format.rs
index ee9c7ec..66741ff 100644
--- a/src/directive/format.rs
+++ b/src/directive/format.rs
@@ -1,3 +1,4 @@
+use crate::directive::{DirectiveImplementation, Processed};
use crate::error::SiteError;
use crate::page::PageMeta;
use crate::site::Site;
@@ -6,18 +7,16 @@ use crate::wikitext::ParsedDirective;
#[derive(Debug, Default, Eq, PartialEq)]
pub struct Format {}
-impl Format {
- pub const REQUIRED: &'static [&'static str] = &[];
- pub const ALLOWED: &'static [&'static str] = &[];
- pub const ALLOW_ANY_UNNAMED: bool = true;
+impl DirectiveImplementation for Format {
+ const REQUIRED: &'static [&'static str] = &[];
+ const ALLOWED: &'static [&'static str] = &[];
+ const ALLOW_ANY_UNNAMED: bool = true;
- pub fn process(&self, _site: &Site, _meta: &mut PageMeta) -> Result<String, SiteError> {
- Err(SiteError::UnimplementedDirective("format".into()))
+ fn from_parsed(_: &ParsedDirective) -> Self {
+ Self::default()
}
-}
-impl From<&ParsedDirective> for Format {
- fn from(_: &ParsedDirective) -> Self {
- Format::default()
+ fn process(&self, _site: &Site, _meta: &mut PageMeta) -> Result<Processed, SiteError> {
+ Err(SiteError::UnimplementedDirective("format".into()))
}
}
diff --git a/src/directive/graph.rs b/src/directive/graph.rs
index 8234ed2..ae90050 100644
--- a/src/directive/graph.rs
+++ b/src/directive/graph.rs
@@ -1,3 +1,4 @@
+use crate::directive::{DirectiveImplementation, Processed};
use crate::error::SiteError;
use crate::page::PageMeta;
use crate::site::Site;
@@ -6,18 +7,17 @@ use crate::wikitext::ParsedDirective;
#[derive(Debug, Default, Eq, PartialEq)]
pub struct Graph {}
-impl Graph {
- pub const REQUIRED: &'static [&'static str] = &[];
- pub const ALLOWED: &'static [&'static str] = &["src", "type"];
- pub const ALLOW_ANY_UNNAMED: bool = true;
+impl DirectiveImplementation for Graph {
+ const REQUIRED: &'static [&'static str] = &[];
+ const ALLOWED: &'static [&'static str] = &["src", "type"];
+ const ALLOW_ANY_UNNAMED: bool = true;
- pub fn process(&self, _site: &Site, _meta: &mut PageMeta) -> Result<String, SiteError> {
- Err(SiteError::UnimplementedDirective("graph".into()))
+ fn from_parsed(_: &ParsedDirective) -> Self {
+ Self::default()
}
-}
-impl From<&ParsedDirective> for Graph {
- fn from(_: &ParsedDirective) -> Self {
- Graph::default()
+ fn process(&self, _site: &Site, _meta: &mut PageMeta) -> Result<Processed, SiteError> {
+ Err(SiteError::UnimplementedDirective("graph".into()))
}
+
}
diff --git a/src/directive/img.rs b/src/directive/img.rs
index 3ee6501..8b74e5e 100644
--- a/src/directive/img.rs
+++ b/src/directive/img.rs
@@ -1,3 +1,4 @@
+use crate::directive::{DirectiveImplementation, Processed};
use crate::error::SiteError;
use crate::page::PageMeta;
use crate::site::Site;
@@ -21,70 +22,67 @@ pub struct Img {
width: Option<usize>,
}
-impl Img {
- pub const REQUIRED: &'static [&'static str] = &[];
- pub const ALLOWED: &'static [&'static str] = &[
+impl DirectiveImplementation for Img {
+ const REQUIRED: &'static [&'static str] = &[];
+ const ALLOWED: &'static [&'static str] = &[
"align", "alt", "class", "hspace", "id", "link", "size", "title", "vspace",
];
- pub const ALLOW_ANY_UNNAMED: bool = true;
+ const ALLOW_ANY_UNNAMED: bool = true;
- fn new(src: String) -> Self {
- Self {
- src,
- link: true,
- align: None,
- alt: None,
- class: None,
- height: None,
- hspace: None,
- id: None,
- title: None,
- vspace: None,
- width: None,
- }
- }
+ fn from_parsed(p: &ParsedDirective) -> Self {
+ let unnamed = p.unnamed_args().pop().unwrap();
+ let mut img = Img::new(unnamed.into());
+ let args = p.args();
- fn link(&mut self, link: bool) {
- self.link = link;
- }
+ if let Some(link) = args.get("link") {
+ if *link == "no" {
+ img.link(false);
+ }
+ }
- fn align(&mut self, align: String) {
- self.align = Some(align);
- }
+ if let Some(size) = args.get("size") {
+ if let Some((w, h)) = size.split_once('x') {
+ if let Ok(w) = w.parse() {
+ img.width(w);
+ }
+ if let Ok(h) = h.parse() {
+ img.height(h);
+ }
+ }
+ }
- fn alt(&mut self, alt: String) {
- self.alt = Some(alt);
- }
+ if let Some(align) = args.get("align") {
+ img.align(align.to_string());
+ }
- fn class(&mut self, class: String) {
- self.class = Some(class);
- }
+ if let Some(alt) = args.get("alt") {
+ img.alt(alt.to_string());
+ }
- fn height(&mut self, h: usize) {
- self.height = Some(h);
- }
+ if let Some(class) = args.get("class") {
+ img.class(class.to_string());
+ }
- fn hspace(&mut self, hspace: String) {
- self.hspace = Some(hspace);
- }
+ if let Some(hspace) = args.get("hspace") {
+ img.hspace(hspace.to_string());
+ }
- fn id(&mut self, id: String) {
- self.id = Some(id);
- }
+ if let Some(id) = args.get("id") {
+ img.id(id.to_string());
+ }
- fn title(&mut self, title: String) {
- self.title = Some(title);
- }
+ if let Some(title) = args.get("title") {
+ img.title(title.to_string());
+ }
- fn vspace(&mut self, vspace: String) {
- self.vspace = Some(vspace);
- }
+ if let Some(vspace) = args.get("vspace") {
+ img.vspace(vspace.to_string());
+ }
- fn width(&mut self, w: usize) {
- self.width = Some(w);
+ img
}
- pub fn process(&self, site: &Site, meta: &mut PageMeta) -> Result<String, SiteError> {
+ fn process(&self, site: &Site, meta: &mut PageMeta) -> Result<Processed, SiteError> {
trace!(
"verify image exists: {} on {}",
self.src,
@@ -123,71 +121,74 @@ impl Img {
img.push_str("</a>");
}
- Ok(img)
+ Ok(Processed::Markdown(img))
}
}
-fn push_attr(s: &mut String, name: &str, value: &Option<String>) {
- if let Some(v) = value {
- s.push_str(&format!(
- " {}=\"{}\"",
- name,
- encode_double_quoted_attribute(v)
- ));
+impl Img {
+ fn new(src: String) -> Self {
+ Self {
+ src,
+ link: true,
+ align: None,
+ alt: None,
+ class: None,
+ height: None,
+ hspace: None,
+ id: None,
+ title: None,
+ vspace: None,
+ width: None,
+ }
}
-}
-impl From<&ParsedDirective> for Img {
- fn from(p: &ParsedDirective) -> Self {
- let unnamed = p.unnamed_args().pop().unwrap();
- let mut img = Img::new(unnamed.into());
- let args = p.args();
+ fn link(&mut self, link: bool) {
+ self.link = link;
+ }
- if let Some(link) = args.get("link") {
- if *link == "no" {
- img.link(false);
- }
- }
+ fn align(&mut self, align: String) {
+ self.align = Some(align);
+ }
- if let Some(size) = args.get("size") {
- if let Some((w, h)) = size.split_once('x') {
- if let Ok(w) = w.parse() {
- img.width(w);
- }
- if let Ok(h) = h.parse() {
- img.height(h);
- }
- }
- }
+ fn alt(&mut self, alt: String) {
+ self.alt = Some(alt);
+ }
- if let Some(align) = args.get("align") {
- img.align(align.to_string());
- }
+ fn class(&mut self, class: String) {
+ self.class = Some(class);
+ }
- if let Some(alt) = args.get("alt") {
- img.alt(alt.to_string());
- }
+ fn height(&mut self, h: usize) {
+ self.height = Some(h);
+ }
- if let Some(class) = args.get("class") {
- img.class(class.to_string());
- }
+ fn hspace(&mut self, hspace: String) {
+ self.hspace = Some(hspace);
+ }
- if let Some(hspace) = args.get("hspace") {
- img.hspace(hspace.to_string());
- }
+ fn id(&mut self, id: String) {
+ self.id = Some(id);
+ }
- if let Some(id) = args.get("id") {
- img.id(id.to_string());
- }
+ fn title(&mut self, title: String) {
+ self.title = Some(title);
+ }
- if let Some(title) = args.get("title") {
- img.title(title.to_string());
- }
+ fn vspace(&mut self, vspace: String) {
+ self.vspace = Some(vspace);
+ }
- if let Some(vspace) = args.get("vspace") {
- img.vspace(vspace.to_string());
- }
+ fn width(&mut self, w: usize) {
+ self.width = Some(w);
+ }
+}
- img
+fn push_attr(s: &mut String, name: &str, value: &Option<String>) {
+ if let Some(v) = value {
+ s.push_str(&format!(
+ " {}=\"{}\"",
+ name,
+ encode_double_quoted_attribute(v)
+ ));
}
}
diff --git a/src/directive/inline.rs b/src/directive/inline.rs
index 78872b4..9ddeef4 100644
--- a/src/directive/inline.rs
+++ b/src/directive/inline.rs
@@ -1,3 +1,4 @@
+use crate::directive::{DirectiveImplementation, Processed};
use crate::error::SiteError;
use crate::page::PageMeta;
use crate::pagespec::PageSpec;
@@ -11,9 +12,9 @@ pub struct Inline {
pages: String,
}
-impl Inline {
- pub const REQUIRED: &'static [&'static str] = &["pages"];
- pub const ALLOWED: &'static [&'static str] = &[
+impl DirectiveImplementation for Inline {
+ const REQUIRED: &'static [&'static str] = &["pages"];
+ const ALLOWED: &'static [&'static str] = &[
"actions",
"archive",
"description",
@@ -29,21 +30,29 @@ impl Inline {
"template",
"trail",
];
- pub const ALLOW_ANY_UNNAMED: bool = true;
+ const ALLOW_ANY_UNNAMED: bool = true;
- pub fn new(pages: String) -> Self {
- Self { pages }
+ fn from_parsed(p: &ParsedDirective) -> Self {
+ let args = p.args();
+ let pages = args.get("pages").unwrap();
+ Inline::new(pages.to_string())
}
- pub fn process(&self, site: &Site, meta: &mut PageMeta) -> Result<String, SiteError> {
+ fn process(&self, site: &Site, meta: &mut PageMeta) -> Result<Processed, SiteError> {
let pagespec = PageSpec::new(meta.path(), &self.pages)?;
let matches: Vec<String> = site
.markdown_pages()
.iter()
- .filter(|page| pagespec.matches(&site, page.meta().path()))
+ .filter(|page| pagespec.matches(site, page.meta().path()))
.map(|page| format!("* {}\n", Self::link(meta.path(), page.meta())))
.collect();
- Ok(matches.join(""))
+ Ok(Processed::Markdown(matches.join("")))
+ }
+}
+
+impl Inline {
+ pub fn new(pages: String) -> Self {
+ Self { pages }
}
fn link(container: &Path, meta: &PageMeta) -> String {
@@ -51,11 +60,3 @@ impl Inline {
format!("[{}]({})", meta.title(), link.display())
}
}
-
-impl From<&ParsedDirective> for Inline {
- fn from(p: &ParsedDirective) -> Self {
- let args = p.args();
- let pages = args.get("pages").unwrap();
- Inline::new(pages.to_string())
- }
-}
diff --git a/src/directive/map.rs b/src/directive/map.rs
index 960b339..e468aad 100644
--- a/src/directive/map.rs
+++ b/src/directive/map.rs
@@ -1,3 +1,4 @@
+use crate::directive::{DirectiveImplementation, Processed};
use crate::error::SiteError;
use crate::page::PageMeta;
use crate::site::Site;
@@ -6,18 +7,16 @@ use crate::wikitext::ParsedDirective;
#[derive(Debug, Default, Eq, PartialEq)]
pub struct Map {}
-impl Map {
- pub const REQUIRED: &'static [&'static str] = &["pages"];
- pub const ALLOWED: &'static [&'static str] = &["show"];
- pub const ALLOW_ANY_UNNAMED: bool = true;
+impl DirectiveImplementation for Map {
+ const REQUIRED: &'static [&'static str] = &["pages"];
+ const ALLOWED: &'static [&'static str] = &["show"];
+ const ALLOW_ANY_UNNAMED: bool = true;
- pub fn process(&self, _site: &Site, _meta: &mut PageMeta) -> Result<String, SiteError> {
- Err(SiteError::UnimplementedDirective("map".into()))
+ fn from_parsed(_: &ParsedDirective) -> Self {
+ Self::default()
}
-}
-impl From<&ParsedDirective> for Map {
- fn from(_: &ParsedDirective) -> Self {
- Map::default()
+ fn process(&self, _site: &Site, _meta: &mut PageMeta) -> Result<Processed, SiteError> {
+ Err(SiteError::UnimplementedDirective("map".into()))
}
}
diff --git a/src/directive/meta.rs b/src/directive/meta.rs
index 490ffb1..882288b 100644
--- a/src/directive/meta.rs
+++ b/src/directive/meta.rs
@@ -1,3 +1,4 @@
+use crate::directive::{DirectiveImplementation, Processed};
use crate::error::SiteError;
use crate::page::PageMeta;
use crate::site::Site;
@@ -10,43 +11,40 @@ pub struct Meta {
date: Option<String>,
}
-impl Meta {
- pub const REQUIRED: &'static [&'static str] = &[];
- pub const ALLOWED: &'static [&'static str] = &["date", "link", "title", "author"];
- pub const ALLOW_ANY_UNNAMED: bool = false;
-
- fn set_date(&mut self, date: &str) {
- self.date = Some(date.into());
- }
+impl DirectiveImplementation for Meta {
+ const REQUIRED: &'static [&'static str] = &[];
+ const ALLOWED: &'static [&'static str] = &["date", "link", "title", "author"];
+ const ALLOW_ANY_UNNAMED: bool = false;
- fn set_title(&mut self, title: &str) {
- self.title = Some(title.into());
+ fn from_parsed(p: &ParsedDirective) -> Self {
+ let mut meta = Self::default();
+ let args = p.args();
+ if let Some(title) = args.get("title") {
+ meta.set_title(title);
+ }
+ if let Some(date) = args.get("date") {
+ meta.set_date(date);
+ }
+ meta
}
- pub fn process(&self, _site: &Site, meta: &mut PageMeta) -> Result<String, SiteError> {
+ fn process(&self, _site: &Site, meta: &mut PageMeta) -> Result<Processed, SiteError> {
if let Some(title) = &self.title {
meta.set_title(title.into());
}
if let Some(mtime) = &self.date {
meta.set_mtime(parse_timestamp(mtime)?);
}
- Ok("".into())
+ Ok(Processed::Markdown("".into()))
}
}
-impl From<&ParsedDirective> for Meta {
- fn from(p: &ParsedDirective) -> Self {
- let mut meta = Meta::default();
- let args = p.args();
- if let Some(title) = args.get("title") {
- meta.set_title(title);
- }
- if let Some(date) = args.get("date") {
- meta.set_date(date);
- }
- meta
+impl Meta {
+ fn set_date(&mut self, date: &str) {
+ self.date = Some(date.into());
}
-}
-#[cfg(test)]
-mod test {}
+ fn set_title(&mut self, title: &str) {
+ self.title = Some(title.into());
+ }
+}
diff --git a/src/directive/mod.rs b/src/directive/mod.rs
index e33f572..ae2dc09 100644
--- a/src/directive/mod.rs
+++ b/src/directive/mod.rs
@@ -6,6 +6,24 @@ use crate::wikitext::ParsedDirective;
use log::{debug, trace};
use std::collections::HashSet;
+pub enum Processed {
+ Markdown(String),
+ Toc(usize),
+}
+
+pub trait DirectiveImplementation {
+ const REQUIRED: &'static [&'static str];
+ const ALLOWED: &'static [&'static str];
+ const ALLOW_ANY_UNNAMED: bool;
+
+ fn from_parsed(p: &ParsedDirective) -> Self;
+ fn process(&self, site: &Site, meta: &mut PageMeta) -> Result<Processed, SiteError>;
+
+ fn prepare(&self, _site: &mut Site) -> Result<(), SiteError> {
+ Ok(())
+ }
+}
+
#[derive(Debug, Eq, PartialEq)]
pub enum Directive {
Simple,
@@ -71,7 +89,7 @@ impl TryFrom<&ParsedDirective> for Directive {
Calendar::ALLOWED,
Calendar::ALLOW_ANY_UNNAMED,
)?;
- Directive::Calendar(Calendar::from(p))
+ Directive::Calendar(Calendar::from_parsed(p))
}
"format" => {
Self::check_args(
@@ -80,15 +98,15 @@ impl TryFrom<&ParsedDirective> for Directive {
Format::ALLOWED,
Format::ALLOW_ANY_UNNAMED,
)?;
- Directive::Format(Format::from(p))
+ Directive::Format(Format::from_parsed(p))
}
"graph" => {
Self::check_args(p, Graph::REQUIRED, Graph::ALLOWED, Graph::ALLOW_ANY_UNNAMED)?;
- Directive::Graph(Graph::from(p))
+ Directive::Graph(Graph::from_parsed(p))
}
"img" => {
Self::check_args(p, Img::REQUIRED, Img::ALLOWED, Img::ALLOW_ANY_UNNAMED)?;
- Directive::Img(Img::from(p))
+ Directive::Img(Img::from_parsed(p))
}
"inline" => {
Self::check_args(
@@ -97,15 +115,15 @@ impl TryFrom<&ParsedDirective> for Directive {
Inline::ALLOWED,
Inline::ALLOW_ANY_UNNAMED,
)?;
- Directive::Inline(Inline::from(p))
+ Directive::Inline(Inline::from_parsed(p))
}
"map" => {
Self::check_args(p, Map::REQUIRED, Map::ALLOWED, Map::ALLOW_ANY_UNNAMED)?;
- Directive::Map(Map::from(p))
+ Directive::Map(Map::from_parsed(p))
}
"meta" => {
Self::check_args(p, Meta::REQUIRED, Meta::ALLOWED, Meta::ALLOW_ANY_UNNAMED)?;
- Directive::Meta(Meta::from(p))
+ Directive::Meta(Meta::from_parsed(p))
}
"pagestats" => {
Self::check_args(
@@ -114,7 +132,7 @@ impl TryFrom<&ParsedDirective> for Directive {
PageStats::ALLOWED,
PageStats::ALLOW_ANY_UNNAMED,
)?;
- Directive::PageStats(PageStats::from(p))
+ Directive::PageStats(PageStats::from_parsed(p))
}
"shortcut" => {
Self::check_args(
@@ -123,7 +141,7 @@ impl TryFrom<&ParsedDirective> for Directive {
Shortcut::ALLOWED,
Shortcut::ALLOW_ANY_UNNAMED,
)?;
- Directive::Shortcut(Shortcut::from(p))
+ Directive::Shortcut(Shortcut::from_parsed(p))
}
"sidebar" => {
Self::check_args(
@@ -132,19 +150,19 @@ impl TryFrom<&ParsedDirective> for Directive {
Sidebar::ALLOWED,
Sidebar::ALLOW_ANY_UNNAMED,
)?;
- Directive::Sidebar(Sidebar::from(p))
+ Directive::Sidebar(Sidebar::from_parsed(p))
}
"tag" => {
Self::check_args(p, Tag::REQUIRED, Tag::ALLOWED, Tag::ALLOW_ANY_UNNAMED)?;
- Directive::Tag(Tag::from(p))
+ Directive::Tag(Tag::from_parsed(p))
}
"table" => {
Self::check_args(p, Table::REQUIRED, Table::ALLOWED, Table::ALLOW_ANY_UNNAMED)?;
- Directive::Table(Table::from(p))
+ Directive::Table(Table::from_parsed(p))
}
"toc" => {
Self::check_args(p, Toc::REQUIRED, Toc::ALLOWED, Toc::ALLOW_ANY_UNNAMED)?;
- Directive::Toc(Toc::from(p))
+ Directive::Toc(Toc::from_parsed(p))
}
"traillink" => {
Self::check_args(
@@ -153,7 +171,7 @@ impl TryFrom<&ParsedDirective> for Directive {
TrailLink::ALLOWED,
TrailLink::ALLOW_ANY_UNNAMED,
)?;
- Directive::TrailLink(TrailLink::from(p))
+ Directive::TrailLink(TrailLink::from_parsed(p))
}
_ => return Err(SiteError::UnknownDirective(p.name().into())),
};
@@ -204,7 +222,7 @@ impl Directive {
Ok(())
}
- pub fn process(&self, site: &mut Site, meta: &mut PageMeta) -> Result<String, SiteError> {
+ pub fn process(&self, site: &mut Site, meta: &mut PageMeta) -> Result<Processed, SiteError> {
match self {
Self::Simple
| Self::UnnamedArg
@@ -262,7 +280,7 @@ mod tag;
use tag::Tag;
mod toc;
-use toc::Toc;
+pub use toc::Toc;
mod traillink;
use traillink::TrailLink;
diff --git a/src/directive/pagestats.rs b/src/directive/pagestats.rs
index 89ba57e..11edc29 100644
--- a/src/directive/pagestats.rs
+++ b/src/directive/pagestats.rs
@@ -1,3 +1,4 @@
+use crate::directive::{DirectiveImplementation, Processed};
use crate::error::SiteError;
use crate::page::PageMeta;
use crate::site::Site;
@@ -6,22 +7,16 @@ use crate::wikitext::ParsedDirective;
#[derive(Debug, Default, Eq, PartialEq)]
pub struct PageStats {}
-impl PageStats {
- pub const REQUIRED: &'static [&'static str] = &["pages"];
- pub const ALLOWED: &'static [&'static str] = &["among", "style"];
- pub const ALLOW_ANY_UNNAMED: bool = true;
+impl DirectiveImplementation for PageStats {
+ const REQUIRED: &'static [&'static str] = &["pages"];
+ const ALLOWED: &'static [&'static str] = &["among", "style"];
+ const ALLOW_ANY_UNNAMED: bool = true;
- pub fn new() -> Self {
+ fn from_parsed(_: &ParsedDirective) -> Self {
Self::default()
}
- pub fn process(&self, _site: &Site, _meta: &mut PageMeta) -> Result<String, SiteError> {
+ fn process(&self, _site: &Site, _meta: &mut PageMeta) -> Result<Processed, SiteError> {
Err(SiteError::UnimplementedDirective("pagestat".into()))
}
}
-
-impl From<&ParsedDirective> for PageStats {
- fn from(_p: &ParsedDirective) -> Self {
- Self::new()
- }
-}
diff --git a/src/directive/shortcut.rs b/src/directive/shortcut.rs
index 7b397d4..48bc482 100644
--- a/src/directive/shortcut.rs
+++ b/src/directive/shortcut.rs
@@ -1,3 +1,4 @@
+use crate::directive::{DirectiveImplementation, Processed};
use crate::error::SiteError;
use crate::page::PageMeta;
use crate::site::{Shortcut as S, Site};
@@ -10,32 +11,32 @@ pub struct Shortcut {
shortcut: S,
}
-impl Shortcut {
- pub const REQUIRED: &'static [&'static str] = &["name", "url"];
- pub const ALLOWED: &'static [&'static str] = &["desc"];
- pub const ALLOW_ANY_UNNAMED: bool = false;
+impl DirectiveImplementation for Shortcut {
+ const REQUIRED: &'static [&'static str] = &["name", "url"];
+ const ALLOWED: &'static [&'static str] = &["desc"];
+ const ALLOW_ANY_UNNAMED: bool = false;
- pub fn new(shortcut: S) -> Self {
- Self { shortcut }
+ fn from_parsed(p: &ParsedDirective) -> Self {
+ let args = p.args();
+ let name = args.get("name").unwrap();
+ let desc = args.get("desc").unwrap_or(&"");
+ let url = args.get("url").unwrap();
+ Self::new(S::new(name, desc, url))
}
- pub fn prepare(&self, site: &mut Site) -> Result<(), SiteError> {
+ fn prepare(&self, site: &mut Site) -> Result<(), SiteError> {
trace!("shortcut: prepare");
site.add_shortcut(self.shortcut.clone());
Ok(())
}
- pub fn process(&self, _site: &mut Site, _meta: &mut PageMeta) -> Result<String, SiteError> {
- Ok("".into())
+ fn process(&self, _site: &Site, _meta: &mut PageMeta) -> Result<Processed, SiteError> {
+ Ok(Processed::Markdown("".into()))
}
}
-impl From<&ParsedDirective> for Shortcut {
- fn from(p: &ParsedDirective) -> Self {
- let args = p.args();
- let name = args.get("name").unwrap();
- let desc = args.get("desc").unwrap_or(&"");
- let url = args.get("url").unwrap();
- Self::new(S::new(name, desc, url))
+impl Shortcut {
+ pub fn new(shortcut: S) -> Self {
+ Self { shortcut }
}
}
diff --git a/src/directive/sidebar.rs b/src/directive/sidebar.rs
index 5148086..1c25144 100644
--- a/src/directive/sidebar.rs
+++ b/src/directive/sidebar.rs
@@ -1,3 +1,4 @@
+use crate::directive::{DirectiveImplementation, Processed};
use crate::error::SiteError;
use crate::page::PageMeta;
use crate::site::Site;
@@ -6,18 +7,16 @@ use crate::wikitext::ParsedDirective;
#[derive(Debug, Default, Eq, PartialEq)]
pub struct Sidebar {}
-impl Sidebar {
- pub const REQUIRED: &'static [&'static str] = &[];
- pub const ALLOWED: &'static [&'static str] = &["content"];
- pub const ALLOW_ANY_UNNAMED: bool = true;
+impl DirectiveImplementation for Sidebar {
+ const REQUIRED: &'static [&'static str] = &[];
+ const ALLOWED: &'static [&'static str] = &["content"];
+ const ALLOW_ANY_UNNAMED: bool = true;
- pub fn process(&self, _site: &Site, _meta: &mut PageMeta) -> Result<String, SiteError> {
- Err(SiteError::UnimplementedDirective("sidebar".into()))
+ fn from_parsed(_: &ParsedDirective) -> Self {
+ Self::default()
}
-}
-impl From<&ParsedDirective> for Sidebar {
- fn from(_: &ParsedDirective) -> Self {
- Sidebar::default()
+ fn process(&self, _site: &Site, _meta: &mut PageMeta) -> Result<Processed, SiteError> {
+ Err(SiteError::UnimplementedDirective("sidebar".into()))
}
}
diff --git a/src/directive/table.rs b/src/directive/table.rs
index 7610a6b..2bbfbc8 100644
--- a/src/directive/table.rs
+++ b/src/directive/table.rs
@@ -1,3 +1,4 @@
+use crate::directive::{DirectiveImplementation, Processed};
use crate::error::SiteError;
use crate::page::PageMeta;
use crate::site::Site;
@@ -9,16 +10,16 @@ pub struct Table {
data: String,
}
-impl Table {
- pub const REQUIRED: &'static [&'static str] = &["data"];
- pub const ALLOWED: &'static [&'static str] = &[];
- pub const ALLOW_ANY_UNNAMED: bool = true;
+impl DirectiveImplementation for Table {
+ const REQUIRED: &'static [&'static str] = &["data"];
+ const ALLOWED: &'static [&'static str] = &[];
+ const ALLOW_ANY_UNNAMED: bool = true;
- pub fn new(data: String) -> Self {
- Self { data }
+ fn from_parsed(p: &ParsedDirective) -> Self {
+ Self::new(p.args().get("data").unwrap().to_string())
}
- pub fn process(&self, _site: &Site, _meta: &mut PageMeta) -> Result<String, SiteError> {
+ fn process(&self, _site: &Site, _meta: &mut PageMeta) -> Result<Processed, SiteError> {
let mut table = String::new();
let mut lines = self.data.trim().lines();
if let Some(first) = lines.next() {
@@ -36,12 +37,12 @@ impl Table {
}
debug!("table data: {}", self.data);
debug!("table: {}", table);
- Ok(table)
+ Ok(Processed::Markdown(table))
}
}
-impl From<&ParsedDirective> for Table {
- fn from(p: &ParsedDirective) -> Self {
- Table::new(p.args().get("data").unwrap().to_string())
+impl Table {
+ pub fn new(data: String) -> Self {
+ Self { data }
}
}
diff --git a/src/directive/tag.rs b/src/directive/tag.rs
index c64acfc..6d4377d 100644
--- a/src/directive/tag.rs
+++ b/src/directive/tag.rs
@@ -1,3 +1,4 @@
+use crate::directive::{DirectiveImplementation, Processed};
use crate::error::SiteError;
use crate::page::PageMeta;
use crate::site::Site;
@@ -8,23 +9,23 @@ pub struct Tag {
tags: Vec<String>,
}
-impl Tag {
- pub const REQUIRED: &'static [&'static str] = &[];
- pub const ALLOWED: &'static [&'static str] = &["class"];
- pub const ALLOW_ANY_UNNAMED: bool = true;
+impl DirectiveImplementation for Tag {
+ const REQUIRED: &'static [&'static str] = &[];
+ const ALLOWED: &'static [&'static str] = &["class"];
+ const ALLOW_ANY_UNNAMED: bool = true;
- pub fn new(tags: Vec<String>) -> Self {
- Self { tags }
+ fn from_parsed(p: &ParsedDirective) -> Self {
+ let tags = p.unnamed_args().iter().map(|s| s.to_string()).collect();
+ Tag::new(tags)
}
- pub fn process(&self, _site: &Site, _meta: &mut PageMeta) -> Result<String, SiteError> {
- Ok("".into())
+ fn process(&self, _site: &Site, _meta: &mut PageMeta) -> Result<Processed, SiteError> {
+ Ok(Processed::Markdown("".into()))
}
}
-impl From<&ParsedDirective> for Tag {
- fn from(p: &ParsedDirective) -> Self {
- let tags = p.unnamed_args().iter().map(|s| s.to_string()).collect();
- Tag::new(tags)
+impl Tag {
+ pub fn new(tags: Vec<String>) -> Self {
+ Self { tags }
}
}
diff --git a/src/directive/toc.rs b/src/directive/toc.rs
index af1bba9..f027931 100644
--- a/src/directive/toc.rs
+++ b/src/directive/toc.rs
@@ -1,27 +1,86 @@
+use crate::directive::{DirectiveImplementation, Processed};
use crate::error::SiteError;
+use crate::html::{Content, Element, ElementTag};
use crate::page::PageMeta;
use crate::site::Site;
use crate::wikitext::ParsedDirective;
#[derive(Debug, Default, Eq, PartialEq)]
-pub struct Toc {}
+pub struct Toc {
+ levels: String,
+}
-impl Toc {
- pub const REQUIRED: &'static [&'static str] = &[];
- pub const ALLOWED: &'static [&'static str] = &["levels"];
- pub const ALLOW_ANY_UNNAMED: bool = true;
+impl DirectiveImplementation for Toc {
+ const REQUIRED: &'static [&'static str] = &[];
+ const ALLOWED: &'static [&'static str] = &["levels"];
+ const ALLOW_ANY_UNNAMED: bool = true;
- pub fn new() -> Self {
- Self::default()
+ fn from_parsed(p: &ParsedDirective) -> Self {
+ let args = p.args();
+ let levels = args.get("levels").unwrap_or(&"9");
+ Self::new(levels.to_string())
}
- pub fn process(&self, _site: &Site, _meta: &mut PageMeta) -> Result<String, SiteError> {
- Err(SiteError::UnimplementedDirective("toc".into()))
+ fn process(&self, _site: &Site, _meta: &mut PageMeta) -> Result<Processed, SiteError> {
+ let levels: usize = self
+ .levels
+ .parse()
+ .map_err(|e| SiteError::LevelsParse(self.levels.clone(), e))?;
+ Ok(Processed::Toc(levels))
}
}
-impl From<&ParsedDirective> for Toc {
- fn from(_p: &ParsedDirective) -> Self {
- Self::new()
+impl Toc {
+ fn new(levels: String) -> Self {
+ Self { levels }
+ }
+
+ pub fn post_process(html: &Element, levels: usize) -> String {
+ let headings: Vec<(usize, &[Content])> = html
+ .children()
+ .iter()
+ .filter_map(|c| match c {
+ Content::Elt(e) => Some(e),
+ _ => None,
+ })
+ .filter_map(|e| match e.tag() {
+ ElementTag::H1 => Some((1, e.children())),
+ ElementTag::H2 => Some((2, e.children())),
+ ElementTag::H3 => Some((3, e.children())),
+ ElementTag::H4 => Some((4, e.children())),
+ ElementTag::H5 => Some((5, e.children())),
+ ElementTag::H6 => Some((6, e.children())),
+ _ => None,
+ })
+ .collect();
+ let mut html = String::new();
+ let mut prev_level: usize = 0;
+ for (level, text) in headings.iter() {
+ if *level > levels {
+ continue;
+ } else if *level > prev_level {
+ html.push_str("<ol>");
+ } else if *level < prev_level {
+ html.push_str("</ol>\n");
+ }
+ html.push_str("<li>");
+ Self::stringify(&mut html, text);
+ html.push_str("</li>\n");
+ prev_level = *level;
+ }
+ for _ in 0..prev_level {
+ html.push_str("</ol>\n");
+ }
+ html
+ }
+
+ fn stringify(buf: &mut String, bits: &[Content]) {
+ for c in bits.iter() {
+ match c {
+ Content::Text(s) => buf.push_str(s),
+ Content::Elt(e) => Self::stringify(buf, e.children()),
+ Content::Html(h) => buf.push_str(h),
+ }
+ }
}
}
diff --git a/src/directive/traillink.rs b/src/directive/traillink.rs
index 9acfa6b..b3e8cb7 100644
--- a/src/directive/traillink.rs
+++ b/src/directive/traillink.rs
@@ -1,3 +1,4 @@
+use crate::directive::{DirectiveImplementation, Processed};
use crate::error::SiteError;
use crate::page::PageMeta;
use crate::site::Site;
@@ -6,22 +7,16 @@ use crate::wikitext::ParsedDirective;
#[derive(Debug, Default, Eq, PartialEq)]
pub struct TrailLink {}
-impl TrailLink {
- pub const REQUIRED: &'static [&'static str] = &[];
- pub const ALLOWED: &'static [&'static str] = &["text"];
- pub const ALLOW_ANY_UNNAMED: bool = true;
+impl DirectiveImplementation for TrailLink {
+ const REQUIRED: &'static [&'static str] = &[];
+ const ALLOWED: &'static [&'static str] = &["text"];
+ const ALLOW_ANY_UNNAMED: bool = true;
- pub fn new() -> Self {
+ fn from_parsed(_: &ParsedDirective) -> Self {
Self::default()
}
- pub fn process(&self, _site: &Site, _meta: &mut PageMeta) -> Result<String, SiteError> {
+ fn process(&self, _site: &Site, _meta: &mut PageMeta) -> Result<Processed, SiteError> {
Err(SiteError::UnimplementedDirective("traillink".into()))
}
}
-
-impl From<&ParsedDirective> for TrailLink {
- fn from(_p: &ParsedDirective) -> Self {
- Self::new()
- }
-}
diff --git a/src/error.rs b/src/error.rs
index 90046b2..d58924e 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -91,6 +91,9 @@ pub enum SiteError {
#[error("directive isn't implemented yet: {0}")]
UnimplementedDirective(String),
+
+ #[error("toc directive arguments 'levels' could not be parsed as an integer: {0}")]
+ LevelsParse(String, #[source] std::num::ParseIntError),
}
impl SiteError {
diff --git a/src/html.rs b/src/html.rs
index 6605333..6d4d009 100644
--- a/src/html.rs
+++ b/src/html.rs
@@ -236,7 +236,11 @@ impl Element {
self.children.push(child);
}
- fn children(&self) -> &[Content] {
+ pub fn tag(&self) -> ElementTag {
+ self.tag
+ }
+
+ pub fn children(&self) -> &[Content] {
&self.children
}
@@ -306,7 +310,7 @@ impl Element {
}
}
-#[derive(Clone, Debug, Eq, PartialEq)]
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum ElementTag {
Html,
Head,
diff --git a/src/page.rs b/src/page.rs
index 2b4bd9f..832e71e 100644
--- a/src/page.rs
+++ b/src/page.rs
@@ -1,3 +1,4 @@
+use crate::directive::{Processed, Toc};
use crate::error::SiteError;
use crate::html::{parse, Content, Element, ElementTag, HtmlPage};
use crate::name::Name;
@@ -24,7 +25,7 @@ impl WikitextPage {
info!("input file: {}", name);
let src = name.source_path();
- let data = std::fs::read(&src).map_err(|e| SiteError::FileRead(src.into(), e))?;
+ let data = std::fs::read(src).map_err(|e| SiteError::FileRead(src.into(), e))?;
let wikitext = String::from_utf8(data).map_err(|e| SiteError::Utf8(src.into(), e))?;
let mtime = get_mtime(src)?;
@@ -84,10 +85,26 @@ impl UnprocessedPage {
pub fn process(&self, site: &mut Site) -> Result<MarkdownPage, SiteError> {
let mut meta = self.meta.clone();
- let mut m = String::new();
+ let mut processed = vec![];
trace!("UnprocessedPage: processing snippets");
for snippet in self.snippets.iter() {
- m.push_str(&snippet.process(site, &mut meta)?);
+ processed.push(snippet.process(site, &mut meta)?);
+ }
+ let page_text = processed
+ .iter()
+ .filter_map(|p| match p {
+ Processed::Markdown(s) => Some(s.as_str()),
+ _ => None,
+ })
+ .collect::<Vec<&str>>()
+ .join("");
+ let body = parse(&page_text)?;
+ let mut m = String::new();
+ for p in processed {
+ match p {
+ Processed::Markdown(s) => m.push_str(&s),
+ Processed::Toc(levels) => m.push_str(&Toc::post_process(&body, levels)),
+ }
}
Ok(MarkdownPage::new(m, meta))
}
diff --git a/src/site.rs b/src/site.rs
index 6fa4625..0b03c85 100644
--- a/src/site.rs
+++ b/src/site.rs
@@ -131,7 +131,7 @@ impl Site {
let mut names = vec![];
for path in srcdir.files().iter().filter(|x| filter.is_included(x)) {
- let relative = path.strip_prefix(&self.builder.srcdir()).unwrap();
+ let relative = path.strip_prefix(self.builder.srcdir()).unwrap();
let mtime = whatchanged.get(relative).copied().unwrap_or(UNIX_EPOCH);
if Self::is_markdown(path) {
names.push(self.builder.page(path, mtime));
diff --git a/src/util.rs b/src/util.rs
index 064e556..bf78404 100644
--- a/src/util.rs
+++ b/src/util.rs
@@ -107,13 +107,13 @@ pub fn make_relative_link<P: AsRef<Path>>(page: P, target: P) -> PathBuf {
}
pub fn make_path_relative_to(dir: &Path, path: &Path) -> PathBuf {
- path.strip_prefix(&dir)
+ path.strip_prefix(dir)
.unwrap_or_else(|_| panic!("remove prefix {} from {}", dir.display(), path.display()))
.into()
}
pub fn make_path_absolute(path: &Path) -> PathBuf {
- Path::new("/").join(&path)
+ Path::new("/").join(path)
}
fn timespec(time: SystemTime) -> Result<timespec, SiteError> {
diff --git a/src/wikitext.rs b/src/wikitext.rs
index 81514f0..b78cd66 100644
--- a/src/wikitext.rs
+++ b/src/wikitext.rs
@@ -1,4 +1,4 @@
-use crate::directive::Directive;
+use crate::directive::{Directive, Processed};
use crate::error::SiteError;
use crate::page::PageMeta;
use crate::site::Site;
@@ -29,16 +29,17 @@ impl Snippet {
Ok(())
}
- pub fn process(&self, site: &mut Site, meta: &mut PageMeta) -> Result<String, SiteError> {
+ pub fn process(&self, site: &mut Site, meta: &mut PageMeta) -> Result<Processed, SiteError> {
trace!("Snippet::process: self={:?}", self);
- let s = match self {
- Snippet::Markdown(text) => text.into(),
+ let processed = match self {
+ Snippet::Markdown(text) => Processed::Markdown(text.into()),
Snippet::WikiLink(w) => {
let resolved = site
.resolve(meta.path(), Path::new(w.target()))
.map_err(|e| SiteError::PageProblem(meta.path().into(), Box::new(e)))?;
trace!("resolved {} to {}", w.target(), resolved.display());
- format!("[{}]({})", w.link_text(), resolved.display())
+ let link = format!("[{}]({})", w.link_text(), resolved.display());
+ Processed::Markdown(link)
}
Snippet::Directive(p) => {
let e = Directive::try_from(p);
@@ -47,13 +48,14 @@ impl Snippet {
.map_err(|e| SiteError::PageProblem(meta.path().into(), Box::new(e)))?
} else if let Some(shortcut) = site.shortcut(p.name()) {
let arg = p.unnamed_args().first().unwrap().to_string();
- format!("[{}]({})", shortcut.desc(&arg), shortcut.url(&arg))
+ let link = format!("[{}]({})", shortcut.desc(&arg), shortcut.url(&arg));
+ Processed::Markdown(link)
} else {
return Err(e.unwrap_err());
}
}
};
- Ok(s)
+ Ok(processed)
}
}