ezml 0.1.1
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/.DS_Store +0 -0
- data/.gitignore +11 -0
- data/.rspec +3 -0
- data/.travis.yml +5 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +149 -0
- data/LICENSE.txt +21 -0
- data/README.md +140 -0
- data/Rakefile +6 -0
- data/bin/ezml +9 -0
- data/examples/helloworld.ezml +44 -0
- data/examples/helloworld.html +61 -0
- data/examples/template.ezml +69 -0
- data/examples/template.html +102 -0
- data/ezml.gemspec +40 -0
- data/lib/ezml.rb +8 -0
- data/lib/ezml/attribute_builder.rb +141 -0
- data/lib/ezml/attribute_compiler.rb +172 -0
- data/lib/ezml/attribute_parser.rb +133 -0
- data/lib/ezml/buffer.rb +166 -0
- data/lib/ezml/compiler.rb +322 -0
- data/lib/ezml/engine.rb +107 -0
- data/lib/ezml/error.rb +59 -0
- data/lib/ezml/escapable.rb +46 -0
- data/lib/ezml/exec.rb +348 -0
- data/lib/ezml/filters.rb +249 -0
- data/lib/ezml/generator.rb +38 -0
- data/lib/ezml/helpers.rb +299 -0
- data/lib/ezml/options.rb +142 -0
- data/lib/ezml/parser.rb +826 -0
- data/lib/ezml/template_engine.rb +106 -0
- data/lib/ezml/temple_line_counter.rb +26 -0
- data/lib/ezml/util.rb +156 -0
- data/lib/ezml/version.rb +3 -0
- metadata +199 -0
@@ -0,0 +1,38 @@
|
|
1
|
+
module EZML
|
2
|
+
class Generator
|
3
|
+
include Temple::Mixins::CompiledDispatcher
|
4
|
+
include Temple::Mixins::Options
|
5
|
+
|
6
|
+
define_options freeze_static: RUBY_VERSION >= '2.1'
|
7
|
+
|
8
|
+
def call(exp)
|
9
|
+
compile(exp)
|
10
|
+
end
|
11
|
+
|
12
|
+
def on_multi(*exp)
|
13
|
+
exp.map { |e| compile(e) }.join('; ')
|
14
|
+
end
|
15
|
+
|
16
|
+
def on_static(text)
|
17
|
+
concat(options[:freeze_static] ? "#{Util.inspect_obj(text)}.freeze" : Util.inspect_obj(text))
|
18
|
+
end
|
19
|
+
|
20
|
+
def on_dynamic(code)
|
21
|
+
concat(code)
|
22
|
+
end
|
23
|
+
|
24
|
+
def on_code(exp)
|
25
|
+
exp
|
26
|
+
end
|
27
|
+
|
28
|
+
def on_newline
|
29
|
+
"\n"
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def concat(str)
|
35
|
+
"_ezmlout.buffer << (#{str});"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
data/lib/ezml/helpers.rb
ADDED
@@ -0,0 +1,299 @@
|
|
1
|
+
require 'erb'
|
2
|
+
|
3
|
+
module EZML
|
4
|
+
module Helpers
|
5
|
+
|
6
|
+
class ErrorReturn
|
7
|
+
def initialize(method)
|
8
|
+
@message = <<MESSAGE
|
9
|
+
#{method} outputs directly to the EZML template.
|
10
|
+
Disregard its return value and use the - operator,
|
11
|
+
or use capture_ezml to get the value as a String.
|
12
|
+
MESSAGE
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_s
|
16
|
+
raise EZML::Error.new(@message)
|
17
|
+
rescue EZML::Error => e
|
18
|
+
e.backtrace.shift
|
19
|
+
|
20
|
+
if e.backtrace.first =~ /^\(eval\):\d+:in `format_script/
|
21
|
+
e.backtrace.shift
|
22
|
+
e.backtrace.first.gsub!(/^\(ezml\):(\d+)/) {|s| "(ezml):#{$1.to_i - 1}"}
|
23
|
+
end
|
24
|
+
raise e
|
25
|
+
end
|
26
|
+
|
27
|
+
def inspect
|
28
|
+
"EZML::Helpers::ErrorReturn(#{@message.inspect})"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
self.extend self
|
33
|
+
|
34
|
+
@@action_view_defined = false
|
35
|
+
|
36
|
+
def self.action_view?
|
37
|
+
@@action_view_defined
|
38
|
+
end
|
39
|
+
|
40
|
+
def init_ezml_helpers
|
41
|
+
@ezml_buffer = EZML::Buffer.new(ezml_buffer, Options.new.for_buffer)
|
42
|
+
nil
|
43
|
+
end
|
44
|
+
|
45
|
+
def non_ezml
|
46
|
+
was_active = @ezml_buffer.active?
|
47
|
+
@ezml_buffer.active = false
|
48
|
+
yield
|
49
|
+
ensure
|
50
|
+
@ezml_buffer.active = was_active
|
51
|
+
end
|
52
|
+
|
53
|
+
def find_and_preserve(input = nil, tags = ezml_buffer.options[:preserve], &block)
|
54
|
+
return find_and_preserve(capture_ezml(&block), input || tags) if block
|
55
|
+
tags = tags.map { |tag| Regexp.escape(tag) }.join('|')
|
56
|
+
re = /<(#{tags})([^>]*)>(.*?)(<\/\1>)/im
|
57
|
+
input.to_s.gsub(re) do |s|
|
58
|
+
s =~ re # Can't rely on $1, etc. existing since Rails' SafeBuffer#gsub is incompatible
|
59
|
+
"<#{$1}#{$2}>#{preserve($3)}</#{$1}>"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def preserve(input = nil, &block)
|
64
|
+
return preserve(capture_ezml(&block)) if block
|
65
|
+
s = input.to_s.chomp("\n")
|
66
|
+
s.gsub!(/\n/, '
')
|
67
|
+
s.delete!("\r")
|
68
|
+
s
|
69
|
+
end
|
70
|
+
alias_method :flatten, :preserve
|
71
|
+
|
72
|
+
def list_of(enum, opts={}, &block)
|
73
|
+
opts_attributes = opts.map { |k, v| " #{k}='#{v}'" }.join
|
74
|
+
enum.map do |i|
|
75
|
+
result = capture_ezml(i, &block)
|
76
|
+
|
77
|
+
if result.count("\n") > 1
|
78
|
+
result.gsub!("\n", "\n ")
|
79
|
+
result = "\n #{result.strip}\n"
|
80
|
+
else
|
81
|
+
result.strip!
|
82
|
+
end
|
83
|
+
|
84
|
+
%Q!<li#{opts_attributes}>#{result}</li>!
|
85
|
+
end.join("\n")
|
86
|
+
end
|
87
|
+
|
88
|
+
def html_attrs(lang = 'en-US')
|
89
|
+
if ezml_buffer.options[:format] == :xhtml
|
90
|
+
{:xmlns => "http://www.w3.org/1999/xhtml", 'xml:lang' => lang, :lang => lang}
|
91
|
+
else
|
92
|
+
{:lang => lang}
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def tab_up(i = 1)
|
97
|
+
ezml_buffer.tabulation += i
|
98
|
+
end
|
99
|
+
|
100
|
+
def tab_down(i = 1)
|
101
|
+
ezml_buffer.tabulation -= i
|
102
|
+
end
|
103
|
+
|
104
|
+
def with_tabs(i)
|
105
|
+
old_tabs = ezml_buffer.tabulation
|
106
|
+
ezml_buffer.tabulation = i
|
107
|
+
yield
|
108
|
+
ensure
|
109
|
+
ezml_buffer.tabulation = old_tabs
|
110
|
+
end
|
111
|
+
|
112
|
+
def surround(front, back = front, &block)
|
113
|
+
output = capture_ezml(&block)
|
114
|
+
|
115
|
+
"#{front}#{output.chomp}#{back}\n"
|
116
|
+
end
|
117
|
+
|
118
|
+
def precede(str, &block)
|
119
|
+
"#{str}#{capture_ezml(&block).chomp}\n"
|
120
|
+
end
|
121
|
+
|
122
|
+
def succeed(str, &block)
|
123
|
+
"#{capture_ezml(&block).chomp}#{str}\n"
|
124
|
+
end
|
125
|
+
|
126
|
+
def capture_ezml(*args, &block)
|
127
|
+
buffer = eval('if defined? _ezmlout then _ezmlout else nil end', block.binding) || ezml_buffer
|
128
|
+
with_ezml_buffer(buffer) do
|
129
|
+
position = ezml_buffer.buffer.length
|
130
|
+
|
131
|
+
ezml_buffer.capture_position = position
|
132
|
+
value = block.call(*args)
|
133
|
+
|
134
|
+
captured = ezml_buffer.buffer.slice!(position..-1)
|
135
|
+
|
136
|
+
if captured == '' and value != ezml_buffer.buffer
|
137
|
+
captured = (value.is_a?(String) ? value : nil)
|
138
|
+
end
|
139
|
+
|
140
|
+
captured
|
141
|
+
end
|
142
|
+
ensure
|
143
|
+
ezml_buffer.capture_position = nil
|
144
|
+
end
|
145
|
+
|
146
|
+
def ezml_concat(text = "")
|
147
|
+
ezml_internal_concat text
|
148
|
+
ErrorReturn.new("ezml_concat")
|
149
|
+
end
|
150
|
+
|
151
|
+
def ezml_internal_concat(text = "", newline = true, indent = true)
|
152
|
+
if ezml_buffer.tabulation == 0
|
153
|
+
ezml_buffer.buffer << "#{text}#{"\n" if newline}"
|
154
|
+
else
|
155
|
+
ezml_buffer.buffer << %[#{ezml_indent if indent}#{text.to_s.gsub("\n", "\n#{ezml_indent}")}#{"\n" if newline}]
|
156
|
+
end
|
157
|
+
end
|
158
|
+
private :ezml_internal_concat
|
159
|
+
|
160
|
+
alias :ezml_internal_concat_raw :ezml_internal_concat
|
161
|
+
|
162
|
+
def ezml_indent
|
163
|
+
' ' * ezml_buffer.tabulation
|
164
|
+
end
|
165
|
+
|
166
|
+
def ezml_tag(name, *rest, &block)
|
167
|
+
ret = ErrorReturn.new("ezml_tag")
|
168
|
+
|
169
|
+
text = rest.shift.to_s unless [Symbol, Hash, NilClass].any? {|t| rest.first.is_a? t}
|
170
|
+
flags = []
|
171
|
+
flags << rest.shift while rest.first.is_a? Symbol
|
172
|
+
attrs = (rest.shift || {})
|
173
|
+
attrs.keys.each {|key| attrs[key.to_s] = attrs.delete(key)} unless attrs.empty?
|
174
|
+
name, attrs = merge_name_and_attributes(name.to_s, attrs)
|
175
|
+
|
176
|
+
attributes = EZML::AttributeBuilder.build_attributes(ezml_buffer.html?,
|
177
|
+
ezml_buffer.options[:attr_wrapper],
|
178
|
+
ezml_buffer.options[:escape_attrs],
|
179
|
+
ezml_buffer.options[:hyphenate_data_attrs],
|
180
|
+
attrs)
|
181
|
+
|
182
|
+
if text.nil? && block.nil? && (ezml_buffer.options[:autoclose].include?(name) || flags.include?(:/))
|
183
|
+
ezml_internal_concat_raw "<#{name}#{attributes}#{' /' if ezml_buffer.options[:format] == :xhtml}>"
|
184
|
+
return ret
|
185
|
+
end
|
186
|
+
|
187
|
+
if flags.include?(:/)
|
188
|
+
raise Error.new(Error.message(:self_closing_content)) if text
|
189
|
+
raise Error.new(Error.message(:illegal_nesting_self_closing)) if block
|
190
|
+
end
|
191
|
+
|
192
|
+
tag = "<#{name}#{attributes}>"
|
193
|
+
end_tag = "</#{name}>"
|
194
|
+
if block.nil?
|
195
|
+
text = text.to_s
|
196
|
+
if text.include?("\n")
|
197
|
+
ezml_internal_concat_raw tag
|
198
|
+
tab_up
|
199
|
+
ezml_internal_concat text
|
200
|
+
tab_down
|
201
|
+
ezml_internal_concat_raw end_tag
|
202
|
+
else
|
203
|
+
ezml_internal_concat_raw tag, false
|
204
|
+
ezml_internal_concat text, false, false
|
205
|
+
ezml_internal_concat_raw end_tag, true, false
|
206
|
+
end
|
207
|
+
return ret
|
208
|
+
end
|
209
|
+
|
210
|
+
if text
|
211
|
+
raise Error.new(Error.message(:illegal_nesting_line, name))
|
212
|
+
end
|
213
|
+
|
214
|
+
if flags.include?(:<)
|
215
|
+
ezml_internal_concat_raw tag, false
|
216
|
+
ezml_internal_concat "#{capture_ezml(&block).strip}", false, false
|
217
|
+
ezml_internal_concat_raw end_tag, true, false
|
218
|
+
return ret
|
219
|
+
end
|
220
|
+
|
221
|
+
ezml_internal_concat_raw tag
|
222
|
+
tab_up
|
223
|
+
block.call
|
224
|
+
tab_down
|
225
|
+
ezml_internal_concat_raw end_tag
|
226
|
+
|
227
|
+
ret
|
228
|
+
end
|
229
|
+
|
230
|
+
def ezml_tag_if(condition, *tag)
|
231
|
+
if condition
|
232
|
+
ezml_tag(*tag){ yield }
|
233
|
+
else
|
234
|
+
yield
|
235
|
+
end
|
236
|
+
ErrorReturn.new("ezml_tag_if")
|
237
|
+
end
|
238
|
+
|
239
|
+
HTML_ESCAPE = { '&' => '&', '<' => '<', '>' => '>', '"' => '"', "'" => ''' }
|
240
|
+
|
241
|
+
HTML_ESCAPE_REGEX = /['"><&]/
|
242
|
+
|
243
|
+
def html_escape(text)
|
244
|
+
ERB::Util.html_escape(text)
|
245
|
+
end
|
246
|
+
|
247
|
+
HTML_ESCAPE_ONCE_REGEX = /['"><]|&(?!(?:[a-zA-Z]+|#(?:\d+|[xX][0-9a-fA-F]+));)/
|
248
|
+
|
249
|
+
def escape_once(text)
|
250
|
+
text = text.to_s
|
251
|
+
text.gsub(HTML_ESCAPE_ONCE_REGEX, HTML_ESCAPE)
|
252
|
+
end
|
253
|
+
|
254
|
+
def is_ezml?
|
255
|
+
!@ezml_buffer.nil? && @ezml_buffer.active?
|
256
|
+
end
|
257
|
+
|
258
|
+
def block_is_ezml?(block)
|
259
|
+
eval('!!defined?(_ezmlout)', block.binding)
|
260
|
+
end
|
261
|
+
|
262
|
+
private
|
263
|
+
|
264
|
+
def merge_name_and_attributes(name, attributes_hash = {})
|
265
|
+
return name, attributes_hash unless name =~ /^(.+?)?([\.#].*)$/
|
266
|
+
|
267
|
+
return $1 || "div", AttributeBuilder.merge_attributes!(
|
268
|
+
EZML::Parser.parse_class_and_id($2), attributes_hash)
|
269
|
+
end
|
270
|
+
|
271
|
+
def with_ezml_buffer(buffer)
|
272
|
+
@ezml_buffer, old_buffer = buffer, @ezml_buffer
|
273
|
+
old_buffer.active, old_was_active = false, old_buffer.active? if old_buffer
|
274
|
+
@ezml_buffer.active, was_active = true, @ezml_buffer.active?
|
275
|
+
yield
|
276
|
+
ensure
|
277
|
+
@ezml_buffer.active = was_active
|
278
|
+
old_buffer.active = old_was_active if old_buffer
|
279
|
+
@ezml_buffer = old_buffer
|
280
|
+
end
|
281
|
+
|
282
|
+
def ezml_buffer
|
283
|
+
@ezml_buffer if defined? @ezml_buffer
|
284
|
+
end
|
285
|
+
|
286
|
+
def ezml_bind_proc(&proc)
|
287
|
+
_ezmlout = ezml_buffer
|
288
|
+
#double assignment is to avoid warnings
|
289
|
+
_erbout = _erbout = _ezmlout.buffer
|
290
|
+
proc { |*args| proc.call(*args) }
|
291
|
+
end
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
class Object
|
296
|
+
def is_ezml?
|
297
|
+
false
|
298
|
+
end
|
299
|
+
end
|
data/lib/ezml/options.rb
ADDED
@@ -0,0 +1,142 @@
|
|
1
|
+
module EZML
|
2
|
+
|
3
|
+
class Options
|
4
|
+
|
5
|
+
@valid_formats = [:html4, :html5, :xhtml]
|
6
|
+
|
7
|
+
@buffer_option_keys = [:autoclose, :preserve, :attr_wrapper, :format,
|
8
|
+
:encoding, :escape_html, :escape_attrs, :hyphenate_data_attrs, :cdata]
|
9
|
+
|
10
|
+
def self.defaults
|
11
|
+
@defaults ||= EZML::TemplateEngine.options.to_hash.merge(encoding: 'UTF-8')
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.valid_formats
|
15
|
+
@valid_formats
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.buffer_option_keys
|
19
|
+
@buffer_option_keys
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.buffer_defaults
|
23
|
+
@buffer_defaults ||= buffer_option_keys.inject({}) do |hash, key|
|
24
|
+
hash.merge(key => defaults[key])
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.wrap(options)
|
29
|
+
if options.is_a?(Options)
|
30
|
+
options
|
31
|
+
else
|
32
|
+
Options.new(options)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
attr_reader :attr_wrapper
|
37
|
+
attr_accessor :autoclose
|
38
|
+
attr_reader :encoding
|
39
|
+
attr_accessor :escape_attrs
|
40
|
+
attr_accessor :escape_html
|
41
|
+
attr_accessor :filename
|
42
|
+
attr_accessor :hyphenate_data_attrs
|
43
|
+
attr_accessor :line
|
44
|
+
attr :format
|
45
|
+
attr_accessor :mime_type
|
46
|
+
attr_accessor :preserve
|
47
|
+
attr_reader :remove_whitespace
|
48
|
+
attr_accessor :suppress_eval
|
49
|
+
attr_accessor :cdata
|
50
|
+
attr_accessor :parser_class
|
51
|
+
attr_accessor :compiler_class
|
52
|
+
attr_accessor :trace
|
53
|
+
attr_accessor :filters
|
54
|
+
|
55
|
+
def initialize(values = {}, &block)
|
56
|
+
defaults.each {|k, v| instance_variable_set :"@#{k}", v}
|
57
|
+
values.each {|k, v| send("#{k}=", v) if defaults.has_key?(k) && !v.nil?}
|
58
|
+
yield if block_given?
|
59
|
+
end
|
60
|
+
|
61
|
+
def [](key)
|
62
|
+
send key
|
63
|
+
end
|
64
|
+
|
65
|
+
def []=(key, value)
|
66
|
+
send "#{key}=", value
|
67
|
+
end
|
68
|
+
|
69
|
+
[:escape_attrs, :hyphenate_data_attrs, :remove_whitespace, :suppress_eval].each do |method|
|
70
|
+
class_eval(<<-END)
|
71
|
+
def #{method}?
|
72
|
+
!! @#{method}
|
73
|
+
end
|
74
|
+
END
|
75
|
+
end
|
76
|
+
|
77
|
+
def xhtml?
|
78
|
+
not html?
|
79
|
+
end
|
80
|
+
|
81
|
+
def html?
|
82
|
+
html4? or html5?
|
83
|
+
end
|
84
|
+
|
85
|
+
def html4?
|
86
|
+
format == :html4
|
87
|
+
end
|
88
|
+
|
89
|
+
def html5?
|
90
|
+
format == :html5
|
91
|
+
end
|
92
|
+
|
93
|
+
def attr_wrapper=(value)
|
94
|
+
@attr_wrapper = value || self.class.defaults[:attr_wrapper]
|
95
|
+
end
|
96
|
+
|
97
|
+
undef :format
|
98
|
+
def format
|
99
|
+
mime_type == "text/xml" ? :xhtml : @format
|
100
|
+
end
|
101
|
+
|
102
|
+
def format=(value)
|
103
|
+
unless self.class.valid_formats.include?(value)
|
104
|
+
raise EZML::Error, "Invalid output format #{value.inspect}"
|
105
|
+
end
|
106
|
+
@format = value
|
107
|
+
end
|
108
|
+
|
109
|
+
undef :cdata
|
110
|
+
def cdata
|
111
|
+
xhtml? || @cdata
|
112
|
+
end
|
113
|
+
|
114
|
+
def remove_whitespace=(value)
|
115
|
+
@remove_whitespace = value
|
116
|
+
end
|
117
|
+
|
118
|
+
def encoding=(value)
|
119
|
+
return unless value
|
120
|
+
@encoding = value.is_a?(Encoding) ? value.name : value.to_s
|
121
|
+
@encoding = "UTF-8" if @encoding.upcase == "US-ASCII"
|
122
|
+
end
|
123
|
+
|
124
|
+
def for_buffer
|
125
|
+
self.class.buffer_option_keys.inject({}) do |hash, key|
|
126
|
+
value = public_send(key)
|
127
|
+
if self.class.buffer_defaults[key] != value
|
128
|
+
hash[key] = value
|
129
|
+
end
|
130
|
+
hash
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
private
|
135
|
+
|
136
|
+
def defaults
|
137
|
+
self.class.defaults
|
138
|
+
end
|
139
|
+
|
140
|
+
end
|
141
|
+
|
142
|
+
end
|