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 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