pakyow-presenter 0.10.2 → 0.11.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.
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