sportdb 1.9.16 → 1.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (148) hide show
  1. checksums.yaml +4 -4
  2. data/HISTORY.md +2 -19
  3. data/Manifest.txt +1 -137
  4. data/README.md +2 -21
  5. data/Rakefile +14 -25
  6. data/lib/sportdb.rb +9 -260
  7. data/lib/sportdb/cli/main.rb +5 -14
  8. data/lib/sportdb/cli/opts.rb +2 -0
  9. data/lib/sportdb/cli/version.rb +26 -0
  10. metadata +10 -278
  11. data/.gemtest +0 -0
  12. data/config/fixtures/de.yml +0 -46
  13. data/config/fixtures/en.yml +0 -54
  14. data/config/fixtures/es.yml +0 -48
  15. data/config/fixtures/fr.yml +0 -53
  16. data/config/fixtures/it.yml +0 -55
  17. data/config/fixtures/pt.yml +0 -46
  18. data/config/fixtures/ro.yml +0 -55
  19. data/data/seasons.txt +0 -74
  20. data/data/setups/all.txt +0 -5
  21. data/lib/sportdb/calc.rb +0 -279
  22. data/lib/sportdb/deleter.rb +0 -52
  23. data/lib/sportdb/finders/date.rb +0 -374
  24. data/lib/sportdb/finders/goals.rb +0 -260
  25. data/lib/sportdb/finders/scores.rb +0 -122
  26. data/lib/sportdb/lang.rb +0 -216
  27. data/lib/sportdb/matcher.rb +0 -31
  28. data/lib/sportdb/models/assoc.rb +0 -106
  29. data/lib/sportdb/models/assoc_assoc.rb +0 -15
  30. data/lib/sportdb/models/badge.rb +0 -14
  31. data/lib/sportdb/models/event.rb +0 -65
  32. data/lib/sportdb/models/event_ground.rb +0 -15
  33. data/lib/sportdb/models/event_team.rb +0 -16
  34. data/lib/sportdb/models/forward.rb +0 -55
  35. data/lib/sportdb/models/game.rb +0 -244
  36. data/lib/sportdb/models/goal.rb +0 -15
  37. data/lib/sportdb/models/ground.rb +0 -100
  38. data/lib/sportdb/models/group.rb +0 -23
  39. data/lib/sportdb/models/group_team.rb +0 -14
  40. data/lib/sportdb/models/league.rb +0 -83
  41. data/lib/sportdb/models/person.rb +0 -21
  42. data/lib/sportdb/models/roster.rb +0 -18
  43. data/lib/sportdb/models/round.rb +0 -22
  44. data/lib/sportdb/models/season.rb +0 -14
  45. data/lib/sportdb/models/stats/alltime_standing.rb +0 -44
  46. data/lib/sportdb/models/stats/alltime_standing_entry.rb +0 -23
  47. data/lib/sportdb/models/stats/event_standing.rb +0 -55
  48. data/lib/sportdb/models/stats/event_standing_entry.rb +0 -21
  49. data/lib/sportdb/models/stats/group_standing.rb +0 -50
  50. data/lib/sportdb/models/stats/group_standing_entry.rb +0 -22
  51. data/lib/sportdb/models/team.rb +0 -119
  52. data/lib/sportdb/models/team_comp.rb +0 -64
  53. data/lib/sportdb/models/utils.rb +0 -78
  54. data/lib/sportdb/models/world/city.rb +0 -21
  55. data/lib/sportdb/models/world/continent.rb +0 -20
  56. data/lib/sportdb/models/world/country.rb +0 -19
  57. data/lib/sportdb/models/world/region.rb +0 -19
  58. data/lib/sportdb/patterns.rb +0 -38
  59. data/lib/sportdb/reader.rb +0 -130
  60. data/lib/sportdb/reader_file.rb +0 -123
  61. data/lib/sportdb/reader_zip.rb +0 -165
  62. data/lib/sportdb/readers/assoc.rb +0 -54
  63. data/lib/sportdb/readers/event.rb +0 -200
  64. data/lib/sportdb/readers/game.rb +0 -877
  65. data/lib/sportdb/readers/ground.rb +0 -53
  66. data/lib/sportdb/readers/league.rb +0 -54
  67. data/lib/sportdb/readers/season.rb +0 -83
  68. data/lib/sportdb/readers/squad_club.rb +0 -201
  69. data/lib/sportdb/readers/squad_national_team.rb +0 -173
  70. data/lib/sportdb/readers/team.rb +0 -53
  71. data/lib/sportdb/schema.rb +0 -373
  72. data/lib/sportdb/standings.rb +0 -178
  73. data/lib/sportdb/stats.rb +0 -27
  74. data/lib/sportdb/utils.rb +0 -89
  75. data/lib/sportdb/utils_date.rb +0 -26
  76. data/lib/sportdb/utils_goals.rb +0 -20
  77. data/lib/sportdb/utils_group.rb +0 -63
  78. data/lib/sportdb/utils_map.rb +0 -44
  79. data/lib/sportdb/utils_round.rb +0 -165
  80. data/lib/sportdb/utils_scores.rb +0 -17
  81. data/lib/sportdb/utils_teams.rb +0 -43
  82. data/lib/sportdb/version.rb +0 -22
  83. data/test/data/at-austria/2013_14/bl.txt +0 -227
  84. data/test/data/at-austria/2013_14/bl.yml +0 -30
  85. data/test/data/at-austria/2013_14/bl_ii.txt +0 -154
  86. data/test/data/at-austria/2013_14/el.txt +0 -4
  87. data/test/data/at-austria/2013_14/el.yml +0 -25
  88. data/test/data/at-austria/2013_14/squads/austria.txt +0 -40
  89. data/test/data/at-austria/2013_14/squads/salzburg.txt +0 -35
  90. data/test/data/at-austria/leagues.txt +0 -11
  91. data/test/data/at-austria/teams.txt +0 -75
  92. data/test/data/at-austria/teams_2.txt +0 -34
  93. data/test/data/national-teams/assocs.txt +0 -231
  94. data/test/data/national-teams/europe/assocs.txt +0 -13
  95. data/test/data/national-teams/europe/teams.txt +0 -13
  96. data/test/data/national-teams/north-america/assocs.txt +0 -10
  97. data/test/data/national-teams/north-america/teams.txt +0 -7
  98. data/test/data/national-teams/teams.txt +0 -19
  99. data/test/data/players/europe/at-austria/players.txt +0 -45
  100. data/test/data/players/europe/de-deutschland/players.txt +0 -41
  101. data/test/data/players/south-america/br-brazil/players.txt +0 -51
  102. data/test/data/world-cup/1930/cup.txt +0 -71
  103. data/test/data/world-cup/1930/cup.yml +0 -23
  104. data/test/data/world-cup/1930/cup_goals.txt +0 -47
  105. data/test/data/world-cup/1930/cup_goals.yml +0 -23
  106. data/test/data/world-cup/1954/cup.txt +0 -90
  107. data/test/data/world-cup/1954/cup.yml +0 -30
  108. data/test/data/world-cup/1962/cup.txt +0 -86
  109. data/test/data/world-cup/1962/cup.yml +0 -32
  110. data/test/data/world-cup/1974/cup.yml +0 -35
  111. data/test/data/world-cup/1974/cup_finals.txt +0 -14
  112. data/test/data/world-cup/1974/cup_i.txt +0 -55
  113. data/test/data/world-cup/1974/cup_ii.txt +0 -34
  114. data/test/data/world-cup/2014/cup.txt +0 -5
  115. data/test/data/world-cup/2014/cup.yml +0 -54
  116. data/test/data/world-cup/2014/squads/br-brazil.txt +0 -46
  117. data/test/data/world-cup/2014/squads/de-deutschland.txt +0 -8
  118. data/test/data/world-cup/2014/squads/jp-japan.txt +0 -30
  119. data/test/data/world-cup/2014/squads/uy-uruguay.txt +0 -32
  120. data/test/data/world-cup/leagues.txt +0 -5
  121. data/test/data/world-cup/seasons_1930.txt +0 -4
  122. data/test/data/world-cup/seasons_1954.txt +0 -4
  123. data/test/data/world-cup/seasons_1962.txt +0 -4
  124. data/test/data/world-cup/seasons_1974.txt +0 -5
  125. data/test/data/world-cup/teams_1930.txt +0 -26
  126. data/test/data/world-cup/teams_1954.txt +0 -30
  127. data/test/data/world-cup/teams_1962.txt +0 -29
  128. data/test/data/world-cup/teams_1974.txt +0 -29
  129. data/test/helper.rb +0 -120
  130. data/test/test_assoc_reader.rb +0 -201
  131. data/test/test_changes.rb +0 -74
  132. data/test/test_cursor.rb +0 -50
  133. data/test/test_date.rb +0 -100
  134. data/test/test_goals.rb +0 -109
  135. data/test/test_lang.rb +0 -130
  136. data/test/test_load.rb +0 -61
  137. data/test/test_reader.rb +0 -88
  138. data/test/test_reader_from_string.rb +0 -65
  139. data/test/test_round_auto.rb +0 -370
  140. data/test/test_round_def.rb +0 -109
  141. data/test/test_round_header.rb +0 -183
  142. data/test/test_scores.rb +0 -70
  143. data/test/test_squad_club_reader.rb +0 -76
  144. data/test/test_squad_national_team_reader.rb +0 -116
  145. data/test/test_standings.rb +0 -279
  146. data/test/test_standings_ii.rb +0 -46
  147. data/test/test_utils.rb +0 -124
  148. data/test/test_winner.rb +0 -95
