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.
- checksums.yaml +4 -4
- data/README.md +866 -201
- data/VERSION +1 -1
- data/lib/glimmer.rb +40 -7
- data/lib/glimmer/config.rb +18 -7
- data/lib/glimmer/data_binding/model_binding.rb +248 -0
- data/lib/glimmer/data_binding/observable.rb +21 -0
- data/lib/glimmer/data_binding/observable_array.rb +86 -0
- data/lib/glimmer/data_binding/observable_model.rb +104 -0
- data/lib/glimmer/data_binding/observer.rb +127 -0
- data/lib/glimmer/dsl/engine.rb +27 -19
- data/lib/glimmer/dsl/expression_handler.rb +3 -3
- data/lib/glimmer/excluded_keyword_error.rb +5 -0
- metadata +50 -2
@@ -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'
|
data/lib/glimmer/dsl/engine.rb
CHANGED
@@ -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
|
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
|
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
|
-
|
102
|
-
|
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
|
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
|
-
|
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
|
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
|
-
|
175
|
-
|
176
|
-
|
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
|
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
|
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
|
37
|
+
# Glimmer::Config.logger.error {message}
|
38
38
|
raise InvalidKeywordError, message
|
39
39
|
end
|
40
40
|
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.
|
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-
|
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:
|