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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c3e154b72da0c4677b811ac922b37fee501f25cb28aa57dbbba608a0bd2787ff
4
- data.tar.gz: be12dab4eceae8bcf4f375baf1680917257b1c0ccd1849004eb9357be0200b33
3
+ metadata.gz: 7de33794f4721fff69b19475defc328a33a8f245d259d26326a2238681cd8255
4
+ data.tar.gz: 4bd10a37c73aef136805a5377c9765ba30a6842257ee8193840cbb2c1651e25f
5
5
  SHA512:
6
- metadata.gz: 20fd53b2c9c5bbeed88ba3148cb7b6e2f92af0fb8aa6c0c5e6e9e6b8e648ba44b16dffbe33f09eb7b5cab331b258d2e94f4aeed5727cb6c5b184847208add4a9
7
- data.tar.gz: afcc3bed4307de477823689ba1d255e63815cddaa7ff5062dac7ca2a2a07cc21b049bc8c92ffe4a7a0694cfd122fd9eff8e06a308e122d6e8db704f6631f8a92
6
+ metadata.gz: 1dec15278cd269eb43327ef227bfb277f5865d48469e20833e6aba0e53167bb792b2761865ecaf0b3bc7889444f496cdbe4348216d6c8fab7572f58bd2dc6e22
7
+ data.tar.gz: 398b9bf89c963c0e60087be743f709c7c4f42c1417cf66b4b22980e6d42f8fc918328efcec2e782d42861324f2763aaf7375681a7c5edd342a1bfa457704ae61
data/CHANGELOG.md CHANGED
@@ -1,4 +1,4 @@
1
- ### 0.1.0
1
+ ### 0.2.0
2
2
 
3
3
  ### 0.0.1 / 2025-04-01
4
4
 
data/Manifest.txt CHANGED
@@ -2,6 +2,7 @@ CHANGELOG.md
2
2
  Manifest.txt
3
3
  README.md
4
4
  Rakefile
5
+ bin/fbok
5
6
  config/codes_alt.csv
6
7
  config/leagues.csv
7
8
  config/leagues_more.csv
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, bra.1,,,
65
+ br.1, bra.1,,,
61
66
 
@@ -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
+
@@ -1,5 +1,4 @@
1
1
  key, zone
2
2
 
3
3
  il, Asia/Jerusalem
4
-
5
-
4
+ sa, Asia/Riyadh
@@ -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
 
@@ -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
- def self.parse_args( args )
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
- new(datasets)
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
- def self.parse( txt )
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
- datasets << [key, []]
50
-
112
+
51
113
  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)
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 ) parse( read_text( path )); end
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
- @recs = recs
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?
@@ -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.
@@ -3,7 +3,7 @@ module SportDb
3
3
  module Module
4
4
  module Leagues
5
5
  MAJOR = 0 ## todo: namespace inside version or something - why? why not??
6
- MINOR = 1
6
+ MINOR = 2
7
7
  PATCH = 0
8
8
  VERSION = [MAJOR,MINOR,PATCH].join('.')
9
9
 
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 ) Leagueset.read( path ); end
32
- def parse_leagueset( txt ) Leagueset.parse( txt ); end
33
- def parse_leagueset_args( args ) Leagueset.parse_args( args ); end
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.1.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-01 00:00:00.000000000 Z
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