football-sources 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +4 -0
  3. data/Manifest.txt +36 -0
  4. data/README.md +28 -0
  5. data/Rakefile +31 -0
  6. data/lib/football-sources.rb +46 -0
  7. data/lib/football-sources/apis.rb +86 -0
  8. data/lib/football-sources/apis/config.rb +17 -0
  9. data/lib/football-sources/apis/convert.rb +239 -0
  10. data/lib/football-sources/apis/convert_cl.rb +267 -0
  11. data/lib/football-sources/apis/download.rb +11 -0
  12. data/lib/football-sources/apis/stat.rb +59 -0
  13. data/lib/football-sources/version.rb +19 -0
  14. data/lib/football-sources/worldfootball.rb +24 -0
  15. data/lib/football-sources/worldfootball/build.rb +245 -0
  16. data/lib/football-sources/worldfootball/config.rb +16 -0
  17. data/lib/football-sources/worldfootball/convert.rb +100 -0
  18. data/lib/football-sources/worldfootball/convert_reports.rb +107 -0
  19. data/lib/football-sources/worldfootball/download.rb +61 -0
  20. data/lib/football-sources/worldfootball/leagues.rb +200 -0
  21. data/lib/football-sources/worldfootball/leagues/asia.rb +53 -0
  22. data/lib/football-sources/worldfootball/leagues/europe--british_isles.rb +59 -0
  23. data/lib/football-sources/worldfootball/leagues/europe--central.rb +127 -0
  24. data/lib/football-sources/worldfootball/leagues/europe--eastern.rb +82 -0
  25. data/lib/football-sources/worldfootball/leagues/europe--northern.rb +57 -0
  26. data/lib/football-sources/worldfootball/leagues/europe--southern.rb +86 -0
  27. data/lib/football-sources/worldfootball/leagues/europe--western.rb +38 -0
  28. data/lib/football-sources/worldfootball/leagues/europe.rb +13 -0
  29. data/lib/football-sources/worldfootball/leagues/north_america.rb +44 -0
  30. data/lib/football-sources/worldfootball/leagues/pacific.rb +21 -0
  31. data/lib/football-sources/worldfootball/leagues/south_america.rb +11 -0
  32. data/lib/football-sources/worldfootball/mods.rb +72 -0
  33. data/lib/football-sources/worldfootball/tool.rb +100 -0
  34. data/lib/football-sources/worldfootball/vacuum.rb +66 -0
  35. data/lib/football/sources.rb +6 -0
  36. data/test/helper.rb +8 -0
  37. data/test/test_version.rb +16 -0
  38. metadata +147 -0
