pakyow-presenter 0.10.2 → 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/pakyow-presenter/CHANGELOG.md +16 -0
  3. data/pakyow-presenter/lib/pakyow-presenter.rb +1 -11
  4. data/pakyow-presenter/lib/pakyow/presenter.rb +8 -0
  5. data/pakyow-presenter/lib/{presenter → pakyow/presenter}/attributes.rb +21 -14
  6. data/pakyow-presenter/lib/pakyow/presenter/base.rb +38 -0
  7. data/pakyow-presenter/lib/{presenter → pakyow/presenter}/binder.rb +19 -6
  8. data/pakyow-presenter/lib/{presenter → pakyow/presenter}/binder_set.rb +18 -21
  9. data/pakyow-presenter/lib/{presenter → pakyow/presenter}/binding_eval.rb +14 -0
  10. data/pakyow-presenter/lib/{presenter → pakyow/presenter}/config/presenter.rb +12 -6
  11. data/pakyow-presenter/lib/{presenter → pakyow/presenter}/container.rb +0 -0
  12. data/pakyow-presenter/lib/{presenter → pakyow/presenter}/doc_helpers.rb +0 -0
  13. data/pakyow-presenter/lib/{presenter → pakyow/presenter}/exceptions.rb +0 -0
  14. data/pakyow-presenter/lib/pakyow/presenter/ext/app.rb +33 -0
  15. data/pakyow-presenter/lib/pakyow/presenter/ext/call_context.rb +28 -0
  16. data/pakyow-presenter/lib/pakyow/presenter/helpers.rb +46 -0
  17. data/pakyow-presenter/lib/{presenter → pakyow/presenter}/page.rb +0 -0
  18. data/pakyow-presenter/lib/{presenter → pakyow/presenter}/partial.rb +0 -0
  19. data/pakyow-presenter/lib/{presenter → pakyow/presenter}/presenter.rb +14 -9
  20. data/pakyow-presenter/lib/{presenter → pakyow/presenter}/string_doc.rb +35 -9
  21. data/pakyow-presenter/lib/{presenter → pakyow/presenter}/string_doc_parser.rb +41 -30
  22. data/pakyow-presenter/lib/{presenter → pakyow/presenter}/string_doc_renderer.rb +0 -0
  23. data/pakyow-presenter/lib/{presenter → pakyow/presenter}/template.rb +0 -0
  24. data/pakyow-presenter/lib/{presenter → pakyow/presenter}/view.rb +79 -36
  25. data/pakyow-presenter/lib/{presenter → pakyow/presenter}/view_collection.rb +10 -4
  26. data/pakyow-presenter/lib/{presenter → pakyow/presenter}/view_composer.rb +43 -3
  27. data/pakyow-presenter/lib/{presenter → pakyow/presenter}/view_context.rb +12 -8
  28. data/pakyow-presenter/lib/{presenter → pakyow/presenter}/view_store.rb +3 -1
  29. data/pakyow-presenter/lib/{presenter → pakyow/presenter}/view_store_loader.rb +0 -0
  30. data/pakyow-presenter/lib/{presenter → pakyow/presenter}/view_version.rb +19 -3
  31. data/pakyow-presenter/lib/pakyow/views/errors/404.erb +26 -0
  32. data/pakyow-presenter/lib/pakyow/views/errors/500.erb +23 -0
  33. metadata +39 -38
  34. data/pakyow-presenter/lib/presenter/base.rb +0 -27
  35. data/pakyow-presenter/lib/presenter/ext/app.rb +0 -63
  36. data/pakyow-presenter/lib/presenter/helpers.rb +0 -40
  37. data/pakyow-presenter/lib/views/errors/404.html +0 -5
  38. data/pakyow-presenter/lib/views/errors/500.html +0 -0
@@ -18,7 +18,11 @@ module Pakyow
18
18
  return contents
19
19
  end
20
20
 
21
- return processor.call(contents)
21
+ processed = processor.call(contents)
22
+
23
+ # reprocess html content unless we just did that
24
+ return processed if format == :html
25
+ process(processed, :html)
22
26
  end
23
27
  end
24
28
 
@@ -33,11 +37,15 @@ module Pakyow
33
37
  }
34
38
 
35
39
  Pakyow::App.after(:route) {
36
- if @presenter.presented?
37
- @found = true
38
- @context.response.body = [@presenter.content]
40
+ if Config.presenter.require_route && !found?
41
+ @found
39
42
  else
40
- @found = false unless found?
43
+ if @presenter.presented?
44
+ @found = true
45
+ @context.response.body = [@presenter.content]
46
+ else
47
+ @found = false unless found?
48
+ end
41
49
  end
42
50
  }
