groue / GRMustache

Flexible and production-ready Mustache templates for MacOS Cocoa and iOS
http://mustache.github.com/
MIT License
1.44k stars 190 forks source link

prevent mustache from looking up the context stack? #19

Closed sl1m3d closed 12 years ago

sl1m3d commented 12 years ago

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 commented 12 years ago

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 commented 12 years ago

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.

sl1m3d commented 12 years ago

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 commented 12 years ago

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

Does the data come from some JSON request?

sl1m3d commented 12 years ago

Yes it comes from JSON

groue commented 12 years ago

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.

sl1m3d commented 12 years ago

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 commented 12 years ago

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.

sl1m3d commented 12 years ago

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;
}
sl1m3d commented 12 years ago

Looks like there's already an issue opened on the spec for this. https://github.com/mustache/spec/issues/10

groue commented 12 years ago

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

sl1m3d commented 12 years ago

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 commented 12 years ago

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

groue commented 12 years ago

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

groue commented 12 years ago

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

groue commented 12 years ago

Issue closed :-)

sl1m3d commented 12 years ago

Awesome. Thanks @groue

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

groue commented 12 years ago

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 commented 12 years ago

@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.

sl1m3d commented 12 years ago

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

groue commented 12 years ago

gr-at-pierlis.com