temple 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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