43
51
 
@@ -163,10 +171,7 @@ module Pakyow
163
171
  end
164
172
 
165
173
  def compose_at(path, opts = {}, &block)
166
- composer = ViewComposer.from_path(store, path, opts, &block)
167
- return composer unless opts.empty? || block_given?
168
-
169
- @composer = composer
174
+ @composer = ViewComposer.from_path(store, path, opts, &block)
170
175
  end
171
176
 
172
177
  def has_path?
@@ -51,6 +51,10 @@ module Pakyow
51
51
  end
52
52
  end
53
53
 
54
+ def attribute?(name)
55
+ attributes.key?(name.to_sym)
56
+ end
57
+
54
58
  def set_attribute(name, value)
55
59
  return if attributes.nil?
56
60
  attributes[name.to_sym] = value
@@ -65,9 +69,24 @@ module Pakyow
65
69
  attributes.delete(name.to_sym)
66
70
  end
67
71
 
72
+ def has_attribute?(name)
73
+ attributes.key?(name)
74
+ end
75
+
68
76
  def remove
69
77
  @structure.delete_if { |n| n.equal?(node) }
70
- @node = ['', {}, [['', {}, []]]]
78
+
79
+ if @node.nil?
80
+ @node = ['', {}, [['', {}, []]]]
81
+ else
82
+ @node[0] = ''
83
+ @node[1] = {}
84
+ @node[2][0][0] = ''
85
+ @node[2][0][1] = {}
86
+ @node[2][0][2] = []
87
+ @node[2].slice!(1..-1)
88
+ end
89
+
71
90
  @removed = true
72
91
  end
73
92
 
@@ -184,6 +203,7 @@ module Pakyow
184
203
  end
185
204
 
186
205
  def to_html
206
+ StringDocRenderer.render((@node && !@removed) ? [@node] : @structure)
187
207
  StringDocRenderer.render(@node ? [@node] : @structure)
188
208
  end
189
209
  alias :to_s :to_html
@@ -195,7 +215,7 @@ module Pakyow
195
215
  end
196
216
 
197
217
  def node
198
- return @structure if @structure.empty?
218
+ return @structure if @structure.empty? && !@removed
199
219
  return @node || @structure[0]
200
220
  end
201
221
 
@@ -241,12 +261,8 @@ module Pakyow
241
261
  node[1]
242
262
  end
243
263
 
244
- def has_attribute?(name)
245
- attributes.key?(name)
246
- end
247
-
248
264
  def children
249
- if @structure.empty?
265
+ if @structure.empty? && !@removed
250
266
  @structure
251
267
  else
252
268
  node[2][0][2]
@@ -267,7 +283,7 @@ module Pakyow
267
283
  def find_partials(structure, primary_structure = @structure, partials = {})
