sportdb 0.9.0 → 0.9.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -44,39 +44,52 @@ class Event < ActiveRecord::Base
44
44
  ### fix/todo: share helper for all text readers/parsers- where to put it?
45
45
  ###
46
46
 
47
- def title_esc_regex( title )
48
- ## todo: how to mark value as regex?
49
- ## for now escape regex special chars e.g. . to \.
50
- title_for_regex = title.gsub( '.', '\.' ) # e.g. Benfica Lis.
51
- title_for_regex = title_for_regex.gsub( '(', '\(' ) # e.g. Club Atlético Colón (Santa Fe)
52
- title_for_regex = title_for_regex.gsub( ')', '\)' )
47
+ def title_esc_regex( title_unescaped )
53
48
 
54
- ## fix: todo: match accented char with or without accents
49
+ ## escape regex special chars e.g. . to \. and ( to \( etc.
50
+ # e.g. Benfica Lis.
51
+ # e.g. Club Atlético Colón (Santa Fe)
52
+
53
+ ## NB: cannot use Regexp.escape! will escape space '' to '\ '
54
+ ## title = Regexp.escape( title_unescaped )
55
+ title = title_unescaped.gsub( '.', '\.' )
56
+ title = title.gsub( '(', '\(' )
57
+ title = title.gsub( ')', '\)' )
58
+
59
+ ## match accented char with or without accents
55
60
  ## add (ü|ue) etc.
56
61
  ## also make - optional change to (-| ) e.g. Blau-Weiss == Blau Weiss
57
- ## reuse for all readers!
58
- title_for_regex = title_for_regex.gsub( '-', '(-| )' )
59
- title_for_regex = title_for_regex.gsub( 'ß', '(ß|ss)' )
60
- title_for_regex = title_for_regex.gsub( 'æ', '(æ|ae)' )
61
- title_for_regex = title_for_regex.gsub( 'á', '(á|a)' ) ## e.g. Bogotá
62
- title_for_regex = title_for_regex.gsub( 'ã', '(ã|a)' ) ## e.g São Paulo
63
- title_for_regex = title_for_regex.gsub( 'ä', '(ä|ae)' ) ## add a ?
64
- title_for_regex = title_for_regex.gsub( 'ö', '(ö|oe)' ) ## add o ?
65
- title_for_regex = title_for_regex.gsub( 'ó', '(ó|o)' ) ## e.g. Colón
66
- title_for_regex = title_for_regex.gsub( 'ü', '(ü|ue)' ) ## add u ?
67
- title_for_regex = title_for_regex.gsub( 'é', '(é|e)' ) ## e.g. Vélez
68
- title_for_regex = title_for_regex.gsub( 'ê', '(ê|e)' ) ## e.g. Grêmio
69
- title_for_regex = title_for_regex.gsub( 'ñ', '(ñ|n)' ) ## e.g. Porteño
70
- title_for_regex = title_for_regex.gsub( 'ú', '(ú|u)' ) ## e.g. Fútbol
71
-
72
- ## todo: add some more; use array for config?
62
+
63
+ ## todo: add some more
73
64
  ## see http://en.wikipedia.org/wiki/List_of_XML_and_HTML_character_entity_references for more
65
+ ##
66
+ ## reuse for all readers!
67
+
68
+ alternatives = [
69
+ ['-', '(-| )'],
70
+ ['ß', '(ß|ss)'],
71
+ ['æ', '(æ|ae)'],
72
+ ['á', '(á|a)'], ## e.g. Bogotá
73
+ ['ã', '(ã|a)'], ## e.g São Paulo
74
+ ['ä', '(ä|ae)'], ## add a ?
75
+ ['Ö', '(Ö|Oe)'], ## e.g. Österreich
76
+ ['ö', '(ö|oe)'], ## add o ?
77
+ ['ó', '(ó|o)'], ## e.g. Colón
78
+ ['ü', '(ü|ue)'], ## add u ?
79
+ ['é', '(é|e)'], ## e.g. Vélez
80
+ ['ê', '(ê|e)'], ## e.g. Grêmio
81
+ ['ñ', '(ñ|n)'], ## e.g. Porteño
82
+ ['ú', '(ú|u)'] ## e.g. Fútbol
83
+ ]
84
+
85
+ alternatives.each do |alt|
86
+ title = title.gsub( alt[0], alt[1] )
87
+ end
74
88
 
