xkeys 1.0.1 → 2.0.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/HISTORY.txt +17 -0
- data/lib/xkeys.rb +112 -49
- data/test/01get.rb +1 -1
- data/test/02hash.rb +1 -1
- data/test/03auto.rb +4 -1
- data/test/04mod_combos.rb +1 -1
- data/test/05xkeys_new.rb +18 -0
- data/xkeys.gemspec +2 -2
- metadata +6 -4
data/HISTORY.txt
CHANGED
@@ -1,4 +1,19 @@
|
|
1
|
+
2014-03-21 Version 2.0.0
|
2
|
+
|
3
|
+
XKeys can now be used with data types other than array or hash
|
4
|
+
(as long as they support array- or hash-like interfaces).
|
5
|
+
|
6
|
+
New custom nodes can be generated by defining an #xkeys_new
|
7
|
+
method. The method is responsible for adding XKeys extensions to
|
8
|
+
the new node as needed/desired.
|
9
|
+
|
10
|
+
See Sarah::XK in gem sarah-xk for an example.
|
11
|
+
|
12
|
+
Using :[] (push mode) as the first of multiple keys now works
|
13
|
+
correctly.
|
14
|
+
|
1
15
|
2013-07-25 Version 1.0.1
|
16
|
+
|
2
17
|
The auto-indexing array key for set has been changed from nil to
|
3
18
|
:[] in order to support nil keys (e.g. in case a NULL value from a
|
4
19
|
database field is being used as a nil key).
|
@@ -23,7 +38,9 @@
|
|
23
38
|
exception.
|
24
39
|
|
25
40
|
2013-07-08 Version 0.0.2
|
41
|
+
|
26
42
|
Packaging and documentation cleanup.
|
27
43
|
|
28
44
|
2013-07-08 Version 0.0.1
|
45
|
+
|
29
46
|
First release.
|
data/lib/xkeys.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
# XKeys - Extended keys to facilitate fetching and storing in nested
|
2
|
-
# hash and array structures with Perl-ish auto-vivification.
|
2
|
+
# hash- and array-like structures with Perl-ish auto-vivification.
|
3
3
|
#
|
4
4
|
# Synopsis:
|
5
5
|
# root = {}.extend XKeys::Hash
|
@@ -20,8 +20,13 @@
|
|
20
20
|
# root[1, 0, {}] # => 'value 1'
|
21
21
|
# root[1, 4, {}] # => nil
|
22
22
|
#
|
23
|
+
# As of version 2, other types with array- or hash-like behavior are
|
24
|
+
# supported as well.
|
25
|
+
#
|
26
|
+
# Version 2.0.0 2014-03-21
|
27
|
+
#
|
23
28
|
# @author Brian Katzung <briank@kappacs.com>, Kappa Computer Solutions, LLC
|
24
|
-
# @copyright 2013 Brian Katzung and Kappa Computer Solutions, LLC
|
29
|
+
# @copyright 2013-2014 Brian Katzung and Kappa Computer Solutions, LLC
|
25
30
|
# @license MIT License
|
26
31
|
|
27
32
|
module XKeys; end
|
@@ -30,20 +35,21 @@ module XKeys; end
|
|
30
35
|
module XKeys::Get
|
31
36
|
|
32
37
|
# Perform an extended fetch using successive keys to traverse a tree
|
33
|
-
# of nested
|
38
|
+
# of nested hash- and/or array-like objects.
|
34
39
|
#
|
35
40
|
# xfetch(key1, ..., keyN [, option_hash])
|
36
41
|
#
|
37
|
-
#
|
42
|
+
# Options:
|
38
43
|
#
|
39
44
|
# :else => default value
|
40
|
-
# The default value to return if the
|
45
|
+
# The default value to return if any of the keys do not exist
|
46
|
+
# (when an underlying #fetch generates a KeyError or IndexError).
|
41
47
|
# The :raise option takes precedence.
|
42
48
|
#
|
43
49
|
# :raise => true
|
44
|
-
#
|
45
|
-
# exist. This is the default behavior for xfetch in the
|
46
|
-
# of an :else option.
|
50
|
+
# Re-raise the original KeyError or IndexError if any of the keys
|
51
|
+
# do not exist. This is the default behavior for xfetch in the
|
52
|
+
# absence of an :else option.
|
47
53
|
#
|
48
54
|
# :raise => *parameters
|
49
55
|
# Like :raise => true but does raise *parameters instead, e.g.
|
@@ -52,12 +58,13 @@ module XKeys::Get
|
|
52
58
|
if args[-1].is_a?(Hash) then options, last = args[-1], -2
|
53
59
|
else options, last = {}, -1
|
54
60
|
end
|
61
|
+
|
55
62
|
args[0..last].inject(self) do |node, key|
|
56
63
|
begin node.fetch key
|
57
64
|
rescue KeyError, IndexError
|
58
|
-
if options[:raise]
|
65
|
+
if options[:raise] && options[:raise] != true
|
59
66
|
raise *options[:raise]
|
60
|
-
elsif options[:raise]
|
67
|
+
elsif options[:raise] || !options.has_key?(:else)
|
61
68
|
raise
|
62
69
|
else return options[:else]
|
63
70
|
end
|
@@ -68,21 +75,21 @@ module XKeys::Get
|
|
68
75
|
# Perform an extended get using successive keys to traverse a tree of
|
69
76
|
# nested hashes and/or arrays.
|
70
77
|
#
|
71
|
-
#
|
72
|
-
#
|
78
|
+
# [key] or [range] returns the normal hash or array element (or
|
79
|
+
# range-based array slice).
|
73
80
|
#
|
74
|
-
#
|
75
|
-
#
|
76
|
-
#
|
81
|
+
# [int1, int2] for arrays (or other objects responding to the #slice
|
82
|
+
# method) returns the object's normal two-parameter (e.g. start + length
|
83
|
+
# slice) index value.
|
77
84
|
#
|
78
|
-
#
|
79
|
-
#
|
85
|
+
# [key1, ..., keyN[, option_hash]] traverses a tree of nested
|
86
|
+
# hash- and/or array-like objects using xfetch.
|
80
87
|
#
|
81
|
-
#
|
82
|
-
#
|
88
|
+
# Option :else => nil is used if no :else option is supplied.
|
89
|
+
# See xfetch for option details.
|
83
90
|
def [] (*args)
|
84
|
-
if args.count == 1
|
85
|
-
args[0].is_a?(Integer)
|
91
|
+
if args.count == 1 || (respond_to?(:slice) && args.count == 2 &&
|
92
|
+
args[0].is_a?(Integer) && args[1].is_a?(Integer))
|
86
93
|
# [key] or array[start, length]
|
87
94
|
super *args
|
88
95
|
else
|
@@ -101,45 +108,97 @@ end
|
|
101
108
|
module XKeys::Set_
|
102
109
|
|
103
110
|
# Common code for XKeys::Set_Hash and XKeys::Set_Auto. This method
|
104
|
-
# returns true if it is handling the set, or false if
|
105
|
-
# handle the set.
|
111
|
+
# returns true if it is handling the set, or false if the caller
|
112
|
+
# should super to handle the set.
|
113
|
+
#
|
114
|
+
# _xkeys_set(key1, ..., keyN[, option_hash], value) { |key, options| block }
|
115
|
+
#
|
116
|
+
# If the root of the tree responds to the #xkeys_new method, it will
|
117
|
+
# be called as follows whenever a new node needs to be created:
|
106
118
|
#
|
107
|
-
#
|
119
|
+
# xkeys_new(key2, info_hash, option_hash)
|
108
120
|
#
|
109
|
-
#
|
110
|
-
# auto-vivify a hash.
|
121
|
+
# where info_hash contains
|
111
122
|
#
|
112
|
-
#
|
123
|
+
# :node => The current node
|
124
|
+
# :key1 => The key in the current node, or :[]
|
125
|
+
# :block => The block passed to _xkeys_set
|
126
|
+
#
|
127
|
+
# The returned new node will be assigned to node[key1] (or pushed onto
|
128
|
+
# the end of the array) and should be appropriate to accept key2.
|
129
|
+
#
|
130
|
+
# Otherwise, the block should return true for array-like keys or false
|
131
|
+
# for hash-like keys. An array or hash node will be added accordingly.
|
132
|
+
#
|
133
|
+
# If a key is :[], the current node responds to the #push method, and
|
134
|
+
# push mode has not been disabled (see below), a new node will be
|
135
|
+
# pushed onto the end of the current node.
|
136
|
+
#
|
137
|
+
# Options:
|
113
138
|
#
|
114
139
|
# :[] => false
|
115
|
-
# Disable :[]
|
116
|
-
def
|
140
|
+
# Disable :[] push mode
|
141
|
+
def _xkeys_set (*args, &block)
|
117
142
|
if args[-2].is_a?(Hash) then options, last = args[-2], -3
|
118
143
|
else options, last = {}, -2
|
119
144
|
end
|
145
|
+
|
146
|
+
push_mode = options[:[]] != false
|
147
|
+
|
120
148
|
if args.count + last == 0
|
121
|
-
if
|
122
|
-
|
123
|
-
|
149
|
+
if args[0] == :[] && push_mode && respond_to?(:push)
|
150
|
+
push args[-1] # array[:[]] = value
|
151
|
+
true # done--don't caller-super
|
152
|
+
else false # use caller-super to do it
|
124
153
|
end
|
125
154
|
else
|
126
155
|
# root[key1, ..., keyN[, option_hash]] = value
|
127
|
-
(node, key) = args[1..last].inject([self, args[0]]) do |
|
128
|
-
if
|
129
|
-
node
|
130
|
-
|
131
|
-
|
156
|
+
(node, key) = args[1..last].inject([self, args[0]]) do |nk1, k2|
|
157
|
+
if nk1[1] == :[] && push_mode && nk1[0].respond_to?(:push)
|
158
|
+
# Push a new node onto an array-like node
|
159
|
+
node = _xkeys_new(k2, { :node => nk1[0],
|
160
|
+
:key1 => nk1[1], :block => block }, options)
|
161
|
+
nk1[0].push node
|
162
|
+
[node, k2]
|
163
|
+
elsif nk1[0][nk1[1]].nil?
|
164
|
+
# Auto-vivify the specified key/index
|
165
|
+
node = _xkeys_new(k2, { :node => nk1[0],
|
166
|
+
:key1 => nk1[1], :block => block }, options)
|
167
|
+
nk1[0][nk1[1]] = node
|
168
|
+
[node, k2]
|
132
169
|
else
|
133
|
-
|
134
|
-
[
|
170
|
+
# Traverse an existing node
|
171
|
+
[nk1[0][nk1[1]], k2]
|
135
172
|
end
|
136
173
|
end
|
137
|
-
|
138
|
-
|
139
|
-
|
174
|
+
|
175
|
+
# Assign (or push) according to the final key.
|
176
|
+
if key == :[] && push_mode && node.respond_to?(:push)
|
177
|
+
node.push args[-1]
|
178
|
+
else
|
179
|
+
node[key] = args[-1]
|
140
180
|
end
|
181
|
+
true # done--don't caller-super
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
# Return a new node for node[key1] suitable to hold key2.
|
186
|
+
# Either key1 or key2 (or both) may be :[].
|
187
|
+
def _xkeys_new (key2, info, options)
|
188
|
+
if respond_to? :xkeys_new
|
189
|
+
# Note: #xkeys_new is responsible for cloning extensions
|
190
|
+
# as desired or needed.
|
191
|
+
xkeys_new key2, info, options
|
192
|
+
else
|
193
|
+
node = info[:block].call(key2, options) ? [] : {}
|
194
|
+
|
195
|
+
# Clone XKeys extensions from the root node
|
196
|
+
node.extend XKeys::Get if is_a? XKeys::Get
|
197
|
+
node.extend XKeys::Set_Auto if is_a? XKeys::Set_Auto
|
198
|
+
node.extend XKeys::Set_Hash if is_a? XKeys::Set_Hash
|
199
|
+
|
200
|
+
node
|
141
201
|
end
|
142
|
-
true
|
143
202
|
end
|
144
203
|
|
145
204
|
end
|
@@ -152,10 +211,12 @@ module XKeys::Set_Hash
|
|
152
211
|
# assignment syntax. :[] keys create nested arrays as needed. Other
|
153
212
|
# keys, including integer keys, create nested hashes as needed.
|
154
213
|
#
|
155
|
-
#
|
214
|
+
# See XKeys::Set_ for additional information.
|
215
|
+
#
|
216
|
+
# root[key1, ..., keyN[, option_hash]] = value
|
156
217
|
def []= (*args)
|
157
|
-
super args[0], args[-1] unless
|
158
|
-
key == :[]
|
218
|
+
super args[0], args[-1] unless _xkeys_set(*args) do |key, options|
|
219
|
+
key == :[] && options[:[]] != false
|
159
220
|
end
|
160
221
|
args[-1]
|
161
222
|
end
|
@@ -171,10 +232,12 @@ module XKeys::Set_Auto
|
|
171
232
|
# create nested arrays as needed. Other keys create nested hashes
|
172
233
|
# as needed.
|
173
234
|
#
|
174
|
-
#
|
235
|
+
# See XKeys::Set_ for additional information.
|
236
|
+
#
|
237
|
+
# root[key1, ..., keyN[, option_hash]] = value
|
175
238
|
def []= (*args)
|
176
|
-
super args[0], args[-1] unless
|
177
|
-
(key == :[]
|
239
|
+
super args[0], args[-1] unless _xkeys_set(*args) do |key, options|
|
240
|
+
(key == :[] && options[:[]] != false) || key.is_a?(Integer)
|
178
241
|
end
|
179
242
|
args[-1]
|
180
243
|
end
|
data/test/01get.rb
CHANGED
data/test/02hash.rb
CHANGED
data/test/03auto.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
require 'minitest/autorun'
|
2
2
|
require 'xkeys'
|
3
3
|
|
4
|
-
class
|
4
|
+
class TestXK_03 < MiniTest::Unit::TestCase
|
5
5
|
|
6
6
|
def test_hash_set_auto
|
7
7
|
h = {}.extend XKeys::Set_Auto
|
@@ -34,6 +34,9 @@ class TestXK < MiniTest::Unit::TestCase
|
|
34
34
|
|
35
35
|
a[0, :[]] = '02'
|
36
36
|
assert_equal([ [ nil, '01', '02' ] ], a, "a[0, :[]] = '02'")
|
37
|
+
|
38
|
+
a.clear; a[0] = ?0; a[:[], 1] = ?1;
|
39
|
+
assert_equal([ ?0, [ nil, ?1 ] ], a, "a[:[], 1]")
|
37
40
|
end
|
38
41
|
|
39
42
|
end
|
data/test/04mod_combos.rb
CHANGED
data/test/05xkeys_new.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
require 'xkeys'
|
3
|
+
|
4
|
+
module MyNew
|
5
|
+
|
6
|
+
def xkeys_new (*args); "abcde"; end
|
7
|
+
|
8
|
+
end
|
9
|
+
|
10
|
+
class TestXK_05 < MiniTest::Unit::TestCase
|
11
|
+
|
12
|
+
def test_xkeys_new
|
13
|
+
a = [].extend(XKeys::Set_Auto).extend(MyNew)
|
14
|
+
a[0, 2] = 'X'
|
15
|
+
assert_equal([ "abXde" ], a, "a[0, 2] = 'X'")
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
data/xkeys.gemspec
CHANGED
metadata
CHANGED
@@ -3,10 +3,10 @@ name: xkeys
|
|
3
3
|
version: !ruby/object:Gem::Version
|
4
4
|
prerelease: false
|
5
5
|
segments:
|
6
|
-
-
|
6
|
+
- 2
|
7
7
|
- 0
|
8
|
-
-
|
9
|
-
version:
|
8
|
+
- 0
|
9
|
+
version: 2.0.0
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Brian Katzung
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date:
|
17
|
+
date: 2014-03-21 00:00:00 -05:00
|
18
18
|
default_executable:
|
19
19
|
dependencies: []
|
20
20
|
|
@@ -34,6 +34,7 @@ files:
|
|
34
34
|
- HISTORY.txt
|
35
35
|
- test/03auto.rb
|
36
36
|
- test/01get.rb
|
37
|
+
- test/05xkeys_new.rb
|
37
38
|
- test/04mod_combos.rb
|
38
39
|
- test/02hash.rb
|
39
40
|
has_rdoc: true
|
@@ -71,5 +72,6 @@ summary: Extended keys to facilitate fetching and storing in nested hash and arr
|
|
71
72
|
test_files:
|
72
73
|
- test/03auto.rb
|
73
74
|
- test/01get.rb
|
75
|
+
- test/05xkeys_new.rb
|
74
76
|
- test/04mod_combos.rb
|
75
77
|
- test/02hash.rb
|