sportdb 0.3.3 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
data/History.markdown CHANGED
@@ -1,3 +1,8 @@
1
+ ### 0.4.0 / 2012-10-16
2
+
3
+ * Add plain text fixture reader/loader
4
+ * Add --generate/g option for generating fixtures from the DB using templates
5
+
1
6
  ### 0.3.1 / 2012-10-14
2
7
 
3
8
  * Move models into its own namespace, that is, SportDB::Models
data/Manifest.txt CHANGED
@@ -40,6 +40,9 @@ lib/sportdb/models/group_team.rb
40
40
  lib/sportdb/models/prop.rb
41
41
  lib/sportdb/models/round.rb
42
42
  lib/sportdb/models/team.rb
43
+ lib/sportdb/reader.rb
43
44
  lib/sportdb/schema.rb
45
+ lib/sportdb/templater.rb
44
46
  lib/sportdb/utils.rb
45
47
  lib/sportdb/version.rb
48
+ templates/fixtures.rb.erb
data/lib/sportdb.rb CHANGED
@@ -11,6 +11,7 @@ require 'pp'
11
11
  require 'logger'
12
12
  require 'optparse'
13
13
  require 'fileutils'
14
+ require 'erb'
14
15
 
15
16
  # rubygems
16
17
 
@@ -31,6 +32,8 @@ require 'sportdb/models/team'
31
32
  require 'sportdb/schema' # NB: requires sportdb/models (include SportDB::Models)
32
33
  require 'sportdb/utils'
33
34
  require 'sportdb/loader'
35
+ require 'sportdb/reader'
36
+ require 'sportdb/templater'
34
37
  require 'sportdb/version'
35
38
  require 'sportdb/cli/opts'
36
39
  require 'sportdb/cli/runner'
@@ -10,7 +10,25 @@ class Opts
10
10
  return false if @create.nil? # default create flag is false
11
11
  @create == true
12
12
  end
13
+
14
+
15
+ def generate=(boolean)
16
+ @generate = boolean
17
+ end
13
18
 
19
+ def generate?
20
+ return false if @generate.nil? # default generate flag is false
21
+ @generate == true
22
+ end
23
+
24
+ def event=(value)
25
+ @event = value
26
+ end
27
+
28
+ def event
29
+ @event # NB: option has no default; return nil ## || '.'
30
+ end
31
+
14
32
 
15
33
  def delete=(boolean)
16
34
  @delete = boolean
@@ -23,6 +23,9 @@ class Runner
23
23
 
24
24
  cmd.banner = "Usage: sportdb [options]"
25
25
 
26
+ cmd.on( '-e', '--event KEY', 'Event to Load or Generate' ) { |key| opts.event = key; }
27
+ cmd.on( '-g', '--generate', 'Generate Fixtures from Template' ) { opts.generate = true }
28
+
26
29
  ## todo: change to different flag?? use -c/--config ???
27
30
  cmd.on( '-c', '--create', 'Create DB Schema' ) { opts.create = true }
28
31
 
@@ -98,19 +101,31 @@ EOS
98
101
  if opts.load?
99
102
  loader = Loader.new
100
103
  end
104
+
105
+ if opts.event.present?
106
+ if opts.generate?
107
+ Templater.new( opts ).run( args )
108
+ else
109
+ Reader.new( opts ).run( args )
110
+ end
111
+ else
101
112
 
102
- args.each do |arg|
103
- name = arg # File.basename( arg, '.*' )
113
+ args.each do |arg|
114
+ name = arg # File.basename( arg, '.*' )
104
115
 
105
- if opts.load?
106
- loader.load_fixtures( name ) # load from gem (built-in)
107
- else
108
- load_fixtures( name ) # load from file system
116
+ if opts.load?
117
+ loader.load_fixtures( name ) # load from gem (built-in)
118
+ else
119
+ load_fixtures( name ) # load from file system
120
+ end
109
121
  end
122
+
123
+ dump_stats
124
+ dump_props
125
+
110
126
  end
111
127
 
112
- dump_stats
113
- dump_props
128
+
114
129
 
115
130
  puts 'Done.'
116
131
 
@@ -15,9 +15,17 @@ class Team < ActiveRecord::Base
15
15
 
16
16
  ## key & title required
17
17
  attr = {
18
- :key => values[0],
19
- :title => values[1]
18
+ :key => values[0]
20
19
  }
