flatten 0.1.1 → 0.2.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: e2f84f65a18c10d88ff02d3253ca5fb405e2b49d
4
- data.tar.gz: 018c691c87d3fbb507f0c185ca84c591f7537712
3
+ metadata.gz: a31b1526da6e96924870c4ae463f72b9964fe586
4
+ data.tar.gz: 77f462a2f917ff489e157ca33abc64051fc385b2
5
5
  SHA512:
6
- metadata.gz: 12135a43237f3ee41bd1523ebce64915818117914dd29a5eedd455016c07996c75e69087cc5b3db1c9e21f0fa2ce8596747195738bdbedc2f8c314225b740827
7
- data.tar.gz: ba99bfd24ed530fd1a40fe500f69f579daeacc31965c6cd2f991e7e90c78274ead1f6b8336506b3f88d44d075bff9a526acced1763a17319358427c58177dee7
6
+ metadata.gz: c2b683d1485f95dd7c9657aff60249857867c9465b509e9d666d341040037a8bfc919a4d500c1e77530a0e29ad3168a720a0c953413e49d24056e7b9c43e720c
7
+ data.tar.gz: da22f29aac628276139eef472f566b17dc5771743417464e9495d917a11248fe8a6b3b2926917d4626db770cd6a2b74ea82a969353726ea16f4aefb5c861e955
@@ -1,6 +1,8 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  require 'flatten/version'
4
+ require 'flatten/string'
5
+ require 'flatten/hash'
4
6
  require 'flatten/utility_methods'
5
7
  require 'flatten/guard_methods'
6
8
  require 'flatten/separator'
@@ -2,17 +2,19 @@
2
2
 
3
3
  module Kernel
4
4
 
5
- # @see Flatten#smash
6
- # @api public
7
- def Flatten(hsh, options = {})
8
- Flatten.smash(hsh, options)
9
- end
10
- private :Flatten
5
+ # @see Flatten#smash
6
+ # @api public
7
+ def Flatten(hsh, options = {})
8
+ Flatten.smash(hsh, options)
9
+ end
11
10
 
12
- # @see Flatten#unsmash
13
- # @api public
14
- def Unflatten(hsh, options = {})
15
- Flatten.unsmash(hsh, options)
16
- end
17
- private :Unflatten
11
+ private :Flatten
12
+
13
+ # @see Flatten#unsmash
14
+ # @api public
15
+ def Unflatten(hsh, options = {})
16
+ Flatten.unsmash(hsh, options)
17
+ end
18
+
19
+ private :Unflatten
18
20
  end
@@ -3,26 +3,26 @@
3
3
  require 'set'
4
4
 
5
5
  module Flatten
6
- module Deprecations
7
- class << self
8
- def deprecate(message, target)
9
- @deprecations ||= Set.new
10
- msg = "Flatten: #{message} is deprecated " +
11
- "and will be removed in #{target} (at #{external_callpoint})"
12
- warn(msg) if @deprecations.add?(msg)
13
- end
6
+ module Deprecations
7
+ class << self
8
+ def deprecate(message, target)
9
+ @deprecations ||= Set.new
10
+ msg = "Flatten: #{message} is deprecated " +
11
+ "and will be removed in #{target} (at #{external_callpoint})"
12
+ warn(msg) if @deprecations.add?(msg)
13
+ end
14
14
 
15
- private
15
+ private
16
16
 
17
- def external_callpoint
18
- caller.drop_while { |loc| loc['lib/flatten/'] }.first
19
- end
20
- end
17
+ def external_callpoint
18
+ caller.drop_while { |loc| loc['lib/flatten/'] }.first
19
+ end
20
+ end
21
21
 
22
- private
22
+ private
23
23
 
24
- def deprecate(message, target)
25
- Deprecations.deprecate(message, target)
26
- end
27
- end
24
+ def deprecate(message, target)
25
+ Deprecations.deprecate(message, target)
26
+ end
27
+ end
28
28
  end
