glimmer 2.4.1 → 2.5.0

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: 374a048642d7ade3614c400b3ffefa627d3c40ca6976acf1a7969592865ecef8
4
- data.tar.gz: 5653e8de7ff5ebe9258d872cb72a4067118217e06e698615c9276b95016fb233
3
+ metadata.gz: 4819523f794404e37e2dc3c9c673b4e33f80e20825a077a775d9d14b7bb2e437
4
+ data.tar.gz: ce87b095f130bd8bde0b076bd8499dfa5ce2e3ccf5d8d956b8452537e181d314
5
5
  SHA512:
6
- metadata.gz: 723e1b9231125f0d3f6507f5312b8a4a12c7bb7e4e3af35cf1f682da317c3a6374d814d0a28e5c2cd37d48255dc3cd7f8e70072b4c0f30bba751ce6730566f45
7
- data.tar.gz: 2449d3783dc09bd4154c62df7ae1d954b0c8c411c98276bfa8ceb4f9d4ac0d1504618614fa33d88afa33b056ba1f04ce8b57050b526aa31ecac36b8954507461
6
+ metadata.gz: 41362408327087fd238ac81ecf3a449505c43eb57a31b0c69253abd2a8c523ca64c48da31ab47030dbba1a756b90435908e19d67cde33fd939bc4b27c7480527
7
+ data.tar.gz: d37570e378e876331282db59bb5276d280d8cfbd8b3d70694a654733fd7cd5d5db9ff08dc7cec1f1fe24a92e413f2b00217d210204bc0d7559130f7195085216
data/CHANGELOG.md CHANGED
@@ -3,6 +3,15 @@
3
3
  Related Change Logs:
