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.
- data/.travis.yml +9 -0
- data/CHANGELOG.md +6 -0
- data/Gemfile +0 -1
- data/lib/sparsify.rb +4 -3
- data/lib/sparsify/deprecations.rb +28 -0
- data/lib/sparsify/separator.rb +75 -0
- data/lib/sparsify/utility_methods.rb +45 -23
- data/lib/sparsify/version.rb +1 -1
- data/spec/sparsify_spec.rb +52 -0
- metadata +18 -6
- checksums.yaml +0 -15
data/.travis.yml
ADDED
data/CHANGELOG.md
CHANGED
@@ -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
data/lib/sparsify.rb
CHANGED
@@ -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
|
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
|
78
|
-
Sparsify.
|
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
|
-
|
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
|
-
|
134
|
-
|
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
|
-
#
|
143
|
-
# @
|
144
|
-
|
145
|
-
|
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
|
-
|
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
|
-
[
|
195
|
+
Separator[separator].join(pre_escaped_prefix, new_part)
|
174
196
|
end
|
175
197
|
end
|
176
198
|
|
data/lib/sparsify/version.rb
CHANGED
data/spec/sparsify_spec.rb
CHANGED
@@ -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
|
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:
|
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:
|
124
|
+
rubygems_version: 1.8.23
|
112
125
|
signing_key:
|
113
|
-
specification_version:
|
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=
|