pakyow-presenter 0.9.1 → 0.10.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 (28) hide show
  1. checksums.yaml +4 -4
  2. data/pakyow-presenter/CHANGELOG.md +94 -0
  3. data/pakyow-presenter/{MIT-LICENSE → LICENSE} +2 -2
  4. data/pakyow-presenter/README.md +36 -0
  5. data/pakyow-presenter/lib/pakyow-presenter.rb +1 -4
  6. data/pakyow-presenter/lib/presenter/attributes.rb +10 -1
  7. data/pakyow-presenter/lib/presenter/base.rb +2 -1
  8. data/pakyow-presenter/lib/presenter/binder.rb +20 -6
  9. data/pakyow-presenter/lib/presenter/binder_set.rb +21 -17
  10. data/pakyow-presenter/lib/presenter/config/presenter.rb +2 -2
  11. data/pakyow-presenter/lib/presenter/ext/app.rb +32 -1
  12. data/pakyow-presenter/lib/presenter/helpers.rb +6 -6
  13. data/pakyow-presenter/lib/presenter/page.rb +4 -4
  14. data/pakyow-presenter/lib/presenter/presenter.rb +32 -10
  15. data/pakyow-presenter/lib/presenter/string_doc.rb +78 -11
  16. data/pakyow-presenter/lib/presenter/string_doc_parser.rb +16 -7
  17. data/pakyow-presenter/lib/presenter/view.rb +55 -17
  18. data/pakyow-presenter/lib/presenter/view_collection.rb +74 -7
  19. data/pakyow-presenter/lib/presenter/view_composer.rb +52 -8
  20. data/pakyow-presenter/lib/presenter/view_context.rb +19 -1
  21. data/pakyow-presenter/lib/presenter/view_store.rb +202 -163
  22. data/pakyow-presenter/lib/presenter/view_store_loader.rb +43 -0
  23. data/pakyow-presenter/lib/presenter/view_version.rb +97 -0
  24. data/pakyow-presenter/lib/views/errors/404.html +5 -0
  25. data/pakyow-presenter/{README → lib/views/errors/500.html} +0 -0
  26. metadata +36 -21
  27. data/pakyow-presenter/CHANGES +0 -57
  28. data/pakyow-presenter/lib/presenter/nokogiri_doc.rb +0 -321
@@ -32,11 +32,11 @@ module Pakyow
32
32
 
33
33
  children = node.children.reject {|n| n.is_a?(Nokogiri::XML::Text)}
34
34
  attributes = node.attributes
35
- if children.empty? && !significant?(node)
35
+ if !structure.empty? && children.empty? && !significant?(node)
36
36
  structure << [node.to_html, {}, []]
37
37
  else
38
38
  if significant?(node)
39
- if scope?(node) || prop?(node) || option?(node)
39
+ if scope?(node) || prop?(node) || option?(node) || component?(node)
40
40
  attr_structure = attributes.inject({}) do |attrs, attr|
41
41
  attrs[attr[1].name.to_sym] = attr[1].value
42
42
  attrs
@@ -55,10 +55,14 @@ module Pakyow
55
55
  structure << [node.to_html, { partial: name }, []]
56
56
  end
57
57
  else
58
- attr_s = attributes.inject('') { |s, a| s << " #{a[1].name}=\"#{a[1].value}\""; s }
59
- closing = [['>', {}, parse(node)]]
60
- closing << ['</' + node.name + '>', {}, []] unless self_closing?(node.name)
61
- structure << ['<' + node.name + attr_s, {}, closing]
58
+ if node.is_a?(Nokogiri::XML::Text)
59
+ structure << [node.text, {}, []]
60
+ else
61
+ attr_s = attributes.inject('') { |s, a| s << " #{a[1].name}=\"#{a[1].value}\""; s }
62
+ closing = [['>', {}, parse(node)]]
63
+ closing << ['</' + node.name + '>', {}, []] unless self_closing?(node.name)
64
+ structure << ['<' + node.name + attr_s, {}, closing]
65
+ end
62
66
  end
