Skip to content

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

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

Map only works with strings #113

Closed
cornr opened this issue Jan 10, 2019 · 14 comments
Closed

Map only works with strings #113

cornr opened this issue Jan 10, 2019 · 14 comments

Comments

@cornr
Copy link

cornr commented Jan 10, 2019

I am trying to filter an array using map. In this case I filter Sourcery variables:

{% map type.allVariables into vars using variable %}{% if variable.readAccess != "private" and variable.readAccess != "fileprivate" and not variable.isComputed %}{{ variable }}{% endif %}{% endmap %}

Afterwards I want to print these filtered vars.
{% for variable in vars where variable != "" %}{{ variable.name }}: {{ variable.name }}{% if not forloop.last %}, {% endif %}{% endfor %}

A get following Exception:

Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<TtGCs19_SwiftStringStorageVs6UInt16 0x7fb2a2df64f0> valueForUndefinedKey:]: this class is not key value coding-compliant for the key name.'

My guess is the problem lies here:

let mappedValues: [String] = try values.enumerated().map { index, item in

The MapNode expects the mapped 0utput to be a String.
I could dig into this and provide a fix if you recognize it as an issue.

@djbe
Copy link
Member

djbe commented Jan 10, 2019

The thing is that for Stencil, everything that you output is a String. The result of a map iteration will always be a String. What you're doing here is actually mapping your array of variables to a list of variable descriptions and empty strings (for those that don't match your if)

What you could do instead is filter in the for loop:

{% for variable in vars where variable.readAccess != "private" and variable.readAccess != "fileprivate" %}{{ variable.name }}: {{ variable.name }}{% if not forloop.last %}, {% endif %}{% endfor %}

@cornr
Copy link
Author

cornr commented Jan 10, 2019

Ok. The filter in the for loop is what I am doing right now. And sure it does work. But I use this for loop and therefore the filter several times and the condition is a lot more complex. Thats why I want to filter the array once and then iterate over it at different places.

@djbe
Copy link
Member

djbe commented Jan 10, 2019

What's useful then is to separate that filter logic into a macro, which returns "true" or "false", and set a variable to that result inside your for loop. Then check the value with an if test:

{% macro myMacro variable %}{% filter removeNewlines:"all" %}
{% if some long test %}true{%else %}false{% endif %}
{% endfilter %}{% endmacro %}

...

{% for variable in vars %}{% filter removeNewlines:"leading" %}
  {% set shouldShow %}{% call myMacro variable %}{% endset %}
  {% if shouldShow == "true" %}
    {{ variable.name }}: {{ variable.name }}
    {% if not forloop.last %}, {% endif %}{% endfor %}
  {% endif %}
{% endfilter %}{% endfor %}

@cornr
Copy link
Author

cornr commented Jan 10, 2019

Thanks for that pattern. I am gonna try this tomorrow.

@AliSoftware
Copy link
Contributor

AliSoftware commented Jan 10, 2019

PS: just missing an {% endfilter %} in the macro and for blocks of your example @djbe 😉

@djbe
Copy link
Member

djbe commented Jan 10, 2019

@AliSoftware I don't know about anything :trollface:

@AliSoftware
Copy link
Contributor

@djbe You can't hide 😄
image

@cornr
Copy link
Author

cornr commented Jan 11, 2019

This works. Thanks. But the {% if not forloop.last %}, {% endif %}does not work anymore since we don't now if the last variable is filtered by the macro.

PS: just an {% endfor %} after {% if not forloop.last %}, {% endif %} too much in your example @djbe 😉

@AliSoftware
Copy link
Contributor

AliSoftware commented Jan 11, 2019

One idea I pitched in our Slack was to create a new filter named call in StencilSwiftKit, that would take as a parameter the name of a 1-arity macro defined in your template, and call it like the call tag does.

That would allow you to use the filter syntax to call macros that take one parameter, like this {{ myvariable|call:"myMacro" }} … as an alternative way of using the set + call tags syntax {% set shouldShow %}{% call myMacro myvariable %}{% endset %}

note that this filter would only accept macros that have exactly one parameter then, and should throw a TemplateError otherwise

If we decide to add such a convenience filter in StencilSwiftKit, that means the code suggested by @djbe would become something like this:

{% for variable in vars where variable|call:"myMacro" == "true" %}{% filter removeNewlines:"all" %}
    {{ variable.name }}: {{ variable.name }}
{% if not forloop.last %}, {% endif %}
{% endfilter %}{% endfor %}

And given that you'd be able to use the "for where" syntax in the for tag, I think that would fix the issue you mention with the forloop.last too!

If you feel like this addition would be interesting and worth it, we'd welcome a PR to add it 😉

@cornr
Copy link
Author

cornr commented Jan 11, 2019

That would a nice addition as the filtering is would ne named and separated from the template. I am going to experiment with this

@cornr
Copy link
Author

cornr commented Jan 11, 2019

I think this can not be implemented as a filter since

protocol FilterType {
    func invoke(value: Any?, arguments: [Any?]) throws -> Any?
}

does not get the context to resolve the CallableBlock from the macro tag.

@djbe
Copy link
Member

djbe commented Jan 11, 2019

@cornr stencilproject/Stencil#203 was merged a while ago that adds a context: Context parameter to the invoke function, so that shouldn't be an issue. It hasn't been released yet, but you can test the latest "master" branch of Stencil.

@claire-lynch-okcupid
Copy link

claire-lynch-okcupid commented Mar 23, 2023

@djbe @cornr it's been a long time but do you remember whether you got this working? I read in the Stencil docs, "The equality operator only supports numerical, string and boolean types." So when I attempt to evaluate something like, {* if myVariable == "true" *} it doesn't ever evaluate to true because myVariable isn't a string. Suggestions?

@cornr
Copy link
Author

cornr commented Mar 24, 2023

Sorry @claire-lynch-okcupid I don't know what was the outcome of this. Unfortunately I did not work with StencilKit lately.

@SwiftGen SwiftGen locked and limited conversation to collaborators Aug 26, 2023
@djbe djbe converted this issue into discussion #173 Aug 26, 2023

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

Projects
None yet
Development

No branches or pull requests

4 participants