RusaMember 1.0.0 → 1.0.1

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.
@@ -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
- FIX (code sample of usage)
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
- * FIX (sudo gem install, anything else)
27
+ sudo gem install RusaMember
25
28
 
26
29
  == LICENSE:
27
30
 
data/Rakefile CHANGED
@@ -13,5 +13,4 @@ Hoe.new('RusaMember', RusaMember::VERSION) do |p|
13
13
 
14
14
  end
15
15
 
16
- #task :gem => 'test'
17
16
  # vim: syntax=Ruby
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby -w
2
+
3
+ require 'rubygems'
4
+ require 'lib/parse_member'
5
+
6
+ ParseMember.run
@@ -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
@@ -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__
@@ -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
@@ -1,3 +1,3 @@
1
1
  class RusaMember
2
- VERSION = '1.0.0'
2
+ VERSION = '1.0.1'
3
3
  end
@@ -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>&copy; 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>