pakyow-presenter 1.0.0.rc3 → 1.0.0.rc4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2edbafe8464ab22539cb0006e33b7e621f469aea71f65f936f53ad7043e098b8
4
- data.tar.gz: e4bbf5824d079520da80228a16a450d5e8b8591f604771d37f11ac85274a9401
3
+ metadata.gz: 13fe32a06ee25c5db22efd7315240022a60ebe898879cf71577589073e7eebf9
4
+ data.tar.gz: ea6a93ad0aacccaa646332b2166b82d8eee0f8e705bc5dde0889c1624bb8f60f
5
5
  SHA512:
6
- metadata.gz: d3009754a80da03f010543e5dbea35e747fd09d0ede2bdfac525ed321418938853d64941c1b88442d65d5aeacdc9c67195e9e4b5377e3b0e0e4c9c181dd102d2
7
- data.tar.gz: 0f8d79a0256297763b366307d97ca1b0aa57a6995c86bffec0c00d3ae14829ebb69c38310cba26562118d5687b593175ac36170b926a9f2299d04d8eb67cf9a3
6
+ metadata.gz: 61102a1e5be58a90408451203129e44e87a43150d6fbc22e8bee588e1a4956f80497376e02ef2e202c108a225388986ba495330ac4f14be29310d31dcc648600
7
+ data.tar.gz: 064fe7f7bc471d8bb8e4ca7e83c00660b20d9995cf4f974a6f08715510bd058817ed2703d72b6d4c7beb8d172d92249c7f053b71127f01bdbf7f8d5306afec5b
@@ -14,6 +14,7 @@ module Pakyow
14
14
 
15
15
  extend Support::ClassState
16
16
  class_state :__presenter_class, default: Presenter, inheritable: true
17
+ class_state :inherit_values, default: false
17
18
 
18
19
  include Support::Hookable
19
20
  events :render
@@ -254,23 +254,12 @@ module Pakyow
254
254
  presenter.use_implicit_version
255
255
  end
256
256
 
257
- used_view = case presenter.view.object
258
- when StringDoc::MetaNode
259
- View.from_object(
260
- presenter.view.object.nodes.find { |node|
261
- node.labeled?(:versioned)
262
- }
263
- )
264
- else
265
- presenter.view.versions.find { |version|
266
- version.object.labeled?(:versioned)
267
- }
268
- end
269
-
270
- used_view.binding_props.map { |binding_prop|
257
+ # Implicitly use binding props.
258
+ #
259
+ presenter.view.binding_props.map { |binding_prop|
271
260
  binding_prop.label(:binding)
272
261
  }.uniq.each do |binding_prop_name|
273
- if found = used_view.find(binding_prop_name)
262
+ if found = presenter.view.find(binding_prop_name)
274
263
  presenter_for(found).use_implicit_version unless found.used?
275
264
  end
276
265
  end
@@ -283,14 +272,14 @@ module Pakyow
283
272
  }.each do |binding_node|
284
273
  plural_binding_node_name = Support.inflector.pluralize(binding_node.label(:binding)).to_sym
285
274
 
286
- nested_view = presenter.find(binding_node.label(:binding))
287
-
288
- if binder.object.include?(binding_node.label(:binding))
289
- nested_view.present(binder.object[binding_node.label(:binding)])
290
- elsif binder.object.include?(plural_binding_node_name)
291
- nested_view.present(binder.object[plural_binding_node_name])
292
- else
293
- nested_view.remove
275
+ if nested_view = presenter.find(binding_node.label(:binding))
276
+ if binder.object.include?(binding_node.label(:binding))
277
+ nested_view.present(binder.object[binding_node.label(:binding)])
278
+ elsif binder.object.include?(plural_binding_node_name)
279
+ nested_view.present(binder.object[plural_binding_node_name])
280
+ else
281
+ nested_view.remove
282
+ end
294
283
  end
295
284
  end
296
285
  end
@@ -389,7 +378,10 @@ module Pakyow
389
378
  def to_html(output = String.new)
390
379
  @view.object.to_html(output, context: self)
391
380
  end
392
- alias to_s to_html
381
+
382
+ def to_s
383
+ @view.to_s
384
+ end
393
385
 
394
386
  def presenter_for(view, type: nil)
395
387
  if view.nil?
@@ -408,13 +400,23 @@ module Pakyow
408
400
 
409
401
  # @api private
410
402
  def endpoint(name)
403
+ found = []
404
+
411
405
  object.each_significant_node(:endpoint) do |endpoint_node|
412
406
  if endpoint_node.label(:endpoint) == name.to_sym
413
- return presenter_for(View.from_object(endpoint_node))
407
+ found << endpoint_node
414
408
  end
415
409
  end
416
410
 
417
- nil
411
+ if found.any?
412
+ if found[0].is_a?(StringDoc::MetaNode)
413
+ presenter_for(View.from_object(found[0]))
414
+ else
415
+ presenter_for(View.from_object(StringDoc::MetaNode.new(found)))
416
+ end
417
+ else
418
+ nil
419
+ end
418
420
  end
419
421
 
420
422
  # @api private
@@ -684,12 +686,7 @@ module Pakyow
684
686
  end
685
687
 
686
688
  views_with_renders.values.each do |view_with_renders, renders_for_view|
687
- attach_to_node = case view_with_renders
688
- when VersionedView
689
- StringDoc::MetaNode.new(view_with_renders.versions.map(&:object))
690
- when View
691
- view_with_renders.object
692
- end
689
+ attach_to_node = view_with_renders.object
693
690
 
694
691
  if attach_to_node.is_a?(StringDoc)
695
692
  attach_to_node = attach_to_node.find_first_significant_node(:html)
@@ -725,7 +722,7 @@ module Pakyow
725
722
  if node.nodes.any?
726
723
  returning = node
727
724
  presenter = context.presenter_for(
728
- VersionedView.new([View.from_object(node)])
725
+ VersionedView.new(View.from_object(node))
729
726
  )
