sportdb-quick 0.5.3 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +1 -1
- data/Manifest.txt +12 -3
- data/Rakefile +2 -2
- data/lib/sportdb/quick/match_parser.rb +12 -590
- data/lib/sportdb/quick/match_tree/goal.rb +67 -0
- data/lib/sportdb/quick/match_tree/group.rb +25 -0
- data/lib/sportdb/quick/match_tree/match.rb +247 -0
- data/lib/sportdb/quick/match_tree/round.rb +36 -0
- data/lib/sportdb/quick/match_tree-helpers.rb +81 -0
- data/lib/sportdb/quick/match_tree.rb +162 -0
- data/lib/sportdb/quick/match_tree_on/on_date_header.rb +45 -0
- data/lib/sportdb/quick/match_tree_on/on_goal_line.rb +87 -0
- data/lib/sportdb/quick/match_tree_on/on_group_def.rb +27 -0
- data/lib/sportdb/quick/match_tree_on/on_match_line.rb +195 -0
- data/lib/sportdb/quick/match_tree_on/on_round_def.rb +85 -0
- data/lib/sportdb/quick/match_tree_on/on_round_outline.rb +104 -0
- data/lib/sportdb/quick/quick_match_reader.rb +65 -68
- data/lib/sportdb/quick/version.rb +2 -2
- data/lib/sportdb/quick.rb +19 -16
- metadata +19 -10
- data/lib/sportdb/quick/outline.rb +0 -96
- data/lib/sportdb/quick/outline_reader.rb +0 -98
- data/lib/sportdb/quick/quick_league_outline.rb +0 -123
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
module SportDb
|
|
2
|
+
class MatchTree
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Goal ### nested (non-freestanding) inside match (match is parent)
|
|
7
|
+
attr_reader :team, ## note - 1|2 expected
|
|
8
|
+
:player,
|
|
9
|
+
:minute,
|
|
10
|
+
:offset,
|
|
11
|
+
:owngoal, ## true|false
|
|
12
|
+
:penalty ## true|false
|
|
13
|
+
|
|
14
|
+
## add alias for player => name - why? why not?
|
|
15
|
+
alias_method :name, :player
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def owngoal?() @owngoal==true; end
|
|
19
|
+
def penalty?() @penalty==true; end
|
|
20
|
+
def team1?() @team == 1; end
|
|
21
|
+
def team2?() @team == 2; end
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
## note: make score1,score2 optional for now !!!!
|
|
25
|
+
def initialize( team:,
|
|
26
|
+
player:,
|
|
27
|
+
minute:,
|
|
28
|
+
offset: nil,
|
|
29
|
+
owngoal: false,
|
|
30
|
+
penalty: false
|
|
31
|
+
)
|
|
32
|
+
@team = team # 1|2
|
|
33
|
+
@player = player
|
|
34
|
+
@minute = minute
|
|
35
|
+
@offset = offset
|
|
36
|
+
@owngoal = owngoal
|
|
37
|
+
@penalty = penalty
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def state
|
|
41
|
+
[@team,
|
|
42
|
+
@player, @minute, @offset, @owngoal, @penalty
|
|
43
|
+
]
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def ==(o)
|
|
47
|
+
o.class == self.class && o.state == state
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def pretty_print( printer )
|
|
51
|
+
buf = String.new
|
|
52
|
+
buf << "<Goal"
|
|
53
|
+
buf << " #{@player} #{@minute}"
|
|
54
|
+
buf << "+#{@offset}" if @offset && @offset > 0
|
|
55
|
+
buf << "'"
|
|
56
|
+
buf << " (og)" if @owngoal
|
|
57
|
+
buf << " (p)" if @penalty
|
|
58
|
+
buf << " for #{@team}" ### team 1 or 2 - use home/away
|
|
59
|
+
buf << ">"
|
|
60
|
+
|
|
61
|
+
printer.text( buf )
|
|
62
|
+
end
|
|
63
|
+
end # class Goal
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
end # class MatchTree
|
|
67
|
+
end # module SportDb
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module SportDb
|
|
2
|
+
class MatchTree
|
|
3
|
+
|
|
4
|
+
class Group
|
|
5
|
+
attr_reader :name, :teams
|
|
6
|
+
|
|
7
|
+
def initialize( name:,
|
|
8
|
+
teams: )
|
|
9
|
+
@name = name
|
|
10
|
+
@teams = teams
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def pretty_print( printer )
|
|
14
|
+
buf = String.new
|
|
15
|
+
buf << "<Group #{@name} "
|
|
16
|
+
buf << @teams.pretty_print_inspect
|
|
17
|
+
buf << ">"
|
|
18
|
+
|
|
19
|
+
printer.text( buf )
|
|
20
|
+
end
|
|
21
|
+
end # class Group
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
end # class MatchTree
|
|
25
|
+
end # module SportDb
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
##
|
|
2
|
+
# move (simpler) struct version inline to MatchTree for now
|
|
3
|
+
#
|
|
4
|
+
module SportDb
|
|
5
|
+
class MatchTree
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Match
|
|
10
|
+
|
|
11
|
+
### note - use inline Score class Match::Score - why? why not?
|
|
12
|
+
## note - score might internally be an array [2,3]
|
|
13
|
+
## or hash { ft:, } etc.
|
|
14
|
+
|
|
15
|
+
## note - score for now might be
|
|
16
|
+
## 1) array e.g. [1,0] or []
|
|
17
|
+
## 2) hash e.g. { ft: [1,0] } etc.
|
|
18
|
+
|
|
19
|
+
attr_reader :num,
|
|
20
|
+
:date,
|
|
21
|
+
:time,
|
|
22
|
+
:time_local,
|
|
23
|
+
:team1, :team2, ## todo/fix: use team1_name, team2_name or similar - for compat with db activerecord version? why? why not?
|
|
24
|
+
:score,
|
|
25
|
+
:round, ## todo/fix: use round_num or similar - for compat with db activerecord version? why? why not?
|
|
26
|
+
:group,
|
|
27
|
+
:status, ## e.g. replay, cancelled, awarded, abadoned, postponed, etc.
|
|
28
|
+
:ground, ## (optional) add as text line for now (incl. city, timezone etc.)
|
|
29
|
+
:att ## (optional) attendance as (integer) number
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
attr_accessor :goals ## todo/fix: make goals like all other attribs!!
|
|
33
|
+
|
|
34
|
+
def initialize( **kwargs )
|
|
35
|
+
@score = []
|
|
36
|
+
## @score1, @score2 = [nil,nil] ## full time
|
|
37
|
+
## @score1i, @score2i = [nil,nil] ## half time (first (i) part)
|
|
38
|
+
## @score1et, @score2et = [nil,nil] ## extra time
|
|
39
|
+
## @score1p, @score2p = [nil,nil] ## penalty
|
|
40
|
+
## @score1agg, @score2agg = [nil,nil] ## full time (all legs) aggregated
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
update( **kwargs ) unless kwargs.empty?
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def update( **kwargs )
|
|
48
|
+
@num = kwargs[:num] if kwargs.has_key?( :num )
|
|
49
|
+
|
|
50
|
+
## note: check with has_key? because value might be nil!!!
|
|
51
|
+
@date = kwargs[:date] if kwargs.has_key?( :date )
|
|
52
|
+
@time = kwargs[:time] if kwargs.has_key?( :time )
|
|
53
|
+
@time_local = kwargs[:time_local] if kwargs.has_key?( :time_local )
|
|
54
|
+
|
|
55
|
+
## todo/fix: use team1_name, team2_name or similar - for compat with db activerecord version? why? why not?
|
|
56
|
+
@team1 = kwargs[:team1] if kwargs.has_key?( :team1 )
|
|
57
|
+
@team2 = kwargs[:team2] if kwargs.has_key?( :team2 )
|
|
58
|
+
|
|
59
|
+
## note: round is a string!!! e.g. '1', '2' for matchday or 'Final', 'Semi-final', etc.
|
|
60
|
+
## todo: use to_s - why? why not?
|
|
61
|
+
@round = kwargs[:round] if kwargs.has_key?( :round )
|
|
62
|
+
@group = kwargs[:group] if kwargs.has_key?( :group )
|
|
63
|
+
@status = kwargs[:status] if kwargs.has_key?( :status )
|
|
64
|
+
|
|
65
|
+
@ground = kwargs[:ground] if kwargs.has_key?( :ground )
|
|
66
|
+
@att = kwargs[:att] if kwargs.has_key?( :att )
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
if kwargs.has_key?( :score ) ## check all-in-one score struct for convenience!!!
|
|
70
|
+
score = kwargs[:score]
|
|
71
|
+
|
|
72
|
+
if score.nil? ## reset all score attribs to nil!!
|
|
73
|
+
@score = [] ## [nil,nil]
|
|
74
|
+
else
|
|
75
|
+
## check if is array - assume "generic" score e.g. 3-2
|
|
76
|
+
## that is, not known if full-time, after extra-time etc.
|
|
77
|
+
if score.is_a?( Array )
|
|
78
|
+
@score = score ## e.g. [3,2]
|
|
79
|
+
else ## assume hash
|
|
80
|
+
@score = score
|
|
81
|
+
# @score1, @score2 = score[:ft] || []
|
|
82
|
+
# @score1i, @score2i = score[:ht] || []
|
|
83
|
+
# @score1et, @score2et = score[:et] || []
|
|
84
|
+
# @score1p, @score2p = score[:p] || score[:pen] || []
|
|
85
|
+
# @score1agg, @score2agg = score[:agg] || []
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
# @score[:ht] = kwargs[:score_ht] if kwargs.has_key?( :score_ht )
|
|
90
|
+
# @score[:et] = kwargs[:score_et] if kwargs.has_key?( :score_et )
|
|
91
|
+
# @score[:p] = kwargs[:score_p] if kwargs.has_key?( :score_p )
|
|
92
|
+
# @score[:agg] = kwargs[:score_agg] if kwargs.has_key?( :score_agg )
|
|
93
|
+
|
|
94
|
+
## note: (always) (auto-)convert scores to integers
|
|
95
|
+
# @score1 = @score1.to_i(10) if @score1
|
|
96
|
+
# @score1i = @score1i.to_i(10) if @score1i
|
|
97
|
+
# @score1et = @score1et.to_i(10) if @score1et
|
|
98
|
+
# @score1p = @score1p.to_i(10) if @score1p
|
|
99
|
+
# @score1agg = @score1agg.to_i(10) if @score1agg
|
|
100
|
+
|
|
101
|
+
# @score2 = @score2.to_i(10) if @score2
|
|
102
|
+
# @score2i = @score2i.to_i(10) if @score2i
|
|
103
|
+
# @score2et = @score2et.to_i(10) if @score2et
|
|
104
|
+
# @score2p = @score2p.to_i(10) if @score2p
|
|
105
|
+
# @score2agg = @score2agg.to_i(10) if @score2agg
|
|
106
|
+
|
|
107
|
+
## todo/fix:
|
|
108
|
+
## gr-greece/2014-15/G1.csv:
|
|
109
|
+
## G1,10/05/15,Niki Volos,OFI,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
|
110
|
+
##
|
|
111
|
+
|
|
112
|
+
## for now score1 and score2 must be present
|
|
113
|
+
## if @score1.nil? || @score2.nil?
|
|
114
|
+
## puts "** WARN: missing scores for match:"
|
|
115
|
+
## pp kwargs
|
|
116
|
+
## ## exit 1
|
|
117
|
+
## end
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
self ## note - MUST return self for chaining
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
####
|
|
125
|
+
## deprecated - use score.to_s and friends - why? why not?
|
|
126
|
+
# def score_str # pretty print (full time) scores; convenience method
|
|
127
|
+
# "#{@score1}-#{@score2}"
|
|
128
|
+
# end
|
|
129
|
+
|
|
130
|
+
# def scorei_str # pretty print (half time) scores; convenience method
|
|
131
|
+
# "#{@score1i}-#{@score2i}"
|
|
132
|
+
# end
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def as_json
|
|
136
|
+
#####
|
|
137
|
+
## note - use string keys (NOT symbol for data keys)
|
|
138
|
+
## for easier json compatibility
|
|
139
|
+
data = {}
|
|
140
|
+
|
|
141
|
+
## check round
|
|
142
|
+
if @round
|
|
143
|
+
data['round'] = if round.is_a?( Integer )
|
|
144
|
+
"Matchday #{@round}"
|
|
145
|
+
else ## assume string
|
|
146
|
+
@round
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
data['num'] = @num if @num
|
|
152
|
+
if @date
|
|
153
|
+
## assume 2020-09-19 date format!!
|
|
154
|
+
data['date'] = @date.is_a?( String ) ? @date : @date.strftime('%Y-%m-%d')
|
|
155
|
+
|
|
156
|
+
data['time'] = @time if @time
|
|
157
|
+
data['time_local'] = @time_local if @time_local
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
data['team1'] = @team1.is_a?( String ) ? @team1 : @team1.name
|
|
162
|
+
|
|
163
|
+
## note - for match status bye team2 is nil!!!
|
|
164
|
+
## e.g. Queen's Park bye
|
|
165
|
+
## Wanderers bye
|
|
166
|
+
## todo/check - keep bye as a match - why? why not?
|
|
167
|
+
## has no date/time & venue & score etc.
|
|
168
|
+
if @team2
|
|
169
|
+
data['team2'] = @team2.is_a?( String ) ? @team2 : @team2.name
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
## note - score might be
|
|
173
|
+
## 1) array e.g. [0,1]
|
|
174
|
+
## 2) hash e.g. { ft: [0,1] } etc.
|
|
175
|
+
## note - w/o (walkout) do NOT add empty score
|
|
176
|
+
if @score.is_a?(Hash)
|
|
177
|
+
# note: make sure hash keys are always strings
|
|
178
|
+
data['score'] = @score.transform_keys(&:to_s)
|
|
179
|
+
elsif @score.is_a?(Array)
|
|
180
|
+
## note:
|
|
181
|
+
## for now always assume full-time (ft)
|
|
182
|
+
## in future check for score note or such
|
|
183
|
+
## to use "plain" array or such - why? why not?
|
|
184
|
+
## data['score'] = { 'ft' => @score } if !@score.empty?
|
|
185
|
+
|
|
186
|
+
data['score'] = @score if !@score.empty?
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
## data['score']['ht'] = [@score1i, @score2i] if @score1i && @score2i
|
|
191
|
+
## data['score']['ft'] = [@score1, @score2] if @score1 && @score2
|
|
192
|
+
## data['score']['et'] = [@score1et, @score2et] if @score1et && @score2et
|
|
193
|
+
## data['score']['p'] = [@score1p, @score2p] if @score1p && @score2p
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
### check for goals
|
|
198
|
+
if @goals && @goals.size > 0
|
|
199
|
+
data['goals1'] = []
|
|
200
|
+
data['goals2'] = []
|
|
201
|
+
|
|
202
|
+
@goals.each do |goal|
|
|
203
|
+
node = {}
|
|
204
|
+
node['name'] = goal.player
|
|
205
|
+
|
|
206
|
+
## note - use a string for minutes for now
|
|
207
|
+
## allows e.g. 45+2 etc. too
|
|
208
|
+
minute_str = "#{goal.minute}"
|
|
209
|
+
minute_str += "+#{goal.offset}" if goal.offset
|
|
210
|
+
|
|
211
|
+
node['minute'] = minute_str
|
|
212
|
+
|
|
213
|
+
node['owngoal'] = true if goal.owngoal
|
|
214
|
+
node['penalty'] = true if goal.penalty
|
|
215
|
+
|
|
216
|
+
if goal.team == 1
|
|
217
|
+
data['goals1'] << node
|
|
218
|
+
else ## assume 2
|
|
219
|
+
data['goals2'] << node
|
|
220
|
+
end
|
|
221
|
+
end # each goal
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
data['status'] = @status if @status
|
|
226
|
+
|
|
227
|
+
data['group'] = @group if @group
|
|
228
|
+
|
|
229
|
+
if @ground
|
|
230
|
+
## note: might be array of string e.g. ['Wembley', 'London']
|
|
231
|
+
##
|
|
232
|
+
## todo/check - auto-join to string - why? why not?
|
|
233
|
+
## e.g. ['Wembley', 'London']
|
|
234
|
+
## to 'Wembley, London'
|
|
235
|
+
## note - auto-join geo tree for now
|
|
236
|
+
data['ground'] = @ground.join(', ')
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
data['attendance'] = @att if @att
|
|
240
|
+
data
|
|
241
|
+
end
|
|
242
|
+
end # class Match
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
end # class MatchTree
|
|
247
|
+
end # module SportDb
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
|
|
2
|
+
module SportDb
|
|
3
|
+
class MatchTree
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Round
|
|
7
|
+
attr_reader :name, :start_date, :end_date
|
|
8
|
+
|
|
9
|
+
def initialize( name:,
|
|
10
|
+
start_date: nil,
|
|
11
|
+
end_date: nil,
|
|
12
|
+
auto: true )
|
|
13
|
+
@name = name
|
|
14
|
+
@start_date = start_date
|
|
15
|
+
@end_date = end_date
|
|
16
|
+
@auto = auto # auto-created (inline reference/header without proper definition before)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def pretty_print( printer )
|
|
20
|
+
## todo/check - how to display/format key - use () or not - why? why not?
|
|
21
|
+
buf = String.new
|
|
22
|
+
buf << "<Round"
|
|
23
|
+
buf << " AUTO" if @auto
|
|
24
|
+
buf << ": "
|
|
25
|
+
buf << "#{@name}, "
|
|
26
|
+
buf << "#{@start_date}"
|
|
27
|
+
buf << " - #{@end_date}" if @start_date != @end_date
|
|
28
|
+
buf << ">"
|
|
29
|
+
|
|
30
|
+
printer.text( buf )
|
|
31
|
+
end
|
|
32
|
+
end # class Round
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
end # class MatchTree
|
|
36
|
+
end # module SportDb
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
|
|
2
|
+
module SportDb
|
|
3
|
+
class MatchTree
|
|
4
|
+
|
|
5
|
+
def log( msg )
|
|
6
|
+
## append msg to ./logs.txt
|
|
7
|
+
## use ./errors.txt - why? why not?
|
|
8
|
+
File.open( './logs.txt', 'a:utf-8' ) do |f|
|
|
9
|
+
f.write( msg )
|
|
10
|
+
f.write( "\n" )
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
### check - rename last_year to running_last_year to make intent clearer - why? why not?
|
|
16
|
+
def _build_date( m:, d:, y:, yy:, wday:,
|
|
17
|
+
start:,
|
|
18
|
+
last_year: )
|
|
19
|
+
|
|
20
|
+
if m.nil? || d.nil?
|
|
21
|
+
puts "[debug] !! ERROR - _build_date required month or day missing:"
|
|
22
|
+
pp [m,d,y,yy,wday,start]
|
|
23
|
+
exit 1
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
## quick debug hack
|
|
28
|
+
if m == 2 && d == 29
|
|
29
|
+
puts "quick check feb/29 dates"
|
|
30
|
+
pp [d,m,y]
|
|
31
|
+
pp start
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
####
|
|
36
|
+
## support two digit shortcut for year
|
|
37
|
+
if yy
|
|
38
|
+
###
|
|
39
|
+
## for now assume 00,01 to 30 is 2000,2001 to 2030
|
|
40
|
+
## and 31 to 99 is 1931 to 1999
|
|
41
|
+
y = yy <= 30 ? 2000+yy : 1900+yy
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
if y.nil? ## try to calculate year
|
|
46
|
+
if last_year && @last_year ## use new formula
|
|
47
|
+
y = @last_year
|
|
48
|
+
elsif start.nil?
|
|
49
|
+
puts "!! ERROR - _build_date - year expected for (first) date; cannot infer/guess; sorry"
|
|
50
|
+
exit 1
|
|
51
|
+
else ## fallback to "old" formula - FIX/FIX remove later
|
|
52
|
+
## puts "[deprecated] WARN - do NOT use old year (date) auto-complete; add year to first date"
|
|
53
|
+
y = if m > start.month ||
|
|
54
|
+
(m == start.month && d >= start.day)
|
|
55
|
+
# assume same year as start_at event (e.g. 2013 for 2013/14 season)
|
|
56
|
+
start.year
|
|
57
|
+
else
|
|
58
|
+
# assume year+1 as start_at event (e.g. 2014 for 2013/14 season)
|
|
59
|
+
start.year+1
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
else
|
|
63
|
+
### note - reset @start to new date
|
|
64
|
+
## use @last_year
|
|
65
|
+
if last_year
|
|
66
|
+
@last_year = y
|
|
67
|
+
puts " [debug] _build_date - set running last_year to #{y}"
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
date = Date.new( y,m,d ) ## y,m,d
|
|
73
|
+
|
|
74
|
+
### todo/fix
|
|
75
|
+
### check/validate wday here
|
|
76
|
+
|
|
77
|
+
date
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
end ## class MatchTree
|
|
81
|
+
end ## module SportDb
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
module SportDb
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
##############################
|
|
5
|
+
## simple (match) parse tree to structs walker/handler/converter
|
|
6
|
+
class MatchTree
|
|
7
|
+
def self.debug=(value) @@debug = value; end
|
|
8
|
+
def self.debug?() @@debug ||= false; end ## note: default is FALSE
|
|
9
|
+
def debug?() self.class.debug?; end
|
|
10
|
+
|
|
11
|
+
include Logging ## e.g. logger#debug, logger#info, etc.
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
##
|
|
16
|
+
## note: allow start(_date) nil
|
|
17
|
+
## if in use (start: nil) years expected on first date!!!
|
|
18
|
+
|
|
19
|
+
def initialize( tree, start: nil )
|
|
20
|
+
@tree = tree
|
|
21
|
+
@start = start
|
|
22
|
+
|
|
23
|
+
@errors = []
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
attr_reader :errors
|
|
27
|
+
def errors?() @errors.size > 0; end
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def convert
|
|
31
|
+
## note: every (new) read call - resets errors list to empty
|
|
32
|
+
@errors = []
|
|
33
|
+
@warns = [] ## track list of warnings (unmatched lines) too - why? why not?
|
|
34
|
+
|
|
35
|
+
### todo/fix - FIX/FIX
|
|
36
|
+
## check start year from first date
|
|
37
|
+
## for now (auto-)update - @start with every date that incl. a year!!!
|
|
38
|
+
@last_year = nil
|
|
39
|
+
@last_date = nil
|
|
40
|
+
@last_time = nil
|
|
41
|
+
|
|
42
|
+
## todo/fix - use stack push/pop in the future - why? why not?
|
|
43
|
+
@last_round = nil ## merge - "top-level" - Round struct
|
|
44
|
+
@last_round_name1 = nil ## level 1 - string
|
|
45
|
+
@last_round_name2 = nil ## level 2 - string
|
|
46
|
+
@last_round_name3 = nil ## level 3 - string
|
|
47
|
+
|
|
48
|
+
@last_group = nil
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@teams = Hash.new(0) ## track counts (only) for now for (interal) team stats - why? why not?
|
|
52
|
+
@rounds = {}
|
|
53
|
+
@groups = {}
|
|
54
|
+
@matches = []
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@tree.each do |node|
|
|
58
|
+
if node.is_a? RaccMatchParser::RoundDef
|
|
59
|
+
## todo/fix: add round definition (w begin n end date)
|
|
60
|
+
## todo: do not patch rounds with definition (already assume begin/end date is good)
|
|
61
|
+
## -- how to deal with matches that get rescheduled/postponed?
|
|
62
|
+
on_round_def( node )
|
|
63
|
+
elsif node.is_a? RaccMatchParser::GroupDef ## NB: group goes after round (round may contain group marker too)
|
|
64
|
+
### todo: add pipe (|) marker (required)
|
|
65
|
+
on_group_def( node )
|
|
66
|
+
elsif node.is_a? RaccMatchParser::RoundOutline
|
|
67
|
+
on_round_outline( node )
|
|
68
|
+
elsif node.is_a? RaccMatchParser::DateHeader
|
|
69
|
+
on_date_header( node )
|
|
70
|
+
elsif node.is_a? RaccMatchParser::MatchLine
|
|
71
|
+
on_match_line( node )
|
|
72
|
+
elsif node.is_a? RaccMatchParser::MatchLineWalkover
|
|
73
|
+
on_match_line_walkover( node )
|
|
74
|
+
elsif node.is_a? RaccMatchParser::MatchLineBye
|
|
75
|
+
on_match_line_bye( node )
|
|
76
|
+
elsif node.is_a? RaccMatchParser::GoalLine
|
|
77
|
+
on_goal_line( node )
|
|
78
|
+
elsif node.is_a?( RaccMatchParser::LineupLine ) ||
|
|
79
|
+
node.is_a?( RaccMatchParser::RefereeLine )
|
|
80
|
+
## skip lineup, referee props for now
|
|
81
|
+
elsif node.is_a?( RaccMatchParser::Heading1 ) ||
|
|
82
|
+
node.is_a?( RaccMatchParser::Heading2 ) ||
|
|
83
|
+
node.is_a?( RaccMatchParser::Heading3 )
|
|
84
|
+
### skip headings (1/2/3) for now
|
|
85
|
+
elsif node.is_a?( RaccMatchParser::BlankLine )
|
|
86
|
+
### skip for now; do nothing
|
|
87
|
+
else
|
|
88
|
+
## report error
|
|
89
|
+
msg = "!! WARN - unknown node (parse tree type) - #{node.class.name}"
|
|
90
|
+
puts msg
|
|
91
|
+
pp node
|
|
92
|
+
|
|
93
|
+
log( msg )
|
|
94
|
+
log( node.pretty_inspect )
|
|
95
|
+
end
|
|
96
|
+
end # tree.each
|
|
97
|
+
|
|
98
|
+
## note - team keys are names and values are "internal" stats e.g. usage count!!
|
|
99
|
+
## and NOT team/club/nat_team structs!!
|
|
100
|
+
[@teams.keys, @matches, @rounds.values, @groups.values]
|
|
101
|
+
end # method convert
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def on_match_line_walkover( node )
|
|
107
|
+
logger.debug( "on match (w/o): >#{node}<" )
|
|
108
|
+
|
|
109
|
+
## note - w/o (walkover) records NO date/time or ground (or score etc.)
|
|
110
|
+
## for now only team1/team2 and match status!!
|
|
111
|
+
## plus inherited round/group
|
|
112
|
+
|
|
113
|
+
status = 'walkover' ## use w/o - why? why not?
|
|
114
|
+
|
|
115
|
+
team1 = node.team1
|
|
116
|
+
team2 = node.team2
|
|
117
|
+
|
|
118
|
+
@teams[ team1 ] += 1
|
|
119
|
+
@teams[ team2 ] += 1
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
group = nil
|
|
123
|
+
group = @last_group if @last_group
|
|
124
|
+
|
|
125
|
+
round = nil
|
|
126
|
+
round = @last_round if @last_round
|
|
127
|
+
|
|
128
|
+
@matches << Match.new( team1: team1, ## note: for now always use mapping value e.g. rec (NOT string e.g. team1.name)
|
|
129
|
+
team2: team2, ## note: for now always use mapping value e.g. rec (NOT string e.g. team2.name)
|
|
130
|
+
round: round ? round.name : nil, ## note: for now always use string (assume unique canonical name for event)
|
|
131
|
+
group: group ? group.name : nil, ## note: for now always use string (assume unique canonical name for event)
|
|
132
|
+
status: status )
|
|
133
|
+
### todo: cache team lookups in hash?
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def on_match_line_bye( node )
|
|
137
|
+
logger.debug( "on match (bye): >#{node}<" )
|
|
138
|
+
|
|
139
|
+
## note - bye records NO date/time or ground (or score etc.)
|
|
140
|
+
## for now only team1/team2 and match status!!
|
|
141
|
+
## plus inherited round/group
|
|
142
|
+
|
|
143
|
+
status = 'bye'
|
|
144
|
+
|
|
145
|
+
team = node.team
|
|
146
|
+
|
|
147
|
+
@teams[ team ] += 1
|
|
148
|
+
|
|
149
|
+
group = nil
|
|
150
|
+
group = @last_group if @last_group
|
|
151
|
+
|
|
152
|
+
round = nil
|
|
153
|
+
round = @last_round if @last_round
|
|
154
|
+
|
|
155
|
+
@matches << Match.new( team1: team, ## note: for now always use mapping value e.g. rec (NOT string e.g. team1.name)
|
|
156
|
+
round: round ? round.name : nil, ## note: for now always use string (assume unique canonical name for event)
|
|
157
|
+
group: group ? group.name : nil, ## note: for now always use string (assume unique canonical name for event)
|
|
158
|
+
status: status )
|
|
159
|
+
### todo: cache team lookups in hash?
|
|
160
|
+
end
|
|
161
|
+
end # class MatchTree
|
|
162
|
+
end # module SportDb
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
module SportDb
|
|
2
|
+
class MatchTree
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def on_date_header( node )
|
|
8
|
+
logger.debug( "date header: >#{node}<")
|
|
9
|
+
|
|
10
|
+
date = _build_date( m: node.date[:m],
|
|
11
|
+
d: node.date[:d],
|
|
12
|
+
y: node.date[:y],
|
|
13
|
+
yy: node.date[:yy],
|
|
14
|
+
wday: node.date[:wday],
|
|
15
|
+
start: @start,
|
|
16
|
+
last_year: true )
|
|
17
|
+
|
|
18
|
+
logger.debug( " date: #{date} with start: #{@start}")
|
|
19
|
+
|
|
20
|
+
@last_date = date # keep a reference for later use
|
|
21
|
+
@last_time = nil
|
|
22
|
+
|
|
23
|
+
### quick "corona" hack - support seasons going beyond 12 month (see swiss league 2019/20 and others!!)
|
|
24
|
+
## find a better way??
|
|
25
|
+
## set @start date to full year (e.g. 1.1.) if date.year is @start.year+1
|
|
26
|
+
## todo/fix: add to linter to check for chronological dates!! - warn if NOT chronological
|
|
27
|
+
### todo/check: just turn on for 2019/20 season or always? why? why not?
|
|
28
|
+
|
|
29
|
+
## todo/fix: add switch back to old @start_org
|
|
30
|
+
## if year is date.year == @start.year-1 -- possible when full date with year set!!!
|
|
31
|
+
=begin
|
|
32
|
+
if @start.month != 1
|
|
33
|
+
if date.year == @start.year+1
|
|
34
|
+
logger.debug( "!! hack - extending start date to full (next/end) year; assumes all dates are chronologigal - always moving forward" )
|
|
35
|
+
@start_org = @start ## keep a copy of the original (old) start date - why? why not? - not used for now
|
|
36
|
+
@start = Date.new( @start.year+1, 1, 1 )
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
=end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
end ## class MatchTree
|
|
45
|
+
end ## module SportDb
|