fbtxt-document 0.9.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 3e52e2c2ec111cd72919ddb7c902d39824f00ada678705eaf58ee72dd996ec85
4
+ data.tar.gz: 03ba7e0bf6255fca7abef9ac0adb0add827dc95d61ce72e1d71be88f641cb037
5
+ SHA512:
6
+ metadata.gz: f6b2fe58e43b658dfd8f5716f05f3f264f70a25845edf1cd3829106ce170b8c181cd067afa79003cbbd3d739135371a4403f2054f7cd6cee740e15e308695763
7
+ data.tar.gz: 43252d347f1619a5e5050de924db180e4d69407b6b250630f82228fb785d89f40844a51267a5e0561805ba8df01e904229528843c475a65f8ca5a6cd3625e0db
data/CHANGELOG.md ADDED
@@ -0,0 +1,4 @@
1
+ ### 0.9.0
2
+ ### 0.0.1 / 2024-08-27
3
+
4
+ * Everything is new. First release.
data/Manifest.txt ADDED
@@ -0,0 +1,19 @@
1
+ CHANGELOG.md
2
+ Manifest.txt
3
+ README.md
4
+ Rakefile
5
+ lib/fbtxt/document.rb
6
+ lib/fbtxt/document/match_tree-helpers.rb
7
+ lib/fbtxt/document/match_tree.rb
8
+ lib/fbtxt/document/match_tree/goal.rb
9
+ lib/fbtxt/document/match_tree/group.rb
10
+ lib/fbtxt/document/match_tree/match.rb
11
+ lib/fbtxt/document/match_tree/round.rb
12
+ lib/fbtxt/document/match_tree_on/on_date_header.rb
13
+ lib/fbtxt/document/match_tree_on/on_goal_line.rb
14
+ lib/fbtxt/document/match_tree_on/on_group_def.rb
15
+ lib/fbtxt/document/match_tree_on/on_match_line.rb
16
+ lib/fbtxt/document/match_tree_on/on_round_def.rb
17
+ lib/fbtxt/document/match_tree_on/on_round_outline.rb
18
+ lib/fbtxt/document/quick_match_reader.rb
19
+ lib/fbtxt/document/version.rb
data/README.md ADDED
@@ -0,0 +1,21 @@
1
+ # fbtxt-document - football.txt match readers (incl. Fbtxt::Document) and more
2
+
3
+
4
+
5
+ ## Usage
6
+
7
+
8
+ ``` ruby
9
+ require 'fbtxt/document'
10
+
11
+
12
+ # path = "./euro/2024--germany/euro.txt"
13
+ path = "./deutschland/2024-25/1-bundesliga.txt"
14
+
15
+ doc = Fbtxt::Document.read( path )
16
+ pp doc
17
+
18
+ # try json for matches
19
+ data = doc.matches.map {|match| match.as_json }
20
+ pp data
21
+ ```
data/Rakefile ADDED
@@ -0,0 +1,33 @@
1
+ require 'hoe'
2
+ require './lib/fbtxt/document/version.rb'
3
+
4
+
5
+
6
+ Hoe.spec 'fbtxt-document' do
7
+
8
+ self.version = Fbtxt::Module::Document::VERSION
9
+
10
+ self.summary = "fbtxt-document - football.txt match readers (incl. Fbtxt::Document) and more"
11
+ self.description = summary
12
+
13
+ self.urls = { home: 'https://github.com/sportdb/sport.db' }
14
+
15
+ self.author = 'Gerald Bauer'
16
+ self.email = 'gerald.bauer@gmail.com'
17
+
18
+ # switch extension to .markdown for gihub formatting
19
+ self.readme_file = 'README.md'
20
+ self.history_file = 'CHANGELOG.md'
21
+
22
+ self.licenses = ['Public Domain']
23
+
24
+ self.extra_deps = [
25
+ ['fbtxt-parser', '>= 0.9.0'],
26
+ ['season-formats', '>= 0.1.0'],
27
+ ## ['logutils', '>= 0.6.1'],
28
+ ]
29
+
30
+ self.spec_extras = {
31
+ required_ruby_version: '>= 3.1.0'
32
+ }
33
+ end
@@ -0,0 +1,67 @@
1
+ module Fbtxt
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( q )
51
+ q.group( 4, '<Goal', '>') do ## group( indent, open, close)
52
+ buf = String.new
53
+ buf << " #{@player} #{@minute}"
54
+ buf << "+#{@offset}" if @offset && @offset > 0
55
+ buf << "'"
56
+ q.text( buf )
57
+
58
+ q.text( "(og)" ) if @owngoal
59
+ q.text( "(p)" ) if @penalty
60
+ q.text( " for #{@team}" ) ### team 1 or 2 - use home/away
61
+ end
62
+ end
63
+ end # class Goal
64
+
65
+
66
+ end # class MatchTree
67
+ end # module Fbtxt
@@ -0,0 +1,23 @@
1
+ module Fbtxt
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( q )
14
+ q.group( 4, '<Group', '>') do ## group( indent, open, close)
15
+ q.text( " #{@name} " )
16
+ q.pp( @teams )
17
+ end
18
+ end
19
+ end # class Group
20
+
21
+
22
+ end # class MatchTree
23
+ end # module Fbtxt
@@ -0,0 +1,249 @@
1
+ ##
2
+ # move (simpler) struct version inline to MatchTree for now
3
+ #
4
+ module Fbtxt
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
+
153
+
154
+ if @date
155
+ ## assume 2020-09-19 date format!!
156
+ data['date'] = @date.is_a?( String ) ? @date : @date.strftime('%Y-%m-%d')
157
+
158
+ data['time'] = @time if @time
159
+ data['time_local'] = @time_local if @time_local
160
+ end
161
+
162
+
163
+ data['team1'] = @team1.is_a?( String ) ? @team1 : @team1.name
164
+
165
+ ## note - for match status bye team2 is nil!!!
166
+ ## e.g. Queen's Park bye
167
+ ## Wanderers bye
168
+ ## todo/check - keep bye as a match - why? why not?
169
+ ## has no date/time & venue & score etc.
170
+ if @team2
171
+ data['team2'] = @team2.is_a?( String ) ? @team2 : @team2.name
172
+ end
173
+
174
+ ## note - score might be
175
+ ## 1) array e.g. [0,1]
176
+ ## 2) hash e.g. { ft: [0,1] } etc.
177
+ ## note - w/o (walkout) do NOT add empty score
178
+ if @score.is_a?(Hash)
179
+ # note: make sure hash keys are always strings
180
+ data['score'] = @score.transform_keys(&:to_s)
181
+ elsif @score.is_a?(Array)
182
+ ## note:
183
+ ## for now always assume full-time (ft)
184
+ ## in future check for score note or such
185
+ ## to use "plain" array or such - why? why not?
186
+ ## data['score'] = { 'ft' => @score } if !@score.empty?
187
+
188
+ data['score'] = @score if !@score.empty?
189
+ end
190
+
191
+
192
+ ## data['score']['ht'] = [@score1i, @score2i] if @score1i && @score2i
193
+ ## data['score']['ft'] = [@score1, @score2] if @score1 && @score2
194
+ ## data['score']['et'] = [@score1et, @score2et] if @score1et && @score2et
195
+ ## data['score']['p'] = [@score1p, @score2p] if @score1p && @score2p
196
+
197
+
198
+
199
+ ### check for goals
200
+ if @goals && @goals.size > 0
201
+ data['goals1'] = []
202
+ data['goals2'] = []
203
+
204
+ @goals.each do |goal|
205
+ node = {}
206
+ node['name'] = goal.player
207
+
208
+ ## note - use a string for minutes for now
209
+ ## allows e.g. 45+2 etc. too
210
+ minute_str = "#{goal.minute}"
211
+ minute_str += "+#{goal.offset}" if goal.offset
212
+
213
+ node['minute'] = minute_str
214
+
215
+ node['owngoal'] = true if goal.owngoal
216
+ node['penalty'] = true if goal.penalty
217
+
218
+ if goal.team == 1
219
+ data['goals1'] << node
220
+ else ## assume 2
221
+ data['goals2'] << node
222
+ end
223
+ end # each goal
224
+ end
225
+
226
+
227
+ data['status'] = @status if @status
228
+
229
+ data['group'] = @group if @group
230
+
231
+ if @ground
232
+ ## note: might be array of string e.g. ['Wembley', 'London']
233
+ ##
234
+ ## todo/check - auto-join to string - why? why not?
235
+ ## e.g. ['Wembley', 'London']
236
+ ## to 'Wembley, London'
237
+ ## note - auto-join geo tree for now
238
+ data['ground'] = @ground.join(', ')
239
+ end
240
+
241
+ data['attendance'] = @att if @att
242
+ data
243
+ end
244
+ end # class Match
245
+
246
+
247
+
248
+ end # class MatchTree
249
+ end # module Fbtxt
@@ -0,0 +1,34 @@
1
+
2
+ module Fbtxt
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( q )
20
+ ## todo/check - how to display/format key - use () or not - why? why not?
21
+ q.group( 4, '<Round', '>') do ## group( indent, open, close)
22
+ q.text( " AUTO" ) if @auto
23
+ q.text( " #{@name}" )
24
+ if @start_date
25
+ q.text( " : #{@start_date}" )
26
+ q.text( " - #{@end_date}" ) if @start_date != @end_date
27
+ end
28
+ end
29
+ end
30
+ end # class Round
31
+
32
+
33
+ end # class MatchTree
34
+ end # module Fbtxt
@@ -0,0 +1,81 @@
1
+
2
+ module Fbtxt
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 Fbtxt
@@ -0,0 +1,126 @@
1
+ module Fbtxt
2
+
3
+
4
+ ##############################
5
+ ## simple (match) parse tree to structs walker/handler/converter
6
+ class MatchTree
7
+ include Debuggable
8
+
9
+
10
+ ##
11
+ ## note: allow start(_date) nil
12
+ ## if in use (start: nil) years expected on first date!!!
13
+
14
+ def initialize( tree, start: nil )
15
+ @tree = tree
16
+ @start = start
17
+
18
+ @errors = []
19
+ end
20
+
21
+ attr_reader :errors
22
+ def errors?() @errors.size > 0; end
23
+
24
+
25
+
26
+ def convert
27
+ ## note: every (new) read call - resets errors list to empty
28
+ @errors = []
29
+ @warns = [] ## track list of warnings (unmatched lines) too - why? why not?
30
+
31
+ ### todo/fix - FIX/FIX
32
+ ## check start year from first date
33
+ ## for now (auto-)update - @start with every date that incl. a year!!!
34
+ @last_year = nil
35
+ @last_date = nil
36
+ @last_time = nil
37
+
38
+ ## todo/fix - use stack push/pop in the future - why? why not?
39
+ @last_round = nil ## merge - "top-level" - Round struct
40
+ @last_round_name1 = nil ## level 1 - string
41
+ @last_round_name2 = nil ## level 2 - string
42
+ @last_round_name3 = nil ## level 3 - string
43
+
44
+ @last_group = nil
45
+
46
+
47
+ @teams = Hash.new(0) ## track counts (only) for now for (interal) team stats - why? why not?
48
+ @rounds = {}
49
+ @groups = {}
50
+ @matches = []
51
+
52
+
53
+ @tree.each do |node|
54
+ if node.is_a? RaccMatchParser::RoundDef
55
+ ## todo/fix: add round definition (w begin n end date)
56
+ ## todo: do not patch rounds with definition (already assume begin/end date is good)
57
+ ## -- how to deal with matches that get rescheduled/postponed?
58
+ on_round_def( node )
59
+ elsif node.is_a? RaccMatchParser::GroupDef ## NB: group goes after round (round may contain group marker too)
60
+ ### todo: add pipe (|) marker (required)
61
+ on_group_def( node )
62
+ elsif node.is_a? RaccMatchParser::RoundOutline
63
+ on_round_outline( node )
64
+ elsif node.is_a? RaccMatchParser::DateHeader
65
+ on_date_header( node )
66
+ elsif node.is_a? RaccMatchParser::MatchLine
67
+ on_match_line( node )
68
+ elsif node.is_a? RaccMatchParser::MatchLineBye
69
+ on_match_line_bye( node )
70
+ elsif node.is_a? RaccMatchParser::GoalLine
71
+ on_goal_line( node )
72
+ elsif node.is_a?( RaccMatchParser::LineupLine ) ||
73
+ node.is_a?( RaccMatchParser::RefereeLine )
74
+ ## skip lineup, referee props for now
75
+ elsif node.is_a?( RaccMatchParser::Heading1 ) ||
76
+ node.is_a?( RaccMatchParser::Heading2 ) ||
77
+ node.is_a?( RaccMatchParser::Heading3 )
78
+ ### skip headings (1/2/3) for now
79
+ elsif node.is_a?( RaccMatchParser::BlankLine )
80
+ ### skip for now; do nothing
81
+ else
82
+ ## report error
83
+ msg = "!! WARN - unknown node (parse tree type) - #{node.class.name}"
84
+ puts msg
85
+ pp node
86
+
87
+ log( msg )
88
+ log( node.pretty_inspect )
89
+ end
90
+ end # tree.each
91
+
92
+ ## note - team keys are names and values are "internal" stats e.g. usage count!!
93
+ ## and NOT team/club/nat_team structs!!
94
+ [@teams.keys, @matches, @rounds.values, @groups.values]
95
+ end # method convert
96
+
97
+
98
+
99
+
100
+ def on_match_line_bye( node )
101
+ _trace( "on match (bye): >#{node}<" )
102
+
103
+ ## note - bye records NO date/time or ground (or score etc.)
104
+ ## for now only team1/team2 and match status!!
105
+ ## plus inherited round/group
106
+
107
+ status = 'bye'
108
+
109
+ team = node.team
110
+
111
+ @teams[ team ] += 1
112
+
113
+ group = nil
114
+ group = @last_group if @last_group
115
+
116
+ round = nil
117
+ round = @last_round if @last_round
118
+
119
+ @matches << Match.new( team1: team, ## note: for now always use mapping value e.g. rec (NOT string e.g. team1.name)
120
+ round: round ? round.name : nil, ## note: for now always use string (assume unique canonical name for event)
121
+ group: group ? group.name : nil, ## note: for now always use string (assume unique canonical name for event)
122
+ status: status )
123
+ ### todo: cache team lookups in hash?
124
+ end
125
+ end # class MatchTree
126
+ end # module Fbtxt