268
284
  structure.inject(partials) { |s, e|
269
285
  if e[1].has_key?(:partial)
270
- s[e[1][:partial]] = StringDoc.from_structure(primary_structure, node: e)
286
+ (s[e[1][:partial]] ||= []) << StringDoc.from_structure(primary_structure, node: e)
271
287
  end
272
288
  find_partials(e[2], e[2], s)
273
289
  s
@@ -308,10 +324,20 @@ module Pakyow
308
324
 
309
325
  def find_node_props(node, primary_structure = @structure, props = [])
310
326
  if node[1].has_key?(:'data-prop')
311
- props << {
327
+ prop = {
312
328
  doc: StringDoc.from_structure(primary_structure, node: node),
313
329
  prop: node[1][:'data-prop'].to_sym,
330
+ parts: {},
314
331
  }
332
+
333
+ if node[1].has_key?(:'data-parts')
334
+ prop[:parts][:include] = node[1][:'data-parts'].split(/\s+/).map(&:to_sym)
335
+ end
336
+
337
+ if node[1].has_key?(:'data-parts-exclude')
338
+ prop[:parts][:exclude] = node[1][:'data-parts-exclude'].split(/\s+/).map(&:to_sym)
339
+ end
340
+ props << prop
315
341
  end
316
342
 
317
343
  unless node[1].has_key?(:'data-scope')
@@ -3,6 +3,7 @@ module Pakyow
3
3
  class StringDocParser
4
4
  PARTIAL_REGEX = /<!--\s*@include\s*([a-zA-Z0-9\-_]*)\s*-->/
5
5
  CONTAINER_REGEX = /@container( ([a-zA-Z0-9\-_]*))*/
6
+ SIGNIFICANT = [:scope?, :prop?, :container?, :partial?, :option?, :component?]
6
7
 
7
8
  def initialize(html)
8
9
  @html = html
@@ -20,45 +21,51 @@ module Pakyow
20
21
  def parse(doc)
21
22
  structure = []
22
23
 
23
- if doc.is_a?(Nokogiri::HTML::Document)
24
+ unless doc.is_a?(Oga::XML::Element) || !doc.respond_to?(:doctype) || doc.doctype.nil?
24
25
  structure << ['<!DOCTYPE html>', {}, []]
25
26
  end
26
27
 
27
28
  breadth_first(doc) do |node, queue|
28
29
  if node == doc
29
- queue.concat(node.children)
30
+ queue.concat(node.children.to_a)
30
31
  next
31
32
  end
32
33
 
33
- children = node.children.reject {|n| n.is_a?(Nokogiri::XML::Text)}
34
- attributes = node.attributes
34
+ children = node.children.reject {|n| n.is_a?(Oga::XML::Text)}
35
+
36
+ if node.is_a?(Oga::XML::Element)
37
+ attributes = node.attributes
38
+ else
39
+ attributes = []
40
+ end
41
+
35
42
  if !structure.empty? && children.empty? && !significant?(node)
36
- structure << [node.to_html, {}, []]
43
+ structure << [node.to_xml, {}, []]
37
44
  else
38
45
  if significant?(node)
39
- if scope?(node) || prop?(node) || option?(node) || component?(node)
46
+ if container?(node)
47
+ match = node.text.strip.match(CONTAINER_REGEX)
48
+ name = (match[2] || :default).to_sym
49
+ structure << [node.to_xml, { container: name }, []]
50
+ elsif partial?(node)
51
+ next unless match = node.to_xml.strip.match(PARTIAL_REGEX)
52
+ name = match[1].to_sym
53
+ structure << [node.to_xml, { partial: name }, []]
54
+ else
40
55
  attr_structure = attributes.inject({}) do |attrs, attr|
41
- attrs[attr[1].name.to_sym] = attr[1].value
56
+ attrs[attr.name.to_sym] = attr.value
42
57
  attrs
43
58
  end
44
59
 
45
60
  closing = [['>', {}, parse(node)]]
46
61
  closing << ["</#{node.name}>", {}, []] unless self_closing?(node.name)
47
62
  structure << ["<#{node.name} ", attr_structure, closing]
48
- elsif container?(node)
49
- match = node.text.strip.match(CONTAINER_REGEX)
50
- name = (match[2] || :default).to_sym
51
- structure << [node.to_html, { container: name }, []]
52
- elsif partial?(node)
53
- next unless match = node.to_html.strip.match(PARTIAL_REGEX)
54
- name = match[1].to_sym
55
- structure << [node.to_html, { partial: name }, []]
56
63
  end
57
64
  else
58
- if node.is_a?(Nokogiri::XML::Text)
59
- structure << [node.text, {}, []]
65
+ if node.is_a?(Oga::XML::Text) || node.is_a?(Oga::XML::Comment)
66
+ structure << [node.to_xml, {}, []]
60
67
  else
61
- attr_s = attributes.inject('') { |s, a| s << " #{a[1].name}=\"#{a[1].value}\""; s }
68
+ attr_s = attributes.inject('') { |s, a| s << " #{a.name}=\"#{a.value}\""; s }
62
69
  closing = [['>', {}, parse(node)]]
63
70
  closing << ['</' + node.name + '>', {}, []] unless self_closing?(node.name)
64
71
  structure << ['<' + node.name + attr_s, {}, closing]
@@ -71,37 +78,45 @@ module Pakyow
71
78
  end
72
79
 
73
80
  def significant?(node)
74
- scope?(node) || prop?(node) || container?(node) || partial?(node) || option?(node) || component?(node)
81
+ SIGNIFICANT.each do |method|
82
+ return true if send(method, node)
83
+ end
84
+
85
+ false
75
86
  end
76
87
 
77
88
  def scope?(node)
78
- return false unless node['data-scope']
89
+ return false unless node.is_a?(Oga::XML::Element)
90
+ return false unless node.attribute('data-scope')
79
91
  return true
80
92
  end
81
93
 
82
94
  def prop?(node)
83
- return false unless node['data-prop']
95
+ return false unless node.is_a?(Oga::XML::Element)
96
+ return false unless node.attribute('data-prop')
84
97
  return true
85
98
  end
86
99
 
87
100
  def container?(node)
88
- return false unless node.is_a?(Nokogiri::XML::Comment)
101
+ return false unless node.is_a?(Oga::XML::Comment)
89
102
  return false unless node.text.strip.match(CONTAINER_REGEX)
90
103
  return true
91
104
  end
92
105
 
93
106
  def partial?(node)
94
- return false unless node.is_a?(Nokogiri::XML::Comment)
95
- return false unless node.to_html.strip.match(PARTIAL_REGEX)
107
+ return false unless node.is_a?(Oga::XML::Comment)
108
+ return false unless node.to_xml.strip.match(PARTIAL_REGEX)
96
109
  return true
97
110
  end
98
111
 
99
112
  def option?(node)
113
+ return false unless node.is_a?(Oga::XML::Element)
100
114
  node.name == 'option'
101
115
  end
102
116
 
103
117
  def component?(node)
104
- return false unless node['data-ui']
118
+ return false unless node.is_a?(Oga::XML::Element)
119
+ return false unless node.attribute('data-ui')
105
120
  return true
106
121
  end
107
122
 
@@ -116,11 +131,7 @@ module Pakyow
116
131
  end
117
132
 
118
133
  def doc_from_string(string)
119
- if string.match(/<html.*>/)
120
- Nokogiri::HTML::Document.parse(string)
121
- else
122
- Nokogiri::HTML.fragment(string)
123
- end
134
+ Oga.parse_html(string)
124
135
  end
125
136
 
126
137
  SELF_CLOSING = %w[area base basefont br hr input img link meta]
@@ -43,7 +43,6 @@ module Pakyow
43
43
  # Creates a view from a doc.
44
44
  #
45
45
  # @see StringDoc
46
- # @see NokogiriDoc
47
46
  #
48
47
  def self.from_doc(doc)
49
48
  view = new
@@ -289,21 +288,31 @@ module Pakyow
289
288
  end
290
289
 
291
290
  def includes(partial_map)
292
- partials = @doc.partials
291
+ doc_partials = @doc.partials
293
292
  partial_map = partial_map.dup
294
293
 
295
294
  # mixin all the partials
296
- partials.each do |partial_info|
297
- partial = partial_map[partial_info[0]]
298
- next if partial.nil?
299
- partial_info[1].replace(partial.doc.dup)
295
+ doc_partials.each do |partial_name, partial_docs|
296
+ partials = Array.ensure(partial_map[partial_name])
297
+
298
+ partial_docs.each_with_index do |partial_doc, i|
299
+ replacement = partials[i]
300
+ next if replacement.nil?
301
+
302
+ if replacement.is_a?(ViewCollection)
303
+ partial_doc.replace(replacement.views.first.doc.dup)
304
+ partials = replacement.views
305
+ else
306
+ partial_doc.replace(replacement.doc)
307
+ end
308
+ end
300
309
  end
301
310
 
302
311
  # refind the partials
303
- partials = @doc.partials
312
+ doc_partials = @doc.partials
304
313
 
305
314
  # if mixed in partials included partials, we want to run includes again with a new map
306
- if partials.count > 0 && (partial_map.keys - partials.keys).count < partial_map.keys.count
315
+ if doc_partials.count > 0 && (partial_map.keys - doc_partials.keys).count < partial_map.keys.count
307
316
  includes(partial_map)
308
317
  end
309
318
 
@@ -324,8 +333,29 @@ module Pakyow
324
333
  attrs.send(:'data-ui').value
325
334
  end
326
335
 
336
+ # Convenience method for parity with Presenter::ViewCollection.
337
+ #
338
+ def length
339
+ 1
340
+ end
341
+
342
+ # Convenience method for parity with Presenter::ViewCollection.
343
+ #
344
+ def first
345
+ self
346
+ end
347
+
327
348
  private
328
349
 
350
+ def adjust_value_parts(value, parts)
351
+ return value unless value.is_a?(Hash)
352
+
353
+ parts_to_keep = parts.fetch(:include, value.keys)
354
+ parts_to_keep -= parts.fetch(:exclude, [])
355
+
356
+ value.keep_if { |part, _| parts_to_keep.include?(part) }
357
+ end
358
+
329
359
  def bind_data_to_scope(data, scope_info, bindings, ctx)
330
360
  return unless data
331
361
  return unless scope_info
@@ -335,8 +365,9 @@ module Pakyow
335
365
 
336
366
  scope_info[:props].each do |prop_info|
337
367
  catch(:unbound) do
338
- prop = prop_info[:prop]
339
- doc = prop_info[:doc]
368
+ prop = prop_info[:prop]
369
+ doc = prop_info[:doc]
370
+ parts = prop_info[:parts]
340
371
 
341
372
  if DocHelpers.form_field?(doc.tagname)
342
373
  set_form_field_name(doc, scope, prop)
@@ -344,6 +375,7 @@ module Pakyow
344
375
 
345
376
  if data_has_prop?(data, prop) || Binder.instance.has_scoped_prop?(scope, prop, bindings)
346
377
  value = Binder.instance.value_for_scoped_prop(scope, prop, data, bindings, ctx)
378
+ value = adjust_value_parts(value, parts)
347
379
 
348
380
  if DocHelpers.form_field?(doc.tagname)
349
381
  bind_to_form_field(doc, scope, prop, value, data, ctx)
@@ -423,30 +455,38 @@ module Pakyow
423
455
  options = Binder.instance.options_for_scoped_prop(scope, prop, bindable, ctx)
424
456
  return if options.nil?
425
457
 
426
- option_nodes = Nokogiri::HTML::DocumentFragment.parse('')
427
- Nokogiri::HTML::Builder.with(option_nodes) do |h|
428
- until options.length == 0
429
- catch :optgroup do
430
- o = options.first
458
+ nodes = Oga::XML::Document.new
459
+
460
+ until options.length == 0
461
+ catch :optgroup do
462
+ o = options.first
463
+
464
+ # an array containing value/content
465
+ if o.is_a?(Array)
466
+ node = Oga::XML::Element.new(name: 'option')
467
+ node.inner_text = o[1].to_s
468
+ node.set('value', o[0].to_s)
469
+ nodes.children << node
470
+ options.shift
471
+ else # likely an object (e.g. string); start a group
472
+ node_group = Oga::XML::Element.new(name: 'optgroup')
473
+ node_group.set('label', o.to_s)
474
+ nodes.children << node_group
431
475
 
432
- # an array containing value/content
433
- if o.is_a?(Array)
434
- h.option o[1], value: o[0]
476
+ options.shift
477
+
478
+ options[0..-1].each_with_index { |o2,i2|
479
+ # starting a new group
480
+ throw :optgroup unless o2.is_a?(Array)
481
+
482
+ h.option o2[1].to_s, value: o2[0].to_s
483
+
484
+ node = Oga::XML::Element.new(name: 'option')
485
+ node.inner_text = o2[1].to_s
486
+ node.set('value', o2[0].to_s)
487
+ node_group.children << node
435
488
  options.shift
436
- # likely an object (e.g. string); start a group
437
- else
438
- h.optgroup(label: o) {
439
- options.shift
440
-
441
- options[0..-1].each_with_index { |o2,i2|
442
- # starting a new group
443
- throw :optgroup unless o2.is_a?(Array)
444
-
445
- h.option o2[1], value: o2[0]
446
- options.shift
447
- }
448
- }
449
- end
489
+ }
450
490
  end
451
491
  end
452
492
  end
@@ -455,7 +495,7 @@ module Pakyow
455
495
  doc.clear
456
496
 
457
497
  # add generated options
458
- doc.append(option_nodes.to_html)
498
+ doc.append(nodes.to_xml)
459
499
  end
460
500
 
461
501
  def select_option_with_value(doc, value)
@@ -483,12 +523,15 @@ module Pakyow
483
523
  attrs = Attributes.new(doc)
484
524
 
485
525
  if v.respond_to?(:to_proc)
486
- # Evaluating the proc will set the value in the doc
487
- v.to_proc.call(attrs.send(attr))
526
+ attribute = attrs.send(attr)
527
+ ret = v.to_proc.call(attribute)
528
+ value = ret.respond_to?(:value) ? ret.value : ret
529
+
530
+ attrs.send("#{attr}=", value)
488
531
  elsif v.nil?
489
532
  doc.remove_attribute(attr)
490
533
  else
491
- attrs.send(:"#{attr}=", v)
534
+ attrs.send("#{attr}=", v)
492
535
  end
493
536
  end
494
537
  end