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

text/template: support lexically scoped variables? #14992

Closed
ghost opened this issue Mar 28, 2016 · 7 comments
Closed

text/template: support lexically scoped variables? #14992

ghost opened this issue Mar 28, 2016 · 7 comments

Comments

@ghost
Copy link

ghost commented Mar 28, 2016

I am trying to do a very simple task in a Go Template which amounts to "if something in a range is found that fulfills a certain condition then output A else output B".

However, this is really trivial task appears to be absolutely impossible to do:

        {{ $foundAnAddress := 0 }}
        {{ range $address := $container.Addresses }}
            {{ if eq $address.Port "8000" }}
                {{ $foundAnAddress := 1 }}
        server backend_{{ $container.Name }} {{ $address.IP }}:{{ $address.Port }}
            {{ end }}
        {{ end }}
        {{ if eq $foundAnAddress 0 }}
        # server is unreachable !! -> set maintenance host
        server maintenance localhost:8000
        {{ end }}

Why is it impossible? Because it appears to be not doable to pass any information from the inner loop outside again since it immediately expires in the scope, so the {{ if .. }} at the end never works as intended.

This stackoverflow answer http://stackoverflow.com/questions/28674199/in-a-go-template-range-loop-are-variables-declared-outside-the-loop-reset-on-ea describes workarounds involving modifying application code.

However, this is completely unrealistic and impractical because in the real world, usually the person writing a template is an end-user of the application and not a developer that would fork it and modify the application internals just to do such a simple task.

Therefore, please stop intentionally crippling the Go Template variable handling and allow people to write functional templates where at least some very simple logical tasks that aren't absolutely trivial can be done without immediately being required to fork and rewrite the entire application. (yes I get it, logic is always supposed to reside in the application. But sometimes that's just not a practical solution if you need a template that works right now and not in 10 years when the application was finally patched with the specific functionality required in your template.)

PS: I'm not very proficient in Go. My sincere apologies if the task above is actually possible without modifying application code. However, I read a lot of docs on Go Template by now and I haven't found a single way of doing it that doesn't require me to patch the host application..

@ghost
Copy link
Author

ghost commented Mar 28, 2016

Just as an additional info, while jinja2 has similar scoping it seems to have many possible ways of still doing this task: http://stackoverflow.com/questions/4870346/can-a-jinja-variables-scope-extend-beyond-in-an-inner-block

I agree they aren't specifically pretty, but maybe Go Template could be one of the first to actually allow this properly?

Suggestion on the syntax: Maybe add something like {{ $.myGlobalVar := 5 }} to set something globally that can be accessed from anywhere

@bradfitz bradfitz changed the title Allow proper variable handling in Go Template text/template: support lexically scoped variables? Mar 28, 2016
@bradfitz
Copy link
Contributor

/cc @robpike @adg

@adg
Copy link
Contributor

adg commented Mar 29, 2016

As you've noticed, the template package was not designed to be a programming language in its own right. In fact, that is a non-goal of the package.

If we were to go down this road, where would it end? Once you give people the essential primitives for implementing algorithms in the template language, they will thirst for more. At some point you end up implementing a new language or re-implementing a weird version of Go. I hope you can appreciate that this is not a minor issue, but rather a fundamental design decision.

I know this is an unsatisfying answer for you.

However, this is completely unrealistic and impractical because in the real world, usually the person writing a template is an end-user of the application and not a developer that would fork it and modify the application internals just to do such a simple task.

If the author of the program expects their users to write template code then they should provide a set of useful template functions and methods on their data types to make these common tasks possible. Or they may choose a different template package that permits this kind of programming.

Do you have a specific use case in mind? What is the application you are writing templates for?

@ghost
Copy link
Author

ghost commented Mar 29, 2016

More info on my use case:

The application: https://github.com/jwilder/docker-gen
What does it use templates for? It inputs data on running docker containers into the template (as different kind of arrays), and the template outputs a written config for any sort of application the user needs it for, e.g. a webserver that needs to redirect web clients to all containers in some specific way.
Why do I need something like lexically scoped variables? I want to iterate through the arrays of containers and do something if one or more fulfilling a condition are present, and output something else if nothing in the array fulfilled the condition (that is the problematic part). This is not the most trivial, but still a more or less basic logical workflow that can be essential for many more complex scenarios.
Why can't it be simply patched into docker-gen? The application will obviously not patch filtering for any specific condition, because that will heavily depend on each end user and the specific application they want to output a configuration for. E.g. the way the test condition looks like for me depends on my specific combination of an nginx container with a let's encrypt container, something docker-gen could never know or prepare for.

I see two solution possibilities for Go Template:

1. Global variables as specified

As described in this feature request's initial post.

Also, where would it end? Exactly here. Just add proper global scope variable support. Again jinja2 and others already support this with workarounds, the major problem of Go Template is that there appear to be none so far. Those special scenarios aren't very common, but if someone runs into one of them this feature will probably save them big time. Global variables make you go from various special scenarios with arrays being very hard to undoable unless the application provides a specific kind of filter function or processing function to create a new array from it, to easily almost Turing complete (minus infinite loops) with all sorts of special stuff being possible with workarounds if really needed in some situation.

2. Construct/assign a new array in the template from a previous one with a given filter condition

If you don't like the idea, you might be able to fix the specific thing I want by allowing to create an array with a filtered condition comparable to Python's list comprehension from an existing array, and then allow the template to iterate and examine the length of the resulting new array instead. However that is both probably more complicated for me to use for my specific use case, and also a more limited solution that doesn't cover many slightly more complex scenarios which I'm sure aren't that impossible to run into. I really think adding global variables that can be set/changed with the template is a much more universally useful idea.

@adg
Copy link
Contributor

adg commented Mar 29, 2016

It looks like docker-gen provides a pretty comprehensive suite of template functions, and that the behavior you're looking for can be obtained through the where function:

        {{ range $address := where $container.Addresses "Port" "8000" }}
          server backend_{{ $container.Name }} {{ $address.IP }}:{{ $address.Port }}
        {{ else }}
          # server is unreachable !! -> set maintenance host
          server maintenance localhost:8000
        {{ end }}

Note that the where function actually makes this cleaner than changing the template language as suggested. Let me know if I have misunderstood or that this doesn't work for you.

@ghost
Copy link
Author

ghost commented Mar 29, 2016

Nice, that seems very suitable and sorry for missing it.

Is there a reason why no such function is in the generic Go Template set of functions? (something like that "where" for array filtering) I also noticed there is no easy starts-with strings check or other basic string operations like

Also I would still like to point out that if another application is not as careful with supplying additional helper functions, generic global scope variables could still help people a lot in some scenarios..

@adg
Copy link
Contributor

adg commented Mar 29, 2016

Is there a reason why no such function is in the generic Go Template set of functions? (something like that "where" for array filtering) I also noticed there is no easy starts-with strings check or other basic string operations like

It has simply never been proposed before. Perhaps we should take a look at the primitives provided by docker-gen and consider adding them to the default set of template functions. I'll create an issue for that now.

Also I would still like to point out that if another application is not as careful with supplying additional helper functions, generic global scope variables could still help people a lot in some scenarios..

Indeed, but for the reasons I described above I don't think this will happen. Sorry. That being the case, I'm going to close this issue for now.

Thank you for taking the time to report the issue in such detail. Your feedback has been very helpful thus far.

@adg adg closed this as completed Mar 29, 2016
@golang golang locked and limited conversation to collaborators Mar 29, 2017
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

3 participants