sportdb 1.8.7 → 1.8.8

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,197 @@
1
+ # encoding: UTF-8
2
+
3
+ module SportDb
4
+
5
+
6
+ class EventReader
7
+
8
+ include LogUtils::Logging
9
+
10
+ ## make models available by default with namespace
11
+ # e.g. lets you use Usage instead of Model::Usage
12
+ include Models
13
+
14
+
15
+ attr_reader :include_path
16
+ attr_reader :event # returns event record; call read first
17
+
18
+ def initialize( include_path, opts = {} )
19
+ @include_path = include_path
20
+
21
+ @name = nil
22
+ @event = nil
23
+ @fixtures = []
24
+ end
25
+
26
+
27
+ def fixtures
28
+ ## note: needs to call read first (to set @name, @fixtures, etc.)
29
+
30
+ if @fixtures.empty?
31
+ ## logger.warn "no fixtures found for event - >#{name}<; assume fixture name is the same as event"
32
+ fixtures_with_path = [ @name ]
33
+ else
34
+ ## add path to fixtures (use path from event e.g)
35
+ # - bl + at-austria!/2012_13/bl -> at-austria!/2012_13/bl
36
+ # - bl_ii + at-austria!/2012_13/bl -> at-austria!/2012_13/bl_ii
37
+
38
+ dir = File.dirname( @name ) # use dir for fixtures
39
+
40
+ fixtures_with_path = @fixtures.map do |fx|
41
+ fx_new = "#{dir}/#{fx}" # add path upfront
42
+ logger.debug "fx: #{fx_new} | >#{fx}< + >#{dir}<"
43
+ fx_new
44
+ end
45
+ end
46
+
47
+ fixtures_with_path
48
+ end
49
+
50
+
51
+
52
+ def read( name, more_attribs={} )
53
+ @fixtures = [] # reset cached fixtures
54
+ @event = nil # reset cached event rec
55
+ @name = name # keep name (needed for fixtures attrib getter)
56
+
57
+ ####
58
+ ## fix!!!!!
59
+ ## use Event.create_or_update_from_hash or similar
60
+ ## use Event.create_or_update_from_hash_reader?? or similar
61
+ # move parsing code to model
62
+
63
+ reader = HashReaderV2.new( name, include_path )
64
+
65
+ event_attribs = {}
66
+
67
+ ## set default sources to basename by convention
68
+ # e.g 2013_14/bl => bl
69
+ # etc.
70
+ # use fixtures/sources: to override default
71
+
72
+ event_attribs[ 'sources' ] = File.basename( name )
73
+ event_attribs[ 'config' ] = File.basename( name ) # name a of .yml file
74
+
75
+ reader.each_typed do |key, value|
76
+
77
+ ## puts "processing event attrib >>#{key}<< >>#{value}<<..."
78
+
79
+ if key == 'league'
80
+ league = League.find_by_key( value.to_s.strip )
81
+
82
+ ## check if it exists
83
+ if league.present?
84
+ event_attribs['league_id'] = league.id
85
+ else
86
+ logger.error "league with key >>#{value.to_s.strip}<< missing"
87
+ exit 1
88
+ end
89
+
90
+ elsif key == 'season'
91
+ season = Season.find_by_key( value.to_s.strip )
92
+
93
+ ## check if it exists
94
+ if season.present?
95
+ event_attribs['season_id'] = season.id
96
+ else
97
+ logger.error "season with key >>#{value.to_s.strip}<< missing"
98
+ exit 1
99
+ end
100
+
101
+ elsif key == 'start_at' || key == 'begin_at'
102
+
103
+ if value.is_a?(DateTime) || value.is_a?(Date)
104
+ start_at = value
105
+ else # assume it's a string
106
+ start_at = DateTime.strptime( value.to_s.strip, '%Y-%m-%d' )
107
+ end
108
+
109
+ event_attribs['start_at'] = start_at
110
+
111
+ elsif key == 'end_at' || key == 'stop_at'
112
+
113
+ if value.is_a?(DateTime) || value.is_a?(Date)
114
+ end_at = value
115
+ else # assume it's a string
116
+ end_at = DateTime.strptime( value.to_s.strip, '%Y-%m-%d' )
117
+ end
118
+
119
+ event_attribs['end_at'] = end_at
120
+
121
+ elsif key == 'grounds' || key == 'stadiums' || key == 'venues'
122
+ ## assume grounds value is an array
123
+
124
+ ##
125
+ ## note: for now we allow invalid ground keys
126
+ ## will skip keys not found
127
+
128
+ ground_ids = []
129
+ value.each do |item|
130
+ ground_key = item.to_s.strip
131
+ ground = Ground.find_by_key( ground_key )
132
+ if ground.nil?
133
+ puts "[warn] ground/stadium w/ key >#{ground_key}< not found; skipping ground"
134
+ else
135
+ ground_ids << ground.id
136
+ end
137
+ end
138
+
139
+ event_attribs['ground_ids'] = ground_ids
140
+ elsif key == 'teams'
141
+ ## assume teams value is an array
142
+
143
+ team_ids = []
144
+ value.each do |item|
145
+ team_key = item.to_s.strip
146
+ team = Team.find_by_key!( team_key )
147
+ team_ids << team.id
148
+ end
149
+
150
+ event_attribs['team_ids'] = team_ids
151
+
152
+ elsif key == 'team3'
153
+ ## for now always assume false # todo: fix - use value and convert to boolean if not boolean
154
+ event_attribs['team3'] = false
155
+
156
+ elsif key == 'fixtures' || key == 'sources'
157
+ ### todo: check for mulitiple fixtures/sources ?? allow disallow?? why? why not?
158
+ if value.kind_of?(Array)
159
+ event_attribs['sources'] = value.join(',')
160
+ @fixtures += value
161
+ else # assume plain (single fixture) string
162
+ event_attribs['sources'] = value.to_s
163
+ @fixtures << value.to_s
164
+ end
165
+ else
166
+ ## todo: add a source location struct to_s or similar (file, line, col)
167
+ logger.error "unknown event attrib #{key}; skipping attrib"
168
+ end
169
+
170
+ end # each key,value
171
+
172
+ league_id = event_attribs['league_id']
173
+ season_id = event_attribs['season_id']
174
+
175
+ logger.debug "find event - league_id: #{league_id}, season_id: #{season_id}"
176
+
177
+ event = Event.find_by_league_id_and_season_id( league_id, season_id )
178
+
179
+ ## check if it exists
180
+ if event.present?
181
+ logger.debug "*** update event #{event.id}-#{event.key}:"
182
+ else
183
+ logger.debug "*** create event:"
184
+ event = Event.new
185
+ end
186
+
187
+ logger.debug event_attribs.to_json
188
+
189
+ event.update_attributes!( event_attribs )
190
+
191
+ # keep a cached reference for later use
192
+ @event = event
193
+ end # method read
194
+
195
+
196
+ end # class EventReader
197
+ end # module SportDb
@@ -0,0 +1,336 @@
1
+ # encoding: UTF-8
2
+
3
+ module SportDb
4
+
5
+
6
+ class GameReader
7
+
8
+ include LogUtils::Logging
9
+
10
+ ## make models available by default with namespace
11
+ # e.g. lets you use Usage instead of Model::Usage
12
+ include Models
13
+
14
+ ## value helpers e.g. is_year?, is_taglist? etc.
15
+ include TextUtils::ValueHelper
16
+
17
+ include FixtureHelpers
18
+
19
+
20
+ attr_reader :include_path
21
+
22
+
23
+ def initialize( include_path, opts = {} )
24
+ @include_path = include_path
25
+ end
26
+
27
+
28
+ def read( name, more_attribs={} )
29
+ reader = EvenReader.new( @include_path )
30
+ reader.read( name )
31
+
32
+ event = reader.event ## was fetch_event( name )
33
+ fixtures = reader.fixtures ## was fetch_event_fixtures( name )
34
+
35
+ fixtures.each do |fixture|
36
+ read_fixtures( event.key, fixture ) ## use read_for or read_for_event - why ??? why not??
37
+ end
38
+ end
39
+
40
+
41
+ def read_fixtures_from_string( event_key, text ) # load from string (e.g. passed in via web form)
42
+
43
+ SportDb.lang.lang = SportDb.lang.classify( text )
44
+
45
+ ## todo/fix: move code into LineReader e.g. use LineReader.fromString() - why? why not?
46
+ reader = StringLineReader.new( text )
47
+
48
+ read_fixtures_worker( event_key, reader )
49
+
50
+ ## fix add prop
51
+ ### Prop.create!( key: "db.#{fixture_name_to_prop_key(name)}.version", value: "file.txt.#{File.mtime(path).strftime('%Y.%m.%d')}" )
52
+ end
53
+
54
+
55
+ def read_fixtures( event_key, name ) # load from file system
56
+
57
+ ## todo: move name_real_path code to LineReaderV2 ????
58
+ pos = name.index( '!/')
59
+ if pos.nil?
60
+ name_real_path = name # not found; real path is the same as name
61
+ else
62
+ # cut off everything until !/ e.g.
63
+ # at-austria!/w-wien/beers becomes
64
+ # w-wien/beers
65
+ name_real_path = name[ (pos+2)..-1 ]
66
+ end
67
+
68
+
69
+ path = "#{include_path}/#{name_real_path}.txt"
70
+
71
+ logger.info "parsing data '#{name}' (#{path})..."
72
+
73
+ SportDb.lang.lang = SportDb.lang.classify_file( path )
74
+
75
+ reader = LineReader.new( path )
76
+
77
+ load_fixtures_worker( event_key, reader )
78
+
79
+ Prop.create_from_fixture!( name, path )
80
+ end
81
+
82
+
83
+ def load_fixtures_worker( event_key, reader )
84
+
85
+ ## assume active activerecord connection
86
+ ##
87
+
88
+ ## reset cached values
89
+ @patch_rounds = {}
90
+ @knockout_flag = false
91
+ @round = nil
92
+
93
+
94
+ @event = Event.find_by_key!( event_key )
95
+
96
+ logger.debug "Event #{@event.key} >#{@event.title}<"
97
+
98
+ ### fix: use build_title_table_for ??? why? why not??
99
+ @known_teams = @event.known_teams_table
100
+
101
+ @known_grounds = TextUtils.build_title_table_for( @event.grounds )
102
+
103
+
104
+ parse_fixtures( reader )
105
+
106
+ end # method load_fixtures
107
+
108
+
109
+ def parse_group( line )
110
+ logger.debug "parsing group line: >#{line}<"
111
+
112
+ match_teams!( line )
113
+ team_keys = find_teams!( line )
114
+
115
+ title, pos = find_group_title_and_pos!( line )
116
+
117
+ logger.debug " line: >#{line}<"
118
+
119
+ group_attribs = {
120
+ title: title
121
+ }
122
+
123
+ @group = Group.find_by_event_id_and_pos( @event.id, pos )
124
+ if @group.present?
125
+ logger.debug "update group #{@group.id}:"
126
+ else
127
+ logger.debug "create group:"
128
+ @group = Group.new
129
+ group_attribs = group_attribs.merge( {
130
+ event_id: @event.id,
131
+ pos: pos
132
+ })
133
+ end
134
+
135
+ logger.debug group_attribs.to_json
136
+
137
+ @group.update_attributes!( group_attribs )
138
+
139
+ @group.teams.clear # remove old teams
140
+ ## add new teams
141
+ team_keys.each do |team_key|
142
+ team = Team.find_by_key!( team_key )
143
+ logger.debug " adding team #{team.title} (#{team.code})"
144
+ @group.teams << team
145
+ end
146
+ end
147
+
148
+ def parse_round( line )
149
+ logger.debug "parsing round line: >#{line}<"
150
+
151
+ ### todo/fix/check: move cut off optional comment in reader for all lines? why? why not?
152
+ cut_off_end_of_line_comment!( line ) # cut off optional comment starting w/ #
153
+
154
+ # NB: cut off optional title2 starting w/ // first
155
+ title2 = find_round_title2!( line )
156
+
157
+ group_title, group_pos = find_group_title_and_pos!( line )
158
+
159
+ pos = find_round_pos!( line )
160
+
161
+ title = find_round_title!( line )
162
+
163
+ ## NB: use extracted round title for knockout check
164
+ @knockout_flag = is_knockout_round?( title )
165
+
166
+
167
+ if group_pos.present?
168
+ @group = Group.find_by_event_id_and_pos!( @event.id, group_pos )
169
+ else
170
+ @group = nil # reset group to no group
171
+ end
172
+
173
+ logger.debug " line: >#{line}<"
174
+
175
+ ## NB: dummy/placeholder start_at, end_at date
176
+ ## replace/patch after adding all games for round
177
+
178
+ round_attribs = {
179
+ title: title,
180
+ title2: title2,
181
+ knockout: @knockout_flag
182
+ }
183
+
184
+
185
+ @round = Round.find_by_event_id_and_pos( @event.id, pos )
186
+ if @round.present?
187
+ logger.debug "update round #{@round.id}:"
188
+ else
189
+ logger.debug "create round:"
190
+ @round = Round.new
191
+
192
+ round_attribs = round_attribs.merge( {
193
+ event_id: @event.id,
194
+ pos: pos,
195
+ start_at: Time.utc('1912-12-12'),
196
+ end_at: Time.utc('1912-12-12')
197
+ })
198
+ end
199
+
200
+ logger.debug round_attribs.to_json
201
+
202
+ @round.update_attributes!( round_attribs )
203
+
204
+ ### store list of round is for patching start_at/end_at at the end
205
+ @patch_rounds[ @round.id ] = @round.id
206
+ end
207
+
208
+ def parse_game( line )
209
+ logger.debug "parsing game (fixture) line: >#{line}<"
210
+
211
+ pos = find_game_pos!( line )
212
+
213
+ match_teams!( line )
214
+ team1_key = find_team1!( line )
215
+ team2_key = find_team2!( line )
216
+
217
+ if is_postponed?( line )
218
+ postponed = true
219
+ date_v2 = find_date!( line, start_at: @event.start_at )
220
+ date = find_date!( line, start_at: @event.start_at )
221
+ else
222
+ postponed = false
223
+ date_v2 = nil
224
+ date = find_date!( line, start_at: @event.start_at )
225
+ end
226
+
227
+ scores = find_scores!( line )
228
+
229
+
230
+ map_ground!( line )
231
+ ground_key = find_ground!( line )
232
+ ground = ground_key.nil? ? nil : Ground.find_by_key!( ground_key )
233
+
234
+
235
+ logger.debug " line: >#{line}<"
236
+
237
+
238
+ ### todo: cache team lookups in hash?
239
+
240
+ team1 = Team.find_by_key!( team1_key )
241
+ team2 = Team.find_by_key!( team2_key )
242
+
243
+
244
+ ### check if games exists
245
+ ## with this teams in this round if yes only update
246
+ game = Game.find_by_round_id_and_team1_id_and_team2_id(
247
+ @round.id, team1.id, team2.id
248
+ )
249
+
250
+ game_attribs = {
251
+ score1: scores[0],
252
+ score2: scores[1],
253
+ score1et: scores[2],
254
+ score2et: scores[3],
255
+ score1p: scores[4],
256
+ score2p: scores[5],
257
+ play_at: date,
258
+ play_at_v2: date_v2,
259
+ postponed: postponed,
260
+ knockout: @knockout_flag,
261
+ ground_id: ground.present? ? ground.id : nil,
262
+ group_id: @group.present? ? @group.id : nil
263
+ }
264
+
265
+ game_attribs[ :pos ] = pos if pos.present?
266
+
267
+ ####
268
+ # note: only update if any changes (or create if new record)
269
+ if game.present? &&
270
+ game.check_for_changes( game_attribs ) == false
271
+ logger.debug " skip update game #{game.id}; no changes found"
272
+ else
273
+ if game.present?
274
+ logger.debug "update game #{game.id}:"
275
+ else
276
+ logger.debug "create game:"
277
+ game = Game.new
278
+
279
+ more_game_attribs = {
280
+ round_id: @round.id,
281
+ team1_id: team1.id,
282
+ team2_id: team2.id
283
+ }
284
+
285
+ ## NB: use round.games.count for pos
286
+ ## lets us add games out of order if later needed
287
+ more_game_attribs[ :pos ] = @round.games.count+1 if pos.nil?
288
+
289
+ game_attribs = game_attribs.merge( more_game_attribs )
290
+ end
291
+
292
+ logger.debug game_attribs.to_json
293
+ game.update_attributes!( game_attribs )
294
+ end
295
+
296
+ end # method parse_game
297
+
298
+
299
+ def parse_fixtures( reader )
300
+
301
+ reader.each_line do |line|
302
+ if is_round?( line )
303
+ parse_round( line )
304
+ elsif is_group?( line ) ## NB: group goes after round (round may contain group marker too)
305
+ parse_group( line )
306
+ else
307
+ parse_game( line )
308
+ end
309
+ end # lines.each
310
+
311
+ @patch_rounds.each do |k,v|
312
+ logger.debug "patch start_at/end_at date for round #{k}:"
313
+ round = Round.find( k )
314
+ games = round.games.order( 'play_at asc' ).all
315
+
316
+ ## skip rounds w/ no games
317
+
318
+ ## todo/fix: what's the best way for checking assoc w/ 0 recs?
319
+ next if games.size == 0
320
+
321
+ round_attribs = {}
322
+
323
+ ## todo: check for no records
324
+ ## e.g. if game[0].present? or just if game[0] ??
325
+
326
+ round_attribs[:start_at] = games[0].play_at
327
+ round_attribs[:end_at ] = games[-1].play_at
328
+
329
+ logger.debug round_attribs.to_json
330
+ round.update_attributes!( round_attribs )
331
+ end
332
+
333
+ end # method parse_fixtures
334
+
335
+ end # class GameReader
336
+ end # module SportDb