match_map 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/CHANGES +4 -0
- data/Gemfile +16 -0
- data/LICENSE.txt +20 -0
- data/README.md +195 -0
- data/Rakefile +47 -0
- data/VERSION +1 -0
- data/bench/bench.rb +62 -0
- data/lib/match_map.rb +102 -0
- data/test/helper.rb +21 -0
- data/test/test_match_map.rb +213 -0
- metadata +115 -0
data/.document
ADDED
data/CHANGES
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
source "http://rubygems.org"
|
2
|
+
# Add dependencies required to use your gem here.
|
3
|
+
# Example:
|
4
|
+
# gem "activesupport", ">= 2.3.5"
|
5
|
+
|
6
|
+
# Add dependencies to develop your gem here.
|
7
|
+
# Include everything needed to run rake, tests, features, etc.
|
8
|
+
|
9
|
+
|
10
|
+
group :development do
|
11
|
+
gem "minitest", ">= 0"
|
12
|
+
gem "yard", "~> 0.6.0"
|
13
|
+
gem "bundler", "~> 1.0.0"
|
14
|
+
gem "jeweler", "~> 1.6.4"
|
15
|
+
gem 'turn', '>=0.9.3'
|
16
|
+
end
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011 Bill Dueber
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,195 @@
|
|
1
|
+
# MatchMap -- a multimap where key matching is based on regular expressions
|
2
|
+
|
3
|
+
`match_map` defines an object with a hash-like interface that allows Regexp patterns as keys and/or multiple simultaneous lookuup arguments. Calling `mm[arg]` checks _arg_ against every key, aggregating their associated values into an array.
|
4
|
+
|
5
|
+
```ruby
|
6
|
+
require 'match_map'
|
7
|
+
|
8
|
+
mm = MatchMap.new
|
9
|
+
mm['a'] = 'a_string'
|
10
|
+
mm[/a/] = 'apat'
|
11
|
+
mm[/b/] = ['bpat1', 'bpat2']
|
12
|
+
mm[/.+b$/] = 'bpat3'
|
13
|
+
|
14
|
+
mm['a'] #=> ['a_string', 'apat'] # order is the same as the key order
|
15
|
+
mm['aa'] #=> ['apat']
|
16
|
+
mm['b'] #=> ['bpat1', 'bpat2']
|
17
|
+
mm['cob'] #=> ['bpat1', 'bpat2', 'bpat3'] # flattened one level!!!
|
18
|
+
mm['cab'] #=> ['apat', 'bpat1', 'bpat2', 'bpat3']
|
19
|
+
mm['c'] #=> nil # no match
|
20
|
+
|
21
|
+
|
22
|
+
# Change the default miss value to ease some processing forms
|
23
|
+
mm.default = []
|
24
|
+
mm['neverGonnaMatch'].each do { #never get here}
|
25
|
+
|
26
|
+
# You can also query on multiple values at once by passing an array
|
27
|
+
mm[['a', 'aa', 'b']] #=> ['a_string', 'apat', 'bpat1', 'bpat2']
|
28
|
+
|
29
|
+
# Or use a Proc as the value; it gets the match variable as its argument
|
30
|
+
|
31
|
+
mm = MatchMap.new
|
32
|
+
mm[/a(b+)/] = Proc.new {|m| [m[1].size]}
|
33
|
+
mm['abbbb'] #=> [4]
|
34
|
+
|
35
|
+
|
36
|
+
# You can set #echo to return the argument :always or only :onmiss
|
37
|
+
mm = MatchMap.new
|
38
|
+
mm[/ab/] = "AB"
|
39
|
+
|
40
|
+
# first, without echo
|
41
|
+
mm['miss'] = nil
|
42
|
+
mm['cab'] = ['AB']
|
43
|
+
|
44
|
+
#...then with echo = :always
|
45
|
+
mm.echo = :always
|
46
|
+
mm['miss'] = ['miss']
|
47
|
+
mm['cab'] = ['cab', 'AB']
|
48
|
+
|
49
|
+
#...and again with echo = :onmiss
|
50
|
+
mm.echo = :onmiss
|
51
|
+
mm['miss'] #=> ['miss'] # because nothing else matched
|
52
|
+
mm['cab'] #=> ['AB'] # because a match was found
|
53
|
+
|
54
|
+
# Need to ditch a key?
|
55
|
+
if mm.has_key? /ab/
|
56
|
+
mm.delete /ab/
|
57
|
+
end
|
58
|
+
|
59
|
+
```
|
60
|
+
|
61
|
+
A MatchMap is a hash-like with the following properties:
|
62
|
+
|
63
|
+
* keys can be anything that responds to '==' (e.g., strings) or regular expressions
|
64
|
+
* keys cannot be repeated (mirroring how a hash works, but see below about multiple values)
|
65
|
+
* arguments are compared to non-pattern keys based on ==
|
66
|
+
* arguments are compared to pattern keys based on pattern match against arg.to_s_
|
67
|
+
* values can be scalars, arrays (treated as multiple return values), or Proc objects
|
68
|
+
* a scalar argument to #[] is left alone for comparison to non-patterns (so a string or integer can be exactly matched), but converted to a string for comparison to patterns. (see "How are arguments compared to keys?", below)
|
69
|
+
* an array argument to #[] is treated as if you want all values for all matches for all array members
|
70
|
+
* the return value from #[] goes through #uniq and #compact (no repeated values, no nil values), which may or may not mess with what you expect the output order to be.
|
71
|
+
|
72
|
+
The idea is that you can set up a bunch of (possibly overlapping) patterns, each of which is associated with one or more values, and easily get back all the values for those patterns that match the argument. `match_map` was originally designed for transforming values for full-text indexing, but has other uses as well.
|
73
|
+
|
74
|
+
|
75
|
+
## What is this good for?
|
76
|
+
|
77
|
+
A match_map can be useful for (among other things) values that map onto a hierarchy.
|
78
|
+
|
79
|
+
Here's part of a map for library call numbers:
|
80
|
+
|
81
|
+
```ruby
|
82
|
+
mm = MatchMap.new
|
83
|
+
mm[/^H/] = 'Social Science'
|
84
|
+
mm[/^HA/] = 'Statistics'
|
85
|
+
mm['HA37 .P27 P16'] #=> ['Social Science', 'Statistics']
|
86
|
+
```
|
87
|
+
|
88
|
+
Or use it as a clean way to extract semi-regular information from free-text strings
|
89
|
+
|
90
|
+
```ruby
|
91
|
+
state = MatchMap.new
|
92
|
+
state[/\bMN\b/i] = 'Minnesota'
|
93
|
+
state[/\bMI\b/i] = 'Michigan'
|
94
|
+
state['St. Paul, MN 55117'] #=> ['Minnesota']
|
95
|
+
state['2274 Delaware Drive, Ann Arbor, MI, 48103'] #=> ['Michigan']
|
96
|
+
```
|
97
|
+
|
98
|
+
|
99
|
+
## How are arguments compared to keys?
|
100
|
+
|
101
|
+
There are three basic rules:
|
102
|
+
|
103
|
+
1. If the argument is an array, each element is handled separately
|
104
|
+
2. If the argument (`a`) is being matched against a pattern key (`pk`), check if `pk.match(a.to_s)`
|
105
|
+
3. If the argument (`a`) is being matched against a key that is not a pattern (`npk`), check if `a == npk`
|
106
|
+
|
107
|
+
Here's a quick example to show how it works
|
108
|
+
|
109
|
+
```ruby
|
110
|
+
mm = MatchMap.new
|
111
|
+
mm[1] = 'fixnum'
|
112
|
+
mm['1'] = 'string'
|
113
|
+
mm[/1/] = 'pattern'
|
114
|
+
mm[1] #=> ['fixnum', 'pattern']
|
115
|
+
mm['1'] #=> ['string', 'pattern']
|
116
|
+
```
|
117
|
+
|
118
|
+
|
119
|
+
## Using Proc objects as values
|
120
|
+
|
121
|
+
You can also use a Proc object as a value. It must:
|
122
|
+
|
123
|
+
* take a single argument; the match variable
|
124
|
+
* return a (possibly empty) _array of values_
|
125
|
+
|
126
|
+
This can be abused, of course, but can be useful. Here's a simple example that reverses the order of a comma-delimited duple.
|
127
|
+
|
128
|
+
```ruby
|
129
|
+
mm = MatchMap.new
|
130
|
+
mm[/^(.+),\s*(.+)$/] = Proc.new {|m| "#{m[2]} #{m[1]}"}
|
131
|
+
mm['Dueber, Bill'] #=> ["Bill Dueber"]
|
132
|
+
```
|
133
|
+
## Using echo to always/sometimes get back the argument
|
134
|
+
|
135
|
+
There are two common requirements when doing this sort of translation for indexing:
|
136
|
+
|
137
|
+
* The raw argument should always appear in the output
|
138
|
+
* The raw argument should appear in the output only if there are no other matches.
|
139
|
+
|
140
|
+
```ruby
|
141
|
+
mm = MatchMap.new
|
142
|
+
mm[/ab/] = "AB"
|
143
|
+
|
144
|
+
# first, without echo
|
145
|
+
mm['miss'] = []
|
146
|
+
mm['cab'] = ['AB']
|
147
|
+
|
148
|
+
#...then with echo = :always
|
149
|
+
mm.echo = :always
|
150
|
+
mm['miss'] = ['miss']
|
151
|
+
mm['cab'] = ['cab', 'AB']
|
152
|
+
|
153
|
+
#...and again with echo = :onmiss
|
154
|
+
mm.echo = :onmiss
|
155
|
+
mm['miss'] #=> ['miss'] # because nothing else matched
|
156
|
+
mm['cab'] #=> ['AB'] # because a match was found
|
157
|
+
```
|
158
|
+
|
159
|
+
Note that the `default` value will never be added to the output if `echo` is set.
|
160
|
+
|
161
|
+
## Optimizing
|
162
|
+
|
163
|
+
You can call `mm.optimize!` to attempt to optimize a MatchMap where none of the keys are regular expressions
|
164
|
+
and none of the values are Proc objects for a significant speed increase (run `rake bench` for an idea
|
165
|
+
of how much faster). This allows you to take advantage of all the differences between MatchMap and a regular
|
166
|
+
hash (pass multiple arguments, flatten return values, echoing, etc.) while remaining an O(1) operation (instead
|
167
|
+
of a O(n) for the standard, try-to-match-each-key-in-turn algorithm).
|
168
|
+
|
169
|
+
Note that a call to `#optimize!` actually picks the best algorithm for that particular map, so if you have a simple map,
|
170
|
+
call `#optmize!`, and add a regular-expression key, another call to `#optmize!` is required to start using the
|
171
|
+
regular algorithm again.
|
172
|
+
|
173
|
+
Obviously, only call `#optimize!` when you're sure you won't be modifying the map anymore.
|
174
|
+
|
175
|
+
## Gotchas
|
176
|
+
|
177
|
+
* Like a hash, repeated assignment to the same key results in a replacement. So `mm[/a/] = 'a'; mm[/a/] = 'A'` will give `mm['a'] #=> ['A']`
|
178
|
+
* Return values are flattened one level. So, a => 1 and b => [2,3], the something that matches both will return [1,2,3]. If you really want to return an array, you need to do something like `m['a'] = [[1,2]]`
|
179
|
+
|
180
|
+
|
181
|
+
## Contributing to MatchMap
|
182
|
+
|
183
|
+
* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
|
184
|
+
* Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
|
185
|
+
* Fork the project
|
186
|
+
* Start a feature/bugfix branch
|
187
|
+
* Commit and push until you are happy with your contribution
|
188
|
+
* Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
|
189
|
+
* Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
|
190
|
+
|
191
|
+
## Copyright
|
192
|
+
|
193
|
+
Copyright (c) 2011 Bill Dueber. See LICENSE.txt for
|
194
|
+
further details.
|
195
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'bundler'
|
5
|
+
begin
|
6
|
+
Bundler.setup(:default, :development)
|
7
|
+
rescue Bundler::BundlerError => e
|
8
|
+
$stderr.puts e.message
|
9
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
10
|
+
exit e.status_code
|
11
|
+
end
|
12
|
+
require 'rake'
|
13
|
+
|
14
|
+
require 'jeweler'
|
15
|
+
Jeweler::Tasks.new do |gem|
|
16
|
+
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
17
|
+
gem.required_ruby_version = '>= 1.9.0' # due to use of define_singleton_method in optimize
|
18
|
+
gem.name = "match_map"
|
19
|
+
gem.homepage = "http://github.com/billdueber/match_map"
|
20
|
+
gem.license = "MIT"
|
21
|
+
gem.summary = "A multimap that allows keys to match regex patterns"
|
22
|
+
gem.description = %Q{MatchMap is a map representing key=>value pairs but where
|
23
|
+
(a) a query argument can match more than one key, and (b) the argument is compraed to the key
|
24
|
+
such that you can use regex patterns as keys}
|
25
|
+
gem.email = "bill@dueber.com"
|
26
|
+
gem.authors = ["Bill Dueber"]
|
27
|
+
# dependencies defined in Gemfile
|
28
|
+
end
|
29
|
+
Jeweler::RubygemsDotOrgTasks.new
|
30
|
+
|
31
|
+
require 'rake/testtask'
|
32
|
+
Rake::TestTask.new(:test) do |test|
|
33
|
+
test.libs << 'lib' << 'test'
|
34
|
+
test.pattern = 'test/**/test_*.rb'
|
35
|
+
test.verbose = true
|
36
|
+
end
|
37
|
+
|
38
|
+
desc "Run a quick benchmark"
|
39
|
+
task :bench do
|
40
|
+
$: << 'lib'
|
41
|
+
load 'bench/bench.rb'
|
42
|
+
end
|
43
|
+
|
44
|
+
task :default => :test
|
45
|
+
|
46
|
+
require 'yard'
|
47
|
+
YARD::Rake::YardocTask.new
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
1.1.0
|
data/bench/bench.rb
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'benchmark'
|
2
|
+
require 'match_map'
|
3
|
+
|
4
|
+
h5 = {
|
5
|
+
'a' => 'A',
|
6
|
+
'b' => 'B',
|
7
|
+
'c' => 'C',
|
8
|
+
'd' => 'D',
|
9
|
+
'e' => 'E',
|
10
|
+
# /a/ => 'AAA'
|
11
|
+
}
|
12
|
+
|
13
|
+
@mm = MatchMap.new
|
14
|
+
h5.each_pair {|k,v| @mm[k] = v}
|
15
|
+
|
16
|
+
@mm2 = MatchMap.new()
|
17
|
+
(1..20).each do |i|
|
18
|
+
@mm2[i] = i*2
|
19
|
+
end
|
20
|
+
# @mm2[/a/] = 'AAA'
|
21
|
+
|
22
|
+
iters = 100_000
|
23
|
+
puts "Testing #{iters} accesses"
|
24
|
+
|
25
|
+
method = (defined? JRUBY_VERSION) ? :bmbm : :bm
|
26
|
+
Benchmark.send(method) do |x|
|
27
|
+
|
28
|
+
x.report('hash ') do
|
29
|
+
1..iters.times do
|
30
|
+
y = h5['a']
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
x.report('straight 5 keys ') do
|
35
|
+
1..iters.times do
|
36
|
+
y = @mm['a']
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
x.report('optimized 5 keys') do
|
41
|
+
@mm.optimize!
|
42
|
+
1..iters.times do
|
43
|
+
y = @mm['a']
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
x.report('straight 20 keys') do
|
48
|
+
1..iters.times do
|
49
|
+
y = @mm2['a']
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
|
54
|
+
x.report('optimized 20 keys') do
|
55
|
+
@mm2.optimize!
|
56
|
+
1..iters.times do
|
57
|
+
y = @mm2['a']
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
|
62
|
+
end
|
data/lib/match_map.rb
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
# A hash-like object that tries to match an argument against
|
2
|
+
# *all* keys (using == for non-Regexp keys and pattern matching
|
3
|
+
# for Regexp keys)
|
4
|
+
|
5
|
+
class MatchMap
|
6
|
+
|
7
|
+
#
|
8
|
+
attr_accessor :default
|
9
|
+
attr_reader :echo
|
10
|
+
|
11
|
+
|
12
|
+
def echo= arg
|
13
|
+
raise RuntimeError.new, "echo value must be :onmiss or :always" unless [:onmiss, :always].include? arg
|
14
|
+
@echo = arg
|
15
|
+
end
|
16
|
+
|
17
|
+
def []= key, val
|
18
|
+
@map[key] = val
|
19
|
+
@keys.push key unless @keys.include? key
|
20
|
+
set_attrs key, val
|
21
|
+
end
|
22
|
+
|
23
|
+
def set_attrs key, val
|
24
|
+
@attrs[key] = {:regexkey => (key.is_a? Regexp), :procval => (val.is_a? Proc)}
|
25
|
+
end
|
26
|
+
|
27
|
+
def [] arg
|
28
|
+
rv = []
|
29
|
+
rv.push *arg if @echo == :always
|
30
|
+
if arg.is_a? Array
|
31
|
+
arg.map {|s| inner = self.inner_get(s); rv.push *inner}
|
32
|
+
else
|
33
|
+
inner = self.inner_get arg
|
34
|
+
rv.push *inner
|
35
|
+
end
|
36
|
+
rv.uniq!
|
37
|
+
rv.compact!
|
38
|
+
if rv.size == 0
|
39
|
+
if @echo == :onmiss
|
40
|
+
return [*arg]
|
41
|
+
else
|
42
|
+
return @default
|
43
|
+
end
|
44
|
+
end
|
45
|
+
return rv
|
46
|
+
end
|
47
|
+
|
48
|
+
def optimized_inner_get arg
|
49
|
+
return [@map[arg]]
|
50
|
+
end
|
51
|
+
|
52
|
+
def normal_inner_get arg
|
53
|
+
rv = []
|
54
|
+
@keys.each do |k|
|
55
|
+
if k.is_a? Regexp
|
56
|
+
m = k.match arg.to_s
|
57
|
+
else
|
58
|
+
m = (k == arg) ? arg : false
|
59
|
+
end
|
60
|
+
if m
|
61
|
+
v = @map[k]
|
62
|
+
if v.is_a? Proc
|
63
|
+
processed = v.call(m)
|
64
|
+
rv.push *processed if processed
|
65
|
+
else
|
66
|
+
rv.push *v
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
return rv
|
71
|
+
end
|
72
|
+
|
73
|
+
def has_key? key
|
74
|
+
@map.has_key? key
|
75
|
+
end
|
76
|
+
|
77
|
+
def delete key
|
78
|
+
@map.delete(key)
|
79
|
+
@keys.delete(key)
|
80
|
+
end
|
81
|
+
|
82
|
+
def initialize hash = {}
|
83
|
+
@default = nil # default miss value is nil
|
84
|
+
@echo = echo
|
85
|
+
@keys = hash.keys
|
86
|
+
@map = hash
|
87
|
+
@attrs = {}
|
88
|
+
@map.each_pair {|k, v| set_attrs k, v}
|
89
|
+
define_singleton_method :inner_get, method(:normal_inner_get)
|
90
|
+
end
|
91
|
+
|
92
|
+
def optimize!
|
93
|
+
@map.each_pair do |k,v|
|
94
|
+
if k.is_a? Regexp or v.is_a? Proc
|
95
|
+
define_singleton_method :inner_get, method(:normal_inner_get)
|
96
|
+
return
|
97
|
+
end
|
98
|
+
end
|
99
|
+
define_singleton_method :inner_get, method(:optimized_inner_get)
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
data/test/helper.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
begin
|
4
|
+
Bundler.setup(:default, :development)
|
5
|
+
rescue Bundler::BundlerError => e
|
6
|
+
$stderr.puts e.message
|
7
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
8
|
+
exit e.status_code
|
9
|
+
end
|
10
|
+
require 'minitest/spec'
|
11
|
+
require 'minitest/benchmark'
|
12
|
+
begin; require 'turn'; rescue LoadError; end
|
13
|
+
|
14
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
15
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
16
|
+
require 'match_map'
|
17
|
+
|
18
|
+
class MiniTest::Unit::TestCase
|
19
|
+
end
|
20
|
+
|
21
|
+
MiniTest::Unit.autorun
|
@@ -0,0 +1,213 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
describe MatchMap do
|
4
|
+
|
5
|
+
before do
|
6
|
+
@h = MatchMap.new
|
7
|
+
end
|
8
|
+
|
9
|
+
describe "when empty" do
|
10
|
+
|
11
|
+
it "should return nil (default)" do
|
12
|
+
@h['a'].must_equal nil
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'should allow set of default' do
|
16
|
+
@h.default = 'def'
|
17
|
+
@h['a'].must_equal 'def'
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
describe "when a single string key" do
|
23
|
+
it 'should set correctly' do
|
24
|
+
@h['a'] = 3
|
25
|
+
@h['a'].must_equal [3]
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'should still return default' do
|
29
|
+
@h['a'] = 3
|
30
|
+
@h['c'].must_equal @h.default
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'should reset a value' do
|
34
|
+
@h['a'] = 3
|
35
|
+
@h['a'] = 4
|
36
|
+
@h['a'].must_equal [4]
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'should allow array values' do
|
40
|
+
@h['a'] = [1,2]
|
41
|
+
@h['a'].must_equal [1,2]
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
|
47
|
+
describe "has/delete key" do
|
48
|
+
it "detects key" do
|
49
|
+
@h['a'] = 'a'
|
50
|
+
@h['b'] = 'b'
|
51
|
+
@h.has_key?('a').must_equal true
|
52
|
+
@h.has_key?('c').must_equal false
|
53
|
+
@h.delete('a')
|
54
|
+
@h.has_key?('a').must_equal false
|
55
|
+
@h['a'].must_equal @h.default
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
describe "works with pattern keys" do
|
60
|
+
|
61
|
+
it 'works with an always-match pattern' do
|
62
|
+
@h[/.?/] = 100
|
63
|
+
@h['a'] = 1
|
64
|
+
@h[10].must_equal [100]
|
65
|
+
@h['a'].must_equal [100, 1]
|
66
|
+
end
|
67
|
+
|
68
|
+
|
69
|
+
it "uses a single pattern" do
|
70
|
+
@h[/.+a/] = 1
|
71
|
+
@h['b'].must_equal @h.default
|
72
|
+
@h['a'].must_equal @h.default
|
73
|
+
@h['aa'].must_equal [1]
|
74
|
+
@h['era'].must_equal [1]
|
75
|
+
end
|
76
|
+
|
77
|
+
it "works with disjoint patterns" do
|
78
|
+
@h[/.+a/] = 1
|
79
|
+
@h[/b/] = 2
|
80
|
+
@h['aa'].must_equal [1]
|
81
|
+
@h['ab'].must_equal [2]
|
82
|
+
@h['cab'].must_equal [1,2]
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
describe "works with non-pattern keys" do
|
87
|
+
|
88
|
+
it "is fine with strings" do
|
89
|
+
@h['a'] = 1
|
90
|
+
@h[/a/] = 2
|
91
|
+
@h['a'].must_equal [1,2]
|
92
|
+
@h['aa'].must_equal [2]
|
93
|
+
end
|
94
|
+
|
95
|
+
it 'is fine with fixnums' do
|
96
|
+
@h[1] = 1
|
97
|
+
@h[2] = 2
|
98
|
+
@h[12] = 3
|
99
|
+
@h[1].must_equal [1]
|
100
|
+
@h[2].must_equal [2]
|
101
|
+
@h[12].must_equal [3]
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
describe "works with a Proc" do
|
106
|
+
it 'does an echo proc' do
|
107
|
+
@h[/ab+/] = Proc.new {|m| m[0]}
|
108
|
+
@h['ab'].must_equal ['ab']
|
109
|
+
end
|
110
|
+
|
111
|
+
it 'deals with match data' do
|
112
|
+
@h[/a(b+)/] = Proc.new {|m| [m[0], m[1].size.to_s]}
|
113
|
+
@h[/abb/].must_equal ['abb', '2']
|
114
|
+
end
|
115
|
+
|
116
|
+
it "works with a more complex proc" do
|
117
|
+
mm = MatchMap.new
|
118
|
+
mm[/^(.+),\s*(.+)$/] = Proc.new {|m| "#{m[2]} #{m[1]}"}
|
119
|
+
mm['Dueber, Bill'].must_equal ["Bill Dueber"]
|
120
|
+
end
|
121
|
+
|
122
|
+
it "calls the Proc for string argument, even though that kind of an abuse" do
|
123
|
+
mm = MatchMap.new
|
124
|
+
mm['a'] = Proc.new {|m| [m[0] + 'bbb']}
|
125
|
+
mm['a'].must_equal ['abbb']
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
describe "works with echo" do
|
130
|
+
before do
|
131
|
+
@j = MatchMap.new
|
132
|
+
end
|
133
|
+
|
134
|
+
it "echos when empty" do
|
135
|
+
@j['miss'].must_equal @h.default # no echoing
|
136
|
+
|
137
|
+
@j.echo = :always
|
138
|
+
@j['miss'].must_equal ['miss']
|
139
|
+
|
140
|
+
@j.echo = :onmiss
|
141
|
+
@j['miss'].must_equal ['miss']
|
142
|
+
end
|
143
|
+
|
144
|
+
it "echos when not empty" do
|
145
|
+
@j[/a/] = 'hello'
|
146
|
+
|
147
|
+
@j['miss'].must_equal @j.default
|
148
|
+
|
149
|
+
@j.echo = :always
|
150
|
+
@j['miss'].must_equal ['miss']
|
151
|
+
@j['ab'].must_equal ['ab', 'hello'].sort
|
152
|
+
|
153
|
+
@j.echo = :onmiss
|
154
|
+
@j['miss'].must_equal ['miss']
|
155
|
+
@j['ab'].must_equal ['hello']
|
156
|
+
|
157
|
+
end
|
158
|
+
|
159
|
+
it "works with a Proc and echo" do
|
160
|
+
mm = MatchMap.new
|
161
|
+
mm[/^(.+),\s*(.+)$/] = Proc.new {|m| "#{m[2]} #{m[1]}"}
|
162
|
+
mm.echo = :always
|
163
|
+
mm['Dueber, Bill'].must_equal ['Dueber, Bill', "Bill Dueber"]
|
164
|
+
end
|
165
|
+
|
166
|
+
end
|
167
|
+
|
168
|
+
describe 'Multiple key arguments' do
|
169
|
+
before do
|
170
|
+
@h = MatchMap.new
|
171
|
+
@h['a'] = 1
|
172
|
+
@h['b'] = 2
|
173
|
+
@h[/c/] = 3
|
174
|
+
end
|
175
|
+
|
176
|
+
it 'works with simple array arg' do
|
177
|
+
@h[['a', 'b']].must_equal [1,2]
|
178
|
+
end
|
179
|
+
|
180
|
+
it "works with echo" do
|
181
|
+
@h.echo = :always
|
182
|
+
@h[['a', 'ac']].must_equal ['a', 'ac', 1, 3]
|
183
|
+
end
|
184
|
+
|
185
|
+
it 'misses hard' do
|
186
|
+
@h[[1,2,3]].must_equal nil
|
187
|
+
end
|
188
|
+
|
189
|
+
end
|
190
|
+
|
191
|
+
describe 'Flattens correctly' do
|
192
|
+
before do
|
193
|
+
@h = MatchMap.new
|
194
|
+
@h[/c/] = 1
|
195
|
+
@h[/cc/] = [2, 3]
|
196
|
+
@h[/ccc/] = [[4,5, 6]]
|
197
|
+
@h[/cccc/] = [7, 8, [9, 10]]
|
198
|
+
end
|
199
|
+
|
200
|
+
it 'flattens one level' do
|
201
|
+
@h['cc'].must_equal [1,2,3]
|
202
|
+
end
|
203
|
+
|
204
|
+
it "doesn't over-flatten" do
|
205
|
+
@h['ccc'].must_equal [1, 2, 3, [4,5,6]]
|
206
|
+
end
|
207
|
+
|
208
|
+
it 'allows mixed depth' do
|
209
|
+
@h['cccc'].must_equal [1, 2, 3, [4, 5, 6], 7, 8, [9, 10]]
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
end
|
metadata
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: match_map
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Bill Dueber
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-02-21 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: minitest
|
16
|
+
requirement: &70104080307140 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70104080307140
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: yard
|
27
|
+
requirement: &70104080321880 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ~>
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 0.6.0
|
33
|
+
type: :development
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70104080321880
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: bundler
|
38
|
+
requirement: &70104080321120 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ~>
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: 1.0.0
|
44
|
+
type: :development
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *70104080321120
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: jeweler
|
49
|
+
requirement: &70104080320260 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 1.6.4
|
55
|
+
type: :development
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: *70104080320260
|
58
|
+
- !ruby/object:Gem::Dependency
|
59
|
+
name: turn
|
60
|
+
requirement: &70104080319220 !ruby/object:Gem::Requirement
|
61
|
+
none: false
|
62
|
+
requirements:
|
63
|
+
- - ! '>='
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: 0.9.3
|
66
|
+
type: :development
|
67
|
+
prerelease: false
|
68
|
+
version_requirements: *70104080319220
|
69
|
+
description: ! "MatchMap is a map representing key=>value pairs but where \n (a)
|
70
|
+
a query argument can match more than one key, and (b) the argument is compraed to
|
71
|
+
the key\n such that you can use regex patterns as keys"
|
72
|
+
email: bill@dueber.com
|
73
|
+
executables: []
|
74
|
+
extensions: []
|
75
|
+
extra_rdoc_files:
|
76
|
+
- LICENSE.txt
|
77
|
+
- README.md
|
78
|
+
files:
|
79
|
+
- .document
|
80
|
+
- CHANGES
|
81
|
+
- Gemfile
|
82
|
+
- LICENSE.txt
|
83
|
+
- README.md
|
84
|
+
- Rakefile
|
85
|
+
- VERSION
|
86
|
+
- bench/bench.rb
|
87
|
+
- lib/match_map.rb
|
88
|
+
- test/helper.rb
|
89
|
+
- test/test_match_map.rb
|
90
|
+
homepage: http://github.com/billdueber/match_map
|
91
|
+
licenses:
|
92
|
+
- MIT
|
93
|
+
post_install_message:
|
94
|
+
rdoc_options: []
|
95
|
+
require_paths:
|
96
|
+
- lib
|
97
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
98
|
+
none: false
|
99
|
+
requirements:
|
100
|
+
- - ! '>='
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: 1.9.0
|
103
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
104
|
+
none: false
|
105
|
+
requirements:
|
106
|
+
- - ! '>='
|
107
|
+
- !ruby/object:Gem::Version
|
108
|
+
version: '0'
|
109
|
+
requirements: []
|
110
|
+
rubyforge_project:
|
111
|
+
rubygems_version: 1.8.15
|
112
|
+
signing_key:
|
113
|
+
specification_version: 3
|
114
|
+
summary: A multimap that allows keys to match regex patterns
|
115
|
+
test_files: []
|