Skip to content

DAOCUONG/GRMustache.swift

 
 

Repository files navigation

GRMustache.swift

GRMustache.swift is a Mustache template engine written in Swift 1.2, from the author of the Objective-C GRMustache.

It ships with built-in goodies and extensibility hooks that let you avoid the strict minimalism of the genuine Mustache language when you need it.

Get release announcements and usage tips: follow @GRMustache on Twitter.

Features

  • Support for the full Mustache syntax
  • Filters, as {{ uppercase(name) }}
  • Template inheritance, as in hogan.js, mustache.java and mustache.php.
  • Built-in goodies
  • Unlike many Swift template engines, GRMustache.swift does not rely on the Objective-C runtime, and does not force you to feed your templates with ad-hoc values. You can use your existing models.

Requirements

  • iOS 7.0+ / OSX 10.9+
  • Xcode 6.3

Usage

document.mustache:

Hello {{name}}
Your beard trimmer will arrive on {{format(date)}}.
{{#late}}
Well, on {{format(real_date)}} because of a Martian attack.
{{/late}}
import Mustache

let template = Template(named: "document")!

let dateFormatter = NSDateFormatter()
dateFormatter.dateStyle = .MediumStyle
template.registerInBaseContext("format", Box(dateFormatter))

let data = [
    "name": "Arthur",
    "date": NSDate(),
    "real_date": NSDate().dateByAddingTimeInterval(60*60*24*3),
    "late": true
]
let rendering = template.render(Box(data))!

Documentation

You'll find in the repository:

  • a Mustache.xcodeproj project to be embedded in your applications so that you can import the Mustache module.
  • a Mustache.xcworkspace workspace that contains a Playground and a demo application

All public types, functions and methods of the library are documented in the source code.

The main entry points are:

  • the Template class, documented in Template.swift, which loads and renders templates:

    let template = Template(named: "template")!
  • the Box() functions, documented in Box.swift, which provide data to templates:

    let data = ["mustaches": ["Charles Bronson", "Errol Flynn", "Clark Gable"]]
    let rendering = template.render(Box(data))!
  • The Configuration type, documented in Configuration.swift, which describes how to tune Mustache rendering:

    // Have all templates render text, and avoid HTML-escaping:
    Mustache.DefaultConfiguration.contentType = .Text

The documentation contains many examples that you can run in the Playground included in Mustache.xcworkspace.

We describe below a few use cases of the library:

Errors

Not funny, but they happen. Whenever the library needs to access the file system or other system resources, you may get standard errors of domain like NSCocoaErrorDomain, etc. Mustache-specific errors are covered by the domain GRMustacheErrorDomain:

  • Code GRMustacheErrorCodeTemplateNotFound:

    // No such template: `inexistent`
    var error: NSError?
    Template(named: "inexistent", error: &error)
    error!.localizedDescription
  • Code GRMustacheErrorCodeParseError:

    // Parse error at line 1: Unclosed Mustache tag
    Template(string: "Hello {{name", error: &error)
    error!.localizedDescription
  • Code GRMustacheErrorCodeRenderingError:

    // Error evaluating {{undefinedFilter(x)}} at line 1: Missing filter
    template = Template(string: "{{undefinedFilter(x)}}")!
    template.render(error: &error)
    error!.localizedDescription

When you render trusted valid templates with trusted valid data, you can avoid error handling: ignore the error argument, and use the bang ! to force the unwrapping of templates and their renderings, as in this example:

// Assume valid parsing and rendering
template = Template(string: "{{name}} has a Mustache.")!
template.render(Box(data))!

Rendering of NSObject and its subclasses

NSObject subclasses can trivially feed your templates:

// An NSObject subclass
class Person : NSObject {
    let name: String
    
    init(name: String) {
        self.name = name
    }
}


// Charlie Chaplin has a mustache.
let person = Person(name: "Charlie Chaplin")
let template = Template(string: "{{name}} has a mustache.")!
let rendering = template.render(Box(person))!

When extracting values from your NSObject subclasses, GRMustache.swift uses the subscripting method objectForKeyedSubscript: method when available, or the Key-Value Coding method valueForKey:. For a full description of the rendering of NSObject, see the "Boxing of Objective-C objects" section in Box.swift.

Rendering of AnyObject

Many standard APIs return values of type AnyObject. You get AnyObject when you deserialize JSON data, or when you extract a value out of an NSArray, for example. AnyObject can be turned into a Mustache box. However, due to constraints in the Swift language, you have to use the dedicated BoxAnyObject() function:

// Decode some JSON data
let data = "{ \"name\": \"Lionel Richie\" }".dataUsingEncoding(NSUTF8StringEncoding)!
let json: AnyObject = NSJSONSerialization.JSONObjectWithData(data, options: nil, error: nil)!

// Lionel Richie has a Mustache.
let template = Template(string: "{{ name }} has a Mustache.")!
let rendering = template.render(BoxAnyObject(json))

Rendering of pure Swift values

Key-Value Coding is not available for Swift types, regardless of eventual @objc or dynamic modifiers. Swift types can still feed templates, though, with a little help.

// Define a pure Swift object:
struct Person {
    let name: String
}

Now we want to let Mustache templates extract the name key out of a person so that they can render {{ name }} tags.

Unlike the NSObject class, Swift types don't provide support for evaluating the name property given its name. We need to explicitly help the Mustache engine by conforming to the MustacheBoxable protocol:

extension Person : MustacheBoxable {
    
    // Here we simply feed templates with a dictionary:
    var mustacheBox: MustacheBox {
        return Box(["name": self.name])
    }
}

Now we can box and render a user:

// Freddy Mercury has a mustache.
let person = Person(name: "Freddy Mercury")
let template = Template(string: "{{name}} has a mustache.")!
let rendering = template.render(Box(person))!

For a more complete discussion, see the "Boxing of Swift types" section in Box.swift

Lambdas

"Mustache lambdas" are functions that let you perform custom rendering. There are two kinds of Mustache lambdas: those that process section tags, and those that render variable tags.

Quoting the Mustache specification:

Lambdas are a special-cased data type for use in interpolations and sections.

When used as the data value for an Variable {{tag}}, the lambda MUST be treatable as an arity 0 function, and invoked as such. The returned value MUST be rendered against the default delimiters, then interpolated in place of the lambda.

When used as the data value for a Section {{#tag}}...{{/tag}}, the lambda MUST be treatable as an arity 1 function, and invoked as such (passing a String containing the unprocessed section contents). The returned value MUST be rendered against the current delimiters, then interpolated in place of the section.

The Lambda function produces spec-compliant "Mustache lambdas":

// `{{fullName}}` renders just as `{{firstName}} {{lastName}}.`
let fullName = Lambda { "{{firstName}} {{lastName}}" }

// `{{#wrapped}}...{{/wrapped}}` renders the content of the section, wrapped in
// a <b> HTML tag.
let wrapped = Lambda { "<b>\($0)</b>" }

// <b>Frank Zappa is awesome.</b>
let templateString = "{{#wrapped}}{{fullName}} is awesome.{{/wrapped}}"
let template = Template(string: templateString)!
let data = [
    "firstName": Box("Frank"),
    "lastName": Box("Zappa"),
    "fullName": Box(fullName),
    "wrapped": Box(wrapped)]
let rendering = template.render(Box(data))!

Those "lambdas" are a special case of custom rendering functions. The raw RenderFunction type gives you extra flexibility when you need to perform custom rendering. See CoreFunctions.swift.

Filters

Filters process values:

// Define the `square` filter.
//
// square(n) evaluates to the square of the provided integer.
let square = Filter { (n: Int, _) in
    return Box(n * n)
}


// Register the square filter in our template:

let template = Template(string: "{{n}} × {{n}} = {{square(n)}}")!
template.registerInBaseContext("square", Box(square))


// 10 × 10 = 100

let rendering = template.render(Box(["n": 10]))!

Filters can chain and generally be part of more complex expressions:

Circle area is {{ format(product(PI, circle.radius, circle.radius)) }} cm².

Filters can provide special rendering of mustache sections:

cats.mustache:

I have {{ cats.count }} {{# pluralize(cats.count) }}cat{{/ }}.
// Define the `pluralize` filter.
//
// {{# pluralize(count) }}...{{/ }} renders the plural form of the
// section content if the `count` argument is greater than 1.

let pluralize = Filter { (count: Int, info: RenderingInfo, _) in
    
    // Pluralize the inner content of the section tag:
    var string = info.tag.innerTemplateString
    if count > 1 {
        string += "s"  // naive
    }
    
    return Rendering(string)
}


// Register the pluralize filter in our template:

let template = Template(named: "cats")!
template.registerInBaseContext("pluralize", Box(pluralize))


// I have 3 cats.

let data = ["cats": ["Kitty", "Pussy", "Melba"]]
let rendering = template.render(Box(data))!

Filters can take several arguments:

// Define the `sum` filter.
//
// sum(x, ...) evaluates to the sum of provided integers

let sum = VariadicFilter { (boxes: [MustacheBox], _) in
    // Extract integers out of input boxes, assuming zero for non numeric values
    let integers = map(boxes) { $0.intValue ?? 0 }
    let sum = reduce(integers, 0, +)
    return Box(sum)
}


// Register the sum filter in our template:

let template = Template(string: "{{a}} + {{b}} + {{c}} = {{ sum(a,b,c) }}")!
template.registerInBaseContext("sum", Box(sum))


// 1 + 2 + 3 = 6

template.render(Box(["a": 1, "b": 2, "c": 3]))!

Filters can validate their arguments and return errors:

// Define the `squareRoot` filter.
//
// squareRoot(x) evaluates to the square root of the provided double, and
// returns an error for negative values.
let squareRoot = Filter { (x: Double, error: NSErrorPointer) in
    if x < 0 {
        if error != nil {
            error.memory = NSError(
                domain: GRMustacheErrorDomain,
                code: GRMustacheErrorCodeRenderingError,
                userInfo: [NSLocalizedDescriptionKey: "Invalid negative value"])
        }
        return nil
    } else {
        return Box(sqrt(x))
    }
}


// Register the squareRoot filter in our template:

let template = Template(string: "√{{x}} = {{squareRoot(x)}}")!
template.registerInBaseContext("squareRoot", Box(squareRoot))


// √100 = 10.0

let rendering = template.render(Box(["x": 100]))!


// Error evaluating {{squareRoot(x)}} at line 1: Invalid negative value

var error: NSError?
template.render(Box(["x": -1]), error: &error)
error!.localizedDescription

Filters are documented with the FilterFunction type in CoreFunctions.swift.

When you want to format values, you don't have to write your own filters: just use NSFormatter objects such as NSNumberFormatter and NSDateFormatter. More info.

Template inheritance

GRMustache template inheritance is compatible with hogan.js, mustache.java and mustache.php.

Templates may contain inheritable sections. In the following layout.mustache template, the page_title and page_content sections may be overriden by other templates:

layout.mustache:

<html>
<head>
    <title>{{$ page_title }}Default title{{/ page_title }}</title>
</head>
<body>
    <h1>{{$ page_title }}Default title{{/ page_title }}</h1>
    {{$ page_content }}
        Default content
    {{/ page_content }}}
</body>
</html>

The article.mustache below inherits from layout.mustache, and overrides its sections:

article.mustache:

{{< layout }}

    {{$ page_title }}{{ article.title }}{{/ page_title }}
    
    {{$ page_content }}
        {{{ article.html_body }}}
        by {{ article.author }}
    {{/ page_content }}
    
{{/ layout }}

When you render article.mustache, you get a full HTML page.

Built-in goodies

The library ships with built-in goodies that will help you render your templates: format values, render array indexes, localize templates, etc.

About

Flexible Mustache templates for Swift

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages

  • Swift 98.3%
  • Objective-C 1.4%
  • Other 0.3%