glimmer 0.9.1 → 0.10.0

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