sportdb-formats 1.1.1 → 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.
- 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
|