sportdb-formats 1.0.4 → 1.1.2

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.
@@ -13,12 +13,22 @@ module SportDb
13
13
  ## leagues.txt or leagues_en.txt
14
14
  ## remove support for en.leagues.txt - why? why not?
15
15
  LEAGUES_RE = %r{ (?: ^|/ ) # beginning (^) or beginning of path (/)
16
- (?: [a-z]{1,4}\. )? # optional country code/key e.g. eng.clubs.wiki.txt
16
+ (?: [a-z]{1,4}\. )? # optional country code/key e.g. eng.leagues.txt
17
17
  leagues
18
18
  (?:_[a-z0-9_-]+)?
19
19
  \.txt$
20
20
  }x
21
21
 
22
+ ## seasons.txt or seasons_en.txt
23
+ ## remove support for br.seasons.txt - why? why not?
24
+ SEASONS_RE = %r{ (?: ^|/ ) # beginning (^) or beginning of path (/)
25
+ (?: [a-z]{1,4}\. )? # optional country code/key e.g. eng.seasons.txt
26
+ seasons
27
+ (?:_[a-z0-9_-]+)?
28
+ \.txt$
29
+ }x
30
+
31
+
22
32
  ## clubs.txt or clubs_en.txt
23
33
  ## remove support for en.clubs.txt - why? why not?
24
34
  CLUBS_RE = %r{ (?: ^|/ ) # beginning (^) or beginning of path (/)
@@ -49,6 +59,8 @@ module SportDb
49
59
  \.txt$
50
60
  }x
51
61
 
62
+
63
+ ### todo/fix: change SEASON_RE to SEASON_KEY_RE (avoid confusion w/ SEASONS_RE for datafile?) - why? why not? !!!!!!!
52
64
  ### season folder:
53
65
  ## e.g. /2019-20 or
54
66
  ## year-only e.g. /2019 or
@@ -73,6 +85,10 @@ module SportDb
73
85
  /[a-z0-9_.-]+\.csv$ ## note: allow dot (.) too e.g /eng.1.csv
74
86
  }x
75
87
 
88
+ ### add "generic" pattern to find all csv datafiles
89
+ CSV_RE = %r{ (?: ^|/ )
90
+ [a-z0-9_.-]+\.csv$ ## note: allow dot (.) too e.g /eng.1.csv
91
+ }x
76
92
 
77
93
 
78
94
  ## move class-level "static" finders to DirPackage (do NOT work for now for zip packages) - why? why not?
@@ -106,6 +122,10 @@ module SportDb
106
122
  def self.find_leagues( path, pattern: LEAGUES_RE ) find( path, pattern ); end
107
123
  def self.match_leagues( path ) LEAGUES_RE.match( path ); end
108
124
 
125
+ def self.find_seasons( path, pattern: SEASONS_RE ) find( path, pattern ); end
126
+ def self.match_seasons( path ) SEASONS_RE.match( path ); end
127
+
128
+
109
129
  def self.find_conf( path, pattern: CONF_RE ) find( path, pattern ); end
110
130
  def self.match_conf( path ) CONF_RE.match( path ); end
111
131
 
@@ -118,6 +138,7 @@ module SportDb
118
138
  end
119
139
  ## add match_match and match_match_csv - why? why not?
120
140
 
141
+
121
142
  class << self
122
143
  alias_method :match_teams?, :match_teams
123
144
  alias_method :teams?, :match_teams
@@ -134,6 +155,9 @@ module SportDb
134
155
  alias_method :match_leagues?, :match_leagues
135
156
  alias_method :leagues?, :match_leagues
136
157
 
158
+ alias_method :match_seasons?, :match_seasons
159
+ alias_method :seasons?, :match_seasons
160
+
137
161
  alias_method :match_conf?, :match_conf
138
162
  alias_method :conf?, :match_conf
139
163
  end
@@ -212,6 +236,8 @@ module SportDb
212
236
  end
213
237
  end
214
238
  def each_match_csv( &blk ) each( pattern: MATCH_CSV_RE, &blk ); end
239
+ def each_csv( &blk ) each( pattern: CSV_RE, &blk ); end
240
+
215
241
  def each_club_props( &blk ) each( pattern: CLUB_PROPS_RE, &blk ); end
216
242
 
217
243
  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]
@@ -2,21 +2,10 @@
2
2
 
3
3
 
4
4
  module SeasonHelper ## use Helpers why? why not?
5
-
6
5
  ##############################################
7
6
  ### deprecated!!! use new Season class!!!
8
7
  ## this code will get removed!!!!
9
8
  ###################################################
10
-
11
- def prev( str ) SportDb::Import::Season.new( str ).prev; end
12
- def key( str ) SportDb::Import::Season.new( str ).key; end
13
- def directory( str, format: nil ) SportDb::Import::Season.new( str ).directory( format: format ); end
14
-
15
- ## note: new start_year now returns an integer number (no longer a string)!!!
16
- def start_year( str ) SportDb::Import::Season.new( str ).start_year; end
17
- ## note: new end_year now returns an integer number (no longer a string)!!!
18
- ## if now end_year (year? == true) than returns nil (no longer the start_year "as fallback")!!!
19
- def end_year( str ) SportDb::Import::Season.new( str ).end_year; end
20
9
  end # module SeasonHelper
