xkeys 1.0.1 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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