rspec_normalized_hash 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +9 -0
- data/.yardopts +4 -0
- data/Gemfile +4 -0
- data/Guardfile +8 -0
- data/LICENSE +17 -0
- data/NormalizedHash.md +134 -0
- data/README.md +44 -0
- data/Rakefile +6 -0
- data/lib/normalized_hash/array_values.rb +124 -0
- data/lib/normalized_hash/hash_enclosed_in_array.rb +142 -0
- data/lib/normalized_hash/hash_keys.rb +68 -0
- data/lib/normalized_hash/hash_values.rb +69 -0
- data/lib/normalized_hash/matchers.rb +26 -0
- data/lib/rspec_normalized_hash.rb +10 -0
- data/lib/version.rb +5 -0
- data/rspec_normalized_hash.gemspec +32 -0
- data/spec/array_spec.rb +46 -0
- data/spec/hash_enclosing_array_spec.rb +47 -0
- data/spec/hash_keys_and_values_spec.rb +78 -0
- data/spec/spec_helper.rb +3 -0
- data/spec/test_data.rb +63 -0
- metadata +176 -0
data/.gitignore
ADDED
data/.yardopts
ADDED
data/Gemfile
ADDED
data/Guardfile
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
# A sample Guardfile
|
2
|
+
# More info at https://github.com/guard/guard#readme
|
3
|
+
|
4
|
+
guard 'rspec', :version => 2, :cli => "--color --format nested" do
|
5
|
+
watch(%r{^spec/[^\.]+_spec\.rb})
|
6
|
+
watch(%r{^lib/\w(.+:w)\.rb}) { |m| "spec/lib/#{m[1]}_spec.rb" }
|
7
|
+
watch('spec/spec_helper.rb') { "spec" }
|
8
|
+
end
|
data/LICENSE
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
#
|
2
|
+
# Author:: Dmytro Kovalov (<dmytro.kovalov@gmail.com>)
|
3
|
+
# Copyright:: Copyright (c) 2012, Dmytro Kovalov
|
4
|
+
# License:: Apache License, Version 2.0
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
#
|
data/NormalizedHash.md
ADDED
@@ -0,0 +1,134 @@
|
|
1
|
+
# @title Normalized Hash Data Structure Standard
|
2
|
+
|
3
|
+
Normalized Hash Data Structure Standard
|
4
|
+
---------------------------------------
|
5
|
+
|
6
|
+
Specification below describes hash data structure, main goal of which is to make data produced by Uliska parser easy to use by software that does not know about internal data structure, i.e. data driven and schema-less. Data structures should be built in such a way as to make data self-documenting, easy adaptable and "software-friendly".
|
7
|
+
|
8
|
+
### Hashes
|
9
|
+
|
10
|
+
* Uliska output data structure is multi-level Hash
|
11
|
+
|
12
|
+
* Hash key is one of the following classes: String, Symbol
|
13
|
+
|
14
|
+
* Hash keys should be descriptive and programmatically convertible to human readable form:
|
15
|
+
|
16
|
+
GOOD:
|
17
|
+
|
18
|
+
````
|
19
|
+
:filesystem_size.humanize
|
20
|
+
````
|
21
|
+
|
22
|
+
BAD:
|
23
|
+
|
24
|
+
````
|
25
|
+
:fs_s.humanize
|
26
|
+
````
|
27
|
+
|
28
|
+
* Hash values belong to one of the classes: Numeric, String, Hash, Array
|
29
|
+
|
30
|
+
* On the lowest level of the Hash only String, Numeric and simple Array are allowed as Hash values. Simple array here is an array containing only scalar values -- String or Numeric.
|
31
|
+
|
32
|
+
|
33
|
+
### Arrays
|
34
|
+
|
35
|
+
* Array can consist of Numeric, String, Hash values
|
36
|
+
|
37
|
+
* All elements of each Array should be of the same class
|
38
|
+
|
39
|
+
* Use of Arrays of Hashes acceptable, although discouraged:
|
40
|
+
|
41
|
+
OK:
|
42
|
+
|
43
|
+
````ruby
|
44
|
+
{ :local_users =>
|
45
|
+
[ { :name => "nobody",
|
46
|
+
:password => "*",
|
47
|
+
:uid => -2,
|
48
|
+
:gid => -2,
|
49
|
+
:gecos => "Unprivileged User",
|
50
|
+
:homedir => "/var/empty",
|
51
|
+
:shell => "/usr/bin/false"
|
52
|
+
},
|
53
|
+
{ :name => "root",
|
54
|
+
:password => "*",
|
55
|
+
:uid => 0,
|
56
|
+
:gid => 0,
|
57
|
+
:gecos => "System Administrator",
|
58
|
+
:homedir => "/var/root",
|
59
|
+
:shell => "/bin/sh"
|
60
|
+
}
|
61
|
+
]
|
62
|
+
}
|
63
|
+
````
|
64
|
+
|
65
|
+
BETTER:
|
66
|
+
|
67
|
+
````ruby
|
68
|
+
{ :local_users =>
|
69
|
+
{
|
70
|
+
:nobody =>
|
71
|
+
{ :name => "nobody",
|
72
|
+
:password => "*",
|
73
|
+
:uid => -2,
|
74
|
+
:gid => -2,
|
75
|
+
:gecos => "Unprivileged User",
|
76
|
+
:homedir => "/var/empty",
|
77
|
+
:shell => "/usr/bin/false"
|
78
|
+
},
|
79
|
+
:root =>
|
80
|
+
{ :name => "root",
|
81
|
+
:password => "*",
|
82
|
+
:uid => 0,
|
83
|
+
:gid => 0,
|
84
|
+
:gecos => "System Administrator",
|
85
|
+
:homedir => "/var/root",
|
86
|
+
:shell => "/bin/sh"
|
87
|
+
}
|
88
|
+
}
|
89
|
+
}
|
90
|
+
````
|
91
|
+
|
92
|
+
* When Arrays of Hashes are used additional conditions must be satisfied:
|
93
|
+
|
94
|
+
- each Hash in an Array should have key `name` or
|
95
|
+
|
96
|
+
- if name of the Hash enclosing Array-of-Hashes is English noun in plural form, each Hash should have key which is singular form of the same noun:
|
97
|
+
|
98
|
+
**Example 1** -- Array of Hashes with `"name"` key:
|
99
|
+
|
100
|
+
````ruby
|
101
|
+
{ :local_users=>
|
102
|
+
[{:name=>"root",
|
103
|
+
:password=>"x",
|
104
|
+
:uid=>0,
|
105
|
+
:gid=>0,
|
106
|
+
:gecos=>"root",
|
107
|
+
:homedir=>"/root",
|
108
|
+
:shell=>"/bin/bash"},
|
109
|
+
{:name=>"daemon",
|
110
|
+
:password=>"x",
|
111
|
+
:uid=>1,
|
112
|
+
:gid=>1,
|
113
|
+
:gecos=>"daemon",
|
114
|
+
:homedir=>"/usr/sbin",
|
115
|
+
:shell=>"/bin/sh"}
|
116
|
+
]
|
117
|
+
}
|
118
|
+
````
|
119
|
+
|
120
|
+
**Example 2** -- Enclosing collection `"groups"` is an Array of Hashes. Each Hash has key `"group"`:
|
121
|
+
|
122
|
+
````ruby
|
123
|
+
{:groups=>
|
124
|
+
[ {:group=>"root", :password=>"x", :gid=>0, :members=>[]},
|
125
|
+
{:group=>"daemon", :password=>"x", :gid=>1, :members=>[]},
|
126
|
+
{:group=>"bin", :password=>"x", :gid=>2, :members=>[]},
|
127
|
+
{:group=>"sys", :password=>"x", :gid=>3, :members=>[]}
|
128
|
+
]
|
129
|
+
}
|
130
|
+
````
|
131
|
+
|
132
|
+
Local Variables:
|
133
|
+
fill-column: 9999
|
134
|
+
End:
|
data/README.md
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
|
2
|
+
Normalized Hash tests for RSpec
|
3
|
+
===============================
|
4
|
+
|
5
|
+
These are RSpec tests for Normalized Hash data structure standard. Standard itself and rationale why we (I) need it, is described in NormalizedHash.md file.
|
6
|
+
|
7
|
+
|
8
|
+
RSpec deep hash tests were insired by https://github.com/vitalish/rspec-deep-matchers specs.
|
9
|
+
|
10
|
+
Usage
|
11
|
+
-----
|
12
|
+
|
13
|
+
|
14
|
+
````ruby
|
15
|
+
|
16
|
+
require 'rspec_normalized_hash'
|
17
|
+
|
18
|
+
describe "Good data structure" do
|
19
|
+
|
20
|
+
before(:each) { subject @data }
|
21
|
+
|
22
|
+
it { should have_keys_in_class [String, Symbol] }
|
23
|
+
it { should have_values_in_class [Fixnum, String, Numeric, Hash, Array] }
|
24
|
+
it { should have_array_values_in_class [String,Numeric,Hash] }
|
25
|
+
it { should have_array_values_of_the_same_class }
|
26
|
+
|
27
|
+
it { NOT IMPLEMENTED: enclosed arrays in Hash }
|
28
|
+
end
|
29
|
+
|
30
|
+
````
|
31
|
+
|
32
|
+
License
|
33
|
+
=======
|
34
|
+
|
35
|
+
Apache 2
|
36
|
+
|
37
|
+
Author
|
38
|
+
======
|
39
|
+
|
40
|
+
Dmytro Kovalov
|
41
|
+
|
42
|
+
dmytro.kovalov@gmail.com
|
43
|
+
|
44
|
+
Aug,Sept 2012
|
data/Rakefile
ADDED
@@ -0,0 +1,124 @@
|
|
1
|
+
module NormalizedHash
|
2
|
+
module Matchers
|
3
|
+
|
4
|
+
# Test that array has only elements of expeted class(es).
|
5
|
+
#
|
6
|
+
# Array is a value in deep (multilevel) hash. Check done
|
7
|
+
# hierarchically for all arrays' values.
|
8
|
+
#
|
9
|
+
# @param [Array, Class] expected class(es) allowed
|
10
|
+
#
|
11
|
+
# = Usage
|
12
|
+
# it { @hash.should have_array_values_in_class [String,Numeric,Hash] }
|
13
|
+
# it { @hash.should have_array_values_in_class Numeric }
|
14
|
+
#
|
15
|
+
def have_array_values_in_class(expected)
|
16
|
+
ArrayValues.new(expected)
|
17
|
+
end
|
18
|
+
|
19
|
+
# Test that array elements are all in the same class.
|
20
|
+
#
|
21
|
+
# Array is a value in deep (multilevel) hash. Check done
|
22
|
+
# hierarchically for all arrays' values.
|
23
|
+
#
|
24
|
+
# @param none
|
25
|
+
#
|
26
|
+
# = Usage
|
27
|
+
# it { @hash.should have_array_values_of_the_same_class }
|
28
|
+
#
|
29
|
+
def have_array_values_of_the_same_class
|
30
|
+
SameArrayValues.new Object # dummy class, not used but needs to
|
31
|
+
# be here because of constructor
|
32
|
+
end
|
33
|
+
|
34
|
+
# RSpec::Matchers class for testing hierarchically arrays, that
|
35
|
+
# are elements of deep hash.
|
36
|
+
#
|
37
|
+
# SameArrayValues class tests that all values of the Array are on
|
38
|
+
# the same class.
|
39
|
+
class SameArrayValues < HashMatchers
|
40
|
+
def description
|
41
|
+
"have all values of the same class"
|
42
|
+
end
|
43
|
+
|
44
|
+
# Test that all values of the tested Array are of the same class
|
45
|
+
#
|
46
|
+
# @param [Array] target tested array
|
47
|
+
#
|
48
|
+
# @return true if pass, false if fail
|
49
|
+
def matches? target
|
50
|
+
result = true
|
51
|
+
case target
|
52
|
+
when Array
|
53
|
+
@actual = target.map(&:class).uniq
|
54
|
+
result = !(@actual.count > 1)
|
55
|
+
when Hash
|
56
|
+
|
57
|
+
target.each_value do |val|
|
58
|
+
if val.is_a? Array
|
59
|
+
result &&= SameArrayValues.new(Object).matches? val
|
60
|
+
@actual = val.map(&:class).uniq unless result
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
result
|
66
|
+
end
|
67
|
+
|
68
|
+
def failure_message_for_should
|
69
|
+
"expected #{@actual.inspect} to be one class"
|
70
|
+
end
|
71
|
+
|
72
|
+
def failure_message_for_should_not
|
73
|
+
"expected #{@actual.inspect} be at least 2 different classes"
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
|
78
|
+
|
79
|
+
# RSpec::Matchers class for testing hierarchically arrays, that
|
80
|
+
# are elements of deep hash.
|
81
|
+
#
|
82
|
+
# ArrayValues class ensures that all values of the tested
|
83
|
+
# Array belong to expected classes.
|
84
|
+
class ArrayValues < HashMatchers
|
85
|
+
|
86
|
+
def description
|
87
|
+
"have every value one of class: #{@expectation.join ','}"
|
88
|
+
end
|
89
|
+
|
90
|
+
# Test that all values of the tested Array belong to expecetd
|
91
|
+
# class(es).
|
92
|
+
#
|
93
|
+
# @param [Array] target array to test
|
94
|
+
#
|
95
|
+
# @return true if pass, false if fail
|
96
|
+
def matches?(target)
|
97
|
+
result = true
|
98
|
+
@target, @actual = target, target.map(&:class).uniq
|
99
|
+
|
100
|
+
case @target
|
101
|
+
when Array
|
102
|
+
result &&= (@actual - @expectation).empty?
|
103
|
+
when Hash
|
104
|
+
@target.each_value do |val|
|
105
|
+
result &&= ArrayValues.new(@expectation).matches?(val) if [Hash, Array].include? val.class
|
106
|
+
@target = val unless result
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
result
|
111
|
+
end
|
112
|
+
|
113
|
+
|
114
|
+
def failure_message_for_should
|
115
|
+
"expected #{@target.inspect} to have values of classes #{@expectation.inspect}"
|
116
|
+
end
|
117
|
+
|
118
|
+
def failure_message_for_should_not
|
119
|
+
"expected #{@target.inspect} differ from #{@expectation.inspect}"
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
end
|
124
|
+
end
|
@@ -0,0 +1,142 @@
|
|
1
|
+
|
2
|
+
require 'active_support/all'
|
3
|
+
|
4
|
+
module NormalizedHash
|
5
|
+
module Matchers
|
6
|
+
|
7
|
+
# Test that array has only elements of expeted class(es).
|
8
|
+
#
|
9
|
+
# @param TODO
|
10
|
+
#
|
11
|
+
# = Usage
|
12
|
+
# it { @TODO.should TODO }
|
13
|
+
#
|
14
|
+
def have_all_keys_be_word_name_or_singular_of (expected)
|
15
|
+
KeyNamesOfEnclosedHash.new(expected)
|
16
|
+
end
|
17
|
+
|
18
|
+
def all_arrays_have_hash_with_key_word_name_or_singular_of_hash_key
|
19
|
+
HashEnclosingArray.new
|
20
|
+
end
|
21
|
+
|
22
|
+
# Simple class to be inherited by other matcher classes that take
|
23
|
+
# String expectation.
|
24
|
+
class KeyNameMatchers
|
25
|
+
def initialize(expectation)
|
26
|
+
expectation = expectation.to_s.downcase
|
27
|
+
@expectation = expectation.singularize
|
28
|
+
raise ArgumentError, "#{expectation} must have singular form. Got #{@expectation}" if @expectation == expectation
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# RSpec::Matchers class for testing hashes enclosed in an array.
|
33
|
+
#
|
34
|
+
# Array of Hashes: each Hash should have key :name or :<collection.singularize>
|
35
|
+
#
|
36
|
+
# Hash here (subject) is an instance of Hash, which is an element
|
37
|
+
# of an Array, and Array itself is a value of a Hash:
|
38
|
+
#
|
39
|
+
# { :foos =>
|
40
|
+
# [ <--- subject
|
41
|
+
# { :foo (or :name) => ...,
|
42
|
+
# :bar => ...,
|
43
|
+
# },
|
44
|
+
# { :foo (or :name) => ...,
|
45
|
+
# :bar => ...,
|
46
|
+
# },
|
47
|
+
# ]
|
48
|
+
# }
|
49
|
+
class KeyNamesOfEnclosedHash < KeyNameMatchers
|
50
|
+
def description
|
51
|
+
"have key :name or '#{@expectation.singularize}' in every enclosed Hash "
|
52
|
+
end
|
53
|
+
|
54
|
+
# Test that all values of the tested Array are of the same class
|
55
|
+
#
|
56
|
+
# @param [Array] target tested array
|
57
|
+
#
|
58
|
+
# @return true if pass, false if fail
|
59
|
+
def matches? target
|
60
|
+
@target = target
|
61
|
+
result = true
|
62
|
+
|
63
|
+
target.each do |hash|
|
64
|
+
next unless hash.is_a? Hash
|
65
|
+
keys = hash.keys.map(&:to_s).map(&:downcase)
|
66
|
+
result &&= false unless (keys.include? @expectation || keys.include?("name"))
|
67
|
+
end
|
68
|
+
|
69
|
+
result
|
70
|
+
end
|
71
|
+
|
72
|
+
def failure_message_for_should
|
73
|
+
"expected each of #{@target.map(&:keys)} include '#{@expectation.singularize}'"
|
74
|
+
end
|
75
|
+
|
76
|
+
def failure_message_for_should_not
|
77
|
+
"expected #{@target.map(&:keys)} not include '#{@expectation.singularize}'"
|
78
|
+
end
|
79
|
+
end # class KeyNamesOfEnclosedHash < HashMatchers
|
80
|
+
|
81
|
+
# Hash (subject) ancloses Array wit ha bunch of Hases inside. Each
|
82
|
+
# aHas is then tested by KeyNamesOfEnclosedHash
|
83
|
+
#
|
84
|
+
#
|
85
|
+
# { :foos => <--- subject
|
86
|
+
# [
|
87
|
+
# { :foo (or :name) => ...,
|
88
|
+
# :bar => ...,
|
89
|
+
# },
|
90
|
+
# { :foo (or :name) => ...,
|
91
|
+
# :bar => ...,
|
92
|
+
# },
|
93
|
+
# ]
|
94
|
+
# }
|
95
|
+
class HashEnclosingArray
|
96
|
+
def description
|
97
|
+
@description
|
98
|
+
end
|
99
|
+
|
100
|
+
def failure_message_for_should
|
101
|
+
[@target.inspect, description, @failure].join ' '
|
102
|
+
end
|
103
|
+
|
104
|
+
def failure_message_for_should_not
|
105
|
+
[@target.inspect, 'not', description, @failure].join ' '
|
106
|
+
end
|
107
|
+
|
108
|
+
|
109
|
+
# Run matcher only if target is Hash with Array embedded at the
|
110
|
+
# first depth level. Not recursive, recursion should be
|
111
|
+
# implemented on level higher.
|
112
|
+
def matches? target
|
113
|
+
|
114
|
+
result = true
|
115
|
+
@target = target
|
116
|
+
|
117
|
+
if (target.is_a?(Hash) && target.values.map(&:class).include?(Array))
|
118
|
+
|
119
|
+
@description = "enclose Arrays of Hashes with key #{@target.keys}"
|
120
|
+
@failure = ":\n"
|
121
|
+
|
122
|
+
target.each do |key,array|
|
123
|
+
if array.is_a? Array
|
124
|
+
@checked = array
|
125
|
+
res = KeyNamesOfEnclosedHash.new(key).matches?(array)
|
126
|
+
result &&= res
|
127
|
+
@failure << { key => array}.inspect unless res
|
128
|
+
end
|
129
|
+
end
|
130
|
+
else
|
131
|
+
@description = "*** spec did not run *** "
|
132
|
+
result = false
|
133
|
+
end
|
134
|
+
|
135
|
+
result
|
136
|
+
end
|
137
|
+
|
138
|
+
end # ArrayEnclosingHashes < KeyNameMatchers
|
139
|
+
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module NormalizedHash
|
2
|
+
module Matchers
|
3
|
+
|
4
|
+
# Test that hash has only keys of expected class(es).
|
5
|
+
#
|
6
|
+
# Check done hierarchically for all Hashes.
|
7
|
+
#
|
8
|
+
# @param [Array, Class] expected class(es) allowed
|
9
|
+
#
|
10
|
+
# == Usage
|
11
|
+
# it { @hash.should have_keys_in_class [String,Numeric,Hash,Array] }
|
12
|
+
# it { @hash.should have_keys_in_class Numeric }
|
13
|
+
#
|
14
|
+
def have_keys_in_class(expected)
|
15
|
+
HashKeys.new(expected)
|
16
|
+
end
|
17
|
+
|
18
|
+
# RSpec::Matchers class for testing hierarchically hashes, that
|
19
|
+
# are elements of deep hash.
|
20
|
+
#
|
21
|
+
# HashKeys class tests that all keys of the Hash belong to list of
|
22
|
+
# Classes.
|
23
|
+
#
|
24
|
+
# == Usage
|
25
|
+
# it { @hash.should have_keys_in_class [String, Symbol] }
|
26
|
+
# it { @hash.should have_keys_in_class String }
|
27
|
+
class HashKeys < HashMatchers
|
28
|
+
|
29
|
+
def description
|
30
|
+
"have (recursively) every key one of class: #{[@expectation].flatten.join ','}"
|
31
|
+
end
|
32
|
+
|
33
|
+
# Test that all keys of the tested Hash belong to provided list
|
34
|
+
# of classes
|
35
|
+
#
|
36
|
+
# @param [Hash] target hash under test
|
37
|
+
#
|
38
|
+
# @return true if pass, false if fail
|
39
|
+
def matches?(target)
|
40
|
+
result = true
|
41
|
+
|
42
|
+
if target.is_a? Hash
|
43
|
+
|
44
|
+
@target, @actual = target, target.keys.map(&:class).uniq
|
45
|
+
|
46
|
+
result &&= (@actual - @expectation).empty?
|
47
|
+
|
48
|
+
@target.each_value do |val|
|
49
|
+
result &&= HashKeys.new(@expectation).matches?(val) if val.is_a? Hash
|
50
|
+
@target = val unless result
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
result
|
55
|
+
end
|
56
|
+
|
57
|
+
|
58
|
+
def failure_message_for_should
|
59
|
+
"expected #{@target.keys.inspect} #{@actual} to be one of #{@expectation.inspect}"
|
60
|
+
end
|
61
|
+
|
62
|
+
def failure_message_for_should_not
|
63
|
+
"expected #{@target.keys.inspect} differ from #{@expectation.inspect}"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module NormalizedHash
|
2
|
+
module Matchers
|
3
|
+
|
4
|
+
# Test that hash has only values of expected class(es).
|
5
|
+
#
|
6
|
+
# Check done hierarchically for all Hashes.
|
7
|
+
#
|
8
|
+
# @param [Array, Class] expected class(es) allowed
|
9
|
+
#
|
10
|
+
# == Usage
|
11
|
+
#
|
12
|
+
# it { @hash.should have_values_in_class [String,Numeric,Hash,Array] }
|
13
|
+
# it { @hash.should have_values_in_class Numeric }
|
14
|
+
#
|
15
|
+
def have_values_in_class(expected)
|
16
|
+
HashValues.new(expected)
|
17
|
+
end
|
18
|
+
|
19
|
+
# RSpec::Matchers class for testing hierarchically hashes, that
|
20
|
+
# are elements of deep hash.
|
21
|
+
#
|
22
|
+
# HashValues class tests that all values of the Hash belong to
|
23
|
+
# list of Classes.
|
24
|
+
#
|
25
|
+
# == Usage
|
26
|
+
# it { @hash.should have_values_in_class [String, Symbol] }
|
27
|
+
# it { @hash.should have_values_in_class String }
|
28
|
+
#
|
29
|
+
class HashValues < HashMatchers
|
30
|
+
|
31
|
+
def description
|
32
|
+
"have (recursively) every value one of class: #{[@expectation].flatten.join ','}"
|
33
|
+
end
|
34
|
+
|
35
|
+
# Test that all values of the tested Hash belong to provided
|
36
|
+
# list of classes
|
37
|
+
#
|
38
|
+
# @param [Hash] target hash under test
|
39
|
+
#
|
40
|
+
# @return true if pass, false if fail
|
41
|
+
def matches?(target)
|
42
|
+
result = true
|
43
|
+
|
44
|
+
if target.is_a? Hash
|
45
|
+
@target, @actual = target, target.values.map(&:class).uniq
|
46
|
+
|
47
|
+
result &&= (@actual - @expectation).empty?
|
48
|
+
|
49
|
+
@target.each_value do |val|
|
50
|
+
result &&= HashValues.new(@expectation).matches?(val) if val.is_a?(Hash)
|
51
|
+
@target = val unless result
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
result
|
56
|
+
end
|
57
|
+
|
58
|
+
|
59
|
+
def failure_message_for_should
|
60
|
+
"expected #{@target.values.inspect} to have values of classes #{@expectation.inspect}"
|
61
|
+
end
|
62
|
+
|
63
|
+
def failure_message_for_should_not
|
64
|
+
"expected #{@target.values.inspect} differ from #{@expectation.inspect}"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module NormalizedHash
|
2
|
+
module Matchers
|
3
|
+
|
4
|
+
# All numeric classes we want to consider
|
5
|
+
NUMBERS = [Fixnum, Bignum, Numeric, Float]
|
6
|
+
|
7
|
+
class HashMatchers
|
8
|
+
|
9
|
+
def initialize(expectation)
|
10
|
+
# Convert to array for simpler checks
|
11
|
+
@expectation = [expectation].flatten
|
12
|
+
|
13
|
+
# All expectionations should be Class, no other classes
|
14
|
+
unless @expectation.map(&:class).uniq == [Class]
|
15
|
+
raise ArgumentError,
|
16
|
+
"#{@expectation.inspect}: Expectation should be Class or Array[of Classes], got #{@expectation.class}"
|
17
|
+
end
|
18
|
+
|
19
|
+
# Expand expectation with all numeric classes if at least one
|
20
|
+
# numeric present.
|
21
|
+
(@expectation += NUMBERS).uniq! unless (@expectation & NUMBERS).empty?
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
$: << File.dirname(__FILE__)
|
2
|
+
require 'rspec'
|
3
|
+
require "normalized_hash/matchers"
|
4
|
+
require "normalized_hash/hash_keys"
|
5
|
+
require "normalized_hash/hash_values"
|
6
|
+
require "normalized_hash/array_values"
|
7
|
+
require "normalized_hash/hash_enclosed_in_array"
|
8
|
+
module RSpec::Matchers
|
9
|
+
include NormalizedHash::Matchers
|
10
|
+
end
|
data/lib/version.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "rspec_normalized_hash"
|
7
|
+
s.version = NormalizedHash::Matchers::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["dmytro"]
|
10
|
+
s.email = ["dmytro.kovalov@gmail.com"]
|
11
|
+
s.homepage = "http://github.com/dmytro/rspec_normalized_hash"
|
12
|
+
s.summary = %q{Recursive Hash structure matcher for rspec.}
|
13
|
+
s.description = " Specification of Normalized Hash data structure main goal is to make data produced by parsers easy to use by software that does not know about internal data structure, i.e. data driven and schema-less. Data structures should be built in such a way as to make data self-documenting, easy adaptable and 'software-friendly'.
|
14
|
+
Gem contains RSpec tests for testing Hash data structure for compliance with the requirements.
|
15
|
+
See README.md and NormalizedHash.md files in gem's root directory."
|
16
|
+
|
17
|
+
s.rubyforge_project = "normalized-hash"
|
18
|
+
|
19
|
+
s.files = `git ls-files`.split("\n")
|
20
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
21
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
22
|
+
s.require_paths = ["lib"]
|
23
|
+
|
24
|
+
s.add_dependency 'rspec', '>= 2.0.0'
|
25
|
+
s.add_dependency 'active_support'
|
26
|
+
s.add_dependency 'i18n'
|
27
|
+
|
28
|
+
|
29
|
+
s.add_development_dependency('rb-fsevent')
|
30
|
+
s.add_development_dependency('guard-rspec')
|
31
|
+
s.add_development_dependency('growl')
|
32
|
+
end
|
data/spec/array_spec.rb
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'Normalized Hash - Array' do
|
4
|
+
|
5
|
+
before(:each) { @hash = GOOD_HASH }
|
6
|
+
|
7
|
+
context "simple" do
|
8
|
+
context "Numeric array" do
|
9
|
+
subject { @hash[:n_array].dup } # Call dup here since I'm modifying data
|
10
|
+
it { should have_array_values_in_class Numeric }
|
11
|
+
it { should have_array_values_of_the_same_class }
|
12
|
+
it "with different values should not have same class" do
|
13
|
+
subject << 'a'
|
14
|
+
should_not have_array_values_of_the_same_class
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
context "String array" do
|
19
|
+
subject { @hash[:s_array] }
|
20
|
+
it { should have_array_values_in_class String }
|
21
|
+
end
|
22
|
+
|
23
|
+
context "mixed array" do
|
24
|
+
subject { @hash[:mix_array] }
|
25
|
+
it { should have_array_values_in_class [String,Numeric,Hash] }
|
26
|
+
it { should_not have_array_values_in_class IO }
|
27
|
+
end
|
28
|
+
|
29
|
+
context "array of Hash'es" do
|
30
|
+
subject { @hash[:array_hash] }
|
31
|
+
it { should have_array_values_in_class [String,Numeric,Hash] }
|
32
|
+
it { should_not have_array_values_in_class IO }
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
context "multi level" do
|
37
|
+
subject { @hash }
|
38
|
+
it { should have_array_values_in_class [String,Numeric,Hash] }
|
39
|
+
it { should_not have_array_values_in_class IO }
|
40
|
+
it { should_not have_array_values_of_the_same_class }
|
41
|
+
it "when removed mixed array should all be in the same class" do
|
42
|
+
subject.delete :mix_array
|
43
|
+
should have_array_values_of_the_same_class
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'Normalized Hash - enclosing array' do
|
4
|
+
|
5
|
+
before(:each) { @hash = GOOD_HASH }
|
6
|
+
|
7
|
+
context "Hash enclosed in Array " do
|
8
|
+
context "Hash-> Array-> [Hash]" do
|
9
|
+
context :simple do
|
10
|
+
subject { @hash[:names] }
|
11
|
+
it { should have_all_keys_be_word_name_or_singular_of :names }
|
12
|
+
it { @hash[:people].should have_all_keys_be_word_name_or_singular_of :people}
|
13
|
+
|
14
|
+
it { should_not have_all_keys_be_word_name_or_singular_of :last_names }
|
15
|
+
it { @hash[:people].should have_all_keys_be_word_name_or_singular_of :people}
|
16
|
+
end
|
17
|
+
|
18
|
+
context :simple_changed do
|
19
|
+
subject { @hash[:names] << { :not_really_name => 'some string', :name => 'name' } }
|
20
|
+
it { should have_all_keys_be_word_name_or_singular_of :names }
|
21
|
+
it { should_not have_all_keys_be_word_name_or_singular_of :last_names }
|
22
|
+
end
|
23
|
+
end # context "keys of Hash enclosed in an Array" do
|
24
|
+
|
25
|
+
context "Hash-> [Array]-> Hash " do
|
26
|
+
context "one level" do
|
27
|
+
subject { {:people => @hash[:people]} }
|
28
|
+
it { should all_arrays_have_hash_with_key_word_name_or_singular_of_hash_key }
|
29
|
+
end
|
30
|
+
|
31
|
+
context '2 levels' do
|
32
|
+
subject {
|
33
|
+
{
|
34
|
+
:deep =>
|
35
|
+
{ :two_levels =>
|
36
|
+
[
|
37
|
+
{ :name => :one},
|
38
|
+
{ :name => :two},
|
39
|
+
]
|
40
|
+
}
|
41
|
+
}
|
42
|
+
}
|
43
|
+
it { should_not all_arrays_have_hash_with_key_word_name_or_singular_of_hash_key }
|
44
|
+
end
|
45
|
+
end # context "Hash-> [Array]-> Hash" do
|
46
|
+
end # context "enclosed in Array Hash" do
|
47
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'Normalized Hash - keys and values' do
|
4
|
+
|
5
|
+
before(:each) { @hash = GOOD_HASH }
|
6
|
+
# ------------------------------------------------------------
|
7
|
+
context 'Hash keys and values' do
|
8
|
+
context "simple hash" do
|
9
|
+
|
10
|
+
context "single Symbol key" do
|
11
|
+
subject { @hash[:symbol] }
|
12
|
+
|
13
|
+
it { should have_keys_in_class Symbol }
|
14
|
+
it { should_not have_keys_in_class String }
|
15
|
+
|
16
|
+
it { should have_values_in_class [String, Numeric, Array, Hash] }
|
17
|
+
it { should_not have_values_in_class IO }
|
18
|
+
|
19
|
+
it { should have_array_values_in_class [Numeric, String, Hash] }
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
context "single String key" do
|
24
|
+
subject { @hash[:string] }
|
25
|
+
|
26
|
+
it { should have_keys_in_class String }
|
27
|
+
it { should_not have_keys_in_class Symbol }
|
28
|
+
|
29
|
+
it { should have_values_in_class [String, Numeric, Array, Hash] }
|
30
|
+
it { should_not have_values_in_class IO }
|
31
|
+
end
|
32
|
+
|
33
|
+
context "Symbol and String keys" do
|
34
|
+
subject { @hash[:sym_str] }
|
35
|
+
|
36
|
+
it { should have_keys_in_class [Symbol, String] }
|
37
|
+
it { should have_keys_in_class [Symbol, String, File] }
|
38
|
+
it { should_not have_keys_in_class [Symbol, File] }
|
39
|
+
it { should_not have_keys_in_class Symbol }
|
40
|
+
it { should_not have_keys_in_class String }
|
41
|
+
end
|
42
|
+
|
43
|
+
context "nested Hash" do
|
44
|
+
|
45
|
+
context "2 levels" do
|
46
|
+
subject { @hash[:l2] }
|
47
|
+
it { should have_keys_in_class [Symbol, String] }
|
48
|
+
it { subject.merge({[1,2] => 1 }).should_not have_keys_in_class [Symbol, String] }
|
49
|
+
|
50
|
+
it { should have_values_in_class [String, Numeric, Array, Hash] }
|
51
|
+
it { should_not have_values_in_class IO }
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
context "3 levels" do
|
56
|
+
subject { @hash }
|
57
|
+
it { should have_keys_in_class [Symbol, String] }
|
58
|
+
|
59
|
+
|
60
|
+
it {
|
61
|
+
bad = { :a => { :b => { :bbb => [1,2,3], 'ccc' => "string", 42 => 42 }}}
|
62
|
+
bad.should_not have_keys_in_class [Symbol, String]
|
63
|
+
}
|
64
|
+
|
65
|
+
it { should have_values_in_class [String, Numeric, Array, Hash] }
|
66
|
+
|
67
|
+
it {
|
68
|
+
subject.merge(
|
69
|
+
{ :file => File.new('.tmp','w') }
|
70
|
+
).should_not have_values_in_class [String, Numeric, Array, Hash]
|
71
|
+
}
|
72
|
+
end # 3 levels
|
73
|
+
end # nested Hash
|
74
|
+
end # Hash
|
75
|
+
|
76
|
+
|
77
|
+
end # context 'Hash keys and values' do
|
78
|
+
end
|
data/spec/spec_helper.rb
ADDED
data/spec/test_data.rb
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
|
2
|
+
#
|
3
|
+
# TEST DATA: Good normalized hash for use in tests
|
4
|
+
# ================================================
|
5
|
+
GOOD_HASH = {
|
6
|
+
:symbol => { :str => 'str', :num => 42, :arr => [1,2,3] }, # single symbol key
|
7
|
+
:string => { 'str' => 'str', 'num' => 42, 'arr' => [1,2,3] }, # single string key
|
8
|
+
:sym_str => {
|
9
|
+
:a => 1, :aa => 'a',
|
10
|
+
'b' => 2, 'bb' => 'bb'
|
11
|
+
},
|
12
|
+
:l2 => { # level 2 hash
|
13
|
+
:aa => 1,
|
14
|
+
:bb => { :bbb => [1,2,3], 'ccc' => "string", :ddd => 42 }, # symbol and string keys
|
15
|
+
'cc' => 3
|
16
|
+
},
|
17
|
+
:b => {
|
18
|
+
:ba => 1,
|
19
|
+
:bb => 2,
|
20
|
+
'bc' => { :caa => 1, :cbb => 'asdfas', 'dd' => 'file'},
|
21
|
+
'abc' => 'a'
|
22
|
+
},
|
23
|
+
:n_array => [1,2,3,4],
|
24
|
+
:s_array => ["a","b","c"],
|
25
|
+
:mix_array => [1, "a", {} ],
|
26
|
+
:array_hash =>
|
27
|
+
[
|
28
|
+
{ :name =>'a' },
|
29
|
+
{ :name =>'b' },
|
30
|
+
{ :name =>'c' },
|
31
|
+
],
|
32
|
+
|
33
|
+
:names =>
|
34
|
+
[
|
35
|
+
{ :name =>'a' },
|
36
|
+
{ :name =>'b' },
|
37
|
+
{ :name =>'c' },
|
38
|
+
],
|
39
|
+
|
40
|
+
:people =>
|
41
|
+
[
|
42
|
+
{ :person =>'a' },
|
43
|
+
{ :person =>'b' },
|
44
|
+
{ :person =>'c' },
|
45
|
+
]
|
46
|
+
}
|
47
|
+
|
48
|
+
# ============================================================
|
49
|
+
@enclosed_hash = {
|
50
|
+
:simple => [
|
51
|
+
{ },
|
52
|
+
{ },
|
53
|
+
{ },
|
54
|
+
],
|
55
|
+
:nested => {
|
56
|
+
:level2 => [
|
57
|
+
{ },
|
58
|
+
{ },
|
59
|
+
{ },
|
60
|
+
]
|
61
|
+
}
|
62
|
+
}
|
63
|
+
# ============================================================
|
metadata
ADDED
@@ -0,0 +1,176 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rspec_normalized_hash
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- dmytro
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-09-06 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rspec
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 2.0.0
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 2.0.0
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: active_support
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: i18n
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: rb-fsevent
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
type: :development
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: guard-rspec
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ! '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
type: :development
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ! '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
- !ruby/object:Gem::Dependency
|
95
|
+
name: growl
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ! '>='
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0'
|
102
|
+
type: :development
|
103
|
+
prerelease: false
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ! '>='
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0'
|
110
|
+
description: ! ' Specification of Normalized Hash data structure main goal is to make
|
111
|
+
data produced by parsers easy to use by software that does not know about internal
|
112
|
+
data structure, i.e. data driven and schema-less. Data structures should be built
|
113
|
+
in such a way as to make data self-documenting, easy adaptable and ''software-friendly''.
|
114
|
+
|
115
|
+
Gem contains RSpec tests for testing Hash data structure for compliance with the
|
116
|
+
requirements.
|
117
|
+
|
118
|
+
See README.md and NormalizedHash.md files in gem''s root directory.'
|
119
|
+
email:
|
120
|
+
- dmytro.kovalov@gmail.com
|
121
|
+
executables: []
|
122
|
+
extensions: []
|
123
|
+
extra_rdoc_files: []
|
124
|
+
files:
|
125
|
+
- .gitignore
|
126
|
+
- .yardopts
|
127
|
+
- Gemfile
|
128
|
+
- Guardfile
|
129
|
+
- LICENSE
|
130
|
+
- NormalizedHash.md
|
131
|
+
- README.md
|
132
|
+
- Rakefile
|
133
|
+
- lib/normalized_hash/array_values.rb
|
134
|
+
- lib/normalized_hash/hash_enclosed_in_array.rb
|
135
|
+
- lib/normalized_hash/hash_keys.rb
|
136
|
+
- lib/normalized_hash/hash_values.rb
|
137
|
+
- lib/normalized_hash/matchers.rb
|
138
|
+
- lib/rspec_normalized_hash.rb
|
139
|
+
- lib/version.rb
|
140
|
+
- rspec_normalized_hash.gemspec
|
141
|
+
- spec/array_spec.rb
|
142
|
+
- spec/hash_enclosing_array_spec.rb
|
143
|
+
- spec/hash_keys_and_values_spec.rb
|
144
|
+
- spec/spec_helper.rb
|
145
|
+
- spec/test_data.rb
|
146
|
+
homepage: http://github.com/dmytro/rspec_normalized_hash
|
147
|
+
licenses: []
|
148
|
+
post_install_message:
|
149
|
+
rdoc_options: []
|
150
|
+
require_paths:
|
151
|
+
- lib
|
152
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
153
|
+
none: false
|
154
|
+
requirements:
|
155
|
+
- - ! '>='
|
156
|
+
- !ruby/object:Gem::Version
|
157
|
+
version: '0'
|
158
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
159
|
+
none: false
|
160
|
+
requirements:
|
161
|
+
- - ! '>='
|
162
|
+
- !ruby/object:Gem::Version
|
163
|
+
version: '0'
|
164
|
+
requirements: []
|
165
|
+
rubyforge_project: normalized-hash
|
166
|
+
rubygems_version: 1.8.24
|
167
|
+
signing_key:
|
168
|
+
specification_version: 3
|
169
|
+
summary: Recursive Hash structure matcher for rspec.
|
170
|
+
test_files:
|
171
|
+
- spec/array_spec.rb
|
172
|
+
- spec/hash_enclosing_array_spec.rb
|
173
|
+
- spec/hash_keys_and_values_spec.rb
|
174
|
+
- spec/spec_helper.rb
|
175
|
+
- spec/test_data.rb
|
176
|
+
has_rdoc:
|