prevent mustache from looking up the context stack? #19

Closed
psybert opened this Issue May 24, 2012 · 21 comments

Projects

None yet

2 participants

@psybert
psybert commented May 24, 2012

I've run into a real world scenario that I've been unable to resolve. Here's a sample of what I'm trying to do:

How can I stop mustache from looking up the context stack when inside an array, or how can i set the context for an array? I know that some other implementations use this. to overcome this, but wasn't sure if there was a better way. I wouldn't want to turn it off totally, but be able to do so through the template syntax.

Here's some sample code so you can see what I'm talking about

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    // Override point for customization after application launch.
    self.window.backgroundColor = [UIColor whiteColor];
    [self.window makeKeyAndVisible];


    NSDictionary* seanDic = [[NSDictionary alloc] initWithObjectsAndKeys:@"sean",@"name", nil];
    NSDictionary* sethDic = [[NSDictionary alloc] initWithObjectsAndKeys:@"seth",@"name", nil]; 
    NSArray* johnsFriends = [[NSArray alloc] initWithObjects:seanDic, sethDic, nil];    
    NSDictionary* johnDic = [[NSDictionary alloc] initWithObjectsAndKeys:@"john",@"name", johnsFriends, @"friends", nil];
    NSDictionary* fredDic = [[NSDictionary alloc] initWithObjectsAndKeys:@"fred",@"name", nil]; //fred has no friends
    NSArray* myEnemies = [[NSArray alloc] initWithObjects:fredDic, johnDic, nil];
    NSDictionary* mikeDic = [[NSDictionary alloc] initWithObjectsAndKeys:@"mike",@"name", nil];
    NSDictionary* mattDic = [[NSDictionary alloc] initWithObjectsAndKeys:@"matt",@"name", nil];
    NSArray* myFriends = [[NSArray alloc] initWithObjects:mikeDic, mattDic, nil];

    NSDictionary* me = [[NSDictionary alloc] initWithObjectsAndKeys:@"rob", @"name", myFriends, @"friends", myEnemies, @"enemies", nil];

    NSString* template = @"Me:{{name}}\n\nMy Friends:{{#friends}}\n\t{{name}}{{/friends}}\n\nMy Enemies:{{#enemies}}\n\t{{name}} and his friends:{{#friends}} {{name}}, {{/friends}}{{/enemies}}";

    NSString *text = [GRMustacheTemplate renderObject:me fromString:template error:NULL];

    UITextView* txtView = [[UITextView alloc] initWithFrame:CGRectMake(0, 20, 320, 460)];
    txtView.text = text;
    [self.window addSubview:txtView];
    return YES;
}

Result:

Me:rob

My Friends:
    mike
    matt

My Enemies:
    fred and his friends: mike,  matt, 
    john and his friends: sean,  seth, 

So as you can see fred has no friends, but according to the template he does (he's stealing my friends!). Maybe I'm looking at this weird, but we haven't been able to figure out a way to make this work.

@groue
Owner
groue commented May 24, 2012

Hi @psybert, nice to see you back :-)

This "stealing" behavior is right is the mustache spec. There's no way to escape it. The solution to your issue is simply to have fred have an empty array of friends.

Generally speaking, a way to look at your code as it is could be: "Hey, Fred, do you have any friends? - Dude, I don't know - You don't know? OK, I'll assume anything then". One could say that fred doesn't fulfill the contract on the "friends" key, which is "return an array of friends". As soon as fred returns an empty array, it would become: "Fred, any friend? - Nope - Good for you! Friends are much overrated."

@groue
Owner
groue commented May 24, 2012

I maintain that fred should return an explicit array, but you deserve a more complete answer.