@@ -0,0 +1,267 @@
1
+ ##########
2
+ ## todo/fix:
3
+ ## use a more generic name?
4
+ ## might also works for euro or worldcup - check?
5
+
6
+
7
+ module Footballdata
8
+
9
+
10
+ STAGE_TO_STAGE = {
11
+ 'PRELIMINARY_SEMI_FINALS' => 'Qualifying',
12
+ 'PRELIMINARY_FINAL' => 'Qualifying',
13
+ '1ST_QUALIFYING_ROUND' => 'Qualifying',
14
+ '2ND_QUALIFYING_ROUND' => 'Qualifying',
15
+ '3RD_QUALIFYING_ROUND' => 'Qualifying',
16
+ 'PLAY_OFF_ROUND' => 'Qualifying',
17
+ 'ROUND_OF_16' => 'Knockout',
18
+ 'QUARTER_FINALS' => 'Knockout',
19
+ 'SEMI_FINALS' => 'Knockout',
20
+ 'FINAL' => 'Knockout',
21
+ }
22
+
23
+ STAGE_TO_ROUND = {
24
+ 'PRELIMINARY_SEMI_FINALS' => 'Preliminary Semifinals',
25
+ 'PRELIMINARY_FINAL' => 'Preliminary Final',
26
+ '1ST_QUALIFYING_ROUND' => 'Qual. Round 1',
27
+ '2ND_QUALIFYING_ROUND' => 'Qual. Round 2',
28
+ '3RD_QUALIFYING_ROUND' => 'Qual. Round 3',
29
+ 'PLAY_OFF_ROUND' => 'Playoff Round',
30
+ 'ROUND_OF_16' => 'Round of 16',
31
+ 'QUARTER_FINALS' => 'Quarterfinals',
32
+ 'SEMI_FINALS' => 'Semifinals',
33
+ 'FINAL' => 'Final',
34
+ }
35
+
36
+
37
+
38
+
39
+ def self.convert_cl( league:, season: )
40
+ season = Season( season ) ## cast (ensure) season class (NOT string, integer, etc.)
41
+
42
+ data = Webcache.read_json( Metal.competition_matches_url( LEAGUES[league.downcase], season.start_year ))
43
+ data_teams = Webcache.read_json( Metal.competition_teams_url( LEAGUES[league.downcase], season.start_year ))
44
+
45
+
46
+ ## build a (reverse) team lookup by name
47
+ puts "#{data_teams['teams'].size} teams"
48
+
49
+ teams_by_name = data_teams['teams'].reduce( {} ) do |h,rec|
50
+ h[ rec['name'] ] = rec
51
+ h
52
+ end
53
+
54
+ pp teams_by_name.keys
55
+
56
+
57
+
58
+ mods = MODS[ league.downcase ] || {}
59
+
60
+
61
+ recs = []
62
+
63
+ teams = Hash.new( 0 )
64
+ stat = Stat.new
65
+
66
+ matches = data[ 'matches' ]
67
+ matches.each do |m|
68
+ stat.update( m )
69
+
70
+ team1 = m['homeTeam']['name']
71
+ team2 = m['awayTeam']['name']
72
+
73
+
74
+ score = m['score']
75
+
76
+
77
+
78
+ if m['stage'] == 'GROUP_STAGE'
79
+ stage = 'Group'
80
+ round = "Matchday #{m['matchday']}"
81
+ if m['group'] =~ /Group ([A-Z])/
82
+ group = $1
83
+ else
84
+ puts "!! ERROR - no group name found for group >#{m['group']}<"
85
+ exit 1
86
+ end
87
+ else
88
+ stage = STAGE_TO_STAGE[ m['stage'] ]
89
+ if stage.nil?
90
+ puts "!! ERROR - no stage mapping found for stage >#{m['stage']}<"
91
+ exit 1
92
+ end
93
+ round = STAGE_TO_ROUND[ m['stage'] ]
94
+ if round.nil?
95
+ puts "!! ERROR - no round mapping found for stage >#{m['stage']}<"
96
+ exit 1
97
+ end
98
+ group = ''
99
+ end
100
+
101
+
102
+ teams[ team1 ] += 1
103
+ teams[ team2 ] += 1
104
+
105
+ ### mods - rename club names
106
+ unless mods.nil? || mods.empty?
107
+ team1 = mods[ team1 ] if mods[ team1 ]
108
+ team2 = mods[ team2 ] if mods[ team2 ]
109
+ end
110
+
111
+
112
+ ## e.g. "utcDate": "2020-05-09T00:00:00Z",
113
+ date_str = m['utcDate']
114
+ date = DateTime.strptime( date_str, '%Y-%m-%dT%H:%M:%SZ' )
115
+
116
+
117
+ comments = ''
118
+ ft = ''
119
+ ht = ''
120
+ et = ''
121
+ pen = ''
122
+
123
+ case m['status']
124
+ when 'SCHEDULED', 'IN_PLAY'
125
+ ft = ''
126
+ ht = ''
127
+ et = ''
128
+ pen = ''
129
+ when 'AWARDED'
130
+ ## todo/fix: assert duration == "REGULAR"
131
+ ft = "#{score['fullTime']['homeTeam']}-#{score['fullTime']['awayTeam']}"
132
+ ft << ' (*)'
133
+ ht = ''
134
+ comments = 'awarded'
135
+ when 'FINISHED'
136
+ ## note: if extraTime present
137
+ ## than fullTime is extraTime score!!
138
+ ## AND fullTime - extraTime is fullTime score!!
139
+ ## double check in other season too??
140
+ ## - checked in cl 2018/19
141
+
142
+ if score['extraTime']['homeTeam'] && score['extraTime']['awayTeam']
143
+ et = "#{score['fullTime']['homeTeam']}-#{score['fullTime']['awayTeam']}"
144
+ ft = "#{score['fullTime']['homeTeam']-score['extraTime']['homeTeam']}-#{score['fullTime']['awayTeam']-score['extraTime']['awayTeam']}"
145
+ else
146
+ ft = "#{score['fullTime']['homeTeam']}-#{score['fullTime']['awayTeam']}"
147
+ end
148
+
149
+ ht = "#{score['halfTime']['homeTeam']}-#{score['halfTime']['awayTeam']}"
150
+
151
+ pen = if score['penalties']['homeTeam'] && score['penalties']['awayTeam']
152
+ "#{score['penalties']['homeTeam']}-#{score['penalties']['awayTeam']}"
153
+ else
154
+ ''
155
+ end
156
+ else
157
+ puts "!! ERROR: unsupported match status >#{m['status']}< - sorry:"
158
+ pp m
159
+ exit 1
160
+ end
161
+
162
+
163
+ recs << [stage,
164
+ round,
165
+ group,
166
+ date.to_date.strftime( '%Y-%m-%d' ),
167
+ team1,
168
+ ft,
169
+ ht,
170
+ team2,
171
+ et,
172
+ pen,
173
+ comments
174
+ ]
175
+
176
+
177
+ print '%2s' % m['matchday']
178
+ print ' - '
179
+ print '%-24s' % team1
180
+ print ' '
181
+ print ft
182
+ print ' '
183
+ print "(#{ht})" unless ht.empty?
184
+ print ' '
185
+ print '%-24s' % team2
186
+ print ' '
187
+ print comments
188
+ print ' | '
189
+ ## print date.to_date ## strip time
190
+ print date.to_date.strftime( '%a %b %-d %Y' )
191
+ print ' -- '
192
+ print date
193
+ print "\n"
194
+
195
+ end # each match
196
+
197
+
198
+ ## note: get season from first match
199
+ ## assert - all other matches include the same season
200
+ ## e.g.
201
+ # "season": {
202
+ # "id": 154,
203
+ # "startDate": "2018-08-03",
204
+ # "endDate": "2019-05-05",
205
+ # "currentMatchday": 46
206
+ # }
207
+
208
+ start_date = Date.strptime( matches[0]['season']['startDate'], '%Y-%m-%d' )
209
+ end_date = Date.strptime( matches[0]['season']['endDate'], '%Y-%m-%d' )
210
+
211
+ dates = "#{start_date.strftime('%b %-d')} - #{end_date.strftime('%b %-d')}"
212
+
213
+ buf = ''
214
+ buf << "#{season.key} (#{dates}) - "
215
+ buf << "#{teams.keys.size} clubs, "
216
+ buf << "#{stat[:all][:matches]} matches, "
217
+ buf << "#{stat[:all][:goals]} goals"
218
+ buf << "\n"
219
+
220
+ puts buf
221
+
222
+
223
+
224
+ # recs = recs.sort { |l,r| l[1] <=> r[1] }
225
+ ## reformat date / beautify e.g. Sat Aug 7 1993
226
+ recs.each { |rec| rec[3] = Date.strptime( rec[3], '%Y-%m-%d' ).strftime( '%a %b %-d %Y' ) }
227
+
228
+ headers = [
229
+ 'Stage',
230
+ 'Round',
231
+ 'Group',
232
+ 'Date',
233
+ 'Team 1',
234
+ 'FT',
235
+ 'HT',
236
+ 'Team 2',
237
+ 'ET',
238
+ 'P',
239
+ 'Comments'
240
+ ]
241
+
242
+ ## note: change season_key from 2019/20 to 2019-20 (for path/directory!!!!)
243
+ Cache::CsvMatchWriter.write( "#{config.convert.out_dir}/#{season.to_path}/#{league.downcase}.csv",
244
+ recs,
245
+ headers: headers )
246
+
247
+
248
+ teams.each do |name, count|
249
+ rec = teams_by_name[ name ]
250
+ print " #{count}x "
251
+ print name
252
+ if rec
253
+ print " | #{rec['shortName']} " if name != rec['shortName']
254
+ print " › #{rec['area']['name']}"
255
+ print " - #{rec['address']}"
256
+ else
257
+ print "!! ERROR - no team record found in teams.json for #{name}"
258
+ exit 1
259
+ end
260
+ print "\n"
261
+ end
262
+
263
+ pp stat
264
+ end # method convert_cl
265
+ end # module Footballdata
266
+
267
+
@@ -0,0 +1,11 @@
1
+
2
+
3
+ module Footballdata
4
+
5
+ def self.schedule( league:, season: )
6
+ season = Season( season ) ## cast (ensure) season class (NOT string, integer, etc.)
7
+
8
+ Metal.competition( LEAGUES[ league.downcase ], season.start_year )
9
+ end
10
+
11
+ end # module Footballdata
@@ -0,0 +1,59 @@
1
+ module Footballdata
2
+
3
+ class Stat ## rename to match stat or something why? why not?
4
+ def initialize
5
+ @data = {}
6
+ end
7
+
8
+ def [](key) @data[ key ]; end
9
+
10
+ def update( match )
11
+ ## keep track of some statistics
12
+ stat = @data[:all] ||= { stage: Hash.new( 0 ),
13
+ duration: Hash.new( 0 ),
14
+ status: Hash.new( 0 ),
15
+ group: Hash.new( 0 ),
16
+ matchday: Hash.new( 0 ),
17
+
18
+ matches: 0,
19
+ goals: 0,
20
+ }
21
+
22
+ stat[:stage][ match['stage'] ] += 1
23
+ stat[:group][ match['group'] ] += 1
24
+ stat[:status][ match['status'] ] += 1
25
+ stat[:matchday][ match['matchday'] ] += 1
26
+
27
+ score = match['score']
28
+
29
+ stat[:duration][ score['duration'] ] += 1 ## track - assert always REGULAR
30
+
31
+ stat[:matches] += 1
32
+ stat[:goals] += score['fullTime']['homeTeam'].to_i if score['fullTime']['homeTeam']
33
+ stat[:goals] += score['fullTime']['awayTeam'].to_i if score['fullTime']['awayTeam']
34
+
35
+
36
+ stage_key = match['stage'].downcase.to_sym # e.g. :regular_season
37
+ stat = @data[ stage_key ] ||= { duration: Hash.new( 0 ),
38
+ status: Hash.new( 0 ),
39
+ group: Hash.new( 0 ),
40
+ matchday: Hash.new( 0 ),
41
+
42
+ matches: 0,
43
+ goals: 0,
44
+ }
45
+ stat[:group][ match['group'] ] += 1
46
+ stat[:status][ match['status'] ] += 1
47
+ stat[:matchday][ match['matchday'] ] += 1
48
+
49
+ stat[:duration][ score['duration'] ] += 1 ## track - assert always REGULAR
50
+
51
+ stat[:matches] += 1
52
+ stat[:goals] += score['fullTime']['homeTeam'].to_i if score['fullTime']['homeTeam']
53
+ stat[:goals] += score['fullTime']['awayTeam'].to_i if score['fullTime']['awayTeam']
54
+ end
55
+ end # class Stat
56
+ end # module Footballdata
57
+
58
+
59
+
@@ -0,0 +1,19 @@
1
+
2
+ module FootballSources
3
+ MAJOR = 0 ## todo: namespace inside version or something - why? why not??
4
+ MINOR = 0
5
+ PATCH = 1
6
+ VERSION = [MAJOR,MINOR,PATCH].join('.')
7
+
8
+ def self.version
9
+ VERSION
10
+ end
11
+
12
+ def self.banner
13
+ "football-sources/#{VERSION} on Ruby #{RUBY_VERSION} (#{RUBY_RELEASE_DATE}) [#{RUBY_PLATFORM}]"
14
+ end
15
+
16
+ def self.root
17
+ File.expand_path( File.dirname(File.dirname(File.dirname(__FILE__))) )
18
+ end
19
+ end # module FootballSources
@@ -0,0 +1,24 @@
1
+ ###########################
2
+ # note: split code in two parts
3
+ # metal - "bare" basics - no ref to sportdb
4
+ # and rest / convert with sportdb references / goodies
5
+
6
+
7
+ ## our own code
8
+ require_relative 'worldfootball/leagues'
9
+
10
+ require_relative 'worldfootball/config'
11
+
12
+ require_relative 'worldfootball/download'
13
+
14
+ require_relative 'worldfootball/mods'
15
+ require_relative 'worldfootball/vacuum'
16
+ require_relative 'worldfootball/build'
17
+ require_relative 'worldfootball/convert'
18
+ require_relative 'worldfootball/convert_reports'
19
+
20
+
21
+ require_relative 'worldfootball/tool'
22
+
23
+
24
+
@@ -0,0 +1,245 @@
1
+
2
+ module Worldfootball
3
+
4
+
5
+ ROUND_TO_EN = {
6
+ '1. Runde' => 'Round 1',
7
+ '2. Runde' => 'Round 2',
8
+ '3. Runde' => 'Round 3',
9
+ '4. Runde' => 'Round 4',
10
+ 'Achtelfinale' => 'Round of 16',
11
+ 'Viertelfinale' => 'Quarterfinals',
12
+ 'Halbfinale' => 'Semifinals',
13
+ 'Finale' => 'Final',
14
+ }
15
+
16
+
17
+ ## todo/check: english league cup/trophy has NO ET - also support - make more flexible!!!
18
+
19
+ ## build "standard" match records from "raw" table rows
20
+ def self.build( rows, season:, league:, stage: '' ) ## rename to fixup or such - why? why not?
21
+ season = Season( season ) ## cast (ensure) season class (NOT string, integer, etc.)
22
+
23
+ raise ArgumentError, "league key as string expected" unless league.is_a?(String) ## note: do NOT pass in league struct! pass in key (string)
24
+
25
+ print " #{rows.size} rows - build #{league} #{season}"
26
+ print " - #{stage}" unless stage.empty?
27
+ print "\n"
28
+
29
+
30
+ ## note: use only first part from key for lookup
31
+ ## e.g. at.1 => at
32
+ ## eng.1 => eng
33
+ ## and so on
34
+ mods = MODS[ league.split('.')[0] ] || {}
35
+
36
+ score_errors = SCORE_ERRORS[ league ] || {}
37
+
38
+
39
+ i = 0
40
+ recs = []
41
+ rows.each do |row|
42
+ i += 1
43
+
44
+
45
+ if row[:round] =~ /Spieltag/
46
+ puts
47
+ print '[%03d] ' % (i+1)
48
+ print row[:round]
49
+
50
+ if m = row[:round].match( /([0-9]+)\. Spieltag/ )
51
+ ## todo/check: always use a string even if number (as a string eg. '1' etc.)
52
+ round = m[1] ## note: keep as string (NOT number)
53
+ print " => #{round}"
54
+ else
55
+ puts "!! ERROR: cannot find matchday number"
56
+ exit 1
57
+ end
58
+ print "\n"
59
+ elsif row[:round] =~ /[1-9]\.[ ]Runde|
60
+ Achtelfinale|
61
+ Viertelfinale|
62
+ Halbfinale|
63
+ Finale
64
+ /x
65
+ puts
66
+ print '[%03d] ' % (i+1)
67
+ print row[:round]
68
+
69
+
70
+ ## do NOT translate rounds (to english) - keep in german / deutsch (de)
71
+ if ['at.cup', 'at.1', ## at.1 - incl. europa league playoff
72
+ 'de.cup'].include?( league )
73
+ round = row[:round]
74
+ else
75
+ round = ROUND_TO_EN[ row[:round] ]
76
+ if round.nil?
77
+ puts "!! ERROR: no mapping for round to english (en) found >#{row[:round]}<:"
78
+ pp row
79
+ exit 1
80
+ end
81
+ print " => #{round}"
82
+ end
83
+ print "\n"
84
+ else
85
+ puts "!! ERROR: unknown round >#{row[:round]}< for league >#{league}<:"
86
+ pp row
87
+ exit 1
88
+ end
89
+
90
+
91
+ date_str = row[:date]
92
+ time_str = row[:time]
93
+ team1_str = row[:team1]
94
+ team2_str = row[:team2]
95
+ score_str = row[:score]
96
+
97
+ ## convert date from string e.g. 2019-25-10
98
+ date = Date.strptime( date_str, '%Y-%m-%d' )
99
+
100
+
101
+ ### check for score_error; first (step 1) lookup by date
102
+ score_error = score_errors[ date.strftime('%Y-%m-%d') ]
103
+ if score_error
104
+ if team1_str == score_error[0] &&
105
+ team2_str == score_error[1]
106
+ ## check if team names match too; if yes, apply fix/patch!!
107
+ if score_str != score_error[2][0]
108
+ puts "!! WARN - score fix changed? - expected #{score_error[2][0]}, got #{score_str} - fixing to #{score_error[2][1]}"
109
+ pp row
110
+ end
111
+ puts "FIX - applying score error fix - from #{score_error[2][0]} to => #{score_error[2][1]}"
112
+ score_str = score_error[2][1]
113
+ end
114
+ end
115
+
116
+
117
+ print '[%03d] ' % (i+1)
118
+ print "%-10s | " % date_str
119
+ print "%-5s | " % time_str
120
+ print "%-22s | " % team1_str
121
+ print "%-22s | " % team2_str
122
+ print score_str
123
+ print "\n"
124
+
125
+
126
+ ## check for 0:3 Wert. - change Wert. to awd. (awarded)
127
+ score_str = score_str.sub( /Wert\./i, 'awd.' )
128
+
129
+ ## clean team name (e.g. remove (old))
130
+ ## and asciify (e.g. ’ to ' )
131
+ team1_str = norm_team( team1_str )
132
+ team2_str = norm_team( team2_str )
133
+
134
+ team1_str = mods[ team1_str ] if mods[ team1_str ]
135
+ team2_str = mods[ team2_str ] if mods[ team2_str ]
136
+
137
+
138
+
139
+
140
+ ht, ft, et, pen, comments = parse_score( score_str )
141
+
142
+
143
+
144
+ recs << [stage,
145
+ round,
146
+ date.strftime( '%Y-%m-%d' ),
147
+ time_str,
148
+ team1_str,
149
+ ft,
150
+ ht,
151
+ team2_str,
152
+ et, # extra: incl. extra time
153
+ pen, # extra: incl. penalties
154
+ comments]
155
+ end # each row
156
+ recs
157
+ end # build
158
+
159
+
160
+
161
+ def self.parse_score( score_str )
162
+ comments = String.new( '' ) ## check - rename to/use status or such - why? why not?
163
+
164
+ ## split score
165
+ ft = ''
166
+ ht = ''
167
+ et = ''
168
+ pen = ''
169
+ if score_str == '---' ## in the future (no score yet) - was -:-
170
+ ft = ''
171
+ ht = ''
172
+ elsif score_str == 'n.gesp.' || ## cancelled (british) / canceled (us)
173
+ score_str == 'ausg.' || ## todo/check: change to some other status ????
174
+ score_str == 'annull.' ## todo/check: change to some other status (see ie 2012) ????
175
+ ft = '(*)'
176
+ ht = ''
177
+ comments = 'cancelled'
178
+ elsif score_str == 'abgebr.' ## abandoned -- waiting for replay?
179
+ ft = '(*)'
180
+ ht = ''
181
+ comments = 'abandoned'
182
+ elsif score_str == 'verl.' ## postponed
183
+ ft = ''
184
+ ht = ''
185
+ comments = 'postponed'
186
+ # 5-4 (0-0, 1-1, 2-2) i.E.
187
+ elsif score_str =~ /([0-9]+) [ ]*-[ ]* ([0-9]+)
188
+ [ ]*
189
+ \(([0-9]+) [ ]*-[ ]* ([0-9]+)
190
+ [ ]*,[ ]*
191
+ ([0-9]+) [ ]*-[ ]* ([0-9]+)
192
+ [ ]*,[ ]*
193
+ ([0-9]+) [ ]*-[ ]* ([0-9]+)\)
194
+ [ ]*
195
+ i\.E\.
196
+ /x
197
+ pen = "#{$1}-#{$2}"
198
+ ht = "#{$3}-#{$4}"
199
+ ft = "#{$5}-#{$6}"
200
+ et = "#{$7}-#{$8}"
201
+ # 2-1 (1-0, 1-1) n.V
202
+ elsif score_str =~ /([0-9]+) [ ]*-[ ]* ([0-9]+)
203
+ [ ]*
204
+ \(([0-9]+) [ ]*-[ ]* ([0-9]+)
205
+ [ ]*,[ ]*
206
+ ([0-9]+) [ ]*-[ ]* ([0-9]+)
207
+ \)
208
+ [ ]*
209
+ n\.V\.
210
+ /x
211
+ et = "#{$1}-#{$2}"
212
+ ht = "#{$3}-#{$4}"
213
+ ft = "#{$5}-#{$6}"
214
+ elsif score_str =~ /([0-9]+)
215
+ [ ]*-[ ]*
216
+ ([0-9]+)
217
+ [ ]*
218
+ \(([0-9]+)
219
+ [ ]*-[ ]*
220
+ ([0-9]+)
221
+ \)
222
+ /x
223
+ ft = "#{$1}-#{$2}"
224
+ ht = "#{$3}-#{$4}"
225
+ elsif score_str =~ /([0-9]+)
226
+ [ ]*-[ ]*
227
+ ([0-9]+)
228
+ [ ]*
229
+ ([a-z.]+)
230
+ /x
231
+ ft = "#{$1}-#{$2} (*)"
232
+ ht = ''
233
+ comments = $3
234
+ elsif score_str =~ /^([0-9]+)-([0-9]+)$/
235
+ ft = "#{$1}-#{$2}" ## e.g. see luxemburg and others
236
+ ht = ''
237
+ else
238
+ puts "!! ERROR - unsupported score format >#{score_str}< - sorry; maybe add a score error fix/patch"
239
+ exit 1
240
+ end
241
+
242
+ [ht, ft, et, pen, comments]
243
+ end
244
+
245
+ end # module Worldfootball