pakyow-presenter 1.0.0.rc3 → 1.0.0.rc4

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.
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