Giter VIP home page Giter VIP logo

grmustache.swift's Introduction

GRMustache.swift Swift Platforms License

Mustache templates for Swift

Latest release: October 23, 2016 • version 2.0.0 • CHANGELOG

Requirements: iOS 8.0+ / OSX 10.9+ / tvOS 9.0+ • Xcode 8+ • Swift 3

Follow @groue on Twitter for release announcements and usage tips.


FeaturesUsageInstallationDocumentation


Features

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

  • Support for the full Mustache syntax
  • Filters, as {{ uppercase(name) }}
  • Template inheritance, as in hogan.js, mustache.java and mustache.php.
  • Built-in goodies
  • GRMustache.swift does not rely on the Objective-C runtime. It lets you feed your templates with ad-hoc values or your existing models, without forcing you to refactor your Swift code into Objective-C objects.

Usage

The library is built around two main APIs:

  • The Template(...) initializer that loads a template.
  • The Template.render(...) method that renders your data.

document.mustache:

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

// Load the `document.mustache` resource of the main bundle
let template = try Template(named: "document")

// Let template format dates with `{{format(...)}}`
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .medium
template.register(dateFormatter, forKey: "format")

// The rendered data
let data: [String: Any] = [
    "name": "Arthur",
    "date": Date(),
    "realDate": Date().addingTimeInterval(60*60*24*3),
    "late": true
]

// The rendering: "Hello Arthur..."
let rendering = try template.render(data)

Installation

CocoaPods

CocoaPods is a dependency manager for Xcode projects.

To use GRMustache.swift with CocoaPods, specify in your Podfile:

source 'https://github.com/CocoaPods/Specs.git'
use_frameworks!

pod 'GRMustache.swift'

Carthage

Carthage is another dependency manager for Xcode projects.

To use GRMustache.swift with Carthage, specify in your Cartfile:

github "groue/GRMustache.swift"

Swift Package Manager

The Swift Package Manager is the open source tool for managing the distribution of Swift code.

To use GRMustache.swift with the Swift Package Manager, add https://github.com/groue/GRMustache.swift to the list of your package dependencies:

dependencies: [
    .package(url: "https://github.com/groue/GRMustache.swift", from: "4.0.0")
]

Add Mustache to your target:

targets: [
    .target(
        name: "MyTool",
        dependencies: ["Mustache"])
]

Manually

  1. Download a copy of GRMustache.swift.

  2. Checkout the latest GRMustache.swift version:

    cd [GRMustache.swift directory]
    git checkout 2.0.0
  3. Embed the Mustache.xcodeproj project in your own project.

  4. Add the MustacheOSX, MustacheiOS, or MustacheWatchOS target in the Target Dependencies section of the Build Phases tab of your application target.

  5. Add the the Mustache.framework from the targetted platform to the Embedded Binaries section of the General tab of your target.

See MustacheDemoiOS for an example of such integration.

Documentation

To fiddle with the library, open the Xcode/Mustache.xcworkspace workspace: it contains a Mustache-enabled Playground at the top of the files list.

External links:

Rendering templates:

Feeding templates:

Misc:

Loading Templates

Templates may come from various sources:

  • Raw Swift strings:

    let template = try Template(string: "Hello {{name}}")
  • Bundle resources:

    // Loads the "document.mustache" resource of the main bundle:
    let template = try Template(named: "document")
  • Files and URLs:

    let template = try Template(path: "/path/to/document.mustache")
    let template = try Template(URL: templateURL)
  • Template Repositories:

    Template repositories represent a group of templates. They can be configured independently, and provide neat features like template caching. For example:

    // The repository of Bash templates, with extension ".sh":
    let repo = TemplateRepository(bundle: Bundle.main, templateExtension: "sh")
    
    // Disable HTML escaping for Bash scripts:
    repo.configuration.contentType = .text
    
    // Load the "script.sh" resource:
    let template = repo.template(named: "script")!

For more information, check:

Errors

Not funny, but they happen. Standard errors of domain NSCocoaErrorDomain, etc. may be thrown whenever the library needs to access the file system or other system resource. Mustache-specific errors are of type MustacheError:

do {
    let template = try Template(named: "Document")
    let rendering = try template.render(data)
} catch let error as MustacheError {
    // Parse error at line 2 of template /path/to/template.mustache:
    // Unclosed Mustache tag.
    error.description
    
    // templateNotFound, parseError, or renderError
    error.kind
    
    // The eventual template at the source of the error. Can be a path, a URL,
    // a resource name, depending on the repository data source.
    error.templateID
    
    // The eventual faulty line.
    error.lineNumber
    
    // The eventual underlying error.
    error.underlyingError
}

Mustache Tags Reference

