angry_mob_common_targets 0.1.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 +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
|