@@ -1,24 +1,24 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  module Flatten
4
- # Methods to ensure Flatten isn't mixed into nonsensical things
5
- module GuardMethods
6
- # Flatten can be *extended* into instances of Hash
7
- # @param base [Hash]
8
- def extended(base)
9
- unless base.is_a? Hash
10
- raise ArgumentError, "<#{base.inspect}> is not a Hash!"
11
- end
12
- end
4
+ # Methods to ensure Flatten isn't mixed into nonsensical things
5
+ module GuardMethods
6
+ # Flatten can be *extended* into instances of Hash
7
+ # @param base [Hash]
8
+ def extended(base)
9
+ unless base.is_a? Hash
10
+ raise ArgumentError, "<#{base.inspect}> is not a Hash!"
11
+ end
12
+ end
13
13
 
14
- # Sparsigy can be *included* into implementations of Hash
15
- # @param base [Hash.class]
16
- def included(base)
17
- unless base <= Hash
18
- raise ArgumentError, "<#{base.inspect} does not inherit Hash"
19
- end
20
- end
21
- end
14
+ # Sparsigy can be *included* into implementations of Hash
15
+ # @param base [Hash.class]
16
+ def included(base)
17
+ unless base <= Hash
18
+ raise ArgumentError, "<#{base.inspect} does not inherit Hash"
19
+ end
20
+ end
21
+ end
22
22
 
23
- extend GuardMethods
23
+ extend GuardMethods
24
24
  end
@@ -0,0 +1,5 @@
1
+ class Hash
2
+ def smash
3
+ Flatten(self)
4
+ end
5
+ end
@@ -3,73 +3,73 @@
3
3
  require 'flatten/deprecations'
4
4
 
5
5
  module Flatten
6
- # Container for a separator and functionality for splitting &
7
- # joining with proper escaping.
8
- # @api private
9
- class Separator
10
- include Deprecations
6
+ # Container for a separator and functionality for splitting &
7
+ # joining with proper escaping.
8
+ # @api private
9
+ class Separator
10
+ include Deprecations
11
11
 
12
- @separators = {}
12
+ @separators = {}
13
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
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
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
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
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
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
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
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
56
 
57
- private
57
+ private
58
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
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
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
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
75
  end
