sportdb-formats 0.4.0 → 1.0.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.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/Manifest.txt +24 -4
  3. data/Rakefile +3 -3
  4. data/lib/sportdb/formats.rb +25 -2
  5. data/lib/sportdb/formats/config.rb +40 -0
  6. data/lib/sportdb/formats/datafile.rb +42 -62
  7. data/lib/sportdb/formats/datafile_package.rb +160 -0
  8. data/lib/sportdb/formats/match/conf_parser.rb +120 -0
  9. data/lib/sportdb/formats/match/mapper.rb +319 -0
  10. data/lib/sportdb/formats/match/mapper_teams.rb +23 -0
  11. data/lib/sportdb/formats/match/match_parser.rb +659 -0
  12. data/lib/sportdb/formats/match/match_parser_auto_conf.rb +202 -0
  13. data/lib/sportdb/formats/name_helper.rb +84 -0
  14. data/lib/sportdb/formats/outline_reader.rb +53 -15
  15. data/lib/sportdb/formats/package.rb +172 -160
  16. data/lib/sportdb/formats/parser_helper.rb +81 -0
  17. data/lib/sportdb/formats/score/score_formats.rb +180 -0
  18. data/lib/sportdb/formats/score/score_parser.rb +196 -0
  19. data/lib/sportdb/formats/structs/country.rb +1 -43
  20. data/lib/sportdb/formats/structs/group.rb +25 -0
  21. data/lib/sportdb/formats/structs/league.rb +7 -26
  22. data/lib/sportdb/formats/structs/match.rb +72 -51
  23. data/lib/sportdb/formats/structs/round.rb +14 -4
  24. data/lib/sportdb/formats/structs/season.rb +3 -0
  25. data/lib/sportdb/formats/structs/team.rb +144 -0
  26. data/lib/sportdb/formats/version.rb +2 -2
  27. data/test/helper.rb +83 -1
  28. data/test/test_clubs.rb +3 -3
  29. data/test/test_conf.rb +65 -0
  30. data/test/test_datafile.rb +21 -30
  31. data/test/test_match.rb +0 -6
  32. data/test/test_match_auto.rb +72 -0
  33. data/test/test_match_auto_champs.rb +45 -0
  34. data/test/test_match_auto_euro.rb +37 -0
  35. data/test/test_match_auto_worldcup.rb +61 -0
  36. data/test/test_match_champs.rb +27 -0
  37. data/test/test_match_eng.rb +26 -0
  38. data/test/test_match_euro.rb +27 -0
  39. data/test/test_match_worldcup.rb +27 -0
  40. data/test/test_name_helper.rb +67 -0
  41. data/test/test_outline_reader.rb +3 -3
  42. data/test/test_package.rb +21 -2
  43. data/test/test_package_match.rb +78 -0
  44. data/test/test_scores.rb +67 -51
  45. metadata +32 -12
  46. data/lib/sportdb/formats/scores.rb +0 -253
  47. data/lib/sportdb/formats/structs/club.rb +0 -213
  48. data/test/test_club_helpers.rb +0 -63
  49. data/test/test_datafile_match.rb +0 -65
@@ -0,0 +1,659 @@
1
+ # encoding: utf-8
2
+
3
+ module SportDb
4
+
5
+ class MatchParserSimpleV2 ## simple match parser for team match schedules
6
+
7
+ def self.parse( lines, teams, start: )
8
+ ## todo/fix: add support for txt and lines
9
+ ## check if lines_or_txt is an array or just a string
10
+ ## use teams: like start: why? why not?
11
+ parser = new( lines, teams, start )
12
+ parser.parse
13
+ end
14
+
15
+
16
+ include Logging ## e.g. logger#debug, logger#info, etc.
17
+ include ParserHelper ## e.g. read_lines, etc.
18
+
19
+
20
+ def initialize( lines, teams, start )
21
+ # for convenience split string into lines
22
+ ## note: removes/strips empty lines
23
+ ## todo/check: change to text instead of array of lines - why? why not?
24
+ @lines = lines.is_a?( String ) ? read_lines( lines ) : lines
25
+
26
+ @mapper_teams = TeamMapper.new( teams )
27
+ @start = start
28
+ end
29
+
30
+
31
+ def parse
32
+ @last_date = nil
33
+ @last_round = nil
34
+ @last_group = nil
35
+
36
+ @rounds = {}
37
+ @groups = {}
38
+ @matches = []
39
+
40
+ @warns = [] ## track list of warnings (unmatched lines) too - why? why not?
41
+
42
+
43
+ @lines.each do |line|
44
+ if is_goals?( line )
45
+ logger.debug "skipping matched goals line: >#{line}<"
46
+ elsif is_round_def?( line )
47
+ ## todo/fix: add round definition (w begin n end date)
48
+ ## todo: do not patch rounds with definition (already assume begin/end date is good)
49
+ ## -- how to deal with matches that get rescheduled/postponed?
50
+ parse_round_def( line )
51
+ elsif is_round?( line )
52
+ parse_round_header( line )
53
+ elsif is_group_def?( line ) ## NB: group goes after round (round may contain group marker too)
54
+ ### todo: add pipe (|) marker (required)
55
+ parse_group_def( line )
56
+ elsif is_group?( line )
57
+ ## -- lets you set group e.g. Group A etc.
58
+ parse_group_header( line )
59
+ elsif try_parse_game( line )
60
+ # do nothing here
61
+ elsif try_parse_date_header( line )
62
+ # do nothing here
63
+ else
64
+ logger.warn "skipping line (no match found): >#{line}<"
65
+ @warns << line
66
+ end
67
+ end # lines.each
68
+
69
+ [@matches, @rounds.values, @groups.values]
70
+ end # method parse
71
+
72
+
73
+
74
+ def parse_group_header( line )
75
+ logger.debug "parsing group header line: >#{line}<"
76
+
77
+ # note: group header resets (last) round (allows, for example):
78
+ # e.g.
79
+ # Group Playoffs/Replays -- round header
80
+ # team1 team2 -- match
81
+ # Group B: -- group header
82
+ # team1 team2 - match (will get new auto-matchday! not last round)
83
+ @last_round = nil
84
+
85
+ title, pos = find_group_title_and_pos!( line )
86
+
87
+ logger.debug " title: >#{title}<"
88
+ logger.debug " pos: >#{pos}<"
89
+ logger.debug " line: >#{line}<"
90
+
91
+ group = @groups[ title ]
92
+ if group.nil?
93
+ puts "!! ERROR - no group def found for >#{title}<"
94
+ exit 1
95
+ end
96
+
97
+ # set group for games
98
+ @last_group = group
99
+ end
100
+
101
+ def parse_group_def( line )
102
+ logger.debug "parsing group def line: >#{line}<"
103
+
104
+ @mapper_teams.map_teams!( line )
105
+ teams = @mapper_teams.find_teams!( line )
106
+
107
+ title, pos = find_group_title_and_pos!( line )
108
+
109
+ logger.debug " line: >#{line}<"
110
+
111
+ group = Import::Group.new( pos: pos,
112
+ title: title,
113
+ teams: teams.map {|team| team.title } )
114
+
115
+ @groups[ title ] = group
116
+ end
117
+
118
+
119
+ def find_group_title_and_pos!( line )
120
+ ## group pos - for now support single digit e.g 1,2,3 or letter e.g. A,B,C or HEX
121
+ ## nb: (?:) = is for non-capturing group(ing)
122
+
123
+ ## fix:
124
+ ## get Group|Gruppe|Grupo from lang!!!! do NOT hardcode in place
125
+
126
+ ## todo:
127
+ ## check if Group A: or [Group A] works e.g. : or ] get matched by \b ???
128
+ regex = /(?:Group|Gruppe|Grupo)\s+((?:\d{1}|[A-Z]{1,3}))\b/
129
+
130
+ m = regex.match( line )
131
+
132
+ return [nil,nil] if m.nil?
133
+
134
+ pos = case m[1]
135
+ when 'A' then 1
136
+ when 'B' then 2
137
+ when 'C' then 3
138
+ when 'D' then 4
139
+ when 'E' then 5
140
+ when 'F' then 6
141
+ when 'G' then 7
142
+ when 'H' then 8
143
+ when 'I' then 9
144
+ when 'J' then 10
145
+ when 'K' then 11
146
+ when 'L' then 12
147
+ when 'HEX' then 666 # HEX for Hexagonal - todo/check: map to something else ??
148
+ else m[1].to_i
149
+ end
150
+
151
+ title = m[0]
152
+
153
+ logger.debug " title: >#{title}<"
154
+ logger.debug " pos: >#{pos}<"
155
+
156
+ line.sub!( regex, '[GROUP.TITLE+POS]' )
157
+
158
+ [title,pos]
159
+ end
160
+
161
+
162
+ def parse_round_def( line )
163
+ logger.debug "parsing round def line: >#{line}<"
164
+
165
+ start_date = find_date!( line, start: @start )
166
+ end_date = find_date!( line, start: @start )
167
+
168
+ # note: if end_date missing -- assume start_date is (==) end_at
169
+ end_date = start_date if end_date.nil?
170
+
171
+ # note: - NOT needed; start_at and end_at are saved as date only (NOT datetime)
172
+ # set hours,minutes,secs to beginning and end of day (do NOT use default 12.00)
173
+ # e.g. use 00.00 and 23.59
174
+ # start_at = start_at.beginning_of_day
175
+ # end_at = end_at.end_of_day
176
+
177
+ # note: make sure start_at/end_at is date only (e.g. use start_at.to_date)
178
+ # sqlite3 saves datetime in date field as datetime, for example (will break date compares later!)
179
+ start_date = start_date.to_date
180
+ end_date = end_date.to_date
181
+
182
+
183
+ pos = find_round_pos!( line )
184
+ title = find_round_def_title!( line )
185
+ # NB: use extracted round title for knockout check
186
+ knockout_flag = is_knockout_round?( title )
187
+
188
+
189
+ logger.debug " start_date: #{start_date}"
190
+ logger.debug " end_date: #{end_date}"
191
+ logger.debug " pos: #{pos}"
192
+ logger.debug " title: >#{title}<"
193
+ logger.debug " knockout_flag: #{knockout_flag}"
194
+
195
+ logger.debug " line: >#{line}<"
196
+
197
+ #######################################
198
+ # todo/fix: add auto flag is false !!!! - why? why not?
199
+ round = Import::Round.new( pos: pos,
200
+ title: title,
201
+ start_date: start_date,
202
+ end_date: end_date,
203
+ knockout: knockout_flag,
204
+ auto: false )
205
+
206
+ @rounds[ title ] = round
207
+ end
208
+
209
+
210
+
211
+ def find_round_pos!( line )
212
+ # pass #1) extract optional round pos from line
213
+ # e.g. (1) - must start line
214
+ regex_pos = /^[ \t]*\((\d{1,3})\)[ \t]+/
215
+
216
+ # pass #2) find free standing number e.g. Matchday 3 or Round 5 or 3. Spieltag etc.
217
+ # note: /\b(\d{1,3})\b/
218
+ # will match -12
219
+ # thus, use space required - will NOT match -2 e.g. Group-2 Play-off
220
+ # note: allow 1. Runde n
221
+ # 1^ Giornata
222
+ regex_num = /(?:^|\s)(\d{1,3})(?:[.\^\s]|$)/
223
+
224
+ if line =~ regex_pos
225
+ logger.debug " pos: >#{$1}<"
226
+
227
+ line.sub!( regex_pos, '[ROUND.POS] ' ) ## NB: add back trailing space that got swallowed w/ regex -> [ \t]+
228
+ return $1.to_i
229
+ elsif line =~ regex_num
230
+ ## assume number in title is pos (e.g. Jornada 3, 3 Runde etc.)
231
+ ## NB: do NOT remove pos from string (will get removed by round title)
232
+
233
+ num = $1.to_i # note: clone capture; keep a copy (another regex follows; will redefine $1)
234
+
235
+ #### fix:
236
+ # use/make keywords required
237
+ # e.g. Round of 16 -> should NOT match 16!
238
+ # Spiel um Platz 3 (or 5) etc -> should NOT match 3!
239
+ # Round 16 - ok
240
+ # thus, check for required keywords
241
+
242
+ ## quick hack for round of 16
243
+ # todo: mask match e.g. Round of xxx ... and try again - might include something
244
+ # reuse pattern for Group XX Replays for example
245
+ if line =~ /^\s*Round of \d{1,3}\b/
246
+ return nil
247
+ end
248
+
249
+ logger.debug " pos: >#{num}<"
250
+ return num
251
+ else
252
+ ## fix: add logger.warn no round pos found in line
253
+ return nil
254
+ end
255
+ end # method find_round_pos!
256
+
257
+ def find_round_def_title!( line )
258
+ # assume everything before pipe (\) is the round title
259
+ # strip [ROUND.POS], todo:?? [ROUND.TITLE2]
260
+
261
+ # todo/fix: add title2 w/ // or / why? why not?
262
+ # -- strip / or / chars
263
+
264
+ buf = line.dup
265
+ logger.debug " find_round_def_title! line-before: >>#{buf}<<"
266
+
267
+ ## cut-off everything after (including) pipe (|)
268
+ buf = buf[ 0...buf.index('|') ]
269
+
270
+ # e.g. remove [ROUND.POS], [ROUND.TITLE2], [GROUP.TITLE+POS] etc.
271
+ buf.gsub!( /\[[^\]]+\]/, '' ) ## fix: use helper for (re)use e.g. remove_match_placeholder/marker or similar?
272
+ # remove leading and trailing whitespace
273
+ buf.strip!
274
+
275
+ logger.debug " find_round_def_title! line-after: >>#{buf}<<"
276
+
277
+ logger.debug " title: >>#{buf}<<"
278
+ line.sub!( buf, '[ROUND.TITLE]' )
279
+
280
+ buf
281
+ end
282
+
283
+ def find_round_header_title!( line )
284
+ # assume everything left is the round title
285
+ # extract all other items first (round title2, round pos, group title n pos, etc.)
286
+
287
+ ## todo/fix:
288
+ ## cleanup method
289
+ ## use buf.index( '//' ) to split string (see found_round_def)
290
+ ## why? simpler why not?
291
+ ## - do we currently allow groups if title2 present? add example if it works?
292
+
293
+ buf = line.dup
294
+ logger.debug " find_round_header_title! line-before: >>#{buf}<<"
295
+
296
+ buf.gsub!( /\[[^\]]+\]/, '' ) # e.g. remove [ROUND.POS], [ROUND.TITLE2], [GROUP.TITLE+POS] etc.
297
+ buf.strip! # remove leading and trailing whitespace
298
+
299
+ logger.debug " find_round_title! line-after: >>#{buf}<<"
300
+
301
+ ### bingo - assume what's left is the round title
302
+
303
+ logger.debug " title: >>#{buf}<<"
304
+ line.sub!( buf, '[ROUND.TITLE]' )
305
+
306
+ buf
307
+ end
308
+
309
+
310
+ def parse_round_header( line )
311
+ logger.debug "parsing round header line: >#{line}<"
312
+
313
+ ## todo/check/fix:
314
+ # make sure Round of 16 will not return pos 16 -- how? possible?
315
+ # add unit test too to verify
316
+ pos = find_round_pos!( line )
317
+
318
+ title = find_round_header_title!( line )
319
+
320
+ logger.debug " line: >#{line}<"
321
+
322
+
323
+ round = @rounds[ title ]
324
+ if round.nil? ## auto-add / create if missing
325
+ round = Import::Round.new( pos: pos,
326
+ title: title )
327
+ @rounds[ title ] = round
328
+ end
329
+
330
+ ## todo/check: if pos match (MUST always match for now)
331
+ @last_round = round
332
+ @last_group = nil # note: reset group to no group - why? why not?
333
+
334
+
335
+ ## NB: dummy/placeholder start_at, end_at date
336
+ ## replace/patch after adding all games for round
337
+
338
+ =begin
339
+ round_attribs = {
340
+ title: title,
341
+ title2: title2,
342
+ knockout: knockout_flag
343
+ }
344
+
345
+ if pos > 999000
346
+ # no pos (e.g. will get autonumbered later) - try match by title for now
347
+ # e.g. lets us use title 'Group Replays', for example, multiple times
348
+ @round = Round.find_by_event_id_and_title( @event.id, title )
349
+ else
350
+ @round = Round.find_by_event_id_and_pos( @event.id, pos )
351
+ end
352
+
353
+ if @round.present?
354
+ logger.debug "update round #{@round.id}:"
355
+ else
356
+ logger.debug "create round:"
357
+ @round = Round.new
358
+
359
+ round_attribs = round_attribs.merge( {
360
+ event_id: @event.id,
361
+ pos: pos,
362
+ start_at: Date.parse('1911-11-11'),
363
+ end_at: Date.parse('1911-11-11')
364
+ })
365
+ end
366
+
367
+ logger.debug round_attribs.to_json
368
+
369
+ @round.update_attributes!( round_attribs )
370
+
371
+ @patch_round_ids_pos << @round.id if pos > 999000
372
+ ### store list of round ids for patching start_at/end_at at the end
373
+ @patch_round_ids_dates << @round.id # todo/fix/check: check if round has definition (do NOT patch if definition (not auto-added) present)
374
+ =end
375
+ end
376
+
377
+
378
+ def find_score!( line )
379
+ # note: always call after find_dates !!!
380
+ # scores match date-like patterns!! e.g. 10-11 or 10:00 etc.
381
+ # -- note: score might have two digits too
382
+
383
+ ScoreFormats.find!( line )
384
+ end
385
+
386
+ def try_parse_game( line )
387
+ # note: clone line; for possible test do NOT modify in place for now
388
+ # note: returns true if parsed, false if no match
389
+ parse_game( line.dup )
390
+ end
391
+
392
+
393
+ def parse_game( line )
394
+ logger.debug "parsing game (fixture) line: >#{line}<"
395
+
396
+ ## split by geo (@) - remove for now
397
+ ## split into parts e.g. break using @ !!!
398
+ values = line.split( '@' )
399
+ line = values[0]
400
+
401
+
402
+ @mapper_teams.map_teams!( line ) ### todo/fix: limit mapping to two(2) teams - why? why not? might avoid matching @ Barcelona ??
403
+ teams = @mapper_teams.find_teams!( line )
404
+ team1 = teams[0]
405
+ team2 = teams[1]
406
+
407
+ ## note: if we do NOT find two teams; return false - no match found
408
+ if team1.nil? || team2.nil?
409
+ logger.debug " no game match (two teams required) found for line: >#{line}<"
410
+ return false
411
+ end
412
+
413
+ ## pos = find_game_pos!( line )
414
+
415
+ date = find_date!( line, start: @start )
416
+
417
+ ###
418
+ # check if date found?
419
+ # note: ruby falsey is nil & false only (not 0 or empty array etc.)
420
+ if date
421
+ ### check: use date_v2 if present? why? why not?
422
+ @last_date = date # keep a reference for later use
423
+ else
424
+ date = @last_date # no date found; (re)use last seen date
425
+ end
426
+
427
+
428
+ score = find_score!( line )
429
+
430
+ logger.debug " line: >#{line}<"
431
+
432
+
433
+ round = nil
434
+ if @last_round
435
+ round = @last_round
436
+ else
437
+ ## find (first) matching round by date
438
+ @rounds.values.each do |round_rec|
439
+ ## note: convert date to date only (no time) with to_date!!!
440
+ if (round_rec.start_date && round_rec.end_date) &&
441
+ (date.to_date >= round_rec.start_date &&
442
+ date.to_date <= round_rec.end_date)
443
+ round = round_rec
444
+ break
445
+ end
446
+ end
447
+ if round.nil?
448
+ puts "!! ERROR - no matching round found for match date:"
449
+ pp date
450
+ exit 1
451
+ end
452
+ end
453
+
454
+
455
+ ## todo/check: scores are integers or strings?
456
+
457
+ ## todo/check: pass along round and group refs or just string (canonical names) - why? why not?
458
+
459
+ @matches << Import::Match.new( date: date,
460
+ team1: team1, ## note: for now always use mapping value e.g. rec (NOT string e.g. team1.title)
461
+ team2: team2, ## note: for now always use mapping value e.g. rec (NOT string e.g. team2.title)
462
+ score: score,
463
+ round: round ? round.title : nil, ## note: for now always use string (assume unique canonical name for event)
464
+ group: @last_group ? @last_group.title : nil ) ## note: for now always use string (assume unique canonical name for event)
465
+
466
+ ### todo: cache team lookups in hash?
467
+
468
+ =begin
469
+ team1 = Team.find_by_key!( team1_key )
470
+ team2 = Team.find_by_key!( team2_key )
471
+
472
+ @last_team1 = team1 # store for later use for goals etc.
473
+ @last_team2 = team2
474
+
475
+
476
+ if @round.nil?
477
+ ## no round header found; calculate round from date
478
+
479
+ ###
480
+ ## todo/fix: add some unit tests for round look up
481
+ # fix: use date_v2 if present!! (old/original date; otherwise use date)
482
+
483
+ #
484
+ # fix: check - what to do with hours e.g. start_at use 00:00 and for end_at use 23.59 ??
485
+ # -- for now - remove hours (e.g. use end_of_day and beginnig_of_day)
486
+
487
+ ##
488
+ # note: start_at and end_at are dates ONLY (note datetime)
489
+ # - do NOT pass in hours etc. in query
490
+ # again use --> date.end_of_day, date.beginning_of_day
491
+ # new: not working: date.to_date, date.to_date
492
+ # will not find round if start_at same as date !! (in theory hours do not matter)
493
+
494
+ ###
495
+ # hack:
496
+ # special case for sqlite3 (date compare not working reliable; use casts)
497
+ # fix: move to adapter_name to activerecord_utils as sqlite? or similar?
498
+
499
+ if ActiveRecord::Base.connection.adapter_name.downcase.starts_with?( 'sqlite' )
500
+ logger.debug( " [sqlite] using sqlite-specific query for date compare for rounds finder" )
501
+ round = Round.where( 'event_id = ? AND ( julianday(start_at) <= julianday(?)'+
502
+ 'AND julianday(end_at) >= julianday(?))',
503
+ @event.id, date.to_date, date.to_date).first
504
+ else # all other dbs (postgresql, mysql, etc.)
505
+ round = Round.where( 'event_id = ? AND (start_at <= ? AND end_at >= ?)',
506
+ @event.id, date.to_date, date.to_date).first
507
+ end
508
+
509
+ pp round
510
+ if round.nil?
511
+ logger.warn( " !!!! no round match found for date #{date}" )
512
+ pp Round.all
513
+
514
+ ###################################
515
+ # -- try auto-adding matchday
516
+ round = Round.new
517
+
518
+ round_attribs = {
519
+ event_id: @event.id,
520
+ title: "Matchday #{date.to_date}",
521
+ pos: 999001+@patch_round_ids_pos.length, # e.g. 999<count> - 999001,999002,etc.
522
+ start_at: date.to_date,
523
+ end_at: date.to_date
524
+ }
525
+
526
+ logger.info( " auto-add round >Matchday #{date.to_date}<" )
527
+ logger.debug round_attribs.to_json
528
+
529
+ round.update_attributes!( round_attribs )
530
+
531
+ @patch_round_ids_pos << round.id # todo/check - add just id or "full" record as now - why? why not?
532
+ end
533
+
534
+ # store pos for auto-number next round if missing
535
+ # - note: only if greater/bigger than last; use max
536
+ # - note: last_round_pos might be nil - thus set to 0
537
+ if round.pos > 999000
538
+ # note: do NOT update last_round_pos for to-be-patched rounds
539
+ else
540
+ @last_round_pos = [round.pos,@last_round_pos||0].max
541
+ end
542
+
543
+ ## note: will crash (round.pos) if round is nil
544
+ logger.debug( " using round #{round.pos} >#{round.title}< start_at: #{round.start_at}, end_at: #{round.end_at}" )
545
+ else
546
+ ## use round from last round header
547
+ round = @round
548
+ end
549
+
550
+
551
+ ### check if games exists
552
+ ## with this teams in this round if yes only update
553
+ game = Game.find_by_round_id_and_team1_id_and_team2_id(
554
+ round.id, team1.id, team2.id
555
+ )
556
+
557
+ game_attribs = {
558
+ score1i: scores[0],
559
+ score2i: scores[1],
560
+ score1: scores[2],
561
+ score2: scores[3],
562
+ score1et: scores[4],
563
+ score2et: scores[5],
564
+ score1p: scores[6],
565
+ score2p: scores[7],
566
+ play_at: date,
567
+ play_at_v2: date_v2,
568
+ postponed: postponed,
569
+ knockout: round.knockout, ## note: for now always use knockout flag from round - why? why not??
570
+ ground_id: ground.present? ? ground.id : nil,
571
+ group_id: @group.present? ? @group.id : nil
572
+ }
573
+
574
+ game_attribs[ :pos ] = pos if pos.present?
575
+
576
+ ####
577
+ # note: only update if any changes (or create if new record)
578
+ if game.present? &&
579
+ game.check_for_changes( game_attribs ) == false
580
+ logger.debug " skip update game #{game.id}; no changes found"
581
+ else
582
+ if game.present?
583
+ logger.debug "update game #{game.id}:"
584
+ else
585
+ logger.debug "create game:"
586
+ game = Game.new
587
+
588
+ more_game_attribs = {
589
+ round_id: round.id,
590
+ team1_id: team1.id,
591
+ team2_id: team2.id
592
+ }
593
+
594
+ ## NB: use round.games.count for pos
595
+ ## lets us add games out of order if later needed
596
+ more_game_attribs[ :pos ] = round.games.count+1 if pos.nil?
597
+
598
+ game_attribs = game_attribs.merge( more_game_attribs )
599
+ end
600
+
601
+ logger.debug game_attribs.to_json
602
+ game.update_attributes!( game_attribs )
603
+ end
604
+
605
+ @last_game = game # store for later reference (e.g. used for goals etc.)
606
+ =end
607
+
608
+ return true # game match found
609
+ end # method parse_game
610
+
611
+
612
+
613
+ def try_parse_date_header( line )
614
+ # note: clone line; for possible test do NOT modify in place for now
615
+ # note: returns true if parsed, false if no match
616
+ parse_date_header( line.dup )
617
+ end
618
+
619
+ def find_date!( line, start: )
620
+ ## NB: lets us pass in start_at/end_at date (for event)
621
+ # for auto-complete year
622
+
623
+ # extract date from line
624
+ # and return it
625
+ # NB: side effect - removes date from line string
626
+ DateFormats.find!( line, start: start )
627
+ end
628
+
629
+
630
+ def parse_date_header( line )
631
+ # note: returns true if parsed, false if no match
632
+
633
+ # line with NO teams plus include date e.g.
634
+ # [Fri Jun/17] or
635
+ # Jun/17 or
636
+ # Jun/17: etc.
637
+
638
+ @mapper_teams.map_teams!( line )
639
+ teams = @mapper_teams.find_teams!( line )
640
+ team1 = teams[0]
641
+ team2 = teams[1]
642
+
643
+ date = find_date!( line, start: @start )
644
+
645
+ if date && team1.nil? && team2.nil?
646
+ logger.debug( "date header line found: >#{line}<")
647
+ logger.debug( " date: #{date}")
648
+
649
+ @last_date = date # keep a reference for later use
650
+ return true
651
+ else
652
+ return false
653
+ end
654
+ end
655
+
656
+
657
+
658
+ end # class MatchParserSimpleV2
659
+ end # module SportDb