leagues 0.1.0 → 0.2.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 +4 -4
- data/CHANGELOG.md +1 -1
- data/Manifest.txt +1 -0
- data/bin/fbok +94 -0
- data/config/codes_alt.csv +6 -1
- data/config/leagues_more.csv +6 -0
- data/config/timezones_middle_east.csv +1 -2
- data/lib/leagues/league_codes.rb +11 -0
- data/lib/leagues/leagueset.rb +142 -50
- data/lib/leagues/timezones.rb +5 -2
- data/lib/leagues/version.rb +1 -1
- data/lib/leagues.rb +10 -3
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7de33794f4721fff69b19475defc328a33a8f245d259d26326a2238681cd8255
|
4
|
+
data.tar.gz: 4bd10a37c73aef136805a5377c9765ba30a6842257ee8193840cbb2c1651e25f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1dec15278cd269eb43327ef227bfb277f5865d48469e20833e6aba0e53167bb792b2761865ecaf0b3bc7889444f496cdbe4348216d6c8fab7572f58bd2dc6e22
|
7
|
+
data.tar.gz: 398b9bf89c963c0e60087be743f709c7c4f42c1417cf66b4b22980e6d42f8fc918328efcec2e782d42861324f2763aaf7375681a7c5edd342a1bfa457704ae61
|
data/CHANGELOG.md
CHANGED
data/Manifest.txt
CHANGED
data/bin/fbok
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
## tip: to test run:
|
4
|
+
## ruby -I ./lib bin/fbok
|
5
|
+
|
6
|
+
require 'leagues'
|
7
|
+
|
8
|
+
|
9
|
+
def read_raw_leagueset( path )
|
10
|
+
## no - normalize and autofill etc.
|
11
|
+
datasets = []
|
12
|
+
recs = read_csv( path )
|
13
|
+
recs.each do |rec|
|
14
|
+
key = rec['league']
|
15
|
+
seasons = SportDb::Leagueset._parse_seasons( rec['seasons'] )
|
16
|
+
|
17
|
+
datasets << [key, seasons]
|
18
|
+
end
|
19
|
+
datasets
|
20
|
+
end
|
21
|
+
|
22
|
+
args = ARGV
|
23
|
+
|
24
|
+
args = ['/sports/tmp/classic.csv' ] if args.size == 0
|
25
|
+
|
26
|
+
|
27
|
+
|
28
|
+
def check_leagueset( path )
|
29
|
+
datasets = read_raw_leagueset( path )
|
30
|
+
pp datasets
|
31
|
+
|
32
|
+
puts "==> #{path} - #{datasets.size} record(s)"
|
33
|
+
|
34
|
+
## pass 1 - league code quick check
|
35
|
+
errors = []
|
36
|
+
datasets.each do |league_query, _|
|
37
|
+
if LeagueCodes.valid?( league_query )
|
38
|
+
puts "OK #{league_query}"
|
39
|
+
else
|
40
|
+
puts "!! #{league_query}"
|
41
|
+
errors << "league code #{league_query} NOT valid"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
if errors.size > 0
|
46
|
+
puts "#{errors.size} error(s):"
|
47
|
+
pp errrors
|
48
|
+
exit 1
|
49
|
+
end
|
50
|
+
|
51
|
+
## pass 2 - league code check by season
|
52
|
+
datasets.each_with_index do |(league_query, seasons),i|
|
53
|
+
puts "-- [#{i+1}/#{datasets.size}] #{league_query}, #{seasons.size} seasons(s)"
|
54
|
+
|
55
|
+
last_league_info = nil
|
56
|
+
seasons.each_with_index do |season,j|
|
57
|
+
league_info = LeagueCodes.find_by( code: league_query, season: season )
|
58
|
+
|
59
|
+
if league_info.nil?
|
60
|
+
puts "!! #{season}"
|
61
|
+
errors << "no league info for #{league_query} #{season} found"
|
62
|
+
next
|
63
|
+
end
|
64
|
+
|
65
|
+
## only print league info (if changed from last season)
|
66
|
+
if last_league_info.nil? ||
|
67
|
+
last_league_info['code'] != league_info['code'] ||
|
68
|
+
last_league_info['name'] != league_info['name'] ||
|
69
|
+
last_league_info['tz'] != league_info['tz']
|
70
|
+
print "\n" if j != 0 ## if new league info MUST start new line
|
71
|
+
pp league_info
|
72
|
+
end
|
73
|
+
print " #{season}"
|
74
|
+
|
75
|
+
last_league_info = league_info
|
76
|
+
end
|
77
|
+
print "\n"
|
78
|
+
end
|
79
|
+
|
80
|
+
if errors.size > 0
|
81
|
+
puts "#{errors.size} error(s):"
|
82
|
+
pp errrors
|
83
|
+
else
|
84
|
+
puts
|
85
|
+
puts "OK no error(s) found"
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
|
90
|
+
|
91
|
+
path = args[0]
|
92
|
+
check_leagueset( path )
|
93
|
+
|
94
|
+
puts 'bye'
|
data/config/codes_alt.csv
CHANGED
@@ -8,6 +8,11 @@ code,alt,start_season,end_season,comments
|
|
8
8
|
##
|
9
9
|
|
10
10
|
at.1, aut.bl,,, ## Bundesliga
|
11
|
+
### todo/fix - allow multiple alt codes at once e.g.
|
12
|
+
## aut.bl | ö.1 etc.
|
13
|
+
at.1, ö.1,,,
|
14
|
+
|
15
|
+
|
11
16
|
at.2, aut.2,,,
|
12
17
|
at.3.o, aut.rlo,,, ## Regionalliga Ost
|
13
18
|
|
@@ -57,5 +62,5 @@ se.2, swe.2,,,
|
|
57
62
|
il.1, isr.1,,,
|
58
63
|
ro.1, rou.1,,,
|
59
64
|
|
60
|
-
br.1,
|
65
|
+
br.1, bra.1,,,
|
61
66
|
|
data/config/leagues_more.csv
CHANGED
@@ -38,3 +38,9 @@ eng.efl.cup,English EFL Cup,eflcup,,
|
|
38
38
|
### fix - upstream uses world.club !!!
|
39
39
|
world.clubs,Club World Cup,clubworldcup,2000,
|
40
40
|
|
41
|
+
|
42
|
+
### quick hack
|
43
|
+
### alternate codes (duplicates); use/settle on one canonical league code!!!
|
44
|
+
br.cup,Copa do Brasil,cup,, ## br.copa
|
45
|
+
|
46
|
+
|
data/lib/leagues/league_codes.rb
CHANGED
@@ -6,6 +6,7 @@
|
|
6
6
|
module SportDb
|
7
7
|
class LeagueCodes
|
8
8
|
|
9
|
+
|
9
10
|
####
|
10
11
|
## (public) api
|
11
12
|
def self.valid?( code )
|
@@ -135,6 +136,16 @@ def find_by( code:, season: )
|
|
135
136
|
end
|
136
137
|
end
|
137
138
|
|
139
|
+
|
140
|
+
if rec ## (quick hack for now) auto-add timezone
|
141
|
+
## use canoncial (league) code
|
142
|
+
## note - if timezone changes MUST auto-create a NEW record
|
143
|
+
## thus, for now always create a new copy (via dup)!!!
|
144
|
+
rec = rec.dup
|
145
|
+
rec['tz'] = find_zone!( league: rec['code'], season: season )
|
146
|
+
end
|
147
|
+
|
148
|
+
|
138
149
|
rec ## return nil if no code record/item found
|
139
150
|
end
|
140
151
|
|
data/lib/leagues/leagueset.rb
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
|
2
|
-
|
3
2
|
##
|
4
3
|
# use/find a better name
|
5
4
|
# League Set, League Sheet,
|
@@ -19,13 +18,43 @@
|
|
19
18
|
module SportDb
|
20
19
|
class Leagueset
|
21
20
|
|
22
|
-
|
21
|
+
|
22
|
+
## autofiller helper
|
23
|
+
## - simple heuristic to find current (latest) season
|
24
|
+
##
|
25
|
+
## maybe move autofiller to fbup or such - why? why not?
|
26
|
+
|
27
|
+
def self.autofiller( league_query, source_path: ['.'] )
|
28
|
+
[ Season('2024/25'),
|
29
|
+
Season('2025')
|
30
|
+
].each do |season|
|
31
|
+
league_info = LeagueCodes.find_by( code: league_query, season: season )
|
32
|
+
league_code = league_info['code']
|
33
|
+
|
34
|
+
filename = "#{season.to_path}/#{league_code}.csv"
|
35
|
+
path = find_file( filename, path: source_path )
|
36
|
+
if path
|
37
|
+
return season
|
38
|
+
end
|
39
|
+
end
|
40
|
+
nil ## return nil if not found
|
41
|
+
end
|
42
|
+
|
43
|
+
|
44
|
+
|
45
|
+
|
46
|
+
###
|
47
|
+
## note - requires autofill (for seasons)
|
48
|
+
## if league querykey without season/empty season
|
49
|
+
def self.parse_args( args, autofill: nil )
|
23
50
|
### split args in datasets with leagues and seasons
|
51
|
+
## e.g. at1 eng1 or
|
52
|
+
## at1 2024/25 br1 2025 etc.
|
24
53
|
datasets = []
|
25
54
|
args.each do |arg|
|
26
55
|
if arg =~ %r{^[0-9/-]+$} ## season
|
27
56
|
if datasets.empty?
|
28
|
-
puts "!! ERROR - league required before season arg; sorry"
|
57
|
+
puts "!! ERROR [leagueset.parse_args] - league required before season arg; sorry"
|
29
58
|
exit 1
|
30
59
|
end
|
31
60
|
|
@@ -36,50 +65,122 @@ def self.parse_args( args )
|
|
36
65
|
datasets << [key, []]
|
37
66
|
end
|
38
67
|
end
|
39
|
-
|
68
|
+
|
69
|
+
new(datasets, autofill: autofill)
|
70
|
+
end
|
71
|
+
|
72
|
+
|
73
|
+
# todo/fix - (maybe) move "upstream" later
|
74
|
+
# e.g. Season.parse_list or parse_lst
|
75
|
+
# or parse_line or ???
|
76
|
+
# or parse_multi(ple) - why? why not?
|
77
|
+
def self._parse_seasons( str )
|
78
|
+
## helper to parse seasons string/column
|
79
|
+
## note: ALWAYS returns an array of seaons (even if only one)
|
80
|
+
result = []
|
81
|
+
seasons = str.split( /[ ]+/ )
|
82
|
+
|
83
|
+
seasons.each do |season_str|
|
84
|
+
## note - add support for ranges e.g. 2001/02..2010/11
|
85
|
+
if season_str.index( '..' )
|
86
|
+
fst,snd = season_str.split( '..' )
|
87
|
+
# pp [fst,snd]
|
88
|
+
fst = Season.parse( fst )
|
89
|
+
snd = Season.parse( snd )
|
90
|
+
if fst < snd && fst.year? == snd.year?
|
91
|
+
result += (fst..snd).to_a
|
92
|
+
else
|
93
|
+
raise ArgumentError, "parse error - invalid season range >#{season_str}<, 1) two seasons required, 2) first < second, 3) same (year/academic) type"
|
94
|
+
end
|
95
|
+
else
|
96
|
+
season = Season.parse( season_str ) ## check season
|
97
|
+
result << season
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
result
|
40
102
|
end
|
41
103
|
|
42
104
|
|
43
|
-
|
105
|
+
|
106
|
+
def self.parse( txt, autofill: nil )
|
44
107
|
### split args in datasets with leagues and seasons
|
45
108
|
datasets = []
|
46
109
|
recs = parse_csv( txt )
|
47
110
|
recs.each do |rec|
|
48
111
|
key = rec['league'].downcase
|
49
|
-
|
50
|
-
|
112
|
+
|
51
113
|
seasons_str = rec['seasons']
|
52
|
-
seasons = seasons_str
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
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)
|
114
|
+
seasons = _parse_seasons( seasons_str )
|
115
|
+
|
116
|
+
datasets << [key, seasons]
|
117
|
+
end
|
118
|
+
|
119
|
+
new(datasets, autofill: autofill)
|
73
120
|
end
|
74
121
|
|
75
|
-
def self.read( path
|
122
|
+
def self.read( path, autofill: nil )
|
123
|
+
parse( read_text( path ), autofill: autofill )
|
124
|
+
end
|
76
125
|
|
77
126
|
|
78
127
|
|
79
|
-
def initialize( recs )
|
80
|
-
@
|
128
|
+
def initialize( recs, autofill: nil )
|
129
|
+
### @org_recs = recs ## keep a record of orginal (MUST clone) - why? why not?
|
130
|
+
|
131
|
+
##### check for empty seasons
|
132
|
+
recs = _autofill( recs, autofill: autofill )
|
133
|
+
@recs = _norm( recs )
|
81
134
|
end
|
82
135
|
|
136
|
+
|
137
|
+
def _norm( recs )
|
138
|
+
datasets = {}
|
139
|
+
|
140
|
+
recs.each do |league_query, seasons|
|
141
|
+
unless LeagueCodes.valid?( league_query )
|
142
|
+
puts "!! ERROR - (leagueset) no league (config) found for code >#{league_query}<; sorry"
|
143
|
+
exit 1
|
144
|
+
end
|
145
|
+
|
146
|
+
seasons.each do |season|
|
147
|
+
## check league code config too - why? why not?
|
148
|
+
league_info = LeagueCodes.find_by( code: league_query, season: season )
|
149
|
+
if league_info.nil?
|
150
|
+
puts "!! ERROR - (leagueset) no league config found for code #{league_query} AND season #{season}; sorry"
|
151
|
+
exit 1
|
152
|
+
end
|
153
|
+
|
154
|
+
rec = datasets[ league_info['code'] ] ||= []
|
155
|
+
rec << season
|
156
|
+
end
|
157
|
+
end # each record
|
158
|
+
|
159
|
+
datasets.to_a ## convert hash to array
|
160
|
+
end
|
161
|
+
|
162
|
+
def _autofill( datasets, autofill: )
|
163
|
+
##### check for empty seasons
|
164
|
+
datasets.each do |league_query, seasons|
|
165
|
+
### try autofill
|
166
|
+
if seasons.empty? && autofill.is_a?(Proc)
|
167
|
+
season = autofill.call( league_query )
|
168
|
+
if season
|
169
|
+
## note - all season as string for autfiller too
|
170
|
+
seasons << Season(season)
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
if seasons.empty?
|
175
|
+
puts "!! ERROR [leagueset] - empty seasons; autofill found no latest season for #{league_query}; sorry"
|
176
|
+
exit 1
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
|
182
|
+
|
183
|
+
|
83
184
|
def size() @recs.size; end
|
84
185
|
|
85
186
|
def each( &blk )
|
@@ -89,10 +190,20 @@ def each( &blk )
|
|
89
190
|
end
|
90
191
|
|
91
192
|
|
193
|
+
|
194
|
+
|
195
|
+
|
196
|
+
|
92
197
|
### use a function for (re)use
|
93
198
|
### note - may add seasons in place!! (if seasons is empty)
|
94
199
|
##
|
95
200
|
## todo/check - change source_path to (simply) path - why? why not?
|
201
|
+
##
|
202
|
+
##
|
203
|
+
## add a flag for allowing empty/auto-fill of seasons - why? why not?
|
204
|
+
## or make it a separate method e.g. complete/fix_seasons or such? - why? why not?
|
205
|
+
|
206
|
+
|
96
207
|
def validate!( source_path: ['.'] )
|
97
208
|
each do |league_key, seasons|
|
98
209
|
|
@@ -101,25 +212,6 @@ def validate!( source_path: ['.'] )
|
|
101
212
|
exit 1
|
102
213
|
end
|
103
214
|
|
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
215
|
## check source path too upfront - why? why not?
|
124
216
|
seasons.each do |season|
|
125
217
|
## check league code config too - why? why not?
|
data/lib/leagues/timezones.rb
CHANGED
@@ -159,11 +159,13 @@ def find_zone( league:, season: )
|
|
159
159
|
zones
|
160
160
|
end
|
161
161
|
|
162
|
-
|
162
|
+
season = Season( season )
|
163
|
+
league_code = league.to_s.downcase
|
164
|
+
|
165
|
+
=begin
|
163
166
|
###
|
164
167
|
## map code here - why? why not?
|
165
168
|
## or (always) require canoncial code???
|
166
|
-
season = Season( season )
|
167
169
|
league_info = LeagueCodes.find_by( code: league, season: season )
|
168
170
|
|
169
171
|
league_code = if league_info
|
@@ -174,6 +176,7 @@ def find_zone( league:, season: )
|
|
174
176
|
## or report error in the future - why? why not?
|
175
177
|
league.to_s.downcase
|
176
178
|
end
|
179
|
+
=end
|
177
180
|
|
178
181
|
|
179
182
|
## e.g. world+2022, etc.
|
data/lib/leagues/version.rb
CHANGED
data/lib/leagues.rb
CHANGED
@@ -28,13 +28,20 @@ LeagueSet = Leagueset
|
|
28
28
|
module LeaguesetHelper
|
29
29
|
###
|
30
30
|
### note - make read_leagueset & friends public/global by default - why? why not?
|
31
|
-
def read_leagueset( path
|
32
|
-
|
33
|
-
|
31
|
+
def read_leagueset( path, autofill: nil )
|
32
|
+
Leagueset.read( path, autofill: autofill )
|
33
|
+
end
|
34
|
+
def parse_leagueset( txt, autofill: nil )
|
35
|
+
Leagueset.parse( txt, autofill: autofill )
|
36
|
+
end
|
37
|
+
def parse_leagueset_args( args, autofill: nil )
|
38
|
+
Leagueset.parse_args( args, autofill: autofill )
|
39
|
+
end
|
34
40
|
end
|
35
41
|
|
36
42
|
|
37
43
|
|
44
|
+
|
38
45
|
module FileHelper
|
39
46
|
def find_file( filename, path: )
|
40
47
|
path.each do |src_dir|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: leagues
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Gerald Bauer
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-04-
|
11
|
+
date: 2025-04-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: tzinfo
|
@@ -88,7 +88,8 @@ dependencies:
|
|
88
88
|
version: '4.2'
|
89
89
|
description: leagues - football leagues & timezone helpers
|
90
90
|
email: gerald.bauer@gmail.com
|
91
|
-
executables:
|
91
|
+
executables:
|
92
|
+
- fbok
|
92
93
|
extensions: []
|
93
94
|
extra_rdoc_files:
|
94
95
|
- CHANGELOG.md
|
@@ -99,6 +100,7 @@ files:
|
|
99
100
|
- Manifest.txt
|
100
101
|
- README.md
|
101
102
|
- Rakefile
|
103
|
+
- bin/fbok
|
102
104
|
- config/codes_alt.csv
|
103
105
|
- config/leagues.csv
|
104
106
|
- config/leagues_more.csv
|