angry_mob_common_targets 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +21 -0
- data/README.md +38 -0
- data/lib/common_mob.rb +9 -0
- data/lib/common_mob/digest.rb +43 -0
- data/lib/common_mob/erb.rb +72 -0
- data/lib/common_mob/file.rb +55 -0
- data/lib/common_mob/patch.rb +51 -0
- data/lib/common_mob/resource_locator.rb +9 -0
- data/lib/common_mob/shell.rb +323 -0
- data/lib/common_mob/template.rb +23 -0
- data/lib/common_mob/version.rb +3 -0
- data/targets/crontab_patch.rb +37 -0
- data/targets/extract.rb +40 -0
- data/targets/fetch.rb +40 -0
- data/targets/files.rb +244 -0
- data/targets/git.rb +84 -0
- data/targets/group.rb +33 -0
- data/targets/packages.rb +94 -0
- data/targets/ruby.rb +13 -0
- data/targets/services.rb +184 -0
- data/targets/shell.rb +43 -0
- data/targets/user.rb +108 -0
- data/vendor/mustache/CONTRIBUTORS +9 -0
- data/vendor/mustache/HISTORY.md +135 -0
- data/vendor/mustache/LICENSE +20 -0
- data/vendor/mustache/README.md +405 -0
- data/vendor/mustache/Rakefile +103 -0
- data/vendor/mustache/benchmarks/complex.erb +15 -0
- data/vendor/mustache/benchmarks/complex.haml +12 -0
- data/vendor/mustache/benchmarks/helper.rb +20 -0
- data/vendor/mustache/benchmarks/simple.erb +5 -0
- data/vendor/mustache/benchmarks/speed.rb +78 -0
- data/vendor/mustache/bin/mustache +90 -0
- data/vendor/mustache/contrib/mustache-mode.el +278 -0
- data/vendor/mustache/contrib/mustache.vim +69 -0
- data/vendor/mustache/examples/hash.rb +16 -0
- data/vendor/mustache/examples/hash.yml +5 -0
- data/vendor/mustache/examples/projects.mustache +26 -0
- data/vendor/mustache/examples/projects.yml +28 -0
- data/vendor/mustache/examples/self.mustache +4 -0
- data/vendor/mustache/examples/self.yml +3 -0
- data/vendor/mustache/examples/simple.mustache +10 -0
- data/vendor/mustache/examples/simple.rb +24 -0
- data/vendor/mustache/lib/mustache.rb +358 -0
- data/vendor/mustache/lib/mustache/context.rb +108 -0
- data/vendor/mustache/lib/mustache/generator.rb +160 -0
- data/vendor/mustache/lib/mustache/parser.rb +230 -0
- data/vendor/mustache/lib/mustache/sinatra.rb +180 -0
- data/vendor/mustache/lib/mustache/template.rb +59 -0
- data/vendor/mustache/lib/mustache/version.rb +3 -0
- data/vendor/mustache/lib/rack/bug/panels/mustache_panel.rb +81 -0
- data/vendor/mustache/lib/rack/bug/panels/mustache_panel/mustache_extension.rb +27 -0
- data/vendor/mustache/lib/rack/bug/panels/mustache_panel/view.mustache +46 -0
- data/vendor/mustache/man/mustache.1 +180 -0
- data/vendor/mustache/man/mustache.1.html +204 -0
- data/vendor/mustache/man/mustache.1.ron +127 -0
- data/vendor/mustache/man/mustache.5 +576 -0
- data/vendor/mustache/man/mustache.5.html +415 -0
- data/vendor/mustache/man/mustache.5.ron +324 -0
- data/vendor/mustache/mustache.gemspec +32 -0
- data/vendor/mustache/test/autoloading_test.rb +52 -0
- data/vendor/mustache/test/fixtures/comments.mustache +1 -0
- data/vendor/mustache/test/fixtures/comments.rb +14 -0
- data/vendor/mustache/test/fixtures/complex_view.mustache +17 -0
- data/vendor/mustache/test/fixtures/complex_view.rb +34 -0
- data/vendor/mustache/test/fixtures/crazy_recursive.mustache +9 -0
- data/vendor/mustache/test/fixtures/crazy_recursive.rb +31 -0
- data/vendor/mustache/test/fixtures/delimiters.mustache +8 -0
- data/vendor/mustache/test/fixtures/delimiters.rb +23 -0
- data/vendor/mustache/test/fixtures/double_section.mustache +7 -0
- data/vendor/mustache/test/fixtures/double_section.rb +14 -0
- data/vendor/mustache/test/fixtures/escaped.mustache +1 -0
- data/vendor/mustache/test/fixtures/escaped.rb +14 -0
- data/vendor/mustache/test/fixtures/inner_partial.mustache +1 -0
- data/vendor/mustache/test/fixtures/inner_partial.txt +1 -0
- data/vendor/mustache/test/fixtures/inverted_section.mustache +7 -0
- data/vendor/mustache/test/fixtures/inverted_section.rb +14 -0
- data/vendor/mustache/test/fixtures/lambda.mustache +7 -0
- data/vendor/mustache/test/fixtures/lambda.rb +31 -0
- data/vendor/mustache/test/fixtures/namespaced.mustache +1 -0
- data/vendor/mustache/test/fixtures/namespaced.rb +25 -0
- data/vendor/mustache/test/fixtures/nested_objects.mustache +17 -0
- data/vendor/mustache/test/fixtures/nested_objects.rb +35 -0
- data/vendor/mustache/test/fixtures/node.mustache +8 -0
- data/vendor/mustache/test/fixtures/partial_with_module.mustache +3 -0
- data/vendor/mustache/test/fixtures/partial_with_module.rb +37 -0
- data/vendor/mustache/test/fixtures/passenger.conf +5 -0
- data/vendor/mustache/test/fixtures/passenger.rb +27 -0
- data/vendor/mustache/test/fixtures/recursive.mustache +4 -0
- data/vendor/mustache/test/fixtures/recursive.rb +14 -0
- data/vendor/mustache/test/fixtures/simple.mustache +5 -0
- data/vendor/mustache/test/fixtures/simple.rb +26 -0
- data/vendor/mustache/test/fixtures/template_partial.mustache +2 -0
- data/vendor/mustache/test/fixtures/template_partial.rb +18 -0
- data/vendor/mustache/test/fixtures/template_partial.txt +4 -0
- data/vendor/mustache/test/fixtures/unescaped.mustache +1 -0
- data/vendor/mustache/test/fixtures/unescaped.rb +14 -0
- data/vendor/mustache/test/fixtures/utf8.mustache +3 -0
- data/vendor/mustache/test/fixtures/utf8_partial.mustache +1 -0
- data/vendor/mustache/test/helper.rb +7 -0
- data/vendor/mustache/test/mustache_test.rb +536 -0
- data/vendor/mustache/test/parser_test.rb +54 -0
- data/vendor/mustache/test/partial_test.rb +168 -0
- metadata +167 -0
@@ -0,0 +1,108 @@
|
|
1
|
+
class Mustache
|
2
|
+
# A ContextMiss is raised whenever a tag's target can not be found
|
3
|
+
# in the current context if `Mustache#raise_on_context_miss?` is
|
4
|
+
# set to true.
|
5
|
+
#
|
6
|
+
# For example, if your View class does not respond to `music` but
|
7
|
+
# your template contains a `{{music}}` tag this exception will be raised.
|
8
|
+
#
|
9
|
+
# By default it is not raised. See Mustache.raise_on_context_miss.
|
10
|
+
class ContextMiss < RuntimeError; end
|
11
|
+
|
12
|
+
# A Context represents the context which a Mustache template is
|
13
|
+
# executed within. All Mustache tags reference keys in the Context.
|
14
|
+
class Context
|
15
|
+
# Expect to be passed an instance of `Mustache`.
|
16
|
+
def initialize(mustache)
|
17
|
+
@stack = [mustache]
|
18
|
+
end
|
19
|
+
|
20
|
+
# A {{>partial}} tag translates into a call to the context's
|
21
|
+
# `partial` method, which would be this sucker right here.
|
22
|
+
#
|
23
|
+
# If the Mustache view handling the rendering (e.g. the view
|
24
|
+
# representing your profile page or some other template) responds
|
25
|
+
# to `partial`, we call it and render the result.
|
26
|
+
def partial(name)
|
27
|
+
# Look for the first Mustache in the stack.
|
28
|
+
mustache = mustache_in_stack
|
29
|
+
|
30
|
+
# Call its `partial` method and render the result.
|
31
|
+
mustache.render(mustache.partial(name), self)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Find the first Mustache in the stack. If we're being rendered
|
35
|
+
# inside a Mustache object as a context, we'll use that one.
|
36
|
+
def mustache_in_stack
|
37
|
+
@stack.detect { |frame| frame.is_a?(Mustache) }
|
38
|
+
end
|
39
|
+
|
40
|
+
# Adds a new object to the context's internal stack.
|
41
|
+
#
|
42
|
+
# Returns the Context.
|
43
|
+
def push(new)
|
44
|
+
@stack.unshift(new)
|
45
|
+
self
|
46
|
+
end
|
47
|
+
alias_method :update, :push
|
48
|
+
|
49
|
+
# Removes the most recently added object from the context's
|
50
|
+
# internal stack.
|
51
|
+
#
|
52
|
+
# Returns the Context.
|
53
|
+
def pop
|
54
|
+
@stack.shift
|
55
|
+
self
|
56
|
+
end
|
57
|
+
|
58
|
+
# Can be used to add a value to the context in a hash-like way.
|
59
|
+
#
|
60
|
+
# context[:name] = "Chris"
|
61
|
+
def []=(name, value)
|
62
|
+
push(name => value)
|
63
|
+
end
|
64
|
+
|
65
|
+
# Alias for `fetch`.
|
66
|
+
def [](name)
|
67
|
+
fetch(name, nil)
|
68
|
+
end
|
69
|
+
|
70
|
+
# Do we know about a particular key? In other words, will calling
|
71
|
+
# `context[key]` give us a result that was set. Basically.
|
72
|
+
def has_key?(key)
|
73
|
+
!!fetch(key)
|
74
|
+
rescue ContextMiss
|
75
|
+
false
|
76
|
+
end
|
77
|
+
|
78
|
+
# Similar to Hash#fetch, finds a value by `name` in the context's
|
79
|
+
# stack. You may specify the default return value by passing a
|
80
|
+
# second parameter.
|
81
|
+
#
|
82
|
+
# If no second parameter is passed (or raise_on_context_miss is
|
83
|
+
# set to true), will raise a ContextMiss exception on miss.
|
84
|
+
def fetch(name, default = :__raise)
|
85
|
+
@stack.each do |frame|
|
86
|
+
# Prevent infinite recursion.
|
87
|
+
next if frame == self
|
88
|
+
|
89
|
+
# Is this frame a hash?
|
90
|
+
hash = frame.respond_to?(:has_key?)
|
91
|
+
|
92
|
+
if hash && frame.has_key?(name)
|
93
|
+
return frame[name]
|
94
|
+
elsif hash && frame.has_key?(name.to_s)
|
95
|
+
return frame[name.to_s]
|
96
|
+
elsif !hash && frame.respond_to?(name)
|
97
|
+
return frame.__send__(name)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
if default == :__raise || mustache_in_stack.raise_on_context_miss?
|
102
|
+
raise ContextMiss.new("Can't find #{name} in #{@stack.inspect}")
|
103
|
+
else
|
104
|
+
default
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,160 @@
|
|
1
|
+
class Mustache
|
2
|
+
# The Generator is in charge of taking an array of Mustache tokens,
|
3
|
+
# usually assembled by the Parser, and generating an interpolatable
|
4
|
+
# Ruby string. This string is considered the "compiled" template
|
5
|
+
# because at that point we're relying on Ruby to do the parsing and
|
6
|
+
# run our code.
|
7
|
+
#
|
8
|
+
# For example, let's take this template:
|
9
|
+
#
|
10
|
+
# Hi {{thing}}!
|
11
|
+
#
|
12
|
+
# If we run this through the Parser we'll get these tokens:
|
13
|
+
#
|
14
|
+
# [:multi,
|
15
|
+
# [:static, "Hi "],
|
16
|
+
# [:mustache, :etag, "thing"],
|
17
|
+
# [:static, "!\n"]]
|
18
|
+
#
|
19
|
+
# Now let's hand that to the Generator:
|
20
|
+
#
|
21
|
+
# >> puts Mustache::Generator.new.compile(tokens)
|
22
|
+
# "Hi #{CGI.escapeHTML(ctx[:thing].to_s)}!\n"
|
23
|
+
#
|
24
|
+
# You can see the generated Ruby string for any template with the
|
25
|
+
# mustache(1) command line tool:
|
26
|
+
#
|
27
|
+
# $ mustache --compile test.mustache
|
28
|
+
# "Hi #{CGI.escapeHTML(ctx[:thing].to_s)}!\n"
|
29
|
+
class Generator
|
30
|
+
# Options are unused for now but may become useful in the future.
|
31
|
+
def initialize(options = {})
|
32
|
+
@options = options
|
33
|
+
end
|
34
|
+
|
35
|
+
# Given an array of tokens, returns an interpolatable Ruby string.
|
36
|
+
def compile(exp)
|
37
|
+
"\"#{compile!(exp)}\""
|
38
|
+
end
|
39
|
+
|
40
|
+
# Given an array of tokens, converts them into Ruby code. In
|
41
|
+
# particular there are three types of expressions we are concerned
|
42
|
+
# with:
|
43
|
+
#
|
44
|
+
# :multi
|
45
|
+
# Mixed bag of :static, :mustache, and whatever.
|
46
|
+
#
|
47
|
+
# :static
|
48
|
+
# Normal HTML, the stuff outside of {{mustaches}}.
|
49
|
+
#
|
50
|
+
# :mustache
|
51
|
+
# Any Mustache tag, from sections to partials.
|
52
|
+
#
|
53
|
+
# To give you an idea of what you'll be dealing with take this
|
54
|
+
# template:
|
55
|
+
#
|
56
|
+
# Hello {{name}}
|
57
|
+
# You have just won ${{value}}!
|
58
|
+
# {{#in_ca}}
|
59
|
+
# Well, ${{taxed_value}}, after taxes.
|
60
|
+
# {{/in_ca}}
|
61
|
+
#
|
62
|
+
# If we run this through the Parser, we'll get back this array of
|
63
|
+
# tokens:
|
64
|
+
#
|
65
|
+
# [:multi,
|
66
|
+
# [:static, "Hello "],
|
67
|
+
# [:mustache, :etag, "name"],
|
68
|
+
# [:static, "\nYou have just won $"],
|
69
|
+
# [:mustache, :etag, "value"],
|
70
|
+
# [:static, "!\n"],
|
71
|
+
# [:mustache,
|
72
|
+
# :section,
|
73
|
+
# "in_ca",
|
74
|
+
# [:multi,
|
75
|
+
# [:static, "Well, $"],
|
76
|
+
# [:mustache, :etag, "taxed_value"],
|
77
|
+
# [:static, ", after taxes.\n"]]]]
|
78
|
+
def compile!(exp)
|
79
|
+
case exp.first
|
80
|
+
when :multi
|
81
|
+
exp[1..-1].map { |e| compile!(e) }.join
|
82
|
+
when :static
|
83
|
+
str(exp[1])
|
84
|
+
when :mustache
|
85
|
+
send("on_#{exp[1]}", *exp[2..-1])
|
86
|
+
else
|
87
|
+
raise "Unhandled exp: #{exp.first}"
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
# Callback fired when the compiler finds a section token. We're
|
92
|
+
# passed the section name and the array of tokens.
|
93
|
+
def on_section(name, content)
|
94
|
+
# Convert the tokenized content of this section into a Ruby
|
95
|
+
# string we can use.
|
96
|
+
code = compile(content)
|
97
|
+
|
98
|
+
# Compile the Ruby for this section now that we know what's
|
99
|
+
# inside the section.
|
100
|
+
ev(<<-compiled)
|
101
|
+
if v = ctx[#{name.to_sym.inspect}]
|
102
|
+
if v == true
|
103
|
+
#{code}
|
104
|
+
elsif v.is_a?(Proc)
|
105
|
+
v.call(#{code})
|
106
|
+
else
|
107
|
+
# Shortcut when passed non-array
|
108
|
+
v = [v] if v.respond_to?(:has_key?) || !v.respond_to?(:map)
|
109
|
+
|
110
|
+
v.map { |h| ctx.push(h); r = #{code}; ctx.pop; r }.join
|
111
|
+
end
|
112
|
+
end
|
113
|
+
compiled
|
114
|
+
end
|
115
|
+
|
116
|
+
# Fired when we find an inverted section. Just like `on_section`,
|
117
|
+
# we're passed the inverted section name and the array of tokens.
|
118
|
+
def on_inverted_section(name, content)
|
119
|
+
# Convert the tokenized content of this section into a Ruby
|
120
|
+
# string we can use.
|
121
|
+
code = compile(content)
|
122
|
+
|
123
|
+
# Compile the Ruby for this inverted section now that we know
|
124
|
+
# what's inside.
|
125
|
+
ev(<<-compiled)
|
126
|
+
v = ctx[#{name.to_sym.inspect}]
|
127
|
+
if v.nil? || v == false || v.respond_to?(:empty?) && v.empty?
|
128
|
+
#{code}
|
129
|
+
end
|
130
|
+
compiled
|
131
|
+
end
|
132
|
+
|
133
|
+
# Fired when the compiler finds a partial. We want to return code
|
134
|
+
# which calls a partial at runtime instead of expanding and
|
135
|
+
# including the partial's body to allow for recursive partials.
|
136
|
+
def on_partial(name)
|
137
|
+
ev("ctx.partial(#{name.to_sym.inspect})")
|
138
|
+
end
|
139
|
+
|
140
|
+
# An unescaped tag.
|
141
|
+
def on_utag(name)
|
142
|
+
ev("ctx[#{name.to_sym.inspect}]")
|
143
|
+
end
|
144
|
+
|
145
|
+
# An escaped tag.
|
146
|
+
def on_etag(name)
|
147
|
+
ev("CGI.escapeHTML(ctx[#{name.to_sym.inspect}].to_s)")
|
148
|
+
end
|
149
|
+
|
150
|
+
# An interpolation-friendly version of a string, for use within a
|
151
|
+
# Ruby string.
|
152
|
+
def ev(s)
|
153
|
+
"#\{#{s}}"
|
154
|
+
end
|
155
|
+
|
156
|
+
def str(s)
|
157
|
+
s.inspect[1..-2]
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
@@ -0,0 +1,230 @@
|
|
1
|
+
require 'strscan'
|
2
|
+
|
3
|
+
class Mustache
|
4
|
+
# The Parser is responsible for taking a string template and
|
5
|
+
# converting it into an array of tokens and, really, expressions. It
|
6
|
+
# raises SyntaxError if there is anything it doesn't understand and
|
7
|
+
# knows which sigil corresponds to which tag type.
|
8
|
+
#
|
9
|
+
# For example, given this template:
|
10
|
+
#
|
11
|
+
# Hi {{thing}}!
|
12
|
+
#
|
13
|
+
# Run through the Parser we'll get these tokens:
|
14
|
+
#
|
15
|
+
# [:multi,
|
16
|
+
# [:static, "Hi "],
|
17
|
+
# [:mustache, :etag, "thing"],
|
18
|
+
# [:static, "!\n"]]
|
19
|
+
#
|
20
|
+
# You can see the array of tokens for any template with the
|
21
|
+
# mustache(1) command line tool:
|
22
|
+
#
|
23
|
+
# $ mustache --tokens test.mustache
|
24
|
+
# [:multi, [:static, "Hi "], [:mustache, :etag, "thing"], [:static, "!\n"]]
|
25
|
+
class Parser
|
26
|
+
# A SyntaxError is raised when the Parser comes across unclosed
|
27
|
+
# tags, sections, illegal content in tags, or anything of that
|
28
|
+
# sort.
|
29
|
+
class SyntaxError < StandardError
|
30
|
+
def initialize(message, position)
|
31
|
+
@message = message
|
32
|
+
@lineno, @column, @line = position
|
33
|
+
@stripped_line = @line.strip
|
34
|
+
@stripped_column = @column - (@line.size - @line.lstrip.size)
|
35
|
+
end
|
36
|
+
|
37
|
+
def to_s
|
38
|
+
<<-EOF
|
39
|
+
#{@message}
|
40
|
+
Line #{@lineno}
|
41
|
+
#{@stripped_line}
|
42
|
+
#{' ' * @stripped_column}^
|
43
|
+
EOF
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# After these types of tags, all whitespace will be skipped.
|
48
|
+
SKIP_WHITESPACE = [ '#', '^', '/' ]
|
49
|
+
|
50
|
+
# The content allowed in a tag name.
|
51
|
+
ALLOWED_CONTENT = /(\w|[?!\/-])*/
|
52
|
+
|
53
|
+
# These types of tags allow any content,
|
54
|
+
# the rest only allow ALLOWED_CONTENT.
|
55
|
+
ANY_CONTENT = [ '!', '=' ]
|
56
|
+
|
57
|
+
attr_reader :scanner, :result
|
58
|
+
attr_writer :otag, :ctag
|
59
|
+
|
60
|
+
# Accepts an options hash which does nothing but may be used in
|
61
|
+
# the future.
|
62
|
+
def initialize(options = {})
|
63
|
+
@options = {}
|
64
|
+
end
|
65
|
+
|
66
|
+
# The opening tag delimiter. This may be changed at runtime.
|
67
|
+
def otag
|
68
|
+
@otag ||= '{{'
|
69
|
+
end
|
70
|
+
|
71
|
+
# The closing tag delimiter. This too may be changed at runtime.
|
72
|
+
def ctag
|
73
|
+
@ctag ||= '}}'
|
74
|
+
end
|
75
|
+
|
76
|
+
# Given a string template, returns an array of tokens.
|
77
|
+
def compile(template)
|
78
|
+
if template.respond_to?(:encoding)
|
79
|
+
@encoding = template.encoding
|
80
|
+
template = template.dup.force_encoding("BINARY")
|
81
|
+
else
|
82
|
+
@encoding = nil
|
83
|
+
end
|
84
|
+
|
85
|
+
# Keeps information about opened sections.
|
86
|
+
@sections = []
|
87
|
+
@result = [:multi]
|
88
|
+
@scanner = StringScanner.new(template)
|
89
|
+
|
90
|
+
# Scan until the end of the template.
|
91
|
+
until @scanner.eos?
|
92
|
+
scan_tags || scan_text
|
93
|
+
end
|
94
|
+
|
95
|
+
if !@sections.empty?
|
96
|
+
# We have parsed the whole file, but there's still opened sections.
|
97
|
+
type, pos, result = @sections.pop
|
98
|
+
error "Unclosed section #{type.inspect}", pos
|
99
|
+
end
|
100
|
+
|
101
|
+
@result
|
102
|
+
end
|
103
|
+
|
104
|
+
# Find {{mustaches}} and add them to the @result array.
|
105
|
+
def scan_tags
|
106
|
+
# Scan until we hit an opening delimiter.
|
107
|
+
return unless @scanner.scan(regexp(otag))
|
108
|
+
|
109
|
+
# Since {{= rewrites ctag, we store the ctag which should be used
|
110
|
+
# when parsing this specific tag.
|
111
|
+
current_ctag = self.ctag
|
112
|
+
type = @scanner.scan(/#|\^|\/|=|!|<|>|&|\{/)
|
113
|
+
@scanner.skip(/\s*/)
|
114
|
+
|
115
|
+
# ANY_CONTENT tags allow any character inside of them, while
|
116
|
+
# other tags (such as variables) are more strict.
|
117
|
+
if ANY_CONTENT.include?(type)
|
118
|
+
r = /\s*#{regexp(type)}?#{regexp(current_ctag)}/
|
119
|
+
content = scan_until_exclusive(r)
|
120
|
+
else
|
121
|
+
content = @scanner.scan(ALLOWED_CONTENT)
|
122
|
+
end
|
123
|
+
|
124
|
+
# We found {{ but we can't figure out what's going on inside.
|
125
|
+
error "Illegal content in tag" if content.empty?
|
126
|
+
|
127
|
+
# Based on the sigil, do what needs to be done.
|
128
|
+
case type
|
129
|
+
when '#'
|
130
|
+
block = [:multi]
|
131
|
+
@result << [:mustache, :section, content, block]
|
132
|
+
@sections << [content, position, @result]
|
133
|
+
@result = block
|
134
|
+
when '^'
|
135
|
+
block = [:multi]
|
136
|
+
@result << [:mustache, :inverted_section, content, block]
|
137
|
+
@sections << [content, position, @result]
|
138
|
+
@result = block
|
139
|
+
when '/'
|
140
|
+
section, pos, result = @sections.pop
|
141
|
+
@result = result
|
142
|
+
|
143
|
+
if section.nil?
|
144
|
+
error "Closing unopened #{content.inspect}"
|
145
|
+
elsif section != content
|
146
|
+
error "Unclosed section #{section.inspect}", pos
|
147
|
+
end
|
148
|
+
when '!'
|
149
|
+
# ignore comments
|
150
|
+
when '='
|
151
|
+
self.otag, self.ctag = content.split(' ', 2)
|
152
|
+
when '>', '<'
|
153
|
+
@result << [:mustache, :partial, content]
|
154
|
+
when '{', '&'
|
155
|
+
# The closing } in unescaped tags is just a hack for
|
156
|
+
# aesthetics.
|
157
|
+
type = "}" if type == "{"
|
158
|
+
@result << [:mustache, :utag, content]
|
159
|
+
else
|
160
|
+
@result << [:mustache, :etag, content]
|
161
|
+
end
|
162
|
+
|
163
|
+
# Skip whitespace and any balancing sigils after the content
|
164
|
+
# inside this tag.
|
165
|
+
@scanner.skip(/\s+/)
|
166
|
+
@scanner.skip(regexp(type)) if type
|
167
|
+
|
168
|
+
# Try to find the closing tag.
|
169
|
+
unless close = @scanner.scan(regexp(current_ctag))
|
170
|
+
error "Unclosed tag"
|
171
|
+
end
|
172
|
+
|
173
|
+
# Skip whitespace following this tag if we need to.
|
174
|
+
@scanner.skip(/\s+/) if SKIP_WHITESPACE.include?(type)
|
175
|
+
end
|
176
|
+
|
177
|
+
# Try to find static text, e.g. raw HTML with no {{mustaches}}.
|
178
|
+
def scan_text
|
179
|
+
text = scan_until_exclusive(regexp(otag))
|
180
|
+
|
181
|
+
if text.nil?
|
182
|
+
# Couldn't find any otag, which means the rest is just static text.
|
183
|
+
text = @scanner.rest
|
184
|
+
# Mark as done.
|
185
|
+
@scanner.clear
|
186
|
+
end
|
187
|
+
|
188
|
+
text.force_encoding(@encoding) if @encoding
|
189
|
+
|
190
|
+
@result << [:static, text]
|
191
|
+
end
|
192
|
+
|
193
|
+
# Scans the string until the pattern is matched. Returns the substring
|
194
|
+
# *excluding* the end of the match, advancing the scan pointer to that
|
195
|
+
# location. If there is no match, nil is returned.
|
196
|
+
def scan_until_exclusive(regexp)
|
197
|
+
pos = @scanner.pos
|
198
|
+
if @scanner.scan_until(regexp)
|
199
|
+
@scanner.pos -= @scanner.matched.size
|
200
|
+
@scanner.pre_match[pos..-1]
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
# Returns [lineno, column, line]
|
205
|
+
def position
|
206
|
+
# The rest of the current line
|
207
|
+
rest = @scanner.check_until(/\n|\Z/).to_s.chomp
|
208
|
+
|
209
|
+
# What we have parsed so far
|
210
|
+
parsed = @scanner.string[0...@scanner.pos]
|
211
|
+
|
212
|
+
lines = parsed.split("\n")
|
213
|
+
|
214
|
+
[ lines.size, lines.last.size - 1, lines.last + rest ]
|
215
|
+
end
|
216
|
+
|
217
|
+
# Used to quickly convert a string into a regular expression
|
218
|
+
# usable by the string scanner.
|
219
|
+
def regexp(thing)
|
220
|
+
/#{Regexp.escape(thing)}/
|
221
|
+
end
|
222
|
+
|
223
|
+
# Raises a SyntaxError. The message should be the name of the
|
224
|
+
# error - other details such as line number and position are
|
225
|
+
# handled for you.
|
226
|
+
def error(message, pos = position)
|
227
|
+
raise SyntaxError.new(message, pos)
|
228
|
+
end
|
229
|
+
end
|
230
|
+
end
|