antw-dyno 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +19 -0
- data/README.markdown +68 -0
- data/Rakefile +45 -0
- data/VERSION.yml +4 -0
- data/lib/dyno.rb +22 -0
- data/lib/dyno/competitor.rb +55 -0
- data/lib/dyno/event.rb +16 -0
- data/lib/dyno/parsers/gtr2_parser.rb +13 -0
- data/lib/dyno/parsers/race07_parser.rb +130 -0
- data/lib/dyno/parsers/rfactor_parser.rb +138 -0
- data/spec/competitor_spec.rb +129 -0
- data/spec/event_spec.rb +59 -0
- data/spec/fixtures/gtr2/full.ini +0 -0
- data/spec/fixtures/gtr2/header_no_track.ini +0 -0
- data/spec/fixtures/gtr2/header_only.ini +0 -0
- data/spec/fixtures/gtr2/no_header_section.ini +0 -0
- data/spec/fixtures/gtr2/single_driver.ini +0 -0
- data/spec/fixtures/race07/full.ini +131 -0
- data/spec/fixtures/race07/header_no_track.ini +11 -0
- data/spec/fixtures/race07/header_only.ini +12 -0
- data/spec/fixtures/race07/no_header_section.ini +6 -0
- data/spec/fixtures/race07/no_steam_id.ini +37 -0
- data/spec/fixtures/race07/single_driver.ini +37 -0
- data/spec/fixtures/race07/single_driver_dnf.ini +34 -0
- data/spec/fixtures/race07/single_driver_dsq.ini +34 -0
- data/spec/fixtures/readme.markdown +5 -0
- data/spec/fixtures/rfactor/arca.xml +187 -0
- data/spec/fixtures/rfactor/event_only.xml +43 -0
- data/spec/fixtures/rfactor/full.xml +1023 -0
- data/spec/fixtures/rfactor/missing_root.xml +6 -0
- data/spec/fixtures/rfactor/single_driver.xml +85 -0
- data/spec/fixtures/rfactor/single_driver_dnf.xml +80 -0
- data/spec/fixtures/rfactor/single_driver_dsq.xml +79 -0
- data/spec/parsers/gtr2_parser_spec.rb +199 -0
- data/spec/parsers/race07_parser_spec.rb +155 -0
- data/spec/parsers/rfactor_parser_spec.rb +158 -0
- data/spec/spec_helper.rb +3 -0
- metadata +107 -0
data/LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2008 Anthony Williams
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in all
|
11
|
+
copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
19
|
+
SOFTWARE.
|
data/README.markdown
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
dyno
|
2
|
+
====
|
3
|
+
|
4
|
+
> _A dynamometer or "dyno" for short, is a machine used to measure torque and
|
5
|
+
> rotational speed (rpm) from which power produced by an engine, motor or
|
6
|
+
> other rotating prime mover can be calculated. ([Wikipedia][dyno-wp])_
|
7
|
+
|
8
|
+
In this context, however, Dyno is a pure Ruby library for parsing sim-racing
|
9
|
+
result files, soon to be used on the upcoming Torque sim-racing app.
|
10
|
+
|
11
|
+
Dyno presently supports files spat out by a number of games:
|
12
|
+
|
13
|
+
* **Race07Parser** parses results from RACE 07, GTR: Evolution, and
|
14
|
+
STCC: The Game
|
15
|
+
* **GTR2Parser** parses results from GTR2.
|
16
|
+
* **RFactorParser** parses results from rFactor (and mods), and ARCA.
|
17
|
+
|
18
|
+
Dyno requires the iniparse gem (available via `gem install iniparse` or on
|
19
|
+
[GitHub][iniparse]) for the Race 07 and GTR2 parsers, and libxml-ruby for
|
20
|
+
the rFactor parser.
|
21
|
+
|
22
|
+
Usage
|
23
|
+
-----
|
24
|
+
|
25
|
+
Dyno::Parsers::Race07Parser.parse_file( '/path/to/result/file' )
|
26
|
+
# => Dyno::Event
|
27
|
+
|
28
|
+
Dyno::Parsers::GTR2Parser.parse_file( '/path/to/result/file' )
|
29
|
+
# => Dyno::Event
|
30
|
+
|
31
|
+
Dyno::Parsers::RFactorParser.parse_file( '/path/to/result/file' )
|
32
|
+
# => Dyno::Event
|
33
|
+
|
34
|
+
Each of these operations will return a Dyno::Event instance. Dyno::Event
|
35
|
+
defines `#competitors` containing a collection of all of those who took part
|
36
|
+
in the event. See the API docs for Dyno::Event and Dyno::Competitor for more
|
37
|
+
details.
|
38
|
+
|
39
|
+
If your results file couldn't be parsed, a Dyno::MalformedInputError exception
|
40
|
+
will be raised.
|
41
|
+
|
42
|
+
License
|
43
|
+
-------
|
44
|
+
|
45
|
+
Dyno is distributed under the MIT/X11 License.
|
46
|
+
|
47
|
+
> Copyright (c) 2008-2009 Anthony Williams
|
48
|
+
>
|
49
|
+
> Permission is hereby granted, free of charge, to any person obtaining a copy
|
50
|
+
> of this software and associated documentation files (the "Software"), to
|
51
|
+
> deal in the Software without restriction, including without limitation the
|
52
|
+
> rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
53
|
+
> sell copies of the Software, and to permit persons to whom the Software is
|
54
|
+
> furnished to do so, subject to the following conditions:
|
55
|
+
>
|
56
|
+
> The above copyright notice and this permission notice shall be included in
|
57
|
+
> all copies or substantial portions of the Software.
|
58
|
+
>
|
59
|
+
> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
60
|
+
> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
61
|
+
> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
62
|
+
> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
63
|
+
> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
64
|
+
> FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
65
|
+
> IN THE SOFTWARE.
|
66
|
+
|
67
|
+
[dyno-wp]: http://en.wikipedia.org/wiki/Dynamometer "Wikipedia"
|
68
|
+
[iniparse]: http://github.com/antw/iniparse "IniParse on GitHub"
|
data/Rakefile
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'spec/rake/spectask'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |s|
|
7
|
+
s.name = 'dyno'
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.has_rdoc = true
|
10
|
+
s.summary = 'A rubygem for parsing sim-racing results files.'
|
11
|
+
s.description = s.summary
|
12
|
+
s.author = 'Anthony Williams'
|
13
|
+
s.email = 'anthony@ninecraft.com'
|
14
|
+
s.homepage = 'http://github.com/anthonyw/dyno'
|
15
|
+
|
16
|
+
s.extra_rdoc_files = ['README.markdown', 'LICENSE']
|
17
|
+
|
18
|
+
# Dependencies.
|
19
|
+
s.add_dependency "iniparse", ">= 0.2.0"
|
20
|
+
|
21
|
+
s.files = %w(LICENSE README.markdown Rakefile VERSION.yml) +
|
22
|
+
Dir.glob("{lib,spec}/**/*")
|
23
|
+
end
|
24
|
+
rescue LoadError
|
25
|
+
puts 'Jeweler not available. Install it with: sudo gem install ' +
|
26
|
+
'technicalpickles-jeweler -s http://gems.github.com'
|
27
|
+
end
|
28
|
+
|
29
|
+
##############################################################################
|
30
|
+
# rSpec & rcov
|
31
|
+
##############################################################################
|
32
|
+
|
33
|
+
desc "Run all examples"
|
34
|
+
Spec::Rake::SpecTask.new('spec') do |t|
|
35
|
+
t.spec_files = FileList['spec/**/*.rb']
|
36
|
+
t.spec_opts = ['-c -f s']
|
37
|
+
end
|
38
|
+
|
39
|
+
desc "Run all examples with RCov"
|
40
|
+
Spec::Rake::SpecTask.new('spec:rcov') do |t|
|
41
|
+
t.spec_files = FileList['spec/**/*.rb']
|
42
|
+
t.spec_opts = ['-c -f s']
|
43
|
+
t.rcov = true
|
44
|
+
t.rcov_opts = ['--exclude', 'spec']
|
45
|
+
end
|
data/VERSION.yml
ADDED
data/lib/dyno.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'time'
|
3
|
+
require 'iniparse'
|
4
|
+
require 'libxml'
|
5
|
+
|
6
|
+
module Dyno
|
7
|
+
# Base exception class.
|
8
|
+
class DynoError < StandardError; end
|
9
|
+
|
10
|
+
# Raised if an input source couldn't be parsed, or was missing something.
|
11
|
+
class MalformedInputError < DynoError; end
|
12
|
+
end
|
13
|
+
|
14
|
+
dir = File.join( File.dirname(__FILE__), "dyno" )
|
15
|
+
|
16
|
+
require File.join( dir, "competitor" )
|
17
|
+
require File.join( dir, "event" )
|
18
|
+
|
19
|
+
# Parsers
|
20
|
+
require File.join( dir, "parsers", "race07_parser" )
|
21
|
+
require File.join( dir, "parsers", "gtr2_parser" )
|
22
|
+
require File.join( dir, "parsers", "rfactor_parser" )
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module Dyno
|
2
|
+
class Competitor
|
3
|
+
attr_accessor :name, :uid, :position, :vehicle, :laps, :race_time,
|
4
|
+
:best_lap, :lap_times
|
5
|
+
|
6
|
+
##
|
7
|
+
# @param [String] name The competitor's name.
|
8
|
+
# @param [Hash] properties Extra information about the competitor.
|
9
|
+
#
|
10
|
+
def initialize(name, properties = {})
|
11
|
+
@name = name
|
12
|
+
@lap_times = properties.fetch(:laps, [])
|
13
|
+
@dnf = false
|
14
|
+
|
15
|
+
[:uid, :position, :vehicle, :laps, :race_time, :best_lap].each do |prop|
|
16
|
+
instance_variable_set "@#{prop}", properties[prop]
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
##
|
21
|
+
# Returns true fi the competitor finished the event.
|
22
|
+
#
|
23
|
+
def finished?
|
24
|
+
not dnf? and not dsq?
|
25
|
+
end
|
26
|
+
|
27
|
+
##
|
28
|
+
# Flags this competitor as having not completed the event.
|
29
|
+
#
|
30
|
+
def dnf!
|
31
|
+
@dnf = :dnf
|
32
|
+
end
|
33
|
+
|
34
|
+
##
|
35
|
+
# Returns true if the competitor failed to finish.
|
36
|
+
#
|
37
|
+
def dnf?
|
38
|
+
@dnf == :dnf
|
39
|
+
end
|
40
|
+
|
41
|
+
##
|
42
|
+
# Flags this competitor has having been disqualified from the event.
|
43
|
+
#
|
44
|
+
def dsq!
|
45
|
+
@dnf = :dsq
|
46
|
+
end
|
47
|
+
|
48
|
+
##
|
49
|
+
# Returns true if the competitor was disqualified.
|
50
|
+
#
|
51
|
+
def dsq?
|
52
|
+
@dnf == :dsq
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
data/lib/dyno/event.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
module Dyno
|
2
|
+
class Event
|
3
|
+
attr_accessor :time, :game, :game_version, :track, :competitors
|
4
|
+
|
5
|
+
##
|
6
|
+
# @param [Hash] properties Event information.
|
7
|
+
#
|
8
|
+
def initialize(properties = {})
|
9
|
+
@time = properties.fetch( :time, Time.now )
|
10
|
+
@track = properties[:track]
|
11
|
+
@game = properties[:game]
|
12
|
+
@game_version = properties[:game_version]
|
13
|
+
@competitors = properties.fetch( :competitors, [] )
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Dyno::Parsers
|
2
|
+
##
|
3
|
+
# Parses a Race 07 results file which are almost identical to Race 07 files.
|
4
|
+
#
|
5
|
+
class GTR2Parser < Race07Parser
|
6
|
+
def self.parse_file( filename )
|
7
|
+
# GTR2 files start with a line which isn't valid INI; remove it.
|
8
|
+
parse( IniParse.parse(
|
9
|
+
File.read( filename ).sub!(/^.*\n/, '')
|
10
|
+
) )
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,130 @@
|
|
1
|
+
module Dyno::Parsers
|
2
|
+
##
|
3
|
+
# Parses a Race 07 results file (which appears to be some variation of the
|
4
|
+
# ini format).
|
5
|
+
#
|
6
|
+
class Race07Parser
|
7
|
+
##
|
8
|
+
# Takes a file path and parses it.
|
9
|
+
#
|
10
|
+
# @param [String] filename The path to the results file.
|
11
|
+
#
|
12
|
+
def self.parse_file( filename )
|
13
|
+
parse( IniParse.open( filename ) )
|
14
|
+
end
|
15
|
+
|
16
|
+
##
|
17
|
+
# Takes an IniParse::Document instance, parses the contents, and returns a
|
18
|
+
# Dyno::Event containing your results.
|
19
|
+
#
|
20
|
+
# @param [IniParse::Document] results The results.
|
21
|
+
# @return [Dyno::Event]
|
22
|
+
#
|
23
|
+
def self.parse( results )
|
24
|
+
new( results ).parse
|
25
|
+
end
|
26
|
+
|
27
|
+
##
|
28
|
+
# Returns your parsed event and competitor information.
|
29
|
+
#
|
30
|
+
def parse
|
31
|
+
parse_event!
|
32
|
+
parse_competitors!
|
33
|
+
@event
|
34
|
+
end
|
35
|
+
|
36
|
+
#######
|
37
|
+
private
|
38
|
+
#######
|
39
|
+
|
40
|
+
##
|
41
|
+
# Takes an IniParse::Document instance and parses the contents.
|
42
|
+
#
|
43
|
+
# @param [IniParse::Document] results The results.
|
44
|
+
#
|
45
|
+
def initialize( results )
|
46
|
+
@raw = results
|
47
|
+
end
|
48
|
+
|
49
|
+
##
|
50
|
+
# Extracts the event information from the results.
|
51
|
+
#
|
52
|
+
def parse_event!
|
53
|
+
raise Dyno::MalformedInputError unless @raw.has_section?('Header')
|
54
|
+
|
55
|
+
@event = Dyno::Event.new( :game => @raw['Header']['Game'] )
|
56
|
+
@event.time = Time.parse( @raw['Header']['TimeString'] )
|
57
|
+
@event.game_version = @raw['Header']['Version']
|
58
|
+
|
59
|
+
# Extract the track name from Race/Scene
|
60
|
+
if @raw.has_section?('Race') && @raw['Race']['Scene']
|
61
|
+
@event.track = @raw['Race']['Scene'].split( '\\' )[-2].gsub( /[_-]+/, ' ' )
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
##
|
66
|
+
# Extracts information about each of the competitors.
|
67
|
+
#
|
68
|
+
def parse_competitors!
|
69
|
+
finished_competitors = []
|
70
|
+
dnf_competitors = []
|
71
|
+
|
72
|
+
@raw.each do |section|
|
73
|
+
# Competitor sections are named SlotNNN.
|
74
|
+
next unless section.key =~ /Slot\d\d\d/
|
75
|
+
|
76
|
+
competitor = Dyno::Competitor.new(section['Driver'],
|
77
|
+
:vehicle => section['Vehicle'],
|
78
|
+
:laps => section['Laps'].to_i
|
79
|
+
)
|
80
|
+
|
81
|
+
# Some results files have a blank ID.
|
82
|
+
if section['SteamId'] && section['SteamId'].kind_of?(Numeric)
|
83
|
+
competitor.uid = section['SteamId']
|
84
|
+
end
|
85
|
+
|
86
|
+
# Sort out the competitors lap times.
|
87
|
+
competitor.best_lap = lap_time_to_float(section['BestLap'])
|
88
|
+
|
89
|
+
competitor.lap_times = section['Lap'].map do |lap|
|
90
|
+
lap = lap.gsub(/\((.*)\)/, '\1')
|
91
|
+
lap_time_to_float(lap.split(',').last.strip)
|
92
|
+
end
|
93
|
+
|
94
|
+
if section['RaceTime'] =~ /D(NF|S?Q)/
|
95
|
+
$1 == 'NF' ? competitor.dnf! : competitor.dsq!
|
96
|
+
competitor.race_time = 0
|
97
|
+
dnf_competitors << competitor
|
98
|
+
else
|
99
|
+
time = section['RaceTime'].split( /:|\./ )
|
100
|
+
|
101
|
+
competitor.race_time = time[2].to_f + ( time[1].to_i * 60 ) +
|
102
|
+
( time[0].to_i * 60 * 60 ) + "0.#{time[3]}".to_f
|
103
|
+
|
104
|
+
finished_competitors << competitor
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
# Sort finished competitors by their race time, lowest (P1) first.
|
109
|
+
finished_competitors = finished_competitors.sort_by do |c|
|
110
|
+
[ - c.laps, c.race_time ]
|
111
|
+
end
|
112
|
+
|
113
|
+
# ... and DNF'ed competitors by how many laps they've done.
|
114
|
+
dnf_competitors = dnf_competitors.sort_by { |c| c.laps }.reverse!
|
115
|
+
|
116
|
+
# Finally let's assign their finishing positions.
|
117
|
+
competitors = finished_competitors + dnf_competitors
|
118
|
+
competitors.each_with_index { |c, i| c.position = i + 1 }
|
119
|
+
|
120
|
+
# All done!
|
121
|
+
@event.competitors = competitors
|
122
|
+
end
|
123
|
+
|
124
|
+
# Converts a lap time (in the format of M:SS:SSS) to a float.
|
125
|
+
def lap_time_to_float(time)
|
126
|
+
time = time.split( /:|\./ )
|
127
|
+
time[1].to_f + ( time[0].to_i * 60 ) + "0.#{time[2]}".to_f
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
@@ -0,0 +1,138 @@
|
|
1
|
+
module Dyno::Parsers
|
2
|
+
##
|
3
|
+
# Parses a rFactor results files.
|
4
|
+
#
|
5
|
+
class RFactorParser
|
6
|
+
##
|
7
|
+
# Takes a file path and parses it.
|
8
|
+
#
|
9
|
+
# @param [String] filename The path to the results file.
|
10
|
+
#
|
11
|
+
def self.parse_file( filename )
|
12
|
+
xmlparser = LibXML::XML::Parser.new
|
13
|
+
xmlparser.file = filename
|
14
|
+
parse( xmlparser.parse )
|
15
|
+
end
|
16
|
+
|
17
|
+
##
|
18
|
+
# Takes a LibXML::XML::Document instance, parses the contents, and returns
|
19
|
+
# a Dyno::Event containing your results.
|
20
|
+
#
|
21
|
+
# @param [LibXML::XML::Document] results The results.
|
22
|
+
# @return [Dyno::Event]
|
23
|
+
#
|
24
|
+
def self.parse( results )
|
25
|
+
new( results ).parse
|
26
|
+
end
|
27
|
+
|
28
|
+
##
|
29
|
+
# Returns your parsed event and competitor information.
|
30
|
+
#
|
31
|
+
def parse
|
32
|
+
parse_event!
|
33
|
+
parse_competitors!
|
34
|
+
@event
|
35
|
+
end
|
36
|
+
|
37
|
+
#######
|
38
|
+
private
|
39
|
+
#######
|
40
|
+
|
41
|
+
##
|
42
|
+
# Takes a LibXML::XML::Document instance and parses the contents.
|
43
|
+
#
|
44
|
+
# @param [LibXML::XML::Document] results The results.
|
45
|
+
#
|
46
|
+
def initialize( results )
|
47
|
+
@doc = results
|
48
|
+
@stack = []
|
49
|
+
end
|
50
|
+
|
51
|
+
##
|
52
|
+
# Extracts the event information from the results.
|
53
|
+
#
|
54
|
+
def parse_event!
|
55
|
+
unless results = @doc.find_first('//RaceResults')
|
56
|
+
raise Dyno::MalformedInputError, 'No //RaceResults node.'
|
57
|
+
end
|
58
|
+
|
59
|
+
with_node(results) do
|
60
|
+
@event = Dyno::Event.new(
|
61
|
+
:game => clean(value('Mod').gsub(/\.rfm$/, '')),
|
62
|
+
:track => clean(value('TrackCourse')),
|
63
|
+
:game_version => value('GameVersion')
|
64
|
+
)
|
65
|
+
|
66
|
+
# Sort out the event time - the rFactor results format has more than
|
67
|
+
# one TimeString node. The first appears to be when the server was
|
68
|
+
# started - we need the time of the event itself, which is held under
|
69
|
+
# the Race, Qualify or Warmup node.
|
70
|
+
if event_node = \
|
71
|
+
(@doc.find_first('//RaceResults/Race') ||
|
72
|
+
@doc.find_first('//RaceResults/Qualify') ||
|
73
|
+
@doc.find_first('//RaceResults/Warmup'))
|
74
|
+
with_node(event_node) do
|
75
|
+
@event.time = Time.parse( value('TimeString') )
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
##
|
82
|
+
# Extracts information about each of the competitors.
|
83
|
+
#
|
84
|
+
def parse_competitors!
|
85
|
+
@doc.find('//Driver').each do |section|
|
86
|
+
with_node(section) do
|
87
|
+
competitor = Dyno::Competitor.new(value('Name'),
|
88
|
+
:vehicle => clean(value('CarType')),
|
89
|
+
:laps => value('Laps').to_i
|
90
|
+
)
|
91
|
+
|
92
|
+
competitor.position = value('Position').to_i
|
93
|
+
competitor.best_lap = lap_time_to_float( value('BestLapTime') )
|
94
|
+
competitor.race_time = lap_time_to_float( value('FinishTime') )
|
95
|
+
|
96
|
+
competitor.lap_times = section.find('Lap').map do |lap|
|
97
|
+
lap_time_to_float( lap.content )
|
98
|
+
end
|
99
|
+
|
100
|
+
if value('FinishStatus') =~ /D(NF|S?Q)/
|
101
|
+
$1 == 'NF' ? competitor.dnf! : competitor.dsq!
|
102
|
+
end
|
103
|
+
|
104
|
+
@event.competitors << competitor
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
@event.competitors = @event.competitors.sort_by { |c| c.position }
|
109
|
+
end
|
110
|
+
|
111
|
+
# --
|
112
|
+
# Utility stuff
|
113
|
+
# ++
|
114
|
+
|
115
|
+
def value( node ) # :nodoc:
|
116
|
+
if found = @stack.last.find_first( node )
|
117
|
+
found.content
|
118
|
+
else
|
119
|
+
nil
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def clean( value ) # :nodoc:
|
124
|
+
value.gsub(/[-_]/, ' ').squeeze(' ') unless value.nil?
|
125
|
+
end
|
126
|
+
|
127
|
+
def with_node( node ) # :nodoc:
|
128
|
+
@stack.push( node )
|
129
|
+
yield
|
130
|
+
@stack.pop
|
131
|
+
end
|
132
|
+
|
133
|
+
# Converts a lap time (in the format of M:SS:SSS) to a float.
|
134
|
+
def lap_time_to_float(time) # :nodoc:
|
135
|
+
(time.nil? || time == '--.----') ? 0.0 : Float(time)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|