groue / GRMustache.swift

Flexible Mustache templates for Swift
http://mustache.github.com/
MIT License
597 stars 155 forks source link

String literals as filter function arguments #9

Closed njdehoog closed 9 years ago

njdehoog commented 9 years ago

It seems like filter functions can not take string literals as arguments currently. I'm trying to build a date formatting function which takes a date format as an argument. Would you say this is inadvisable, or would it make sense to build support for this?

let dateFilter = VariadicFilter() { (params: [MustacheBox]) in
    guard let date = params.first?.value as? NSDate, let format = params[1].value as? String else {
        return Box()
    }
    let dateFormatter = NSDateFormatter()
    dateFormatter.dateFormat = format
    return Box(dateFormatter.stringFromDate(date))
}
groue commented 9 years ago

Hi again @njdehoog!

The Mustache language has no support for literals, and most feature requests for them have a flaw which prevents me from rolling up my sleeves and perform the necessary changes to the GRMustache parser so that it would support literals.

In particular, date formatting: string formats are handy, but kind of a joke as soon as localization comes in. Besides, templates don't usually require to support any date formatting, but only a bunch of them.

So please investigate the built-in support for NSDateFormatter:

First, with a fixed list of known dateformatters:

// Configure a fixed list of dateformatters:
let dateFormatter1 = NSDateFormatter() ...
let dateFormatter2 = NSDateFormatter() ...

// Register the fixed list of date formatters:
let template = try Template(string: "{{ format1(date) }}, {{ format2(date) }}")
template.registerInBaseContext("format1", Box(dateFormatter1))
template.registerInBaseContext("format2", Box(dateFormatter2))

let data = ["date": NSDate()]
try template.render(Box(data))

Next, with date formatters chosen on the fly:

let data = [
    "items": [
        ["date": NSDate(), "format": NSDateFormatter() ...],    // each item has its own formatter
        ["date": NSDate(), "format": NSDateFormatter() ...],
        ["date": NSDate(), "format": NSDateFormatter() ...],
    ]
]

// No registering of date formatters, but instead extract them from each items:
let template = try Template(string: "{{#items}}...{{ format(date) }}...{{/items}}")
try template.render(Box(data))

Normally, once of those patterns should fit your needs. If they do not, let's discuss and clear this out: I'm open to discussion :-)

Now back on literals themselves: GRMustache.swift has a design issue in its TemplateParser/TemplateCompiler objects, which prevent eventual string literals to contain }}. A template has two parsing passes: first tags, then expressions. {{ "}} {{" }} is parsed as two tags: {{ "}} and {{" }}... The branch https://github.com/groue/GRMustache.swift/tree/StringLiterals is the current state of the job, before the required refactoring.

njdehoog commented 9 years ago

Hi @groue :) Thanks again for your thorough explanation. A fixed list of date formatters would work to some extent, in fact that is the interim solution I decided to go for, but for my use case, which is building a static site generator, I fear that this solution would ultimately prove too inflexible.

Since the person using the static site generator will not (easily) be able to add custom filters, they will be relying on the filters that I have already added, and this list will never be exhaustive. In other circumstances I would argue that date formatting should not be part of templating logic, but in my case I believe this would be the most pragmatic solution.

Also, localization is less relevant in this case, because static sites are inherently not localized for the end user (except if I were to implement some JS solution), the person generating the site dictates the format.

I hope this clarifies the issue somewhat. Let me know what you think :)

groue commented 9 years ago

I hope this clarifies the issue somewhat.

It does, thanks for the context.

localization is less relevant in this case, because static sites are inherently not localized.

True.

I would argue that date formatting should not be part of templating logic, but in my case I believe this would be the most pragmatic solution.

I like pragmatic solutions as well.

Since the person using the static site generator will not (easily) be able to add custom filters [...]

One quick remark: is it reasonable to prevent your users to add their custom filters? I mean, do you need to artificially limit your static site generator to a list of blessed built-in filters, when you could open it and embrace any custom need?

Also, I guess that not only filters are "uneasy" to add, but also string values: you don't quite support a pattern where your user would provide a template like ...{{ dateFormat(postDate, shortDateFormat) }}..., where the static site generator would provide dateFormat and postDate, and your user the value for shortDateFormat.

Finally I guess those difficulties comes from the fact that you provide a command-line tool, not an API.

OK. Pragmatically speaking, the StringLiterals branch already has what your need. It has a flaw that I described above, which prevents it from being merged into the main trunk. But this flaw does not affect your date formats. And also, it may miss a few commits from the Swift2 branch that would need to be merged in.

What do you think?

groue commented 9 years ago

Finally I guess those difficulties comes from the fact that you provide a command-line tool, not an API.

Well, keeping on the wild guesses (forgive me if I'm totally mistaken), I can easily imagine that a command-line interface for a static site generator has a configuration file. In there you could provide a way to let the user add custom keys exposed to templates:

posts.path = /path/to/posts
site.postsPerPage = 5
site.generateArchivePage = true
templateKeys.myDateFormat = 'YYYY-MM-DD'

User-provided post.mustache:

...
Posted on {{ dateFormat(post.date, myDateFormat) }}.
...
njdehoog commented 9 years ago

Hm, that's an interesting thought. This might be an option, although I would prefer to keep configuration as minimal as possible. If I can make it work, I would probably want users to be able to create their own filters as well, but at the same time I don't want it to be necessary.

It sounds promising that you are already working on string literals. What do you think it will take to do the required refactoring to make it work?

groue commented 9 years ago

Nothing more than a full rewrite of the parser :-) But check the StringLiterals branch first : it is not complete, but it should already support your date formats.

njdehoog commented 9 years ago

Sounds easy enough ;) I will check out the StringLiterals branch. Will it work with Swift 2, or do I need to merge in those changes first? Thanks again for your help.

groue commented 9 years ago

I've merged Swift2 on top on StringLiterals. It has one failing test, the one that involves }} in string literal. Besides this, it is up-to-date. Tell me if you experience any issue.

njdehoog commented 9 years ago

Great, thanks! Will let you know how it goes.

njdehoog commented 9 years ago

I'm using the StringLiterals branch now, and it works great! :+1:

groue commented 9 years ago

OK I'm happy you'd found your solution. Now I really have to support them in the main branch :smile:

njdehoog commented 8 years ago

Any chance you could merge the 0.10.0 version into the StringLiterals branch? I would love to be able to use this version with string literals.

groue commented 8 years ago

I think my next week will be my Mustache week. Both the Swift and Objective-C versions are in need. Please hold on.

njdehoog commented 8 years ago

Sounds good! :+1:

tkrajacic commented 7 years ago

Is there any update on this? Hasn't been touched in over a year it seems.

groue commented 7 years ago

Hello @tkrajacic. The subject of literal does indeed stall, due to parsing difficulties: Mustache grammar is not a regular one, and this feature request would require a heavy refactoring of the state machine used to parse templates.

agiletortoise commented 2 years ago

FWIW, I was faced with this issue, explored the StringLiterals branch, and decided - at least for my specific usage - it would work nicely to pre-process the literal parameters in templates before passing them to GRMustache. This may not be a good solution for others, but, basically, I am:

Perhaps not ideal, but works nicely.