wunderbar 0.17.3 → 0.18.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.
@@ -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