21
10
 
22
11
 
@@ -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
@@ -1,26 +1,19 @@
1
1
  # encoding: utf-8
2
2
 
3
3
 
4
- module SportDb
5
- module Import
4
+ ### note: make Season like Date a "top-level" / "generic" class
5
+
6
6
 
7
7
 
8
8
  class Season
9
9
  ##
10
10
  ## todo: add (optional) start_date and end_date - why? why not?
11
- ## add next
12
-
13
-
14
- attr_reader :start_year,
15
- :end_year
16
11
 
17
- def year?() @end_year.nil?; end ## single-year season e.g. 2011 if no end_year present
18
-
19
-
20
- def initialize( str ) ## assume only string / line gets passed in for now
21
- @start_year, @end_year = parse( str )
22
- end
12
+ ## todo/todo/todo/check/check/check !!!
13
+ ## todo: add a kernel Seaons e.g. Season('2011/12')
14
+ ## forward to Season.convert( *args ) - why? why not?
23
15
 
16
+ ## todo: add unicode - too - why? why not? see wikipedia pages, for example
24
17
 
25
18
  YYYY_YYYY_RE = %r{^ ## e.g. 2011-2012 or 2011/2012
26
19
  (\d{4})
@@ -45,79 +38,155 @@ class Season
45
38
  $
46
39
  }x
47
40
 
48
- def parse( str )
41
+
42
+ def self.parse( str )
43
+ new( *_parse( str ))
44
+ end
45
+
46
+ def self._parse( str ) ## "internal" parse helper
49
47
  if str =~ YYYY_YYYY_RE ## e.g. 2011/2012
50
48
  [$1.to_i, $2.to_i]
51
49
  elsif str =~ YYYY_YY_RE ## e.g. 2011/12
52
50
  fst = $1.to_i
53
51
  snd = $2.to_i
54
52
  snd_exp = '%02d' % [(fst+1) % 100] ## double check: e.g 00 == 00, 01==01 etc.
55
- raise ArgumentError.new( "[Season#parse] invalid year in season >>#{str}<<; expected #{snd_exp} but got #{$2}") if snd_exp != $2
53
+ raise ArgumentError, "[Season.parse] invalid year in season >>#{str}<<; expected #{snd_exp} but got #{$2}" if snd_exp != $2
56
54
  [fst, fst+1]
57
55
  elsif str =~ YYYY_Y_RE ## e.g. 2011/2
58
56
  fst = $1.to_i
59
57
  snd = $2.to_i
60
58
  snd_exp = '%d' % [(fst+1) % 10] ## double check: e.g 0 == 0, 1==1 etc.
61
- raise ArgumentError.new( "[Season#parse] invalid year in season >>#{str}<<; expected #{snd_exp} but got #{$2}") if snd_exp != $2
59
+ raise ArgumentError, "[Season.parse] invalid year in season >>#{str}<<; expected #{snd_exp} but got #{$2}" if snd_exp != $2
62
60
  [fst, fst+1]
63
61
  elsif str =~ YYYY_RE ## e.g. 2011
64
62
  [$1.to_i]
65
63
  else
66
- raise ArgumentError.new( "[Season#parse] unkown season format >>#{str}<<; sorry cannot parse")
64
+ raise ArgumentError, "[Season.parse] unkown season format >>#{str}<<; sorry cannot parse"
65
+ end
66
+ end
67
+
68
+
69
+
70
+ attr_reader :start_year,
71
+ :end_year
72
+
73
+ def initialize( *args ) ## assume only string / line gets passed in for now
74
+ if args.size == 1 && args[0].is_a?( String )
75
+ @start_year, @end_year = self.class._parse( args[0] )
76
+ elsif args.size == 1 && args[0].is_a?( Integer )
77
+ @start_year = args[0]
78
+ @end_year = nil
79
+ elsif args.size == 2 && args[0].is_a?( Integer ) &&
80
+ args[1].is_a?( Integer )
81
+ @start_year = args[0]
82
+ @end_year = args[1]
83
+ end_year_exp = @start_year+1
84
+ raise ArgumentError, "[Season] invalid year in season >>#{to_s}<<; expected #{end_year_exp} but got #{@end_year}" if end_year_exp != @end_year
85
+ else
86
+ pp args
87
+ raise ArgumentError, "[Season] expected season string or season start year (integer) with opt. end year"
88
+ end
89
+ end
90
+
91
+
92
+ ###
93
+ ## convenience helper
94
+ def start_date ## generate "generic / syntetic start date" - keep helper - why? why not?
95
+ if year?
96
+ Date.new( start_year, 1, 1 )
97
+ else
98
+ Date.new( start_year 1, 7 )
67
99
  end
68
100
  end
69
101
 
70
102
 
103
+ ## single-year season e.g. 2011 if no end_year present - todo - find a better name?
104
+ def year?() @end_year.nil?; end
71
105
 
72
106
  def prev
73
107
  if year?
74
- Season.new( "#{@start_year-1}" )
108
+ Season.new( @start_year-1 )
75
109
  else
76
- Season.new( "#{@start_year-1}/#{@start_year}" )
110
+ Season.new( @start_year-1, @start_year )
77
111
  end
78
112
  end
79
113
 
80
- def key
114
+ def next
81
115
  if year?
82
- '%d' % @start_year
116
+ Season.new( @start_year+1 )
83
117
  else
84
- '%d/%02d' % [@start_year, @end_year % 100]
118
+ Season.new( @end_year, @end_year+1 )
85
119
  end
86
120
  end
87
- alias_method :to_key, :key
88
- alias_method :to_s, :key
121
+ alias_method :succ, :next ## add support for ranges
122
+
123
+ include Comparable
124
+ def <=>(other)
125
+ ## todo/fix/fix: check if other is_a?( Season )!!!
126
+ ## what to return if other type/class ??
89
127
 
90
- alias_method :name, :key
91
- alias_method :title, :key
128
+ res = @start_year <=> other.start_year
92
129
 
130
+ ## check special edge case - year season and other e.g.
131
+ ## 2010 <=> 2010/2011
132
+ if res == 0 && @end_year != other.end_year
133
+ res = @end_year ? 1 : -1 # the season with an end year is greater / wins for now
134
+ end
93
135
 
94
- def path( format: nil )
95
- ## todo: find better names for formats - why? why not?:
96
- ## long | archive | decade(?) => 1980s/1988-89, 2010s/2017-18, ...
97
- ## short | std(?) => 1988-89, 2017-18, ...
136
+ res
137
+ end
98
138
 
99
- ## convert season name to "standard" season name for directory
100
139
 
101
- if ['l', 'long', 'archive' ].include?( format.to_s ) ## note: allow passing in of symbol to e.g. 'long' or :long
102
- if year? ## e.g. 2000s/2001
103
- "%3d0s/%4d" % [@start_year / 10, @start_year]
104
- else ## e.g. 2000s/2001-02
105
- "%3d0s/%4d-%02d" % [@start_year / 10, @start_year, @end_year % 100]
106
- end
107
- else ## default 'short' format / fallback
108
- if year? ## e.g. 2001
109
- "%4d" % @start_year
110
- else ## e.g. 2001-02
111
- "%4d-%02d" % [@start_year, @end_year % 100]
140
+
141
+ def to_formatted_s( format=:default, sep: '/' )
142
+ if year?
143
+ '%d' % @start_year
144
+ else
145
+ case format
146
+ when :default, :short, :s ## e.g. 1999/00 or 2019/20
147
+ "%d#{sep}%02d" % [@start_year, @end_year % 100]
148
+ when :long, :l ## e.g. 1999/2000 or 2019/2020
149
+ "%d#{sep}%d" % [@start_year, @end_year]
150
+ else
151
+ raise ArgumentError, "[Season.to_s] unsupported format >#{format}<"
112
152
  end
113
153
  end
114
- end # method path
115
- alias_method :directory, :path ## keep "legacy" directory alias - why? why not?
116
- alias_method :to_path, :path
154
+ end
155
+ alias_method :to_s, :to_formatted_s
156
+
157
+ def key() to_s( :short ); end
158
+ alias_method :to_key, :key
159
+ alias_method :name, :key
160
+ alias_method :title, :key
117
161
 
162
+ alias_method :inspect, :key ## note: add inspect debug support change debug output to string!!
163
+
164
+
165
+
166
+ def to_path( format=:default )
167
+ case format
168
+ when :default, :short, :s ## e.g. 1999-00 or 2019-20
169
+ to_s( :short, sep: '-' )
170
+ when :long, :l ## e.g. 1999-2000 or 2019-2000
171
+ to_s( :long, sep: '-' )
172
+ when :archive, :decade, :d ## e.g. 1990s/1999-00 or 2010s/2019-20
173
+ "%3d0s/%s" % [@start_year / 10, to_s( :short, sep: '-' )]
174
+ when :century, :c ## e.g. 1900s/1990-00 or 2000s/2019-20
175
+ "%2d00s/%s" % [@start_year / 100, to_s( :short, sep: '-' )]
176
+ else
177
+ raise ArgumentError, "[Season.to_path] unsupported format >#{format}<"
178
+ end
179
+ end # method to_path
180
+ alias_method :directory, :to_path ## keep "legacy" directory alias - why? why not?
181
+ alias_method :path, :to_path
118
182
 
119
183
  end # class Season
120
184
 
121
185
 
186
+
187
+
188
+ module SportDb
189
+ module Import
190
+ Season = ::Season ## add a convenience alias
122
191
  end # module Import
123
192
  end # module SportDb
@@ -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