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

A way to evaluate a String inside a template #230

Closed
electrotype opened this issue Oct 7, 2016 · 10 comments
Closed

A way to evaluate a String inside a template #230

electrotype opened this issue Oct 7, 2016 · 10 comments

Comments

@electrotype
Copy link
Contributor

electrotype commented Oct 7, 2016

Let's say using this Map :

user
 |___ firstName : "John"
 |___ lastName : "Doe"

Then something like this would be nice:

{% evaluate firstName = "user.firstName " %} 

==> 'John'

I'm not sure if this would be useful for many people, but it would be for me!

@ebussieres
Copy link
Member

Why don't you just use something like this

{% set firstname = user.firstName %}

@electrotype
Copy link
Contributor Author

@ebussieres I agree there is a critical point missing in my question! : The "user.firstName" key is dynamically built, I don't know it at compile time!

This would be a better example :

{% set key= user + ".firstName " %} 
{% evaluate result = key %}

@ebussieres
Copy link
Member

ebussieres commented Oct 12, 2016

Ah ok i see now ! It don't think it's supported but adding a evaluate tag would be useful I agree.

However, it'll be difficult to implement. The lexer splits the template into Token objects. The string "user.firstName" represent a single String Token. If we want to evaluate it, we need to split it in 3 Tokens

  • One for "user"
  • One for punctuation "."
  • One for "firstName"

If we add a evaluate tag, it'll be too late because it runs after the lexer and manipulates token.

@mbosecke
Copy link
Collaborator

If it's only the attribute name that is dynamic but the root variable isn't, you can use the square bracket notation to look up dynamic attributes:

{% set attribute = 'firstName' %}
{{ user[attribute] }} 

But if the root variable is dynamic, i.e. "user", then I don't think there's currently a good solution. Feature request #235 is for string interpolation which I believe will solve your issue. If you think that's the case, we'll close this task as a duplicate.

@electrotype
Copy link
Contributor Author

If it's possible to pass a root variable name to that string interpolation construct, I'm pretty sure it would do the trick, yes! No problem for me if you close this task as a duplicate. Thanks.

@electrotype
Copy link
Contributor Author

So, after checking the PR #240 , the current suggested String Interpolation solution doesn't help with this issue.

@mbosecke
Copy link
Collaborator

Yup, I was thinking about this issue a bit more and I think we'll need to come up with something a bit more creative. I'll have to take a look at what other template engines are doing. I believe twig uses a special variable called _context that can be used to access dynamic variables, ex. {{ _context[variableName] }} but I'm not a fan of it.

@DanStout
Copy link
Contributor

DanStout commented Nov 14, 2016

This is pretty hacky, but if I've understood what you are looking for correctly, couldn't you implement a function like this:

private static class Func implements Function {
    @Override
    public List<String> getArgumentNames() {
        return Arrays.asList("name");
    }

    @Override
    public Object execute(Map<String, Object> args) {
        String key = (String) args.get("name");

        EvaluationContext ctx = (EvaluationContext) args.get("_context");
        ScopeChain chain = ctx.getScopeChain();

        String[] parts = key.split("\\.");
        String name = parts[0];
        String fieldName = parts[1];
        Object val = chain.get(name);
        try {
            Field field = val.getClass().getDeclaredField(fieldName);
            field.setAccessible(true);
            return field.get(val);
        } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
            return null;
        }
    }
}

and use it like so:

@Test
public void test() throws PebbleException, IOException {
    String src = "{{ func('user.firstName') }}";
    Map<String, Object> map = new HashMap<>();
    map.put("user", new User());
    assertEquals("joe", evaluate(src, map));
}

private static class User {
    public String firstName = "joe";
}

GetAttributeExpression is already doing the logic for using reflection to retrieve properties of objects/maps/arrays by name, so that could be reused.

@jayaramcs
Copy link

jayaramcs commented Nov 14, 2016

Hi guys

Would such interpolation be applicable to dynamic generation of macro names?

For Eg :

{% macro file_macro(files) %}
   do files stuff  
{% endmacro %}

{% macro directory_macro(dir) %}
   do directory stuff  
{% endmacro %}

{% set macroName = 'directory' + "_macro" %}
{{ #macroName( items ) }}

I know this may be a tall order; Our code currently has way too many conditional macro selector code :(

Thanks

@ebussieres
Copy link
Member

Resolved by adding the DynamicAttributeProvider interface

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

5 participants