Do you know that {{foo.bar}} is different from {{#foo}}{{bar}}{{/foo}}? The dot has bar looked right into foo, and not up the context stack.

This scope-restriction syntax could help here, and you could write: {{#enemies}}...{{#self.friends}}...{{/self.friends}}...{{/enemies}}

Beware, however, that the "self" key is not defined by mustache: this template would work because of the underlying KVC implementation of GRMustache. So this technique is not cross-language, and the template could not be reused in another mustache implementation.

One could wish writing {{#enemies}}...{{#.friends}}...{{/.friends}}...{{/enemies}}. This is not in the spec, and not supported by GRMustache. It may be a good idea to open an issue in the https://github.com/mustache/spec repo.

Final thought: really have fred return an empty array.

@psybert
psybert commented May 24, 2012

Hmm... Wasn't able to get the {{#self.friends}} syntax to work. Any ideas?

I agree on the data issue, (fred should have an array of no friends) except I don't own the data :(

I really like the {{#.friends}} syntax, as we also thought about it; the . just seemed natural to us yesterday when trying to get it to work.

@groue
Owner
groue commented May 24, 2012

The {{self.friends}} stuff doesn't work with NSDictionary… Damned, we're out of luck.

Does the data come from some JSON request?

@psybert
psybert commented May 24, 2012

Yes it comes from JSON

@groue
Owner
groue commented May 24, 2012

No chance you have the server fix its representation?

I see only two solutions, none of them satisfying:

  1. process the data, replacing nil with empty array for the "friends" key
  2. or, setup a template delegate, and have it replace nil invocation values with empty array when the key is "friends".

Now this really deserves an issue in the https://github.com/mustache/spec repo. You can link to this real story.

@psybert
psybert commented May 24, 2012

Yea, it's not my server. We've been using the same data source for about 1.5 years now with another template system for iOS. I don't know if using the delegate to look for "friends" is a good idea considering there's hundreds of templates we use with varying data sets.

What would it take to code support for {{#.friends}},{{#self.friends}} or {{#this.friends}}?

(I'm willing to use a modified version for now)

I'll open an issue with the mustache spec repo too.

@groue
Owner
groue commented May 24, 2012

The method you need to change in order to support {{#.friends}} is [GRMustacheTemplateParser invocationWithToken:error:].

It may not be easy to update. But your goal is, quite definitely, to have the keys local variable contain an array of two objects : @"." and @"friends". Now the last line of the method, return [GRMustacheInvocation invocationWithToken:token keys:keys] will return an invocation which gives what you need.

When you're happy, rebuild GRMustache with make clean && make in the terminal. This will give you an updated static library.

@psybert
psybert commented May 24, 2012

Ok I created the hack for now. Thanks for all your help. Going to create an issue at the spec page.

for those who want this hack too, add this at line 255 in GRMustacheTemplateParser.m. It's not really bullet proof as I'm sure varying whitespace will blow it up.

if (i == 0 && length > 1) {
    [keys addObject:[content substringWithRange:NSMakeRange(identifierStart, i+1-identifierStart)]];
    identifierStart = 1;
}
@psybert
psybert commented May 24, 2012

Looks like there's already an issue opened on the spec for this. mustache/spec#10

@groue
Owner
groue commented May 25, 2012

I've read this issue 10, which doesn't look like it's anywhere close to a resolution of the issue :-(

@psybert
psybert commented May 25, 2012

Oh well :\

I think the . to limit the context makes sense, as clearly a couple other people were thinking the same thing. Perhaps there can be a setting in GRMustache for it (setAllowDotToLimitContext) default to NO. :)

@groue
Owner
groue commented May 25, 2012

You know what? Bundle your changes in a nice bullet proof pull request, and I'll accept it.

@groue groue added a commit that referenced this issue May 26, 2012
@groue #19 support for `.foo` keys
A `.foo` key will look for `foo` at the top of the context stack only.
3642305
@groue
Owner
groue commented May 26, 2012

@psybert I'm doing it. GRMustache4 is about to be released.

@groue
Owner
groue commented May 26, 2012

GRMustache4 is out, with solutions for #18 and #19. Thanks for your contribution, @psybert.

@groue
Owner
groue commented May 26, 2012

Issue closed :-)

@groue groue closed this May 26, 2012
@psybert
psybert commented May 26, 2012

Awesome. Thanks @groue

Now if only we can figure out a clean way to write our own filters and formatters :)

@groue
Owner
groue commented May 26, 2012

It's actually hard not to fight against Mustache, considering the simplicity of the spec. But this simplicity is also key to its success. My line is to let GRMustache users tweak and hack, without compromising the Mustache spec. Thanks to users like you, most common use cases have an answer now. It has to be ugly sometimes. It used to be much more awful :-)

You know, before I shipped GRMustache, the unavoidable templating system was MGTemplateEngine. It's certainly not a bad beast. Besides, it's much richer, feature-wise. Even if Mustache is trendy, remember that one should always choose the tool that best fits her needs!

@groue
Owner
groue commented May 26, 2012

@psybert, you wrote: "We've been using the same data source for about 1.5 years now with another template system for iOS." Was it MGTemplateEngine? Another one? I'd be interested in the rationale behind switching to Mustache, if you have a couple of minutes.

@psybert
psybert commented May 26, 2012

sure, what's a good e-mail address i can reach you at?

@groue
Owner
groue commented May 26, 2012

gr-at-pierlis.com

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment