Skip to content

Commit

Permalink
Add shortcode 'invocation' variable to allow a shortcode to track how… (
Browse files Browse the repository at this point in the history
#1236)

* add shortcode 'invocation' variable to allow a shortcode to track how many times it has been invoked in a given Markdown file

* use closure (implicit struct) instead of explicit struct for invocation tracking

* update variable name to "nth"
  • Loading branch information
NathanaelLane authored and Keats committed Dec 14, 2020
1 parent a93063b commit a210abc
Show file tree
Hide file tree
Showing 3 changed files with 71 additions and 2 deletions.
25 changes: 23 additions & 2 deletions components/rendering/src/shortcode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use pest::iterators::Pair;
use pest::Parser;
use pest_derive::Parser;
use regex::Regex;
use std::collections::HashMap;
use tera::{to_value, Context, Map, Value};

use crate::context::RenderContext;
Expand Down Expand Up @@ -102,6 +103,7 @@ fn render_shortcode(
name: &str,
args: &Map<String, Value>,
context: &RenderContext,
invocation_count: u32,
body: Option<&str>,
) -> Result<String> {
let mut tera_context = Context::new();
Expand All @@ -112,6 +114,7 @@ fn render_shortcode(
// Trimming right to avoid most shortcodes with bodies ending up with a HTML new line
tera_context.insert("body", b.trim_end());
}
tera_context.insert("nth", &invocation_count);
tera_context.extend(context.tera_context.clone());

let mut template_name = format!("shortcodes/{}.md", name);
Expand Down Expand Up @@ -139,6 +142,12 @@ fn render_shortcode(

pub fn render_shortcodes(content: &str, context: &RenderContext) -> Result<String> {
let mut res = String::with_capacity(content.len());
let mut invocation_map: HashMap<String, u32> = HashMap::new();
let mut get_invocation_count = |name: &str| {
let invocation_number = invocation_map.entry(String::from(name)).or_insert(0);
*invocation_number += 1;
*invocation_number
};

let mut pairs = match ContentParser::parse(Rule::page, content) {
Ok(p) => p,
Expand Down Expand Up @@ -184,15 +193,27 @@ pub fn render_shortcodes(content: &str, context: &RenderContext) -> Result<Strin
Rule::text => res.push_str(p.as_span().as_str()),
Rule::inline_shortcode => {
let (name, args) = parse_shortcode_call(p);
res.push_str(&render_shortcode(&name, &args, context, None)?);
res.push_str(&render_shortcode(
&name,
&args,
context,
get_invocation_count(&name),
None,
)?);
}
Rule::shortcode_with_body => {
let mut inner = p.into_inner();
// 3 items in inner: call, body, end
// we don't care about the closing tag
let (name, args) = parse_shortcode_call(inner.next().unwrap());
let body = inner.next().unwrap().as_span().as_str();
res.push_str(&render_shortcode(&name, &args, context, Some(body))?);
res.push_str(&render_shortcode(
&name,
&args,
context,
get_invocation_count(&name),
Some(body),
)?);
}
Rule::ignored_inline_shortcode => {
res.push_str(
Expand Down
30 changes: 30 additions & 0 deletions components/rendering/tests/markdown.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1059,6 +1059,36 @@ fn emoji_aliases_are_ignored_when_disabled_in_config() {
assert_eq!(res.body, "<p>Hello, World! :smile:</p>\n");
}

#[test]
fn invocation_count_increments_in_shortcode() {
let permalinks_ctx = HashMap::new();
let mut tera = Tera::default();
tera.extend(&ZOLA_TERA).unwrap();

let shortcode_template_a = r#"<p>a: {{ nth }}</p>"#;
let shortcode_template_b = r#"<p>b: {{ nth }}</p>"#;

let markdown_string = r#"{{ a() }}
{{ b() }}
{{ a() }}
{{ b() }}
"#;

let expected = r#"<p>a: 1</p>
<p>b: 1</p>
<p>a: 2</p>
<p>b: 2</p>
"#;

tera.add_raw_template("shortcodes/a.html", shortcode_template_a).unwrap();
tera.add_raw_template("shortcodes/b.html", shortcode_template_b).unwrap();
let config = Config::default();
let context = RenderContext::new(&tera, &config, "", &permalinks_ctx, InsertAnchor::None);

let res = render_content(markdown_string, &context).unwrap();
assert_eq!(res.body, expected);
}

#[test]
fn basic_external_links_unchanged() {
let permalinks_ctx = HashMap::new();
Expand Down
18 changes: 18 additions & 0 deletions docs/content/documentation/content/shortcodes.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,24 @@ If you want to have some content that looks like a shortcode but not have Zola t
you will need to escape it by using `{%/*` and `*/%}` instead of `{%` and `%}`. You won't need to escape
anything else until the closing tag.

### Invocation Count

Every shortcode context is passed in a variable named `nth` that tracks how many times a particular shortcode has
been invoked in a Markdown file. Given a shortcode `true_statement.html` template:

```jinja2
<p id="number{{ nth }}">{{ value }} is equal to {{ nth }}.</p>
```

It could be used in our Markdown as follows:

```md
{{/* true_statement(value=1) */}}
{{/* true_statement(value=2) */}}
```

This is useful when implementing custom markup for features such as sidenotes or end notes.

## Built-in shortcodes

Zola comes with a few built-in shortcodes. If you want to override a default shortcode template,
Expand Down

0 comments on commit a210abc

Please sign in to comment.