sparsify 1.0.1 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,9 @@
1
+ ---
2
+ language: ruby
3
+ script: "bundle exec rspec"
4
+ rvm:
5
+ - 1.9.3
6
+ - 2.0.0
7
+ - 2.1.0
8
+ - jruby-19mode
9
+ - rbx
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## v1.1.0 (2015-06-04)
4
+
5
+ - Added Sparsify::expand(), which expands a subset of the given sparse hash (@yaauie)
6
+ - Fixed extraneous characters in documentation (@yaauie)
7
+ - Actually provided Sparsify#sparse_get to line up with documentation (@yaauie)
8
+
3
9
  ## v1.0.1 (2013-12-22)
4
10
 
5
11
  - Made Sparsify() and Unsparsify() helpers private methods on Kernel instead
data/Gemfile CHANGED
@@ -1,5 +1,4 @@
1
1
  # encoding: utf-8
2
- source 'http://gems.production.int.simplymeasured.com:9292'
3
2
  source 'https://rubygems.org'
4
3
 
5
4
  # Specify your gem's dependencies in sparsify.gemspec
@@ -3,6 +3,7 @@
3
3
  require 'sparsify/version'
4
4
  require 'sparsify/utility_methods'
5
5
  require 'sparsify/guard_methods'
6
+ require 'sparsify/separator'
6
7
  require 'sparsify/core_ext/kernel'
7
8
 
8
9
  # Provides sparse-key access to a Hash.
@@ -39,7 +40,7 @@ module Sparsify
39
40
  # Used internally by both Sparsify::Utility#sparse and
40
41
  # Sparsify::Utility#unsparse
41
42
  #
42
- # @overload sparse_eachrm (options = {}, &block)
43
+ # @overload sparse_each(options = {}, &block)
43
44
  # Yields once per key in sparse version of itself.
44
45
  # @param options (see #sparse)
45
46
  # @yieldparam [(sparse_key,value)]
@@ -74,8 +75,8 @@ module Sparsify
74
75
  # @overload sparse_get(sparse_key, options = {})
75
76
  # @param options (see #sparse)
76
77
  # @return [Object] at that address or nil if none found
77
- def sparse_fetch(*args, &block)
78
- Sparsify.sparse_fetch(self, *args, &block)
78
+ def sparse_get(*args)
79
+ Sparsify.sparse_get(self, *args)
79
80
  end
80
81
 
81
82
  # Returns a deeply-nested hash version of self.