75
- title_for_regex
89
+ title
76
90
  end
77
91
 
78
92
 
79
-
80
93
  def known_teams_table
81
94
 
82
95
  ## build known teams table w/ synonyms e.g.
@@ -40,14 +40,13 @@ class Reader
40
40
 
41
41
  puts "*** parsing data '#{name}' (#{path})..."
42
42
 
43
- code = File.read( path )
43
+ ## nb: assume/enfore utf-8 encoding (with or without BOM - byte order mark)
44
+ ## - see sportdb/utils.rb
45
+ code = File.read_utf8( path )
44
46
 
45
47
  load_fixtures_worker( event_key, code )
46
48
 
47
- ### fix:
48
- ## for loaded from fs use filestat? for version - why? why not?
49
-
50
- Prop.create!( key: "db.#{fixture_name_to_prop_key(name)}.version", value: "txt.1" )
49
+ Prop.create!( key: "db.#{fixture_name_to_prop_key(name)}.version", value: "file.txt.#{File.mtime(path).strftime('%Y.%m.%d')}" )
51
50
  end
52
51
 
53
52
  def load_fixtures_builtin( event_key, name ) # load from gem (built-in)
@@ -55,7 +54,7 @@ class Reader
55
54
 
56
55
  puts "*** parsing data '#{name}' (#{path})..."
57
56
 
58
- code = File.read( path )
57
+ code = File.read_utf8( path )
59
58
 
60
59
  load_fixtures_worker( event_key, code )
61
60
 
@@ -84,6 +83,12 @@ private
84
83
  ## assume active activerecord connection
85
84
  ##
86
85
 
86
+ ## reset cached values
87
+ @patch_rounds = {}
88
+ @knockout_flag = false
89
+ @round = nil
90
+
91
+
87
92
  @event = Event.find_by_key!( event_key )
88
93
 
89
94
  puts "Event #{@event.key} >#{@event.title}<"
@@ -99,8 +104,15 @@ private
99
104
  line =~ /Spieltag|Runde|Achtelfinale|Viertelfinale|Halbfinale|Finale/
100
105
  end
101
106
 
107
+ def is_group?( line )
108
+ # NB: check after is_round? (round may contain group reference!)
109
+ line =~ /Gruppe|Group/
110
+ end
111
+
112
+ ### todo: rename to is_knockout_round?
113
+ ##
102
114
  def find_knockout_flag( line )
103
- if line =~ /Achtelfinale|Viertelfinale|Halbfinale|Finale|K\.O\.|Knockout/
115
+ if line =~ /Achtelfinale|Viertelfinale|Halbfinale|Spiel um Platz 3|Finale|K\.O\.|Knockout/
104
116
  puts " setting knockout flag to true"
105
117
  true
106
118
  else
@@ -108,6 +120,50 @@ private
108
120
  end
109
121
  end
110
122
 
123
+ def find_group_title_and_pos!( line )
124
+ ## group pos - for now support single digit e.g 1,2,3 or letter e.g. A,B,C
125
+ ## nb: (?:) = is for non-capturing group(ing)
126
+ regex = /(?:Group|Gruppe)\s+((?:\d{1}|[A-Z]{1}))\b/
127
+
128
+ match = regex.match( line )
129
+ unless match.nil?
130
+ if match[1] == 'A'
131
+ pos = 1
132
+ elsif match[1] == 'B'
133
+ pos = 2
134
+ elsif match[1] == 'C'
135
+ pos = 3
136
+ elsif match[1] == 'D'
137
+ pos = 4
138
+ elsif match[1] == 'E'
139
+ pos = 5
140
+ elsif match[1] == 'F'
141
+ pos = 6
142
+ elsif match[1] == 'G'
143
+ pos = 7
144
+ elsif match[1] == 'H'
145
+ pos = 8
146
+ elsif match[1] == 'I'
147
+ pos = 9
148
+ elsif match[1] == 'J'
149
+ pos = 10
150
+ else
151
+ pos = match[1].to_i
152
+ end
153
+
154
+ title = match[0]
155
+
156
+ puts " title: >#{title}<"
157
+ puts " pos: >#{pos}<"
158
+
159
+ line.sub!( regex, '[GROUP|TITLE+POS]' )
160
+
161
+ return [title,pos]
162
+ else
163
+ return [nil,nil]
164
+ end
165
+ end
166
+
111
167
  def find_round_pos!( line )
112
168
  ## fix/todo:
113
169
  ## if no round found assume last_pos+1 ??? why? why not?
@@ -118,7 +174,7 @@ private
118
174
  value = $1.to_i
119
175
  puts " pos: >#{value}<"
120
176
 
121
- line.sub!( regex, '[POS]' )
177
+ line.sub!( regex, '[ROUND|POS]' )
122
178
 
123
179
  return value
124
180
  else
@@ -241,6 +297,20 @@ private
241
297
  return nil
242
298
  end
243
299
  end
300
+
301
+ def find_teams!( line )
302
+ counter = 1
303
+ teams = []
304
+
305
+ team = find_team_worker!( line, counter )
306
+ while team.present?
307
+ teams << team
308
+ counter += 1
309
+ team = find_team_worker!( line, counter )
310
+ end
311
+
312
+ teams
313
+ end
244
314
 
245
315
  def find_team1!( line )
246
316
  find_team_worker!( line, 1 )
@@ -257,7 +327,7 @@ private
257
327
  ## (thus add it, allows match for Benfica Lis. for example - note . at the end)
258
328
 
259
329
  ## check add $ e.g. (\b| |\t|$) does this work? - check w/ Benfica Lis.$
260
- regex = /\b#{value}(\b| |\t)/ # wrap with world boundry (e.g. match only whole words e.g. not wac in wacker)
330
+ regex = /\b#{value}(\b| |\t|$)/ # wrap with world boundry (e.g. match only whole words e.g. not wac in wacker)
261
331
  if line =~ regex
262
332
  puts " match for team >#{key}< >#{value}<"
263
333
  # make sure @@oo{key}oo@@ doesn't match itself with other key e.g. wacker, wac, etc.
@@ -276,135 +346,195 @@ private
276
346
  end # each known_teams
277
347
  end # method translate_teams!
278
348
 
349
+
350
+ def parse_group( line )
351
+ puts "parsing group line: >#{line}<"
352
+
353
+ match_teams!( line )
354
+ team_keys = find_teams!( line )
355
+
356
+ title, pos = find_group_title_and_pos!( line )
279
357
 
280
- def parse_fixtures( data )
358
+ puts " line: >#{line}<"
359
+
360
+ ## world2 = Group.create!( event: world, pos: 2, title: 'Gruppe 2' )
361
+ ## world2.add_teams_from_ary!( team_keys_world2 )
362
+
363
+ group_attribs = {
364
+ title: title
365
+ }
366
+
367
+ @group = Group.find_by_event_id_and_pos( @event.id, pos )
368
+ if @group.present?
369
+ puts "*** update group #{@group.id}:"
370
+ else
371
+ puts "*** create group:"
372
+ @group = Group.new
373
+ group_attribs = group_attribs.merge( {
374
+ event_id: @event.id,
375
+ pos: pos
376
+ })
377
+ end
281
378
 
282
- data.each_line do |line|
379
+ puts group_attribs.to_json
380
+
381
+ @group.update_attributes!( group_attribs )
382
+
383
+ @group.teams.clear # remove old teams
384
+ ## add new teams
385
+ team_keys.each do |team_key|
386
+ team = Team.find_by_key!( team_key )
387
+ puts " adding team #{team.title} (#{team.code})"
388
+ @group.teams << team
389
+ end
390
+ end
283
391
 
284
- if line =~ /^\s*#/
285
- # skip komments and do NOT copy to result (keep comments secret!)
286
- logger.debug 'skipping comment line'
287
- next
288
- end
392
+ def parse_round( line )
393
+ puts "parsing round line: >#{line}<"
394
+ pos = find_round_pos!( line )
289
395
 
