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.
@@ -0,0 +1,322 @@
1
+ require_relative 'attribute_builder'
2
+ require_relative 'attribute_compiler'
3
+ require_relative 'temple_line_counter'
4
+
5
+ module EZML
6
+
7
+ class Compiler
8
+ include EZML::Util
9
+
10
+ attr_accessor :options
11
+
12
+ def initialize(options)
13
+ @options = Options.wrap(options)
14
+ @to_merge = []
15
+ @temple = [:multi]
16
+ @node = nil
17
+ @filters = Filters.defined.merge(options[:filters])
18
+ @attribute_compiler = AttributeCompiler.new(@options)
19
+ end
20
+
21
+ def call(node)
22
+ compile(node)
23
+ @temple
24
+ end
25
+
26
+ def compile(node)
27
+ parent, @node = @node, node
28
+ if node.children.empty?
29
+ send(:"compile_#{node.type}")
30
+ else
31
+ send(:"compile_#{node.type}") {node.children.each {|c| compile c}}
32
+ end
33
+ ensure
34
+ @node = parent
35
+ end
36
+
37
+ private
38
+
39
+ def compile_root
40
+ @output_line = 1
41
+ yield if block_given?
42
+ flush_merged_text
43
+ end
44
+
45
+ def compile_plain
46
+ push_text("#{@node.value[:text]}\n")
47
+ end
48
+
49
+ def nuke_inner_whitespace?(node)
50
+ if node.value && node.value[:nuke_inner_whitespace]
51
+ true
52
+ elsif node.parent
53
+ nuke_inner_whitespace?(node.parent)
54
+ else
55
+ false
56
+ end
57
+ end
58
+
59
+ def compile_script(&block)
60
+ push_script(@node.value[:text],
61
+ :preserve_script => @node.value[:preserve],
62
+ :escape_html => @node.value[:escape_html],
63
+ :nuke_inner_whitespace => nuke_inner_whitespace?(@node),
64
+ &block)
65
+ end
66
+
67
+ def compile_silent_script
68
+ return if @options.suppress_eval
69
+ push_silent(@node.value[:text])
70
+ keyword = @node.value[:keyword]
71
+
72
+ if block_given?
73
+ yield
74
+ push_silent("end", :can_suppress) unless @node.value[:dont_push_end]
75
+ elsif keyword == "end"
76
+ if @node.parent.children.last.equal?(@node)
77
+ # Since this "end" is ending the block,
78
+ # we don't need to generate an additional one
79
+ @node.parent.value[:dont_push_end] = true
80
+ end
81
+ # Don't restore dont_* for end because it isn't a conditional branch.
82
+ end
83
+ end
84
+
85
+ def compile_ezml_comment; end
86
+
87
+ def compile_tag
88
+ t = @node.value
89
+
90
+ # Get rid of whitespace outside of the tag if we need to
91
+ rstrip_buffer! if t[:nuke_outer_whitespace]
92
+
93
+ if @options.suppress_eval
94
+ object_ref = :nil
95
+ parse = false
96
+ value = t[:parse] ? nil : t[:value]
97
+ dynamic_attributes = EZML::Parser::DynamicAttributes.new
98
+ else
99
+ object_ref = t[:object_ref]
100
+ parse = t[:parse]
101
+ value = t[:value]
102
+ dynamic_attributes = t[:dynamic_attributes]
103
+ end
104
+
105
+ if @options[:trace]
106
+ t[:attributes].merge!({"data-trace" => @options.filename.split('/views').last << ":" << @node.line.to_s})
107
+ end
108
+
109
+ push_text("<#{t[:name]}")
110
+ push_temple(@attribute_compiler.compile(t[:attributes], object_ref, dynamic_attributes))
111
+ push_text(
112
+ if t[:self_closing] && @options.xhtml?
113
+ " />#{"\n" unless t[:nuke_outer_whitespace]}"
114
+ else
115
+ ">#{"\n" unless (t[:self_closing] && @options.html?) ? t[:nuke_outer_whitespace] : (!block_given? || t[:preserve_tag] || t[:nuke_inner_whitespace])}"
116
+ end
117
+ )
118
+
119
+ if value && !parse
120
+ push_text("#{value}</#{t[:name]}>#{"\n" unless t[:nuke_outer_whitespace]}")
121
+ end
122
+
123
+ return if t[:self_closing]
124
+
125
+ if value.nil?
126
+ yield if block_given?
127
+ rstrip_buffer! if t[:nuke_inner_whitespace]
128
+ push_text("</#{t[:name]}>#{"\n" unless t[:nuke_outer_whitespace]}")
129
+ return
130
+ end
131
+
132
+ if parse
133
+ push_script(value, t.merge(:in_tag => true))
134
+ push_text("</#{t[:name]}>#{"\n" unless t[:nuke_outer_whitespace]}")
135
+ end
136
+ end
137
+
138
+ def compile_comment
139
+ condition = "#{@node.value[:conditional]}>" if @node.value[:conditional]
140
+ revealed = @node.value[:revealed]
141
+
142
+ open = "<!--#{condition}#{'<!-->' if revealed}"
143
+
144
+ close = "#{'<!--' if revealed}#{'<![endif]' if condition}-->"
145
+
146
+ unless block_given?
147
+ push_text("#{open} ")
148
+
149
+ if @node.value[:parse]
150
+ push_script(@node.value[:text], :in_tag => true, :nuke_inner_whitespace => true)
151
+ else
152
+ push_text(@node.value[:text])
153
+ end
154
+
155
+ push_text(" #{close}\n")
156
+ return
157
+ end
158
+
159
+ push_text("#{open}\n")
160
+ yield if block_given?
161
+ push_text("#{close}\n")
162
+ end
163
+
164
+ def compile_doctype
165
+ doctype = text_for_doctype
166
+ push_text("#{doctype}\n") if doctype
167
+ end
168
+
169
+ def compile_filter
170
+ unless filter = @filters[@node.value[:name]]
171
+ name = @node.value[:name]
172
+ if ["maruku", "textile"].include?(name)
173
+ raise Error.new(Error.message(:install_ezml_contrib, name), @node.line - 1)
174
+ else
175
+ raise Error.new(Error.message(:filter_not_defined, name), @node.line - 1)
176
+ end
177
+ end
178
+ filter.internal_compile(self, @node.value[:text])
179
+ end
180
+
181
+ def text_for_doctype
182
+ if @node.value[:type] == "xml"
183
+ return nil if @options.html?
184
+ wrapper = @options.attr_wrapper
185
+ return "<?xml version=#{wrapper}1.0#{wrapper} encoding=#{wrapper}#{@node.value[:encoding] || "utf-8"}#{wrapper} ?>"
186
+ end
187
+
188
+ if @options.html5?
189
+ '<!DOCTYPE html>'
190
+ else
191
+ if @options.xhtml?
192
+ if @node.value[:version] == "1.1"
193
+ '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">'
194
+ elsif @node.value[:version] == "5"
195
+ '<!DOCTYPE html>'
196
+ else
197
+ case @node.value[:type]
198
+ when "strict"; '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">'
199
+ when "frameset"; '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">'
200
+ when "mobile"; '<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.2//EN" "http://www.openmobilealliance.org/tech/DTD/xhtml-mobile12.dtd">'
201
+ when "rdfa"; '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML+RDFa 1.0//EN" "http://www.w3.org/MarkUp/DTD/xhtml-rdfa-1.dtd">'
202
+ when "basic"; '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN" "http://www.w3.org/TR/xhtml-basic/xhtml-basic11.dtd">'
203
+ else '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">'
204
+ end
205
+ end
206
+
207
+ elsif @options.html4?
208
+ case @node.value[:type]
209
+ when "strict"; '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">'
210
+ when "frameset"; '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">'
211
+ else '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">'
212
+ end
213
+ end
214
+ end
215
+ end
216
+
217
+ def push_silent(text, can_suppress = false)
218
+ flush_merged_text
219
+ return if can_suppress && @options.suppress_eval?
220
+ newline = (text == "end") ? ";" : "\n"
221
+ @temple << [:code, "#{resolve_newlines}#{text}#{newline}"]
222
+ @output_line = @output_line + text.count("\n") + newline.count("\n")
223
+ end
224
+
225
+ def push_text(text)
226
+ @to_merge << [:text, text]
227
+ end
228
+
229
+ def push_temple(temple)
230
+ flush_merged_text
231
+ @temple.concat([[:newline]] * resolve_newlines.count("\n"))
232
+ @temple << temple
233
+ @output_line += TempleLineCounter.count_lines(temple)
234
+ end
235
+
236
+ def flush_merged_text
237
+ return if @to_merge.empty?
238
+
239
+ @to_merge.each do |type, val|
240
+ case type
241
+ when :text
242
+ @temple << [:static, val]
243
+ when :script
244
+ @temple << [:dynamic, val]
245
+ else
246
+ raise SyntaxError.new("[EZML BUG] Undefined entry in EZML::Compiler@to_merge.")
247
+ end
248
+ end
249
+
250
+ @to_merge = []
251
+ end
252
+
253
+ def push_script(text, opts = {})
254
+ return if @options.suppress_eval?
255
+
256
+ no_format = !(opts[:preserve_script] || opts[:preserve_tag] || opts[:escape_html])
257
+
258
+ unless block_given?
259
+ push_generated_script(no_format ? "(#{text}\n).to_s" : build_script_formatter("(#{text}\n)", opts))
260
+ push_text("\n") unless opts[:in_tag] || opts[:nuke_inner_whitespace]
261
+ return
262
+ end
263
+
264
+ flush_merged_text
265
+ push_silent "ezml_temp = #{text}"
266
+ yield
267
+ push_silent('end', :can_suppress) unless @node.value[:dont_push_end]
268
+ @temple << [:dynamic, no_format ? 'ezml_temp.to_s;' : build_script_formatter('ezml_temp', opts)]
269
+ end
270
+
271
+ def build_script_formatter(text, opts)
272
+ text = "(#{text}).to_s"
273
+ if opts[:escape_html]
274
+ text = "::EZML::Helpers.html_escape(#{text})"
275
+ end
276
+ if opts[:nuke_inner_whitespace]
277
+ text = "(#{text}).strip"
278
+ end
279
+ if opts[:preserve_tag]
280
+ text = "_ezmlout.fix_textareas!(::EZML::Helpers.preserve(#{text}))"
281
+ elsif opts[:preserve_script]
282
+ text = "_ezmlout.fix_textareas!(::EZML::Helpers.find_and_preserve(#{text}, _ezmlout.options[:preserve]))"
283
+ end
284
+ "#{text};"
285
+ end
286
+
287
+ def push_generated_script(text)
288
+ @to_merge << [:script, resolve_newlines + text]
289
+ @output_line += text.count("\n")
290
+ end
291
+
292
+ def resolve_newlines
293
+ diff = @node.line - @output_line
294
+ return "" if diff <= 0
295
+ @output_line = @node.line
296
+ "\n" * diff
297
+ end
298
+
299
+ def rstrip_buffer!(index = -1)
300
+ last = @to_merge[index]
301
+ if last.nil?
302
+ push_silent("_ezmlout.rstrip!", false)
303
+ return
304
+ end
305
+
306
+ case last.first
307
+ when :text
308
+ last[1] = last[1].rstrip
309
+ if last[1].empty?
310
+ @to_merge.slice! index
311
+ rstrip_buffer! index
312
+ end
313
+ when :script
314
+ last[1].gsub!(/\(ezml_temp, (.*?)\);$/, '(ezml_temp.rstrip, \1);')
315
+ rstrip_buffer! index - 1
316
+ else
317
+ raise SyntaxError.new("[EZML BUG] Undefined entry in EZML::Compiler@to_merge.")
318
+ end
319
+ end
320
+ end
321
+
322
+ end
@@ -0,0 +1,107 @@
1
+ require 'forwardable'
2
+
3
+ require_relative 'parser'
4
+ require_relative 'compiler'
5
+ require_relative 'options'
6
+ require_relative 'helpers'
7
+ require_relative 'buffer'
8
+ require_relative 'filters'
9
+ require_relative 'error'
10
+ require_relative 'template_engine'
11
+
12
+ module EZML
13
+
14
+ class Engine
15
+ extend Forwardable
16
+ include EZML::Util
17
+
18
+ attr_accessor :options
19
+
20
+ attr_accessor :indentation
21
+
22
+ def_delegators :compiler, :precompiled, :precompiled_method_return_value
23
+
24
+ def options_for_buffer
25
+ @options.for_buffer
26
+ end
27
+
28
+ def initialize(template, options = {})
29
+ @options = Options.new(options)
30
+
31
+ @template = check_ezml_encoding(template) do |msg, line|
32
+ raise EZML::Error.new(msg, line)
33
+ end
34
+
35
+ @template_engine = TemplateEngine.new(options)
36
+ @template_engine.compile(@template)
37
+ end
38
+
39
+ #Deprecated
40
+ def compiler
41
+ @template_engine
42
+ end
43
+
44
+ def render(scope = Object.new, locals = {}, &block)
45
+ parent = scope.instance_variable_defined?(:@ezml_buffer) ? scope.instance_variable_get(:@ezml_buffer) : nil
46
+ buffer = EZML::Buffer.new(parent, @options.for_buffer)
47
+
48
+ if scope.is_a?(Binding)
49
+ scope_object = eval("self", scope)
50
+ scope = scope_object.instance_eval{binding} if block_given?
51
+ else
52
+ scope_object = scope
53
+ scope = scope_object.instance_eval{binding}
54
+ end
55
+
56
+ set_locals(locals.merge(:_ezmlout => buffer, :_erbout => buffer.buffer), scope, scope_object)
57
+
58
+ scope_object.extend(EZML::Helpers)
59
+ scope_object.instance_variable_set(:@ezml_buffer, buffer)
60
+ begin
61
+ eval(@template_engine.precompiled_with_return_value, scope, @options.filename, @options.line)
62
+ rescue ::SyntaxError => e
63
+ raise SyntaxError, e.message
64
+ end
65
+ ensure
66
+ scope_object.instance_variable_set(:@ezml_buffer, buffer.upper) if buffer
67
+ end
68
+ alias_method :to_html, :render
69
+
70
+ def render_proc(scope = Object.new, *local_names)
71
+ if scope.is_a?(Binding)
72
+ scope_object = eval("self", scope)
73
+ else
74
+ scope_object = scope
75
+ scope = scope_object.instance_eval{binding}
76
+ end
77
+
78
+ begin
79
+ str = @template_engine.precompiled_with_ambles(local_names)
80
+ eval(
81
+ "Proc.new { |*_ezml_locals| _ezml_locals = _ezml_locals[0] || {}; #{str}}\n",
82
+ scope,
83
+ @options.filename,
84
+ @options.line
85
+ )
86
+ rescue ::SyntaxError => e
87
+ raise SyntaxError, e.message
88
+ end
89
+ end
90
+
91
+ def def_method(object, name, *local_names)
92
+ method = object.is_a?(Module) ? :module_eval : :instance_eval
93
+
94
+ object.send(method, "def #{name}(_ezml_locals = {}); #{@template_engine.precompiled_with_ambles(local_names)}; end",
95
+ @options.filename, @options.line)
96
+ end
97
+
98
+ private
99
+
100
+ def set_locals(locals, scope, scope_object)
101
+ scope_object.instance_variable_set :@_ezml_locals, locals
102
+ set_locals = locals.keys.map { |k| "#{k} = @_ezml_locals[#{k.inspect}]" }.join("\n")
103
+ eval(set_locals, scope)
104
+ end
105
+ end
106
+
107
+ end
@@ -0,0 +1,59 @@
1
+ module EZML
2
+
3
+ class Error < StandardError
4
+ MESSAGES = {
5
+ :bad_script_indent => '"%s" is indented at wrong level: expected %d, but was at %d.',
6
+ :cant_run_filter => 'Can\'t run "%s" filter; you must require its dependencies first',
7
+ :cant_use_tabs_and_spaces => "Indentation can't use both tabs and spaces.",
8
+ :deeper_indenting => "The line was indented %d levels deeper than the previous line.",
9
+ :filter_not_defined => 'Filter "%s" is not defined.',
10
+ :gem_install_filter_deps => '"%s" filter\'s %s dependency missing: try installing it or adding it to your Gemfile',
11
+ :illegal_element => "Illegal element: classes and ids must have values.",
12
+ :illegal_nesting_content => "Illegal nesting: nesting within a tag that already has content is illegal.",
13
+ :illegal_nesting_header => "Illegal nesting: nesting within a header command is illegal.",
14
+ :illegal_nesting_line => "Illegal nesting: content can't be both given on the same line as %%%s and nested within it.",
15
+ :illegal_nesting_plain => "Illegal nesting: nesting within plain text is illegal.",
16
+ :illegal_nesting_self_closing => "Illegal nesting: nesting within a self-closing tag is illegal.",
17
+ :inconsistent_indentation => "Inconsistent indentation: %s used for indentation, but the rest of the document was indented using %s.",
18
+ :indenting_at_start => "Indenting at the beginning of the document is illegal.",
19
+ :install_ezml_contrib => 'To use the "%s" filter, please install the ezml-contrib gem.',
20
+ :invalid_attribute_list => 'Invalid attribute list: %s.',
21
+ :invalid_filter_name => 'Invalid filter name ":%s".',
22
+ :invalid_tag => 'Invalid tag: "%s".',
23
+ :missing_if => 'Got "%s" with no preceding "if"',
24
+ :no_ruby_code => "There's no Ruby code for %s to evaluate.",
25
+ :self_closing_content => "Self-closing tags can't have content.",
26
+ :unbalanced_brackets => 'Unbalanced brackets.',
27
+ :no_end => <<-END
28
+ You don't need to use "- end" in EZML. Un-indent to close a block:
29
+ - if foo?
30
+ %strong Foo!
31
+ - else
32
+ Not foo.
33
+ %p This line is un-indented, so it isn't part of the "if" block
34
+ END
35
+ }
36
+
37
+ def self.message(key, *args)
38
+ string = MESSAGES[key] or raise "[EZML BUG] No error messages for #{key}"
39
+ (args.empty? ? string : string % args).rstrip
40
+ end
41
+
42
+ # The line of the template on which the error occurred.
43
+ #
44
+ # @return [Fixnum]
45
+ attr_reader :line
46
+
47
+ # @param message [String] The error message
48
+ # @param line [Fixnum] See \{#line}
49
+ def initialize(message = nil, line = nil)
50
+ super(message)
51
+ @line = line
52
+ end
53
+ end
54
+
55
+ class SyntaxError < Error; end
56
+
57
+ class InvalidAttributeNameError < SyntaxError; end
58
+
59
+ end