@@ -0,0 +1,28 @@
1
+ # encoding: utf-8
2
+
3
+ require 'set'
4
+
5
+ module Sparsify
6
+ module Deprecations
7
+ class << self
8
+ def deprecate(message, target)
9
+ @deprecations ||= Set.new
10
+ msg = "Sparsify: #{message} is deprecated " +
11
+ "and will be removed in #{target} (at #{external_callpoint})"
12
+ warn(msg) if @deprecations.add?(msg)
13
+ end
14
+
15
+ private
16
+
17
+ def external_callpoint
18
+ caller.drop_while { |loc| loc['lib/sparsify/'] }.first
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def deprecate(message, target)
25
+ Deprecations.deprecate(message, target)
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,75 @@
1
+ # encoding: utf-8
2
+
3
+ require 'sparsify/deprecations'
4
+
5
+ module Sparsify
6
+ # Container for a separator and functionality for splitting &
7
+ # joining with proper escaping.
8
+ # @api private
9
+ class Separator
10
+ include Deprecations
11
+
12
+ @separators = {}
13
+
14
+ # Returns a memoized Separator object for the given separator_character
15
+ # Evicts a member at random if memoized pool grows beyond 100.
16
+ #
17
+ # @param separator_character [String] (see #initialize)
18
+ def self.[](separator_character)
19
+ @separators[separator_character] ||= begin
20
+ @separators.delete(@separators.keys.sample) if @separators.size > 100
21
+ new(separator_character)
22
+ end
23
+ end
24
+
25
+ # @param separator [String] single-character string
26
+ def initialize(separator)
27
+ unless separator.kind_of?(String) && separator.size > 0
28
+ fail ArgumentError, "separator must be a non-empty String " +
29
+ "got #{separator.inspect}"
30
+ end
31
+ deprecate('multi-character separator', '2.0') unless separator.size == 1
32
+ @separator = separator
33
+ end
34
+
35
+ # Joins a pre-escaped string with a not-yet escaped string on our separator,
36
+ # escaping the new part before joining.
37
+ # @param pre_escaped_prefix [String]
38
+ # @param new_part [String] - will be escaped before joining
39
+ # @return [String]
40
+ def join(pre_escaped_prefix, new_part)
41
+ [pre_escaped_prefix, escape(new_part)].compact.join(@separator)
42
+ end
43
+
44
+ # Splits a string by our separator into non-escaped parts
45
+ # @param str [String]
46
+ # @return [Array<String>]
47
+ def split(str)
48
+ @unescaped_separator ||= /(?<!\\)(#{Regexp.escape(@separator)})/
49
+ # String#split(<Regexp>) on non zero-width matches yields the match
50
+ # as the even entries in the array.
51
+ parts = str.split(@unescaped_separator).each_slice(2).map(&:first)
52
+ parts.map do |part|
53
+ unescape(part)
54
+ end
55
+ end
56
+
57
+ private
58
+
59
+ # backslash-escapes our separator and backlashes in a string
60
+ # @param str [String]
61
+ # @return [String]
62
+ def escape(str)
63
+ @escape_pattern ||= /(\\|#{Regexp.escape(@separator)})/
64
+ str.gsub(@escape_pattern, '\\\\\1')
65
+ end
66
+
67
+ # removes backslash-escaping of our separator and backslashes from a string
68
+ # @param str [String]
69
+ # @return [String]
70
+ def unescape(str)
71
+ @unescape_pattern ||= /\\(\\|#{Regexp.escape(@separator)})/
72
+ str.gsub(@unescape_pattern, '\1')
73
+ end
74
+ end
75
+ end
@@ -128,49 +128,71 @@ module Sparsify
128
128
  sparse_fetch(hsh, sparse_key, options) { nil }
129
129
  end
130
130
 
131
- private
131
+ # Given a sparse hash, unsparsify a subset by address, returning
132
+ # a *modified copy* of the original sparse hash.
133
+ #
134
+ # @overload expand(sparse_hsh, sparse_key, options = {}, &block)
135
+ # @param sparse_hsh [Hash{String=>Object}]
136
+ # @param sparse_key [String]
137
+ # @param options (see Sparsify::UtilityMethods#sparse)
138
+ # @return [Object]
139
+ #
140
+ # @example
141
+ # ~~~ ruby
142
+ # sparse = {'a.b' => 2, 'a.c.d' => 4, 'a.c.e' => 3, 'b.f' => 4}
143
+ # Sparsify::expand(sparse, 'a.c')
144
+ # # => {'a.b' => 2, 'a.c' => {'d' => 4, 'e' => 3}, 'b.f' => 4}
145
+ # ~~~
146
+ def expand(sparse_hsh, sparse_key, *args)
147
+ # if sparse_hsh includes our key, its value is already expanded.
148
+ return sparse_hsh if sparse_hsh.include?(sparse_key)
149
+
150
+ options = (args.last.kind_of?(Hash) ? args.pop : {})
151
+ separator = options.fetch(:separator, DEFAULT_SEPARATOR)
152
+ pattern = /\A#{Regexp.escape(sparse_key)}#{Regexp.escape(separator)}/i
153
+
154
+ match = {}
155
+ unmatch = {}
156
+ sparse_hsh.each do |k, v|
157
+ if pattern =~ k
158
+ sk = k.gsub(pattern, '')
159
+ match[sk] = v
160
+ else
161
+ unmatch[k] = v
162
+ end
163
+ end
132
164
 
133
- # Utility method for backslash-escaping a string
134
- # @param str [String]
135
- # @param separator [String] single-character string
136
- # @return [String]
137
- def escape(str, separator)
138
- pattern = /(\\|#{Regexp.escape(separator)})/
139
- str.gsub(pattern, '\\\\\1')
165
+ unmatch.update(sparse_key => unsparse(match, options)) unless match.empty?
166
+ unmatch
140
167
  end
141
168
 
142
- # Utility method for removing backslash-escaping from a string
143
- # @param str [String]
144
- # @param separator [String] single-character string
145
- # @return [String]
146
- def unescape(str, separator)
147
- pattern = /\\(\\|#{Regexp.escape(separator)})/
148
- str.gsub(pattern, '\1')
169
+ # Given a sparse hash, unsparsify a subset by address *in place*
170
+ # (@see Sparsify::UtilityMethods#expand)
171
+ def expand!(sparse_hsh, *args)
172
+ sparse_hsh.replace expand(sparse_hsh, *args)
149
173
  end
150
174
 
175
+ private
176
+
151
177
  # Utility method for splitting a string by a separator into
152
178
  # non-escaped parts
179
+ # @api private
153
180
  # @param str [String]
154
181
  # @param separator [String] single-character string
155
182
  # @return [Array<String>]
156
183
  def escaped_split(str, separator)
157
- unescaped_separator = /(?<!\\)(#{Regexp.escape(separator)})/
158
- # String#split(<Regexp>) on non zero-width matches yields the match
159
- # as the even entries in the array.
160
- parts = str.split(unescaped_separator).each_slice(2).map(&:first)
161
- parts.map do |part|
162
- unescape(part, separator)
163
- end
184
+ Separator[separator].split(str)
164
185
  end
165
186
 
166
187
  # Utility method for joining a pre-escaped string with a not-yet escaped
167
188
  # string on a given separator, escaping the new part before joining.
189
+ # @api private
168
190
  # @param pre_escaped_prefix [String]
169
191
  # @param new_part [String] - will be escaped before joining
170
192
  # @param separator [String] single-character string
171
193
  # @return [String]
172
194
  def escaped_join(pre_escaped_prefix, new_part, separator)
173
- [pre_escaped_prefix, escape(new_part, separator)].compact.join(separator)
195
+ Separator[separator].join(pre_escaped_prefix, new_part)
174
196
  end
175
197
  end
176
198
 
@@ -1,4 +1,4 @@
1
1
  # encoding: utf-8
2
2
  module Sparsify
3
- VERSION = '1.0.1'
3
+ VERSION = '1.1.0'
4
4
  end
@@ -50,6 +50,11 @@ describe 'Sparsify' do
50
50
  end
51
51
  it { should eq nested_hash }
52
52
  end
53
+ context 'deprecated multi-character separators (remove in 2.0)' do
54
+ it 'only escapes the first character in the separator'
55
+ it 'round-trips ok'
56
+ it 'warns appropriately'
57
+ end
53
58
  end
54
59
 
55
60
  context 'sparse_array' do
@@ -194,4 +199,51 @@ describe 'Sparsify' do
194
199
  end
195
200
  end
196
201
  end
202
+
203
+ shared_examples_for('.expand') do
204
+ let(:source_sparse_hash) do
205
+ {
206
+ 'foo.bar.baz' => 'bingo',
207
+ 'foo.bar.whee' => {},
208
+ 'asdf' => 'qwer',
209
+ }
210
+ end
211
+ let(:sparse_key) { 'foo.bar' }
212
+ let(:expander) { proc { Sparsify.send(method, source_sparse_hash, sparse_key) } }
213
+ let(:the_result) { expander.call }
214
+
215
+ it 'expands the item at the address' do
216
+ expect(the_result).to eq({'foo.bar' => {'baz'=>'bingo', 'whee'=> {}},'asdf'=>'qwer'})
217
+ end
218
+ context 'when no item exists at the address' do
219
+ let(:sparse_key) { 'qwer' }
220
+ it 'is a no-op' do
221
+ expect(the_result).to eq(source_sparse_hash)
222
+ end
223
+ end
224
+ context 'when an object exists at the address exactly' do
225
+ let(:sparse_key) { 'foo.bar.baz' }
226
+ it 'is a no-op' do
227
+ expect(the_result).to eq(source_sparse_hash)
228
+ end
229
+ end
230
+ end
231
+
232
+ context '.expand' do
233
+ let(:method) { :expand }
234
+ include_examples('.expand') do
235
+ it 'does not modify the original' do
236
+ expect(the_result).to_not equal(source_sparse_hash)
237
+ end
238
+ end
239
+ end
240
+
241
+ context '.expand!' do
242
+ let(:method) { :expand! }
243
+ include_examples('.expand') do
244
+ it 'modifies the original' do
245
+ expect(the_result).to equal(source_sparse_hash)
246
+ end
247
+ end
248
+ end
197
249
  end
metadata CHANGED
@@ -1,18 +1,20 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sparsify
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ version: 1.1.0
5
+ prerelease:
5
6
  platform: ruby
6
7
  authors:
7
8
  - Ryan Biesemeyer
8
9
  autorequire:
9
10
  bindir: bin
10
11
  cert_chain: []
11
- date: 2014-07-08 00:00:00.000000000 Z
12
+ date: 2015-06-04 00:00:00.000000000 Z
12
13
  dependencies:
13
14
  - !ruby/object:Gem::Dependency
14
15
  name: bundler
15
16
  requirement: !ruby/object:Gem::Requirement
17
+ none: false
16
18
  requirements:
17
19
  - - ~>
18
20
  - !ruby/object:Gem::Version
@@ -20,6 +22,7 @@ dependencies:
20
22
  type: :development
21
23
  prerelease: false
22
24
  version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
23
26
  requirements:
24
27
  - - ~>
25
28
  - !ruby/object:Gem::Version
@@ -27,6 +30,7 @@ dependencies:
27
30
  - !ruby/object:Gem::Dependency
28
31
  name: rake
29
32
  requirement: !ruby/object:Gem::Requirement
33
+ none: false
30
34
  requirements:
31
35
  - - ! '>='
32
36
  - !ruby/object:Gem::Version
@@ -34,6 +38,7 @@ dependencies:
34
38
  type: :development
35
39
  prerelease: false
36
40
  version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
37
42
  requirements:
38
43
  - - ! '>='
39
44
  - !ruby/object:Gem::Version
@@ -41,6 +46,7 @@ dependencies:
41
46
  - !ruby/object:Gem::Dependency
42
47
  name: rspec
43
48
  requirement: !ruby/object:Gem::Requirement
49
+ none: false
44
50
  requirements:
45
51
  - - ! '>='
46
52
  - !ruby/object:Gem::Version
@@ -48,6 +54,7 @@ dependencies:
48
54
  type: :development
49
55
  prerelease: false
50
56
  version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
51
58
  requirements:
52
59
  - - ! '>='
53
60
  - !ruby/object:Gem::Version
@@ -55,6 +62,7 @@ dependencies:
55
62
  - !ruby/object:Gem::Dependency
56
63
  name: ruby-appraiser-rubocop
57
64
  requirement: !ruby/object:Gem::Requirement
65
+ none: false
58
66
  requirements:
59
67
  - - ! '>='
60
68
  - !ruby/object:Gem::Version
@@ -62,6 +70,7 @@ dependencies:
62
70
  type: :development
63
71
  prerelease: false
64
72
  version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
65
74
  requirements:
66
75
  - - ! '>='
67
76
  - !ruby/object:Gem::Version
@@ -74,6 +83,7 @@ extensions: []
74
83
  extra_rdoc_files: []
75
84
  files:
76
85
  - .gitignore
86
+ - .travis.yml
77
87
  - CHANGELOG.md
78
88
  - CONTRIBUTING.md
79
89
  - Gemfile
@@ -83,7 +93,9 @@ files:
83
93
  - git_hooks/pre-commit/ruby-appraiser
84
94
  - lib/sparsify.rb
85
95
  - lib/sparsify/core_ext/kernel.rb
96
+ - lib/sparsify/deprecations.rb
86
97
  - lib/sparsify/guard_methods.rb
98
+ - lib/sparsify/separator.rb
87
99
  - lib/sparsify/utility_methods.rb
88
100
  - lib/sparsify/version.rb
89
101
  - sparsify.gemspec
@@ -91,27 +103,27 @@ files:
91
103
  homepage: https://www.github.com/simplymeasured/sparsify
92
104
  licenses:
93
105
  - Apache 2
94
- metadata: {}
95
106
  post_install_message:
96
107
  rdoc_options: []
97
108
  require_paths:
98
109
  - lib
99
110
  required_ruby_version: !ruby/object:Gem::Requirement
111
+ none: false
100
112
  requirements:
101
113
  - - ! '>='
102
114
  - !ruby/object:Gem::Version
103
115
  version: '0'
104
116
  required_rubygems_version: !ruby/object:Gem::Requirement
117
+ none: false
105
118
  requirements:
106
119
  - - ! '>='
107
120
  - !ruby/object:Gem::Version
108
121
  version: '0'
109
122
  requirements: []
110
123
  rubyforge_project:
111
- rubygems_version: 2.2.2
124
+ rubygems_version: 1.8.23
112
125
  signing_key:
113
- specification_version: 4
126
+ specification_version: 3
114
127
  summary: Flattens deeply-nested hashes into sparse hashes
115
128
  test_files:
116
129
  - spec/sparsify_spec.rb
117
- has_rdoc:
checksums.yaml DELETED
@@ -1,15 +0,0 @@
1
- ---
2
- !binary "U0hBMQ==":
3
- metadata.gz: !binary |-
4
- ZjU2OWYwNjg0ZGU2NDhmMTQ1NDY5MGExYWFhNmM4ZjQzMjU5OWMyOQ==
5
- data.tar.gz: !binary |-
6
- YzFiMmYzYzU2ODdjYTFiZmE4ODkzYmM1MWVhMTVmZmZhNDhjOGY2OQ==
7
- SHA512:
8
- metadata.gz: !binary |-
9
- N2JiMDE4MTE1ZDliN2JiMTVkMDJhMzg0Yzg5MDJjZDk4ZmI3ODEyNGRlZjQ1
10
- MDM5MjU5NGE5YjZjNDI3OGRhOGFiNjVhMWUyYTFiNjM3MWU2ODIxOThmM2Jj
11
- M2ZlMDViYjE4OThmNjBkZmI3M2NkN2U3ZjY0NWQzMTAyMzQwNDY=
12
- data.tar.gz: !binary |-
13
- YTgyOGIyY2ZjNTdhZjU0YWQ1NzA5OGQwMzJiZDFiZmYwN2QyMjI4Njc4MGMx
14
- MGIxYWIxMzhiYzhmNzc3NzZhZjdmYTk3N2I4N2RhMWMxYjE1ODM2MjNhOWE5
15
- ODk4ZmYzNDE5NTU3MDViOTNlMTFkYTNkOWFiMmVhOTRmMzUxY2Y=