sparsify 1.0.1 → 1.1.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.
@@ -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=