sportdb 0.3.3 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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