footty 2024.8.28 → 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: 2866ba25c1b7a6d08787aaff2bff8821bbc92dada92bcc85ad084b65ceace8b9
4
- data.tar.gz: bad3de36a89de81e96565aa3c5c245d5cc53b56ea2faddfae38a0e8810f565e2
3
+ metadata.gz: e3101d3bc2cbc21aae12dcc1a108f7972211d55c86415a06c48e0a8db945ac3f
4
+ data.tar.gz: afaa93ffb303fd4205acdaa1aee1c80ec810a7bf723dabb99960025edb03076b
5
5
  SHA512:
6
- metadata.gz: fabcae4fbe59527bfb4dc35555d0ffa87271240a8ee385a6ea26842d3d268d58b4d6109ebee9159c362b1327447e5d36fae63b2e51f07931d32e5578103d7a70
7
- data.tar.gz: df46117a0ab611d90315a17525308f71bd82aac6b00c2a07388e115d92381efdf2f8d339e98311278a80b8b68e3f83283b2eaef3d9ee7ab2c01f753a252d90c5
6
+ metadata.gz: 8d23098b1142d238c5f18bec351b6aff504682cb21e724df04dace6a9e40294267463037313c0fafae9a51e46ae80f88e976b681fb6d79fd51832282610957b7
7
+ data.tar.gz: 3906e217d2f73536b52295c6502eed1b96af0d4cd436a331c075ecc1780ab8c005ebbad260ef3856a70009cd5a4befd037f700e4a3f1d050b7bc3842b70a5b90
data/CHANGELOG.md CHANGED
@@ -1,4 +1,4 @@
1
- ### 2024.8.28
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,5 @@
1
- # footty - football.db command line client for european ("euro") championship 2024 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
  * home :: [github.com/sportdb/footty](https://github.com/sportdb/footty)
4
5
  * bugs :: [github.com/sportdb/footty/issues](https://github.com/sportdb/footty/issues)
@@ -11,39 +12,78 @@
11
12
 
12
13
  ## Usage - Who's playing today?
13
14
 
14
- The footty command line tool lets you query the online football.db HTTP JSON API services
15
+ The footty (or ftty) command line tool lets you query the online football.db via HTTP
15
16
  for upcoming or past matches. For example:
16
17
 
17
- $ footty # Defaults to today's euro 2024 matches
18
+ $ footty # Defaults to today's matches of top leagues
19
+
20
+
21
+ prints on Sep 27, 2024:
22
+
23
+ ==> English Premier League 2024/25
24
+ Upcoming matches:
25
+ Sat Sep/28 12:30 (in 1d) Newcastle United FC vs Manchester City FC Matchday 6
26
+ Sat Sep/28 15:00 (in 1d) Arsenal FC vs Leicester City FC Matchday 6
27
+ Sat Sep/28 15:00 (in 1d) Brentford FC vs West Ham United FC Matchday 6
28
+ Sat Sep/28 15:00 (in 1d) Chelsea FC vs Brighton & Hove Albion Matchday 6
29
+ Sat Sep/28 15:00 (in 1d) Everton FC vs Crystal Palace FC Matchday 6
30
+ Sat Sep/28 15:00 (in 1d) Nottingham Forest FC vs Fulham FC Matchday 6
31
+ Sat Sep/28 17:30 (in 1d) Wolverhampton Wanderers FC vs Liverpool FC Matchday 6
32
+ Sun Sep/29 14:00 (in 2d) Ipswich Town FC vs Aston Villa FC Matchday 6
33
+ Sun Sep/29 16:30 (in 2d) Manchester United FC vs Tottenham Hotspur FC Matchday 6
34
+ Mon Sep/30 20:00 (in 3d) AFC Bournemouth vs Southampton FC Matchday 6
35
+
18
36
 
19
- prints on Jun/14 2024:
20
37
 
21
- #1 Fri Jun/14 Germany (GER) vs Scotland (SCO) Group A / Matchday 1
22
-
23
- 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.:
24
39
 
25
- $ footty tomorrow # -or-
26
- $ footty t
40
+ $ footty --tomorrow # -or-
41
+ $ footty -t
27
42
 
28
- 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.:
29
44
 
30
- $ footty yesterday # -or-
31
- $ footty y
45
+ $ footty --yesterday # -or-
46
+ $ footty -y
32
47
 
33
- 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.:
34
49
 
35
- $ footty upcoming # -or-
36
- $ footty up
50
+ $ footty --upcoming # -or-
51
+ $ footty --up
37
52
 
38
- Use `past` or `p` to print all past matches e.g.:
53
+ Use `--past` or `-p` to print all past matches e.g.:
54
+
55
+ $ footty --past # -or-
56
+ $ footty -p
39
57
 
40
- $ footty past # -or-
41
- $ footty p
42
58
 
43
59
 
44
60
  That's it. Enjoy the beautiful game.
45
61
 
46
62
 
63
+ ## Bonus - More Leagues & Cups - Bundesliga, Serie A, Ligue 1, La Liga & More
64
+
65
+ Pass in the league code to display the German Bundesliga, Spanish La Liga, etc:
66
+
67
+ $ footty de # who's playing in the bundesliga today?
68
+ $ footty es # who's playing in la liga today?
69
+ ...
70
+
71
+ League codes include:
72
+
73
+ - `de` => Bundesliga
74
+ - `es` => La Liga
75
+ - `it` => Serie A
76
+ - `fr` => Ligue 1
77
+ - ...
78
+
79
+ More
80
+
81
+ - `world` => World Cup
82
+ - `euro` => "Euro" - European Championship
83
+
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).
85
+
86
+
47
87
 
48
88
 
49
89
  ## Trivia
@@ -66,7 +106,6 @@ Use it as you please with no restrictions whatsoever.
66
106
 
67
107
 
68
108
 
69
-
70
109
  ## Questions? Comments?
71
110
 
72
111
  Yes, you can. More than welcome.
data/Rakefile CHANGED
@@ -2,23 +2,23 @@ require 'hoe'
2
2
  require './lib/footty/version.rb'
3
3
 
4
4
  Hoe.spec 'footty' do
5
-
6
5
  self.version = Footty::VERSION
7
6
 
8
- self.summary = 'footty - football.db command line client for european "euro" championship 2024 and more - who is playing today?'
7
+ self.summary = "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)"
9
8
  self.description = summary
10
9
 
11
10
  self.urls = { home: 'https://github.com/sportdb/footty' }
12
11
 
13
12
  self.author = 'Gerald Bauer'
14
- self.email = 'gerald.bauer@gmail.com'
13
+ self.email = 'gerald.bauer@gmail.com'
15
14
 
16
15
  # switch extension to .markdown for gihub formatting
17
16
  self.readme_file = 'README.md'
18
17
  self.history_file = 'CHANGELOG.md'
19
18
 
20
19
  self.extra_deps = [
21
- ['sportdb-quick']
20
+ ['sportdb-quick', '>= 0.2.0'],
21
+ ['webget'],
22
22
  ]
23
23
 
24
24
  self.licenses = ['Public Domain']
@@ -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
@@ -1,49 +1,22 @@
1
1
  module Footty
2
2
 
3
3
 
4
-
5
4
  class Dataset
6
5
 
7
- def initialize( league:, year: )
8
- @league = league
9
- @year = year
10
- @season = Season( "#{year}/#{year+1}" )
11
- end
12
-
13
6
 
14
- APIS = {
15
- world: 'https://raw.githubusercontent.com/openfootball/worldcup.json/master/$year$/worldcup.json',
16
- euro: 'https://raw.githubusercontent.com/openfootball/euro.json/master/$year$/euro.json',
17
- de: 'https://raw.githubusercontent.com/openfootball/deutschland/master/$season$/1-bundesliga.txt',
18
- en: 'https://raw.githubusercontent.com/openfootball/england/master/$season$/1-premierleague.txt',
19
- at: 'https://raw.githubusercontent.com/openfootball/austria/master/$season$/1-bundesliga-i.txt',
20
- }
21
-
22
- ### note:
23
- ## cache ALL methods - only do one web request for match schedule & results
24
7
  def matches
25
- @data ||= begin
26
- url = APIS[ @league.downcase.to_sym ]
27
- url = url.gsub( '$year$', @year.to_s )
28
- url = url.gsub( '$season$', @season.to_path )
29
-
30
- res = get!( url ) ## use "memoized" / cached result
31
- if url.end_with?( '.json' )
32
- JSON.parse( res.text )
33
- else ## assume football.txt format
34
- matches = SportDb::QuickMatchReader.parse( res.text )
35
- data = matches.map {|match| match.as_json } # convert to json
36
- ## quick hack to get keys as strings not symbols!!
37
- ## fix upstream
38
- JSON.parse( JSON.generate( data ))
39
- end
40
- end
8
+ raise ArgumentError, "method matches must be implemented by concrete class!!"
41
9
  end
42
10
 
11
+ def league_name
12
+ raise ArgumentError, "method league_name must be implemented by concrete class!!"
13
+ end
14
+
15
+
43
16
  def end_date
44
17
  @end_date ||= begin
45
18
  end_date = nil
46
- each do |match|
19
+ matches.each do |match|
47
20
  date = Date.strptime(match['date'], '%Y-%m-%d' )
48
21
  end_date = date if end_date.nil? ||
49
22
  date > end_date
@@ -55,7 +28,7 @@ module Footty
55
28
  def start_date
56
29
  @start_date ||= begin
57
30
  start_date = nil
58
- each do |match|
31
+ matches.each do |match|
59
32
  date = Date.strptime(match['date'], '%Y-%m-%d' )
60
33
  start_date = date if start_date.nil? ||
61
34
  date < start_date
@@ -89,54 +62,21 @@ module Footty
89
62
  def past_matches( date: Date.today )
90
63
  matches = select_matches { |match| date > Date.parse( match['date'] ) }
91
64
  ## note reveserve matches (chronological order/last first)
92
- matches.reverse
65
+ ## matches.reverse
66
+ matches
93
67
  end
94
68
 
95
69
 
96
70
  private
97
- def each
98
- data = matches
99
- if data.is_a?( Hash) && data.has_key?('rounds')
100
- data['rounds'].each do |round|
101
- round['matches'].each do |match|
102
- ## hack: add (outer) round to match
103
- match['round'] = round['name']
104
- yield( match )
105
- end
106
- end
107
- else ## assume flat matches in Array
108
- data.each do |match|
109
- yield( match )
110
- end
111
- end
112
- end
113
-
114
-
115
- def select_matches
116
- matches = []
117
- each do |match|
118
- matches << match if yield( match )
71
+ def select_matches( &blk)
72
+ selected = []
73
+ matches.each do |match|
74
+ selected << match if blk.call( match )
119
75
  end
120
76
 
121
77
  ## todo/fix:
122
78
  ## sort matches here; might not be chronologicial (by default)
123
- matches
79
+ selected
124
80
  end # method select_matches
125
-
126
-
127
- def get!( url )
128
- response = Webclient.get( url )
129
-
130
- if response.status.ok?
131
- response
132
- else
133
- puts "!! HTTP ERROR - #{response.status.code} #{response.status.message}"
134
- ## dump headers
135
- response.headers.each do |key,value|
136
- puts " #{key}: #{value}"
137
- end
138
- exit 1
139
- end
140
- end # method get!
141
- end # class Client
81
+ end # class Dataset
142
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.8.28'
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,187 +1,189 @@
1
1
  require 'sportdb/quick' ## note - pulls in cocos et al
2
+ require 'webget' ## add webcache support
2
3
 
4
+ require 'optparse'
3
5
 
4
- # our own code
5
- require_relative 'footty/version' # let it always go first
6
- require_relative 'footty/dataset'
7
6
 
8
7
 
9
- module Footty
10
8
 
11
- def self.main
12
- puts banner # say hello
9
+ # our own code
10
+ require_relative 'footty/version' # let version always go first
11
+ require_relative 'footty/dataset'
12
+ require_relative 'footty/openfootball'
13
+ ## require_relative 'footty/datalib'
13
14
 
14
- league = 'en'
15
- year = 2024
15
+ require_relative 'footty/print'
16
16
 
17
- ## filter ARGV for league or year
18
- args = ARGV.select do |arg|
19
- if arg =~ /^\d{4}$/
20
- year = arg.to_i(10)
21
- false ## eat-up
22
- elsif ['world',
23
- 'euro',
24
- 'de',
25
- 'en',
26
- 'at'].include?( arg )
27
- league = arg
28
- false
29
- else
30
- true
31
- end
32
- end
33
17
 
34
18
 
35
- what = args[0] || 'today'
36
- what = what.downcase
19
+ ## set cache to local .cache dir for now - why? why not?
20
+ Webcache.root = './cache'
21
+ # pp Webcache.root
22
+ Webget.config.sleep = 1 ## set delay in secs (to 1 sec - default is/maybe 3)
37
23
 
38
24
 
39
- # Dataset.new( league: 'euro', year: 2024 )
40
- dataset = Dataset.new( league: league, year: year )
25
+ module Footty
26
+ def self.main( args=ARGV )
27
+ puts banner # say hello
41
28
 
42
29
 
43
- ## in the future make today "configurable" as param - why? why not?
44
- today = Date.today
30
+ opts = { debug: false,
31
+ verbose: false, ## add more details
32
+ ## add cache/cache_dir - why? why not?
45
33
 
46
- if ['yesterday', 'y', '-1'].include?( what )
47
- matches = dataset.yesterdays_matches
48
- if matches.empty?
49
- puts "** No matches played yesterday.\n"
50
- end
51
- elsif ['tomorrow', 't', '+1', '1'].include?( what )
52
- matches = dataset.tomorrows_matches
53
- if matches.empty?
54
- puts "** No matches scheduled tomorrow.\n"
55
- end
56
- elsif ['past', 'p', 'prev'].include?( what )
57
- matches = dataset.past_matches
58
- if matches.empty?
59
- puts "** No matches played yet.\n"
60
- end
61
- elsif ['upcoming', 'up', 'u', 'next', 'n'].include?( what )
62
- matches = dataset.upcoming_matches
63
- if matches.empty?
64
- puts "** No more matches scheduled.\n"
65
- end
66
- else
67
- matches = dataset.todays_matches
68
-
69
- ## no matches today
70
- if matches.empty?
71
- puts "** No matches scheduled today.\n"
72
-
73
- ## note: was world cup 2018 - end date -- Date.new( 2018, 7, 11 )
74
- ## note: was euro 2020 (in 2021) - end date -- Date.new( 2021, 7, 11 )
75
- if Date.today > dataset.end_date ## tournament is over, look back
76
- puts "Past matches:"
77
- matches = dataset.past_matches
78
- else ## world cup is upcoming /in-progress,look forward
79
- puts "Upcoming matches:"
80
- matches = dataset.upcoming_matches( limit: 18 )
81
- end
82
- end
83
- end
34
+ ## display format/mode - week/window/upcoming/past (default is today)
35
+ yesterday: nil,
36
+ tomorrow: nil,
37
+ upcoming: nil,
38
+ past: nil,
84
39
 
85
- print_matches( matches )
86
- end
40
+ # week: nil,
41
+ # window: nil, ## 2 day plus/minus +2/-2
42
+ }
87
43
 
44
+
45
+ parser = OptionParser.new do |parser|
46
+ parser.banner = "Usage: #{$PROGRAM_NAME} [options] LEAGUES"
88
47
 
89
- def self.print_matches( matches )
90
- ## print games
91
-
92
- today = Date.today
48
+ parser.on( "--verbose",
49
+ "turn on verbose output (default: #{opts[:verbose]})" ) do |verbose|
50
+ opts[:verbose] = true
51
+ end
93
52
 
94
- matches.each do |match|
95
- print " %5s" % "\##{match['num']} " if match['num']
96
53
 
97
- date = Date.strptime( match['date'], '%Y-%m-%d' )
98
- print "#{date.strftime('%a %b/%d')} " ## e.g. Thu Jun/14
99
- print "#{match['time']} " if match['time']
100
-
101
- if date > today
102
- diff = (date - today).to_i
103
- print "%10s" % "(in #{diff}d) "
54
+ parser.on( "-y", "--yesterday" ) do |yesterday|
55
+ opts[:yesterday] = true
104
56
  end
105
-
106
-
107
- if match['team1'].is_a?( Hash )
108
- print "%22s" % "#{match['team1']['name']} (#{match['team1']['code']})"
109
- else
110
- print "%22s" % "#{match['team1']}"
57
+ parser.on( "-t", "--tomorrow" ) do |tomorrow|
58
+ opts[:tomorrow] = true
111
59
  end
112
-
113
-
114
- if match['score'].is_a?( Hash ) &&
115
- match['score']['ft']
116
- if match['score']['ft']
117
- print " #{match['score']['ft'][0]}-#{match['score']['ft'][1]} "
118
- end
119
- if match['score']['et']
120
- print "aet #{match['score']['et'][0]}-#{match['score']['et'][1]} "
121
- end
122
- if match['score']['p']
123
- print "pen #{match['score']['p'][0]}-#{match['score']['p'][1]} "
124
- end
125
- elsif match['score1'] && match['score2']
126
- ## todo/fix: add support for knockout scores
127
- ## with score1et/score1p (extra time and penalty)
128
- print " #{match['score1']}-#{match['score2']} "
129
- print "(#{match['score1i']}-#{match['score2i']}) "
130
- else
131
- print " vs "
60
+ parser.on( "-p", "--past" ) do |past|
61
+ opts[:past] = true
132
62
  end
133
-
134
- if match['team2'].is_a?( Hash )
135
- print "%-22s" % "#{match['team2']['name']} (#{match['team2']['code']})"
136
- else
137
- print "%-22s" % "#{match['team2']}"
63
+ parser.on( "-u", "--up", "--upcoming" ) do |upcoming|
64
+ opts[:upcoming] = true
138
65
  end
66
+ 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
+
139
110
 
140
111
 
141
- if match['group']
142
- print " #{match['group']} /" ## group phase/stage
143
- end
112
+ # Dataset.new( league: 'euro', year: 2024 )
113
+ # dataset = Dataset.new( league: league, year: year )
144
114
 
145
- print " #{match['round']} " ## knock out (k.o.) phase/stage
115
+ ## in the future make today "configurable" as param - why? why not?
116
+ today = Date.today
146
117
 
147
- if match['stadium']
148
- print " @ #{match['stadium']['name']}, #{match['city']}"
149
- end
150
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)"
151
138
  print "\n"
