leagues 0.1.0
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 +7 -0
- data/CHANGELOG.md +6 -0
- data/Manifest.txt +19 -0
- data/README.md +25 -0
- data/Rakefile +32 -0
- data/config/codes_alt.csv +61 -0
- data/config/leagues.csv +423 -0
- data/config/leagues_more.csv +40 -0
- data/config/timezones_africa.csv +21 -0
- data/config/timezones_america.csv +41 -0
- data/config/timezones_asia.csv +9 -0
- data/config/timezones_europe.csv +98 -0
- data/config/timezones_middle_east.csv +5 -0
- data/config/timezones_pacific.csv +5 -0
- data/config/timezones_world.csv +45 -0
- data/lib/leagues/league_codes.rb +169 -0
- data/lib/leagues/leagueset.rb +185 -0
- data/lib/leagues/timezones.rb +195 -0
- data/lib/leagues/version.rb +24 -0
- data/lib/leagues.rb +63 -0
- metadata +142 -0
@@ -0,0 +1,98 @@
|
|
1
|
+
key, zone
|
2
|
+
|
3
|
+
eng, Europe/London
|
4
|
+
sco, Europe/London ## check if entry or Edingburgh or such exits
|
5
|
+
wal, Europe/London
|
6
|
+
nir, Europe/London
|
7
|
+
ie, Europe/Dublin
|
8
|
+
|
9
|
+
|
10
|
+
es, Europe/Madrid
|
11
|
+
pt, Europe/Lisbon
|
12
|
+
gi, Europe/Gibraltar
|
13
|
+
ad, Europe/Andorra
|
14
|
+
|
15
|
+
fr, Europe/Paris
|
16
|
+
mc, Europe/Monaco
|
17
|
+
|
18
|
+
it, Europe/Rome
|
19
|
+
sm, Europe/San_Marino
|
20
|
+
va, Europe/Vatican
|
21
|
+
|
22
|
+
|
23
|
+
|
24
|
+
gr, Europe/Athens
|
25
|
+
tr, Europe/Istanbul
|
26
|
+
cy, Asia/Nicosia
|
27
|
+
mt, Europe/Malta
|
28
|
+
|
29
|
+
|
30
|
+
ro, Europe/Bucharest
|
31
|
+
bg, Europe/Sofia
|
32
|
+
|
33
|
+
ua, Europe/Kyiv
|
34
|
+
by, Europe/Minsk
|
35
|
+
ru, Europe/Moscow
|
36
|
+
|
37
|
+
|
38
|
+
de, Europe/Berlin
|
39
|
+
at, Europe/Vienna
|
40
|
+
ch, Europe/Zurich
|
41
|
+
li, Europe/Vaduz
|
42
|
+
|
43
|
+
hu, Europe/Budapest
|
44
|
+
cz, Europe/Prague
|
45
|
+
sk, Europe/Bratislava # link to Prague !!!
|
46
|
+
si, Europe/Ljubljana
|
47
|
+
pl, Europe/Warsaw
|
48
|
+
|
49
|
+
|
50
|
+
hr, Europe/Zagreb # link to Belgrade
|
51
|
+
ba, Europe/Sarajevo # link to Belgrade
|
52
|
+
rs, Europe/Belgrade # see en.wikipedia.org/wiki/Time_in_Serbia
|
53
|
+
al, Europe/Tirane
|
54
|
+
me, Europe/Podgorica
|
55
|
+
mk, Europe/Skopje
|
56
|
+
md, Europe/Chisinau
|
57
|
+
|
58
|
+
kos, Europe/Belgrade # en.wikipedia.org/wiki/Time_in_Kosovo
|
59
|
+
xk, Europe/Belgrade # note - add both codes for now eg. xk & kos
|
60
|
+
|
61
|
+
|
62
|
+
|
63
|
+
be, Europe/Brussels
|
64
|
+
lu, Europe/Luxembourg # link to Brussels !!!
|
65
|
+
nl, Europe/Amsterdam # link to Brussels !!!
|
66
|
+
|
67
|
+
|
68
|
+
dk, Europe/Copenhagen ## link to Berlin
|
69
|
+
se, Europe/Stockholm ## link to Berlin
|
70
|
+
no, Europe/Oslo # link to Berlin
|
71
|
+
fi, Europe/Helsinki
|
72
|
+
|
73
|
+
lv, Europe/Riga
|
74
|
+
ee, Europe/Tallinn
|
75
|
+
lt, Europe/Vilnius
|
76
|
+
|
77
|
+
is, Iceland
|
78
|
+
fo, Atlantic/Faroe
|
79
|
+
|
80
|
+
|
81
|
+
|
82
|
+
am, Asia/Yerevan # en.wikipedia.org/wiki/Armenia_Time
|
83
|
+
ge, Asia/Tbilisi # en.wikipedia.org/wiki/Georgia_Time
|
84
|
+
az, Asia/Baku
|
85
|
+
|
86
|
+
|
87
|
+
|
88
|
+
uefa, Europe/Paris
|
89
|
+
uefa.cl, Europe/Paris
|
90
|
+
uefa.champs, Europe/Paris ## for champs default for now to cet (central european time) - why? why not?
|
91
|
+
uefa.el, Europe/Paris
|
92
|
+
uefa.europa, Europe/Paris
|
93
|
+
uefa.conf, Europe/Paris
|
94
|
+
uefa.con, Europe/Paris
|
95
|
+
uefa.nl, Europe/Paris
|
96
|
+
uefa.nations, Europe/Paris
|
97
|
+
euro, Europe/Paris
|
98
|
+
|
@@ -0,0 +1,45 @@
|
|
1
|
+
key, zone
|
2
|
+
|
3
|
+
|
4
|
+
## todo/check - allow/use country ref
|
5
|
+
## e.g. world+1998, fr
|
6
|
+
## world+2006, de
|
7
|
+
## and lookup timezone fr instead - why? why not?
|
8
|
+
|
9
|
+
|
10
|
+
world+2022, Asia/Qatar # Qatar
|
11
|
+
world+2018, Europe/Moscow # Russia
|
12
|
+
world+2014, America/Sao_Paulo # Brazil
|
13
|
+
world+2010, Africa/Johannesburg # South Africa
|
14
|
+
world+2006, Europe/Berlin # Germany
|
15
|
+
world+2002, Asia/Tokyo # Japan & South Korea
|
16
|
+
world+1998, Europe/Paris # France
|
17
|
+
|
18
|
+
|
19
|
+
friendlies, Europe/London ## use (generic) utc - why? why not?
|
20
|
+
|
21
|
+
|
22
|
+
|
23
|
+
## add club world cups
|
24
|
+
|
25
|
+
world.clubs+2000, America/Sao_Paulo # Brazil
|
26
|
+
world.clubs+2005, Asia/Tokyo # Japan
|
27
|
+
world.clubs+2006, Asia/Tokyo # Japan
|
28
|
+
world.clubs+2007, Asia/Tokyo # Japan
|
29
|
+
world.clubs+2008, Asia/Tokyo # Japan
|
30
|
+
world.clubs+2009, Asia/Dubai # UAE
|
31
|
+
world.clubs+2010, Asia/Dubai # UAE
|
32
|
+
world.clubs+2011, Asia/Tokyo # Japan
|
33
|
+
world.clubs+2012, Asia/Tokyo # Japan
|
34
|
+
world.clubs+2013, Africa/Casablanca # Morocco
|
35
|
+
world.clubs+2014, Africa/Casablanca # Morocco
|
36
|
+
world.clubs+2015, Asia/Tokyo # Japan
|
37
|
+
world.clubs+2016, Asia/Tokyo # Japan
|
38
|
+
world.clubs+2017, Asia/Dubai # UAE
|
39
|
+
world.clubs+2018, Asia/Dubai # UAE
|
40
|
+
world.clubs+2019, Asia/Qatar # Qatar
|
41
|
+
world.clubs+2020, Asia/Qatar # Qatar
|
42
|
+
world.clubs+2021, Asia/Dubai # UAE
|
43
|
+
world.clubs+2022, Africa/Casablanca # Morocco
|
44
|
+
world.clubs+2023, Asia/Riyadh # Saudi Arabia
|
45
|
+
world.clubs+2025, America/New_York # United States
|
@@ -0,0 +1,169 @@
|
|
1
|
+
#####
|
2
|
+
#
|
3
|
+
# quick & dirty league code lookup (and mapping)
|
4
|
+
|
5
|
+
|
6
|
+
module SportDb
|
7
|
+
class LeagueCodes
|
8
|
+
|
9
|
+
####
|
10
|
+
## (public) api
|
11
|
+
def self.valid?( code )
|
12
|
+
## check if code is valid
|
13
|
+
builtin.valid?( code )
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.find_by( code:, season: )
|
17
|
+
## return league code record/item or nil
|
18
|
+
builtin.find_by( code: code, season: season )
|
19
|
+
end
|
20
|
+
|
21
|
+
|
22
|
+
#####
|
23
|
+
## (static) helpers
|
24
|
+
def self.builtin
|
25
|
+
## get builtin league code index (build on demand)
|
26
|
+
@leagues ||= begin
|
27
|
+
leagues = SportDb::LeagueCodes.new
|
28
|
+
['leagues',
|
29
|
+
'leagues_more',
|
30
|
+
].each do |name|
|
31
|
+
recs = read_csv( "#{SportDb::Module::Leagues.root}/config/#{name}.csv" )
|
32
|
+
leagues.add( recs )
|
33
|
+
end
|
34
|
+
|
35
|
+
['codes_alt',
|
36
|
+
].each do |name|
|
37
|
+
recs = read_csv( "#{SportDb::Module::Leagues.root}/config/#{name}.csv" )
|
38
|
+
leagues.add_alt( recs )
|
39
|
+
end
|
40
|
+
leagues
|
41
|
+
end
|
42
|
+
@leagues
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.norm( code ) ## use norm_(league)code - why? why not?
|
46
|
+
## norm league code
|
47
|
+
## downcase
|
48
|
+
## and remove all non-letters/digits e.g. at.1 => at1, at 1 => at1 etc.
|
49
|
+
## ö.1 => ö1
|
50
|
+
## note - allow unicode letters!!!
|
51
|
+
## note - assume downcase works for unicode too e.g. Ö=>ö
|
52
|
+
## for now no need to use our own downcase - why? why not?
|
53
|
+
|
54
|
+
code.downcase.gsub( /[^\p{Ll}0-9]/, '' )
|
55
|
+
end
|
56
|
+
|
57
|
+
|
58
|
+
|
59
|
+
|
60
|
+
def initialize
|
61
|
+
## keep to separate (hash) table for now - why? why not?
|
62
|
+
@leagues = {}
|
63
|
+
@codes = {}
|
64
|
+
end
|
65
|
+
|
66
|
+
|
67
|
+
def add( recs )
|
68
|
+
recs.each do |rec|
|
69
|
+
key = LeagueCodes.norm( rec['code'] )
|
70
|
+
@leagues[ key ] ||= []
|
71
|
+
|
72
|
+
## note: auto-change seasons to season object or nil
|
73
|
+
@leagues[ key ] << { 'code' => rec['code'],
|
74
|
+
'name' => rec['name'],
|
75
|
+
'basename' => rec['basename'],
|
76
|
+
'start_season' => rec['start_season'].empty? ? nil : Season.parse( rec['start_season'] ),
|
77
|
+
'end_season' => rec['end_season'].empty? ? nil : Season.parse( rec['end_season'] ),
|
78
|
+
}
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
### step two - add alt(ernative) codes
|
83
|
+
def add_alt( recs )
|
84
|
+
recs.each do |rec|
|
85
|
+
key = LeagueCodes.norm( rec['alt'] )
|
86
|
+
@codes[ key ] ||= []
|
87
|
+
|
88
|
+
### double check code reference
|
89
|
+
## MUST be present for now!!
|
90
|
+
ref_key = LeagueCodes.norm( rec['code'] )
|
91
|
+
unless @leagues.has_key?( ref_key )
|
92
|
+
raise ArgumentError, "league code >#{rec['code']}< for alt code >#{rec['alt']}< not found; sorry"
|
93
|
+
end
|
94
|
+
|
95
|
+
## note: auto-change seasons to season object or nil
|
96
|
+
@codes[ key ] << { 'code' => rec['code'],
|
97
|
+
'alt' => rec['alt'],
|
98
|
+
'start_season' => rec['start_season'].empty? ? nil : Season.parse( rec['start_season'] ),
|
99
|
+
'end_season' => rec['end_season'].empty? ? nil : Season.parse( rec['end_season'] ),
|
100
|
+
}
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
|
105
|
+
def valid?( code )
|
106
|
+
## check if code is valid
|
107
|
+
## 1) canonical codes (check first - why? why not?)
|
108
|
+
## 2) alt codes
|
109
|
+
raise ArgumentError, "league code as string|symbol expected" unless code.is_a?(String) || code.is_a?(Symbol)
|
110
|
+
|
111
|
+
key = LeagueCodes.norm( code )
|
112
|
+
found = @leagues.has_key?( key )
|
113
|
+
found = @codes.has_key?( key ) if found == false
|
114
|
+
found
|
115
|
+
end
|
116
|
+
|
117
|
+
|
118
|
+
def find_by( code:, season: )
|
119
|
+
raise ArgumentError, "league code as string|symbol expected" unless code.is_a?(String) || code.is_a?(Symbol)
|
120
|
+
|
121
|
+
## return league code record/item or nil
|
122
|
+
## check for alt code first
|
123
|
+
season = Season( season )
|
124
|
+
key = LeagueCodes.norm( code )
|
125
|
+
rec = nil
|
126
|
+
|
127
|
+
if !@leagues.has_key?( key ) ## try alt codes
|
128
|
+
key = _find_alt_code( key, season )
|
129
|
+
end
|
130
|
+
|
131
|
+
if key
|
132
|
+
recs = @leagues[ key ]
|
133
|
+
if recs
|
134
|
+
rec = _find_by_season( recs, season )
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
rec ## return nil if no code record/item found
|
139
|
+
end
|
140
|
+
|
141
|
+
|
142
|
+
def _find_alt_code( key, season )
|
143
|
+
## check alt keys
|
144
|
+
ref_key = nil
|
145
|
+
recs = @codes[key]
|
146
|
+
if recs
|
147
|
+
rec = _find_by_season( recs, season )
|
148
|
+
## norm code
|
149
|
+
ref_key = LeagueCodes.norm( rec['code'] ) if rec
|
150
|
+
end
|
151
|
+
|
152
|
+
ref_key ## return nil if no mapping found
|
153
|
+
end
|
154
|
+
|
155
|
+
|
156
|
+
def _find_by_season( recs, season )
|
157
|
+
recs.each do |rec|
|
158
|
+
start_season = rec['start_season']
|
159
|
+
end_season = rec['end_season']
|
160
|
+
return rec if (start_season.nil? || start_season <= season) &&
|
161
|
+
(end_season.nil? || end_season >= season)
|
162
|
+
end
|
163
|
+
nil
|
164
|
+
end
|
165
|
+
|
166
|
+
|
167
|
+
end # class LeagueCodes
|
168
|
+
end # module SportDb
|
169
|
+
|
@@ -0,0 +1,185 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
##
|
4
|
+
# use/find a better name
|
5
|
+
# League Set, League Sheet,
|
6
|
+
# Leagueset/LeagueSet, LeagueSheet/Leaguesheet
|
7
|
+
# or Leagues (only)??
|
8
|
+
# or League Book, League Setup, ??
|
9
|
+
# or Workset, Worksheet, Workbook, ...
|
10
|
+
#
|
11
|
+
# move league config over here from sportdb-writers too!!!!!
|
12
|
+
#
|
13
|
+
#
|
14
|
+
# find a better way to handle league codes
|
15
|
+
# always map to canoncial codes - why? why not?
|
16
|
+
|
17
|
+
|
18
|
+
|
19
|
+
module SportDb
|
20
|
+
class Leagueset
|
21
|
+
|
22
|
+
def self.parse_args( args )
|
23
|
+
### split args in datasets with leagues and seasons
|
24
|
+
datasets = []
|
25
|
+
args.each do |arg|
|
26
|
+
if arg =~ %r{^[0-9/-]+$} ## season
|
27
|
+
if datasets.empty?
|
28
|
+
puts "!! ERROR - league required before season arg; sorry"
|
29
|
+
exit 1
|
30
|
+
end
|
31
|
+
|
32
|
+
season = Season.parse( arg ) ## check season
|
33
|
+
datasets[-1][1] << season
|
34
|
+
else ## assume league key
|
35
|
+
key = arg.downcase
|
36
|
+
datasets << [key, []]
|
37
|
+
end
|
38
|
+
end
|
39
|
+
new(datasets)
|
40
|
+
end
|
41
|
+
|
42
|
+
|
43
|
+
def self.parse( txt )
|
44
|
+
### split args in datasets with leagues and seasons
|
45
|
+
datasets = []
|
46
|
+
recs = parse_csv( txt )
|
47
|
+
recs.each do |rec|
|
48
|
+
key = rec['league'].downcase
|
49
|
+
datasets << [key, []]
|
50
|
+
|
51
|
+
seasons_str = rec['seasons']
|
52
|
+
seasons = seasons_str.split( /[ ]+/ )
|
53
|
+
|
54
|
+
seasons.each do |season_str|
|
55
|
+
## note - add support for ranges e.g. 2001/02..2010/11
|
56
|
+
if season_str.index( '..' )
|
57
|
+
fst,snd = season_str.split( '..' )
|
58
|
+
# pp [fst,snd]
|
59
|
+
fst = Season.parse( fst )
|
60
|
+
snd = Season.parse( snd )
|
61
|
+
if fst < snd && fst.year? == snd.year?
|
62
|
+
datasets[-1][1] += (fst..snd).to_a
|
63
|
+
else
|
64
|
+
raise ArgumentError, "parse error - invalid season range >#{str}<, 1) two seasons required, 2) first < second, 3) same (year/academic) type"
|
65
|
+
end
|
66
|
+
else
|
67
|
+
season = Season.parse( season_str ) ## check season
|
68
|
+
datasets[-1][1] << season
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
new(datasets)
|
73
|
+
end
|
74
|
+
|
75
|
+
def self.read( path ) parse( read_text( path )); end
|
76
|
+
|
77
|
+
|
78
|
+
|
79
|
+
def initialize( recs )
|
80
|
+
@recs = recs
|
81
|
+
end
|
82
|
+
|
83
|
+
def size() @recs.size; end
|
84
|
+
|
85
|
+
def each( &blk )
|
86
|
+
@recs.each do |league_key, seasons|
|
87
|
+
blk.call( league_key, seasons )
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
|
92
|
+
### use a function for (re)use
|
93
|
+
### note - may add seasons in place!! (if seasons is empty)
|
94
|
+
##
|
95
|
+
## todo/check - change source_path to (simply) path - why? why not?
|
96
|
+
def validate!( source_path: ['.'] )
|
97
|
+
each do |league_key, seasons|
|
98
|
+
|
99
|
+
unless LeagueCodes.valid?( league_key )
|
100
|
+
puts "!! ERROR - (leagueset) no league (config) found for code >#{league_key}<; sorry"
|
101
|
+
exit 1
|
102
|
+
end
|
103
|
+
|
104
|
+
|
105
|
+
if seasons.empty?
|
106
|
+
## simple heuristic to find current season
|
107
|
+
[ Season( '2024/25'), Season( '2025') ].each do |season|
|
108
|
+
league_info = LeagueCodes.find_by( code: league_key, season: season )
|
109
|
+
filename = "#{season.to_path}/#{league_info['code']}.csv"
|
110
|
+
path = find_file( filename, path: source_path )
|
111
|
+
if path
|
112
|
+
seasons << season
|
113
|
+
break
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
if seasons.empty?
|
118
|
+
puts "!! ERROR - (leagueset) no latest auto-season via source found for #{league_key}; sorry"
|
119
|
+
exit 1
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
## check source path too upfront - why? why not?
|
124
|
+
seasons.each do |season|
|
125
|
+
## check league code config too - why? why not?
|
126
|
+
league_info = LeagueCodes.find_by( code: league_key, season: season )
|
127
|
+
if league_info.nil?
|
128
|
+
puts "!! ERROR - (leagueset) no league config found for code #{league_key} AND season #{season}; sorry"
|
129
|
+
exit 1
|
130
|
+
end
|
131
|
+
|
132
|
+
filename = "#{season.to_path}/#{league_info['code']}.csv"
|
133
|
+
path = find_file( filename, path: source_path )
|
134
|
+
|
135
|
+
if path.nil?
|
136
|
+
puts "!! ERROR - (leagueset) no source found for #{filename}; sorry"
|
137
|
+
exit 1
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end # each record
|
141
|
+
end
|
142
|
+
|
143
|
+
|
144
|
+
|
145
|
+
## todo/check: find a better name for helper?
|
146
|
+
## find_all_datasets, filter_datatsets - add alias(es???
|
147
|
+
## queries (lik ARGV) e.g. ['at'] or ['eng', 'de'] etc. list of strings
|
148
|
+
##
|
149
|
+
## todo/fix - check if used anywhere???
|
150
|
+
## - check if works with new alt codes too (or needs update)???
|
151
|
+
|
152
|
+
def filter( queries=[] )
|
153
|
+
## find all matching leagues (that is, league keys)
|
154
|
+
if queries.empty? ## no filter - get all league keys
|
155
|
+
self
|
156
|
+
else
|
157
|
+
recs = @recs.find_all do |league_key, seasons|
|
158
|
+
found = false
|
159
|
+
## note: normalize league key
|
160
|
+
## (remove dot and downcase)
|
161
|
+
norm_key = league_key.gsub( '.', '' )
|
162
|
+
queries.each do |query|
|
163
|
+
q = query.gsub( '.', '' ).downcase
|
164
|
+
if norm_key.start_with?( q )
|
165
|
+
found = true
|
166
|
+
break
|
167
|
+
end
|
168
|
+
end
|
169
|
+
found
|
170
|
+
end
|
171
|
+
## return new typed leagueset
|
172
|
+
self.class.new( recs )
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
|
177
|
+
def pretty_print( printer )
|
178
|
+
printer.text( "<Leagueset: " )
|
179
|
+
printer.text( @recs )
|
180
|
+
printer.text( ">")
|
181
|
+
end
|
182
|
+
|
183
|
+
end # module Leagueset
|
184
|
+
end # module SportDb
|
185
|
+
|