pakyow-presenter 0.9.1 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
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)