@@ -1,200 +0,0 @@
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
- attr_reader :event # returns event record; call read first
15
- attr_reader :fixtures # fixtures/sources entry from event config
16
-
17
- def self.from_zip( zip_file, entry_path, more_attribs={} )
18
- ## get text content from zip
19
- entry = zip_file.find_entry( entry_path )
20
-
21
- text = entry.get_input_stream().read()
22
- text = text.force_encoding( Encoding::UTF_8 )
23
-
24
- config = File.basename( entry_path ) # name a of .yml file
25
-
26
- self.from_string( text, config, more_attribs )
27
- end
28
-
29
- def self.from_file( path, more_attribs={} )
30
- ## note: assume/enfore utf-8 encoding (with or without BOM - byte order mark)
31
- ## - see textutils/utils.rb
32
- text = File.read_utf8( path )
33
-
34
- config = File.basename( name ) # name a of .yml file
35
-
36
- self.from_string( text, config, more_attribs )
37
- end
38
-
39
- def self.from_string( text, config, more_attribs={} )
40
- EventReader.new( text, config, more_attribs )
41
- end
42
-
43
- def initialize( text, config, more_attribs={} )
44
- ## todo/fix: how to add opts={} ???
45
- @text = text
46
- @more_attribs = more_attribs
47
-
48
- @config = config # name of event configuration (relative basename w/o path or string)
49
- @sources_default = config # note: use same a config for now
50
-
51
- @event = nil
52
- @fixtures = []
53
- end
54
-
55
-
56
- def read()
57
- @fixtures = [] # reset cached fixtures
58
- @event = nil # reset cached event rec
59
-
60
- ####
61
- ## fix!!!!!
62
- ## use Event.create_or_update_from_hash or similar
63
- ## use Event.create_or_update_from_hash_reader?? or similar
64
- # move parsing code to model
65
-
66
- reader = HashReader.from_string( @text )
67
-
68
- event_attribs = {}
69
-
70
- ## set default sources to basename by convention
71
- # e.g 2013_14/bl => bl
72
- # etc.
73
- # use fixtures/sources: to override default
74
-
75
- event_attribs[ 'sources' ] = @sources_default
76
- event_attribs[ 'config' ] = @config # name a of .yml file
77
-
78
- reader.each_typed do |key, value|
79
-
80
- ## puts "processing event attrib >>#{key}<< >>#{value}<<..."
81
-
82
- if key == 'league'
83
- league = League.find_by_key( value.to_s.strip )
84
-
85
- ## check if it exists
86
- if league.present?
87
- event_attribs['league_id'] = league.id
88
- else
89
- logger.error "league with key >>#{value.to_s.strip}<< missing"
90
- exit 1
91
- end
92
-
93
- elsif key == 'season'
94
- season = Season.find_by_key( value.to_s.strip )
95
-
96
- ## check if it exists
97
- if season.present?
98
- event_attribs['season_id'] = season.id
99
- else
100
- logger.error "season with key >>#{value.to_s.strip}<< missing"
101
- exit 1
102
- end
103
-
104
- elsif key == 'start_at' || key == 'begin_at'
105
-
106
- if value.is_a?(DateTime) || value.is_a?(Date)
107
- start_at = value
108
- else # assume it's a string
109
- start_at = DateTime.strptime( value.to_s.strip, '%Y-%m-%d' )
110
- end
111
-
112
- event_attribs['start_at'] = start_at
113
-
114
- elsif key == 'end_at' || key == 'stop_at'
115
-
116
- if value.is_a?(DateTime) || value.is_a?(Date)
117
- end_at = value
118
- else # assume it's a string
119
- end_at = DateTime.strptime( value.to_s.strip, '%Y-%m-%d' )
120
- end
121
-
122
- event_attribs['end_at'] = end_at
123
-
124
- elsif key == 'grounds' || key == 'stadiums' || key == 'venues'
125
- ## assume grounds value is an array
126
-
127
- ##
128
- ## note: for now we allow invalid ground keys
129
- ## will skip keys not found
130
-
131
- ground_ids = []
132
- value.each do |item|
133
- ground_key = item.to_s.strip
134
- ground = Ground.find_by_key( ground_key )
135
- if ground.nil?
136
- puts "[warn] ground/stadium w/ key >#{ground_key}< not found; skipping ground"
137
- else
138
- ground_ids << ground.id
139
- end
140
- end
141
-
142
- event_attribs['ground_ids'] = ground_ids
143
- elsif key == 'teams'
144
- ## assume teams value is an array
145
-
146
- team_ids = []
147
- value.each do |item|
148
- team_key = item.to_s.strip
149
- team = Team.find_by_key!( team_key )
150
- team_ids << team.id
151
- end
152
-
153
- event_attribs['team_ids'] = team_ids
154
-
155
- elsif key == 'team3'
156
- ## for now always assume false # todo: fix - use value and convert to boolean if not boolean
157
- event_attribs['team3'] = false
158
-
159
- elsif key == 'fixtures' || key == 'sources'
160
- ### todo: check for mulitiple fixtures/sources ?? allow disallow?? why? why not?
161
- if value.kind_of?(Array)
162
- event_attribs['sources'] = value.join(',')
163
- @fixtures += value
164
- else # assume plain (single fixture) string
165
- event_attribs['sources'] = value.to_s
166
- @fixtures << value.to_s
167
- end
168
- else
169
- ## todo: add a source location struct to_s or similar (file, line, col)
170
- logger.error "unknown event attrib #{key}; skipping attrib"
171
- end
172
-
173
- end # each key,value
174
-
175
- league_id = event_attribs['league_id']
176
- season_id = event_attribs['season_id']
177
-
178
- logger.debug "find event - league_id: #{league_id}, season_id: #{season_id}"
179
-
180
- event = Event.find_by_league_id_and_season_id( league_id, season_id )
181
-
182
- ## check if it exists
183
- if event.present?
184
- logger.debug "*** update event #{event.id}-#{event.key}:"
185
- else
186
- logger.debug "*** create event:"
187
- event = Event.new
188
- end
189
-
190
- logger.debug event_attribs.to_json
191
-
192
- event.update_attributes!( event_attribs )
193
-
194
- # keep a cached reference for later use
195
- @event = event
196
- end # method read
197
-
198
-
199
- end # class EventReader
200
- end # module SportDb
@@ -1,877 +0,0 @@
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
- def self.from_zip( zip_file, entry_path, more_attribs={} )
20
-
21
- logger = LogKernel::Logger.root
22
-
23
- reader = EventReader.from_zip( zip_file, entry_path )
24
- reader.read()
25
-
26
- event = reader.event ## was fetch_event( name )
27
- fixtures = reader.fixtures ## was fetch_event_fixtures( name )
28
-
29
- if fixtures.empty?
30
- ## logger.warn "no fixtures found for event - >#{name}<; assume fixture name is the same as event"
31
- ## change extension from .yml to .txt
32
- fixtures_with_path = [ entry_path.sub('.yml','.txt') ]
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( entry_path ) # use dir for fixtures
39
-
40
- fixtures_with_path = fixtures.map do |fx|
41
- fx_new = "#{dir}/#{fx}.txt" # add path upfront
42
- logger.debug "fx: #{fx_new} | >#{fx}< + >#{dir}<"
43
- fx_new
44
- end
45
- end
46
-
47
- ## fix-fix-fix: change file extension to ??
48
- text_ary = []
49
- fixtures_with_path.each do |fixture_path|
50
- entry = zip_file.find_entry( fixture_path )
51
-
52
- text = entry.get_input_stream().read()
53
- text = text.force_encoding( Encoding::UTF_8 )
54
-
55
- text_ary << text
56
- end
57
-
58
- self.from_string( event, text_ary, more_attribs )
59
- end
60
-
61
- def self.from_file( path, more_attribs={} )
62
-
63
- logger = LogKernel::Logger.root
64
-
65
- ### NOTE: fix-fix-fix - pass in event path!!!!!!! (not fixture path!!!!)
66
-
67
- ## - ## note: assume/enfore utf-8 encoding (with or without BOM - byte order mark)
68
- ## - ## - see textutils/utils.rb
69
- ## - text = File.read_utf8( path )
70
-
71
- reader = EventReader.from_file( path )
72
- reader.read()
73
-
74
- event = reader.event ## was fetch_event( name )
75
- fixtures = reader.fixtures ## was fetch_event_fixtures( name )
76
-
77
-
78
- if fixtures.empty?
79
- ## logger.warn "no fixtures found for event - >#{name}<; assume fixture name is the same as event"
80
- ## change extension from .yml to .txt
81
- fixtures_with_path = [ path.sub('.yml','.txt') ]
82
- else
83
- ## add path to fixtures (use path from event e.g)
84
- # - bl + at-austria!/2012_13/bl -> at-austria!/2012_13/bl
85
- # - bl_ii + at-austria!/2012_13/bl -> at-austria!/2012_13/bl_ii
86
-
87
- dir = File.dirname( path ) # use dir for fixtures
88
-
89
- fixtures_with_path = fixtures.map do |fx|
90
- fx_new = "#{dir}/#{fx}.txt" # add path upfront
91
- logger.debug "fx: #{fx_new} | >#{fx}< + >#{dir}<"
92
- fx_new
93
- end
94
- end
95
-
96
- ## fix-fix-fix: change file extension to ??
97
- text_ary = []
98
- fixtures_with_path.each do |fixture_path|
99
- text_ary << File.read_utf8( fixture_path )
100
- end
101
-
102
- self.from_string( event, text_ary, more_attribs )
103
- end
104
-
105
-
106
- def self.from_string( event, text_or_text_ary, more_attribs={} )
107
- ### fix - fix -fix:
108
- ## change event to event_or_event_key !!!!! - allow event_key as string passed in
109
- GameReader.new( event, text_or_text_ary, more_attribs )
110
- end
111
-
112
-
113
- def initialize( event, text_or_text_ary, more_attribs={} )
114
- ### fix - fix -fix:
115
- ## change event to event_or_event_key !!!!! - allow event_key as string passed in
116
-
117
- ## todo/fix: how to add opts={} ???
118
- @event = event
119
- @text_or_text_ary = text_or_text_ary
120
- @more_attribs = more_attribs
121
- end
122
-
123
-
124
- def read()
125
- if @text_or_text_ary.is_a?( String )
126
- text_ary = [@text_or_text_ary]
127
- else
128
- text_ary = @text_or_text_ary
129
- end
130
-
131
- ## reset cached values
132
- ## for auto-number rounds etc.
133
- @last_round_pos = nil
134
-
135
- text_ary.each do |text|
136
- ## assume en for now? why? why not?
137
- ## fix (cache) store lang in event table (e.g. auto-add and auto-update)!!!
138
- SportDb.lang.lang = SportDb.lang.classify( text )
139
-
140
- reader = LineReader.from_string( text )
141
-
142
- read_fixtures_worker( @event.key, reader )
143
- end
144
-
145
- ## fix add prop ??
146
- ### Prop.create!( key: "db.#{fixture_name_to_prop_key(name)}.version", value: "file.txt.#{File.mtime(path).strftime('%Y.%m.%d')}" )
147
- end
148
-
149
-
150
- def read_fixtures_worker( event_key, reader )
151
- ## NB: assume active activerecord connection
152
-
153
- ## reset cached values
154
- @patch_round_ids_dates = []
155
- @patch_round_ids_pos = []
156
-
157
- @round = nil ## fix: change/rename to @last_round !!!
158
- @group = nil ## fix: change/rename to @last_group !!!
159
- @last_date = nil
160
-
161
- @last_team1 = nil # used for goals (to match players via squads)
162
- @last_team2 = nil
163
- @last_game = nil
164
-
165
-
166
- #####
167
- # fix: move to read and share event/known_teams
168
- # for all 1-n fixture files (no need to configure every time!!)
169
-
170
- @event = Event.find_by_key!( event_key )
171
-
172
- logger.debug "Event #{@event.key} >#{@event.title}<"
173
-
174
- ### fix: use build_title_table_for ??? why? why not??
175
- @known_teams = @event.known_teams_table
176
-
177
- @known_grounds = TextUtils.build_title_table_for( @event.grounds )
178
-
179
-
180
- parse_fixtures( reader )
181
-
182
- end # method load_fixtures
183
-
184
-
185
-
186
- def parse_group_header( line )
187
- logger.debug "parsing group header line: >#{line}<"
188
-
189
- # note: group header resets (last) round (allows, for example):
190
- # e.g.
191
- # Group Playoffs/Replays -- round header
192
- # team1 team2 -- match
193
- # Group B: -- group header
194
- # team1 team2 - match (will get new auto-matchday! not last round)
195
- @round = nil ## fix: change/rename to @last_round !!!
196
-
197
- title, pos = find_group_title_and_pos!( line )
198
-
199
- logger.debug " title: >#{title}<"
200
- logger.debug " pos: >#{pos}<"
201
- logger.debug " line: >#{line}<"
202
-
203
- # set group for games
204
- @group = Group.find_by_event_id_and_pos!( @event.id, pos )
205
- end
206
-
207
-
208
- def parse_group_def( line )
209
- logger.debug "parsing group def line: >#{line}<"
210
-
211
- match_teams!( line )
212
- team_keys = find_teams!( line )
213
-
214
- title, pos = find_group_title_and_pos!( line )
215
-
216
- logger.debug " line: >#{line}<"
217
-
218
- group_attribs = {
219
- title: title
220
- }
221
-
222
- group = Group.find_by_event_id_and_pos( @event.id, pos )
223
- if group.present?
224
- logger.debug "update group #{group.id}:"
225
- else
226
- logger.debug "create group:"
227
- group = Group.new
228
- group_attribs = group_attribs.merge( {
229
- event_id: @event.id,
230
- pos: pos
231
- })
232
- end
233
-
234
- logger.debug group_attribs.to_json
235
-
236
- group.update_attributes!( group_attribs )
237
-
238
- group.teams.clear # remove old teams
239
- ## add new teams
240
- team_keys.each do |team_key|
241
- team = Team.find_by_key!( team_key )
242
- logger.debug " adding team #{team.title} (#{team.code})"
243
- group.teams << team
244
- end
245
- end
246
-
247
-
248
- def parse_round_def( line )
249
- logger.debug "parsing round def line: >#{line}<"
250
-
251
- ### todo/fix/check: move cut off optional comment in reader for all lines? why? why not?
252
- cut_off_end_of_line_comment!( line ) # cut off optional comment starting w/ #
253
-
254
- start_at = find_date!( line, start_at: @event.start_at )
255
- end_at = find_date!( line, start_at: @event.start_at )
256
-
257
- # note: if end_at missing -- assume start_at is (==) end_at
258
- end_at = start_at if end_at.nil?
259
-
260
- # note: - NOT needed; start_at and end_at are saved as date only (NOT datetime)
261
- # set hours,minutes,secs to beginning and end of day (do NOT use default 12.00)
262
- # e.g. use 00.00 and 23.59
263
- # start_at = start_at.beginning_of_day
264
- # end_at = end_at.end_of_day
265
-
266
- # note: make sure start_at/end_at is date only (e.g. use start_at.to_date)
267
- # sqlite3 saves datetime in date field as datetime, for example (will break date compares later!)
268
- start_at = start_at.to_date
269
- end_at = end_at.to_date
270
-
271
-
272
- pos = find_round_pos!( line )
273
- title = find_round_def_title!( line )
274
- # NB: use extracted round title for knockout check
275
- knockout_flag = is_knockout_round?( title )
276
-
277
-
278
- logger.debug " start_at: #{start_at}"
279
- logger.debug " end_at: #{end_at}"
280
- logger.debug " pos: #{pos}"
281
- logger.debug " title: >#{title}<"
282
- logger.debug " knockout_flag: #{knockout_flag}"
283
-
284
- logger.debug " line: >#{line}<"
285
-
286
- #######################################
287
- # fix: add auto flag is false !!!!
288
-
289
- round_attribs = {
290
- title: title,
291
- knockout: knockout_flag,
292
- start_at: start_at,
293
- end_at: end_at
294
- }
295
-
296
- round = Round.find_by_event_id_and_pos( @event.id, pos )
297
- if round.present?
298
- logger.debug "update round #{round.id}:"
299
- else
300
- logger.debug "create round:"
301
- round = Round.new
302
-
303
- round_attribs = round_attribs.merge( {
304
- event_id: @event.id,
305
- pos: pos
306
- })
307
- end
308
-
309
- logger.debug round_attribs.to_json
310
-
311
- round.update_attributes!( round_attribs )
312
- end
313
-
314
-
315
- def parse_round_header( line )
316
- logger.debug "parsing round header line: >#{line}<"
317
-
318
- ### todo/fix/check: move cut off optional comment in reader for all lines? why? why not?
319
- cut_off_end_of_line_comment!( line ) # cut off optional comment starting w/ #
320
-
321
- # NB: cut off optional title2 starting w/ // first
322
- title2 = find_round_header_title2!( line )
323
-
324
- # todo/fix: check if it is possible title2 w/ group?
325
- # add an example here
326
- group_title, group_pos = find_group_title_and_pos!( line )
327
-
328
- ## todo/check/fix:
329
- # make sure Round of 16 will not return pos 16 -- how? possible?
330
- # add unit test too to verify
331
- pos = find_round_pos!( line )
332
-
333
- ## check if pos available; if not auto-number/calculate
334
- if pos.nil?
335
- if @patch_round_ids_pos.empty?
336
- pos = (@last_round_pos||0)+1
337
- logger.debug( " no round pos found; auto-number round - use (#{pos})" )
338
- else
339
- # note: if any rounds w/o pos already seen (add for auto-numbering at the end)
340
- # will get auto-numbered sorted by start_at date
341
- pos = 999001+@patch_round_ids_pos.length # e.g. 999<count> - 999001,999002,etc.
342
- logger.debug( " no round pos found; auto-number round w/ patch (backtrack) at the end" )
343
- end
344
- end
345
-
346
- # store pos for auto-number next round if missing
347
- # - note: only if greater/bigger than last; use max
348
- # - note: last_round_pos might be nil - thus set to 0
349
- if pos > 999000
350
- # note: do NOT update last_round_pos for to-be-patched rounds
351
- else
352
- @last_round_pos = [pos,@last_round_pos||0].max
353
- end
354
-
355
-
356
- title = find_round_header_title!( line )
357
-
358
- ## NB: use extracted round title for knockout check
359
- knockout_flag = is_knockout_round?( title )
360
-
361
-
362
- if group_pos.present?
363
- @group = Group.find_by_event_id_and_pos!( @event.id, group_pos )
364
- else
365
- @group = nil # reset group to no group
366
- end
367
-
368
- logger.debug " line: >#{line}<"
369
-
370
- ## NB: dummy/placeholder start_at, end_at date
371
- ## replace/patch after adding all games for round
372
-
373
- round_attribs = {
374
- title: title,
375
- title2: title2,
376
- knockout: knockout_flag
377
- }
378
-
379
- if pos > 999000
380
- # no pos (e.g. will get autonumbered later) - try match by title for now
381
- # e.g. lets us use title 'Group Replays', for example, multiple times
382
- @round = Round.find_by_event_id_and_title( @event.id, title )
383
- else
384
- @round = Round.find_by_event_id_and_pos( @event.id, pos )
385
- end
386
-
387
- if @round.present?
388
- logger.debug "update round #{@round.id}:"
389
- else
390
- logger.debug "create round:"
391
- @round = Round.new
392
-
393
- round_attribs = round_attribs.merge( {
394
- event_id: @event.id,
395
- pos: pos,
396
- start_at: Date.parse('1911-11-11'),
397
- end_at: Date.parse('1911-11-11')
398
- })
399
- end
400
-
401
- logger.debug round_attribs.to_json
402
-
403
- @round.update_attributes!( round_attribs )
404
-
405
- @patch_round_ids_pos << @round.id if pos > 999000
406
- ### store list of round ids for patching start_at/end_at at the end
407
- @patch_round_ids_dates << @round.id # todo/fix/check: check if round has definition (do NOT patch if definition (not auto-added) present)
408
- end
409
-
410
-
411
- def try_parse_game( line )
412
- # note: clone line; for possible test do NOT modify in place for now
413
- # note: returns true if parsed, false if no match
414
- parse_game( line.dup )
415
- end
416
-
417
- def parse_game( line )
418
- logger.debug "parsing game (fixture) line: >#{line}<"
419
-
420
- match_teams!( line )
421
- team1_key = find_team1!( line )
422
- team2_key = find_team2!( line )
423
-
424
- ## note: if we do NOT find two teams; return false - no match found
425
- if team1_key.nil? || team2_key.nil?
426
- logger.debug " no game match (two teams required) found for line: >#{line}<"
427
- return false
428
- end
429
-
430
- pos = find_game_pos!( line )
431
-
432
- if is_postponed?( line )
433
- postponed = true
434
- date_v2 = find_date!( line, start_at: @event.start_at )
435
- date = find_date!( line, start_at: @event.start_at )
436
- else
437
- postponed = false
438
- date_v2 = nil
439
- date = find_date!( line, start_at: @event.start_at )
440
- end
441
-
442
- ###
443
- # check if date found?
444
- # NB: ruby falsey is nil & false only (not 0 or empty array etc.)
445
- if date
446
- ### check: use date_v2 if present? why? why not?
447
- @last_date = date # keep a reference for later use
448
- else
449
- date = @last_date # no date found; (re)use last seen date
450
- end
451
-
452
-
453
- scores = find_scores!( line )
454
-
455
-
456
- ####
457
- # note:
458
- # only map ground if we got any grounds (setup/configured in event)
459
-
460
- if @event.grounds.count > 0
461
-
462
- ## todo/check: use @known_grounds for check?? why? why not??
463
- ## use in @known_grounds = TextUtils.build_title_table_for( @event.grounds )
464
-
465
- ##
466
- # fix: mark mapped title w/ type (ground-) or such!! - too avoid fallthrough match
467
- # e.g. three teams match - but only two get mapped, third team gets match for ground
468
- # e.g Somalia v Djibouti @ Djibouti
469
- map_ground!( line )
470
- ground_key = find_ground!( line )
471
- ground = ground_key.nil? ? nil : Ground.find_by_key!( ground_key )
472
- else
473
- # no grounds configured; always nil
474
- ground = nil
475
- end
476
-
477
- logger.debug " line: >#{line}<"
478
-
479
-
480
- ### todo: cache team lookups in hash?
481
-
482
- team1 = Team.find_by_key!( team1_key )
483
- team2 = Team.find_by_key!( team2_key )
484
-
485
- @last_team1 = team1 # store for later use for goals etc.
486
- @last_team2 = team2
487
-
488
-
489
- if @round.nil?
490
- ## no round header found; calculate round from date
491
-
492
- ###
493
- ## todo/fix: add some unit tests for round look up
494
- # fix: use date_v2 if present!! (old/original date; otherwise use date)
495
-
496
- #
497
- # fix: check - what to do with hours e.g. start_at use 00:00 and for end_at use 23.59 ??
498
- # -- for now - remove hours (e.g. use end_of_day and beginnig_of_day)
499
-
500
- ##
501
- # note: start_at and end_at are dates ONLY (note datetime)
502
- # - do NOT pass in hours etc. in query
503
- # again use --> date.end_of_day, date.beginning_of_day
504
- # new: not working: date.to_date, date.to_date
505
- # will not find round if start_at same as date !! (in theory hours do not matter)
506
-
507
- ###
508
- # hack:
509
- # special case for sqlite3 (date compare not working reliable; use casts)
510
- # fix: move to adapter_name to activerecord_utils as sqlite? or similar?
511
-
512
- if ActiveRecord::Base.connection.adapter_name.downcase.starts_with?( 'sqlite' )
513
- logger.debug( " [sqlite] using sqlite-specific query for date compare for rounds finder" )
514
- round = Round.where( 'event_id = ? AND ( julianday(start_at) <= julianday(?)'+
515
- 'AND julianday(end_at) >= julianday(?))',
516
- @event.id, date.to_date, date.to_date).first
517
- else # all other dbs (postgresql, mysql, etc.)
518
- round = Round.where( 'event_id = ? AND (start_at <= ? AND end_at >= ?)',
519
- @event.id, date.to_date, date.to_date).first
520
- end
521
-
522
- pp round
523
- if round.nil?
524
- logger.warn( " !!!! no round match found for date #{date}" )
525
- pp Round.all
526
-
527
- ###################################
528
- # -- try auto-adding matchday
529
- round = Round.new
530
-
531
- round_attribs = {
532
- event_id: @event.id,
533
- title: "Matchday #{date.to_date}",
534
- pos: 999001+@patch_round_ids_pos.length, # e.g. 999<count> - 999001,999002,etc.
535
- start_at: date.to_date,
536
- end_at: date.to_date
537
- }
538
-
539
- logger.info( " auto-add round >Matchday #{date.to_date}<" )
540
- logger.debug round_attribs.to_json
541
-
542
- round.update_attributes!( round_attribs )
543
-
544
- @patch_round_ids_pos << round.id # todo/check - add just id or "full" record as now - why? why not?
545
- end
546
-
547
- # store pos for auto-number next round if missing
548
- # - note: only if greater/bigger than last; use max
549
- # - note: last_round_pos might be nil - thus set to 0
550
- if round.pos > 999000
551
- # note: do NOT update last_round_pos for to-be-patched rounds
552
- else
553
- @last_round_pos = [round.pos,@last_round_pos||0].max
554
- end
555
-
556
- ## note: will crash (round.pos) if round is nil
557
- logger.debug( " using round #{round.pos} >#{round.title}< start_at: #{round.start_at}, end_at: #{round.end_at}" )
558
- else
559
- ## use round from last round header
560
- round = @round
561
- end
562
-
563
-
564
- ### check if games exists
565
- ## with this teams in this round if yes only update
566
- game = Game.find_by_round_id_and_team1_id_and_team2_id(
567
- round.id, team1.id, team2.id
568
- )
569
-
570
- game_attribs = {
571
- score1: scores[0],
572
- score2: scores[1],
573
- score1et: scores[2],
574
- score2et: scores[3],
575
- score1p: scores[4],
576
- score2p: scores[5],
577
- play_at: date,
578
- play_at_v2: date_v2,
579
- postponed: postponed,
580
- knockout: round.knockout, ## note: for now always use knockout flag from round - why? why not??
581
- ground_id: ground.present? ? ground.id : nil,
582
- group_id: @group.present? ? @group.id : nil
583
- }
584
-
585
- game_attribs[ :pos ] = pos if pos.present?
586
-
587
- ####
588
- # note: only update if any changes (or create if new record)
589
- if game.present? &&
590
- game.check_for_changes( game_attribs ) == false
591
- logger.debug " skip update game #{game.id}; no changes found"
592
- else
593
- if game.present?
594
- logger.debug "update game #{game.id}:"
595
- else
596
- logger.debug "create game:"
597
- game = Game.new
598
-
599
- more_game_attribs = {
600
- round_id: round.id,
601
- team1_id: team1.id,
602
- team2_id: team2.id
603
- }
604
-
605
- ## NB: use round.games.count for pos
606
- ## lets us add games out of order if later needed
607
- more_game_attribs[ :pos ] = round.games.count+1 if pos.nil?
608
-
609
- game_attribs = game_attribs.merge( more_game_attribs )
610
- end
611
-
612
- logger.debug game_attribs.to_json
613
- game.update_attributes!( game_attribs )
614
- end
615
-
616
- @last_game = game # store for later reference (e.g. used for goals etc.)
617
-
618
- return true # game match found
619
- end # method parse_game
620
-
621
-
622
- def try_parse_date_header( line )
623
- # note: clone line; for possible test do NOT modify in place for now
624
- # note: returns true if parsed, false if no match
625
- parse_date_header( line.dup )
626
- end
627
-
628
- def parse_date_header( line )
629
- # note: returns true if parsed, false if no match
630
-
631
- # line with NO teams plus include date e.g.
632
- # [Fri Jun/17] or
633
- # Jun/17 or
634
- # Jun/17: etc.
635
-
636
-
637
- match_teams!( line )
638
- team1_key = find_team1!( line )
639
- team2_key = find_team2!( line )
640
-
641
- date = find_date!( line, start_at: @event.start_at )
642
-
643
- if date && team1_key.nil? && team2_key.nil?
644
- logger.debug( "date header line found: >#{line}<")
645
- logger.debug( " date: #{date}")
646
-
647
- @last_date = date # keep a reference for later use
648
- return true
649
- else
650
- return false
651
- end
652
- end
653
-
654
-
655
- def parse_goals( line )
656
- logger.debug "parsing goals (fixture) line: >#{line}<"
657
-
658
- goals = GoalsFinder.new.find!( line )
659
-
660
- ## check if squads/rosters present for player mappings
661
- #
662
- squad1_count = Roster.where( event_id: @event.id, team_id: @last_team1 ).count
663
- if squad1_count > 0
664
- squad1 = Roster.where( event_id: @event.id, team_id: @last_team1 )
665
- else
666
- squad1 = []
667
- end
668
-
669
- squad2_count = Roster.where( event_id: @event.id, team_id: @last_team2 ).count
670
- if squad2_count > 0
671
- squad2 = Roster.where( event_id: @event.id, team_id: @last_team2 )
672
- else
673
- squad2 = []
674
- end
675
-
676
- #####
677
- # todo/fix: try lookup by squads first!!!
678
- # issue warning if player not included in squad!!
679
-
680
- ##########
681
- # try mapping player names to player
682
-
683
- ## note: first delete all goals for match (and recreate new ones
684
- # no need to figure out update/merge strategy)
685
- @last_game.goals.delete_all
686
-
687
-
688
- goals.each do |goal|
689
- player_name = goal.name
690
-
691
- player = Person.where( name: player_name ).first
692
- if player
693
- logger.info " player match (name eq) - using player key #{player.key}"
694
- else
695
- # try like match (player name might only include part of name e.g. Messi)
696
- # try three variants
697
- # try %Messi
698
- # try Messi%
699
- # try %Messi% -- check if there's an easier way w/ "one" where clause?
700
- player = Person.where( 'name LIKE ? OR name LIKE ? OR name LIKE ?',
701
- "%#{player_name}",
702
- "#{player_name}%",
703
- "%#{player_name}%"
704
- ).first
705
-
706
- if player
707
- logger.info " player match (name like) - using player key #{player.key}"
708
- else
709
- # try synonyms
710
- player = Person.where( 'synonyms LIKE ? OR synonyms LIKE ? OR synonyms LIKE ?',
711
- "%#{player_name}",
712
- "#{player_name}%",
713
- "%#{player_name}%"
714
- ).first
715
- if player
716
- logger.info " player match (synonyms like) - using player key #{player.key}"
717
- else
718
- # auto-create player (player not found)
719
- logger.info " player NOT found >#{player_name}< - auto-create"
720
-
721
- ## fix: add auto flag (for auto-created persons/players)
722
- ## fix: move title_to_key logic to person model etc.
723
- player_key = TextUtils.title_to_key( player_name )
724
- player_attribs = {
725
- key: player_key,
726
- title: player_name
727
- }
728
- logger.info " using attribs: #{player_attribs.inspect}"
729
-
730
- player = Person.create!( player_attribs )
731
- end
732
- end
733
- end
734
-
735
- goal_attribs = {
736
- game_id: @last_game.id,
737
- team_id: goal.team == 1 ? @last_team1.id : @last_team2.id,
738
- person_id: player.id,
739
- minute: goal.minute,
740
- offset: goal.offset,
741
- penalty: goal.penalty,
742
- owngoal: goal.owngoal,
743
- score1: goal.score1,
744
- score2: goal.score2
745
- }
746
-
747
- logger.info " adding goal using attribs: #{goal_attribs.inspect}"
748
- Goal.create!( goal_attribs )
749
- end # each goals
750
-
751
- end # method parse_goals
752
-
753
-
754
- =begin
755
- ###### add to person and use!!!
756
- def self.create_or_update_from_values( values, more_attribs={} )
757
- ## key & title required
758
-
759
- attribs, more_values = find_key_n_title( values )
760
- attribs = attribs.merge( more_attribs )
761
-
762
- ## check for optional values
763
- Person.create_or_update_from_attribs( attribs, more_values )
764
- end
765
- =end
766
-
767
-
768
- def parse_fixtures( reader )
769
-
770
- reader.each_line do |line|
771
-
772
- if is_goals?( line )
773
- parse_goals( line )
774
- elsif is_round_def?( line )
775
- ## todo/fix: add round definition (w begin n end date)
776
- ## todo: do not patch rounds with definition (already assume begin/end date is good)
777
- ## -- how to deal with matches that get rescheduled/postponed?
778
- parse_round_def( line )
779
- elsif is_round?( line )
780
- parse_round_header( line )
781
- elsif is_group_def?( line ) ## NB: group goes after round (round may contain group marker too)
782
- ### todo: add pipe (|) marker (required)
783
- parse_group_def( line )
784
- elsif is_group?( line )
785
- ## -- lets you set group e.g. Group A etc.
786
- parse_group_header( line )
787
- elsif try_parse_game( line )
788
- # do nothing here
789
- elsif try_parse_date_header( line )
790
- # do nothing here
791
- else
792
- logger.info "skipping line (no match found): >#{line}<"
793
- end
794
- end # lines.each
795
-
796
- ###########################
797
- # backtrack and patch round pos and round dates (start_at/end_at)
798
- # note: patch dates must go first! (otherwise sort_by_date will not work for round pos)
799
-
800
- unless @patch_round_ids_dates.empty?
801
- ###
802
- # fix: do NOT patch if auto flag is set to false !!!
803
- # e.g. rounds got added w/ round def (not w/ round header)
804
-
805
- # note: use uniq - to allow multiple round headers (possible?)
806
-
807
- Round.find( @patch_round_ids_dates.uniq ).each do |r|
808
- logger.debug "patch round start_at/end_at date for #{r.title}:"
809
-
810
- ## note:
811
- ## will add "scope" pos first e.g
812
- #
813
- ## SELECT "games".* FROM "games" WHERE "games"."round_id" = ?
814
- # ORDER BY pos, play_at asc [["round_id", 7]]
815
- # thus will NOT order by play_at but by pos first!!!
816
- # =>
817
- # need to unscope pos!!! or use unordered_games - games_by_play_at_date etc.??
818
- # thus use reorder()!!! - not just order('play_at asc')
819
-
820
- games = r.games.reorder( 'play_at asc' ).all
821
-
822
- ## skip rounds w/ no games
823
-
824
- ## todo/check/fix: what's the best way for checking assoc w/ 0 recs?
825
- next if games.size == 0
826
-
827
- # note: make sure start_at/end_at is date only (e.g. use play_at.to_date)
828
- # sqlite3 saves datetime in date field as datetime, for example (will break date compares later!)
829
-
830
- round_attribs = {
831
- start_at: games[0].play_at.to_date, # use games.first ?
832
- end_at: games[-1].play_at.to_date # use games.last ? why? why not?
833
- }
834
-
835
- logger.debug round_attribs.to_json
836
- r.update_attributes!( round_attribs )
837
- end
838
- end
839
-
840
- unless @patch_round_ids_pos.empty?
841
-
842
- # step 0: check for offset (last_round_pos)
843
- if @last_round_pos
844
- offset = @last_round_pos
845
- logger.info " +++ patch round pos - use offset; start w/ #{offset}"
846
- else
847
- offset = 0
848
- logger.debug " patch round pos - no offset; start w/ 0"
849
- end
850
-
851
- # step 1: sort by date
852
- # step 2: update pos
853
- # note: use uniq - to allow multiple round headers (possible?)
854
- Round.order( 'start_at asc').find( @patch_round_ids_pos.uniq ).each_with_index do |r,idx|
855
- # note: starts counting w/ zero(0)
856
- logger.debug "[#{idx+1}] patch round pos >#{offset+idx+1}< for #{r.title}:"
857
- round_attribs = {
858
- pos: offset+idx+1
859
- }
860
-
861
- # update title if Matchday XXXX e.g. use Matchday 1 etc.
862
- if r.title.starts_with?('Matchday')
863
- round_attribs[:title] = "Matchday #{offset+idx+1}"
864
- end
865
-
866
- logger.debug round_attribs.to_json
867
- r.update_attributes!( round_attribs )
868
-
869
- # update last_round_pos offset too
870
- @last_round_pos = [offset+idx+1,@last_round_pos||0].max
871
- end
872
- end
873
-
874
- end # method parse_fixtures
875
-
876
- end # class GameReader
877
- end # module SportDb