glimmer 0.9.4 → 0.10.3

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.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.9.4
1
+ 0.10.3
@@ -4,6 +4,7 @@
4
4
  # UI with domain models.
5
5
  require 'logger'
6
6
  require 'set'
7
+ require 'array_include_methods'
7
8
 
8
9
  $LOAD_PATH.unshift(File.expand_path('..', __FILE__))
9
10
 
@@ -17,7 +18,6 @@ require 'glimmer/config'
17
18
  # Glimmer DSL dynamic keywords (e.g. label, combo, etc...) are available via method_missing
18
19
  module Glimmer
19
20
  #TODO make it configurable to include or not include perhaps reverting to using included
20
- REGEX_METHODS_EXCLUDED = /^(to_|\[)/
21
21
 
22
22
  # TODO add loop detection support to avoid infinite loops (perhaps breaks after 3 repetitions and provides an option to allow it if intentional)
23
23
  class << self
@@ -41,27 +41,22 @@ module Glimmer
41
41
  new_loop_data = [method_symbol, args, block]
42
42
  if new_loop_data == Glimmer.loop_last_data
43
43
  Glimmer.loop_increment!
44
- if Glimmer.loop == Config.loop_max_count
45
- raise "Glimmer looped #{Config.loop_max_count} times with keyword '#{new_loop_data[0]}'! Check code for errors."
46
- end
44
+ raise "Glimmer looped #{Config.loop_max_count} times with keyword '#{new_loop_data[0]}'! Check code for errors." if Glimmer.loop == Config.loop_max_count
47
45
  else
48
46
  Glimmer.loop_reset!
49
47
  end
50
48
  Glimmer.loop_last_data = new_loop_data
51
49
  # This if statement speeds up Glimmer in girb or whenever directly including on main object
52
- if method_symbol.to_s.match(REGEX_METHODS_EXCLUDED)
53
- raise ExcludedKeywordError, "Glimmer excluded keyword: #{method_symbol}"
54
- end
55
- Glimmer::Config.logger&.debug "Interpreting keyword: #{method_symbol}"
50
+ is_excluded = Config.excluded_keyword_checkers.reduce(false) {|result, checker| result || instance_exec(method_symbol, *args, &checker) }
51
+ raise ExcludedKeywordError, "Glimmer excluded keyword: #{method_symbol}" if is_excluded
52
+ Glimmer::Config.logger.info {"Interpreting keyword: #{method_symbol}"}
56
53
  Glimmer::DSL::Engine.interpret(method_symbol, *args, &block)
57
54
  rescue ExcludedKeywordError => e
58
55
  # TODO add a feature to show excluded keywords optionally for debugging purposes
59
56
  super(method_symbol, *args, &block)
60
57
  rescue InvalidKeywordError => e
61
- if !method_symbol.to_s.match(REGEX_METHODS_EXCLUDED)
62
- Glimmer::Config.logger&.error e.message
63
- end
64
- Glimmer::Config.logger&.debug "#{e.message}\n#{e.backtrace.join("\n")}"
58
+ Glimmer::Config.logger.error {"Encountered an invalid keyword at this object: #{self}"}
59
+ Glimmer::Config.logger.error {e.full_message}
65
60
  super(method_symbol, *args, &block)
66
61
  end
67
62
  end
@@ -2,29 +2,46 @@ module Glimmer
2
2
  module Config
3
3
  class << self
4
4
  LOOP_MAX_COUNT_DEFAULT = 100
5
+ REGEX_METHODS_EXCLUDED = /^(to_|\[)/
5
6
 
6
7
  attr_writer :loop_max_count
7
8
 
9
+ def excluded_keyword_checkers
10
+ @excluded_keyword_checkers ||= reset_excluded_keyword_checkers!
11
+ end
12
+
13
+ def excluded_keyword_checkers=(checkers)
14
+ @excluded_keyword_checkers = checkers
15
+ end
16
+
17
+ def reset_excluded_keyword_checkers!
18
+ @excluded_keyword_checkers = [ lambda { |method_symbol, *args| method_symbol.to_s.match(REGEX_METHODS_EXCLUDED) } ]
19
+ end
20
+
8
21
  def loop_max_count
9
22
  @loop_max_count ||= LOOP_MAX_COUNT_DEFAULT
10
23
  end
11
24
 
12
25
  # Returns Glimmer logger (standard Ruby logger)
13
- def logger
14
- # unless defined? @@logger
15
- # @@logger = Logger.new(STDOUT).tap {|logger| logger.level = Logger::WARN}
16
- # end
17
- @@logger if defined? @@logger
26
+ def logger
27
+ reset_logger! unless defined? @@logger
28
+ @@logger
29
+ end
30
+
31
+ def logger=(custom_logger)
32
+ @@logger = custom_logger
18
33
  end
19
34
 
20
- def enable_logging
21
- @@logger = Logger.new(STDOUT).tap {|logger| logger.level = Logger::WARN}
35
+ def reset_logger!
36
+ self.logger = Logger.new(STDOUT).tap do |logger|
37
+ logger.level = Logger::ERROR
38
+ begin
39
+ logger.level = ENV['GLIMMER_LOGGER_LEVEL'].strip.downcase if ENV['GLIMMER_LOGGER_LEVEL']
40
+ rescue => e
41
+ puts e.message
42
+ end
43
+ end
22
44
  end
23
45
  end
24
46
  end
25
47
  end
26
-
27
- if ENV['GLIMMER_LOGGER_LEVEL']
28
- Glimmer::Config.enable_logging
29
- Glimmer::Config.logger.level = ENV['GLIMMER_LOGGER_LEVEL'].downcase
30
- end
@@ -1,77 +1,280 @@
1
1
  require 'set'
2
+ require 'array_include_methods'
2
3
 
3
4
  require 'glimmer/data_binding/observable'
4
5
 
6
+ using ArrayIncludeMethods
7
+
5
8
  module Glimmer
6
9
  module DataBinding
7
10
  # TODO prefix utility methods with double-underscore
8
11
  module ObservableArray
9
12
  include Observable
10
13
 
11
- def add_observer(observer, element_properties=nil)
12
- return observer if has_observer?(observer) && element_properties.nil?
14
+ def add_observer(observer, *element_properties)
15
+ element_properties = element_properties.flatten.compact.uniq
16
+ return observer if has_observer?(observer) && has_observer_element_properties?(observer, element_properties)
13
17
  property_observer_list << observer
14
- [element_properties].flatten.compact.each do |property|
15
- each do |element|
16
- observer.observe(element, property)
17
- end
18
- end
18
+ observer_element_properties[observer] = element_properties_for(observer) + Set.new(element_properties)
19
+ each { |element| add_element_observer(element, observer) }
19
20
  observer
20
21
  end
22
+
23
+ def add_element_observers(element)
24
+ property_observer_list.each do |observer|
25
+ add_element_observer(element, observer)
26
+ end
27
+ end
21
28
 
22
- def remove_observer(observer, element_properties=nil)
23
- property_observer_list.delete(observer)
24
- [element_properties].flatten.compact.each do |property|
29
+ def add_element_observer(element, observer)
30
+ element_properties_for(observer).each do |property|
31
+ observer.observe(element, property)
32
+ end
33
+ end
34
+
35
+ def remove_observer(observer, *element_properties)
36
+ element_properties = element_properties.flatten.compact.uniq
37
+ if !element_properties.empty?
38
+ old_element_properties = element_properties_for(observer)
39
+ observer_element_properties[observer] = element_properties_for(observer) - Set.new(element_properties)
25
40
  each do |element|
26
- observer.unobserve(element, property)
41
+ element_properties.each do |property|
42
+ observer.unobserve(element, property)
43
+ end
27
44
  end
28
45
  end
46
+ if element_properties_for(observer).empty?
47
+ property_observer_list.delete(observer)
48
+ observer_element_properties.delete(observer)
49
+ each { |element| remove_element_observer(element, observer) }
50
+ end
29
51
  observer
30
52
  end
31
53
 
54
+ def remove_element_observers(element)
55
+ property_observer_list.each do |observer|
56
+ remove_element_observer(element, observer)
57
+ end
58
+ end
59
+
60
+ def remove_element_observer(element, observer)
61
+ element_properties_for(observer).each do |property|
62
+ observer.unobserve(element, property)
63
+ end
64
+ end
65
+
32
66
  def has_observer?(observer)
33
67
  property_observer_list.include?(observer)
34
68
  end
69
+
70
+ def has_observer_element_properties?(observer, element_properties)
71
+ element_properties_for(observer).to_a.include_all?(element_properties)
72
+ end
35
73
 
36
74
  def property_observer_list
37
75
  @property_observer_list ||= Set.new
38
76
  end
39
77
 
78
+ def observer_element_properties
79
+ @observer_element_properties ||= {}
80
+ end
81
+
82
+ def element_properties_for(observer)
83
+ observer_element_properties[observer] ||= Set.new
84
+ end
85
+
40
86
  def notify_observers
41
- property_observer_list.each {|observer| observer.call}
87
+ property_observer_list.to_a.each(&:call)
42
88
  end
43
89
 
44
90
  def <<(element)
45
- super(element)
46
- notify_observers
91
+ super(element).tap do
92
+ add_element_observers(element)
93
+ notify_observers
94
+ end
47
95
  end
96
+ alias push <<
48
97
 
49
98
  def []=(index, value)
50
99
  old_value = self[index]
51
100
  unregister_dependent_observers(old_value)
52
- super(index, value)
53
- notify_observers
101
+ remove_element_observers(old_value)
102
+ add_element_observers(value)
103
+ super(index, value).tap do
104
+ notify_observers
105
+ end
106
+ end
107
+
108
+ def pop
109
+ popped_element = last
110
+ unregister_dependent_observers(popped_element)
111
+ remove_element_observers(popped_element)
112
+ super.tap do
113
+ notify_observers
114
+ end
54
115
  end
55
116
 
56
117
  def delete(element)
57
118
  unregister_dependent_observers(element)
58
- super(element)
59
- notify_observers
119
+ remove_element_observers(element)
120
+ super(element).tap do
121
+ notify_observers
122
+ end
60
123
  end
61
124
 
62
125
  def delete_at(index)
63
126
  old_value = self[index]
64
127
  unregister_dependent_observers(old_value)
65
- super(index)
66
- notify_observers
128
+ remove_element_observers(old_value)
129
+ super(index).tap do
130
+ notify_observers
131
+ end
132
+ end
133
+
134
+ def delete_if(&block)
135
+ if block_given?
136
+ old_array = Array.new(self)
137
+ super(&block).tap do |new_array|
138
+ (old_array - new_array).each do |element|
139
+ unregister_dependent_observers(element)
140
+ remove_element_observers(element)
141
+ end
142
+ notify_observers
143
+ end
144
+ else
145
+ super
146
+ end
67
147
  end
68
148
 
69
149
  def clear
70
150
  each do |old_value|
71
151
  unregister_dependent_observers(old_value)
152
+ remove_element_observers(old_value)
153
+ end
154
+ super.tap do
155
+ notify_observers
156
+ end
157
+ end
158
+
159
+ def reverse!
160
+ super.tap do
161
+ notify_observers
162
+ end
163
+ end
164
+
165
+ def collect!(&block)
166
+ if block_given?
167
+ each do |old_value|
168
+ unregister_dependent_observers(old_value)
169
+ remove_element_observers(old_value)
170
+ end
171
+ super(&block).tap do
172
+ each do |element|
173
+ add_element_observers(element)
174
+ end
175
+ notify_observers
176
+ end
177
+ else
178
+ super
179
+ end
180
+ end
181
+ alias map! collect!
182
+
183
+ def compact!
184
+ super.tap do
185
+ notify_observers
186
+ end
187
+ end
188
+
189
+ def flatten!(level=nil)
190
+ each do |old_value|
191
+ unregister_dependent_observers(old_value)
192
+ remove_element_observers(old_value)
193
+ end
194
+ (level.nil? ? super() : super(level)).tap do
195
+ each do |element|
196
+ add_element_observers(element)
197
+ end
198
+ notify_observers
199
+ end
200
+ end
201
+
202
+ def rotate!(count=1)
203
+ super(count).tap do
204
+ notify_observers
205
+ end
206
+ end
207
+
208
+ def select!(&block)
209
+ if block_given?
210
+ old_array = Array.new(self)
211
+ super(&block).tap do
212
+ (old_array - self).each do |old_value|
213
+ unregister_dependent_observers(old_value)
214
+ remove_element_observers(old_value)
215
+ end
216
+ notify_observers
217
+ end
218
+ else
219
+ super
220
+ end
221
+ end
222
+
223
+ def shuffle!(hash = nil)
224
+ (hash.nil? ? super() : super(random: hash[:random])).tap do
225
+ notify_observers
226
+ end
227
+ end
228
+
229
+ def slice!(arg1, arg2=nil)
230
+ old_array = Array.new(self)
231
+ (arg2.nil? ? super(arg1) : super(arg1, arg2)).tap do
232
+ (old_array - self).each do |old_value|
233
+ unregister_dependent_observers(old_value)
234
+ remove_element_observers(old_value)
235
+ end
236
+ notify_observers
237
+ end
238
+ end
239
+
240
+ def sort!(&block)
241
+ (block.nil? ? super() : super(&block)).tap do
242
+ notify_observers
243
+ end
244
+ end
245
+
246
+ def sort_by!(&block)
247
+ (block.nil? ? super() : super(&block)).tap do
248
+ notify_observers
249
+ end
250
+ end
251
+
252
+ def uniq!(&block)
253
+ each do |old_value|
254
+ unregister_dependent_observers(old_value)
255
+ remove_element_observers(old_value)
256
+ end
257
+ (block.nil? ? super() : super(&block)).tap do
258
+ each do |element|
259
+ add_element_observers(element)
260
+ end
261
+ notify_observers
262
+ end
263
+ end
264
+
265
+ def reject!(&block)
266
+ if block_given?
267
+ old_array = Array.new(self)
268
+ super(&block).tap do
269
+ (old_array - self).each do |old_value|
270
+ unregister_dependent_observers(old_value)
271
+ remove_element_observers(old_value)
272
+ end
273
+ notify_observers
274
+ end
275
+ else
276
+ super
72
277
  end
73
- super()
74
- notify_observers
75
278
  end
76
279
 
77
280
  def unregister_dependent_observers(old_value)
@@ -34,7 +34,7 @@ module Glimmer
34
34
  end
35
35
 
36
36
  def has_observer_for_any_property?(observer)
37
- property_observer_hash.values.map(&:to_a).sum.include?(observer)
37
+ property_observer_hash.values.map(&:to_a).reduce(:+).include?(observer)
38
38
  end
39
39
 
40
40
  def property_observer_hash
@@ -47,7 +47,9 @@ module Glimmer
47
47
  end
48
48
 
49
49
  def notify_observers(property_name)
50
- property_observer_list(property_name).each {|observer| observer.call(send(property_name))}
50
+ property_observer_list(property_name).to_a.each do |observer|
51
+ observer.call(send(property_name))
52
+ end
51
53
  end
52
54
  #TODO upon updating values, make sure dependent observers are cleared (not added as dependents here)
53
55
 
@@ -70,7 +72,7 @@ module Glimmer
70
72
  end
71
73
  rescue => e
72
74
  #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")}"
75
+ Glimmer::Config.logger.debug {"No need to observe property writer: #{property_writer_name}\n#{e.message}\n#{e.backtrace.join("\n")}"}
74
76
  end
75
77
 
76
78
  def unregister_dependent_observers(property_name, old_value)
@@ -59,6 +59,7 @@ module Glimmer
59
59
  # registers observer in an observable on a property (optional)
60
60
  # observer maintains registration list to unregister later
61
61
  def register(observable, property = nil)
62
+ return if observable.nil?
62
63
  unless observable.is_a?(Observable)
63
64
  # TODO refactor code to be more smart/polymorphic/automated and honor open/closed principle
64
65
  if observable.is_a?(Array)
@@ -75,6 +76,7 @@ module Glimmer
75
76
  alias observe register
76
77
 
77
78
  def unregister(observable, property = nil)
79
+ return unless observable.is_a?(Observable)
78
80
  # TODO optimize performance in the future via indexing and/or making a registration official object/class
79
81
  observable.remove_observer(*[self, property].compact)
80
82
  registration = registration_for(observable, property)
@@ -90,16 +92,12 @@ module Glimmer
90
92
  thedependents = dependents_for(registration).select do |thedependent|
91
93
  thedependent.observable == dependent_observable
92
94
  end
93
- thedependents.each do |thedependent|
94
- thedependent.unregister
95
- end
95
+ thedependents.each(&:unregister)
96
96
  end
97
97
 
98
98
  # cleans up all registrations in observables
99
99
  def unregister_all_observables
100
- registrations.each do |registration|
101
- registration.unregister
102
- end
100
+ registrations.each(&:unregister)
103
101
  end
104
102
  alias unobserve_all_observables unregister_all_observables
105
103