xkeys 0.0.1
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/lib/xkeys.rb +137 -0
- data/test/01get.rb +60 -0
- data/test/02hash.rb +38 -0
- data/test/03auto.rb +38 -0
- data/test/04mod_combos.rb +20 -0
- data/test/run_tests.sh +8 -0
- data/xkeys.gemspec +14 -0
- metadata +75 -0
data/lib/xkeys.rb
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# XKeys - Extended keys to facilitate fetching and storing in nested
|
|
2
|
+
# hash and array structures with Perl-ish auto-vivification.
|
|
3
|
+
#
|
|
4
|
+
# Synopsis:
|
|
5
|
+
# root = {}.extend XKeys::Hash
|
|
6
|
+
# root = [].extend XKeys::Auto
|
|
7
|
+
#
|
|
8
|
+
# Author:: Brian Katzung <briank@kappacs.com>, Kappa Computer Solutions, LLC
|
|
9
|
+
# Copyright:: 2013 Brian Katzung and Kappa Computer Solutions, LLC
|
|
10
|
+
# License:: MIT License
|
|
11
|
+
|
|
12
|
+
module XKeys; end
|
|
13
|
+
|
|
14
|
+
# Extended fetch and get ([])
|
|
15
|
+
module XKeys::Get
|
|
16
|
+
|
|
17
|
+
# Perform an extended fetch using successive keys to traverse a tree
|
|
18
|
+
# of nested hashes and/or arrays.
|
|
19
|
+
#
|
|
20
|
+
# xfetch([nil,] key1, ..., keyN [, :else => default_value])
|
|
21
|
+
#
|
|
22
|
+
# An optional leading nil key is ignored (see []). If the specified
|
|
23
|
+
# keys do not exist, the default value is returned (if provided) or
|
|
24
|
+
# the standard exception (e.g. KeyError or IndexError) is raised.
|
|
25
|
+
def xfetch (*args)
|
|
26
|
+
if args[-1].is_a?(Hash) then options, last = args[-1], -2
|
|
27
|
+
else options, last = {}, -1
|
|
28
|
+
end
|
|
29
|
+
first = (args[0] == nil) ? 1 : 0
|
|
30
|
+
args[first..last].inject(self) do |node, key|
|
|
31
|
+
begin node.fetch key
|
|
32
|
+
rescue KeyError, IndexError
|
|
33
|
+
return options[:else] if options.has_key? :else
|
|
34
|
+
raise
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Perform an extended get using successive keys to traverse a tree of
|
|
40
|
+
# nested hashes and/or arrays.
|
|
41
|
+
#
|
|
42
|
+
# [key] returns the hash or array element (or range-based array slice)
|
|
43
|
+
# as normal.
|
|
44
|
+
#
|
|
45
|
+
# array[int1, int2] returns a length-based array slice as normal.
|
|
46
|
+
# Prepend a nil key and/or append an option hash to force nested index
|
|
47
|
+
# behavior for two integer array indexes: array[nil, index1, index2].
|
|
48
|
+
#
|
|
49
|
+
# [[nil,] key1, ..., keyN[, option_hash]] traverses a tree of nested
|
|
50
|
+
# hashes and/or arrays using xfetch. The optional leading nil key is
|
|
51
|
+
# always ignored. In the absence of an option hash, the default is
|
|
52
|
+
# :else => nil.
|
|
53
|
+
def [] (*args)
|
|
54
|
+
if args.count == 1 || (self.is_a?(Array) && args.count == 2 &&
|
|
55
|
+
args[0].is_a?(Integer) && args[1].is_a?(Integer))
|
|
56
|
+
# [key] or array[start, length]
|
|
57
|
+
super *args
|
|
58
|
+
elsif args[-1].is_a?(Hash) then xfetch(*args)
|
|
59
|
+
else xfetch(*args, :else => nil)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# "Private" module for XKeys::Set_* common code
|
|
66
|
+
module XKeys::Set_
|
|
67
|
+
|
|
68
|
+
# Common code for XKeys::Set_Hash and XKeys::Set_Auto
|
|
69
|
+
def _xset (*args)
|
|
70
|
+
if args.count == 2
|
|
71
|
+
if self.is_a?(Array) && args[0] == nil
|
|
72
|
+
self << args[1] # array[nil] = value
|
|
73
|
+
else return false # [key] = value ==> super *args
|
|
74
|
+
end
|
|
75
|
+
else
|
|
76
|
+
# root[key1, ..., keyN] = value
|
|
77
|
+
(node, key) = args[1..-2].inject([self, args[0]]) do |node, key|
|
|
78
|
+
if yield key
|
|
79
|
+
node[0][node[1]] ||= []
|
|
80
|
+
[node[0][node[1]], key || node[0][node[1]].size]
|
|
81
|
+
else
|
|
82
|
+
node[0][node[1]] ||= {}
|
|
83
|
+
[node[0][node[1]], key]
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
if yield key then node[key || node.size] = args[-1]
|
|
87
|
+
else node[key] = args[-1]
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
true
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Extended set ([]=) with hash keys
|
|
96
|
+
module XKeys::Set_Hash
|
|
97
|
+
include XKeys::Set_
|
|
98
|
+
|
|
99
|
+
# Auto-vivify nested hash trees using extended hash key/array index
|
|
100
|
+
# assignment syntax. Nil keys create nested arrays as needed. Other
|
|
101
|
+
# keys, including integer keys, create nested hashes as needed.
|
|
102
|
+
#
|
|
103
|
+
# root[key1, ..., keyN] = value
|
|
104
|
+
def []= (*args)
|
|
105
|
+
super *args unless _xset(*args) { |key| key == nil }
|
|
106
|
+
args[-1]
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Extended set ([]=) with automatic selection of hash keys or array indexes
|
|
112
|
+
module XKeys::Set_Auto
|
|
113
|
+
include XKeys::Set_
|
|
114
|
+
|
|
115
|
+
# Auto-vivify nested hash and/or array trees using extended hash
|
|
116
|
+
# key/array index assignment syntax. Nil keys and integer keys
|
|
117
|
+
# created nested arrays as needed. Other keys create nested hashes
|
|
118
|
+
# as needed.
|
|
119
|
+
#
|
|
120
|
+
# root[key1, ..., keyN] = value
|
|
121
|
+
def []= (*args)
|
|
122
|
+
super *args unless
|
|
123
|
+
_xset(*args) { |key| key == nil || key.is_a?(Integer) }
|
|
124
|
+
args[-1]
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Combined interfaces
|
|
130
|
+
|
|
131
|
+
# XKeys::Hash combines XKeys::Get and XKeys::Set_Hash
|
|
132
|
+
module XKeys::Hash; include XKeys::Get; include XKeys::Set_Hash; end
|
|
133
|
+
|
|
134
|
+
# XKeys::Auto combines XKeys::Get and XKeys::Set_Auto
|
|
135
|
+
module XKeys::Auto; include XKeys::Get; include XKeys::Set_Auto; end
|
|
136
|
+
|
|
137
|
+
# END
|
data/test/01get.rb
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
require 'minitest/autorun'
|
|
2
|
+
require 'xkeys'
|
|
3
|
+
|
|
4
|
+
class TestXK < MiniTest::Unit::TestCase
|
|
5
|
+
|
|
6
|
+
def test_hash_get
|
|
7
|
+
h = { :a => 'a', :b => { :c => 'bc' },
|
|
8
|
+
:d => { :e => { :f => 'def' }}}.extend XKeys::Get
|
|
9
|
+
|
|
10
|
+
assert_respond_to(h, :xfetch)
|
|
11
|
+
assert_respond_to(h, :[])
|
|
12
|
+
|
|
13
|
+
assert_equal('a', h.xfetch(:a), 'h.xfetch :a')
|
|
14
|
+
assert_equal({ :c => 'bc' }, h.xfetch(:b), 'h.xfetch :b')
|
|
15
|
+
assert_equal('bc', h.xfetch(:b, :c), 'h.xfetch :b, :c')
|
|
16
|
+
|
|
17
|
+
assert_equal(false, h.xfetch(:b, :d, :else => false),
|
|
18
|
+
'h.xfetch :b, :d, :else => false')
|
|
19
|
+
assert_raises(KeyError, 'h.xfetch :b, :d') { h.xfetch :b, :d }
|
|
20
|
+
|
|
21
|
+
assert_equal('a', h[:a], 'h[:a]')
|
|
22
|
+
assert_equal({ :c => 'bc' }, h[:b], 'h[:b]')
|
|
23
|
+
assert_equal('bc', h[:b, :c], 'h[:b, :c]')
|
|
24
|
+
|
|
25
|
+
assert_equal(false, h[:b, :d, :else=>false], 'h[:b, :d, :else=>false]')
|
|
26
|
+
assert_equal(nil, h[:b, :d], 'h[:b, :d]')
|
|
27
|
+
assert_raises(KeyError, 'h[:b, :d, {}]') { h[:b, :d, {}] }
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def test_array_get
|
|
31
|
+
a = [ '0', [ '1.0' ], [ '2.0', [ '2.1.0', '2.1.1' ]]].extend XKeys::Get
|
|
32
|
+
|
|
33
|
+
assert_respond_to(a, :xfetch)
|
|
34
|
+
assert_respond_to(a, :[])
|
|
35
|
+
|
|
36
|
+
assert_equal('0', a.xfetch(0), 'a.xfetch 0')
|
|
37
|
+
assert_equal('1.0', a.xfetch(1, 0), 'a.xfetch 1, 0')
|
|
38
|
+
assert_equal('1.0', a.xfetch(nil, 1, 0), 'a.xfetch nil, 1, 0')
|
|
39
|
+
assert_equal('1.0', a.xfetch(1, 0, {}), 'a.xfetch 1, 0, {}')
|
|
40
|
+
assert_equal('2.1.1', a.xfetch(2, 1, 1), 'a.xfetch 2, 1, 1')
|
|
41
|
+
|
|
42
|
+
assert_equal(false, a.xfetch(1, 1, :else => false),
|
|
43
|
+
'a.xfetch 1, 1, :else => false')
|
|
44
|
+
assert_raises(IndexError, 'a.xfetch 1, 1') { a.xfetch 1, 1 }
|
|
45
|
+
|
|
46
|
+
assert_equal('0', a[0], 'a[0]')
|
|
47
|
+
assert_equal([['1.0']], a[1, 1], 'a[1, 1]')
|
|
48
|
+
assert_equal('1.0', a[nil, 1, 0], 'a[nil, 1, 0]')
|
|
49
|
+
assert_equal('1.0', a[1, 0, {}], 'a[1, 0, {}]')
|
|
50
|
+
assert_equal('1.0', a[nil, 1, 0, {}], 'a[nil, 1, 0, {}]')
|
|
51
|
+
assert_equal('2.1.1', a[2, 1, 1], 'a[2, 1, 1]')
|
|
52
|
+
|
|
53
|
+
assert_equal(false, a[1, 1, :else=>false], 'a[1, 1, :else=>false]')
|
|
54
|
+
assert_equal(nil, a[nil, 1, 1], 'a[nil, 1, 1]')
|
|
55
|
+
assert_raises(IndexError, 'a[1, 1, {}]') { a[1, 1, {}] }
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# END
|
data/test/02hash.rb
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
require 'minitest/autorun'
|
|
2
|
+
require 'xkeys'
|
|
3
|
+
|
|
4
|
+
class TestXK < MiniTest::Unit::TestCase
|
|
5
|
+
|
|
6
|
+
def test_hash_set_hash
|
|
7
|
+
h = {}.extend XKeys::Set_Hash
|
|
8
|
+
|
|
9
|
+
assert_respond_to(h, :[]=)
|
|
10
|
+
|
|
11
|
+
h[:a] = 'a'
|
|
12
|
+
assert_equal({ :a => 'a' }, h, "h[:a] = 'a'")
|
|
13
|
+
|
|
14
|
+
h.clear; h[:a, :b] = 'ab'
|
|
15
|
+
assert_equal({ :a => { :b => 'ab' }}, h, "h[:a, :b] = 'ab'")
|
|
16
|
+
|
|
17
|
+
h.clear; h[1, 2] = '12'
|
|
18
|
+
assert_equal({ 1 => { 2 => '12' }}, h, "h[1, 2] = '12'")
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def test_array_set_hash
|
|
22
|
+
a = [].extend XKeys::Set_Hash
|
|
23
|
+
|
|
24
|
+
assert_respond_to(a, :[]=)
|
|
25
|
+
|
|
26
|
+
a[0] = '0'
|
|
27
|
+
assert_equal(['0'], a, "a[0] = '0'")
|
|
28
|
+
|
|
29
|
+
a.clear; a[0, :a] = '0:a'
|
|
30
|
+
assert_equal([ { :a => '0:a' } ], a, "a[0, :a] = '0:a'")
|
|
31
|
+
|
|
32
|
+
a.clear; a[0, 1] = '01'
|
|
33
|
+
assert_equal([ { 1 => '01' } ], a, "a[0, 1] = '01'")
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# END
|
data/test/03auto.rb
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
require 'minitest/autorun'
|
|
2
|
+
require 'xkeys'
|
|
3
|
+
|
|
4
|
+
class TestXK < MiniTest::Unit::TestCase
|
|
5
|
+
|
|
6
|
+
def test_hash_set_auto
|
|
7
|
+
h = {}.extend XKeys::Set_Auto
|
|
8
|
+
|
|
9
|
+
assert_respond_to(h, :[]=)
|
|
10
|
+
|
|
11
|
+
h[:a] = 'a'
|
|
12
|
+
assert_equal({ :a => 'a' }, h, "h[:a] = 'a'")
|
|
13
|
+
|
|
14
|
+
h.clear; h[:a, :b] = 'ab'
|
|
15
|
+
assert_equal({ :a => { :b => 'ab' }}, h, "h[:a, :b] = 'ab'")
|
|
16
|
+
|
|
17
|
+
h.clear; h[1, 2] = '12'
|
|
18
|
+
assert_equal({ 1 => [ nil, nil, '12' ]}, h, "h[1, 2] = '12'")
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def test_array_set_auto
|
|
22
|
+
a = [].extend XKeys::Set_Auto
|
|
23
|
+
|
|
24
|
+
assert_respond_to(a, :[]=)
|
|
25
|
+
|
|
26
|
+
a[0] = '0'
|
|
27
|
+
assert_equal(['0'], a, "a[0] = '0'")
|
|
28
|
+
|
|
29
|
+
a.clear; a[0, :a] = '0:a'
|
|
30
|
+
assert_equal([ { :a => '0:a' } ], a, "a[0, :a] = '0:a'")
|
|
31
|
+
|
|
32
|
+
a.clear; a[0, 1] = '01'
|
|
33
|
+
assert_equal([ [ nil, '01' ] ], a, "a[0, 1] = '01'")
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# END
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
require 'minitest/autorun'
|
|
2
|
+
require 'xkeys'
|
|
3
|
+
|
|
4
|
+
class TestXK < MiniTest::Unit::TestCase
|
|
5
|
+
|
|
6
|
+
def test_hash
|
|
7
|
+
a = [].extend XKeys::Hash
|
|
8
|
+
assert_kind_of(XKeys::Get, a, 'Hash => Get')
|
|
9
|
+
assert_kind_of(XKeys::Set_Hash, a, 'Hash => Set_Hash')
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def test_auto
|
|
13
|
+
a = [].extend XKeys::Auto
|
|
14
|
+
assert_kind_of(XKeys::Get, a, 'Auto => Get')
|
|
15
|
+
assert_kind_of(XKeys::Set_Auto, a, 'Auto => Set_Auto')
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# END
|
data/test/run_tests.sh
ADDED
data/xkeys.gemspec
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
Gem::Specification.new do |s|
|
|
2
|
+
s.name = "xkeys"
|
|
3
|
+
s.version = "0.0.1"
|
|
4
|
+
s.authors = ["Brian Katzung"]
|
|
5
|
+
s.email = ["briank@kappacs.com"]
|
|
6
|
+
s.homepage = "http://rubygems.org/gems/xkeys"
|
|
7
|
+
s.summary = "Extended keys to facilitate fetching and storing in nested hash and array structures with Perl-ish auto-vivification."
|
|
8
|
+
s.description = "Extended keys to facilitate fetching and storing in nested hash and array structures with Perl-ish auto-vivification."
|
|
9
|
+
s.license = "MIT"
|
|
10
|
+
|
|
11
|
+
s.files = Dir.glob("lib/**/*") + %w{xkeys.gemspec}
|
|
12
|
+
s.test_files = Dir.glob("test/**/*")
|
|
13
|
+
s.require_path = 'lib'
|
|
14
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: xkeys
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
prerelease: false
|
|
5
|
+
segments:
|
|
6
|
+
- 0
|
|
7
|
+
- 0
|
|
8
|
+
- 1
|
|
9
|
+
version: 0.0.1
|
|
10
|
+
platform: ruby
|
|
11
|
+
authors:
|
|
12
|
+
- Brian Katzung
|
|
13
|
+
autorequire:
|
|
14
|
+
bindir: bin
|
|
15
|
+
cert_chain: []
|
|
16
|
+
|
|
17
|
+
date: 2013-07-08 00:00:00 -05:00
|
|
18
|
+
default_executable:
|
|
19
|
+
dependencies: []
|
|
20
|
+
|
|
21
|
+
description: Extended keys to facilitate fetching and storing in nested hash and array structures with Perl-ish auto-vivification.
|
|
22
|
+
email:
|
|
23
|
+
- briank@kappacs.com
|
|
24
|
+
executables: []
|
|
25
|
+
|
|
26
|
+
extensions: []
|
|
27
|
+
|
|
28
|
+
extra_rdoc_files: []
|
|
29
|
+
|
|
30
|
+
files:
|
|
31
|
+
- lib/xkeys.rb
|
|
32
|
+
- xkeys.gemspec
|
|
33
|
+
- test/03auto.rb
|
|
34
|
+
- test/01get.rb
|
|
35
|
+
- test/run_tests.sh
|
|
36
|
+
- test/04mod_combos.rb
|
|
37
|
+
- test/02hash.rb
|
|
38
|
+
has_rdoc: true
|
|
39
|
+
homepage: http://rubygems.org/gems/xkeys
|
|
40
|
+
licenses:
|
|
41
|
+
- MIT
|
|
42
|
+
post_install_message:
|
|
43
|
+
rdoc_options: []
|
|
44
|
+
|
|
45
|
+
require_paths:
|
|
46
|
+
- lib
|
|
47
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
48
|
+
none: false
|
|
49
|
+
requirements:
|
|
50
|
+
- - ">="
|
|
51
|
+
- !ruby/object:Gem::Version
|
|
52
|
+
segments:
|
|
53
|
+
- 0
|
|
54
|
+
version: "0"
|
|
55
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
56
|
+
none: false
|
|
57
|
+
requirements:
|
|
58
|
+
- - ">="
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
segments:
|
|
61
|
+
- 0
|
|
62
|
+
version: "0"
|
|
63
|
+
requirements: []
|
|
64
|
+
|
|
65
|
+
rubyforge_project:
|
|
66
|
+
rubygems_version: 1.3.7
|
|
67
|
+
signing_key:
|
|
68
|
+
specification_version: 3
|
|
69
|
+
summary: Extended keys to facilitate fetching and storing in nested hash and array structures with Perl-ish auto-vivification.
|
|
70
|
+
test_files:
|
|
71
|
+
- test/03auto.rb
|
|
72
|
+
- test/01get.rb
|
|
73
|
+
- test/run_tests.sh
|
|
74
|
+
- test/04mod_combos.rb
|
|
75
|
+
- test/02hash.rb
|