football-sources 0.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.
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