152
139
 
153
-
154
- if match['goals1'] && match['goals2']
155
- print " ["
156
- match['goals1'].each_with_index do |goal,i|
157
- print " " if i > 0
158
- print "#{goal['name']}"
159
- print " #{goal['minute']}"
160
- print "+#{goal['offset']}" if goal['offset']
161
- print "'"
162
- print " (o.g.)" if goal['owngoal']
163
- 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"
164
149
  end
165
- match['goals2'].each_with_index do |goal,i|
166
- if i == 0
167
- print "; "
168
- else
169
- print " "
170
- end
171
- print "#{goal['name']}"
172
- print " #{goal['minute']}"
173
- print "+#{goal['offset']}" if goal['offset']
174
- print "'"
175
- print " (o.g.)" if goal['owngoal']
176
- 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"
177
154
  end
178
- 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 )
179
181
  end
180
- end
181
- end
182
182
 
183
+ end # method self.main
183
184
  end # module Footty
184
185
 
185
186
 
186
187
 
188
+
187
189
  Footty.main if __FILE__ == $0
metadata CHANGED
@@ -1,17 +1,31 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: footty
3
3
  version: !ruby/object:Gem::Version
4
- version: 2024.8.28
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-08-28 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
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 0.2.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 0.2.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: webget
15
29
  requirement: !ruby/object:Gem::Requirement
16
30
  requirements:
17
31
  - - ">="
@@ -50,16 +64,16 @@ dependencies:
50
64
  requirements:
51
65
  - - "~>"
52
66
  - !ruby/object:Gem::Version
53
- version: '4.1'
67
+ version: '4.2'
54
68
  type: :development
55
69
  prerelease: false
56
70
  version_requirements: !ruby/object:Gem::Requirement
57
71
  requirements:
58
72
  - - "~>"
59
73
  - !ruby/object:Gem::Version
60
- version: '4.1'
61
- description: footty - football.db command line client for european "euro" championship
62
- 2024 and more - who is playing today?
74
+ version: '4.2'
75
+ description: footty - football.db command line tool for national & int'l football
76
+ club leagues (& cups) from around the world (bonus - incl. world cup, euro and more)
63
77
  email: gerald.bauer@gmail.com
64
78
  executables:
65
79
  - footty
@@ -77,7 +91,10 @@ files:
77
91
  - bin/footty
78
92
  - bin/ftty
79
93
  - lib/footty.rb
94
+ - lib/footty/datalib.rb
80
95
  - lib/footty/dataset.rb
96
+ - lib/footty/openfootball.rb
97
+ - lib/footty/print.rb
81
98
  - lib/footty/version.rb
82
99
  homepage: https://github.com/sportdb/footty
83
100
  licenses:
@@ -100,9 +117,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
100
117
  - !ruby/object:Gem::Version
101
118
  version: '0'
102
119
  requirements: []
103
- rubygems_version: 3.4.10
120
+ rubygems_version: 3.5.22
104
121
  signing_key:
105
122
  specification_version: 4
106
- summary: footty - football.db command line client for european "euro" championship
107
- 2024 and more - who is playing today?
123
+ summary: footty - football.db command line tool for national & int'l football club
124
+ leagues (& cups) from around the world (bonus - incl. world cup, euro and more)
108
125
  test_files: []