stencilproject / Stencil

Stencil is a simple and powerful template language for Swift.
https://stencil.fuller.li
BSD 2-Clause "Simplified" License
2.34k stars 221 forks source link

Subscript syntax for Variables #215

Closed djbe closed 6 years ago

djbe commented 6 years ago

This allows users to refer to variables (and properties) using indirection.

Let's say for example we have the following context:

[
  "object": [
    "name": "John",
    "role": "Developer",
    "location": "Somewhere"
  ],
  "visible": ["name", "role"]
]

A user can then write:

{% for property in visible %}
 - {{property}}: {{ object.$property }}
{% endfor %}

To produce:

- name: John
- role: Developer
djbe commented 6 years ago

Note: the reason I went with the object.$property syntax instead of something like object[property] is that there's no cross-platform regex support (NSRegularExpression is macOS only).

AliSoftware commented 6 years ago

I love the idea.

It's a shame that we can't use subscript for that though

1) FWIW I think you could be able to use .[prop] instead of .$prop even without using regex, eg:

if bit.hasPrefix("[") && bit.hasSuffix("]"),
       let resolved = try? Variable(String(bit.dropFirst().dropLast())).resolve(context),
…

2) but even with such a solution that would still mean the syntax need to use the dot, and would look like object.[property] where what we would ideally want would be object[property]. So in that regard if the solution needs for technical reasons to use a dot then I prefer o.$p to o.[p] and if we want to support o[p] we'd have to find a different solution for the implementation

3) Before validating that kind of syntax I'd suggest to look at the syntax Jinja uses for such feature (if it does support it, that is). We have to remember that Stencil is inspired by Jinja-like template engines and it's better for the end user of we can use the same or very similar syntax and tags whenever possible/relevant for similar features.

djbe commented 6 years ago

Jinja uses braces for lookup: https://stackoverflow.com/questions/27208891/jinja2-dictonary-lookup-using-a-variable-key#27208914

AliSoftware commented 6 years ago

Damn so that would still be better to find a way to do that with braces without dot, both because it feels more natural and because it's similar to Jinja… but I guess you tried to find a solution for that already?

djbe commented 6 years ago

I'm honestly not sure how to tackle that without regular expressions, and without blowing this code up into some massive thing.

ilyapuchka commented 6 years ago

I didn't test this code, but this might work:

  fileprivate func lookup() -> [String] {
    return variable.split(separator: ".")
        .flatMap({ $0.split(separator: "[", maxSplits: 1) })
        .map({ String($0).trim(character: "]") })
  }

This will though only work on single brackets, like foo[bar], but not foo[bar[abc]], which I'm not sure we should even support.

@AliSoftware good point about filter. Simple alternative to all of that can be a filter like foo|attr:bar (or with any other maningful name)

ilyapuchka commented 6 years ago

Can also try split(maxSplits: 2, whereSeparator isSeparator: { $0 == "[" || $0 == "]" }) variant where separator can be either [ or ] so there will be no need for trim, I guess.

AliSoftware commented 6 years ago

@ilyapuchka Mmmh interesting idea to use a filter to extract the property. But that would still mean we couldn't apply a filter on that filter 😅 it would enable us to do object|attr:property.name to do the equivalent of Jinja's object[property.name] but we still wouldn't be able to do the equivalent of Jinja's object[property.name|lowercase] for example

AliSoftware commented 6 years ago

I really think the right syntax is to use the same syntax as Jinja, the question is how to implement it in Stencil without breaking all the code…

ilyapuchka commented 6 years ago

@AliSoftware true. We can for that add support for brackets in filter expressions item|attr:(bar|attr:abc) which might be also useful in general. I have added that for boolean expressions in #165

djbe commented 6 years ago

I'm working on a solution right now, might take a bit. I'm going to iterate over the characters (in the lookup function), and keep track if I'm in a lookup (between [ and ]). I'll even support multiple lookup levels 😄

djbe commented 6 years ago

Since the force push is hiding the commit comments:

Does that mean that object[[[]]]...[]..[] would be valid, as you filter out empty bits?

We should define the syntax a bit more. To make things a bit easer: