sportdb-readers 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f26c8211ce5ab383f35775134f230cbc22d71112
4
- data.tar.gz: 7bfb83d1e338e101578fdd4487e3fa35c7f13580
3
+ metadata.gz: a0bb0a6a7c3690ca023fec024bc3a1126ee97899
4
+ data.tar.gz: 15d4d25225734b4931d47cbb727e02c291c4d887
5
5
  SHA512:
6
- metadata.gz: cee60d57c7f687397f0daa0333cdd6eaeee924141215b57ecc89691d3ae6daf5b40bf6f4aca4d65cf713613732e2ccafd7c9cd0256564b58e3d516241b495a14
7
- data.tar.gz: fc4dcbc250a5d56b31bdbe6f82b78073e45ec1df499554861cc623695deb752ce94fe1934fe4b69b47b2245e44a6baeafb07b2eb9de0f2134317cac1eccb15d6
6
+ metadata.gz: 8b0e884cf228c6d7982a452985004984b1e427db31fad1ccd4dee18586d89aae03732bceeb152541d0ede5976bd6f079a1d4021c0d0ef9243757cd36da4ab558
7
+ data.tar.gz: 57cc903ae9fd50aa346c98dc3025a44c8d6723bdca91329c8606c3cf9f1fdb3ddc5480c5e1ba6dfa401bb8ff57191f44883036aec15afb62060edccb2205dee4
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # sportdb-readers - sport.db readers
1
+ # sportdb-readers - sport.db readers for leagues, seasons, clubs, match schedules and results, and more
2
2
 
3
3
 
