f1results 0.6 → 3.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.
- checksums.yaml +5 -5
- data/Rakefile +53 -5
- data/bin/f1results +20 -15
- data/lib/f1results/agent.rb +98 -67
- data/lib/f1results/event.rb +32 -34
- data/lib/f1results/parser.rb +95 -0
- data/lib/f1results/result.rb +25 -17
- data/lib/f1results/version.rb +1 -1
- data/lib/f1results.rb +11 -10
- data/test/agent_test.rb +60 -50
- data/test/event_test.rb +15 -26
- data/test/parser_test.rb +97 -0
- data/test/result_test.rb +25 -21
- metadata +15 -31
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
|
-
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: aa2c935bee2392688a8b77b2c56668778bb6b84525c3fb6ea4331d5520675d59
|
|
4
|
+
data.tar.gz: 0a035e5ce0d87d87dad070597b860cdb33e2a32cb77f0c48e5b5f9bf4968f8b8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7664c1698d0b058b1e7a2da0889e322869eac9fbc59c3589297690f843e5c4fcb38867b78448aa50aace0f9d73b32ec1415017f78f443d451497d98522c31235
|
|
7
|
+
data.tar.gz: d5deaedc384e08a0f02434cca1bd1e902c8b276b2bbf4d324ebc4b74ff44c1f51ab6709ea9e6df5ee3e5b1580052da5069b3eefd77ab4a6eea3f21683096a327
|
data/Rakefile
CHANGED
|
@@ -1,10 +1,58 @@
|
|
|
1
|
-
require
|
|
2
|
-
require
|
|
3
|
-
require
|
|
1
|
+
require "bundler/setup"
|
|
2
|
+
require "rake"
|
|
3
|
+
require "rake/testtask"
|
|
4
|
+
require "./test/lib/fixtures"
|
|
5
|
+
require "open-uri"
|
|
6
|
+
require "fileutils"
|
|
7
|
+
require "tempfile"
|
|
4
8
|
|
|
5
9
|
task default: :test
|
|
6
10
|
|
|
7
11
|
Rake::TestTask.new do |t|
|
|
8
|
-
t.libs <<
|
|
9
|
-
t.pattern =
|
|
12
|
+
t.libs << "test/lib"
|
|
13
|
+
t.pattern = "test/*_test.rb"
|
|
14
|
+
t.warning = false
|
|
10
15
|
end
|
|
16
|
+
|
|
17
|
+
def fixture_downloads
|
|
18
|
+
Fixtures::STUBS.flat_map do |_name, mappings|
|
|
19
|
+
mappings.filter_map do |path, fixture|
|
|
20
|
+
next if fixture.is_a?(Integer)
|
|
21
|
+
|
|
22
|
+
{
|
|
23
|
+
url: URI.join(F1Results::BASE_URL, path).to_s,
|
|
24
|
+
destination: File.join(Fixtures::FIXTURES_DIR, fixture)
|
|
25
|
+
}
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
namespace :fixtures do
|
|
31
|
+
desc "List fixture URLs and destinations without downloading them"
|
|
32
|
+
task :list do
|
|
33
|
+
fixture_downloads.each do |download|
|
|
34
|
+
puts "#{download[:url]} => #{download[:destination]}"
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
desc "Download fixtures from formula1.com"
|
|
39
|
+
task :download do
|
|
40
|
+
WebMock.disable!
|
|
41
|
+
FileUtils.mkdir_p(Fixtures::FIXTURES_DIR)
|
|
42
|
+
|
|
43
|
+
fixture_downloads.each do |download|
|
|
44
|
+
puts "Downloading #{download[:url]} => #{download[:destination]}"
|
|
45
|
+
|
|
46
|
+
Tempfile.create(["f1results-fixture-", ".html"], Fixtures::FIXTURES_DIR) do |temporary|
|
|
47
|
+
URI.open(download[:url]) do |remote_file|
|
|
48
|
+
IO.copy_stream(remote_file, temporary)
|
|
49
|
+
end
|
|
50
|
+
temporary.close
|
|
51
|
+
FileUtils.mv(temporary.path, download[:destination])
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
desc "Download fixtures from formula1.com (alias for fixtures:download)"
|
|
58
|
+
task download_fixtures: "fixtures:download"
|
data/bin/f1results
CHANGED
|
@@ -3,54 +3,59 @@
|
|
|
3
3
|
require 'rubygems'
|
|
4
4
|
require 'optparse'
|
|
5
5
|
require 'terminal-table'
|
|
6
|
-
|
|
6
|
+
require_relative '../lib/f1results'
|
|
7
7
|
|
|
8
8
|
year = Time.now.year
|
|
9
|
-
|
|
9
|
+
grand_prix = ''
|
|
10
10
|
event_type = :race
|
|
11
11
|
url = nil
|
|
12
12
|
|
|
13
13
|
optparse = OptionParser.new do |opts|
|
|
14
|
-
opts.on('-y', '--year=YEAR', Integer)
|
|
15
|
-
opts.on('-
|
|
16
|
-
opts.on('-e', '--event=EVENT', String)
|
|
17
|
-
opts.on('-u', '--url=URL', String)
|
|
14
|
+
opts.on('-y', '--year=YEAR', Integer) { |val| year = val }
|
|
15
|
+
opts.on('-g', '--grandprix=GRANDPRIX', String) { |val| grand_prix = val.capitalize }
|
|
16
|
+
opts.on('-e', '--event=EVENT', String) { |val| event_type = val.downcase.to_sym }
|
|
17
|
+
opts.on('-u', '--url=URL', String) { |val| url = val }
|
|
18
18
|
opts.parse!
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
begin
|
|
22
22
|
event = if url.nil?
|
|
23
|
-
F1Results.fetch(year,
|
|
23
|
+
F1Results.fetch(year, grand_prix, event_type)
|
|
24
24
|
else
|
|
25
25
|
F1Results.fetch_with_url(url)
|
|
26
26
|
end
|
|
27
|
-
rescue
|
|
28
|
-
|
|
29
|
-
puts "No results for #{event_summary}"
|
|
27
|
+
rescue RuntimeError => e
|
|
28
|
+
puts e
|
|
30
29
|
exit
|
|
31
30
|
end
|
|
32
31
|
|
|
33
|
-
title = url.nil? ? "#{event.year} #{event.
|
|
32
|
+
title = url.nil? ? "#{event.year} #{event.grand_prix} #{event.type_name}\n#{event.name}" : event.name
|
|
34
33
|
table = Terminal::Table.new title: title, rows: event.to_a
|
|
35
34
|
|
|
36
35
|
if event.practice?
|
|
37
|
-
table.headings = %W(Pos
|
|
36
|
+
table.headings = %W(Pos No Driver Team Time Gap Laps)
|
|
38
37
|
table.align_column 0, :right
|
|
39
38
|
table.align_column 1, :right
|
|
40
39
|
table.align_column 4, :right
|
|
40
|
+
table.align_column 5, :right
|
|
41
41
|
table.align_column 6, :right
|
|
42
42
|
|
|
43
43
|
elsif event.qualifying?
|
|
44
|
-
table.headings = %W(Pos Driver Team Q1 Q2 Q3 Laps)
|
|
44
|
+
table.headings = %W(Pos No Driver Team Q1 Q2 Q3 Laps)
|
|
45
45
|
table.align_column 0, :right
|
|
46
|
+
table.align_column 1, :right
|
|
47
|
+
table.align_column 4, :right
|
|
48
|
+
table.align_column 5, :right
|
|
46
49
|
table.align_column 6, :right
|
|
50
|
+
table.align_column 7, :right
|
|
47
51
|
|
|
48
52
|
elsif event.race?
|
|
49
|
-
table.headings = %W(Pos Driver
|
|
53
|
+
table.headings = %W(Pos No Driver Team Laps Time/Retired Points)
|
|
50
54
|
table.align_column 0, :right
|
|
51
|
-
table.align_column
|
|
55
|
+
table.align_column 1, :right
|
|
52
56
|
table.align_column 4, :right
|
|
53
57
|
table.align_column 5, :right
|
|
58
|
+
table.align_column 6, :right
|
|
54
59
|
end
|
|
55
60
|
|
|
56
61
|
puts table
|
data/lib/f1results/agent.rb
CHANGED
|
@@ -2,103 +2,134 @@ require 'mechanize'
|
|
|
2
2
|
|
|
3
3
|
module F1Results
|
|
4
4
|
class Agent < ::Mechanize
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
def initialize(event = nil)
|
|
8
|
-
if event.nil?
|
|
9
|
-
@event = F1Results::Event.new
|
|
10
|
-
else
|
|
11
|
-
self.event = event
|
|
12
|
-
end
|
|
5
|
+
def initialize
|
|
13
6
|
super
|
|
7
|
+
self.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
|
14
8
|
end
|
|
15
9
|
|
|
16
|
-
def
|
|
17
|
-
|
|
10
|
+
def get_url(event)
|
|
11
|
+
@event = event
|
|
12
|
+
return url
|
|
18
13
|
end
|
|
19
14
|
|
|
20
|
-
def
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
@event.name = parse_event_title
|
|
26
|
-
@event.results = parse_event_results
|
|
27
|
-
return @event
|
|
28
|
-
rescue Mechanize::ResponseCodeError, Net::HTTPNotFound
|
|
29
|
-
raise "No results found at #{url}"
|
|
30
|
-
end
|
|
15
|
+
def get_results(new_event)
|
|
16
|
+
@event = new_event
|
|
17
|
+
event.url ||= url
|
|
18
|
+
update_event
|
|
19
|
+
return event
|
|
31
20
|
end
|
|
32
21
|
|
|
33
|
-
def
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
22
|
+
def get_results_with_url(new_url)
|
|
23
|
+
event.url = new_url
|
|
24
|
+
update_event
|
|
25
|
+
return event
|
|
37
26
|
end
|
|
38
27
|
|
|
39
28
|
private
|
|
40
29
|
|
|
41
|
-
def
|
|
42
|
-
|
|
30
|
+
def event
|
|
31
|
+
@event ||= Event.new
|
|
43
32
|
end
|
|
44
33
|
|
|
45
|
-
def
|
|
46
|
-
|
|
47
|
-
|
|
34
|
+
def url
|
|
35
|
+
base_uri = URI.join F1Results::BASE_URL, "en/results/#{event.year}/"
|
|
36
|
+
year_uri = URI.join base_uri, 'races'
|
|
37
|
+
begin
|
|
38
|
+
get(year_uri)
|
|
39
|
+
rescue Mechanize::ResponseCodeError, Net::HTTPNotFound
|
|
40
|
+
raise "No results found for #{event.year}"
|
|
41
|
+
end
|
|
48
42
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
43
|
+
grand_prix_nodes = page.parser.xpath("//table//tbody/tr/td[1]//a[contains(@href, '/races/')]").map do |a|
|
|
44
|
+
# Remove SVG icons from the links
|
|
45
|
+
a2 = a.dup
|
|
46
|
+
a2.search('.//svg').remove
|
|
47
|
+
a2
|
|
48
|
+
end
|
|
52
49
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
table = page.parser.xpath('//tbody')
|
|
50
|
+
grand_prix_node = grand_prix_nodes.find { |node| node.text.strip.downcase == event.grand_prix.downcase }
|
|
51
|
+
raise "No grand prix '#{event.grand_prix}' found for #{event.year}" if grand_prix_node.nil?
|
|
56
52
|
|
|
57
|
-
|
|
58
|
-
table.xpath('//span[@class="tla"]').each(&:remove)
|
|
53
|
+
grand_prix_uri = URI.join base_uri, grand_prix_node.attr('href')
|
|
59
54
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
cell.blank? ? nil : cell
|
|
65
|
-
end
|
|
55
|
+
begin
|
|
56
|
+
get(grand_prix_uri)
|
|
57
|
+
rescue Mechanize::ResponseCodeError, Net::HTTPNotFound
|
|
58
|
+
raise "Error fetching #{grand_prix_uri}"
|
|
66
59
|
end
|
|
67
60
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
61
|
+
event_names = {
|
|
62
|
+
"race result" => :race_result_node,
|
|
63
|
+
"fastest laps" => :fastest_laps_node,
|
|
64
|
+
"pit stop summary" => :pit_stop_summary_node,
|
|
65
|
+
"starting grid" => :starting_grid_node,
|
|
66
|
+
"overall qualifying" => :overall_qualifying_node,
|
|
67
|
+
"qualifying" => :qualifying_node,
|
|
68
|
+
"practice 3" => :practice3_node,
|
|
69
|
+
"practice 2" => :practice2_node,
|
|
70
|
+
"practice 1" => :practice1_node,
|
|
71
|
+
"sprint" => :sprint_node,
|
|
72
|
+
"sprint grid" => :sprint_grid_node,
|
|
73
|
+
"sprint qualifying" => :sprint_qualifying_node
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
event_nodes = page.parser.xpath("//menu[@aria-labelledby='sidebar-dropdown']//li/a").map do |a|
|
|
77
|
+
# Remove SVG icons from the links
|
|
78
|
+
a2 = a.dup
|
|
79
|
+
a2.search('.//svg').remove
|
|
80
|
+
a2
|
|
81
|
+
end
|
|
71
82
|
|
|
72
|
-
|
|
83
|
+
# Create a hash to store the nodes
|
|
84
|
+
nodes = {}
|
|
85
|
+
event_names.each do |name, key|
|
|
86
|
+
nodes[key] = event_nodes.find { |node| node.text.strip.downcase == name }
|
|
87
|
+
end
|
|
73
88
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
89
|
+
# TODO: add support for Starting Grid, Warm Up, Pit Stop Summary, Fastest Laps
|
|
90
|
+
node = case
|
|
91
|
+
when event.race?
|
|
92
|
+
nodes[:race_result_node]
|
|
93
|
+
when event.qualifying?
|
|
94
|
+
nodes[:qualifying_node] || nodes[:overall_qualifying_node]
|
|
95
|
+
when event.practice3?
|
|
96
|
+
nodes[:practice3_node]
|
|
97
|
+
when event.practice2?
|
|
98
|
+
nodes[:practice2_node]
|
|
99
|
+
when event.practice1?
|
|
100
|
+
nodes[:practice1_node]
|
|
79
101
|
else
|
|
80
|
-
|
|
102
|
+
raise "Unsupported event type '#{event.type}'"
|
|
81
103
|
end
|
|
82
104
|
|
|
83
|
-
|
|
84
|
-
hash = Hash[header.zip(row)]
|
|
85
|
-
hash[:index] = i
|
|
105
|
+
raise "No results for event type '#{event.type}' at #{event.year} #{event.grand_prix}" if node.nil?
|
|
86
106
|
|
|
87
|
-
|
|
88
|
-
|
|
107
|
+
uri = URI.join base_uri, node.attr('href')
|
|
108
|
+
return uri.to_s
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def update_event
|
|
112
|
+
begin
|
|
113
|
+
get(event.url)
|
|
114
|
+
rescue Mechanize::ResponseCodeError, Net::HTTPNotFound
|
|
115
|
+
raise "No results found at '#{url}'"
|
|
89
116
|
end
|
|
90
117
|
|
|
91
|
-
|
|
118
|
+
new_event = Parser.new(page).parse
|
|
119
|
+
event.type ||= new_event.type
|
|
120
|
+
validate_event(new_event)
|
|
121
|
+
|
|
122
|
+
event.grand_prix ||= new_event.grand_prix
|
|
123
|
+
event.circuit ||= new_event.circuit
|
|
124
|
+
event.name ||= new_event.name
|
|
125
|
+
event.results = new_event.results
|
|
92
126
|
end
|
|
93
127
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
# Fix i18n cells that look like this
|
|
98
|
-
# {'Position' @ i18n}, {'Driver' @ i18n}, ...
|
|
99
|
-
cell = cell.match(/(')(.+)(')/)[2] if /i18n/ =~ cell
|
|
100
|
-
cell.to_s.strip.parameterize('_').to_sym
|
|
128
|
+
def validate_event(new_event)
|
|
129
|
+
if event.type != new_event.type
|
|
130
|
+
raise "Results at '#{url}' are returning from '#{new_event.type_name}', not the requested '#{event.type_name}'"
|
|
101
131
|
end
|
|
102
132
|
end
|
|
133
|
+
|
|
103
134
|
end
|
|
104
135
|
end
|
data/lib/f1results/event.rb
CHANGED
|
@@ -1,46 +1,40 @@
|
|
|
1
|
-
require 'active_support/core_ext/string'
|
|
2
|
-
|
|
3
1
|
module F1Results
|
|
4
2
|
class Event
|
|
5
|
-
attr_accessor :year, :
|
|
6
|
-
|
|
7
|
-
TYPE_ALIASES = {
|
|
8
|
-
p1: :practice1,
|
|
9
|
-
p2: :practice2,
|
|
10
|
-
p3: :practice3,
|
|
11
|
-
q: :qualifying,
|
|
12
|
-
r: :race
|
|
13
|
-
}
|
|
3
|
+
attr_accessor :year, :grand_prix, :circuit, :type, :name, :results, :url
|
|
14
4
|
|
|
15
5
|
def initialize(args = {})
|
|
16
6
|
@results = []
|
|
17
7
|
args.each { |k,v| send("#{k}=", v) }
|
|
18
8
|
end
|
|
19
9
|
|
|
20
|
-
def
|
|
21
|
-
|
|
10
|
+
def grand_prix_slug
|
|
11
|
+
grand_prix.strip.downcase.gsub(/[:space:]+/, '-')
|
|
22
12
|
end
|
|
23
13
|
|
|
24
|
-
|
|
25
|
-
|
|
14
|
+
# Human readable type, e.g. "Practice 1", "Qualifying", "Starting Grid"
|
|
15
|
+
def type_name
|
|
16
|
+
@type.to_s
|
|
17
|
+
.gsub(/\d/, ' \0')
|
|
18
|
+
.gsub('_', ' ')
|
|
19
|
+
.split(' ')
|
|
20
|
+
.map(&:capitalize)
|
|
21
|
+
.join(' ')
|
|
26
22
|
end
|
|
27
23
|
|
|
28
|
-
def
|
|
29
|
-
|
|
30
|
-
@type = TYPE_ALIASES[type] || type
|
|
24
|
+
def practice?
|
|
25
|
+
/^practice(1|2|3)$/ =~ @type.to_s
|
|
31
26
|
end
|
|
32
27
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
@type.to_s.gsub(/\d/, ' \0').capitalize
|
|
28
|
+
def practice1?
|
|
29
|
+
@type == :practice1
|
|
36
30
|
end
|
|
37
31
|
|
|
38
|
-
def
|
|
39
|
-
|
|
32
|
+
def practice2?
|
|
33
|
+
@type == :practice2
|
|
40
34
|
end
|
|
41
35
|
|
|
42
|
-
def
|
|
43
|
-
|
|
36
|
+
def practice3?
|
|
37
|
+
@type == :practice3
|
|
44
38
|
end
|
|
45
39
|
|
|
46
40
|
def qualifying?
|
|
@@ -55,21 +49,25 @@ module F1Results
|
|
|
55
49
|
@results.map(&:to_a)
|
|
56
50
|
end
|
|
57
51
|
|
|
58
|
-
def
|
|
59
|
-
@results
|
|
52
|
+
def [](i)
|
|
53
|
+
@results[i]
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def winning
|
|
57
|
+
self[0]
|
|
60
58
|
end
|
|
61
59
|
|
|
62
|
-
def
|
|
63
|
-
|
|
60
|
+
def losing
|
|
61
|
+
self[-1]
|
|
64
62
|
end
|
|
65
63
|
|
|
66
|
-
def
|
|
67
|
-
agent = F1Results::Agent.new
|
|
68
|
-
agent.
|
|
64
|
+
def get_results
|
|
65
|
+
agent = F1Results::Agent.new
|
|
66
|
+
agent.get_results(self)
|
|
69
67
|
end
|
|
70
68
|
|
|
71
|
-
def
|
|
72
|
-
agent = F1Results::Agent.new
|
|
69
|
+
def get_results_with_url(url)
|
|
70
|
+
agent = F1Results::Agent.new
|
|
73
71
|
agent.fetch_results_with_url(url)
|
|
74
72
|
end
|
|
75
73
|
end
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
module F1Results
|
|
2
|
+
class Parser
|
|
3
|
+
def initialize(page)
|
|
4
|
+
@page = page
|
|
5
|
+
@event = Event.new
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def parse
|
|
9
|
+
@event.name = event_name
|
|
10
|
+
@event.type = event_type
|
|
11
|
+
@event.grand_prix = event_grand_prix
|
|
12
|
+
@event.circuit = event_circuit
|
|
13
|
+
@event.results = event_results
|
|
14
|
+
# TODO: date
|
|
15
|
+
return @event
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def event_name
|
|
21
|
+
return @page.parser.at_xpath('//h1').text.gsub(/[[:space:]]+/, ' ').strip
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def event_type
|
|
25
|
+
if m = @event.name.match(/(?:.+) - (.+)/)
|
|
26
|
+
result = m[1]
|
|
27
|
+
.gsub(/(?<=RACE )RESULT/i, '')
|
|
28
|
+
.gsub(/OVERALL(?= QUALIFYING)/i, '')
|
|
29
|
+
.gsub(/(?<=PRACTICE)( )(?=[1-3])/i, '')
|
|
30
|
+
.sub(/(?<=QUALIFYING)\s*\d+\z/i, '') # strip numbers for Qualifying
|
|
31
|
+
.strip
|
|
32
|
+
.downcase
|
|
33
|
+
result.to_sym
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def event_grand_prix
|
|
38
|
+
node = @page.parser.at_xpath('(//dialog)[3]//a[.//title[text()="Active"]]')
|
|
39
|
+
node.xpath('.//svg').remove
|
|
40
|
+
|
|
41
|
+
return node.text.strip
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def event_circuit
|
|
45
|
+
node = @page.parser.at_xpath('//h1/../../../../../div/following-sibling::div[1]//p[2]')
|
|
46
|
+
return node.text
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def event_results
|
|
50
|
+
results = []
|
|
51
|
+
tbody = @page.parser.at_xpath('//tbody')
|
|
52
|
+
|
|
53
|
+
# Remove driver abbreviation from driver cell
|
|
54
|
+
tbody.xpath('.//span[@class="md:hidden"]').each(&:remove)
|
|
55
|
+
|
|
56
|
+
# Turn HTML tbody into an array of arrays
|
|
57
|
+
data = tbody.xpath('.//tr').map do |row|
|
|
58
|
+
row.xpath('./td|./th').map do |cell|
|
|
59
|
+
cell = cell.text.gsub(/[[:space:]]+/, ' ').strip
|
|
60
|
+
cell.empty? ? nil : cell
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
header = @page.parser.xpath('//thead//th').map do |cell|
|
|
65
|
+
cell.text.to_s
|
|
66
|
+
.gsub(/\./, '') # Pos. -> Pos
|
|
67
|
+
.gsub(/[[:space:]]+/, '')
|
|
68
|
+
.gsub(/\/.*/, '') # Time/Retired -> Time
|
|
69
|
+
.downcase
|
|
70
|
+
.to_sym
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Set result class type
|
|
74
|
+
result_class = case
|
|
75
|
+
when @event.practice?
|
|
76
|
+
PracticeResult
|
|
77
|
+
when @event.qualifying?
|
|
78
|
+
QualifyingResult
|
|
79
|
+
else
|
|
80
|
+
RaceResult
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Make each table row a Result
|
|
84
|
+
data.each_with_index do |row, i|
|
|
85
|
+
hash = Hash[header.zip(row)]
|
|
86
|
+
hash[:position] = i + 1
|
|
87
|
+
|
|
88
|
+
result = result_class.new(hash)
|
|
89
|
+
results << result
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
return results
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
data/lib/f1results/result.rb
CHANGED
|
@@ -1,15 +1,10 @@
|
|
|
1
1
|
module F1Results
|
|
2
2
|
class Result
|
|
3
|
-
attr_accessor :
|
|
3
|
+
attr_accessor :position, :driver, :driver_number, :driver_country_abbr, :team, :time, :gap, :laps
|
|
4
|
+
attr_reader :position_name
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
alias :name= :driver=
|
|
8
|
-
alias :no= :driver_number=
|
|
9
|
-
alias :country= :driver_country_abbr=
|
|
10
|
-
alias :best_time= :time=
|
|
11
|
-
alias :race_time= :time=
|
|
12
|
-
alias :fastest= :time=
|
|
6
|
+
alias_method :no=, :driver_number=
|
|
7
|
+
alias_method :car=, :team=
|
|
13
8
|
|
|
14
9
|
def initialize(args = {})
|
|
15
10
|
args.each do |k, v|
|
|
@@ -18,16 +13,18 @@ module F1Results
|
|
|
18
13
|
end
|
|
19
14
|
end
|
|
20
15
|
|
|
21
|
-
def
|
|
22
|
-
@
|
|
16
|
+
def position_name=(n)
|
|
17
|
+
@position_name = case
|
|
23
18
|
when n.to_s.empty?
|
|
24
19
|
nil
|
|
25
20
|
when n.to_i == 0
|
|
26
21
|
n
|
|
27
22
|
else
|
|
28
|
-
n.to_i
|
|
23
|
+
@position = n.to_i
|
|
29
24
|
end
|
|
30
25
|
end
|
|
26
|
+
alias_method :p=, :position_name=
|
|
27
|
+
alias_method :pos=, :position_name=
|
|
31
28
|
|
|
32
29
|
def driver_number
|
|
33
30
|
@driver_number.to_i == 0 ? nil : @driver_number.to_i
|
|
@@ -38,35 +35,42 @@ module F1Results
|
|
|
38
35
|
end
|
|
39
36
|
|
|
40
37
|
def gap
|
|
38
|
+
# TODO: parse gap properly
|
|
41
39
|
@gap.to_f == 0.0 ? nil : @gap.to_f
|
|
42
40
|
end
|
|
43
41
|
end
|
|
44
42
|
|
|
45
43
|
class PracticeResult < Result
|
|
46
44
|
def to_a
|
|
47
|
-
[
|
|
45
|
+
[position_name, driver_number, driver, team, time, gap, laps]
|
|
48
46
|
end
|
|
47
|
+
|
|
48
|
+
# TODO: to_h, to_s
|
|
49
49
|
end
|
|
50
50
|
|
|
51
51
|
class QualifyingResult < Result
|
|
52
52
|
attr_accessor :q1, :q2, :q3
|
|
53
53
|
|
|
54
54
|
def to_a
|
|
55
|
-
[
|
|
55
|
+
[position_name, driver_number, driver, team, q1, q2, q3, laps]
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def q1
|
|
59
|
+
@time || @q1
|
|
56
60
|
end
|
|
57
61
|
|
|
58
62
|
def time
|
|
59
|
-
q3 || q2 || q1
|
|
63
|
+
@q3 || @q2 || @q1 || @time
|
|
60
64
|
end
|
|
61
65
|
end
|
|
62
66
|
|
|
63
67
|
class RaceResult < Result
|
|
64
68
|
attr_accessor :retired, :points
|
|
65
69
|
|
|
66
|
-
|
|
70
|
+
alias_method :pts=, :points=
|
|
67
71
|
|
|
68
72
|
def to_a
|
|
69
|
-
[
|
|
73
|
+
[position_name, driver_number, driver, team, laps, time_or_retired, points]
|
|
70
74
|
end
|
|
71
75
|
|
|
72
76
|
def time_or_retired=(str)
|
|
@@ -76,6 +80,7 @@ module F1Results
|
|
|
76
80
|
@retired = str
|
|
77
81
|
end
|
|
78
82
|
end
|
|
83
|
+
alias_method :time_retired=, :time_or_retired=
|
|
79
84
|
|
|
80
85
|
def time_or_retired
|
|
81
86
|
time || retired
|
|
@@ -85,4 +90,7 @@ module F1Results
|
|
|
85
90
|
@points.to_i
|
|
86
91
|
end
|
|
87
92
|
end
|
|
93
|
+
|
|
94
|
+
# TODO: PitStop < Result
|
|
95
|
+
# TODO: FastestLap < Result
|
|
88
96
|
end
|
data/lib/f1results/version.rb
CHANGED
data/lib/f1results.rb
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
require_relative './f1results/version'
|
|
2
|
+
require_relative './f1results/agent'
|
|
3
|
+
require_relative './f1results/parser'
|
|
4
|
+
require_relative './f1results/event'
|
|
5
|
+
require_relative './f1results/result'
|
|
5
6
|
|
|
6
7
|
module F1Results
|
|
7
|
-
|
|
8
|
+
BASE_URL = 'https://www.formula1.com/'
|
|
8
9
|
|
|
9
|
-
# Get results from formula1.com for a given year,
|
|
10
|
+
# Get results from formula1.com for a given year, grand prix, and event type
|
|
10
11
|
# (race or qualifying)
|
|
11
12
|
#
|
|
12
13
|
# F1Results.f1results(2010, 'australia', :qualifying)
|
|
@@ -14,14 +15,14 @@ module F1Results
|
|
|
14
15
|
# Returns an `F1Results::Event` object which has a method `results`, which
|
|
15
16
|
# returns multiple objects of type `F1Results::Result`
|
|
16
17
|
|
|
17
|
-
def self.fetch(year,
|
|
18
|
-
event = Event.new(year: year,
|
|
19
|
-
event.
|
|
18
|
+
def self.fetch(year, grand_prix, type = :race)
|
|
19
|
+
event = Event.new(year: year, grand_prix: grand_prix, type: type)
|
|
20
|
+
event.get_results
|
|
20
21
|
return event
|
|
21
22
|
end
|
|
22
23
|
|
|
23
24
|
def self.fetch_with_url(url)
|
|
24
25
|
agent = Agent.new
|
|
25
|
-
return agent.
|
|
26
|
+
return agent.get_results_with_url(url)
|
|
26
27
|
end
|
|
27
28
|
end
|
data/test/agent_test.rb
CHANGED
|
@@ -1,74 +1,84 @@
|
|
|
1
1
|
require 'test_helper'
|
|
2
2
|
|
|
3
|
-
class AgentTest <
|
|
3
|
+
class AgentTest < Minitest::Test
|
|
4
4
|
include Fixtures
|
|
5
5
|
|
|
6
6
|
def setup
|
|
7
|
+
stub_all
|
|
7
8
|
@agent = F1Results::Agent.new
|
|
8
9
|
end
|
|
9
10
|
|
|
10
|
-
def
|
|
11
|
-
event = F1Results::Event.new(year:
|
|
12
|
-
|
|
13
|
-
@agent.
|
|
14
|
-
assert_equal '2015 FORMULA 1 ROLEX AUSTRALIAN GRAND PRIX', event.name
|
|
15
|
-
assert_equal 17, event.results.length
|
|
16
|
-
assert_equal 'Lewis Hamilton', event.winner
|
|
17
|
-
assert_equal 'Kevin Magnussen', event.loser
|
|
11
|
+
def test_get_url_2016_australia_practice1
|
|
12
|
+
event = F1Results::Event.new(year: 2016, grand_prix: 'Australia', type: :practice1)
|
|
13
|
+
url = 'https://www.formula1.com/en/results/2016/races/938/australia/practice/1'
|
|
14
|
+
assert_equal url, @agent.get_url(event)
|
|
18
15
|
end
|
|
19
16
|
|
|
20
|
-
def
|
|
21
|
-
event = F1Results::Event.new(year:
|
|
22
|
-
|
|
23
|
-
@agent.
|
|
24
|
-
assert_equal '2015 FORMULA 1 ROLEX AUSTRALIAN GRAND PRIX', event.name
|
|
25
|
-
assert_equal 20, event.results.length
|
|
26
|
-
assert_equal 'Lewis Hamilton', event.winner
|
|
27
|
-
assert_equal 'Roberto Merhi', event.loser
|
|
17
|
+
def test_get_url_2016_australia_qualifying
|
|
18
|
+
event = F1Results::Event.new(year: 2016, grand_prix: 'Australia', type: :qualifying)
|
|
19
|
+
url = 'https://www.formula1.com/en/results/2016/races/938/australia/qualifying'
|
|
20
|
+
assert_equal url, @agent.get_url(event)
|
|
28
21
|
end
|
|
29
22
|
|
|
30
|
-
def
|
|
31
|
-
event = F1Results::Event.new(year:
|
|
32
|
-
|
|
33
|
-
@agent.
|
|
34
|
-
assert_equal '2015 FORMULA 1 ROLEX AUSTRALIAN GRAND PRIX', event.name
|
|
35
|
-
assert_equal 16, event.results.length
|
|
36
|
-
assert_equal 'Nico Rosberg', event.winner
|
|
37
|
-
assert_equal 'Romain Grosjean', event.loser
|
|
23
|
+
def test_get_url_2016_australia_race
|
|
24
|
+
event = F1Results::Event.new(year: 2016, grand_prix: 'Australia', type: :race)
|
|
25
|
+
url = 'https://www.formula1.com/en/results/2016/races/938/australia/race-result'
|
|
26
|
+
assert_equal url, @agent.get_url(event)
|
|
38
27
|
end
|
|
39
28
|
|
|
40
|
-
def
|
|
41
|
-
event = F1Results::Event.new(year:
|
|
42
|
-
|
|
43
|
-
@agent.
|
|
44
|
-
assert_equal '2015 FORMULA 1 PETRONAS MALAYSIA GRAND PRIX', event.name
|
|
45
|
-
assert_equal 20, event.results.length
|
|
46
|
-
assert_equal 'Lewis Hamilton', event.winner
|
|
47
|
-
assert_equal 'Roberto Merhi', event.loser
|
|
29
|
+
def test_get_url_1984_brazil_qualifying
|
|
30
|
+
event = F1Results::Event.new(year: 1984, grand_prix: 'Brazil', type: :qualifying)
|
|
31
|
+
url = 'https://www.formula1.com/en/results/1984/races/466/brazil/qualifying/0'
|
|
32
|
+
assert_equal url, @agent.get_url(event)
|
|
48
33
|
end
|
|
49
34
|
|
|
50
|
-
def
|
|
51
|
-
event =
|
|
52
|
-
|
|
53
|
-
assert_equal
|
|
54
|
-
assert event.race?
|
|
55
|
-
assert_equal 20, event.results.length
|
|
56
|
-
assert_equal 'Sebastian Vettel', event.winner
|
|
57
|
-
assert_equal 'Nico Hulkenberg', event.loser
|
|
35
|
+
def test_get_url_1984_brazil_race
|
|
36
|
+
event = F1Results::Event.new(year: 1984, grand_prix: 'Brazil', type: :race)
|
|
37
|
+
url = 'https://www.formula1.com/en/results/1984/races/466/brazil/race-result'
|
|
38
|
+
assert_equal url, @agent.get_url(event)
|
|
58
39
|
end
|
|
59
40
|
|
|
60
|
-
def
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
41
|
+
def test_get_url_1950_indianapolis_500_qualifying
|
|
42
|
+
event = F1Results::Event.new(year: 1950, grand_prix: 'Indianapolis', type: :qualifying)
|
|
43
|
+
url = 'https://www.formula1.com/en/results/1950/races/96/indianapolis/qualifying/0'
|
|
44
|
+
assert_equal url, @agent.get_url(event)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def test_get_url_1950_indianapolis_500_race
|
|
48
|
+
event = F1Results::Event.new(year: 1950, grand_prix: 'Indianapolis', type: :race)
|
|
49
|
+
url = 'https://www.formula1.com/en/results/1950/races/96/indianapolis/race-result'
|
|
50
|
+
assert_equal url, @agent.get_url(event)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def test_get_results_1950_indianapolis_500_race
|
|
54
|
+
event = F1Results::Event.new(year: 1950, grand_prix: 'Indianapolis', type: :race)
|
|
55
|
+
event = @agent.get_results(event)
|
|
56
|
+
assert_equal 36, event.results.length
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def test_api
|
|
60
|
+
event = F1Results.fetch(1950, 'Indianapolis', :race)
|
|
61
|
+
assert_equal 36, event.results.length
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def test_event_with_bad_year
|
|
65
|
+
assert_raise_with_message RuntimeError, "No grand prix 'Australia' found for 1900" do
|
|
66
|
+
event = F1Results::Event.new(year: 1900, grand_prix: 'Australia', type: :race)
|
|
67
|
+
@agent.get_url(event)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def test_event_with_bad_country
|
|
72
|
+
assert_raise_with_message RuntimeError, "No grand prix 'New Zealand' found for 2016" do
|
|
73
|
+
event = F1Results::Event.new(year: 2016, grand_prix: 'New Zealand', type: :race)
|
|
74
|
+
@agent.get_url(event)
|
|
65
75
|
end
|
|
66
76
|
end
|
|
67
77
|
|
|
68
|
-
def
|
|
69
|
-
|
|
70
|
-
event = F1Results::Event.new(year:
|
|
71
|
-
event
|
|
78
|
+
def test_event_with_bad_event_name
|
|
79
|
+
assert_raise_with_message RuntimeError, "Unsupported event type 'practice4'" do
|
|
80
|
+
event = F1Results::Event.new(year: 2016, grand_prix: 'Australia', type: :practice4)
|
|
81
|
+
@agent.get_url(event)
|
|
72
82
|
end
|
|
73
83
|
end
|
|
74
84
|
end
|
data/test/event_test.rb
CHANGED
|
@@ -1,37 +1,31 @@
|
|
|
1
1
|
require 'test_helper'
|
|
2
2
|
|
|
3
|
-
class EventTest <
|
|
4
|
-
include Fixtures
|
|
5
|
-
|
|
3
|
+
class EventTest < Minitest::Test
|
|
6
4
|
def setup
|
|
7
5
|
@event = F1Results::Event.new
|
|
8
6
|
end
|
|
9
7
|
|
|
10
|
-
def test_type
|
|
11
|
-
@event.type = :qualifying
|
|
12
|
-
assert_equal 'qualifying', @event.type_slug
|
|
13
|
-
@event.type = :practice3
|
|
14
|
-
assert_equal 'practice-3', @event.type_slug
|
|
15
|
-
@event.type = :p3
|
|
16
|
-
assert_equal :practice3, @event.type
|
|
17
|
-
end
|
|
18
|
-
|
|
19
8
|
def test_type_name
|
|
9
|
+
@event.type = :practice1
|
|
10
|
+
assert_equal 'Practice 1', @event.type_name
|
|
20
11
|
@event.type = :practice3
|
|
21
12
|
assert_equal 'Practice 3', @event.type_name
|
|
22
13
|
@event.type = :qualifying
|
|
23
14
|
assert_equal 'Qualifying', @event.type_name
|
|
24
15
|
@event.type = :race
|
|
25
16
|
assert_equal 'Race', @event.type_name
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
@event.
|
|
32
|
-
|
|
33
|
-
@event.
|
|
34
|
-
|
|
17
|
+
@event.type = :starting_grid
|
|
18
|
+
assert_equal 'Starting Grid', @event.type_name
|
|
19
|
+
@event.type = :warm_up
|
|
20
|
+
assert_equal 'Warm Up', @event.type_name
|
|
21
|
+
@event.type = :pit_stop_summary
|
|
22
|
+
assert_equal 'Pit Stop Summary', @event.type_name
|
|
23
|
+
@event.type = :fastest_laps
|
|
24
|
+
assert_equal 'Fastest Laps', @event.type_name
|
|
25
|
+
@event.type = :sprint
|
|
26
|
+
assert_equal 'Sprint', @event.type_name
|
|
27
|
+
@event.type = :sprint_grid
|
|
28
|
+
assert_equal 'Sprint Grid', @event.type_name
|
|
35
29
|
end
|
|
36
30
|
|
|
37
31
|
def test_type_boolean_methods
|
|
@@ -49,11 +43,6 @@ class EventTest < MiniTest::Test
|
|
|
49
43
|
refute @event.practice?
|
|
50
44
|
end
|
|
51
45
|
|
|
52
|
-
def test_country_name
|
|
53
|
-
@event.country = 'great britain'
|
|
54
|
-
assert_equal 'Great Britain', @event.country_name
|
|
55
|
-
end
|
|
56
|
-
|
|
57
46
|
def test_to_array
|
|
58
47
|
@event.results << F1Results::RaceResult.new(position: 1, driver: 'Mark Webber', driver_country_abbr: 'AUS', team: 'Red Bull Racing-Renault', time: '1:25:11.288', points: 2)
|
|
59
48
|
@event.results << F1Results::RaceResult.new(position: 2, driver: 'Fernando Alonso', driver_country_abbr: 'ESP', team: 'Ferrari', time: '+3.0 secs', points: 1)
|
data/test/parser_test.rb
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
require 'test_helper'
|
|
2
|
+
|
|
3
|
+
class ParserTest < Minitest::Test
|
|
4
|
+
include Fixtures
|
|
5
|
+
|
|
6
|
+
def test_2016_australia_practice1
|
|
7
|
+
event = events(:event_2016_australia_practice_1)
|
|
8
|
+
assert_equal '2016 FORMULA 1 ROLEX AUSTRALIAN GRAND PRIX - PRACTICE 1', event.name
|
|
9
|
+
assert_equal 'Australia', event.grand_prix
|
|
10
|
+
assert_equal 'Albert Park Grand Prix Circuit, Melbourne', event.circuit
|
|
11
|
+
assert_equal :practice1, event.type
|
|
12
|
+
assert_equal 22, event.results.length
|
|
13
|
+
assert_equal 'Lewis Hamilton', event.winning.driver
|
|
14
|
+
assert_equal 'Carlos Sainz', event.losing.driver
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def test_2016_australia_qualifying
|
|
18
|
+
event = events(:event_2016_australia_qualifying)
|
|
19
|
+
assert_equal '2016 FORMULA 1 ROLEX AUSTRALIAN GRAND PRIX - QUALIFYING', event.name
|
|
20
|
+
assert_equal 'Australia', event.grand_prix
|
|
21
|
+
assert_equal 'Albert Park Grand Prix Circuit, Melbourne', event.circuit
|
|
22
|
+
assert_equal :qualifying, event.type
|
|
23
|
+
assert_equal 22, event.results.length
|
|
24
|
+
|
|
25
|
+
assert_equal 'Lewis Hamilton', event.winning.driver
|
|
26
|
+
assert_equal 'Pascal Wehrlein', event.losing.driver
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def test_2016_australia_race
|
|
30
|
+
event = events(:event_2016_australia_race_result)
|
|
31
|
+
assert_equal '2016 FORMULA 1 ROLEX AUSTRALIAN GRAND PRIX - RACE RESULT', event.name
|
|
32
|
+
assert_equal 'Australia', event.grand_prix
|
|
33
|
+
assert_equal 'Albert Park Grand Prix Circuit, Melbourne', event.circuit
|
|
34
|
+
assert_equal :race, event.type
|
|
35
|
+
assert_equal 22, event.results.length
|
|
36
|
+
assert_equal 'Nico Rosberg', event.winning.driver
|
|
37
|
+
assert_equal 'Daniil Kvyat', event.losing.driver
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def test_1984_brazil_qualifying
|
|
41
|
+
event = events(:event_1984_brazil_qualifying_0)
|
|
42
|
+
assert_equal 'BRAZILIAN GRAND PRIX 1984 - OVERALL QUALIFYING', event.name
|
|
43
|
+
assert_equal 'Brazil', event.grand_prix
|
|
44
|
+
assert_equal 'Autódromo Internacional do Rio de Janeiro, Brazil', event.circuit
|
|
45
|
+
assert_equal :qualifying, event.type
|
|
46
|
+
assert_equal 24, event.results.length
|
|
47
|
+
assert_equal 'Elio de Angelis', event.winning.driver
|
|
48
|
+
assert_equal 'Jonathan Palmer', event.losing.driver
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def test_1984_brazil_race
|
|
52
|
+
event = events(:event_1984_brazil_race_result)
|
|
53
|
+
assert_equal 'BRAZILIAN GRAND PRIX 1984 - RACE RESULT', event.name
|
|
54
|
+
assert_equal 'Brazil', event.grand_prix
|
|
55
|
+
assert_equal 'Autódromo Internacional do Rio de Janeiro, Brazil', event.circuit
|
|
56
|
+
assert_equal :race, event.type
|
|
57
|
+
assert_equal 24, event.results.length
|
|
58
|
+
assert_equal 'Alain Prost', event.winning.driver
|
|
59
|
+
assert_equal 'Ayrton Senna', event.losing.driver
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def test_1950_indianapolis_500_qualifying
|
|
63
|
+
event = events(:event_1950_indianapolis_qualifying_0)
|
|
64
|
+
assert_equal '1950 INDIANAPOLIS 500 - QUALIFYING', event.name
|
|
65
|
+
assert_equal 'Indianapolis', event.grand_prix
|
|
66
|
+
assert_equal 'Indianapolis Motor Speedway, United States', event.circuit
|
|
67
|
+
assert_equal :qualifying, event.type
|
|
68
|
+
assert_equal 1, event.results.length
|
|
69
|
+
assert_equal 'Walt Faulkner', event.winning.driver
|
|
70
|
+
assert_equal 'Walt Faulkner', event.losing.driver
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def test_1950_indianapolis_500_race
|
|
74
|
+
event = events(:event_1950_indianapolis_race_result)
|
|
75
|
+
assert_equal '1950 INDIANAPOLIS 500 - RACE RESULT', event.name
|
|
76
|
+
assert_equal 'Indianapolis', event.grand_prix
|
|
77
|
+
assert_equal 'Indianapolis Motor Speedway, United States', event.circuit
|
|
78
|
+
assert_equal :race, event.type
|
|
79
|
+
# Note: 3 ties, so 36 finishers, 33 positions in this race
|
|
80
|
+
assert_equal 36, event.results.length
|
|
81
|
+
assert_equal 'Johnnie Parsons', event.winning.driver
|
|
82
|
+
assert_equal 'Duke Dinsmore', event.losing.driver
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def test_2024_belgium_race
|
|
86
|
+
event = events(:event_2024_belgium_race_result)
|
|
87
|
+
assert_equal 'FORMULA 1 ROLEX BELGIAN GRAND PRIX 2024 - RACE RESULT', event.name
|
|
88
|
+
assert_equal 'Belgium', event.grand_prix
|
|
89
|
+
assert_equal 'Circuit de Spa-Francorchamps, Spa-Francorchamps', event.circuit
|
|
90
|
+
assert event.race?
|
|
91
|
+
assert_equal 20, event.results.length
|
|
92
|
+
# Note: George Russell won but was DQ'd, but still shows as first result
|
|
93
|
+
assert_equal 'George Russell', event.winning.driver
|
|
94
|
+
assert_equal 'DQ', event.results[0].position_name
|
|
95
|
+
assert_equal 'Zhou Guanyu', event.losing.driver
|
|
96
|
+
end
|
|
97
|
+
end
|
data/test/result_test.rb
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
require 'test_helper'
|
|
2
2
|
|
|
3
|
-
class ResultTest <
|
|
3
|
+
class ResultTest < Minitest::Test
|
|
4
4
|
def test_time
|
|
5
5
|
result = F1Results::RaceResult.new
|
|
6
6
|
result.time_or_retired = '1:25:11.288'
|
|
@@ -30,45 +30,49 @@ class ResultTest < MiniTest::Test
|
|
|
30
30
|
end
|
|
31
31
|
|
|
32
32
|
def test_parse_practice
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
assert_equal
|
|
36
|
-
assert_equal '
|
|
37
|
-
assert_equal '
|
|
38
|
-
assert_equal
|
|
33
|
+
hash = { pos: '2', no: '26', driver: 'Daniil Kvyat', car: 'Red Bull Racing TAG Heuer', time: '1:30.146', gap: '+0.421s', laps: '14' }
|
|
34
|
+
result = F1Results::PracticeResult.new hash
|
|
35
|
+
assert_equal 2, result.position
|
|
36
|
+
assert_equal 'Daniil Kvyat', result.driver
|
|
37
|
+
assert_equal 'Red Bull Racing TAG Heuer', result.team
|
|
38
|
+
assert_equal '1:30.146', result.time
|
|
39
|
+
assert_equal 14, result.laps
|
|
39
40
|
end
|
|
40
41
|
|
|
41
42
|
def test_parse_qualifying
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
assert_equal
|
|
45
|
-
assert_equal '
|
|
46
|
-
assert_equal '
|
|
47
|
-
assert_equal '1:
|
|
48
|
-
assert_equal '1:
|
|
49
|
-
assert_equal
|
|
43
|
+
hash = { pos: '8', no: '3', driver: 'Daniel Ricciardo', car: 'Red Bull Racing TAG Heuer', q1: '1:26.945', q2: '1:25.599', q3: '1:25.589', laps: '15' }
|
|
44
|
+
result = F1Results::QualifyingResult.new hash
|
|
45
|
+
assert_equal 8, result.position
|
|
46
|
+
assert_equal 'Daniel Ricciardo', result.driver
|
|
47
|
+
assert_equal 'Red Bull Racing TAG Heuer', result.team
|
|
48
|
+
assert_equal '1:26.945', result.q1
|
|
49
|
+
assert_equal '1:25.599', result.q2
|
|
50
|
+
assert_equal '1:25.589', result.q3
|
|
51
|
+
assert_equal 15, result.laps
|
|
50
52
|
end
|
|
51
53
|
|
|
52
54
|
def test_parse_race
|
|
53
|
-
|
|
55
|
+
hash = { pos: '3', no: '5', driver: 'Sebastian Vettel', car: 'Ferrari', laps: '57', time_retired: '+9.643s', pts: '15' }
|
|
56
|
+
result = F1Results::RaceResult.new hash
|
|
54
57
|
assert_equal 3, result.position
|
|
55
58
|
assert_equal 'Sebastian Vettel', result.driver
|
|
56
59
|
assert_equal 'Ferrari', result.team
|
|
57
|
-
assert_equal '+
|
|
60
|
+
assert_equal '+9.643s', result.time
|
|
58
61
|
assert_equal 15, result.points
|
|
59
62
|
end
|
|
60
63
|
|
|
61
64
|
def test_parse_qualifying_with_blank_values
|
|
62
65
|
result = F1Results::QualifyingResult.new(position: '16', driver: 'Marcus Ericsson', team: 'Sauber', q1: '1:31.376', q2: nil, q3: nil, laps: 10)
|
|
63
66
|
assert_equal '1:31.376', result.q1
|
|
64
|
-
|
|
65
|
-
|
|
67
|
+
assert_nil result.q2
|
|
68
|
+
assert_nil result.q3
|
|
66
69
|
assert_equal 10, result.laps
|
|
67
70
|
end
|
|
68
71
|
|
|
69
72
|
def test_race_to_a
|
|
70
|
-
|
|
71
|
-
|
|
73
|
+
hash = { pos: '3', no: '5', driver: 'Sebastian Vettel', car: 'Ferrari', laps: '57', time_retired: '+9.643s', pts: '15' }
|
|
74
|
+
result = F1Results::RaceResult.new hash
|
|
75
|
+
assert_equal [3, 5, 'Sebastian Vettel', 'Ferrari', 57, '+9.643s', 15], result.to_a
|
|
72
76
|
end
|
|
73
77
|
|
|
74
78
|
def test_unknown_key_doesnt_raise_error
|
metadata
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: f1results
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: '0
|
|
4
|
+
version: '3.0'
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Darren Jeacocke
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: bin
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
13
|
name: mechanize
|
|
@@ -16,84 +15,70 @@ dependencies:
|
|
|
16
15
|
requirements:
|
|
17
16
|
- - "~>"
|
|
18
17
|
- !ruby/object:Gem::Version
|
|
19
|
-
version: '2.
|
|
18
|
+
version: '2.14'
|
|
20
19
|
type: :runtime
|
|
21
20
|
prerelease: false
|
|
22
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
23
22
|
requirements:
|
|
24
23
|
- - "~>"
|
|
25
24
|
- !ruby/object:Gem::Version
|
|
26
|
-
version: '2.
|
|
25
|
+
version: '2.14'
|
|
27
26
|
- !ruby/object:Gem::Dependency
|
|
28
27
|
name: terminal-table
|
|
29
28
|
requirement: !ruby/object:Gem::Requirement
|
|
30
29
|
requirements:
|
|
31
30
|
- - "~>"
|
|
32
31
|
- !ruby/object:Gem::Version
|
|
33
|
-
version: '
|
|
32
|
+
version: '4.0'
|
|
34
33
|
type: :runtime
|
|
35
34
|
prerelease: false
|
|
36
35
|
version_requirements: !ruby/object:Gem::Requirement
|
|
37
36
|
requirements:
|
|
38
37
|
- - "~>"
|
|
39
38
|
- !ruby/object:Gem::Version
|
|
40
|
-
version: '
|
|
41
|
-
- !ruby/object:Gem::Dependency
|
|
42
|
-
name: activesupport
|
|
43
|
-
requirement: !ruby/object:Gem::Requirement
|
|
44
|
-
requirements:
|
|
45
|
-
- - "~>"
|
|
46
|
-
- !ruby/object:Gem::Version
|
|
47
|
-
version: '4'
|
|
48
|
-
type: :runtime
|
|
49
|
-
prerelease: false
|
|
50
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
-
requirements:
|
|
52
|
-
- - "~>"
|
|
53
|
-
- !ruby/object:Gem::Version
|
|
54
|
-
version: '4'
|
|
39
|
+
version: '4.0'
|
|
55
40
|
- !ruby/object:Gem::Dependency
|
|
56
41
|
name: rake
|
|
57
42
|
requirement: !ruby/object:Gem::Requirement
|
|
58
43
|
requirements:
|
|
59
44
|
- - "~>"
|
|
60
45
|
- !ruby/object:Gem::Version
|
|
61
|
-
version: '
|
|
46
|
+
version: '13.3'
|
|
62
47
|
type: :development
|
|
63
48
|
prerelease: false
|
|
64
49
|
version_requirements: !ruby/object:Gem::Requirement
|
|
65
50
|
requirements:
|
|
66
51
|
- - "~>"
|
|
67
52
|
- !ruby/object:Gem::Version
|
|
68
|
-
version: '
|
|
53
|
+
version: '13.3'
|
|
69
54
|
- !ruby/object:Gem::Dependency
|
|
70
55
|
name: minitest
|
|
71
56
|
requirement: !ruby/object:Gem::Requirement
|
|
72
57
|
requirements:
|
|
73
58
|
- - "~>"
|
|
74
59
|
- !ruby/object:Gem::Version
|
|
75
|
-
version: '5.
|
|
60
|
+
version: '5.25'
|
|
76
61
|
type: :development
|
|
77
62
|
prerelease: false
|
|
78
63
|
version_requirements: !ruby/object:Gem::Requirement
|
|
79
64
|
requirements:
|
|
80
65
|
- - "~>"
|
|
81
66
|
- !ruby/object:Gem::Version
|
|
82
|
-
version: '5.
|
|
67
|
+
version: '5.25'
|
|
83
68
|
- !ruby/object:Gem::Dependency
|
|
84
69
|
name: webmock
|
|
85
70
|
requirement: !ruby/object:Gem::Requirement
|
|
86
71
|
requirements:
|
|
87
72
|
- - "~>"
|
|
88
73
|
- !ruby/object:Gem::Version
|
|
89
|
-
version:
|
|
74
|
+
version: 3.25.1
|
|
90
75
|
type: :development
|
|
91
76
|
prerelease: false
|
|
92
77
|
version_requirements: !ruby/object:Gem::Requirement
|
|
93
78
|
requirements:
|
|
94
79
|
- - "~>"
|
|
95
80
|
- !ruby/object:Gem::Version
|
|
96
|
-
version:
|
|
81
|
+
version: 3.25.1
|
|
97
82
|
description: Get F1 results from formula1.com
|
|
98
83
|
email: dazonic@me.com
|
|
99
84
|
executables:
|
|
@@ -108,16 +93,17 @@ files:
|
|
|
108
93
|
- lib/f1results.rb
|
|
109
94
|
- lib/f1results/agent.rb
|
|
110
95
|
- lib/f1results/event.rb
|
|
96
|
+
- lib/f1results/parser.rb
|
|
111
97
|
- lib/f1results/result.rb
|
|
112
98
|
- lib/f1results/version.rb
|
|
113
99
|
- test/agent_test.rb
|
|
114
100
|
- test/event_test.rb
|
|
101
|
+
- test/parser_test.rb
|
|
115
102
|
- test/result_test.rb
|
|
116
103
|
homepage: https://github.com/daz/f1results
|
|
117
104
|
licenses:
|
|
118
105
|
- MIT
|
|
119
106
|
metadata: {}
|
|
120
|
-
post_install_message:
|
|
121
107
|
rdoc_options: []
|
|
122
108
|
require_paths:
|
|
123
109
|
- lib
|
|
@@ -132,9 +118,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
132
118
|
- !ruby/object:Gem::Version
|
|
133
119
|
version: '0'
|
|
134
120
|
requirements: []
|
|
135
|
-
|
|
136
|
-
rubygems_version: 2.4.5
|
|
137
|
-
signing_key:
|
|
121
|
+
rubygems_version: 3.6.7
|
|
138
122
|
specification_version: 4
|
|
139
123
|
summary: F1 Results
|
|
140
124
|
test_files: []
|