handlebars 0.0.1 → 0.0.2
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/.gitignore +1 -0
- data/README.md +39 -0
- data/handlebars.gemspec +1 -1
- data/lib/handlebars.rb +2 -27
- data/lib/handlebars/generator.rb +4 -0
- data/lib/handlebars/parser.rb +240 -0
- data/lib/handlebars/version.rb +1 -1
- data/spec/generator_spec.rb +5 -0
- data/spec/parser_spec.rb +163 -0
- metadata +8 -5
- data/lib/handlebars/compiler.rb +0 -96
- data/spec/test_spec.rb +0 -8
data/.gitignore
CHANGED
data/README.md
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
Handlebars
|
2
|
+
==========
|
3
|
+
|
4
|
+
Handlebars is a implementation of [handlebars.js][2], an extension of
|
5
|
+
mustache by Yehuda Katz, in Ruby.
|
6
|
+
|
7
|
+
|
8
|
+
Current status
|
9
|
+
--------------
|
10
|
+
|
11
|
+
So far only the parser has been implemented. It supports the whole
|
12
|
+
handlebars syntax.
|
13
|
+
|
14
|
+
|
15
|
+
Installation
|
16
|
+
------------
|
17
|
+
|
18
|
+
### [RubyGems](http://rubygems.org/)
|
19
|
+
|
20
|
+
$ gem install handlebars
|
21
|
+
|
22
|
+
|
23
|
+
Acknowledgements
|
24
|
+
----------------
|
25
|
+
|
26
|
+
Thanks to all the [implementers][3] of the original mustache gem.
|
27
|
+
Handlebars is based on their codebase and hard work.
|
28
|
+
|
29
|
+
|
30
|
+
Meta
|
31
|
+
----
|
32
|
+
|
33
|
+
* Code: `git clone http://github.com/MSch/handlebars-ruby`
|
34
|
+
* Bugs: <http://github.com/MSch/handlebars-ruby/issues>
|
35
|
+
* Gems: <http://rubygems.org/gems/handlebars>
|
36
|
+
|
37
|
+
[1]:http://github.com/wycats/handlebars.js
|
38
|
+
[2]:http://yehudakatz.com/2010/09/09/announcing-handlebars-js/
|
39
|
+
[3]:http://github.com/defunkt/mustache/raw/master/CONTRIBUTORS
|
data/handlebars.gemspec
CHANGED
@@ -15,7 +15,7 @@ Gem::Specification.new do |s|
|
|
15
15
|
s.rubyforge_project = "handlebars"
|
16
16
|
|
17
17
|
s.add_development_dependency "bundler", ">= 1.0.0"
|
18
|
-
s.add_development_dependency "rspec", "
|
18
|
+
s.add_development_dependency "rspec", "~> 2.0.0"
|
19
19
|
|
20
20
|
s.files = `git ls-files`.split("\n")
|
21
21
|
s.executables = `git ls-files`.split("\n").map{|f| f =~ /^bin\/(.*)/ ? $1 : nil}.compact
|
data/lib/handlebars.rb
CHANGED
@@ -1,30 +1,5 @@
|
|
1
|
-
require 'handlebars/
|
1
|
+
require 'handlebars/generator'
|
2
|
+
require 'handlebars/parser'
|
2
3
|
|
3
4
|
class Handlebars
|
4
|
-
attr_accessor :compiled_partials
|
5
|
-
def compile(input)
|
6
|
-
end
|
7
|
-
|
8
|
-
def compile_to_string(input)
|
9
|
-
end
|
10
|
-
|
11
|
-
def compile_function_body(input)
|
12
|
-
end
|
13
|
-
|
14
|
-
def escape_text(input)
|
15
|
-
CGI.escapeHTML(input)
|
16
|
-
end
|
17
|
-
|
18
|
-
def escape_expression(input)
|
19
|
-
end
|
20
|
-
|
21
|
-
|
22
|
-
def compile_partials(input)
|
23
|
-
end
|
24
|
-
|
25
|
-
def parse_path(input)
|
26
|
-
end
|
27
|
-
|
28
|
-
def filter_output(input, escape=true)
|
29
|
-
end
|
30
5
|
end
|
@@ -0,0 +1,240 @@
|
|
1
|
+
require 'strscan'
|
2
|
+
|
3
|
+
class Handlebars
|
4
|
+
class Parser
|
5
|
+
# A SyntaxError is raised when the Parser comes across unclosed
|
6
|
+
# tags, sections, illegal content in tags, or anything of that
|
7
|
+
# sort.
|
8
|
+
class SyntaxError < StandardError
|
9
|
+
def initialize(message, position)
|
10
|
+
@message = message
|
11
|
+
@lineno, @column, @line = position
|
12
|
+
@stripped_line = @line.strip
|
13
|
+
@stripped_column = @column - (@line.size - @line.lstrip.size)
|
14
|
+
end
|
15
|
+
|
16
|
+
def to_s
|
17
|
+
<<-EOF
|
18
|
+
#{@message}
|
19
|
+
Line #{@lineno}
|
20
|
+
#{@stripped_line}
|
21
|
+
#{' ' * @stripped_column}^
|
22
|
+
EOF
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# After these types of tags, all whitespace will be skipped.
|
27
|
+
SKIP_WHITESPACE = [ '#', '^', '/' ]
|
28
|
+
|
29
|
+
# The content allowed in a tag name.
|
30
|
+
ALLOWED_CONTENT = /(\w|[.?!\/-])*/
|
31
|
+
|
32
|
+
# These types of tags allow any content,
|
33
|
+
# the rest only allow ALLOWED_CONTENT.
|
34
|
+
ANY_CONTENT = [ '!', '=' ]
|
35
|
+
|
36
|
+
attr_reader :scanner, :result
|
37
|
+
attr_writer :otag, :ctag
|
38
|
+
|
39
|
+
# Accepts an options hash which does nothing but may be used in
|
40
|
+
# the future.
|
41
|
+
def initialize(options = {})
|
42
|
+
@options = {}
|
43
|
+
end
|
44
|
+
|
45
|
+
# The opening tag delimiter. This may be changed at runtime.
|
46
|
+
def otag
|
47
|
+
@otag ||= '{{'
|
48
|
+
end
|
49
|
+
|
50
|
+
# The closing tag delimiter. This too may be changed at runtime.
|
51
|
+
def ctag
|
52
|
+
@ctag ||= '}}'
|
53
|
+
end
|
54
|
+
|
55
|
+
# Given a string template, returns an array of tokens.
|
56
|
+
def compile(template)
|
57
|
+
if template.respond_to?(:encoding)
|
58
|
+
@encoding = template.encoding
|
59
|
+
template = template.dup.force_encoding("BINARY")
|
60
|
+
else
|
61
|
+
@encoding = nil
|
62
|
+
end
|
63
|
+
|
64
|
+
# Keeps information about opened sections.
|
65
|
+
@sections = []
|
66
|
+
@result = [:multi]
|
67
|
+
@scanner = StringScanner.new(template)
|
68
|
+
|
69
|
+
# Scan until the end of the template.
|
70
|
+
until @scanner.eos?
|
71
|
+
scan_tags || scan_text
|
72
|
+
end
|
73
|
+
|
74
|
+
if !@sections.empty?
|
75
|
+
# We have parsed the whole file, but there's still opened sections.
|
76
|
+
type, pos, result = @sections.pop
|
77
|
+
error "Unclosed section #{type.inspect}", pos
|
78
|
+
end
|
79
|
+
|
80
|
+
@result
|
81
|
+
end
|
82
|
+
|
83
|
+
# Find {{mustaches}} and add them to the @result array.
|
84
|
+
def scan_tags
|
85
|
+
# Scan until we hit an opening delimiter.
|
86
|
+
return unless @scanner.scan(regexp(otag))
|
87
|
+
|
88
|
+
# Since {{= rewrites ctag, we store the ctag which should be used
|
89
|
+
# when parsing this specific tag.
|
90
|
+
current_ctag = self.ctag
|
91
|
+
type = @scanner.scan(/#|\^|\/|=|!|<|>|&|\{/)
|
92
|
+
@scanner.skip(/\s*/)
|
93
|
+
|
94
|
+
# ANY_CONTENT tags allow any character inside of them, while
|
95
|
+
# other tags (such as variables) are more strict.
|
96
|
+
if ANY_CONTENT.include?(type)
|
97
|
+
r = /\s*#{regexp(type)}?#{regexp(current_ctag)}/
|
98
|
+
content = scan_until_exclusive(r)
|
99
|
+
else
|
100
|
+
content = @scanner.scan(ALLOWED_CONTENT)
|
101
|
+
end
|
102
|
+
|
103
|
+
# We found {{ but we can't figure out what's going on inside.
|
104
|
+
# This applies to all tags except handlebar's shortened {{^}}
|
105
|
+
if type != '^'
|
106
|
+
error "Illegal content in tag" if content.empty?
|
107
|
+
end
|
108
|
+
|
109
|
+
def find_context
|
110
|
+
# A space after the helper indicates a context 'switch'
|
111
|
+
if @scanner.peek(1) == ' '
|
112
|
+
@scanner.skip(/ /)
|
113
|
+
@scanner.scan(ALLOWED_CONTENT)
|
114
|
+
else
|
115
|
+
nil
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
# Based on the sigil, do what needs to be done.
|
120
|
+
case type
|
121
|
+
when '#'
|
122
|
+
block = [:multi]
|
123
|
+
@result << [:mustache, :section, content, find_context(), block]
|
124
|
+
@sections << [content, position, @result]
|
125
|
+
@result = block
|
126
|
+
when '^'
|
127
|
+
if content.empty?
|
128
|
+
# We are dealing with handlebar's shortened {{^}}
|
129
|
+
|
130
|
+
# Close the section
|
131
|
+
section, pos, result = @sections.pop
|
132
|
+
@result = result
|
133
|
+
if section.nil?
|
134
|
+
error "Inverting unopened section"
|
135
|
+
end
|
136
|
+
|
137
|
+
# Open a new inverted section with the same name
|
138
|
+
block = [:multi]
|
139
|
+
@result << [:mustache, :inverted_section, section, block]
|
140
|
+
@sections << [section, position, @result]
|
141
|
+
@result = block
|
142
|
+
else
|
143
|
+
block = [:multi]
|
144
|
+
@result << [:mustache, :inverted_section, content, block]
|
145
|
+
@sections << [content, position, @result]
|
146
|
+
@result = block
|
147
|
+
end
|
148
|
+
when '/'
|
149
|
+
section, pos, result = @sections.pop
|
150
|
+
@result = result
|
151
|
+
|
152
|
+
if section.nil?
|
153
|
+
error "Closing unopened #{content.inspect}"
|
154
|
+
elsif section != content
|
155
|
+
error "Unclosed section #{section.inspect}", pos
|
156
|
+
end
|
157
|
+
when '!'
|
158
|
+
# ignore comments
|
159
|
+
when '='
|
160
|
+
self.otag, self.ctag = content.split(' ', 2)
|
161
|
+
when '>', '<'
|
162
|
+
@result << [:mustache, :partial, content]
|
163
|
+
when '{', '&'
|
164
|
+
# The closing } in unescaped tags is just a hack for
|
165
|
+
# aesthetics.
|
166
|
+
type = "}" if type == "{"
|
167
|
+
@result << [:mustache, :utag, content]
|
168
|
+
else
|
169
|
+
@result << [:mustache, :etag, content, find_context()]
|
170
|
+
end
|
171
|
+
|
172
|
+
# Skip whitespace and any balancing sigils after the content
|
173
|
+
# inside this tag.
|
174
|
+
@scanner.skip(/\s+/)
|
175
|
+
@scanner.skip(regexp(type)) if type
|
176
|
+
|
177
|
+
# Try to find the closing tag.
|
178
|
+
unless close = @scanner.scan(regexp(current_ctag))
|
179
|
+
error "Unclosed tag"
|
180
|
+
end
|
181
|
+
|
182
|
+
# Skip whitespace following this tag if we need to.
|
183
|
+
@scanner.skip(/\s+/) if SKIP_WHITESPACE.include?(type)
|
184
|
+
end
|
185
|
+
|
186
|
+
# Try to find static text, e.g. raw HTML with no {{mustaches}}.
|
187
|
+
def scan_text
|
188
|
+
text = scan_until_exclusive(regexp(otag))
|
189
|
+
|
190
|
+
if text.nil?
|
191
|
+
# Couldn't find any otag, which means the rest is just static text.
|
192
|
+
text = @scanner.rest
|
193
|
+
# Mark as done.
|
194
|
+
@scanner.clear
|
195
|
+
end
|
196
|
+
|
197
|
+
text.force_encoding(@encoding) if @encoding
|
198
|
+
|
199
|
+
@result << [:static, text]
|
200
|
+
end
|
201
|
+
|
202
|
+
# Scans the string until the pattern is matched. Returns the substring
|
203
|
+
# *excluding* the end of the match, advancing the scan pointer to that
|
204
|
+
# location. If there is no match, nil is returned.
|
205
|
+
def scan_until_exclusive(regexp)
|
206
|
+
pos = @scanner.pos
|
207
|
+
if @scanner.scan_until(regexp)
|
208
|
+
@scanner.pos -= @scanner.matched.size
|
209
|
+
@scanner.pre_match[pos..-1]
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
# Returns [lineno, column, line]
|
214
|
+
def position
|
215
|
+
# The rest of the current line
|
216
|
+
rest = @scanner.check_until(/\n|\Z/).to_s.chomp
|
217
|
+
|
218
|
+
# What we have parsed so far
|
219
|
+
parsed = @scanner.string[0...@scanner.pos]
|
220
|
+
|
221
|
+
lines = parsed.split("\n")
|
222
|
+
|
223
|
+
[ lines.size, lines.last.size - 1, lines.last + rest ]
|
224
|
+
end
|
225
|
+
|
226
|
+
# Used to quickly convert a string into a regular expression
|
227
|
+
# usable by the string scanner.
|
228
|
+
def regexp(thing)
|
229
|
+
/#{Regexp.escape(thing)}/
|
230
|
+
end
|
231
|
+
|
232
|
+
# Raises a SyntaxError. The message should be the name of the
|
233
|
+
# error - other details such as line number and position are
|
234
|
+
# handled for you.
|
235
|
+
def error(message, pos = position)
|
236
|
+
raise SyntaxError.new(message, pos)
|
237
|
+
end
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
data/lib/handlebars/version.rb
CHANGED
data/spec/parser_spec.rb
ADDED
@@ -0,0 +1,163 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'handlebars'
|
3
|
+
|
4
|
+
describe Handlebars::Parser do
|
5
|
+
|
6
|
+
it 'fails when the handlebars inverted section syntax is used without an opened section' do
|
7
|
+
lexer = Handlebars::Parser.new
|
8
|
+
proc {
|
9
|
+
lexer.compile(<<-EOF)
|
10
|
+
{{^}}
|
11
|
+
<h1>No projects</h1>
|
12
|
+
{{/project}}
|
13
|
+
EOF
|
14
|
+
}.should raise_error(Handlebars::Parser::SyntaxError)
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'parses the handlebars inverted section syntax' do
|
18
|
+
lexer = Handlebars::Parser.new
|
19
|
+
tokens = lexer.compile(<<-EOF)
|
20
|
+
{{#project}}
|
21
|
+
<h1>{{name}}</h1>
|
22
|
+
<div>{{body}}</div>
|
23
|
+
{{^}}
|
24
|
+
<h1>No projects</h1>
|
25
|
+
{{/project}}
|
26
|
+
EOF
|
27
|
+
expected = [:multi,
|
28
|
+
[:mustache, :section, "project", nil, [:multi,
|
29
|
+
[:static, "<h1>"],
|
30
|
+
[:mustache, :etag, "name", nil],
|
31
|
+
[:static, "</h1>\n<div>"],
|
32
|
+
[:mustache, :etag, "body", nil],
|
33
|
+
[:static, "</div>\n"]]],
|
34
|
+
[:mustache, :inverted_section, "project", [:multi,
|
35
|
+
[:static, "<h1>No projects</h1>\n"]]]]
|
36
|
+
tokens.should eq(expected)
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'parses the mustache inverted section syntax' do
|
40
|
+
lexer = Handlebars::Parser.new
|
41
|
+
tokens = lexer.compile(<<-EOF)
|
42
|
+
{{#project}}
|
43
|
+
<h1>{{name}}</h1>
|
44
|
+
<div>{{body}}</div>
|
45
|
+
{{/project}}
|
46
|
+
{{^project}}
|
47
|
+
<h1>No projects</h1>
|
48
|
+
{{/project}}
|
49
|
+
EOF
|
50
|
+
expected = [:multi,
|
51
|
+
[:mustache, :section, "project", nil, [:multi,
|
52
|
+
[:static, "<h1>"],
|
53
|
+
[:mustache, :etag, "name", nil],
|
54
|
+
[:static, "</h1>\n<div>"],
|
55
|
+
[:mustache, :etag, "body", nil],
|
56
|
+
[:static, "</div>\n"]]],
|
57
|
+
[:mustache, :inverted_section, "project", [:multi,
|
58
|
+
[:static, "<h1>No projects</h1>\n"]]]]
|
59
|
+
tokens.should eq(expected)
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'parses helpers with context paths' do
|
63
|
+
lexer = Handlebars::Parser.new
|
64
|
+
tokens = lexer.compile(<<-EOF)
|
65
|
+
<h1>{{helper context}}</h1>
|
66
|
+
<h1>{{helper ..}}</h1>
|
67
|
+
{{#items ..}}
|
68
|
+
haha
|
69
|
+
{{haha}}
|
70
|
+
{{/items}}
|
71
|
+
EOF
|
72
|
+
|
73
|
+
expected = [:multi,
|
74
|
+
[:static, "<h1>"],
|
75
|
+
[:mustache, :etag, "helper", "context"],
|
76
|
+
[:static, "</h1>\n<h1>"],
|
77
|
+
[:mustache, :etag, "helper", ".."],
|
78
|
+
[:static, "</h1>\n"],
|
79
|
+
[:mustache, :section, "items", "..", [:multi,
|
80
|
+
[:static, "haha\n"],
|
81
|
+
[:mustache, :etag, "haha", nil],
|
82
|
+
[:static, "\n"]]]]
|
83
|
+
tokens.should eq(expected)
|
84
|
+
end
|
85
|
+
|
86
|
+
it 'parses extended paths' do
|
87
|
+
lexer = Handlebars::Parser.new
|
88
|
+
tokens = lexer.compile(<<-EOF)
|
89
|
+
<h1>{{../../header}}</h1>
|
90
|
+
<div>{{./header}}
|
91
|
+
{{hans/hubert/header}}</div>
|
92
|
+
{{#ines/ingrid/items}}
|
93
|
+
a
|
94
|
+
{{/ines/ingrid/items}}
|
95
|
+
EOF
|
96
|
+
|
97
|
+
expected = [:multi,
|
98
|
+
[:static, "<h1>"],
|
99
|
+
[:mustache, :etag, "../../header", nil],
|
100
|
+
[:static, "</h1>\n<div>"],
|
101
|
+
[:mustache, :etag, "./header", nil],
|
102
|
+
[:static, "\n"],
|
103
|
+
[:mustache, :etag, "hans/hubert/header", nil],
|
104
|
+
[:static, "</div>\n"],
|
105
|
+
[:mustache, :section, "ines/ingrid/items", nil, [:multi,
|
106
|
+
[:static, "a\n"]]]]
|
107
|
+
tokens.should eq(expected)
|
108
|
+
end
|
109
|
+
|
110
|
+
it 'parses the mustache example' do
|
111
|
+
lexer = Handlebars::Parser.new
|
112
|
+
tokens = lexer.compile(<<-EOF)
|
113
|
+
<h1>{{header}}</h1>
|
114
|
+
{{#items}}
|
115
|
+
{{#first}}
|
116
|
+
<li><strong>{{name}}</strong></li>
|
117
|
+
{{/first}}
|
118
|
+
{{#link}}
|
119
|
+
<li><a href="{{url}}">{{name}}</a></li>
|
120
|
+
{{/link}}
|
121
|
+
{{/items}}
|
122
|
+
|
123
|
+
{{#empty}}
|
124
|
+
<p>The list is empty.</p>
|
125
|
+
{{/empty}}
|
126
|
+
EOF
|
127
|
+
|
128
|
+
expected = [:multi,
|
129
|
+
[:static, "<h1>"],
|
130
|
+
[:mustache, :etag, "header", nil],
|
131
|
+
[:static, "</h1>\n"],
|
132
|
+
[:mustache,
|
133
|
+
:section,
|
134
|
+
"items",
|
135
|
+
nil,
|
136
|
+
[:multi,
|
137
|
+
[:mustache,
|
138
|
+
:section,
|
139
|
+
"first",
|
140
|
+
nil,
|
141
|
+
[:multi,
|
142
|
+
[:static, "<li><strong>"],
|
143
|
+
[:mustache, :etag, "name", nil],
|
144
|
+
[:static, "</strong></li>\n"]]],
|
145
|
+
[:mustache,
|
146
|
+
:section,
|
147
|
+
"link",
|
148
|
+
nil,
|
149
|
+
[:multi,
|
150
|
+
[:static, "<li><a href=\""],
|
151
|
+
[:mustache, :etag, "url", nil],
|
152
|
+
[:static, "\">"],
|
153
|
+
[:mustache, :etag, "name", nil],
|
154
|
+
[:static, "</a></li>\n"]]]]],
|
155
|
+
[:mustache,
|
156
|
+
:section,
|
157
|
+
"empty",
|
158
|
+
nil,
|
159
|
+
[:multi, [:static, "<p>The list is empty.</p>\n"]]]]
|
160
|
+
|
161
|
+
tokens.should eq(expected)
|
162
|
+
end
|
163
|
+
end
|
metadata
CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 0
|
7
7
|
- 0
|
8
|
-
-
|
9
|
-
version: 0.0.
|
8
|
+
- 2
|
9
|
+
version: 0.0.2
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Martin Schuerrer
|
@@ -38,7 +38,7 @@ dependencies:
|
|
38
38
|
requirement: &id002 !ruby/object:Gem::Requirement
|
39
39
|
none: false
|
40
40
|
requirements:
|
41
|
-
- -
|
41
|
+
- - ~>
|
42
42
|
- !ruby/object:Gem::Version
|
43
43
|
segments:
|
44
44
|
- 2
|
@@ -59,13 +59,16 @@ extra_rdoc_files: []
|
|
59
59
|
files:
|
60
60
|
- .gitignore
|
61
61
|
- Gemfile
|
62
|
+
- README.md
|
62
63
|
- Rakefile
|
63
64
|
- handlebars.gemspec
|
64
65
|
- lib/handlebars.rb
|
65
|
-
- lib/handlebars/
|
66
|
+
- lib/handlebars/generator.rb
|
67
|
+
- lib/handlebars/parser.rb
|
66
68
|
- lib/handlebars/version.rb
|
69
|
+
- spec/generator_spec.rb
|
70
|
+
- spec/parser_spec.rb
|
67
71
|
- spec/spec_helper.rb
|
68
|
-
- spec/test_spec.rb
|
69
72
|
has_rdoc: true
|
70
73
|
homepage: http://github.com/MSch/handlebars-ruby
|
71
74
|
licenses: []
|
data/lib/handlebars/compiler.rb
DELETED
@@ -1,96 +0,0 @@
|
|
1
|
-
require 'stringio'
|
2
|
-
|
3
|
-
class Handlebars
|
4
|
-
class Compiler
|
5
|
-
attr_accessor :input, :pointer, :mustache, :text, :fn, :newlines, :comment, :escaped, :partial, :inverted, :end_condition, :continue_inverted
|
6
|
-
def initialize(input)
|
7
|
-
@input = input
|
8
|
-
@pointer = -1
|
9
|
-
@mustache = false
|
10
|
-
@text = ''
|
11
|
-
@fn = 'out = ""; lookup = nil; '
|
12
|
-
@newlines = ''
|
13
|
-
@comment = false
|
14
|
-
@escaped = true
|
15
|
-
@partial = false
|
16
|
-
@inverted = false
|
17
|
-
@end_condition = nil
|
18
|
-
@continue_inverted = false
|
19
|
-
end
|
20
|
-
|
21
|
-
def add_text
|
22
|
-
if not @text.empty?
|
23
|
-
@fn << 'out = out + \'' + CGI.escapeHTML(@text) + '\''
|
24
|
-
@fn << @newlines
|
25
|
-
@newlines = ''
|
26
|
-
@text = ''
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
def parse_mustache
|
31
|
-
chr, part, mustache, param = nil
|
32
|
-
@s = StringScanner.new(input)
|
33
|
-
|
34
|
-
next_char = @s.peek
|
35
|
-
case next_char
|
36
|
-
when '!'
|
37
|
-
@comment = true
|
38
|
-
when '#'
|
39
|
-
@open_block = true
|
40
|
-
when '>'
|
41
|
-
@partial = true
|
42
|
-
when '^'
|
43
|
-
@inverted = true
|
44
|
-
@open_block = true
|
45
|
-
when '{'
|
46
|
-
@escaped = false
|
47
|
-
when '&'
|
48
|
-
@escaped = false
|
49
|
-
end
|
50
|
-
@s.getch
|
51
|
-
|
52
|
-
add_text
|
53
|
-
@mustache = ' '
|
54
|
-
|
55
|
-
while(chr = @s.getch)
|
56
|
-
if @mustache && chr == '}' && @s.peek == '}'
|
57
|
-
parts = @mustache.chomp.split(/\s+/)
|
58
|
-
mustache = parts[0]
|
59
|
-
param = lookup_for(parts[1])
|
60
|
-
|
61
|
-
@mustace = false
|
62
|
-
|
63
|
-
# finish reading off the close of the handlebars
|
64
|
-
@s.getch
|
65
|
-
|
66
|
-
# {{{expression}} is techically valid, but if we started with {{{ we'll try to read
|
67
|
-
# }}} off of the close of the handlebars
|
68
|
-
@s.getch if (!@escaped && @s.peek == '}')
|
69
|
-
|
70
|
-
if @comment
|
71
|
-
@comment = false
|
72
|
-
return
|
73
|
-
elsif @partial
|
74
|
-
add_partial(mustache, param)
|
75
|
-
return
|
76
|
-
elsif @inverted
|
77
|
-
add_inverted_section(mustache)
|
78
|
-
return
|
79
|
-
elsif @open_block
|
80
|
-
add_block(mustache, param, parts)
|
81
|
-
return
|
82
|
-
else
|
83
|
-
return this.add_expression(mustache, param)
|
84
|
-
end
|
85
|
-
|
86
|
-
@escaped = true
|
87
|
-
elsif @comment
|
88
|
-
;
|
89
|
-
else
|
90
|
-
@mustace << chr
|
91
|
-
end
|
92
|
-
end
|
93
|
-
end
|
94
|
-
|
95
|
-
end
|
96
|
-
end
|