20
+
21
+ ## title (split of optional synonyms)
22
+ # e.g. FC Bayern Muenchen|Bayern Muenchen|Bayern
23
+ titles = values[1].split('|')
24
+
25
+ attr[ :title ] = titles[0]
26
+ ## add optional synonyms
27
+ attr[ :synonyms ] = titles[1..-1].join('|') if titles.size > 1
28
+
21
29
 
22
30
  attr = attr.merge( more_values )
23
31
 
@@ -0,0 +1,316 @@
1
+ module SportDB
2
+
3
+ class Reader
4
+
5
+ ## make models available in sportdb module by default with namespace
6
+ # e.g. lets you use Team instead of Models::Team
7
+ include SportDB::Models
8
+
9
+
10
+ def initialize( opts )
11
+ @logger = Logger.new(STDOUT)
12
+ @logger.level = Logger::INFO
13
+
14
+ @opts = opts
15
+ end
16
+
17
+ attr_reader :logger, :opts
18
+
19
+ def run( args )
20
+
21
+ puts SportDB.banner
22
+
23
+ puts "working directory: #{Dir.pwd}"
24
+
25
+ ## assume active activerecord connection
26
+ ##
27
+
28
+ @event = Event.find_by_key!( opts.event )
29
+
30
+ puts "Event #{@event.key} >#{@event.title}<"
31
+
32
+
33
+ ## build known teams table w/ synonyms e.g.
34
+ #
35
+ # nb: synonyms can be a regex not just a literal string
36
+ # [[ 'wolfsbrug', [ 'VfL Wolfsburg' ]],
37
+ # [ 'augsburg', [ 'FC Augsburg', 'Augi2', 'Augi3' ]],
38
+ # [ 'stuttgart', [ 'VfB Stuttgart' ]] ]
39
+
40
+ @known_teams = []
41
+
42
+ @event.teams.each_with_index do |team,index|
43
+
44
+ titles = []
45
+ titles << team.title
46
+ titles += team.synonyms.split('|') if team.synonyms.present?
47
+
48
+ ## NB: sort here by length (largest goes first - best match)
49
+ # exclude tag and key (key should always go last)
50
+ titles = titles.sort { |left,right| right.length <=> left.length }
51
+
52
+ titles << team.tag if team.tag.present?
53
+ titles << team.key
54
+
55
+ @known_teams << [ team.key, titles ]
56
+
57
+ puts " Team[#{index+1}] #{team.key} >#{titles.join('|')}<"
58
+ end
59
+
60
+
61
+ args.each do |arg|
62
+ name = arg # File.basename( arg, '.*' )
63
+ parse_fixtures( name )
64
+ end
65
+
66
+
67
+ puts 'Done.'
68
+
69
+ end # method run
70
+
71
+
72
+ def is_round?( line )
73
+ line =~ /Spieltag|Runde/
74
+ end
75
+
76
+ def find_round_pos!( line )
77
+ regex = /\b(\d+)\b/
78
+
79
+ if line =~ regex
80
+ value = $1.to_i
81
+ puts " pos: >#{value}<"
82
+
83
+ line.sub!( regex, '[POS]' )
84
+
85
+ return value
86
+ else
87
+ return nil
88
+ end
89
+ end
90
+
91
+ def find_date!( line )
92
+ # extract date from line
93
+ # and return it
94
+ # NB: side effect - removes date from line string
95
+
96
+ # e.g. 14.09. 20:30
97
+ regex = /\b(\d{2})\.(\d{2})\.\s+(\d{2}):(\d{2})\b/
98
+
99
+ if line =~ regex
100
+ value = "2012-#{$2}-#{$1} #{$3}:#{$4}"
101
+ puts " date: >#{value}<"
102
+
103
+ ## todo: lets you configure year
104
+ ## and time zone (e.g. cet, eet, utc, etc.)
105
+
106
+ line.sub!( regex, '[DATE]' )
107
+
108
+ return DateTime.strptime( value, '%Y-%m-%d %H:%M' )
109
+ else
110
+ return nil
111
+ end
112
+ end
113
+
114
+ def find_score!( line )
115
+ # extract score from line
116
+ # and return it
117
+ # NB: side effect - removes date from line string
118
+
119
+ # e.g. 1:2 or 0:2 or 3:3
120
+ regex = /\b(\d):(\d)\b/
121
+
122
+ if line =~ regex
123
+ value = "#{$1}-#{$2}"
124
+ puts " score: >#{value}<"
125
+
126
+ line.sub!( regex, '[SCORE]' )
127
+
128
+ return [$1.to_i,$2.to_i]
129
+ else
130
+ return []
131
+ end
132
+ end
133
+
134
+
135
+ def find_team_worker!( line, index )
136
+ regex = /@@oo([^@]+?)oo@@/ # e.g. everything in @@ .... @@ (use non-greedy +? plus all chars but not @, that is [^@])
137
+
138
+ if line =~ regex
139
+ value = "#{$1}"
140
+ puts " team#{index}: >#{value}<"
141
+
142
+ line.sub!( regex, "[TEAM#{index}]" )
143
+
144
+ return $1
145
+ else
146
+ return nil
147
+ end
148
+ end
149
+
150
+ def find_team1!( line )
151
+ find_team_worker!( line, 1 )
152
+ end
153
+
154
+ def find_team2!( line )
155
+ find_team_worker!( line, 2 )
156
+ end
157
+
158
+
159
+ def match_team_worker!( line, key, values )
160
+ values.each do |value|
161
+ regex = Regexp.new( "\\b#{value}\\b" ) # wrap with world boundry (e.g. match only whole words e.g. not wac in wacker)
162
+ if line =~ regex
163
+ puts " match for team >#{key}< >#{value}<"
164
+ # make sure @@oo{key}oo@@ doesn't match itself with other key e.g. wacker, wac, etc.
165
+ line.sub!( regex, "@@oo#{key}oo@@" )
166
+ return true # break out after first match (do NOT continue)
167
+ end
168
+ end
169
+ return false
170
+ end
171
+
172
+ def match_teams!( line )
173
+ @known_teams.each do |rec|
174
+ key = rec[0]
175
+ values = rec[1]
176
+ match_team_worker!( line, key, values )
177
+ end # each known_teams
178
+ end # method translate_teams!
179
+
180
+
181
+ def parse_fixtures( name )
182
+
183
+ path = "#{opts.data_path}/#{name}.txt"
184
+
185
+ puts "*** parsing data '#{name}' (#{path})..."
186
+
187
+ old_lines = File.read( path )
188
+
189
+ old_lines.each_line do |line|
190
+
191
+ if line =~ /^\s*#/
192
+ # skip komments and do NOT copy to result (keep comments secret!)
193
+ logger.debug 'skipping comment line'
194
+ next
195
+ end
196
+
197
+ if line =~ /^\s*$/
198
+ # kommentar oder leerzeile überspringen
199
+ logger.debug 'skipping blank line'
200
+ next
201
+ end
202
+
203
+ # remove leading and trailing whitespace
204
+ line = line.strip
205
+
206
+ if is_round?( line )
207
+ puts "parsing round line: >#{line}<"
208
+ pos = find_round_pos!( line )
209
+
210
+ ## NB: dummy/placeholder start_at, end_at date
211
+ ## replace/patch after adding all games for round
212
+
213
+ round_attribs = {
214
+ title: "#{pos}. Runde"
215
+ }
216
+
217
+ @round = Round.find_by_event_id_and_pos( @event.id, pos )
218
+ if @round.present?
219
+ puts "*** update round #{@round.id}:"
220
+ else
221
+ puts "*** create round:"
222
+ @round = Round.new
223
+
224
+ round_attribs = round_attribs.merge( {
225
+ event_id: @event.id,
226
+ pos: pos,
227
+ start_at: Time.utc('1999-12-12'),
228
+ end_at: Time.utc('1999-12-12')
229
+ })
230
+ end
231
+
232
+ puts round_attribs.to_json
233
+
234
+ @round.update_attributes!( round_attribs )
235
+
236
+ ### store list of round is for patching start_at/end_at at the end
237
+ @patch_rounds ||= {}
238
+ @patch_rounds[ @round.id ] = @round.id
239
+
240
+ puts " line: >#{line}<"
241
+
242
+ else
243
+ puts "parsing game (fixture) line: >#{line}<"
244
+ date = find_date!( line )
245
+ score = find_score!( line )
246
+
247
+ match_teams!( line )
248
+ team1 = find_team1!( line )
249
+ team2 = find_team2!( line )
250
+
251
+
252
+ ### todo: cache team lookups in hash?
253
+
254
+ team1_id = Team.find_by_key!( team1 ).id
255
+ team2_id = Team.find_by_key!( team2 ).id
256
+
257
+ ### check if games exists
258
+ ## with this teams in this round if yes only update
259
+ @game = Game.find_by_round_id_and_team1_id_and_team2_id(
260
+ @round.id, team1_id, team2_id
261
+ )
262
+
263
+ game_attribs = {
264
+ score1: score[0],
265
+ score2: score[1],
266
+ play_at: date
267
+ }
268
+
269
+ if @game.present?
270
+ puts "*** update game #{@game.id}:"
271
+ else
272
+ puts "*** create game:"
273
+ @game = Game.new
274
+
275
+ more_game_attribs = {
276
+ ## NB: use round.games.count for pos
277
+ ## lets us add games out of order if later needed
278
+ pos: @round.games.count+1,
279
+ round_id: @round.id,
280
+ team1_id: team1_id,
281
+ team2_id: team2_id
282
+ }
283
+ game_attribs = game_attribs.merge( more_game_attribs )
284
+ end
285
+
286
+ puts game_attribs.to_json
287
+
288
+ @game.update_attributes!( game_attribs )
289
+
290
+ puts " line: >#{line}<"
291
+ end
292
+ end # oldlines.each
293
+
294
+ @patch_rounds ||= {}
295
+ @patch_rounds.each do |k,v|
296
+ puts "*** patch start_at/end_at date for round #{k}:"
297
+ round = Round.find( k )
298
+ games = round.games.order( 'play_at asc' ).all
299
+
300
+ round_attribs = {}
301
+
302
+ ## todo: check for no records
303
+ ## e.g. if game[0].present? or just if game[0] ??
304
+
305
+ round_attribs[:start_at] = games[0].play_at
306
+ round_attribs[:end_at ] = games[-1].play_at
307
+
308
+ puts round_attribs.to_json
309
+ round.update_attributes!( round_attribs )
310
+ end
311
+
312
+ end # method parse_fixtures
313
+
314
+
315
+ end # class Reader
316
+ end # module SportDB
@@ -31,6 +31,7 @@ create_table :teams do |t|
31
31
  t.string :title2