290
- if line =~ /^\s*$/
291
- # kommentar oder leerzeile überspringen
292
- logger.debug 'skipping blank line'
293
- next
294
- end
396
+ @knockout_flag = find_knockout_flag( line )
295
397
 
296
- # remove leading and trailing whitespace
297
- line = line.strip
398
+ group_title, group_pos = find_group_title_and_pos!( line )
298
399
 
299
- if is_round?( line )
300
- puts "parsing round line: >#{line}<"
301
- pos = find_round_pos!( line )
302
-
303
- @knockout_flag = find_knockout_flag( line )
304
- puts " line: >#{line}<"
400
+ if group_pos.present?
401
+ @group = Group.find_by_event_id_and_pos!( @event.id, group_pos )
402
+ else
403
+ @group = nil # reset group to no group
404
+ end
405
+
406
+ puts " line: >#{line}<"
305
407
 
306
- ## NB: dummy/placeholder start_at, end_at date
307
- ## replace/patch after adding all games for round
408
+ ## NB: dummy/placeholder start_at, end_at date
409
+ ## replace/patch after adding all games for round
308
410
 
309
- round_attribs = {
310
- title: "#{pos}. Runde"
311
- }
411
+ round_attribs = {
412
+ title: "#{pos}. Runde"
413
+ }
414
+
312
415
 
313
- @round = Round.find_by_event_id_and_pos( @event.id, pos )
314
- if @round.present?
315
- puts "*** update round #{@round.id}:"
316
- else
317
- puts "*** create round:"
318
- @round = Round.new
416
+ @round = Round.find_by_event_id_and_pos( @event.id, pos )
417
+ if @round.present?
418
+ puts "*** update round #{@round.id}:"
419
+ else
420
+ puts "*** create round:"
421
+ @round = Round.new
319
422
 
320
- round_attribs = round_attribs.merge( {
321
- event_id: @event.id,
322
- pos: pos,
323
- start_at: Time.utc('1999-12-12'),
324
- end_at: Time.utc('1999-12-12')
325
- })
326
- end
423
+ round_attribs = round_attribs.merge( {
424
+ event_id: @event.id,
425
+ pos: pos,
426
+ start_at: Time.utc('1912-12-12'),
427
+ end_at: Time.utc('1912-12-12')
428
+ })
429
+ end
327
430
 
328
- puts round_attribs.to_json
431
+ puts round_attribs.to_json
329
432
 
330
- @round.update_attributes!( round_attribs )
433
+ @round.update_attributes!( round_attribs )
331
434
 
332
- ### store list of round is for patching start_at/end_at at the end
333
- @patch_rounds ||= {}
334
- @patch_rounds[ @round.id ] = @round.id
335
-
336
-
337
- else
338
- puts "parsing game (fixture) line: >#{line}<"
435
+ ### store list of round is for patching start_at/end_at at the end
436
+ @patch_rounds[ @round.id ] = @round.id
437
+ end
438
+
439
+ def parse_game( line )
440
+ puts "parsing game (fixture) line: >#{line}<"
339
441
 
340
- pos = find_game_pos!( line )
442
+ pos = find_game_pos!( line )
341
443
 
342
- match_teams!( line )
343
- team1_key = find_team1!( line )
344
- team2_key = find_team2!( line )
444
+ match_teams!( line )
445
+ team1_key = find_team1!( line )
446
+ team2_key = find_team2!( line )
345
447
 
346
- date = find_date!( line )
347
- scores = find_scores!( line )
448
+ date = find_date!( line )
449
+ scores = find_scores!( line )
348
450
 
349
- puts " line: >#{line}<"
451
+ puts " line: >#{line}<"
350
452
 
351
453
 
352
- ### todo: cache team lookups in hash?
454
+ ### todo: cache team lookups in hash?
353
455
 
354
- team1 = Team.find_by_key!( team1_key )
355
- team2 = Team.find_by_key!( team2_key )
456
+ team1 = Team.find_by_key!( team1_key )
457
+ team2 = Team.find_by_key!( team2_key )
356
458
 
357
- ### check if games exists
358
- ## with this teams in this round if yes only update
359
- game = Game.find_by_round_id_and_team1_id_and_team2_id(
459
+ ### check if games exists
460
+ ## with this teams in this round if yes only update
461
+ game = Game.find_by_round_id_and_team1_id_and_team2_id(
360
462
  @round.id, team1.id, team2.id
361
- )
362
-
363
- game_attribs = {
364
- score1: scores[0],
365
- score2: scores[1],
366
- score3: scores[2],
367
- score4: scores[3],
368
- score5: scores[4],
369
- score6: scores[5],
370
- play_at: date,
371
- knockout: @knockout_flag
372
- }
373
-
374
- game_attribs[ :pos ] = pos if pos.present?
375
-
376
- if game.present?
377
- puts "*** update game #{game.id}:"
378
- else
379
- puts "*** create game:"
380
- game = Game.new
381
-
382
- more_game_attribs = {
383
- round_id: @round.id,
384
- team1_id: team1.id,
385
- team2_id: team2.id
386
- }
463
+ )
464
+
465
+ game_attribs = {
466
+ score1: scores[0],
467
+ score2: scores[1],
468
+ score3: scores[2],
469
+ score4: scores[3],
470
+ score5: scores[4],
471
+ score6: scores[5],
472
+ play_at: date,
473
+ knockout: @knockout_flag,
474
+ group_id: @group.present? ? @group.id : nil
475
+ }
476
+
477
+ game_attribs[ :pos ] = pos if pos.present?
478
+
479
+ if game.present?
480
+ puts "*** update game #{game.id}:"
481
+ else
482
+ puts "*** create game:"
483
+ game = Game.new
484
+
485
+ more_game_attribs = {
486
+ round_id: @round.id,
487
+ team1_id: team1.id,
488
+ team2_id: team2.id
489
+ }
387
490
 
388
- ## NB: use round.games.count for pos
389
- ## lets us add games out of order if later needed
390
- more_game_attribs[ :pos ] = @round.games.count+1 if pos.nil?
491
+ ## NB: use round.games.count for pos
492
+ ## lets us add games out of order if later needed
493
+ more_game_attribs[ :pos ] = @round.games.count+1 if pos.nil?
391
494
 
392
- game_attribs = game_attribs.merge( more_game_attribs )
393
- end
495
+ game_attribs = game_attribs.merge( more_game_attribs )
496
+ end
497
+
498
+ puts game_attribs.to_json
499
+
500
+ game.update_attributes!( game_attribs )
501
+ end
502
+
503
+
504
+ def parse_fixtures( data )
505
+
506
+ data.each_line do |line|
507
+
508
+ if line =~ /^\s*#/
509
+ # skip komments and do NOT copy to result (keep comments secret!)
510
+ logger.debug 'skipping comment line'
511
+ next
512
+ end
513
+
514
+ if line =~ /^\s*$/
515
+ # kommentar oder leerzeile überspringen
516
+ logger.debug 'skipping blank line'
517
+ next
518
+ end
394
519
 
395
- puts game_attribs.to_json
520
+ # remove leading and trailing whitespace
521
+ line = line.strip
396
522
 
397
- game.update_attributes!( game_attribs )
523
+ if is_round?( line )
524
+ parse_round( line )
525
+ elsif is_group?( line ) ## NB: group goes after round (round may contain group marker too)
526
+ parse_group( line )
527
+ else
528
+ parse_game( line )
398
529
  end
399
- end # oldlines.each
530
+ end # lines.each
400
531
 
401
- @patch_rounds ||= {}
402
532
  @patch_rounds.each do |k,v|
403
533
  puts "*** patch start_at/end_at date for round #{k}:"
404
534
  round = Round.find( k )
405
535
  games = round.games.order( 'play_at asc' ).all
406
536
 
407
- ## skip rouns w/ no games
537
+ ## skip rounds w/ no games
408
538
 
409
539
  ## todo/fix: what's the best way for checking assoc w/ 0 recs?
410
540
  next if games.size == 0