4
4
  * home :: [github.com/sportdb/sport.db](https://github.com/sportdb/sport.db)
@@ -11,7 +11,44 @@
11
11
 
12
12
  ## Usage
13
13
 
14
- To be done
14
+
15
+ **Step 1**
16
+
17
+ Setup the (SQL) database. Let's use and build a single-file SQLite database (from scratch),
18
+ as an example:
19
+
20
+ ``` ruby
21
+ require 'sportdb/readers'
22
+
23
+ SportDb.connect( adapter: 'sqlite3',
24
+ database: './england.db' )
25
+ SportDb.create_all ## build database schema (tables, indexes, etc.)
26
+ ```
27
+
28
+ **Step 2**
29
+
30
+ Let's read in some leagues, seasons, clubs, and match schedules and results.
31
+ Let's use the public domain football.db datasets for England (see [`openfootball/england`](https://github.com/openfootball/england)), as an example:
32
+
33
+
34
+ ``` ruby
35
+ ## turn on logging to console
36
+ ActiveRecord::Base.logger = Logger.new( STDOUT )
37
+
38
+ ## assumes football.db datasets for England in ./england directory
39
+ ## see github.com/openfootball/england
40
+ SportDb::EventReaderV2.read( './england/2015-16/.conf.txt' )
41
+ SportDb::MatchReaderV2.read( './england/2015-16/1-premierleague-i.txt' )
42
+ SportDb::MatchReaderV2.read( './england/2015-16/1-premierleague-ii.txt' )
43
+
44
+ ## let's try another season
45
+ SportDb::EventReaderV2.read( './england/2019-20/.conf.txt' )
46
+ SportDb::MatchReaderV2.read( './england/2019-20/1-premierleague.txt' )
47
+ ```
48
+
49
+ That's it. All leagues, seasons, clubs, match days and rounds, match fixtures and results,
50
+ and more are now in your (SQL) database of choice.
51
+
15
52
 
16
53
  ## License
17
54
 
data/Rakefile CHANGED
@@ -5,7 +5,7 @@ Hoe.spec 'sportdb-readers' do
5
5
 
6
6
  self.version = SportDb::Readers::VERSION
7
7
 
8
- self.summary = "sportdb-readers - sport.db readers"
8
+ self.summary = "sportdb-readers - sport.db readers for leagues, seasons, clubs, match schedules and results, and more"
9
9
  self.description = summary
10
10
 
11
11
  self.urls = ['https://github.com/sportdb/sport.db']
@@ -5,6 +5,9 @@ module SportDb
5
5
 
6
6
  class EventReaderV2 ## todo/check: rename to EventsReaderV2 (use plural?) why? why not?
7
7
 
8
+ def self.config() Import.config; end ## shortcut convenience helper
9
+
10
+
8
11
  def self.read( path ) ## use - rename to read_file or from_file etc. - why? why not?
9
12
  txt = File.open( path, 'r:utf-8' ).read
10
13
  parse( txt )
@@ -74,16 +77,16 @@ class EventReaderV2 ## todo/check: rename to EventsReaderV2 (use plural?) why
74
77
 
75
78
  def self.find_club( name, country ) ## todo/fix: add international or league flag?
76
79
  club = nil
77
- m = CLUBS.match_by( name: name, country: country )
80
+ m = config.clubs.match_by( name: name, country: country )
78
81
 
79
82
  if m.nil?
80
83
  ## (re)try with second country - quick hacks for known leagues
81
84
  ## todo/fix: add league flag to activate!!!
82
- m = CLUBS.match_by( name: name, country: COUNTRIES['wal']) if country.key == 'eng'
83
- m = CLUBS.match_by( name: name, country: COUNTRIES['nir']) if country.key == 'ie'
84
- m = CLUBS.match_by( name: name, country: COUNTRIES['mc']) if country.key == 'fr'
85
- m = CLUBS.match_by( name: name, country: COUNTRIES['li']) if country.key == 'ch'
86
- m = CLUBS.match_by( name: name, country: COUNTRIES['ca']) if country.key == 'us'
85
+ m = config.clubs.match_by( name: name, country: config.countries['wal']) if country.key == 'eng'
86
+ m = config.clubs.match_by( name: name, country: config.countries['nir']) if country.key == 'ie'
87
+ m = config.clubs.match_by( name: name, country: config.countries['mc']) if country.key == 'fr'
88
+ m = config.clubs.match_by( name: name, country: config.countries['li']) if country.key == 'ch'
89
+ m = config.clubs.match_by( name: name, country: config.countries['ca']) if country.key == 'us'
87
90
  end
88
91
 
89
92
  if m.nil?
@@ -37,916 +37,3 @@ class MatchReaderV2 ## todo/check: rename to MatchReaderV2 (use plural?) why?
37
37
  end # method read
38
38
  end # class MatchReaderV2
39
39
  end # module SportDb
40
-
41
-
42
-
43
- __END__
44
-
45
- class GameReader
46
-
47
- include LogUtils::Logging
48
-
49
- ## make models available by default with namespace
50
- # e.g. lets you use Usage instead of Model::Usage
51
- include Models
52
-
53
- attr_reader :event # returns event record
54
-
55
- ## value helpers e.g. is_year?, is_taglist? etc.
56
- include TextUtils::ValueHelper
57
-
58
- include FixtureHelpers
59
-
60
- def self.from_zip( zip_file, entry_path, more_attribs={} )
61
-
62
- logger = LogKernel::Logger.root
63
-
64
- ## get text content from zip
65
- entry = zip_file.find_entry( entry_path )
66
- event_text = entry.get_input_stream().read()
67
- event_text = event_text.force_encoding( Encoding::UTF_8 )
68
-
69
- ## hack:
70
- ## support old event config format for now (will get removed later)
71
- ## e.g. check for
72
- ## teams:
73
- ## 12 teams: etc.
74
- if event_text =~ /^teams:/i ||
75
- event_text =~ /^\d{1,2} teams:/i ||
76
- event_text =~ /^start_at:/i
77
- ## old format
78
- puts "*** [DEPRECATED] old event config format w/ yaml, please use new plain text format >#{entry_path}<"
79
- reader = EventReader.from_zip( zip_file, entry_path )
80
- reader.read()
81
- else
82
- ## new format
83
- reader = EventMetaReader.from_zip( zip_file, entry_path )
84
- reader.read()
85
- end
86
-
87
- event = reader.event ## was fetch_event( name )
88
- fixtures = reader.fixtures ## was fetch_event_fixtures( name )
89
-
90
- ## add path to fixtures (use path from event e.g)
91
- # - bl + at-austria!/2012_13/bl -> at-austria!/2012_13/bl
92
- # - bl_ii + at-austria!/2012_13/bl -> at-austria!/2012_13/bl_ii
93
-
94
- dir = File.dirname( entry_path ) # use dir for fixtures
95
-
96
- fixtures_with_path = fixtures.map do |fx|
97
- fx_new = "#{dir}/#{fx}.txt" # add path upfront
98
- logger.debug "fx: #{fx_new} | >#{fx}< + >#{dir}<"
99
- fx_new
100
- end
101
-
102
- ## fix-fix-fix: change file extension to ??
103
- text_ary = []
104
- fixtures_with_path.each do |fixture_path|
105
- entry = zip_file.find_entry( fixture_path )
106
-
107
- text = entry.get_input_stream().read()
108
- text = text.force_encoding( Encoding::UTF_8 )
109
-
110
- text_ary << text
111
- end
112
-
113
- self.from_string( event, text_ary, more_attribs )
114
- end
115
-
116
- def self.from_file( path, more_attribs={} )
117
-
118
- logger = LogKernel::Logger.root
119
-
120
- ### NOTE: fix-fix-fix - pass in event path!!!!!!! (not fixture path!!!!)
121
-
122
- ## - ## note: assume/enfore utf-8 encoding (with or without BOM - byte order mark)
123
- ## - ## - see textutils/utils.rb
124
- ## - text = File.read_utf8( path )
125
-
126
- event_text = File.read_utf8( path )
127
-
128
- ## hack:
129
- ## support old event config format for now (will get removed later)
130
- ## e.g. check for
131
- ## teams:
132
- ## 12 teams: etc.
133
- if event_text =~ /^teams:/i ||
134
- event_text =~ /^\d{1,2} teams:/i ||
135
- event_text =~ /^start_at:/i
136
-
137
- ## old format
138
- puts "*** [DEPRECATED] old event config format w/ yaml, please use new plain text format >#{path}<"
139
- reader = EventReader.from_file( path )
140
- reader.read()
141
- else
142
- ## new format
143
- reader = EventMetaReader.from_file( path )
144
- reader.read()
145
- end
146
-
147
-
148
- event = reader.event ## was fetch_event( name )
149
- fixtures = reader.fixtures ## was fetch_event_fixtures( name )
150
-
151
-
152
- ## add path to fixtures (use path from event e.g)
153
- # - bl + at-austria!/2012_13/bl -> at-austria!/2012_13/bl
154
- # - bl_ii + at-austria!/2012_13/bl -> at-austria!/2012_13/bl_ii
155
-
156
- dir = File.dirname( path ) # use dir for fixtures
157
-
158
- fixtures_with_path = fixtures.map do |fx|
159
- fx_new = "#{dir}/#{fx}.txt" # add path upfront
160
- logger.debug "fx: #{fx_new} | >#{fx}< + >#{dir}<"
161
- fx_new
162
- end
163
-
164
- ## fix-fix-fix: change file extension to ??
165
- text_ary = []
166
- fixtures_with_path.each do |fixture_path|
167
- text_ary << File.read_utf8( fixture_path )
168
- end
169
-
170
- self.from_string( event, text_ary, more_attribs )
171
- end
172
-
173
-
174
- def self.from_string( event, text_or_text_ary, more_attribs={} )
175
- ### fix - fix -fix:
176
- ## change event to event_or_event_key !!!!! - allow event_key as string passed in
177
- self.new( event, text_or_text_ary, more_attribs )
178
- end
179
-
180
-
181
- def initialize( event, text_or_text_ary, more_attribs={} )
182
- ### fix - fix -fix:
183
- ## change event to event_or_event_key !!!!! - allow event_key as string passed in
184
-
185
- ## todo/fix: how to add opts={} ???
186
- @event = event
187
- @text_or_text_ary = text_or_text_ary
188
- @more_attribs = more_attribs
189
- end
190
-
191
-
192
- def read
193
- if @text_or_text_ary.is_a?( String )
194
- text_ary = [@text_or_text_ary]
195
- else
196
- text_ary = @text_or_text_ary
197
- end
198
-
199
- ## reset cached values
200
- ## for auto-number rounds etc.
201
- @last_round_pos = nil
202
-
203
- text_ary.each do |text|
204
- ## assume en for now? why? why not?
205
- ## fix (cache) store lang in event table (e.g. auto-add and auto-update)!!!
206
- SportDb.lang.lang = SportDb.lang.classify( text )
207
-
208
- reader = LineReader.from_string( text )
209
-
210
- read_fixtures_worker( @event.key, reader )
211
- end
212
-
213
- ## fix add prop ??
214
- ### Prop.create!( key: "db.#{fixture_name_to_prop_key(name)}.version", value: "file.txt.#{File.mtime(path).strftime('%Y.%m.%d')}" )
215
- end
216
-
217
-
218
- def read_fixtures_worker( event_key, reader )
219
- ## NB: assume active activerecord connection
220
-
221
- ## reset cached values
222
- @patch_round_ids_dates = []
223
- @patch_round_ids_pos = []
224
-
225
- @round = nil ## fix: change/rename to @last_round !!!
226
- @group = nil ## fix: change/rename to @last_group !!!
227
- @last_date = nil
228
-
229
- @last_team1 = nil # used for goals (to match players via squads)
230
- @last_team2 = nil
231
- @last_game = nil
232
-
233
-
234
- #####
235
- # fix: move to read and share event/known_teams
236
- # for all 1-n fixture files (no need to configure every time!!)
237
-
238
- @event = Event.find_by_key!( event_key )
239
-
240
- logger.debug "Event #{@event.key} >#{@event.title}<"
241
-
242
- ### fix: use build_title_table_for ??? why? why not??
243
- ## @known_teams = @event.known_teams_table
244
-
245
- @mapper_teams = TeamMapper.new( @event.teams )
246
-
247
-
248
- @known_grounds = TextUtils.build_title_table_for( @event.grounds )
249
-
250
-
251
- parse_fixtures( reader )
252
-
253
- end # method load_fixtures
254
-
255
-
256
-
257
- def parse_group_header( line )
258
- logger.debug "parsing group header line: >#{line}<"
259
-
260
- # note: group header resets (last) round (allows, for example):
261
- # e.g.
262
- # Group Playoffs/Replays -- round header
263
- # team1 team2 -- match
264
- # Group B: -- group header
265
- # team1 team2 - match (will get new auto-matchday! not last round)
266
- @round = nil ## fix: change/rename to @last_round !!!
267
-
268
- title, pos = find_group_title_and_pos!( line )
269
-
270
- logger.debug " title: >#{title}<"
271
- logger.debug " pos: >#{pos}<"
272
- logger.debug " line: >#{line}<"
273
-
274
- # set group for games
275
- @group = Group.find_by_event_id_and_pos!( @event.id, pos )
276
- end
277
-
278
-
279
- def parse_group_def( line )
280
- logger.debug "parsing group def line: >#{line}<"
281
-
282
- @mapper_teams.map_teams!( line )
283
- team_keys = @mapper_teams.find_teams!( line )
284
-
285
- title, pos = find_group_title_and_pos!( line )
286
-
287
- logger.debug " line: >#{line}<"
288
-
289
- group_attribs = {
290
- title: title
291
- }
292
-
293
- group = Group.find_by_event_id_and_pos( @event.id, pos )
294
- if group.present?
295
- logger.debug "update group #{group.id}:"
296
- else
297
- logger.debug "create group:"
298
- group = Group.new
299
- group_attribs = group_attribs.merge( {
300
- event_id: @event.id,
301
- pos: pos
302
- })
303
- end
304
-
305
- logger.debug group_attribs.to_json
306
-
307
- group.update_attributes!( group_attribs )
308
-
309
- group.teams.clear # remove old teams
310
- ## add new teams
311
- team_keys.each do |team_key|
312
- team = Team.find_by_key!( team_key )
313
- logger.debug " adding team #{team.title} (#{team.code})"
314
- group.teams << team
315
- end
316
- end
317
-
318
-
319
- def parse_round_def( line )
320
- logger.debug "parsing round def line: >#{line}<"
321
-
322
- ### todo/fix/check: move cut off optional comment in reader for all lines? why? why not?
323
- cut_off_end_of_line_comment!( line ) # cut off optional comment starting w/ #
324
-
325
- start_at = find_date!( line, start_at: @event.start_at )
326
- end_at = find_date!( line, start_at: @event.start_at )
327
-
328
- # note: if end_at missing -- assume start_at is (==) end_at
329
- end_at = start_at if end_at.nil?
330
-
331
- # note: - NOT needed; start_at and end_at are saved as date only (NOT datetime)
332
- # set hours,minutes,secs to beginning and end of day (do NOT use default 12.00)
333
- # e.g. use 00.00 and 23.59
334
- # start_at = start_at.beginning_of_day
335
- # end_at = end_at.end_of_day
336
-
337
- # note: make sure start_at/end_at is date only (e.g. use start_at.to_date)
338
- # sqlite3 saves datetime in date field as datetime, for example (will break date compares later!)
339
- start_at = start_at.to_date
340
- end_at = end_at.to_date
341
-
342
-
343
- pos = find_round_pos!( line )
344
- title = find_round_def_title!( line )
345
- # NB: use extracted round title for knockout check
346
- knockout_flag = is_knockout_round?( title )
347
-
348
-
349
- logger.debug " start_at: #{start_at}"
350
- logger.debug " end_at: #{end_at}"
351
- logger.debug " pos: #{pos}"
352
- logger.debug " title: >#{title}<"
353
- logger.debug " knockout_flag: #{knockout_flag}"
354
-
355
- logger.debug " line: >#{line}<"
356
-
357
- #######################################
358
- # fix: add auto flag is false !!!!
359
-
360
- round_attribs = {
361
- title: title,
362
- knockout: knockout_flag,
363
- start_at: start_at,
364
- end_at: end_at
365
- }
366
-
367
- round = Round.find_by_event_id_and_pos( @event.id, pos )
368
- if round.present?
369
- logger.debug "update round #{round.id}:"
370
- else
371
- logger.debug "create round:"
372
- round = Round.new
373
-
374
- round_attribs = round_attribs.merge( {
375
- event_id: @event.id,
376
- pos: pos
377
- })
378
- end
379
-
380
- logger.debug round_attribs.to_json
381
-
382
- round.update_attributes!( round_attribs )
383
- end
384
-
385
-
386
- def parse_round_header( line )
387
- logger.debug "parsing round header line: >#{line}<"
388
-
389
- ### todo/fix/check: move cut off optional comment in reader for all lines? why? why not?
390
- cut_off_end_of_line_comment!( line ) # cut off optional comment starting w/ #
391
-
392
- # NB: cut off optional title2 starting w/ // first
393
- title2 = find_round_header_title2!( line )
394
-
395
- # todo/fix: check if it is possible title2 w/ group?
396
- # add an example here
397
- group_title, group_pos = find_group_title_and_pos!( line )
398
-
399
- ## todo/check/fix:
400
- # make sure Round of 16 will not return pos 16 -- how? possible?
401
- # add unit test too to verify
402
- pos = find_round_pos!( line )
403
-
404
- ## check if pos available; if not auto-number/calculate
405
- if pos.nil?
406
- if @patch_round_ids_pos.empty?
407
- pos = (@last_round_pos||0)+1
408
- logger.debug( " no round pos found; auto-number round - use (#{pos})" )
409
- else
410
- # note: if any rounds w/o pos already seen (add for auto-numbering at the end)
411
- # will get auto-numbered sorted by start_at date
412
- pos = 999001+@patch_round_ids_pos.length # e.g. 999<count> - 999001,999002,etc.
413
- logger.debug( " no round pos found; auto-number round w/ patch (backtrack) at the end" )
414
- end
415
- end
416
-
417
- # store pos for auto-number next round if missing
418
- # - note: only if greater/bigger than last; use max
419
- # - note: last_round_pos might be nil - thus set to 0
420
- if pos > 999000
421
- # note: do NOT update last_round_pos for to-be-patched rounds
422
- else
423
- @last_round_pos = [pos,@last_round_pos||0].max
424
- end
425
-
426
-
427
- title = find_round_header_title!( line )
428
-
429
- ## NB: use extracted round title for knockout check
430
- knockout_flag = is_knockout_round?( title )
431
-
432
-
433
- if group_pos.present?
434
- @group = Group.find_by_event_id_and_pos!( @event.id, group_pos )
435
- else
436
- @group = nil # reset group to no group
437
- end
438
-
439
- logger.debug " line: >#{line}<"
440
-
441
- ## NB: dummy/placeholder start_at, end_at date
442
- ## replace/patch after adding all games for round
443
-
444
- round_attribs = {
445
- title: title,
446
- title2: title2,
447
- knockout: knockout_flag
448
- }
449
-
450
- if pos > 999000
451
- # no pos (e.g. will get autonumbered later) - try match by title for now
452
- # e.g. lets us use title 'Group Replays', for example, multiple times
453
- @round = Round.find_by_event_id_and_title( @event.id, title )
454
- else
455
- @round = Round.find_by_event_id_and_pos( @event.id, pos )
456
- end
457
-
458
- if @round.present?
459
- logger.debug "update round #{@round.id}:"
460
- else
461
- logger.debug "create round:"
462
- @round = Round.new
463
-
464
- round_attribs = round_attribs.merge( {
465
- event_id: @event.id,
466
- pos: pos,
467
- start_at: Date.parse('1911-11-11'),
468
- end_at: Date.parse('1911-11-11')
469
- })
470
- end
471
-
472
- logger.debug round_attribs.to_json
473
-
474
- @round.update_attributes!( round_attribs )
475
-
476
- @patch_round_ids_pos << @round.id if pos > 999000
477
- ### store list of round ids for patching start_at/end_at at the end
478
- @patch_round_ids_dates << @round.id # todo/fix/check: check if round has definition (do NOT patch if definition (not auto-added) present)
479
- end
480
-
481
-
482
- def try_parse_game( line )
483
- # note: clone line; for possible test do NOT modify in place for now
484
- # note: returns true if parsed, false if no match
485
- parse_game( line.dup )
486
- end
487
-
488
- def parse_game( line )
489
- logger.debug "parsing game (fixture) line: >#{line}<"
490
-
491
- @mapper_teams.map_teams!( line ) ### todo/fix: limit mapping to two(2) teams - why? why not? might avoid matching @ Barcelona ??
492
- team_keys = @mapper_teams.find_teams!( line )
493
- team1_key = team_keys[0]
494
- team2_key = team_keys[1]
495
-
496
- ## note: if we do NOT find two teams; return false - no match found
497
- if team1_key.nil? || team2_key.nil?
498
- logger.debug " no game match (two teams required) found for line: >#{line}<"
499
- return false
500
- end
501
-
502
- pos = find_game_pos!( line )
503
-
504
- if is_postponed?( line )
505
- postponed = true
506
- date_v2 = find_date!( line, start_at: @event.start_at )
507
- date = find_date!( line, start_at: @event.start_at )
508
- else
509
- postponed = false
510
- date_v2 = nil
511
- date = find_date!( line, start_at: @event.start_at )
512
- end
513
-
514
- ###
515
- # check if date found?
516
- # NB: ruby falsey is nil & false only (not 0 or empty array etc.)
517
- if date
518
- ### check: use date_v2 if present? why? why not?
519
- @last_date = date # keep a reference for later use
520
- else
521
- date = @last_date # no date found; (re)use last seen date
522
- end
523
-
524
-
525
- scores = find_scores!( line )
526
-
527
-
528
- ####
529
- # note:
530
- # only map ground if we got any grounds (setup/configured in event)
531
-
532
- if @event.grounds.count > 0
533
-
534
- ## todo/check: use @known_grounds for check?? why? why not??
535
- ## use in @known_grounds = TextUtils.build_title_table_for( @event.grounds )
536
-
537
- ##
538
- # fix: mark mapped title w/ type (ground-) or such!! - too avoid fallthrough match
539
- # e.g. three teams match - but only two get mapped, third team gets match for ground
540
- # e.g Somalia v Djibouti @ Djibouti
541
- map_ground!( line )
542
- ground_key = find_ground!( line )
543
- ground = ground_key.nil? ? nil : Ground.find_by_key!( ground_key )
544
- else
545
- # no grounds configured; always nil
546
- ground = nil
547
- end
548
-
549
- logger.debug " line: >#{line}<"
550
-
551
-
552
- ### todo: cache team lookups in hash?
553
-
554
- team1 = Team.find_by_key!( team1_key )
555
- team2 = Team.find_by_key!( team2_key )
556
-
557
- @last_team1 = team1 # store for later use for goals etc.
558
- @last_team2 = team2
559
-
560
-
561
- if @round.nil?
562
- ## no round header found; calculate round from date
563
-
564
- ###
565
- ## todo/fix: add some unit tests for round look up
566
- # fix: use date_v2 if present!! (old/original date; otherwise use date)
567
-
568
- #
569
- # fix: check - what to do with hours e.g. start_at use 00:00 and for end_at use 23.59 ??
570
- # -- for now - remove hours (e.g. use end_of_day and beginnig_of_day)
571
-
572
- ##
573
- # note: start_at and end_at are dates ONLY (note datetime)
574
- # - do NOT pass in hours etc. in query
575
- # again use --> date.end_of_day, date.beginning_of_day
576
- # new: not working: date.to_date, date.to_date
577
- # will not find round if start_at same as date !! (in theory hours do not matter)
578
-
579
- ###
580
- # hack:
581
- # special case for sqlite3 (date compare not working reliable; use casts)
582
- # fix: move to adapter_name to activerecord_utils as sqlite? or similar?
583
-
584
- if ActiveRecord::Base.connection.adapter_name.downcase.starts_with?( 'sqlite' )
585
- logger.debug( " [sqlite] using sqlite-specific query for date compare for rounds finder" )
586
- round = Round.where( 'event_id = ? AND ( julianday(start_at) <= julianday(?)'+
587
- 'AND julianday(end_at) >= julianday(?))',
588
- @event.id, date.to_date, date.to_date).first
589
- else # all other dbs (postgresql, mysql, etc.)
590
- round = Round.where( 'event_id = ? AND (start_at <= ? AND end_at >= ?)',
591
- @event.id, date.to_date, date.to_date).first
592
- end
593
-
594
- pp round
595
- if round.nil?
596
- logger.warn( " !!!! no round match found for date #{date}" )
597
- pp Round.all
598
-
599
- ###################################
600
- # -- try auto-adding matchday
601
- round = Round.new
602
-
603
- round_attribs = {
604
- event_id: @event.id,
605
- title: "Matchday #{date.to_date}",
606
- pos: 999001+@patch_round_ids_pos.length, # e.g. 999<count> - 999001,999002,etc.
607
- start_at: date.to_date,
608
- end_at: date.to_date
609
- }
610
-
611
- logger.info( " auto-add round >Matchday #{date.to_date}<" )
612
- logger.debug round_attribs.to_json
613
-
614
- round.update_attributes!( round_attribs )
615
-
616
- @patch_round_ids_pos << round.id # todo/check - add just id or "full" record as now - why? why not?
617
- end
618
-
619
- # store pos for auto-number next round if missing
620
- # - note: only if greater/bigger than last; use max
621
- # - note: last_round_pos might be nil - thus set to 0
622
- if round.pos > 999000
623
- # note: do NOT update last_round_pos for to-be-patched rounds
624
- else
625
- @last_round_pos = [round.pos,@last_round_pos||0].max
626
- end
627
-
628
- ## note: will crash (round.pos) if round is nil
629
- logger.debug( " using round #{round.pos} >#{round.title}< start_at: #{round.start_at}, end_at: #{round.end_at}" )
630
- else
631
- ## use round from last round header
632
- round = @round
633
- end
634
-
635
-
636
- ### check if games exists
637
- ## with this teams in this round if yes only update
638
- game = Game.find_by_round_id_and_team1_id_and_team2_id(
639
- round.id, team1.id, team2.id
640
- )
641
-
642
- game_attribs = {
643
- score1i: scores[0],
644
- score2i: scores[1],
645
- score1: scores[2],
646
- score2: scores[3],
647
- score1et: scores[4],
648
- score2et: scores[5],
649
- score1p: scores[6],
650
- score2p: scores[7],
651
- play_at: date,
652
- play_at_v2: date_v2,
653
- postponed: postponed,
654
- knockout: round.knockout, ## note: for now always use knockout flag from round - why? why not??
655
- ground_id: ground.present? ? ground.id : nil,
656
- group_id: @group.present? ? @group.id : nil
657
- }
658
-
659
- game_attribs[ :pos ] = pos if pos.present?
660
-
661
- ####
662
- # note: only update if any changes (or create if new record)
663
- if game.present? &&
664
- game.check_for_changes( game_attribs ) == false
665
- logger.debug " skip update game #{game.id}; no changes found"
666
- else
667
- if game.present?
668
- logger.debug "update game #{game.id}:"
669
- else
670
- logger.debug "create game:"
671
- game = Game.new
672
-
673
- more_game_attribs = {
674
- round_id: round.id,
675
- team1_id: team1.id,
676
- team2_id: team2.id
677
- }
678
-
679
- ## NB: use round.games.count for pos
680
- ## lets us add games out of order if later needed
681
- more_game_attribs[ :pos ] = round.games.count+1 if pos.nil?
682
-
683
- game_attribs = game_attribs.merge( more_game_attribs )
684
- end
685
-
686
- logger.debug game_attribs.to_json
687
- game.update_attributes!( game_attribs )
688
- end
689
-
690
- @last_game = game # store for later reference (e.g. used for goals etc.)
691
-
692
- return true # game match found
693
- end # method parse_game
694
-
695
-
696
- def try_parse_date_header( line )
697
- # note: clone line; for possible test do NOT modify in place for now
698
- # note: returns true if parsed, false if no match
699
- parse_date_header( line.dup )
700
- end
701
-
702
- def parse_date_header( line )
703
- # note: returns true if parsed, false if no match
704
-
705
- # line with NO teams plus include date e.g.
706
- # [Fri Jun/17] or
707
- # Jun/17 or
708
- # Jun/17: etc.
709
-
710
-
711
- @mapper_teams.map_teams!( line )
712
- team_keys = @mapper_teams.find_teams!( line )
713
- team1_key = team_keys[0]
714
- team2_key = team_keys[1]
715
-
716
- date = find_date!( line, start_at: @event.start_at )
717
-
718
- if date && team1_key.nil? && team2_key.nil?
719
- logger.debug( "date header line found: >#{line}<")
720
- logger.debug( " date: #{date}")
721
-
722
- @last_date = date # keep a reference for later use
723
- return true
724
- else
725
- return false
726
- end
727
- end
728
-
729
-
730
- def parse_goals( line )
731
- logger.debug "parsing goals (fixture) line: >#{line}<"
732
-
733
- goals = GoalsFinder.new.find!( line )
734
-
735
- ## check if squads/rosters present for player mappings
736
- #
737
- squad1_count = Roster.where( event_id: @event.id, team_id: @last_team1 ).count
738
- if squad1_count > 0
739
- squad1 = Roster.where( event_id: @event.id, team_id: @last_team1 )
740
- else
741
- squad1 = []
742
- end
743
-
744
- squad2_count = Roster.where( event_id: @event.id, team_id: @last_team2 ).count
745
- if squad2_count > 0
746
- squad2 = Roster.where( event_id: @event.id, team_id: @last_team2 )
747
- else
748
- squad2 = []
749
- end
750
-
751
- #####
752
- # todo/fix: try lookup by squads first!!!
753
- # issue warning if player not included in squad!!
754
-
755
- ##########
756
- # try mapping player names to player
757
-
758
- ## note: first delete all goals for match (and recreate new ones
759
- # no need to figure out update/merge strategy)
760
- @last_game.goals.delete_all
761
-
762
-
763
- goals.each do |goal|
764
- player_name = goal.name
765
-
766
- player = Person.where( name: player_name ).first
767
- if player
768
- logger.info " player match (name eq) - using player key #{player.key}"
769
- else
770
- # try like match (player name might only include part of name e.g. Messi)
771
- # try three variants
772
- # try %Messi
773
- # try Messi%
774
- # try %Messi% -- check if there's an easier way w/ "one" where clause?
775
- player = Person.where( 'name LIKE ? OR name LIKE ? OR name LIKE ?',
776
- "%#{player_name}",
777
- "#{player_name}%",
778
- "%#{player_name}%"
779
- ).first
780
-
781
- if player
782
- logger.info " player match (name like) - using player key #{player.key}"
783
- else
784
- # try synonyms
785
- player = Person.where( 'synonyms LIKE ? OR synonyms LIKE ? OR synonyms LIKE ?',
786
- "%#{player_name}",
787
- "#{player_name}%",
788
- "%#{player_name}%"
789
- ).first
790
- if player
791
- logger.info " player match (synonyms like) - using player key #{player.key}"
792
- else
793
- # auto-create player (player not found)
794
- logger.info " player NOT found >#{player_name}< - auto-create"
795
-
796
- ## fix: add auto flag (for auto-created persons/players)
797
- ## fix: move title_to_key logic to person model etc.
798
- player_key = TextUtils.title_to_key( player_name )
799
- player_attribs = {
800
- key: player_key,
801
- title: player_name
802
- }
803
- logger.info " using attribs: #{player_attribs.inspect}"
804
-
805
- player = Person.create!( player_attribs )
806
- end
807
- end
808
- end
809
-
810
- goal_attribs = {
811
- game_id: @last_game.id,
812
- team_id: goal.team == 1 ? @last_team1.id : @last_team2.id,
813
- person_id: player.id,
814
- minute: goal.minute,
815
- offset: goal.offset,
816
- penalty: goal.penalty,
817
- owngoal: goal.owngoal,
818
- score1: goal.score1,
819
- score2: goal.score2
820
- }
821
-
822
- logger.info " adding goal using attribs: #{goal_attribs.inspect}"
823
- Goal.create!( goal_attribs )
824
- end # each goals
825
-
826
- end # method parse_goals
827
-
828
-
829
- =begin
830
- ###### add to person and use!!!
831
- def self.create_or_update_from_values( values, more_attribs={} )
832
- ## key & title required
833
-
834
- attribs, more_values = find_key_n_title( values )
835
- attribs = attribs.merge( more_attribs )
836
-
837
- ## check for optional values
838
- Person.create_or_update_from_attribs( attribs, more_values )
839
- end
840
- =end
841
-
842
-
843
- def parse_fixtures( reader )
844
-
845
- reader.each_line do |line|
846
-
847
- if is_goals?( line )
848
- parse_goals( line )
849
- elsif is_round_def?( line )
850
- ## todo/fix: add round definition (w begin n end date)
851
- ## todo: do not patch rounds with definition (already assume begin/end date is good)
852
- ## -- how to deal with matches that get rescheduled/postponed?
853
- parse_round_def( line )
854
- elsif is_round?( line )
855
- parse_round_header( line )
856
- elsif is_group_def?( line ) ## NB: group goes after round (round may contain group marker too)
857
- ### todo: add pipe (|) marker (required)
858
- parse_group_def( line )
859
- elsif is_group?( line )
860
- ## -- lets you set group e.g. Group A etc.
861
- parse_group_header( line )
862
- elsif try_parse_game( line )
863
- # do nothing here
864
- elsif try_parse_date_header( line )
865
- # do nothing here
866
- else
867
- logger.info "skipping line (no match found): >#{line}<"
868
- end
869
- end # lines.each
870
-
871
- ###########################
872
- # backtrack and patch round pos and round dates (start_at/end_at)
873
- # note: patch dates must go first! (otherwise sort_by_date will not work for round pos)
874
-
875
- unless @patch_round_ids_dates.empty?
876
- ###
877
- # fix: do NOT patch if auto flag is set to false !!!
878
- # e.g. rounds got added w/ round def (not w/ round header)
879
-
880
- # note: use uniq - to allow multiple round headers (possible?)
881
-
882
- Round.find( @patch_round_ids_dates.uniq ).each do |r|
883
- logger.debug "patch round start_at/end_at date for #{r.title}:"
884
-
885
- ## note:
886
- ## will add "scope" pos first e.g
887
- #
888
- ## SELECT "games".* FROM "games" WHERE "games"."round_id" = ?
889
- # ORDER BY pos, play_at asc [["round_id", 7]]
890
- # thus will NOT order by play_at but by pos first!!!
891
- # =>
892
- # need to unscope pos!!! or use unordered_games - games_by_play_at_date etc.??
893
- # thus use reorder()!!! - not just order('play_at asc')
894
-
895
- games = r.games.reorder( 'play_at asc' ).all
896
-
897
- ## skip rounds w/ no games
898
-
899
- ## todo/check/fix: what's the best way for checking assoc w/ 0 recs?
900
- next if games.size == 0
901
-
902
- # note: make sure start_at/end_at is date only (e.g. use play_at.to_date)
903
- # sqlite3 saves datetime in date field as datetime, for example (will break date compares later!)
904
-
905
- round_attribs = {
906
- start_at: games[0].play_at.to_date, # use games.first ?
907
- end_at: games[-1].play_at.to_date # use games.last ? why? why not?
908
- }
909
-
910
- logger.debug round_attribs.to_json
911
- r.update_attributes!( round_attribs )
912
- end
913
- end
914
-
915
- unless @patch_round_ids_pos.empty?
916
-
917
- # step 0: check for offset (last_round_pos)
918
- if @last_round_pos
919
- offset = @last_round_pos
920
- logger.info " +++ patch round pos - use offset; start w/ #{offset}"
921
- else
922
- offset = 0
923
- logger.debug " patch round pos - no offset; start w/ 0"
924
- end
925
-
926
- # step 1: sort by date
927
- # step 2: update pos
928
- # note: use uniq - to allow multiple round headers (possible?)
929
- Round.order( 'start_at asc').find( @patch_round_ids_pos.uniq ).each_with_index do |r,idx|
930
- # note: starts counting w/ zero(0)
931
- logger.debug "[#{idx+1}] patch round pos >#{offset+idx+1}< for #{r.title}:"
932
- round_attribs = {
933
- pos: offset+idx+1
934
- }
935
-
936
- # update title if Matchday XXXX e.g. use Matchday 1 etc.
937
- if r.title.starts_with?('Matchday')
938
- round_attribs[:title] = "Matchday #{offset+idx+1}"
939
- end
940
-
941
- logger.debug round_attribs.to_json
942
- r.update_attributes!( round_attribs )
943
-
944
- # update last_round_pos offset too
945
- @last_round_pos = [offset+idx+1,@last_round_pos||0].max
946
- end
947
- end
948
-
949
- end # method parse_fixtures
950
-
951
- end # class GameReader
952
- end # module SportDb
@@ -5,7 +5,11 @@ module SportDb
5
5
 
6
6
  ## shared "higher-level" outline reader
7
7
  ## todo: add CountryOutlineReader - why? why not?
8
+
8
9
  class LeagueOutlineReader
10
+
11
+ def self.config() Import.config; end ## shortcut convenience helper
12
+
9
13
  ## split into league + season
10
14
  ## e.g. Österr. Bundesliga 2015/16 ## or 2015-16
11
15
  ## World Cup 2018
@@ -62,7 +66,7 @@ class LeagueOutlineReader
62
66
 
63
67
  def self.find_league( name )
64
68
  league = nil
65
- m = LEAGUES.match( name )
69
+ m = config.leagues.match( name )
66
70
  # pp m
67
71
 
68
72
  if m.nil?
@@ -5,8 +5,8 @@ module SportDb
5
5
  module Readers
6
6
 
7
7
  MAJOR = 0 ## todo: namespace inside version or something - why? why not??
8
- MINOR = 0
9
- PATCH = 1
8
+ MINOR = 1
9
+ PATCH = 0
10
10
  VERSION = [MAJOR,MINOR,PATCH].join('.')
11
11
 
12
12
  def self.version
data/test/helper.rb CHANGED
@@ -13,9 +13,3 @@ require 'sportdb/readers'
13
13
  ## use (switch to) "external" datasets
14
14
  SportDb::Import.config.clubs_dir = "../../../openfootball/clubs"
15
15
  SportDb::Import.config.leagues_dir = "../../../openfootball/leagues"
16
-
17
-
18
-
19
- LEAGUES = SportDb::Import.config.leagues
20
- CLUBS = SportDb::Import.config.clubs
21
- COUNTRIES = SportDb::Import.config.countries
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sportdb-readers
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gerald Bauer
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-10-29 00:00:00.000000000 Z
11
+ date: 2019-10-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: sportdb-config
@@ -66,7 +66,8 @@ dependencies:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
68
  version: '3.16'
69
- description: sportdb-readers - sport.db readers
69
+ description: sportdb-readers - sport.db readers for leagues, seasons, clubs, match
70
+ schedules and results, and more
70
71
  email: opensport@googlegroups.com
71
72
  executables: []
72
73
  extensions: []
@@ -114,5 +115,6 @@ rubyforge_project:
114
115
  rubygems_version: 2.5.2
115
116
  signing_key:
116
117
  specification_version: 4
117
- summary: sportdb-readers - sport.db readers
118
+ summary: sportdb-readers - sport.db readers for leagues, seasons, clubs, match schedules
119
+ and results, and more
118
120
  test_files: []