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