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.
- data/LICENSE +15 -0
- data/bin/ember +47 -0
- data/doc/api/classes/Ember.html +61 -0
- data/doc/api/classes/Ember/Template.html +396 -0
- data/doc/api/classes/Ember/Template/Program.html +497 -0
- data/doc/api/created.rid +1 -0
- data/doc/api/css/main.css +263 -0
- data/doc/api/css/panel.css +383 -0
- data/doc/api/css/reset.css +53 -0
- data/doc/api/files/LICENSE.html +76 -0
- data/doc/api/files/lib/ember/template_rb.html +66 -0
- data/doc/api/files/lib/ember_rb.html +63 -0
- data/doc/api/i/arrows.png +0 -0
- data/doc/api/i/results_bg.png +0 -0
- data/doc/api/i/tree_bg.png +0 -0
- data/doc/api/index.html +14 -0
- data/doc/api/js/jquery-1.3.2.min.js +19 -0
- data/doc/api/js/jquery-effect.js +593 -0
- data/doc/api/js/main.js +22 -0
- data/doc/api/js/searchdoc.js +605 -0
- data/doc/api/panel/index.html +63 -0
- data/doc/api/panel/search_index.js +1 -0
- data/doc/api/panel/tree.js +1 -0
- data/doc/example.erb +2 -0
- data/doc/example.txt +2 -0
- data/doc/history.erb +9 -0
- data/doc/index.erb +11 -0
- data/doc/index.xhtml +758 -0
- data/doc/intro.erb +79 -0
- data/doc/setup.erb +29 -0
- data/doc/usage.erb +249 -0
- data/lib/ember.rb +16 -0
- data/lib/ember/template.rb +586 -0
- data/rakefile +14 -0
- data/test/ember.rb +17 -0
- data/test/ember/template.rb +141 -0
- metadata +109 -0
data/doc/intro.erb
ADDED
@@ -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"
|
data/doc/setup.erb
ADDED
@@ -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.
|
data/doc/usage.erb
ADDED
@@ -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
|
+
| | | 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
|
+
%>
|
data/lib/ember.rb
ADDED
@@ -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
|