Mustache.js: Returning template variable names

Created on 31 Dec 2015  ·  12Comments  ·  Source: janl/mustache.js

Hi! This is a rather weird request but is there anyway to return the list of of input variables as an array of string or as an object? For a code generation function in my code, I need to map a series of inputs into
a mustache-templated string. The input variables are named exactly the same as the templating variables and will shadow/map to them perfectly and because this needs to be done automatically, hence the strange request. Here's an example of what I mean:

Assuming a function Mustache.getTemplateVariablesListAsObject

var pycode = <see below>
Blockly.JavaScript['math_foo'] = function(block) {
  var value_name = Blockly.JavaScript.valueToCode(block, 'NAME', Blockly.JavaScript.ORDER_ATOMIC);
// result would be something like: {value_name: value_name}
var inputList =  Mustache.getTemplateVariablesListAsObject(pycode)

  var code = Mustache.render(pycode,  inputList)
  return code;
};
def hello(foo):
  print foo
hello({{value_name}})

I have been thinking over this for more than an hour and I still can't find a better way to do this. Would be really grateful if you can offer some alternative methods etc.

Most helpful comment

Simple solution to get just the top level:

Mustache.parse(template).filter(function(v) { return v[0] === 'name' || v[0] === '#' || v[0] === '&' }).map(function(v) { return v[1]; });

All 12 comments

If I'm understanding you correctly you want something like this

var myTemplate = "{{foo}} is {{bar}}";
var variableNames = Mustache.VariableNames(myTemplate) // ['foo', 'bar']

That's assuming VariableNames went and returned all the variable names from the template. If that's what you want then you could probably hack an implementation using the exposed parse function on the mustache writer.

Here's some code to get you started:

var results = Mustache.parse('{{foo}} is {{bar}}')
                       .filter(function(v) { return v[0] === 'name' })
                       .map(function(v) { return v[1]; });

console.log(results) // ["foo", "bar"]

Note that while it's possible to implement a naive version of this, there is no way to unambiguously extract all tag names, because things like this:

{{# foo }}
  * {{ bar }}
{{/ foo }}

… could mean either {foo: {bar: 'baz'}} _or_ {foo: true, bar: 'baz'}}.

@bobthecow is entirely right in this circumstance. The example i gave would only pull out all the identifier nodes, it would also remove all structure effectively flattening the tree.

@Romanx @bobthecow thanks for your help!

No problem. Good luck :)

I had a similar request, but needed to traverse the tree to find all variable names. Thought I'd share the solution I used if anyone needs a reference.

var parseTree = Mustache.parse('{{#foo}}{{bar}}{{/foo}} {{baz}}');
var variableList = parseTree.reduce(function flattenVariablesFromParseTree(acc, v){
                    if(v[0] === 'name'){
                      return acc.concat([v]);
                    } else if (v[0] === '#') {
                      return acc.concat(v[4].reduce(flattenVariablesFromParseTree, []));
                    } else {
                      return acc;
                    }
                  }, [])
                  .map(function(v){ return v[1]; });
//variableList: ["bar", "baz"]

@nicluo does this also suffer from the ambiguity problem?

Yep. It's inherent in the language spec.

The ambiguity problem is interesting, the docs mention that it would try to find the value in this context and then search the parent's context if no values are found. A bit of investigation got me into this:

{{bar}} 
{{#foo}}
  {{bar}} 
  {{#foo}}
    {{bar}} 
    {{#baz}}
      {{no}} 
      {{yes}}
    {{/baz}}
  {{/foo}}
{{/foo}}

var renderString = '{{bar}} {{#foo}}{{bar}} {{#foo}}{{bar}} {{#baz}}{{no}} {{yes}}{{/baz}}{{/foo}}{{/foo}}';
var renderContext = new Mustache.Context({
  bar: 'bar',
  baz: {
    no: 'no'
  },
  foo: {
    bar: 'y',
    foo: {
      bar: 'z',
      yes: 'yes'
    }
  }});

var parseTree = Mustache.parse(renderString);
var variableRefList = [];
var variableNameList = parseTree.reduce(function flattenVariablesFromParseTree(acc, v){
                    // Skip non-name or non-# tags
                    if(v[0] !== 'name' && v[0] !== '#'){
                      return acc;
                    }

                    var paths = [v[1]].concat(this.parents.slice(0).map(function(e){
                      return [e, v[1]].join('.');
                    }));

                    // Pops available context until a value is found
                    var path;
                    while(path = paths.pop()){
                      if(renderContext.lookup(path)){
                        //push to advanced list
                        variableRefList.push(path);
                        contextFound = true;
                        break;
                      }
                    }

                    if(v[0] === 'name'){
                      return acc.concat([v]);
                    } else if (v[0] === '#')  {
                      if(typeof renderContext.lookup(path) === 'object'){
                        this.parents = this.parents.concat([path]);
                      }

                      return acc.concat(v[4].reduce(
                        flattenVariablesFromParseTree.bind({
                          parents: this.parents
                        }), []));
                    }
                  }.bind({parents: []}), [])
                  .map(function(v){ return v[1]; });

//variableNameList: ["bar", "bar", "bar", "no", "yes"]
//variableRefList: ["bar", "foo", "foo.bar", "foo.foo", "foo.foo.bar", "baz", "baz.no", "foo.foo.yes"]
//Mustache.render(renderString, renderContext): bar y z no yes

The example is very contrived, and there are many tricks used to stay concise, but it should show how difficult I find it is to reinvent the wheel. Cheers

@Immortalin Can you elaborate / provide a better definition of the problem? What happens with properties nested in objects? Can you provide a more complete input and output?

@dasilvacontin the project that requires this feature is currently on hiatus so I am going to close this for the time being

Simple solution to get just the top level:

Mustache.parse(template).filter(function(v) { return v[0] === 'name' || v[0] === '#' || v[0] === '&' }).map(function(v) { return v[1]; });

Was this page helpful?
0 / 5 - 0 ratings

Related issues

ForbesLindesay picture ForbesLindesay  ·  14Comments

funston picture funston  ·  7Comments

amper5and picture amper5and  ·  5Comments

zekth picture zekth  ·  18Comments

kuldeepdhaka picture kuldeepdhaka  ·  9Comments