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.
@@ -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.
@@ -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 hashes and/or arrays.
38
+ # of nested hash- and/or array-like objects.
34
39
  #
35
40
  # xfetch(key1, ..., keyN [, option_hash])
36
41
  #
37
- # Options:
42
+ # Options:
38
43
  #
39
44
  # :else => default value
40
- # The default value to return if the specified keys do not exist.
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
- # Raise a KeyError or IndexError if the specified keys do not
45
- # exist. This is the default behavior for xfetch in the absence
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] and options[:raise] != true
65
+ if options[:raise] && options[:raise] != true
59
66
  raise *options[:raise]
60
- elsif options[:raise] or !options.has_key? :else
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
- # [key] returns the hash or array element (or range-based array slice)
72
- # as normal.
78
+ # [key] or [range] returns the normal hash or array element (or
79
+ # range-based array slice).
73
80
  #
74
- # array[int1, int2] returns a length-based array slice as normal.
75
- # Append an option hash to force nested index behavior for two
76
- # integer array indexes: array[index1, index2, {}].
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
- # [key1, ..., keyN[, option_hash]] traverses a tree of nested
79
- # hashes and/or arrays using xfetch.
85
+ # [key1, ..., keyN[, option_hash]] traverses a tree of nested
86
+ # hash- and/or array-like objects using xfetch.
80
87
  #
81
- # Option :else => nil is used if no :else option is supplied.
82
- # See xfetch for option details.
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 or (self.is_a?(Array) and args.count == 2 and
85
- args[0].is_a?(Integer) and args[1].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 super should
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
- # _xset(key1, ..., keyN[, options_hash], value) { |key, options| block }
119
+ # xkeys_new(key2, info_hash, option_hash)
108
120
  #
109
- # The block should return true to auto-vivify an array or false to
110
- # auto-vivify a hash.
121
+ # where info_hash contains
111
122
  #
112
- # Options:
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 :[] auto-indexing
116
- def _xset (*args)
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 self.is_a?(Array) && args[0] == :[]
122
- self << args[-1] # array[:[]] = value
123
- else return false # [key] = value ==> super
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 |node, key|
128
- if yield key, options
129
- node[0][node[1]] ||= []
130
- [node[0][node[1]], (key != :[]) ? key :
131
- node[0][node[1]].size]
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
- node[0][node[1]] ||= {}
134
- [node[0][node[1]], key]
170
+ # Traverse an existing node
171
+ [nk1[0][nk1[1]], k2]
135
172
  end
136
173
  end
137
- if yield key, options
138
- node[(key != :[])? key : node.size] = args[-1]
139
- else node[key] = args[-1]
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
- # root[key1, ..., keyN[, options_hash]] = value
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 _xset(*args) do |key, opts|
158
- key == :[] and opts[:[]] != false
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
- # root[key1, ..., keyN[, options_hash]] = value
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 _xset(*args) do |key, opts|
177
- (key == :[] and opts[:[]] != false) or key.is_a?(Integer)
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
@@ -1,7 +1,7 @@
1
1
  require 'minitest/autorun'
2
2
  require 'xkeys'
3
3
 
4
- class TestXK < MiniTest::Unit::TestCase
4
+ class TestXK_01 < MiniTest::Unit::TestCase
5
5
 
6
6
  def test_hash_get
7
7
  h = { :a => 'a', :b => { :c => 'bc' },
@@ -1,7 +1,7 @@
1
1
  require 'minitest/autorun'
2
2
  require 'xkeys'
3
3
 
4
- class TestXK < MiniTest::Unit::TestCase
4
+ class TestXK_02 < MiniTest::Unit::TestCase
5
5
 
6
6
  def test_hash_set_hash
7
7
  h = {}.extend XKeys::Set_Hash
@@ -1,7 +1,7 @@
1
1
  require 'minitest/autorun'
2
2
  require 'xkeys'
3
3
 
4
- class TestXK < MiniTest::Unit::TestCase
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
@@ -1,7 +1,7 @@
1
1
  require 'minitest/autorun'
2
2
  require 'xkeys'
3
3
 
4
- class TestXK < MiniTest::Unit::TestCase
4
+ class TestXK_04 < MiniTest::Unit::TestCase
5
5
 
6
6
  def test_hash
7
7
  a = [].extend XKeys::Hash
@@ -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
@@ -1,7 +1,7 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = "xkeys"
3
- s.version = "1.0.1"
4
- s.date = "2013-07-25"
3
+ s.version = "2.0.0"
4
+ s.date = "2014-03-21"
5
5
  s.authors = ["Brian Katzung"]
6
6
  s.email = ["briank@kappacs.com"]
7
7
  s.homepage = "http://rubygems.org/gems/xkeys"
metadata CHANGED
@@ -3,10 +3,10 @@ name: xkeys
3
3
  version: !ruby/object:Gem::Version
4
4
  prerelease: false
5
5
  segments:
6
- - 1
6
+ - 2
7
7
  - 0
8
- - 1
9
- version: 1.0.1
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: 2013-07-25 00:00:00 -05:00
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