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,87 @@
|
|
|
1
|
+
module SportDb
|
|
2
|
+
class MatchTree
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def on_goal_line( node )
|
|
6
|
+
logger.debug "on goal line: >#{node}<"
|
|
7
|
+
|
|
8
|
+
goals1 = node.goals1
|
|
9
|
+
goals2 = node.goals2
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
pp [goals1,goals2] if debug?
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
## special rule
|
|
16
|
+
## if goals 2 empty check if score for team 1 is zero
|
|
17
|
+
## and team 2 is NOT zero than
|
|
18
|
+
## make goals1 goald2!!
|
|
19
|
+
## e.g. Norway 0-1 Austria
|
|
20
|
+
## (Hof 32)
|
|
21
|
+
|
|
22
|
+
if goals2.empty? && !goals1.empty?
|
|
23
|
+
|
|
24
|
+
match = @matches[-1]
|
|
25
|
+
|
|
26
|
+
##
|
|
27
|
+
## todo/fix
|
|
28
|
+
## move upstream
|
|
29
|
+
## use score1_zero? or such - why? why not?
|
|
30
|
+
if (match.score.is_a?(Array) && match.score[0] == 0 ) ||
|
|
31
|
+
(match.score.is_a?(Hash) && match.score[:et] && match.score[:et][0] == 0) ||
|
|
32
|
+
(match.score.is_a?(Hash) && match.score[:et].nil? &&
|
|
33
|
+
match.score[:ft] && match.score[:ft][0] == 0)
|
|
34
|
+
## "parallel assignment (or multiple assignment") - swap values in single line
|
|
35
|
+
goals2, goals1 = goals1, goals2
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
goals = []
|
|
42
|
+
|
|
43
|
+
goals1.each do |rec|
|
|
44
|
+
rec.minutes.each do |minute|
|
|
45
|
+
goal = Goal.new(
|
|
46
|
+
player: rec.player,
|
|
47
|
+
team: 1,
|
|
48
|
+
minute: minute.m,
|
|
49
|
+
offset: minute.offset,
|
|
50
|
+
penalty: minute.pen || false, # note: pass along/use false NOT nil
|
|
51
|
+
owngoal: minute.og || false
|
|
52
|
+
)
|
|
53
|
+
goals << goal
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
goals2.each do |rec|
|
|
58
|
+
rec.minutes.each do |minute|
|
|
59
|
+
goal = Goal.new(
|
|
60
|
+
player: rec.player,
|
|
61
|
+
team: 2,
|
|
62
|
+
minute: minute.m,
|
|
63
|
+
offset: minute.offset,
|
|
64
|
+
penalty: minute.pen || false, # note: pass along/use false NOT nil
|
|
65
|
+
owngoal: minute.og || false
|
|
66
|
+
)
|
|
67
|
+
goals << goal
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
pp goals if debug?
|
|
72
|
+
|
|
73
|
+
## quick & dirty - auto add goals to last match
|
|
74
|
+
## note - for hacky (quick& dirty) multi-line support
|
|
75
|
+
## always append for now
|
|
76
|
+
match = @matches[-1]
|
|
77
|
+
match.goals ||= []
|
|
78
|
+
match.goals += goals
|
|
79
|
+
|
|
80
|
+
## todo/fix
|
|
81
|
+
## sort by minute
|
|
82
|
+
## PLUS auto-fill score1,score2 - why? why not?
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
end ## class MatchTree
|
|
87
|
+
end ## module SportDb
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
module SportDb
|
|
2
|
+
class MatchTree
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def on_group_def( node )
|
|
6
|
+
logger.debug "on group def: >#{node}<"
|
|
7
|
+
|
|
8
|
+
## e.g
|
|
9
|
+
## [:group_def, "Group A"],
|
|
10
|
+
## [:team, "Germany"],
|
|
11
|
+
## [:team, "Scotland"],
|
|
12
|
+
## [:team, "Hungary"],
|
|
13
|
+
## [:team, "Switzerland"]
|
|
14
|
+
|
|
15
|
+
node.teams.each do |team|
|
|
16
|
+
@teams[ team ] += 1
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
group = Group.new( name: node.name,
|
|
20
|
+
teams: node.teams )
|
|
21
|
+
|
|
22
|
+
@groups[ node.name ] = group
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
end ## class MatchTree
|
|
27
|
+
end ## module SportDb
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
module SportDb
|
|
2
|
+
class MatchTree
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def on_match_line( node )
|
|
7
|
+
logger.debug( "on match: >#{node}<" )
|
|
8
|
+
|
|
9
|
+
## collect (possible) nodes by type
|
|
10
|
+
num = nil
|
|
11
|
+
num = node.num if node.num
|
|
12
|
+
|
|
13
|
+
date = nil
|
|
14
|
+
date = _build_date( m: node.date[:m],
|
|
15
|
+
d: node.date[:d],
|
|
16
|
+
y: node.date[:y],
|
|
17
|
+
yy: node.date[:yy],
|
|
18
|
+
wday: node.date[:wday],
|
|
19
|
+
start: @start,
|
|
20
|
+
last_year: true ) if node.date
|
|
21
|
+
|
|
22
|
+
## note - there's no time (-only) type in ruby
|
|
23
|
+
## use string (e.g. '14:56', '1:44')
|
|
24
|
+
## use 01:44 or 1:44 ?
|
|
25
|
+
## check for 0:00 or 24:00 possible?
|
|
26
|
+
time = nil
|
|
27
|
+
if node.time
|
|
28
|
+
time = ('%d:%02d' % [node.time[:h], node.time[:m]])
|
|
29
|
+
## check for timezone
|
|
30
|
+
time += " #{node.time[:timezone]}" if node.time[:timezone]
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
## todo/fix -
|
|
36
|
+
## keep time & time_local as pairs for @last_time/@last_time_local
|
|
37
|
+
## - check for timezone
|
|
38
|
+
## incl. timezone in time (string) - why? why not?
|
|
39
|
+
time_local = nil
|
|
40
|
+
if node.time_local
|
|
41
|
+
time_local = ('%d:%02d' % [node.time_local[:h], node.time_local[:m]])
|
|
42
|
+
time_local += " #{node.time_local[:timezone]}" if node.time_local[:timezone]
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
### todo/fix
|
|
47
|
+
## add keywords (e.g. ht, ft or such) to Score.new - why? why not?
|
|
48
|
+
## or use new Score.build( ht:, ft:, ) or such - why? why not?
|
|
49
|
+
## pp score
|
|
50
|
+
score = nil
|
|
51
|
+
score = node.score if node.score
|
|
52
|
+
|
|
53
|
+
## if node.score.is_a?(Array)
|
|
54
|
+
## ## assume "undefined" score
|
|
55
|
+
## score = node.score
|
|
56
|
+
## else ## (default) assume Hash
|
|
57
|
+
## # ht = node.score[:ht] || [nil,nil]
|
|
58
|
+
## # ft = node.score[:ft] || [nil,nil]
|
|
59
|
+
## # et = node.score[:et] || [nil,nil]
|
|
60
|
+
## # p = node.score[:p] || [nil,nil]
|
|
61
|
+
## # values = [*ht, *ft, *et, *p]
|
|
62
|
+
## # pp values
|
|
63
|
+
## ## pp node.score
|
|
64
|
+
## score = node.score
|
|
65
|
+
## end
|
|
66
|
+
## end
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
status = nil
|
|
70
|
+
status = node.status if node.status ### assume text for now
|
|
71
|
+
## if node_type == :status # e.g. awarded, canceled, postponed, etc.
|
|
72
|
+
## status = node[1]
|
|
73
|
+
#
|
|
74
|
+
## todo - add ## find (optional) match status e.g. [abandoned] or [replay] or [awarded]
|
|
75
|
+
## or [cancelled] or [postponed] etc.
|
|
76
|
+
## status = find_status!( line ) ## todo/check: allow match status also in geo part (e.g. after @) - why? why not?
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
team1 = node.team1
|
|
81
|
+
team2 = node.team2
|
|
82
|
+
|
|
83
|
+
@teams[ team1 ] += 1
|
|
84
|
+
@teams[ team2 ] += 1
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
if node.header ## note - date/time for matches (w/ header) CANNOT get inherited!!
|
|
88
|
+
@last_date = nil
|
|
89
|
+
@last_time = nil
|
|
90
|
+
else ## no (match header), use date/time inheritance rules
|
|
91
|
+
###
|
|
92
|
+
# check if date found?
|
|
93
|
+
# note: ruby falsey is nil & false only (not 0 or empty array etc.)
|
|
94
|
+
if date
|
|
95
|
+
### check: use date_v2 if present? why? why not?
|
|
96
|
+
@last_date = date # keep a reference for later use
|
|
97
|
+
@last_time = nil
|
|
98
|
+
# @last_time = nil
|
|
99
|
+
else
|
|
100
|
+
date = @last_date # no date found; (re)use last seen date
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
if time
|
|
104
|
+
@last_time = time
|
|
105
|
+
else
|
|
106
|
+
time = @last_time
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
group = nil
|
|
113
|
+
group = @last_group if @last_group
|
|
114
|
+
|
|
115
|
+
round = nil
|
|
116
|
+
round = @last_round if @last_round
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
### try auto-fill round
|
|
120
|
+
## find (first) matching round by date if rounds / matchdays defined
|
|
121
|
+
## if not rounds / matchdays defined - YES, allow matches WITHOUT rounds!!!
|
|
122
|
+
if date && round.nil?
|
|
123
|
+
if @rounds.size > 0
|
|
124
|
+
@rounds.values.each do |round_rec|
|
|
125
|
+
## note: convert date to date only (no time) with to_date!!!
|
|
126
|
+
if (round_rec.start_date && round_rec.end_date) &&
|
|
127
|
+
(date.to_date >= round_rec.start_date &&
|
|
128
|
+
date.to_date <= round_rec.end_date)
|
|
129
|
+
round = round_rec
|
|
130
|
+
break
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
if round.nil?
|
|
134
|
+
## todo/fix - issue a warning (do NOT stop)
|
|
135
|
+
puts "!! PARSE ERROR - no matching round found for match date:"
|
|
136
|
+
pp date
|
|
137
|
+
exit 1
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
## todo/check: scores are integers or strings?
|
|
144
|
+
|
|
145
|
+
## todo/check: pass along round and group refs or just string (canonical names) - why? why not?
|
|
146
|
+
|
|
147
|
+
## split date in date & time if DateTime
|
|
148
|
+
=begin
|
|
149
|
+
time_str = nil
|
|
150
|
+
date_str = nil
|
|
151
|
+
if date.is_a?( DateTime )
|
|
152
|
+
date_str = date.strftime('%Y-%m-%d')
|
|
153
|
+
time_str = date.strftime('%H:%M')
|
|
154
|
+
elsif date.is_a?( Date )
|
|
155
|
+
date_str = date.strftime('%Y-%m-%d')
|
|
156
|
+
else # assume date is nil
|
|
157
|
+
end
|
|
158
|
+
=end
|
|
159
|
+
|
|
160
|
+
time_str = nil
|
|
161
|
+
time_local_str = nil
|
|
162
|
+
date_str = nil
|
|
163
|
+
|
|
164
|
+
date_str = date.strftime('%Y-%m-%d') if date
|
|
165
|
+
time_str = time if date && time
|
|
166
|
+
time_local_str = time_local if date && time_local
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
ground = nil
|
|
171
|
+
ground = node.geo if node.geo
|
|
172
|
+
|
|
173
|
+
## attendance
|
|
174
|
+
att = nil
|
|
175
|
+
att = node.att if node.att
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
@matches << Match.new( num: num,
|
|
179
|
+
date: date_str,
|
|
180
|
+
time: time_str,
|
|
181
|
+
time_local: time_local_str,
|
|
182
|
+
team1: team1, ## note: for now always use mapping value e.g. rec (NOT string e.g. team1.name)
|
|
183
|
+
team2: team2, ## note: for now always use mapping value e.g. rec (NOT string e.g. team2.name)
|
|
184
|
+
score: score,
|
|
185
|
+
round: round ? round.name : nil, ## note: for now always use string (assume unique canonical name for event)
|
|
186
|
+
group: group ? group.name : nil, ## note: for now always use string (assume unique canonical name for event)
|
|
187
|
+
status: status,
|
|
188
|
+
ground: ground,
|
|
189
|
+
att: att )
|
|
190
|
+
### todo: cache team lookups in hash?
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
end ## class MatchTree
|
|
195
|
+
end ## module SportDb
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
module SportDb
|
|
2
|
+
class MatchTree
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def on_round_def( node )
|
|
6
|
+
logger.debug "on round def: >#{node}<"
|
|
7
|
+
|
|
8
|
+
## e.g. [[:round_def, "Matchday 1"], [:duration, "Fri Jun 14 - Tue Jun 18"]]
|
|
9
|
+
## [[:round_def, "Matchday 2"], [:duration, "Wed Jun 19 - Sat Jun 22"]]
|
|
10
|
+
## [[:round_def, "Matchday 3"], [:duration, "Sun Jun 23 - Wed Jun 26"]]
|
|
11
|
+
|
|
12
|
+
name = node.name
|
|
13
|
+
# NB: use extracted round name for knockout check
|
|
14
|
+
# knockout_flag = is_knockout_round?( name )
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
##
|
|
18
|
+
## note - do NOT update @last_year on round def dates!!
|
|
19
|
+
## only update for running dates in matches!!
|
|
20
|
+
|
|
21
|
+
if node.date
|
|
22
|
+
start_date = end_date = _build_date( m: node.date[:m],
|
|
23
|
+
d: node.date[:d],
|
|
24
|
+
y: node.date[:y],
|
|
25
|
+
yy: node.date[:yy],
|
|
26
|
+
wday: node.date[:wday],
|
|
27
|
+
start: @start,
|
|
28
|
+
last_year: false )
|
|
29
|
+
elsif node.duration
|
|
30
|
+
## reuse year in start date e.g. July 26-27 1930
|
|
31
|
+
## July 26 [1930], [July] 27 1930
|
|
32
|
+
start_date = _build_date( m: node.duration[:start][:m],
|
|
33
|
+
d: node.duration[:start][:d],
|
|
34
|
+
y: node.duration[:start][:y] || node.duration[:end][:y],
|
|
35
|
+
yy: node.duration[:start][:yy] || node.duration[:end][:yy],
|
|
36
|
+
wday: node.duration[:start][:wday],
|
|
37
|
+
start: @start,
|
|
38
|
+
last_year: false )
|
|
39
|
+
|
|
40
|
+
## reuse month in end date e.g. July 26-27
|
|
41
|
+
## July 26, [July] 27
|
|
42
|
+
end_date = _build_date( m: node.duration[:end][:m] || node.duration[:start][:m],
|
|
43
|
+
d: node.duration[:end][:d],
|
|
44
|
+
y: node.duration[:end][:y],
|
|
45
|
+
yy: node.duration[:end][:yy],
|
|
46
|
+
wday: node.duration[:end][:wday],
|
|
47
|
+
start: @start,
|
|
48
|
+
last_year: false )
|
|
49
|
+
else
|
|
50
|
+
puts "!! PARSE ERROR - expected date or duration for round def; got:"
|
|
51
|
+
pp node
|
|
52
|
+
exit 1
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# note: - NOT needed; start_at and end_at are saved as date only (NOT datetime)
|
|
56
|
+
# set hours,minutes,secs to beginning and end of day (do NOT use default 12.00)
|
|
57
|
+
# e.g. use 00.00 and 23.59
|
|
58
|
+
# start_at = start_at.beginning_of_day
|
|
59
|
+
# end_at = end_at.end_of_day
|
|
60
|
+
|
|
61
|
+
# note: make sure start_at/end_at is date only (e.g. use start_at.to_date)
|
|
62
|
+
# sqlite3 saves datetime in date field as datetime, for example (will break date compares later!)
|
|
63
|
+
|
|
64
|
+
# note - _build_date always returns Date for now - no longer needed!!
|
|
65
|
+
# start_date = start_date.to_date
|
|
66
|
+
# end_date = end_date.to_date
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
logger.debug " start_date: #{start_date}"
|
|
72
|
+
logger.debug " end_date: #{end_date}"
|
|
73
|
+
logger.debug " name: >#{name}<"
|
|
74
|
+
|
|
75
|
+
round = Round.new( name: name,
|
|
76
|
+
start_date: start_date,
|
|
77
|
+
end_date: end_date,
|
|
78
|
+
auto: false )
|
|
79
|
+
|
|
80
|
+
@rounds[ name ] = round
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
end ## class MatchTree
|
|
85
|
+
end ## module SportDb
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
module SportDb
|
|
2
|
+
class MatchTree
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def on_round_outline( node )
|
|
7
|
+
logger.debug "on round outline: >#{node}<"
|
|
8
|
+
|
|
9
|
+
## always reset dates - why? why not?
|
|
10
|
+
## note - needs last_date for year
|
|
11
|
+
## track last_year with extra variable
|
|
12
|
+
|
|
13
|
+
name = node.outline
|
|
14
|
+
level = node.level
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
####
|
|
18
|
+
# check for "old" group header in ("automagic") round outline for now
|
|
19
|
+
##
|
|
20
|
+
## todo/fix - use only names from group def for lookup/is_group match!!!
|
|
21
|
+
## do NOT use (generic) regex!!
|
|
22
|
+
if level == 1 && _is_group?( name )
|
|
23
|
+
logger.debug "on group header: >#{node}<"
|
|
24
|
+
|
|
25
|
+
group = @groups[ name ]
|
|
26
|
+
|
|
27
|
+
if group
|
|
28
|
+
# set group for matches
|
|
29
|
+
@last_group = group
|
|
30
|
+
# note: group header resets (last) round (allows, for example):
|
|
31
|
+
# e.g.
|
|
32
|
+
# Group Playoffs/Replays -- round header
|
|
33
|
+
# team1 team2 -- match
|
|
34
|
+
# Group B -- group header
|
|
35
|
+
# team1 team2 - match (will get new auto-matchday! not last round)
|
|
36
|
+
@last_round = nil
|
|
37
|
+
return ## note - return here; do NOT fall through to std round processing!
|
|
38
|
+
else
|
|
39
|
+
puts "!! WARN - no group def found for >#{name}<; will use a (plain) round"
|
|
40
|
+
end
|
|
41
|
+
end ## is_group?
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
##
|
|
45
|
+
## todo/fix - also reset round name levels on heading 1/2/3 etc.
|
|
46
|
+
## why? why not?
|
|
47
|
+
|
|
48
|
+
if level == 1
|
|
49
|
+
@last_round_name1 = name
|
|
50
|
+
@last_round_name2 = nil
|
|
51
|
+
@last_round_name3 = nil
|
|
52
|
+
elsif level == 2
|
|
53
|
+
@last_round_name2 = name
|
|
54
|
+
@last_round_name3 = nil
|
|
55
|
+
name = [@last_round_name1, name].join( ', ' )
|
|
56
|
+
elsif level == 3
|
|
57
|
+
@last_round_name3 == name
|
|
58
|
+
name = [@last_round_name1, @last_round_name2, name].join( ', ')
|
|
59
|
+
else
|
|
60
|
+
puts "!! ERROR - unsupported round outline level #{level}; use 1-3 - sorry"
|
|
61
|
+
exit 1
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
round = @rounds[ name ]
|
|
66
|
+
if round.nil? ## auto-add / create if missing
|
|
67
|
+
round = Round.new( name: name )
|
|
68
|
+
@rounds[ name ] = round
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
@last_round = round
|
|
72
|
+
@last_group = nil # note: always reset group to no group - why? why not?
|
|
73
|
+
|
|
74
|
+
## todo/fix/check
|
|
75
|
+
## make round a scope for date(time) - why? why not?
|
|
76
|
+
## reset date/time e.g. @last_date = nil !!!!
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
###
|
|
82
|
+
## helpers
|
|
83
|
+
|
|
84
|
+
##
|
|
85
|
+
## note - do NOT match group phase or group playoff or such
|
|
86
|
+
## for now only works for group a,b,c or group 1, group 2, etc.
|
|
87
|
+
|
|
88
|
+
GROUP_RE = %r{\A
|
|
89
|
+
Group [ ] (?:
|
|
90
|
+
[a-z]
|
|
91
|
+
| \d+
|
|
92
|
+
)
|
|
93
|
+
\z}ix
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _is_group?( text )
|
|
97
|
+
## use regex for match
|
|
98
|
+
GROUP_RE.match?( text )
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
end ## class MatchTree
|
|
104
|
+
end ## module SportDb
|
|
@@ -24,7 +24,10 @@ class QuickMatchReader
|
|
|
24
24
|
|
|
25
25
|
def initialize( txt )
|
|
26
26
|
@errors = []
|
|
27
|
-
@
|
|
27
|
+
@txt = txt
|
|
28
|
+
|
|
29
|
+
@league_name = ''
|
|
30
|
+
@matches = []
|
|
28
31
|
end
|
|
29
32
|
|
|
30
33
|
attr_reader :errors
|
|
@@ -35,59 +38,82 @@ class QuickMatchReader
|
|
|
35
38
|
# helpers get matches & more after parse; all stored in data
|
|
36
39
|
#
|
|
37
40
|
## change/rename to event - why? why not?
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
41
|
+
## note - may or may not include season
|
|
42
|
+
def league_name() @league_name; end
|
|
43
|
+
def matches() @matches; end
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
## try to find season in heading
|
|
47
|
+
## e.g. Österr. Bundesliga 2015/16 or 2015-16
|
|
48
|
+
## World Cup 2018 or 2018 World Cup etc.
|
|
49
|
+
SEASON_IN_HEADING_RE = %r{
|
|
50
|
+
\b
|
|
51
|
+
(?<season>\d{4}
|
|
52
|
+
(?:[\/-]\d{1,4})? ## optional 2nd year in season
|
|
53
|
+
)\b
|
|
54
|
+
}x
|
|
51
55
|
|
|
52
56
|
|
|
53
57
|
def parse
|
|
54
58
|
## note: every (new) read call - resets errors list to empty
|
|
55
59
|
@errors = []
|
|
60
|
+
|
|
61
|
+
@league_name = ''
|
|
62
|
+
@matches = []
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
## note - source file MUST always start with heading 1 for now
|
|
66
|
+
tree = []
|
|
67
|
+
parser = RaccMatchParser.new( @txt, debug: debug? ) ## use own parser instance (not shared) - why? why not?
|
|
68
|
+
tree = parser.parse
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
##
|
|
72
|
+
## !! (QUICK) PARSE ERROR - source MUST start with Heading1; got 34 nodes:
|
|
73
|
+
## [<BlankLine>,
|
|
74
|
+
## <BlankLine>,
|
|
75
|
+
## <Heading1 World Cup 1930>,
|
|
76
|
+
|
|
77
|
+
## remove leading BlankLines if any!!
|
|
78
|
+
while tree[0].is_a? RaccMatchParser::BlankLine
|
|
79
|
+
tree.shift ## remove (leading) blank line from parse tree
|
|
80
|
+
end
|
|
56
81
|
|
|
57
|
-
@data = {} # return data hash with leagues
|
|
58
|
-
# and seasons
|
|
59
|
-
# for now merge stage into matches
|
|
60
82
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
83
|
+
if tree[0].is_a? RaccMatchParser::Heading1
|
|
84
|
+
## try to get league and season
|
|
85
|
+
@league_name = tree[0].text
|
|
86
|
+
else
|
|
87
|
+
### report error - source MUST start with heading 1
|
|
88
|
+
puts "!! (QUICK) PARSE ERROR - source MUST start with Heading1; got #{tree.size} nodes:"
|
|
89
|
+
pp tree
|
|
90
|
+
exit
|
|
91
|
+
end
|
|
67
92
|
|
|
93
|
+
## todo/fix
|
|
94
|
+
## make season optional
|
|
95
|
+
m = SEASON_IN_HEADING_RE.match( @league_name )
|
|
96
|
+
if m.nil?
|
|
97
|
+
puts "!! (QUICK) PARSE ERROR - no season found in Heading1 >#{@league_name}; sorry"
|
|
98
|
+
exit
|
|
99
|
+
end
|
|
100
|
+
season = Season.parse( m[:season] ) ## convert (str) to season obj!!!
|
|
68
101
|
start = if season.year?
|
|
69
102
|
Date.new( season.start_year, 1, 1 )
|
|
70
103
|
else
|
|
71
104
|
Date.new( season.start_year, 7, 1 )
|
|
72
105
|
end
|
|
73
106
|
|
|
74
|
-
# if debug?
|
|
75
|
-
# puts " (sec) lines:"
|
|
76
|
-
# pp lines
|
|
77
|
-
# end
|
|
78
|
-
|
|
79
|
-
### note - skip section if no lines !!!!!
|
|
80
|
-
next if lines.empty? ## or use lines.size == 0
|
|
81
|
-
|
|
82
107
|
|
|
83
|
-
|
|
84
|
-
|
|
108
|
+
############
|
|
109
|
+
### "walk" tree to get structs (matches/teams/etc.)
|
|
110
|
+
conv = MatchTree.new( tree, start: start )
|
|
111
|
+
|
|
112
|
+
auto_conf_teams, matches, rounds, groups = conv.convert
|
|
85
113
|
|
|
86
|
-
auto_conf_teams, matches, rounds, groups = parser.parse
|
|
87
114
|
|
|
88
115
|
## auto-add "upstream" errors from parser
|
|
89
|
-
@errors += parser.errors if parser.errors?
|
|
90
|
-
|
|
116
|
+
## @errors += parser.errors if parser.errors?
|
|
91
117
|
|
|
92
118
|
if debug?
|
|
93
119
|
puts ">>> #{auto_conf_teams.size} teams:"
|
|
@@ -100,40 +126,11 @@ class QuickMatchReader
|
|
|
100
126
|
pp groups
|
|
101
127
|
end
|
|
102
128
|
|
|
103
|
-
## note: pass along stage (if present): stage - optional from heading!!!!
|
|
104
|
-
if stage
|
|
105
|
-
matches.each do |match|
|
|
106
|
-
match = match.update( stage: stage )
|
|
107
|
-
end
|
|
108
|
-
end
|
|
109
|
-
|
|
110
|
-
@data[ league ] ||= {}
|
|
111
|
-
@data[ league ][ season.key ] ||= []
|
|
112
129
|
|
|
113
|
-
@
|
|
130
|
+
@matches = matches
|
|
114
131
|
## note - skip teams, rounds, and groups for now
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
## check - only one league and one season
|
|
118
|
-
## allowed in quick style
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
leagues = @data.keys
|
|
122
|
-
if leagues.size != 1
|
|
123
|
-
puts "!! (QUICK) PARSE ERROR - expected one league only; got #{leagues.size}:"
|
|
124
|
-
pp leagues
|
|
125
|
-
exit 1
|
|
126
|
-
end
|
|
127
|
-
|
|
128
|
-
seasons = @data[ leagues[0] ].keys
|
|
129
|
-
if seasons.size != 1
|
|
130
|
-
puts "!! (QUICK) PARSE ERROR - expected one #{leagues[0]} season only; got #{seasons.size}:"
|
|
131
|
-
pp seasons
|
|
132
|
-
exit 1
|
|
133
|
-
end
|
|
134
|
-
|
|
135
|
-
@data[ leagues[0] ][ seasons[0] ]
|
|
136
|
-
end # method parse
|
|
132
|
+
@matches
|
|
133
|
+
end # method parse
|
|
137
134
|
|
|
138
135
|
end # class QuickMatchReader
|
|
139
136
|
end # module SportDb
|