Mustache is based on tags: {{name}}, {{#registered}}...{{/registered}}, {{>include}}, etc.

Each one of them performs its own little task:

Variable Tags

A Variable tag {{value}} renders the value associated with the key value, HTML-escaped. To avoid HTML-escaping, use triple mustache tags {{{value}}}:

let template = try Template(string: "{{value}} - {{{value}}}")

// Mario & Luigi - Mario & Luigi
let data = ["value": "Mario & Luigi"]
let rendering = try template.render(data)

Section Tags

A Section tag {{#value}}...{{/value}} is a common syntax for three different usages:

  • conditionally render a section.
  • loop over a collection.
  • dig inside an object.

Those behaviors are triggered by the value associated with value:

Falsey values

If the value is falsey, the section is not rendered. Falsey values are:

  • missing values
  • false boolean
  • zero numbers
  • empty strings
  • empty collections
  • NSNull

For example:

let template = try Template(string: "<{{#value}}Truthy{{/value}}>")

// "<Truthy>"
try template.render(["value": true])
// "<>"
try template.render([:])                  // missing value
try template.render(["value": false])     // false boolean

Collections

If the value is a collection (an array or a set), the section is rendered as many times as there are elements in the collection, and inner tags have direct access to the keys of elements:

Template:

{{# friends }}
- {{ name }}
{{/ friends }}

Data:

[
  "friends": [
    [ "name": "Hulk Hogan" ],
    [ "name": "Albert Einstein" ],
    [ "name": "Tom Selleck" ],
  ]
]

Rendering:

- Hulk Hogan
- Albert Einstein
- Tom Selleck

Other Values

If the value is not falsey, and not a collection, then the section is rendered once, and inner tags have direct access to the value's keys:

Template:

{{# user }}
- {{ name }}
- {{ score }}
{{/ user }}

Data:

[
  "user": [
    "name": "Mario"
    "score": 1500
  ]
]

Rendering:

- Mario
- 1500

Inverted Section Tags

An Inverted section tag {{^value}}...{{/value}} renders when a regular section {{#value}}...{{/value}} would not. You can think of it as the Mustache "else" or "unless".

Template:

{{# persons }}
- {{name}} is {{#alive}}alive{{/alive}}{{^alive}}dead{{/alive}}.
{{/ persons }}
{{^ persons }}
Nobody
{{/ persons }}

Data:

[
  "persons": []
]

Rendering:

Nobody

Data:

[
  "persons": [
    ["name": "Errol Flynn", "alive": false],
    ["name": "Sacha Baron Cohen", "alive": true]
  ]
]

Rendering:

- Errol Flynn is dead.
- Sacha Baron Cohen is alive.

Partial Tags

A Partial tag {{> partial }} includes another template, identified by its name. The included template has access to the currently available data:

document.mustache:

Guests:
{{# guests }}
  {{> person }}
{{/ guests }}

person.mustache:

{{ name }}

Data:

[
  "guests": [
    ["name": "Frank Zappa"],
    ["name": "Lionel Richie"]
  ]
]

Rendering:

Guests:
- Frank Zappa
- Lionel Richie

Recursive partials are supported, but your data should avoid infinite loops.

Partial lookup depends on the origin of the main template:

File System

Partial names are relative paths when the template comes from the file system (via paths or URLs):

// Load /path/document.mustache
let template = Template(path: "/path/document.mustache")

// {{> partial }} includes /path/partial.mustache.
// {{> shared/partial }} includes /path/shared/partial.mustache.

Partials have the same file extension as the main template.

// Loads /path/document.html
let template = Template(path: "/path/document.html")

// {{> partial }} includes /path/partial.html.

When your templates are stored in a hierarchy of directories, you can use absolute paths to partials, with a leading slash. For that, you need a template repository which will define the root of absolute partial paths:

let repository = TemplateRepository(directoryPath: "/path")
let template = repository.template(named: ...)

// {{> /shared/partial }} includes /path/shared/partial.mustache.

Bundle Resources

Partial names are interpreted as resource names when the template is a bundle resource:

// Load the document.mustache resource from the main bundle
let template = Template(named: "document")

// {{> partial }} includes the partial.mustache resource.

Partials have the same file extension as the main template.

// Load the document.html resource from the main bundle
let template = Template(named: "document", templateExtension: "html")

// {{> partial }} includes the partial.html resource.

General case

Generally speaking, partial names are always interpreted by a Template Repository:

  • Template(named:...) uses a bundle-based template repository: partial names are resource names.
  • Template(path:...) uses a file-based template repository: partial names are relative paths.
  • Template(URL:...) uses a URL-based template repository: partial names are relative URLs.
  • Template(string:...) uses a template repository that can’t load any partial.
  • templateRepository.template(named:...) uses the partial loading mechanism of the template repository.

Check TemplateRepository.swift for more information (read on cocoadocs.org).

Dynamic Partials

A tag {{> partial }} includes a template, the one that is named "partial". One can say it is statically determined, since that partial has already been loaded before the template is rendered:

let repo = TemplateRepository(bundle: Bundle.main)
let template = try repo.template(string: "{{#user}}{{>partial}}{{/user}}")

// Now the `partial.mustache` resource has been loaded. It will be used when
// the template is rendered. Nothing can change that.

You can also include dynamic partials. To do so, use a regular variable tag {{ partial }}, and provide the template of your choice for the key "partial" in your rendered data:

// A template that delegates the rendering of a user to a partial.
// No partial has been loaded yet.
let template = try Template(string: "{{#user}}{{partial}}{{/user}}")

// The user
let user = ["firstName": "Georges", "lastName": "Brassens", "occupation": "Singer"]

// Two different partials:
let partial1 = try Template(string: "{{firstName}} {{lastName}}")
let partial2 = try Template(string: "{{occupation}}")

// Two different renderings of the same template:
// "Georges Brassens"
try template.render(["user": user, "partial": partial1])
// "Singer"
try template.render(["user": user, "partial": partial2])

Partial Override Tags

GRMustache.swift supports Template Inheritance, like hogan.js, mustache.java and mustache.php.

A Partial Override Tag {{< layout }}...{{/ layout }} includes another template inside the rendered template, just like a regular partial tag {{> partial}}.

However, this time, the included template can contain blocks, and the rendered template can override them. Blocks look like sections, but use a dollar sign: {{$ overrideMe }}...{{/ overrideMe }}.

The included template layout.mustache below has title and content blocks that the rendered template can override:

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

The rendered template article.mustache:

{{< layout }}

    {{$ title }}{{ article.title }}{{/ title }}
    
    {{$ content }}
        {{{ article.html_body }}}
        <p>by {{ article.author }}</p>
    {{/ content }}
    
{{/ layout }}
let template = try Template(named: "article")
let data = [
    "article": [
        "title": "The 10 most amazing handlebars",
        "html_body": "<p>...</p>",
        "author": "John Doe"
    ]
]
let rendering = try template.render(data)

The rendering is a full HTML page:

<html>
<head>
    <title>The 10 most amazing handlebars</title>
</head>
<body>
    <h1>The 10 most amazing handlebars</h1>
    <p>...</p>
    <p>by John Doe</p>
</body>
</html>

A few things to know:

  • A block {{$ title }}...{{/ title }} is always rendered, and rendered once. There is no boolean checks, no collection iteration. The "title" identifier is a name that allows other templates to override the block, not a key in your rendered data.

  • A template can contain several partial override tags.

  • A template can override a partial which itself overrides another one. Recursion is possible, but your data should avoid infinite loops.

  • Generally speaking, any part of a template can be refactored with partials and partial override tags, without requiring any modification anywhere else (in other templates that depend on it, or in your code).

Dynamic Partial Overrides

Like a regular partial tag, a partial override tag {{< layout }}...{{/ layout }} includes a statically determined template, the very one that is named "layout".

To override a dynamic partial, use a regular section tag {{# layout }}...{{/ layout }}, and provide the template of your choice for the key "layout" in your rendered data.

Set Delimiters Tags

Mustache tags are generally enclosed by "mustaches" {{ and }}. A Set Delimiters Tag can change that, right inside a template.

Default tags: {{ name }}
{{=<% %>=}}
ERB-styled tags: <% name %>
<%={{ }}=%>
Default tags again: {{ name }}

There are also APIs for setting those delimiters. Check Configuration.tagDelimiterPair in Configuration.swift (read on cocoadocs.org).

Comment Tags

{{! Comment tags }} are simply not rendered at all.

Pragma Tags

Several Mustache implementations use Pragma tags. They start with a percent % and are not rendered at all. Instead, they trigger implementation-specific features.

GRMustache.swift interprets two pragma tags that set the content type of the template:

  • {{% CONTENT_TYPE:TEXT }}
  • {{% CONTENT_TYPE:HTML }}

HTML templates is the default. They HTML-escape values rendered by variable tags {{name}}.

In a text template, there is no HTML-escaping. Both {{name}} and {{{name}}} have the same rendering. Text templates are globally HTML-escaped when included in HTML templates.

For a more complete discussion, see the documentation of Configuration.contentType in Configuration.swift.

The Context Stack and Expressions

The Context Stack

Variable and section tags fetch values in the data you feed your templates with: {{name}} looks for the key "name" in your input data, or, more precisely, in the context stack.

That context stack grows as the rendering engine enters sections, and shrinks when it leaves. Its top value, pushed by the last entered section, is where a {{name}} tag starts looking for the "name" identifier. If this top value does not provide the key, the tag digs further down the stack, until it finds the name it looks for.

For example, given the template:

{{#family}}
- {{firstName}} {{lastName}}
{{/family}}

Data:

[
    "lastName": "Johnson",
    "family": [
        ["firstName": "Peter"],
        ["firstName": "Barbara"],
        ["firstName": "Emily", "lastName": "Scott"],
    ]
]

The rendering is:

- Peter Johnson
- Barbara Johnson
- Emily Scott

The context stack is usually initialized with the data you render your template with:

// The rendering starts with a context stack containing `data`
template.render(data)

Precisely speaking, a template has a base context stack on top of which the rendered data is added. This base context is always available whatever the rendered data. For example:

// The base context contains `baseData`
template.extendBaseContext(baseData)

// The rendering starts with a context stack containing `baseData` and `data`
template.render(data)

The base context is usually a good place to register filters:

template.extendBaseContext(["each": StandardLibrary.each])

But you will generally register filters with the register(:forKey:) method, because it prevents the rendered data from overriding the name of the filter:

template.register(StandardLibrary.each, forKey: "each")

See Template for more information on the base context.

Expressions

Variable and section tags contain Expressions. name is an expression, but also article.title, and format(article.modificationDate). When a tag renders, it evaluates its expression, and renders the result.

There are four kinds of expressions:

  • The dot . aka "Implicit Iterator" in the Mustache lingo:

    Implicit iterator evaluates to the top of the context stack, the value pushed by the last entered section.

    It lets you iterate over collection of strings, for example. {{#items}}<{{.}}>{{/items}} renders <1><2><3> when given [1,2,3].

  • Identifiers like name:

    Evaluation of identifiers like name goes through the context stack until a value provides the name key.

    Identifiers can not contain white space, dots, parentheses and commas. They can not start with any of those characters: {}&$#^/<>.

  • Compound expressions like article.title and generally <expression>.<identifier>:

    This time there is no going through the context stack: article.title evaluates to the title of the article, regardless of title keys defined by enclosing contexts.

    .title (with a leading dot) is a compound expression based on the implicit iterator: it looks for title at the top of the context stack.

    Compare these three templates:

    • ...{{# article }}{{ title }}{{/ article }}...
    • ...{{# article }}{{ .title }}{{/ article }}...
    • ...{{ article.title }}...

    The first will look for title anywhere in the context stack, starting with the article object.

    The two others are identical: they ensure the title key comes from the very article object.

  • Filter expressions like format(date) and generally <expression>(<expression>, ...):

    Filters are introduced below.

Values

Templates render values:

template.render(["name": "Luigi"])
template.render(Person(name: "Luigi"))

You can feed templates with:

  • Values that adopt the MustacheBoxable protocol such as String, Int, NSObject and its subclasses (see Standard Swift Types Reference and Custom Types)

  • Arrays, sets, and dictionaries (Swift arrays, sets, dictionaries, and Foundation collections). This does not include other collections, such as Swift ranges.

  • A few function types such as filter functions, lambdas, and other functions involved in advanced boxes.

  • Goodies such as Foundation's formatters.

Standard Swift Types Reference

GRMustache.swift comes with built-in support for the following standard Swift types:

Bool

  • {{bool}} renders "0" or "1".
  • {{#bool}}...{{/bool}} renders if and only if bool is true.
  • {{^bool}}...{{/bool}} renders if and only if bool is false.

Numeric Types

GRMustache supports Int, UInt, Int64, UInt64, Float, Double and CGFloat:

  • {{number}} renders the standard Swift string interpolation of number.
  • {{#number}}...{{/number}} renders if and only if number is not 0 (zero).
  • {{^number}}...{{/number}} renders if and only if number is 0 (zero).

The Swift types Int8, UInt8, etc. have no built-in support: turn them into one of the three general types before injecting them into templates.

To format numbers, you can use NumberFormatter:

let percentFormatter = NumberFormatter()
percentFormatter.numberStyle = .percent

let template = try Template(string: "{{ percent(x) }}")
template.register(percentFormatter, forKey: "percent")

// Rendering: 50%
let data = ["x": 0.5]
let rendering = try template.render(data)

More info on Formatter.

String

  • {{string}} renders string, HTML-escaped.
  • {{{string}}} renders string, not HTML-escaped.
  • {{#string}}...{{/string}} renders if and only if string is not empty.
  • {{^string}}...{{/string}} renders if and only if string is empty.

Exposed keys:

  • string.length: the length of the string.

Set

  • {{set}} renders the concatenation of the renderings of set elements.
  • {{#set}}...{{/set}} renders as many times as there are elements in the set, pushing them on top of the context stack.
  • {{^set}}...{{/set}} renders if and only if the set is empty.

Exposed keys:

  • set.first: the first element.
  • set.count: the number of elements in the set.

Array

  • {{array}} renders the concatenation of the renderings of array elements.
  • {{#array}}...{{/array}} renders as many times as there are elements in the array, pushing them on top of the context stack.
  • {{^array}}...{{/array}} renders if and only if the array is empty.

Exposed keys:

  • array.first: the first element.
  • array.last: the last element.
  • array.count: the number of elements in the array.

In order to render array indexes, or vary the rendering according to the position of elements in the array, use the each filter from the Standard Library:

document.mustache:

Users with their positions:
{{# each(users) }}
- {{ @indexPlusOne }}: {{ name }}
{{/}}

Comma-separated user names:
{{# each(users) }}{{ name }}{{^ @last }}, {{/}}{{/}}.
let template = try! Template(named: "document")

// Register StandardLibrary.each for the key "each":
template.register(StandardLibrary.each, forKey: "each")

// Users with their positions:
// - 1: Alice
// - 2: Bob
// - 3: Craig
// 
// Comma-separated user names: Alice, Bob, Craig.
let users = [["name": "Alice"], ["name": "Bob"], ["name": "Craig"]]
let rendering = try! template.render(["users": users])

Dictionary

  • {{dictionary}} renders the standard Swift string interpolation of dictionary (not very useful).
  • {{#dictionary}}...{{/dictionary}} renders once, pushing the dictionary on top of the context stack.
  • {{^dictionary}}...{{/dictionary}} does not render.

In order to iterate over the key/value pairs of a dictionary, use the each filter from the Standard Library:

document.mustache:

{{# each(dictionary) }}
    key: {{ @key }}, value: {{.}}
{{/}}
let template = try! Template(named: "document")

// Register StandardLibrary.each for the key "each":
template.register(StandardLibrary.each, forKey: "each")

// Renders "key: name, value: Freddy Mercury"
let dictionary = ["name": "Freddy Mercury"]
let rendering = try! template.render(["dictionary": dictionary])

NSObject

The rendering of NSObject depends on the actual class:

  • NSFastEnumeration

    When an object conforms to the NSFastEnumeration protocol, like NSArray, it renders just like Swift Array. NSSet is an exception, rendered as a Swift Set. NSDictionary, the other exception, renders as a Swift Dictionary.

  • NSNumber is rendered as a Swift Bool, Int, UInt, Int64, UInt64, Float or Double, depending on its value.

  • NSString is rendered as String

  • NSNull renders as:

    • {{null}} does not render.
    • {{#null}}...{{/null}} does not render.
    • {{^null}}...{{/null}} renders.
  • For other NSObject, those default rules apply:

    • {{object}} renders the description method, HTML-escaped.
    • {{{object}}} renders the description method, not HTML-escaped.
    • {{#object}}...{{/object}} renders once, pushing the object on top of the context stack.
    • {{^object}}...{{/object}} does not render.

    With support for Objective-C runtime, templates can render object properties: {{ user.name }}.

    Subclasses can alter this behavior by overriding the mustacheBox method of the MustacheBoxable protocol. For more information, check the rendering of Custom Types below.

Custom Types

NSObject 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 = try Template(string: "{{name}} has a mustache.")
let rendering = try template.render(person)

When extracting values from your NSObject subclasses, GRMustache.swift uses the Key-Value Coding method valueForKey:, as long as the key is "safe" (safe keys are the names of declared properties, including NSManagedObject attributes).

Subclasses can alter this default behavior by overriding the mustacheBox method of the MustacheBoxable protocol, described below:

Pure Swift Values and MustacheBoxable

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

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

To let Mustache templates extract the name key out of a person so that they can render {{ name }} tags, we need to explicitly help the Mustache engine by conforming to the MustacheBoxable protocol:

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

Your mustacheBox implementation will generally call the Box function on a regular value that itself adopts the MustacheBoxable protocol (such as String or Int), or an array, a set, or a dictionary.

Now we can render persons, arrays of persons, dictionaries of persons, etc:

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

Boxing a dictionary is an easy way to build a box. However there are many kinds of boxes: check the rest of this documentation.

Lambdas

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

// `{{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 { (string) in "<b>\(string)</b>" }

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

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 (read on cocoadocs.org).

☝️ Note: Mustache lambdas slightly overlap with dynamic partials. Lambdas are required by the Mustache specification. Dynamic partials are more efficient because they avoid parsing lambda strings over and over.

Filters

Filters apply like functions, with parentheses: {{ uppercase(name) }}.

Generally speaking, using filters is a three-step process:

// 1. Define the filter using the `Filter()` function:
let uppercase = Filter(...)

// 2. Assign a name to your filter, and register it in a template:
template.register(uppercase, forKey: "uppercase")

// 3. Render
template.render(...)

It helps thinking about four kinds of filters:

Value Filters

Value filters transform any type of input. They can return anything as well.

For example, here is a square filter which squares integers:

// Define the `square` filter.
//
// square(n) evaluates to the square of the provided integer.
let square = Filter { (n: Int?) in
    guard let n = n else {
        // No value, or not an integer: return nil.
        // We could throw an error as well.
        return nil
    }
    
    // Return the result
    return n * n
}

// Register the square filter in our template:
let template = try Template(string: "{{n}} × {{n}} = {{square(n)}}")
template.register(square, forKey:"square")

// 10 × 10 = 100
let rendering = try template.render(["n": 10])

Filters can accept a precisely typed argument as above. You may prefer managing the value type yourself:

// Define the `abs` filter.
//
// abs(x) evaluates to the absolute value of x (Int or Double):
let absFilter = Filter { (box: MustacheBox) in
    switch box.value {
    case let int as Int:
        return abs(int)
    case let double as Double:
        return abs(double)
    default:
        return nil
    }
}

You can process collections and dictionaries as well, and return new ones:

// Define the `oneEveryTwoItems` filter.
//
// oneEveryTwoItems(collection) returns the array of even items in the input
// collection.
let oneEveryTwoItems = Filter { (box: MustacheBox) in
    // `box.arrayValue` returns a `[MustacheBox]` for all boxed collections
    // (Array, Set, NSArray, etc.).
    guard let boxes = box.arrayValue else {
        // No value, or not a collection: return the empty box
        return nil
    }
    
    // Rebuild another array with even indexes:
    var result: [MustacheBox] = []
    for (index, box) in boxes.enumerated() where index % 2 == 0 {
        result.append(box)
    }
    
    return result
}

// A template where the filter is used in a section, so that the items in the
// filtered array are iterated:
let templateString = "{{# oneEveryTwoItems(items) }}<{{.}}>{{/ oneEveryTwoItems(items) }}"
let template = try Template(string: templateString)

// Register the oneEveryTwoItems filter in our template:
template.register(oneEveryTwoItems, forKey: "oneEveryTwoItems")

// <1><3><5><7><9>
let rendering = try template.render(["items": Array(1..<10)])

Multi-arguments filters are OK as well. but you use the VariadicFilter() function, this time:

// Define the `sum` filter.
//
// sum(x, ...) evaluates to the sum of provided integers
let sum = VariadicFilter { (boxes: [MustacheBox]) in
    var sum = 0
    for box in boxes {
        sum += (box.value as? Int) ?? 0
    }
    return sum
}

// Register the sum filter in our template:
let template = try Template(string: "{{a}} + {{b}} + {{c}} = {{ sum(a,b,c) }}")
template.register(sum, forKey: "sum")

// 1 + 2 + 3 = 6
let rendering = try template.render(["a": 1, "b": 2, "c": 3])

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

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

When you want to format values, just use NumberFormatter, DateFormatter, or generally any Foundation's Formatter. They are ready-made filters:

let percentFormatter = NumberFormatter()
percentFormatter.numberStyle = .percent

let template = try Template(string: "{{ percent(x) }}")
template.register(percentFormatter, forKey: "percent")

// Rendering: 50%
let data = ["x": 0.5]
let rendering = try template.render(data)

More info on formatters.

Pre-Rendering Filters

Value filters as seen above process input values, which may be of any type (bools, ints, collections, etc.). Pre-rendering filters always process strings, whatever the input value. They have the opportunity to alter those strings before they get actually included in the final template rendering.

You can, for example, reverse a rendering:

// Define the `reverse` filter.
//
// reverse(x) renders the reversed rendering of its argument:
let reverse = Filter { (rendering: Rendering) in
    let reversedString = String(rendering.string.characters.reversed())
    return Rendering(reversedString, rendering.contentType)
}

// Register the reverse filter in our template:
let template = try Template(string: "{{reverse(value)}}")
template.register(reverse, forKey: "reverse")

// ohcuorG
try template.render(["value": "Groucho"])

// 321
try template.render(["value": 123])

Such filter does not quite process a raw string, as you have seen. It processes a Rendering, which is a flavored string, a string with its contentType (text or HTML).

This rendering will usually be text: simple values (ints, strings, etc.) render as text. Our reversing filter preserves this content-type, and does not mangle HTML entities:

// &gt;lmth&lt;
try template.render(["value": "<html>"])

Custom Rendering Filters

An example will show how they can be used:

// 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
    
    // The inner content of the section tag:
    var string = info.tag.innerTemplateString
    
    // Pluralize if needed:
    if let count = count, count > 1 {
        string += "s"  // naive
    }
    
    return Rendering(string)
}

// Register the pluralize filter in our template:
let templateString = "I have {{ cats.count }} {{# pluralize(cats.count) }}cat{{/ }}."
let template = try Template(string: templateString)
template.register(pluralize, forKey: "pluralize")

// I have 3 cats.
let data = ["cats": ["Kitty", "Pussy", "Melba"]]
let rendering = try template.render(data)

As those filters perform custom rendering, they are based on RenderFunction, just like lambdas. Check the RenderFunction type in CoreFunctions.swift for more information about the RenderingInfo and Rendering types (read on cocoadocs.org).

Advanced Filters

All the filters seen above are particular cases of FilterFunction. "Value filters", "Pre-rendering filters" and "Custom rendering filters" are common use cases that are granted with specific APIs.

Yet the library ships with a few built-in filters that don't quite fit any of those categories. Go check their documentation. And since they are all written with public GRMustache.swift APIs, check also their source code, for inspiration. The general FilterFunction itself is detailed in CoreFunctions.swift (read on cocoadocs.org).

Advanced Boxes

Values that feed templates are able of many different behaviors. Let's review some of them:

  • Bool can trigger or prevent the rendering of sections:

    {{# isVerified }}VERIFIED{{/ isVerified }}
    {{^ isVerified }}NOT VERIFIED{{/ isVerified }}
    
  • Arrays render sections multiple times, and expose the count, first, and last keys:

    You see {{ objects.count }} objects:
    {{# objects }}
    - {{ name }}
    {{/ objects }}
    
  • Dictionaries expose all their keys:

    {{# user }}
    - {{ name }}
    - {{ age }}
    {{/ user }}
    
  • NSObject exposes all its properties:

    {{# user }}
    - {{ name }}
    - {{ age }}
    {{/ user }}
    
  • Foundation's Formatter is able to format values (more information):

    {{ format(date) }}
    
  • StandardLibrary.each is a filter that defines some extra keys when iterating an array (more information):

    {{# each(items) }}
    - {{ @indexPlusOne }}: {{ name }}
    {{/}}
    

This variety of behaviors is made possible by the MustacheBox type. Whenever a value, array, filter, etc. feeds a template, it is turned into a box that interact with the rendering engine.

Let's describe in detail the rendering of the {{ F(A) }} tag, and shed some light on the available customizations:

  1. The A and F expressions are evaluated: the rendering engine looks in the context stack for boxes that return a non-empty box for the keys "A" and "F". The key-extraction service is provided by a customizable KeyedSubscriptFunction.

    This is how NSObject exposes its properties, and Dictionary, its keys.

  2. The customizable FilterFunction of the F box is evaluated with the A box as an argument.

    The Result box may well depend on the customizable value of the A box, but all other facets of the A box may be involved. This is why there are various types of filters.

  3. The rendering engine then looks in the context stack for all boxes that have a customized WillRenderFunction. Those functions have an opportunity to process the Result box, and eventually return another one.

    This is how, for example, a boxed DateFormatter can format all dates in a section: its WillRenderFunction formats dates into strings.

  4. The resulting box is ready to be rendered. For regular and inverted section tags, the rendering engine queries the customizable boolean value of the box, so that {{# F(A) }}...{{/}} and {{^ F(A) }}...{{/}} can't be both rendered.

    The Bool type obviously has a boolean value, but so does String, so that empty strings are considered falsey.

  5. The resulting box gets eventually rendered: its customizable RenderFunction is executed. Its Rendering result is HTML-escaped, depending on its content type, and appended to the final template rendering.

    Lambdas use such a RenderFunction, so do pre-rendering filters and custom rendering filters.

  6. Finally the rendering engine looks in the context stack for all boxes that have a customized DidRenderFunction.

    This one is used by Localizer and Logger goodies.

All those customizable properties are exposed in the low-level MustacheBox initializer:

// MustacheBox initializer
init(
    value value: Any? = nil,
    boolValue: Bool? = nil,
    keyedSubscript: KeyedSubscriptFunction? = nil,
    filter: FilterFunction? = nil,
    render: RenderFunction? = nil,
    willRender: WillRenderFunction? = nil,
    didRender: DidRenderFunction? = nil)

We'll below describe each of them individually, even though you can provide several ones at the same time:

  • value

    The optional value parameter gives the boxed value. The value is used when the box is rendered (unless you provide a custom RenderFunction). It is also returned by the value property of MustacheBox.

    let aBox = MustacheBox(value: 1)
    
    // Renders "1"
    let template = try Template(string: "{{a}}")
    try template.render(["a": aBox])
  • boolValue

    The optional boolValue parameter tells whether the Box should trigger or prevent the rendering of regular {{#section}}...{{/}} and inverted {{^section}}...{{/}} tags. The default value is true.

    // Render "true", "false"
    let template = try Template(string:"{{#.}}true{{/.}}{{^.}}false{{/.}}")
    try template.render(MustacheBox(boolValue: true))
    try template.render(MustacheBox(boolValue: false))
  • keyedSubscript

    The optional keyedSubscript parameter is a KeyedSubscriptFunction that lets the Mustache engine extract keys out of the box. For example, the {{a}} tag would call the subscript function with "a" as an argument, and render the returned box.

    The default value is nil, which means that no key can be extracted.

    Check the KeyedSubscriptFunction type in CoreFunctions.swift for more information (read on cocoadocs.org).

    let box = MustacheBox(keyedSubscript: { (key: String) in
        return Box("key:\(key)")
    })
    
    // Renders "key:a"
    let template = try Template(string:"{{a}}")
    try template.render(box)
  • filter

    The optional filter parameter is a FilterFunction that lets the Mustache engine evaluate filtered expression that involve the box. The default value is nil, which means that the box can not be used as a filter.

    Check the FilterFunction type in CoreFunctions.swift for more information (read on cocoadocs.org).

    let box = MustacheBox(filter: Filter { (x: Int?) in
        return x! * x!
    })
    
    // Renders "100"
    let template = try Template(string:"{{square(x)}}")
    try template.render(["square": box, "x": Box(10)])
  • render

    The optional render parameter is a RenderFunction that is evaluated when the Box is rendered.

    The default value is nil, which makes the box perform default Mustache rendering:

    • {{box}} renders the built-in Swift String Interpolation of the value, HTML-escaped.
    • {{{box}}} renders the built-in Swift String Interpolation of the value, not HTML-escaped.
    • {{#box}}...{{/box}} pushes the box on the top of the context stack, and renders the section once.

    Check the RenderFunction type in CoreFunctions.swift for more information (read on cocoadocs.org).

    let box = MustacheBox(render: { (info: RenderingInfo) in
        return Rendering("foo")
    })
    
    // Renders "foo"
    let template = try Template(string:"{{.}}")
    try template.render(box)
  • willRender & didRender

    The optional willRender and didRender parameters are a WillRenderFunction and DidRenderFunction that are evaluated for all tags as long as the box is in the context stack.

    Check the WillRenderFunction and DidRenderFunction type in CoreFunctions.swift for more information (read on cocoadocs.org).

    let box = MustacheBox(willRender: { (tag: Tag, box: MustacheBox) in
        return "baz"
    })
    
    // Renders "baz baz"
    let template = try Template(string:"{{#.}}{{foo}} {{bar}}{{/.}}")
    try template.render(box)

By mixing all those parameters, you can finely tune the behavior of a box.

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.

grmustache.swift's People

Contributors

acwright avatar antoninbiret avatar ariarijp avatar blixlt avatar fumito-ito avatar groue avatar herrernst avatar marcelofabri avatar nshaosong avatar objcolumnist avatar readmecritic avatar saagarjha avatar samuelschepp avatar samus avatar yannickl avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

grmustache.swift's Issues

Fail to execute testing on Xcode 14.0

On Xcode 14.0, executing test fails every time. Followings are log detail. On Xcode 13.3.1, everything goes well.

**Details**

  

Process spawn via launchd failed.

Domain: NSPOSIXErrorDomain

Code: 22

Failure Reason: Invalid argument

User Info: {

    DVTErrorCreationDateKey = "2022-09-17 02:48:34 +0000";

    IDERunOperationFailingWorker = IDELaunchiPhoneSimulatorLauncher;

    Session = "com.apple.CoreSimulator.SimDevice.7283EAE1-17BE-499F-9A59-9ADADE294882";

}

--

The operation couldn’t be completed. (Mach error 22 - (os/kern) invalid host)

Domain: NSMachErrorDomain

Code: 22

--

  

Analytics Event: com.apple.dt.IDERunOperationWorkerFinished : {

    "device_model" = "iPhone14,2";

    "device_osBuild" = "16.0 (20A360)";

    "device_platform" = "com.apple.platform.iphonesimulator";

    "launchSession_schemeCommand" = Test;

    "launchSession_state" = 1;

    "launchSession_targetArch" = arm64;

    "operation_duration_ms" = 6;

    "operation_errorCode" = 22;

    "operation_errorDomain" = NSPOSIXErrorDomain;

    "operation_errorWorker" = IDELaunchiPhoneSimulatorLauncher;

    "operation_name" = IDERunOperationWorkerGroup;

    "param_consoleMode" = 0;

    "param_debugger_attachToExtensions" = 0;

    "param_debugger_attachToXPC" = 1;

    "param_debugger_type" = 3;

    "param_destination_isProxy" = 0;

    "param_destination_platform" = "com.apple.platform.iphonesimulator";

    "param_diag_MainThreadChecker_stopOnIssue" = 0;

    "param_diag_MallocStackLogging_enableDuringAttach" = 0;

    "param_diag_MallocStackLogging_enableForXPC" = 0;

    "param_diag_allowLocationSimulation" = 1;

    "param_diag_checker_tpc_enable" = 0;

    "param_diag_gpu_frameCapture_enable" = 3;

    "param_diag_gpu_shaderValidation_enable" = 0;

    "param_diag_gpu_validation_enable" = 1;

    "param_diag_memoryGraphOnResourceException" = 0;

    "param_diag_queueDebugging_enable" = 1;

    "param_diag_runtimeProfile_generate" = 0;

    "param_diag_sanitizer_asan_enable" = 0;

    "param_diag_sanitizer_tsan_enable" = 0;

    "param_diag_sanitizer_tsan_stopOnIssue" = 0;

    "param_diag_sanitizer_ubsan_stopOnIssue" = 0;

    "param_diag_showNonLocalizedStrings" = 0;

    "param_diag_viewDebugging_enabled" = 1;

    "param_diag_viewDebugging_insertDylibOnLaunch" = 0;

    "param_install_style" = 0;

    "param_launcher_UID" = 2;

    "param_launcher_allowDeviceSensorReplayData" = 0;

    "param_launcher_kind" = 0;

    "param_launcher_style" = 0;

    "param_launcher_substyle" = 0;

    "param_runnable_appExtensionHostRunMode" = 0;

    "param_runnable_productType" = "com.apple.product-type.tool";

    "param_runnable_type" = 1;

    "param_testing_launchedForTesting" = 1;

    "param_testing_suppressSimulatorApp" = 0;

    "param_testing_usingCLI" = 0;

    "sdk_canonicalName" = "iphonesimulator16.0";

    "sdk_osVersion" = "16.0";

    "sdk_variant" = iphonesimulator;

}

--

  

  

System Information

  

macOS Version 12.5 (Build 21G72)

Xcode 14.0 (21335) (Build 14A309)

Timestamp: 2022-09-17T11:48:34+09:00

link css is not work

like this <link href="news.css" rel="stylesheet" type="text/css" /> is not work

Segmentation fault 11

App runs fine in simulator and on device, but when archiving for deployment, compiler chokes with Segmentation fault, and the following output:

While emitting IR SIL function @TTSGSaV16theabsolutesound11MustacheBox_GSaS0__Ss14CollectionType_GVSs17IndexingGeneratorGSaS0___GS2_GSaS0___Ss13GeneratorType_S0_S0_S_15MustacheBoxable_SiSiSs16ForwardIndexType_SiSiSs18_SignedIntegerType_SiSiSs33_BuiltinIntegerLiteralConvertible_Si_S0____TF16theabsolutesoundP33_923FE48AD3BF7E753B76F3E22D01634016renderCollectionUSs14CollectionType_USs13GeneratorType_S_15MustacheBoxable_Ss16ForwardIndexType_Ss18_SignedIntegerType_Ss33_BuiltinIntegerLiteralConvertible____FTQ_VS_13RenderingInfoGVSs33AutoreleasingUnsafeMutablePointerGSqCSo7NSError___GSqVS_9Rendering for 'renderCollection' at /u/theabsolutesound/theabsolutesound/Mustache/Rendering/MustacheBox.swift:979:9

Seemingly to do with:

private func renderCollection<C: CollectionType where C.Generator.Element: MustacheBoxable, C.Index: BidirectionalIndexType, C.Index.Distance == Int>(collection: C, var info: RenderingInfo, error: NSErrorPointer) -> Rendering? {

Just upgraded to Xcode 6.2, and hadn't ever archived in previous version, so can't say whether this is a bug introduced by 6.2 or not.

Any ideas what might be going on here?

Thread Safety Improvements

One does not generally trigger threading issues with GRMustache:

// Thread-safe
let template = try Template(path: "/path/to/templates/document.mustache")
let rendering = try template.render(data)

But should one go down one level and expose Template Repositories to make good use of their configurability, template caching, or support for absolute paths to partials, threading issues may arise:

let templatesURL = URL(fileURLWithPath: "/path/to/templates")
let repository = TemplateRepository(baseURL: templatesURL)

// Not thread-safe (1)
repository.configuration.register(StandardLibrary.eachFilter, forKey: "each") 

// Not thread-safe (2)
let template = try repository.template(named: "document")

// Thread-safe
let rendering = try template.render(data)
  1. The configuration of a template repository is not protected against concurrent accesses: should two threads modify or read the configuration of a single repository at the same time, the program may behave in an unexpected way, or crash.

    This issue is not a big deal: template repository configuration is supposed to happen once, before any template is loaded from the repository. We can just update the documentation and add a warning.

  2. Template loading is not thread-safe: should two threads load a template at the same time, the program may behave in an unexpected way, or crash.

    Now this is a real issue, which needs to be addressed. Basically the problem lies in the fact that a template repository's cache and locked configuration are not protected against concurrent uses.

    The cache contains Mustache template ASTs, and prevents GRMustache from parsing again and again the same templates and partials. It also gives support for recursive templates. The locked configuration is a copy of the repository's configuration made as soon as the repository loads its first template. Once the configuration locked, further updates to the repository's configuration have no effect.

Boxing a dictionary of type [String: Any]

Hey @groue, was wondering if you could help me out with an issue I have. I have a dictionary of type [String: Any] which contains metadata for the files I'm rendering. Any advice on how I can make this work with the MustacheBoxable protocol?

GRMustache interferes with the AnyObject subscript (Cast from 'MustacheBox!' to unrelated type 'String' always fails)

Before using Mustache.swift the codebase was accessing the subscript of an AnyObject (that was a Dictionary underneath). I know it's not a best practice but this was working.
After adding Mustache, it seems that the subscript access is override and a MustacheBox is returned.

I looked at the MustacheBox and I can't figure out why this is happening.

image

We easily fixed the issue by not treating the Dictionary like an AnyObject but like an Dictionary. But I'd like to understand why MustacheBox interfered with the AnyObject subscript.

Thanks for the library, it's so useful!

Note: I'm using the Swift2 branch

Linux support

@fumito-ito @groue Thanks for updating GRMustache to Swift 5.

Picking up something that we dropped long ago. GRMustache doesn't build on Linux yet. Though I've been spending some time getting it built as a Swift package on Linux, I wanted to check if any work has already been done on this. Also, is there anything else I should know about a possible Linux support?

Crashes in Xcode 10

Hi,
I tried your changes in Xcode 9 it works fine. However it crashes for Xcode 10.
Crash location is in MustacheBox.swift on line 470 i.e
case .section:
// {{# box }}...{{/ box }}

                // Push the value on the top of the context stack:
                let context = info.context.extendedContext(self)

This crashes for :
temple = {{#children}}{{{.}}}{{/children}}
values = ["children" : ["New string"]]

This works fine in case of values for dictionary is array of Int, double etc.

I am stuck. Please could you help me?

Abandoned?

Sad to see the project not being maintained anymore while there are still people providing pull requests with Swift updates for example.

Let's find a solution here? Maybe somebody could take over the ownership or become a contributor?

Question: Apply filter without having value in JSON

Let me put the simplest possible case:

I have something like this:
{{ myMethod(THIS_IS_A_STRING) }}

And my JSON is empty: { }

If i define this filter:

let myMethod = Filter { (rendering: Rendering) in
                let value = String(rendering.string) **// Value is always "" !!**
                let result = value.WHATEVER_TRANSFORMATION_HERE
                return Rendering(result, rendering.contentType)
            }

I correctly register for the context like:
template.registerInBaseContext("myMethod", Box(myMethod))

But when debugging, I don't get any value.

Maybe is a parser problem that if it doesn't match with something from the json is not passed to the filter. I really don't know but I don't know how I should implement this with this framework.

Maybe is this in MustacheBox.swift where is already empty if it's not present in the JSON:

if let value = self.value {
                        // Use the built-in Swift String Interpolation:
                        return Rendering("\(value)", .Text)
                    } else {
                        return Rendering("", .Text)
                    }

Any help would be much much appreciated.

typo in ZipFilter.swift?

Line 40:
zippedGenerators.append(AnyGenerator(array.generate())) <-- errors out with AnyGenerator not accepting any arguments

should it perhaps be
zippedGenerators.append(anyGenerator(array.generate()))

Declarations in extensions cannot override yet - Swift 4

When I tried to migrate my app to use Xcode 9 + Swift 4 I received the error Declarations in extensions cannot override yet in the files: Box.swift, Formatter.swift and Foundation.swift.

Example:

In the file Box.swift in the code below occurs the error in the line: open override var mustacheBox: MustacheBox {.

/// GRMustache provides built-in support for rendering `NSNull`.
extension NSNull {
   
   open override var mustacheBox: MustacheBox {
        return MustacheBox(
            value: self,
            boolValue: false,
            render: { (info: RenderingInfo) in return Rendering("") })
    }
}

How to solve?

Thanks,
Luciano

Issues with using GRMustache with multiple data types.

Below are some of the test's that I wrote to show the issue that I'm seeing with the library. Please let me know if anyone else is seeing the issues. Looking into the library, the issues seem to be related to fix #83 on the GRMustache library. Please let me know if you think this is an issue and if I could help.

When there are Int types along with Strings in the value dictionary, the whole dictionary gets replaced in the place where a value for a key should be replaced in mustache. Below are some tests that I wrote to explain what's wrong.

func test_mustache_with_different_value_types() throws {
    let mustache = try? Mustache.Template(string: "{{#position}} column--{{.}}{{/position}}")
    let rendered = try? mustache?.render(["position": Int64(1),
                                          "css": "string",
                                          "style": true])
    XCTAssertEqual(rendered!, "column--1")
}

This fails with the below error:

XCTAssertEqual failed: ("Optional(" column--[AnyHashable(&quot;position&quot;): Optional(1), AnyHashable(&quot;css&quot;): Optional(&quot;string&quot;), AnyHashable(&quot;style&quot;): Optional(true)]")") is not equal to ("Optional("column--1")”)

Comments: As we can see, mustache should have returned column—1, but it replaces position with the whole dictionary. However, this never happens if they are all strings, as we can see in the below example. Also, this happens even if the position was a regular Int.I’m using Int64 because for some reason that’s what the apple SDK converts the values from the JSON to, maybe just to accommodate huge numbers.

func test_mustache_with_different_value_types() throws {
    let mustache = try? Mustache.Template(string: "{{#position}}column--{{.}}{{/position}}")
    let rendered = try? mustache?.render(["position": "1",
                                          "css": "string",
                                          "style": true])
    XCTAssertEqual(rendered!, "column--1")
}

The above test passes since position here is a string.

func test_mustache_with_different_value_types() throws {
    let mustache = try? Mustache.Template(string: "{{#position}}column--{{.}}{{/position}}")
    let rendered = try? mustache?.render(["position": [Int64(1), Int64(2)],
                                          "css": "string",
                                          "style": true])
    XCTAssertEqual(rendered!, "column--1column--2")
}

However, if position is an Array of Int64 or Int for that matter, it works as it should. The above test does pass.

Pardon my force unwraps, just trying to show issue that I'm facing.

Mustache tags on new lines are replaced with empty space

First off, great library!

I might be missing some obvious syntax here, but given the following example:

{{#someOptionalValue}}
The value is {{someOptionalValue}}
{{/someOptionalValue}}

The output we get (assuming someOptionalValue is "Hello, World") is:


The value is Hello, World

Is there a way to modify the tags, such that the newlines before / after "The value is Hello, World" are removed? Eg:

The value is Hello, World

Currently it seems I can only achieve this by putting everything on one line:

{{#someOptionalValue}}The value is {{someOptionalValue}}{{/someOptionalValue}}

I have seem some references to Handlebars using a tilde to accomplish this, eg:

{{~#someOptionalValue~}}
The value is {{someOptionalValue}}
{{/someOptionalValue}}

But in attempting to do this with this repository's Mustache implementation, I get an error "Unmatched closing tag".

Thanks,

  • Adam

Localization Customization

The Localizer filter brings localization to your mustache templates:

let template = ...
template.register(StandardLibrary.Localizer(), forKey: "localize")

It can localize:

  • variables: {{ localize(greeting) }} renders the localization of the greeting variable
  • template snippets: {{# localize }}Hello{{/ localize }} renders the localization of "Hello"
  • embedded variables: {{# localize }}Hello {{name}}{{/ localize }} localizes "Hello %@", and uses the result at a format string in which in injects the rendering of the name variable

As it is today, Localizer uses NSLocalizedString. Two areas of improvements are:

  • Make Localizer extensible so that it can be fueled by another localization engine than NSLocalizedString
  • Make it use a custom locale

Line breaks

Hey, great library!
Would it be possible to ignore line breaks in a template when a line only contains a single {{# tag?
This would mean you could write:

enum {{enumName}} { 
    {{#enums}}
    case {{name}} = "{{value}}"
    {{/enums}}
}

instead of

enum {{enumName}} { {{#enums}}
    case {{name}} = "{{value}}"{{/enums}}
}

where you would want each case of that enum to be on a seperate line with no empty lines in between.

It greatly cleans up mustache template files, especially if you have many nested loops

Carthage build

Hi,

Tried to install latest 1.0.0 version using Carthage and have following issue:

*** Building scheme "MustacheiOS" in Mustache.xcworkspace
** BUILD FAILED **

The following build commands failed:
ProcessInfoPlistFile /Users/rayand/Library/Developer/Xcode/DerivedData/Mustache-docnnakqnaasqpccvubxiwwwydbs/Build/Products/Release-iphoneos/Mustache.framework/Info.plist Mustache/Info.plist
(1 failure)
error: could not read data from '/Users/rayand/Projects/Freelance/MobileAngels/isangel/iOSApplication/SnowAngel/SnowAngel/Carthage/Checkouts/GRMustache.swift/Xcode/Mustache/Info.plist': The file “Info.plist” couldn’t be opened because there is no such file.
A shell task failed with exit code 65:
** BUILD FAILED **

The following build commands failed:
ProcessInfoPlistFile /Users/rayand/Library/Developer/Xcode/DerivedData/Mustache-docnnakqnaasqpccvubxiwwwydbs/Build/Products/Release-iphoneos/Mustache.framework/Info.plist Mustache/Info.plist
(1 failure)

I'm on latest Xcode and latest Carthage.

Thanks.

Swift Package Manager support

Now that Swift is open source, the need for a robust templating library has skyrocketed. I'd love to be able to use GRMustache on Linux, and since it doesn't depend on the Obj-C runtime, GRMustache is an awesome candidate!

Could we get a Swift package manager package?

Post-rendering Lambda?

Apologies if I'm missing it, but is there a way to have a lambda/filter/whatever that post-processes the rendering of its nested content. I want to have something like:

{{#section}}{{value}}{{/section}}

But where the section has access to and post-processes the renderer result of its nested content. Right now the Lambda get {{value}} as its string input. I want it to get the post-rendered version to process.

How to render nested objects?

Been trying to make a nested structure to map against the json response I'm working with but I don't really understand how the example works.

With support for Objective-C runtime, templates can render object properties: {{ user.name }}.

This make it sound like it's possible but is there a more thorough example of how this would be done? This is what I'm trying to do:

class FirstTemplate : NSObject {
    let nestedObject: NSObject
    
    init(nestedObject: NSObject) {
        self.nestedObject = nestedObject
    }
}

class SecondTemplate : NSObject {
    let someValue: String
    
    init(someValue: String) {
        self.someValue = someValue
    }
}

let template = try Template(string: "{{nestedObject.someValue}}") // .. json response
let rendering = try template.render(FirstTemplate(nestedObject: SecondTemplate(someValue: "Hello world!"))) //  .. renders blank

This just renders blank but I also tried:

struct FirstTemplate {
    let nestedObject: MustacheBoxable
}

extension FirstTemplate : MustacheBoxable {
    var mustacheBox: MustacheBox {
        return Box(["nestedObject": self.nestedObject])
    }
}

struct SecondTemplate {
    let someValue: String
}

extension SecondTemplate : MustacheBoxable {
    var mustacheBox: MustacheBox {
        return Box(["someValue": self.someValue])
    }
}

let context = FirstTemplate(nestedObject: SecondTemplate(someValue: "Hello world!"))
let template = try Template(string: "{{{nestedObject.someValue}}") // 
let rendering = try template.render(context) //  .. renders blank

.. but this works
// let context = FirstTemplate(nestedObject: SecondTemplate(someValue: "Hello world!"))
// let nestedContext = context.secondTemplate
// let rendering = try template.render(nestedContext) //  "Hello world!"

Error building with Swift 3.0.2 on Sierra (10.12.2)

Running swift package results in:

error: the module at Tests/Carthage has an invalid name ('Carthage'): the name of a test module has no ‘Tests’ suffix
fix: rename the module at ‘Tests/Carthage’ to have a ‘Tests’ suffix

Contents of the Package.swift file:

import PackageDescription

let package = Package(
    name: "FCPXShotExporter",
    targets: [],
    dependencies: [
        .Package(url: "https://github.com/groue/GRMustache.swift", majorVersion: 2, minor: 0),
        ]
)```

Zero-parameter filters

Firstly, let me say thanks so much for this project. Soooooo good!

I've been adding my own custom filters, and I want to add one that I can call like: randomNumber(), however, it doesn't look like GRMustache supports filters with zero parameters.

I thought I'd be able to use something like:

let filter = VariadicFilter { (boxes: [MustacheBox]) in
    let randomNumber = generateRandomNumber()
    return Box(randomNumber)
}
template.registerInBaseContext("randomNumber", Box(filter))

and invoke using {{ randomNumber() }}, however, my filter never gets invoked. If, however, I use {{ randomNumber(XXX) }} where XXX is anything at all (a variable that doesn't exist, a ., whatever), then my filter gets invoked.

I tried just using a normal Filter too, but that doesn't get invoked either. Any thoughts?

cocoapods support

I find that I can't include this framework with command pod install.
could you please add cocoapods support? I tried obj-c version and it is supported

3.x and 4.x huge performance issue

Hello,
First of all, thank you for the library.

I used version 2.0.0 till today without any problem, when I wanted to update the whole development stack to Xcode 10.2 and Swift 5. So, I had to update to 4.x or eventually 3.x. Carthage update and build passed OK, but I noticed huge performance issue in call: template = try Template(URL: url). It takes up to 10-15 seconds in version 3.x and 4.x, in compare with less than a second in version 2.0.0.

This issue prevents me to update the whole project to Swift 5/Xcode 10.2.

Can you please check?

Thank you very much!
Uros

Problems with PFObject

I had issued a previous issue request but failed to truly debug the issue. The issue is that it doesn't work with PFObject. It doesn't seem to fail, just removes everything. The old pod version of this worked and continues to work but we need to move to SPM.

Parsing large templates causes tremendous memory allocation

Thanks for the great library!

I've been using GRMustache.swift for several months now and have been happy with the results. I'm using v0.11.0 from CocoaPods to generate a single-page report for printing in an enterprise business application.

Recently, my team has been experiencing out of memory crashes, so I profiled the memory usage with Instruments and found the following culprit: return templateString.substringFromIndex(index).hasPrefix(string) (line 47 of TemplateParser.swift)

I have 8 total templates, listed by their size in bytes:
template file sizes in bytes

My app preloads the templates in the background at start. It is protected by a GCD dispatch group, so subsequent uses of the templates wait for initial preloading to finish (which avoids a crash):
appdelegate mustache preloading

As you can see in the following screenshot, Mustache allocates 96.7% of the application's total allocated memory before it is killed by iOS:
mustache heap allocations with cfstring

For comparison, here is Instrument's display of the allocation statistics:
overall heap allocation

The problem seems to be that Swift is creating new immutable copies of the templateString with each call to substringFromIndex(String.CharacterView.Index) -> String, and these appear to be permanent as long as the app is alive. Being that the closure atString() is called throughout Mustache's parse(_:templateId:) method, the later parts of templateString become duplicated countless times for each template, causing massive amounts of memory usage.

For my own usage, I am investigating using the sequences templateString.characters and string.characters that were introduced in iOS 9.0. This isn't ideal; I prefer to stick to an unmodified library. At the same time, I understand that compatibility with iOS 7.0+ greatly restricts string processing features in Swift.

Any help is greatly appreciated.

Boxed NSObjects fail for Cocoapods installation

When installing the framework via Cocoapods, NSObject subclasses aren't properly boxed, due to the compiler conditional in Box.swift.

Here's a temporary workaround hook for a project's Podfile:

post_install do |installer|
    installer.pods_project.targets.each do |target|
        if target.name == "GRMustache.swift"
            target.build_configurations.each do |config|
                config.build_settings['OTHER_SWIFT_FLAGS'] ||= ['$(inherited)', '-DOBJC']
            end
        end
    end
end

Linux support

Looks like it should work on Linux #16 , but it actually throws tons of errors and warnings:

swift build
Compiling Swift Module 'Mustache' (32 sources)
/home/saulius/temp/GRMustacheSPM/Packages/GRMustache.swift-1.0.0/Sources/TemplateAST.swift:38:10: error: type member may not be named 'Type', since it would conflict with the 'foo.Type' expression
    enum Type {
         ^
/home/saulius/temp/GRMustacheSPM/Packages/GRMustache.swift-1.0.0/Sources/TemplateAST.swift:38:10: note: backticks can escape this name if it is important to use
    enum Type {
         ^~~~
         `Type`
/home/saulius/temp/GRMustacheSPM/Packages/GRMustache.swift-1.0.0/Sources/TemplateAST.swift:38:10: error: type member may not be named 'Type', since it would conflict with the 'foo.Type' expression
    enum Type {
         ^
/home/saulius/temp/GRMustacheSPM/Packages/GRMustache.swift-1.0.0/Sources/TemplateAST.swift:38:10: note: backticks can escape this name if it is important to use
    enum Type {
         ^~~~
         `Type`
/home/saulius/temp/GRMustacheSPM/Packages/GRMustache.swift-1.0.0/Sources/TemplateCompiler.swift:383:14: error: type member may not be named 'Type', since it would conflict with the 'foo.Type' expression
        enum Type {
             ^
/home/saulius/temp/GRMustacheSPM/Packages/GRMustache.swift-1.0.0/Sources/TemplateCompiler.swift:383:14: note: backticks can escape this name if it is important to use
        enum Type {
             ^~~~
             `Type`
/home/saulius/temp/GRMustacheSPM/Packages/GRMustache.swift-1.0.0/Sources/TemplateCompiler.swift:383:14: error: type member may not be named 'Type', since it would conflict with the 'foo.Type' expression
        enum Type {
             ^
/home/saulius/temp/GRMustacheSPM/Packages/GRMustache.swift-1.0.0/Sources/TemplateCompiler.swift:383:14: note: backticks can escape this name if it is important to use
        enum Type {
             ^~~~
             `Type`
/home/saulius/temp/GRMustacheSPM/Packages/GRMustache.swift-1.0.0/Sources/TemplateCompiler.swift:81:232: error: cannot convert value of type 'String' to type 'NSString' in coercion
                    if (try! NSRegularExpression(pattern: "^CONTENT_TYPE\\s*:\\s*TEXT$", options: NSRegularExpressionOptions(rawValue: 0))).firstMatchInString(pragma, options: NSMatchingOptions(rawValue: 0), range: NSMakeRange(0, (pragma as NSString).length)) != nil {
                                                                                                                                                                                                                                       ^~~~~~
/home/saulius/temp/GRMustacheSPM/Packages/GRMustache.swift-1.0.0/Sources/TemplateCompiler.swift:88:239: error: cannot convert value of type 'String' to type 'NSString' in coercion
                    } else if (try! NSRegularExpression(pattern: "^CONTENT_TYPE\\s*:\\s*HTML$", options: NSRegularExpressionOptions(rawValue: 0))).firstMatchInString(pragma, options: NSMatchingOptions(rawValue: 0), range: NSMakeRange(0, (pragma as NSString).length)) != nil {
                                                                                                                                                                                                                                              ^~~~~~
/home/saulius/temp/GRMustacheSPM/Packages/GRMustache.swift-1.0.0/Sources/Context.swift:218:18: error: type member may not be named 'Type', since it would conflict with the 'foo.Type' expression
    private enum Type {
                 ^
/home/saulius/temp/GRMustacheSPM/Packages/GRMustache.swift-1.0.0/Sources/Context.swift:218:18: note: backticks can escape this name if it is important to use
    private enum Type {
                 ^~~~
                 `Type`
/home/saulius/temp/GRMustacheSPM/Packages/GRMustache.swift-1.0.0/Sources/Context.swift:218:18: error: type member may not be named 'Type', since it would conflict with the 'foo.Type' expression
    private enum Type {
                 ^
/home/saulius/temp/GRMustacheSPM/Packages/GRMustache.swift-1.0.0/Sources/Context.swift:218:18: note: backticks can escape this name if it is important to use
    private enum Type {
                 ^~~~
                 `Type`
/home/saulius/temp/GRMustacheSPM/Packages/GRMustache.swift-1.0.0/Sources/Template.swift:64:22: error: cannot convert value of type 'String' to type 'NSString' in coercion
        let nsPath = path as NSString
                     ^~~~
/home/saulius/temp/GRMustacheSPM/Packages/GRMustache.swift-1.0.0/Sources/Template.swift:91:50: error: cannot convert value of type 'String' to type 'NSString' in coercion
        let templateName = (URL.lastPathComponent! as NSString).stringByDeletingPathExtension
                            ~~~~~~~~~~~~~~~~~~~~~^
/home/saulius/temp/GRMustacheSPM/Packages/GRMustache.swift-1.0.0/Sources/NSFormatter.swift:88:25: error: declarations in extensions cannot override yet
    public override var mustacheBox: MustacheBox {
                        ^
/home/saulius/temp/GRMustacheSPM/Packages/GRMustache.swift-1.0.0/Sources/Box.swift:516:16: note: overridden declaration is here
    public var mustacheBox: MustacheBox {
               ^
/home/saulius/temp/GRMustacheSPM/Packages/GRMustache.swift-1.0.0/Sources/TemplateToken.swift:25:10: error: type member may not be named 'Type', since it would conflict with the 'foo.Type' expression
    enum Type {
         ^
/home/saulius/temp/GRMustacheSPM/Packages/GRMustache.swift-1.0.0/Sources/TemplateToken.swift:25:10: note: backticks can escape this name if it is important to use
    enum Type {
         ^~~~
         `Type`
/home/saulius/temp/GRMustacheSPM/Packages/GRMustache.swift-1.0.0/Sources/TemplateToken.swift:25:10: error: type member may not be named 'Type', since it would conflict with the 'foo.Type' expression
    enum Type {
         ^
/home/saulius/temp/GRMustacheSPM/Packages/GRMustache.swift-1.0.0/Sources/TemplateToken.swift:25:10: note: backticks can escape this name if it is important to use
    enum Type {
         ^~~~
         `Type`
/home/saulius/temp/GRMustacheSPM/Packages/GRMustache.swift-1.0.0/Sources/TemplateRepository.swift:178:54: error: type 'NSURL' has no member 'fileURLWithPath'
        self.init(dataSource: URLDataSource(baseURL: NSURL.fileURLWithPath(directoryPath, isDirectory: true), templateExtension: templateExtension, encoding: encoding))
                                                     ^~~~~ ~~~~~~~~~~~~~~~
/home/saulius/temp/GRMustacheSPM/Packages/GRMustache.swift-1.0.0/Sources/TemplateRepository.swift:445:74: error: value of optional type 'String?' not unwrapped; did you mean to use '!' or '?'?
            self.baseURLAbsoluteString = baseURL.URLByStandardizingPath!.absoluteString
                                                                         ^
                                                                                       !
/home/saulius/temp/GRMustacheSPM/Packages/GRMustache.swift-1.0.0/Sources/TemplateRepository.swift:468:37: error: cannot convert value of type 'String' to type 'NSString' in coercion
                templateFilename = (normalizedName as NSString).stringByAppendingPathExtension(templateExtension)!
                                    ^~~~~~~~~~~~~~
/home/saulius/temp/GRMustacheSPM/Packages/GRMustache.swift-1.0.0/Sources/TemplateRepository.swift:484:16: error: value of optional type 'String?' not unwrapped; did you mean to use '!' or '?'?
            if templateAbsoluteString.rangeOfString(baseURLAbsoluteString)?.startIndex == templateAbsoluteString.startIndex {
               ^
                                     !
/home/saulius/temp/GRMustacheSPM/Packages/GRMustache.swift-1.0.0/Sources/TemplateRepository.swift:484:91: error: value of optional type 'String?' not unwrapped; did you mean to use '!' or '?'?
            if templateAbsoluteString.rangeOfString(baseURLAbsoluteString)?.startIndex == templateAbsoluteString.startIndex {
                                                                                          ^
                                                                                                                !
/home/saulius/temp/GRMustacheSPM/Packages/GRMustache.swift-1.0.0/Sources/TemplateRepository.swift:492:24: error: cannot convert value of type 'NSString' to type 'String' in coercion
            return try NSString(contentsOfURL: NSURL(string: templateID)!, encoding: encoding) as String
                       ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/home/saulius/temp/GRMustacheSPM/Packages/GRMustache.swift-1.0.0/Sources/TemplateRepository.swift:528:37: error: cannot convert value of type 'TemplateID' (aka 'String') to type 'NSString' in coercion
                let relativePath = (normalizedBaseTemplateID as NSString).stringByDeletingLastPathComponent.stringByReplacingOccurrencesOfString(bundle.resourcePath!, withString:"")
                                    ^~~~~~~~~~~~~~~~~~~~~~~~
/home/saulius/temp/GRMustacheSPM/Packages/GRMustache.swift-1.0.0/Sources/TemplateRepository.swift:536:24: error: cannot convert value of type 'NSString' to type 'String' in coercion
            return try NSString(contentsOfFile: templateID, encoding: encoding) as String
                       ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/home/saulius/temp/GRMustacheSPM/Packages/GRMustache.swift-1.0.0/Sources/Box.swift:145:25: error: declarations in extensions cannot override yet
    public override var mustacheBox: MustacheBox {
                        ^
/home/saulius/temp/GRMustacheSPM/Packages/GRMustache.swift-1.0.0/Sources/Box.swift:516:16: note: overridden declaration is here
    public var mustacheBox: MustacheBox {
               ^
/home/saulius/temp/GRMustacheSPM/Packages/GRMustache.swift-1.0.0/Sources/Box.swift:572:25: error: declarations in extensions cannot override yet
    public override var mustacheBox: MustacheBox {
                        ^
/home/saulius/temp/GRMustacheSPM/Packages/GRMustache.swift-1.0.0/Sources/Box.swift:516:16: note: overridden declaration is here
    public var mustacheBox: MustacheBox {
               ^
/home/saulius/temp/GRMustacheSPM/Packages/GRMustache.swift-1.0.0/Sources/Box.swift:612:25: error: declarations in extensions cannot override yet
    public override var mustacheBox: MustacheBox {
                        ^
/home/saulius/temp/GRMustacheSPM/Packages/GRMustache.swift-1.0.0/Sources/Box.swift:516:16: note: overridden declaration is here
    public var mustacheBox: MustacheBox {
               ^
/home/saulius/temp/GRMustacheSPM/Packages/GRMustache.swift-1.0.0/Sources/Box.swift:696:25: error: declarations in extensions cannot override yet
    public override var mustacheBox: MustacheBox {
                        ^
/home/saulius/temp/GRMustacheSPM/Packages/GRMustache.swift-1.0.0/Sources/Box.swift:516:16: note: overridden declaration is here
    public var mustacheBox: MustacheBox {
               ^
/home/saulius/temp/GRMustacheSPM/Packages/GRMustache.swift-1.0.0/Sources/Box.swift:1192:25: error: declarations in extensions cannot override yet
    public override var mustacheBox: MustacheBox {
                        ^
/home/saulius/temp/GRMustacheSPM/Packages/GRMustache.swift-1.0.0/Sources/Box.swift:516:16: note: overridden declaration is here
    public var mustacheBox: MustacheBox {
               ^
/home/saulius/temp/GRMustacheSPM/Packages/GRMustache.swift-1.0.0/Sources/Box.swift:1557:25: error: declarations in extensions cannot override yet
    public override var mustacheBox: MustacheBox {
                        ^
/home/saulius/temp/GRMustacheSPM/Packages/GRMustache.swift-1.0.0/Sources/Box.swift:516:16: note: overridden declaration is here
    public var mustacheBox: MustacheBox {
               ^
/home/saulius/temp/GRMustacheSPM/Packages/GRMustache.swift-1.0.0/Sources/Box.swift:517:38: error: use of undeclared type 'NSFastEnumeration'
        if let enumerable = self as? NSFastEnumeration {
                                     ^~~~~~~~~~~~~~~~~
/home/saulius/temp/GRMustacheSPM/Packages/GRMustache.swift-1.0.0/Sources/Box.swift:521:43: error: use of unresolved identifier 'NSFastGenerator'
            let array = GeneratorSequence(NSFastGenerator(enumerable)).map(BoxAnyObject)
                                          ^~~~~~~~~~~~~~~
/home/saulius/temp/GRMustacheSPM/Packages/GRMustache.swift-1.0.0/Sources/Box.swift:621:43: error: value of type 'NSNumber' has no member 'objCType'
        let objCType = String.fromCString(self.objCType)!
                                          ^~~~ ~~~~~~~~
/home/saulius/temp/GRMustacheSPM/Packages/GRMustache.swift-1.0.0/Sources/Box.swift:650:13: error: use of unresolved identifier 'NSLog'
            NSLog("GRMustache support for NSNumber of type \(objCType) is not implemented: value is discarded.")
            ^~~~~
/home/saulius/temp/GRMustacheSPM/Packages/GRMustache.swift-1.0.0/Sources/Box.swift:697:20: error: cannot convert value of type 'NSString' to type 'String' in coercion
        return Box(self as String)
                   ^~~~
/home/saulius/temp/GRMustacheSPM/Packages/GRMustache.swift-1.0.0/Sources/Box.swift:906:9: error: use of unresolved identifier 'NSLog'
        NSLog("Mustache.BoxAnyObject(): value `\(object)` is does not conform to MustacheBoxable: it is discarded.")
        ^~~~~
/home/saulius/temp/GRMustacheSPM/Packages/GRMustache.swift-1.0.0/Sources/Box.swift:1202:39: error: use of unresolved identifier 'NSFastGenerator'
        let array = GeneratorSequence(NSFastGenerator(self)).map(BoxAnyObject)
                                      ^~~~~~~~~~~~~~~
/home/saulius/temp/GRMustacheSPM/Packages/GRMustache.swift-1.0.0/Sources/Box.swift:1560:52: error: use of unresolved identifier 'NSFastGenerator'
                dictionaryValue: GeneratorSequence(NSFastGenerator(self)).reduce([String: MustacheBox](), combine: { (boxDictionary, key) in
                                                   ^~~~~~~~~~~~~~~
<unknown>:0: error: build had 1 command failures
error: exit(1): ["/home/saulius/Downloads/swift-DEVELOPMENT-SNAPSHOT-2016-01-25-a-ubuntu15.10/usr/bin/swift-build-tool", "-f", "/home/saulius/temp/GRMustacheSPM/.build/debug/Mustache.o/llbuild.yaml"]

No such module 'Mustache' + Xcode 9

I've migrated the Xcode to version 9 and my app starts give me the error: No such module 'Mustache'.

I already erased the Pods files and I installed the Pods again.

How can I solve it?

Static library support

When we compile this Framework as Static Library (Mach-O Type as staticlib), at run time it doesn't type cast String, Int, Number as MustacheBox. I made a hot fix by adding below code changes in Box.swift to catch other data types and return as MustacheBox.

Screenshot 2022-02-23 at 16 16 12

Swift 3.1 and warnings.

Two warnings appear, both: "String interpolation produces a debug description for an optional value; did you mean to make this explicit?"

Would you like me to produce a PR to resolve it? If I do, what version of swift should it work back to?

String literals as filter function arguments

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))
}

3.1.0 and 4.0.0 releases not available via Cocoapods

Firstly, thanks very much for the Swift 4 and Swift 5 compatible releases, it's great to no longer be depending on a fork of this project, particularly as we use it as a sub-dependency of our own framework.

The issue I'm seeing is that the 3.1.0 (Swift 4) and 4.0.0 (Swift 5) releases are not available as Cocoapods dependencies without pointing the Podfile at the relevant git tag explicitly. Whilst this is possible, although not desirable, for project Podfiles, it is not supported for sub-dependencies specified in Podspecs where only a version specification is supported.

The version in the GRMustache.swift.podspec file for both these releases is still set to 2.0.0 which would appear to be the root of the problem.

Is it possible to detect plain text URLs and phone links and make it an actionable link?

Currently I use Mustache to display HTML articles that I retrieve from our web service.

I wanted to know if there's a way to make Mustache detect URLs and/or telephones without the href tag.

Mustache does its thing when we receive something like this:

  <p><a href=\ "http://www.instagram.com\">www.instagram.com</a>&nbsp;</p>
  <p><a href=\ "tel://+19145006953\" target=\ "_blank\">+19145006953</a></p>

But doesn't detect it anything when it's just raw:

<p>www.instagram.com</p>
<p>+19145006953</p>

Any suggestions? Thanks!

IOS7 and Xcode 7.0.1

Hello, can I still to use GRMustache.swift with target ios7 Xcode 7? I try to copy manually the sources, but have some error (eg. attach).

With GRMustache.swift 0.9.4 anche Xcode 6 I had no problems

Thank's
schermata 2015-10-13 alle 23 24 59

Swift Package Manager

Hi,

I have this issue when I try to use GRMustache.swift with Swift Package Manager.
Zewo/Mustache which is a fork of GRMustache.swift seems to work with Swift Package Manager.

➜  MyPackage swift build
Cloning https://github.com/groue/GRMustache.swift
Using version 1.0.0 of package GRMustache.swift
Compiling Swift Module 'Mustache' (32 sources)
/Users/jeffreymacko/project/MyPackage/Packages/GRMustache.swift-1.0.0/Sources/Context.swift:218:18: error: type member may not be named 'Type', since it would conflict with the 'foo.Type' expression
    private enum Type {
                 ^
/Users/jeffreymacko/project/MyPackage/Packages/GRMustache.swift-1.0.0/Sources/Context.swift:218:18: note: backticks can escape this name if it is important to use
    private enum Type {
                 ^~~~
                 `Type`
/Users/jeffreymacko/project/MyPackage/Packages/GRMustache.swift-1.0.0/Sources/Context.swift:218:18: error: type member may not be named 'Type', since it would conflict with the 'foo.Type' expression
    private enum Type {
                 ^
/Users/jeffreymacko/project/MyPackage/Packages/GRMustache.swift-1.0.0/Sources/Context.swift:218:18: note: backticks can escape this name if it is important to use
    private enum Type {
                 ^~~~
                 `Type`
/Users/jeffreymacko/project/MyPackage/Packages/GRMustache.swift-1.0.0/Sources/Logger.swift:80:46: warning: '++' is deprecated: it will be removed in Swift 3
                        self.indentationLevel++
                                             ^~
                                              += 1
/Users/jeffreymacko/project/MyPackage/Packages/GRMustache.swift-1.0.0/Sources/Logger.swift:86:46: warning: '--' is deprecated: it will be removed in Swift 3
                        self.indentationLevel--
                                             ^~
                                              -= 1
/Users/jeffreymacko/project/MyPackage/Packages/GRMustache.swift-1.0.0/Sources/Logger.swift:80:46: warning: '++' is deprecated: it will be removed in Swift 3
                        self.indentationLevel++
                                             ^
/Users/jeffreymacko/project/MyPackage/Packages/GRMustache.swift-1.0.0/Sources/Logger.swift:86:46: warning: '--' is deprecated: it will be removed in Swift 3
                        self.indentationLevel--
                                             ^
/Users/jeffreymacko/project/MyPackage/Packages/GRMustache.swift-1.0.0/Sources/TemplateAST.swift:38:10: error: type member may not be named 'Type', since it would conflict with the 'foo.Type' expression
    enum Type {
         ^
/Users/jeffreymacko/project/MyPackage/Packages/GRMustache.swift-1.0.0/Sources/TemplateAST.swift:38:10: note: backticks can escape this name if it is important to use
    enum Type {
         ^~~~
         `Type`
/Users/jeffreymacko/project/MyPackage/Packages/GRMustache.swift-1.0.0/Sources/TemplateAST.swift:38:10: error: type member may not be named 'Type', since it would conflict with the 'foo.Type' expression
    enum Type {
         ^
/Users/jeffreymacko/project/MyPackage/Packages/GRMustache.swift-1.0.0/Sources/TemplateAST.swift:38:10: note: backticks can escape this name if it is important to use
    enum Type {
         ^~~~
         `Type`
/Users/jeffreymacko/project/MyPackage/Packages/GRMustache.swift-1.0.0/Sources/TemplateCompiler.swift:383:14: error: type member may not be named 'Type', since it would conflict with the 'foo.Type' expression
        enum Type {
             ^
/Users/jeffreymacko/project/MyPackage/Packages/GRMustache.swift-1.0.0/Sources/TemplateCompiler.swift:383:14: note: backticks can escape this name if it is important to use
        enum Type {
             ^~~~
             `Type`
/Users/jeffreymacko/project/MyPackage/Packages/GRMustache.swift-1.0.0/Sources/TemplateCompiler.swift:383:14: error: type member may not be named 'Type', since it would conflict with the 'foo.Type' expression
        enum Type {
             ^
/Users/jeffreymacko/project/MyPackage/Packages/GRMustache.swift-1.0.0/Sources/TemplateCompiler.swift:383:14: note: backticks can escape this name if it is important to use
        enum Type {
             ^~~~
             `Type`
/Users/jeffreymacko/project/MyPackage/Packages/GRMustache.swift-1.0.0/Sources/TemplateParser.swift:62:21: warning: '++' is deprecated: it will be removed in Swift 3
                    ++lineNumber
                    ^~
                                 += 1
/Users/jeffreymacko/project/MyPackage/Packages/GRMustache.swift-1.0.0/Sources/TemplateParser.swift:77:21: warning: '++' is deprecated: it will be removed in Swift 3
                    ++lineNumber
                    ^~
                                 += 1
/Users/jeffreymacko/project/MyPackage/Packages/GRMustache.swift-1.0.0/Sources/TemplateParser.swift:126:21: warning: '++' is deprecated: it will be removed in Swift 3
                    ++lineNumber
                    ^~
                                 += 1
/Users/jeffreymacko/project/MyPackage/Packages/GRMustache.swift-1.0.0/Sources/TemplateParser.swift:248:21: warning: '++' is deprecated: it will be removed in Swift 3
                    ++lineNumber
                    ^~
                                 += 1
/Users/jeffreymacko/project/MyPackage/Packages/GRMustache.swift-1.0.0/Sources/TemplateParser.swift:266:21: warning: '++' is deprecated: it will be removed in Swift 3
                    ++lineNumber
                    ^~
                                 += 1
/Users/jeffreymacko/project/MyPackage/Packages/GRMustache.swift-1.0.0/Sources/TemplateToken.swift:25:10: error: type member may not be named 'Type', since it would conflict with the 'foo.Type' expression
    enum Type {
         ^
/Users/jeffreymacko/project/MyPackage/Packages/GRMustache.swift-1.0.0/Sources/TemplateToken.swift:25:10: note: backticks can escape this name if it is important to use
    enum Type {
         ^~~~
         `Type`
/Users/jeffreymacko/project/MyPackage/Packages/GRMustache.swift-1.0.0/Sources/TemplateToken.swift:25:10: error: type member may not be named 'Type', since it would conflict with the 'foo.Type' expression
    enum Type {
         ^
/Users/jeffreymacko/project/MyPackage/Packages/GRMustache.swift-1.0.0/Sources/TemplateToken.swift:25:10: note: backticks can escape this name if it is important to use
    enum Type {
         ^~~~
         `Type`
<unknown>:0: error: build had 1 command failures
error: exit(1): ["/Library/Developer/Toolchains/swift-DEVELOPMENT-SNAPSHOT-2016-02-08-a.xctoolchain/usr/bin/swift-build-tool", "-f", "/Users/jeffreymacko/project/MyPackage/.build/debug/Mustache.o/llbuild.yaml"]

Using template inheritance throws malloc error

Hi,

I tried to make a simple project with 1 file inheriting from a layout file and it crashes with this error:

malloc: *** error for object 0xfffffffc: Invalid signature for pointer dequeued from free list
*** set a breakpoint in malloc_error_break to debug

It is worth to note that I then tried the very same 2 files with GRMustache and it works without error

For the record here is the code with GRMustache.swift:

          var error: NSError?
          if let template = Template(named: "link", bundle: webRootBundle, templateExtension: "html", encoding: NSUTF8StringEncoding, error: &error) {

            var person = Person(name: "Foo", age: 12)

            if let string = template.render(Box(person), error: &error) {
              NSLog("html: %@", string)
              // Crashes here!
            }
        }

Here is the one also from swift but using GRMustache bridged:

var error: NSError?
        var templateRepository = GRMustacheTemplateRepository(baseURL: webRootBundle.resourceURL!, templateExtension: "html", encoding: NSUTF8StringEncoding)

        if let template = templateRepository.templateNamed("link", error: &error) {

          var person = Person(name: "Foo", age: 12)

          if let string = template.renderObject(person, error: &error) {
            NSLog("html: %@", string)
            // Works
          }

The version I was using was "master" no tag.

Regards,
Thierry

Is it possible to throw an error when a tag inside a template is missing?

Is it possible to throw an error when a tag inside a template is missing? I could not find any issues mentioning this topic.

For example, using the example in the readme as a reference, instead of:

let template = try Template(string: "<{{#value}}Truthy{{/value}}>")
// "<>"
try template.render([:])                  // missing value

do:

let template = try Template(string: "<{{#value}}Truthy{{/value}}>")
// Throw error: missing value
try template.render([:])                  // missing value

Thanks!

Swift2 branch fails to compile with Xcode 7beta5

Hi Gwendal,

Here is the carthage log:

*** Building scheme "Shared MustacheiOS" in Mustache.xcworkspace
** BUILD FAILED **


The following build commands failed:
    CompileSwift normal x86_64
    CompileSwiftSources normal x86_64 com.apple.xcode.tools.swift.compiler
    CompileSwift normal i386
    CompileSwiftSources normal i386 com.apple.xcode.tools.swift.compiler
    Ld /Users/orion/Library/Developer/Xcode/DerivedData/Mustache-ajobsmoxgrhhescihiwvjygyqbpr/Build/Intermediates/Mustache.build/Release-iphonesimulator/MustacheiOS.build/Objects-normal/x86_64/Mustache normal x86_64
    Ld /Users/orion/Library/Developer/Xcode/DerivedData/Mustache-ajobsmoxgrhhescihiwvjygyqbpr/Build/Intermediates/Mustache.build/Release-iphonesimulator/MustacheiOS.build/Objects-normal/i386/Mustache normal i386
    CreateUniversalBinary /Users/orion/Library/Developer/Xcode/DerivedData/Mustache-ajobsmoxgrhhescihiwvjygyqbpr/Build/Products/Release-iphonesimulator/Mustache.framework/Mustache normal i386\ x86_64
    GenerateDSYMFile /Users/orion/Library/Developer/Xcode/DerivedData/Mustache-ajobsmoxgrhhescihiwvjygyqbpr/Build/Products/Release-iphonesimulator/Mustache.framework.dSYM /Users/orion/Library/Developer/Xcode/DerivedData/Mustache-ajobsmoxgrhhescihiwvjygyqbpr/Build/Products/Release-iphonesimulator/Mustache.framework/Mustache
(8 failures)
/Users/orion/LocalDevels/Capture/Carthage/Checkouts/GRMustache.swift/Mustache/Goodies/EachFilter.swift:52:45: error: ambiguous use of 'Box'
/Users/orion/LocalDevels/Capture/Carthage/Checkouts/GRMustache.swift/Mustache/Goodies/EachFilter.swift:53:44: error: ambiguous use of 'Box'
/Users/orion/LocalDevels/Capture/Carthage/Checkouts/GRMustache.swift/Mustache/Goodies/EachFilter.swift:54:38: error: ambiguous use of 'Box'
/Users/orion/LocalDevels/Capture/Carthage/Checkouts/GRMustache.swift/Mustache/Goodies/EachFilter.swift:55:37: error: ambiguous use of 'Box'
/Users/orion/LocalDevels/Capture/Carthage/Checkouts/GRMustache.swift/Mustache/Goodies/EachFilter.swift:84:45: error: ambiguous use of 'Box'
/Users/orion/LocalDevels/Capture/Carthage/Checkouts/GRMustache.swift/Mustache/Goodies/EachFilter.swift:85:44: error: ambiguous use of 'Box'
/Users/orion/LocalDevels/Capture/Carthage/Checkouts/GRMustache.swift/Mustache/Goodies/EachFilter.swift:86:38: error: ambiguous use of 'Box'
/Users/orion/LocalDevels/Capture/Carthage/Checkouts/GRMustache.swift/Mustache/Goodies/EachFilter.swift:87:37: error: ambiguous use of 'Box'
/Users/orion/LocalDevels/Capture/Carthage/Checkouts/GRMustache.swift/Mustache/Rendering/Box.swift:620:20: error: ambiguous use of 'Box'
/Users/orion/LocalDevels/Capture/Carthage/Checkouts/GRMustache.swift/Mustache/Rendering/Box.swift:622:20: error: ambiguous use of 'Box'
/Users/orion/LocalDevels/Capture/Carthage/Checkouts/GRMustache.swift/Mustache/Rendering/Box.swift:624:20: error: ambiguous use of 'Box'
/Users/orion/LocalDevels/Capture/Carthage/Checkouts/GRMustache.swift/Mustache/Rendering/Box.swift:626:20: error: ambiguous use of 'Box'
/Users/orion/LocalDevels/Capture/Carthage/Checkouts/GRMustache.swift/Mustache/Rendering/Box.swift:628:20: error: ambiguous use of 'Box'
/Users/orion/LocalDevels/Capture/Carthage/Checkouts/GRMustache.swift/Mustache/Rendering/Box.swift:630:20: error: ambiguous use of 'Box'
/Users/orion/LocalDevels/Capture/Carthage/Checkouts/GRMustache.swift/Mustache/Rendering/Box.swift:632:20: error: ambiguous use of 'Box'
/Users/orion/LocalDevels/Capture/Carthage/Checkouts/GRMustache.swift/Mustache/Rendering/Box.swift:634:20: error: ambiguous use of 'Box'
/Users/orion/LocalDevels/Capture/Carthage/Checkouts/GRMustache.swift/Mustache/Rendering/Box.swift:636:20: error: ambiguous use of 'Box'
/Users/orion/LocalDevels/Capture/Carthage/Checkouts/GRMustache.swift/Mustache/Rendering/Box.swift:638:20: error: ambiguous use of 'Box'
/Users/orion/LocalDevels/Capture/Carthage/Checkouts/GRMustache.swift/Mustache/Rendering/Box.swift:640:20: error: ambiguous use of 'Box'
/Users/orion/LocalDevels/Capture/Carthage/Checkouts/GRMustache.swift/Mustache/Rendering/Box.swift:1248:56: error: ambiguous use of 'Box'
/Users/orion/LocalDevels/Capture/Carthage/Checkouts/GRMustache.swift/Mustache/Rendering/Box.swift:1299:62: error: ambiguous use of 'Box'
/Users/orion/LocalDevels/Capture/Carthage/Checkouts/GRMustache.swift/Mustache/Rendering/Box.swift:1350:62: error: ambiguous use of 'Box'
Assertion failed: (!failed && "Call arguments did not match up?"), function coerceCallArguments, file /Library/Caches/com.apple.xbs/Sources/swiftlang/swiftlang-700.0.52.2/src/swift/lib/Sema/CSApply.cpp, line 4085.
<unknown>:0: error: unable to execute command: Abort trap: 6
<unknown>:0: error: swift frontend command failed due to signal (use -v to see invocation)
/Users/orion/LocalDevels/Capture/Carthage/Checkouts/GRMustache.swift/Mustache/Goodies/EachFilter.swift:52:45: error: ambiguous use of 'Box'
/Users/orion/LocalDevels/Capture/Carthage/Checkouts/GRMustache.swift/Mustache/Goodies/EachFilter.swift:53:44: error: ambiguous use of 'Box'
/Users/orion/LocalDevels/Capture/Carthage/Checkouts/GRMustache.swift/Mustache/Goodies/EachFilter.swift:54:38: error: ambiguous use of 'Box'
/Users/orion/LocalDevels/Capture/Carthage/Checkouts/GRMustache.swift/Mustache/Goodies/EachFilter.swift:55:37: error: ambiguous use of 'Box'
/Users/orion/LocalDevels/Capture/Carthage/Checkouts/GRMustache.swift/Mustache/Goodies/EachFilter.swift:84:45: error: ambiguous use of 'Box'
/Users/orion/LocalDevels/Capture/Carthage/Checkouts/GRMustache.swift/Mustache/Goodies/EachFilter.swift:85:44: error: ambiguous use of 'Box'
/Users/orion/LocalDevels/Capture/Carthage/Checkouts/GRMustache.swift/Mustache/Goodies/EachFilter.swift:86:38: error: ambiguous use of 'Box'
/Users/orion/LocalDevels/Capture/Carthage/Checkouts/GRMustache.swift/Mustache/Goodies/EachFilter.swift:87:37: error: ambiguous use of 'Box'
/Users/orion/LocalDevels/Capture/Carthage/Checkouts/GRMustache.swift/Mustache/Rendering/Box.swift:620:20: error: ambiguous use of 'Box'
/Users/orion/LocalDevels/Capture/Carthage/Checkouts/GRMustache.swift/Mustache/Rendering/Box.swift:622:20: error: ambiguous use of 'Box'
/Users/orion/LocalDevels/Capture/Carthage/Checkouts/GRMustache.swift/Mustache/Rendering/Box.swift:624:20: error: ambiguous use of 'Box'
/Users/orion/LocalDevels/Capture/Carthage/Checkouts/GRMustache.swift/Mustache/Rendering/Box.swift:626:20: error: ambiguous use of 'Box'
/Users/orion/LocalDevels/Capture/Carthage/Checkouts/GRMustache.swift/Mustache/Rendering/Box.swift:628:20: error: ambiguous use of 'Box'
/Users/orion/LocalDevels/Capture/Carthage/Checkouts/GRMustache.swift/Mustache/Rendering/Box.swift:630:20: error: ambiguous use of 'Box'
/Users/orion/LocalDevels/Capture/Carthage/Checkouts/GRMustache.swift/Mustache/Rendering/Box.swift:632:20: error: ambiguous use of 'Box'
/Users/orion/LocalDevels/Capture/Carthage/Checkouts/GRMustache.swift/Mustache/Rendering/Box.swift:634:20: error: ambiguous use of 'Box'
/Users/orion/LocalDevels/Capture/Carthage/Checkouts/GRMustache.swift/Mustache/Rendering/Box.swift:636:20: error: ambiguous use of 'Box'
/Users/orion/LocalDevels/Capture/Carthage/Checkouts/GRMustache.swift/Mustache/Rendering/Box.swift:638:20: error: ambiguous use of 'Box'
/Users/orion/LocalDevels/Capture/Carthage/Checkouts/GRMustache.swift/Mustache/Rendering/Box.swift:640:20: error: ambiguous use of 'Box'
/Users/orion/LocalDevels/Capture/Carthage/Checkouts/GRMustache.swift/Mustache/Rendering/Box.swift:1248:56: error: ambiguous use of 'Box'
/Users/orion/LocalDevels/Capture/Carthage/Checkouts/GRMustache.swift/Mustache/Rendering/Box.swift:1299:62: error: ambiguous use of 'Box'
/Users/orion/LocalDevels/Capture/Carthage/Checkouts/GRMustache.swift/Mustache/Rendering/Box.swift:1350:62: error: ambiguous use of 'Box'
Assertion failed: (!failed && "Call arguments did not match up?"), function coerceCallArguments, file /Library/Caches/com.apple.xbs/Sources/swiftlang/swiftlang-700.0.52.2/src/swift/lib/Sema/CSApply.cpp, line 4085.
<unknown>:0: error: unable to execute command: Abort trap: 6
<unknown>:0: error: swift frontend command failed due to signal (use -v to see invocation)
clang: error: linker command failed with exit code 1 (use -v to see invocation)
clang: error: linker command failed with exit code 1 (use -v to see invocation)
fatal error: /Applications/Xcode-beta.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/lipo: can't open input file: /Users/orion/Library/Developer/Xcode/DerivedData/Mustache-ajobsmoxgrhhescihiwvjygyqbpr/Build/Intermediates/Mustache.build/Release-iphonesimulator/MustacheiOS.build/Objects-normal/i386/Mustache (No such file or directory)
error: cannot parse the debug map for "/Users/orion/Library/Developer/Xcode/DerivedData/Mustache-ajobsmoxgrhhescihiwvjygyqbpr/Build/Products/Release-iphonesimulator/Mustache.framework/Mustache": No such file or directory
A shell task failed with exit code 65

Thanks and regards,
Thierry

Incorrect use of URL causes failure on files containing spaces.

In several places URL(string:) is called when constructing file paths. I believe these should instead be URL(fileURLWithPath:) calls. The existing code crashes when the filename string contains spaces (or other characters that are illegal in a URL, but not a filename)

let fails = URL(string: "my file.txt") // nil
let works = URL(fileURLWithPath: "my file.txt") // my%20file.txt -- file:///

If anyone agrees I'm more than happy to put up a PR with the changes.

Swift 3 Support

Hello and thanks for a great library!

Are there any plans to upgrade GRMustache.swift for swift 3 syntax?

Memory leaks

Hey @groue, it's me again ;) Have you ever noticed any memory leaks in the framework? I have some memory leaks in my app, and when I use Instruments to track down the leak, it seems to point to GRMustache as the source:

screen shot 2015-10-21 at 16 24 00

I spent a bunch of time trying to figure out what could be the root cause, and if maybe I was doing something wrong that would cause this issue, but I can't figure it out. Do you have any ideas maybe?

Is it Possible to passe text from template to a function?

Hello, thank you for creating and maintaining GRMustache. I apologize for any inconvenience, but I have a question.

Is it possible, using GRMustache, to extract text from a template to pass it to a function? I've gone through the documentation, but I don't have the impression that it's feasible to provide a function with a parameter coming from the template rather than from the render method.

I've prepared a simplified example of what I'm trying to achieve. Do you think this would be possible?

func canThisBePossible() throws {
  let extractDataFromJSONFile = VariadicFilter { (boxes: [MustacheBox]) in
    guard let fromFile = boxes[0].value as? String
    else { throw "Failed to extract fromFile from boxes[0] = \(boxes[0].value)" }

    guard let jsonPath = boxes[1].value as? String
    else { throw "Failed to extract jsonPath from boxes[1] = \(boxes[1].value)" }

    guard let transformer = boxes[2].value as? String
    else { throw "Failed to extract transformer from boxes[2] = \(boxes[2].value)" }

    guard FileManager.default.fileExists(atPath: fromFile)
    else { throw "File \(fromFile) don't exist" }

    guard FileManager.default.isReadableFile(atPath: fromFile)
    else { throw "File \(fromFile) is not readable" }

    guard let dataTransformer = mapper[transformer]
    else { throw "Data transformer not found for key \(transformer)" }

    guard let loadFile = try? String(contentsOfFile: fromFile)
    else { throw "Failed to transform \(fromFile) data to a String" }

    // JSONPath data extraction using Sextant https://www.swift-linux.com/sextant/
    guard let extractedData = loadFile.query(paths: jsonPath)?.first as? String
    else { throw "Failed to find data from \(fromFile) at path \(jsonPath)" }

    return dataTransformer(extractedData)
  }

  let template = try Template(string: #"var backgroundColor: Color = {{ getToken("component/badge/ios.json", "$.vp.component.badge.primary.color.background.default.value", "Color") }}"#)
  template.register(extractDataFromJSONFile, forKey: "getToken")

  // var backgroundColor: Color = DesignTokens.Semantic.Color.containerHighlight"
  let rendering = try template.render()
}

func colorTransformer(_ input: String) -> String {
  "DesignTokens.Semantic.Color.containerHighlight"
}

let mapper: [String: (String) -> String] = [
  "Color": colorTransformer
]

Just not working

So, I thought it might have been my ParseObject, so I tried a simple dict. If finds the {{vars}} but replaces them with nothing.

Xcode 14.3, SPM install

   do {
            let headerTemplate = try Template(named: templateFileName, bundle: Bundle.main, templateExtension: "html")
            //on Master 2.0 this returns a GRMustacheTemplate, here we get back HTML
            let dict = ["title": "INV0018", "no": "18", "year": "2023", "month": "5" ]
            html = try! headerTemplate.render(dict)
        }
        catch  {
            throw A4PrintPagerRendererError.TemplateNotFound
        }

What can I do?

Deprecated 'class' keyword

We are trying to get rid of all warnings in our project and noticed one in Mustache:

xxx/Tag.swift:56:22: Using 'class' keyword to define a class-constrained protocol is deprecated; use 'AnyObject' instead

Hopefully you will fix this in the future. Thanks.

Indexed arrays don't appear to work

Hi... I'm using the Swift1.2 branch, and am having trouble referencing array values via a direct index. eg. {{list.0}}. The following just outputs an empty string:

let input = [
    "list" : [ "aaa", "bbb" ]
]
let template = Template(string: "{{list.0}}")!
println(template.render(Box(input))!)

Interestingly, however, if I use {{list}}, then it outputs aaabbb. Just curious if fetching data from arrays using an index is supported?

Of course, it could also be that (as a Mustache newbie) I am using the wrong notation.

Thanks.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.