730
727
  else
731
728
  next node
@@ -8,15 +8,10 @@ module Pakyow
8
8
  module Presenters
9
9
  class Endpoint < DelegateClass(Presenter)
10
10
  def setup
11
- if endpoint_method == :delete
12
- setup_endpoint_for_removal(
13
- path: endpoint_path
14
- )
15
- else
16
- setup_endpoint(
17
- path: endpoint_path,
18
- method: endpoint_method
19
- )
11
+ setup_endpoint(path: endpoint_path, method: endpoint_method)
12
+
13
+ unless endpoint_method == :get
14
+ setup_non_get_endpoint(path: endpoint_path, method: endpoint_method)
20
15
  end
21
16
  end
22
17
 
@@ -88,23 +83,38 @@ module Pakyow
88
83
  end
89
84
  end
90
85
 
91
- def setup_endpoint_for_removal(path:)
92
- if object.tagname == "form"
93
- form_presenter = presenter_for(__getobj__, type: Form)
94
- form_presenter.action = path
95
- form_presenter.method = :delete
96
- attributes[:"data-ui"] = "confirmable"
97
- else
98
- replace(
99
- View.new(
100
- <<~HTML
101
- <form action="#{path}" method="post" data-ui="confirmable">
102
- <input type="hidden" name="pw-http-method" value="delete">
103
- #{view.object.render}
104
- </form>
105
- HTML
106
- )
107
- )
86
+ def setup_non_get_endpoint(path:, method:)
87
+ unless object.tagname == "form"
88
+ object.attributes.delete(:"data-e")
89
+
90
+ if object.tagname == "a"
91
+ object.attributes[:href] = "javascript:void(0)"
92
+ end
93
+
94
+ # FIXME: Everything below could probably be streamlined and improved. Some ideas:
95
+ #
96
+ # * Build the form once, then attach a render that fills in the dynamic parts.
97
+ # * Define the presenter class once, attached to the view in step one.
98
+ # * Continue replacing with a string but all we'd be doing is building the string.
99
+ #
100
+ form_node = StringDoc.new(
101
+ <<~HTML
102
+ <form action="#{path}" method="post">
103
+ <input type="hidden" name="pw-http-method" value="#{method}">
104
+ #{object.render}
105
+ </form>
106
+ HTML
107
+ ).nodes[0]
108
+
109
+ form_view = View.from_object(form_node)
110
+ Renderer::Behavior::SetupForms.build(form_view, __getobj__.app)
111
+
112
+ presenter_class = Class.new(Presenter)
113
+ Renderer::Behavior::SetupForms.attach(presenter_class, __getobj__.app)
114
+
115
+ presenter_class.attach(form_view)
116
+ form_presenter = presenter_class.new(form_view, app: __getobj__.app, presentables: __getobj__.presentables)
117
+ replace(html_safe(form_presenter.to_html))
108
118
  end
109
119
  end
110
120
 
@@ -86,7 +86,7 @@ module Pakyow
86
86
  # Populates a select field with options.
87
87
  #
88
88
  def options_for(field, options = nil)
89
- unless field_presenter = find(field) || find(Support.inflector.singularize(field)) || find(Support.inflector.pluralize(field))
89
+ unless field_presenter = find(Support.inflector.singularize(field)) || find(Support.inflector.pluralize(field))
90
90
  raise ArgumentError.new("could not find field named `#{field}'")
91
91
  end
92
92
 
@@ -211,7 +211,8 @@ module Pakyow
211
211
 
212
212
  def use_binding_nodes
213
213
  view.object.set_label(:bound, true)
214
- view.object.children.each_significant_node(:binding, descend: true) do |object|
214
+
215
+ view.object.each_significant_node(:binding, descend: true) do |object|
215
216
  if Pakyow::Presenter::Views::Form::FIELD_TAGS.include?(object.tagname)
216
217
  object.set_label(:bound, true)
217
218
  end
@@ -337,8 +338,8 @@ module Pakyow
337
338
  values = Array.ensure(values).compact
338
339
 
339
340
  if values.any?
340
- field_view = Pakyow::Presenter::Views::Form.from_object(field_presenter.view.object)
341
- field_template = field_view.dup
341
+ field_view = field_presenter.view
342
+ field_template = field_view.soft_copy
342
343
  insertable_field = field_view
343
344
  current_field = field_view
344
345
 
@@ -350,7 +351,7 @@ module Pakyow
350
351
  insertable_field = current_field
351
352
  end
352
353
 
353
- current_field = field_template.dup
354
+ current_field = field_template.soft_copy
354
355
  end
355
356
  else
356
357
  field_presenter.remove
@@ -361,8 +362,8 @@ module Pakyow
361
362
  values = Array.ensure(original_values).compact
362
363
 
363
364
  if values.any?
364
- field_view = Pakyow::Presenter::Views::Form.from_object(field_presenter.view.object)
365
- template = field_view.dup
365
+ field_view = field_presenter.view
366
+ template = field_view.soft_copy
366
367
  insertable = field_view
367
368
  current = field_view
368
369
 
@@ -432,7 +433,7 @@ module Pakyow
432
433
  end
433
434
 
434
435
  current.object.set_label(:bound, true)
435
- current = template.dup
436
+ current = template.soft_copy
436
437
  end
437
438
  else
438
439
  field_presenter.remove
@@ -8,7 +8,7 @@ module Pakyow
8
8
  extend Support::ClassState
9
9
  class_state :name
10
10
  class_state :block
11
- class_state :extensions, default: [], getter: false
11
+ class_state :extensions, default: [], reader: false
12
12
 
13
13
  extend Support::Makeable
14
14
 
@@ -105,6 +105,14 @@ module Pakyow
105
105
  component_connection = component_connection.class.from_connection(component_connection, :@app => component_connection.app.parent)
