• Stars
    star
    482
  • Rank 87,843 (Top 2 %)
  • Language
    Ruby
  • License
    MIT License
  • Created over 14 years ago
  • Updated 9 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Template compilation framework in Ruby

Temple

test Code Climate Gem Version Yard Docs

Temple is an abstraction and a framework for compiling templates to pure Ruby. It's all about making it easier to experiment, implement and optimize template languages. If you're interested in implementing your own template language, or anything else related to the internals of a template engine: You've come to the right place.

Have a look around, and if you're still wondering: Ask on the mailing list and we'll try to do our best. In fact, it doesn't have to be related to Temple at all. As long as it has something to do with template languages, we're interested: http://groups.google.com/group/guardians-of-the-temple.

Links

Overview

Temple is built on a theory that every template consists of three elements:

  • Static text
  • Dynamic text (pieces of Ruby which are evaluated and sent to the client)
  • Codes (pieces of Ruby which are evaluated and not sent to the client, but might change the control flow).

The goal of a template engine is to take the template and eventually compile it into the core abstraction:

 [:multi,
   [:static, "Hello "],
   [:dynamic, "@user.name"],
   [:static, "!\n"],
   [:code, "if @user.birthday == Date.today"],
   [:static, "Happy birthday!"],
   [:code, "end"]]

Then you can apply some optimizations, feed it to Temple and it generates fast Ruby code for you:

 _buf = []
 _buf << ("Hello #{@user.name}!\n")
 if @user.birthday == Date.today
   _buf << "Happy birthday!"
 end
 _buf.join

S-expression

In Temple, an Sexp is simply an array (or a subclass) where the first element is the type and the rest are the arguments. The type must be a symbol and it's recommended to only use strings, symbols, arrays and numbers as arguments.

Temple uses Sexps to represent templates because it's a simple and straightforward data structure, which can easily be written by hand and manipulated by computers.

Some examples:

 [:static, "Hello World!"]

 [:multi,
   [:static, "Hello "],
   [:dynamic, "@world"]]

 [:html, :tag, "em", [:html, :attrs], [:static, "Hey hey"]]

NOTE: SexpProcessor, a library written by Ryan Davis, includes a Sexp class. While you can use this class (since it's a subclass of Array), it's not what Temple mean by "Sexp".

Abstractions

The idea behind Temple is that abstractions are good, and it's better to have too many than too few. While you should always end up with the core abstraction, you shouldn't stress about it. Take one step at a time, and only do one thing at every step.

So what's an abstraction? An abstraction is when you introduce a new types:

 # Instead of:
 [:static, "<strong>Use the force</strong>"]

 # You use:
 [:html, :tag, "strong", [:html, :attrs], [:static, "Use the force"]]

Why are abstractions so important?

First of all, it means that several template engines can share code. Instead of having two engines which goes all the way to generating HTML, you have two smaller engines which only compiles to the HTML abstraction together with something that compiles the HTML abstraction to the core abstraction.

Often you also introduce abstractions because there's more than one way to do it. There's not a single way to generate HTML. Should it be indented? If so, with tabs or spaces? Or should it remove as much whitespace as possible? Single or double quotes in attributes? Escape all weird UTF-8 characters?

With an abstraction you can easily introduce a completely new HTML compiler, and whatever is below doesn't have to care about it at all. They just continue to use the HTML abstraction. Maybe you even want to write your compiler in another language? Sexps are easily serialized and if you don't mind working across processes, it's not a problem at all.

All abstractions used by Temple are documented in EXPRESSIONS.md.

Compilers

A compiler is simply an object which responds a method called #call which takes one argument and returns a value. It's illegal for a compiler to mutate the argument, and it should be possible to use the same instance several times (although not by several threads at the same time).

While a compiler can be any object, you very often want to structure it as a class. Temple then assumes the initializer takes an optional option hash:

 class MyCompiler
   def initialize(options = {})
     @options = options
   end

   def call(exp)
     # do stuff
   end
 end

Parsers

In Temple, a parser is also a compiler, because a compiler is just something that takes some input and produces some output. A parser is then something that takes a string and returns an Sexp.

It's important to remember that the parser should be dumb. No optimization, no guesses. It should produce an Sexp that is as close to the source as possible. You should invent your own abstraction. Maybe you even want to separate the parsers into several parts and introduce several abstractions on the way?

Filters

A filter is a compiler which take an Sexp and returns an Sexp. It might turn convert it one step closer to the core-abstraction, it might create a new abstraction, or it might just optimize in the current abstraction. Ultimately, it's still just a compiler which takes an Sexp and returns an Sexp.

For instance, Temple ships with {Temple::Filters::DynamicInliner} and {Temple::Filters::StaticMerger} which are general optimization filters which works on the core abstraction.

An HTML compiler would be a filter, since it would take an Sexp in the HTML abstraction and compile it down to the core abstraction.

Generators

A generator is a compiler which takes an Sexp and returns a string which is valid Ruby code.

Most of the time you would just use {Temple::Generators::ArrayBuffer} or any of the other generators in {Temple::Generators}, but nothing stops you from writing your own.

In fact, one of the great things about Temple is that if you write a new generator which turns out to be a lot faster then the others, it's going to make every single engine based on Temple faster! So if you have any ideas, please share them - it's highly appreciated.

Engines

When you have a chain of a parsers, some filters and a generator you can finally create your engine. Temple provides {Temple::Engine} which makes this very easy:

 class MyEngine < Temple::Engine
   # First run MyParser
   use MyParser

   # Then a custom filter
   use MyFilter

   # Then some general optimizations filters
   filter :MultiFlattener
   filter :StaticMerger
   filter :DynamicInliner

   # Finally the generator
   generator :ArrayBuffer
 end

 engine = MyEngine.new(strict: "For MyParser")
 engine.call(something)

And then?

You've ran the template through the parser, some filters and in the end a generator. What happens next?

Temple provides helpers to create template classes for Tilt and Rails.

 require 'tilt'

 # Create template class MyTemplate and register your file extension
 MyTemplate = Temple::Templates::Tilt(MyEngine, register_as: 'ext')

 Tilt.new('example.ext').render     # => Render a file
 MyTemplate.new { "String" }.render # => Render a string

Installation

You need at least Ruby 1.9.3 to work with Temple. Temple is published as a Ruby Gem which can be installed as following:

 $ gem install temple

Engines using Temple

Acknowledgements

Thanks to _why for creating an excellent template engine (Markaby) which is quite slow. That's how I started experimenting with template engines in the first place.

I also owe Ryan Davis a lot for his excellent projects ParserTree, RubyParser, Ruby2Ruby and SexpProcessor. Temple is heavily inspired by how these tools work.

More Repositories

1

perloku

Perl on Heroku
Shell
154
star
2

timeless

A mixture of a blog, wiki and CMS, inspired by Christoffer Sawicki's Termos and James Adam's Vanilla.rb
Ruby
116
star
3

parkaby

ParseTree meets Markaby
Ruby
104
star
4

glush

A parser toolkit
Ruby
95
star
5

duktape.rb

Ruby bindings to the Duktape JavaScript interpreter
C
76
star
6

gash

Git + Hash
Ruby
58
star
7

github-js

GitHub.js
JavaScript
52
star
8

tubby

HTML templates as Ruby
Ruby
46
star
9

minz

Minimal string compression
Zig
37
star
10

minitest-line

Ruby
35
star
11

rubyscript

A Ruby VM implemented in JavaScript
Ruby
33
star
12

bankid-api

API documentation for the Norwegian BankID protocol
32
star
13

zini

Minimal perfect hash function for Zig
Zig
28
star
14

camping

the 4k pocket full-of-gags web microframework
Ruby
27
star
15

parg

Lightweight argument parser for Zig
Zig
25
star
16

cab

Lightweight code reloader for Ruby/Rack
Ruby
24
star
17

rcpu

DCPU assembler and emulator in Ruby
Ruby
22
star
18

ish

Sketches for Zig
Zig
20
star
19

gemify

The lightweight gemspec editor.
Ruby
20
star
20

recursive

The Recursive Benchmark
JavaScript
20
star
21

nokogirl

Nokogirl โ€” because developers can't spell
Ruby
16
star
22

kramer

Generalized parser combinators in Ruby
Ruby
15
star
23

rgb-tree

RGB trees, a generalization of red-black trees. Implemented in Zig.
Zig
15
star
24

grancher

Easily copy folders and files to other Git branches
Ruby
15
star
25

quickgem

Speed up load time
Ruby
13
star
26

devlicious

Chrome DevTools for Mojolicious
Perl
13
star
27

quickedit

Quickedit is a micro-CMS which you can easily embed in any Rails, Sinatra or Rack apps
13
star
28

handlebars-jit

A JIT for Mustache
Ruby
13
star
29

imba-rails

Ruby
12
star
30

ippon

The gentle way
Ruby
11
star
31

woop-2018

a programming language and environment that is almost entirely written in itself
10
star
32

try-camping

RSoC: Interactive tutorials for learning web development in Ruby
10
star
33

ImbaKit

C
8
star
34

mockie

The beginning of a proper mockup-driven template language
JavaScript
8
star
35

hox

Web toolkit in Nim
Nim
7
star
36

filtering_camping

Adding before/after-filters to Camping
Ruby
7
star
37

quickload.js

JavaScript
7
star
38

rumble

Ruby
6
star
39

toml.nim

TOML parser for Nim
Nim
6
star
40

imba-loader

JavaScript
6
star
41

imba-styles

JavaScript
6
star
42

dep.js

a tiny dependency manager
JavaScript
5
star
43

sodium.nim

Nim
5
star
44

solarbeam

Super effective Solr client in Perl that uses Mojolicious' event loop
Perl
5
star
45

aoc2018

Brainfuck
5
star
46

tvo-lang

Ruby
4
star
47

arch-musl

PKGBUILDs for various statically built libraries using musl
Shell
4
star
48

shasm.js

JavaScript
4
star
49

camping-test

Lightweight testing framework for Camping
Ruby
4
star
50

uno-lang

A programming language
Ruby
4
star
51

sexp_template

A template engine which allows you to express S-expressions in pure Ruby
Ruby
3
star
52

imba-experiments

JavaScript
3
star
53

sexp_builder

Easily process and rewrite S-expressions using SexpPath
Ruby
3
star
54

ethercode

Ruby
3
star
55

imba-template

Ruby
3
star
56

zig-dot-builder

Easily create .dot files from Zig
Zig
3
star
57

rpython-example

Just me playing with RPython
Python
3
star
58

forter

Forte implementation in Ruby
Ruby
3
star
59

random

Ruby
3
star
60

flexmex

C
2
star
61

doo

Development tool for starting and running services
Go
2
star
62

dotfiles

Vim Script
2
star
63

jason.lua

Lua
2
star
64

hix

HTTP server for Nim
Nim
2
star
65

bob

Bob the Builder
Nim
2
star
66

imba-source

Ruby
2
star
67

tapper

Write simple TAP tests in Ruby
Ruby
2
star
68

glick

Implementation of Glicko-2 in Ruby
Ruby
2
star
69

mockle

TODO: Implement this
JavaScript
2
star
70

wordle-analyze

Ruby
2
star
71

mockle.rb

Just me playing with another template engine. Move along.
Ruby
2
star
72

nir

C++
2
star
73

heffigy

Fast DOM manipulation template framework
Ruby
2
star
74

sofaskull

Skull+
Ruby
2
star
75

Paws.rb

Paws implementation in Ruby
2
star
76

tipi

Ruby
1
star
77

ufwx

Just 4-letter random project name that Mike Perham won't take. Still in need of some code thoughโ€ฆ
1
star
78

latcher

A more precise Matcher (for CtrlP)
Lua
1
star
79

zrb

Ruby
1
star
80

json-builder

Perl
1
star
81

minad

Shell
1
star
82

willdo

Task manager in Vim for programmers
Vim Script
1
star
83

minitest-chain

Ruby
1
star
84

tada

Ruby
1
star
85

smockle

1
star
86

dominic

Ruby
1
star
87

appy

Ruby
1
star
88

mockle.spec

Specification for the Mockle template language
Ruby
1
star
89

coordinated-omission-example

Go
1
star
90

perloku-debug

Shell
1
star
91

phat

No idea what this project is about. I just wanted the name so people in the future can call me "the fat guy" the same way the call Marc "the thin guy"
1
star