32
32
  t.string :key, :null => false # import/export key
33
33
  t.string :tag # make it not null? - three letter tag (short title)
34
+ t.string :synonyms # comma separated list of synonyms
34
35
  t.references :country, :null => false
35
36
  t.boolean :club, :null => false, :default => false # is it a club (not a national team)?
36
37
  t.boolean :national, :null => false, :default => false # is it a national selection team (not a club)?
@@ -57,6 +58,7 @@ create_table :rounds do |t|
57
58
  t.integer :pos, :null => false
58
59
  t.boolean :playoff, :null => false, :default => false # "regular" season (group) games or post-season (playoff) knockouts (k.o's)
59
60
  t.datetime :start_at, :null => false
61
+ t.datetime :end_at # todo: make it required e.g. :null => false
60
62
  t.timestamps
61
63
  end
62
64
 
@@ -0,0 +1,83 @@
1
+ module SportDB
2
+
3
+ class Template
4
+
5
+ def initialize( path )
6
+ @path = path
7
+ end
8
+
9
+ def render( binding )
10
+ ## '<>' means omit newline for lines starting with <% and ending in %>
11
+ ERB.new( load_template(), 0, '<>' ).result( binding )
12
+ end
13
+
14
+ private
15
+ def load_template
16
+ puts " Loading template >#{@path}<..."
17
+ File.read( @path )
18
+ end
19
+
20
+ end # class Template
21
+
22
+
23
+ class Templater
24
+
25
+ ## make models available in sportdb module by default with namespace
26
+ # e.g. lets you use Team instead of Models::Team
27
+ include SportDB::Models
28
+
29
+
30
+ def initialize( opts )
31
+ @logger = Logger.new(STDOUT)
32
+ @logger.level = Logger::INFO
33
+
34
+ @opts = opts
35
+ end
36
+
37
+ attr_reader :logger, :opts
38
+
39
+ # make props available for template
40
+ attr_reader :event
41
+
42
+ def run( args )
43
+
44
+ puts SportDB.banner
45
+
46
+ puts "working directory: #{Dir.pwd}"
47
+
48
+ ## assume active activerecord connection
49
+ ##
50
+
51
+ @event = Event.find_by_key!( opts.event )
52
+
53
+ puts "Event #{@event.key} >#{@event.title}<"
54
+
55
+
56
+ args.each do |arg|
57
+ name = File.basename( arg, '.*' )
58
+ gen_fixtures( name )
59
+ end
60
+
61
+
62
+ puts 'Done.'
63
+
64
+ end # method run
65
+
66
+
67
+ def gen_fixtures( name )
68
+
69
+ ## todo: honor -o/--output option ??
70
+
71
+ dest = "#{name}.rb"
72
+ source = "#{SportDB.root}/templates/fixtures.rb.erb"
73
+
74
+ puts " Merging template #{source} to #{dest}..."
75
+
76
+ out = File.new( dest, 'w+' )
77
+ out << Template.new( source ).render( binding )
78
+ out.flush
79
+ out.close
80
+ end
81
+
82
+ end # class Templater
83
+ end # module SportDB
@@ -1,4 +1,4 @@
1
1
 
