sportdb 0.9.0 → 0.9.1
Sign up to get free protection for your applications and to get access to all the features.
- data/Manifest.txt +1 -2
- data/db/america/2011.rb +0 -13
- data/db/america/2011.txt +20 -12
- data/db/america/teams.rb +1 -1
- data/db/copa/teams.rb +5 -5
- data/db/es/teams.rb +2 -2
- data/db/euro/2008.rb +1 -14
- data/db/euro/2008.txt +22 -12
- data/db/euro/teams.rb +2 -2
- data/db/world/2010.rb +1 -96
- data/db/world/2010.txt +42 -30
- data/db/world/quali_2012_13_america.rb +0 -6
- data/db/world/quali_2012_13_america.txt +8 -8
- data/db/world/quali_2012_13_europe.rb +18 -0
- data/db/world/quali_2012_13_europe_c.txt +14 -12
- data/db/world/quali_2012_13_europe_i.txt +14 -12
- data/db/world/teams.rb +3 -3
- data/lib/sportdb.rb +14 -11
- data/lib/sportdb/cli/runner.rb +1 -1
- data/lib/sportdb/console.rb +54 -0
- data/lib/sportdb/keys.rb +2 -4
- data/lib/sportdb/loader.rb +5 -6
- data/lib/sportdb/models/event.rb +38 -25
- data/lib/sportdb/reader.rb +235 -105
- data/lib/sportdb/utils.rb +12 -0
- data/lib/sportdb/version.rb +1 -1
- metadata +5 -6
- data/db/world/quali_2012_13_europe_c.rb +0 -26
- data/db/world/quali_2012_13_europe_i.rb +0 -26
data/lib/sportdb/models/event.rb
CHANGED
@@ -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(
|
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
|
-
##
|
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
|
-
|
58
|
-
|
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
|
-
|
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.
|
data/lib/sportdb/reader.rb
CHANGED
@@ -40,14 +40,13 @@ class Reader
|
|
40
40
|
|
41
41
|
puts "*** parsing data '#{name}' (#{path})..."
|
42
42
|
|
43
|
-
|
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
|
-
|
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.
|
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
|
-
|
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
|
-
|
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
|
-
|
285
|
-
|
286
|
-
|
287
|
-
next
|
288
|
-
end
|
392
|
+
def parse_round( line )
|
393
|
+
puts "parsing round line: >#{line}<"
|
394
|
+
pos = find_round_pos!( line )
|
289
395
|
|
290
|
-
|
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
|
-
|
297
|
-
line = line.strip
|
398
|
+
group_title, group_pos = find_group_title_and_pos!( line )
|
298
399
|
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
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
|
-
|
307
|
-
|
408
|
+
## NB: dummy/placeholder start_at, end_at date
|
409
|
+
## replace/patch after adding all games for round
|
308
410
|
|
309
|
-
|
310
|
-
|
311
|
-
|
411
|
+
round_attribs = {
|
412
|
+
title: "#{pos}. Runde"
|
413
|
+
}
|
414
|
+
|
312
415
|
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
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
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
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
|
-
|
431
|
+
puts round_attribs.to_json
|
329
432
|
|
330
|
-
|
433
|
+
@round.update_attributes!( round_attribs )
|
331
434
|
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
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
|
-
|
442
|
+
pos = find_game_pos!( line )
|
341
443
|
|
342
|
-
|
343
|
-
|
344
|
-
|
444
|
+
match_teams!( line )
|
445
|
+
team1_key = find_team1!( line )
|
446
|
+
team2_key = find_team2!( line )
|
345
447
|
|
346
|
-
|
347
|
-
|
448
|
+
date = find_date!( line )
|
449
|
+
scores = find_scores!( line )
|
348
450
|
|
349
|
-
|
451
|
+
puts " line: >#{line}<"
|
350
452
|
|
351
453
|
|
352
|
-
|
454
|
+
### todo: cache team lookups in hash?
|
353
455
|
|
354
|
-
|
355
|
-
|
456
|
+
team1 = Team.find_by_key!( team1_key )
|
457
|
+
team2 = Team.find_by_key!( team2_key )
|
356
458
|
|
357
|
-
|
358
|
-
|
359
|
-
|
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
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
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
|
-
|
389
|
-
|
390
|
-
|
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
|
-
|
393
|
-
|
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
|
-
|
520
|
+
# remove leading and trailing whitespace
|
521
|
+
line = line.strip
|
396
522
|
|
397
|
-
|
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 #
|
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
|
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
|