ezml 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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