collapsium 0.4.1 → 0.5.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 1b85cef5b1becbc08cec24005e6e7c31b4e8156a
4
- data.tar.gz: 1a25c135dcb2d7784dedcb04cdee7e6633e841eb
3
+ metadata.gz: c4eb75d59a4fd999a789ee4fb811263dea86b493
4
+ data.tar.gz: 15a3e7ff204d3268112e41a565d6d441d60db873
5
5
  SHA512:
6
- metadata.gz: 2d54cd48ceef5cd706e662b40451f349595e88ecc7987ee54544110adff1a8493a11345e83d2ef659f825741029630d8d911b4be8325111ea28d7558fb2a7ad3
7
- data.tar.gz: 24dc1841b56c2b5304d13a8977e1123aabfe196ec87f912bc4251c575ffdb9d7cfd17703a9b10d933b03a335111350ff4aa6d70099c93eeebc893d7cbb4631f2
6
+ metadata.gz: 31d5e290424e9e75eda116508351e2ae7ff3873ba977257140bf1112532278f4eae00590e9974b351d84afa89b1e1943d7ed4b4f74705c2ae54d6538b948b71f
7
+ data.tar.gz: 395b15971c754dea2eea60d7ebd601691f02214e7eeb97fa25281626b84cdaf6b4ecbb40585ebbfd8daa3914aa770f28856c81de8d7543a3e43ce15b8d4bc5a2
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- collapsium (0.4.1)
4
+ collapsium (0.5.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
@@ -12,16 +12,16 @@ GEM
12
12
  diff-lcs (1.2.5)
13
13
  docile (1.1.5)
14
14
  json (2.0.2)
15
- parser (2.3.1.2)
15
+ parser (2.3.1.4)
16
16
  ast (~> 2.2)
17
17
  powerpack (0.1.1)
18
18
  rainbow (2.1.0)
19
- rake (11.2.2)
19
+ rake (11.3.0)
20
20
  rspec (3.5.0)
21
21
  rspec-core (~> 3.5.0)
22
22
  rspec-expectations (~> 3.5.0)
23
23
  rspec-mocks (~> 3.5.0)
24
- rspec-core (3.5.2)
24
+ rspec-core (3.5.4)
25
25
  rspec-support (~> 3.5.0)
26
26
  rspec-expectations (3.5.0)
27
27
  diff-lcs (>= 1.2.0, < 2.0)
@@ -30,7 +30,7 @@ GEM
30
30
  diff-lcs (>= 1.2.0, < 2.0)
31
31
  rspec-support (~> 3.5.0)
32
32
  rspec-support (3.5.0)
33
- rubocop (0.42.0)
33
+ rubocop (0.44.1)
34
34
  parser (>= 2.3.1.1, < 3.0)
35
35
  powerpack (~> 0.1)
36
36
  rainbow (>= 1.99.1, < 3.0)
@@ -42,7 +42,7 @@ GEM
42
42
  json (>= 1.8, < 3)
43
43
  simplecov-html (~> 0.10.0)
44
44
  simplecov-html (0.10.0)
45
- unicode-display_width (1.1.0)
45
+ unicode-display_width (1.1.1)
46
46
  yard (0.9.5)
47
47
 
48
48
  PLATFORMS
@@ -52,9 +52,9 @@ DEPENDENCIES
52
52
  bundler (~> 1.12)
53
53
  codeclimate-test-reporter
54
54
  collapsium!
55
- rake (~> 11.2)
55
+ rake (~> 11.3)
56
56
  rspec (~> 3.5)
57
- rubocop (~> 0.42)
57
+ rubocop (~> 0.44)
58
58
  simplecov (~> 0.12)
59
59
  yard (~> 0.9)
60
60
 
data/collapsium.gemspec CHANGED
@@ -35,8 +35,8 @@ Gem::Specification.new do |spec|
35
35
  spec.required_ruby_version = '>= 2.0'
36
36
 
37
37
  spec.add_development_dependency "bundler", "~> 1.12"
38
- spec.add_development_dependency "rubocop", "~> 0.42"
39
- spec.add_development_dependency "rake", "~> 11.2"
38
+ spec.add_development_dependency "rubocop", "~> 0.44"
39
+ spec.add_development_dependency "rake", "~> 11.3"
40
40
  spec.add_development_dependency "rspec", "~> 3.5"
41
41
  spec.add_development_dependency "simplecov", "~> 0.12"
42
42
  spec.add_development_dependency "yard", "~> 0.9"
@@ -39,6 +39,7 @@ module Collapsium
39
39
  # wrap accessor methods.
40
40
  class << self
41
41
  include ::Collapsium::Support::HashMethods
42
+ include ::Collapsium::Support::ArrayMethods
42
43
  include ::Collapsium::Support::Methods
43
44
 
44
45
  ##
@@ -62,6 +63,54 @@ module Collapsium
62
63
  # supported, we'll try environment variables of the path, starting
63
64
  # with the full qualified path and ending with just the last key
64
65
  # component.
66
+ env_keys = EnvironmentOverride.environment_variables_for_key(receiver, key)
67
+
68
+ # When we have the keys (in priority order), try to see if the
69
+ # environment yields something useful.
70
+ value = EnvironmentOverride.override_value(env_keys, receiver, key)
71
+
72
+ if not value.nil?
73
+ # We can't just return the value, because that doesn't respect the
74
+ # method being called. We also can't store the value, because that
75
+ # would not reset the Hash after the environment variable was
76
+ # cleared.
77
+ #
78
+ # We can deal with this by duplicating the receiver, writing the value
79
+ # we found into the appropriate key, and then sending the
80
+ # wrapped_method to the duplicate.
81
+ double = receiver.recursive_dup
82
+ double[key] = value
83
+ meth = double.method(wrapped_method.name)
84
+ next meth.call(*args, &block)
85
+ end
86
+
87
+ # Otherwise, fall back on the super method.
88
+ next wrapped_method.call(*args, &block)
89
+ end.freeze # proc
90
+
91
+ def included(base)
92
+ enhance(base)
93
+ end
94
+
95
+ def extended(base)
96
+ enhance(base)
97
+ end
98
+
99
+ def prepended(base)
100
+ enhance(base)
101
+ end
102
+
103
+ def enhance(base)
104
+ # Make the capabilities of classes using EnvironmentOverride viral.
105
+ base.extend(ViralCapabilities)
106
+
107
+ # Wrap read accessor functions to deal with paths
108
+ (INDEXED_READ_METHODS + KEYED_READ_METHODS).each do |method|
109
+ wrap_method(base, method, raise_on_missing: false, &ENV_ACCESS_READER)
110
+ end
111
+ end
112
+
113
+ def environment_variables_for_key(receiver, key)
65
114
  env_keys = []
66
115
  if receiver.respond_to?(:path_prefix)
67
116
 
@@ -92,14 +141,16 @@ module Collapsium
92
141
  else
93
142
  env_keys = [key.to_s]
94
143
  end
95
- env_keys.map! { |k| receiver.key_to_env(k) }
144
+ env_keys.map! { |k| key_to_env(k) }
96
145
  env_keys.select! { |k| not k.empty? }
97
146
  env_keys.uniq!
98
147
 
99
- # When we have the keys (in priority order), try to see if the
100
- # environment yields something useful.
148
+ return env_keys
149
+ end
150
+
151
+ def override_value(variables, receiver, key)
101
152
  value = nil
102
- env_keys.each do |env_key|
153
+ variables.each do |env_key|
103
154
  # Grab the environment value; skip if there's nothing there.
104
155
  env_value = ENV[env_key]
105
156
  if env_value.nil?
@@ -131,60 +182,22 @@ module Collapsium
131
182
  break
132
183
  end
133
184
 
134
- if not value.nil?
135
- # We can't just return the value, because that doesn't respect the
136
- # method being called. We also can't store the value, because that
137
- # would not reset the Hash after the environment variable was
138
- # cleared.
139
- #
140
- # We can deal with this by duplicating the receiver, writing the value
141
- # we found into the appropriate key, and then sending the
142
- # wrapped_method to the duplicate.
143
- double = receiver.recursive_dup
144
- double[key] = value
145
- meth = double.method(wrapped_method.name)
146
- next meth.call(*args, &block)
147
- end
148
-
149
- # Otherwise, fall back on the super method.
150
- next wrapped_method.call(*args, &block)
151
- end.freeze # proc
152
-
153
- def included(base)
154
- enhance(base)
185
+ return value
155
186
  end
156
187
 
157
- def extended(base)
158
- enhance(base)
159
- end
188
+ def key_to_env(key)
189
+ # First, convert to upper case
190
+ env_key = key.upcase
160
191
 
161
- def prepended(base)
162
- enhance(base)
163
- end
192
+ # Next, replace non-alphanumeric characters to underscore. This also
193
+ # collapses them into a single undescore.
194
+ env_key.gsub!(/[^[:alnum:]]+/, '_')
164
195
 
165
- def enhance(base)
166
- # Make the capabilities of classes using EnvironmentOverride viral.
167
- base.extend(ViralCapabilities)
196
+ # Strip leading and trailing underscores.
197
+ env_key.gsub!(/^_*(.*?)_*$/, '\1')
168
198
 
169
- # Wrap read accessor functions to deal with paths
170
- KEYED_READ_METHODS.each do |method|
171
- wrap_method(base, method, &ENV_ACCESS_READER)
172
- end
199
+ return env_key
173
200
  end
174
201
  end # class << self
175
-
176
- def key_to_env(key)
177
- # First, convert to upper case
178
- env_key = key.upcase
179
-
180
- # Next, replace non-alphanumeric characters to underscore. This also
181
- # collapses them into a single undescore.
182
- env_key.gsub!(/[^[:alnum:]]+/, '_')
183
-
184
- # Strip leading and trailing underscores.
185
- env_key.gsub!(/^_*(.*?)_*$/, '\1')
186
-
187
- return env_key
188
- end
189
202
  end # module EnvironmentOverride
190
203
  end # module Collapsium
@@ -9,6 +9,7 @@
9
9
 
10
10
  require 'collapsium/support/hash_methods'
11
11
  require 'collapsium/support/methods'
12
+ require 'collapsium/support/path_components'
12
13
 
13
14
  require 'collapsium/viral_capabilities'
14
15
 
@@ -27,40 +28,24 @@ module Collapsium
27
28
  # for a path.
28
29
  module PathedAccess
29
30
 
30
- # @return [String] the separator is the character or pattern splitting paths.
31
- def separator
32
- @separator ||= DEFAULT_SEPARATOR
33
- return @separator
34
- end
35
-
36
- ##
37
- # Assume any pathed access has this prefix.
38
- def path_prefix=(value)
39
- @path_prefix = normalize_path(value)
40
- end
41
-
42
- def path_prefix
43
- @path_prefix ||= ''
44
- return @path_prefix
45
- end
46
-
47
- # @api private
48
- # Default path separator
49
- DEFAULT_SEPARATOR = '.'.freeze
50
-
51
- ##
52
- # @return [RegExp] the pattern to split paths at; based on `separator`
53
- def split_pattern
54
- /(?<!\\)#{Regexp.escape(separator)}/
55
- end
31
+ include ::Collapsium::Support::PathComponents
56
32
 
57
33
  ##
58
34
  # If the module is included, extended or prepended in a class, it'll
59
35
  # wrap accessor methods.
60
36
  class << self
61
- include ::Collapsium::Support::HashMethods
62
37
  include ::Collapsium::Support::Methods
63
38
 
39
+ # We want to wrap methods for Arrays and Hashes alike
40
+ READ_METHODS = (
41
+ ::Collapsium::Support::HashMethods::KEYED_READ_METHODS \
42
+ + ::Collapsium::Support::ArrayMethods::INDEXED_READ_METHODS
43
+ ).uniq.freeze
44
+ WRITE_METHODS = (
45
+ ::Collapsium::Support::HashMethods::KEYED_WRITE_METHODS \
46
+ + ::Collapsium::Support::ArrayMethods::INDEXED_WRITE_METHODS
47
+ ).uniq.freeze
48
+
64
49
  ##
65
50
  # Returns a proc for either read or write access. Procs for write
66
51
  # access will create intermediary hashes when e.g. setting a value for
@@ -90,18 +75,10 @@ module Collapsium
90
75
  # Try to find the leaf, based on the given components.
91
76
  leaf = recursive_fetch(components, receiver, [], create: write_access)
92
77
 
93
- # The tricky part is what to do with the leaf.
94
- meth = nil
95
- if receiver.object_id == leaf.object_id
96
- # a) if the leaf and the receiver are identical, then the receiver
97
- # itself was requested, and we really just need to delegate to its
98
- # wrapped_method.
99
- meth = wrapped_method
100
- else
101
- # b) if the leaf is different from the receiver, we want to delegate
102
- # to the leaf.
103
- meth = leaf.method(wrapped_method.name)
104
- end
78
+ # Since Methods already contains loop prevention and we may want to
79
+ # call wrapped methods, let's just find the method to call from the
80
+ # leaf by name.
81
+ meth = leaf.method(wrapped_method.name)
105
82
 
106
83
  # If the first argument was a symbol key, we want to use it verbatim.
107
84
  # Otherwise we had pathed access, and only want to pass the last
@@ -112,8 +89,21 @@ module Collapsium
112
89
  the_args[0] = components.last
113
90
  end
114
91
 
92
+ # Array methods we're modifying here are indexed, so the first argument
93
+ # must be an integer. Let's make it so :)
94
+ if leaf.is_a? Array
95
+ the_args[0] = the_args[0].to_i
96
+ end
97
+
115
98
  # Then we can continue with that method.
116
- next meth.call(*the_args, &block)
99
+ result = meth.call(*the_args, &block)
100
+
101
+ # Sadly, we can't just return the result and be done with it.
102
+ # We need to tell the virality function (below) what we know about the
103
+ # result's path prefix, so we enhance the result value explicitly here.
104
+ result_path = receiver.path_components(receiver.path_prefix)
105
+ result_path += components
106
+ next ViralCapabilities.enhance_value(leaf, result, result_path)
117
107
  end # proc
118
108
  end # create_proc
119
109
 
@@ -138,11 +128,11 @@ module Collapsium
138
128
  base.extend(ViralCapabilities)
139
129
 
140
130
  # Wrap all accessor functions to deal with paths
141
- KEYED_READ_METHODS.each do |method|
142
- wrap_method(base, method, &PATHED_ACCESS_READER)
131
+ READ_METHODS.each do |method|
132
+ wrap_method(base, method, raise_on_missing: false, &PATHED_ACCESS_READER)
143
133
  end
144
- KEYED_WRITE_METHODS.each do |method|
145
- wrap_method(base, method, &PATHED_ACCESS_WRITER)
134
+ WRITE_METHODS.each do |method|
135
+ wrap_method(base, method, raise_on_missing: false, &PATHED_ACCESS_WRITER)
146
136
  end
147
137
  end
148
138
 
@@ -155,14 +145,6 @@ module Collapsium
155
145
  current_path << head
156
146
  tail = path.slice(1, path.length)
157
147
 
158
- # We know that the data has the current path. We also know that thanks to
159
- # virality, data will respond to :path_prefix. So we might as well set the
160
- # path, as long as it is more specific than what was previously there.
161
- current_normalized = data.normalize_path(current_path)
162
- if current_normalized.length > data.path_prefix.length
163
- data.path_prefix = current_normalized
164
- end
165
-
166
148
  # For the leaf element, we do nothing because that's where we want to
167
149
  # dispatch to.
168
150
  if path.length == 1
@@ -189,46 +171,29 @@ module Collapsium
189
171
  end # class << self
190
172
 
191
173
  ##
192
- # Break path into components. Expects a String path separated by the
193
- # `#separator`, and returns the path split into components (an Array of
194
- # String).
195
- def path_components(path)
196
- return filter_components(path.split(split_pattern))
197
- end
198
-
199
- ##
200
- # Given path components, filters out unnecessary ones.
201
- def filter_components(components)
202
- return components.select { |c| not c.nil? and not c.empty? }
203
- end
204
-
205
- ##
206
- # Join path components with the `#separator`.
207
- def join_path(components)
208
- return components.join(separator)
209
- end
174
+ # Ensure that all values have their path_prefix set.
175
+ def virality(value, *args)
176
+ # Figure out what path prefix to set on the value, if any.
177
+ # Candidates for the prefix are:
178
+ explicit = args[0] || []
179
+ from_self = path_components(path_prefix)
180
+ from_value = path_components(value.path_prefix)
181
+
182
+ prefix = []
183
+ if not explicit.empty?
184
+ # If we got explicit information, we most likely want to use that.
185
+ prefix = explicit
186
+ elsif not from_self.empty?
187
+ # If we got information from self, that's the next best candidate.
188
+ prefix = from_self
189
+ end
210
190
 
211
- ##
212
- # Normalizes a String path so that there are no empty components, and it
213
- # starts with a separator.
214
- def normalize_path(path)
215
- components = []
216
- if path.respond_to?(:split) # likely a String
217
- components = path_components(path)
218
- elsif path.respond_to?(:join) # likely an Array
219
- components = filter_components(path)
191
+ # However, if the value already has a better path prefix than either
192
+ # of the above, we want to keep that.
193
+ if prefix.length > from_value.length
194
+ value.path_prefix = normalize_path(prefix)
220
195
  end
221
- return separator + join_path(components)
222
- end
223
196
 
224
- ##
225
- # Ensure that all values have their path_prefix set.
226
- def virality(value)
227
- # If a value was set via a nested Hash, it may not have got its
228
- # path_prefix set during storing (i.e. x[key] = { nested: some_hash }
229
- # In that case, we do always know that the value's path prefix is the same
230
- # as the receiver.
231
- value.path_prefix = path_prefix
232
197
  return value
233
198
  end
234
199
 
@@ -0,0 +1,48 @@
1
+ # coding: utf-8
2
+ #
3
+ # collapsium
4
+ # https://github.com/jfinkhaeuser/collapsium
5
+ #
6
+ # Copyright (c) 2016 Jens Finkhaeuser and other collapsium contributors.
7
+ # All rights reserved.
8
+ #
9
+
10
+ module Collapsium
11
+
12
+ ##
13
+ # Support functionality for Collapsium
14
+ module Support
15
+
16
+ ##
17
+ # @api private
18
+ # Defines which read and write functions we expect Array to have.
19
+ module ArrayMethods
20
+
21
+ # @api private
22
+ # Read access methods with index parameter
23
+ INDEXED_READ_METHODS = [
24
+ :[], :at, :fetch, :include?,
25
+ ].freeze
26
+
27
+ # @api private
28
+ # All read access methods
29
+ READ_METHODS = INDEXED_READ_METHODS + [
30
+ :dup, :first, :last, :take, :drop,
31
+ ].freeze
32
+
33
+ # @api private
34
+ # Write access methods with index parameter
35
+ INDEXED_WRITE_METHODS = [
36
+ :[]=, :insert, :compact,
37
+ ].freeze
38
+
39
+ # All write access methods
40
+ WRITE_METHODS = INDEXED_WRITE_METHODS + [
41
+ :unshift, :pop, :shift,
42
+ ].freeze
43
+
44
+ end # module ArrayMethods
45
+
46
+ end # module Support
47
+
48
+ end # module Collapsium