wunderbar 0.17.3 → 0.18.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -8,7 +8,7 @@ that conforms to the
8
8
  the emerging results from the [XML Error Recovery Community
9
9
  Group](http://www.w3.org/community/xml-er/wiki/Main_Page).
10
10
 
11
- Wunderbar is both inspired by, and builds upon Jim Weirich's
11
+ Wunderbar was inspired by Jim Weirich's
12
12
  [Builder](https://github.com/jimweirich/builder#readme), and provides
13
13
  the element id and class id syntax and based on the implementation from
14
14
  [Markaby](http://markaby.rubyforge.org/).
@@ -130,12 +130,20 @@ Capture exceptions:
130
130
 
131
131
  Class attribute shortcut (equivalent to class="front"):
132
132
 
133
- div.front do
133
+ _div.front do
134
134
  end
135
135
 
136
136
  Id attributes shortcut (equivalent to id="search"):
137
137
 
138
- div.search! do
138
+ _div.search! do
139
+ end
140
+
141
+ Complete lists/rows can be defined using arrays:
142
+
143
+ _ul %w(apple orange pear)
144
+ _ol %w(apple orange pear)
145
+ _table do
146
+ _tr %w(apple orange pear)
139
147
  end
140
148
 
141
149
  Basic interface
@@ -208,6 +216,10 @@ Access to all of the builder _defined_ methods (typically these end in an esclam
208
216
  * `_.tag! :foo`: insert elements where the name can be dynamic
209
217
  * `_.error 'Log message'`: write a message to the server log
210
218
 
219
+ Underscores in element and attribute names are converted to dashes. To
220
+ disable this behavior, express attribute names as strings and use the `_.tag!`
221
+ method for element names.
222
+
211
223
  XHTML differs from HTML in the escaping of inline style and script elements.
212
224
  XHTML will also fall back to HTML output unless the user agent indicates it
213
225
  supports XHTML via the HTTP Accept header.
@@ -227,6 +239,12 @@ If one of the attributes passed on the `_html` declaration is `:_width`, an
227
239
  attempt will be made to reflow text in order to not exceed this line width.
228
240
  This won't be done if it will affect what actually is displayed.
229
241
 
242
+ If none of the child elements for the `html` element are either `head` or
243
+ `body`, then these tags will be created for you, and the relevant children
244
+ will be moved to the appropriate section. If the body contains a `h1`
245
+ element, and the `head` doesn't contain a `title`, a title element will be
246
+ created based on the text supplied to the first `h1` element.
247
+
230
248
  Methods provided to Wunderbar.json
231
249
  ---
232
250
 
@@ -406,3 +424,11 @@ The following gems, if installed, will produce cleaner and prettier output:
406
424
 
407
425
  * `nokogiri` cleans up HTML fragments inserted via `<<`
408
426
  * `escape` prettier quoting of `system` commands
427
+
428
+ Related efforts:
429
+ ---
430
+ * [Builder](https://github.com/jimweirich/builder#readme)
431
+ * [JBuilder](https://github.com/rails/jbuilder)
432
+ * [Markaby](http://markaby.rubyforge.org/)
433
+ * [Nokogiri::HTML::Builder](http://nokogiri.org/Nokogiri/HTML/Builder.html)
434
+ * [Tagz](https://github.com/ahoward/tagz)
@@ -1,93 +1,4 @@
1
1
  module Wunderbar
2
- # XmlMarkup handles indentation of elements beautifully, this class extends
3
- # that support to text, data, and spacing between elements
4
- class SpacedMarkup < Builder::XmlMarkup
5
- def indented_text!(text)
6
- indented_data!(text) {|data| text! data}
7
- end
8
-
9
- def indented_data!(data, pre=nil, post=nil, &block)
10
- return if data.strip.length == 0
11
-
12
- self << pre + target!.pop if pre
13
-
14
- if @indent > 0
15
- data = data.sub(/\n\s*\Z/, '').sub(/\A\s*\n/, '')
16
-
17
- unindent = data.sub(/s+\Z/,'').scan(/^ *\S/).map(&:length).min || 1
18
-
19
- before = ::Regexp.new('^'.ljust(unindent))
20
- after = " " * (@level * @indent)
21
- data.gsub! before, after
22
-
23
- _newline if @pending_newline and not @first_tag
24
- @pending_newline = @pending_margin
25
- @first_tag = @pending_margin = false
26
- end
27
-
28
- if block
29
- block.call(data)
30
- else
31
- self << data
32
- end
33
-
34
- _newline unless data =~ /\n\Z/
35
-
36
- self << post if post
37
- end
38
-
39
- def disable_indentation!(&block)
40
- indent, level, pending_newline, pending_margin =
41
- indentation_state! [0, 0, @pending_newline, @pending_margin]
42
- text! " "*indent*level
43
- block.call
44
- ensure
45
- indentation_state! [indent, level, pending_newline, pending_margin]
46
- end
47
-
48
- def indentation_state! new_state=nil
49
- result = [@indent, @level, @pending_newline, @pending_margin]
50
- if new_state
51
- text! "\n" if @indent == 0 and new_state.first > 0
52
- @indent, @level, @pending_newline, @pending_margin = new_state
53
- end
54
- result
55
- end
56
-
57
- def margin!
58
- _newline unless @first_tag
59
- @pending_newline = false
60
- @pending_margin = true
61
- end
62
-
63
- def _nested_structures(*args)
64
- pending_newline = @pending_newline
65
- @pending_newline = false
66
- @first_tag = true
67
- super
68
- @first_tag = @pending_margin = false
69
- @pending_newline = pending_newline
70
- end
71
-
72
- def tag!(sym, *args, &block)
73
- _newline if @pending_newline
74
- @pending_newline = @pending_margin
75
- @first_tag = @pending_margin = false
76
- # workaround for https://github.com/jimweirich/builder/commit/7c824996637d2d76455c87ad47d76ba440937e38
77
- sym = "#{sym}:#{args.shift}" if args.first.kind_of?(::Symbol)
78
- if not block and args.first == ''
79
- attrs = {}
80
- attrs.merge!(args.last) if ::Hash === args.last
81
- _indent
82
- _start_tag(sym, attrs)
83
- _end_tag(sym)
84
- _newline
85
- else
86
- super
87
- end
88
- end
89
- end
90
-
91
2
  class BuilderBase
92
3
  def set_variables_from_params(locals={})
93
4
  @_scope.params.merge(locals).each do |key,value|
@@ -117,18 +28,19 @@ module Wunderbar
117
28
  class XmlMarkup < BuilderClass
118
29
  def initialize(args)
119
30
  @_scope = args.delete(:scope)
120
- @_builder = SpacedMarkup.new(args)
31
+ @_indent = args.delete(:indent) or 2
121
32
  @_pdf = false
33
+ @doc = Node.new(nil)
34
+ @node = @doc
35
+ @indentation_enabled = true
36
+ @width = nil
37
+ @spaced = false
122
38
  end
123
39
 
124
- # forward to Wunderbar, XmlMarkup, or @_scope
40
+ # forward to Wunderbar or @_scope
125
41
  def method_missing(method, *args, &block)
126
42
  if Wunderbar.respond_to? method
127
43
  Wunderbar.send method, *args, &block
128
- elsif SpacedMarkup.public_instance_methods.include? method
129
- @_builder.__send__ method, *args, &block
130
- elsif SpacedMarkup.public_instance_methods.include? method.to_s
131
- @_builder.__send__ method, *args, &block
132
44
  elsif @_scope and @_scope.respond_to? method
133
45
  @_scope.send method, *args, &block
134
46
  else
@@ -151,6 +63,46 @@ module Wunderbar
151
63
  super
152
64
  end
153
65
 
66
+ def text! text
67
+ @node.add_text text
68
+ end
69
+
70
+ def declare! *args
71
+ @node.children << DocTypeNode.new(*args)
72
+ end
73
+
74
+ def comment! text
75
+ @node.children << CommentNode.new(text)
76
+ end
77
+
78
+ def indented_text!(text)
79
+ return if text.strip.length == 0
80
+ @node.children << IndentedTextNode.new(text)
81
+ end
82
+
83
+ def target!
84
+ "#{@doc.serialize(indent: ' ' * @_indent).join("\n")}\n"
85
+ end
86
+
87
+ def clear!
88
+ @doc.children.clear
89
+ @node = @doc
90
+ end
91
+
92
+ def compact!(width, &block)
93
+ begin
94
+ @width = width
95
+ @indentation_enabled = false
96
+ block.call
97
+ ensure
98
+ @indentation_enabled = true
99
+ end
100
+ end
101
+
102
+ def spaced!
103
+ @spaced = true
104
+ end
105
+
154
106
  # avoid method_missing overhead for the most common case
155
107
  def tag!(sym, *args, &block)
156
108
  if sym.respond_to? :children
@@ -173,19 +125,32 @@ module Wunderbar
173
125
  end
174
126
  end
175
127
 
176
- if !block and (args.empty? or args == [''])
177
- CssProxy.new(@_builder, @_builder.target!, sym, args)
128
+ if Class === args.first and args.first < Node
129
+ node = args.shift.new sym, *args
178
130
  else
179
- @_builder.tag! sym, *args, &block
131
+ node = Node.new sym, *args
132
+ end
133
+
134
+ unless @indentation_enabled
135
+ node.extend CompactNode
136
+ node.width = @width
137
+ end
138
+
139
+ if @spaced
140
+ node.extend SpacedNode
141
+ @spaced = false
180
142
  end
181
- end
182
143
 
183
- def proxiable_tag!(sym, *args, &block)
144
+ node.text = args.first if String === args.first
145
+ @node.add_child node
146
+ @node = node
184
147
  if block
185
- tag!(sym, *args, &block)
186
- else
187
- CssProxy.new(@_builder, @_builder.target!, sym, args)
148
+ block.call(self)
149
+ @node.children << nil if @node.children.empty?
188
150
  end
151
+ @node = @node.parent
152
+
153
+ node
189
154
  end
190
155
 
191
156
  def pdf=(value)
@@ -230,7 +195,7 @@ module Wunderbar
230
195
  stderr = output_class[:stderr] || '_stderr'
231
196
  hilite = output_class[:hilite] || '_stdout _hilite'
232
197
 
233
- @_builder.tag! tag, echo, :class=>stdin unless opts[:echo] == false
198
+ tag! tag, echo, :class=>stdin unless opts[:echo] == false
234
199
 
235
200
  require 'thread'
236
201
  semaphore = Mutex.new
@@ -241,9 +206,9 @@ module Wunderbar
241
206
  out_line = pout.readline.chomp
242
207
  semaphore.synchronize do
243
208
  if patterns.any? {|pattern| out_line =~ pattern}
244
- @_builder.tag! tag, out_line, :class=>hilite
209
+ tag! tag, out_line, :class=>hilite
245
210
  else
246
- @_builder.tag! tag, out_line, :class=>stdout
211
+ tag! tag, out_line, :class=>stdout
247
212
  end
248
213
  end
249
214
  end
@@ -253,7 +218,7 @@ module Wunderbar
253
218
  until perr.eof?
254
219
  err_line = perr.readline.chomp
255
220
  semaphore.synchronize do
256
- @_builder.tag! tag, err_line, :class=>stderr
221
+ tag! tag, err_line, :class=>stderr
257
222
  end
258
223
  end
259
224
  end,
@@ -272,44 +237,37 @@ module Wunderbar
272
237
  end
273
238
  end
274
239
 
275
- # declaration (DOCTYPE, etc)
276
- def declare(*args)
277
- @_builder.declare!(*args)
278
- end
279
-
280
- # comment
281
- def comment(*args)
282
- @_builder.comment! *args
283
- end
284
-
285
240
  # insert verbatim
286
241
  def <<(data)
287
242
  if not String === data or data.include? '<' or data.include? '&'
288
243
  require 'nokogiri'
289
244
  data = Nokogiri::HTML::fragment(data.to_s).to_xml
290
- end
291
245
 
292
- # fix CDATA in most cases (notably scripts)
293
- data.gsub!(/<!\[CDATA\[(.*?)\]\]>/m) do |cdata|
294
- if $1.include? '<' or $1.include? '&'
295
- "//<![CDATA[\n#{$1}\n//]]>"
296
- else
297
- $1
246
+ # fix CDATA in most cases (notably scripts)
247
+ data.gsub!(/<!\[CDATA\[(.*?)\]\]>/m) do
248
+ if $1.include? '<' or $1.include? '&'
249
+ "//<![CDATA[\n#{$1}\n//]]>"
250
+ else
251
+ $1
252
+ end
298
253
  end
299
- end
300
254
 
301
- # fix CDATA for style elements
302
- data.gsub!(/<style([^>])*>\/\/<!\[CDATA\[\n(.*?)\s+\/\/\]\]>/m) do |cdata|
303
- if $2.include? '<' or $2.include? '&'
304
- "<style#{$1}>/*<![CDATA[*/\n#{$2.gsub("\n\Z",'')}\n/*]]>*/"
305
- else
306
- $1
255
+ # fix CDATA for style elements
256
+ data.gsub!(/<style([^>])*>\/\/<!\[CDATA\[\n(.*?)\s+\/\/\]\]>/m) do
257
+ if $2.include? '<' or $2.include? '&'
258
+ "<style#{$1}>/*<![CDATA[*/\n#{$2.gsub("\n\Z",'')}\n/*]]>*/"
259
+ else
260
+ $1
261
+ end
307
262
  end
308
263
  end
309
-
310
- @_builder << data
311
264
  rescue LoadError
312
- @_builder << data
265
+ ensure
266
+ if String === data
267
+ @node.children << data
268
+ else
269
+ @node.add_child data
270
+ end
313
271
  end
314
272
 
315
273
  def [](*children)
@@ -331,10 +289,8 @@ module Wunderbar
331
289
  text = child.text
332
290
  if text.strip.empty?
333
291
  text! "\n" if text.count("\n")>1
334
- elsif indentation_state!.first == 0
335
- indented_text! text
336
292
  else
337
- indented_text! text.strip
293
+ indented_text! text
338
294
  end
339
295
  elsif child.comment?
340
296
  comment! child.text.sub(/\A /,'').sub(/ \Z/, '')
@@ -354,7 +310,7 @@ module Wunderbar
354
310
  if stop == 0
355
311
  self[children.shift]
356
312
  else
357
- disable_indentation! do
313
+ compact!(nil) do
358
314
  self[*children.shift(stop || children.length)]
359
315
  end
360
316
  end
@@ -362,7 +318,7 @@ module Wunderbar
362
318
  end
363
319
  else
364
320
  # disable indentation on the entire element
365
- disable_indentation! do
321
+ compact!(nil) do
366
322
  tag!(child) {self[*child.children]}
367
323
  end
368
324
  end
@@ -103,13 +103,8 @@ module Wunderbar
103
103
  headers['status'] = "500 Internal Server Error"
104
104
  x.clear!
105
105
  output = x.html(*args) do
106
- _head do
107
- _title 'Internal Server Error'
108
- end
109
- _body do
110
- _h1 'Internal Server Error'
111
- _exception exception
112
- end
106
+ _h1 'Internal Server Error'
107
+ _exception exception
113
108
  end
114
109
  end
115
110
 
@@ -192,6 +187,11 @@ module Wunderbar
192
187
  self.text(scope, *args, &block)
193
188
  return
194
189
  end
190
+ when Proc
191
+ unless xhr_json or text
192
+ instance_exec scope, args, block, &type
193
+ return
194
+ end
195
195
  end
196
196
  end
197
197
  end
@@ -1,27 +1,12 @@
1
1
  module Wunderbar
2
- # Class "lifted" from Markaby to store element options. Methods called
2
+ # Class inspired by Markaby to store element options. Methods called
3
3
  # against the CssProxy object are added as element classes or IDs.
4
4
  #
5
- # Modified to accept args for empty, non-void elements, and to capture and
6
- # restore indentation state.
7
- #
8
5
  # See the README for examples.
9
- class CssProxy
10
- def initialize(builder, stream, sym, args)
6
+ class CssProxy < BasicObject
7
+ def initialize(builder, node)
11
8
  @builder = builder
12
- @indent = builder.indentation_state!
13
- @stream = stream
14
- @sym = sym
15
- @args = args
16
- @attrs = {}
17
-
18
- @original_stream_length = @stream.length
19
-
20
- @builder.tag! sym, *args
21
- end
22
-
23
- def respond_to?(sym, include_private = false)
24
- include_private || !private_methods.map { |m| m.to_sym }.include?(sym.to_sym) ? true : false
9
+ @node = node
25
10
  end
26
11
 
27
12
  private
@@ -29,44 +14,29 @@ module Wunderbar
29
14
  # Adds attributes to an element. Bang methods set the :id attribute.
30
15
  # Other methods add to the :class attribute.
31
16
  def method_missing(id_or_class, *args, &block)
17
+ empty = args.empty?
18
+ attrs = @node.attrs
19
+
32
20
  if id_or_class.to_s =~ /(.*)!$/
33
- @attrs[:id] = $1
21
+ attrs[:id] = $1
22
+ elsif attrs[:class]
23
+ attrs[:class] = "#{attrs[:class]} #{id_or_class}"
34
24
  else
35
- id = id_or_class
36
- @attrs[:class] = @attrs[:class] ? "#{@attrs[:class]} #{id}".strip : id
37
- end
38
-
39
- @attrs.merge! args.pop.to_hash if args.last.respond_to? :to_hash
40
- @attrs.merge! @args.pop.to_hash if @args.last.respond_to? :to_hash
41
-
42
- # delete attrs with false/nil values; change true to attribute name
43
- @attrs.delete_if {|key, value| !value}
44
- @attrs.each {|key, value| @attrs[key]=key if value == true}
45
-
46
- args.push(@attrs)
47
- args = @args + args unless block or String === args.first
48
-
49
- if @sym == :pre
50
- args.first.chomp! if String === args.first and args.first.end_with? "\n"
25
+ attrs[:class] = id_or_class
51
26
  end
52
27
 
53
- while @stream.length > @original_stream_length
54
- @stream.pop
55
- end
28
+ attrs.merge! args.pop.to_hash if args.last.respond_to? :to_hash
29
+ args.push(attrs)
56
30
 
57
- begin
58
- indent = @builder.indentation_state! @indent
31
+ @node.parent.children.delete(@node)
59
32
 
60
- if block
61
- @builder.tag! @sym, *args, &block
62
- else
63
- @builder.tag! @sym, *args
64
- end
65
- ensure
66
- @builder.indentation_state! indent
33
+ if empty and not block
34
+ @builder.proxiable_tag! @node.name, *args
35
+ elsif SpacedNode === @node
36
+ @builder.__send__ "_#{@node.name}_", *args, &block
37
+ else
38
+ @builder.__send__ "_#{@node.name}", *args, &block
67
39
  end
68
-
69
- self
70
40
  end
71
41
  end
72
42
  end
@@ -23,7 +23,7 @@ module Wunderbar
23
23
  # contained within data provided by users of the site.
24
24
  $:.each_with_index do |path, index|
25
25
  if path.tainted?
26
- $:[index] = File.expand_path(path.untaint).untaint
26
+ $:[index] = File.expand_path(path.dup.untaint).untaint
27
27
  end
28
28
  end
29
29
  end
@@ -37,6 +37,12 @@ module Wunderbar
37
37
  @env = env
38
38
  end
39
39
  end
40
+
41
+ @@templates = {}
42
+
43
+ def self.templates
44
+ @@templates
45
+ end
40
46
  end
41
47
 
42
48
  require 'socket'
@@ -75,6 +81,10 @@ if self.to_s == 'main'
75
81
  Wunderbar.websocket(*args, &block)
76
82
  end
77
83
 
84
+ def _template(name, &block)
85
+ Wunderbar.templates[name.to_s.gsub('_','-')] = block
86
+ end
87
+
78
88
  def env
79
89
  ENV
80
90
  end