sportdb-formats 1.0.4 → 1.1.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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