sportdb-formats 1.0.3 → 1.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -100,6 +100,9 @@ module SportDb
100
100
  ## optional headers - note: find_header returns nil if header NOT found
101
101
  header_stage = find_header( headers, ['Stage'] )
102
102
  headers_mapping[:stage] = header_stage if header_stage
103
+
104
+ header_league = find_header( headers, ['League'] )
105
+ headers_mapping[:league] = header_league if header_league
103
106
  else
104
107
  ## else try footballdata.uk and others
105
108
  headers_mapping[:team1] = find_header( headers, ['HomeTeam', 'HT', 'Home'] )
@@ -167,7 +170,10 @@ module SportDb
167
170
 
168
171
 
169
172
  ## check if data present - if not skip (might be empty row)
170
- if team1.nil? && team2.nil?
173
+ ## note: (old classic) csv reader returns nil for empty fields
174
+ ## new modern csv reader ALWAYS returns strings (and empty strings for data not available (n/a))
175
+ if (team1.nil? || team1.empty?) &&
176
+ (team2.nil? || team2.empty?)
171
177
  puts "*** WARN: skipping empty? row[#{i}] - no teams found:"
172
178
  pp row
173
179
  next
@@ -182,9 +188,11 @@ module SportDb
182
188
  col = row[ headers_mapping[ :date ]]
183
189
  col = col.strip # make sure not leading or trailing spaces left over
184
190
 
185
- if col.empty? || col == '-' || col == '?'
186
- ## note: allow missing / unknown date for match
187
- date = nil
191
+ if col.empty? ||
192
+ col =~ /^-{1,}$/ || # e.g. - or ---
193
+ col =~ /^\?{1,}$/ # e.g. ? or ???
194
+ ## note: allow missing / unknown date for match
195
+ date = nil
188
196
  else
189
197
  ## remove possible weekday or weeknumber e.g. (Fri) (4) etc.
190
198
  col = col.sub( /\(W?\d{1,2}\)/, '' ) ## e.g. (W11), (4), (21) etc.
@@ -199,6 +207,8 @@ module SportDb
199
207
  date_fmt = '%Y-%m-%d' # e.g. 1995-08-04
200
208
  elsif col =~ /^\d{1,2} \w{3} \d{4}$/
201
209
  date_fmt = '%d %b %Y' # e.g. 8 Jul 2017
210
+ elsif col =~ /^\w{3} \w{3} \d{1,2} \d{4}$/
211
+ date_fmt = '%a %b %d %Y' # e.g. Sat Aug 7 1993
202
212
  else
203
213
  puts "*** !!! wrong (unknown) date format >>#{col}<<; cannot continue; fix it; sorry"
204
214
  ## todo/fix: add to errors/warns list - why? why not?
@@ -283,13 +293,17 @@ module SportDb
283
293
  end
284
294
  end
285
295
 
296
+ league = nil
297
+ league = row[ headers_mapping[ :league ]] if headers_mapping[ :league ]
298
+
286
299
 
287
300
  match = Import::Match.new( date: date,
288
301
  team1: team1, team2: team2,
289
302
  score1: score1, score2: score2,
290
303
  score1i: score1i, score2i: score2i,
291
304
  round: round,
292
- stage: stage )
305
+ stage: stage,
306
+ league: league )
293
307
  matches << match
294
308
  end
295
309
 
@@ -4,31 +4,50 @@ module SportDb
4
4
 
5
5
  ## todo/fix: make all regexes case-insensitive with /i option - why? why not?
6
6
  ## e.g. .TXT and .txt
7
+ ## yes!! use /i option!!!!!
7
8
 
8
9
  CONF_RE = %r{ (?: ^|/ ) # beginning (^) or beginning of path (/)
9
10
  \.conf\.txt$
10
11
  }x
11
12
 