4
4
  - [glimmer-dsl-swt/CHANGELOG.md](https://github.com/AndyObtiva/glimmer-dsl-swt/blob/master/CHANGELOG.md)
5
5
 
6
+ ### 2.5.0
7
+
8
+ - Support observing `Struct`/`OpenStruct` changes via `:[]=` method in addition to attribute writers.
9
+ - Support read-only direct observation of `Hash` object without key via `ModelBinding` (e.g. `ModelBinding.new(some_hash)`)
10
+ - Support read-only direct observation of `Array` object without index via `ModelBinding` (e.g. `ModelBinding.new(some_array)`)
11
+ - Support observing `Hash` attribute with `ModelBinding` (all keys or a single key)
12
+ - Disable `#ensure_hash_object_observer` in ObservableModel/ObservableHash/ObservableArray since it has performance implications and is not necessary
13
+ - Fix issue with `#ensure_array_object_observer` not receiving `recursive: true` option when updating value of an attribute in `ObservableArray`, `ObservableModel`, and `ObservableHash`
14
+
6
15
  ### 2.4.1
7
16
 
8
17
  - Support `recursive: [integer]` option for ObservableArray#add_observer for finite recursion
data/README.md CHANGED
@@ -34,6 +34,7 @@ Start by checking out Glimmer's original GUI DSL, which got extracted into its o
34
34
  - [glimmer-dsl-opal](https://github.com/AndyObtiva/glimmer-dsl-opal): Glimmer DSL for Opal (Pure Ruby Web GUI and Auto-Webifier of Desktop Apps)
35
35
  - [glimmer-dsl-tk](https://github.com/AndyObtiva/glimmer-dsl-tk): Glimmer DSL for Tk (MRI Ruby Desktop Development GUI Library)
36
36
  - [glimmer-dsl-libui](https://github.com/AndyObtiva/glimmer-dsl-libui): Glimmer DSL for LibUI (Prerequisite-Free Ruby Desktop Development GUI Library)
37
+ - [glimmer-dsl-gtk](https://github.com/AndyObtiva/glimmer-dsl-gtk): Glimmer DSL for GTK (Ruby-GNOME Desktop Development GUI Library)
37
38
  - [glimmer-dsl-xml](https://github.com/AndyObtiva/glimmer-dsl-xml): Glimmer DSL for XML (& HTML)
38
39
  - [glimmer-dsl-css](https://github.com/AndyObtiva/glimmer-dsl-css): Glimmer DSL for CSS
39
40
 
@@ -229,7 +230,7 @@ end
229
230
  ### Setup
230
231
 
231
232
  Follow these steps to author a [Glimmer](https://rubygems.org/gems/glimmer) DSL:
232
- - Add `gem 'glimmer', '~> 2.4.1'` to `Gemfile` and run `bundle` or run `gem install glimmer -v2.4.1` and add `require 'glimmer'`
233
+ - Add `gem 'glimmer', '~> 2.5.0'` to `Gemfile` and run `bundle` or run `gem install glimmer -v2.5.0` and add `require 'glimmer'`
233
234
  - Create `glimmer/dsl/[dsl_name]/dsl.rb`, which requires and adds all dynamic expressions for the [dsl_name] Glimmer DSL module as per the code shown in the previous section (or [Official DSLs](#official-dsls) as examples)
234
235
  - Create `glimmer/dsl/[dsl_name]/[expresion_name]_expresion.rb` for every [expresion_name] expression needed, whether dynamic or static
235
236
 
@@ -1186,9 +1187,9 @@ This relies mainly on the Observer Design Pattern and the MVP (Model-View-Presen
1186
1187
  These are the main classes concerning data-binding:
1187
1188
  - `Glimmer::DataBinding::Observer`: Provides general observer support including unique registration and deregistration for cleanup and prevention of memory leaks. Main methods concerned are: `call`, `register` (alias: `observe`), and `unregister` (alias: `unobserve` or `deregister`)
1188
1189
  - `Glimmer::DataBinding::Observable`: General super-module for all observables. Main methods concerned are: `add_observer` and `remove_observer`
1189
- - `Glimmer::DataBinding::ObservableModel`: Mixin module for any observable model with observable attributes. In addition to `Observable` methods, it has a `notify_observers` method to be called when changes occur. It automatically enhances all attribute setters (ending with `=`) to notify observers on changes. Also, it automatically handles observing array attributes using `ObservableArray` appropriately so they would notify observers upon array mutation changes.
1190
+ - `Glimmer::DataBinding::ObservableModel`: Mixin module for any observable model (`Object`, `Struct` or `OpenStruct`) with observable attributes (observes attribute writers and `Struct`/`OpenStruct` `:[]=` method). In addition to `Observable` methods, it has a `notify_observers` method to be called when changes occur. It automatically enhances all attribute setters (ending with `=`) to notify observers on changes. Also, it automatically handles observing array attributes using `ObservableArray` appropriately so they would notify observers upon array mutation changes.
1190
1191
  - `Glimmer::DataBinding::ObservableArray`: Mixin module for any observable array collection that automatically handles notifying observers upon performing array mutation operations (e.g. `push`, `select!`, or `delete`) recursively (meaning if an array contained arrays and they changed, observers are notified). Accepts `recursive: true` option in `add_observer` method to recursively observe nested arrays all the way down. Alternatively, pass `recursive: [integer]` to limit recursion in `Array` observation to a specific number of levels beyond the first level (which is always included).
1191
- - `Glimmer::DataBinding::ObservableHash`: Mixin module for any observable hash that automatically handles notifying observers upon performing hash mutation operations (e.g. `hash[key]=value`, `select!`, `merge!`)
1192
+ - `Glimmer::DataBinding::ObservableHash`: Mixin module for any observable hash that automatically handles notifying observers upon performing hash mutation operations (e.g. `hash[key]=value`, `select!`, `merge!`). Also, it automatically handles observing array values using `ObservableArray` appropriately so they would notify observers upon array mutation changes.
1192
1193
  - `Glimmer::DataBinding::ModelBinding`: a higher-level abstraction that relies on all the other observer/observable classes to support basic data-binding, nested data-binding, and computed data-binding
1193
1194
  - `Glimmer::DataBinding::Shine`: enables highly intuitive and visually expressive syntax to perform bidirectional (two-way) data-binding with `<=>` and unidirectional (one-way) data-binding with `<=`
1194
1195
 
@@ -1254,7 +1255,7 @@ end
1254
1255
 
1255
1256
  Note that if an observed model attribute or hash key is an array, it is automatically observed for array changes, not just attribute/key-value changes.
1256
1257
 
1257
- All of the features above make Glimmer's data-binding library one of the most sophisticated and advanced in the industry since they automate everything instead of requiring endless manual configuration, thus resulting in some of the tersest most declarative syntax for using observers and data-binding.
1258
+ All of the features above make Glimmer's data-binding library one of the most sophisticated and advanced in the industry since it automates everything instead of requiring endless manual configuration, thus resulting in some of the tersest most declarative syntax for using observers and data-binding.
1258
1259
 
1259
1260
  You may learn more by looking into [data-binding specs](/Users/andy/code/glimmer/spec/lib/glimmer/data_binding) as well as [Data-Binding](https://github.com/AndyObtiva/glimmer-dsl-swt/blob/master/docs/reference/GLIMMER_GUI_DSL_SYNTAX.md#data-binding) and [Observer](https://github.com/AndyObtiva/glimmer-dsl-swt/blob/master/docs/reference/GLIMMER_GUI_DSL_SYNTAX.md#observer) usage in [Glimmer DSL for SWT](https://github.com/AndyObtiva/glimmer-dsl-swt)
1260
1261
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 2.4.1
1
+ 2.5.0
data/glimmer.gemspec CHANGED
@@ -2,17 +2,17 @@
2
2
  # DO NOT EDIT THIS FILE DIRECTLY
3
3
  # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
4
  # -*- encoding: utf-8 -*-
5
- # stub: glimmer 2.4.1 ruby lib
5
+ # stub: glimmer 2.5.0 ruby lib
6
6
 
7
7
  Gem::Specification.new do |s|
8
8
  s.name = "glimmer".freeze
9
- s.version = "2.4.1"
9
+ s.version = "2.5.0"
10
10
 
11
11
  s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
12
12
  s.require_paths = ["lib".freeze]
13
13
  s.authors = ["AndyMaleh".freeze]
14
- s.date = "2021-11-01"
15
- s.description = "Glimmer is a Ruby DSL Framework for Ruby GUI and More, consisting of a DSL Engine and an Observable / Observer / Data-Binding Library (including Observable Model, Observable Array, and Observable Hash). Used in Glimmer DSL for SWT (JRuby Desktop Development GUI Framework), Glimmer DSL for Tk (Ruby Desktop Development GUI Library), Glimmer DSL for LibUI (Prerequisite-Free Ruby Desktop Development GUI Library), Glimmer DSL for Opal (Pure Ruby Web GUI and Auto-Webifier of Desktop Apps), Glimmer DSL for XML (& HTML), and Glimmer DSL for CSS.".freeze
14
+ s.date = "2021-11-19"
15
+ s.description = "Glimmer is a Ruby DSL Framework for Ruby GUI and More, consisting of a DSL Engine and an Observable / Observer / Data-Binding Library (including Observable Model, Observable Array, and Observable Hash). Used in Glimmer DSL for SWT (JRuby Desktop Development GUI Framework), Glimmer DSL for Opal (Pure Ruby Web GUI and Auto-Webifier of Desktop Apps), Glimmer DSL for Tk (Ruby Desktop Development GUI Library), Glimmer DSL for LibUI (Prerequisite-Free Ruby Desktop Development GUI Library), Glimmer DSL for GTK (Ruby-GNOME Desktop Development GUI Library), Glimmer DSL for XML (& HTML), and Glimmer DSL for CSS.".freeze
16
16
  s.email = "andy.am@gmail.com".freeze
17
17
  s.extra_rdoc_files = [
18
18
  "CHANGELOG.md",
@@ -33,6 +33,7 @@ Gem::Specification.new do |s|
33
33
  "lib/glimmer/data_binding/observable.rb",
34
34
  "lib/glimmer/data_binding/observable_array.rb",
35
35
  "lib/glimmer/data_binding/observable_hash.rb",
36
+ "lib/glimmer/data_binding/observable_hashable.rb",
36
37
  "lib/glimmer/data_binding/observable_model.rb",
37
38
  "lib/glimmer/data_binding/observer.rb",
38
39
  "lib/glimmer/data_binding/shine.rb",
@@ -51,7 +52,7 @@ Gem::Specification.new do |s|
51
52
  ]
52
53
  s.homepage = "http://github.com/AndyObtiva/glimmer".freeze
53
54
  s.licenses = ["MIT".freeze]
54
- s.rubygems_version = "3.2.22".freeze
55
+ s.rubygems_version = "3.2.31".freeze
55
56
  s.summary = "Glimmer - DSL Engine for Ruby GUI and More".freeze
56
57
 
57
58
  if s.respond_to? :specification_version then
@@ -70,7 +71,7 @@ Gem::Specification.new do |s|
70
71
  s.add_development_dependency(%q<coveralls>.freeze, [">= 0"])
71
72
  s.add_development_dependency(%q<simplecov>.freeze, ["~> 0.16.1"])
72
73
  s.add_development_dependency(%q<simplecov-lcov>.freeze, ["~> 0.7.0"])
73
- s.add_development_dependency(%q<rake-tui>.freeze, [">= 0"])
74
+ s.add_development_dependency(%q<rake-tui>.freeze, ["> 0"])
74
75
  else
75
76
  s.add_dependency(%q<array_include_methods>.freeze, ["~> 1.4.0"])
76
77
  s.add_dependency(%q<facets>.freeze, [">= 3.1.0", "< 4.0.0"])
@@ -83,7 +84,7 @@ Gem::Specification.new do |s|
83
84
  s.add_dependency(%q<coveralls>.freeze, [">= 0"])
84
85
  s.add_dependency(%q<simplecov>.freeze, ["~> 0.16.1"])
85
86
  s.add_dependency(%q<simplecov-lcov>.freeze, ["~> 0.7.0"])
86
- s.add_dependency(%q<rake-tui>.freeze, [">= 0"])
87
+ s.add_dependency(%q<rake-tui>.freeze, ["> 0"])
87
88
  end
88
89
  end
89
90
 
@@ -30,9 +30,9 @@ module Glimmer
30
30
 
31
31
  attr_reader :binding_options, :property_name_expression
32
32
 
33
- def initialize(base_model, property_name_expression, binding_options = nil)
34
- @base_model = base_model
35
- @property_name_expression = property_name_expression
33
+ def initialize(*args)
34
+ binding_options = args.pop if args.size > 1 && args.last.is_a?(Hash)
35
+ @base_model, @property_name_expression = args
36
36
  @binding_options = binding_options || Concurrent::Hash.new
37
37
  if computed?
38
38
  @computed_model_bindings = Concurrent::Array.new(computed_by.map do |computed_by_property_expression|
@@ -91,7 +91,7 @@ module Glimmer
91
91
  end
92
92
 
93
93
  def nested_property?
94
- property_name_expression.match(/[.\[]/)
94
+ property_name_expression.to_s.match(/[.\[]/)
95
95
  end
96
96
 
97
97
  def computed?
@@ -137,7 +137,7 @@ module Glimmer
137
137
  apply_processor(@binding_options[:after_read], converted_value)
138
138
  end
139
139
  end
140
- observer_registration = model_binding_observer.observe(model, property_name, observation_options)
140
+ observer_registration = model_binding_observer.observe(*[model, property_name, observation_options].compact)
141
141
  my_registration = observer.registration_for(self)
142
142
  observer.add_dependent(my_registration => observer_registration)
143
143
  end
@@ -206,7 +206,7 @@ module Glimmer
206
206
  def call(value, *extra_args)
207
207
  return if model.nil?
208
208
  converted_value = value
209
- invoke_property_writer(model, "#{property_name}=", converted_value) unless converted_value == evaluate_property
209
+ invoke_property_writer(model, model.is_a?(Hash) ? property_name : "#{property_name}=", converted_value) unless converted_value == evaluate_property || property_name.nil?
210
210
  end
211
211
 
212
212
  def evaluate_property
@@ -263,11 +263,18 @@ module Glimmer
263
263
  property_argument = property_argument.to_i if property_argument.match(/\d+/)
264
264
  object.send(property_method, property_argument)
265
265
  else
266
- object.send(property_expression)
266
+ if property_expression.nil?
267
+ object
268
+ elsif object.is_a?(Hash)
269
+ object[property_expression]
270
+ else
271
+ object.send(property_expression)
272
+ end
267
273
  end
268
274
  end
269
275
 
270
276
  def invoke_property_writer(object, property_expression, value)
277
+ return if property_expression.nil?
271
278
  raise "Cannot invoke `#{property_expression}` because ModelBinding#binding_options[:read_only]=true" if @binding_options[:read_only]
272
279
  apply_processor(@binding_options[:before_write], value)
273
280
  converted_value = convert_on_write(value)
@@ -277,7 +284,11 @@ module Glimmer
277
284
  property_argument = property_argument.to_i if property_argument.match(/\d+/)
278
285
  object.send(property_method, property_argument, converted_value)
279
286
  else
280
- object.send(property_expression, converted_value)
287
+ if object.is_a?(Hash)
288
+ object[property_expression] = converted_value
289
+ else
290
+ object.send(property_expression, converted_value)
291
+ end
281
292
  end
282
293
  apply_processor(@binding_options[:after_write], converted_value)
283
294
  end
@@ -50,7 +50,7 @@ module Glimmer
50
50
  element_properties = args
51
51
  element_properties = element_properties.flatten.compact.uniq
52
52
  return observer if has_observer?(observer) && has_observer_element_properties?(observer, element_properties)
53
- property_observer_list << observer
53
+ property_observer_list[observer] = options
54
54
  observer_element_properties[observer] = element_properties_for(observer) + Concurrent::Set.new(element_properties)
55
55
  if !options.empty? && options[:recursive].is_a?(Integer)
56
56
  options = options.clone
@@ -60,9 +60,9 @@ module Glimmer
60
60
  observer
61
61
  end
62
62
 
63
- def add_element_observers(element, options = {})
64
- property_observer_list.each do |observer|
65
- add_element_observer(element, observer, options)
63
+ def add_element_observers(element, general_options = {})
64
+ property_observer_list.each do |observer, options|
65
+ add_element_observer(element, observer, options.merge(general_options))
66
66
  end
67
67
  end
68
68
 
@@ -79,7 +79,7 @@ module Glimmer
79
79
  return unless object&.is_a?(Array)
80
80
  array_object_observer = array_object_observer_for(object)
81
81
  array_observer_registration = array_object_observer.observe(object, options)
82
- property_observer_list.each do |observer|
82
+ property_observer_list.each do |observer, options|
83
83
  my_registration = observer.registration_for(self)
84
84
  observer.add_dependent(my_registration => array_observer_registration)
85
85
  end
@@ -109,7 +109,7 @@ module Glimmer
109
109
  end
110
110
 
111
111
  def remove_element_observers(element)
112
- property_observer_list.each do |observer|
112
+ property_observer_list.each do |observer, options|
113
113
  remove_element_observer(element, observer)
114
114
  end
115
115
  end
@@ -120,15 +120,15 @@ module Glimmer
120
120
  end
121
121
  if element.is_a?(ObservableArray)
122
122
  array_object_observer_for(element).unobserve(element)
123
- element.property_observer_list.select {|o| o.respond_to?(:observable_array) && o.observable_array == self}.each do |o|
124
- o.deregister_all_observables
123
+ element.property_observer_list.select {|obs, opt| obs.respond_to?(:observable_array) && obs.observable_array == self}.each do |o|
124
+ o.deregister_all_observables if o.respond_to?(:deregister_all_observables)
125
125
  @array_object_observers.reject! {|k, v| v == o}
126
126
  end
127
127
  end
128
128
  end
129
129
 
130
130
  def has_observer?(observer)
131
- property_observer_list.include?(observer)
131
+ property_observer_list.keys.include?(observer)
132
132
  end
133
133
 
134
134
  def has_observer_element_properties?(observer, element_properties)
@@ -136,7 +136,7 @@ module Glimmer
136
136
  end
137
137
 
138
138
  def property_observer_list
139
- @property_observer_list ||= Concurrent::Set.new
139
+ @property_observer_list ||= Concurrent::Hash.new
140
140
  end
141
141
 
142
142
  def observer_element_properties
@@ -148,7 +148,7 @@ module Glimmer
148
148
  end
149
149
 
150
150
  def notify_observers
151
- property_observer_list.to_a.each { |o| o.call(self) }
151
+ property_observer_list.to_a.each { |obs, opt| obs.call(self) }
152
152
  end
153
153
 
154
154
  def <<(element)
@@ -372,7 +372,7 @@ module Glimmer
372
372
 
373
373
  def unregister_dependent_observers(old_value)
374
374
  return unless old_value.is_a?(ObservableModel) || old_value.is_a?(ObservableArray)
375
- property_observer_list.each { |observer| observer.unregister_dependents_with_observable(observer.registration_for(self), old_value) }
375
+ property_observer_list.each { |observer, options| observer.unregister_dependents_with_observable(observer.registration_for(self), old_value) }
376
376
  end
377
377
  alias deregister_dependent_observers unregister_dependent_observers
378
378
  end
@@ -19,13 +19,13 @@
19
19
  # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
20
  # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
21
 
22
- require 'glimmer/data_binding/observable'
22
+ require 'glimmer/data_binding/observable_hashable'
23
23
  require 'glimmer/data_binding/observer'
24
24
 
25
25
  module Glimmer
26
26
  module DataBinding
27
27
  module ObservableHash
28
- include Observable
28
+ include ObservableHashable
29
29
 
30
30
  class Notifier
31
31
  include Observer
@@ -40,28 +40,6 @@ module Glimmer
40
40
  end
41
41
  end
42
42
 
43
- OBSERVED_STORE_METHOD = lambda do |key, value|
44
- if key_observer_list(key).empty?
45
- if all_key_observer_list.empty?
46
- self.send('__original__store', key, value)
47
- else
48
- old_value = self[key]
49
- unregister_dependent_observers(nil, old_value) # remove dependent observers previously installed in ensure_array_object_observer and ensure_hash_object_observer
50
- self.send('__original__store', key, value)
51
- notify_observers(key)
52
- ensure_array_object_observer(nil, value, old_value)
53
- ensure_hash_object_observer(nil, value, old_value)
54
- end
55
- else
56
- old_value = self[key]
57
- unregister_dependent_observers(key, old_value) # remove dependent observers previously installed in ensure_array_object_observer and ensure_hash_object_observer
58
- self.send('__original__store', key, value)
59
- notify_observers(key)
60
- ensure_array_object_observer(key, value, old_value)
61
- ensure_hash_object_observer(key, value, old_value)
62
- end
63
- end
64
-
65
43
  def add_observer(observer, key = nil, options = {})
66
44
  if key.is_a?(Hash)
67
45
  options = key
@@ -123,24 +101,6 @@ module Glimmer
123
101
  (key_observer_list(key).to_a - all_key_observer_list.to_a).each { |observer| observer.call(self[key], key) }
124
102
  end
125
103
 
126
- def add_key_writer_observer(key = nil, options)
127
- ensure_array_object_observer(key, self[key], nil, options)
128
- ensure_hash_object_observer(key, self[key], nil, options)
129
- begin
130
- method('__original__store')
131
- rescue
132
- define_singleton_method('__original__store', store_method)
133
- define_singleton_method('[]=', &OBSERVED_STORE_METHOD)
134
- end
135
- rescue => e
136
- #ignore writing if no key writer exists
137
- Glimmer::Config.logger.debug {"No need to observe store method: '[]='\n#{e.message}\n#{e.backtrace.join("\n")}"}
138
- end
139
-
140
- def store_method
141
- self.class.instance_method('[]=') rescue self.method('[]=')
142
- end
143
-
144
104
  def unregister_dependent_observers(key, old_value)
145
105
  # TODO look into optimizing this
146
106
  return unless old_value.is_a?(ObservableModel) || old_value.is_a?(ObservableArray) || old_value.is_a?(ObservableHash)
@@ -166,24 +126,6 @@ module Glimmer
166
126
  @array_object_observers[key]
167
127
  end
168
128
 
169
- def ensure_hash_object_observer(key, object, old_object = nil, options = {})
170
- options ||= {}
171
- return unless object&.is_a?(Hash)
172
- hash_object_observer = hash_object_observer_for(key)
173
- hash_observer_registration = hash_object_observer.observe(object, options)
174
- key_observer_list(key).each do |observer|
175
- my_registration = observer.registration_for(self, key) # TODO eliminate repetition
176
- observer.add_dependent(my_registration => hash_observer_registration)
177
- end
178
- hash_object_observer_for(key).unregister(old_object) if old_object.is_a?(ObservableHash)
179
- end
180
-
181
- def hash_object_observer_for(key)
182
- @hash_object_observers ||= Concurrent::Hash.new
183
- @hash_object_observers[key] = ObservableModel::Notifier.new(self, key) unless @hash_object_observers.has_key?(key)
184
- @hash_object_observers[key]
185
- end
186
-
187
129
  def delete(key, &block)
188
130
  old_value = self[key]
189
131
  unless old_value.nil?
@@ -0,0 +1,75 @@
1
+ # Copyright (c) 2007-2021 Andy Maleh
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining
4
+ # a copy of this software and associated documentation files (the
5
+ # "Software"), to deal in the Software without restriction, including
6
+ # without limitation the rights to use, copy, modify, merge, publish,
7
+ # distribute, sublicense, and/or sell copies of the Software, and to
8
+ # permit persons to whom the Software is furnished to do so, subject to
9
+ # the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be
12
+ # included in all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+
22
+ require 'glimmer/data_binding/observable'
23
+
24
+ module Glimmer
25
+ module DataBinding
26
+ # Represents a Hash-like object with attributes writable via :[]= store method like Hash, Struct, and OpenStruct
27
+ # Expects including class to have the following methods:
28
+ # - key_observer_list
29
+ # - all_key_observer_list
30
+ # - unregister_dependent_observer
31
+ # - ensure_array_object_observer
32
+ module ObservableHashable
33
+ include Observable
34
+
35
+ OBSERVED_STORE_METHOD = lambda do |options|
36
+ lambda do |key, value|
37
+ if key_observer_list(key).empty?
38
+ if all_key_observer_list.empty?
39
+ self.send('__original__store', key, value)
40
+ else
41
+ old_value = self[key]
42
+ unregister_dependent_observers(nil, old_value) # remove dependent observers previously installed in ensure_array_object_observer
43
+ self.send('__original__store', key, value)
44
+ notify_observers(key)
45
+ ensure_array_object_observer(nil, value, old_value, options)
46
+ end
47
+ else
48
+ old_value = self[key]
49
+ unregister_dependent_observers(key, old_value) # remove dependent observers previously installed in ensure_array_object_observer
50
+ self.send('__original__store', key, value)
51
+ notify_observers(key)
52
+ ensure_array_object_observer(key, value, old_value, options)
53
+ end
54
+ end
55
+ end
56
+
57
+ def add_key_writer_observer(key = nil, options)
58
+ ensure_array_object_observer(key, self[key], nil, options)
59
+ begin
60
+ method('__original__store')
61
+ rescue
62
+ define_singleton_method('__original__store', store_method)
63
+ define_singleton_method('[]=', &OBSERVED_STORE_METHOD.call(options))
64
+ end
65
+ rescue => e
66
+ #ignore writing if no key writer exists
67
+ Glimmer::Config.logger.debug {"No need to observe store method: '[]='\n#{e.message}\n#{e.backtrace.join("\n")}"}
68
+ end
69
+
70
+ def store_method
71
+ self.class.instance_method('[]=') rescue self.method('[]=')
72
+ end
73
+ end
74
+ end
75
+ end
@@ -19,13 +19,13 @@
19
19
  # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
20
  # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
21
 
22
- require 'glimmer/data_binding/observable'
22
+ require 'glimmer/data_binding/observable_hashable'
23
23
  require 'glimmer/data_binding/observer'
24
24
 
25
25
  module Glimmer
26
26
  module DataBinding
27
27
  module ObservableModel
28
- include Observable
28
+ include ObservableHashable
29
29
 
30
30
  class Notifier
31
31
  include Observer
@@ -40,25 +40,25 @@ module Glimmer
40
40
  end
41
41
  end
42
42
 
43
- PROPERTY_WRITER_FACTORY = lambda do |property_name|
43
+ PROPERTY_WRITER_FACTORY = lambda do |property_name, options|
44
44
  property_writer_name = "#{property_name}="
45
45
  lambda do |value|
46
46
  old_value = self.send(property_name)
47
- unregister_dependent_observers(property_name, old_value) # remove dependent observers previously installed in ensure_array_object_observer and ensure_hash_object_observer
47
+ unregister_dependent_observers(property_name, old_value) # remove dependent observers previously installed in ensure_array_object_observer
48
48
  self.send("__original__#{property_writer_name}", value)
49
49
  notify_observers(property_name)
50
- ensure_array_object_observer(property_name, value, old_value)
51
- ensure_hash_object_observer(property_name, value, old_value)
50
+ ensure_array_object_observer(property_name, value, old_value, options)
52
51
  end
53
52
  end
54
-
53
+
55
54
  def add_observer(observer, property_name, options = {})
56
55
  return observer if has_observer?(observer, property_name)
57
56
  property_observer_list(property_name) << observer
58
57
  add_property_writer_observers(property_name, options)
58
+ add_key_writer_observer(property_name, options) if is_a?(Struct) || is_a?(OpenStruct)
59
59
  observer
60
60
  end
61
-
61
+
62
62
  def remove_observer(observer, property_name, options = {})
63
63
  if has_observer?(observer, property_name)
64
64
  property_observer_list(property_name).delete(observer)
@@ -67,10 +67,11 @@ module Glimmer
67
67
  end
68
68
 
69
69
  def remove_observers(property_name)
70
- property_observer_hash[property_name.to_sym].each do |observer|
70
+ property_key = property_name&.to_sym
71
+ property_observer_hash[property_key].each do |observer|
71
72
  remove_observer(observer, property_name)
72
73
  end
73
- property_observer_hash.delete(property_name.to_sym)
74
+ property_observer_hash.delete(property_key)
74
75
  end
75
76
 
76
77
  def remove_all_observers
@@ -95,24 +96,32 @@ module Glimmer
95
96
  end
96
97
 
97
98
  def property_observer_list(property_name)
98
- property_observer_hash[property_name.to_sym] = Concurrent::Set.new unless property_observer_hash[property_name.to_sym]
99
- property_observer_hash[property_name.to_sym]
99
+ property_key = property_name&.to_sym
100
+ property_observer_hash[property_key] = Concurrent::Set.new unless property_observer_hash[property_key]
101
+ property_observer_hash[property_key]
100
102
  end
103
+ alias key_observer_list property_observer_list
104
+
105
+ def all_property_observer_list
106
+ property_observer_list(nil)
107
+ end
108
+ alias all_key_observer_list all_property_observer_list
101
109
 
102
110
  def notify_observers(property_name)
103
111
  property_observer_list(property_name).to_a.each { |observer| observer.call(send(property_name)) }
104
112
  end
105
-
113
+
106
114
  def add_property_writer_observers(property_name, options)
107
115
  property_writer_name = "#{property_name}="
108
116
  method(property_writer_name)
109
117
  ensure_array_object_observer(property_name, send(property_name), nil, options)
110
- ensure_hash_object_observer(property_name, send(property_name), nil, options)
111
118
  begin
112
119
  method("__original__#{property_writer_name}")
113
120
  rescue
114
121
  define_singleton_method("__original__#{property_writer_name}", property_writer_method(property_writer_name))
115
- define_singleton_method(property_writer_name, &PROPERTY_WRITER_FACTORY.call(property_name))
122
+ # Note the limitation that the first observe call options apply to all subsequent observations meaning even if unobserve was called, options do not change from initial ones
123
+ # It is good enough for now. If there is a need to address this in the future, this is where to start the work
124
+ define_singleton_method(property_writer_name, &PROPERTY_WRITER_FACTORY.call(property_name, options))
116
125
  end
117
126
  rescue => e
118
127
  #ignore writing if no property writer exists
@@ -130,7 +139,7 @@ module Glimmer
130
139
  end
131
140
  alias deregister_dependent_observers unregister_dependent_observers
132
141
 
133
- def ensure_array_object_observer(property_name, object, old_object = nil, options = {})
142
+ def ensure_array_object_observer(property_name, object, old_object = nil, options = nil)
134
143
  options ||= {}
135
144
  return unless object&.is_a?(Array)
136
145
  array_object_observer = array_object_observer_for(property_name)
@@ -147,24 +156,6 @@ module Glimmer
147
156
  @array_object_observers[property_name] = Notifier.new(self, property_name) unless @array_object_observers.has_key?(property_name)
148
157
  @array_object_observers[property_name]
149
158
  end
150
-
151
- def ensure_hash_object_observer(property_name, object, old_object = nil, options)
152
- options ||= {}
153
- return unless object&.is_a?(Hash)
154
- hash_object_observer = hash_object_observer_for(property_name)
155
- hash_observer_registration = hash_object_observer.observe(object, options)
156
- property_observer_list(property_name).each do |observer|
157
- my_registration = observer.registration_for(self, property_name) # TODO eliminate repetition
158
- observer.add_dependent(my_registration => hash_observer_registration)
159
- end
160
- hash_object_observer_for(property_name).unregister(old_object) if old_object.is_a?(ObservableHash)
161
- end
162
-
163
- def hash_object_observer_for(property_name)
164
- @hash_object_observers ||= Concurrent::Hash.new
165
- @hash_object_observers[property_name] = Notifier.new(self, property_name) unless @hash_object_observers.has_key?(property_name)
166
- @hash_object_observers[property_name]
167
- end
168
159
  end
169
160
  end
170
161
  end
@@ -47,11 +47,11 @@ module Glimmer
47
47
  end
48
48
 
49
49
  class Registration < Struct.new(:observer, :observable, :args, keyword_init: true)
50
- def unregister
50
+ def deregister
51
51
  observer.unobserve(observable, *args)
52
52
  end
53
- alias unobserve unregister
54
- alias deregister unregister
53
+ alias unregister deregister
54
+ alias unobserve deregister
55
55
  end
56
56
 
57
57
  class << self
@@ -82,7 +82,7 @@ module Glimmer
82
82
 
83
83
  # registers observer in an observable on args usually containing a property and options (optional)
84
84
  # observer maintains registration list to unregister later
85
- def register(observable, *args)
85
+ def observe(observable, *args)
86
86
  return if observable.nil?
87
87
  unless observable.is_a?(Observable)
88
88
  # TODO refactor code to be more smart/polymorphic/automated and honor open/closed principle (e.g. for SomeClass, search if there is ObservableSomeClass)
@@ -98,9 +98,9 @@ module Glimmer
98
98
  observable.add_observer(self, *args)
99
99
  ensure_registration_for!(observable, *args)
100
100
  end
101
- alias observe register
101
+ alias register observe
102
102
 
103
- def unregister(observable, *args)
103
+ def unobserve(observable, *args)
104
104
  return unless observable.is_a?(Observable)
105
105
  args = compact_args(args)
106
106
  registration = registration_for(observable, *args)
@@ -113,27 +113,27 @@ module Glimmer
113
113
  observable.remove_observer(self, *args)
114
114
  end
115
115
  end
116
- alias unobserve unregister
117
- alias deregister unregister
116
+ alias unregister unobserve
117
+ alias deregister unobserve
118
118
 
119
- def unregister_dependents_with_observable(registration, dependent_observable)
119
+ def unobserve_dependents_with_observable(registration, dependent_observable)
120
120
  thedependents = dependents_for(registration).select do |thedependent|
121
121
  thedependent.observable == dependent_observable
122
122
  end
123
123
  thedependents.each(&:deregister)
124
124
  end
125
- alias unobserve_dependents_with_observable unregister_dependents_with_observable
126
- alias deregister_dependents_with_observable unregister_dependents_with_observable
125
+ alias unregister_dependents_with_observable unobserve_dependents_with_observable
126
+ alias deregister_dependents_with_observable unobserve_dependents_with_observable
127
127
 
128
128
  # cleans up all registrations in observables
129
- def unregister_all_observables
129
+ def unobserve_all_observables
130
130
  registrations.values.dup.each do |registration|
131
131
  registration.deregister
132
132
  registrations.delete([registration.observable.object_id, registration.args])
133
133
  end
134
134
  end
135
- alias unobserve_all_observables unregister_all_observables
136
- alias deregister_all_observables unregister_all_observables
135
+ alias unregister_all_observables unobserve_all_observables
136
+ alias deregister_all_observables unobserve_all_observables
137
137
 
138
138
  # add dependent observer to unregister when unregistering observer
139
139
  def add_dependent(parent_to_dependent_hash)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: glimmer
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.4.1
4
+ version: 2.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - AndyMaleh
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-11-01 00:00:00.000000000 Z
11
+ date: 2021-11-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: array_include_methods
@@ -192,23 +192,24 @@ dependencies:
192
192
  name: rake-tui
193
193
  requirement: !ruby/object:Gem::Requirement
194
194
  requirements:
195
- - - ">="
195
+ - - ">"
196
196
  - !ruby/object:Gem::Version
197
197
  version: '0'
198
198
  type: :development
199
199
  prerelease: false
200
200
  version_requirements: !ruby/object:Gem::Requirement
201
201
  requirements:
202
- - - ">="
202
+ - - ">"
203
203
  - !ruby/object:Gem::Version
204
204
  version: '0'
205
205
  description: Glimmer is a Ruby DSL Framework for Ruby GUI and More, consisting of
206
206
  a DSL Engine and an Observable / Observer / Data-Binding Library (including Observable
207
207
  Model, Observable Array, and Observable Hash). Used in Glimmer DSL for SWT (JRuby
208
- Desktop Development GUI Framework), Glimmer DSL for Tk (Ruby Desktop Development
209
- GUI Library), Glimmer DSL for LibUI (Prerequisite-Free Ruby Desktop Development
210
- GUI Library), Glimmer DSL for Opal (Pure Ruby Web GUI and Auto-Webifier of Desktop
211
- Apps), Glimmer DSL for XML (& HTML), and Glimmer DSL for CSS.
208
+ Desktop Development GUI Framework), Glimmer DSL for Opal (Pure Ruby Web GUI and
209
+ Auto-Webifier of Desktop Apps), Glimmer DSL for Tk (Ruby Desktop Development GUI
210
+ Library), Glimmer DSL for LibUI (Prerequisite-Free Ruby Desktop Development GUI
211
+ Library), Glimmer DSL for GTK (Ruby-GNOME Desktop Development GUI Library), Glimmer
212
+ DSL for XML (& HTML), and Glimmer DSL for CSS.
212
213
  email: andy.am@gmail.com
213
214
  executables: []
214
215
  extensions: []
@@ -230,6 +231,7 @@ files:
230
231
  - lib/glimmer/data_binding/observable.rb
231
232
  - lib/glimmer/data_binding/observable_array.rb
232
233
  - lib/glimmer/data_binding/observable_hash.rb
234
+ - lib/glimmer/data_binding/observable_hashable.rb
233
235
  - lib/glimmer/data_binding/observable_model.rb
234
236
  - lib/glimmer/data_binding/observer.rb
235
237
  - lib/glimmer/data_binding/shine.rb
@@ -264,7 +266,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
264
266
  - !ruby/object:Gem::Version
265
267
  version: '0'
266
268
  requirements: []
267
- rubygems_version: 3.2.22
269
+ rubygems_version: 3.2.31
268
270
  signing_key:
269
271
  specification_version: 4
270
272
  summary: Glimmer - DSL Engine for Ruby GUI and More