sportdb-formats 1.1.1 → 1.1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Manifest.txt +3 -0
- data/lib/sportdb/formats.rb +14 -0
- data/lib/sportdb/formats/event/event_index.rb +143 -0
- data/lib/sportdb/formats/event/event_reader.rb +183 -0
- data/lib/sportdb/formats/match/match_parser.rb +20 -3
- data/lib/sportdb/formats/match/match_parser_csv.rb +4 -1
- data/lib/sportdb/formats/package.rb +20 -1
- data/lib/sportdb/formats/season_utils.rb +0 -11
- data/lib/sportdb/formats/structs/season.rb +114 -45
- data/lib/sportdb/formats/version.rb +1 -1
- data/test/test_match_start_date.rb +44 -0
- data/test/test_season.rb +68 -19
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 89c3ae173bed0c7a68f30a897e4cf34da8d72d6e
|
4
|
+
data.tar.gz: f3898e8b03177b62075cced2a90be2502555bf63
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4c938af88f6dd86f1736532c9d8271a3e56f1769b955a31ce6d84a0b1fcf5a6eec7cfa42e0c019bd7072351d777a1988d6637c4297bee0dad9592bb8ec7450e8
|
7
|
+
data.tar.gz: a80df68f64e13eeb9a85931e2d2dd4381f204f94a4ea9254c89b4b28346db4c1542321aed4fd123aef5ddedc7e4cd2c08731dd340326fae5dc94b57fa6797835
|
data/Manifest.txt
CHANGED
@@ -8,6 +8,8 @@ lib/sportdb/formats/country/country_index.rb
|
|
8
8
|
lib/sportdb/formats/country/country_reader.rb
|
9
9
|
lib/sportdb/formats/datafile.rb
|
10
10
|
lib/sportdb/formats/datafile_package.rb
|
11
|
+
lib/sportdb/formats/event/event_index.rb
|
12
|
+
lib/sportdb/formats/event/event_reader.rb
|
11
13
|
lib/sportdb/formats/goals.rb
|
12
14
|
lib/sportdb/formats/league/league_index.rb
|
13
15
|
lib/sportdb/formats/league/league_outline_reader.rb
|
@@ -68,6 +70,7 @@ test/test_match_auto_worldcup.rb
|
|
68
70
|
test/test_match_champs.rb
|
69
71
|
test/test_match_eng.rb
|
70
72
|
test/test_match_euro.rb
|
73
|
+
test/test_match_start_date.rb
|
71
74
|
test/test_match_worldcup.rb
|
72
75
|
test/test_name_helper.rb
|
73
76
|
test/test_outline_reader.rb
|
data/lib/sportdb/formats.rb
CHANGED
@@ -136,6 +136,20 @@ end # module Import
|
|
136
136
|
end # module SportDb
|
137
137
|
|
138
138
|
|
139
|
+
require 'sportdb/formats/event/event_reader'
|
140
|
+
require 'sportdb/formats/event/event_index'
|
141
|
+
|
142
|
+
## add convenience helper
|
143
|
+
module SportDb
|
144
|
+
module Import
|
145
|
+
class EventInfo
|
146
|
+
def self.read( path ) EventInfoReader.read( path ); end
|
147
|
+
def self.parse( txt ) EventInfoReader.parse( txt ); end
|
148
|
+
end # class EventInfo
|
149
|
+
end # module Import
|
150
|
+
end # module SportDb
|
151
|
+
|
152
|
+
|
139
153
|
|
140
154
|
|
141
155
|
|
@@ -0,0 +1,143 @@
|
|
1
|
+
module SportDb
|
2
|
+
module Import
|
3
|
+
|
4
|
+
|
5
|
+
class EventIndex
|
6
|
+
|
7
|
+
def self.build( path )
|
8
|
+
datafiles = Package.find_seasons( path )
|
9
|
+
|
10
|
+
puts
|
11
|
+
puts "#{datafiles.size} seasons datafile(s):"
|
12
|
+
pp datafiles
|
13
|
+
|
14
|
+
index = new
|
15
|
+
datafiles.each do |datafile|
|
16
|
+
recs = EventInfoReader.read( datafile )
|
17
|
+
# pp recs
|
18
|
+
|
19
|
+
index.add( recs )
|
20
|
+
end
|
21
|
+
|
22
|
+
index
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
attr_reader :events
|
27
|
+
def initialize
|
28
|
+
@events = []
|
29
|
+
@leagues = {}
|
30
|
+
end
|
31
|
+
|
32
|
+
def add( recs )
|
33
|
+
@events += recs ## add to "linear" records
|
34
|
+
|
35
|
+
recs.each do |rec|
|
36
|
+
league = rec.league
|
37
|
+
season = rec.season
|
38
|
+
|
39
|
+
seasons = @leagues[ league.key ] ||= {}
|
40
|
+
seasons[season.key] = rec
|
41
|
+
end
|
42
|
+
## build search index by leagues (and season)
|
43
|
+
end
|
44
|
+
|
45
|
+
def find_by( league:, season: )
|
46
|
+
league_key = league.is_a?( String ) ? league : league.key
|
47
|
+
season_key = season.is_a?( String ) ? season : season.key
|
48
|
+
|
49
|
+
seasons = @leagues[ league_key ]
|
50
|
+
if seasons
|
51
|
+
seasons[ season_key ]
|
52
|
+
else
|
53
|
+
nil
|
54
|
+
end
|
55
|
+
end # method find_by
|
56
|
+
end ## class EventIndex
|
57
|
+
|
58
|
+
|
59
|
+
|
60
|
+
class SeasonIndex
|
61
|
+
def initialize( *args )
|
62
|
+
@leagues = {} ## use a league hash by years for now; change later
|
63
|
+
|
64
|
+
if args.size == 1 && args[0].is_a?( EventIndex )
|
65
|
+
## convenience setup/hookup
|
66
|
+
## (auto-)add all events from event index
|
67
|
+
add( args[0].events )
|
68
|
+
else
|
69
|
+
pp args
|
70
|
+
raise ArgumentError.new( 'unsupported arguments' )
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def add( recs )
|
75
|
+
## use a lookup index by year for now
|
76
|
+
## todo - find something better/more generic for searching/matching date periods!!!
|
77
|
+
recs.each do |rec|
|
78
|
+
league = rec.league
|
79
|
+
season = rec.season
|
80
|
+
|
81
|
+
years = @leagues[ league.key ] ||= {}
|
82
|
+
if season.year?
|
83
|
+
years[season.start_year] ||= []
|
84
|
+
years[season.start_year] << rec
|
85
|
+
else
|
86
|
+
years[season.start_year] ||= []
|
87
|
+
years[season.end_year] ||= []
|
88
|
+
years[season.start_year] << rec
|
89
|
+
years[season.end_year] << rec
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end # method add
|
93
|
+
|
94
|
+
def find_by( date:, league: )
|
95
|
+
date = Date.strptime( date, '%Y-%m-%d' ) if date.is_a?( String )
|
96
|
+
league_key = league.is_a?( String ) ? league : league.key
|
97
|
+
|
98
|
+
years = @leagues[ league_key ]
|
99
|
+
if years
|
100
|
+
year = years[ date.year ]
|
101
|
+
if year
|
102
|
+
season_key = nil
|
103
|
+
year.each do |event|
|
104
|
+
## todo/check: rename/use between? instead of include? - why? why not?
|
105
|
+
if event.include?( date )
|
106
|
+
season_key = event.season.key
|
107
|
+
break
|
108
|
+
end
|
109
|
+
end
|
110
|
+
if season_key.nil?
|
111
|
+
puts "!! WARN: date >#{date}< out-of-seasons for year #{date.year} in league #{league_key}:"
|
112
|
+
year.each do |event|
|
113
|
+
puts " #{event.season.key} | #{event.start_date} - #{event.end_date}"
|
114
|
+
end
|
115
|
+
## retry again and pick season with "overflow" at the end (date is great end_date)
|
116
|
+
year.each do |event|
|
117
|
+
if date > event.end_date
|
118
|
+
diff_in_days = date.to_date.jd - event.end_date.to_date.jd
|
119
|
+
puts " +#{diff_in_days} days - adding overflow to #{event.season.key} ending on #{event.end_date} ++ #{date}"
|
120
|
+
season_key = event.season.key
|
121
|
+
break
|
122
|
+
end
|
123
|
+
end
|
124
|
+
## exit now for sure - if still empty!!!!
|
125
|
+
if season_key.nil?
|
126
|
+
puts "!! ERROR: CANNOT auto-fix / (auto-)append date at the end of an event; check season setup - sorry"
|
127
|
+
exit 1
|
128
|
+
end
|
129
|
+
end
|
130
|
+
season_key
|
131
|
+
else
|
132
|
+
nil ## no year defined / found for league
|
133
|
+
end
|
134
|
+
else
|
135
|
+
nil ## no league defined / found
|
136
|
+
end
|
137
|
+
end # method find
|
138
|
+
|
139
|
+
end # class SeasonIndex
|
140
|
+
|
141
|
+
|
142
|
+
end # module Import
|
143
|
+
end # module SportDb
|
@@ -0,0 +1,183 @@
|
|
1
|
+
|
2
|
+
module SportDb
|
3
|
+
module Import
|
4
|
+
|
5
|
+
|
6
|
+
class EventInfo
|
7
|
+
## "high level" info (summary) about event (like a "wikipedia infobox")
|
8
|
+
## use for checking dataset imports; lets you check e.g.
|
9
|
+
## - dates within range
|
10
|
+
## - number of teams e.g. 20
|
11
|
+
## - matches played e.g. 380
|
12
|
+
## - goals scored e.g. 937
|
13
|
+
## etc.
|
14
|
+
|
15
|
+
attr_reader :league,
|
16
|
+
:season,
|
17
|
+
:teams,
|
18
|
+
:matches,
|
19
|
+
:goals,
|
20
|
+
:start_date,
|
21
|
+
:end_date
|
22
|
+
|
23
|
+
def initialize( league:, season:,
|
24
|
+
start_date: nil, end_date: nil,
|
25
|
+
teams: nil,
|
26
|
+
matches: nil,
|
27
|
+
goals: nil )
|
28
|
+
|
29
|
+
@league = league
|
30
|
+
@season = season
|
31
|
+
|
32
|
+
@start_date = start_date
|
33
|
+
@end_date = end_date
|
34
|
+
|
35
|
+
@teams = teams ## todo/check: rename/use teams_count ??
|
36
|
+
@matches = matches ## todo/check: rename/use match_count ??
|
37
|
+
@goals = goals
|
38
|
+
end
|
39
|
+
|
40
|
+
def include?( date )
|
41
|
+
## todo/fix: add options e.g.
|
42
|
+
## - add delta/off_by_one or such?
|
43
|
+
## - add strict (for) only return true if date range (really) defined (no generic auto-rules)
|
44
|
+
|
45
|
+
### note: for now allow off by one error (via timezone/local time errors)
|
46
|
+
## todo/fix: issue warning if off by one!!!!
|
47
|
+
if @start_date && @end_date
|
48
|
+
date >= (@start_date-1) &&
|
49
|
+
date <= (@end_date+1)
|
50
|
+
else
|
51
|
+
if @season.year?
|
52
|
+
# assume generic rule
|
53
|
+
## same year e.g. Jan 1 - Dec 31; always true for now
|
54
|
+
date.year == @season.start_year
|
55
|
+
else
|
56
|
+
# assume generic rule
|
57
|
+
## July 1 - June 30 (Y+1)
|
58
|
+
## - todo/check -start for some countries/leagues in June 1 or August 1 ????
|
59
|
+
date >= Date.new( @season.start_year, 7, 1 ) &&
|
60
|
+
date <= Date.new( @season.end_year, 6, 30 )
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end # method include?
|
64
|
+
alias_method :between?, :include?
|
65
|
+
end # class EventInfo
|
66
|
+
|
67
|
+
|
68
|
+
class EventInfoReader
|
69
|
+
def catalog() Import.catalog; end
|
70
|
+
|
71
|
+
|
72
|
+
def self.read( path )
|
73
|
+
txt = File.open( path, 'r:utf-8') {|f| f.read }
|
74
|
+
new( txt ).parse
|
75
|
+
end
|
76
|
+
|
77
|
+
def self.parse( txt )
|
78
|
+
new( txt ).parse
|
79
|
+
end
|
80
|
+
|
81
|
+
def initialize( txt )
|
82
|
+
@txt = txt
|
83
|
+
end
|
84
|
+
|
85
|
+
def parse
|
86
|
+
recs = []
|
87
|
+
|
88
|
+
parse_csv( @txt ).each do |row|
|
89
|
+
league_col = row['League']
|
90
|
+
season_col = row['Season'] || row['Year']
|
91
|
+
dates_col = row['Dates']
|
92
|
+
|
93
|
+
season = Import::Season.new( season_col )
|
94
|
+
league = catalog.leagues.find!( league_col )
|
95
|
+
|
96
|
+
|
97
|
+
dates = []
|
98
|
+
if dates_col.nil? || dates_col.empty?
|
99
|
+
## do nothing; no dates - keep dates array empty
|
100
|
+
else
|
101
|
+
## squish spaces
|
102
|
+
dates_col = dates_col.gsub( /[ ]{2,}/, ' ' ) ## squish/fold spaces
|
103
|
+
|
104
|
+
puts "#{league.name} (#{league.key}) | #{season.key} | #{dates_col}"
|
105
|
+
|
106
|
+
### todo/check: check what parts "Aug 15" return ???
|
107
|
+
### short form for "Aug 15 -" - works?
|
108
|
+
|
109
|
+
## todo/fix!!! - check EventInfo.include?
|
110
|
+
## now allow dates with only start_date too!! (WITHOUT end_date)
|
111
|
+
parts = dates_col.split( /[ ]*[–-][ ]*/ )
|
112
|
+
if parts.size == 1
|
113
|
+
pp parts
|
114
|
+
dates << DateFormats.parse( parts[0], start: Date.new( season.start_year, 1, 1 ), lang: 'en' )
|
115
|
+
pp dates
|
116
|
+
elsif parts.size == 2
|
117
|
+
pp parts
|
118
|
+
dates << DateFormats.parse( parts[0], start: Date.new( season.start_year, 1, 1 ), lang: 'en' )
|
119
|
+
dates << DateFormats.parse( parts[1], start: Date.new( season.end_year ? season.end_year : season.start_year, 1, 1 ), lang: 'en' )
|
120
|
+
pp dates
|
121
|
+
|
122
|
+
## assert/check if period is less than 365 days for now
|
123
|
+
diff = dates[1].to_date.jd - dates[0].to_date.jd
|
124
|
+
puts "#{diff}d"
|
125
|
+
if diff > 365
|
126
|
+
puts "!! ERROR - date range / period assertion failed; expected diff < 365 days"
|
127
|
+
exit 1
|
128
|
+
end
|
129
|
+
else
|
130
|
+
puts "!! ERRROR - expected data range / period - one or two dates; got #{parts.size}:"
|
131
|
+
pp dates_col
|
132
|
+
pp parts
|
133
|
+
exit 1
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
|
138
|
+
teams_col = row['Clubs'] || row['Teams']
|
139
|
+
goals_col = row['Goals']
|
140
|
+
|
141
|
+
## note: remove (and allow) all non-digits e.g. 370 goals, 20 clubs, etc.
|
142
|
+
teams_col = teams_col.gsub( /[^0-9]/, '' ) if teams_col
|
143
|
+
goals_col = goals_col.gsub( /[^0-9]/, '' ) if goals_col
|
144
|
+
|
145
|
+
teams = (teams_col.nil? || teams_col.empty?) ? nil : teams_col.to_i
|
146
|
+
goals = (goals_col.nil? || goals_col.empty?) ? nil : goals_col.to_i
|
147
|
+
|
148
|
+
matches_col = row['Matches']
|
149
|
+
## note: support additions in matches (played) e.g.
|
150
|
+
# 132 + 63 Play-off-Spiele
|
151
|
+
matches_col = matches_col.gsub( /[^0-9+]/, '' ) if matches_col
|
152
|
+
|
153
|
+
matches = if matches_col.nil? || matches_col.empty?
|
154
|
+
nil
|
155
|
+
else
|
156
|
+
if matches_col.index( '+' ) ### check for calculations
|
157
|
+
## note: for now only supports additions
|
158
|
+
matches_col.split( '+' ).reduce( 0 ) do |sum,str|
|
159
|
+
sum + str.to_i
|
160
|
+
end
|
161
|
+
else ## assume single (integer) number
|
162
|
+
matches_col.to_i
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
rec = EventInfo.new( league: league,
|
167
|
+
season: season,
|
168
|
+
start_date: dates[0],
|
169
|
+
end_date: dates[1],
|
170
|
+
teams: teams,
|
171
|
+
matches: matches,
|
172
|
+
goals: goals
|
173
|
+
)
|
174
|
+
recs << rec
|
175
|
+
end # each row
|
176
|
+
recs
|
177
|
+
end # method parse
|
178
|
+
end # class EventInfoReader
|
179
|
+
|
180
|
+
|
181
|
+
end ## module Import
|
182
|
+
end ## module SportDb
|
183
|
+
|
@@ -563,12 +563,29 @@ class MatchParser ## simple match parser for team match schedules
|
|
563
563
|
|
564
564
|
if date && team1.nil? && team2.nil?
|
565
565
|
logger.debug( "date header line found: >#{line}<")
|
566
|
-
logger.debug( " date: #{date}")
|
566
|
+
logger.debug( " date: #{date} with start: #{@start}")
|
567
567
|
|
568
568
|
@last_date = date # keep a reference for later use
|
569
|
-
|
569
|
+
|
570
|
+
### quick "corona" hack - support seasons going beyond 12 month (see swiss league 2019/20 and others!!)
|
571
|
+
## find a better way??
|
572
|
+
## set @start date to full year (e.g. 1.1.) if date.year is @start.year+1
|
573
|
+
## todo/fix: add to linter to check for chronological dates!! - warn if NOT chronological
|
574
|
+
### todo/check: just turn on for 2019/20 season or always? why? why not?
|
575
|
+
|
576
|
+
## todo/fix: add switch back to old @start_org
|
577
|
+
## if year is date.year == @start.year-1 -- possible when full date with year set!!!
|
578
|
+
if @start.month != 1
|
579
|
+
if date.year == @start.year+1
|
580
|
+
logger.debug( "!! hack - extending start date to full (next/end) year; assumes all dates are chronologigal - always moving forward" )
|
581
|
+
@start_org = @start ## keep a copy of the original (old) start date - why? why not? - not used for now
|
582
|
+
@start = Date.new( @start.year+1, 1, 1 )
|
583
|
+
end
|
584
|
+
end
|
585
|
+
|
586
|
+
true
|
570
587
|
else
|
571
|
-
|
588
|
+
false
|
572
589
|
end
|
573
590
|
end
|
574
591
|
|
@@ -95,7 +95,7 @@ module SportDb
|
|
95
95
|
headers_mapping[:score] = find_header( headers, ['FT'] )
|
96
96
|
headers_mapping[:scorei] = find_header( headers, ['HT'] )
|
97
97
|
|
98
|
-
headers_mapping[:round] = find_header( headers, ['Round'] )
|
98
|
+
headers_mapping[:round] = find_header( headers, ['Round', 'Matchday'] )
|
99
99
|
|
100
100
|
## optional headers - note: find_header returns nil if header NOT found
|
101
101
|
header_stage = find_header( headers, ['Stage'] )
|
@@ -221,6 +221,9 @@ module SportDb
|
|
221
221
|
end
|
222
222
|
|
223
223
|
|
224
|
+
##
|
225
|
+
## todo/fix: round might not always be just a simple integer number!!!
|
226
|
+
## might be text such as Final | Leg 1 or such!!!!
|
224
227
|
round = nil
|
225
228
|
## check for (optional) round / matchday
|
226
229
|
if headers_mapping[ :round ]
|
@@ -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.
|
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
|
@@ -110,6 +122,10 @@ module SportDb
|
|
110
122
|
def self.find_leagues( path, pattern: LEAGUES_RE ) find( path, pattern ); end
|
111
123
|
def self.match_leagues( path ) LEAGUES_RE.match( path ); end
|
112
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
|
+
|
113
129
|
def self.find_conf( path, pattern: CONF_RE ) find( path, pattern ); end
|
114
130
|
def self.match_conf( path ) CONF_RE.match( path ); end
|
115
131
|
|
@@ -139,6 +155,9 @@ module SportDb
|
|
139
155
|
alias_method :match_leagues?, :match_leagues
|
140
156
|
alias_method :leagues?, :match_leagues
|
141
157
|
|
158
|
+
alias_method :match_seasons?, :match_seasons
|
159
|
+
alias_method :seasons?, :match_seasons
|
160
|
+
|
142
161
|
alias_method :match_conf?, :match_conf
|
143
162
|
alias_method :conf?, :match_conf
|
144
163
|
end
|
@@ -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
|
|
@@ -1,26 +1,19 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
3
|
|
4
|
-
|
5
|
-
|
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
|
-
|
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
|
-
|
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
|
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
|
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
|
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(
|
108
|
+
Season.new( @start_year-1 )
|
75
109
|
else
|
76
|
-
Season.new(
|
110
|
+
Season.new( @start_year-1, @start_year )
|
77
111
|
end
|
78
112
|
end
|
79
113
|
|
80
|
-
def
|
114
|
+
def next
|
81
115
|
if year?
|
82
|
-
|
116
|
+
Season.new( @start_year+1 )
|
83
117
|
else
|
84
|
-
|
118
|
+
Season.new( @end_year, @end_year+1 )
|
85
119
|
end
|
86
120
|
end
|
87
|
-
alias_method :
|
88
|
-
|
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
|
-
|
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
|
-
|
95
|
-
|
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
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
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
|
115
|
-
alias_method :
|
116
|
-
|
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
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
###
|
4
|
+
# to run use
|
5
|
+
# ruby -I ./lib -I ./test test/test_match_start_date.rb
|
6
|
+
|
7
|
+
|
8
|
+
require 'helper'
|
9
|
+
|
10
|
+
|
11
|
+
class TestMatchStart < MiniTest::Test
|
12
|
+
|
13
|
+
def test_eng
|
14
|
+
txt =<<TXT
|
15
|
+
Matchday 1
|
16
|
+
[Aug 2]
|
17
|
+
A - B
|
18
|
+
Matchday 2
|
19
|
+
[Jan 3]
|
20
|
+
A - B
|
21
|
+
Matchday 3
|
22
|
+
[Aug 4]
|
23
|
+
A - B
|
24
|
+
TXT
|
25
|
+
|
26
|
+
teams =<<TXT
|
27
|
+
A
|
28
|
+
B
|
29
|
+
TXT
|
30
|
+
SportDb::Import.config.lang = 'en'
|
31
|
+
|
32
|
+
start = Date.new( 2017, 7, 1 )
|
33
|
+
|
34
|
+
parser = SportDb::MatchParser.new( txt, teams, start )
|
35
|
+
matches, rounds = parser.parse
|
36
|
+
|
37
|
+
pp rounds
|
38
|
+
pp matches ## only dump last record for now
|
39
|
+
|
40
|
+
assert_equal Date.new( 2017, 8, 2), matches[0].date
|
41
|
+
assert_equal Date.new( 2018, 1, 3), matches[1].date
|
42
|
+
assert_equal Date.new( 2018, 8, 4), matches[2].date
|
43
|
+
end # method test_end
|
44
|
+
end # class TestMatchStart
|
data/test/test_season.rb
CHANGED
@@ -9,25 +9,35 @@ require 'helper'
|
|
9
9
|
|
10
10
|
class TestSeason < MiniTest::Test
|
11
11
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
assert_equal '2010-11', Season.new( '2010-
|
16
|
-
assert_equal '2010-11', Season.new( '2010
|
17
|
-
assert_equal '2010-11', Season.new( '2010/
|
18
|
-
assert_equal '2010-11', Season.new( '2010/
|
19
|
-
assert_equal '2010
|
20
|
-
|
21
|
-
|
22
|
-
assert_equal '
|
23
|
-
|
24
|
-
assert_equal '2010s/2010',
|
25
|
-
|
26
|
-
assert_equal '
|
27
|
-
|
28
|
-
assert_equal '
|
29
|
-
assert_equal '
|
30
|
-
|
12
|
+
|
13
|
+
def test_to_path
|
14
|
+
assert_equal '2010-11', Season.new( '2010-11' ).to_path
|
15
|
+
assert_equal '2010-11', Season.new( '2010-2011' ).to_path
|
16
|
+
assert_equal '2010-11', Season.new( '2010/11' ).to_path
|
17
|
+
assert_equal '2010-11', Season.new( '2010/1' ).to_path
|
18
|
+
assert_equal '2010-11', Season.new( '2010/2011' ).to_path
|
19
|
+
assert_equal '2010', Season.new( '2010' ).to_path
|
20
|
+
|
21
|
+
assert_equal '2010-11', Season.new( 2010, 2011 ).to_path
|
22
|
+
assert_equal '2010', Season.new( 2010 ).to_path
|
23
|
+
|
24
|
+
assert_equal '2010s/2010-11', Season.new( '2010-11' ).to_path( :decade )
|
25
|
+
assert_equal '2010s/2010-11', Season.new( '2010-2011' ).to_path( :decade )
|
26
|
+
assert_equal '2010s/2010', Season.new( '2010' ).to_path( :decade )
|
27
|
+
|
28
|
+
assert_equal '1999-00', Season.new( '1999-00' ).to_path
|
29
|
+
assert_equal '1999-00', Season.new( '1999-2000' ).to_path
|
30
|
+
assert_equal '1990s/1999-00', Season.new( '1999-00' ).to_path( :decade )
|
31
|
+
assert_equal '1990s/1999-00', Season.new( '1999-2000' ).to_path( :decade )
|
32
|
+
|
33
|
+
assert_equal '2000s/2010-11', Season.new( '2010-11' ).to_path( :century )
|
34
|
+
assert_equal '2000s/2010-11', Season.new( '2010-2011' ).to_path( :century )
|
35
|
+
assert_equal '2000s/2010', Season.new( '2010' ).to_path( :century )
|
36
|
+
|
37
|
+
assert_equal '1900s/1999-00', Season.new( '1999-00' ).to_path( :century )
|
38
|
+
assert_equal '1900s/1999-00', Season.new( '1999-2000' ).to_path( :century )
|
39
|
+
end # method test_to_path
|
40
|
+
|
31
41
|
|
32
42
|
def test_key
|
33
43
|
assert_equal '2010/11', Season.new( '2010-11' ).key
|
@@ -59,4 +69,43 @@ class TestSeason < MiniTest::Test
|
|
59
69
|
assert_equal '1998/99', Season.new( '1999-00' ).prev.key
|
60
70
|
assert_equal '1998/99', Season.new( '1999-2000' ).prev.key
|
61
71
|
end
|
72
|
+
|
73
|
+
def test_next
|
74
|
+
assert_equal '2009/10', Season.new( '2008-09' ).next.key
|
75
|
+
assert_equal '2009/10', Season.new( '2008-2009' ).next.key
|
76
|
+
assert_equal '2009', Season.new( '2008' ).next.key
|
77
|
+
|
78
|
+
assert_equal '1998/99', Season.new( '1997-98' ).next.key
|
79
|
+
assert_equal '1998/99', Season.new( '1997-1998' ).next.key
|
80
|
+
end
|
81
|
+
|
82
|
+
|
83
|
+
def test_range
|
84
|
+
s2010 = Season.new( '2010' )..Season.new( '2019' )
|
85
|
+
pp s2010.to_a
|
86
|
+
# => [2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019]
|
87
|
+
|
88
|
+
s2010 = Season.new( '2010-11')..Season.new( '2019-20')
|
89
|
+
pp s2010.to_a
|
90
|
+
# => [2010/11, 2011/12, 2012/13, 2013/14, 2014/15,
|
91
|
+
# 2015/16, 2016/17, 2017/18, 2018/19, 2019/20]
|
92
|
+
|
93
|
+
puts s2010 === Season.new( '2015-16' ) # true
|
94
|
+
puts s2010 === Season.new( '2015' ) # false - why? if using >= <=
|
95
|
+
puts s2010 === Season.new( '1999-00' ) # false
|
96
|
+
puts s2010 === Season.new( '2020-21' ) # false
|
97
|
+
|
98
|
+
puts Season.new( '2010-11' ) < Season.new( '2015' ) # true
|
99
|
+
puts Season.new( '2015' ) < Season.new( '2019-20') # true
|
100
|
+
|
101
|
+
puts Season.new( '2015' ) == Season.new( '2015-16') # false
|
102
|
+
puts Season.new( '2015' ) < Season.new( '2015-16') # true
|
103
|
+
puts Season.new( '2015' ) == Season.new( '2015') # true
|
104
|
+
|
105
|
+
puts
|
106
|
+
puts s2010.include? Season.new( '2015-16' ) # true
|
107
|
+
puts s2010.include? Season.new( '2015' ) # false
|
108
|
+
puts s2010.include? Season.new( '1999-00' ) # false
|
109
|
+
end
|
110
|
+
|
62
111
|
end # class TestSeason
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sportdb-formats
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.1.
|
4
|
+
version: 1.1.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Gerald Bauer
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-07-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: alphabets
|
@@ -127,6 +127,8 @@ files:
|
|
127
127
|
- lib/sportdb/formats/country/country_reader.rb
|
128
128
|
- lib/sportdb/formats/datafile.rb
|
129
129
|
- lib/sportdb/formats/datafile_package.rb
|
130
|
+
- lib/sportdb/formats/event/event_index.rb
|
131
|
+
- lib/sportdb/formats/event/event_reader.rb
|
130
132
|
- lib/sportdb/formats/goals.rb
|
131
133
|
- lib/sportdb/formats/league/league_index.rb
|
132
134
|
- lib/sportdb/formats/league/league_outline_reader.rb
|
@@ -187,6 +189,7 @@ files:
|
|
187
189
|
- test/test_match_champs.rb
|
188
190
|
- test/test_match_eng.rb
|
189
191
|
- test/test_match_euro.rb
|
192
|
+
- test/test_match_start_date.rb
|
190
193
|
- test/test_match_worldcup.rb
|
191
194
|
- test/test_name_helper.rb
|
192
195
|
- test/test_outline_reader.rb
|