fast_haml 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.
- checksums.yaml +7 -0
- data/.gitignore +16 -0
- data/.gitmodules +3 -0
- data/.rspec +2 -0
- data/.travis.yml +25 -0
- data/Appraisals +26 -0
- data/CHANGELOG.md +2 -0
- data/Gemfile +8 -0
- data/LICENSE.txt +22 -0
- data/README.md +96 -0
- data/Rakefile +11 -0
- data/benchmark/rendering.rb +29 -0
- data/bin/fast_haml +4 -0
- data/ext/attribute_builder/attribute_builder.c +259 -0
- data/ext/attribute_builder/extconf.rb +3 -0
- data/fast_haml.gemspec +35 -0
- data/gemfiles/rails_4.0.gemfile +9 -0
- data/gemfiles/rails_4.1.gemfile +9 -0
- data/gemfiles/rails_4.2.gemfile +9 -0
- data/gemfiles/rails_edge.gemfile +10 -0
- data/haml_spec_test.rb +22 -0
- data/lib/fast_haml/ast.rb +112 -0
- data/lib/fast_haml/cli.rb +38 -0
- data/lib/fast_haml/compiler.rb +325 -0
- data/lib/fast_haml/element_parser.rb +288 -0
- data/lib/fast_haml/engine.rb +32 -0
- data/lib/fast_haml/filter_compilers/base.rb +29 -0
- data/lib/fast_haml/filter_compilers/cdata.rb +15 -0
- data/lib/fast_haml/filter_compilers/css.rb +15 -0
- data/lib/fast_haml/filter_compilers/escaped.rb +22 -0
- data/lib/fast_haml/filter_compilers/javascript.rb +15 -0
- data/lib/fast_haml/filter_compilers/plain.rb +17 -0
- data/lib/fast_haml/filter_compilers/preserve.rb +30 -0
- data/lib/fast_haml/filter_compilers/ruby.rb +13 -0
- data/lib/fast_haml/filter_compilers.rb +37 -0
- data/lib/fast_haml/filter_parser.rb +54 -0
- data/lib/fast_haml/html.rb +44 -0
- data/lib/fast_haml/indent_tracker.rb +84 -0
- data/lib/fast_haml/line_parser.rb +66 -0
- data/lib/fast_haml/parser.rb +251 -0
- data/lib/fast_haml/parser_utils.rb +17 -0
- data/lib/fast_haml/rails_handler.rb +10 -0
- data/lib/fast_haml/railtie.rb +8 -0
- data/lib/fast_haml/ruby_multiline.rb +23 -0
- data/lib/fast_haml/static_hash_parser.rb +113 -0
- data/lib/fast_haml/syntax_error.rb +10 -0
- data/lib/fast_haml/text_compiler.rb +69 -0
- data/lib/fast_haml/tilt.rb +16 -0
- data/lib/fast_haml/version.rb +3 -0
- data/lib/fast_haml.rb +10 -0
- data/spec/rails/Rakefile +6 -0
- data/spec/rails/app/assets/images/.keep +0 -0
- data/spec/rails/app/assets/javascripts/application.js +13 -0
- data/spec/rails/app/assets/stylesheets/application.css +15 -0
- data/spec/rails/app/controllers/application_controller.rb +5 -0
- data/spec/rails/app/controllers/books_controller.rb +8 -0
- data/spec/rails/app/controllers/concerns/.keep +0 -0
- data/spec/rails/app/helpers/application_helper.rb +2 -0
- data/spec/rails/app/mailers/.keep +0 -0
- data/spec/rails/app/models/.keep +0 -0
- data/spec/rails/app/models/book.rb +9 -0
- data/spec/rails/app/models/concerns/.keep +0 -0
- data/spec/rails/app/views/books/hello.html.haml +2 -0
- data/spec/rails/app/views/books/with_capture.html.haml +4 -0
- data/spec/rails/app/views/books/with_variables.html.haml +4 -0
- data/spec/rails/app/views/layouts/application.html.haml +9 -0
- data/spec/rails/bin/bundle +3 -0
- data/spec/rails/bin/rails +4 -0
- data/spec/rails/bin/rake +4 -0
- data/spec/rails/bin/setup +29 -0
- data/spec/rails/config/application.rb +12 -0
- data/spec/rails/config/boot.rb +3 -0
- data/spec/rails/config/database.yml +25 -0
- data/spec/rails/config/environment.rb +5 -0
- data/spec/rails/config/environments/development.rb +41 -0
- data/spec/rails/config/environments/production.rb +79 -0
- data/spec/rails/config/environments/test.rb +42 -0
- data/spec/rails/config/initializers/assets.rb +11 -0
- data/spec/rails/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/rails/config/initializers/cookies_serializer.rb +3 -0
- data/spec/rails/config/initializers/filter_parameter_logging.rb +4 -0
- data/spec/rails/config/initializers/inflections.rb +16 -0
- data/spec/rails/config/initializers/mime_types.rb +4 -0
- data/spec/rails/config/initializers/secret_key_base.rb +6 -0
- data/spec/rails/config/initializers/session_store.rb +3 -0
- data/spec/rails/config/initializers/wrap_parameters.rb +14 -0
- data/spec/rails/config/locales/en.yml +23 -0
- data/spec/rails/config/routes.rb +7 -0
- data/spec/rails/config/secrets.yml +22 -0
- data/spec/rails/config.ru +4 -0
- data/spec/rails/db/seeds.rb +7 -0
- data/spec/rails/lib/assets/.keep +0 -0
- data/spec/rails/lib/tasks/.keep +0 -0
- data/spec/rails/log/.keep +0 -0
- data/spec/rails/public/404.html +67 -0
- data/spec/rails/public/422.html +67 -0
- data/spec/rails/public/500.html +66 -0
- data/spec/rails/public/favicon.ico +0 -0
- data/spec/rails/public/robots.txt +5 -0
- data/spec/rails/spec/requests/fast_haml_spec.rb +41 -0
- data/spec/rails/vendor/assets/javascripts/.keep +0 -0
- data/spec/rails/vendor/assets/stylesheets/.keep +0 -0
- data/spec/rails_helper.rb +4 -0
- data/spec/render/attribute_spec.rb +209 -0
- data/spec/render/comment_spec.rb +61 -0
- data/spec/render/doctype_spec.rb +62 -0
- data/spec/render/element_spec.rb +165 -0
- data/spec/render/filters/cdata_spec.rb +12 -0
- data/spec/render/filters/css_spec.rb +45 -0
- data/spec/render/filters/escaped_spec.rb +14 -0
- data/spec/render/filters/javascript_spec.rb +44 -0
- data/spec/render/filters/plain_spec.rb +24 -0
- data/spec/render/filters/preserve_spec.rb +25 -0
- data/spec/render/filters/ruby_spec.rb +13 -0
- data/spec/render/filters_spec.rb +11 -0
- data/spec/render/haml_comment_spec.rb +24 -0
- data/spec/render/multiline_spec.rb +39 -0
- data/spec/render/plain_spec.rb +20 -0
- data/spec/render/preserve_spec.rb +8 -0
- data/spec/render/sanitize_spec.rb +36 -0
- data/spec/render/script_spec.rb +74 -0
- data/spec/render/silent_script_spec.rb +97 -0
- data/spec/render/unescape_spec.rb +40 -0
- data/spec/spec_helper.rb +49 -0
- data/spec/tilt_spec.rb +33 -0
- metadata +427 -0
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
require 'strscan'
|
|
2
|
+
require 'fast_haml/ast'
|
|
3
|
+
require 'fast_haml/parser_utils'
|
|
4
|
+
require 'fast_haml/ruby_multiline'
|
|
5
|
+
require 'fast_haml/syntax_error'
|
|
6
|
+
|
|
7
|
+
module FastHaml
|
|
8
|
+
class ElementParser
|
|
9
|
+
def initialize(text, lineno, line_parser)
|
|
10
|
+
@text = text
|
|
11
|
+
@lineno = lineno
|
|
12
|
+
@line_parser = line_parser
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
ELEMENT_REGEXP = /\A%([-:\w]+)([-:\w.#]*)(.+)?\z/o
|
|
16
|
+
|
|
17
|
+
def parse
|
|
18
|
+
m = @text.match(ELEMENT_REGEXP)
|
|
19
|
+
unless m
|
|
20
|
+
syntax_error!('Invalid element declaration')
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
element = Ast::Element.new
|
|
24
|
+
element.tag_name = m[1]
|
|
25
|
+
element.static_class, element.static_id = parse_class_and_id(m[2])
|
|
26
|
+
rest = m[3] || ''
|
|
27
|
+
|
|
28
|
+
element.attributes, rest = parse_attributes(rest.lstrip)
|
|
29
|
+
element.nuke_inner_whitespace, element.nuke_outer_whitespace, rest = parse_nuke_whitespace(rest)
|
|
30
|
+
element.self_closing, rest = parse_self_closing(rest)
|
|
31
|
+
element.oneline_child = parse_oneline_child(rest)
|
|
32
|
+
|
|
33
|
+
element
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
def parse_class_and_id(class_and_id)
|
|
39
|
+
classes = []
|
|
40
|
+
id = ''
|
|
41
|
+
class_and_id.scan(/([#.])([-:_a-zA-Z0-9]+)/) do |type, prop|
|
|
42
|
+
case type
|
|
43
|
+
when '.'
|
|
44
|
+
classes << prop
|
|
45
|
+
when '#'
|
|
46
|
+
id = prop
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
[classes.join(' '), id]
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
OLD_ATTRIBUTE_BEGIN = '{'
|
|
54
|
+
NEW_ATTRIBUTE_BEGIN = '('
|
|
55
|
+
|
|
56
|
+
def parse_attributes(rest)
|
|
57
|
+
old_attributes = ''
|
|
58
|
+
new_attributes = ''
|
|
59
|
+
|
|
60
|
+
loop do
|
|
61
|
+
case rest[0]
|
|
62
|
+
when OLD_ATTRIBUTE_BEGIN
|
|
63
|
+
unless old_attributes.empty?
|
|
64
|
+
break
|
|
65
|
+
end
|
|
66
|
+
old_attributes, rest = parse_old_attributes(rest)
|
|
67
|
+
when NEW_ATTRIBUTE_BEGIN
|
|
68
|
+
unless new_attributes.empty?
|
|
69
|
+
break
|
|
70
|
+
end
|
|
71
|
+
new_attributes, rest = parse_new_attributes(rest)
|
|
72
|
+
else
|
|
73
|
+
break
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
attributes = old_attributes
|
|
78
|
+
unless new_attributes.empty?
|
|
79
|
+
t = to_old_syntax(new_attributes)
|
|
80
|
+
if attributes.empty?
|
|
81
|
+
attributes = t
|
|
82
|
+
else
|
|
83
|
+
attributes << ", " << t
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
[attributes, rest]
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def parse_old_attributes(text)
|
|
90
|
+
text = text.dup
|
|
91
|
+
s = StringScanner.new(text)
|
|
92
|
+
s.pos = 1
|
|
93
|
+
depth = 1
|
|
94
|
+
loop do
|
|
95
|
+
depth = ParserUtils.balance(s, '{', '}')
|
|
96
|
+
if depth == 0
|
|
97
|
+
attr = s.pre_match + s.matched
|
|
98
|
+
return [attr[1, attr.size-2], s.rest.lstrip]
|
|
99
|
+
else
|
|
100
|
+
if text[-1] == ',' && @line_parser.has_next?
|
|
101
|
+
text << @line_parser.next_line
|
|
102
|
+
else
|
|
103
|
+
syntax_error!('Unmatched brace')
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def parse_new_attributes(text)
|
|
110
|
+
text = text.dup
|
|
111
|
+
s = StringScanner.new(text)
|
|
112
|
+
s.pos = 1
|
|
113
|
+
depth = 1
|
|
114
|
+
new_attributes = []
|
|
115
|
+
loop do
|
|
116
|
+
pre_pos = s.pos
|
|
117
|
+
depth = ParserUtils.balance(s, '(', ')', depth)
|
|
118
|
+
if depth == 0
|
|
119
|
+
t = s.string.byteslice(pre_pos ... s.pos-1)
|
|
120
|
+
new_attributes.concat(parse_new_attribute_list(t))
|
|
121
|
+
return [new_attributes, s.rest.lstrip]
|
|
122
|
+
else
|
|
123
|
+
if @line_parser.has_next?
|
|
124
|
+
text << ' ' << @line_parser.next_line
|
|
125
|
+
else
|
|
126
|
+
syntax_error!('Unmatched paren')
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def parse_new_attribute_list(text)
|
|
133
|
+
s = StringScanner.new(text)
|
|
134
|
+
list = []
|
|
135
|
+
until s.eos?
|
|
136
|
+
name = scan_key(s)
|
|
137
|
+
s.skip(/\s*/)
|
|
138
|
+
|
|
139
|
+
if scan_operator(s)
|
|
140
|
+
s.skip(/\s*/)
|
|
141
|
+
value = scan_value(s)
|
|
142
|
+
else
|
|
143
|
+
value = 'true'
|
|
144
|
+
end
|
|
145
|
+
s.skip(/\s*/)
|
|
146
|
+
|
|
147
|
+
list << [name, value]
|
|
148
|
+
end
|
|
149
|
+
list
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def scan_key(scanner)
|
|
153
|
+
scanner.scan(/[-:\w]+/).tap do |name|
|
|
154
|
+
unless name
|
|
155
|
+
syntax_error!('Invalid attribute list (missing attributename)')
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def scan_operator(scanner)
|
|
161
|
+
scanner.skip(/=/)
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def scan_value(scanner)
|
|
165
|
+
if quote = scanner.scan(/["']/)
|
|
166
|
+
scan_quoted_value(scanner, quote)
|
|
167
|
+
else
|
|
168
|
+
scan_variable_value(scanner)
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def scan_quoted_value(scanner, quote)
|
|
173
|
+
re = /((?:\\.|\#(?!\{)|[^#{quote}\\#])*)(#{quote}|#\{)/
|
|
174
|
+
pos = scanner.pos
|
|
175
|
+
loop do
|
|
176
|
+
unless scanner.scan(re)
|
|
177
|
+
syntax_error!('Invalid attribute list (mismatched quotation)')
|
|
178
|
+
end
|
|
179
|
+
if scanner[2] == quote
|
|
180
|
+
break
|
|
181
|
+
end
|
|
182
|
+
depth = ParserUtils.balance(scanner, '{', '}')
|
|
183
|
+
if depth != 0
|
|
184
|
+
syntax_error!('Invalid attribute list (mismatched interpolation)')
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
str = scanner.string.byteslice(pos-1 .. scanner.pos-1)
|
|
188
|
+
|
|
189
|
+
# Even if the quote is single, string interpolation is performed in Haml.
|
|
190
|
+
str[0] = '"'
|
|
191
|
+
str[-1] = '"'
|
|
192
|
+
str
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def scan_variable_value(scanner)
|
|
196
|
+
scanner.scan(/(@@?|\$)?\w+/).tap do |var|
|
|
197
|
+
unless var
|
|
198
|
+
syntax_error!('Invalid attribute list (invalid variable name)')
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def to_old_syntax(new_attributes)
|
|
204
|
+
new_attributes.map { |k, v| "#{k.inspect} => #{v}" }.join(', ')
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def parse_nuke_whitespace(rest)
|
|
208
|
+
m = rest.match(/\A(><|<>|[><])(.*)\z/)
|
|
209
|
+
if m
|
|
210
|
+
nuke_whitespace = m[1]
|
|
211
|
+
[
|
|
212
|
+
nuke_whitespace.include?('<'),
|
|
213
|
+
nuke_whitespace.include?('>'),
|
|
214
|
+
m[2],
|
|
215
|
+
]
|
|
216
|
+
else
|
|
217
|
+
[false, false, rest]
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def parse_self_closing(rest)
|
|
222
|
+
if rest[0] == '/'
|
|
223
|
+
if rest.size > 1
|
|
224
|
+
syntax_error!("Self-closing tags can't have content")
|
|
225
|
+
end
|
|
226
|
+
[true, '']
|
|
227
|
+
else
|
|
228
|
+
[false, rest]
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
def parse_oneline_child(rest)
|
|
233
|
+
case rest[0]
|
|
234
|
+
when '=', '~'
|
|
235
|
+
if rest[1] == '='
|
|
236
|
+
Ast::Text.new(rest[2 .. -1].strip)
|
|
237
|
+
else
|
|
238
|
+
script = rest[1 .. -1].lstrip
|
|
239
|
+
if script.empty?
|
|
240
|
+
syntax_error!('No Ruby code to evaluate')
|
|
241
|
+
end
|
|
242
|
+
script += RubyMultiline.read(@line_parser, script)
|
|
243
|
+
Ast::Script.new([], script)
|
|
244
|
+
end
|
|
245
|
+
when '&'
|
|
246
|
+
case
|
|
247
|
+
when rest.start_with?('&==')
|
|
248
|
+
Ast::Text.new(rest[3 .. -1].lstrip)
|
|
249
|
+
when rest[1] == '=' || rest[1] == '~'
|
|
250
|
+
script = rest[2 .. -1].lstrip
|
|
251
|
+
if script.empty?
|
|
252
|
+
syntax_error!('No Ruby code to evaluate')
|
|
253
|
+
end
|
|
254
|
+
script += RubyMultiline.read(@line_parser, script)
|
|
255
|
+
Ast::Script.new([], script, true, rest[1] == '~')
|
|
256
|
+
else
|
|
257
|
+
Ast::Text.new(rest[1 .. -1].strip)
|
|
258
|
+
end
|
|
259
|
+
when '!'
|
|
260
|
+
case
|
|
261
|
+
when rest.start_with?('!==')
|
|
262
|
+
Ast::Text.new(rest[3 .. -1].lstrip, false)
|
|
263
|
+
when rest[1] == '=' || rest[1] == '~'
|
|
264
|
+
script = rest[2 .. -1].lstrip
|
|
265
|
+
if script.empty?
|
|
266
|
+
syntax_error!('No Ruby code to evaluate')
|
|
267
|
+
end
|
|
268
|
+
script += RubyMultiline.read(@line_parser, script)
|
|
269
|
+
Ast::Script.new([], script, false, rest[1] == '~')
|
|
270
|
+
Ast::Script.new([], script, false, true)
|
|
271
|
+
else
|
|
272
|
+
Ast::Text.new(rest[1 .. -1].lstrip, false)
|
|
273
|
+
end
|
|
274
|
+
else
|
|
275
|
+
rest = rest.lstrip
|
|
276
|
+
if rest.empty?
|
|
277
|
+
nil
|
|
278
|
+
else
|
|
279
|
+
Ast::Text.new(rest)
|
|
280
|
+
end
|
|
281
|
+
end
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
def syntax_error!(message)
|
|
285
|
+
raise SyntaxError.new(message, @lineno)
|
|
286
|
+
end
|
|
287
|
+
end
|
|
288
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
require 'temple'
|
|
2
|
+
require 'fast_haml/compiler'
|
|
3
|
+
require 'fast_haml/html'
|
|
4
|
+
require 'fast_haml/parser'
|
|
5
|
+
|
|
6
|
+
module FastHaml
|
|
7
|
+
class Engine < Temple::Engine
|
|
8
|
+
define_options(
|
|
9
|
+
generator: Temple::Generators::ArrayBuffer,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
DEFAULT_OPTIONS = {
|
|
13
|
+
format: :html,
|
|
14
|
+
attr_quote: "'",
|
|
15
|
+
}.freeze
|
|
16
|
+
|
|
17
|
+
def initialize(opts = {})
|
|
18
|
+
super(DEFAULT_OPTIONS.merge(opts))
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
use Parser
|
|
22
|
+
use Compiler
|
|
23
|
+
use Html
|
|
24
|
+
filter :Escapable
|
|
25
|
+
filter :ControlFlow
|
|
26
|
+
filter :MultiFlattener
|
|
27
|
+
filter :StaticMerger
|
|
28
|
+
use :Generator do
|
|
29
|
+
options[:generator].new(options.to_hash.reject {|k,v| !options[:generator].options.valid_key?(k) })
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
require 'fast_haml/text_compiler'
|
|
2
|
+
|
|
3
|
+
module FastHaml
|
|
4
|
+
module FilterCompilers
|
|
5
|
+
class Base
|
|
6
|
+
protected
|
|
7
|
+
|
|
8
|
+
def compile_texts(temple, texts, tab_width: 0)
|
|
9
|
+
tabs = ' ' * tab_width
|
|
10
|
+
texts.each do |text|
|
|
11
|
+
temple << [:static, tabs] << text_compiler.compile(text) << [:static, "\n"]
|
|
12
|
+
end
|
|
13
|
+
nil
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def text_compiler
|
|
17
|
+
@text_compiler ||= TextCompiler.new(escape_html: false)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def strip_last_empty_lines(texts)
|
|
21
|
+
texts = texts.dup
|
|
22
|
+
while texts.last && texts.last.empty?
|
|
23
|
+
texts.pop
|
|
24
|
+
end
|
|
25
|
+
texts
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
require 'fast_haml/filter_compilers/base'
|
|
2
|
+
|
|
3
|
+
module FastHaml
|
|
4
|
+
module FilterCompilers
|
|
5
|
+
class Cdata < Base
|
|
6
|
+
def compile(texts)
|
|
7
|
+
temple = [:multi, [:static, "<![CDATA[\n"]]
|
|
8
|
+
compile_texts(temple, strip_last_empty_lines(texts), tab_width: 4)
|
|
9
|
+
temple << [:static, "]]>"]
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
register(:cdata, Cdata)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
require 'fast_haml/filter_compilers/base'
|
|
2
|
+
|
|
3
|
+
module FastHaml
|
|
4
|
+
module FilterCompilers
|
|
5
|
+
class Css < Base
|
|
6
|
+
def compile(texts)
|
|
7
|
+
temple = [:multi, [:static, "\n"]]
|
|
8
|
+
compile_texts(temple, strip_last_empty_lines(texts), tab_width: 2)
|
|
9
|
+
[:haml, :tag, 'style', false, [:html, :attrs], temple]
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
register(:css, Css)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
require 'fast_haml/filter_compilers/base'
|
|
2
|
+
|
|
3
|
+
module FastHaml
|
|
4
|
+
module FilterCompilers
|
|
5
|
+
class Escaped < Base
|
|
6
|
+
include Temple::Utils
|
|
7
|
+
|
|
8
|
+
def compile(texts)
|
|
9
|
+
temple = [:multi]
|
|
10
|
+
compile_texts(temple, strip_last_empty_lines(texts))
|
|
11
|
+
escape_code = Temple::Filters::Escapable.new.instance_variable_get(:@escape_code)
|
|
12
|
+
sym = unique_name
|
|
13
|
+
[:multi,
|
|
14
|
+
[:capture, sym, temple],
|
|
15
|
+
[:dynamic, escape_code % sym],
|
|
16
|
+
]
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
register(:escaped, Escaped)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
require 'fast_haml/filter_compilers/base'
|
|
2
|
+
|
|
3
|
+
module FastHaml
|
|
4
|
+
module FilterCompilers
|
|
5
|
+
class Javascript < Base
|
|
6
|
+
def compile(texts)
|
|
7
|
+
temple = [:multi, [:static, "\n"]]
|
|
8
|
+
compile_texts(temple, strip_last_empty_lines(texts), tab_width: 2)
|
|
9
|
+
[:haml, :tag, 'script', false, [:html, :attrs], [:html, :js, temple]]
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
register(:javascript, Javascript)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
require 'fast_haml/filter_compilers/base'
|
|
2
|
+
|
|
3
|
+
module FastHaml
|
|
4
|
+
module FilterCompilers
|
|
5
|
+
class Plain < Base
|
|
6
|
+
def compile(texts)
|
|
7
|
+
temple = [:multi]
|
|
8
|
+
texts = strip_last_empty_lines(texts)
|
|
9
|
+
compile_texts(temple, texts[0 .. -2])
|
|
10
|
+
temple << text_compiler.compile(texts[-1])
|
|
11
|
+
temple
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
register(:plain, Plain)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
require 'fast_haml/filter_compilers/base'
|
|
2
|
+
|
|
3
|
+
module FastHaml
|
|
4
|
+
module FilterCompilers
|
|
5
|
+
class Preserve < Base
|
|
6
|
+
include Temple::Utils
|
|
7
|
+
|
|
8
|
+
def compile(texts)
|
|
9
|
+
temple = [:multi]
|
|
10
|
+
texts.each do |text|
|
|
11
|
+
temple << text_compiler.compile(text)
|
|
12
|
+
unless texts.last.equal?(text)
|
|
13
|
+
temple << [:static, "\n"]
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
sym = unique_name
|
|
17
|
+
[:multi,
|
|
18
|
+
[:capture, sym, temple],
|
|
19
|
+
[:dynamic, "::FastHaml::FilterCompilers::Preserve.preserve(#{sym})"],
|
|
20
|
+
]
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def self.preserve(str)
|
|
24
|
+
str.gsub("\n", '
')
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
register(:preserve, Preserve)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
require 'fast_haml/filter_compilers/base'
|
|
2
|
+
|
|
3
|
+
module FastHaml
|
|
4
|
+
module FilterCompilers
|
|
5
|
+
class Ruby < Base
|
|
6
|
+
def compile(texts)
|
|
7
|
+
[:multi, [:code, strip_last_empty_lines(texts).join("\n")], [:newline]]
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
register(:ruby, Ruby)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
module FastHaml
|
|
2
|
+
module FilterCompilers
|
|
3
|
+
class NotFound < StandardError
|
|
4
|
+
attr_reader
|
|
5
|
+
|
|
6
|
+
def initialize(name)
|
|
7
|
+
super("Unable to find compiler for #{name}")
|
|
8
|
+
@name = name
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def self.compilers
|
|
13
|
+
@compilers ||= {}
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.register(name, compiler)
|
|
17
|
+
compilers[name.to_s] = compiler
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def self.find(name)
|
|
21
|
+
name = name.to_s
|
|
22
|
+
if compilers.has_key?(name.to_s)
|
|
23
|
+
compilers[name].new
|
|
24
|
+
else
|
|
25
|
+
raise NotFound.new(name)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
require 'fast_haml/filter_compilers/cdata'
|
|
32
|
+
require 'fast_haml/filter_compilers/css'
|
|
33
|
+
require 'fast_haml/filter_compilers/escaped'
|
|
34
|
+
require 'fast_haml/filter_compilers/javascript'
|
|
35
|
+
require 'fast_haml/filter_compilers/plain'
|
|
36
|
+
require 'fast_haml/filter_compilers/preserve'
|
|
37
|
+
require 'fast_haml/filter_compilers/ruby'
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
module FastHaml
|
|
2
|
+
class FilterParser
|
|
3
|
+
def initialize(indent_tracker)
|
|
4
|
+
@ast = nil
|
|
5
|
+
@indent_level = nil
|
|
6
|
+
@indent_tracker = indent_tracker
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def enabled?
|
|
10
|
+
!!@ast
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def start(name)
|
|
14
|
+
@ast = Ast::Filter.new
|
|
15
|
+
@ast.name = name
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def append(line)
|
|
19
|
+
indent, text = @indent_tracker.split(line)
|
|
20
|
+
if text.empty?
|
|
21
|
+
@ast.texts << ''
|
|
22
|
+
return
|
|
23
|
+
end
|
|
24
|
+
indent_level = indent.size
|
|
25
|
+
|
|
26
|
+
if @indent_level
|
|
27
|
+
if indent_level < @indent_level
|
|
28
|
+
# Finish filter
|
|
29
|
+
@indent_level = nil
|
|
30
|
+
ast = @ast
|
|
31
|
+
@ast = nil
|
|
32
|
+
return ast
|
|
33
|
+
end
|
|
34
|
+
else
|
|
35
|
+
if indent_level > @indent_tracker.current_level
|
|
36
|
+
# Start filter
|
|
37
|
+
@indent_level = indent_level
|
|
38
|
+
else
|
|
39
|
+
# Empty filter
|
|
40
|
+
@ast = nil
|
|
41
|
+
return nil
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
text = line[@indent_level .. -1]
|
|
46
|
+
@ast.texts << text
|
|
47
|
+
nil
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def finish
|
|
51
|
+
@ast
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
require 'fast_haml/attribute_builder'
|
|
2
|
+
|
|
3
|
+
module FastHaml
|
|
4
|
+
class Html < Temple::HTML::Fast
|
|
5
|
+
def on_haml_tag(name, self_closing, attrs, content = nil)
|
|
6
|
+
name = name.to_s
|
|
7
|
+
closed = self_closing && (!content || empty_exp?(content))
|
|
8
|
+
result = [:multi, [:static, "<#{name}"], compile(attrs)]
|
|
9
|
+
result << [:static, (closed && @format != :html ? ' /' : '') + '>']
|
|
10
|
+
result << compile(content) if content
|
|
11
|
+
result << [:static, "</#{name}>"] if !closed
|
|
12
|
+
result
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def on_haml_attrs(code)
|
|
16
|
+
[:dynamic, "::FastHaml::AttributeBuilder.build(#{options[:attr_quote].inspect}, #{code})"]
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def on_haml_attr(name, value)
|
|
20
|
+
if empty_exp?(value)
|
|
21
|
+
if @format == :html
|
|
22
|
+
[:static, " #{name}"]
|
|
23
|
+
else
|
|
24
|
+
[:static, " #{name}=#{options[:attr_quote]}#{name}#{options[:attr_quote]}"]
|
|
25
|
+
end
|
|
26
|
+
else
|
|
27
|
+
[:multi,
|
|
28
|
+
[:static, " #{name}=#{options[:attr_quote]}"],
|
|
29
|
+
compile(value),
|
|
30
|
+
[:static, options[:attr_quote]]]
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def on_haml_doctype(type)
|
|
35
|
+
compile([:html, :doctype, type])
|
|
36
|
+
rescue Temple::FilterError
|
|
37
|
+
[:multi]
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def on_haml_preserve(sym)
|
|
41
|
+
[:dynamic, "::FastHaml::Compiler.find_and_preserve(#{sym}.to_s)"]
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
module FastHaml
|
|
2
|
+
class IndentTracker
|
|
3
|
+
class IndentMismatch < StandardError
|
|
4
|
+
attr_reader :lineno
|
|
5
|
+
|
|
6
|
+
def initialize(message, lineno)
|
|
7
|
+
super("#{message} at line #{lineno}")
|
|
8
|
+
@lineno = lineno
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def initialize(on_enter: nil, on_leave: nil)
|
|
13
|
+
@indent_levels = [0]
|
|
14
|
+
@on_enter = on_enter || lambda { |level, text| }
|
|
15
|
+
@on_leave = on_leave || lambda { |level, text| }
|
|
16
|
+
@comment_level = nil
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def process(line, lineno)
|
|
20
|
+
indent, text = split(line)
|
|
21
|
+
indent_level = indent.size
|
|
22
|
+
|
|
23
|
+
unless text.empty?
|
|
24
|
+
track(indent_level, text, lineno)
|
|
25
|
+
end
|
|
26
|
+
[text, indent]
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def split(line)
|
|
30
|
+
m = line.match(/\A( *)(.*)\z/)
|
|
31
|
+
[m[1], m[2]]
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def finish
|
|
35
|
+
indent_leave(0, '', -1)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def current_level
|
|
39
|
+
@indent_levels.last
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def enter_comment!
|
|
43
|
+
@comment_level = @indent_levels[-2]
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
def track(indent_level, text, lineno)
|
|
49
|
+
if indent_level > @indent_levels.last
|
|
50
|
+
indent_enter(indent_level, text)
|
|
51
|
+
elsif indent_level < @indent_levels.last
|
|
52
|
+
indent_leave(indent_level, text, lineno)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def indent_enter(indent_level, text)
|
|
57
|
+
unless @comment_level
|
|
58
|
+
@indent_levels.push(indent_level)
|
|
59
|
+
@on_enter.call(indent_level, text)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def indent_leave(indent_level, text, lineno)
|
|
64
|
+
if @comment_level
|
|
65
|
+
if indent_level <= @comment_level
|
|
66
|
+
# finish comment mode
|
|
67
|
+
@comment_level = nil
|
|
68
|
+
else
|
|
69
|
+
# still in comment
|
|
70
|
+
return
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
while indent_level < @indent_levels.last
|
|
75
|
+
@indent_levels.pop
|
|
76
|
+
@on_leave.call(indent_level, text)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
if indent_level != @indent_levels.last
|
|
80
|
+
raise IndentMismatch.new("Unexpected indent level: #{indent_level}: indent_level=#{@indent_levels}", lineno)
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|