Skip to content

Commit

Permalink
Add keyword arguments for Liquid shortcodes
Browse files Browse the repository at this point in the history
This is an adaptation of 11ty#1733 to use the built-in Liquid argument
parser that was added in 11ty#2679.

Co-Authored-By: Dylan Awalt-Conley <web@dylan.ac>
  • Loading branch information
nex3 and dawaltconley committed Sep 15, 2024
1 parent 26c0fcf commit f8a4ef0
Show file tree
Hide file tree
Showing 2 changed files with 123 additions and 30 deletions.
71 changes: 41 additions & 30 deletions src/Engines/Liquid.js
Original file line number Diff line number Diff line change
Expand Up @@ -172,20 +172,57 @@ class Liquid extends TemplateEngine {
let tokenizer = new Tokenizer(args);
let parsedArgs = [];

let value = tokenizer.readValue();
function readValue() {
let value = tokenizer.readHash() ?? tokenizer.readValue();
// readHash() treats unmarked identifiers as hash keys with undefined
// values, but we want to parse them as positional arguments instead.
return value?.kind === 64 && value.value === undefined ? value.name : value;
}

let value = readValue();
while (value) {
parsedArgs.push(value);
tokenizer.skipBlank();
if (tokenizer.peek() === ",") {
tokenizer.advance();
}
value = tokenizer.readValue();
value = readValue();
}
tokenizer.end();

return parsedArgs;
}

static *evalArguments(tag, ctx) {
let argArray = [];
let namedArgs = {};
if (tag.legacyArgs) {
let rawArgs = Liquid.parseArguments(_t.argLexer, tag.legacyArgs);
for (let arg of rawArgs) {
let b = yield liquidEngine.evalValue(arg, ctx);
argArray.push(b);
}
} else if (tag.orderedArgs) {
for (let arg of tag.orderedArgs) {
if (arg.kind == 64) {
if (arg.value === undefined) {
namedArgs[arg.name.content] = true;
} else {
namedArgs[arg.name.content] = yield evalToken(arg.value, ctx);
}
} else {
let b = yield evalToken(arg, ctx);
argArray.push(b);
}
}
}

if (Object.keys(namedArgs).length > 0) {
argArray.push(namedArgs);
}
return argArray;
}

addShortcode(shortcodeName, shortcodeFn) {
let _t = this;
this.addTag(shortcodeName, function (liquidEngine) {
Expand All @@ -200,21 +237,7 @@ class Liquid extends TemplateEngine {
}
},
render: function* (ctx) {
let argArray = [];

if (this.legacyArgs) {
let rawArgs = Liquid.parseArguments(_t.argLexer, this.legacyArgs);
for (let arg of rawArgs) {
let b = yield liquidEngine.evalValue(arg, ctx);
argArray.push(b);
}
} else if (this.orderedArgs) {
for (let arg of this.orderedArgs) {
let b = yield evalToken(arg, ctx);
argArray.push(b);
}
}

let argArray = yield* Liquid.evalArguments(this, ctx);
let ret = yield shortcodeFn.call(Liquid.normalizeScope(ctx), ...argArray);
return ret;
},
Expand Down Expand Up @@ -249,19 +272,7 @@ class Liquid extends TemplateEngine {
stream.start();
},
render: function* (ctx /*, emitter*/) {
let argArray = [];
if (this.legacyArgs) {
let rawArgs = Liquid.parseArguments(_t.argLexer, this.legacyArgs);
for (let arg of rawArgs) {
let b = yield liquidEngine.evalValue(arg, ctx);
argArray.push(b);
}
} else if (this.orderedArgs) {
for (let arg of this.orderedArgs) {
let b = yield evalToken(arg, ctx);
argArray.push(b);
}
}
let argArray = yield* Liquid.evalArguments(this, ctx);

const html = yield liquidEngine.renderer.renderTemplates(this.templates, ctx);

Expand Down
82 changes: 82 additions & 0 deletions test/TemplateRenderLiquidTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -658,6 +658,38 @@ test("Liquid Nested Paired Shortcode", async (t) => {
);
});

test("Liquid Paired Kwargs Shortcode with Tag Inside", async (t) => {
let tr = await getNewTemplateRender("liquid", "./test/stubs/");
tr.engine.addPairedShortcode("postfixWithZach", function (content, kwargs) {
var { str } = kwargs ?? {};
return str + content + "Zach";
});

t.is(
await tr._testRender(
"{% postfixWithZach str: name %}Content{% if tester %}If{% endif %}{% endpostfixWithZach %}",
{ name: "test", tester: true }
),
"testContentIfZach"
);
});

test("Liquid Nested Paired Kwargs Shortcode", async (t) => {
let tr = await getNewTemplateRender("liquid", "./test/stubs/");
tr.engine.addPairedShortcode("postfixWithZach", function (content, kwargs) {
var { str } = kwargs ?? {};
return str + content + "Zach";
});

t.is(
await tr._testRender(
"{% postfixWithZach str: name %}Content{% postfixWithZach str: name2 %}Content{% endpostfixWithZach %}{% endpostfixWithZach %}",
{ name: "test", name2: "test2" }
),
"testContenttest2ContentZachZach"
);
});

test("Liquid Shortcode Multiple Args", async (t) => {
let tr = await getNewTemplateRender("liquid", "./test/stubs/");
tr.engine.addShortcode("postfixWithZach", function (str, str2) {
Expand All @@ -673,6 +705,56 @@ test("Liquid Shortcode Multiple Args", async (t) => {
);
});

test("Liquid Shortcode Keyword Arg", async (t) => {
let tr = await getNewTemplateRender("liquid", "./test/stubs/");
tr.engine.addShortcode("postfixWithZach", function (str, kwargs) {
let { append } = kwargs ?? {};
return str + "Zach" + append;
});

t.is(
await tr._testRender("{% postfixWithZach name append: other %}", {
name: "test",
other: "howdy",
}),
"testZachhowdy"
);
});

test("Liquid Shortcode Multiple Keyword Args", async (t) => {
let tr = await getNewTemplateRender("liquid", "./test/stubs/");
tr.engine.addShortcode("postfixWithZach", function (str, kwargs) {
let { prepend, append } = kwargs ?? {};
return prepend + str + "Zach" + append;
});

t.is(
await tr._testRender(
"{% postfixWithZach name prepend: 'string' append: other %}",
{
name: "test",
other: "howdy",
}
),
"stringtestZachhowdy"
);
});

test("Liquid Shortcode Only Keyword Args", async (t) => {
let tr = await getNewTemplateRender("liquid", "./test/stubs/");
tr.engine.addShortcode("postfixWithZach", function (kwargs) {
let { prepend, append } = kwargs ?? {};
return prepend + "Zach" + append;
});

t.is(
await tr._testRender("{% postfixWithZach prepend: 'string' append: name %}", {
name: "test",
}),
"stringZachtest"
);
});

test("Liquid Include Scope Leak", async (t) => {
let tr1 = await getNewTemplateRender("liquid", "./test/stubs/");
t.is(tr1.getEngineName(), "liquid");
Expand Down

0 comments on commit f8a4ef0

Please sign in to comment.