diff options
-rw-r--r-- | riki.md | 21 | ||||
-rw-r--r-- | src/directive/img.rs | 31 | ||||
-rw-r--r-- | src/directive/meta.rs | 1 | ||||
-rw-r--r-- | src/directive/mod.rs | 36 | ||||
-rw-r--r-- | src/error.rs | 5 | ||||
-rw-r--r-- | src/wikitext.rs | 90 |
6 files changed, 147 insertions, 37 deletions
@@ -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, ""); } } |