13
+ ## leagues.txt or leagues_en.txt
14
+ ## remove support for en.leagues.txt - why? why not?
12
15
  LEAGUES_RE = %r{ (?: ^|/ ) # beginning (^) or beginning of path (/)
13
16
  (?: [a-z]{1,4}\. )? # optional country code/key e.g. eng.clubs.wiki.txt
14
- leagues\.txt$
17
+ leagues
18
+ (?:_[a-z0-9_-]+)?
19
+ \.txt$
15
20
  }x
16
21
 
22
+ ## clubs.txt or clubs_en.txt
23
+ ## remove support for en.clubs.txt - why? why not?
17
24
  CLUBS_RE = %r{ (?: ^|/ ) # beginning (^) or beginning of path (/)
18
25
  (?: [a-z]{1,4}\. )? # optional country code/key e.g. eng.clubs.txt
19
- clubs\.txt$
26
+ clubs
27
+ (?:_[a-z0-9_-]+)?
28
+ \.txt$
20
29
  }x
21
30
 
22
31
  CLUBS_WIKI_RE = %r{ (?:^|/) # beginning (^) or beginning of path (/)
23
32
  (?:[a-z]{1,4}\.)? # optional country code/key e.g. eng.clubs.wiki.txt
24
- clubs\.wiki\.txt$
33
+ clubs
34
+ (?:_[a-z0-9_-]+)?
35
+ \.wiki\.txt$
25
36
  }x
26
37
 
27
38
  CLUB_PROPS_RE = %r{ (?: ^|/ ) # beginning (^) or beginning of path (/)
28
39
  (?: [a-z]{1,4}\. )? # optional country code/key e.g. eng.clubs.props.txt
29
- clubs\.props\.txt$
40
+ clubs
41
+ (?:_[a-z0-9_-]+)?
42
+ \.props\.txt$
30
43
  }x
31
44
 
45
+ ## teams.txt or teams_history.txt
46
+ TEAMS_RE = %r{ (?: ^|/ ) # beginning (^) or beginning of path (/)
47
+ teams
48
+ (?:_[a-z0-9_-]+)?
49
+ \.txt$
50
+ }x
32
51
 
33
52
  ### season folder:
34
53
  ## e.g. /2019-20 or
@@ -36,7 +55,7 @@ module SportDb
36
55
  ## /2016--france
37
56
  SEASON_RE = %r{ (?:
38
57
  \d{4}-\d{2}
39
- | \d{4}(--[^/]+)?
58
+ | \d{4}(--[a-z0-9_-]+)?
40
59
  )
41
60
  }x
42
61
  SEASON = SEASON_RE.source ## "inline" helper for embedding in other regexes - keep? why? why not?
@@ -54,6 +73,10 @@ module SportDb
54
73
  /[a-z0-9_.-]+\.csv$ ## note: allow dot (.) too e.g /eng.1.csv
55
74
  }x
56
75
 
76
+ ### add "generic" pattern to find all csv datafiles
77
+ CSV_RE = %r{ (?: ^|/ )
78
+ [a-z0-9_.-]+\.csv$ ## note: allow dot (.) too e.g /eng.1.csv
79
+ }x
57
80
 
58
81
 
59
82
  ## move class-level "static" finders to DirPackage (do NOT work for now for zip packages) - why? why not?
@@ -74,6 +97,8 @@ module SportDb
74
97
  end
75
98
 
76
99
 
100
+ def self.find_teams( path, pattern: TEAMS_RE ) find( path, pattern ); end
101
+ def self.match_teams( path ) TEAMS_RE.match( path ); end
77
102
 
78
103
  def self.find_clubs( path, pattern: CLUBS_RE ) find( path, pattern ); end
79
104
  def self.find_clubs_wiki( path, pattern: CLUBS_WIKI_RE ) find( path, pattern ); end
@@ -97,7 +122,11 @@ module SportDb
97
122
  end
98
123
  ## add match_match and match_match_csv - why? why not?
99
124
 
125
+
100
126
  class << self
127
+ alias_method :match_teams?, :match_teams
128
+ alias_method :teams?, :match_teams
129
+
101
130
  alias_method :match_clubs?, :match_clubs
