wunderbar 0.17.3 → 0.18.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,6 +1,30 @@
1
1
  # Wrapper class that understands HTML
2
2
  module Wunderbar
3
- class HtmlMarkup < BuilderBase
3
+ # factored out so that these methods can be overriden (e.g., by opal.rb)
4
+ class Overridable < BuilderBase
5
+ def _script(*args, &block)
6
+ args << {} unless Hash === args.last
7
+ args.last[:lang] ||= 'text/javascript'
8
+ args.unshift ScriptNode
9
+ proxiable_tag! 'script', *args, &block
10
+ end
11
+
12
+ def _style(*args, &block)
13
+ if args == [:system]
14
+ args[0] = %{
15
+ pre._stdin {font-weight: bold; color: #800080; margin: 1em 0 0 0}
16
+ pre._stdout {font-weight: bold; color: #000; margin: 0}
17
+ pre._stderr {font-weight: bold; color: #F00; margin: 0}
18
+ }
19
+ end
20
+ args << {} unless Hash === args.last
21
+ args.last[:type] ||= 'text/css'
22
+ args.unshift StyleNode
23
+ proxiable_tag! 'style', *args, &block
24
+ end
25
+ end
26
+
27
+ class HtmlMarkup < Overridable
4
28
  VOID = %w(
5
29
  area base br col command embed hr img input keygen
6
30
  link meta param source track wbr
@@ -13,9 +37,11 @@ module Wunderbar
13
37
  hr noscript ol output p pre section table tfoot ul video
14
38
  )
15
39
 
40
+ HEAD = %w(title base link style meta)
41
+
16
42
  def initialize(scope)
17
43
  @_scope = scope
18
- @x = XmlMarkup.new :scope => scope, :indent => 2, :target => []
44
+ @x = XmlMarkup.new :scope => scope, :indent => 2
19
45
  end
20
46
 
21
47
  def html(*args, &block)
@@ -23,21 +49,66 @@ module Wunderbar
23
49
  args << {} if args.empty?
24
50
  if Hash === args.first
25
51
  args.first[:xmlns] ||= 'http://www.w3.org/1999/xhtml'
52
+ @_width = args.first.delete(:_width).to_i if args.first[:_width]
53
+ end
54
+
55
+ if ''.respond_to? :encoding
56
+ bom = "\ufeff"
57
+ else
58
+ bom = "\xEF\xBB\xBF"
26
59
  end
27
- @_width = args.first.delete(:_width) if Hash === args.first
28
60
 
29
- @x.text! "\xEF\xBB\xBF"
30
61
  @x.declare! :DOCTYPE, :html
31
- @x.tag! :html, *args do
62
+ html = tag! :html, *args do
32
63
  set_variables_from_params
33
64
  instance_eval(&block)
34
65
  end
35
66
 
36
- if @_width
37
- self.class.reflow(@x.target!, @_width)
67
+ pending_head = []
68
+ pending_body = []
69
+ head = nil
70
+ body = nil
71
+ html.children.each do |child|
72
+ next unless child
73
+ if child.name == 'head'
74
+ head = child
75
+ elsif child.name == 'body'
76
+ body = child
77
+ elsif HEAD.include? child.name
78
+ pending_head << child
79
+ elsif child.name == 'script'
80
+ if pending_body.empty?
81
+ pending_head << child
82
+ else
83
+ pending_body << child
84
+ end
85
+ else
86
+ pending_body << child
87
+ end
88
+ end
89
+
90
+ @x.instance_eval {@node = html}
91
+ head = _head_ if not head
92
+ body = _body nil if not body
93
+ html.children.unshift(head.parent.children.delete(head))
94
+ html.children.push(body.parent.children.delete(body))
95
+ head.parent = body.parent = html
96
+ head.children.compact!
97
+ body.children.compact!
98
+
99
+ [ [pending_head, head], [pending_body, body] ].each do |list, node|
100
+ list.each do |child|
101
+ html.children.delete(child)
102
+ node.add_child child
103
+ end
104
+ end
105
+
106
+ if not head.children.any? {|child| child.name == 'title'}
107
+ h1 = body.children.find {|child| child.name == 'h1'}
108
+ head.add_child Node.new('title', h1.text) if h1 and h1.text
38
109
  end
39
110
 
40
- @x.target!.join
111
+ bom + @x.target!
41
112
  end
42
113
 
43
114
  def _html(*args, &block)
@@ -45,7 +116,7 @@ module Wunderbar
45
116
  end
46
117
 
47
118
  def method_missing(name, *args, &block)
48
- if name.to_s =~ /^_(\w+)(!|\?|)$/
119
+ if name =~ /^_(\w+)(!|\?|)$/
49
120
  name, flag = $1, $2
50
121
  elsif @_scope and @_scope.respond_to? name
51
122
  return @_scope.__send__ name, *args, &block
@@ -56,69 +127,26 @@ module Wunderbar
56
127
  end
57
128
 
58
129
  if name.sub!(/_$/,'')
59
- @x.margin!
130
+ @x.spaced!
60
131
  return __send__ "_#{name}", *args, &block if respond_to? "_#{name}"
61
132
  end
62
133
 
63
- if flag != '!'
64
- if %w(script style).include?(name)
65
- if String === args.first and not block
66
- text = args.shift
67
- if !text.include? '&' and !text.include? '<'
68
- block = Proc.new do
69
- @x.indented_data!(text)
70
- end
71
- elsif name == 'style'
72
- block = Proc.new do
73
- @x.indented_data!(text, "/*<![CDATA[*/", "/*]]>*/")
74
- end
75
- else
76
- block = Proc.new do
77
- @x.indented_data!(text, "//<![CDATA[", "//]]>")
78
- end
79
- end
80
- end
81
-
82
- args << {} if args.length == 0
83
- if Hash === args.last
84
- args.last[:lang] ||= 'text/javascript' if name == 'script'
85
- args.last[:type] ||= 'text/css' if name == 'style'
86
- end
87
- end
88
-
89
- # ensure that non-void elements are explicitly closed
90
- if not block and not VOID.include?(name)
91
- args[0] = '' if args.length > 1 and args.first == nil
92
- symbol = (args.shift if args.length > 0 and Symbol === args.first)
93
- if args.length == 0 or (args.length == 1 and Hash === args.first)
94
- args.unshift ''
95
- end
96
- args.unshift(symbol) if symbol
97
- end
134
+ name = name.to_s.gsub('_', '-')
98
135
 
136
+ if flag != '!'
99
137
  if String === args.first and args.first.respond_to? :html_safe?
100
138
  if args.first.html_safe? and not block and args.first =~ /[>&]/
101
139
  markup = args.shift
102
140
  block = Proc.new {_ {markup}}
103
141
  end
104
142
  end
105
-
106
- if Hash === args.last
107
- # remove attributes with nil, false values
108
- args.last.delete_if {|key, value| !value}
109
-
110
- # replace boolean 'true' attributes with the name of the attribute
111
- args.last.each {|key, value| args.last[key]=key if value == true}
112
- end
113
143
  end
114
144
 
115
145
  if flag == '!'
116
- @x.disable_indentation! do
117
- @x.tag! name, *args, &block
118
- end
146
+ @x.compact!(@_width) { tag! name, *args, &block }
119
147
  elsif flag == '?'
120
148
  # capture exceptions, produce filtered tracebacks
121
- @x.tag!(name, *args) do
149
+ tag!(name, *args) do
122
150
  begin
123
151
  block.call
124
152
  rescue ::Exception => exception
@@ -127,14 +155,44 @@ module Wunderbar
127
155
  _exception exception, options
128
156
  end
129
157
  end
130
- else
131
- target = @x.tag! name, *args, &block
132
- if block and %w(script style).include?(name)
133
- if %w{//]]> /*]]>*/}.include? target[-4]
134
- target[-4], target[-3] = target[-3], target[-4]
158
+ elsif Wunderbar.templates.include? name
159
+ x = self.class.new({})
160
+ instance_variables.each do |ivar|
161
+ x.instance_variable_set ivar, instance_variable_get(ivar)
162
+ end
163
+ if Hash === args.last
164
+ args.last.each do |name, value|
165
+ x.instance_variable_set "@#{name}", value
135
166
  end
136
167
  end
137
- target
168
+ save_yield = Wunderbar.templates['yield']
169
+ begin
170
+ Wunderbar.templates['yield'] = block if block
171
+ x.instance_eval &Wunderbar.templates[name]
172
+ ensure
173
+ Wunderbar.templates['yield'] = save_yield
174
+ Wunderbar.templates.delete 'yield' unless save_yield
175
+ end
176
+ else
177
+ tag! name, *args, &block
178
+ end
179
+ end
180
+
181
+ def tag!(name, *args, &block)
182
+ node = @x.tag! name, *args, &block
183
+ if !block and args.empty?
184
+ CssProxy.new(self, node)
185
+ else
186
+ node
187
+ end
188
+ end
189
+
190
+ def proxiable_tag!(name, *args, &block)
191
+ node = @x.tag! name, *args, &block
192
+ if !block
193
+ CssProxy.new(self, node)
194
+ else
195
+ node
138
196
  end
139
197
  end
140
198
 
@@ -157,9 +215,9 @@ module Wunderbar
157
215
  end
158
216
 
159
217
  if traceback_class
160
- @x.tag! :pre, text, :class=>traceback_class
218
+ tag! :pre, text, :class=>traceback_class
161
219
  else
162
- @x.tag! :pre, text, :style=>traceback_style
220
+ tag! :pre, text, :style=>traceback_style
163
221
  end
164
222
  else
165
223
  super
@@ -167,8 +225,8 @@ module Wunderbar
167
225
  end
168
226
 
169
227
  def _head(*args, &block)
170
- @x.tag!('head', *args) do
171
- @x.tag! :meta, :charset => 'utf-8'
228
+ tag!('head', *args) do
229
+ tag! :meta, :charset => 'utf-8'
172
230
  block.call if block
173
231
  instance_eval &Wunderbar::Asset.declarations
174
232
  end
@@ -177,7 +235,7 @@ module Wunderbar
177
235
  def _p(*args, &block)
178
236
  if args.length >= 1 and String === args.first and args.first.include? "\n"
179
237
  text = args.shift
180
- @x.tag! :p, *args do
238
+ tag! :p, *args do
181
239
  @x.indented_text! text
182
240
  end
183
241
  else
@@ -188,7 +246,7 @@ module Wunderbar
188
246
  def _svg(*args, &block)
189
247
  args << {} if args.empty?
190
248
  args.first['xmlns'] = 'http://www.w3.org/2000/svg' if Hash === args.first
191
- @x.proxiable_tag! :svg, *args, &block
249
+ proxiable_tag! :svg, *args, &block
192
250
  end
193
251
 
194
252
  def _math(*args, &block)
@@ -196,16 +254,46 @@ module Wunderbar
196
254
  if Hash === args.first
197
255
  args.first['xmlns'] = 'http://www.w3.org/1998/Math/MathML'
198
256
  end
199
- @x.proxiable_tag! :math, *args, &block
257
+ proxiable_tag! :math, *args, &block
200
258
  end
201
259
 
202
260
  def _pre(*args, &block)
203
261
  args.first.chomp! if String === args.first and args.first.end_with? "\n"
204
- @x.disable_indentation! { @x.tag! :pre, *args, &block }
262
+ @x.compact!(@_width) { tag! :pre, *args, &block }
263
+ end
264
+
265
+ def _ul(*args, &block)
266
+ iterable = args.first.respond_to? :each
267
+ if iterable and (args.length > 1 or not args.first.respond_to? :to_hash)
268
+ list = args.shift.dup
269
+ tag!(:ul, *args) {list.each {|arg| _li arg }}
270
+ else
271
+ super
272
+ end
273
+ end
274
+
275
+ def _ol(*args, &block)
276
+ iterable = args.first.respond_to? :each
277
+ if iterable and (args.length > 1 or not args.first.respond_to? :to_hash)
278
+ list = args.shift
279
+ tag!(:ol, *args) {list.each {|arg| _li arg }}
280
+ else
281
+ super
282
+ end
283
+ end
284
+
285
+ def _tr(*args, &block)
286
+ iterable = args.first.respond_to? :each
287
+ if iterable and (args.length > 1 or not args.first.respond_to? :to_hash)
288
+ list = args.shift
289
+ tag!(:tr, *args) {list.each {|arg| _td arg }}
290
+ else
291
+ super
292
+ end
205
293
  end
206
294
 
207
295
  def _!(text)
208
- @x.text! text.to_s
296
+ @x.text! text.to_s.chomp
209
297
  end
210
298
 
211
299
  def _(text=nil, &block)
@@ -244,7 +332,7 @@ module Wunderbar
244
332
  end
245
333
 
246
334
  def clear!
247
- @x.target!.clear
335
+ @x.clear!
248
336
  end
249
337
 
250
338
  def self.flatten?(children)
@@ -264,65 +352,5 @@ module Wunderbar
264
352
  end
265
353
  flatten
266
354
  end
267
-
268
- # reflow long lines
269
- def self.reflow(stream, width)
270
- source = stream.slice!(0..-1)
271
- indent = col = 0
272
- breakable = true
273
- pre = false
274
- while not source.empty?
275
- token = source.shift
276
- indent = token[/^ */].length if col == 0
277
-
278
- if token.start_with? '<'
279
- breakable = false
280
- pre = true if token == '<pre'
281
- end
282
-
283
- # flow text
284
- while token.length + col > width and breakable and not pre
285
- break if token[0...-1].include? "\n"
286
- split = token.rindex(' ', [width-col,0].max) || token.index(' ')
287
- break unless split
288
- break if col+split < indent+width/2
289
- stream << token[0...split] << "\n" << (' '*indent)
290
- col = indent
291
- token = token[split+1..-1]
292
- end
293
-
294
- # break around tags
295
- if token.end_with? '>'
296
- if col > indent + 4 and stream[-2..-1] == ['<br', '/']
297
- stream << token << "\n"
298
- col = 0
299
- token = ' '*indent
300
- source[0] = source.first.lstrip unless source.empty?
301
- elsif col > width and not pre
302
- # break on previous space within text
303
- pcol = col
304
- stream.reverse_each do |xtoken|
305
- break if xtoken.include? "\n"
306
- split = xtoken.rindex(' ')
307
- breakable = false if xtoken.end_with? '>'
308
- if breakable and split
309
- col = col - pcol + xtoken.length - split + indent
310
- xtoken[split] = "\n#{' '*indent}"
311
- break
312
- end
313
- breakable = true if xtoken.start_with? '<'
314
- pcol -= xtoken.length
315
- break if pcol < (width + indent)/2
316
- end
317
- end
318
- breakable = true
319
- pre = false if token == '</pre>'
320
- end
321
-
322
- stream << token
323
- col += token.length
324
- col = 0 if token.end_with? "\n"
325
- end
326
- end
327
355
  end
328
356
  end
@@ -0,0 +1,208 @@
1
+ module Wunderbar
2
+ class Node
3
+ attr_accessor :name, :text, :attrs, :node, :children, :parent
4
+
5
+ VOID = %w(
6
+ area base br col command embed hr img input keygen
7
+ link meta param source track wbr
8
+ )
9
+
10
+ ESCAPE = {
11
+ "'" => '&apos;',
12
+ '&' => '&amp;',
13
+ '"' => '&quot;',
14
+ '<' => '&lt;',
15
+ '>' => '&gt;',
16
+ }
17
+
18
+ def initialize(name, *args)
19
+ @name = name
20
+ @text = nil
21
+ @attrs = {}
22
+ @children = []
23
+ @name += args.shift.inspect if Symbol === args.first
24
+ @attrs = args.pop.to_hash if args.last.respond_to? :to_hash
25
+ @text = args.shift.to_s unless args.empty?
26
+ end
27
+
28
+ def method_missing(*args)
29
+ if args.length == 0
30
+ attrs[:class] = (attrs[:class].to_s.split(' ') + [name]).join(' ')
31
+ else
32
+ name = args.first.to_s
33
+ err = NameError.new "undefined local variable or method `#{name}'", name
34
+ err.set_backtrace caller
35
+ raise err
36
+ end
37
+ end
38
+
39
+ def add_child(child)
40
+ @children << child
41
+ child.parent = self
42
+ end
43
+
44
+ def add_text(text)
45
+ @children << text.to_s.gsub(/[&<>]/,ESCAPE)
46
+ end
47
+
48
+ def walk(result, indent, options)
49
+ indent += options[:indent] if indent and parent
50
+ first = true
51
+ spaced = false
52
+ children.each do |child|
53
+ next unless child
54
+ result << '' if (spaced or SpacedNode === child) and not first
55
+ if String === child
56
+ result << child
57
+ else
58
+ child.serialize(options, result, indent)
59
+ end
60
+ first = false
61
+ spaced = (SpacedNode === child)
62
+ end
63
+ end
64
+
65
+ def serialize(options = {}, result = [], indent = '')
66
+ line = "#{indent}<#{name}"
67
+
68
+ attrs.each do |name, value|
69
+ next unless value
70
+ name = name.to_s.gsub('_','-') if Symbol === name
71
+ value=name if value==true
72
+ line += " #{name}=\"#{value.to_s.gsub(/[&\"<>]/,ESCAPE)}\""
73
+ end
74
+
75
+ if children.empty?
76
+ if text
77
+ if options[:pre]
78
+ line += ">#{options[:pre]}#{text}#{options[:post]}</#{name}>"
79
+ else
80
+ line += ">#{text.to_s.gsub(/[&<>]/,ESCAPE)}</#{name}>"
81
+ end
82
+ elsif VOID.include? name.to_s
83
+ line += "/>"
84
+ else
85
+ line += "></#{name}>"
86
+ end
87
+ elsif CompactNode === self
88
+ work = []
89
+ walk(work, nil, options)
90
+ if @width
91
+ line += ">"
92
+ (work+["</#{name}>"]).each do |node|
93
+ if line.length + node.length > @width
94
+ result << line.rstrip
95
+ line = indent
96
+ end
97
+ line += node
98
+ end
99
+ else
100
+ line += ">#{work.join}</#{name}>"
101
+ end
102
+ else
103
+ result << line+">#{options[:pre]}" if parent
104
+
105
+ walk(result, indent, options) unless children.empty?
106
+
107
+ line = "#{indent}#{options[:post]}</#{name}>"
108
+ end
109
+
110
+ result << line if parent
111
+ result
112
+ end
113
+ end
114
+
115
+ class CommentNode
116
+ def initialize(text)
117
+ @text = text
118
+ end
119
+
120
+ def serialize(options, result, indent)
121
+ result << "#{indent}<!-- #{@text} -->"
122
+ result
123
+ end
124
+ end
125
+
126
+ class DocTypeNode
127
+ def initialize(*args)
128
+ @declare = args.shift
129
+ @name = args.shift
130
+ end
131
+
132
+ def serialize(options, result, indent)
133
+ result << "<!#{@declare} #{@name.to_s}>"
134
+ result
135
+ end
136
+ end
137
+
138
+ class CDATANode < Node
139
+ def self.normalize(data, indent)
140
+ data = data.sub(/\n\s*\Z/, '').sub(/\A\s*\n/, '')
141
+
142
+ unindent = data.sub(/s+\Z/,'').scan(/^ *\S/).map(&:length).min || 0
143
+
144
+ before = ::Regexp.new('^'.ljust(unindent))
145
+ node = @node
146
+ data.gsub! before, indent
147
+ data.gsub! /^#{indent}$/, '' if unindent == 0
148
+ data
149
+ end
150
+
151
+ def serialize(options = {}, result = [], indent='')
152
+ if @text and @text.include? "\n"
153
+ tindent = (indent ? "#{indent}#{options[:indent]}" : indent)
154
+ children.unshift CDATANode.normalize(@text, tindent).rstrip
155
+ @text = nil
156
+ end
157
+
158
+ if @text and @text =~ /[<^>]/
159
+ indent += ' ' if indent
160
+ children.unshift @text.gsub(/^/, indent).gsub(/^ +$/,'').rstrip
161
+ @text = nil
162
+ super(options.merge(pre: pre, post: post), result, indent)
163
+ elsif children && children.any? {|node| String===node && node =~ /[<^>]/}
164
+ super(options.merge(pre: pre, post: post), result, indent)
165
+ else
166
+ super
167
+ end
168
+ end
169
+
170
+ def add_text(text)
171
+ @children << text
172
+ end
173
+ end
174
+
175
+ class IndentedTextNode < Node
176
+ def serialize(options, result, indent)
177
+ if indent
178
+ text = CDATANode.normalize(name, indent)
179
+ else
180
+ text = name
181
+ end
182
+
183
+ result << text.to_s.gsub(/[&<>]/,ESCAPE)
184
+ end
185
+ end
186
+
187
+ class ScriptNode < CDATANode
188
+ def pre; "//<![CDATA["; end
189
+ def post; "//]]>"; end
190
+ end
191
+
192
+ class StyleNode < CDATANode
193
+ def pre; "/*<![CDATA[*/"; end
194
+ def post; "/*]]>*/"; end
195
+ end
196
+
197
+ module CompactNode
198
+ def width=(value)
199
+ @width = value
200
+ end
201
+ def width
202
+ @width
203
+ end
204
+ end
205
+
206
+ module SpacedNode; end
207
+
208
+ end