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