Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support keyword arguments for Liquid shortcodes. #1263

Closed
dawaltconley opened this issue Jun 16, 2020 · 7 comments
Closed

Support keyword arguments for Liquid shortcodes. #1263

dawaltconley opened this issue Jun 16, 2020 · 7 comments

Comments

@dawaltconley
Copy link

Is your feature request related to a problem? Please describe.

Currently, a Nunjucks shortcode or custom tag like this...

{% test 'arg1', 'arg2', foo='bar', baz=5 %}

...will pass the following arguments to its function:

[ 'arg1', 'arg2', { __keywords: true, foo: 'bar', baz: 5 } ]

This makes a lot of sense to me and I'd like to see its behavior replicated in Liquid shortcodes.

Describe the solution you'd like

I wrote a parser for Liquid arguments, which (mostly) matches the moo lexer currently used for Liquid shortcodes, but also supports keyword arguments and mimics the above behavior.

I can submit this as a pull request, though I think it partially depends on #1058 and how 11ty handles async argument evaluations.

Describe alternatives you've considered

There are some slight differences between mine and the moo parser, which I think bring it closer to Liquid syntax but which could be (theoretically?) breaking:

  1. Whitespace / arg separators are /,?[ \t\n\r]+/ instead of /[, \t]+/. So arg1 ,arg2 or arg1,,,arg2 would be invalid.
  2. Numbers only allow one dot: /[0-9]+\.?[0-9]*/ instead of /[0-9]+\.*[0-9]*/
  3. Variables can only have periods separating valid variable names, i.e foo.bar.baz and not ..foobar.baz.

I could bring it exactly in line with the moo lexer if that's preferred. I assume it might be possible to do this with moo too, though I haven't used it before.

@AleksandrHovhannisyan
Copy link
Contributor

Came up with a workaround in the meantime that involves wrapping the shortcode in an include, assembling JSON with Liquid, and passing along the parsed JSON object to the shortcode. Hope it's okay for me to share it here: https://www.aleksandrhovhannisyan.com/blog/passing-object-arguments-to-liquid-shortcodes-in-11ty/

@ghost
Copy link

ghost commented Nov 10, 2021

Would love to revive this issue! Currently working on our syntax for "props" on Vite components, but it's a shame nunjucks is the only option (especially since liquid is the default templating language).

@dawaltconley
Copy link
Author

dawaltconley commented Nov 10, 2021

Would also like to help close this. I see there are conflicts now in my open pr (#1733) which I can take a look at. Is there anything else there that needs to be addressed? I added error tests to the parser & fixed the biggest breaking change from the original pr.

Down to look over any of that again but guidance would be welcome. :)

Also just fwiw it's currently possible to emulate this by using the parser in a custom tag, which is what I've been doing for personal plugins & such.

const argParse = require('liquid-args');

// this function adds custom tags that mimic shortcodes with kwarg support
const addLiquidShortcode = (eleventyConfig, tagName, shortcodeFunc) =>
  eleventyConfig.addLiquidTag(tagName, function(liquidEngine) {
    return {
      parse: function(tagToken) {
        // an unparsed string of everything following the tagName
        this.args = tagToken.args;
      },
      render: async function(scope) {
        // function to evaluate each argument within the current scope
        // so liquid variables get converted to their values
        const evalValue = arg => liquidEngine.evalValue(arg, scope);

        // pass that to the parser function, which
        // (because it's async) returns an array of promises
        const args = await Promise.all(argParse(this.args, evalValue));

        // the resolved promises are the parsed arg & kwarg values
        // pass them directly to the function
        return shortcodeFunc(...args);
      }
    };
  });

@mphstudios
Copy link

@dawaltconley are you extending the UserConfig to override the UserConfig .addLiquidShortcode method, or do you have an example of how you are using the above method in your plugins?

@dawaltconley
Copy link
Author

dawaltconley commented Nov 18, 2021

@mphstudios not overriding config behavior, just using the addLiquidTag method for my plugins, where I would otherwise create a shortcode, like above. As I understand, shortcodes are basically just wrappers around this method.

I'm currently doing this with a very basic / incomplete site header plugin. Basically it works like:

const argParse = require('liquid-args');

const buildHeader = function(...args) {
  let kwargs = args.find(a => a.__keywords);
  // do stuff that returns html based positional and keyword args
};

// plugin export
module.exports.eleventy = (eleventyConfig, opts={}) => {
  let { tagName = 'header' } = opts;
  // add shortcodes for any other templating languages individually
  eleventyConfig.addNunjucksShortcode(tagName, buildHeader);
  // instead of a shortcode, create a custom tag for Liquid using the arg parser
  eleventyConfig.addLiquidTag(tagName, function(liquidEngine) {
    return {
      parse: function(tagToken) {
        this.args = tagToken.args;
      },
      render: async function(scope) {
        const evalValue = arg => liquidEngine.evalValue(arg, scope);
        const args = await Promise.all(argParse(this.args, evalValue));
        return buildHeader(...args);
      }
    };
  });
};

Then that gets imported into a config file like:

const header = require('./modules/header.js');

module.exports = eleventyConfig => {
  // set shortcodes/tags for all languages in one place
  eleventyConfig.addPlugin(header.eleventy);
};

The addLiquidTag stuff could also be done directly in the config file, using something like the addLiquidShortcode function above. But imo it's nicer to keep the shortcode / tag methods in a plugin. :)

@mphstudios
Copy link

@dawaltconley thanks, this is helpful. Is there a plan to include #1733 in a the 1.0.0 release?

@zachleat
Copy link
Member

zachleat commented Dec 6, 2022

My apologies here, folks. I think this one is going to have to be closed. liquidjs has added a parameter parsing Tokenizer that we’ll be migrating over to long term. Please follow along with that work at #2679

Really sorry we didn’t get this one in 💔

@zachleat zachleat closed this as completed Dec 6, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
4 participants