sanichi-chess_icu 0.1.0
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.
- 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
|