sanichi-chess_icu 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/CHANGELOG +2 -0
- data/LICENCE +22 -0
- data/README.rdoc +11 -0
- data/Rakefile +41 -0
- data/VERSION.yml +4 -0
- data/lib/chess_icu.rb +3 -0
- data/lib/name.rb +217 -0
- data/lib/player.rb +143 -0
- data/lib/result.rb +103 -0
- data/lib/tournament.rb +81 -0
- data/lib/tournament_fcsv.rb +155 -0
- data/lib/util.rb +15 -0
- data/spec/name_spec.rb +172 -0
- data/spec/player_spec.rb +276 -0
- data/spec/result_spec.rb +165 -0
- data/spec/spec_helper.rb +3 -0
- data/spec/tournament_fcsv_spec.rb +306 -0
- data/spec/tournament_spec.rb +166 -0
- data/spec/util_spec.rb +33 -0
- metadata +78 -0
data/lib/result.rb
ADDED
@@ -0,0 +1,103 @@
|
|
1
|
+
module ICU
|
2
|
+
class Result
|
3
|
+
attr_reader :round, :player, :score, :colour, :opponent, :rateable
|
4
|
+
|
5
|
+
def initialize(round, player, score, opt={})
|
6
|
+
self.round = round
|
7
|
+
self.player = player
|
8
|
+
self.score = score
|
9
|
+
[:colour, :opponent].each { |a| self.send("#{a}=", opt[a]) unless opt[a].nil? }
|
10
|
+
self.rateable = opt[:rateable] # always attempt to set this, and do it last, to get the right default
|
11
|
+
end
|
12
|
+
|
13
|
+
# Round number. Must be a positive integer.
|
14
|
+
def round=(round)
|
15
|
+
@round = round.to_i
|
16
|
+
raise "invalid round number (#{round})" unless @round > 0
|
17
|
+
end
|
18
|
+
|
19
|
+
# Player number. Can be any integer.
|
20
|
+
def player=(player)
|
21
|
+
@player = case player
|
22
|
+
when Fixnum then player
|
23
|
+
else player.to_i
|
24
|
+
end
|
25
|
+
raise "invalid player number (#{player})" if @player == 0 && !player.to_s.match(/\d/)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Score for the game, even if a default. One of 'W', 'L' or 'D' (after some cleaning up).
|
29
|
+
def score=(score)
|
30
|
+
@score = case score.to_s.strip
|
31
|
+
when /^(1\.0|1|\+|W|w)$/ then 'W'
|
32
|
+
when /^(0\.5|½|\=|D|d)$/ then 'D'
|
33
|
+
when /^(0\.0|0|\-|L|l)$/ then 'L'
|
34
|
+
else raise "invalid score (#{score})"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# The score as a number.
|
39
|
+
def points
|
40
|
+
case @score
|
41
|
+
when 'W' then 1.0
|
42
|
+
when 'L' then 0.0
|
43
|
+
else 0.5
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# Colour. Either 'W' or 'B' after some cleaning up.
|
48
|
+
def colour=(colour)
|
49
|
+
@colour = case colour.to_s
|
50
|
+
when '' then nil
|
51
|
+
when /W/i then 'W'
|
52
|
+
when /B/i then 'B'
|
53
|
+
else raise "invalid colour (#{colour})"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# Opponent player number. Either absent (nil) or any integer except the player number.
|
58
|
+
def opponent=(opponent)
|
59
|
+
@opponent = case opponent
|
60
|
+
when nil then nil
|
61
|
+
when Fixnum then opponent
|
62
|
+
when /^\s*$/ then nil
|
63
|
+
else opponent.to_i
|
64
|
+
end
|
65
|
+
raise "invalid opponent number (#{opponent})" if @opponent == 0 && !opponent.to_s.match(/\d/)
|
66
|
+
raise "opponent number and player number (#{@opponent}) must be different" if @opponent == player
|
67
|
+
self.rateable = true if @opponent
|
68
|
+
end
|
69
|
+
|
70
|
+
# Rateable flag. If false, game is not rateable. Can only be true if there is an opponent.
|
71
|
+
def rateable=(rateable)
|
72
|
+
if opponent.nil?
|
73
|
+
@rateable = false
|
74
|
+
return
|
75
|
+
end
|
76
|
+
@rateable = case rateable
|
77
|
+
when nil then true # default (when absent) is true
|
78
|
+
when false then false # this is the only way to turn it off
|
79
|
+
else true
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# Reverse a result so it is seen from the opponent's perspective.
|
84
|
+
def reverse(rateable=nil)
|
85
|
+
return unless @opponent
|
86
|
+
r = Result.new(@round, @opponent, @score == 'W' ? 'L' : (@score == 'L' ? 'W' : 'D'))
|
87
|
+
r.opponent = @player
|
88
|
+
r.colour = 'W' if @colour == 'B'
|
89
|
+
r.colour = 'B' if @colour == 'W'
|
90
|
+
r.rateable = rateable || @rateable
|
91
|
+
r
|
92
|
+
end
|
93
|
+
|
94
|
+
# Loose equality.
|
95
|
+
def ==(other)
|
96
|
+
return unless other.is_a? Result
|
97
|
+
[:round, :player, :opponent, :colour, :score].each do |m|
|
98
|
+
return false unless self.send(m) == other.send(m)
|
99
|
+
end
|
100
|
+
true
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
data/lib/tournament.rb
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
module ICU
|
2
|
+
class Tournament
|
3
|
+
attr_reader :name, :start, :rounds, :site
|
4
|
+
|
5
|
+
# Constructor.
|
6
|
+
def initialize(name, start, opt={})
|
7
|
+
self.name = name
|
8
|
+
self.start = start
|
9
|
+
[:rounds, :site].each { |a| self.send("#{a}=", opt[a]) unless opt[a].nil? }
|
10
|
+
@player = {}
|
11
|
+
end
|
12
|
+
|
13
|
+
# Tournament name.
|
14
|
+
def name=(name)
|
15
|
+
raise "invalid tournament name (#{name})" unless name.to_s.match(/[a-z]/i)
|
16
|
+
@name = name.to_s.strip
|
17
|
+
end
|
18
|
+
|
19
|
+
# Start data in yyyy-mm-dd format.
|
20
|
+
def start=(start)
|
21
|
+
start = start.to_s.strip
|
22
|
+
@start = Util.parsedate(start)
|
23
|
+
raise "invalid start date (#{start})" unless @start
|
24
|
+
end
|
25
|
+
|
26
|
+
# Number of rounds. Is either unknown (nil) or a positive integer.
|
27
|
+
def rounds=(rounds)
|
28
|
+
@rounds = case rounds
|
29
|
+
when nil then nil
|
30
|
+
when Fixnum then rounds
|
31
|
+
when /^\s*$/ then nil
|
32
|
+
else rounds.to_i
|
33
|
+
end
|
34
|
+
raise "invalid number of rounds (#{rounds})" unless @rounds.nil? || @rounds > 0
|
35
|
+
end
|
36
|
+
|
37
|
+
# Web site. Either unknown or a reasonably valid looking URL.
|
38
|
+
def site=(site)
|
39
|
+
@site = site.to_s.strip
|
40
|
+
@site = nil if @site == ''
|
41
|
+
@site = "http://#{@site}" if @site && !@site.match(/^https?:\/\//)
|
42
|
+
raise "invalid site (#{site})" unless @site.nil? || @site.match(/^https?:\/\/[-\w]+(\.[-\w]+)+(\/[^\s]*)?$/i)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Add players.
|
46
|
+
def add_player(player)
|
47
|
+
raise "invalid player" unless player.class == ICU::Player
|
48
|
+
raise "player number (#{player.num}) should be unique" if @player[player.num]
|
49
|
+
@player[player.num] = player
|
50
|
+
end
|
51
|
+
|
52
|
+
# Get a player by their number.
|
53
|
+
def player(num)
|
54
|
+
@player[num]
|
55
|
+
end
|
56
|
+
|
57
|
+
# Return an array of all the players.
|
58
|
+
def players
|
59
|
+
@player.values
|
60
|
+
end
|
61
|
+
|
62
|
+
# Lookup a player in the tournament.
|
63
|
+
def find_player(player)
|
64
|
+
players.find { |p| p == player }
|
65
|
+
end
|
66
|
+
|
67
|
+
# Add results.
|
68
|
+
def add_result(result, reverse_rateable=true)
|
69
|
+
raise "invalid result" unless result.class == ICU::Result
|
70
|
+
raise "result round number (#{result.round}) inconsistent with number of tournament rounds" if @rounds && result.round > @rounds
|
71
|
+
raise "player number (#{result.player}) does not exist" unless @player[result.player]
|
72
|
+
@player[result.player].add_result(result)
|
73
|
+
if result.opponent
|
74
|
+
raise "opponent number (#{result.opponent}) does not exist" unless @player[result.opponent]
|
75
|
+
reverse = result.reverse
|
76
|
+
reverse.rateable = false unless reverse_rateable
|
77
|
+
@player[result.opponent].add_result(reverse)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,155 @@
|
|
1
|
+
require 'fastercsv'
|
2
|
+
|
3
|
+
module ICU
|
4
|
+
class Tournament
|
5
|
+
class ForeignCSV
|
6
|
+
attr_reader :tournament
|
7
|
+
|
8
|
+
# Constructor.
|
9
|
+
def initialize(csv)
|
10
|
+
@state, @line, @round, @sum = 0, 0, nil, nil
|
11
|
+
parse(csv)
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def parse(csv)
|
17
|
+
@tournament = Tournament.new('Dummy', '2000-01-01')
|
18
|
+
FasterCSV.parse(csv, :row_sep => :auto) do |r|
|
19
|
+
@line += 1 # increment line number
|
20
|
+
next if r.size == 0 # skip empty lines
|
21
|
+
r = r.map{|c| c.nil? ? '' : c.strip} # trim all spaces, turn nils to blanks
|
22
|
+
next if r[0] == '' # skip blanks in column 1
|
23
|
+
@r = r # remember this record for later
|
24
|
+
|
25
|
+
begin
|
26
|
+
case @state
|
27
|
+
when 0 then event
|
28
|
+
when 1 then start
|
29
|
+
when 2 then rounds
|
30
|
+
when 3 then website
|
31
|
+
when 4 then player
|
32
|
+
when 5 then result
|
33
|
+
when 6 then total
|
34
|
+
else raise "internal error - state #{@state} does not exist"
|
35
|
+
end
|
36
|
+
rescue => err
|
37
|
+
raise err.class, "line #{@line}: #{err.message}", err.backtrace unless err.message.match(/^line [1-9]/)
|
38
|
+
raise
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
unless @state == 4
|
43
|
+
exp = case @state
|
44
|
+
when 0 then "the event name"
|
45
|
+
when 1 then "the start date"
|
46
|
+
when 2 then "the number of rounds"
|
47
|
+
when 3 then "the website address"
|
48
|
+
when 5 then "a result for round #{@round+1}"
|
49
|
+
when 6 then "a total score"
|
50
|
+
end
|
51
|
+
raise "line #{@line}: premature termination - expected #{exp}"
|
52
|
+
end
|
53
|
+
raise "line #{@line}: no players found in file" if @tournament.players.size == 0
|
54
|
+
end
|
55
|
+
|
56
|
+
def event
|
57
|
+
abort "the 'Event' keyword", 0 unless @r[0].match(/^(Event|Tournament)$/i)
|
58
|
+
abort "the event name", 1 unless @r.size > 1 && @r[1] != ''
|
59
|
+
@tournament.name = @r[1]
|
60
|
+
@state = 1
|
61
|
+
end
|
62
|
+
|
63
|
+
def start
|
64
|
+
abort "the 'Start' keyword", 0 unless @r[0].match(/^(Start(\s+Date)?|Date)$/i)
|
65
|
+
abort "the start date", 1 unless @r.size > 1 && @r[1] != ''
|
66
|
+
@tournament.start = @r[1]
|
67
|
+
@state = 2
|
68
|
+
end
|
69
|
+
|
70
|
+
def rounds
|
71
|
+
abort "the 'Rounds' keyword", 0 unless @r[0].match(/(Number of )?Rounds$/)
|
72
|
+
abort "the number of rounds", 1 unless @r.size > 1 && @r[1].match(/^[1-9]\d*/)
|
73
|
+
@tournament.rounds = @r[1]
|
74
|
+
@state = 3
|
75
|
+
end
|
76
|
+
|
77
|
+
def website
|
78
|
+
abort "the 'Website' keyword", 0 unless @r[0].match(/^(Web(\s?site)?|Site)$/i)
|
79
|
+
abort "the event website", 1 unless @r.size > 1 && @r[1] != ''
|
80
|
+
@tournament.site = @r[1]
|
81
|
+
@state = 4
|
82
|
+
end
|
83
|
+
|
84
|
+
def player
|
85
|
+
abort "the 'Player' keyword", 0 unless @r[0].match(/^Player$/i)
|
86
|
+
abort "a player's ICU number", 1 unless @r.size > 1 && @r[1].match(/^[1-9]/i)
|
87
|
+
abort "a player's last name", 2 unless @r.size > 2 && @r[2].match(/[a-z]/i)
|
88
|
+
abort "a player's first name", 3 unless @r.size > 3 && @r[3].match(/[a-z]/i)
|
89
|
+
@player = Player.new(@r[3], @r[2], @tournament.players.size + 1, :id => @r[1])
|
90
|
+
old_player = @tournament.find_player(@player)
|
91
|
+
if old_player
|
92
|
+
raise "two players with the same name (#{@player.name}) have conflicting details" unless old_player.eql?(@player)
|
93
|
+
raise "same player (#{@player.name}) has more than one set of results" if old_player.id
|
94
|
+
old_player.subsume(@player)
|
95
|
+
@player = old_player
|
96
|
+
else
|
97
|
+
@tournament.add_player(@player)
|
98
|
+
end
|
99
|
+
@round = 0
|
100
|
+
@state = 5
|
101
|
+
end
|
102
|
+
|
103
|
+
def result
|
104
|
+
@round+= 1
|
105
|
+
abort "round number #{round}", 0 unless @r[0].to_i == @round
|
106
|
+
abort "a colour (W/B) or dash (for a bye)", 2 unless @r.size > 2 && @r[2].match(/^(W|B|-)/i)
|
107
|
+
result = Result.new(@round, @player.num, @r[1])
|
108
|
+
if @r[2] == '-'
|
109
|
+
@tournament.add_result(result)
|
110
|
+
else
|
111
|
+
result.colour = @r[2]
|
112
|
+
opponent = Player.new(@r[4], @r[3], @tournament.players.size + 1, :rating => @r[5], :title => @r[6], :fed => @r[7])
|
113
|
+
raise "opponent must have a rating and federation" unless opponent.rating && opponent.fed
|
114
|
+
old_player = @tournament.find_player(opponent)
|
115
|
+
if old_player
|
116
|
+
raise "two players with the same name (#{opponent.name}) have conflicting details" unless old_player.eql?(opponent)
|
117
|
+
result.opponent = old_player.num
|
118
|
+
if old_player.id
|
119
|
+
old_player.subsume(opponent)
|
120
|
+
old_result = @player.find_result(@round)
|
121
|
+
raise "missing result for player (#{@player.name}) in round #{@round}" unless old_result
|
122
|
+
raise "mismatched results for player (#{old_player.name}) in round #{@round}" unless result == old_result
|
123
|
+
old_result.rateable = true
|
124
|
+
else
|
125
|
+
old_result = old_player.find_result(@round)
|
126
|
+
raise "a player (#{old_player.name}) has more than one game in the same round (#{@round})" if old_result
|
127
|
+
@tournament.add_result(result, false)
|
128
|
+
end
|
129
|
+
else
|
130
|
+
@tournament.add_player(opponent)
|
131
|
+
result.opponent = opponent.num
|
132
|
+
@tournament.add_result(result, false)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
@state = 6 if @round == @tournament.rounds
|
136
|
+
end
|
137
|
+
|
138
|
+
def total
|
139
|
+
points = @player.points
|
140
|
+
abort "the 'Total' keyword", 0 unless @r[0].match(/^Total$/i)
|
141
|
+
abort "the player's (#{@player.object_id}, #{@player.results.size}) total points to be #{points}", 1 unless @r[1].to_f == points
|
142
|
+
@state = 4
|
143
|
+
end
|
144
|
+
|
145
|
+
def abort(expected, cell)
|
146
|
+
got = @r[cell]
|
147
|
+
error = "line #{@line}"
|
148
|
+
error << ", cell #{cell+1}"
|
149
|
+
error << ": expected #{expected}"
|
150
|
+
error << " but got #{got == '' ? 'a blank cell' : "'#{got}'"}"
|
151
|
+
raise error
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
data/lib/util.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
module ICU
|
2
|
+
class Util
|
3
|
+
# Parse dates into yyyy-mm-dd format, preferring European format. Return nil on error.
|
4
|
+
def self.parsedate(date)
|
5
|
+
date = date.to_s
|
6
|
+
return nil unless date.match(/[1-9]/)
|
7
|
+
date.sub!(/^([1-9]|0[1-9]|[12][0-9]|3[01])([^\d])([1-9]|0[1-9]|1[0-2])([^\d])/, '\3\2\1\4')
|
8
|
+
begin
|
9
|
+
Date.parse(date, true).to_s
|
10
|
+
rescue
|
11
|
+
return nil
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
data/spec/name_spec.rb
ADDED
@@ -0,0 +1,172 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
module ICU
|
4
|
+
describe Name do
|
5
|
+
context "public methods" do
|
6
|
+
before(:each) do
|
7
|
+
@simple = Name.new('mark j l', 'orr')
|
8
|
+
end
|
9
|
+
|
10
|
+
it "#first returns the first name(s)" do
|
11
|
+
@simple.first.should == 'Mark J. L.'
|
12
|
+
end
|
13
|
+
|
14
|
+
it "#last returns the last name(s)" do
|
15
|
+
@simple.last.should == 'Orr'
|
16
|
+
end
|
17
|
+
|
18
|
+
it "#name returns the full name with first name(s) first" do
|
19
|
+
@simple.name.should == 'Mark J. L. Orr'
|
20
|
+
end
|
21
|
+
|
22
|
+
it "#rname returns the full name with last name(s) first" do
|
23
|
+
@simple.rname.should == 'Orr, Mark J. L.'
|
24
|
+
end
|
25
|
+
|
26
|
+
it "#to_s is the same as rname" do
|
27
|
+
@simple.to_s.should == 'Orr, Mark J. L.'
|
28
|
+
end
|
29
|
+
|
30
|
+
it "#match returns true if and only if two names match" do
|
31
|
+
@simple.match('mark j l orr').should be_true
|
32
|
+
@simple.match('malcolm g l orr').should be_false
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
context "names that are already canonical" do
|
37
|
+
it "should not be altered" do
|
38
|
+
Name.new('Mark J. L.', 'Orr').name.should == 'Mark J. L. Orr'
|
39
|
+
Name.new('Anna-Marie J.-K.', 'Liviu-Dieter').name.should == 'Anna-Marie J.-K. Liviu-Dieter'
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
context "last names beginning with a single letter followed by a quote" do
|
44
|
+
it "should be handled correctly" do
|
45
|
+
Name.new('una', "O'boyle").name.should == "Una O'Boyle"
|
46
|
+
Name.new('jonathan', 'd`arcy').name.should == "Jonathan D'Arcy"
|
47
|
+
Name.new('erwin e', "L'AMI").name.should == "Erwin E. L'Ami"
|
48
|
+
Name.new('cormac', "o brien").name.should == "Cormac O'Brien"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
context "last beginning with Mc" do
|
53
|
+
it "should be handled correctly" do
|
54
|
+
Name.new('shane', "mccabe").name.should == "Shane McCabe"
|
55
|
+
Name.new('shawn', "macDonagh").name.should == "Shawn MacDonagh"
|
56
|
+
Name.new('shawn', "macdonagh").name.should == "Shawn Macdonagh"
|
57
|
+
Name.new('bartlomiej', "macieja").name.should == "Bartlomiej Macieja"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
context "doubled barrelled names or initials" do
|
62
|
+
it "should be handled correctly" do
|
63
|
+
Name.new('anna-marie', 'den-otter').name.should == 'Anna-Marie Den-Otter'
|
64
|
+
Name.new('j-k', 'rowling').name.should == 'J.-K. Rowling'
|
65
|
+
Name.new("mark j. - l", 'ORR').name.should == 'Mark J.-L. Orr'
|
66
|
+
Name.new('JOHANNA', "lowry-o'REILLY").name.should == "Johanna Lowry-O'Reilly"
|
67
|
+
Name.new('hannah', "lowry - o reilly").name.should == "Hannah Lowry-O'Reilly"
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
context "extraneous white space" do
|
72
|
+
it "should be handled correctly" do
|
73
|
+
Name.new(' mark j l ', " \t\r\n orr \n").name.should == 'Mark J. L. Orr'
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
context "extraneous full stops" do
|
78
|
+
it "should be handled correctly" do
|
79
|
+
Name.new('. mark j..l', 'orr.').name.should == 'Mark J. L. Orr'
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
context "construction from a single string" do
|
84
|
+
it "should be possible in simple cases" do
|
85
|
+
Name.new('ORR, mark j l').name.should == 'Mark J. L. Orr'
|
86
|
+
Name.new('MARK J L ORR').name.should == 'Mark J. L. Orr'
|
87
|
+
Name.new("O'Reilly, j-k").name.should == "J.-K. O'Reilly"
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
context "construction from an instance" do
|
92
|
+
it "should be possible" do
|
93
|
+
Name.new(Name.new('ORR, mark j l')).name.should == 'Mark J. L. Orr'
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
context "constuction corner cases" do
|
98
|
+
it "should be handled correctly" do
|
99
|
+
Name.new('Orr').name.should == 'Orr'
|
100
|
+
Name.new('Orr').rname.should == 'Orr'
|
101
|
+
Name.new('').name.should == ''
|
102
|
+
Name.new('').rname.should == ''
|
103
|
+
Name.new.name.should == ''
|
104
|
+
Name.new.rname.should == ''
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
context "inputs to matching" do
|
109
|
+
before(:all) do
|
110
|
+
@mark = Name.new('Mark', 'Orr')
|
111
|
+
@kram = Name.new('Mark', 'Orr')
|
112
|
+
end
|
113
|
+
|
114
|
+
it "should be flexible" do
|
115
|
+
@mark.match('Mark', 'Orr').should be_true
|
116
|
+
@mark.match('Mark Orr').should be_true
|
117
|
+
@mark.match('Orr, Mark').should be_true
|
118
|
+
@mark.match(@kram).should be_true
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
context "first name matches" do
|
123
|
+
it "should match when first names are the same" do
|
124
|
+
Name.new('Mark', 'Orr').match('Mark', 'Orr').should be_true
|
125
|
+
end
|
126
|
+
|
127
|
+
it "should be flexible with regards to hyphens in double barrelled names" do
|
128
|
+
Name.new('J.-K.', 'Rowling').match('J. K.', 'Rowling').should be_true
|
129
|
+
Name.new('Joanne-K.', 'Rowling').match('Joanne K.', 'Rowling').should be_true
|
130
|
+
end
|
131
|
+
|
132
|
+
it "should match initials" do
|
133
|
+
Name.new('M. J. L.', 'Orr').match('Mark John Legard', 'Orr').should be_true
|
134
|
+
Name.new('M.', 'Orr').match('Mark', 'Orr').should be_true
|
135
|
+
Name.new('M. J. L.', 'Orr').match('Mark', 'Orr').should be_true
|
136
|
+
Name.new('M.', 'Orr').match('M. J.', 'Orr').should be_true
|
137
|
+
Name.new('M. J. L.', 'Orr').match('M. G.', 'Orr').should be_false
|
138
|
+
end
|
139
|
+
|
140
|
+
it "should not match on full names not in first position or without an exact match" do
|
141
|
+
Name.new('J. M.', 'Orr').match('John', 'Orr').should be_true
|
142
|
+
Name.new('M. J.', 'Orr').match('John', 'Orr').should be_false
|
143
|
+
Name.new('M. John', 'Orr').match('John', 'Orr').should be_true
|
144
|
+
end
|
145
|
+
|
146
|
+
it "should handle common nicknames" do
|
147
|
+
Name.new('William', 'Orr').match('Bill', 'Orr').should be_true
|
148
|
+
Name.new('David', 'Orr').match('Dave', 'Orr').should be_true
|
149
|
+
Name.new('Mick', 'Orr').match('Mike', 'Orr').should be_true
|
150
|
+
end
|
151
|
+
|
152
|
+
it "should not mix up nick names" do
|
153
|
+
Name.new('David', 'Orr').match('Bill', 'Orr').should be_false
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
context "last name matches" do
|
158
|
+
it "should be flexible with regards to hyphens in double barrelled names" do
|
159
|
+
Name.new('Johanna', "Lowry-O'Reilly").match('Johanna', "Lowry O'Reilly").should be_true
|
160
|
+
end
|
161
|
+
|
162
|
+
it "should be case insensitive in matches involving Macsomething and MacSomething" do
|
163
|
+
Name.new('Alan', 'MacDonagh').match('Alan', 'Macdonagh').should be_true
|
164
|
+
end
|
165
|
+
|
166
|
+
it "should cater for the common mispelling of names beginning with Mc or Mac" do
|
167
|
+
Name.new('Alan', 'McDonagh').match('Alan', 'MacDonagh').should be_true
|
168
|
+
Name.new('Darko', 'Polimac').match('Darko', 'Polimc').should be_false
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|