temple 0.0.1 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/.yardopts ADDED
@@ -0,0 +1 @@
1
+ --title Temple
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 Magnus Holm
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,246 @@
1
+ Temple
2
+ ======
3
+
4
+ Temple is an abstraction and a framework for compiling templates to pure Ruby.
5
+ It's all about making it easier to experiment, implement and optimize template
6
+ languages. If you're interested in implementing your own template language, or
7
+ anything else related to the internals of a template engine: You've come to
8
+ the right place.
9
+
10
+ Have a look around, and if you're still wondering: Ask on the mailing list and
11
+ we'll try to do our best. In fact, it doesn't have to be related to Temple at
12
+ all. As long as it has something to do with template languages, we're
13
+ interested: <http://groups.google.com/group/guardians-of-the-temple>.
14
+
15
+ Meta
16
+ ----
17
+
18
+ * Home: <http://github.com/judofyr/temple>
19
+ * Bugs: <http://github.com/judofyr/temple/issues>
20
+ * List: <http://groups.google.com/group/guardians-of-the-temple>
21
+ * Core abstraction: {Temple::Core}
22
+
23
+
24
+ Overview
25
+ --------
26
+
27
+ Temple is built on a theory that every template consists of three elements:
28
+
29
+ * Static text
30
+ * Dynamic text (pieces of Ruby which are evaluated and sent to the client)
31
+ * Blocks (pieces of Ruby which are evaluated and *not* sent to the client, but
32
+ might change the control flow).
33
+
34
+ The goal of a template engine is to take the template and eventually compile
35
+ it into *the core abstraction*:
36
+
37
+ [:multi,
38
+ [:static, "Hello "],
39
+ [:dynamic, "@user.name"],
40
+ [:static, "!\n"],
41
+ [:block, "if @user.birthday == Date.today"],
42
+ [:static, "Happy birthday!"],
43
+ [:block, "end"]]
44
+
45
+ Then you can apply some optimizations, feed it to Temple and it generates fast
46
+ Ruby code for you:
47
+
48
+ _buf = []
49
+ _buf << ("Hello #{@user.name}!\n")
50
+ if @user.birthday == Date.today
51
+ _buf << "Happy birthday!"
52
+ end
53
+ _buf.join
54
+
55
+ S-expression
56
+ ------------
57
+
58
+ In Temple, an Sexp is simply an array (or a subclass) where the first element
59
+ is the *type* and the rest are the *arguments*. The type must be a symbol and
60
+ it's recommended to only use strings, symbols, arrays and numbers as
61
+ arguments.
62
+
63
+ Temple uses Sexps to represent templates because it's a simple and
64
+ straightforward data structure, which can easily be written by hand and
65
+ manipulated by computers.
66
+
67
+ Some examples:
68
+
69
+ [:static, "Hello World!"]
70
+
71
+ [:multi,
72
+ [:static, "Hello "],
73
+ [:dynamic, "@world"]]
74
+
75
+ [:html, :tag, "em", "Hey hey"]
76
+
77
+ *NOTE:* SexpProcessor, a library written by Ryan Davis, includes a `Sexp`
78
+ class. While you can use this class (since it's a subclass of Array), it's not
79
+ what Temple mean by "Sexp".
80
+
81
+ Abstractions
82
+ ------------
83
+
84
+ The idea behind Temple is that abstractions are good, and it's better to have
85
+ too many than too few. While you should always end up with the core
86
+ abstraction, you shouldn't stress about it. Take one step at a time, and only
87
+ do one thing at every step.
88
+
89
+ So what's an abstraction? An abstraction is when you introduce a new types:
90
+
91
+ # Instead of:
92
+ [:static, "<strong>Use the force</strong>"]
93
+
94
+ # You use:
95
+ [:html, :tag, "strong", [:static, "Use the force"]]
96
+
97
+ ### Why are abstractions so important?
98
+
99
+ First of all, it means that several template engines can share code. Instead
100
+ of having two engines which goes all the way to generating HTML, you have two
101
+ smaller engines which only compiles to the HTML abstraction together with
102
+ something that compiles the HTML abstraction to the core abstraction.
103
+
104
+ Often you also introduce abstractions because there's more than one way to do
105
+ it. There's not a single way to generate HTML. Should it be indented? If so,
106
+ with tabs or spaces? Or should it remove as much whitespace as possible?
107
+ Single or double quotes in attributes? Escape all weird UTF-8 characters?
108
+
109
+ With an abstraction you can easily introduce a completely new HTML compiler,
110
+ and whatever is below doesn't have to care about it *at all*. They just
111
+ continue to use the HTML abstraction. Maybe you even want to write your
112
+ compiler in another language? Sexps are easily serialized and if you don't
113
+ mind working across processes, it's not a problem at all.
114
+
115
+
116
+ Compilers
117
+ ---------
118
+
119
+ A *compiler* is simply an object which responds a method called #compile which
120
+ takes one argument and returns a value. It's illegal for a compiler to mutate
121
+ the argument, and it should be possible to use the same instance several times
122
+ (although not by several threads at the same time).
123
+
124
+ While a compiler can be any object, you very often want to structure it as a
125
+ class. Temple then assumes the initializer takes an optional option hash:
126
+
127
+ class MyCompiler
128
+ def initialize(options = {})
129
+ @options = options
130
+ end
131
+
132
+ def compile(exp)
133
+ # do stuff
134
+ end
135
+ end
136
+
137
+ ### Parsers
138
+
139
+ In Temple, a parser is also a compiler, because a compiler is just something
140
+ that takes some input and produces some output. A parser is then something
141
+ that takes a string and returns an Sexp.
142
+
143
+ It's important to remember that the parser *should be dumb*. No optimization,
144
+ no guesses. It should produce an Sexp that is as close to the source as
145
+ possible. You should invent your own abstraction. Maybe you even want to
146
+ separate the parsers into several parts and introduce several abstractions on
147
+ the way?
148
+
149
+ ### Filters
150
+
151
+ A filter is a compiler which take an Sexp and returns an Sexp. It might turn
152
+ convert it one step closer to the core-abstraction, it might create a new
153
+ abstraction, or it might just optimize in the current abstraction. Ultimately,
154
+ it's still just a compiler which takes an Sexp and returns an Sexp.
155
+
156
+ For instance, Temple ships with {Temple::Filters::DynamicInliner} and
157
+ {Temple::Filters::StaticMerger} which are general optimization filters which
158
+ works on the core abstraction.
159
+
160
+ An HTML compiler would be a filter, since it would take an Sexp in the HTML
161
+ abstraction and compile it down to the core abstraction.
162
+
163
+ ### Generators
164
+
165
+ A generator is a compiler which takes an Sexp and returns a string which is
166
+ valid Ruby code.
167
+
168
+ Most of the time you would just use {Temple::Core::ArrayBuffer} or any of the
169
+ other generators in {Temple::Core}, but nothing stops you from writing your
170
+ own.
171
+
172
+ In fact, one of the great things about Temple is that if you write a new
173
+ generator which turns out to be a lot faster then the others, it's going to
174
+ make *every single engine* based on Temple faster! So if you have any ideas,
175
+ please share them - it's highly appreciated.
176
+
177
+ Engines
178
+ -------
179
+
180
+ 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:
181
+
182
+ class MyEngine < Temple::Engine
183
+ # First run MyParser, passing the :strict option
184
+ use MyParser, :strict
185
+
186
+ # Then a custom filter
187
+ use MyFilter
188
+
189
+ # Then some general optimizations filters
190
+ filter :MultiFlattener
191
+ filter :StaticMerger
192
+ filter :DynamicInliner
193
+
194
+ # Finally the generator
195
+ generator :ArrayBuffer, :buffer
196
+ end
197
+
198
+ engine = MyEngine.new(:strict => "For MyParser")
199
+ engine.compile(something)
200
+
201
+ And then?
202
+ ---------
203
+
204
+ You've ran the template through the parser, some filters and in the end a
205
+ generator. What happens next?
206
+
207
+ Temple's mission ends here, so it's all up to you, but we recommend using
208
+ [Tilt](http://github.com/rtomayko/tilt), the generic interface to Ruby
209
+ template engines. This gives you a wide range of features and your engine can
210
+ be used right away in many projects.
211
+
212
+ require 'tilt'
213
+
214
+ class MyTemplate < Tilt::Template
215
+ def prepare
216
+ @src = MyEngine.new(options).compile(data)
217
+ end
218
+
219
+ def template_source
220
+ @src
221
+ end
222
+ end
223
+
224
+ # Register your file extension:
225
+ Tilt.register 'ext', MyTemplate
226
+
227
+ Tilt.new('example.ext').render # => Render a file
228
+ MyTemplate.new { "String" }.render # => Render a string
229
+
230
+
231
+ Installation
232
+ ------------
233
+
234
+ $ gem install temple
235
+
236
+
237
+ Acknowledgements
238
+ ----------------
239
+
240
+ Thanks to [_why](http://en.wikipedia.org/wiki/Why_the_lucky_stiff) for
241
+ creating an excellent template engine (Markaby) which is quite slow. That's
242
+ how I started experimenting with template engines in the first place.
243
+
244
+ I also owe [Ryan Davis](http://zenspider.com/) a lot for his excellent
245
+ projects ParserTree, RubyParser, Ruby2Ruby and SexpProcessor. Temple is
246
+ heavily inspired by how these tools work.
data/Rakefile CHANGED
@@ -1,20 +1,20 @@
1
- task :default => :spec
2
- require 'spec/rake/spectask'
3
- Spec::Rake::SpecTask.new {|t| t.spec_opts = ['--color']}
1
+ require 'rake/testtask'
4
2
 
3
+ def command?(command)
4
+ system("type #{command} > /dev/null")
5
+ end
5
6
 
6
- begin
7
- project = 'temple'
8
- require 'jeweler'
9
- Jeweler::Tasks.new do |gem|
10
- gem.name = project
11
- gem.summary = "Template compilation framework in Ruby"
12
- gem.email = "judofyr@gmail.com"
13
- gem.homepage = "http://github.com/judofyr/#{project}"
14
- gem.authors = ["Magnus Holm"]
15
- end
7
+ task :default => :test
16
8
 
17
- Jeweler::GemcutterTasks.new
18
- rescue LoadError
19
- puts "Jeweler, or one of its dependencies, is not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
9
+ if RUBY_VERSION[0,3] == "1.8" and command?("turn")
10
+ task :test do
11
+ suffix = "-n #{ENV['TEST']}" if ENV['TEST']
12
+ sh "turn test/test_*.rb test/**/test_*.rb #{suffix}"
13
+ end
14
+ else
15
+ Rake::TestTask.new do |t|
16
+ t.libs << 'lib'
17
+ t.pattern = 'test/**/test_*.rb'
18
+ t.verbose = false
19
+ end
20
20
  end
data/lib/temple.rb CHANGED
@@ -1,9 +1,14 @@
1
1
  module Temple
2
- VERSION = File.read( File.join(File.dirname(__FILE__),'..','VERSION') ).strip.split('.').map{|i|i.to_i}
2
+ VERSION = "0.0.1"
3
3
 
4
4
  autoload :Core, 'temple/core'
5
5
  autoload :Engine, 'temple/engine'
6
6
  autoload :Generator, 'temple/generator'
7
+ autoload :Utils, 'temple/utils'
8
+
9
+ module Engines
10
+ autoload :ERB, 'temple/engines/erb'
11
+ end
7
12
 
8
13
  module Parsers
9
14
  autoload :ERB, 'temple/parsers/erb'
@@ -12,6 +17,7 @@ module Temple
12
17
 
13
18
  module Filters
14
19
  autoload :Mustache, 'temple/filters/mustache'
20
+ autoload :MultiFlattener, 'temple/filters/multi_flattener'
15
21
  autoload :StaticMerger, 'temple/filters/static_merger'
16
22
  autoload :DynamicInliner, 'temple/filters/dynamic_inliner'
17
23
  autoload :Escapable, 'temple/filters/escapable'
data/lib/temple/core.rb CHANGED
@@ -1,5 +1,83 @@
1
1
  module Temple
2
+ # == The Core Abstraction
3
+ #
4
+ # The core abstraction is what every template evetually should be compiled
5
+ # to. Currently it consists of four essential and two convenient types:
6
+ # multi, static, dynamic, block, newline and capture.
7
+ #
8
+ # When compiling, there's two different strings we'll have to think about.
9
+ # First we have the generated code. This is what your engine (from Temple's
10
+ # point of view) spits out. If you construct this carefully enough, you can
11
+ # make exceptions report correct line numbers, which is very convenient.
12
+ #
13
+ # Then there's the result. This is what your engine (from the user's point
14
+ # of view) spits out. It's what happens if you evaluate the generated code.
15
+ #
16
+ # === [:multi, *sexp]
17
+ #
18
+ # Multi is what glues everything together. It's simply a sexp which combines
19
+ # several others sexps:
20
+ #
21
+ # [:multi,
22
+ # [:static, "Hello "],
23
+ # [:dynamic, "@world"]]
24
+ #
25
+ # === [:static, string]
26
+ #
27
+ # Static indicates that the given string should be appended to the result.
28
+ # Every \n will be also cause a newline in the generated code. \r\n on the
29
+ # other hand, only causes a newline in the result.
30
+ #
31
+ # Example:
32
+ #
33
+ # [:static, "Hello World"]
34
+ # # is the same as:
35
+ # _buf << "Hello World"
36
+ #
37
+ # [:static, "Hello \n World"]
38
+ # # is the same as:
39
+ # _buf << "Hello
40
+ # World"
41
+ #
42
+ # [:static, "Hello \r\n World"]
43
+ # # is the same as
44
+ # _buf << "Hello\nWorld"
45
+ #
46
+ # === [:dynamic, ruby]
47
+ #
48
+ # Dynamic indicates that the given Ruby code should be evaluated and then
49
+ # appended to the result. Any \n causes a newline in the generated code.
50
+ #
51
+ # The Ruby code must be a complete expression in the sense that you can pass
52
+ # it to eval() and it would not raise SyntaxError.
53
+ #
54
+ # === [:block, ruby]
55
+ #
56
+ # Block indicates that the given Ruby code should be evaluated, and may
57
+ # change the control flow. Any \n causes a newline in the generated code.
58
+ #
59
+ # === [:newline]
60
+ #
61
+ # Newline causes a newline in the generated code, but not in the result.
62
+ #
63
+ # === [:capture, variable_name, sexp]
64
+ #
65
+ # Evaluates the Sexp using the rules above, but instead of appending to the
66
+ # result, it sets the content to the variable given.
67
+ #
68
+ # Example:
69
+ #
70
+ # [:multi,
71
+ # [:static, "Some content"],
72
+ # [:capture, "foo", [:static, "More content"]],
73
+ # [:dynamic, "foo.downcase"]]
74
+ # # is the same as:
75
+ # _buf << "Some content"
76
+ # foo = "More content"
77
+ # _buf << foo.downcase
2
78
  module Core
79
+ # Implements an array buffer.
80
+ #
3
81
  # _buf = []
4
82
  # _buf << "static"
5
83
  # _buf << dynamic
@@ -8,19 +86,19 @@ module Temple
8
86
  # end
9
87
  # _buf.join
10
88
  class ArrayBuffer < Generator
11
- def preamble; buffer " = []\n" end
89
+ def preamble; buffer " = []" end
12
90
  def postamble; buffer ".join" end
13
91
 
14
92
  def on_static(text)
15
- buffer " << #{text.inspect}\n"
93
+ to_ruby(text)
16
94
  end
17
95
 
18
96
  def on_dynamic(code)
19
- buffer " << (#{code})\n"
97
+ code
20
98
  end
21
99
 
22
100
  def on_block(code)
23
- code + "\n"
101
+ code
24
102
  end
25
103
  end
26
104
 
@@ -29,6 +107,8 @@ module Temple
29
107
  def postamble; buffer; end
30
108
  end
31
109
 
110
+ # Implements a string buffer.
111
+ #
32
112
  # _buf = ''
33
113
  # _buf << "static"
34
114
  # _buf << dynamic.to_s
@@ -37,47 +117,15 @@ module Temple
37
117
  # end
38
118
  # _buf
39
119
  class StringBuffer < ArrayBuffer
40
- def preamble; buffer " = ''\n" end
120
+ def preamble; buffer " = ''" end
41
121
  def postamble; buffer end
42
122
 
43
123
  def on_dynamic(code)
44
- buffer " << (#{code}).to_s\n"
45
- end
46
- end
47
-
48
- # Compiles into a single double-quoted string.
49
- #
50
- # This doesn't make so much sense when you have blocks, so it's
51
- # kinda funky. You probably want to use Filters::DynamicInliner instead.
52
- # For now, check out the source for #on_multi.
53
- class Interpolation < Generator
54
- def preamble; '"' end
55
- def postamble; '"' end
56
-
57
- def on_static(text)
58
- text.inspect[1..-2]
59
- end
60
-
61
- def on_dynamic(code)
62
- '#{%s}' % code
63
- end
64
-
65
- def on_multi(*exps)
66
- if exps.detect { |exp| exp[0] == :block }
67
- '#{%s}' % exps.map do |exp|
68
- if exp[0] == :block
69
- exp[1]
70
- else
71
- compile(exp)
72
- end
73
- end.join
124
+ if @options[:check_literal] && Utils.literal_string?(code)
125
+ code
74
126
  else
75
- super
76
- end
77
- end
78
-
79
- def on_block(code)
80
- '#{%s;nil}' % code
127
+ "(#{code}).to_s"
128
+ end
81
129
  end
82
130
  end
83
131
  end