summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--riki.md21
-rw-r--r--src/directive/img.rs31
-rw-r--r--src/directive/meta.rs1
-rw-r--r--src/directive/mod.rs36
-rw-r--r--src/error.rs5
-rw-r--r--src/wikitext.rs90
6 files changed, 147 insertions, 37 deletions
diff --git a/riki.md b/riki.md
index 7f61ea0..fa982b2 100644
--- a/riki.md
+++ b/riki.md
@@ -424,3 +424,24 @@ then file output/index.html contains "<title>Yo</title>"
~~~{#meta .file .markdown}
[[!meta title=Yo]]]
~~~
+
+### `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.
+~~~
diff --git a/src/directive/img.rs b/src/directive/img.rs
new file mode 100644
index 0000000..54fdd6c
--- /dev/null
+++ b/src/directive/img.rs
@@ -0,0 +1,31 @@
+use crate::error::SiteError;
+use crate::page::PageMeta;
+use crate::site::Site;
+use crate::wikitext::ParsedDirective;
+
+#[derive(Debug, Eq, PartialEq)]
+pub struct Img {
+ src: String,
+}
+
+impl Img {
+ pub const REQUIRED: &'static [&'static str] = &[];
+ pub const ALLOWED: &'static [&'static str] = &["class"];
+ pub const ALLOW_ANY_UNNAMED: bool = true;
+
+ fn new(src: String) -> Self {
+ Self {
+ src: src.into(),
+ }
+ }
+
+ pub fn process(&self, _site: &Site, _meta: &mut PageMeta) -> Result<String, SiteError> {
+ Ok(format!("![]({})", self.src))
+ }
+}
+
+impl From<ParsedDirective> for Img {
+ fn from(p: ParsedDirective) -> Self {
+ Img::new(p.unnamed_arg().expect("img must have filename argument").into())
+ }
+}
diff --git a/src/directive/meta.rs b/src/directive/meta.rs
index e8a4c02..789a6b0 100644
--- a/src/directive/meta.rs
+++ b/src/directive/meta.rs
@@ -11,6 +11,7 @@ pub struct Meta {
impl Meta {
pub const REQUIRED: &'static [&'static str] = &[];
pub const ALLOWED: &'static [&'static str] = &["date", "link", "title"];
+ pub const ALLOW_ANY_UNNAMED: bool = false;
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 5baa4ec..79c6d08 100644
--- a/src/directive/mod.rs
+++ b/src/directive/mod.rs
@@ -7,11 +7,13 @@ use std::collections::HashSet;
#[derive(Debug, Eq, PartialEq)]
pub enum Directive {
Simple,
+ UnnamedArg,
SimpleArg,
QuotedArg,
MultilineArg,
Meta(Meta),
+ Img(Img),
}
impl TryFrom<ParsedDirective> for Directive {
@@ -20,23 +22,31 @@ impl TryFrom<ParsedDirective> for Directive {
fn try_from(p: ParsedDirective) -> Result<Self, Self::Error> {
let d = match p.name() {
"simple" => {
- Self::check_args(&p, &[], &[])?;
+ Self::check_args(&p, &[], &[], false)?;
Directive::Simple
}
+ "unnamedarg" => {
+ Self::check_args(&p, &[], &[], true)?;
+ Directive::UnnamedArg
+ }
"simplearg" => {
- Self::check_args(&p, &["foo"], &[])?;
+ Self::check_args(&p, &["foo"], &[], false)?;
Directive::SimpleArg
}
"quotedarg" => {
- Self::check_args(&p, &["bar"], &[])?;
+ Self::check_args(&p, &["bar"], &[], false)?;
Directive::QuotedArg
}
"multilinearg" => {
- Self::check_args(&p, &["yo"], &[])?;
+ Self::check_args(&p, &["yo"], &[], false)?;
Directive::MultilineArg
}
+ "img" => {
+ Self::check_args(&p, Img::REQUIRED, Img::ALLOWED, Img::ALLOW_ANY_UNNAMED)?;
+ Directive::Img(Img::from(p))
+ }
"meta" => {
- Self::check_args(&p, Meta::REQUIRED, Meta::ALLOWED)?;
+ Self::check_args(&p, Meta::REQUIRED, Meta::ALLOWED, Meta::ALLOW_ANY_UNNAMED)?;
Directive::Meta(Meta::from(p))
}
_ => return Err(SiteError::UnknownDirective(p.name().into())),
@@ -50,6 +60,7 @@ impl Directive {
p: &ParsedDirective,
required: &[&str],
allowed: &[&str],
+ allow_any_unnamed: bool,
) -> Result<(), SiteError> {
let args = p.args();
for arg in required.iter() {
@@ -63,8 +74,9 @@ impl Directive {
let required: HashSet<String> = required.iter().map(|arg| arg.to_string()).collect();
let allowed: HashSet<String> = allowed.iter().map(|arg| arg.to_string()).collect();
let allowed: HashSet<String> = required.union(&allowed).cloned().collect();
- for arg in args.keys() {
- if !allowed.contains(*arg) {
+ for (arg, value) in p.args().iter() {
+ if allow_any_unnamed && value.is_empty() {
+ } else if !allowed.contains(*arg) {
return Err(SiteError::DirectiveUnknownArg(
p.name().into(),
arg.to_string(),
@@ -76,9 +88,14 @@ impl Directive {
pub fn process(&self, site: &mut Site, meta: &mut PageMeta) -> Result<String, SiteError> {
match self {
- Self::Simple | Self::SimpleArg | Self::QuotedArg | Self::MultilineArg => {
+ Self::Simple
+ | Self::UnnamedArg
+ | Self::SimpleArg
+ | Self::QuotedArg
+ | Self::MultilineArg => {
panic!("directive {:?} may only be used in parsing tests", self)
}
+ Self::Img(x) => x.process(site, meta),
Self::Meta(x) => x.process(site, meta),
}
}
@@ -86,3 +103,6 @@ impl Directive {
mod meta;
use meta::Meta;
+
+mod img;
+use img::Img;
diff --git a/src/error.rs b/src/error.rs
index fa8ddf2..a5e65e1 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -38,10 +38,13 @@ pub enum SiteError {
#[error("unknown directive {0}")]
UnknownDirective(String),
+ #[error("directive {0} has more than one unnamed argument")]
+ TooManyUnnamedArgs(String),
+
#[error("directive {0} is missing required argument {1}")]
DirectiveMissingArg(String, String),
- #[error("directive {0} is missing required argument {1}")]
+ #[error("directive {0} has unknown argument {1}")]
DirectiveUnknownArg(String, String),
#[error("link to missing page {1} on {0}")]
diff --git a/src/wikitext.rs b/src/wikitext.rs
index e8416e8..1a9b49c 100644
--- a/src/wikitext.rs
+++ b/src/wikitext.rs
@@ -60,13 +60,15 @@ impl WikitextParser {
let name = c.name("name").unwrap().as_str();
let args = c.name("args").unwrap().as_str();
let args = self.parse_args(args);
- let d = ParsedDirective::new(name, args);
+ let d = ParsedDirective::new(name, args)?;
+ trace!("WikitextParser: directive={:?}", d);
let d = Directive::try_from(d)?;
Snippet::Directive(d)
} else if pat.as_str() == self.directive_no_args.as_str() {
let name = c.name("name").unwrap().as_str();
let args = self.parse_args("");
- let d = ParsedDirective::new(name, args);
+ let d = ParsedDirective::new(name, args)?;
+ trace!("WikitextParser: directive={:?}", d);
let d = Directive::try_from(d)?;
Snippet::Directive(d)
} else if pat.as_str() == self.wikilink_bare.as_str() {
@@ -141,7 +143,7 @@ impl Default for WikitextParser {
.unwrap(),
directive_no_args: Regex::new(r"^\[\[!(?P<name>\w+)\s*\]\]").unwrap(),
directive_args: Regex::new(r#"\[\[!(?P<name>\w+)\s+(?P<args>[^]]*?)\s*\]\]"#).unwrap(),
- plain: Regex::new(r"(?P<key>\w+)").unwrap(),
+ plain: Regex::new(r"(?P<key>[a-z0-9._-]+)").unwrap(),
simple: Regex::new(r"(?P<key>\w+)=(?P<value>\S+)").unwrap(),
quoted: Regex::new(r#"(?P<key>\w+)="(?P<value>[^"]*)""#).unwrap(),
multiline: Regex::new(r#"(?P<key>\w+)=("""(?P<value>(.|\n)*?)""")"#).unwrap(),
@@ -199,14 +201,40 @@ impl WikiLink {
pub struct ParsedDirective {
name: String,
args: HashMap<String, String>,
+ unnamed_arg: Option<String>,
}
impl ParsedDirective {
- fn new(name: &str, args: HashMap<String, String>) -> Self {
- Self {
+ fn new(name: &str, mut args: HashMap<String, String>) -> Result<Self, SiteError> {
+ trace!("ParsedDirective::new: name={:?} args={:?}", name, args);
+ let unnamed_args: Vec<String> = args
+ .iter()
+ .filter_map(|(k, v)| {
+ if v.is_empty() {
+ Some(k.to_string())
+ } else {
+ None
+ }
+ })
+ .collect();
+ trace!("ParsedDirective::new: unnamed_args={:?}", unnamed_args);
+
+ let unnamed_arg = match unnamed_args.len() {
+ 0 => None,
+ 1 => {
+ let key = unnamed_args.get(0).unwrap();
+ args.remove(key);
+ Some(key.to_string())
+ }
+ _ => return Err(SiteError::TooManyUnnamedArgs(name.into())),
+ };
+ trace!("ParsedDirective::new: unnamed_arg={:?}", unnamed_arg);
+
+ Ok(Self {
name: name.into(),
args,
- }
+ unnamed_arg,
+ })
}
pub fn name(&self) -> &str {
@@ -219,6 +247,10 @@ impl ParsedDirective {
.map(|(k, v)| (k.as_str(), v.as_str()))
.collect()
}
+
+ pub fn unnamed_arg(&self) -> Option<&str> {
+ self.unnamed_arg.as_deref()
+ }
}
#[derive(Debug, thiserror::Error)]
@@ -272,7 +304,9 @@ mod test {
fn complex_wikilink() {
let p = WikitextParser::default();
- let (snippet, rest) = p.parse("hello, [[whomever we greet|planet-earth]]").unwrap();
+ let (snippet, rest) = p
+ .parse("hello, [[whomever we greet|planet-earth]]")
+ .unwrap();
assert_eq!(snippet, Snippet::Markdown("hello, ".into()));
assert_eq!(rest, "[[whomever we greet|planet-earth]]");
@@ -288,7 +322,9 @@ mod test {
fn complex_wikilink_to_subpage() {
let p = WikitextParser::default();
- let (snippet, rest) = p.parse("hello, [[whomever we greet|planets/earth]]").unwrap();
+ let (snippet, rest) = p
+ .parse("hello, [[whomever we greet|planets/earth]]")
+ .unwrap();
assert_eq!(snippet, Snippet::Markdown("hello, ".into()));
assert_eq!(rest, "[[whomever we greet|planets/earth]]");
@@ -317,21 +353,23 @@ mod test {
fn simple_directive() {
let p = WikitextParser::default();
let (snippet, rest) = p.parse("[[!simple]]").unwrap();
- assert_eq!(
- snippet,
- Snippet::Directive(Directive::Simple)
- );
+ assert_eq!(snippet, Snippet::Directive(Directive::Simple));
+ assert_eq!(rest, "");
+ }
+
+ #[test]
+ fn directive_unnamed_arg() {
+ let p = WikitextParser::default();
+ let (snippet, rest) = p.parse("[[!unnamedarg foo.jpg]]").unwrap();
+ assert_eq!(snippet, Snippet::Directive(Directive::UnnamedArg));
assert_eq!(rest, "");
}
#[test]
fn directive_simple_arg() {
let p = WikitextParser::default();
- let (snippet, rest) = p.parse("[[!simplearg foo]]").unwrap();
- assert_eq!(
- snippet,
- Snippet::Directive(Directive::SimpleArg)
- );
+ let (snippet, rest) = p.parse("[[!simplearg foo=bar]]").unwrap();
+ assert_eq!(snippet, Snippet::Directive(Directive::SimpleArg));
assert_eq!(rest, "");
}
@@ -339,24 +377,20 @@ mod test {
fn directive_quoted_arg() {
let p = WikitextParser::default();
let (snippet, rest) = p.parse(r#"[[!quotedarg bar="foobar"]]"#).unwrap();
- assert_eq!(
- snippet,
- Snippet::Directive(Directive::QuotedArg)
- );
+ assert_eq!(snippet, Snippet::Directive(Directive::QuotedArg));
assert_eq!(rest, "");
}
#[test]
fn directive_multiline_arg() {
let p = WikitextParser::default();
- let (snippet, rest) = p.parse(
- r#"[[!multilinearg yo="""foo
+ let (snippet, rest) = p
+ .parse(
+ r#"[[!multilinearg yo="""foo
bar"""]]"#,
- ).unwrap();
- assert_eq!(
- snippet,
- Snippet::Directive(Directive::MultilineArg)
- );
+ )
+ .unwrap();
+ assert_eq!(snippet, Snippet::Directive(Directive::MultilineArg));
assert_eq!(rest, "");
}
}