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 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
@@ -0,0 +1,8 @@
1
+ #!/bin/sh
2
+
3
+ export RUBYLIB=../lib
4
+
5
+ # Run the specified tests (or all of them)
6
+ for t in ${*:-[0-9]*.rb}
7
+ do ruby $t
8
+ done
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