102
131
  alias_method :clubs?, :match_clubs
103
132
 
@@ -188,6 +217,8 @@ module SportDb
188
217
  end
189
218
  end
190
219
  def each_match_csv( &blk ) each( pattern: MATCH_CSV_RE, &blk ); end
220
+ def each_csv( &blk ) each( pattern: CSV_RE, &blk ); end
221
+
191
222
  def each_club_props( &blk ) each( pattern: CLUB_PROPS_RE, &blk ); end
192
223
 
193
224
  def each_leagues( &blk ) each( pattern: LEAGUES_RE, &blk ); end
@@ -18,10 +18,19 @@ module SportDb
18
18
 
19
19
 
20
20
  def is_round?( line )
21
- ## note: =~ return nil if not match found, and 0,1, etc for match
22
- (line =~ SportDb.lang.regex_round) != nil
21
+ ## note: =~ returns nil if not match found, and 0,1, etc for match
22
+
23
+ ## note: allow "free standing" leg 1 and leg 2 too
24
+ ## (e.g. Hinspiel, Rückspiel etc. used for now in Relegation, for example)
25
+ ## note ONLY allowed if "free standing", that is, full line with nothing else
26
+ ## use "custom" regex for special case for now
27
+ ## avoids match HIN in PascHINg, for example (hin in german for leg 1)
28
+ line =~ SportDb.lang.regex_round ||
29
+ line =~ /^(#{SportDb.lang.leg1})$/i ||
30
+ line =~ /^(#{SportDb.lang.leg2})$/i
23
31
  end
24
32
 
33
+
25
34
  def is_knockout_round?( line )
26
35
 
27
36
  ## todo: check for adding ignore case for regex (e.g. 1st leg/1st Leg)
@@ -90,6 +90,12 @@ class ScoreParser
90
90
 
91
91
 
92
92
  def parse( line )
93
+
94
+ ##########
95
+ ## todo/fix/check: add unicode to regular dash conversion - why? why not?
96
+ ## e.g. – becomes - (yes, the letters a different!!!)
97
+ #############
98
+
93
99
  score = nil
94
100
  @formats.each do |format|
95
101
  re = format[0]
@@ -11,11 +11,14 @@ class Country
11
11
 
12
12
  ## note: is read-only/immutable for now - why? why not?
13
13
  ## add cities (array/list) - why? why not?
14
- attr_reader :key, :name, :fifa, :tags
14
+ attr_reader :key, :name, :code, :tags
15
15
  attr_accessor :alt_names
16
16
 
17
- def initialize( key:, name:, fifa:, tags: [] )
18
- @key, @name, @fifa = key, name, fifa
17
+ def initialize( key: nil, name:, code:, tags: [] )
18
+ ## note: auto-generate key "on-the-fly" if missing for now - why? why not?
19
+ ## note: quick hack - auto-generate key, that is, remove all non-ascii chars and downcase
20
+ @key = key || name.downcase.gsub( /[^a-z]/, '' )
21
+ @name, @code = name, code
19
22
  @alt_names = []
20
23
  @tags = tags
21
24
  end
@@ -2,20 +2,13 @@ module SportDb
2
2
  module Import
3
3
 
4
4
  class Group
5
- attr_reader :title, :pos, :teams
5
+ attr_reader :key, :name, :teams
6
6
 
7
- ##
8
- ## todo: change db schema
9
- ## make start and end date optional
10
- ## change pos to num - why? why not?
11
- ## make pos/num optional too
12
- ##
13
- ## sort round by scheduled/planed start date
14
- def initialize( title:,
15
- pos:,
7
+ def initialize( key: nil,
8
+ name:,
16
9
  teams: )
17
- @title = title
18
- @pos = pos
10
+ @key = key ## e.g. A,B,C or 1,2,3, - note: always a string or nil
11
+ @name = name
19
12
  @teams = teams
20
13
  end
21
14
  end # class Group
@@ -20,7 +20,8 @@ class Match
20
20
  :group,
21
21
  :conf1, :conf2, ## special case for mls e.g. conference1, conference2 (e.g. west, east, central)
22
22
  :country1, :country2, ## special case for champions league etc. - uses FIFA country code
23
- :comments
23
+ :comments,
24
+ :league ## (optinal) added as text for now (use struct?)
24
25
 
25
26
  def initialize( **kwargs )
26
27
  update( kwargs ) unless kwargs.empty?
@@ -47,6 +48,9 @@ class Match
47
48
  @group = kwargs[:group] if kwargs.has_key? :group
48
49
  @comments = kwargs[:comments] if kwargs.has_key? :comments
49
50
 
51
+ @league = kwargs[:league] if kwargs.has_key? :league
52
+
53
+
50
54
  if kwargs.has_key?( :score ) ## check all-in-one score struct for convenience!!!
51
55
  score = kwargs[:score]
52
56
  if score.nil? ## reset all score attribs to nil!!
@@ -2,24 +2,17 @@ module SportDb
2
2
  module Import
3
3
 
4
4
  class Round
5
- attr_reader :title, :start_date, :end_date, :knockout
6
- attr_accessor :pos # note: make read & writable
5
+ attr_reader :name, :start_date, :end_date, :knockout
6
+ attr_accessor :num # note: make read & writable - why? why not?
7
7
 
8
- ##
9
- ## todo: change db schema
10
- ## make start and end date optional
11
- ## change pos to num - why? why not?
12
- ## make pos/num optional too
13
- ##
14
- ## sort round by scheduled/planed start date
15
- def initialize( title:,
16
- pos: nil,
8
+ def initialize( name:,
9
+ num: nil,
17
10
  start_date: nil,
18
11
  end_date: nil,
19
12
  knockout: false,
20
13
  auto: true )
21
- @title = title
22
- @pos = pos
14
+ @name = name
15
+ @num = num
23
16
  @start_date = start_date
24
17
  @end_date = end_date
25
18
  @knockout = knockout
@@ -12,7 +12,7 @@ module SportDb
12
12
 
13
13
  class Standings
14
14
 
15
- class StandingsLine ## nested class StandinsLine
15
+ class StandingsLine ## nested class StandinsLine - todo/fix: change to Line - why? why not?
16
16
  attr_accessor :rank, :team,
17
17
  :played, :won, :lost, :drawn, ## -- total
18
18
  :goals_for, :goals_against, :pts,
@@ -21,8 +21,13 @@ class Standings
21
21
  :away_played, :away_won, :away_lost, :away_drawn, ## -- away
22
22
  :away_goals_for, :away_goals_against, :away_pts
23
23
 
24
+ alias_method :team_name, :team ## note: team for now always a string
25
+ alias_method :pos, :rank ## rename back to use pos instead of rank - why? why not?
26
+
27
+
24
28
  def initialize( team )
25
29
  @rank = nil # use 0? why? why not?
30
+ ## change rank back to pos - why? why not?
26
31
  @team = team
27
32
  @played = @home_played = @away_played = 0
28
33
  @won = @home_won = @away_won = 0
@@ -49,7 +54,12 @@ class Standings
49
54
 
50
55
  def update( match_or_matches )
51
56
  ## convenience - update all matches at once
52
- matches = match_or_matches.is_a?(Array) ? match_or_matches : [match_or_matches]
57
+ ## todo/check: check for ActiveRecord_Associations_CollectionProxy and than use to_a (to "force" array) - why? why not?
58
+ matches = if match_or_matches.is_a?(Array)
59
+ match_or_matches
60
+ else
61
+ [match_or_matches]
62
+ end
53
63
 
54
64
  matches.each_with_index do |match,i| # note: index(i) starts w/ zero (0)
55
65
  update_match( match )
@@ -171,19 +181,30 @@ class Standings
171
181
  private
172
182
  def update_match( m ) ## add a match
173
183
 
174
- ## puts " #{m.team1} - #{m.team2} #{m.score_str}"
184
+ ## note: always use team as string for now
185
+ ## for now allow passing in of string OR struct - why? why not?
186
+ ## todo/fix: change to m.team1_name and m.team2_name - why? why not?
187
+ team1 = m.team1.is_a?( String ) ? m.team1 : m.team1.name
188
+ team2 = m.team2.is_a?( String ) ? m.team2 : m.team2.name
189
+
190
+ score = m.score_str
191
+
192
+ ## puts " #{team1} - #{team2} #{score}"
193
+
175
194
  unless m.over?
176
- puts " !!!! skipping match - not yet over (play_at date in the future)"
195
+ puts " !!!! skipping match - not yet over (date in the future) => #{m.date}"
177
196
  return
178
197
  end
179
198
 
180
199
  unless m.complete?
181
- puts "!!! [calc_standings] skipping match #{m.team1} - #{m.team2} - scores incomplete #{m.score_str}"
200
+ puts "!!! [calc_standings] skipping match #{team1} - #{team2} w/ past date #{m.date} - scores incomplete => #{score}"
182
201
  return
183
202
  end
184
203
 
185
- line1 = @lines[ m.team1 ] || StandingsLine.new( m.team1 )
186
- line2 = @lines[ m.team2 ] || StandingsLine.new( m.team2 )
204
+
205
+
206
+ line1 = @lines[ team1 ] || StandingsLine.new( team1 )
207
+ line2 = @lines[ team2 ] || StandingsLine.new( team2 )
187
208
 
188
209
  line1.played += 1
189
210
  line1.home_played += 1
@@ -236,8 +257,8 @@ private
236
257
  puts "*** warn: [standings] skipping match with missing scores: #{m.inspect}"
237
258
  end
238
259
 
239
- @lines[ m.team1 ] = line1
240
- @lines[ m.team2 ] = line2
260
+ @lines[ team1 ] = line1
261
+ @lines[ team2 ] = line2
241
262
  end # method update_match
242
263
 
243
264
  end # class Standings
@@ -11,10 +11,9 @@ class Team
11
11
  ## todo: use just names for alt_names - why? why not?
12
12
  attr_accessor :key, :name, :alt_names,
13
13
  :code, ## code == abbreviation e.g. ARS etc.
14
- :year, :year_end, ## todo/fix: change year_end to end_year (like in season)!!!
14
+ :year, :year_end, ## todo/fix: change year to start_year and year_end to end_year (like in season)!!!
15
15
  :country
16
16
 
17
- alias_method :title, :name ## add alias/compat - why? why not
18
17
 
19
18
  def names
20
19
  ## todo/check: add alt_names_auto too? - why? why not?
@@ -6,8 +6,8 @@ module SportDb
6
6
  module Formats
7
7
 
8
8
  MAJOR = 1 ## todo: namespace inside version or something - why? why not??
9
- MINOR = 0
10
- PATCH = 3
9
+ MINOR = 1
10
+ PATCH = 1
11
11
  VERSION = [MAJOR,MINOR,PATCH].join('.')
12
12
 
13
13
  def self.version
@@ -1,5 +1,6 @@
1
1
  ## note: use the local version of gems
2
2
  $LOAD_PATH.unshift( File.expand_path( '../date-formats/lib' ))
3
+ $LOAD_PATH.unshift( File.expand_path( '../sportdb-langs/lib' ))
3
4
 
4
5
 
5
6
  ## minitest setup
@@ -16,12 +16,12 @@ class TestCountryIndex < MiniTest::Test
16
16
  eng = countries[:eng]
17
17
  assert_equal 'eng', eng.key
18
18
  assert_equal 'England', eng.name
19
- assert_equal 'ENG', eng.fifa
19
+ assert_equal 'ENG', eng.code
20
20
 
21
21
  at = countries[:at]
22
22
  assert_equal 'at', at.key
23
23
  assert_equal 'Austria', at.name
24
- assert_equal 'AUT', at.fifa
24
+ assert_equal 'AUT', at.code
25
25
  assert_equal ['Österreich [de]'], at.alt_names
26
26
 
27
27
  assert at == countries['AT']
@@ -48,13 +48,13 @@ class TestCountryIndex < MiniTest::Test
48
48
  assert at == countries.parse( 'Österreich • Austria' )
49
49
  assert at == countries.parse( 'Austria' )
50
50
  assert at == countries.parse( 'at' ) ## (iso alpha2) country code
51
- assert at == countries.parse( 'AUT' ) ## fifa code
51
+ assert at == countries.parse( 'AUT' ) ## (fifa) country code
52
52
 
53
53
 
54
54
  de = countries[:de]
55
55
  assert_equal 'de', de.key
56
56
  assert_equal 'Germany', de.name
57
- assert_equal 'GER', de.fifa
57
+ assert_equal 'GER', de.code
58
58
  assert_equal ['Deutschland [de]'], de.alt_names
59
59
 
60
60
  assert de == countries.parse( 'Deutschland (de) • Germany' )
@@ -16,12 +16,12 @@ class TestCountryReader < MiniTest::Test
16
16
  assert_equal 232, recs.size
17
17
 
18
18
  assert_equal 'Albania', recs[0].name
19
- assert_equal 'ALB', recs[0].fifa
19
+ assert_equal 'ALB', recs[0].code
20
20
  assert_equal 'al', recs[0].key
21
21
  assert_equal ['fifa', 'uefa'], recs[0].tags
22
22
 
23
23
  assert_equal 'Andorra', recs[1].name
24
- assert_equal 'AND', recs[1].fifa
24
+ assert_equal 'AND', recs[1].code
25
25
  assert_equal 'ad', recs[1].key
26
26
  assert_equal ['fifa', 'uefa'], recs[1].tags
27
27
  end
@@ -44,16 +44,46 @@ TXT
44
44
 
45
45
  assert_equal 4, recs.size
46
46
  assert_equal 'Afghanistan', recs[0].name
47
- assert_equal 'AFG', recs[0].fifa
47
+ assert_equal 'AFG', recs[0].code
48
48
  assert_equal 'af', recs[0].key
49
49
  assert_equal [], recs[0].alt_names
50
50
  assert_equal ['fifa', 'afc'], recs[0].tags
51
51
 
52
52
  assert_equal 'American Samoa', recs[3].name
53
- assert_equal 'ASA', recs[3].fifa
53
+ assert_equal 'ASA', recs[3].code
54
54
  assert_equal 'as', recs[3].key
55
55
  assert_equal ['Am. Samoa'], recs[3].alt_names
56
56
  assert_equal [], recs[3].tags
57
57
  end
58
58
 
59
+ def test_parse_historic
60
+ recs = SportDb::Import::CountryReader.parse( <<TXT )
61
+ ###########################################
62
+ # Former national teams
63
+ # with former FIFA country codes etc.
64
+ -1992 Czechoslovakia, TCH ⇒ Czech Republic
65
+
66
+ -1991 Soviet Union, URS ⇒ Russia
67
+
68
+ -1989 West Germany, FRG => Germany
69
+ -1989 East Germany, GDR => Germany
70
+ TXT
71
+
72
+ pp recs
73
+
74
+ assert_equal 4, recs.size
75
+ assert_equal 'Czechoslovakia (-1992)', recs[0].name
76
+ assert_equal 'TCH', recs[0].code
77
+ assert_equal 'czechoslovakia', recs[0].key
78
+ assert_equal [], recs[0].alt_names
79
+ assert_equal [], recs[0].tags
80
+
81
+ assert_equal 'East Germany (-1989)', recs[3].name
82
+ assert_equal 'GDR', recs[3].code
83
+ assert_equal 'eastgermany', recs[3].key
84
+ assert_equal [], recs[3].alt_names
85
+ assert_equal [], recs[3].tags
86
+ end
87
+
88
+
59
89
  end # class TestCountryReader