2
2
  module SportDB
3
- VERSION = '0.3.3'
3
+ VERSION = '0.4.0'
4
4
  end
@@ -0,0 +1,43 @@
1
+ # encoding: utf-8
2
+
3
+ ####################################################################
4
+ # generiert am <%= Time.now %>
5
+ # using <%= SportDB.banner %>
6
+ ####################################################################
7
+
8
+
9
+ ###########################################
10
+ # <%= event.title %>
11
+
12
+
13
+ <%# todo: use proper key from event for variable e.g. pl or similar (strip season, etc.) %>
14
+
15
+ ev = Event.find_by_key!( '<%= event.key %>' )
16
+
17
+ <% event.teams.each do |team| %>
18
+ <%= '%-18s' % team.key %> = Team.find_by_key!( '<%= team.key %>' )
19
+ <% end %>
20
+
21
+
22
+ <% event.rounds.each do |round| %>
23
+ <%= 'r%02d' % round.pos %> = Round.create!( event: ev, pos: <%= round.pos %>, title: '<%= round.title %>', start_at: Time.utc('<%= round.start_at.strftime('%Y-%m-%d %H:%M') %>'))
24
+ <% end %>
25
+
26
+
27
+ <% event.rounds.each do |round| %>
28
+ <%= 'games%02d' % round.pos %> = [
29
+ <% round.games.each_with_index do |game| %>
30
+ [ <%= '%-18s' % "#{game.team1.key}," %> [<%= "#{game.score1},#{game.score2}" if game.score1.present? && game.score2.present? %>], <%= '%-18s' % "#{game.team2.key}," %> Time.utc('<%= game.play_at.strftime('%Y-%m-%d %H:%M') %>') ],
31
+ <%# does comma for last entry matter? ruby ignores it for sure? check %>
32
+ <% end %>
33
+ ]
34
+
35
+ <% end %>
36
+
37
+
38
+ <% event.rounds.each do |round| %>
39
+ Game.create_from_ary!( <%= 'games%02d' % round.pos %>, <%= 'r%02d' % round.pos %> )
40
+ <% end %>
41
+
42
+ <%# todo: what to put for value??? %>
43
+ Prop.create!( key: 'db.<%= event.key %>.fixtures.version', value: '1' )
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sportdb
3
3
  version: !ruby/object:Gem::Version
4
- hash: 21
4
+ hash: 15
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
- - 3
9
- - 3
10
- version: 0.3.3
8
+ - 4
9
+ - 0
10
+ version: 0.4.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Gerald Bauer
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2012-10-14 00:00:00 Z
18
+ date: 2012-10-16 00:00:00 Z
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
21
21
  name: activerecord
@@ -113,9 +113,12 @@ files:
113
113
  - lib/sportdb/models/prop.rb
114
114
  - lib/sportdb/models/round.rb
115
115
  - lib/sportdb/models/team.rb
116
+ - lib/sportdb/reader.rb
116
117
  - lib/sportdb/schema.rb
118
+ - lib/sportdb/templater.rb
117
119
  - lib/sportdb/utils.rb
118
120
  - lib/sportdb/version.rb
121
+ - templates/fixtures.rb.erb
119
122
  homepage: http://geraldb.github.com/sport.db
120
123
  licenses: []
121
124