RusaMember 1.0.0 → 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/Manifest.txt +24 -0
- data/README.txt +6 -3
- data/Rakefile +0 -1
- data/bin/rusa_member +6 -0
- data/lib/member.rb +71 -0
- data/lib/parse_member.rb +164 -0
- data/lib/parse_result.rb +94 -0
- data/lib/rusa_member.rb +1 -1
- data/test/files/boston.html +209 -0
- data/test/files/clubMembers.txt +1674 -0
- data/test/files/doe.txt +8 -0
- data/test/files/dussler.html +101 -0
- data/test/files/dussler.txt +8 -0
- data/test/files/dussler_results_2006.html +243 -0
- data/test/files/dussler_results_2007.html +278 -0
- data/test/files/dussler_results_2008.html +211 -0
- data/test/files/member_pre.txt +82 -0
- data/test/files/member_search.txt +82 -0
- data/test/files/multiSmith.txt +241 -0
- data/test/files/no_results.html +164 -0
- data/test/files/one_result.html +172 -0
- data/test/files/post.txt +13 -0
- data/test/files/smith.html +173 -0
- data/test/files/smith.txt +10 -0
- data/test/files/thomas.html +101 -0
- data/test/files/thomas_results_2008.html +369 -0
- data/test/html_frags.rb +113 -0
- data/test/test_parse_results.rb +94 -20
- metadata +34 -2
data/Manifest.txt
CHANGED
@@ -3,4 +3,28 @@ Manifest.txt
|
|
3
3
|
README.txt
|
4
4
|
Rakefile
|
5
5
|
bin/rusa_member
|
6
|
+
lib/member.rb
|
7
|
+
lib/parse_member.rb
|
8
|
+
lib/parse_result.rb
|
6
9
|
lib/rusa_member.rb
|
10
|
+
test/files/boston.html
|
11
|
+
test/files/clubMembers.txt
|
12
|
+
test/files/doe.txt
|
13
|
+
test/files/dussler.html
|
14
|
+
test/files/dussler.txt
|
15
|
+
test/files/dussler_results_2006.html
|
16
|
+
test/files/dussler_results_2007.html
|
17
|
+
test/files/dussler_results_2008.html
|
18
|
+
test/files/member_pre.txt
|
19
|
+
test/files/member_search.txt
|
20
|
+
test/files/multiSmith.txt
|
21
|
+
test/files/no_results.html
|
22
|
+
test/files/one_result.html
|
23
|
+
test/files/post.txt
|
24
|
+
test/files/smith.html
|
25
|
+
test/files/smith.txt
|
26
|
+
test/files/thomas.html
|
27
|
+
test/files/thomas_results_2008.html
|
28
|
+
test/html_frags.rb
|
29
|
+
test/test_parse_member.rb
|
30
|
+
test/test_parse_results.rb
|
data/README.txt
CHANGED
@@ -12,8 +12,11 @@ Extract data from RUSA web site to get members information, results, awards
|
|
12
12
|
* FIX (list of features or problems)
|
13
13
|
|
14
14
|
== SYNOPSIS:
|
15
|
-
|
16
|
-
|
15
|
+
print membership information, current year results for member 136
|
16
|
+
bin/rusa_member --id 136
|
17
|
+
|
18
|
+
print membership information, 2003 and 2007 results for Peter Dusel
|
19
|
+
bin/rusa_member --lname dusel --year 2003,2007
|
17
20
|
|
18
21
|
== REQUIREMENTS:
|
19
22
|
|
@@ -21,7 +24,7 @@ Extract data from RUSA web site to get members information, results, awards
|
|
21
24
|
|
22
25
|
== INSTALL:
|
23
26
|
|
24
|
-
|
27
|
+
sudo gem install RusaMember
|
25
28
|
|
26
29
|
== LICENSE:
|
27
30
|
|
data/Rakefile
CHANGED
data/bin/rusa_member
CHANGED
data/lib/member.rb
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
##
|
4
|
+
# collection of people in the club
|
5
|
+
class Members < Hash
|
6
|
+
|
7
|
+
def << person
|
8
|
+
self[person.id.to_i] = person
|
9
|
+
end
|
10
|
+
|
11
|
+
def find_by_id id
|
12
|
+
self[id]
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
class Person
|
18
|
+
ATTRIBUTES =
|
19
|
+
[:first_name, :last_name, :id, :member_expiration_date, :club, :results, :awards]
|
20
|
+
attr_accessor(*ATTRIBUTES)
|
21
|
+
|
22
|
+
def initialize meta = nil
|
23
|
+
## I was trying to avoid initializing an ivar in the do loop
|
24
|
+
## if it had already been initialized, but couldn't figure out how to do that
|
25
|
+
##
|
26
|
+
## I was trying to make this work without having to init the array and hash
|
27
|
+
## after the do loop
|
28
|
+
#@results = []
|
29
|
+
#@awards = {}
|
30
|
+
#ATTRIBUTES.find_all{|a| self.a.nil?}.each do |item| <-- does not work
|
31
|
+
#
|
32
|
+
# the 'unless' below does work but doesn't look right
|
33
|
+
|
34
|
+
@results = []
|
35
|
+
@awards = {}
|
36
|
+
|
37
|
+
ATTRIBUTES.each do |item|
|
38
|
+
self.send("#{item}=", (meta[item] || "")) unless item == :results or item == :awards
|
39
|
+
end if meta
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
##
|
46
|
+
# the result of an event that the member participated in
|
47
|
+
class Result
|
48
|
+
ATTRIBUTES = [ :certificate, :date, :distance, :organizer, :type ]
|
49
|
+
attr_accessor(*ATTRIBUTES)
|
50
|
+
|
51
|
+
def initialize meta = nil
|
52
|
+
ATTRIBUTES.each do |item|
|
53
|
+
self.send("#{item}=", (meta[item] || ""))
|
54
|
+
end if meta
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
|
59
|
+
##
|
60
|
+
# awards for total distance, number of events
|
61
|
+
class Award
|
62
|
+
ATTRIBUTES = [ :type, :year ]
|
63
|
+
attr_accessor(*ATTRIBUTES)
|
64
|
+
|
65
|
+
def initialize meta = nil
|
66
|
+
ATTRIBUTES.each do |item|
|
67
|
+
self.send("#{item}=", (meta[item] || ""))
|
68
|
+
end if meta
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
data/lib/parse_member.rb
ADDED
@@ -0,0 +1,164 @@
|
|
1
|
+
#!/usr/bin/env ruby -w
|
2
|
+
|
3
|
+
##
|
4
|
+
# Student Name: bill dussler
|
5
|
+
# Homework Week: 8
|
6
|
+
#
|
7
|
+
# screen scrape data from a web site, parse content
|
8
|
+
# and instatntiate club member information
|
9
|
+
#
|
10
|
+
require 'rubygems'
|
11
|
+
require 'nokogiri'
|
12
|
+
require 'net/http'
|
13
|
+
require 'optparse'
|
14
|
+
require 'time'
|
15
|
+
require 'lib/member'
|
16
|
+
require 'lib/parse_result'
|
17
|
+
|
18
|
+
class ParseMember
|
19
|
+
attr_accessor :members, :options, :url, :parse_result
|
20
|
+
|
21
|
+
MEMBER_QUERY_URL = 'http://www.rusa.org/cgi-bin/membersearch_PF.pl'
|
22
|
+
|
23
|
+
# match parm key with parameter we will pass
|
24
|
+
PARMS = {'clubs' => 'club',
|
25
|
+
'ids' => 'mid',
|
26
|
+
'lnames' => 'sname'}
|
27
|
+
|
28
|
+
def self.run
|
29
|
+
pm = ParseMember.new
|
30
|
+
pm.run
|
31
|
+
pm.report
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
def initialize
|
36
|
+
@members = Members.new
|
37
|
+
@url = MEMBER_QUERY_URL
|
38
|
+
@parse_result = ParseResult.new
|
39
|
+
|
40
|
+
# The options specified on the command line will be collected in *options*.
|
41
|
+
# We set default values here.
|
42
|
+
@options = {}
|
43
|
+
@options['status'] = 'current'
|
44
|
+
@options['year'] = [Time.now.year]
|
45
|
+
|
46
|
+
opts = OptionParser.new do |opts|
|
47
|
+
opts.banner = "Usage: parse_member.rb [options]"
|
48
|
+
|
49
|
+
# include these clubs
|
50
|
+
opts.on("--club seattle,oregon,boston", Array, "include the following clubs") do |list|
|
51
|
+
@options['clubs'] = list
|
52
|
+
end
|
53
|
+
# include membership ids
|
54
|
+
opts.on("--id 137,2245,2157", Array, "include the following membership ids") do |list|
|
55
|
+
@options['ids'] = list
|
56
|
+
end
|
57
|
+
|
58
|
+
# include these last names
|
59
|
+
opts.on("--lname smith,jones,brown", Array, "Include members with the following last names") do |list|
|
60
|
+
@options['lnames'] = list
|
61
|
+
end
|
62
|
+
|
63
|
+
# get results for these years
|
64
|
+
opts.on("--year ", Array, "get results for the following years. Defaults to current year") do |list|
|
65
|
+
@options['year'] = list
|
66
|
+
end
|
67
|
+
|
68
|
+
# Boolean switch. default is current members (no expired memberships)
|
69
|
+
opts.on("-a", "Current and past members") do |a|
|
70
|
+
@options['status'] = a ? 'all' : 'current'
|
71
|
+
end
|
72
|
+
|
73
|
+
# No argument, shows at tail. This will print an options summary.
|
74
|
+
opts.on_tail("-h", "--help", "Show this message") do
|
75
|
+
puts opts
|
76
|
+
exit
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
opts.parse!(ARGV)
|
81
|
+
|
82
|
+
end
|
83
|
+
|
84
|
+
def run &block
|
85
|
+
if block_given? then
|
86
|
+
doc = fetch(@url, {}, &block)
|
87
|
+
parse(doc, &block)
|
88
|
+
|
89
|
+
else
|
90
|
+
# get requested parms and fetch url with them
|
91
|
+
parms = {'status' => @options['status']}
|
92
|
+
PARMS.each do |key, parm|
|
93
|
+
next if @options[key].nil?
|
94
|
+
@options[key].each do |value|
|
95
|
+
parms[parm] = value
|
96
|
+
doc = fetch(@url, parms, &block)
|
97
|
+
parse(doc)
|
98
|
+
end
|
99
|
+
parms.delete parm
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def fetch url, parms=nil
|
105
|
+
if block_given? then
|
106
|
+
f = yield url
|
107
|
+
else
|
108
|
+
f = Net::HTTP.post_form(URI.parse(url), parms).body
|
109
|
+
end
|
110
|
+
Nokogiri::HTML(f)
|
111
|
+
end
|
112
|
+
|
113
|
+
def parse doc
|
114
|
+
##
|
115
|
+
# the data we care about starts in <td> that lists count of matches found
|
116
|
+
doc.css('td').each do |link|
|
117
|
+
if ( link.text =~ /(\d+) Matches Found\n/im ) then
|
118
|
+
# member element is in a <p>, instantiate Person with relevant data
|
119
|
+
link.css('p').each do |member|
|
120
|
+
member.text =~ /(.*), (.*) \| RUSA No\. (\d+) \|.*\n(.*) \|.*(\d{4}\/\d{2}\/\d{2})/im
|
121
|
+
|
122
|
+
member = Person.new({
|
123
|
+
:last_name => $1.strip,
|
124
|
+
:first_name => $2.strip,
|
125
|
+
:id => $3,
|
126
|
+
:club => $4.strip,
|
127
|
+
:member_expiration_date => Time.parse($5)
|
128
|
+
} )
|
129
|
+
|
130
|
+
# don't run if block given (such as during test)
|
131
|
+
unless block_given? then
|
132
|
+
@options['year'].each do |year|
|
133
|
+
@parse_result.results member, year
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
@members<< member
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def report
|
144
|
+
@members.sort{|a,b| a[1].last_name + a[1].first_name <=> b[1].last_name + b[1].first_name}.each do |id, member|
|
145
|
+
puts "%25s\t%7s\t%12s\t%30s\n" % ["#{member.first_name} #{member.last_name}",
|
146
|
+
member.id,
|
147
|
+
member.member_expiration_date,
|
148
|
+
member.club]
|
149
|
+
member.results.sort{|a,b| a.date <=> b.date}.each do |result|
|
150
|
+
puts " Result: %12s\t%10s\t%10s\t%8s\n" % [result.date, result.distance,
|
151
|
+
result.certificate, result.type]
|
152
|
+
end
|
153
|
+
|
154
|
+
member.awards.each do |year, awards|
|
155
|
+
awards.each do |award|
|
156
|
+
puts " Award: %8s\t%10s\n" % [award.year, award.type]
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
end
|
163
|
+
|
164
|
+
ParseMember.run if $0 == __FILE__
|
data/lib/parse_result.rb
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
#!/usr/bin/env ruby -w
|
2
|
+
|
3
|
+
##
|
4
|
+
# Student Name: bill dussler
|
5
|
+
# Homework Week: 8
|
6
|
+
#
|
7
|
+
# screen scrape data from a web site, parse content
|
8
|
+
# and instatntiate club member information
|
9
|
+
#
|
10
|
+
require 'rubygems'
|
11
|
+
require 'nokogiri'
|
12
|
+
require 'net/http'
|
13
|
+
require 'optparse'
|
14
|
+
require 'time'
|
15
|
+
require 'date'
|
16
|
+
require 'lib/member'
|
17
|
+
|
18
|
+
class ParseResult
|
19
|
+
RESULT_QUERY_URL = 'http://www.rusa.org/cgi-bin/results.pl'
|
20
|
+
|
21
|
+
# match parm key with parameter we will pass
|
22
|
+
PARMS = {'clubs' => 'club',
|
23
|
+
'ids' => 'mid',
|
24
|
+
'lnames' => 'Last_Name',
|
25
|
+
'fnames' => 'First_Name',
|
26
|
+
'years' => 'searchyear',
|
27
|
+
}
|
28
|
+
|
29
|
+
def results member, year=Time.now.year, url=RESULT_QUERY_URL
|
30
|
+
|
31
|
+
if block_given? then
|
32
|
+
f = yield url
|
33
|
+
else
|
34
|
+
parms = {'Last_Name' => member.last_name,
|
35
|
+
'First_Name' => member.first_name,
|
36
|
+
'searchyear' => year
|
37
|
+
}
|
38
|
+
|
39
|
+
f = Net::HTTP.post_form(URI.parse(url), parms).body
|
40
|
+
end
|
41
|
+
doc = Nokogiri::HTML(f)
|
42
|
+
|
43
|
+
parse(member, doc)
|
44
|
+
end
|
45
|
+
|
46
|
+
def parse member, doc
|
47
|
+
##
|
48
|
+
# sanity check-- the li items gives us search terms. compare to member data
|
49
|
+
doc.css('li').each_with_index do |li, i|
|
50
|
+
if li.text =~ /first name=(.*)/i && member.first_name.downcase != $1.downcase then
|
51
|
+
raise "first name #{$1} in results does not match #{member.first_name}"
|
52
|
+
end
|
53
|
+
|
54
|
+
if li.text =~ /last name=(.*)/i && member.last_name.downcase != $1.downcase then
|
55
|
+
raise "last name #{$1} in results does not match #{member.last_name}"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# year is only displayed in results, not as parm anywhere
|
60
|
+
year_results = nil
|
61
|
+
##
|
62
|
+
# the data we care about starts in a <td> that lists the count of matches found
|
63
|
+
doc.css('table table tr').each do |row|
|
64
|
+
result_data = row.css('td')
|
65
|
+
next if result_data.size.zero?
|
66
|
+
|
67
|
+
member.results<< Result.new({
|
68
|
+
:certificate => result_data[0].text,
|
69
|
+
:type => result_data[1].text,
|
70
|
+
:distance => result_data[2].text,
|
71
|
+
# Time.parse needs slashes, not dashes
|
72
|
+
:date => Time.parse(result_data[3].text.gsub("-","/")),
|
73
|
+
:organizer => result_data[4].text,
|
74
|
+
} )
|
75
|
+
end
|
76
|
+
|
77
|
+
year_results = member.results.last.date.year unless member.results.empty?
|
78
|
+
member.awards[year_results] = [] unless year_results.nil? or member.awards.has_key? year_results
|
79
|
+
##
|
80
|
+
# find the awards for this year
|
81
|
+
doc.css('table tr td').each do |row|
|
82
|
+
next unless row.text =~ /Award status: (.*)/i
|
83
|
+
|
84
|
+
$1.split(",").each do |award_type|
|
85
|
+
member.awards[year_results]<< Award.new({
|
86
|
+
:type => award_type.strip,
|
87
|
+
:year => year_results.to_i,
|
88
|
+
})
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
data/lib/rusa_member.rb
CHANGED
@@ -0,0 +1,209 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
|
4
|
+
|
5
|
+
|
6
|
+
|
7
|
+
|
8
|
+
|
9
|
+
|
10
|
+
|
11
|
+
|
12
|
+
|
13
|
+
|
14
|
+
|
15
|
+
|
16
|
+
|
17
|
+
|
18
|
+
|
19
|
+
|
20
|
+
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML Level 3.2//EN">
|
21
|
+
<HTML>
|
22
|
+
<HEAD>
|
23
|
+
<TITLE>RUSA: Members </TITLE>
|
24
|
+
<META HTTP-EQUIV="Keywords" CONTENT="Bicycle Touring, Bicycle Racing, Long Distance Cycling, Cycling, Bicycling, Randonneur, Randonneuring, Brevet, RUSA">
|
25
|
+
<META HTTP-EQUIV="Reply-to" CONTENT="lois_springsteen@prodigy.net (Lois Springsteen)">
|
26
|
+
<BASEFONT SIZE=3>
|
27
|
+
</HEAD>
|
28
|
+
<BODY BGCOLOR="#FFFFFF" TEXT="#003399" LINK="#FF0000" VLINK="#FF0000" ALINK="#003399">
|
29
|
+
<TABLE BORDER=0 HSPACE=0 cellpadding=10 WIDTH=100%>
|
30
|
+
<TR>
|
31
|
+
<TD ALIGN=left VALIGN=middle width=140>
|
32
|
+
<IMG SRC="http://www.rusa.org/Images/logorusa.gif" ALT="RUSA Logo" HSPACE=0 ALIGN=absmiddle>
|
33
|
+
</TD>
|
34
|
+
<TD ALIGN=left VALIGN=middle>
|
35
|
+
<H1><FONT FACE="ARIAL, HELVETICA"><B>Members</B></FONT></H1>
|
36
|
+
</TD>
|
37
|
+
<TD ALIGN=right VALIGN=middle></TD>
|
38
|
+
</TR>
|
39
|
+
</TABLE>
|
40
|
+
<HR SIZE=3 WIDTH="100%" ALIGN=CENTER>
|
41
|
+
<TABLE BORDER=0 HSPACE=0 cellpadding=10>
|
42
|
+
<TR ALIGN=left VALIGN=top width=140>
|
43
|
+
<TD NOWRAP>
|
44
|
+
<FONT FACE="ARIAL, HELVETICA" SIZE=-1>
|
45
|
+
<B>General</B><SMALL>
|
46
|
+
<BR>-- <A HREF="http://www.rusa.org/announce.html"><FONT COLOR="#3cb545">Announcements (11/28)</FONT></A>
|
47
|
+
<BR>-- <A HREF="http://www.rusa.org/about.html">About Randonneuring</A>
|
48
|
+
<BR>-- <A HREF="http://www.rusa.org/links.html">Links</A>
|
49
|
+
<BR>-- <A HREF="http://www.rusa.org/index.html">Home</A>
|
50
|
+
</SMALL>
|
51
|
+
<P><B>Search For</B><SMALL>
|
52
|
+
<BR>-- <A HREF="http://www.rusa.org/cgi-bin/eventsearch_GF.pl">Rides</A>
|
53
|
+
<BR>-- <A HREF="http://www.rusa.org/results_search.html">Results</A>
|
54
|
+
<BR>-- <A HREF="http://www.rusa.org/cgi-bin/membersearch_GF.pl">Members</A>
|
55
|
+
<BR>-- <A HREF="http://www.rusa.org/cgi-bin/officialsearch_GF.pl">Officials</A>
|
56
|
+
<BR>-- <A HREF="http://www.rusa.org/cgi-bin/clubsearch_GF.pl">ACP Club Codes</A>
|
57
|
+
<BR>-- <A HREF="http://www.rusa.org/cgi-bin/routesearch_GF.pl">Routes</A>
|
58
|
+
<BR>-- <A HREF="http://www.rusa.org/teamrando.html">Team Events</A>
|
59
|
+
<BR>-- <A HREF="http://www.rusa.org/perminfo.html">Permanents</A>
|
60
|
+
</SMALL>
|
61
|
+
<P><B>Members' Info</B><SMALL>
|
62
|
+
<BR>-- <A HREF="http://www.rusa.org/join.html">Join/Renew</A>
|
63
|
+
<BR>-- <A HREF="http://www.rusa.org/awards.html">Awards</A>
|
64
|
+
<BR>-- <A HREF="http://www.rusa.org/cgi-bin/store_GF.pl">Online Store</A>
|
65
|
+
<BR>-- <A HREF="http://www.rusa.org/nletter.html">Newsletter</A>
|
66
|
+
<BR>-- <A HREF="http://www.rusa.org/pbpphotogallery.html">Photo Gallery</A>
|
67
|
+
<BR>-- <A HREF="http://www.rusa.org/stories.html">Rider Stories</A>
|
68
|
+
</SMALL>
|
69
|
+
<P><B>Long Brevets</B><SMALL>
|
70
|
+
<BR>-- <A HREF="http://www.rusa.org/pbp.html">PBP: Paris-Brest-Paris</A>
|
71
|
+
<BR>-- <A HREF="http://www.rusa.org/1200kms.html">Others</A>
|
72
|
+
</SMALL>
|
73
|
+
<P><B>Administration</B><SMALL>
|
74
|
+
<BR>-- <A HREF="http://www.rusa.org/rules.html">Rules</A>
|
75
|
+
<BR>-- <A HREF="http://www.rusa.org/rbakit.html">RBAs</A>
|
76
|
+
<BR>-- <A HREF="http://www.rusa.org/records.html">Records</A>
|
77
|
+
</SMALL>
|
78
|
+
</FONT>
|
79
|
+
</TD>
|
80
|
+
<TD ALIGN=left VALIGN=top>
|
81
|
+
|
82
|
+
|
83
|
+
28 Matches Found
|
84
|
+
<P><B>Beaulieu</B>, Lynda M | RUSA No. 5213 | Cambridge, MA
|
85
|
+
<BR>Boston Brevet Series | ACP Club Code: 921030
|
86
|
+
<BR><A HREF="/join.html">Membership Expires:</A>
|
87
|
+
<B>2009/12/31</B>
|
88
|
+
<P><B>Brandhorst</B>, Eric | RUSA No. 5138 | Carlisle, MA
|
89
|
+
<BR>Boston Brevet Series | ACP Club Code: 921030
|
90
|
+
<BR><A HREF="/join.html">Membership Expires:</A>
|
91
|
+
<B>2009/12/31</B>
|
92
|
+
<P><B>Brooke</B>, Justin | RUSA No. 4193 | Cambridge, MA
|
93
|
+
<BR>Boston Brevet Series | ACP Club Code: 921030
|
94
|
+
<BR><A HREF="/join.html">Membership Expires:</A>
|
95
|
+
<B>2010/12/31</B>
|
96
|
+
<P><B>Carrington</B>, Dave | RUSA No. 4696 | Newport, RI
|
97
|
+
<BR>Boston Montreal Boston | ACP Club Code: 921003
|
98
|
+
<BR><A HREF="/join.html">Membership Expires:</A>
|
99
|
+
<B>2009/12/31</B>
|
100
|
+
<P><B>Coldwell</B>, Charles | RUSA No. 2271 | Somerville, MA
|
101
|
+
<BR>Boston Brevet Series | ACP Club Code: 921030
|
102
|
+
<BR><A HREF="/join.html">Membership Expires:</A>
|
103
|
+
<B>2009/12/31</B>
|
104
|
+
<P><B>Concepcion</B>, Cris | RUSA No. 3425 | Belmont, MA
|
105
|
+
<BR>Boston Brevet Series | ACP Club Code: 921030
|
106
|
+
<BR><A HREF="/join.html">Membership Expires:</A>
|
107
|
+
<B>2009/12/31</B>
|
108
|
+
<P><B>Creason</B>, Gary L | RUSA No. 3354 | Bedford, MA
|
109
|
+
<BR>Boston Brevet Series | ACP Club Code: 921030
|
110
|
+
<BR><A HREF="/join.html">Membership Expires:</A>
|
111
|
+
<B>2008/12/31</B>
|
112
|
+
<P><B>Dancy</B>, Abram | RUSA No. 1258 | Shrewsbury, MA
|
113
|
+
<BR>Boston Brevet Series | ACP Club Code: 921030
|
114
|
+
<BR><A HREF="/join.html">Membership Expires:</A>
|
115
|
+
<B>2009/12/31</B>
|
116
|
+
<P><B>Dancy</B>, Leigh | RUSA No. 5205 | Shrewsbury, MA
|
117
|
+
<BR>Boston Brevet Series | ACP Club Code: 921030
|
118
|
+
<BR><A HREF="/join.html">Membership Expires:</A>
|
119
|
+
<B>2009/12/31</B>
|
120
|
+
<P><B>Fallon</B>, David | RUSA No. 3604 | Somerville, MA
|
121
|
+
<BR>Boston Brevet Series | ACP Club Code: 921030
|
122
|
+
<BR><A HREF="/join.html">Membership Expires:</A>
|
123
|
+
<B>2009/12/31</B>
|
124
|
+
<P><B>Gafgen</B>, Pierce | RUSA No. 0009 | Middletown, RI
|
125
|
+
<BR>Boston Montreal Boston | ACP Club Code: 921003
|
126
|
+
<BR><A HREF="/join.html">Membership Expires:</A>
|
127
|
+
<B>2009/12/31</B>
|
128
|
+
<P><B>Howland</B>, David | RUSA No. 4950 | Worcester, MA
|
129
|
+
<BR>Boston Brevet Series | ACP Club Code: 921030
|
130
|
+
<BR><A HREF="/join.html">Membership Expires:</A>
|
131
|
+
<B>2008/12/31</B>
|
132
|
+
<P><B>Ingle</B>, Bruce | RUSA No. 0607 | Framingham, MA
|
133
|
+
<BR>Boston Brevet Series | ACP Club Code: 921030
|
134
|
+
<BR><A HREF="/join.html">Membership Expires:</A>
|
135
|
+
<B>2008/12/31</B>
|
136
|
+
<P><B>Ingle</B>, Tracey A L | RUSA No. 1591 | Stow, MA
|
137
|
+
<BR>Boston Brevet Series | ACP Club Code: 921030
|
138
|
+
<BR><A HREF="/join.html">Membership Expires:</A>
|
139
|
+
<B>2008/12/31</B>
|
140
|
+
<P><B>Kassen</B>, Jonathan | RUSA No. 3598 | Medford, MA
|
141
|
+
<BR>Boston Brevet Series | ACP Club Code: 921030
|
142
|
+
<BR><A HREF="/join.html">Membership Expires:</A>
|
143
|
+
<B>2010/12/31</B>
|
144
|
+
<P><B>Lafferty</B>, David | RUSA No. 4284 | Billerica, MA
|
145
|
+
<BR>Boston Brevet Series | ACP Club Code: 921030
|
146
|
+
<BR><A HREF="/join.html">Membership Expires:</A>
|
147
|
+
<B>2008/12/31</B>
|
148
|
+
<P><B>Levesque</B>, Daniel | RUSA No. 2930 | Wilton, NH
|
149
|
+
<BR>Boston Brevet Series | ACP Club Code: 921030
|
150
|
+
<BR><A HREF="/join.html">Membership Expires:</A>
|
151
|
+
<B>2008/12/31</B>
|
152
|
+
<P><B>Martin</B>, Tim | RUSA No. 4234 | Westford, MA
|
153
|
+
<BR>Boston Brevet Series | ACP Club Code: 921030
|
154
|
+
<BR><A HREF="/join.html">Membership Expires:</A>
|
155
|
+
<B>2008/12/31</B>
|
156
|
+
<P><B>McLaughlin</B>, Jake | RUSA No. 5063 | Hanover, NH
|
157
|
+
<BR>Boston Brevet Series | ACP Club Code: 921030
|
158
|
+
<BR><A HREF="/join.html">Membership Expires:</A>
|
159
|
+
<B>2008/12/31</B>
|
160
|
+
<P><B>Omara</B>, Bill | RUSA No. 2512 | Briston, RI
|
161
|
+
<BR>Boston Brevet Series | ACP Club Code: 921030
|
162
|
+
<BR><A HREF="/join.html">Membership Expires:</A>
|
163
|
+
<B>2009/12/31</B>
|
164
|
+
<P><B>Page</B>, Walter | RUSA No. 0980 | Lincoln, MA
|
165
|
+
<BR>Boston Brevet Series | ACP Club Code: 921030
|
166
|
+
<BR><A HREF="/join.html">Membership Expires:</A>
|
167
|
+
<B>2009/12/31</B>
|
168
|
+
<P><B>Pope-Lance</B>, Elton | RUSA No. 4734 | Sudbury, MA
|
169
|
+
<BR>Boston Brevet Series | ACP Club Code: 921030
|
170
|
+
<BR><A HREF="/join.html">Membership Expires:</A>
|
171
|
+
<B>2008/12/31</B>
|
172
|
+
<P><B>Roy</B>, Matt | RUSA No. 4947 | Arlington, MA
|
173
|
+
<BR>Boston Brevet Series | ACP Club Code: 921030
|
174
|
+
<BR><A HREF="/join.html">Membership Expires:</A>
|
175
|
+
<B>2009/12/31</B>
|
176
|
+
<P><B>Schwarz</B>, Bill | RUSA No. 0586 | Kinderhook, NY
|
177
|
+
<BR>Boston Brevet Series | ACP Club Code: 921030
|
178
|
+
<BR><A HREF="/join.html">Membership Expires:</A>
|
179
|
+
<B>2009/12/31</B>
|
180
|
+
<P><B>Searles</B>, Emily | RUSA No. 4813 | Billerica, MA
|
181
|
+
<BR>Boston Brevet Series | ACP Club Code: 921030
|
182
|
+
<BR><A HREF="/join.html">Membership Expires:</A>
|
183
|
+
<B>2008/12/31</B>
|
184
|
+
<P><B>Spatz</B>, Harry | RUSA No. 4362 | Lexington, MA
|
185
|
+
<BR>Boston Brevet Series | ACP Club Code: 921030
|
186
|
+
<BR><A HREF="/join.html">Membership Expires:</A>
|
187
|
+
<B>2008/12/31</B>
|
188
|
+
<P><B>Tolonen</B>, Andrew | RUSA No. 4292 | Boston, MA
|
189
|
+
<BR>Boston Brevet Series | ACP Club Code: 921030
|
190
|
+
<BR><A HREF="/join.html">Membership Expires:</A>
|
191
|
+
<B>2008/12/31</B>
|
192
|
+
<P><B>Wise</B>, Jennifer | RUSA No. 0001 | Middletown, RI
|
193
|
+
<BR>Boston Montreal Boston | ACP Club Code: 921003
|
194
|
+
<BR><A HREF="/join.html">Membership Expires:</A>
|
195
|
+
<B>2009/12/31</B>
|
196
|
+
|
197
|
+
</TD>
|
198
|
+
</TR>
|
199
|
+
</TABLE>
|
200
|
+
<HR SIZE=3 WIDTH="100%" ALIGN=Center>
|
201
|
+
<P><FONT FACE="ARIAL, HELVETICA" SIZE=-1>Revision: November 29, 2008<BR>
|
202
|
+
Please direct questions, comments, or problem reports to the <A HREF="http://www.rusa.org/webmaster.html">webmaster</A>.<BR>
|
203
|
+
<B>© Copyright 2008, <A HREF="http://www.rusa.org">Randonneurs USA</A></B>,
|
204
|
+
except as noted otherwise.</FONT></P>
|
205
|
+
</BODY>
|
206
|
+
</HTML>
|
207
|
+
|
208
|
+
</BODY>
|
209
|
+
</HTML>
|