glimmer 0.10.1 → 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +365 -0
- data/CONTRIBUTING.md +62 -0
- data/LICENSE.txt +1 -1
- data/PROCESS.md +35 -0
- data/README.md +485 -2871
- data/VERSION +1 -1
- data/glimmer.gemspec +81 -0
- data/lib/glimmer.rb +37 -19
- data/lib/glimmer/config.rb +31 -9
- data/lib/glimmer/data_binding/model_binding.rb +37 -27
- data/lib/glimmer/data_binding/observable.rb +21 -0
- data/lib/glimmer/data_binding/observable_array.rb +228 -29
- data/lib/glimmer/data_binding/observable_model.rb +43 -22
- data/lib/glimmer/data_binding/observer.rb +22 -0
- data/lib/glimmer/dsl/engine.rb +66 -58
- data/lib/glimmer/dsl/expression.rb +21 -0
- data/lib/glimmer/dsl/expression_handler.rb +21 -0
- data/lib/glimmer/dsl/parent_expression.rb +21 -0
- data/lib/glimmer/dsl/static_expression.rb +21 -0
- data/lib/glimmer/dsl/top_level_expression.rb +21 -0
- data/lib/glimmer/error.rb +21 -0
- data/lib/glimmer/invalid_keyword_error.rb +21 -0
- metadata +47 -20
- data/lib/glimmer/excluded_keyword_error.rb +0 -5
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
1.0.1
|
data/glimmer.gemspec
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
# stub: glimmer 1.0.1 ruby lib
|
6
|
+
|
7
|
+
Gem::Specification.new do |s|
|
8
|
+
s.name = "glimmer".freeze
|
9
|
+
s.version = "1.0.1"
|
10
|
+
|
11
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
|
12
|
+
s.require_paths = ["lib".freeze]
|
13
|
+
s.authors = ["AndyMaleh".freeze]
|
14
|
+
s.date = "2020-10-04"
|
15
|
+
s.description = "Glimmer is a Ruby DSL engine with support for the Glimmer DSL for SWT (JRuby Desktop Development GUI Library), the Glimmer DSL for Tk (Ruby Desktop Development GUI Library), the Glimmer DSL for Opal (Web GUI Adapter for Desktop Apps), the Glimmer DSL for XML (& HTML), and the Glimmer DSL for CSS.".freeze
|
16
|
+
s.email = "andy.am@gmail.com".freeze
|
17
|
+
s.extra_rdoc_files = [
|
18
|
+
"CHANGELOG.md",
|
19
|
+
"LICENSE.txt",
|
20
|
+
"README.md"
|
21
|
+
]
|
22
|
+
s.files = [
|
23
|
+
"CHANGELOG.md",
|
24
|
+
"CONTRIBUTING.md",
|
25
|
+
"LICENSE.txt",
|
26
|
+
"PROCESS.md",
|
27
|
+
"README.md",
|
28
|
+
"VERSION",
|
29
|
+
"glimmer.gemspec",
|
30
|
+
"lib/glimmer.rb",
|
31
|
+
"lib/glimmer/config.rb",
|
32
|
+
"lib/glimmer/data_binding/model_binding.rb",
|
33
|
+
"lib/glimmer/data_binding/observable.rb",
|
34
|
+
"lib/glimmer/data_binding/observable_array.rb",
|
35
|
+
"lib/glimmer/data_binding/observable_model.rb",
|
36
|
+
"lib/glimmer/data_binding/observer.rb",
|
37
|
+
"lib/glimmer/dsl/engine.rb",
|
38
|
+
"lib/glimmer/dsl/expression.rb",
|
39
|
+
"lib/glimmer/dsl/expression_handler.rb",
|
40
|
+
"lib/glimmer/dsl/parent_expression.rb",
|
41
|
+
"lib/glimmer/dsl/static_expression.rb",
|
42
|
+
"lib/glimmer/dsl/top_level_expression.rb",
|
43
|
+
"lib/glimmer/error.rb",
|
44
|
+
"lib/glimmer/invalid_keyword_error.rb"
|
45
|
+
]
|
46
|
+
s.homepage = "http://github.com/AndyObtiva/glimmer".freeze
|
47
|
+
s.licenses = ["MIT".freeze]
|
48
|
+
s.rubygems_version = "3.1.4".freeze
|
49
|
+
s.summary = "Glimmer Ruby DSL Engine".freeze
|
50
|
+
|
51
|
+
if s.respond_to? :specification_version then
|
52
|
+
s.specification_version = 4
|
53
|
+
end
|
54
|
+
|
55
|
+
if s.respond_to? :add_runtime_dependency then
|
56
|
+
s.add_runtime_dependency(%q<array_include_methods>.freeze, [">= 1.0.3", "< 2.0.0"])
|
57
|
+
s.add_runtime_dependency(%q<facets>.freeze, [">= 3.1.0", "< 4.0.0"])
|
58
|
+
s.add_development_dependency(%q<rspec-mocks>.freeze, ["~> 3.5.0"])
|
59
|
+
s.add_development_dependency(%q<rspec>.freeze, ["~> 3.5.0"])
|
60
|
+
s.add_development_dependency(%q<puts_debuggerer>.freeze, ["~> 0.10.0"])
|
61
|
+
s.add_development_dependency(%q<rake>.freeze, [">= 10.1.0", "< 14.0.0"])
|
62
|
+
s.add_development_dependency(%q<jeweler>.freeze, [">= 2.0.0", "< 3.0.0"])
|
63
|
+
s.add_development_dependency(%q<rdoc>.freeze, [">= 6.2.1", "< 7.0.0"])
|
64
|
+
s.add_development_dependency(%q<coveralls>.freeze, ["= 0.8.23"])
|
65
|
+
s.add_development_dependency(%q<simplecov>.freeze, ["~> 0.16.1"])
|
66
|
+
s.add_development_dependency(%q<simplecov-lcov>.freeze, ["~> 0.7.0"])
|
67
|
+
else
|
68
|
+
s.add_dependency(%q<array_include_methods>.freeze, [">= 1.0.3", "< 2.0.0"])
|
69
|
+
s.add_dependency(%q<facets>.freeze, [">= 3.1.0", "< 4.0.0"])
|
70
|
+
s.add_dependency(%q<rspec-mocks>.freeze, ["~> 3.5.0"])
|
71
|
+
s.add_dependency(%q<rspec>.freeze, ["~> 3.5.0"])
|
72
|
+
s.add_dependency(%q<puts_debuggerer>.freeze, ["~> 0.10.0"])
|
73
|
+
s.add_dependency(%q<rake>.freeze, [">= 10.1.0", "< 14.0.0"])
|
74
|
+
s.add_dependency(%q<jeweler>.freeze, [">= 2.0.0", "< 3.0.0"])
|
75
|
+
s.add_dependency(%q<rdoc>.freeze, [">= 6.2.1", "< 7.0.0"])
|
76
|
+
s.add_dependency(%q<coveralls>.freeze, ["= 0.8.23"])
|
77
|
+
s.add_dependency(%q<simplecov>.freeze, ["~> 0.16.1"])
|
78
|
+
s.add_dependency(%q<simplecov-lcov>.freeze, ["~> 0.7.0"])
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
data/lib/glimmer.rb
CHANGED
@@ -1,9 +1,27 @@
|
|
1
|
-
#
|
2
|
-
#
|
3
|
-
#
|
4
|
-
#
|
1
|
+
# Copyright (c) 2007-2020 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
|
+
|
5
22
|
require 'logger'
|
6
23
|
require 'set'
|
24
|
+
require 'array_include_methods'
|
7
25
|
|
8
26
|
$LOAD_PATH.unshift(File.expand_path('..', __FILE__))
|
9
27
|
|
@@ -37,22 +55,23 @@ module Glimmer
|
|
37
55
|
end
|
38
56
|
|
39
57
|
def method_missing(method_symbol, *args, &block)
|
40
|
-
new_loop_data = [method_symbol, args, block]
|
41
|
-
if new_loop_data == Glimmer.loop_last_data
|
42
|
-
Glimmer.loop_increment!
|
43
|
-
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
|
44
|
-
else
|
45
|
-
Glimmer.loop_reset!
|
46
|
-
end
|
47
|
-
Glimmer.loop_last_data = new_loop_data
|
48
58
|
# This if statement speeds up Glimmer in girb or whenever directly including on main object
|
49
59
|
is_excluded = Config.excluded_keyword_checkers.reduce(false) {|result, checker| result || instance_exec(method_symbol, *args, &checker) }
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
60
|
+
if is_excluded
|
61
|
+
Glimmer::Config.logger.debug "Glimmer excluded keyword: #{method_symbol}" if Glimmer::Config.log_excluded_keywords?
|
62
|
+
super(method_symbol, *args, &block)
|
63
|
+
else
|
64
|
+
new_loop_data = [method_symbol, args, block]
|
65
|
+
if new_loop_data == Glimmer.loop_last_data
|
66
|
+
Glimmer.loop_increment!
|
67
|
+
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
|
68
|
+
else
|
69
|
+
Glimmer.loop_reset!
|
70
|
+
end
|
71
|
+
Glimmer.loop_last_data = new_loop_data
|
72
|
+
Glimmer::Config.logger.info {"Interpreting keyword: #{method_symbol}"}
|
73
|
+
Glimmer::DSL::Engine.interpret(method_symbol, *args, &block)
|
74
|
+
end
|
56
75
|
rescue InvalidKeywordError => e
|
57
76
|
Glimmer::Config.logger.error {"Encountered an invalid keyword at this object: #{self}"}
|
58
77
|
Glimmer::Config.logger.error {e.full_message}
|
@@ -61,6 +80,5 @@ module Glimmer
|
|
61
80
|
end
|
62
81
|
|
63
82
|
require 'glimmer/error'
|
64
|
-
require 'glimmer/excluded_keyword_error'
|
65
83
|
require 'glimmer/invalid_keyword_error'
|
66
84
|
require 'glimmer/dsl/engine'
|
data/lib/glimmer/config.rb
CHANGED
@@ -1,10 +1,33 @@
|
|
1
|
+
# Copyright (c) 2007-2020 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
|
+
|
1
22
|
module Glimmer
|
2
23
|
module Config
|
3
24
|
class << self
|
4
25
|
LOOP_MAX_COUNT_DEFAULT = 100
|
5
|
-
REGEX_METHODS_EXCLUDED = /^(to_|\[)/
|
26
|
+
REGEX_METHODS_EXCLUDED = /^(to_|\[|load_iseq)/
|
6
27
|
|
7
28
|
attr_writer :loop_max_count
|
29
|
+
attr_accessor :log_excluded_keywords
|
30
|
+
alias log_excluded_keywords? log_excluded_keywords
|
8
31
|
|
9
32
|
def excluded_keyword_checkers
|
10
33
|
@excluded_keyword_checkers ||= reset_excluded_keyword_checkers!
|
@@ -15,9 +38,7 @@ module Glimmer
|
|
15
38
|
end
|
16
39
|
|
17
40
|
def reset_excluded_keyword_checkers!
|
18
|
-
@excluded_keyword_checkers = [
|
19
|
-
lambda { |method_symbol, *args| method_symbol.to_s.match(REGEX_METHODS_EXCLUDED) }
|
20
|
-
]
|
41
|
+
@excluded_keyword_checkers = [ lambda { |method_symbol, *args| method_symbol.to_s.match(REGEX_METHODS_EXCLUDED) } ]
|
21
42
|
end
|
22
43
|
|
23
44
|
def loop_max_count
|
@@ -25,7 +46,7 @@ module Glimmer
|
|
25
46
|
end
|
26
47
|
|
27
48
|
# Returns Glimmer logger (standard Ruby logger)
|
28
|
-
def logger
|
49
|
+
def logger
|
29
50
|
reset_logger! unless defined? @@logger
|
30
51
|
@@logger
|
31
52
|
end
|
@@ -37,12 +58,13 @@ module Glimmer
|
|
37
58
|
def reset_logger!
|
38
59
|
self.logger = Logger.new(STDOUT).tap do |logger|
|
39
60
|
logger.level = Logger::ERROR
|
61
|
+
begin
|
62
|
+
logger.level = ENV['GLIMMER_LOGGER_LEVEL'].strip.downcase if ENV['GLIMMER_LOGGER_LEVEL']
|
63
|
+
rescue => e
|
64
|
+
puts e.message
|
65
|
+
end
|
40
66
|
end
|
41
67
|
end
|
42
68
|
end
|
43
69
|
end
|
44
70
|
end
|
45
|
-
|
46
|
-
if ENV['GLIMMER_LOGGER_LEVEL']
|
47
|
-
Glimmer::Config.logger.level = ENV['GLIMMER_LOGGER_LEVEL'].downcase
|
48
|
-
end
|
@@ -1,3 +1,24 @@
|
|
1
|
+
# Copyright (c) 2007-2020 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
|
+
|
1
22
|
require 'glimmer/data_binding/observable'
|
2
23
|
require 'glimmer/data_binding/observer'
|
3
24
|
|
@@ -7,7 +28,7 @@ module Glimmer
|
|
7
28
|
include Observable
|
8
29
|
include Observer
|
9
30
|
|
10
|
-
attr_reader :binding_options
|
31
|
+
attr_reader :binding_options, :property_name_expression
|
11
32
|
|
12
33
|
def initialize(base_model, property_name_expression, binding_options = nil)
|
13
34
|
@base_model = base_model
|
@@ -60,19 +81,10 @@ module Glimmer
|
|
60
81
|
end
|
61
82
|
|
62
83
|
def apply_converter(converter, value)
|
63
|
-
if converter.nil?
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
value.send(converter)
|
68
|
-
else
|
69
|
-
raise Glimmer::Error, "Unsupported bind converter: #{converter.inspect}"
|
70
|
-
end
|
71
|
-
elsif converter.respond_to?(:call, value)
|
72
|
-
converter.call(value)
|
73
|
-
else
|
74
|
-
raise Glimmer::Error, "Unsupported bind converter: #{converter.inspect}"
|
75
|
-
end
|
84
|
+
return value if converter.nil?
|
85
|
+
return value.send(converter) if (converter.is_a?(String) || converter.is_a?(Symbol)) && value.respond_to?(converter)
|
86
|
+
return converter.call(value) if converter.respond_to?(:call, value)
|
87
|
+
raise Glimmer::Error, "Unsupported bind converter: #{converter.inspect}"
|
76
88
|
end
|
77
89
|
|
78
90
|
# All nested property names
|
@@ -99,10 +111,6 @@ module Glimmer
|
|
99
111
|
property_name_expression.match(/[.\[]/)
|
100
112
|
end
|
101
113
|
|
102
|
-
def property_name_expression
|
103
|
-
@property_name_expression
|
104
|
-
end
|
105
|
-
|
106
114
|
def computed?
|
107
115
|
!computed_by.empty?
|
108
116
|
end
|
@@ -179,17 +187,19 @@ module Glimmer
|
|
179
187
|
model, property_name = zip
|
180
188
|
nested_property_observer = nested_property_observers[property_name]
|
181
189
|
previous_index = i - 1
|
182
|
-
|
183
|
-
|
184
|
-
|
190
|
+
if previous_index.negative?
|
191
|
+
parent_model = self
|
192
|
+
parent_property_name = nil
|
193
|
+
parent_observer = observer
|
194
|
+
else
|
195
|
+
parent_model = nested_models[previous_index]
|
196
|
+
parent_property_name = nested_property_names[previous_index]
|
197
|
+
parent_observer = nested_property_observers[parent_property_name]
|
198
|
+
end
|
185
199
|
parent_property_name = nil if parent_property_name.to_s.start_with?('[')
|
186
200
|
unless model.nil?
|
187
|
-
|
188
|
-
|
189
|
-
observer_registration = nested_property_observer.observe(model)
|
190
|
-
else
|
191
|
-
observer_registration = nested_property_observer.observe(model, property_name)
|
192
|
-
end
|
201
|
+
# TODO figure out a way to deal with this more uniformly
|
202
|
+
observer_registration = property_indexed?(property_name) ? nested_property_observer.observe(model) : nested_property_observer.observe(model, property_name)
|
193
203
|
parent_registration = parent_observer.registration_for(parent_model, parent_property_name)
|
194
204
|
parent_observer.add_dependent(parent_registration => observer_registration)
|
195
205
|
end
|
@@ -1,3 +1,24 @@
|
|
1
|
+
# Copyright (c) 2007-2020 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
|
+
|
1
22
|
require 'glimmer/error'
|
2
23
|
|
3
24
|
module Glimmer
|
@@ -1,85 +1,284 @@
|
|
1
|
-
|
1
|
+
# Copyright (c) 2007-2020 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.
|
2
21
|
|
22
|
+
require 'set'
|
3
23
|
require 'glimmer/data_binding/observable'
|
24
|
+
require 'array_include_methods'
|
25
|
+
|
26
|
+
using ArrayIncludeMethods
|
4
27
|
|
5
28
|
module Glimmer
|
6
29
|
module DataBinding
|
7
|
-
# TODO prefix utility methods with double-underscore
|
8
30
|
module ObservableArray
|
9
31
|
include Observable
|
10
32
|
|
11
|
-
def add_observer(observer, element_properties
|
12
|
-
|
33
|
+
def add_observer(observer, *element_properties)
|
34
|
+
element_properties = element_properties.flatten.compact.uniq
|
35
|
+
return observer if has_observer?(observer) && has_observer_element_properties?(observer, element_properties)
|
13
36
|
property_observer_list << observer
|
14
|
-
[
|
15
|
-
|
16
|
-
observer.observe(element, property)
|
17
|
-
end
|
18
|
-
end
|
37
|
+
observer_element_properties[observer] = element_properties_for(observer) + Set.new(element_properties)
|
38
|
+
each { |element| add_element_observer(element, observer) }
|
19
39
|
observer
|
20
40
|
end
|
41
|
+
|
42
|
+
def add_element_observers(element)
|
43
|
+
property_observer_list.each do |observer|
|
44
|
+
add_element_observer(element, observer)
|
45
|
+
end
|
46
|
+
end
|
21
47
|
|
22
|
-
def
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
48
|
+
def add_element_observer(element, observer)
|
49
|
+
element_properties_for(observer).each do |property|
|
50
|
+
observer.observe(element, property)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def remove_observer(observer, *element_properties)
|
55
|
+
element_properties = element_properties.flatten.compact.uniq
|
56
|
+
if !element_properties.empty?
|
57
|
+
old_element_properties = element_properties_for(observer)
|
58
|
+
observer_element_properties[observer] = element_properties_for(observer) - Set.new(element_properties)
|
59
|
+
each { |element| element_properties.each { |property| observer.unobserve(element, property) } }
|
28
60
|
end
|
61
|
+
if element_properties_for(observer).empty?
|
62
|
+
property_observer_list.delete(observer)
|
63
|
+
observer_element_properties.delete(observer)
|
64
|
+
each { |element| remove_element_observer(element, observer) }
|
65
|
+
end
|
29
66
|
observer
|
30
67
|
end
|
31
68
|
|
69
|
+
def remove_element_observers(element)
|
70
|
+
property_observer_list.each do |observer|
|
71
|
+
remove_element_observer(element, observer)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def remove_element_observer(element, observer)
|
76
|
+
element_properties_for(observer).each do |property|
|
77
|
+
observer.unobserve(element, property)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
32
81
|
def has_observer?(observer)
|
33
82
|
property_observer_list.include?(observer)
|
34
83
|
end
|
84
|
+
|
85
|
+
def has_observer_element_properties?(observer, element_properties)
|
86
|
+
element_properties_for(observer).to_a.include_all?(element_properties)
|
87
|
+
end
|
35
88
|
|
36
89
|
def property_observer_list
|
37
90
|
@property_observer_list ||= Set.new
|
38
91
|
end
|
39
92
|
|
93
|
+
def observer_element_properties
|
94
|
+
@observer_element_properties ||= {}
|
95
|
+
end
|
96
|
+
|
97
|
+
def element_properties_for(observer)
|
98
|
+
observer_element_properties[observer] ||= Set.new
|
99
|
+
end
|
100
|
+
|
40
101
|
def notify_observers
|
41
102
|
property_observer_list.to_a.each(&:call)
|
42
103
|
end
|
43
104
|
|
44
105
|
def <<(element)
|
45
|
-
super(element)
|
46
|
-
|
106
|
+
super(element).tap do
|
107
|
+
add_element_observers(element)
|
108
|
+
notify_observers
|
109
|
+
end
|
47
110
|
end
|
111
|
+
alias push <<
|
48
112
|
|
49
113
|
def []=(index, value)
|
50
114
|
old_value = self[index]
|
51
115
|
unregister_dependent_observers(old_value)
|
52
|
-
|
53
|
-
|
116
|
+
remove_element_observers(old_value)
|
117
|
+
add_element_observers(value)
|
118
|
+
super(index, value).tap do
|
119
|
+
notify_observers
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def pop
|
124
|
+
popped_element = last
|
125
|
+
unregister_dependent_observers(popped_element)
|
126
|
+
remove_element_observers(popped_element)
|
127
|
+
super.tap do
|
128
|
+
notify_observers
|
129
|
+
end
|
54
130
|
end
|
55
131
|
|
56
132
|
def delete(element)
|
57
133
|
unregister_dependent_observers(element)
|
58
|
-
|
59
|
-
|
134
|
+
remove_element_observers(element)
|
135
|
+
super(element).tap do
|
136
|
+
notify_observers
|
137
|
+
end
|
60
138
|
end
|
61
139
|
|
62
140
|
def delete_at(index)
|
63
141
|
old_value = self[index]
|
64
142
|
unregister_dependent_observers(old_value)
|
65
|
-
|
66
|
-
|
143
|
+
remove_element_observers(old_value)
|
144
|
+
super(index).tap do
|
145
|
+
notify_observers
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def delete_if(&block)
|
150
|
+
if block_given?
|
151
|
+
old_array = Array.new(self)
|
152
|
+
super(&block).tap do |new_array|
|
153
|
+
(old_array - new_array).each do |element|
|
154
|
+
unregister_dependent_observers(element)
|
155
|
+
remove_element_observers(element)
|
156
|
+
end
|
157
|
+
notify_observers
|
158
|
+
end
|
159
|
+
else
|
160
|
+
super
|
161
|
+
end
|
67
162
|
end
|
68
163
|
|
69
164
|
def clear
|
70
165
|
each do |old_value|
|
71
166
|
unregister_dependent_observers(old_value)
|
167
|
+
remove_element_observers(old_value)
|
168
|
+
end
|
169
|
+
super.tap do
|
170
|
+
notify_observers
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
def reverse!
|
175
|
+
super.tap do
|
176
|
+
notify_observers
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
def collect!(&block)
|
181
|
+
if block_given?
|
182
|
+
each do |old_value|
|
183
|
+
unregister_dependent_observers(old_value)
|
184
|
+
remove_element_observers(old_value)
|
185
|
+
end
|
186
|
+
super(&block).tap do
|
187
|
+
each { |element| add_element_observers(element) }
|
188
|
+
notify_observers
|
189
|
+
end
|
190
|
+
else
|
191
|
+
super
|
192
|
+
end
|
193
|
+
end
|
194
|
+
alias map! collect!
|
195
|
+
|
196
|
+
def compact!
|
197
|
+
super.tap { notify_observers }
|
198
|
+
end
|
199
|
+
|
200
|
+
def flatten!(level=nil)
|
201
|
+
each do |old_value|
|
202
|
+
unregister_dependent_observers(old_value)
|
203
|
+
remove_element_observers(old_value)
|
204
|
+
end
|
205
|
+
(level.nil? ? super() : super(level)).tap do
|
206
|
+
each { |element| add_element_observers(element) }
|
207
|
+
notify_observers
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
def rotate!(count=1)
|
212
|
+
super(count).tap { notify_observers }
|
213
|
+
end
|
214
|
+
|
215
|
+
def select!(&block)
|
216
|
+
if block_given?
|
217
|
+
old_array = Array.new(self)
|
218
|
+
super(&block).tap do
|
219
|
+
(old_array - self).each do |old_value|
|
220
|
+
unregister_dependent_observers(old_value)
|
221
|
+
remove_element_observers(old_value)
|
222
|
+
end
|
223
|
+
notify_observers
|
224
|
+
end
|
225
|
+
else
|
226
|
+
super
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
def shuffle!(hash = nil)
|
231
|
+
(hash.nil? ? super() : super(random: hash[:random])).tap { notify_observers }
|
232
|
+
end
|
233
|
+
|
234
|
+
def slice!(arg1, arg2=nil)
|
235
|
+
old_array = Array.new(self)
|
236
|
+
(arg2.nil? ? super(arg1) : super(arg1, arg2)).tap do
|
237
|
+
(old_array - self).each do |old_value|
|
238
|
+
unregister_dependent_observers(old_value)
|
239
|
+
remove_element_observers(old_value)
|
240
|
+
end
|
241
|
+
notify_observers
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
def sort!(&block)
|
246
|
+
(block.nil? ? super() : super(&block)).tap { notify_observers }
|
247
|
+
end
|
248
|
+
|
249
|
+
def sort_by!(&block)
|
250
|
+
(block.nil? ? super() : super(&block)).tap { notify_observers }
|
251
|
+
end
|
252
|
+
|
253
|
+
def uniq!(&block)
|
254
|
+
each do |old_value|
|
255
|
+
unregister_dependent_observers(old_value)
|
256
|
+
remove_element_observers(old_value)
|
257
|
+
end
|
258
|
+
(block.nil? ? super() : super(&block)).tap do
|
259
|
+
each { |element| add_element_observers(element) }
|
260
|
+
notify_observers
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
def reject!(&block)
|
265
|
+
if block.nil?
|
266
|
+
super
|
267
|
+
else
|
268
|
+
old_array = Array.new(self)
|
269
|
+
super(&block).tap do
|
270
|
+
(old_array - self).each do |old_value|
|
271
|
+
unregister_dependent_observers(old_value)
|
272
|
+
remove_element_observers(old_value)
|
273
|
+
end
|
274
|
+
notify_observers
|
275
|
+
end
|
72
276
|
end
|
73
|
-
super()
|
74
|
-
notify_observers
|
75
277
|
end
|
76
278
|
|
77
279
|
def unregister_dependent_observers(old_value)
|
78
|
-
# TODO look into optimizing this
|
79
280
|
return unless old_value.is_a?(ObservableModel) || old_value.is_a?(ObservableArray)
|
80
|
-
property_observer_list.each
|
81
|
-
observer.unregister_dependents_with_observable(observer.registration_for(self), old_value)
|
82
|
-
end
|
281
|
+
property_observer_list.each { |observer| observer.unregister_dependents_with_observable(observer.registration_for(self), old_value) }
|
83
282
|
end
|
84
283
|
end
|
85
284
|
end
|