object-template 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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 56e379a6cf0a215d88542bde7bdae2ffba74fbf7
4
+ data.tar.gz: 6da3bba0bf52782c243023ebdd6e544a04db5d5a
5
+ SHA512:
6
+ metadata.gz: 56a120f187e1439c0a40f29d56207e72036f815180b9c86327f9e8d5b4604ddb9cbde43419bedaed9030c94a2bb1f01236cff7025d33e6d6b90bbd7dfa887c8e
7
+ data.tar.gz: e0c16129bfb20ff2bb44977e16b7fdecd8ce928e1f96efb4ab5969c4bb694fdc30edd5a3f4aaaf9027272b572e413873339daba0ea2bfad1831f5ec7c406c736
data/COPYING ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013, Joel VanderWerf, vjoel@users.sourceforge.net
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without
5
+ modification, are permitted provided that the following conditions are met:
6
+
7
+ 1. Redistributions of source code must retain the above copyright notice, this
8
+ list of conditions and the following disclaimer.
9
+ 2. Redistributions in binary form must reproduce the above copyright notice,
10
+ this list of conditions and the following disclaimer in the documentation
11
+ and/or other materials provided with the distribution.
12
+
13
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
14
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
15
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
16
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
17
+ ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
18
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
19
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
20
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
22
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
data/README.md ADDED
@@ -0,0 +1,4 @@
1
+ object-template
2
+ ===============
3
+
4
+ Templates for matching objects.
data/Rakefile ADDED
@@ -0,0 +1,16 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+
4
+ desc "Run tests"
5
+ Rake::TestTask.new :test do |t|
6
+ t.libs << "lib"
7
+ t.libs << "ext"
8
+ t.test_files = FileList["test/*.rb"]
9
+ end
10
+
11
+ desc "Run benchmarks"
12
+ Rake::TestTask.new :bench do |t|
13
+ t.libs << "lib"
14
+ t.libs << "ext"
15
+ t.test_files = FileList["bench/*.rb"]
16
+ end
@@ -0,0 +1,47 @@
1
+ $LOAD_PATH.unshift File.join(__dir__, "lib")
2
+
3
+ require 'bench'
4
+ require 'object-template'
5
+
6
+ ROT = RubyObjectTemplate
7
+ POT = PortableObjectTemplate
8
+
9
+ SEED = 1237
10
+
11
+ def make_dataset
12
+ strs = ("a".."z").map {|c| "fo" + c}
13
+ (1..1000).map {|i| [ i, strs.sample, rand(50) ]}
14
+ end
15
+
16
+ def run_bench_on_template template, seed: nil
17
+ srand seed
18
+ objs = make_dataset
19
+
20
+ matched = 0
21
+ rslt = bench_rate n_sec: 1.0, notify: proc {|c| print c} do
22
+ matched += 1 if template === objs.sample
23
+ end
24
+
25
+ puts
26
+ puts "seed = %8d" % seed
27
+ puts "matched = %8d" % matched
28
+ puts "n_iter = %8d" % rslt[:n_iter]
29
+ puts "n_chunk = %8d" % rslt[:n_chunk]
30
+ puts "t_warmup = %12.3f sec" % rslt[:t_warmup]
31
+ puts "t_run = %12.3f sec" % rslt[:t_run]
32
+ puts "rate = %12.3f iter/sec" % rslt[:rate]
33
+
34
+ rslt
35
+ end
36
+
37
+ template = POT.new [ {set: [0,1,2,*100..999]}, {regex: "foo"}, {value: 0} ]
38
+ puts "Unoptimized:"
39
+ r0 = run_bench_on_template template, seed: SEED
40
+
41
+ puts
42
+ puts "Optimized:"
43
+ template.optimize!
44
+ r1 = run_bench_on_template template, seed: SEED
45
+
46
+ puts
47
+ puts "Speed-up: %5.2f" % (r1[:rate] / r0[:rate])
@@ -0,0 +1,55 @@
1
+ def bench
2
+ times = Process.times
3
+ t0 = times.utime + times.stime
4
+
5
+ yield
6
+
7
+ times = Process.times
8
+ t1 = times.utime + times.stime
9
+ t1 - t0
10
+ end
11
+
12
+ def bench_rate n_sec: 1.0, n_chunk: 1, notify: nil
13
+ times = Process.times
14
+ t_init = times.utime + times.stime
15
+ #puts "init at #{t_init}"
16
+
17
+ t_run = 0
18
+ n_iter = 0
19
+ t_now = nil
20
+ t_start = nil
21
+
22
+ while t_run < n_sec
23
+ n_chunk.times do
24
+ yield
25
+ end
26
+
27
+ times = Process.times
28
+ t_now = times.utime + times.stime
29
+
30
+ if t_now - t_init < n_sec/4.0 # warm up and seek stride
31
+ n_chunk *= 2
32
+ #puts "n_chunk = #{n_chunk}"
33
+ notify and notify.call "."
34
+ else
35
+ if not t_start
36
+ t_start = t_now
37
+ #puts "start at #{t_start}"
38
+ end
39
+
40
+ notify and notify.call "0"
41
+
42
+ n_iter += n_chunk
43
+ t_run = t_now - t_start
44
+ end
45
+ end
46
+
47
+ {
48
+ n_iter: n_iter,
49
+ t_warmup: t_start - t_init,
50
+ t_run: t_run,
51
+ rate: n_iter / t_run.to_f,
52
+ n_chunk: n_chunk
53
+ }
54
+ end
55
+
@@ -0,0 +1,142 @@
1
+ require 'set'
2
+
3
+ # Base class for classes of templates used to match somewhat arbitrary objects.
4
+ class ObjectTemplate
5
+ # A set implementation that treats the matching operator (===) as membership.
6
+ # Used internally by PortableObjectTemplate, but can also be used in
7
+ # RubyObjectTemplate or in case statements.
8
+ class MemberMatchingSet < Set
9
+ alias === member?
10
+ end
11
+
12
+ # The key_converter is for matching objects that, for example, have had symbol
13
+ # keys serialized to strings. Using a converter that round-trips through the
14
+ # same serialializer, symbols in keys will match strings. However, symbols in
15
+ # values will not.
16
+ def initialize spec, key_converter = nil
17
+ unless spec.respond_to? :size and spec.respond_to? :each
18
+ raise ArgumentError, "cannot be used as a template: #{spec.inspect}"
19
+ end
20
+
21
+ @spec = spec
22
+ @size = spec.size
23
+ @matchers = []
24
+
25
+ if spec.respond_to? :to_hash # assume hash-like
26
+ @shibboleth = :to_hash
27
+ spec.each do |k, v|
28
+ kc = key_converter ? key_converter[k]: k
29
+ # Note: cannot use key_converter[v] because v may have class, regex,
30
+ # or other non-serializable object.
31
+ fill_matchers kc, v
32
+ end
33
+
34
+ else # assume array-like
35
+ @shibboleth = :to_ary
36
+ spec.each_with_index do |v, i|
37
+ fill_matchers i, v unless v.nil?
38
+ end
39
+ end
40
+ end
41
+
42
+ # Reorders the list of matchers so that easy ones come first.
43
+ # For example: nil, then single values (==), then patterns (===).
44
+ # Returns self.
45
+ def optimize!
46
+ @matchers.sort_by! do |k, v|
47
+ case v
48
+ when nil; 0
49
+ when Range; 2
50
+ when Module; 3
51
+ when Regexp; 4
52
+ when MemberMatchingSet; 4
53
+ when Proc; 5
54
+ else 1 # assume it is a value
55
+ end
56
+ end
57
+ self
58
+ end
59
+
60
+ # True if the template matches the given object.
61
+ # Adapted from rinda/rinda.rb.
62
+ def === obj
63
+ return false unless obj.respond_to?(@shibboleth)
64
+ return false unless @size == obj.size
65
+ @matchers.each do |k, v|
66
+ begin
67
+ it = obj.fetch(k)
68
+ rescue
69
+ return false
70
+ end
71
+ next if v.nil?
72
+ next if v == it
73
+ next if v === it
74
+ return false
75
+ end
76
+ return true
77
+ end
78
+
79
+ def inspect
80
+ "<#{self.class}: #{@spec}>"
81
+ end
82
+ end
83
+
84
+ # Template specified by array or hash of ruby objects. The #== and #===
85
+ # methods of entry values are used in matching. Entry values may include
86
+ # classes, regexes, ranges, and so on, in addition to single values.
87
+ class RubyObjectTemplate < ObjectTemplate
88
+ def fill_matchers k, v # :nodoc:
89
+ @matchers << [k, v]
90
+ end
91
+ end
92
+
93
+ # Template specified by array or hash in a portable format composed of
94
+ # strings, numbers, booleans, arrays, and hashes. Special entry values
95
+ # correspond to wildcards and matchers of several kinds. See the unit
96
+ # tests for examples.
97
+ #
98
+ # The objects matched include anything constructed out of numbers, booleans,
99
+ # including null, and strings using hashes and arrays. In other words,
100
+ # objects that can be serialized with json or msgpack.
101
+ #
102
+ class PortableObjectTemplate < ObjectTemplate
103
+ def fill_matchers k, v # :nodoc:
104
+ case v
105
+ when nil
106
+ @matchers << [k, nil]
107
+ # This must be there to ensure the key exists in the hash case.
108
+ when Hash
109
+ v.each do |kk, vv|
110
+ case kk
111
+ when :value, "value"
112
+ @matchers << [k, vv]
113
+ when :set, "set"
114
+ @matchers << [k, MemberMatchingSet.new(vv)]
115
+ when :type, "type"
116
+ @matchers << [k, CLASS_FOR[vv.to_s]]
117
+ when :range, "range"
118
+ @matchers << [k, Range.new(*vv)]
119
+ when :regex, "regex"
120
+ @matchers << [k, Regexp.new(vv)]
121
+ else
122
+ raise ArgumentError,
123
+ "unrecognized match specifier: #{kk.inspect}"
124
+ end
125
+ end
126
+ else
127
+ raise ArgumentError,
128
+ "expected nil or Hash in template, found #{v.inspect}"
129
+ end
130
+ end
131
+
132
+ CLASS_FOR = {
133
+ "number" => Numeric,
134
+ "string" => String,
135
+ "list" => Array,
136
+ "map" => Hash
137
+ }
138
+
139
+ CLASS_FOR.default_proc = proc do |h,k|
140
+ raise ArgumentError, "no known class for matching type #{k.inspect}"
141
+ end
142
+ end
@@ -0,0 +1,11 @@
1
+ module AssertThreequal
2
+ def assert_threequal o1, o2, msg = nil
3
+ msg = message(msg) { "Expected #{mu_pp(o1)} to be === #{mu_pp(o2)}" }
4
+ assert o1.__send__(:===, o2), msg
5
+ end
6
+
7
+ def assert_not_threequal o1, o2, msg = nil
8
+ msg = message(msg) { "Expected #{mu_pp(o1)} to be !== #{mu_pp(o2)}" }
9
+ assert !(o1.__send__(:===, o2)), msg
10
+ end
11
+ end
data/test/lib/eq3.rb ADDED
@@ -0,0 +1,48 @@
1
+ require 'assert-threequal'
2
+
3
+ module Eq3
4
+ include AssertThreequal
5
+
6
+ ROT = RubyObjectTemplate
7
+ POT = PortableObjectTemplate
8
+
9
+ KEY_CONV = proc {|x| Symbol === x ? x.to_s : x}
10
+
11
+ FIELD_NAMES = ["zero", "one", "two", "three", "four", "five", "six"]
12
+
13
+ def mkhash ary
14
+ Hash[ FIELD_NAMES[0...ary.size].zip(ary) ]
15
+ end
16
+
17
+ def pot_for(pot)
18
+ POT.new(pot, KEY_CONV).optimize!
19
+ end
20
+
21
+ def rot_for(rot)
22
+ ROT.new(rot, KEY_CONV).optimize!
23
+ end
24
+
25
+ def eq3 literal, rot, pot
26
+ assert_threequal(rot_for(rot), literal)
27
+ assert_threequal(pot_for(pot), literal)
28
+
29
+ if literal.kind_of? Array
30
+ hlit = mkhash(literal)
31
+ hrot = mkhash(rot)
32
+ hpot = mkhash(pot)
33
+ eq3 hlit, hrot, hpot
34
+ end
35
+ end
36
+
37
+ def ne3 literal, rot, pot
38
+ assert_not_threequal(rot_for(rot), literal)
39
+ assert_not_threequal(pot_for(pot), literal)
40
+
41
+ if literal.kind_of? Array
42
+ hlit = mkhash(literal)
43
+ hrot = mkhash(rot)
44
+ hpot = mkhash(pot)
45
+ ne3 hlit, hrot, hpot
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,28 @@
1
+ $LOAD_PATH.unshift File.join(__dir__, "lib")
2
+
3
+ require 'minitest/autorun'
4
+ require 'object-template'
5
+ require 'eq3'
6
+
7
+ class TestErrors < Minitest::Test
8
+ POT = PortableObjectTemplate
9
+ ROT = RubyObjectTemplate
10
+
11
+ def test_bad_type
12
+ assert_raises ArgumentError do
13
+ POT.new [ {type: "float"} ]
14
+ end
15
+ end
16
+
17
+ def test_bad_entry
18
+ assert_raises ArgumentError do
19
+ POT.new [ "number" ]
20
+ end
21
+ end
22
+
23
+ def test_bad_match_specifier
24
+ assert_raises ArgumentError do
25
+ POT.new [ {foo: 1} ]
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,44 @@
1
+ $LOAD_PATH.unshift File.join(__dir__, "lib")
2
+
3
+ require 'minitest/autorun'
4
+ require 'object-template'
5
+ require 'assert-threequal'
6
+
7
+ class TestKeyConv < Minitest::Test
8
+ include AssertThreequal
9
+
10
+ POT = PortableObjectTemplate
11
+ ROT = RubyObjectTemplate
12
+
13
+ def test_match_converted_key
14
+ assert_threequal(
15
+ POT.new(
16
+ {foo: {value: 1}},
17
+ proc {|x| Symbol === x ? x.to_s : x}),
18
+ {"foo" => 1}
19
+ )
20
+
21
+ assert_threequal(
22
+ ROT.new(
23
+ {foo: 1},
24
+ proc {|x| Symbol === x ? x.to_s : x}),
25
+ {"foo" => 1}
26
+ )
27
+ end
28
+
29
+ def test_not_match_unconverted_key
30
+ assert_not_threequal(
31
+ POT.new(
32
+ {foo: {value: 1}},
33
+ proc {|x| x}),
34
+ {"foo" => 1}
35
+ )
36
+
37
+ assert_not_threequal(
38
+ ROT.new(
39
+ {foo: 1},
40
+ proc {|x| x}),
41
+ {"foo" => 1}
42
+ )
43
+ end
44
+ end
@@ -0,0 +1,165 @@
1
+ $LOAD_PATH.unshift File.join(__dir__, "lib")
2
+
3
+ require 'minitest/autorun'
4
+ require 'object-template'
5
+ require 'set'
6
+ require 'eq3'
7
+
8
+ Member = ObjectTemplate::MemberMatchingSet
9
+
10
+ class TestMatch < Minitest::Test
11
+ include Eq3
12
+
13
+ def test_cardinality
14
+ #=========================================================
15
+ #= LITERAL | ROT | POT
16
+ #=========================================================
17
+ #= 1. every test that can be expressed in terms of presence of column/field
18
+ #= or cardinality of columns/fields, without matching values
19
+ #=
20
+ eq3 [], [], []
21
+ eq3 ["anything"], [nil], [nil]
22
+ eq3 ["any", "thing"], [nil, nil], [nil, nil]
23
+ #=========================================================
24
+ ne3 ["anything"], [], []
25
+ ne3 [], [nil], [nil] # cardinality mismatch
26
+ ne3 ["any", "thing"], [nil], [nil] # ditto
27
+ ne3 ["anything"], [nil, nil], [nil, nil]
28
+ #=========================================================
29
+ #= 1a. specific to hashes
30
+ ne3( {foo: "anything"}, {bar: nil}, {bar: nil} ) # name mismatch
31
+ #=========================================================
32
+ end
33
+
34
+ def test_literal
35
+ #=========================================================
36
+ #= LITERAL | ROT | POT
37
+ #=========================================================
38
+ #= 2. every test that can be expressed in terms of a literal value in the
39
+ #= template
40
+ #=
41
+ literals = [
42
+ true, false,
43
+ 42, -1.2e13, "baz",
44
+ 43, 2.4507, "zap",
45
+ [1,2,3], {a: 1, b: 2},
46
+ ["a", "b"], {x: "y"}
47
+ ]
48
+ literals.each do |x1|
49
+ literals.each do |x2|
50
+ if x1 == x2
51
+ eq3 [x1], [x1], [{value: x1}]
52
+ else
53
+ ne3 [x1], [x2], [{value: x2}]
54
+ end
55
+ eq3 [x1, x2], [x1, x2], [{value: x1}, {value: x2}]
56
+ end
57
+ end
58
+ #=========================================================
59
+ end
60
+
61
+ def test_set
62
+ #=========================================================
63
+ #= LITERAL | ROT | POT
64
+ #=========================================================
65
+ #= 3. every test that can be expressed in terms of a set of values in the
66
+ #= template
67
+ #=
68
+ eq3 [1], [Member[0,1,2]], [{set: [0,1,2]}]
69
+ #=========================================================
70
+ ne3 [4], [Member[0,1,2]], [{set: [0,1,2]}]
71
+ #=========================================================
72
+ end
73
+
74
+ def test_range
75
+ #=========================================================
76
+ #= LITERAL | ROT | POT
77
+ #=========================================================
78
+ #= 4. every test that can be expressed in terms of a range in the
79
+ #= template
80
+ #=
81
+ eq3 [0], [0..2], [{range: [0,2]}]
82
+ eq3 [2], [0..2], [{range: [0,2]}]
83
+ eq3 [1], [0...2], [{range: [0,2, true]}]
84
+ #=========================================================
85
+ ne3 [-1], [0..2], [{range: [0,2]}]
86
+ ne3 [3], [0..2], [{range: [0,2]}]
87
+ ne3 [2], [0...2], [{range: [0,2, true]}]
88
+ #=========================================================
89
+ end
90
+
91
+ def test_regex
92
+ #=========================================================
93
+ #= LITERAL | ROT | POT
94
+ #=========================================================
95
+ #= 5. every test that can be expressed in terms of a regex in the
96
+ #= template
97
+ #=
98
+ eq3 ["foo"], [/oo/], [{regex: "oo"}]
99
+ eq3 ["foo"], [/[f]\w\w/], [{regex: "[f]\\w\\w"}]
100
+ #=========================================================
101
+ ne3 ["foo"], [/zz/], [{regex: "zz"}]
102
+ ne3 ["foo"], [/o{3,}/], [{regex: "o{3,}"}]
103
+ #=========================================================
104
+ end
105
+
106
+ def test_type
107
+ #=========================================================
108
+ #= LITERAL | ROT | POT
109
+ #=========================================================
110
+ #= 6. every test that can be expressed in terms of a type in the
111
+ #= template
112
+ #=
113
+ eq3 [42], [Numeric], [{type: "number"}]
114
+ eq3 ["foo"], [String], [{type: "string"}]
115
+ eq3 [[1,2,3]], [Array], [{type: "list"}]
116
+ eq3 [{a:1, b:2}], [Hash], [{type: "map"}]
117
+ #=========================================================
118
+ ne3 ["42"], [Numeric], [{type: "number"}]
119
+ ne3 [123], [String], [{type: "string"}]
120
+ ne3 [{a:1, b:2}], [Array], [{type: "list"}]
121
+ ne3 [[1,2,3]], [Hash], [{type: "map"}]
122
+ #=========================================================
123
+ end
124
+
125
+ def test_conjunction
126
+ ## note that ROT doesn't support conjunctions, though it would be easy
127
+ ## to implement.
128
+ eq3 ["abc"], [nil], [{type: "string", regex: "b", set: ["abc", "def"]}]
129
+ ne3 ["abc"], [0], [{type: "list", regex: "b", set: ["abc", "def"]}]
130
+ ne3 ["abc"], [0], [{type: "string", regex: "d", set: ["abc", "def"]}]
131
+ ne3 ["abc"], [0], [{type: "string", regex: "b", set: ["xyz", "def"]}]
132
+ end
133
+
134
+ # test that a mismatch in one entry makes the template match fail
135
+ def test_all_entries
136
+ eq3 [ 5, 1.23, "bar", "baz", [1,2], {a: 1} ],
137
+ [ Numeric, 1..3, String, /az/, Array, {a: 1} ],
138
+ [ {type: "number"}, {range: [1,3]}, {type: "string"}, {regex: "az"}, {type: "list"}, {value: {a: 1}} ]
139
+
140
+ ne3 [ "foo", 1.23, "bar", "baz", [1,2], {a: 1} ],
141
+ [ Numeric, 1..3, String, /az/, Array, {a: 1} ],
142
+ [ {type: "number"}, {range: [1,3]}, {type: "string"}, {regex: "az"}, {type: "list"}, {value: {a: 1}} ]
143
+
144
+ ne3 [ 5, 4.23, "bar", "baz", [1,2], {a: 1} ],
145
+ [ Numeric, 1..3, String, /az/, Array, {a: 1} ],
146
+ [ {type: "number"}, {range: [1,3]}, {type: "string"}, {regex: "az"}, {type: "list"}, {value: {a: 1}} ]
147
+
148
+ ne3 [ 5, 1.23, ["bar"], "baz", [1,2], {a: 1} ],
149
+ [ Numeric, 1..3, String, /az/, Array, {a: 1} ],
150
+ [ {type: "number"}, {range: [1,3]}, {type: "string"}, {regex: "az"}, {type: "list"}, {value: {a: 1}} ]
151
+
152
+ ne3 [ 5, 1.23, "bar", "bAz", [1,2], {a: 1} ],
153
+ [ Numeric, 1..3, String, /az/, Array, {a: 1} ],
154
+ [ {type: "number"}, {range: [1,3]}, {type: "string"}, {regex: "az"}, {type: "list"}, {value: {a: 1}} ]
155
+
156
+ ne3 [ 5, 1.23, "bar", "baz", {a:2}, {a: 1} ],
157
+ [ Numeric, 1..3, String, /az/, Array, {a: 1} ],
158
+ [ {type: "number"}, {range: [1,3]}, {type: "string"}, {regex: "az"}, {type: "list"}, {value: {a: 1}} ]
159
+
160
+ ne3 [ 5, 1.23, "bar", "baz", [1,2], {a: 2} ],
161
+ [ Numeric, 1..3, String, /az/, Array, {a: 1} ],
162
+ [ {type: "number"}, {range: [1,3]}, {type: "string"}, {regex: "az"}, {type: "list"}, {value: {a: 1}} ]
163
+
164
+ end
165
+ end
metadata ADDED
@@ -0,0 +1,68 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: object-template
3
+ version: !ruby/object:Gem::Version
4
+ version: '0.1'
5
+ platform: ruby
6
+ authors:
7
+ - Joel VanderWerf
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-07-12 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Templates for matching objects.
14
+ email: vjoel@users.sourceforge.net
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files:
18
+ - README.md
19
+ - COPYING
20
+ files:
21
+ - README.md
22
+ - COPYING
23
+ - Rakefile
24
+ - lib/object-template.rb
25
+ - bench/lib/bench.rb
26
+ - bench/bench-match.rb
27
+ - test/lib/assert-threequal.rb
28
+ - test/lib/eq3.rb
29
+ - test/test-match.rb
30
+ - test/test-key-conv.rb
31
+ - test/test-errors.rb
32
+ homepage: https://github.com/vjoel/object-template
33
+ licenses:
34
+ - BSD
35
+ metadata: {}
36
+ post_install_message:
37
+ rdoc_options:
38
+ - --quiet
39
+ - --line-numbers
40
+ - --inline-source
41
+ - --title
42
+ - object-template
43
+ - --main
44
+ - README.md
45
+ require_paths:
46
+ - lib
47
+ - ext
48
+ required_ruby_version: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - '>='
51
+ - !ruby/object:Gem::Version
52
+ version: '0'
53
+ required_rubygems_version: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - '>='
56
+ - !ruby/object:Gem::Version
57
+ version: '0'
58
+ requirements: []
59
+ rubyforge_project:
60
+ rubygems_version: 2.0.4
61
+ signing_key:
62
+ specification_version: 4
63
+ summary: Templates for matching objects
64
+ test_files:
65
+ - test/test-match.rb
66
+ - test/test-key-conv.rb
67
+ - test/test-errors.rb
68
+ has_rdoc: