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

Renderer.context won't update when looping through an array #158

Open
crusaderky opened this issue Dec 18, 2013 · 4 comments
Open

Renderer.context won't update when looping through an array #158

crusaderky opened this issue Dec 18, 2013 · 4 comments

Comments

@crusaderky
Copy link

My target: print the following lines:

Al,John,Jack
Tim,Tom,Todd

without a final comma.

I tried this way:

ctx = {
    'gangs': [
        {'gangsters': [ {'name': 'Al' }, {'name': 'John'}, {'name': 'Jack'}]},
        {'gangsters': [ {'name': 'Tim'}, {'name': 'Tom'} , {'name': 'Todd'}]},
    ]
}

class Lambdas(object):
    def __init__(self, renderer):
        self.renderer = renderer

    def rstrip(self):
        "Remove last character"
        print self.renderer.context
        return lambda s: self.renderer.render(s, self.renderer.context)[:-1]

renderer = pystache.Renderer(missing_tags='strict')

print renderer.render("""
    {{#gangs}}
        {{#rstrip}}{{#gangsters}}{{name}},{{/gangsters}}{{/rstrip}}
    {{/gangs}}
""", ctx, Lambdas(renderer))

The output:

ContextStack({'gangs': [{'gangsters': [{'name': 'Al'}, {'name': 'John'}, {'name': 'Jack'}]}, {'gangsters': [{'name': 'Tim'}, {'name': 'Tom'}, {'name': 'Todd'}]}]}, <__main__.Lambdas object at 0x15cadb10>, {'gangsters': [{'name': 'Al'}, {'name': 'John'}, {'name': 'Jack'}]})
ContextStack({'gangs': [{'gangsters': [{'name': 'Al'}, {'name': 'John'}, {'name': 'Jack'}]}, {'gangsters': [{'name': 'Tim'}, {'name': 'Tom'}, {'name': 'Todd'}]}]}, <__main__.Lambdas object at 0x15cadb10>, {'gangsters': [{'name': 'Al'}, {'name': 'John'}, {'name': 'Jack'}]})

Al,John,Jack
Al,John,Jack

The culprit is the invocation to render() inside rstrip. Notice how, during the second call, the 3d element of the ContextStack is exactly identical to the previous call, instead of changing to {'gangsters': [ {'name': 'Tim'}, {'name': 'Tom'} , {'name': 'Todd'}]}.

Is this a bug, or am I missing something?!?

@crusaderky
Copy link
Author

Rewriting it as dicts won't work either:

ctx = {
    'gang1': { 'gangsters': [ {'name': 'Al' }, {'name': 'John'}, {'name': 'Jack'}] },
    'gang2': { 'gangsters': [ {'name': 'Tim'}, {'name': 'Tom'} , {'name': 'Todd'}] }
}

print renderer.render("""
    {{#gang1}}
        {{#rstrip}}{{#gangsters}}{{name}},{{/gangsters}}{{/rstrip}}
    {{/gang1}}
    {{#gang2}}
        {{#rstrip}}{{#gangsters}}{{name}},{{/gangsters}}{{/rstrip}}
    {{/gang2}}
""", ctx, Lambdas(renderer))

@cjerdonek
Copy link
Collaborator

Yes, it looks like there may be an issue. I will try to track this down.

@cjerdonek
Copy link
Collaborator

It looks like the issue is that calling self.renderer.render() inside the lambda has the side effect of overwriting the renderer's original context property with a new context stack. Later calls to self.renderer.context then no longer reference the original context which has the right gang.

This is related to this TODO.

One work-around is to avoid changing the original renderer's context by creating a new renderer each time:

def rstrip(self):
    "Remove last character"
    print self.renderer.context
    renderer = pystache.Renderer(missing_tags='strict')
    return lambda s: renderer.render(s, self.renderer.context)[:-1]

Another work-around is to make sure you're always referencing the original context:

class Lambdas(object):
    def __init__(self, renderer):
        self.renderer = renderer
        self.context = None

    def rstrip(self):
        "Remove last character"
        if self.context is None:
            self.context = self.renderer.context
        print self.renderer.context
        return lambda s: self.renderer.render(s, self.context)[:-1]

I think issue #157 is related and may have other suggestions.

@crusaderky
Copy link
Author

Thanks, this works fine:

def rstrip(self):
    "Remove last character"
    return lambda s: copy.deepcopy(self.renderer).render(s, self.renderer.context)[:-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

2 participants