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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2254b1fa1cf0832e403b6bc0e6509d1ffee06e0f552fec2b805b124c0a867c0d
4
- data.tar.gz: 8c8182dabe0e72801b580e46bed8471baacd01080fedac360b2532b63673d9cb
3
+ metadata.gz: e3101d3bc2cbc21aae12dcc1a108f7972211d55c86415a06c48e0a8db945ac3f
4
+ data.tar.gz: afaa93ffb303fd4205acdaa1aee1c80ec810a7bf723dabb99960025edb03076b
5
5
  SHA512:
6
- metadata.gz: 3e7421b9f2bf8ec572a55075970301343604a4dbaaabfeceec10827df9a712184fa9372a72c34e0f79811a4ae47b8a773115916e0e195d46def89a9e509b509d
7
- data.tar.gz: 4ef82377e9caead4e3ec0048b8eac6120fe77c8de4018e69e74faec2842f33909626234a5bc2ccef137661f999c7825f9ef834c64cab292bcf4e9c357467d645
6
+ metadata.gz: 8d23098b1142d238c5f18bec351b6aff504682cb21e724df04dace6a9e40294267463037313c0fafae9a51e46ae80f88e976b681fb6d79fd51832282610957b7
7
+ data.tar.gz: 3906e217d2f73536b52295c6502eed1b96af0d4cd436a331c075ecc1780ab8c005ebbad260ef3856a70009cd5a4befd037f700e4a3f1d050b7bc3842b70a5b90
data/CHANGELOG.md CHANGED
@@ -1,4 +1,4 @@
1
- ### 2024.9.27
1
+ ### 2025.4.9
2
2
  ### 1.0.0 / 2018-06-09
3
3
 
4
4
  * Everything is new (again). First release.
data/Manifest.txt CHANGED
@@ -5,5 +5,8 @@ Rakefile
5
5
  bin/footty
6
6
  bin/ftty
7
7
  lib/footty.rb
8
+ lib/footty/datalib.rb
8
9
  lib/footty/dataset.rb
10
+ lib/footty/openfootball.rb
11
+ lib/footty/print.rb
9
12
  lib/footty/version.rb
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 English Premier League 2024/25 matches
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 `tomorrow` or `t` or `+1` to print tomorrow's matches e.g.:
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 `yesterday` or `y` or `-1` to print yesterday's matches e.g.:
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 `upcoming` or `up` or `u` to print all upcoming matches e.g.:
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 `past` or `p` to print all past matches e.g.:
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 Germand Bundesliga, Spanish La Liga, etc:
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/dataset](https://github.com/sportdb/footty/blob/master/lib/footty/dataset.rb) for the complete built-in list of data sources (and league keys).
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
@@ -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 openfootball_url( path, year: )
73
- repo, local_path = path.split( '/', 2)
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
- ### note:
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
@@ -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
@@ -1,6 +1,6 @@
1
1
 
2
2
  module Footty
3
- VERSION = '2024.9.27'
3
+ VERSION = '2025.4.9'
4
4
 
5
5
  def self.banner
6
6
  "footty/#{VERSION} on Ruby #{RUBY_VERSION} (#{RUBY_RELEASE_DATE}) [#{RUBY_PLATFORM}] in (#{root})"
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 it always go first
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
- leagues = Dataset.leagues ## e.g. ['world','euro',
24
- ## 'de','en','at']
30
+ opts = { debug: false,
31
+ verbose: false, ## add more details
32
+ ## add cache/cache_dir - why? why not?
25
33
 
26
- ## filter args for league or year
27
- args = args.select do |arg|
28
- if arg =~ /^\d{4}$/
29
- year = arg.to_i(10)
30
- false ## eat-up
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
- what = args[0] || 'today'
41
- what = what.downcase
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
- if ['yesterday', 'y', '-1'].include?( what )
52
- matches = dataset.yesterdays_matches
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
- elsif ['tomorrow', 't', '+1', '1'].include?( what )
57
- matches = dataset.tomorrows_matches
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
- elsif ['past', 'p', 'prev'].include?( what )
62
- matches = dataset.past_matches
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
- elsif ['upcoming', 'up', 'u', 'next', 'n'].include?( what )
67
- matches = dataset.upcoming_matches
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
- print_matches( matches )
91
- end
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
- def self.print_matches( matches )
95
- ## print games
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
- if match['goals1'] && match['goals2']
160
- print " ["
161
- match['goals1'].each_with_index do |goal,i|
162
- print " " if i > 0
163
- print "#{goal['name']}"
164
- print " #{goal['minute']}"
165
- print "+#{goal['offset']}" if goal['offset']
166
- print "'"
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
- match['goals2'].each_with_index do |goal,i|
171
- if i == 0
172
- print "; "
173
- else
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
- print "]\n"
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: 2024.9.27
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: 2024-09-27 00:00:00.000000000 Z
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.1'
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.1'
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.4.10
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