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

Returning template variable names #538

Closed
Immortalin opened this issue Dec 31, 2015 · 12 comments
Closed

Returning template variable names #538

Immortalin opened this issue Dec 31, 2015 · 12 comments

Comments

@Immortalin
Copy link

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.

@Romanx
Copy link

Romanx commented Jan 5, 2016

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"]

@bobthecow
Copy link

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'}}.

@Romanx
Copy link

Romanx commented Jan 5, 2016

@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.

@Immortalin
Copy link
Author

@Romanx @bobthecow thanks for your help!

@bobthecow
Copy link

No problem. Good luck :)

@nicluo
Copy link

nicluo commented Jan 12, 2016

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"]

@Immortalin
Copy link
Author

@nicluo does this also suffer from the ambiguity problem?

@bobthecow
Copy link

Yep. It's inherent in the language spec.

@nicluo
Copy link

nicluo commented Jan 12, 2016

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

@dasilvacontin
Copy link
Collaborator

@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?

@Immortalin
Copy link
Author

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

@shoebmogal
Copy link

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]; });

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants