glimmer 0.9.1 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,104 @@
1
+ require 'glimmer/data_binding/observable'
2
+ require 'glimmer/data_binding/observer'
3
+
4
+ module Glimmer
5
+ module DataBinding
6
+ # TODO prefix utility methods with double-underscore
7
+ module ObservableModel
8
+ include Observable
9
+
10
+ class Notifier
11
+ include Observer
12
+ def initialize(observable_model, property_name)
13
+ @observable_model = observable_model
14
+ @property_name = property_name
15
+ end
16
+ def call(new_value=nil)
17
+ @observable_model.notify_observers(@property_name)
18
+ end
19
+ end
20
+
21
+ def add_observer(observer, property_name)
22
+ return observer if has_observer?(observer, property_name)
23
+ property_observer_list(property_name) << observer
24
+ add_property_writer_observers(property_name)
25
+ observer
26
+ end
27
+
28
+ def remove_observer(observer, property_name)
29
+ property_observer_list(property_name).delete(observer)
30
+ end
31
+
32
+ def has_observer?(observer, property_name)
33
+ property_observer_list(property_name).include?(observer)
34
+ end
35
+
36
+ def has_observer_for_any_property?(observer)
37
+ property_observer_hash.values.map(&:to_a).sum.include?(observer)
38
+ end
39
+
40
+ def property_observer_hash
41
+ @property_observers ||= Hash.new
42
+ end
43
+
44
+ def property_observer_list(property_name)
45
+ property_observer_hash[property_name.to_sym] = Set.new unless property_observer_hash[property_name.to_sym]
46
+ property_observer_hash[property_name.to_sym]
47
+ end
48
+
49
+ def notify_observers(property_name)
50
+ property_observer_list(property_name).each {|observer| observer.call(send(property_name))}
51
+ end
52
+ #TODO upon updating values, make sure dependent observers are cleared (not added as dependents here)
53
+
54
+ def add_property_writer_observers(property_name)
55
+ property_writer_name = "#{property_name}="
56
+ method(property_writer_name)
57
+ ensure_array_object_observer(property_name, send(property_name))
58
+ begin
59
+ singleton_method("__original_#{property_writer_name}")
60
+ rescue
61
+ old_method = self.class.instance_method(property_writer_name) rescue self.method(property_writer_name)
62
+ define_singleton_method("__original_#{property_writer_name}", old_method)
63
+ define_singleton_method(property_writer_name) do |value|
64
+ old_value = self.send(property_name)
65
+ unregister_dependent_observers(property_name, old_value)
66
+ self.send("__original_#{property_writer_name}", value)
67
+ notify_observers(property_name)
68
+ ensure_array_object_observer(property_name, value, old_value)
69
+ end
70
+ end
71
+ rescue => e
72
+ #ignore writing if no property writer exists
73
+ Glimmer::Config.logger.debug {"No need to observe property writer: #{property_writer_name}\n#{e.message}\n#{e.backtrace.join("\n")}"}
74
+ end
75
+
76
+ def unregister_dependent_observers(property_name, old_value)
77
+ # TODO look into optimizing this
78
+ return unless old_value.is_a?(ObservableModel) || old_value.is_a?(ObservableArray)
79
+ property_observer_list(property_name).each do |observer|
80
+ observer.unregister_dependents_with_observable(observer.registration_for(self, property_name), old_value)
81
+ end
82
+ end
83
+
84
+ def ensure_array_object_observer(property_name, object, old_object = nil)
85
+ return unless object.is_a?(Array)
86
+ array_object_observer = array_object_observer_for(property_name)
87
+ array_observer_registration = array_object_observer.observe(object)
88
+ property_observer_list(property_name).each do |observer|
89
+ my_registration = observer.registration_for(self, property_name) # TODO eliminate repetition
90
+ observer.add_dependent(my_registration => array_observer_registration)
91
+ end
92
+ array_object_observer_for(property_name).unregister(old_object) if old_object.is_a?(ObservableArray)
93
+ end
94
+
95
+ def array_object_observer_for(property_name)
96
+ @array_object_observers ||= {}
97
+ unless @array_object_observers.has_key?(property_name)
98
+ @array_object_observers[property_name] = ObservableModel::Notifier.new(self, property_name)
99
+ end
100
+ @array_object_observers[property_name]
101
+ end
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,127 @@
1
+ require 'glimmer/error'
2
+
3
+ module Glimmer
4
+ module DataBinding
5
+ # Mixin representing Observer trait from Observer Design Pattern
6
+ # Allows classes to include without interfering with their
7
+ # inheritance hierarchy.
8
+ #
9
+ # Includes a default implementation that can receive an observer block
10
+ # Example: Observer.proc {|new_value| puts new_value}
11
+ # Subclasses may override
12
+ module Observer
13
+ # Observer Proc default implementation that takes an observer block to process updates
14
+ # via call method
15
+ class Proc
16
+ include Observer
17
+
18
+ def initialize(&observer_block)
19
+ @observer_block = observer_block
20
+ end
21
+
22
+ # Called by observables once updates occur sending in the new_value if any
23
+ def call(new_value=nil)
24
+ @observer_block.call(new_value)
25
+ end
26
+ end
27
+
28
+ class Registration < Struct.new(:observer, :observable, :property, keyword_init: true)
29
+ def unregister
30
+ observer.unobserve(observable, property)
31
+ end
32
+ alias unobserve unregister
33
+ end
34
+
35
+ class << self
36
+ def proc(&observer_block)
37
+ Proc.new(&observer_block)
38
+ end
39
+ end
40
+
41
+ def registrations
42
+ @registrations ||= Set.new
43
+ end
44
+
45
+ def registration_for(observable, property = nil)
46
+ Registration.new(observer: self, observable: observable, property: property)
47
+ end
48
+
49
+ # mapping of registrations to dependents
50
+ # {[observable, property] => [[dependent, dependent_observable, dependent_property], ...]}
51
+ def dependents
52
+ @dependents ||= Hash.new
53
+ end
54
+
55
+ def dependents_for(registration)
56
+ dependents[registration] ||= Set.new
57
+ end
58
+
59
+ # registers observer in an observable on a property (optional)
60
+ # observer maintains registration list to unregister later
61
+ def register(observable, property = nil)
62
+ unless observable.is_a?(Observable)
63
+ # TODO refactor code to be more smart/polymorphic/automated and honor open/closed principle
64
+ if observable.is_a?(Array)
65
+ observable.extend(ObservableArray)
66
+ else
67
+ observable.extend(ObservableModel)
68
+ end
69
+ end
70
+ observable.add_observer(*[self, property].compact)
71
+ registration_for(observable, property).tap do |registration|
72
+ self.registrations << registration
73
+ end
74
+ end
75
+ alias observe register
76
+
77
+ def unregister(observable, property = nil)
78
+ # TODO optimize performance in the future via indexing and/or making a registration official object/class
79
+ observable.remove_observer(*[self, property].compact)
80
+ registration = registration_for(observable, property)
81
+ dependents_for(registration).each do |dependent|
82
+ dependent.unregister
83
+ remove_dependent(registration => dependent)
84
+ end
85
+ registrations.delete(registration)
86
+ end
87
+ alias unobserve unregister
88
+
89
+ def unregister_dependents_with_observable(registration, dependent_observable)
90
+ thedependents = dependents_for(registration).select do |thedependent|
91
+ thedependent.observable == dependent_observable
92
+ end
93
+ thedependents.each do |thedependent|
94
+ thedependent.unregister
95
+ end
96
+ end
97
+
98
+ # cleans up all registrations in observables
99
+ def unregister_all_observables
100
+ registrations.each do |registration|
101
+ registration.unregister
102
+ end
103
+ end
104
+ alias unobserve_all_observables unregister_all_observables
105
+
106
+ # add dependent observer to unregister when unregistering observer
107
+ def add_dependent(parent_to_dependent_hash)
108
+ registration = parent_to_dependent_hash.keys.first
109
+ dependent = parent_to_dependent_hash.values.first
110
+ dependents_for(registration) << dependent
111
+ end
112
+
113
+ def remove_dependent(parent_to_dependent_hash)
114
+ registration = parent_to_dependent_hash.keys.first
115
+ dependent = parent_to_dependent_hash.values.first
116
+ dependents_for(registration).delete(dependent)
117
+ end
118
+
119
+ def call(new_value)
120
+ raise Error, 'Not implemented!'
121
+ end
122
+ end
123
+ end
124
+ end
125
+
126
+ require 'glimmer/data_binding/observable_model'
127
+ require 'glimmer/data_binding/observable_array'
@@ -11,6 +11,8 @@ module Glimmer
11
11
  # When DSL engine interprets an expression, it attempts to handle
12
12
  # with ordered expression array specified via `.expressions=` method.
13
13
  class Engine
14
+ MESSAGE_NO_DSLS = "Glimmer has no DSLs configured. Add glimmer-dsl-swt gem or visit https://github.com/AndyObtiva/glimmer#multi-dsl-support for more details.\n"
15
+
14
16
  class << self
15
17
  def dsl=(dsl_name)
16
18
  dsl_name = dsl_name&.to_sym
@@ -69,6 +71,16 @@ module Glimmer
69
71
  # Static expressions indexed by keyword and dsl
70
72
  def static_expressions
71
73
  @static_expressions ||= {}
74
+ end
75
+
76
+ # Sets dynamic expression chains of responsibility. Useful for internal testing
77
+ def dynamic_expression_chains_of_responsibility=(chains)
78
+ @dynamic_expression_chains_of_responsibility = chains
79
+ end
80
+
81
+ # Sets static expressions. Useful for internal testing
82
+ def static_expressions=(expressions)
83
+ @static_expressions = expressions
72
84
  end
73
85
 
74
86
  # Sets an ordered array of DSL expressions to support
@@ -84,7 +96,7 @@ module Glimmer
84
96
  dynamic_expression_chains_of_responsibility[dsl] = expression_names.reverse.map do |expression_name|
