summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLars Wirzenius <liw@liw.fi>2022-08-10 06:07:07 +0000
committerLars Wirzenius <liw@liw.fi>2022-08-10 06:07:07 +0000
commit561141e67a77195f5502acbb418dd5307a9b289c (patch)
tree047ec38a73489c2fd32dd3cb2481787c05438a31
parent454368dfbf11dad54567f004b94db90118bfc0b2 (diff)
parent1a2ea902b9a2df7c6e2b95acaf8e7e9e2c43851c (diff)
downloadriki-561141e67a77195f5502acbb418dd5307a9b289c.tar.gz
Merge branch 'shortcut' into 'main'
feat: implement shortcut directive Closes #18 See merge request larswirzenius/riki!38
-rw-r--r--riki.md114
-rw-r--r--src/directive/brokenlinks.rs4
-rw-r--r--src/directive/img.rs4
-rw-r--r--src/directive/inline.rs4
-rw-r--r--src/directive/meta.rs4
-rw-r--r--src/directive/mod.rs34
-rw-r--r--src/directive/pagestats.rs4
-rw-r--r--src/directive/shortcut.rs29
-rw-r--r--src/directive/tag.rs4
-rw-r--r--src/directive/toc.rs4
-rw-r--r--src/parser.rs48
-rw-r--r--src/site.rs39
-rw-r--r--src/token.rs8
-rw-r--r--src/wikitext.rs14
14 files changed, 212 insertions, 102 deletions
diff --git a/riki.md b/riki.md
index e5ae0f0..912dbac 100644
--- a/riki.md
+++ b/riki.md
@@ -334,7 +334,7 @@ then AST of site/page.mdwn matches that of output/page.html
* [x] done
~~~
-## Definition list
+### Definition list
_Requirement: Markup indicating use of a definition list should be
flagged as an error._
@@ -380,40 +380,7 @@ foo
~~~
-## Input files other than Markdown
-
-_Requirement: Input files that aren't Markdown files must be copied
-into the destination directory as-is._
-
-~~~scenario
-given an installed riki
-given file site/image.jpg from image
-when I run riki build --plain-body site output
-then files site/image.jpg and output/image.jpg match
-~~~
-
-
-~~~{#image .file}
-# Dummy
-Pretend this is an image.
-~~~
-
-## Input files in sub=directories
-
-_Requirement: If an source page or file is in a sub-directory, it
-should be put in the corresponding sub-directory in the target
-directory._
-
-~~~scenario
-given an installed riki
-given file site/foo/page.mdwn from image
-given file site/bar/image.jpg from para
-when I run riki build --plain-body site output
-then AST of site/foo/page.mdwn matches that of output/foo/page.html
-then files site/bar//image.jpg and output/bar/image.jpg match
-~~~
-
-## Wiki links to other pages on the site
+### Wiki links to other pages on the site
_Requirement: Pages can link to other pages on the site, the same
way ikiwiki does, including subpages._
@@ -442,7 +409,7 @@ Note the uppercase link to the `child` page in the test page below.
[[CHILD]]
~~~
-## Wiki links to pages that don't exist
+### Wiki links to pages that don't exist
_Requirement: Linking to a page that doesn't exist is an error._
@@ -459,6 +426,27 @@ then command fails
## Directives
+### `img`
+
+_Requirement: the `img` directive embeds an image in the generated
+HTML page._
+
+~~~scenario
+given an installed riki
+given file site/index.mdwn from img
+given file site/img.jpg from jpeg
+when I run riki build site output
+then file output/index.html contains "<img src="img.jpg""
+~~~
+
+~~~{#img .file .markdown}
+[[!img img.jpg]]]
+~~~
+
+~~~{#jpeg .file}
+This is a dummy JPEG image.
+~~~
+
### `meta title`
_Requirement: the `meta title` directive sets page title._
@@ -474,25 +462,22 @@ then file output/index.html contains "<title>Yo</title>"
[[!meta title=Yo]]]
~~~
-### `img`
+### `shortcut`
-_Requirement: the `img` directive embeds an image in the generated
-HTML page._
+_Requirement: the `shortcut` directive created a shortcut that looks
+like a directive._
~~~scenario
given an installed riki
-given file site/index.mdwn from img
-given file site/img.jpg from jpeg
+given file site/index.mdwn from shortcut
when I run riki build site output
-then file output/index.html contains "<img src="img.jpg""
+then file output/index.html contains "<a href="https://example.com/foo/123">foo!123</a>"
~~~
-~~~{#img .file .markdown}
-[[!img img.jpg]]]
-~~~
+~~~{#shortcut .file .markdown}
+[[!shortcut name="foo" url="https://example.com/foo/%s" desc="foo!%s"]]
-~~~{#jpeg .file}
-This is a dummy JPEG image.
+[[!foo 123]]
~~~
## Source file tree
@@ -530,6 +515,41 @@ then stdout doesn't contain "index.mdwn~"
then stdout doesn't contain "#index.mdwn#"
~~~
+
+
+## Input files other than Markdown
+
+_Requirement: Input files that aren't Markdown files must be copied
+into the destination directory as-is._
+
+~~~scenario
+given an installed riki
+given file site/image.jpg from image
+when I run riki build --plain-body site output
+then files site/image.jpg and output/image.jpg match
+~~~
+
+
+~~~{#image .file}
+# Dummy
+Pretend this is an image.
+~~~
+
+## Input files in sub-directories
+
+_Requirement: If an source page or file is in a sub-directory, it
+should be put in the corresponding sub-directory in the target
+directory._
+
+~~~scenario
+given an installed riki
+given file site/foo/page.mdwn from image
+given file site/bar/image.jpg from para
+when I run riki build --plain-body site output
+then AST of site/foo/page.mdwn matches that of output/foo/page.html
+then files site/bar//image.jpg and output/bar/image.jpg match
+~~~
+
## Output directory tree
### No markdown files in output tree
diff --git a/src/directive/brokenlinks.rs b/src/directive/brokenlinks.rs
index c6364a4..3091be9 100644
--- a/src/directive/brokenlinks.rs
+++ b/src/directive/brokenlinks.rs
@@ -20,8 +20,8 @@ impl BrokenLinks {
}
}
-impl From<ParsedDirective> for BrokenLinks {
- fn from(_p: ParsedDirective) -> Self {
+impl From<&ParsedDirective> for BrokenLinks {
+ fn from(_p: &ParsedDirective) -> Self {
Self::new()
}
}
diff --git a/src/directive/img.rs b/src/directive/img.rs
index dea2625..307f035 100644
--- a/src/directive/img.rs
+++ b/src/directive/img.rs
@@ -27,8 +27,8 @@ impl Img {
}
}
-impl From<ParsedDirective> for Img {
- fn from(p: ParsedDirective) -> Self {
+impl From<&ParsedDirective> for Img {
+ fn from(p: &ParsedDirective) -> Self {
let unnamed = p.unnamed_args().pop().unwrap();
Img::new(unnamed.into())
}
diff --git a/src/directive/inline.rs b/src/directive/inline.rs
index efaf7ee..5d4f786 100644
--- a/src/directive/inline.rs
+++ b/src/directive/inline.rs
@@ -31,8 +31,8 @@ impl Inline {
}
}
-impl From<ParsedDirective> for Inline {
- fn from(_p: ParsedDirective) -> Self {
+impl From<&ParsedDirective> for Inline {
+ fn from(_p: &ParsedDirective) -> Self {
Inline::new()
}
}
diff --git a/src/directive/meta.rs b/src/directive/meta.rs
index da595eb..6f47d45 100644
--- a/src/directive/meta.rs
+++ b/src/directive/meta.rs
@@ -25,8 +25,8 @@ impl Meta {
}
}
-impl From<ParsedDirective> for Meta {
- fn from(p: ParsedDirective) -> Self {
+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") {
diff --git a/src/directive/mod.rs b/src/directive/mod.rs
index ee96bfc..4697663 100644
--- a/src/directive/mod.rs
+++ b/src/directive/mod.rs
@@ -26,39 +26,47 @@ impl TryFrom<ParsedDirective> for Directive {
type Error = SiteError;
fn try_from(p: ParsedDirective) -> Result<Self, Self::Error> {
+ Self::try_from(&p)
+ }
+}
+
+impl TryFrom<&ParsedDirective> for Directive {
+ type Error = SiteError;
+
+ fn try_from(p: &ParsedDirective) -> Result<Self, Self::Error> {
let d = match p.name() {
"simple" => {
- Self::check_args(&p, &[], &[], false)?;
+ Self::check_args(p, &[], &[], false)?;
Directive::Simple
}
"unnamedarg" => {
- Self::check_args(&p, &[], &[], true)?;
+ Self::check_args(p, &[], &[], true)?;
Directive::UnnamedArg
}
"simplearg" => {
- Self::check_args(&p, &["foo"], &[], false)?;
+ Self::check_args(p, &["foo"], &[], false)?;
Directive::SimpleArg
}
"quotedarg" => {
- Self::check_args(&p, &["bar"], &[], false)?;
+ Self::check_args(p, &["bar"], &[], false)?;
Directive::QuotedArg
}
"multilinearg" => {
- Self::check_args(&p, &["yo"], &["then", "else"], false)?;
+ Self::check_args(p, &["yo"], &["then", "else"], false)?;
Directive::MultilineArg
}
"brokenlinks" => {
- Self::check_args(&p, BrokenLinks::REQUIRED, BrokenLinks::ALLOWED, BrokenLinks::ALLOW_ANY_UNNAMED)?;
+ Self::check_args(p, BrokenLinks::REQUIRED, BrokenLinks::ALLOWED, BrokenLinks::ALLOW_ANY_UNNAMED)?;
Directive::BrokenLinks(BrokenLinks::from(p))
}
"img" => {
- Self::check_args(&p, Img::REQUIRED, Img::ALLOWED, Img::ALLOW_ANY_UNNAMED)?;
+ Self::check_args(p, Img::REQUIRED, Img::ALLOWED, Img::ALLOW_ANY_UNNAMED)?;
Directive::Img(Img::from(p))
}
"inline" => {
Self::check_args(
- &p,
+ p,
Inline::REQUIRED,
Inline::ALLOWED,
Inline::ALLOW_ANY_UNNAMED,
@@ -66,12 +74,12 @@ impl TryFrom<ParsedDirective> for Directive {
Directive::Inline(Inline::from(p))
}
"meta" => {
- Self::check_args(&p, Meta::REQUIRED, Meta::ALLOWED, Meta::ALLOW_ANY_UNNAMED)?;
+ Self::check_args(p, Meta::REQUIRED, Meta::ALLOWED, Meta::ALLOW_ANY_UNNAMED)?;
Directive::Meta(Meta::from(p))
}
"pagestats" => {
Self::check_args(
- &p,
+ p,
PageStats::REQUIRED,
PageStats::ALLOWED,
PageStats::ALLOW_ANY_UNNAMED,
@@ -79,15 +87,15 @@ impl TryFrom<ParsedDirective> for Directive {
Directive::PageStats(PageStats::from(p))
}
"shortcut" => {
- Self::check_args(&p, Shortcut::REQUIRED, Shortcut::ALLOWED, Shortcut::ALLOW_ANY_UNNAMED)?;
+ Self::check_args(p, Shortcut::REQUIRED, Shortcut::ALLOWED, Shortcut::ALLOW_ANY_UNNAMED)?;
Directive::Shortcut(Shortcut::from(p))
}
"tag" => {
- Self::check_args(&p, Tag::REQUIRED, Tag::ALLOWED, Tag::ALLOW_ANY_UNNAMED)?;
+ Self::check_args(p, Tag::REQUIRED, Tag::ALLOWED, Tag::ALLOW_ANY_UNNAMED)?;
Directive::Tag(Tag::from(p))
}
"toc" => {
- Self::check_args(&p, Toc::REQUIRED, Toc::ALLOWED, Toc::ALLOW_ANY_UNNAMED)?;
+ Self::check_args(p, Toc::REQUIRED, Toc::ALLOWED, Toc::ALLOW_ANY_UNNAMED)?;
Directive::Toc(Toc::from(p))
}
_ => return Err(SiteError::UnknownDirective(p.name().into())),
diff --git a/src/directive/pagestats.rs b/src/directive/pagestats.rs
index 1a6f811..164ac9c 100644
--- a/src/directive/pagestats.rs
+++ b/src/directive/pagestats.rs
@@ -20,8 +20,8 @@ impl PageStats {
}
}
-impl From<ParsedDirective> for PageStats {
- fn from(_p: ParsedDirective) -> Self {
+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 03fd648..534300a 100644
--- a/src/directive/shortcut.rs
+++ b/src/directive/shortcut.rs
@@ -1,27 +1,34 @@
use crate::error::SiteError;
use crate::page::PageMeta;
-use crate::site::Site;
+use crate::site::{Shortcut as S, Site};
use crate::wikitext::ParsedDirective;
-#[derive(Debug, Default, Eq, PartialEq)]
-pub struct Shortcut {}
+#[derive(Debug, Eq, PartialEq)]
+pub struct Shortcut {
+ shortcut: S,
+}
impl Shortcut {
pub const REQUIRED: &'static [&'static str] = &["desc", "name", "url"];
pub const ALLOWED: &'static [&'static str] = &[];
- pub const ALLOW_ANY_UNNAMED: bool = true;
+ pub const ALLOW_ANY_UNNAMED: bool = false;
- pub fn new() -> Self {
- Self::default()
+ pub fn new(shortcut: S) -> Self {
+ Self { shortcut }
}
- pub fn process(&self, _site: &Site, _meta: &mut PageMeta) -> Result<String, SiteError> {
- Ok("FIXME:inline".into())
+ pub fn process(&self, site: &mut Site, _meta: &mut PageMeta) -> Result<String, SiteError> {
+ site.add_shortcut(self.shortcut.clone());
+ Ok("".into())
}
}
-impl From<ParsedDirective> for Shortcut {
- fn from(_p: ParsedDirective) -> Self {
- Self::new()
+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();
+ let url = args.get("url").unwrap();
+ Self::new(S::new(name, desc, url))
}
}
diff --git a/src/directive/tag.rs b/src/directive/tag.rs
index baf4561..c64acfc 100644
--- a/src/directive/tag.rs
+++ b/src/directive/tag.rs
@@ -22,8 +22,8 @@ impl Tag {
}
}
-impl From<ParsedDirective> for Tag {
- fn from(p: ParsedDirective) -> Self {
+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)
}
diff --git a/src/directive/toc.rs b/src/directive/toc.rs
index 6db46d2..cc94be4 100644
--- a/src/directive/toc.rs
+++ b/src/directive/toc.rs
@@ -20,8 +20,8 @@ impl Toc {
}
}
-impl From<ParsedDirective> for Toc {
- fn from(_p: ParsedDirective) -> Self {
+impl From<&ParsedDirective> for Toc {
+ fn from(_p: &ParsedDirective) -> Self {
Self::new()
}
}
diff --git a/src/parser.rs b/src/parser.rs
index 662ffd0..d283d90 100644
--- a/src/parser.rs
+++ b/src/parser.rs
@@ -1,9 +1,7 @@
-use crate::directive::Directive;
use crate::error::SiteError;
use crate::token::{Token, TokenParser, TokenPatterns};
use crate::wikitext::{ParsedDirective, Snippet, WikiLink};
use std::collections::HashMap;
-use std::convert::TryFrom;
#[derive(Debug)]
pub struct WikitextParser {
@@ -102,8 +100,7 @@ impl WikitextParser {
_ => panic!("yikes: {:?}", self.tokens),
}
}
- let p = ParsedDirective::new(&name, args)?;
- Snippet::Directive(Directive::try_from(p)?)
+ Snippet::Directive(ParsedDirective::new(&name, args)?)
}
[Token::Bang, Token::OpenBracket, ..] => {
let mut link_text = String::new();
@@ -200,8 +197,16 @@ impl WikitextParser {
#[cfg(test)]
mod test {
- use super::{Directive, Snippet, TokenPatterns, WikiLink, WikitextParser};
- use crate::directive::Img;
+ use super::{ParsedDirective, Snippet, TokenPatterns, WikiLink, WikitextParser};
+ use std::collections::HashMap;
+
+ fn parsed_directive(name: &str, kv: &[(&str, &str)]) -> ParsedDirective {
+ ParsedDirective::new(
+ name,
+ HashMap::from_iter(kv.iter().map(|(k, v)| (k.to_string(), v.to_string()))),
+ )
+ .unwrap()
+ }
#[test]
fn empty_input() {
@@ -285,7 +290,7 @@ mod test {
let mut p = WikitextParser::new("[[!simple]]", &patterns);
assert_eq!(
p.parse().unwrap(),
- Some(Snippet::Directive(Directive::Simple))
+ Some(Snippet::Directive(parsed_directive("simple", &[])))
);
assert_eq!(p.parse().unwrap(), None);
}
@@ -296,7 +301,10 @@ mod test {
let mut p = WikitextParser::new("[[!unnamedarg foo.jpg]]", &patterns);
assert_eq!(
p.parse().unwrap(),
- Some(Snippet::Directive(Directive::UnnamedArg))
+ Some(Snippet::Directive(parsed_directive(
+ "unnamedarg",
+ &[("foo.jpg", "")]
+ )))
);
assert_eq!(p.parse().unwrap(), None);
}
@@ -307,7 +315,10 @@ mod test {
let mut p = WikitextParser::new("[[!simplearg foo=bar]]", &patterns);
assert_eq!(
p.parse().unwrap(),
- Some(Snippet::Directive(Directive::SimpleArg))
+ Some(Snippet::Directive(parsed_directive(
+ "simplearg",
+ &[("foo", "bar")]
+ )))
);
assert_eq!(p.parse().unwrap(), None);
}
@@ -318,7 +329,10 @@ mod test {
let mut p = WikitextParser::new(r#"[[!quotedarg bar="foobar"]]"#, &patterns);
assert_eq!(
p.parse().unwrap(),
- Some(Snippet::Directive(Directive::QuotedArg))
+ Some(Snippet::Directive(parsed_directive(
+ "quotedarg",
+ &[("bar", "foobar")]
+ )))
);
assert_eq!(p.parse().unwrap(), None);
}
@@ -328,12 +342,15 @@ mod test {
let patterns = TokenPatterns::default();
let mut p = WikitextParser::new(
r#"[[!multilinearg yo="""foo
- bar"""]]"#,
+bar"""]]"#,
&patterns,
);
assert_eq!(
p.parse().unwrap(),
- Some(Snippet::Directive(Directive::MultilineArg))
+ Some(Snippet::Directive(parsed_directive(
+ "multilinearg",
+ &[("yo", "foo\nbar")]
+ )))
);
assert_eq!(p.parse().unwrap(), None);
}
@@ -344,9 +361,10 @@ mod test {
let mut p = WikitextParser::new(r#"[[!img foo.jpg class=image]]"#, &patterns);
assert_eq!(
p.parse().unwrap(),
- Some(Snippet::Directive(Directive::Img(Img::new(
- "foo.jpg".into()
- ))))
+ Some(Snippet::Directive(parsed_directive(
+ "img",
+ &[("foo.jpg", ""), ("class", "image")]
+ )))
);
assert_eq!(p.parse().unwrap(), None);
}
diff --git a/src/site.rs b/src/site.rs
index 3200b58..f1ff333 100644
--- a/src/site.rs
+++ b/src/site.rs
@@ -21,6 +21,7 @@ pub struct Site {
name_queue: Vec<Name>,
page_queue: PageSet,
whatchanged: HashMap<PathBuf, SystemTime>,
+ shortcuts: HashMap<String, Shortcut>,
}
impl Site {
@@ -41,6 +42,7 @@ impl Site {
name_queue: vec![],
page_queue: PageSet::default(),
whatchanged: HashMap::new(),
+ shortcuts: HashMap::new(),
}
}
@@ -267,6 +269,14 @@ impl Site {
}
false
}
+
+ pub fn add_shortcut(&mut self, shortcut: Shortcut) {
+ self.shortcuts.insert(shortcut.name().into(), shortcut);
+ }
+
+ pub fn shortcut(&self, name: &str) -> Option<&Shortcut> {
+ self.shortcuts.get(name)
+ }
}
#[derive(Default)]
@@ -290,6 +300,35 @@ impl PageSet {
}
}
+#[derive(Debug, Clone, Eq, PartialEq)]
+pub struct Shortcut {
+ name: String,
+ desc: String,
+ url: String,
+}
+
+impl Shortcut {
+ pub fn new(name: &str, desc: &str, url: &str) -> Self {
+ Self {
+ name: name.into(),
+ desc: desc.into(),
+ url: url.into(),
+ }
+ }
+
+ pub fn name(&self) -> &str {
+ &self.name
+ }
+
+ pub fn desc(&self, arg: &str) -> String {
+ self.desc.replace("%s", arg)
+ }
+
+ pub fn url(&self, arg: &str) -> String {
+ self.url.replace("%s", arg)
+ }
+}
+
#[cfg(test)]
mod test {
use super::{NameBuilder, Site, SiteError, WikitextPage};
diff --git a/src/token.rs b/src/token.rs
index 5109ce1..eedf655 100644
--- a/src/token.rs
+++ b/src/token.rs
@@ -224,6 +224,14 @@ mod test {
}
#[test]
+ fn number_word() {
+ let patterns = TokenPatterns::default();
+ let mut p = parser("123", &patterns);
+ assert_eq!(p.parse(), Token::Word("123".into()));
+ assert_eq!(p.parse(), Token::End);
+ }
+
+ #[test]
fn complex_word() {
let patterns = TokenPatterns::default();
let mut p = parser("foo-1.2_3[[bar/subpage]]", &patterns);
diff --git a/src/wikitext.rs b/src/wikitext.rs
index b425021..91ac30b 100644
--- a/src/wikitext.rs
+++ b/src/wikitext.rs
@@ -9,7 +9,7 @@ use std::path::Path;
pub enum Snippet {
Markdown(String),
WikiLink(WikiLink),
- Directive(Directive),
+ Directive(ParsedDirective),
}
impl Snippet {
@@ -22,7 +22,17 @@ impl Snippet {
trace!("resolved {} to {}", w.target(), resolved.display());
format!("[{}]({})", w.link_text(), resolved.display())
}
- Snippet::Directive(d) => d.process(site, meta)?,
+ Snippet::Directive(p) => {
+ let e = Directive::try_from(p);
+ if let Ok(d) = e {
+ d.process(site, meta)?
+ } else if let Some(shortcut) = site.shortcut(p.name()) {
+ let arg = p.unnamed_args().get(0).unwrap().to_string();
+ format!("[{}]({})", shortcut.desc(&arg), shortcut.url(&arg))
+ } else {
+ return Err(e.unwrap_err());
+ }
+ }
};
Ok(s)
}