63
67
  end
64
68
  end
@@ -67,7 +71,7 @@ module Pakyow
67
71
  end
68
72
 
69
73
  def significant?(node)
70
- scope?(node) || prop?(node) || container?(node) || partial?(node) || option?(node)
74
+ scope?(node) || prop?(node) || container?(node) || partial?(node) || option?(node) || component?(node)
71
75
  end
72
76
 
73
77
  def scope?(node)
@@ -96,6 +100,11 @@ module Pakyow
96
100
  node.name == 'option'
97
101
  end
98
102
 
103
+ def component?(node)
104
+ return false unless node['data-ui']
105
+ return true
106
+ end
107
+
99
108
  def breadth_first(doc)
100
109
  queue = [doc]
101
110
  until queue.empty?
@@ -5,7 +5,7 @@ module Pakyow
5
5
  class View
6
6
  extend Forwardable
7
7
 
8
- def_delegators :@doc, :title=, :title, :remove, :clear, :text, :html
8
+ def_delegators :@doc, :title=, :title, :remove, :clear, :text, :html, :exists?
9
9
 
10
10
  # The object responsible for parsing, manipulating, and rendering
11
11
  # the underlying HTML document for the view.
@@ -103,7 +103,7 @@ module Pakyow
103
103
 
104
104
  def scope(name)
105
105
  name = name.to_sym
106
- @doc.scope(name).inject(ViewCollection.new) do |coll, scope|
106
+ @doc.scope(name).inject(ViewCollection.new(name)) do |coll, scope|
107
107
  view = View.from_doc(scope[:doc])
108
108
  view.scoped_as = name
109
109
  coll << view
@@ -112,13 +112,31 @@ module Pakyow
112
112
 
113
113
  def prop(name)
114
114
  name = name.to_sym
115
- @doc.prop(scoped_as, name).inject(ViewCollection.new) do |coll, prop|
115
+ @doc.prop(scoped_as, name).inject(ViewCollection.new(scoped_as)) do |coll, prop|
116
116
  view = View.from_doc(prop[:doc])
117
117
  view.scoped_as = scoped_as
118
118
  coll << view
119
119
  end
120
120
  end
121
121
 
122
+ def version
123
+ return unless versioned?
124
+ @doc.get_attribute(:'data-version').to_sym
125
+ end
126
+
127
+ def versioned?
128
+ !@doc.get_attribute(:'data-version').nil?
129
+ end
130
+
131
+ def component(name)
132
+ name = name.to_sym
133
+ @doc.component(name).inject(ViewCollection.new(scoped_as)) do |coll, component|
134
+ view = View.from_doc(component[:doc])
135
+ view.scoped_as = scoped_as
136
+ coll << view
137
+ end
138
+ end
139
+
122
140
  # call-seq:
123
141
  # with {|view| block}
124
142
  #
@@ -176,22 +194,22 @@ module Pakyow
176
194
  #
177
195
  def match(data)
178
196
  data = Array.ensure(data)
179
- coll = ViewCollection.new
197
+ coll = ViewCollection.new(scoped_as)
180
198
 
181
199
  # an empty set always means an empty view
182
200
  if data.empty?
183
201
  remove
184
202
  else
185
- # dup for later
186
- original_view = dup if data.length > 1
187
-
188
203
  # the original view match the first datum
189
204
  coll << self
190
205
 
206
+ working = self
207
+
191
208
  # create views for the other datums
192
209
  data[1..-1].inject(coll) { |coll|
193
- duped_view = original_view.dup
194
- after(duped_view)
210
+ duped_view = working.soft_copy
211
+ working.after(duped_view)
212
+ working = duped_view
195
213
  coll << duped_view
196
214
  }
197
215
  end
