ember 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,79 @@
1
+ %#--
2
+ %# Copyright 2009 Suraj N. Kurapati
3
+ %# See the LICENSE file for details.
4
+ %#++
5
+
6
+ % api_url = './api/index.html'
7
+ % repo_url = 'http://github.com/sunaku/' + $program
8
+ % repo_scm = '[Git](http://git-scm.com)'
9
+
10
+ %|chapter "Introduction"
11
+ %|project
12
+ <%= $project %> is an [eRuby template](http://en.wikipedia.org/wiki/ERuby) processsor that facilitates debugging, reduces markup, and improves composability of eRuby templates.
13
+
14
+ <%= $project %> is exciting because:
15
+ * It reports correct line numbers in stack traces.
16
+ * It can infer <tt><%% end %></tt> based on indentation.
17
+ * It can unindent block content hierarchically.
18
+ * It completely silences code-only eRuby directives.
19
+ * It is implemented in <%= `sloccount lib`[/^\d+/] %> lines of pure Ruby.
20
+
21
+ These features distinguish <%= $project %> from the competition:
22
+ * [Erubis](http://www.kuwata-lab.com/erubis/)
23
+ * [eruby](http://modruby.net/en/)
24
+ * [ERB](http://www.ruby-doc.org/stdlib/libdoc/erb/rdoc/)
25
+
26
+ %|paragraph "Etymology"
27
+ <%= $project %> stands for *embe*dded *r*uby.
28
+
29
+ %|section "Logistics"
30
+ * <%= xref "History", "Release notes" %> --- history of project releases.
31
+ * [Source code](<%= repo_url %>) --- obtain via <%= repo_scm %> or browse online.
32
+ * [API reference](<%= api_url %>) --- documentation for source code.
33
+ * [Project home](<%= $website %>) --- the <%= $project %> project home page.
34
+
35
+ To get help or provide feedback, simply
36
+ <%= xref "License", "contact the author(s)" %>.
37
+
38
+ %|paragraph "Version numbers"
39
+ <%= $project %> releases are numbered in *major.minor.patch*
40
+ form according to the [RubyGems rational versioning
41
+ policy](http://www.rubygems.org/read/chapter/7), which
42
+ can be summarized thus:
43
+
44
+ <table markdown="1">
45
+ <thead>
46
+ <tr>
47
+ <td rowspan="2">What increased in the version number?</td>
48
+ <td colspan="3">The increase indicates that the release:</td>
49
+ </tr>
50
+ <tr>
51
+ <th>Is backward compatible?</th>
52
+ <th>Has new features?</th>
53
+ <th>Has bug fixes?</th>
54
+ </tr>
55
+ </thead>
56
+ <tbody>
57
+ <tr>
58
+ <th>major</th>
59
+ <td style="background-color: #FFE4E1;">No</td>
60
+ <td>Yes</td>
61
+ <td>Yes</td>
62
+ </tr>
63
+ <tr>
64
+ <th>minor</th>
65
+ <td>Yes</td>
66
+ <td>Yes</td>
67
+ <td>Yes</td>
68
+ </tr>
69
+ <tr>
70
+ <th>patch</th>
71
+ <td>Yes</td>
72
+ <td style="background-color: #FFE4E1;">No</td>
73
+ <td>Yes</td>
74
+ </tr>
75
+ </tbody>
76
+ </table>
77
+
78
+ %|paragraph "License"
79
+ %< "../LICENSE"
@@ -0,0 +1,29 @@
1
+ %#--
2
+ %# Copyright 2009 Suraj N. Kurapati
3
+ %# See the LICENSE file for details.
4
+ %#++
5
+
6
+ %|chapter "Setup"
7
+ %|section "Requirements"
8
+ Your system needs the following software to run <%= $project %>.
9
+
10
+ | Software | Description | Notes |
11
+ | -------- | ----------- | ----- |
12
+ | [Ruby](http://ruby-lang.org) | Ruby language interpreter | Version 1.8.6, 1.8.7, and 1.9.1 have been tested successfully. |
13
+ | [RubyGems](http://rubygems.org) | Ruby packaging system | Version 1.3.1 or newer is required. |
14
+
15
+ %|section "Installation"
16
+ You can install <%= $project %> by running this command:
17
+
18
+ gem install <%= $program %>
19
+
20
+ To check whether the installation was sucessful, run this command:
21
+
22
+ <%= $program %> --version
23
+
24
+ If the installation was successful, you will see output like this:
25
+
26
+ <pre><%= verbatim `ruby bin/#{$program} --version` %></pre>
27
+
28
+ If you do not see such output, you may
29
+ <%= xref "License", "ask the author(s)" %> for help.
@@ -0,0 +1,249 @@
1
+ %#--
2
+ %# Copyright 2009 Suraj N. Kurapati
3
+ %# See the LICENSE file for details.
4
+ %#++
5
+
6
+ %|chapter "Usage"
7
+ %|section "Command-line interface"
8
+ When you run this command:
9
+
10
+ <%= $program %> --help
11
+
12
+ You will see this output:
13
+
14
+ <pre><%= verbatim `ruby bin/#{$program} --help` %></pre>
15
+
16
+ %|section "Ruby library interface"
17
+ Begin by loading <%= $project %> into Ruby:
18
+
19
+ <code>
20
+ require 'rubygems'
21
+ require '<%= $program %>'
22
+ </code>
23
+
24
+ Instantiate a template processor:
25
+
26
+ <code>
27
+ source = "your eRuby template here"
28
+ options = { :unindent => true, :shorthand => true }
29
+
30
+ template = Ember::Template.new(source, options)
31
+ </code>
32
+
33
+ Inspect the Ruby program that is used to evaluate the eRuby template:
34
+
35
+ <code>
36
+ puts template.program
37
+ </code>
38
+
39
+ View the result of evaluating the eRuby template:
40
+
41
+ <code>
42
+ puts template.render
43
+ </code>
44
+
45
+ See the [API documentation](<%= api_url %>) for details and examples.
46
+
47
+ %|section "eRuby template directives", "Directives"
48
+ eRuby templates are plain-text documents that contain special processing instructions known as **directives**. Directives may be expressed using either **standard** or **shorthand** notation:
49
+
50
+ | Notation | Directive | Head | Operation | Body | Tail |
51
+ | -------- | --------- | ---- | --------- | ---- | ---- |
52
+ | Standard | <%%XY%> | <%% | X | Y | %> |
53
+ | Shorthand | %XY | % | X | Y | |
54
+
55
+ In standard notation, the directive is composed of a head, <%= xref "Operations", "an operation" %>, a body, and a tail. Furthermore, the directive may appear anywhere in the text.
56
+
57
+ In shorthand notation, the directive is composed of a head, <%= xref "Operations", "an operation" %> and a body. Furthermore, the directive may only appear in the text if it occupies an entire line.
58
+
59
+ In any case, directives are atomic constructs; they may not be nested.
60
+
61
+ %|section "Operations"
62
+ The first character that follows the head of a directive is known as an **operation**. Operations specify how the directive should be processed:
63
+
64
+ | Operation | Effect | Example |
65
+ | --------- | ------ | ------- |
66
+ | <%= Ember::Template::OPERATION_COMMENT_LINE %> | The entire directive is omitted from the output. | <%= xref "Comment directives" %> |
67
+ | <%= Ember::Template::OPERATION_EVAL_EXPRESSION %> | The body of the directive is evaluated as Ruby code, and the result of this evaluation is inserted into the output. | <%= xref "Vocal directives" %> |
68
+ | <%= Ember::Template::OPERATION_EVAL_TEMPLATE_STRING %> | The body of the directive is evaluated as an eRuby template, and the result of this evaluation is inserted into the output. | <%= xref "Dynamic template evaluation" %> |
69
+ | <%= Ember::Template::OPERATION_EVAL_TEMPLATE_FILE %> | The body of the directive is evaluated as Ruby code, and the result of this evaluation is assumed to be a string that specifies the path (either absolute or relative to the eRuby template file in which this directive is found) to a file containing an eRuby template. This file is read and its contents are evaluated as an eRuby template, and the result of this evaluation is inserted into the output. | <%= xref "Template file inclusion" %> |
70
+ | <%= Ember::Template::OPERATION_INSERT_PLAIN_FILE %> | The body of the directive is evaluated as Ruby code, and the result of this evaluation is assumed to be a string that specifies the path (either absolute or relative to the eRuby template file in which this directive is found) to a file. This file is read and its contents are inserted into the output. | <%= xref "Raw file inclusion" %> |
71
+ | &#124; | The body of the directive is treated as the beginning of a Ruby block. The `do` keyword is automatically appended to the body of the directive if missing. | <%= xref "Block directives" %> |
72
+ | % | One "%" character is omitted from the head of the directive and the entire directive is inserted into the output. | <%= xref "Escaped directives" %> |
73
+ | (none of the above) | The body of the directive is evaluated as Ruby code, but the result of this evaluation *is not* inserted into the output. | <%= xref "Silent directives" %> |
74
+
75
+ <%
76
+ standard_directive = lambda do |body|
77
+ '<' + '%' + body + ' %' + '>'
78
+ end
79
+
80
+ shorthand_directive = lambda do |body|
81
+ '%' + body
82
+ end
83
+
84
+ template_example = lambda do |input, options|
85
+ input = input.strip.gsub(/^ {4}/, '') << "\n" # remove indentation
86
+ template = Ember::Template.new(input, options)
87
+
88
+ options_display =
89
+ if options.empty?
90
+ "This"
91
+ else
92
+ "With `#{options.inspect}`, this"
93
+ end
94
+
95
+ [
96
+ "<pre>#{input}</pre>",
97
+
98
+ "#{options_display} template compiles into:",
99
+ "<code>#{template.program}</code>",
100
+
101
+ "And renders as:",
102
+ "<pre>#{template.render}</pre>",
103
+
104
+ ].join("\n\n")
105
+ end
106
+ %>
107
+
108
+ %|example "An empty template"
109
+ Begin with an empty template:
110
+
111
+ <%= template_example.call "", {} %>
112
+
113
+ %|example "Comment directives"
114
+ Add comment directives:
115
+
116
+ <%=
117
+ template_example.call %{
118
+ #{standard_directive['# this is a comment']}
119
+ #{shorthand_directive['# this is also a comment']}
120
+
121
+ #{standard_directive["# this is a\n\n multi-line comment"]}
122
+ }, :shorthand => true
123
+ %>
124
+
125
+ %|example "Escaped directives"
126
+ Add escaped directives:
127
+
128
+ <%=
129
+ example = '% this is an escaped directive'
130
+
131
+ template_example.call %{
132
+ #{standard_directive[example]}
133
+ #{shorthand_directive[example]}
134
+ }, :shorthand => true
135
+ %>
136
+
137
+ %|example "Vocal directives"
138
+ Add vocal directives, which produce output:
139
+
140
+ <%=
141
+ template_example.call %{
142
+ #{standard_directive['= "hello"']}
143
+ #{shorthand_directive['= "world"']}
144
+
145
+ }, :shorthand => true
146
+ %>
147
+
148
+ %|example "Silent directives"
149
+ Add silent directives, which do not produce output:
150
+
151
+ <%=
152
+ template_example.call %{
153
+ #{standard_directive[' a = "hello"']}
154
+ #{shorthand_directive[' b = "world"']}
155
+
156
+ #{standard_directive['= a']}
157
+ #{shorthand_directive['= b']}
158
+
159
+ }, :shorthand => true
160
+ %>
161
+
162
+ %|example "Block directives"
163
+ Add some Ruby blocks:
164
+
165
+ <%=
166
+ template_example.call %{
167
+ #{shorthand_directive[' words = %w[hello world]']}
168
+
169
+ #{standard_directive[' words.each do |w|']}
170
+ #{standard_directive['= w']}
171
+ #{standard_directive[' end']}
172
+
173
+ #{shorthand_directive[' words.each do |w|']}
174
+ #{shorthand_directive['= w']}
175
+ #{shorthand_directive[' end']}
176
+
177
+ #{shorthand_directive['|words.each |w|']}
178
+ #{shorthand_directive['= w']}
179
+ #{shorthand_directive[' end']}
180
+
181
+ }, :shorthand => true
182
+ %>
183
+
184
+ %|example "Infer block endings"
185
+ Omit <tt><%= standard_directive[' end'] %></tt> directives from the template:
186
+
187
+ <%=
188
+ template_example.call %{
189
+ #{shorthand_directive[' words = %w[hello world]']}
190
+
191
+ #{standard_directive[' words.each do |w|']}
192
+ #{standard_directive['= w']}
193
+
194
+ #{shorthand_directive[' words.each do |w|']}
195
+ #{shorthand_directive['= w']}
196
+
197
+ #{shorthand_directive['|words.each |w|']}
198
+ #{shorthand_directive['= w']}
199
+
200
+ }, :shorthand => true, :infer_end => true
201
+ %>
202
+
203
+ %|example "Raw file inclusion"
204
+ When <tt>doc/example.txt</tt> contains:
205
+
206
+ <pre><%< "example.txt" %></pre>
207
+
208
+ And the eRuby template is:
209
+
210
+ <%=
211
+ example = '< "example.txt"'
212
+
213
+ template_example.call %{
214
+ #{standard_directive[example]}
215
+
216
+ #{shorthand_directive[example]}
217
+
218
+ }, :shorthand => true, :source_file => __FILE__
219
+ %>
220
+
221
+ %|example "Template file inclusion"
222
+ When <tt>doc/example.erb</tt> contains:
223
+
224
+ <code lang="rhtml"><%< "example.erb" %></code>
225
+
226
+ And the eRuby template is:
227
+
228
+ <%=
229
+ example = '+ "example.erb"'
230
+
231
+ template_example.call %{
232
+ #{standard_directive[example]}
233
+
234
+ #{shorthand_directive[example]}
235
+
236
+ }, :shorthand => true, :source_file => __FILE__
237
+ %>
238
+
239
+ %|example "Dynamic template evaluation"
240
+ <%=
241
+ example = %{~ "#{shorthand_directive['= 2 + 2']}"}
242
+
243
+ template_example.call %{
244
+ #{standard_directive[example]}
245
+
246
+ #{shorthand_directive[example]}
247
+
248
+ }, :shorthand => true
249
+ %>
@@ -0,0 +1,16 @@
1
+ #--
2
+ # Copyright 2009 Suraj N. Kurapati
3
+ # See the LICENSE file for details.
4
+ #++
5
+
6
+ require 'rubygems'
7
+ gem 'inochi', '~> 1'
8
+ require 'inochi'
9
+
10
+ Inochi.init :Ember,
11
+ :version => '0.0.0',
12
+ :release => '2009-05-02',
13
+ :website => 'http://snk.tuxfamily.org/lib/ember',
14
+ :tagline => 'eRuby template processor'
15
+
16
+ require 'ember/template'
@@ -0,0 +1,586 @@
1
+ #--
2
+ # Copyright 2009 Suraj N. Kurapati
3
+ # See the LICENSE file for details.
4
+ #++
5
+
6
+ require 'pathname'
7
+
8
+ module Ember
9
+ class Template
10
+ ##
11
+ # Builds a processor that evaluates eRuby directives
12
+ # in the given input according to the given options.
13
+ #
14
+ # This processor transforms the given input into an
15
+ # executable Ruby program (provided by the #to_s() method)
16
+ # which is then executed by the #render() method on demand.
17
+ #
18
+ # eRuby directives that contribute to the output of
19
+ # the given template are called "vocal" directives.
20
+ # Those that do not are called "silent" directives.
21
+ #
22
+ # ==== Options
23
+ #
24
+ # [:result_variable]
25
+ # Name of the variable which stores the result of
26
+ # template evaluation during template evaluation.
27
+ #
28
+ # The default value is "_erbout".
29
+ #
30
+ # [:continue_result]
31
+ # Append to the result variable if it already exists?
32
+ #
33
+ # The default value is false.
34
+ #
35
+ # [:source_file]
36
+ # Name of the file which contains the given input. This
37
+ # is shown in stack traces when reporting error messages.
38
+ #
39
+ # The default value is "SOURCE".
40
+ #
41
+ # [:source_line]
42
+ # Line number at which the given input exists in the :source_file.
43
+ # This is shown in stack traces when reporting error messages.
44
+ #
45
+ # The default value is 1.
46
+ #
47
+ # [:shorthand]
48
+ # Treat lines beginning with "%" as eRuby directives?
49
+ #
50
+ # The default value is false.
51
+ #
52
+ # [:infer_end]
53
+ # Add missing <% end %> statements based on indentation?
54
+ #
55
+ # The default value is false.
56
+ #
57
+ # [:unindent]
58
+ # Unindent the content of eRuby blocks (everything
59
+ # between <% do %> ... <% end %>) hierarchically?
60
+ #
61
+ # The default value is false.
62
+ #
63
+ def initialize input, options = {}
64
+ @options = options
65
+ @compile = compile(input.to_s)
66
+ end
67
+
68
+ ##
69
+ # Ruby source code assembled from the eRuby template
70
+ # provided as input to the constructor of this class.
71
+ #
72
+ def program
73
+ @compile
74
+ end
75
+
76
+ ##
77
+ # Returns the result of executing the Ruby program for this template
78
+ # (provided by the #to_s() method) inside the given context binding.
79
+ #
80
+ def render(context = TOPLEVEL_BINDING)
81
+ eval @compile, context,
82
+ (@options[:source_file] || :SOURCE).to_s,
83
+ (@options[:source_line] || 1).to_i
84
+ end
85
+
86
+ class << self
87
+ ##
88
+ # Builds a template whose body is read from the given source.
89
+ #
90
+ # If the source is a relative path, it will be resolved
91
+ # relative to options[:source_file] if that is a valid path.
92
+ #
93
+ def load_file path, options = {}
94
+ path = resolve_path(path, options)
95
+ new File.read(path), options.merge(:source_file => path)
96
+ end
97
+
98
+ ##
99
+ # Returns the contents of the given file, which can be relative to
100
+ # the current template in which this command is being executed.
101
+ #
102
+ # If the source is a relative path, it will be resolved
103
+ # relative to options[:source_file] if that is a valid path.
104
+ #
105
+ def read_file path, options = {}
106
+ File.read resolve_path(path, options)
107
+ end
108
+
109
+ private
110
+
111
+ def resolve_path path, options = {}
112
+ unless Pathname.new(path).absolute?
113
+ if base = options[:source_file] and File.exist? base
114
+ # target is relative to the file in
115
+ # which the include directive exists
116
+ path = File.join(File.dirname(base), path)
117
+ end
118
+ end
119
+
120
+ path
121
+ end
122
+ end
123
+
124
+
125
+ private
126
+
127
+ OPERATION_EVAL_EXPRESSION = '='
128
+ OPERATION_COMMENT_LINE = '#'
129
+ OPERATION_BEGIN_LAMBDA = '|'
130
+ OPERATION_EVAL_TEMPLATE_FILE = '+'
131
+ OPERATION_EVAL_TEMPLATE_STRING = '~'
132
+ OPERATION_INSERT_PLAIN_FILE = '<'
133
+
134
+ #:stopdoc:
135
+
136
+ OPERATIONS = [
137
+ OPERATION_COMMENT_LINE,
138
+ OPERATION_BEGIN_LAMBDA,
139
+ OPERATION_EVAL_EXPRESSION,
140
+ OPERATION_EVAL_TEMPLATE_FILE,
141
+ OPERATION_EVAL_TEMPLATE_STRING,
142
+ OPERATION_INSERT_PLAIN_FILE,
143
+ ]
144
+
145
+ SILENT_OPERATIONS = [
146
+ OPERATION_COMMENT_LINE,
147
+ OPERATION_BEGIN_LAMBDA,
148
+ ]
149
+
150
+ VOCAL_OPERATIONS = OPERATIONS - SILENT_OPERATIONS
151
+
152
+ DIRECTIVE_HEAD = '<%'
153
+ DIRECTIVE_BODY = '(?:(?#
154
+ there is nothing here before the alternation
155
+ because we want to match the "<%%>" base case
156
+ )|[^%](?:.(?!<%))*?)'
157
+ DIRECTIVE_TAIL = '-?%>'
158
+
159
+ SHORTHAND_HEAD = '%'
160
+ SHORTHAND_BODY = '(?:(?#
161
+ there is nothing here before the alternation
162
+ because we want to match the "<%%>" base case
163
+ )|[^%].*)'
164
+ SHORTHAND_TAIL = '$'
165
+
166
+ NEWLINE = '\r?\n'
167
+ SPACING = '[[:blank:]]*'
168
+
169
+ MARGIN_REGEXP = /^#{SPACING}(?=\S)/o
170
+
171
+ LAMBDA_BEGIN_REGEXP = /\b(do)\b\s*(\|.*?\|)?\s*$/
172
+
173
+ build_keyword_regexp = lambda {|*words| /\A\s*\b(#{words.join '|'})\b/ }
174
+
175
+ BLOCK_BEGIN_REGEXP = build_keyword_regexp[
176
+ # generic
177
+ :begin,
178
+
179
+ # conditional
180
+ :if, :unless, :case,
181
+
182
+ # loops
183
+ :for, :while, :until
184
+ ]
185
+
186
+ BLOCK_CONTINUE_REGEXP = build_keyword_regexp[
187
+ # generic
188
+ :rescue, :ensure,
189
+
190
+ # conditional
191
+ :else, :elsif, :when
192
+ ]
193
+
194
+ BLOCK_END_REGEXP = build_keyword_regexp[
195
+ # generic
196
+ :end
197
+ ]
198
+
199
+ #:startdoc:
200
+
201
+ ##
202
+ # Transforms the given eRuby template into an executable Ruby program.
203
+ #
204
+ def compile template
205
+ @program = Program.new(
206
+ @options[:result_variable] || :_erbout,
207
+ @options[:continue_result]
208
+ )
209
+
210
+ # convert "% at beginning of line" usage into <% normal %> usage
211
+ if @options[:shorthand]
212
+ i = 0
213
+ contents, directives =
214
+ template.split(/(#{DIRECTIVE_HEAD}#{DIRECTIVE_BODY}#{DIRECTIVE_TAIL})/mo).
215
+ partition { (i += 1) & 1 == 1 } # even/odd partition
216
+
217
+ # only process the content; do not touch the directives
218
+ # because they may contain code lines beginning with "%"
219
+ contents.each do |content|
220
+ # process unescaped directives
221
+ content.gsub! %r/^(#{SPACING})(#{SHORTHAND_HEAD}#{SHORTHAND_BODY})#{SHORTHAND_TAIL}/o, '\1<\2%>'
222
+
223
+ # unescape escaped directives
224
+ content.gsub! %r/^(#{SPACING})(#{SHORTHAND_HEAD})#{SHORTHAND_HEAD}/o, '\1\2'
225
+ end
226
+
227
+ template = contents.zip(directives).join
228
+ end
229
+
230
+ # translate template into Ruby code
231
+ @margins = []
232
+ @crowns = []
233
+
234
+ directive_matches = template.scan(/#{
235
+ '((%s)%s(%s)%s(%s)(%s?))' % [
236
+ SPACING,
237
+ DIRECTIVE_HEAD,
238
+ DIRECTIVE_BODY,
239
+ DIRECTIVE_TAIL,
240
+ SPACING,
241
+ NEWLINE,
242
+ ]
243
+ }/mo)
244
+
245
+ directive_matches.each do |match|
246
+ # iteratively whittle the template
247
+ before_content, after_content = template.split(match[0], 2)
248
+ template = after_content
249
+
250
+ # process the raw content before the directive
251
+ process_content before_content
252
+
253
+ # process the directive itself
254
+ args = match + [after_content]
255
+ process_directive(*args)
256
+ end
257
+
258
+ # process remaining raw content *after* last directive
259
+ process_content template
260
+
261
+ # handle missing ends
262
+ if @options[:infer_end]
263
+ @margins.each { emit_end }
264
+ else
265
+ warn "There are at least #{@margins.length} missing '<% end %>' statements in the eRuby template." unless @margins.empty?
266
+ end
267
+
268
+ @program.compile
269
+ end
270
+
271
+ def close_block
272
+ raise 'cannot close unopened block' if @margins.empty?
273
+ @margins.pop
274
+ @crowns.pop
275
+ end
276
+
277
+ def emit_end
278
+ @program.emit_end
279
+ end
280
+
281
+ def infer_end line, skip_last_level = false
282
+ if @options[:infer_end] and
283
+ @program.new_line? and
284
+ not line.empty? and
285
+ current = line[MARGIN_REGEXP]
286
+ then
287
+ # number of levels to ascend
288
+ levels = @crowns.select {|previous| current <= previous }.length
289
+
290
+ # in the case of block-continuation and -ending directives,
291
+ # we must not ascend the very last (outmost) level at this
292
+ # point of the algorithm. that work will be done later on
293
+ levels -= 1 if skip_last_level
294
+
295
+ levels.times do |i|
296
+ p :infer => line if $DEBUG
297
+ close_block
298
+ emit_end
299
+ end
300
+ end
301
+ end
302
+
303
+ ##
304
+ # Returns a new string containing the result of unindentation.
305
+ #
306
+ def unindent line
307
+ if @options[:unindent] and
308
+ @program.new_line? and
309
+ margin = @margins.last and
310
+ crown = @crowns.first
311
+ then
312
+ line.gsub(/^#{margin}/, crown)
313
+ else
314
+ line
315
+ end
316
+ end
317
+
318
+ def process_content content
319
+ content.split(/^/).each do |content_line|
320
+ # before_spacing
321
+ infer_end content_line, false
322
+ content_line = unindent(content_line)
323
+
324
+ # content + after_spacing
325
+ content_line.gsub! '<%%', '<%' # unescape escaped directives
326
+ @program.text content_line
327
+
328
+ # after_newline
329
+ @program.new_line if content_line =~ /\n\z/
330
+ end
331
+ end
332
+
333
+ def process_directive content_line, before_spacing, directive, after_spacing, after_newline, after_content
334
+ operation = directive[0, 1]
335
+
336
+ if OPERATIONS.include? operation
337
+ arguments = directive[1..-1]
338
+ else
339
+ operation = ''
340
+ arguments = directive
341
+ end
342
+
343
+ arguments = unindent(arguments)
344
+
345
+ is_vocal = VOCAL_OPERATIONS.include? operation
346
+
347
+ # before_spacing
348
+ after_margin = after_content[MARGIN_REGEXP]
349
+
350
+ open_block = lambda do
351
+ @margins << after_margin
352
+ @crowns << before_spacing
353
+ end
354
+
355
+ # '.' stands in place of the directive body,
356
+ # which may be empty in the case of '<%%>'
357
+ infer_end before_spacing + '.',
358
+ operation.empty? &&
359
+ arguments =~ BLOCK_END_REGEXP ||
360
+ arguments =~ BLOCK_CONTINUE_REGEXP
361
+
362
+ @program.text unindent(before_spacing) if is_vocal
363
+
364
+ # directive
365
+ template_class_name = '::Ember::Template'
366
+ nested_template_args = "(#{arguments}), #{@options.inspect}"
367
+
368
+ nest_template_with = lambda do |meth|
369
+ @program.code "#{template_class_name}.#{meth}(#{
370
+ nested_template_args
371
+ }.merge!(:continue_result => true)).render(binding)"
372
+ end
373
+
374
+ case operation
375
+ when OPERATION_EVAL_EXPRESSION
376
+ @program.expr arguments
377
+
378
+ when OPERATION_COMMENT_LINE
379
+ @program.code directive.gsub(/\S/, ' ')
380
+
381
+ when OPERATION_BEGIN_LAMBDA
382
+ arguments =~ /(\bdo\b)?\s*(\|[^\|]*\|)?\s*\z/
383
+ @program.code "#{$`} #{$1 || 'do'} #{$2}"
384
+
385
+ p :begin => directive if $DEBUG
386
+ open_block.call
387
+
388
+ when OPERATION_EVAL_TEMPLATE_STRING
389
+ nest_template_with[:new]
390
+
391
+ when OPERATION_EVAL_TEMPLATE_FILE
392
+ nest_template_with[:load_file]
393
+
394
+ when OPERATION_INSERT_PLAIN_FILE
395
+ @program.expr "#{template_class_name}.read_file(#{nested_template_args})"
396
+
397
+ else
398
+ @program.code arguments
399
+
400
+ unless arguments =~ /\n/ # don't bother parsing multi-line directives
401
+ case arguments
402
+ when BLOCK_BEGIN_REGEXP, LAMBDA_BEGIN_REGEXP
403
+ p :begin => directive if $DEBUG
404
+ open_block.call
405
+
406
+ when BLOCK_CONTINUE_REGEXP
407
+ # reopen because the new block might have a different margin
408
+ p :continue => directive if $DEBUG
409
+ close_block
410
+ open_block.call
411
+
412
+ when BLOCK_END_REGEXP
413
+ p :close => directive if $DEBUG
414
+ close_block
415
+ end
416
+ end
417
+ end
418
+
419
+ # after_spacing
420
+ @program.text after_spacing if is_vocal || after_newline.empty?
421
+
422
+ # after_newline
423
+ @program.text after_newline if is_vocal
424
+ @program.new_line unless after_newline.empty?
425
+ end
426
+
427
+ class Program
428
+ ##
429
+ # Transforms this program into Ruby code which uses
430
+ # the given variable name as the evaluation buffer.
431
+ #
432
+ # If continue_result is true, the evaluation buffer is
433
+ # reused if it already exists in the rendering context.
434
+ #
435
+ def initialize result_variable, continue_result
436
+ @result_variable = result_variable
437
+ @continue_result = continue_result
438
+ @source_lines = [] # each line is composed of multiple statements
439
+ end
440
+
441
+ ##
442
+ # Returns true if there are no source lines in this program.
443
+ #
444
+ def empty?
445
+ @source_lines.empty?
446
+ end
447
+
448
+ ##
449
+ # Begins a new line in the program's source code.
450
+ #
451
+ def new_line
452
+ @source_lines << []
453
+ end
454
+
455
+ ##
456
+ # Returns true if a new (blank) line is
457
+ # ready in the program's source code.
458
+ #
459
+ def new_line?
460
+ ary = insertion_point
461
+ ary.empty? || ary.all? {|stmt| stmt.type == :code }
462
+ end
463
+
464
+ ##
465
+ # Schedules the given text to be inserted verbatim
466
+ # into the evaluation buffer when this program is run.
467
+ #
468
+ def text value
469
+ # don't bother emitting empty strings
470
+ return if value.empty?
471
+
472
+ # combine adjacent statements to reduce code size
473
+ if prev = insertion_point.last and prev.type == :text
474
+ prev.value << value
475
+ else
476
+ statement :text, value
477
+ end
478
+ end
479
+
480
+ ##
481
+ # Schedules the given Ruby code to be
482
+ # evaluated when this program is run.
483
+ #
484
+ def code value
485
+ statement :code, value
486
+ end
487
+
488
+ ##
489
+ # Schedules the given Ruby code to be evaluated and inserted
490
+ # into the evaluation buffer when this program is run.
491
+ #
492
+ def expr value
493
+ statement :expr, value
494
+ end
495
+
496
+ ##
497
+ # Inserts an <% end %> directive before the
498
+ # oldest non-whitespace statement possible.
499
+ #
500
+ # Preceding lines that only emit whitespace are skipped.
501
+ #
502
+ def emit_end
503
+ ending = Statement.new(:code, :end)
504
+ current = insertion_point
505
+
506
+ can_skip_line = lambda do |line|
507
+ line.empty? ||
508
+ line.all? {|stmt| stmt.type == :text && stmt.value =~ /\A\s*\z/ }
509
+ end
510
+
511
+ if can_skip_line[current]
512
+ target = current
513
+
514
+ # skip past empty whitespace in previous lines
515
+ @source_lines.reverse_each do |line|
516
+ break unless can_skip_line[line]
517
+ target = line
518
+ end
519
+
520
+ target.unshift ending
521
+ else
522
+ current.push ending
523
+ end
524
+ end
525
+
526
+ ##
527
+ # Transforms this program into executable Ruby source code.
528
+ #
529
+ def compile
530
+ '(%s %s []; %s; %s.join)' % [
531
+ @result_variable,
532
+ @continue_result ? '||=' : '=',
533
+
534
+ @source_lines.map do |source_line|
535
+ compiled_line = []
536
+ combine_prev = false
537
+
538
+ source_line.each do |stmt|
539
+ is_code = stmt.type == :code
540
+ is_expr = stmt.type == :expr
541
+
542
+ if is_code
543
+ compiled_line << stmt.value
544
+ combine_prev = false
545
+
546
+ else
547
+ code =
548
+ if is_expr
549
+ " << (#{stmt.value})"
550
+ else
551
+ " << #{stmt.value.inspect}"
552
+ end
553
+
554
+ if combine_prev
555
+ compiled_line.last << code
556
+ else
557
+ compiled_line << @result_variable.to_s + code
558
+ end
559
+
560
+ combine_prev = true
561
+ end
562
+ end
563
+
564
+ compiled_line.join('; ')
565
+
566
+ end.join("\n"),
567
+
568
+ @result_variable,
569
+ ]
570
+ end
571
+
572
+ private
573
+
574
+ def insertion_point
575
+ new_line if empty?
576
+ @source_lines.last
577
+ end
578
+
579
+ def statement *args
580
+ insertion_point << Statement.new(*args)
581
+ end
582
+
583
+ Statement = Struct.new :type, :value
584
+ end
585
+ end
586
+ end