footty 2024.9.27 → 2025.4.9
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 +3 -0
- data/README.md +19 -17
- data/lib/footty/datalib.rb +20 -0
- data/lib/footty/dataset.rb +4 -111
- data/lib/footty/openfootball.rb +145 -0
- data/lib/footty/print.rb +102 -0
- data/lib/footty/version.rb +1 -1
- data/lib/footty.rb +142 -145
- metadata +8 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e3101d3bc2cbc21aae12dcc1a108f7972211d55c86415a06c48e0a8db945ac3f
|
4
|
+
data.tar.gz: afaa93ffb303fd4205acdaa1aee1c80ec810a7bf723dabb99960025edb03076b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8d23098b1142d238c5f18bec351b6aff504682cb21e724df04dace6a9e40294267463037313c0fafae9a51e46ae80f88e976b681fb6d79fd51832282610957b7
|
7
|
+
data.tar.gz: 3906e217d2f73536b52295c6502eed1b96af0d4cd436a331c075ecc1780ab8c005ebbad260ef3856a70009cd5a4befd037f700e4a3f1d050b7bc3842b70a5b90
|
data/CHANGELOG.md
CHANGED
data/Manifest.txt
CHANGED
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# footty - football.db command line tool for national & int'l football club leagues (& cups) from around the world (bonus - incl. world cup, euro and more)
|
1
|
+
# footty/ftty - football.db command line tool for national & int'l football club leagues (& cups) from around the world (bonus - incl. world cup, euro and more)
|
2
2
|
|
3
3
|
|
4
4
|
* home :: [github.com/sportdb/footty](https://github.com/sportdb/footty)
|
@@ -12,13 +12,15 @@
|
|
12
12
|
|
13
13
|
## Usage - Who's playing today?
|
14
14
|
|
15
|
-
The footty command line tool lets you query the online football.db via HTTP
|
15
|
+
The footty (or ftty) command line tool lets you query the online football.db via HTTP
|
16
16
|
for upcoming or past matches. For example:
|
17
17
|
|
18
|
-
$ footty # Defaults to today's
|
18
|
+
$ footty # Defaults to today's matches of top leagues
|
19
|
+
|
19
20
|
|
20
21
|
prints on Sep 27, 2024:
|
21
22
|
|
23
|
+
==> English Premier League 2024/25
|
22
24
|
Upcoming matches:
|
23
25
|
Sat Sep/28 12:30 (in 1d) Newcastle United FC vs Manchester City FC Matchday 6
|
24
26
|
Sat Sep/28 15:00 (in 1d) Arsenal FC vs Leicester City FC Matchday 6
|
@@ -33,25 +35,25 @@ prints on Sep 27, 2024:
|
|
33
35
|
|
34
36
|
|
35
37
|
|
36
|
-
Use
|
38
|
+
Use `--tomorrow` or `-t` ` to print tomorrow's matches e.g.:
|
37
39
|
|
38
|
-
$ footty tomorrow # -or-
|
39
|
-
$ footty t
|
40
|
+
$ footty --tomorrow # -or-
|
41
|
+
$ footty -t
|
40
42
|
|
41
|
-
Use
|
43
|
+
Use `--yesterday` or `-y` to print yesterday's matches e.g.:
|
42
44
|
|
43
|
-
$ footty yesterday # -or-
|
44
|
-
$ footty y
|
45
|
+
$ footty --yesterday # -or-
|
46
|
+
$ footty -y
|
45
47
|
|
46
|
-
Use
|
48
|
+
Use `--upcoming` or `--up` or `-u` to print all upcoming matches e.g.:
|
47
49
|
|
48
|
-
$ footty upcoming # -or-
|
49
|
-
$ footty up
|
50
|
+
$ footty --upcoming # -or-
|
51
|
+
$ footty --up
|
50
52
|
|
51
|
-
Use
|
53
|
+
Use `--past` or `-p` to print all past matches e.g.:
|
52
54
|
|
53
|
-
$ footty past # -or-
|
54
|
-
$ footty p
|
55
|
+
$ footty --past # -or-
|
56
|
+
$ footty -p
|
55
57
|
|
56
58
|
|
57
59
|
|
@@ -60,7 +62,7 @@ That's it. Enjoy the beautiful game.
|
|
60
62
|
|
61
63
|
## Bonus - More Leagues & Cups - Bundesliga, Serie A, Ligue 1, La Liga & More
|
62
64
|
|
63
|
-
Pass in the league code to display the
|
65
|
+
Pass in the league code to display the German Bundesliga, Spanish La Liga, etc:
|
64
66
|
|
65
67
|
$ footty de # who's playing in the bundesliga today?
|
66
68
|
$ footty es # who's playing in la liga today?
|
@@ -79,7 +81,7 @@ More
|
|
79
81
|
- `world` => World Cup
|
80
82
|
- `euro` => "Euro" - European Championship
|
81
83
|
|
82
|
-
See [footty/
|
84
|
+
See [footty/openfootball](https://github.com/sportdb/footty/blob/master/lib/footty/openfootball.rb) for the complete built-in list of data sources (and league codes).
|
83
85
|
|
84
86
|
|
85
87
|
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Footty
|
2
|
+
|
3
|
+
class Datalib ## Library (Selection) of datasets
|
4
|
+
## nameing - use datasel(ection) or datacol(lection)
|
5
|
+
## or databox or dataman(ger) or
|
6
|
+
## or datacache or
|
7
|
+
## or datacomposite/view
|
8
|
+
## or dtataportal or ???
|
9
|
+
|
10
|
+
|
11
|
+
def initialize( datasets=[] )
|
12
|
+
@datasets = datasets
|
13
|
+
end
|
14
|
+
|
15
|
+
|
16
|
+
|
17
|
+
|
18
|
+
|
19
|
+
end # class Datalib
|
20
|
+
end # module Footty
|
data/lib/footty/dataset.rb
CHANGED
@@ -2,98 +2,14 @@ module Footty
|
|
2
2
|
|
3
3
|
|
4
4
|
class Dataset
|
5
|
-
SOURCES = {
|
6
|
-
'world' => { '2022' => [ 'worldcup/2022--qatar/cup.txt',
|
7
|
-
'worldcup/2022--qatar/cup_finals.txt'],
|
8
|
-
'2018' => [ 'worldcup/2018--russia/cup.txt',
|
9
|
-
'worldcup/2018--russia/cup_finals.txt']
|
10
|
-
},
|
11
|
-
'euro' => { '2024' => 'euro/2024--germany/euro.txt',
|
12
|
-
'2021' => 'euro/2021--europe/euro.txt'
|
13
|
-
},
|
14
|
-
'de' => 'deutschland/$season$/1-bundesliga.txt',
|
15
|
-
'de2' => 'deutschland/$season$/2-bundesliga2.txt',
|
16
|
-
'en'=> 'england/$season$/1-premierleague.txt',
|
17
|
-
'es'=> 'espana/$season$/1-liga.txt',
|
18
|
-
'it'=> 'italy/$season$/1-seriea.txt',
|
19
|
-
'at'=> 'austria/$season$/1-bundesliga.txt',
|
20
|
-
'at2' => 'austria/$season$/2-liga2.txt',
|
21
|
-
'at3o' => 'austria/$season$/3-regionalliga-ost.txt',
|
22
|
-
'atcup' => 'austria/$season$/cup.txt',
|
23
|
-
|
24
|
-
'fr'=> 'europe/france/$season$/1-ligue1.txt',
|
25
|
-
'nl'=> 'europe/netherlands/$season$/1-eredivisie.txt',
|
26
|
-
'be'=> 'europe/belgium/$season$/1-firstdivisiona.txt',
|
27
|
-
|
28
|
-
'champs'=> 'champions-league/$season$/cl.txt',
|
29
|
-
|
30
|
-
'br' => 'south-america/brazil/$year$/1-seriea.txt',
|
31
|
-
'ar' => 'south-america/argentina/$year$/1-primeradivision.txt',
|
32
|
-
'co' => 'south-america/colombia/$year$/1-primeraa.txt',
|
33
|
-
## use a different code for copa libertadores? why? why not?
|
34
|
-
'copa' => 'south-america/copa-libertadores/$year$/libertadores.txt',
|
35
|
-
|
36
|
-
'mx' => 'mexico/$season$/1-ligamx.txt',
|
37
|
-
|
38
|
-
'eg' => 'africa/egypt/$season$/1-premiership.txt',
|
39
|
-
'ma' => 'africa/morocco/$season$/1-botolapro1.txt',
|
40
|
-
}
|
41
|
-
|
42
|
-
|
43
|
-
## return built-in league keys
|
44
|
-
def self.leagues() SOURCES.keys; end
|
45
|
-
|
46
|
-
|
47
|
-
def initialize( league:, year: nil )
|
48
|
-
@league = league
|
49
|
-
|
50
|
-
spec = SOURCES[ @league.downcase ]
|
51
|
-
urls = if spec.is_a?( Hash ) ## assume lookup by year
|
52
|
-
if year.nil? ## lookup default first entry
|
53
|
-
year = spec.keys[0].to_i
|
54
|
-
spec.values[0]
|
55
|
-
else
|
56
|
-
spec[ year.to_s ]
|
57
|
-
end
|
58
|
-
else ## assume vanilla urls (no lookup by year)
|
59
|
-
## default to 2024 for now
|
60
|
-
year = 2024 if year.nil?
|
61
|
-
spec
|
62
|
-
end
|
63
|
-
raise ArgumentError, "no dataset (source) for league #{league} found" if urls.nil?
|
64
|
-
|
65
|
-
## wrap single sources (strings) in array
|
66
|
-
urls = urls.is_a?( Array ) ? urls : [urls]
|
67
|
-
## expand shortened url and fill-in template vars
|
68
|
-
@urls = urls.map { |url| openfootball_url( url, year: year ) }
|
69
|
-
end
|
70
5
|
|
71
6
|
|
72
|
-
def
|
73
|
-
|
74
|
-
url = "https://raw.githubusercontent.com/openfootball/#{repo}/master/#{local_path}"
|
75
|
-
## check for template vars too
|
76
|
-
season = Season( "#{year}/#{year+1}" )
|
77
|
-
url = url.gsub( '$year$', year.to_s )
|
78
|
-
url = url.gsub( '$season$', season.to_path )
|
79
|
-
url
|
7
|
+
def matches
|
8
|
+
raise ArgumentError, "method matches must be implemented by concrete class!!"
|
80
9
|
end
|
81
10
|
|
82
|
-
|
83
|
-
|
84
|
-
## cache ALL methods - only do one web request for match schedule & results
|
85
|
-
def matches
|
86
|
-
@data ||= begin
|
87
|
-
matches = []
|
88
|
-
@urls.each do |url|
|
89
|
-
txt = get!( url ) ## use "memoized" / cached result
|
90
|
-
matches += SportDb::QuickMatchReader.parse( txt )
|
91
|
-
end
|
92
|
-
data = matches.map {|match| match.as_json } # convert to json
|
93
|
-
## quick hack to get keys as strings not symbols!!
|
94
|
-
## fix upstream
|
95
|
-
JSON.parse( JSON.generate( data ))
|
96
|
-
end
|
11
|
+
def league_name
|
12
|
+
raise ArgumentError, "method league_name must be implemented by concrete class!!"
|
97
13
|
end
|
98
14
|
|
99
15
|
|
@@ -162,28 +78,5 @@ private
|
|
162
78
|
## sort matches here; might not be chronologicial (by default)
|
163
79
|
selected
|
164
80
|
end # method select_matches
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
def get!( url )
|
169
|
-
## use cached urls for 12h by default
|
170
|
-
## if expired in cache (or not present) than get/fetch
|
171
|
-
if Webcache.expired_in_12h?( url )
|
172
|
-
response = Webget.text( url )
|
173
|
-
|
174
|
-
if response.status.ok?
|
175
|
-
response.text # note - return text (utf-8)
|
176
|
-
else
|
177
|
-
## dump headers
|
178
|
-
response.headers.each do |key,value|
|
179
|
-
puts " #{key}: #{value}"
|
180
|
-
end
|
181
|
-
puts "!! HTTP ERROR - #{response.status.code} #{response.status.message}"
|
182
|
-
exit 1
|
183
|
-
end
|
184
|
-
else
|
185
|
-
Webcache.read( url )
|
186
|
-
end
|
187
|
-
end # method get_txt!
|
188
81
|
end # class Dataset
|
189
82
|
end # module Footty
|
@@ -0,0 +1,145 @@
|
|
1
|
+
module Footty
|
2
|
+
|
3
|
+
|
4
|
+
class OpenfootballDataset < Dataset
|
5
|
+
SOURCES = {
|
6
|
+
'world' => { '2022' => [ 'worldcup/2022--qatar/cup.txt',
|
7
|
+
'worldcup/2022--qatar/cup_finals.txt'],
|
8
|
+
'2018' => [ 'worldcup/2018--russia/cup.txt',
|
9
|
+
'worldcup/2018--russia/cup_finals.txt']
|
10
|
+
},
|
11
|
+
'euro' => { '2024' => 'euro/2024--germany/euro.txt',
|
12
|
+
'2021' => 'euro/2021--europe/euro.txt'
|
13
|
+
},
|
14
|
+
'de' => 'deutschland/$season$/1-bundesliga.txt',
|
15
|
+
'de2' => 'deutschland/$season$/2-bundesliga2.txt',
|
16
|
+
|
17
|
+
'en'=> 'england/$season$/1-premierleague.txt',
|
18
|
+
'es'=> 'espana/$season$/1-liga.txt',
|
19
|
+
'it'=> 'italy/$season$/1-seriea.txt',
|
20
|
+
|
21
|
+
'at'=> 'austria/$season$/1-bundesliga.txt',
|
22
|
+
'at2' => 'austria/$season$/2-liga2.txt',
|
23
|
+
'at3o' => 'austria/$season$/3-regionalliga-ost.txt',
|
24
|
+
'atcup' => 'austria/$season$/cup.txt',
|
25
|
+
|
26
|
+
'fr'=> 'europe/france/$season$_fr1.txt',
|
27
|
+
'nl'=> 'europe/netherlands/$season$_nl1.txt',
|
28
|
+
'be'=> 'europe/belgium/$season$_be1.txt',
|
29
|
+
|
30
|
+
'champs'=> 'champions-league/$season$/cl.txt',
|
31
|
+
|
32
|
+
'br' => 'south-america/brazil/$year$_br1.txt',
|
33
|
+
'ar' => 'south-america/argentina/$year$_ar1.txt',
|
34
|
+
'co' => 'south-america/colombia/$year$_co1.txt',
|
35
|
+
## use a different code for copa libertadores? why? why not?
|
36
|
+
'copa' => 'south-america/copa-libertadores/$year$_copal.txt',
|
37
|
+
|
38
|
+
'mx' => 'world/north-america/mexico/$season$_mx1.txt',
|
39
|
+
|
40
|
+
'eg' => 'world/africa/egypt/$season$_eg1.txt',
|
41
|
+
'ma' => 'world/africa/morocco/$season$_ma1.txt',
|
42
|
+
}
|
43
|
+
|
44
|
+
|
45
|
+
## return built-in league keys
|
46
|
+
def self.leagues() SOURCES.keys; end
|
47
|
+
|
48
|
+
|
49
|
+
|
50
|
+
### auto-fill latest season
|
51
|
+
def self.latest_season( league: )
|
52
|
+
spec = SOURCES[ league.downcase ]
|
53
|
+
|
54
|
+
## todo/fix - report error if no spec found
|
55
|
+
season = if spec.is_a?( Hash ) ## assume lookup by year
|
56
|
+
spec.keys[0]
|
57
|
+
else ## assume vanilla urls (no lookup by year)
|
58
|
+
## default to 2025 or 2024/25 for now
|
59
|
+
spec.index( '$year$') ? '2025' : '2024/25'
|
60
|
+
end
|
61
|
+
season
|
62
|
+
end
|
63
|
+
|
64
|
+
|
65
|
+
|
66
|
+
def initialize( league:, season: )
|
67
|
+
spec = SOURCES[ league.downcase ]
|
68
|
+
|
69
|
+
urls = if spec.is_a?( Hash ) ## assume lookup by year
|
70
|
+
spec[ season ]
|
71
|
+
else ## assume vanilla urls (no lookup by year)
|
72
|
+
spec
|
73
|
+
end
|
74
|
+
raise ArgumentError, "no dataset (source) for league #{league} found" if urls.nil?
|
75
|
+
|
76
|
+
## wrap single sources (strings) in array
|
77
|
+
urls = urls.is_a?( Array ) ? urls : [urls]
|
78
|
+
## expand shortened url and fill-in template vars
|
79
|
+
urls = urls.map { |url| openfootball_url( url, season: season ) }
|
80
|
+
|
81
|
+
matches = []
|
82
|
+
urls.each do |url|
|
83
|
+
txt = get!( url ) ## use "memoized" / cached result
|
84
|
+
parser = SportDb::QuickMatchReader.new( txt )
|
85
|
+
matches += parser.parse
|
86
|
+
### for multiple source file use latest name as "definitive"
|
87
|
+
@league_name = parser.league_name
|
88
|
+
### todo/fix - report errors
|
89
|
+
end
|
90
|
+
|
91
|
+
matches = matches.map {|match| match.as_json } # convert to json
|
92
|
+
|
93
|
+
## note - sort by date/time
|
94
|
+
## (assume stable sort; no reshuffle of matches if already sorted by date/time)
|
95
|
+
|
96
|
+
matches = matches.sort do |l,r|
|
97
|
+
result = l['date'] <=> r['date']
|
98
|
+
result = l['time'] <=> r['time'] if result == 0 &&
|
99
|
+
(l['time'] && r['time'])
|
100
|
+
result
|
101
|
+
end
|
102
|
+
|
103
|
+
@matches = matches
|
104
|
+
end
|
105
|
+
|
106
|
+
|
107
|
+
def openfootball_url( path, season: )
|
108
|
+
repo, local_path = path.split( '/', 2)
|
109
|
+
url = "https://raw.githubusercontent.com/openfootball/#{repo}/master/#{local_path}"
|
110
|
+
## check for template vars too
|
111
|
+
season = Season( season )
|
112
|
+
url = url.gsub( '$year$', season.start_year.to_s )
|
113
|
+
url = url.gsub( '$season$', season.to_path )
|
114
|
+
url
|
115
|
+
end
|
116
|
+
|
117
|
+
|
118
|
+
def matches() @matches; end
|
119
|
+
def league_name() @league_name; end
|
120
|
+
|
121
|
+
|
122
|
+
|
123
|
+
def get!( url )
|
124
|
+
## use cached urls for 12h by default
|
125
|
+
## if expired in cache (or not present) than get/fetch
|
126
|
+
if Webcache.expired_in_12h?( url )
|
127
|
+
response = Webget.text( url )
|
128
|
+
|
129
|
+
if response.status.ok?
|
130
|
+
response.text # note - return text (utf-8)
|
131
|
+
else
|
132
|
+
## dump headers
|
133
|
+
response.headers.each do |key,value|
|
134
|
+
puts " #{key}: #{value}"
|
135
|
+
end
|
136
|
+
puts "!! HTTP ERROR - #{response.status.code} #{response.status.message}"
|
137
|
+
exit 1
|
138
|
+
end
|
139
|
+
else
|
140
|
+
Webcache.read( url )
|
141
|
+
end
|
142
|
+
end # method get_txt!
|
143
|
+
|
144
|
+
end # class OpenfootballDaset
|
145
|
+
end # module Footty
|
data/lib/footty/print.rb
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
|
2
|
+
module Footty
|
3
|
+
|
4
|
+
def self.print_matches( matches )
|
5
|
+
|
6
|
+
today = Date.today
|
7
|
+
|
8
|
+
matches.each do |match|
|
9
|
+
print " %5s" % "\##{match['num']} " if match['num']
|
10
|
+
|
11
|
+
date = Date.strptime( match['date'], '%Y-%m-%d' )
|
12
|
+
print "#{date.strftime('%a %b/%d')} " ## e.g. Thu Jun/14
|
13
|
+
print "#{match['time']} " if match['time']
|
14
|
+
|
15
|
+
if date > today
|
16
|
+
diff = (date - today).to_i
|
17
|
+
print "%10s" % "(in #{diff}d) "
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
if match['team1'].is_a?( Hash )
|
22
|
+
print "%22s" % "#{match['team1']['name']} (#{match['team1']['code']})"
|
23
|
+
else
|
24
|
+
print "%22s" % "#{match['team1']}"
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
if match['score'].is_a?( Hash ) &&
|
29
|
+
match['score']['ft']
|
30
|
+
if match['score']['ft']
|
31
|
+
print " #{match['score']['ft'][0]}-#{match['score']['ft'][1]} "
|
32
|
+
end
|
33
|
+
if match['score']['et']
|
34
|
+
print "aet #{match['score']['et'][0]}-#{match['score']['et'][1]} "
|
35
|
+
end
|
36
|
+
if match['score']['p']
|
37
|
+
print "pen #{match['score']['p'][0]}-#{match['score']['p'][1]} "
|
38
|
+
end
|
39
|
+
elsif match['score1'] && match['score2']
|
40
|
+
## todo/fix: add support for knockout scores
|
41
|
+
## with score1et/score1p (extra time and penalty)
|
42
|
+
print " #{match['score1']}-#{match['score2']} "
|
43
|
+
print "(#{match['score1i']}-#{match['score2i']}) "
|
44
|
+
else
|
45
|
+
print " vs "
|
46
|
+
end
|
47
|
+
|
48
|
+
if match['team2'].is_a?( Hash )
|
49
|
+
print "%-22s" % "#{match['team2']['name']} (#{match['team2']['code']})"
|
50
|
+
else
|
51
|
+
print "%-22s" % "#{match['team2']}"
|
52
|
+
end
|
53
|
+
|
54
|
+
if match['stage']
|
55
|
+
print " #{match['stage']} /" ## stage
|
56
|
+
end
|
57
|
+
|
58
|
+
if match['group']
|
59
|
+
print " #{match['group']} /" ## group phase
|
60
|
+
end
|
61
|
+
|
62
|
+
print " #{match['round']} " ## knock out (k.o.) phase/stage
|
63
|
+
|
64
|
+
## todo/fix - check for ground name in use???
|
65
|
+
if match['stadium']
|
66
|
+
print " @ #{match['stadium']['name']}, #{match['city']}"
|
67
|
+
end
|
68
|
+
|
69
|
+
print "\n"
|
70
|
+
|
71
|
+
|
72
|
+
if match['goals1'] && match['goals2']
|
73
|
+
print " ["
|
74
|
+
match['goals1'].each_with_index do |goal,i|
|
75
|
+
print " " if i > 0
|
76
|
+
print "#{goal['name']}"
|
77
|
+
print " #{goal['minute']}"
|
78
|
+
print "+#{goal['offset']}" if goal['offset']
|
79
|
+
print "'"
|
80
|
+
print " (o.g.)" if goal['owngoal']
|
81
|
+
print " (pen.)" if goal['penalty']
|
82
|
+
end
|
83
|
+
match['goals2'].each_with_index do |goal,i|
|
84
|
+
if i == 0
|
85
|
+
print "; "
|
86
|
+
else
|
87
|
+
print " "
|
88
|
+
end
|
89
|
+
print "#{goal['name']}"
|
90
|
+
print " #{goal['minute']}"
|
91
|
+
print "+#{goal['offset']}" if goal['offset']
|
92
|
+
print "'"
|
93
|
+
print " (o.g.)" if goal['owngoal']
|
94
|
+
print " (pen.)" if goal['penalty']
|
95
|
+
end
|
96
|
+
print "]\n"
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
|
102
|
+
end # module Footty
|
data/lib/footty/version.rb
CHANGED
data/lib/footty.rb
CHANGED
@@ -1,9 +1,18 @@
|
|
1
1
|
require 'sportdb/quick' ## note - pulls in cocos et al
|
2
2
|
require 'webget' ## add webcache support
|
3
3
|
|
4
|
+
require 'optparse'
|
5
|
+
|
6
|
+
|
7
|
+
|
8
|
+
|
4
9
|
# our own code
|
5
|
-
require_relative 'footty/version' # let
|
10
|
+
require_relative 'footty/version' # let version always go first
|
6
11
|
require_relative 'footty/dataset'
|
12
|
+
require_relative 'footty/openfootball'
|
13
|
+
## require_relative 'footty/datalib'
|
14
|
+
|
15
|
+
require_relative 'footty/print'
|
7
16
|
|
8
17
|
|
9
18
|
|
@@ -17,176 +26,164 @@ module Footty
|
|
17
26
|
def self.main( args=ARGV )
|
18
27
|
puts banner # say hello
|
19
28
|
|
20
|
-
league = 'en'
|
21
|
-
year = nil
|
22
29
|
|
23
|
-
|
24
|
-
|
30
|
+
opts = { debug: false,
|
31
|
+
verbose: false, ## add more details
|
32
|
+
## add cache/cache_dir - why? why not?
|
25
33
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
elsif leagues.include?( arg )
|
32
|
-
league = arg
|
33
|
-
false
|
34
|
-
else
|
35
|
-
true
|
36
|
-
end
|
37
|
-
end
|
34
|
+
## display format/mode - week/window/upcoming/past (default is today)
|
35
|
+
yesterday: nil,
|
36
|
+
tomorrow: nil,
|
37
|
+
upcoming: nil,
|
38
|
+
past: nil,
|
38
39
|
|
40
|
+
# week: nil,
|
41
|
+
# window: nil, ## 2 day plus/minus +2/-2
|
42
|
+
}
|
39
43
|
|
40
|
-
|
41
|
-
|
44
|
+
|
45
|
+
parser = OptionParser.new do |parser|
|
46
|
+
parser.banner = "Usage: #{$PROGRAM_NAME} [options] LEAGUES"
|
42
47
|
|
48
|
+
parser.on( "--verbose",
|
49
|
+
"turn on verbose output (default: #{opts[:verbose]})" ) do |verbose|
|
50
|
+
opts[:verbose] = true
|
51
|
+
end
|
43
52
|
|
44
|
-
# Dataset.new( league: 'euro', year: 2024 )
|
45
|
-
dataset = Dataset.new( league: league, year: year )
|
46
|
-
|
47
|
-
|
48
|
-
## in the future make today "configurable" as param - why? why not?
|
49
|
-
today = Date.today
|
50
53
|
|
51
|
-
|
52
|
-
|
53
|
-
if matches.empty?
|
54
|
-
puts "** No matches played yesterday.\n"
|
54
|
+
parser.on( "-y", "--yesterday" ) do |yesterday|
|
55
|
+
opts[:yesterday] = true
|
55
56
|
end
|
56
|
-
|
57
|
-
|
58
|
-
if matches.empty?
|
59
|
-
puts "** No matches scheduled tomorrow.\n"
|
57
|
+
parser.on( "-t", "--tomorrow" ) do |tomorrow|
|
58
|
+
opts[:tomorrow] = true
|
60
59
|
end
|
61
|
-
|
62
|
-
|
63
|
-
if matches.empty?
|
64
|
-
puts "** No matches played yet.\n"
|
60
|
+
parser.on( "-p", "--past" ) do |past|
|
61
|
+
opts[:past] = true
|
65
62
|
end
|
66
|
-
|
67
|
-
|
68
|
-
if matches.empty?
|
69
|
-
puts "** No more matches scheduled.\n"
|
63
|
+
parser.on( "-u", "--up", "--upcoming" ) do |upcoming|
|
64
|
+
opts[:upcoming] = true
|
70
65
|
end
|
71
|
-
else
|
72
|
-
matches = dataset.todays_matches
|
73
|
-
|
74
|
-
## no matches today
|
75
|
-
if matches.empty?
|
76
|
-
puts "** No matches scheduled today.\n"
|
77
|
-
|
78
|
-
## note: was world cup 2018 - end date -- Date.new( 2018, 7, 11 )
|
79
|
-
## note: was euro 2020 (in 2021) - end date -- Date.new( 2021, 7, 11 )
|
80
|
-
if Date.today > dataset.end_date ## tournament is over, look back
|
81
|
-
puts "Past matches:"
|
82
|
-
matches = dataset.past_matches
|
83
|
-
else ## world cup is upcoming /in-progress,look forward
|
84
|
-
puts "Upcoming matches:"
|
85
|
-
matches = dataset.upcoming_matches( limit: 18 )
|
86
|
-
end
|
87
|
-
end
|
88
66
|
end
|
89
|
-
|
90
|
-
|
91
|
-
|
67
|
+
parser.parse!( args )
|
68
|
+
|
69
|
+
|
70
|
+
puts "OPTS:"
|
71
|
+
p opts
|
72
|
+
puts "ARGV:"
|
73
|
+
p args
|
74
|
+
|
75
|
+
|
76
|
+
top = [['world', '2022'],
|
77
|
+
['euro', '2024'],
|
78
|
+
['mx', '2024/25'],
|
79
|
+
['copa', '2025'], ## copa libertadores
|
80
|
+
['en', '2024/25'],
|
81
|
+
['es', '2024/25'],
|
82
|
+
['it', '2024/25'],
|
83
|
+
['fr', '2024/25'],
|
84
|
+
['de', '2024/25'],
|
85
|
+
['at', '2024/25'],
|
86
|
+
['champs', '2024/25'],
|
87
|
+
]
|
88
|
+
|
89
|
+
|
90
|
+
leagues = if args.size == 0
|
91
|
+
top
|
92
|
+
else
|
93
|
+
### auto-fill (latest) season/year
|
94
|
+
args.map do |arg|
|
95
|
+
[arg, OpenfootballDataset.latest_season( league: arg )]
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
|
100
|
+
## fetch leagues
|
101
|
+
datasets = leagues.map do |league, season|
|
102
|
+
dataset = OpenfootballDataset.new( league: league, season: season )
|
103
|
+
## parse matches
|
104
|
+
matches = dataset.matches
|
105
|
+
puts " #{league} #{season} - #{matches.size} match(es)"
|
106
|
+
dataset
|
107
|
+
end
|
108
|
+
|
109
|
+
|
92
110
|
|
93
111
|
|
94
|
-
|
95
|
-
|
112
|
+
# Dataset.new( league: 'euro', year: 2024 )
|
113
|
+
# dataset = Dataset.new( league: league, year: year )
|
96
114
|
|
115
|
+
## in the future make today "configurable" as param - why? why not?
|
97
116
|
today = Date.today
|
98
117
|
|
99
|
-
matches.each do |match|
|
100
|
-
print " %5s" % "\##{match['num']} " if match['num']
|
101
|
-
|
102
|
-
date = Date.strptime( match['date'], '%Y-%m-%d' )
|
103
|
-
print "#{date.strftime('%a %b/%d')} " ## e.g. Thu Jun/14
|
104
|
-
print "#{match['time']} " if match['time']
|
105
|
-
|
106
|
-
if date > today
|
107
|
-
diff = (date - today).to_i
|
108
|
-
print "%10s" % "(in #{diff}d) "
|
109
|
-
end
|
110
|
-
|
111
|
-
|
112
|
-
if match['team1'].is_a?( Hash )
|
113
|
-
print "%22s" % "#{match['team1']['name']} (#{match['team1']['code']})"
|
114
|
-
else
|
115
|
-
print "%22s" % "#{match['team1']}"
|
116
|
-
end
|
117
|
-
|
118
|
-
|
119
|
-
if match['score'].is_a?( Hash ) &&
|
120
|
-
match['score']['ft']
|
121
|
-
if match['score']['ft']
|
122
|
-
print " #{match['score']['ft'][0]}-#{match['score']['ft'][1]} "
|
123
|
-
end
|
124
|
-
if match['score']['et']
|
125
|
-
print "aet #{match['score']['et'][0]}-#{match['score']['et'][1]} "
|
126
|
-
end
|
127
|
-
if match['score']['p']
|
128
|
-
print "pen #{match['score']['p'][0]}-#{match['score']['p'][1]} "
|
129
|
-
end
|
130
|
-
elsif match['score1'] && match['score2']
|
131
|
-
## todo/fix: add support for knockout scores
|
132
|
-
## with score1et/score1p (extra time and penalty)
|
133
|
-
print " #{match['score1']}-#{match['score2']} "
|
134
|
-
print "(#{match['score1i']}-#{match['score2i']}) "
|
135
|
-
else
|
136
|
-
print " vs "
|
137
|
-
end
|
138
|
-
|
139
|
-
if match['team2'].is_a?( Hash )
|
140
|
-
print "%-22s" % "#{match['team2']['name']} (#{match['team2']['code']})"
|
141
|
-
else
|
142
|
-
print "%-22s" % "#{match['team2']}"
|
143
|
-
end
|
144
|
-
|
145
|
-
|
146
|
-
if match['group']
|
147
|
-
print " #{match['group']} /" ## group phase/stage
|
148
|
-
end
|
149
|
-
|
150
|
-
print " #{match['round']} " ## knock out (k.o.) phase/stage
|
151
|
-
|
152
|
-
if match['stadium']
|
153
|
-
print " @ #{match['stadium']['name']}, #{match['city']}"
|
154
|
-
end
|
155
118
|
|
119
|
+
what = if opts[:yesterday]
|
120
|
+
'yesterday'
|
121
|
+
elsif opts[:tomorrow]
|
122
|
+
'tomorrow'
|
123
|
+
elsif opts[:past]
|
124
|
+
'past'
|
125
|
+
elsif opts[:upcoming]
|
126
|
+
'upcoming'
|
127
|
+
else
|
128
|
+
'today'
|
129
|
+
end
|
130
|
+
|
131
|
+
## start with two empty lines - assume (massive) debug output before ;-)
|
132
|
+
puts
|
133
|
+
puts
|
134
|
+
datasets.each do |dataset|
|
135
|
+
print "==> #{dataset.league_name}"
|
136
|
+
print " #{dataset.start_date} - #{dataset.end_date}"
|
137
|
+
print " -- #{dataset.matches.size} match(es)"
|
156
138
|
print "\n"
|
157
139
|
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
print " (o.g.)" if goal['owngoal']
|
168
|
-
print " (pen.)" if goal['penalty']
|
140
|
+
if what == 'yesterday'
|
141
|
+
matches = dataset.yesterdays_matches
|
142
|
+
if matches.empty?
|
143
|
+
puts (' '*4) + "** No matches played yesterday.\n"
|
144
|
+
end
|
145
|
+
elsif what == 'tomorrow'
|
146
|
+
matches = dataset.tomorrows_matches
|
147
|
+
if matches.empty?
|
148
|
+
puts (' '*4) + "** No matches scheduled tomorrow.\n"
|
169
149
|
end
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
print " "
|
175
|
-
end
|
176
|
-
print "#{goal['name']}"
|
177
|
-
print " #{goal['minute']}"
|
178
|
-
print "+#{goal['offset']}" if goal['offset']
|
179
|
-
print "'"
|
180
|
-
print " (o.g.)" if goal['owngoal']
|
181
|
-
print " (pen.)" if goal['penalty']
|
150
|
+
elsif what == 'past'
|
151
|
+
matches = dataset.past_matches
|
152
|
+
if matches.empty?
|
153
|
+
puts (' '*4) + "** No matches played yet.\n"
|
182
154
|
end
|
183
|
-
|
155
|
+
elsif what == 'upcoming'
|
156
|
+
matches = dataset.upcoming_matches
|
157
|
+
if matches.empty?
|
158
|
+
puts (' '*4) + "** No more matches scheduled.\n"
|
159
|
+
end
|
160
|
+
else ## assume today
|
161
|
+
matches = dataset.todays_matches
|
162
|
+
|
163
|
+
## no matches today
|
164
|
+
if matches.empty?
|
165
|
+
puts (' '*4) + "** No matches scheduled today.\n"
|
166
|
+
|
167
|
+
if opts[:verbose]
|
168
|
+
## note: was world cup 2018 - end date -- Date.new( 2018, 7, 11 )
|
169
|
+
## note: was euro 2020 (in 2021) - end date -- Date.new( 2021, 7, 11 )
|
170
|
+
if Date.today > dataset.end_date ## tournament is over, look back
|
171
|
+
puts "Past matches:"
|
172
|
+
matches = dataset.past_matches
|
173
|
+
else ## world cup is upcoming /in-progress,look forward
|
174
|
+
puts "Upcoming matches:"
|
175
|
+
matches = dataset.upcoming_matches( limit: 18 )
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
print_matches( matches )
|
184
181
|
end
|
185
|
-
end
|
186
|
-
end
|
187
182
|
|
183
|
+
end # method self.main
|
188
184
|
end # module Footty
|
189
185
|
|
190
186
|
|
191
187
|
|
188
|
+
|
192
189
|
Footty.main if __FILE__ == $0
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: footty
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2025.4.9
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Gerald Bauer
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2025-04-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: sportdb-quick
|
@@ -64,14 +64,14 @@ dependencies:
|
|
64
64
|
requirements:
|
65
65
|
- - "~>"
|
66
66
|
- !ruby/object:Gem::Version
|
67
|
-
version: '4.
|
67
|
+
version: '4.2'
|
68
68
|
type: :development
|
69
69
|
prerelease: false
|
70
70
|
version_requirements: !ruby/object:Gem::Requirement
|
71
71
|
requirements:
|
72
72
|
- - "~>"
|
73
73
|
- !ruby/object:Gem::Version
|
74
|
-
version: '4.
|
74
|
+
version: '4.2'
|
75
75
|
description: footty - football.db command line tool for national & int'l football
|
76
76
|
club leagues (& cups) from around the world (bonus - incl. world cup, euro and more)
|
77
77
|
email: gerald.bauer@gmail.com
|
@@ -91,7 +91,10 @@ files:
|
|
91
91
|
- bin/footty
|
92
92
|
- bin/ftty
|
93
93
|
- lib/footty.rb
|
94
|
+
- lib/footty/datalib.rb
|
94
95
|
- lib/footty/dataset.rb
|
96
|
+
- lib/footty/openfootball.rb
|
97
|
+
- lib/footty/print.rb
|
95
98
|
- lib/footty/version.rb
|
96
99
|
homepage: https://github.com/sportdb/footty
|
97
100
|
licenses:
|
@@ -114,7 +117,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
114
117
|
- !ruby/object:Gem::Version
|
115
118
|
version: '0'
|
116
119
|
requirements: []
|
117
|
-
rubygems_version: 3.
|
120
|
+
rubygems_version: 3.5.22
|
118
121
|
signing_key:
|
119
122
|
specification_version: 4
|
120
123
|
summary: footty - football.db command line tool for national & int'l football club
|