@@ -226,6 +244,15 @@ module Pakyow
226
244
  def bind(data, bindings: {}, context: nil, &block)
227
245
  datum = Array.ensure(data).first
228
246
  bind_data_to_scope(datum, doc.scopes.first, bindings, context)
247
+
248
+ id = nil
249
+ if data.is_a?(Hash)
250
+ id = data[:id]
251
+ elsif data.respond_to?(:id)
252
+ id = data.id
253
+ end
254
+
255
+ attrs.send(:'data-id=', data[:id]) unless id.nil?
229
256
  return if block.nil?
230
257
 
231
258
  if block.arity == 1
@@ -233,6 +260,8 @@ module Pakyow
233
260
  else
234
261
  block.call(self, datum)
235
262
  end
263
+
264
+ self
236
265
  end
237
266
 
238
267
  # call-seq:
@@ -286,6 +315,15 @@ module Pakyow
286
315
  end
287
316
  alias :to_s :to_html
288
317
 
318
+ def component?
319
+ !attrs.send(:'data-ui').value.empty?
320
+ end
321
+
322
+ def component_name
323
+ return unless component?
324
+ attrs.send(:'data-ui').value
325
+ end
326
+
289
327
  private
290
328
 
291
329
  def bind_data_to_scope(data, scope_info, bindings, ctx)
@@ -305,7 +343,7 @@ module Pakyow
305
343
  end
306
344
 
307
345
  if data_has_prop?(data, prop) || Binder.instance.has_scoped_prop?(scope, prop, bindings)
308
- value = Binder.instance.value_for_scoped_prop(scope, prop, data, bindings, ctx)
346
+ value = Binder.instance.value_for_scoped_prop(scope, prop, data, bindings, ctx)
309
347
 
310
348
  if DocHelpers.form_field?(doc.tagname)
311
349
  bind_to_form_field(doc, scope, prop, value, data, ctx)
@@ -436,18 +474,18 @@ module Pakyow
436
474
  attrs.each do |attr, v|
437
475
  case attr
438
476
  when :content
439
- v = v.call(doc.inner_html) if v.is_a?(Proc)
477
+ v = v.to_proc.call(doc.html) if v.respond_to?(:to_proc)
440
478
  bind_value_to_doc(v, doc)
441
- next
442
479
  when :view
443
- v.call(self)
444
- next
480
+ v.call(View.from_doc(doc))
445
481
  else
446
- attr = attr.to_s
482
+ attr = attr.to_s
447
483
  attrs = Attributes.new(doc)
448
- v = v.call(attrs.send(attr)) if v.is_a?(Proc)
449
484
 
450
- if v.nil?
485
+ 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))
488
+ elsif v.nil?
451
489
  doc.remove_attribute(attr)
452
490
  else
453
491
  attrs.send(:"#{attr}=", v)
@@ -3,8 +3,19 @@ module Pakyow
3
3
  class ViewCollection
4
4
  include Enumerable
5
5
 
6
- def initialize
6
+ attr_reader :views, :scoped_as
7
+
8
+ def initialize(scope = nil)
7
9
  @views = []
10
+ @scoped_as = scope
11
+ end
12
+
13
+ def ==(other)
14
+ @views.each_with_index do |view, i|
15
+ return false if view != other.views[i]
16
+ end
17
+
18
+ return true
8
19
  end
9
20
 
10
21
  def each
@@ -80,7 +91,7 @@ module Pakyow
80
91
  end
81
92
 
82
93
  def scope(name)
83
- inject(ViewCollection.new) { |coll, view|
94
+ collection = inject(ViewCollection.new(name)) { |coll, view|
84
95
  scopes = view.scope(name)
85
96
  next if scopes.nil?
86
97
 
@@ -88,10 +99,16 @@ module Pakyow
88
99
  coll << scoped_view
89
100
  }
90
101
  }
