sportdb-formats 1.0.3 → 1.1.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.
@@ -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