106
106
  end
107
107
 
108
+ unless component[:class].inherit_values == true
109
+ component_connection.values.each_key do |key|
110
+ unless key.to_s.start_with?("__") || (component[:class].inherit_values && component[:class].inherit_values.include?(key))
111
+ component_connection.values.delete(key)
112
+ end
113
+ end
114
+ end
115
+
108
116
  component_instance = component[:class].new(
109
117
  connection: component_connection,
110
118
  config: component[:config]
@@ -15,158 +15,170 @@ module Pakyow
15
15
 
16
16
  apply_extension do
17
17
  build do |view, app:|
18
- forms = view.forms
19
- if !view.object.is_a?(StringDoc) && view.object.significant?(:form)
20
- forms << view
18
+ SetupForms.build(view, app)
19
+ end
20
+
21
+ attach do |presenter, app:|
22
+ SetupForms.attach(presenter, app)
23
+ end
24
+
25
+ expose do |connection|
26
+ connection.set(:__params, connection.params)
27
+ connection.set(:__endpoint, connection.endpoint)
28
+ connection.set(:__verifier, connection.verifier)
29
+
30
+ origin = if connection.set?(:__form)
31
+ connection.get(:__form)[:origin]
32
+ else
33
+ connection.fullpath
21
34
  end
22
35
 
23
- forms.each do |form|
24
- # Allows app renders to set metadata values on forms.
25
- #
26
- form.object.set_label(:form, {})
27
-
28
- # Set the form id.
29
- #
30
- form_id = SecureRandom.hex(24)
31
- form.object.label(:form)[:id] = form_id
32
- form.object.set_label(Presenters::Form::ID_LABEL, form_id)
33
-
34
- # Set the form binding.
35
- #
36
- form.object.label(:form)[:binding] = form.object.label(:channeled_binding)
37
-
38
- # Setup field names.
39
- #
40
- form.object.children.each_significant_node(:binding) do |binding_node|
41
- if Pakyow::Presenter::Views::Form::FIELD_TAGS.include?(binding_node.tagname)
42
- if binding_node.attributes[:name].to_s.empty?
43
- binding_node.attributes[:name] = "#{form.object.label(:binding)}[#{binding_node.label(:binding)}]"
44
- end
36
+ connection.set(:__origin, origin)
37
+ end
38
+ end
45
39
 
46
- if binding_node.tagname == "select" && binding_node.attributes[:multiple]
47
- Presenters::Form.pluralize_field_name(binding_node)
48
- end
40
+ # @api private
41
+ def self.build(view, app)
42
+ forms = view.forms
43
+ if !view.object.is_a?(StringDoc) && view.object.significant?(:form)
44
+ forms << view
45
+ end
46
+
47
+ forms.each do |form|
48
+ # Allows app renders to set metadata values on forms.
49
+ #
50
+ form.object.set_label(:form, {})
51
+
52
+ # Set the form id.
53
+ #
54
+ form_id = SecureRandom.hex(24)
55
+ form.object.label(:form)[:id] = form_id
56
+ form.object.set_label(Presenters::Form::ID_LABEL, form_id)
57
+
58
+ # Set the form binding.
59
+ #
60
+ form.object.label(:form)[:binding] = form.object.label(:channeled_binding)
61
+
62
+ # Setup field names.
63
+ #
64
+ form.object.children.each_significant_node(:binding) do |binding_node|
65
+ if Pakyow::Presenter::Views::Form::FIELD_TAGS.include?(binding_node.tagname)
66
+ if binding_node.attributes[:name].to_s.empty?
67
+ binding_node.attributes[:name] = "#{form.object.label(:binding)}[#{binding_node.label(:binding)}]"
49
68
  end
50
- end
51
69
 
52
- # Connect labels.
53
- #
54
- form.object.children.each_significant_node(:label) do |label_node|
55
- if label_node.attributes[:for] && input = form.find(*label_node.attributes[:for].to_s.split("."))
56
- Presenters::Form.connect_input_to_label(input, label_node)
70
+ if binding_node.tagname == "select" && binding_node.attributes[:multiple]
71
+ Presenters::Form.pluralize_field_name(binding_node)
57
72
  end
58
73
  end
74
+ end
59
75
 
60
- form.prepend(
61
- Support::SafeStringHelpers.html_safe(
62
- "<input type=\"hidden\" name=\"pw-http-method\">"
63
- )
76
+ # Connect labels.
77
+ #
78
+ form.object.children.each_significant_node(:label) do |label_node|
79
+ if label_node.attributes[:for] && input = form.find(*label_node.attributes[:for].to_s.split("."))
80
+ Presenters::Form.connect_input_to_label(input, label_node)
81
+ end
82
+ end
83
+
84
+ form.prepend(
85
+ Support::SafeStringHelpers.html_safe(
86
+ "<input type=\"hidden\" name=\"pw-http-method\">"
87
+ )
88
+ )
89
+
90
+ form.prepend(
91
+ Support::SafeStringHelpers.html_safe(
92
+ "<input type=\"hidden\" name=\"pw-form\">"
64
93
  )
94
+ )
65
95
 
96
+ if app.config.presenter.embed_authenticity_token
66
97
  form.prepend(
67
98
  Support::SafeStringHelpers.html_safe(
68
- "<input type=\"hidden\" name=\"pw-form\">"
99
+ "<input type=\"hidden\" name=\"#{app.config.security.csrf.param}\">"
69
100
  )
70
101
  )
71
-
72
- if app.config.presenter.embed_authenticity_token
73
- form.prepend(
74
- Support::SafeStringHelpers.html_safe(
75
- "<input type=\"hidden\" name=\"#{app.config.security.csrf.param}\">"
76
- )
77
- )
78
- end
79
102
  end
80
103
  end
104
+ end
81
105
 
82
- attach do |presenter, app:|
83
- presenter.render node: -> {
84
- forms = self.forms
85
- if !object.is_a?(StringDoc) && object.significant?(:form)
86
- forms << self
87
- end
106
+ # @api private
107
+ def self.attach(presenter, app)
108
+ presenter.render node: -> {
109
+ forms = self.forms
110
+ if !object.is_a?(StringDoc) && object.significant?(:form)
111
+ forms << self
112
+ end
88
113
 
89
- forms
90
- } do
91
- unless setup?
92
- if object = object_for_form
93
- if app.class.includes_framework?(:data) && object.is_a?(Data::Proxy)
94
- object = object.one
95
- end
114
+ forms
115
+ } do
116
+ unless setup?
117
+ if object = object_for_form
118
+ if app.class.includes_framework?(:data) && object.is_a?(Data::Proxy)
119
+ object = object.one
96
120
  end
121
+ end
97
122
 
98
- if !object.nil?
99
- if labeled?(:endpoint)
100
- setup(object)
101
- else
102
- if object.key?(:id)
103
- update(object)
104
- else
105
- create(object)
106
- end
107
- end
108
- elsif labeled?(:binding)
109
- case presentables[:__endpoint_name]
110
- when :edit
111
- update(
112
- __endpoint.params.each_with_object({}) { |(key, _), passed_params|
113
- key = key.to_sym
114
- passed_params[key] = __params[key]
115
- }
116
- )
123
+ if !object.nil?
124
+ if labeled?(:endpoint)
125
+ setup(object)
126
+ else
127
+ if object.key?(:id)
128
+ update(object)
117
129
  else
118
- create
130
+ create(object)
119
131
  end
120
132
  end
121
- end
122
-
123
- view.object.label(:form)[:origin] = presentables[:__origin]
124
-
125
- node = view.object.each_significant_node(:field).find { |field_node|
126
- field_node.attributes[:name] == "pw-form"
127
- }
128
-
129
- unless node.nil?
130
- node.attributes[:value] = presentables[:__verifier].sign(
131
- label(:form).to_json
132
- )
133
+ elsif labeled?(:binding)
134
+ case presentables[:__endpoint_name]
135
+ when :edit
136
+ update(
137
+ __endpoint.params.each_with_object({}) { |(key, _), passed_params|
138
+ key = key.to_sym
139
+ passed_params[key] = __params[key]
140
+ }
141
+ )
142
+ else
143
+ create
144
+ end
145
+ else
146
+ # setup
133
147
  end
134
148
  end
135
149
 
136
- presenter.render node: -> {
137
- stringified_param = app.config.security.csrf.param.to_s
138
- node = object.each_significant_node(:field, descend: true).find { |field_node|
139
- field_node.attributes[:name] == stringified_param
140
- }
150
+ view.object.label(:form)[:origin] = presentables[:__origin]
141
151
 
142
- unless node.nil?
143
- View.from_object(node)
144
- end
145
- } do
146
- attributes[:value] = presentables[:__verifier].sign(Support::MessageVerifier.key)
147
- end
152
+ node = view.object.each_significant_node(:field).find { |field_node|
153
+ field_node.attributes[:name] == "pw-form"
154
+ }
148
155
 
149
- presenter.render node: -> {
150
- object.each_significant_node(:method_override, descend: true).map { |node|
151
- View.from_object(node)
152
- }
153
- } do
154
- remove if attributes[:value].empty?
156
+ unless node.nil?
157
+ node.attributes[:value] = presentables[:__verifier].sign(
158
+ label(:form).to_json
159
+ )
155
160
  end
156
161
  end
157
162
 
158
- expose do |connection|
159
- connection.set(:__params, connection.params)
160
- connection.set(:__endpoint, connection.endpoint)
161
- connection.set(:__verifier, connection.verifier)
163
+ presenter.render node: -> {
164
+ stringified_param = app.config.security.csrf.param.to_s
165
+ node = object.each_significant_node(:field, descend: true).find { |field_node|
166
+ field_node.attributes[:name] == stringified_param
167
+ }
162
168
 
163
- origin = if connection.set?(:__form)
164
- connection.get(:__form)[:origin]
165
- else
166
- connection.fullpath
169
+ unless node.nil?
170
+ View.from_object(node)
167
171
  end
172
+ } do
173
+ attributes[:value] = presentables[:__verifier].sign(Support::MessageVerifier.key)
174
+ end
168
175
 
169
- connection.set(:__origin, origin)
176
+ presenter.render node: -> {
177
+ object.each_significant_node(:method_override, descend: true).map { |node|
178
+ View.from_object(node)
179
+ }
180
+ } do
181
+ remove if attributes[:value].empty?
170
182
  end
171
183
  end
172
184
  end
@@ -90,6 +90,7 @@ module Pakyow
90
90
  end
91
91
 
92
92
  presenter.attach(presenter_view)
93
+ presenter_view.object.finalize_labels(keep: [:form, :endpoint_params])
93
94
  presenter_view.deep_freeze
94
95
 
95
96
  self.class.__presenter_views[presenter_view_key] = presenter_view
@@ -183,7 +183,7 @@ module Pakyow
183
183
  StringDoc.significant :form, self, descend: false
184
184
 
185
185
  def self.significant?(node)
186
- node.is_a?(Oga::XML::Element) && node.attribute(:binding) && node.name == FORM_TAG
186
+ node.is_a?(Oga::XML::Element) && node.name == FORM_TAG
187
187
  end
188
188
 
189
189
  def self.decorate(node)
@@ -12,33 +12,24 @@ module Pakyow
12
12
 
13
13
  attr_reader :names
14
14
 
15
- def initialize(versions)
16
- @versions = versions
17
- @names = self.versions.map { |versioned_view| versioned_view.label(:version) }
18
- determine_working_version
15
+ def initialize(view)
16
+ __setobj__(view)
17
+ @names = view.object.nodes.map { |node| node.label(:version) }
19
18
  @used = false
20
19
  end
21
20
 
22
21
  def initialize_dup(_)
23
22
  super
24
23
 
25
- @versions = @versions.map(&:dup)
26
24
  @names = @names.map(&:dup)
27
- determine_working_version
28
25
  end
29
26
 
30
27
  # @api private
31
28
  def soft_copy
32
29
  instance = self.class.allocate
33
-
34
- instance.instance_variable_set(:@versions, @versions.map { |version|
35
- version.soft_copy
36
- })
37
-
30
+ instance.__setobj__(__getobj__.soft_copy)
38
31
  instance.instance_variable_set(:@names, @names)
39
32
  instance.instance_variable_set(:@used, @used)
40
- instance.send(:determine_working_version)
41
-
42
33
  instance
43
34
  end
44
35
 
@@ -51,17 +42,8 @@ module Pakyow
51
42
  # Returns the view matching +version+.
52
43
  #
53
44
  def versioned(version)
54
- if versioned = version_named(version.to_sym)
55
- case versioned.object
56
- when StringDoc::MetaNode
57
- node = versioned.object.nodes.find { |n|
58
- version == (n.label(:version) || DEFAULT_VERSION).to_sym
59
- }
60
-
61
- View.from_object(node)
62
- else
63
- versioned
64
- end
45
+ if node = version_named(version.to_sym)
46
+ View.from_object(node)
65
47
  else
66
48
  nil
67
49
  end
@@ -72,67 +54,25 @@ module Pakyow
72
54
  def use(version)
73
55
  version = version.to_sym
74
56
 
75
- tap do
76
- if view = version_named(version)
77
- case view.object
78
- when StringDoc::MetaNode
79
- versioned_node = view.object.internal_nodes.find { |node|
80
- version == (node.label(:version) || DEFAULT_VERSION).to_sym
81
- }
82
-
83
- versioned_node.set_label(:versioned, true)
84
- else
85
- view.object.set_label(:versioned, true)
86
- end
87
-
88
- self.versioned_view = view
89
-
90
- cleanup
91
- else
92
- cleanup(all: true)
93
- end
94
- end
95
- end
96
-
97
- def transform(object)
98
- @versions.each do |version|
99
- version.transform(object)
100
- end
101
-
102
- yield self, object if block_given?
103
- end
104
-
105
- def bind(object)
106
- @versions.each do |version|
107
- version.bind(object)
57
+ if node = version_named(version)
58
+ node.set_label(:versioned, true)
59
+ cleanup
60
+ else
61
+ cleanup(all: true)
108
62
  end
109
63
 
110
- yield self, object if block_given?
64
+ self
111
65
  end
112
66
 
113
67
  def used?
114
- @versions.any? { |versioned_view|
115
- case versioned_view.object
116
- when StringDoc::MetaNode
117
- versioned_view.object.nodes.any? { |node|
118
- node.labeled?(:versioned)
119
- }
120
- else
121
- versioned_view.object.labeled?(:versioned)
122
- end
68
+ __getobj__.object.internal_nodes.any? { |node|
69
+ node.labeled?(:versioned)
123
70
  }
124
71
  end
125
72
 
126
73
  def versions
127
- @versions.each_with_object([]) { |versioned_view, versions|
128
- case versioned_view.object
129
- when StringDoc::MetaNode
130
- versioned_view.object.nodes.each do |node|
131
- versions << View.from_object(node)
132
- end
133
- else
134
- versions << versioned_view
135
- end
74
+ __getobj__.object.nodes.map { |node|
75
+ View.from_object(node)
136
76
  }
137
77
  end
138
78
 
@@ -146,65 +86,25 @@ module Pakyow
146
86
 
147
87
  def cleanup(all: false)
148
88
  if all
149
- while version = @versions.shift
150
- version.remove
151
- end
89
+ remove
152
90
  else
153
- versions_to_remove = []
154
-
155
- @versions.each do |versioned_view|
156
- case versioned_view.object
157
- when StringDoc::MetaNode
158
- nodes_to_remove = []
159
-
160
- versioned_view.object.internal_nodes.each do |node|
161
- if !node.is_a?(StringDoc::MetaNode) && !node.labeled?(:versioned)
162
- nodes_to_remove << node
163
- end
164
- end
165
-
166
- nodes_to_remove.each(&:remove)
167
- else
168
- unless versioned_view.object.labeled?(:versioned)
169
- versions_to_remove << versioned_view
170
- end
91
+ nodes_to_remove = []
92
+
93
+ __getobj__.object.internal_nodes.each do |node|
94
+ unless node.is_a?(StringDoc::MetaNode) || node.labeled?(:versioned)
95
+ nodes_to_remove << node
171
96
  end
172
97
  end
173
98
 
174
- versions_to_remove.each do |versioned_view|
175
- versioned_view.remove; @versions.delete(versioned_view)
176
- end
99
+ nodes_to_remove.each(&:remove)
177
100
  end
178
101
  end
179
102
 
180
- def determine_working_version
181
- self.versioned_view = default_version
182
- end
183
-
184
- def versioned_view=(view)
185
- __setobj__(view)
186
- end
187
-
188
- def default_version
189
- version_named(DEFAULT_VERSION) || first_version
190
- end
191
-
192
103
  def version_named(version)
193
- @versions.find { |view|
194
- case view.object
195
- when StringDoc::MetaNode
196
- view.object.internal_nodes.any? { |node|
197
- version == (node.label(:version) || DEFAULT_VERSION).to_sym
198
- }
199
- else
200
- view.version == version
201
- end
104
+ __getobj__.object.internal_nodes.find { |node|
105
+ version == (node.label(:version) || DEFAULT_VERSION).to_sym
202
106
  }
203
107
  end
204
-
205
- def first_version
206
- @versions[0]
207
- end
208
108
  end
209
109
  end
210
110
  end
@@ -122,15 +122,16 @@ module Pakyow
122
122
  def find(*names)
123
123
  if names.any?
124
124
  named = names.shift.to_sym
125
-
126
- found = each_binding(named).map { |node|
127
- View.from_object(node)
128
- }
125
+ found = each_binding(named).map(&:itself)
129
126
 
130
127
  result = if names.empty? && !found.empty? # found everything; wrap it up
131
- VersionedView.new(found)
128
+ if found[0].is_a?(StringDoc::MetaNode)
129
+ VersionedView.new(View.from_object(found[0]))
130
+ else
131
+ VersionedView.new(View.from_object(StringDoc::MetaNode.new(found)))
132
+ end
132
133
  elsif !found.empty? && names.count > 0 # descend further
133
- found.first.find(*names)
134
+ View.from_object(found[0]).find(*names)
134
135
  else
135
136
  nil
136
137
  end
@@ -417,7 +418,10 @@ module Pakyow
417
418
  def to_html
418
419
  @object.to_html
419
420
  end
420
- alias :to_s :to_html
421
+
422
+ def to_s
423
+ @object.to_s
424
+ end
421
425
 
422
426
  # @api private
423
427
  def binding_name
@@ -38,11 +38,9 @@ class StringDoc
38
38
  end
39
39
 
40
40
  # @api private
41
- def wrap
41
+ def wrap(&block)
42
42
  @attributes.each do |attributes|
43
- attributes.each do |key, value|
44
- yield value, key
45
- end
43
+ attributes.wrap(&block)
46
44
  end
47
45
  end
48
46
  end
@@ -11,12 +11,20 @@ class StringDoc
11
11
  attr_reader :doc, :transforms, :internal_nodes
12
12
 
13
13
  def initialize(nodes)
14
- nodes.first.parent.replace_node(nodes.first, self)
15
-
16
- nodes[1..-1].each do |node|
17
- # Remove the node, but don't make it appear to have been removed for transforms.
14
+ # Reparent nodes that belong to the same parent.
15
+ #
16
+ nodes.group_by { |node| node.parent }.each_pair do |parent, children|
17
+ # If the children already belong to a meta node doc, don't reparent them again.
18
18
  #
19
- node.remove; node.delete_label(:removed)
19
+ unless children.first.labeled?(:__meta_node)
20
+ parent.replace_node(children.first, self)
21
+ end
22
+
23
+ children[1..-1].each do |node|
24
+ # Remove the node, but don't make it appear to have been removed for transforms.
25
+ #
26
+ node.remove(false, false)
27
+ end
20
28
  end
21
29
 
22
30
  nodes.each do |node|
@@ -26,9 +34,7 @@ class StringDoc
26
34
  @doc = StringDoc.from_nodes(nodes)
27
35
  @transforms = { high: [], default: [], low: [] }
28
36
 
29
- @internal_nodes = nodes.select { |node|
30
- !node.is_a?(MetaNode) && node.labeled?(:__meta_node)
31
- }
37
+ @internal_nodes = nodes.dup
32
38
 
33
39
  @pipeline = nil
34
40
  end
@@ -37,15 +43,23 @@ class StringDoc
37
43
  def initialize_copy(_)
38
44
  super
39
45
 
40
- @doc = @doc.dup
46
+ nodes, internal_nodes = [], []
47
+ @doc.nodes.each do |current_node|
48
+ duped_node = current_node.dup
49
+ nodes << duped_node
50
+
51
+ if @internal_nodes.any? { |current_internal_node| current_internal_node.equal?(current_node) }
52
+ internal_nodes << duped_node
53
+ end
54
+ end
55
+
56
+ @doc = StringDoc.from_nodes(nodes)
41
57
 
42
58
  @transforms = @transforms.each_with_object({}) { |(key, value), hash|
43
59
  hash[key] = value.dup
44
60
  }
45
61
 
46
- @internal_nodes = nodes.select { |node|
47
- !node.is_a?(MetaNode) && node.labeled?(:__meta_node)
48
- }
62
+ @internal_nodes = internal_nodes
49
63
 
50
64
  @pipeline = nil
51
65
  end
@@ -54,19 +68,32 @@ class StringDoc
54
68
  def soft_copy
55
69
  instance = self.class.allocate
56
70
 
57
- new_doc = @doc.soft_copy
58
- instance.instance_variable_set(:@doc, new_doc)
71
+ nodes, internal_nodes = [], []
72
+ @doc.nodes.each do |current_node|
73
+ duped_node = current_node.soft_copy
74
+ nodes << duped_node
75
+
76
+ if @internal_nodes.any? { |current_internal_node| current_internal_node.equal?(current_node) }
77
+ internal_nodes << duped_node
78
+ end
79
+ end
80
+
81
+ instance.instance_variable_set(:@doc, StringDoc.from_nodes(nodes))
59
82
  instance.instance_variable_set(:@transforms, @transforms)
60
83
 
61
- instance.instance_variable_set(:@internal_nodes, new_doc.nodes.select { |node|
62
- !node.is_a?(MetaNode) && node.labeled?(:__meta_node)
63
- })
84
+ instance.instance_variable_set(:@internal_nodes, internal_nodes)
64
85
 
65
86
  instance.instance_variable_set(:@pipeline, @pipeline.dup)
66
87
 
67
88
  instance
68
89
  end
69
90
 
91
+ def finalize_labels(keep: [])
92
+ nodes.each do |node|
93
+ node.finalize_labels(keep: keep)
94
+ end
95
+ end
96
+
70
97
  def freeze(*)
71
98
  pipeline
72
99
  super
@@ -128,10 +155,16 @@ class StringDoc
128
155
  internal_nodes.each do |each_node|
129
156
  each_node.replace(replacement)
130
157
  end
158
+
159
+ @internal_nodes = StringDoc.nodes_from_doc_or_string(replacement)
131
160
  end
132
161
 
133
- def remove
134
- internal_nodes.each(&:remove)
162
+ def remove(label = true, descend = true)
163
+ internal_nodes.each do |node|
164
+ node.remove(label, descend)
165
+ end
166
+
167
+ @internal_nodes = []
135
168
  end
136
169
 
137
170
  def text
@@ -217,8 +250,23 @@ class StringDoc
217
250
  end
218
251
 
219
252
  def each(descend: false, &block)
220
- internal_nodes.each do |node|
221
- node.each(descend: descend, &block)
253
+ return enum_for(:each, descend: descend) unless block_given?
254
+
255
+ yield self
256
+
257
+ nodes.each do |node|
258
+ # Yield each node that isn't an internal node (e.g. added before/after).
259
+ #
260
+ unless @internal_nodes.any? { |internal_node| internal_node.equal?(node) }
261
+ case node
262
+ when MetaNode
263
+ node.each do |each_meta_node|
264
+ yield each_meta_node
265
+ end
266
+ else
267
+ yield node
268
+ end
269
+ end
222
270
  end
223
271
  end
224
272
 
@@ -268,6 +316,10 @@ class StringDoc
268
316
  }
269
317
  end
270
318
 
319
+ def removed?
320
+ internal_nodes.all?(&:removed?)
321
+ end
322
+
271
323
  # Converts the node to an xml string.
272
324
  #
273
325
  def render(output = String.new, context: nil)
@@ -278,6 +330,8 @@ class StringDoc
278
330
  each_node.render(output, context: context)
279
331
  end
280
332
  end
333
+
334
+ output
281
335
  end
282
336
  alias :to_html :render
283
337
  alias :to_xml :render
@@ -313,17 +367,17 @@ class StringDoc
313
367
  when StringDoc
314
368
  return_value.render(string, context: context); return
315
369
  when Node, MetaNode
316
- current = return_value
370
+ if return_value.removed?
371
+ return
372
+ else
373
+ current = return_value
374
+ end
317
375
  else
318
376
  string << return_value.to_s; return
319
377
  end
320
378
  end
321
379
 
322
- # Don't render if the node was removed during the transform.
323
- #
324
- if !current.is_a?(Node) || !current.labeled?(:removed)
325
- current.render(string, context: context)
326
- end
380
+ current.render(string, context: context)
327
381
  end
328
382
  end
329
383
  end
@@ -35,7 +35,7 @@ class StringDoc
35
35
  attr_reader :attributes
36
36
 
37
37
  # @api private
38
- attr_reader :node, :parent, :children, :tag_open_start, :tag_open_end, :tag_close, :transforms, :significance, :labels
38
+ attr_reader :node, :parent, :children, :tag_open_start, :tag_open_end, :tag_close, :transforms, :significance
39
39
 
40
40
  # @api private
41
41
  attr_writer :parent
@@ -50,6 +50,7 @@ class StringDoc
50
50
  @parent, @labels, @significance = parent, labels, significance
51
51
  @transforms = { high: [], default: [], low: [] }
52
52
  @pipeline = nil
53
+ @finalized_labels = {}
53
54
  end
54
55
 
55
56
  # @api private
@@ -57,6 +58,7 @@ class StringDoc
57
58
  super
58
59
 
59
60
  @labels = @labels.deep_dup
61
+ @finalized_labels = @finalized_labels.deep_dup
60
62
  @attributes = @attributes.dup
61
63
  @children = @children.dup
62
64
  @significance = @significance.dup
@@ -78,6 +80,7 @@ class StringDoc
78
80
  instance.instance_variable_set(:@parent, @parent)
79
81
  instance.instance_variable_set(:@significance, @significance)
80
82
  instance.instance_variable_set(:@transforms, @transforms)
83
+ instance.instance_variable_set(:@finalized_labels, @finalized_labels)
81
84
 
82
85
  instance.instance_variable_set(:@attributes, @attributes.dup)
83
86
  instance.instance_variable_set(:@children, @children.is_a?(StringDoc) ? @children.soft_copy : @children.dup)
@@ -87,6 +90,21 @@ class StringDoc
87
90
  instance
88
91
  end
89
92
 
93
+ def finalize_labels(keep: [])
94
+ @finalized_labels = @labels
95
+ @labels = keep.each_with_object({}) { |key, hash|
96
+ hash[key] = @finalized_labels.delete(key).deep_dup
97
+ }
98
+
99
+ if children.is_a?(StringDoc)
100
+ children.finalize_labels(keep: keep)
101
+ end
102
+ end
103
+
104
+ def labels
105
+ @labels.merge(@finalized_labels)
106
+ end
107
+
90
108
  def freeze(*)
91
109
  pipeline
92
110
  super
@@ -145,9 +163,18 @@ class StringDoc
145
163
 
146
164
  # Removes the node.
147
165
  #
148
- def remove
149
- set_label(:removed, true)
166
+ def remove(label = true, descend = true)
167
+ if label
168
+ set_label(:removed, true)
169
+ end
170
+
150
171
  @parent.remove_node(self)
172
+
173
+ if descend && children.is_a?(StringDoc)
174
+ children.each do |child|
175
+ child.remove(label, descend)
176
+ end
177
+ end
151
178
  end
152
179
 
153
180
  REGEX_TAGS = /<[^>]*>/
@@ -221,13 +248,18 @@ class StringDoc
221
248
  # Returns the value for label with +name+.
222
249
  #
223
250
  def label(name)
224
- @labels[name.to_sym]
251
+ name = name.to_sym
252
+ if @labels.key?(name)
253
+ @labels[name.to_sym]
254
+ else
255
+ @finalized_labels[name.to_sym]
256
+ end
225
257
  end
226
258
 
227
259
  # Returns true if label exists with +name+.
228
260
  #
229
261
  def labeled?(name)
230
- @labels.key?(name.to_sym)
262
+ @labels.key?(name.to_sym) || @finalized_labels.key?(name.to_sym)
231
263
  end
232
264
 
233
265
  # Sets the label with +name+ and +value+.
@@ -236,6 +268,10 @@ class StringDoc
236
268
  @labels[name.to_sym] = value
237
269
  end
238
270
 
271
+ def removed?
272
+ labeled?(:removed)
273
+ end
274
+
239
275
  # Delete the label with +name+.
240
276
  #
241
277
  def delete_label(name)
@@ -263,6 +299,8 @@ class StringDoc
263
299
 
264
300
  output << tag_close
265
301
  end
302
+
303
+ output
266
304
  end
267
305
  alias :to_html :render
268
306
  alias :to_xml :render
@@ -358,17 +396,17 @@ class StringDoc
358
396
  when StringDoc
359
397
  return_value.render(string, context: context); return
360
398
  when Node, MetaNode
361
- current = return_value
399
+ if return_value.removed?
400
+ return
401
+ else
402
+ current = return_value
403
+ end
362
404
  else
363
405
  string << return_value.to_s; return
364
406
  end
365
407
  end
366
408
 
367
- # Don't render if the node was removed during the transform.
368
- #
369
- if !current.is_a?(Node) || !current.labeled?(:removed)
370
- current.render(string, context: context)
371
- end
409
+ current.render(string, context: context)
372
410
  end
373
411
 
374
412
  def string_nodes
data/lib/string_doc.rb CHANGED
@@ -171,13 +171,26 @@ class StringDoc
171
171
  instance
172
172
  end
173
173
 
174
+ def finalize_labels(keep: [])
175
+ @nodes.each do |node|
176
+ node.finalize_labels(keep: keep)
177
+ end
178
+ end
179
+
174
180
  include Enumerable
175
181
 
176
182
  def each(descend: false, &block)
177
183
  return enum_for(:each, descend: descend) unless block_given?
178
184
 
179
185
  @nodes.each do |node|
180
- yield node
186
+ case node
187
+ when MetaNode
188
+ node.each do |each_meta_node|
189
+ yield each_meta_node
190
+ end
191
+ else
192
+ yield node
193
+ end
181
194
 
182
195
  if descend || node.label(:descend) != false
183
196
  if node.children.is_a?(StringDoc)
@@ -195,7 +208,18 @@ class StringDoc
195
208
  return enum_for(:each_significant_node, type, descend: descend) unless block_given?
196
209
 
197
210
  each(descend: descend) do |node|
198
- yield node if (node.is_a?(Node) || node.is_a?(MetaNode)) && node.significant?(type)
211
+ case node
212
+ when MetaNode
213
+ if node.significant?(type)
214
+ node.each do |each_meta_node|
215
+ yield each_meta_node
216
+ end
217
+ end
218
+ when Node
219
+ if node.significant?(type)
220
+ yield node
221
+ end
222
+ end
199
223
  end
200
224
  end
201
225
 
@@ -207,7 +231,14 @@ class StringDoc
207
231
  @nodes.each do |node|
208
232
  if node.is_a?(Node) || node.is_a?(MetaNode)
209
233
  if node.significant?(type)
210
- yield node
234
+ case node
235
+ when MetaNode
236
+ node.each do |each_meta_node|
237
+ yield each_meta_node
238
+ end
239
+ when Node
240
+ yield node
241
+ end
211
242
  else
212
243
  if descend || node.label(:descend) != false
213
244
  if node.children.is_a?(StringDoc)
@@ -367,7 +398,7 @@ class StringDoc
367
398
  def remove_node(node_to_delete)
368
399
  tap do
369
400
  @nodes.delete_if { |node|
370
- node.object_id == node_to_delete.object_id
401
+ node.equal?(node_to_delete)
371
402
  }
372
403
  end
373
404
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pakyow-presenter
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0.rc3
4
+ version: 1.0.0.rc4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bryan Powell
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2019-07-09 00:00:00.000000000 Z
12
+ date: 2019-07-15 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: pakyow-core
@@ -17,42 +17,42 @@ dependencies:
17
17
  requirements:
18
18
  - - '='
19
19
  - !ruby/object:Gem::Version
20
- version: 1.0.0.rc3
20
+ version: 1.0.0.rc4
21
21
  type: :runtime
22
22
  prerelease: false
23
23
  version_requirements: !ruby/object:Gem::Requirement
24
24
  requirements:
25
25
  - - '='
26
26
  - !ruby/object:Gem::Version
27
- version: 1.0.0.rc3
27
+ version: 1.0.0.rc4
28
28
  - !ruby/object:Gem::Dependency
29
29
  name: pakyow-routing
30
30
  requirement: !ruby/object:Gem::Requirement
31
31
  requirements:
32
32
  - - '='
33
33
  - !ruby/object:Gem::Version
34
- version: 1.0.0.rc3
34
+ version: 1.0.0.rc4
35
35
  type: :runtime
36
36
  prerelease: false
37
37
  version_requirements: !ruby/object:Gem::Requirement
38
38
  requirements:
39
39
  - - '='
40
40
  - !ruby/object:Gem::Version
41
- version: 1.0.0.rc3
41
+ version: 1.0.0.rc4
42
42
  - !ruby/object:Gem::Dependency
43
43
  name: pakyow-support
44
44
  requirement: !ruby/object:Gem::Requirement
45
45
  requirements:
46
46
  - - '='
47
47
  - !ruby/object:Gem::Version
48
- version: 1.0.0.rc3
48
+ version: 1.0.0.rc4
49
49
  type: :runtime
50
50
  prerelease: false
51
51
  version_requirements: !ruby/object:Gem::Requirement
52
52
  requirements:
53
53
  - - '='
54
54
  - !ruby/object:Gem::Version
55
- version: 1.0.0.rc3
55
+ version: 1.0.0.rc4
56
56
  - !ruby/object:Gem::Dependency
57
57
  name: oga
58
58
  requirement: !ruby/object:Gem::Requirement