102
+
103
+ if collection.versioned?
104
+ ViewVersion.new(collection.views)
105
+ else
106
+ collection
107
+ end
91
108
  end
92
109
 
93
110
  def prop(name)
94
- inject(ViewCollection.new) { |coll, view|
111
+ inject(ViewCollection.new(scoped_as)) { |coll, view|
95
112
  scopes = view.prop(name)
96
113
  next if scopes.nil?
97
114
 
@@ -101,6 +118,53 @@ module Pakyow
101
118
  }
102
119
  end
103
120
 
121
+ def versioned?
122
+ each do |view|
123
+ return true if view.versioned?
124
+ end
125
+
126
+ false
127
+ end
128
+
129
+ def exists?
130
+ each do |view|
131
+ return true if view.exists?
132
+ end
133
+
134
+ false
135
+ end
136
+
137
+ def component(name)
138
+ collection = inject(ViewCollection.new(scoped_as)) { |coll, view|
139
+ scopes = view.component(name)
140
+ next if scopes.nil?
141
+
142
+ scopes.inject(coll) { |coll, scoped_view|
143
+ coll << scoped_view
144
+ }
145
+ }
146
+
147
+ if collection.versioned?
148
+ ViewVersion.new(collection.views)
149
+ else
150
+ collection
151
+ end
152
+ end
153
+
154
+ def component?
155
+ each do |view|
156
+ return true if view.component?
157
+ end
158
+
159
+ false
160
+ end
161
+
162
+ def component_name
163
+ each do |view|
164
+ return view.component_name if view.component?
165
+ end
166
+ end
167
+
104
168
  # call-seq:
105
169
  # with {|view| block}
106
170
  #
@@ -137,6 +201,8 @@ module Pakyow
137
201
  block.call(view, data[i])
138
202
  end
139
203
  end
204
+
205
+ self
140
206
  end
141
207
 
142
208
  # call-seq:
@@ -166,22 +232,23 @@ module Pakyow
166
232
  # will consist n copies of self[data index] || self[-1], where n = data.length.
167
233
  #
168
234
  def match(data)
235
+ return self if length == 0
169
236
  data = Array.ensure(data)
170
237
 
171
238
  # an empty set always means an empty view
172
239
  if data.empty?
173
240
  remove
174
241
  else
175
- original_view = self[-1].soft_copy if data.length > length
176
-
177
242
  if length > data.length
178
243
  self[data.length..-1].each do |view|
179
244
  view.remove
180
245
  end
181
246
  else
247
+ working = self[-1]
182
248
  data[length..-1].each do
183
- duped_view = original_view.soft_copy
184
- self[-1].after(duped_view)
249
+ duped_view = working.soft_copy
250
+ working.after(duped_view)
251
+ working = duped_view
185
252
  self << duped_view
186
253
  end
187
254
  end
@@ -10,7 +10,8 @@ module Pakyow
10
10
  extend Forwardable
11
11
 
12
12
  def_delegators :template, :title, :title=
13
- def_delegators :parts, :scope, :prop
13
+ def_delegators :parts, :prop, :component
14
+ def_delegators :view, :to_html
14
15
 
15
16
  attr_reader :store, :path, :page, :partials
16
17
 
@@ -72,7 +73,7 @@ module Pakyow
72
73
  def template=(template)
73
74
  unless template.is_a?(Template)
74
75
  # get template by name
75
- template = @store.template(template)
76
+ template = @store.template(template.to_sym)
76
77
  end
77
78
 
78
79
  @template = template
@@ -110,17 +111,43 @@ module Pakyow
110
111
  end
111
112
 
112
113
  def parts
114
+ # create an array to hold the parts
113
115
  parts = ViewCollection.new
116
+
117
+ # add the current template
114
118
  parts << @template
115
- @page.each_container { |name, container| parts << container }
116
119
 
117
- # only include available partials as parts
118
- available_partials = parts.inject([]) { |sum, part| sum.concat(part.doc.partials.keys) }
119
- partials.select { |name, partial| available_partials.include?(name) }.each_pair { |name, partial| parts << partial }
120
-
120
+ # add each page container
121
+ @page.each_container do |_, container|
122
+ parts << container
123
+ end
124
+
125
+ parts.concat(partials_for_parts(parts))
126
+
121
127
  return parts
122
128
  end
123
129
 
130
+ def scope(name)
131
+ collection = parts.scope(name)
132
+
133
+ if collection.is_a?(ViewVersion)
134
+ collection = collection.versions.inject(ViewCollection.new) { |c, v| c << v; c }
135
+ end
136
+
137
+ # include partials so nested scopes/props can be bound to
138
+ collection.each do |view|
139
+ view.includes(partials)
140
+ end
141
+
142
+ #TODO make sure anytime we return a collection it tries to version
143
+ # make this a class level helper method on ViewVersion
144
+ if !collection.is_a?(ViewVersion) && collection.versioned?
145
+ ViewVersion.new(collection.views)
146
+ else
147
+ collection
148
+ end
149
+ end
150
+
124
151
  private
125
152
 
126
153
  def build_view
@@ -141,13 +168,30 @@ module Pakyow
141
168
  if partial_or_path.is_a?(Partial)
142
169
  partial = partial_or_path
143
170
  else
144
- partial = Partial.load(@store.expand_partial_path(path))
171
+ partial = Partial.load(@store.expand_partial_path(partial_or_path))
145
172
  end
146
173
 
147
174
  [name, partial]
148
175
  }]
