ember 0.0.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.
@@ -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