Skip to content

A minimalistic Swift implementation of the Jinja templating engine, specifically designed for parsing and rendering ML chat templates.

License

Notifications You must be signed in to change notification settings

huggingface/swift-jinja

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

68 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Jinja

A Swift implementation of the Jinja2 template engine.

Jinja templates are widely used for generating HTML, configuration files, code generation, and text processing. This implementation is focused primarily on the features needed to generate LLM chat templates.

Requirements

  • Swift 6.0+ / Xcode 16+

Installation

Swift Package Manager

Add the following to your Package.swift file:

dependencies: [
    .package(url: "https://github.com/huggingface/swift-jinja.git", from: "2.0.0")
]

Then add the dependency to your target:

.target(
    name: "YourTarget",
    dependencies: [
        .product(name: "Jinja", package: "swift-jinja")
    ]
)

Features

This package implements a subset of the functionality of the official Python implementation.

Supported Features âś…

  • Variables: {{ variable }}, {{ object.attribute }}, {{ dict['key'] }}
  • Comments: {# comment #}
  • Statements: {% statement %}
  • Value Types: Boolean (true, false), integers (42), floats (3.14), strings ("hello"), arrays ([1, 2, 3]), objects ({"key": "value"}), and null (null)
  • Arithmetic Operators: +, -, *, /, // (floor division), ** (exponentiation), %
  • String Concatenation Operators: ~ and automatic concatenation of adjacent string literals
  • Comparison Operators: ==, !=, <, <=, >, >=
  • Logical Operators: and, or, not
  • Membership Operator: in
  • Attribute Access: . and []
  • Conditionals: {% if %}, {% elif %}, {% else %}, {% endif %}
  • Loops: {% for item in list %}...{% endfor %}
  • Loop Variables: loop.index, loop.index0, loop.first, loop.last, loop.length
  • Loop Filtering: {% for item in list if condition %}
  • Loop Controls: {% break %}, {% continue %}
  • Variable Assignment: {% set variable = value %}
  • Macros: {% macro name() %}...{% endmacro %}
  • Macro Calls: {% call macro_name() %}
  • Filter Statements: {{ name | upper }}
  • Filter Blocks: {% filter upper %}...{% endfilter %}
  • Tests: is operator for type/value checks (e.g. {% if value is number %})
  • Global Functions: range(), lipsum(), dict(), cycler(), joiner(), namespace(), strftime_now()
  • Exception Handling: raise_exception() (throws Exception error)
Supported Filters
  • abs()
  • attr()
  • batch()
  • capitalize()
  • center()
  • default()
  • dictsort()
  • escape()
  • filesizeformat()
  • first()
  • float()
  • forceescape()
  • format()
  • groupby()
  • indent()
  • int()
  • items()
  • join()
  • last()
  • length()
  • list()
  • lower()
  • map()
  • max()
  • min()
  • pprint()
  • random()
  • reject()
  • rejectattr()
  • replace()
  • reverse()
  • round()
  • safe()
  • select()
  • selectattr()
  • slice()
  • sort()
  • string()
  • striptags()
  • sum()
  • title()
  • tojson()
  • trim()
  • truncate()
  • unique()
  • upper()
  • urlencode()
  • urlize()
  • wordcount()
  • wordwrap()
  • xmlattr()
Supported tests
  • boolean()
  • callable()
  • defined()
  • divisibleby()
  • eq()
  • escaped()
  • even()
  • false()
  • filter()
  • float()
  • ge()
  • gt()
  • in()
  • integer()
  • iterable()
  • le()
  • lower()
  • lt()
  • mapping()
  • ne()
  • none()
  • number()
  • odd()
  • sameas()
  • sequence()
  • string()
  • test()
  • true()
  • undefined()
  • upper()

Not Supported Features ❌

  • Template Inheritance: {% extends %} and {% block %}
  • Template Includes: {% include %}
  • Template Imports: {% import %}, {% from ... import %}
  • Block Inheritance: super(), block scoping, required blocks
  • With Statement: {% with %} for variable scoping
  • Raw Blocks: {% raw %}...{% endraw %}
  • Internationalization: {% trans %}, {% pluralize %}, i18n extension
  • Debug Statement: {% debug %}
  • Do Statement: {% do expression %} (expression without output)
  • Autoescape: {% autoescape %} blocks and automatic HTML escaping
  • Line Statements: Alternative syntax with prefix characters (# for item in seq instead of {% for item in seq %})

Usage

Basic Template Rendering

import Jinja

// Create and render a simple template
let template = try Template("Hello, {{ name }}!")
let result = try template.render(["name": "World"])
print(result) // "Hello, World!"

Template with Context Variables

// Template with multiple variables
let template = try Template("""
    Welcome, {{ user.name }}!
    You have {{ messages | length }} new messages.
    """)

let context: [String: Value] = [
    "user": ["name": "Alice"],
    "messages": [
        "Hello",
        "How are you?",
        "See you later"
    ]
]

let result = try template.render(context)
// "Welcome, Alice!\nYou have 3 new messages."

Important

Migrating to Jinja v2.0: Most code using Jinja v1 should work with v2 with minimal changes. The biggest breaking change is that the context parameter for rendering templates has changed from [String: Any] to [String: Value].

Thanks to Value being expressible by literals, existing code may work as-is. You can also try the Value(any:) constructor to automatically convert complex values:

// Create template and context
let template = try Template("Hello {{ user.name }}!")

var context: [String: Value] = [
    // Use literals:
    "user": ["name": "Alice"],
]

// ...or convert from Any value:
let settings: [String: Any] = ["theme": "dark", "notifications": true]
context["settings"] = try Value(any: settings)

let result = try template.render(context)
// "Hello Alice!"

Control Flow

// Conditional rendering
let template = try Template("""
    {% for item in items %}
        {% if item.active %}
            * {{ item.name }} ({{ item.price }})
        {% endif %}
    {% endfor %}
    """)

let context: [String: Value] = [
    "items": [
        [
            "name": "Coffee",
            "price": 4.50,
            "active": true
        ],
        [
            "name": "Tea",
            "price": 3.25,
            "active": false
        ]
    ]
]

let result = try template.render(context)
// "    * Coffee (4.5)\n"

Built-in Filters

Templates support Jinja built-in filters for data transformation and manipulation.

// String manipulation filters
let template = try Template("""
    {{ name | upper }}
    {{ description | truncate(50) }}
    {{ tags | join(", ") }}
    """)

let context: [String: Value] = [
    "name": "swift package",
    "description": "A powerful template engine for Swift applications",
    "tags": ["swift", "templates", "web"]
]

let result = try template.render(context)

Tests

Jinja provides built-in tests for conditional logic and type checking. Tests are used with the is operator to evaluate conditions in templates. These tests help you make decisions based on the type, value, or properties of variables.

// Type and value checking with tests
let template = try Template("""
    {% if user is defined %}
        Welcome, {{ user.name }}!
    {% else %}
        Please log in.
    {% endif %}

    {% if messages is iterable and messages | length > 0 %}
        You have {{ messages | length }} messages.
    {% endif %}

    {% if age is number and age >= 18 %}
        You are an adult.
    {% elif age is number and age < 18 %}
        You are a minor.
    {% endif %}

    {% if status is none %}
        Status not set.
    {% elif status is true %}
        Active
    {% elif status is false %}
        Inactive
    {% endif %}
    """)

let context: [String: Value] = [
    "user": ["name": "Alice"],
    "messages": ["Hello", "How are you?"],
    "age": 25,
    "status": true
]

let result = try template.render(context)
// "Welcome, Alice!\nYou have 2 messages.\nYou are an adult.\nActive"

Template Options

// Configure template behavior
let options = Template.Options(
    lstripBlocks: true,  // Strip leading whitespace from blocks
    trimBlocks: true     // Remove trailing newlines from blocks
)

let template = try Template("""
    {% for item in items %}
        {{ item }}
    {% endfor %}
    """, with: options)

Value Types

The Value enum represents all possible template values:

// Creating values directly
let context: [String: Value] = [
    "text": "Hello",
    "number": 42,
    "decimal": 3.14,
    "flag": true,
    "items": ["a", "b"],
    "user": ["name": "John", "age": 30],
    "missing": .null
]

// ...or from Swift types
let swiftValue: Any? = ["name": "John", "items": [1, 2, 3]]
let jinjaValue = try Value(any: swiftValue)

Examples

HTML Generation

import Jinja

// Generate HTML from template
let htmlTemplate = try Template("""
    <!DOCTYPE html>
    <html>
    <head>
        <title>{{ page.title }}</title>
    </head>
    <body>
        <h1>{{ page.heading }}</h1>
        <ul>
        {% for item in page.items %}
            <li><a href="{{ item.url }}">{{ item.title }}</a></li>
        {% endfor %}
        </ul>
    </body>
    </html>
    """)

let context: [String: Value] = [
    "page": [
        "title": "My Website",
        "heading": "Welcome",
        "items": .array([
            ["title": "Home", "url": "/"),
            ["title": "About", "url": "/about"],
            ["title": "Contact", "url": "/contact"]
        ]
    ]
]

let html = try htmlTemplate.render(context)

Configuration File Generation

// Generate configuration files
let configTemplate = try Template("""
    # {{ app.name }} Configuration

    [server]
    host = "{{ server.host }}"
    port = {{ server.port }}
    debug = {{ server.debug | lower }}

    [database]
    {% for db in databases %}
    [database.{{ db.name }}]
    url = "{{ db.url }}"
    pool_size = {{ db.pool_size }}
    {% endfor %}
    """)

let context: [String: Value] = [
    "app": ["name": "MyApp"],
    "server": [
        "host": "localhost",
        "port": 8080,
        "debug": true
    ],
    "databases": [
        [
            "name": "primary",
            "url": "postgresql://localhost/myapp",
            "pool_size": 10
        ]
    ]
]

let config = try configTemplate.render(context)

Chat Message Formatting

// Format chat messages (useful for AI/LLM applications)
let chatTemplate = try Template("""
    {% for message in messages %}
        {% if message.role == "system" %}
            System: {{ message.content }}
        {% elif message.role == "user" %}
            User: {{ message.content }}
        {% elif message.role == "assistant" %}
            Assistant: {{ message.content }}
        {% endif %}
    {% endfor %}
    """, with: Template.Options(lstripBlocks: true, trimBlocks: true))

let messages: [String: Value] = [
    "messages": [
        [
            "role": "system",
            "content": "You are a helpful assistant."
        ],
        [
            "role": "user",
            "content": "What's the weather like?"
        ],
        [
            "role": "assistant",
            "content": "I'd be happy to help with weather information!"
        ]
    ]
]

let formatted = try chatTemplate.render(messages)

Contributing

This is a community project and we welcome contributions. Please check out Issues tagged with good first issue if you are looking for a place to start!

Please ensure your code passes the build and test suite before submitting a pull request. You can run the tests with swift test.

License

Apache 2.

About

A minimalistic Swift implementation of the Jinja templating engine, specifically designed for parsing and rendering ML chat templates.

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Contributors 7

Languages