@@ -0,0 +1,5 @@
1
+ class String
2
+ def smash
3
+ Flatten(JSON.parse(self)).to_json
4
+ end
5
+ end
@@ -1,201 +1,208 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  module Flatten
4
- # The Utility Methods provide a significant (~4x) performance increase
5
- # over extend-ing instance methods everywhere we need them.
6
- module UtilityMethods
7
-
8
- # Provides a way to iterate through a deeply-nested hash as if it were
9
- # a smash-hash. Used internally for generating and deconstructing smash
10
- # hashes.
11
- #
12
- # @overload smash_each(hsh, options = {}, &block)
13
- # Yields once per key in smash version of itself.
14
- # @param hsh [Hash<#to_s,Object>]
15
- # @param options (see Flatten::UtilityMethods#smash)
16
- # @yieldparam [(smash_key,value)]
17
- # @return [void]
18
- # @overload smash_each(hsh, options = {})
19
- # @param hsh [Hash<#to_s,Object>]
20
- # @param options (see Flatten::UtilityMethods#smash)
21
- # @return [Enumerator<(smash_key,value)>]
22
- def smash_each(hsh, options = {}, &block)
23
- return enum_for(:smash_each, hsh, options) unless block_given?
24
-
25
- inherited_prefix = options.fetch(:prefix, nil)
26
- separator = options.fetch(:separator, DEFAULT_SEPARATOR)
27
- smash_array = options.fetch(:smash_array, false)
28
-
29
- hsh.each do |partial_key, value|
30
- key = escaped_join(inherited_prefix, partial_key.to_s, separator)
31
- if value.kind_of?(Hash) && !value.empty?
32
- smash_each(value, options.merge(prefix: key), &block)
33
- elsif smash_array && value.kind_of?(Array) && !value.empty?
34
- zps = (smash_array == :zero_pad ? "%0#{value.count.to_s.size}d" : '%d')# zero-pad string
35
- smash_each(value.count.times.map(&zps.method(:%)).zip(value), options.merge(prefix: key), &block)
36
- else
37
- yield key, value
38
- end
39
- end
40
- end
41
-
42
- # Returns a smash version of the given hash
43
- #
44
- # @param hsh [Hash<#to_s,Object>]
45
- # @param options (see Flatten::UtilityMethods#smash)
46
- # @return [Hash<String,Object>]
47
- def smash(hsh, options = {})
48
- enum = smash_each(hsh, options)
49
- enum.each_with_object(Hash.new) do |(key, value), memo|
50
- memo[key] = value
51
- end
52
- end
53
-
54
- # Returns a deeply-nested version of the given smash hash
55
- # @param hsh [Hash<#to_s,Object>]
56
- # @param options (see Flatten::UtilityMethods#smash)
57
- # @return [Hash<String,Object>]
58
- def unsmash(hsh, options = {})
59
- separator = options.fetch(:separator, DEFAULT_SEPARATOR)
60
- hsh.each_with_object({}) do |(k, v), memo|
61
- current = memo
62
- key = escaped_split(k, separator)
63
- puts "key: #{key}"
64
- up_next = partial = key.shift
65
- until key.size.zero?
66
- up_next = key.shift
67
- up_next = up_next.to_i if (up_next =~ /\A[0-9]+\Z/)
68
- current = (current[partial] ||= (up_next.kind_of?(Integer) ? [] : {}))
69
- case up_next
70
- when Integer then raise KeyError unless current.kind_of?(Array)
71
- else raise KeyError unless current.kind_of?(Hash)
72
- end
73
- partial = up_next
74
- end
75
- current[up_next] = v
76
- end
77
- end
78
-
79
- # Fetch a smash key from the given deeply-nested hash.
80
- #
81
- # @overload smash_fetch(hsh, smash_key, default, options = {})
82
- # @param hsh [Hash<#to_s,Object>]
83
- # @param smash_key [#to_s]
84
- # @param default [Object] returned if smash key not found
85
- # @param options (see Flatten::UtilityMethods#smash)
86
- # @return [Object]
87
- # @overload smash_fetch(hsh, smash_key, options = {}, &block)
88
- # @param hsh [Hash<#to_s,Object>]
89
- # @param smash_key [#to_s]
90
- # @param options (see Flatten::UtilityMethods#smash)
91
- # @yieldreturn is returned if key not found
92
- # @return [Object]
93
- # @overload smash_fetch(hsh, smash_key, options = {})
94
- # @param hsh [Hash<#to_s,Object>]
95
- # @param smash_key [#to_s]
96
- # @param options (see Flatten::UtilityMethods#smash)
97
- # @raise KeyError if key not found
98
- # @return [Object]
99
- def smash_fetch(hsh, smash_key, *args, &block)
100
- options = ( args.last.kind_of?(Hash) ? args.pop : {})
101
- default = args.pop
102
-
103
- separator = options.fetch(:separator, DEFAULT_SEPARATOR)
104
-
105
- escaped_split(smash_key, separator).reduce(hsh) do |memo, kp|
106
- if memo.kind_of?(Hash) and memo.has_key?(kp)
107
- memo.fetch(kp)
108
- elsif default
109
- return default
110
- elsif block_given?
111
- return yield
112
- else
113
- raise KeyError, smash_key
114
- end
115
- end
116
- end
117
-
118
- # Get a smash key from the given deeply-nested hash, or return nil
119
- # if key not found.
120
- #
121
- # Worth noting is that Hash#default_proc is *not* used, as the intricacies
122
- # of implementation would lead to all sorts of terrible surprises.
123
- #
124
- # @param hsh [Hash<#to_s,Object>]
125
- # @param smash_key [#to_s]
126
- # @param options (see Flatten::UtilityMethods#smash)
127
- # @return [Object]
128
- def smash_get(hsh, smash_key, options = {})
129
- smash_fetch(hsh, smash_key, options) { nil }
130
- end
131
-
132
- # Given a smash hash, unflatten a subset by address, returning
133
- # a *modified copy* of the original smash hash.
134
- #
135
- # @overload expand(smash_hsh, smash_key, options = {}, &block)
136
- # @param smash_hsh [Hash{String=>Object}]
137
- # @param smash_key [String]
138
- # @param options (see Flatten::UtilityMethods#smash)
139
- # @return [Object]
140
- #
141
- # @example
142
- # ~~~ ruby
143
- # smash = {'a.b' => 2, 'a.c.d' => 4, 'a.c.e' => 3, 'b.f' => 4}
144
- # Flatten::expand(smash, 'a.c')
145
- # # => {'a.b' => 2, 'a.c' => {'d' => 4, 'e' => 3}, 'b.f' => 4}
146
- # ~~~
147
- def expand(smash_hsh, smash_key, *args)
148
- # if smash_hsh includes our key, its value is already expanded.
149
- return smash_hsh if smash_hsh.include?(smash_key)
150
-
151
- options = (args.last.kind_of?(Hash) ? args.pop : {})
152
- separator = options.fetch(:separator, DEFAULT_SEPARATOR)
153
- pattern = /\A#{Regexp.escape(smash_key)}#{Regexp.escape(separator)}/i
154
-
155
- match = {}
156
- unmatch = {}
157
- smash_hsh.each do |k, v|
158
- if pattern =~ k
159
- sk = k.gsub(pattern, '')
160
- match[sk] = v
161
- else
162
- unmatch[k] = v
163
- end
164
- end
165
-
166
- unmatch.update(smash_key => unsmash(match, options)) unless match.empty?
167
- unmatch
168
- end
169
-
170
- # Given a smash hash, unflatten a subset by address *in place*
171
- # (@see Flatten::UtilityMethods#expand)
172
- def expand!(smash_hsh, *args)
173
- smash_hsh.replace expand(smash_hsh, *args)
174
- end
175
-
176
- private
177
-
178
- # Utility method for splitting a string by a separator into
179
- # non-escaped parts
180
- # @api private
181
- # @param str [String]
182
- # @param separator [String] single-character string
183
- # @return [Array<String>]
184
- def escaped_split(str, separator)
185
- Separator[separator].split(str)
186
- end
187
-
188
- # Utility method for joining a pre-escaped string with a not-yet escaped
189
- # string on a given separator, escaping the new part before joining.
190
- # @api private
191
- # @param pre_escaped_prefix [String]
192
- # @param new_part [String] - will be escaped before joining
193
- # @param separator [String] single-character string
194
- # @return [String]
195
- def escaped_join(pre_escaped_prefix, new_part, separator)
196
- Separator[separator].join(pre_escaped_prefix, new_part)
197
- end
198
- end
199
-
200
- extend UtilityMethods
4
+ # The Utility Methods provide a significant (~4x) performance increase
5
+ # over extend-ing instance methods everywhere we need them.
6
+ module UtilityMethods
7
+
8
+ # Provides a way to iterate through a deeply-nested hash as if it were
9
+ # a smash-hash. Used internally for generating and deconstructing smash
10
+ # hashes.
11
+ #
12
+ # @overload smash_each(hsh, options = {}, &block)
13
+ # Yields once per key in smash version of itself.
14
+ # @param hsh [Hash<#to_s,Object>]
15
+ # @param options (see Flatten::UtilityMethods#smash)
16
+ # @yieldparam [(smash_key,value)]
17
+ # @return [void]
18
+ # @overload smash_each(hsh, options = {})
19
+ # @param hsh [Hash<#to_s,Object>]
20
+ # @param options (see Flatten::UtilityMethods#smash)
21
+ # @return [Enumerator<(smash_key,value)>]
22
+ def smash_each(hsh, options = {}, &block)
23
+ return enum_for(:smash_each, hsh, options) unless block_given?
24
+
25
+ inherited_prefix = options.fetch(:prefix, nil)
26
+ separator = options.fetch(:separator, DEFAULT_SEPARATOR)
27
+ smash_array = options.fetch(:smash_array, false)
28
+
29
+ hsh.each do |partial_key, value|
30
+ key = escaped_join(inherited_prefix, partial_key.to_s, separator)
31
+ if value.kind_of?(Hash) && !value.empty?
32
+ smash_each(value, options.merge(prefix: key), &block)
33
+ elsif smash_array && value.kind_of?(Array) && !value.empty?
34
+ zps = (smash_array == :zero_pad ? "%0#{value.count.to_s.size}d" : '%d') # zero-pad string
35
+ smash_each(value.count.times.map(&zps.method(:%)).zip(value), options.merge(prefix: key), &block)
36
+ else
37
+ yield key, value
38
+ end
39
+ end
40
+ end
41
+
42
+ # Returns a smash version of the given hash
43
+ #
44
+ # @param hsh [Hash<#to_s,Object>]
45
+ # @param options (see Flatten::UtilityMethods#smash)
46
+ # @return [Hash<String,Object>]
47
+ def smash(hsh, options = {})
48
+ enum = smash_each(hsh, options)
49
+ enum.each_with_object(Hash.new) do |(key, value), memo|
50
+ a = key.split('.');
51
+ flat_key = a[a.size - 1]
52
+ if flat_key
53
+ key = flat_key
54
+ end
55
+ memo[key] = value
56
+ end
57
+ end
58
+
59
+ # Returns a deeply-nested version of the given smash hash
60
+ # @param hsh [Hash<#to_s,Object>]
61
+ # @param options (see Flatten::UtilityMethods#smash)
62
+ # @return [Hash<String,Object>]
63
+ def unsmash(hsh, options = {})
64
+ separator = options.fetch(:separator, DEFAULT_SEPARATOR)
65
+ hsh.each_with_object({}) do |(k, v), memo|
66
+ current = memo
67
+ key = escaped_split(k, separator)
68
+ puts "key: #{key}"
69
+ up_next = partial = key.shift
70
+ until key.size.zero?
71
+ up_next = key.shift
72
+ up_next = up_next.to_i if (up_next =~ /\A[0-9]+\Z/)
73
+ current = (current[partial] ||= (up_next.kind_of?(Integer) ? [] : {}))
74
+ case up_next
75
+ when Integer then
76
+ raise KeyError unless current.kind_of?(Array)
77
+ else
78
+ raise KeyError unless current.kind_of?(Hash)
79
+ end
80
+ partial = up_next
81
+ end
82
+ current[up_next] = v
83
+ end
84
+ end
85
+
86
+ # Fetch a smash key from the given deeply-nested hash.
87
+ #
88
+ # @overload smash_fetch(hsh, smash_key, default, options = {})
89
+ # @param hsh [Hash<#to_s,Object>]
90
+ # @param smash_key [#to_s]
91
+ # @param default [Object] returned if smash key not found
92
+ # @param options (see Flatten::UtilityMethods#smash)
93
+ # @return [Object]
94
+ # @overload smash_fetch(hsh, smash_key, options = {}, &block)
95
+ # @param hsh [Hash<#to_s,Object>]
96
+ # @param smash_key [#to_s]
97
+ # @param options (see Flatten::UtilityMethods#smash)
98
+ # @yieldreturn is returned if key not found
99
+ # @return [Object]
100
+ # @overload smash_fetch(hsh, smash_key, options = {})
101
+ # @param hsh [Hash<#to_s,Object>]
102
+ # @param smash_key [#to_s]
103
+ # @param options (see Flatten::UtilityMethods#smash)
104
+ # @raise KeyError if key not found
105
+ # @return [Object]
106
+ def smash_fetch(hsh, smash_key, *args, &block)
107
+ options = (args.last.kind_of?(Hash) ? args.pop : {})
108
+ default = args.pop
109
+
110
+ separator = options.fetch(:separator, DEFAULT_SEPARATOR)
111
+
112
+ escaped_split(smash_key, separator).reduce(hsh) do |memo, kp|
113
+ if memo.kind_of?(Hash) and memo.has_key?(kp)
114
+ memo.fetch(kp)
115
+ elsif default
116
+ return default
117
+ elsif block_given?
118
+ return yield
119
+ else
120
+ raise KeyError, smash_key
121
+ end
122
+ end
123
+ end
124
+
125
+ # Get a smash key from the given deeply-nested hash, or return nil
126
+ # if key not found.
127
+ #
128
+ # Worth noting is that Hash#default_proc is *not* used, as the intricacies
129
+ # of implementation would lead to all sorts of terrible surprises.
130
+ #
131
+ # @param hsh [Hash<#to_s,Object>]
132
+ # @param smash_key [#to_s]
133
+ # @param options (see Flatten::UtilityMethods#smash)
134
+ # @return [Object]
135
+ def smash_get(hsh, smash_key, options = {})
136
+ smash_fetch(hsh, smash_key, options) { nil }
137
+ end
138
+
139
+ # Given a smash hash, unflatten a subset by address, returning
140
+ # a *modified copy* of the original smash hash.
141
+ #
142
+ # @overload expand(smash_hsh, smash_key, options = {}, &block)
143
+ # @param smash_hsh [Hash{String=>Object}]
144
+ # @param smash_key [String]
145
+ # @param options (see Flatten::UtilityMethods#smash)
146
+ # @return [Object]
147
+ #
148
+ # @example
149
+ # ~~~ ruby
150
+ # smash = {'a.b' => 2, 'a.c.d' => 4, 'a.c.e' => 3, 'b.f' => 4}
151
+ # Flatten::expand(smash, 'a.c')
152
+ # # => {'a.b' => 2, 'a.c' => {'d' => 4, 'e' => 3}, 'b.f' => 4}
153
+ # ~~~
154
+ def expand(smash_hsh, smash_key, *args)
155
+ # if smash_hsh includes our key, its value is already expanded.
156
+ return smash_hsh if smash_hsh.include?(smash_key)
157
+
158
+ options = (args.last.kind_of?(Hash) ? args.pop : {})
159
+ separator = options.fetch(:separator, DEFAULT_SEPARATOR)
160
+ pattern = /\A#{Regexp.escape(smash_key)}#{Regexp.escape(separator)}/i
161
+
162
+ match = {}
163
+ unmatch = {}
164
+ smash_hsh.each do |k, v|
165
+ if pattern =~ k
166
+ sk = k.gsub(pattern, '')
167
+ match[sk] = v
168
+ else
169
+ unmatch[k] = v
170
+ end
171
+ end
172
+
173
+ unmatch.update(smash_key => unsmash(match, options)) unless match.empty?
174
+ unmatch
175
+ end
176
+
177
+ # Given a smash hash, unflatten a subset by address *in place*
178
+ # (@see Flatten::UtilityMethods#expand)
179
+ def expand!(smash_hsh, *args)
180
+ smash_hsh.replace expand(smash_hsh, *args)
181
+ end
182
+
183
+ private
184
+
185
+ # Utility method for splitting a string by a separator into
186
+ # non-escaped parts
187
+ # @api private
188
+ # @param str [String]
189
+ # @param separator [String] single-character string
190
+ # @return [Array<String>]
191
+ def escaped_split(str, separator)
192
+ Separator[separator].split(str)
193
+ end
194
+
195
+ # Utility method for joining a pre-escaped string with a not-yet escaped
196
+ # string on a given separator, escaping the new part before joining.
197
+ # @api private
198
+ # @param pre_escaped_prefix [String]
199
+ # @param new_part [String] - will be escaped before joining
200
+ # @param separator [String] single-character string
201
+ # @return [String]
202
+ def escaped_join(pre_escaped_prefix, new_part, separator)
203
+ Separator[separator].join(pre_escaped_prefix, new_part)
204
+ end
205
+ end
206
+
207
+ extend UtilityMethods
201
208
  end
@@ -1,3 +1,3 @@
1
1
  module Flatten
2
- VERSION = "0.1.1"
2
+ VERSION = "0.2.0"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: flatten
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - l3x
@@ -73,7 +73,9 @@ files:
73
73
  - lib/flatten/core_ext/kernel.rb
74
74
  - lib/flatten/deprecations.rb
75
75
  - lib/flatten/guard_methods.rb
76
+ - lib/flatten/hash.rb
76
77
  - lib/flatten/separator.rb
78
+ - lib/flatten/string.rb
77
79
  - lib/flatten/utility_methods.rb
78
80
  - lib/flatten/version.rb
79
81
  homepage: https://github.com/l3x/flatten