149
176
  end
150
177
 
178
+ def partials_for_parts(parts, acc = [])
179
+ # determine the partials to be included
180
+ available_partials = parts.inject([]) { |sum, part|
181
+ sum.concat(part.doc.partials.keys)
182
+ }
183
+
184
+ # add available partials as parts
185
+ partials.select { |name|
186
+ available_partials.include?(name)
187
+ }.each_pair { |_, partial|
188
+ acc << partial
189
+ partials_for_parts([partial], acc)
190
+ }
191
+
192
+ return acc
193
+ end
194
+
151
195
  end
152
196
  end
153
197
  end
@@ -6,7 +6,7 @@ module Pakyow
6
6
  #
7
7
  class ViewContext
8
8
  include Helpers
9
- VIEW_CLASSES = [View, ViewCollection, Partial, Template, Container]
9
+ VIEW_CLASSES = [View, ViewCollection, Partial, Template, Container, ViewVersion]
10
10
 
11
11
  # The arities of misc view methods that switch the behavior from
12
12
  # instance_exec to yield.
@@ -14,11 +14,17 @@ module Pakyow
14
14
  EXEC_ARITIES = { with: 0, for: 1, for_with_index: 2, repeat: 1,
15
15
  repeat_with_index: 2, bind: 1, bind_with_index: 2, apply: 1 }
16
16
 
17
+ attr_reader :context
18
+
17
19
  def initialize(view, context)
18
20
  @view = view
19
21
  @context = context
20
22
  end
21
23
 
24
+ def subject
25
+ @view
26
+ end
27
+
22
28
  # View methods that expect context, so it can be mixed in.
23
29
  #
24
30
  %i[bind bind_with_index apply].each do |method|
@@ -38,6 +44,18 @@ module Pakyow
38
44
  end
39
45
  end
40
46
 
47
+ def scope(name)
48
+ collection = @view.scope(name)
49
+
50
+ if !collection.is_a?(ViewVersion) && collection.versioned?
51
+ ret = ViewVersion.new(collection.views)
52
+ else
53
+ ret = collection
54
+ end
55
+
56
+ handle_return_value(ret)
57
+ end
58
+
41
59
  # Pass these through, handling the return value.
42
60
  #
43
61
  def method_missing(method, *args, &block)