85
97
  expression_class(dsl_namespace, expression_name).new
86
98
  end.reduce(nil) do |last_expresion_handler, expression|
87
- Glimmer::Config.logger&.debug "Adding dynamic expression: #{expression.class.name}"
99
+ Glimmer::Config.logger.debug {"Adding dynamic expression: #{expression.class.name}"}
88
100
  expression_handler = ExpressionHandler.new(expression)
89
101
  expression_handler.next = last_expresion_handler if last_expresion_handler
90
102
  expression_handler
@@ -92,21 +104,22 @@ module Glimmer
92
104
  end
93
105
 
94
106
  def add_static_expression(static_expression)
95
- Glimmer::Config.logger&.debug "Adding static expression: #{static_expression.class.name}"
107
+ Glimmer::Config.logger.debug {"Adding static expression: #{static_expression.class.name}"}
96
108
  keyword = static_expression.class.keyword
97
109
  static_expression_dsl = static_expression.class.dsl
98
110
  static_expressions[keyword] ||= {}
99
111
  static_expressions[keyword][static_expression_dsl] = static_expression
100
112
  Glimmer.send(:define_method, keyword) do |*args, &block|
101
- begin
102
- retrieved_static_expression = Glimmer::DSL::Engine.static_expressions[keyword][Glimmer::DSL::Engine.dsl]
113
+ if Glimmer::DSL::Engine.no_dsls?
114
+ puts Glimmer::DSL::Engine::MESSAGE_NO_DSLS
115
+ else
116
+ retrieved_static_expression = Glimmer::DSL::Engine.static_expressions[keyword][Glimmer::DSL::Engine.dsl]
103
117
  static_expression_dsl = (Glimmer::DSL::Engine.static_expressions[keyword].keys - Glimmer::DSL::Engine.disabled_dsls).first if retrieved_static_expression.nil?
104
118
  interpretation = nil
105
119
  if retrieved_static_expression.nil? && Glimmer::DSL::Engine.dsl && (static_expression_dsl.nil? || !Glimmer::DSL::Engine.static_expressions[keyword][static_expression_dsl].is_a?(TopLevelExpression))
106
120
  begin
107
121
  interpretation = Glimmer::DSL::Engine.interpret(keyword, *args, &block)
108
122
  rescue => e
109
- Glimmer::DSL::Engine.reset
110
123
  raise e if static_expression_dsl.nil? || !Glimmer::DSL::Engine.static_expressions[keyword][static_expression_dsl].is_a?(TopLevelExpression)
111
124
  end
112
125
  end
@@ -119,18 +132,14 @@ module Glimmer
119
132
  if !static_expression.can_interpret?(Glimmer::DSL::Engine.parent, keyword, *args, &block)
120
133
  raise Error, "Invalid use of Glimmer keyword #{keyword} with args #{args} under parent #{Glimmer::DSL::Engine.parent}"
121
134
  else
122
- Glimmer::Config.logger&.debug "#{static_expression.class.name} will handle expression keyword #{keyword}"
135
+ Glimmer::Config.logger.debug {"#{static_expression.class.name} will handle expression keyword #{keyword}"}
123
136
  static_expression.interpret(Glimmer::DSL::Engine.parent, keyword, *args, &block).tap do |ui_object|
124
137
  Glimmer::DSL::Engine.add_content(ui_object, static_expression, &block) unless block.nil?
125
138
  Glimmer::DSL::Engine.dsl_stack.pop
126
139
  end
127
140
  end
128
141
  end
129
- rescue StandardError => e
130
- # Glimmer::DSL::Engine.dsl_stack.pop
131
- Glimmer::DSL::Engine.reset
132
- raise e
133
- end
142
+ end
134
143
  end
135
144
  end
136
145
 
@@ -145,7 +154,7 @@ module Glimmer
145
154
  # Interprets Glimmer dynamic DSL expression consisting of keyword, args, and block (e.g. shell(:no_resize) { ... })
146
155
  def interpret(keyword, *args, &block)
147
156
  if no_dsls?
148
- puts "Glimmer has no DSLs configured. Add glimmer-dsl-swt gem or visit https://github.com/AndyObtiva/glimmer#multi-dsl-support for more details.\n"
157
+ puts MESSAGE_NO_DSLS
149
158
  return
150
159
  end
151
160
  keyword = keyword.to_s
@@ -156,10 +165,6 @@ module Glimmer
156
165
  add_content(ui_object, expression, &block)
157
166
  dsl_stack.pop
158
167
  end
159
- rescue StandardError => e
160
- # dsl_stack.pop
161
- reset
162
- raise e
163
168
  end
164
169
 
165
170
  # Adds content block to parent UI object
@@ -171,9 +176,12 @@ module Glimmer
171
176
  if block_given? && expression.is_a?(ParentExpression)
172
177
  dsl_stack.push(expression.class.dsl)
173
178
  parent_stack.push(parent)
174
- expression.add_content(parent, &block)
175
- parent_stack.pop
176
- dsl_stack.pop
179
+ begin
180
+ expression.add_content(parent, &block)
181
+ ensure
182
+ parent_stack.pop
183
+ dsl_stack.pop
184
+ end
177
185
  end
178
186
  end
179
187
 
@@ -23,9 +23,9 @@ module Glimmer
23
23
  # Otherwise, it forwards to the next handler configured via `#next=` method
24
24
  # If there is no handler next, then it raises an error
25
25
  def handle(parent, keyword, *args, &block)
26
- Glimmer::Config.logger&.debug "Attempting to handle #{keyword} with #{@expression.class.name.split(":").last}"
26
+ Glimmer::Config.logger.debug {"Attempting to handle #{keyword} with #{@expression.class.name.split(":").last}"}
27
27
  if @expression.can_interpret?(parent, keyword, *args, &block)
28
- Glimmer::Config.logger&.debug "#{@expression.class.name} will handle expression keyword #{keyword}"
28
+ Glimmer::Config.logger.debug {"#{@expression.class.name} will handle expression keyword #{keyword}"}
29
29
  return @expression
30
30
  elsif @next_expression_handler
31
31
  return @next_expression_handler.handle(parent, keyword, *args, &block)
@@ -34,7 +34,7 @@ module Glimmer
34
34
  message = "Glimmer keyword #{keyword} with args #{args} cannot be handled"
35
35
  message += " inside parent #{parent}" if parent
36
36
  message += "! Check the validity of the code."
37
- # Glimmer::Config.logger&.error message
37
+ # Glimmer::Config.logger.error {message}
38
38
  raise InvalidKeywordError, message
39
39
  end
40
40
  end
@@ -0,0 +1,5 @@
1
+ module Glimmer
2
+ # Represents Glimmer errors that occur due to excluded keywords passing through method_missing
3
+ class ExcludedKeywordError < RuntimeError
4
+ end
5
+ end
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: 0.9.1
4
+ version: 0.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - AndyMaleh
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-06-14 00:00:00.000000000 Z
11
+ date: 2020-07-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  requirement: !ruby/object:Gem::Requirement
@@ -132,6 +132,48 @@ dependencies:
132
132
  - - "<"
133
133
  - !ruby/object:Gem::Version
134
134
  version: 7.0.0
135
+ - !ruby/object:Gem::Dependency
136
+ requirement: !ruby/object:Gem::Requirement
137
+ requirements:
138
+ - - '='
139
+ - !ruby/object:Gem::Version
140
+ version: 0.8.23
141
+ name: coveralls
142
+ type: :development
143
+ prerelease: false
144
+ version_requirements: !ruby/object:Gem::Requirement
145
+ requirements:
146
+ - - '='
147
+ - !ruby/object:Gem::Version
148
+ version: 0.8.23
149
+ - !ruby/object:Gem::Dependency
150
+ requirement: !ruby/object:Gem::Requirement
151
+ requirements:
152
+ - - "~>"
153
+ - !ruby/object:Gem::Version
154
+ version: 0.16.1
155
+ name: simplecov
156
+ type: :development
157
+ prerelease: false
158
+ version_requirements: !ruby/object:Gem::Requirement
159
+ requirements:
160
+ - - "~>"
161
+ - !ruby/object:Gem::Version
162
+ version: 0.16.1
163
+ - !ruby/object:Gem::Dependency
164
+ requirement: !ruby/object:Gem::Requirement
165
+ requirements:
166
+ - - "~>"
167
+ - !ruby/object:Gem::Version
168
+ version: 0.7.0
169
+ name: simplecov-lcov
170
+ type: :development
171
+ prerelease: false
172
+ version_requirements: !ruby/object:Gem::Requirement
173
+ requirements:
174
+ - - "~>"
175
+ - !ruby/object:Gem::Version
176
+ version: 0.7.0
135
177
  description: Ruby Desktop Development GUI Library (JRuby on SWT)
136
178
  email: andy.am@gmail.com
137
179
  executables: []
@@ -145,6 +187,11 @@ files:
145
187
  - VERSION
146
188
  - lib/glimmer.rb
147
189
  - lib/glimmer/config.rb
190
+ - lib/glimmer/data_binding/model_binding.rb
191
+ - lib/glimmer/data_binding/observable.rb
192
+ - lib/glimmer/data_binding/observable_array.rb
193
+ - lib/glimmer/data_binding/observable_model.rb
194
+ - lib/glimmer/data_binding/observer.rb
148
195
  - lib/glimmer/dsl/engine.rb
149
196
  - lib/glimmer/dsl/expression.rb
150
197
  - lib/glimmer/dsl/expression_handler.rb
@@ -152,6 +199,7 @@ files:
152
199
  - lib/glimmer/dsl/static_expression.rb
153
200
  - lib/glimmer/dsl/top_level_expression.rb
154
201
  - lib/glimmer/error.rb
202
+ - lib/glimmer/excluded_keyword_error.rb
155
203
  - lib/glimmer/invalid_keyword_error.rb
156
204
  homepage: http://github.com/AndyObtiva/glimmer
157
205
  licenses: