sports 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.
data/lib/sports/season.rb DELETED
@@ -1,199 +0,0 @@
1
-
2
- ### note: make Season like Date a "top-level" / "generic" class
3
-
4
-
5
- class Season
6
- ##
7
- ## todo: add (optional) start_date and end_date - why? why not?
8
-
9
- ## todo/todo/todo/check/check/check !!!
10
- ## todo: add a kernel Seaons e.g. Season('2011/12')
11
- ## forward to Season.convert( *args ) - why? why not?
12
-
13
- ## todo: add unicode - too - why? why not? see wikipedia pages, for example
14
-
15
- YYYY_YYYY_RE = %r{^ ## e.g. 2011-2012 or 2011/2012
16
- (\d{4})
17
- [/-]
18
- (\d{4})
19
- $
20
- }x
21
- YYYY_YY_RE = %r{^ ## e.g. 2011-12 or 2011/12
22
- (\d{4})
23
- [/-]
24
- (\d{2})
25
- $
26
- }x
27
- YYYY_Y_RE = %r{^ ## e.g. 2011-2 or 2011/2
28
- (\d{4})
29
- [/-]
30
- (\d{1})
31
- $
32
- }x
33
- YYYY_RE = %r{^ ## e.g. 2011
34
- (\d{4})
35
- $
36
- }x
37
-
38
-
39
- def self.parse( str )
40
- new( *_parse( str ))
41
- end
42
-
43
- def self._parse( str ) ## "internal" parse helper
44
- if str =~ YYYY_YYYY_RE ## e.g. 2011/2012
45
- [$1.to_i, $2.to_i]
46
- elsif str =~ YYYY_YY_RE ## e.g. 2011/12
47
- fst = $1.to_i
48
- snd = $2.to_i
49
- snd_exp = '%02d' % [(fst+1) % 100] ## double check: e.g 00 == 00, 01==01 etc.
50
- raise ArgumentError, "[Season.parse] invalid year in season >>#{str}<<; expected #{snd_exp} but got #{$2}" if snd_exp != $2
51
- [fst, fst+1]
52
- elsif str =~ YYYY_Y_RE ## e.g. 2011/2
53
- fst = $1.to_i
54
- snd = $2.to_i
55
- snd_exp = '%d' % [(fst+1) % 10] ## double check: e.g 0 == 0, 1==1 etc.
56
- raise ArgumentError, "[Season.parse] invalid year in season >>#{str}<<; expected #{snd_exp} but got #{$2}" if snd_exp != $2
57
- [fst, fst+1]
58
- elsif str =~ YYYY_RE ## e.g. 2011
59
- [$1.to_i]
60
- else
61
- raise ArgumentError, "[Season.parse] unkown season format >>#{str}<<; sorry cannot parse"
62
- end
63
- end
64
-
65
-
66
- def self.convert( *args ) ## note: used by Kernel method Season()
67
- if args.size == 1 && args[0].is_a?( Season )
68
- args[0] # pass through / along as is 1:1
69
- elsif args.size == 1 && args[0].is_a?( String )
70
- parse( args[0] )
71
- elsif args.size == 1 && args[0].is_a?( Integer ) && args[0] > 9999
72
- ## note: allow convenience "hack" such as:
73
- # 202021 or 2020_21 => '2020/21' or
74
- # 2020_1 or 2020_1 => '2020/21' or
75
- # 20202021 or 2020_2021 => '2020/21'
76
- str = args[0].to_s
77
- parse( "#{str[0..3]}/#{str[4..-1]}" )
78
- else ## assume all integer args e.g. 2020 or 2020, 2021 and such
79
- new( *args ) ## try conversion with new
80
- end
81
- end
82
-
83
-
84
- attr_reader :start_year,
85
- :end_year
86
-
87
- def initialize( *args ) ## change args to years - why? why not?
88
- if args.size == 1 && args[0].is_a?( Integer )
89
- @start_year = args[0]
90
- @end_year = args[0]
91
- elsif args.size == 2 && args[0].is_a?( Integer ) &&
92
- args[1].is_a?( Integer )
93
- @start_year = args[0]
94
- @end_year = args[1]
95
- end_year_exp = @start_year+1
96
- raise ArgumentError, "[Season] invalid year in season >>#{to_s}<<; expected #{end_year_exp} but got #{@end_year}" if end_year_exp != @end_year
97
- else
98
- pp args
99
- raise ArgumentError, "[Season] expected season start year (integer) with opt. end year"
100
- end
101
- end
102
-
103
-
104
-
105
- ## convenience helper - move to sportdb or such - remove - why - why not???
106
- def start_date ## generate "generic / syntetic start date" - keep helper - why? why not?
107
- if year?
108
- Date.new( start_year, 1, 1 )
109
- else
110
- Date.new( start_year 1, 7 )
111
- end
112
- end
113
-
114
-
115
- ## single-year season e.g. 2011 if start_year is end_year - todo - find a better name?
116
- def year?() @start_year == @end_year; end
117
-
118
- def prev
119
- if year?
120
- Season.new( @start_year-1 )
121
- else
122
- Season.new( @start_year-1, @end_year-1 )
123
- end
124
- end
125
-
126
- def next
127
- if year?
128
- Season.new( @start_year+1 )
129
- else
130
- Season.new( @start_year+1, @end_year+1 )
131
- end
132
- end
133
- alias_method :succ, :next ## add support for ranges
134
-
135
-
136
- include Comparable
137
- def <=>(other)
138
- ## todo/fix/fix: check if other is_a?( Season )!!!
139
- ## what to return if other type/class ??
140
- ## note: check special edge case - year season and other e.g.
141
- ## 2010 <=> 2010/2011
142
-
143
- res = @start_year <=> other.start_year
144
- res = @end_year <=> other.end_year if res == 0
145
- res
146
- end
147
-
148
-
149
- def to_formatted_s( format=:default, sep: '/' )
150
- if year?
151
- '%d' % @start_year
152
- else
153
- case format
154
- when :default, :short, :s ## e.g. 1999/00 or 2019/20
155
- "%d#{sep}%02d" % [@start_year, @end_year % 100]
156
- when :long, :l ## e.g. 1999/2000 or 2019/2020
157
- "%d#{sep}%d" % [@start_year, @end_year]
158
- else
159
- raise ArgumentError, "[Season.to_s] unsupported format >#{format}<"
160
- end
161
- end
162
- end
163
- alias_method :to_s, :to_formatted_s
164
-
165
- def key() to_s( :short ); end
166
- alias_method :to_key, :key
167
- alias_method :name, :key
168
- alias_method :title, :key
169
-
170
- alias_method :inspect, :key ## note: add inspect debug support change debug output to string!!
171
-
172
-
173
-
174
- def to_path( format=:default )
175
- case format
176
- when :default, :short, :s ## e.g. 1999-00 or 2019-20
177
- to_s( :short, sep: '-' )
178
- when :long, :l ## e.g. 1999-2000 or 2019-2000
179
- to_s( :long, sep: '-' )
180
- when :archive, :decade, :d ## e.g. 1990s/1999-00 or 2010s/2019-20
181
- "%3d0s/%s" % [@start_year / 10, to_s( :short, sep: '-' )]
182
- when :century, :c ## e.g. 1900s/1990-00 or 2000s/2019-20
183
- "%2d00s/%s" % [@start_year / 100, to_s( :short, sep: '-' )]
184
- else
185
- raise ArgumentError, "[Season.to_path] unsupported format >#{format}<"
186
- end
187
- end # method to_path
188
- alias_method :directory, :to_path ## keep "legacy" directory alias - why? why not?
189
- alias_method :path, :to_path
190
-
191
- end # class Season
192
-
193
-
194
-
195
- ### note: add a convenience "shortcut" season kernel method conversion method
196
- ## use like Season( '2012/3' ) or such
197
- module Kernel
198
- def Season( *args ) Season.convert( *args ); end
199
- end
@@ -1,26 +0,0 @@
1
- module Sports
2
-
3
- ##
4
- # note: check that shape/structure/fields/attributes match
5
- # the ActiveRecord model !!!!
6
-
7
- class Country
8
-
9
- ## note: is read-only/immutable for now - why? why not?
10
- ## add cities (array/list) - why? why not?
11
- attr_reader :key, :name, :code, :tags
12
- attr_accessor :alt_names
13
-
14
- def initialize( key: nil, name:, code:, tags: [] )
15
- ## note: auto-generate key "on-the-fly" if missing for now - why? why not?
16
- ## note: quick hack - auto-generate key, that is, remove all non-ascii chars and downcase
17
- @key = key || name.downcase.gsub( /[^a-z]/, '' )
18
- @name, @code = name, code
19
- @alt_names = []
20
- @tags = tags
21
- end
22
-
23
- end # class Country
24
-
25
- end # module Sports
26
-
@@ -1,231 +0,0 @@
1
-
2
- module Sports
3
-
4
-
5
- ## "free-standing" goal event - for import/export in separate event / goal datafiles
6
- ## returned by CsvGoalParser and others
7
- class GoalEvent
8
-
9
- def self.build( row ) ## rename to parse or such - why? why not?
10
-
11
- ## split match_id
12
- team_str, more_str = row['Match'].split( '|' )
13
- team1_str, team2_str = team_str.split( ' - ' )
14
-
15
- more_str = more_str.strip
16
- team1_str = team1_str.strip
17
- team2_str = team2_str.strip
18
-
19
- # check if more_str is a date otherwise assume round
20
- date_fmt = if more_str =~ /^[A-Z]{3} [0-9]{1,2}$/i ## Apr 4
21
- '%b %d'
22
- elsif more_str =~ /^[A-Z]{3} [0-9]{1,2} [0-9]{4}$/i ## Apr 4 2019
23
- '%b %d %Y'
24
- else
25
- nil
26
- end
27
-
28
- if date_fmt
29
- date = Date.strptime( more_str, date_fmt )
30
- round = nil
31
- else
32
- date = nil
33
- round = more_str
34
- end
35
-
36
-
37
- values = row['Score'].split('-')
38
- values = values.map { |value| value.strip }
39
- score1 = values[0].to_i
40
- score2 = values[1].to_i
41
-
42
- minute = nil
43
- offset = nil
44
- if m=%r{([0-9]+)
45
- (?:[ ]+
46
- \+([0-9]+)
47
- )?
48
- ['.]
49
- $}x.match( row['Minute'])
50
- minute = m[1].to_i
51
- offset = m[2] ? m[2].to_i : nil
52
- else
53
- puts "!! ERROR - unsupported minute (goal) format >#{row['Minute']}<"
54
- exit 1
55
- end
56
-
57
- attributes = {
58
- team1: team1_str,
59
- team2: team2_str,
60
- date: date,
61
- round: round,
62
- score1: score1,
63
- score2: score2,
64
- minute: minute,
65
- offset: offset,
66
- player: row['Player'],
67
- owngoal: ['(og)', '(o.g.)'].include?( row['Extra']),
68
- penalty: ['(pen)', '(pen.)'].include?( row['Extra']),
69
- notes: (row['Notes'].nil? || row['Notes'].empty?) ? nil : row['Notes']
70
- }
71
-
72
- new( **attributes )
73
- end
74
-
75
-
76
- ## match id
77
- attr_reader :team1,
78
- :team2,
79
- :round, ## optional
80
- :date ## optional
81
-
82
- ## main attributes
83
- attr_reader :score1,
84
- :score2,
85
- :player,
86
- :minute,
87
- :offset,
88
- :owngoal,
89
- :penalty,
90
- :notes
91
-
92
-
93
- ## todo/check: or just use match.hash or such if match mapping known - why? why not?
94
- def match_id
95
- if round
96
- "#{@team1} - #{@team2} | #{@round}"
97
- else
98
- "#{@team1} - #{@team2} | #{@date}"
99
- end
100
- end
101
-
102
-
103
- def owngoal?() @owngoal==true; end
104
- def penalty?() @penalty==true; end
105
-
106
- def initialize( team1:,
107
- team2:,
108
- round: nil,
109
- date: nil,
110
- score1:,
111
- score2:,
112
- player:,
113
- minute:,
114
- offset: nil,
115
- owngoal: false,
116
- penalty: false,
117
- notes: nil
118
- )
119
- @team1 = team1
120
- @team2 = team2
121
- @round = round
122
- @date = date
123
-
124
- @score1 = score1
125
- @score2 = score2
126
- @player = player
127
- @minute = minute
128
- @offset = offset
129
- @owngoal = owngoal
130
- @penalty = penalty
131
- @notes = notes
132
- end
133
-
134
-
135
- ## note: lets you use normalize teams or such acts like a Match struct
136
- def update( **kwargs )
137
- ## todo/fix: use team1_name, team2_name or similar - for compat with db activerecord version? why? why not?
138
- @team1 = kwargs[:team1] if kwargs.has_key? :team1
139
- @team2 = kwargs[:team2] if kwargs.has_key? :team2
140
- end
141
- end # class GoalEvent
142
-
143
-
144
-
145
-
146
- class Goal ### nested (non-freestanding) inside match (match is parent)
147
- def self.build( events ) ## check/todo - rename to build_from_event/row or such - why? why not?
148
- ## build an array of goal structs from (csv) recs
149
- recs = []
150
-
151
- last_score1 = 0
152
- last_score2 = 0
153
-
154
- events.each do |event|
155
-
156
- if last_score1+1 == event.score1 && last_score2 == event.score2
157
- team = 1
158
- elsif last_score2+1 == event.score2 && last_score1 == event.score1
159
- team = 2
160
- else
161
- puts "!! ERROR - unexpected score advance (one goal at a time expected):"
162
- puts " #{last_score1}-#{last_score2}=> #{event.score1}-#{event.score2}"
163
- exit 1
164
- end
165
-
166
- last_score1 = event.score1
167
- last_score2 = event.score2
168
-
169
-
170
- attributes = {
171
- score1: event.score1,
172
- score2: event.score2,
173
- team: team,
174
- minute: event.minute,
175
- offset: event.offset,
176
- player: event.player,
177
- owngoal: event.owngoal,
178
- penalty: event.penalty,
179
- notes: event.notes
180
- }
181
-
182
- recs << Goal.new( **attributes )
183
- end
184
-
185
- recs
186
- end
187
-
188
-
189
-
190
- attr_reader :score1,
191
- :score2,
192
- :team,
193
- :player,
194
- :minute,
195
- :offset,
196
- :owngoal,
197
- :penalty,
198
- :notes
199
-
200
-
201
-
202
- def owngoal?() @owngoal==true; end
203
- def penalty?() @penalty==true; end
204
- def team1?() @team == 1; end
205
- def team2?() @team == 2; end
206
-
207
- def initialize( score1:,
208
- score2:,
209
- team:,
210
- player:,
211
- minute:,
212
- offset: nil,
213
- owngoal: false,
214
- penalty: false,
215
- notes: nil
216
- )
217
- @score1 = score1
218
- @score2 = score2
219
- @team = team # 1 or 2
220
- @player = player
221
- @minute = minute
222
- @offset = offset
223
- @owngoal = owngoal
224
- @penalty = penalty
225
- @notes = notes
226
- end
227
- end # class Goal
228
-
229
-
230
- end # module Sports
231
-
@@ -1,16 +0,0 @@
1
- module Sports
2
-
3
- class Group
4
- attr_reader :key, :name, :teams
5
-
6
- def initialize( key: nil,
7
- name:,
8
- teams: )
9
- @key = key ## e.g. A,B,C or 1,2,3, - note: always a string or nil
10
- @name = name
11
- @teams = teams
12
- end
13
- end # class Group
14
-
15
- end # module Sports
16
-
@@ -1,35 +0,0 @@
1
-
2
- module Sports
3
-
4
-
5
- class League
6
- attr_reader :key, :name, :country, :intl
7
- attr_accessor :alt_names
8
-
9
- ## special import only attribs
10
- attr_accessor :alt_names_auto ## auto-generated alt names
11
-
12
- def initialize( key:, name:, alt_names: [], alt_names_auto: [],
13
- country: nil, intl: false, clubs: true )
14
- @key = key
15
- @name = name
16
- @alt_names = alt_names
17
- @alt_names_auto = alt_names_auto
18
-
19
- @country = country
20
- @intl = intl
21
- @clubs = clubs
22
- end
23
-
24
- def intl?() @intl == true; end
25
- def national?() @intl == false; end
26
- alias_method :domestic?, :national?
27
-
28
- def clubs?() @clubs == true; end
29
- def national_teams?() @clubs == false; end
30
- alias_method :club?, :clubs?
31
- alias_method :national_team?, :national_teams?
32
-
33
- end # class League
34
-
35
- end # module Sports
@@ -1,180 +0,0 @@
1
-
2
- module Sports
3
-
4
-
5
- class Match
6
-
7
- attr_reader :date,
8
- :time,
9
- :team1, :team2, ## todo/fix: use team1_name, team2_name or similar - for compat with db activerecord version? why? why not?
10
- :score1, :score2, ## full time
11
- :score1i, :score2i, ## half time (first (i) part)
12
- :score1et, :score2et, ## extra time
13
- :score1p, :score2p, ## penalty
14
- :score1agg, :score2agg, ## full time (all legs) aggregated
15
- :winner, # return 1,2,0 1 => team1, 2 => team2, 0 => draw/tie
16
- :round, ## todo/fix: use round_num or similar - for compat with db activerecord version? why? why not?
17
- :leg, ## e.g. '1','2','3','replay', etc. - use leg for marking **replay** too - keep/make leg numeric?! - why? why not?
18
- :stage,
19
- :group,
20
- :status, ## e.g. replay, cancelled, awarded, abadoned, postponed, etc.
21
- :conf1, :conf2, ## special case for mls e.g. conference1, conference2 (e.g. west, east, central)
22
- :country1, :country2, ## special case for champions league etc. - uses FIFA country code
23
- :comments,
24
- :league ## (optinal) added as text for now (use struct?)
25
-
26
-
27
- attr_accessor :goals ## todo/fix: make goals like all other attribs!!
28
-
29
- def initialize( **kwargs )
30
- @score1 = @score2 = nil ## full time
31
- @score1i = @score2i = nil ## half time (first (i) part)
32
- @score1et = @score2et = nil ## extra time
33
- @score1p = @score2p = nil ## penalty
34
- @score1agg = @score2agg = nil ## full time (all legs) aggregated
35
-
36
-
37
- update( kwargs ) unless kwargs.empty?
38
- end
39
-
40
- def update( **kwargs )
41
- ## note: check with has_key? because value might be nil!!!
42
- @date = kwargs[:date] if kwargs.has_key? :date
43
- @time = kwargs[:time] if kwargs.has_key? :time
44
-
45
- ## todo/fix: use team1_name, team2_name or similar - for compat with db activerecord version? why? why not?
46
- @team1 = kwargs[:team1] if kwargs.has_key? :team1
47
- @team2 = kwargs[:team2] if kwargs.has_key? :team2
48
-
49
- @conf1 = kwargs[:conf1] if kwargs.has_key? :conf1
50
- @conf2 = kwargs[:conf2] if kwargs.has_key? :conf2
51
- @country1 = kwargs[:country1] if kwargs.has_key? :country1
52
- @country2 = kwargs[:country2] if kwargs.has_key? :country2
53
-
54
- ## note: round is a string!!! e.g. '1', '2' for matchday or 'Final', 'Semi-final', etc.
55
- ## todo: use to_s - why? why not?
56
- @round = kwargs[:round] if kwargs.has_key? :round
57
- @stage = kwargs[:stage] if kwargs.has_key? :stage
58
- @leg = kwargs[:leg] if kwargs.has_key? :leg
59
- @group = kwargs[:group] if kwargs.has_key? :group
60
- @status = kwargs[:status] if kwargs.has_key? :status
61
- @comments = kwargs[:comments] if kwargs.has_key? :comments
62
-
63
- @league = kwargs[:league] if kwargs.has_key? :league
64
-
65
-
66
- if kwargs.has_key?( :score ) ## check all-in-one score struct for convenience!!!
67
- score = kwargs[:score]
68
- if score.nil? ## reset all score attribs to nil!!
69
- @score1 = nil
70
- @score1i = nil
71
- @score1et = nil
72
- @score1p = nil
73
- ## @score1agg = nil
74
-
75
- @score2 = nil
76
- @score2i = nil
77
- @score2et = nil
78
- @score2p = nil
79
- ## @score2agg = nil
80
- else
81
- @score1 = score.score1
82
- @score1i = score.score1i
83
- @score1et = score.score1et
84
- @score1p = score.score1p
85
- ## @score1agg = score.score1agg
86
-
87
- @score2 = score.score2
88
- @score2i = score.score2i
89
- @score2et = score.score2et
90
- @score2p = score.score2p
91
- ## @score2agg = score.score2agg
92
- end
93
- else
94
- @score1 = kwargs[:score1] if kwargs.has_key? :score1
95
- @score1i = kwargs[:score1i] if kwargs.has_key? :score1i
96
- @score1et = kwargs[:score1et] if kwargs.has_key? :score1et
97
- @score1p = kwargs[:score1p] if kwargs.has_key? :score1p
98
- @score1agg = kwargs[:score1agg] if kwargs.has_key? :score1agg
99
-
100
- @score2 = kwargs[:score2] if kwargs.has_key? :score2
101
- @score2i = kwargs[:score2i] if kwargs.has_key? :score2i
102
- @score2et = kwargs[:score2et] if kwargs.has_key? :score2et
103
- @score2p = kwargs[:score2p] if kwargs.has_key? :score2p
104
- @score2agg = kwargs[:score2agg] if kwargs.has_key? :score2agg
105
-
106
- ## note: (always) (auto-)convert scores to integers
107
- @score1 = @score1.to_i if @score1
108
- @score1i = @score1i.to_i if @score1i
109
- @score1et = @score1et.to_i if @score1et
110
- @score1p = @score1p.to_i if @score1p
111
- @score1agg = @score1agg.to_i if @score1agg
112
-
113
- @score2 = @score2.to_i if @score2
114
- @score2i = @score2i.to_i if @score2i
115
- @score2et = @score2et.to_i if @score2et
116
- @score2p = @score2p.to_i if @score2p
117
- @score2agg = @score2agg.to_i if @score2agg
118
- end
119
-
120
- ## todo/fix:
121
- ## gr-greece/2014-15/G1.csv:
122
- ## G1,10/05/15,Niki Volos,OFI,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
123
- ##
124
-
125
- ## for now score1 and score2 must be present
126
- ## if @score1.nil? || @score2.nil?
127
- ## puts "** WARN: missing scores for match:"
128
- ## pp kwargs
129
- ## ## exit 1
130
- ## end
131
-
132
- ## todo/fix: auto-calculate winner
133
- # return 1,2,0 1 => team1, 2 => team2, 0 => draw/tie
134
- ### calculate winner - use 1,2,0
135
- ##
136
- ## move winner calc to score class - why? why not?
137
- if @score1 && @score2
138
- if @score1 > @score2
139
- @winner = 1
140
- elsif @score2 > @score1
141
- @winner = 2
142
- elsif @score1 == @score2
143
- @winner = 0
144
- else
145
- end
146
- else
147
- @winner = nil # unknown / undefined
148
- end
149
-
150
- self ## note - MUST return self for chaining
151
- end
152
-
153
-
154
-
155
- def over?() true; end ## for now all matches are over - in the future check date!!!
156
- def complete?() true; end ## for now all scores are complete - in the future check scores; might be missing - not yet entered
157
-
158
-
159
- def score
160
- Score.new( @score1i, @score2i, ## half time (first (i) part)
161
- @score1, @score2, ## full time
162
- @score1et, @score2et, ## extra time
163
- @score1p, @score2p ) ## penalty
164
- end
165
-
166
-
167
- ####
168
- ## deprecated - use score.to_s and friends - why? why not?
169
- # def score_str # pretty print (full time) scores; convenience method
170
- # "#{@score1}-#{@score2}"
171
- # end
172
-
173
- # def scorei_str # pretty print (half time) scores; convenience method
174
- # "#{@score1i}-#{@score2i}"
175
- # end
176
- end # class Match
177
-
178
- end # module Sports
179
-
180
-