object-template 0.1

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