Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

add code from sport.db; welcome

  • Loading branch information...
commit feb02e5abaa86840bfbb4d72fafbd1d44f138a87 1 parent cd6dd00
@geraldb geraldb authored
Showing with 2,437 additions and 3 deletions.
  1. +12 −0 .gitignore
  2. +21 −0 History.md
  3. +82 −0 Manifest.txt
  4. +22 −3 README.md
  5. +45 −0 Rakefile
  6. +5 −0 bin/sportdb
  7. +331 −0 lib/sportdb.rb
  8. +102 −0 lib/sportdb/cli/opts.rb
  9. +147 −0 lib/sportdb/cli/runner.rb
  10. +92 −0 lib/sportdb/console.rb
  11. +27 −0 lib/sportdb/keys.rb
  12. +82 −0 lib/sportdb/loader.rb
  13. +12 −0 lib/sportdb/models/badge.rb
  14. +14 −0 lib/sportdb/models/city.rb
  15. +18 −0 lib/sportdb/models/country.rb
  16. +113 −0 lib/sportdb/models/event.rb
  17. +12 −0 lib/sportdb/models/event_team.rb
  18. +34 −0 lib/sportdb/models/forward.rb
  19. +197 −0 lib/sportdb/models/game.rb
  20. +23 −0 lib/sportdb/models/group.rb
  21. +13 −0 lib/sportdb/models/group_team.rb
  22. +43 −0 lib/sportdb/models/league.rb
  23. +7 −0 lib/sportdb/models/prop.rb
  24. +16 −0 lib/sportdb/models/region.rb
  25. +13 −0 lib/sportdb/models/round.rb
  26. +11 −0 lib/sportdb/models/season.rb
  27. +75 −0 lib/sportdb/models/team.rb
  28. +328 −0 lib/sportdb/reader.rb
  29. +161 −0 lib/sportdb/schema.rb
  30. +81 −0 lib/sportdb/templater.rb
  31. +243 −0 lib/sportdb/utils.rb
  32. +4 −0 lib/sportdb/version.rb
  33. +51 −0 templates/fixtures.rb.erb
View
12 .gitignore
@@ -16,3 +16,15 @@ tmp
.yardoc
_yardoc
doc/
+
+
+#############
+# ignore komodo project files
+
+*.kpf
+
+###########
+# ignore linked data folder (from world.db) do NOT duplicate/checkin
+
+data
+data/
View
21 History.md
@@ -0,0 +1,21 @@
+### 0.4.0 / 2012-10-16
+
+* Add plain text fixture reader/loader
+* Add --generate/g option for generating fixtures from the DB using templates
+
+### 0.3.1 / 2012-10-14
+
+* Move models into its own namespace, that is, SportDB::Models
+* Add ActiveRecord logging for --verbose option
+
+### 0.3.0 / 2012-10-07
+
+* Add --delete option to delete all records
+
+### 0.2.0 / 2012-10-06
+
+* Add --load option for builtin fixtures
+
+### 0.1.0 / 2012-10-06
+
+* Everything is new. First release
View
82 Manifest.txt
@@ -0,0 +1,82 @@
+History.md
+Manifest.txt
+README.md
+Rakefile
+bin/sportdb
+data/america/2011.rb
+data/america/2011.txt
+data/america/2011.yml
+data/america/teams.txt
+data/at/2011_12/bl.rb
+data/at/2011_12/bl.txt
+data/at/2011_12/cup.rb
+data/at/2012_13/bl.rb
+data/at/2012_13/bl.txt
+data/at/2012_13/cup.rb
+data/at/2012_13/cup.txt
+data/at/badges.rb
+data/at/teams.txt
+data/cl/2011_12/cl.rb
+data/cl/2011_12/el.rb
+data/cl/2012_13/cl.rb
+data/cl/badges.rb
+data/cl/teams.txt
+data/copa/sud_2012_13.rb
+data/copa/sud_2012_13.txt
+data/copa/teams.txt
+data/de/2012_13/bl.rb
+data/de/2012_13/bl.txt
+data/de/teams.txt
+data/en/2012_13/pl.rb
+data/en/2012_13/pl.txt
+data/en/teams.txt
+data/es/teams.txt
+data/euro/2008.rb
+data/euro/2008.txt
+data/euro/2012.rb
+data/euro/2012.txt
+data/euro/teams.txt
+data/leagues.rb
+data/mx/apertura_2012.rb
+data/mx/apertura_2012.txt
+data/mx/teams.txt
+data/nfl/teams.rb
+data/nhl/teams.txt
+data/ro/l1_2012_13.rb
+data/ro/teams.txt
+data/seasons.rb
+data/world/2010.rb
+data/world/2010.txt
+data/world/quali_2012_13_america.rb
+data/world/quali_2012_13_america.txt
+data/world/quali_2012_13_europe.rb
+data/world/quali_2012_13_europe_c.txt
+data/world/quali_2012_13_europe_i.txt
+data/world/teams.txt
+lib/sportdb.rb
+lib/sportdb/cli/opts.rb
+lib/sportdb/cli/runner.rb
+lib/sportdb/console.rb
+lib/sportdb/keys.rb
+lib/sportdb/loader.rb
+lib/sportdb/models/badge.rb
+lib/sportdb/models/city.rb
+lib/sportdb/models/country.rb
+lib/sportdb/models/event.rb
+lib/sportdb/models/event_team.rb
+lib/sportdb/models/forward.rb
+lib/sportdb/models/game.rb
+lib/sportdb/models/group.rb
+lib/sportdb/models/group_team.rb
+lib/sportdb/models/league.rb
+lib/sportdb/models/prop.rb
+lib/sportdb/models/region.rb
+lib/sportdb/models/round.rb
+lib/sportdb/models/season.rb
+lib/sportdb/models/team.rb
+lib/sportdb/reader.rb
+lib/sportdb/schema.rb
+lib/sportdb/templater.rb
+lib/sportdb/utils.rb
+lib/sportdb/version.rb
+templates/fixtures.rb.erb
View
25 README.md
@@ -1,4 +1,23 @@
-sport.db.ruby
-=============
+# sportdb
-sportdb gem - Open Sports Database Schema & Command Line Tool in Ruby
+sport.db Command Line Tool in Ruby
+
+* [geraldb.github.com/sport.db](http://geraldb.github.com/sport.db)
+
+
+## Usage
+
+TBD
+
+
+## Install
+
+Just install the gem:
+
+ $ gem install sportdb
+
+
+## License
+
+The `sportdb` scripts are dedicated to the public domain.
+Use it as you please with no restrictions whatsoever.
View
45 Rakefile
@@ -0,0 +1,45 @@
+require 'hoe'
+require './lib/sportdb/version.rb'
+
+## NB: plugin (hoe-manifest) not required; just used for future testing/development
+Hoe::plugin :manifest # more options for manifests (in the future; not yet)
+
+Hoe.spec 'sportdb' do
+
+ self.version = SportDB::VERSION
+
+ self.summary = 'sportdb - sport.db command line tool'
+ self.description = summary
+
+ self.urls = ['https://github.com/geraldb/sport.db.ruby']
+
+ self.author = 'Gerald Bauer'
+ self.email = 'opensport@googlegroups.com'
+
+ # switch extension to .markdown for gihub formatting
+ # -- NB: auto-changed when included in manifest
+ # self.readme_file = 'README.md'
+ # self.history_file = 'History.md'
+
+ self.extra_deps = [
+ ['activerecord', '~> 3.2'], # NB: will include activesupport,etc.
+ ['worlddb', '~> 0.6.8']
+ ### ['sqlite3', '~> 1.3'] # NB: install on your own; remove dependency
+ ]
+
+ self.licenses = ['Public Domain']
+
+ self.spec_extras = {
+ :required_ruby_version => '>= 1.9.2'
+ }
+
+ self.post_install_message =<<EOS
+******************************************************************************
+
+Questions? Comments? Send them along to the mailing list.
+https://groups.google.com/group/opensport
+
+******************************************************************************
+EOS
+
+end
View
5 bin/sportdb
@@ -0,0 +1,5 @@
+#!/usr/bin/env ruby
+
+require 'sportdb'
+
+SportDB.main
View
331 lib/sportdb.rb
@@ -0,0 +1,331 @@
+###
+# NB: for local testing run like:
+#
+# 1.9.x: ruby -Ilib lib/sportdb.rb
+
+# core and stlibs
+
+require 'yaml'
+require 'pp'
+require 'logger'
+require 'optparse'
+require 'fileutils'
+require 'erb'
+
+# rubygems
+
+require 'active_record' ## todo: add sqlite3? etc.
+require 'worlddb'
+
+
+# our own code
+
+require 'sportdb/version'
+
+require 'sportdb/keys'
+require 'sportdb/models/forward'
+require 'sportdb/models/badge'
+require 'sportdb/models/city'
+require 'sportdb/models/country'
+require 'sportdb/models/event'
+require 'sportdb/models/event_team'
+require 'sportdb/models/game'
+require 'sportdb/models/group'
+require 'sportdb/models/group_team'
+require 'sportdb/models/league'
+require 'sportdb/models/prop'
+require 'sportdb/models/region'
+require 'sportdb/models/round'
+require 'sportdb/models/season'
+require 'sportdb/models/team'
+require 'sportdb/schema' # NB: requires sportdb/models (include SportDB::Models)
+require 'sportdb/utils'
+require 'sportdb/loader'
+require 'sportdb/reader'
+require 'sportdb/templater'
+require 'sportdb/cli/opts'
+require 'sportdb/cli/runner'
+
+
+module SportDB
+
+ def self.banner
+ "sportdb #{VERSION} on Ruby #{RUBY_VERSION} (#{RUBY_RELEASE_DATE}) [#{RUBY_PLATFORM}]"
+ end
+
+ def self.root
+ "#{File.expand_path( File.dirname(File.dirname(__FILE__)) )}"
+ end
+
+ def self.main
+ Runner.new.run(ARGV)
+ end
+
+ def self.create
+ CreateDB.up
+ end
+
+ class Fixtures
+ ## todo: move into its own file???
+
+ ## make constants in Keys availabe (get include in Models) - do NOT pollute/include in SportDB
+ ## make models available in sportdb module by default with namespace
+ # e.g. lets you use Team instead of Models::Team
+ include SportDB::Models
+
+
+ def self.fixtures_rb_test
+ ['leagues',
+ 'seasons',
+# 'at/2011_12/bl',
+# 'at/2011_12/cup',
+# 'at/2012_13/bl',
+# 'at/2012_13/cup',
+# 'copa/sud_2012_13',
+# 'world/quali_2012_13_america'
+ ]
+ end
+
+ def self.fixtures_txt_test
+ [
+# [ AT_2011_12, 'at/2011_12/bl'],
+# [ AT_2012_13, 'at/2012_13/bl'],
+# [ AT_CUP_2012_13, 'at/2012_13/cup'],
+# ['copa.sud.2012/13', 'copa/sud_2012_13'],
+# [ WORLD_QUALI_AMERICA_2012_13, 'world/quali_2012_13_america']
+ ]
+ end
+
+
+ def self.team_fixtures
+ at = Country.find_by_key!( 'at' )
+ de = Country.find_by_key!( 'de' )
+ en = Country.find_by_key!( 'en' )
+ es = Country.find_by_key!( 'es' )
+ ro = Country.find_by_key!( 'ro' )
+ mx = Country.find_by_key!( 'mx' )
+
+ [
+ [ 'america/teams', { national: true } ],
+ [ 'euro/teams', { national: true } ],
+ [ 'world/teams', { national: true } ],
+ [ 'at/teams', { club: true, country_id: at.id } ],
+ [ 'de/teams', { club: true, country_id: de.id } ],
+ [ 'en/teams', { club: true, country_id: en.id } ],
+ [ 'es/teams', { club: true, country_id: es.id } ],
+# [ 'ro/teams', { club: true, country_id: ro.id } ],
+ [ 'mx/teams', { club: true, country_id: mx.id } ],
+ [ 'cl/teams', { club: true } ],
+ [ 'copa/teams', { club: true } ],
+ [ 'nhl/teams', { club: true } ]
+ ]
+ end
+
+ def self.fixtures_rb # all builtin ruby fixtures; helper for covenience
+ ['leagues',
+ 'seasons',
+ 'at/badges',
+ 'at/2011_12/bl',
+ 'at/2011_12/cup',
+ 'at/2012_13/bl',
+ 'at/2012_13/cup',
+ 'cl/badges',
+ 'cl/2011_12/cl',
+ 'cl/2011_12/el',
+ 'cl/2012_13/cl',
+ 'de/2012_13/bl',
+ 'en/2012_13/pl',
+ 'euro/2008',
+ 'euro/2012',
+ 'america/2011',
+ 'copa/sud_2012_13',
+ 'mx/apertura_2012',
+ 'world/2010',
+ 'world/quali_2012_13_europe',
+ 'world/quali_2012_13_america'
+ ]
+ end
+
+ def self.fixtures_txt
+ [[ AT_2011_12, 'at/2011_12/bl'],
+ [ AT_2012_13, 'at/2012_13/bl'],
+ [ AT_CUP_2012_13, 'at/2012_13/cup'],
+ ['de.2012/13', 'de/2012_13/bl'],
+ ['en.2012/13', 'en/2012_13/pl'],
+ ['america.2011', 'america/2011'],
+ ['mx.apertura.2012', 'mx/apertura_2012'],
+ ['copa.sud.2012/13', 'copa/sud_2012_13'],
+ [ EURO_2008, 'euro/2008'],
+ [ WORLD_2010, 'world/2010'],
+ [ WORLD_QUALI_EURO_2012_13, 'world/quali_2012_13_europe_c'],
+ [ WORLD_QUALI_AMERICA_2012_13, 'world/quali_2012_13_america']]
+ end
+ end # class Fixtures
+
+ def self.team_fixtures
+ Fixtures.team_fixtures
+ end
+
+ def self.fixtures_rb # all builtin ruby fixtures; helper for covenience
+ Fixtures.fixtures_rb
+ end
+
+ def self.fixtures_txt
+ Fixtures.fixtures_txt
+ end
+
+ def self.load_all
+ ## load teams first
+ read( team_fixtures ) # converted to plain text fixtures (.rb no longer exist)
+
+ load( fixtures_rb )
+ end
+
+
+ def self.read_all
+ ## todo/fix: remove!! roll into load_all
+ read( fixtures_txt )
+ end
+
+ # load built-in (that is, bundled within the gem) named seeds
+ # - pass in an array of seed names e.g. [ 'cl/teams', 'cl/2012_13/cl' ] etc.
+
+ def self.load( ary )
+ loader = Loader.new
+ ary.each do |name|
+ loader.load_fixtures_builtin( name )
+ end
+ end
+
+ # load built-in (that is, bundled within the gem) named plain text seeds
+ # - pass in an array of pairs of event/seed names e.g. [['at.2012/13', 'at/2012_13/bl'], ['cl.2012/13', 'cl/2012_13/cl']] etc.
+
+ def self.read( ary )
+ reader = Reader.new
+ ary.each do |rec|
+ ## todo: check for teams in name too?
+ if rec[1].nil? || rec[1].kind_of?( Hash ) ## assume team fixtures
+ reader.load_teams_builtin( rec[0], rec[1] ) ## NB: name goes first than opt more_values hash
+ else
+ reader.load_fixtures_builtin( rec[0], rec[1] ) # event_key, name -- assume game fixtures
+ end
+ end
+ end
+
+
+ class Deleter
+ ## todo: move into its own file???
+
+ ## make models available in sportdb module by default with namespace
+ # e.g. lets you use Team instead of Models::Team
+ include SportDB::Models
+
+ def run( args=[] )
+ # for now delete all tables
+
+ Team.delete_all
+ Game.delete_all
+ Event.delete_all
+ EventTeam.delete_all
+ Group.delete_all
+ GroupTeam.delete_all
+ Round.delete_all
+ Badge.delete_all
+ League.delete_all
+ Season.delete_all
+ end
+
+ end
+
+ # delete ALL records (use with care!)
+ def self.delete!
+ puts '*** deleting sport table records/data...'
+ Deleter.new.run
+ end # method delete!
+
+
+ class Stats
+ include SportDB::Models
+
+ def tables
+ puts "Stats:"
+ puts " #{Event.count} events / #{Round.count} rounds / #{Group.count} groups"
+ puts " #{League.count} leagues / #{Season.count} seasons"
+ puts " #{Country.count} countries / #{Region.count} regions / #{City.count} cities"
+ puts " #{Team.count} teams"
+ puts " #{Game.count} games"
+ puts " #{Badge.count} badges"
+
+ ## todo: add tags / taggings from worlddb
+ end
+
+ def props
+ puts "Props:"
+ Prop.order( 'created_at asc' ).all.each do |prop|
+ puts " #{prop.key} / #{prop.value} || #{prop.created_at}"
+ end
+ end
+ end
+
+ def self.stats
+ stats = Stats.new
+ stats.tables
+ stats.props
+ end
+
+ def self.tables
+ Stats.new.tables
+ end
+
+ def self.props
+ Stats.new.props
+ end
+
+
+
+
+ def self.load_plugins
+
+ @found ||= []
+ @loaded ||= {}
+ @files ||= Gem.find_files( 'sportdb_plugin.rb' )
+
+ puts "#{@files.size} plugin files found:"
+ @files.each do |file|
+ puts " >#{file}<"
+ end
+
+ ## todo: extract version and name of gem?
+ puts "normalized/match pattern:"
+ @files.each do |file|
+ if file =~ /sportdb-([a-z]+)-(\d\.\d.\d)/
+ puts " >#{$1}< | >#{$2}<"
+ @found << file
+ else
+ puts "*** error: ignoring plugin script >#{file}< not matching gem naming pattern"
+ end
+ end
+
+ @found.each do |file|
+ begin
+ puts "loading plugin script #{file}"
+ require file
+ rescue LoadError => e
+ puts "*** error loading plugin script #{file.inspect}: #{e.message}. skipping..."
+ end
+ end
+
+ end
+
+end # module SportDB
+
+
+## SportDB::load_plugins
+
+
+if __FILE__ == $0
+ SportDB.main
+else
+ ## say hello
+ puts SportDB.banner
+end
View
102 lib/sportdb/cli/opts.rb
@@ -0,0 +1,102 @@
+module SportDB
+
+class Opts
+
+ def create=(boolean)
+ @create = boolean
+ end
+
+ def create?
+ return false if @create.nil? # default create flag is false
+ @create == true
+ end
+
+ def setup=(boolean)
+ @setup = boolean
+ end
+
+ def setup?
+ return false if @setup.nil? # default setup flag is false
+ @setup == true
+ end
+
+ def sport=(boolean)
+ @sport = boolean
+ end
+
+ def sport?
+ return false if @sport.nil? # default sport flag is false
+ @sport == true
+ end
+
+
+ def world=(boolean)
+ @world = boolean
+ end
+
+ def world?
+ return false if @world.nil? # default populate world tables flag is false
+ @world == true
+ end
+
+
+ def generate=(boolean)
+ @generate = boolean
+ end
+
+ def generate?
+ return false if @generate.nil? # default generate flag is false
+ @generate == true
+ end
+
+ def event=(value)
+ @event = value
+ end
+
+ def event
+ @event # NB: option has no default; return nil ## || '.'
+ end
+
+
+ def delete=(boolean)
+ @delete = boolean
+ end
+
+ def delete?
+ return false if @delete.nil? # default create flag is false
+ @delete == true
+ end
+
+
+ # use loader? (that is, built-in seed data)
+ def load=(boolean)
+ @load = boolean
+ end
+
+ def load?
+ return false if @load.nil? # default create flag is false
+ @load == true
+ end
+
+
+ def output_path=(value)
+ @output_path = value
+ end
+
+ def output_path
+ @output_path || '.'
+ end
+
+
+ def data_path=(value)
+ @data_path = value
+ end
+
+ def data_path
+ @data_path || '.'
+ end
+
+
+end # class Opts
+
+end # module SportDB
View
147 lib/sportdb/cli/runner.rb
@@ -0,0 +1,147 @@
+
+module SportDB
+
+class Runner
+
+
+## make models available in sportdb module by default with namespace
+# e.g. lets you use Team instead of Models::Team
+ include SportDB::Models
+
+ def initialize
+ @logger = Logger.new(STDOUT)
+ @logger.level = Logger::INFO
+
+ @opts = Opts.new
+ end
+
+ attr_reader :logger, :opts
+
+ def run( args )
+ opt=OptionParser.new do |cmd|
+
+ cmd.banner = "Usage: sportdb [options]"
+
+
+ ## NB: reserve -c for use with -c/--config
+ cmd.on( '--create', 'Create DB schema' ) { opts.create = true }
+ cmd.on( '--setup', "Create DB schema 'n' load all builtin data" ) { opts.setup = true }
+
+ cmd.on( '--world', "Populate world tables with builtin data (version #{WorldDB::VERSION})" ) { opts.world = true }
+ cmd.on( '--sport', "Populate sport tables with builtin data" ) { opts.sport = true }
+
+
+ cmd.on( '--delete', 'Delete all records' ) { opts.delete = true }
+
+ ### todo: in future allow multiple search path??
+ cmd.on( '-i', '--include PATH', "Data path (default is #{opts.data_path})" ) { |path| opts.data_path = path }
+
+ cmd.on( '--load', 'Use loader for builtin sports data' ) { opts.load = true }
+
+ cmd.on( '-e', '--event KEY', 'Event to load or generate' ) { |key| opts.event = key; }
+
+ cmd.on( '-o', '--output PATH', "Output path (default is #{opts.output_path})" ) { |path| opts.output_path = path }
+ cmd.on( '-g', '--generate', 'Generate fixtures from template' ) { opts.generate = true }
+
+
+ cmd.on( '-v', '--version', "Show version" ) do
+ puts SportDB.banner
+ exit
+ end
+
+ cmd.on( "--verbose", "Show debug trace" ) do
+ logger.datetime_format = "%H:%H:%S"
+ logger.level = Logger::DEBUG
+
+ ActiveRecord::Base.logger = Logger.new(STDOUT)
+ end
+
+ cmd.on_tail( "-h", "--help", "Show this message" ) do
+ puts <<EOS
+
+sportdb - sport.db command line tool, version #{VERSION}
+
+#{cmd.help}
+
+Examples:
+ sportdb cl/teams cl/2012_13/cl # import champions league (cl)
+ sportdb --create # create database schema
+
+More Examples:
+ sportdb # show stats (table counts, table props)
+ sportdb -i ../sport.db/db cl/teams cl/2012_13/cl # import champions league (cl) in db folder
+
+Further information:
+ http://geraldb.github.com/sport.db
+
+EOS
+ exit
+ end
+ end
+
+ opt.parse!( args )
+
+ puts SportDB.banner
+
+ puts "working directory: #{Dir.pwd}"
+
+ db_config = {
+ :adapter => 'sqlite3',
+ :database => "#{opts.output_path}/sport.db"
+ }
+
+ puts "Connecting to db using settings: "
+ pp db_config
+
+ ActiveRecord::Base.establish_connection( db_config )
+
+ if opts.setup?
+ WorldDB.create
+ SportDB.create
+ WorldDB.read_all
+ SportDB.load_all # ruby (.rb) fixtures
+ SportDB.read_all # plain text (.txt) fixtures
+ else
+
+ if opts.create?
+ WorldDB.create
+ SportDB.create
+ end
+
+ if opts.world? || opts.sport?
+ if opts.world?
+ WorldDB.delete! if opts.delete?
+ WorldDB.read_all
+ end
+
+ if opts.sport?
+ SportDB.delete! if opts.delete?
+ SportDB.load_all
+ SportDB.read_all
+ end
+ else # no sport or world flag
+ if opts.delete?
+ SportDB.delete!
+ WorldDB.delete! # countries,regions,cities,tags,taggings,props
+ end
+ end
+
+ if opts.event.present?
+ if opts.generate?
+ Templater.new( logger ).run( opts, args ) # export/generate ruby fixtures
+ else
+ Reader.new( logger ).run( opts, args ) # load/read plain text fixtures
+ end
+ else
+ Loader.new( logger ).run( opts, args ) # load ruby fixtures
+ end
+ end
+
+ SportDB.stats
+
+ puts 'Done.'
+
+ end # method run
+
+end # class Runner
+end # module SportDB
View
92 lib/sportdb/console.rb
@@ -0,0 +1,92 @@
+## for use to run with interactive ruby (irb)
+## e.g. irb -r sportdb/console
+
+require 'sportdb'
+
+# some ruby stdlibs
+
+require 'logger'
+require 'pp' # pretty printer
+require 'uri'
+require 'json'
+require 'yaml'
+
+
+## shortcuts for models
+
+Event = SportDB::Models::Event
+Team = SportDB::Models::Team
+Game = SportDB::Models::Game
+Group = SportDB::Models::Group
+Round = SportDB::Models::Round
+Season = SportDB::Models::Season
+League = SportDB::Models::League
+Badge = SportDB::Models::Badge
+
+Tag = WorldDB::Models::Tag
+Tagging = WorldDB::Models::Tagging
+Country = WorldDB::Models::Country
+Region = WorldDB::Models::Region
+City = WorldDB::Models::City
+Prop = WorldDB::Models::Prop
+
+## connect to db
+
+DB_CONFIG = {
+ adapter: 'sqlite3',
+ database: 'sport.db'
+}
+
+pp DB_CONFIG
+ActiveRecord::Base.establish_connection( DB_CONFIG )
+
+## test drive
+
+puts "Welcome to sport.db, version #{SportDB::VERSION} (world.db, version #{WorldDB::VERSION})!"
+puts " #{'%5d' % Event.count} events"
+puts " #{'%5d' % Team.count} teams"
+puts " #{'%5d' % Game.count} games"
+puts " #{'%5d' % City.count} cities"
+puts "Ready."
+
+## add some predefined shortcuts
+
+##### some countries
+
+AT = Country.find_by_key( 'at' )
+DE = Country.find_by_key( 'de' )
+EN = Country.find_by_key( 'en' )
+
+US = Country.find_by_key( 'us' )
+CA = Country.find_by_key( 'ca' )
+MX = Country.find_by_key( 'mx' )
+
+#### some events
+
+EURO2008 = Event.find_by_key( 'euro.2008' )
+EURO2012 = Event.find_by_key( 'euro.2012' )
+EURO = EURO2012 # add alias
+
+BL = Event.find_by_key( 'de.2012/13' )
+PL = Event.find_by_key( 'en.2012/13' )
+
+### some club teams
+
+BARCA = Team.find_by_key( 'barcelona' )
+MANU = Team.find_by_key( 'manunited' )
+MUN = MANUNITED = MANU # add alias
+BAYERN = Team.find_by_key( 'bayern' )
+AUSTRIA = Team.find_by_key( 'austria' )
+
+### some national teams (three letter fifa codes)
+
+ESP = Team.find_by_key( 'esp' )
+GER = Team.find_by_key( 'ger' )
+AUT = Team.find_by_key( 'aut' )
+
+MEX = Team.find_by_key( 'mex' )
+ARG = Team.find_by_key( 'arg' )
+
+## turn on activerecord logging to console
+
+ActiveRecord::Base.logger = Logger.new( STDOUT )
View
27 lib/sportdb/keys.rb
@@ -0,0 +1,27 @@
+
+module SportDB::Keys
+
+ module EventKeys
+ # use constants for known keys; lets us define aliases (if things change)
+
+ AT_2011_12 = 'at.2011/12'
+ AT_2012_13 = 'at.2012/13'
+ AT_CUP_2012_13 = 'at.cup.2012/13'
+
+ CL_2012_13 = 'cl.2012/13'
+
+ EURO_2008 = 'euro.2008'
+ EURO_2012 = 'euro.2012'
+
+ WORLD_2010 = 'world.2010'
+
+ WORLD_QUALI_EURO_2012_13 = 'world.quali.euro.2012/13'
+ WORLD_QUALI_AMERICA_2012_13 = 'world.quali.america.2012/13'
+
+ ############################
+ ## NB: see db/leagues.rb for keys in use
+ end
+
+ include SportDB::Keys::EventKeys
+
+end # module SportDB::Keys
View
82 lib/sportdb/loader.rb
@@ -0,0 +1,82 @@
+module SportDB
+
+class Loader
+
+## make models available in sportdb module by default with namespace
+# e.g. lets you use Team instead of Models::Team
+ include SportDB::Models
+
+
+ def initialize( logger=nil )
+ if logger.nil?
+ @logger = Logger.new(STDOUT)
+ @logger.level = Logger::INFO
+ else
+ @logger = logger
+ end
+ end
+
+ attr_reader :logger
+
+
+ def run( opts, args )
+
+ args.each do |arg|
+ name = arg # File.basename( arg, '.*' )
+
+ if opts.load?
+ load_fixtures_builtin( name )
+ else
+ load_fixtures_with_include_path( name, opts.data_path )
+ end
+ end
+
+ end # method run
+
+
+ def load_fixtures_with_include_path( name, include_path ) # load from file system
+ path = "#{include_path}/#{name}.rb"
+
+ puts "*** loading data '#{name}' (#{path})..."
+
+ ## nb: assume/enfore utf-8 encoding (with or without BOM - byte order mark)
+ ## - see sportdb/utils.rb
+ code = File.read_utf8( path )
+
+ load_fixtures_worker( code )
+
+ Prop.create!( key: "db.#{fixture_name_to_prop_key(name)}.version", value: "file.rb.#{File.mtime(path).strftime('%Y.%m.%d')}" )
+ end
+
+ def load_fixtures_builtin( name ) # load from gem (built-in)
+ path = "#{SportDB.root}/db/#{name}.rb"
+
+ puts "*** loading data '#{name}' (#{path})..."
+
+ code = File.read_utf8( path )
+
+ load_fixtures_worker( code )
+
+ ## for builtin fixtures use VERSION of gem
+
+ Prop.create!( key: "db.#{fixture_name_to_prop_key(name)}.version", value: "sport.rb.#{SportDB::VERSION}" )
+ end
+
+
+private
+
+
+ def load_fixtures_worker( code )
+
+ self.class_eval( code )
+
+ # NB: same as
+ #
+ # module SportDB
+ # include SportDB::Models
+ # <code here>
+ # end
+ end
+
+end # class Loader
+end # module SportDB
View
12 lib/sportdb/models/badge.rb
@@ -0,0 +1,12 @@
+module SportDB::Models
+
+class Badge < ActiveRecord::Base
+
+ belongs_to :team
+ belongs_to :league
+ belongs_to :season
+
+end # class Badge
+
+
+end # module SportDB::Models
View
14 lib/sportdb/models/city.rb
@@ -0,0 +1,14 @@
+
+## todo: how to best extends city model?
+
+module WorldDB::Models
+ class City
+ has_many :teams, :class_name => 'SportDB::Models::Team', :foreign_key => 'city_id'
+ end
+end # module WorldDB::Models
+
+
+## moved to models/forward
+# module SportDB::Models
+# City = WorldDB::Models::City
+# end # module SportDB::Models
View
18 lib/sportdb/models/country.rb
@@ -0,0 +1,18 @@
+# encoding: utf-8
+
+## todo: how to best extends country model?
+
+module WorldDB::Models
+
+ class Country
+ has_many :teams, :class_name => 'SportDB::Models::Team', :foreign_key => 'country_id'
+ has_many :leagues, :class_name => 'SportDB::Models::League', :foreign_key => 'country_id'
+ end # class Country
+
+end # module WorldDB::Models
+
+
+## moved to models/forward
+# module SportDB::Models
+# Country = WorldDB::Models::Country
+# end # module SportDB::Models
View
113 lib/sportdb/models/event.rb
@@ -0,0 +1,113 @@
+# encoding: utf-8
+
+module SportDB::Models
+
+class Event < ActiveRecord::Base
+
+ belongs_to :league
+ belongs_to :season
+
+ has_many :rounds, :order => 'pos' # all (fix and flex) rounds
+ has_many :games, :through => :rounds
+
+ has_many :groups, :order => 'pos'
+
+ has_many :event_teams, :class_name => 'EventTeam'
+ has_many :teams, :through => :event_teams
+
+ before_save :on_before_save
+
+ def add_teams_from_ary!( team_keys )
+ team_keys.each do |team_key|
+ team = Team.find_by_key!( team_key )
+ self.teams << team
+ end
+ end
+
+ def on_before_save
+ # event key is composite of league + season (e.g. at.2012/13) etc.
+ self.key = "#{league.key}.#{season.key}"
+ end
+
+ def title
+ league.title
+ end
+
+ def full_title # includes season (e.g. year)
+ "#{league.title} #{season.title}"
+ end
+
+ #####################
+ ## convenience helper for text parser/reader
+
+ def known_teams_table
+
+ ## build known teams table w/ synonyms e.g.
+ #
+ # [[ 'wolfsbrug', [ 'VfL Wolfsburg' ]],
+ # [ 'augsburg', [ 'FC Augsburg', 'Augi2', 'Augi3' ]],
+ # [ 'stuttgart', [ 'VfB Stuttgart' ]] ]
+
+ known_teams = []
+
+ teams.each_with_index do |team,index|
+
+ titles = []
+ titles << team.title
+ titles += team.synonyms.split('|') if team.synonyms.present?
+
+ ## NB: sort here by length (largest goes first - best match)
+ # exclude code and key (key should always go last)
+ titles = titles.sort { |left,right| right.length <=> left.length }
+
+ ## escape for regex plus allow subs for special chars/accents
+ titles = titles.map { |title| title_esc_regex( title ) }
+
+ titles << team.code if team.code.present?
+ titles << team.key
+
+ known_teams << [ team.key, titles ]
+
+ puts " Team[#{index+1}] #{team.key} >#{titles.join('|')}<"
+ end
+
+ known_teams
+ end # method known_teams_table
+
+ ###########################
+ ## convenience helpers
+
+ include SportDB::Keys::EventKeys
+
+ def self.find_at_2012_13!
+ self.find_by_key!( AT_2012_13 )
+ end
+
+ def self.find_at_cup_2012_13!
+ self.find_by_key!( AT_CUP_2012_13 )
+ end
+
+ def self.find_cl_2012_13!
+ self.find_by_key!( CL_2012_13 )
+ end
+
+ def self.find_euro_2012!
+ self.find_by_key!( EURO_2012 )
+ end
+
+ def self.find_world_2010!
+ self.find_by_key!( WORLD_2010 )
+ end
+
+ def self.find_world_quali_euro_2012_13!
+ self.find_by_key!( WORLD_QUALI_EURO_2012_13 )
+ end
+
+ def self.find_world_quali_america_2012_13!
+ self.find_by_key!( WORLD_QUALI_AMERICA_2012_13 )
+ end
+
+
+end # class Event
+
+end # module SportDB::Models
View
12 lib/sportdb/models/event_team.rb
@@ -0,0 +1,12 @@
+module SportDB::Models
+
+
+class EventTeam < ActiveRecord::Base
+ self.table_name = 'events_teams'
+
+ belongs_to :event
+ belongs_to :team
+end # class EventTeam
+
+
+end # module SportDB::Models
View
34 lib/sportdb/models/forward.rb
@@ -0,0 +1,34 @@
+
+### forward references
+## require first to resolve circular references
+
+module SportDB::Models
+
+ ## todo: why? why not use include WorldDB::Models here???
+
+ Country = WorldDB::Models::Country
+ Region = WorldDB::Models::Region
+ City = WorldDB::Models::City
+ Prop = WorldDB::Models::Prop
+
+ ## nb: for now only team and league use worlddb tables
+ # e.g. with belongs_to assoc (country,region)
+
+ class Team < ActiveRecord::Base ; end
+ class League < ActiveRecord::Base ; end
+
+ #### make constanst such as AT_2012_13, WORLD_2010, etc. available
+ include SportDB::Keys
+
+end
+
+
+module WorldDB::Models
+
+ # add alias? why? why not? # is there a better way?
+ # - just include SportDB::Models - why? why not?
+ # - just include once in loader??
+ Team = SportDB::Models::Team
+ League = SportDB::Models::League
+
+end
View
197 lib/sportdb/models/game.rb
@@ -0,0 +1,197 @@
+module SportDB::Models
+
+
+class Game < ActiveRecord::Base
+
+ belongs_to :team1, :class_name => 'Team', :foreign_key => 'team1_id'
+ belongs_to :team2, :class_name => 'Team', :foreign_key => 'team2_id'
+
+ belongs_to :round
+ belongs_to :group # group is optional
+
+ before_save :calc_toto12x
+
+
+ def self.create_knockouts_from_ary!( games, round )
+ Game.create_from_ary!( games, round, true )
+ end
+
+ def self.create_from_ary!( games, round, knockout=false )
+
+### fix:
+# replace knockout=false with more attribs
+# see create teams and than merge attribs
+
+ games.each_with_index do |values,index|
+
+ value_pos = index+1
+ value_scores = []
+ value_teams = []
+ value_knockout = knockout
+ value_play_at = round.start_at # if no date present use it from round
+ value_group = nil
+
+ ### lets you use arguments in any order
+ ## makes pos optional (if not present counting from 1 to n)
+
+ values.each do |value|
+ if value.kind_of? Numeric
+ value_pos = value
+ elsif value.kind_of?( TrueClass ) || value.kind_of?( FalseClass )
+ value_knockout = value
+ elsif value.kind_of? Array
+ value_scores = value
+ elsif value.kind_of? Team
+ value_teams << value
+ elsif value.kind_of? Group
+ value_group = value
+ elsif value.kind_of?( Date ) || value.kind_of?( Time ) || value.kind_of?( DateTime )
+ value_play_at = value
+ else
+ # issue an error/warning here
+ end
+ end
+
+ Game.create!(
+ :round => round,
+ :pos => value_pos,
+ :team1 => value_teams[0],
+ :score1 => value_scores[0],
+ :score2 => value_scores[1],
+ :score3 => value_scores[2],
+ :score4 => value_scores[3],
+ :score5 => value_scores[4],
+ :score6 => value_scores[5],
+ :team2 => value_teams[1],
+ :play_at => value_play_at,
+ :group => value_group, # Note: group is optional (may be null/nil)
+ :knockout => value_knockout )
+ end # each games
+ end
+
+ def self.create_pairs_from_ary_for_group!( pairs, group )
+
+ pairs.each do |pair|
+ game1_attribs = {
+ :round =>pair[0][5],
+ :pos =>pair[0][0],
+ :team1 =>pair[0][1],
+ :score1 =>pair[0][2][0],
+ :score2 =>pair[0][2][1],
+ :team2 =>pair[0][3],
+ :play_at =>pair[0][4],
+ :group =>group }
+
+ game2_attribs = {
+ :round =>pair[1][5],
+ :pos =>pair[1][0],
+ :team1 =>pair[1][1],
+ :score1 =>pair[1][2][0],
+ :score2 =>pair[1][2][1],
+ :team2 =>pair[1][3],
+ :play_at =>pair[1][4],
+ :group =>group }
+
+ game1 = Game.create!( game1_attribs )
+ game2 = Game.create!( game2_attribs )
+
+ # linkup games
+ game1.next_game_id = game2.id
+ game1.save!
+
+ game2.prev_game_id = game1.id
+ game2.save!
+ end # each pair
+ end
+
+ def self.create_knockout_pairs_from_ary!( pairs, round1, round2 )
+
+ pairs.each do |pair|
+ game1_attribs = {
+ :round =>round1,
+ :pos =>pair[0][0],
+ :team1 =>pair[0][1],
+ :score1 =>pair[0][2][0],
+ :score2 =>pair[0][2][1],
+ :team2 =>pair[0][3],
+ :play_at =>pair[0][4] }
+
+ game2_attribs = {
+ :round =>round2,
+ :pos =>pair[1][0],
+ :team1 =>pair[1][1],
+ :score1 =>pair[1][2][0],
+ :score2 =>pair[1][2][1],
+ :score3 =>pair[1][2][2],
+ :score4 =>pair[1][2][3],
+ :score5 =>pair[1][2][4],
+ :score6 =>pair[1][2][5],
+ :team2 =>pair[1][3],
+ :play_at =>pair[1][4],
+ :knockout =>true }
+
+ game1 = Game.create!( game1_attribs )
+ game2 = Game.create!( game2_attribs )
+
+ # linkup games
+ game1.next_game_id = game2.id
+ game1.save!
+
+ game2.prev_game_id = game1.id
+ game2.save!
+ end # each pair
+ end
+
+
+
+ def calc_toto12x
+ if score1.nil? || score2.nil?
+ self.toto12x = nil
+ elsif score1 == score2
+ self.toto12x = 'X'
+ elsif score1 > score2
+ self.toto12x = '1'
+ elsif score1 < score2
+ self.toto12x = '2'
+ end
+ end
+
+
+ def over? # game over?
+ play_at <= Time.now
+ end
+
+ ## fix/todo: already added by ar magic ??? remove code
+ def knockout?
+ knockout == true
+ end
+
+ def complete?
+ score1.present? && score2.present?
+ end
+
+############# convenience helpers for styling
+##
+
+ def team1_style_class
+ buf = ''
+ ## NB: remove if calc?
+ buf << 'game-team-winner ' if complete? && (score1 > score2)
+ buf << 'game-team-draw ' if complete? && (score1 == score2)
+ buf
+ end
+
+ def team2_style_class
+ buf = ''
+ ## NB: remove if calc?
+ buf << 'game-team-winner ' if complete? && (score2 > score1)
+ buf << 'game-team-draw ' if complete? && (score2 == score1)
+ buf
+ end
+
+
+
+end # class Game
+
+
+end # module SportDB::Models
View
23 lib/sportdb/models/group.rb
@@ -0,0 +1,23 @@
+module SportDB::Models
+
+
+class Group < ActiveRecord::Base
+
+ has_many :games, :order => 'pos'
+ belongs_to :event
+
+ has_many :group_teams, :class_name => 'GroupTeam'
+ has_many :teams, :through => :group_teams
+
+ def add_teams_from_ary!( team_keys )
+ team_keys.each do |team_key|
+ team = Team.find_by_key!( team_key )
+ self.teams << team
+ end
+ end
+
+end # class Group
+
+
+end # module SportDB::Models
+
View
13 lib/sportdb/models/group_team.rb
@@ -0,0 +1,13 @@
+module SportDB::Models
+
+
+class GroupTeam < ActiveRecord::Base
+ self.table_name = 'groups_teams'
+
+ belongs_to :group
+ belongs_to :team
+end # class GroupTeam
+
+
+end # module SportDB::Models
+
View
43 lib/sportdb/models/league.rb
@@ -0,0 +1,43 @@
+module SportDB::Models
+
+
+class League < ActiveRecord::Base
+
+ ## leagues also used for conferences, world series, cups, etc.
+ #
+ ## league (or cup/conference/series/etc.) + season (or year) = event
+
+ has_many :events
+ has_many :seasons, :through => :events
+
+ belongs_to :country, :class_name => 'WorldDB::Models::Country', :foreign_key => 'country_id'
+
+
+ def self.create_from_ary!( leagues, more_values={} )
+ leagues.each do |values|
+
+ ## key & title required
+ attr = {
+ key: values[0],
+ title: values[1]
+ }
+
+ attr = attr.merge( more_values )
+
+ ## check for optional values
+ values[2..-1].each do |value|
+ if value.is_a? Country
+ attr[ :country_id ] = value.id
+ else
+ # issue warning: unknown type for value
+ end
+ end
+
+ League.create!( attr )
+ end # each league
+ end
+
+end # class League
+
+
+end # module SportDB::Models
View
7 lib/sportdb/models/prop.rb
@@ -0,0 +1,7 @@
+# encoding: utf-8
+
+
+## moved to models/forward
+# module SportDB::Models
+# Prop = WorldDB::Models::Prop
+# end # module SportDB::Models
View
16 lib/sportdb/models/region.rb
@@ -0,0 +1,16 @@
+# encoding: utf-8
+
+## todo: how to best extends country model?
+
+module WorldDB::Models
+
+ class Region
+ has_many :teams, :class_name => 'SportDB::Models::Team', :through => :cities
+ end # class Region
+
+end # module WorldDB::Models
+
+## moved to models/forward
+# module SportDB::Models
+# Region = WorldDB::Models::Region
+# end # module SportDB::Models
View
13 lib/sportdb/models/round.rb
@@ -0,0 +1,13 @@
+module SportDB::Models
+
+
+class Round < ActiveRecord::Base
+
+ has_many :games, :order => 'pos'
+ belongs_to :event
+
+end # class Round
+
+
+end # module SportDB::Models
+
View
11 lib/sportdb/models/season.rb
@@ -0,0 +1,11 @@
+module SportDB::Models
+
+
+class Season < ActiveRecord::Base
+
+ has_many :events
+
+end # class Season
+
+
+end # module SportDB::Models
View
75 lib/sportdb/models/team.rb
@@ -0,0 +1,75 @@
+module SportDB::Models
+
+
+class Team < ActiveRecord::Base
+
+ has_many :home_games, :class_name => 'Game', :foreign_key => 'team1_id'
+ has_many :away_games, :class_name => 'Game', :foreign_key => 'team2_id'
+
+ REGEX_CODE = /^[A-Z][A-Z0-9_]{2}$/ # must start w/ letter a-z (2 n 3 can be number or underscore _)
+
+ validates :key, :format => { :with => /^[a-z]{2,}$/, :message => 'expected two or more lowercase letters a-z' }
+ validates :code, :format => { :with => REGEX_CODE, :message => 'expected three uppercase letters A-Z (and _)' }, :allow_nil => true
+
+
+ ### fix - how to do it with has_many macro? use finder_sql?
+ def games
+ Game.where( 'team1_id = ? or team2_id = ?', id, id ).order( 'play_at' ).all
+ end
+
+ has_many :badges # Winner, 2nd, Cupsieger, Aufsteiger, Absteiger, etc.
+
+ belongs_to :country, :class_name => 'WorldDB::Models::Country', :foreign_key => 'country_id'
+ belongs_to :city, :class_name => 'WorldDB::Models::City', :foreign_key => 'city_id'
+
+
+ def self.create_from_ary!( teams, more_values={} )
+ teams.each do |values|
+
+ ## key & title required
+ attr = {
+ key: values[0]
+ }
+
+ ## title (split of optional synonyms)
+ # e.g. FC Bayern Muenchen|Bayern Muenchen|Bayern
+ titles = values[1].split('|')
+
+ attr[ :title ] = titles[0]
+ ## add optional synonyms
+ attr[ :synonyms ] = titles[1..-1].join('|') if titles.size > 1
+
+
+ attr = attr.merge( more_values )
+
+ ## check for optional values
+ values[2..-1].each do |value|
+ if value.is_a? Country
+ attr[ :country_id ] = value.id
+ elsif value.is_a? City
+ attr[ :city_id ] = value.id
+ elsif value =~ REGEX_CODE ## assume its three letter code (e.g. ITA or S04 etc.)
+ attr[ :code ] = value
+ elsif value =~ /^city:/ ## city:
+ value_city_key = value[5..-1] ## cut off city: prefix
+ value_city = City.find_by_key!( value_city_key )
+ attr[ :city_id ] = value_city.id
+ else
+ attr[ :title2 ] = value
+ end
+ end
+
+ ## check if exists
+ team = Team.find_by_key( values[0] )
+ if team.present?
+ puts "*** warning team with key '#{values[0]}' exists; skipping create"
+ else
+ Team.create!( attr )
+ end
+ end # each team
+ end
+
+end # class Team
+
+
+end # module SportDB::Models
View
328 lib/sportdb/reader.rb
@@ -0,0 +1,328 @@
+# encoding: utf-8
+
+module SportDB
+
+class Reader
+
+## make models available in sportdb module by default with namespace
+# e.g. lets you use Team instead of Models::Team
+ include SportDB::Models
+
+
+ def initialize( logger=nil )
+ if logger.nil?
+ @logger = Logger.new(STDOUT)
+ @logger.level = Logger::INFO
+ else
+ @logger = logger
+ end
+ end
+
+ attr_reader :logger
+
+ def run( opts, args )
+
+ args.each do |arg|
+ name = arg # File.basename( arg, '.*' )
+
+ if opts.load?
+ load_fixtures_builtin( opts.event, name )
+ else
+ load_fixtures_with_include_path( opts.event, name, opts.data_path )
+ end
+ end
+
+ end
+
+
+ def load_fixtures_with_include_path( event_key, name, include_path ) # load from file system
+ path = "#{include_path}/#{name}.txt"
+
+ puts "*** parsing data '#{name}' (#{path})..."
+
+ reader = LineReader.new( logger, path )
+
+ load_fixtures_worker( event_key, reader )
+
+ Prop.create!( key: "db.#{fixture_name_to_prop_key(name)}.version", value: "file.txt.#{File.mtime(path).strftime('%Y.%m.%d')}" )
+ end
+
+ def load_fixtures_builtin( event_key, name ) # load from gem (built-in)
+ path = "#{SportDB.root}/db/#{name}.txt"
+
+ puts "*** parsing data '#{name}' (#{path})..."
+
+ reader = LineReader.new( logger, path )
+
+ load_fixtures_worker( event_key, reader )
+
+ Prop.create!( key: "db.#{fixture_name_to_prop_key(name)}.version", value: "sport.txt.#{SportDB::VERSION}" )
+ end
+
+
+ def load_teams_builtin( name, more_values={} )
+ path = "#{SportDB.root}/db/#{name}.txt"
+
+ puts "*** parsing data '#{name}' (#{path})..."
+
+ reader = ValuesReader.new( logger, path, more_values )
+
+ load_teams_worker( reader )
+
+ Prop.create!( key: "db.#{fixture_name_to_prop_key(name)}.version", value: "sport.txt.#{SportDB::VERSION}" )
+ end
+
+
+private
+
+ include SportDB::FixtureHelpers
+
+ def load_teams_worker( reader )
+
+ reader.each_line do |attribs, values|
+
+ ## check optional values
+ values.each_with_index do |value, index|
+ if value =~ /^city:/ ## city:
+ value_city_key = value[5..-1] ## cut off city: prefix
+ value_city = City.find_by_key!( value_city_key )
+ attribs[ :city_id ] = value_city.id
+ elsif value =~ /^[A-Z]{3}$/ ## assume three-letter code e.g. FCB, RBS, etc.
+ attribs[ :code ] = value
+ elsif value =~ /^[a-z]{2}$/ ## assume two-letter country key e.g. at,de,mx,etc.
+ value_country = Country.find_by_key!( value )
+ attribs[ :country_id ] = value_country.id
+ else
+ ## todo: assume title2 ??
+ # issue warning: unknown type for value
+ puts "*** warning: unknown type for value >#{value}<"
+ end
+ end
+
+ rec = Team.find_by_key( attribs[ :key ] )
+ if rec.present?
+ puts "*** update Team #{rec.id}-#{rec.key}:"
+ else
+ puts "*** create Team:"
+ rec = Team.new
+ end
+
+ puts attribs.to_json
+
+ rec.update_attributes!( attribs )
+
+ end # each lines
+ end # method load_teams_worker
+
+ def load_fixtures_worker( event_key, reader )
+
+ ## assume active activerecord connection
+ ##
+
+ ## reset cached values
+ @patch_rounds = {}
+ @knockout_flag = false
+ @round = nil
+
+
+ @event = Event.find_by_key!( event_key )
+
+ puts "Event #{@event.key} >#{@event.title}<"
+
+ @known_teams = @event.known_teams_table
+
+ parse_fixtures( reader )
+
+ end # method load_fixtures
+
+
+ def parse_group( line )
+ puts "parsing group line: >#{line}<"
+
+ match_teams!( line )
+ team_keys = find_teams!( line )
+
+ title, pos = find_group_title_and_pos!( line )
+
+ puts " line: >#{line}<"
+
+ group_attribs = {
+ title: title
+ }
+
+ @group = Group.find_by_event_id_and_pos( @event.id, pos )
+ if @group.present?
+ puts "*** update group #{@group.id}:"
+ else
+ puts "*** create group:"
+ @group = Group.new
+ group_attribs = group_attribs.merge( {
+ event_id: @event.id,
+ pos: pos
+ })
+ end
+
+ puts group_attribs.to_json
+
+ @group.update_attributes!( group_attribs )
+
+ @group.teams.clear # remove old teams
+ ## add new teams
+ team_keys.each do |team_key|
+ team = Team.find_by_key!( team_key )
+ puts " adding team #{team.title} (#{team.code})"
+ @group.teams << team
+ end
+ end
+
+ def parse_round( line )
+ puts "parsing round line: >#{line}<"
+ pos = find_round_pos!( line )
+
+ @knockout_flag = is_knockout_round?( line )
+
+ group_title, group_pos = find_group_title_and_pos!( line )
+
+ if group_pos.present?
+ @group = Group.find_by_event_id_and_pos!( @event.id, group_pos )
+ else
+ @group = nil # reset group to no group
+ end
+
+ puts " line: >#{line}<"
+
+ ## NB: dummy/placeholder start_at, end_at date
+ ## replace/patch after adding all games for round
+
+ round_attribs = {
+ title: "#{pos}. Runde"
+ }
+
+
+ @round = Round.find_by_event_id_and_pos( @event.id, pos )
+ if @round.present?
+ puts "*** update round #{@round.id}:"
+ else
+ puts "*** create round:"
+ @round = Round.new
+
+ round_attribs = round_attribs.merge( {
+ event_id: @event.id,
+ pos: pos,
+ start_at: Time.utc('1912-12-12'),
+ end_at: Time.utc('1912-12-12')
+ })
+ end
+
+ puts round_attribs.to_json
+
+ @round.update_attributes!( round_attribs )
+
+ ### store list of round is for patching start_at/end_at at the end
+ @patch_rounds[ @round.id ] = @round.id
+ end
+
+ def parse_game( line )
+ puts "parsing game (fixture) line: >#{line}<"
+
+ pos = find_game_pos!( line )
+
+ match_teams!( line )
+ team1_key = find_team1!( line )
+ team2_key = find_team2!( line )
+
+ date = find_date!( line )
+ scores = find_scores!( line )
+
+ puts " line: >#{line}<"
+
+
+ ### todo: cache team lookups in hash?
+
+ team1 = Team.find_by_key!( team1_key )
+ team2 = Team.find_by_key!( team2_key )
+
+ ### check if games exists
+ ## with this teams in this round if yes only update
+ game = Game.find_by_round_id_and_team1_id_and_team2_id(
+ @round.id, team1.id, team2.id
+ )
+
+ game_attribs = {
+ score1: scores[0],
+ score2: scores[1],
+ score3: scores[2],
+ score4: scores[3],
+ score5: scores[4],
+ score6: scores[5],
+ play_at: date,
+ knockout: @knockout_flag,
+ group_id: @group.present? ? @group.id : nil
+ }
+
+ game_attribs[ :pos ] = pos if pos.present?
+
+ if game.present?
+ puts "*** update game #{game.id}:"
+ else
+ puts "*** create game:"
+ game = Game.new
+
+ more_game_attribs = {
+ round_id: @round.id,
+ team1_id: team1.id,
+ team2_id: team2.id
+ }
+
+ ## NB: use round.games.count for pos
+ ## lets us add games out of order if later needed
+ more_game_attribs[ :pos ] = @round.games.count+1 if pos.nil?
+
+ game_attribs = game_attribs.merge( more_game_attribs )
+ end
+
+ puts game_attribs.to_json
+
+ game.update_attributes!( game_attribs )
+ end
+
+
+ def parse_fixtures( reader )
+
+ reader.each_line do |line|
+ if is_round?( line )
+ parse_round( line )
+ elsif is_group?( line ) ## NB: group goes after round (round may contain group marker too)
+ parse_group( line )
+ else
+ parse_game( line )
+ end
+ end # lines.each
+
+ @patch_rounds.each do |k,v|
+ puts "*** patch start_at/end_at date for round #{k}:"
+ round = Round.find( k )
+ games = round.games.order( 'play_at asc' ).all
+
+ ## skip rounds w/ no games
+
+ ## todo/fix: what's the best way for checking assoc w/ 0 recs?
+ next if games.size == 0
+
+ round_attribs = {}
+
+ ## todo: check for no records
+ ## e.g. if game[0].present? or just if game[0] ??
+
+ round_attribs[:start_at] = games[0].play_at
+ round_attribs[:end_at ] = games[-1].play_at
+
+ puts round_attribs.to_json
+ round.update_attributes!( round_attribs )
+ end
+
+ end # method parse_fixtures
+
+
+end # class Reader
+end # module SportDB
View
161 lib/sportdb/schema.rb
@@ -0,0 +1,161 @@
+
+module SportDB
+
+class CreateDB ## fix/todo: change to ActiveRecord::Migration why? why not?
+
+
+## make models available in sportdb module by default with namespace
+# e.g. lets you use Team instead of Models::Team
+ include SportDB::Models
+
+
+def self.up
+
+ ActiveRecord::Schema.define do
+
+
+create_table :teams do |t|
+ t.string :title, :null => false
+ t.string :title2
+ t.string :key, :null => false # import/export key
+ t.string :code # make it not null? - three letter code (short title)
+ t.string :synonyms # comma separated list of synonyms
+ t.references :country, :null => false
+ t.references :city # NB: city is optional (should be required for clubs e.g. non-national teams)
+ t.boolean :club, :null => false, :default => false # is it a club (not a national team)?
+ t.boolean :national, :null => false, :default => false # is it a national selection team (not a club)?
+ t.timestamps
+end
+
+add_index :teams, :key, :unique => true
+
+
+create_table :events do |t|
+ t.string :key, :null => false # import/export key
+ t.references :league, :null => false
+ t.references :season, :null => false
+ t.datetime :start_at, :null => false
+ t.datetime :end_at # make it required???
+ t.boolean :team3, :null => false, :default => true ## e.g. Champions League has no 3rd place (only 1st and 2nd/final)
+ t.timestamps
+end
+
+add_index :events, :key, :unique => true
+
+
+create_table :rounds do |t|
+ t.references :event, :null => false
+ t.string :title, :null => false
+ t.string :title2
+ t.integer :pos, :null => false
+ ## add new table stage/stages for grouping rounds in group rounds and playoff rounds, for example???
+ ## # "regular" season (group) games or post-season (playoff) knockouts (k.o's)
+ t.boolean :knockout, :null => false, :default => false
+ t.datetime :start_at, :null => false
+ t.datetime :end_at # todo: make it required e.g. :null => false
+ t.timestamps
+end
+
+add_index :rounds, :event_id # fk event_id index
+
+
+create_table :groups do |t| # Teamgruppe (zB Gruppe A, Gruppe B, etc.)
+ t.references :event, :null => false
+ t.string :title, :null => false
+ t.integer :pos, :null => false
+ t.timestamps
+end
+
+add_index :groups, :event_id # fk event_id index
+
+
+create_table :games do |t|
+ t.references :round, :null => false
+ t.integer :pos, :null => false
+ t.references :group # note: group is optional
+ t.references :team1, :null => false
+ t.references :team2, :null => false
+ t.datetime :play_at, :null => false
+ t.boolean :knockout, :null => false, :default => false
+ t.boolean :home, :null => false, :default => true # is team1 play at home (that is, at its home stadium)
+ t.integer :score1
+ t.integer :score2
+ t.integer :score3 # verlaengerung (opt)
+ t.integer :score4
+ t.integer :score5 # elfmeter (opt)
+ t.integer :score6
+ t.references :next_game # for hinspiel bei rueckspiel in knockout game
+ t.references :prev_game
+ t.string :toto12x # 1,2,X,nil calculate on save
+ t.string :key # import/export key
+ t.timestamps
+end
+
+add_index :games, :key, :unique => true
+add_index :games, :round_id # fk round_id index
+add_index :games, :group_id # fk group_id index
+add_index :games, :next_game_id # fk next_game_id index
+add_index :games, :prev_game_id # fk next_game_id index
+
+
+
+# todo: remove id from join table (without extra fields)? why?? why not??
+create_table :events_teams do |t|
+ t.references :event, :null => false
+ t.references :team, :null => false
+ t.timestamps
+end
+
+add_index :events_teams, [:event_id,:team_id], :unique => true
+add_index :events_teams, :event_id
+
+
+create_table :groups_teams do |t|
+ t.references :group, :null => false
+ t.references :team, :null => false
+ t.timestamps
+end
+
+add_index :groups_teams, [:group_id,:team_id], :unique => true
+add_index :groups_teams, :group_id
+
+
+### todo: add models and some seed data
+
+create_table :seasons do |t| ## also used for years
+ t.string :key, :null => false
+ t.string :title, :null => false # e.g. 2011/12, 2012/13 ### what to do w/ 2012? for world cup etc?
+ t.timestamps
+end
+
+create_table :leagues do |t| ## also for cups/conferences/tournaments/world series/etc.
+ t.string :key, :null => false
+ t.string :title, :null => false # e.g. Premier League, Deutsche Bundesliga, World Cup, Champions League, etc.
+ t.references :country ## optional for now , :null => false ### todo: create "virtual" country for international leagues e.g. use int? or world (ww?)/europe (eu)/etc. similar? already taken??
+ t.boolean :club, :null => false, :default => false # club teams or national teams?
+ ## todo: add t.boolean :national flag? for national teams?
+ ## t.boolean :international, :null => false, :default => false # national league or international?
+ ## t.boolean :cup ## or regular season league??
+ t.timestamps
+end
+
+create_table :badges do |t|
+ t.references :team, :null => false
+ ## todo/fix: use event insead of league+season ??
+ ## t.references :event, :null => false # event => league+season
+ t.references :league, :null => false
+ t.references :season, :null => false
+ t.string :title, :null => false # Meister, Weltmeister, Europameister, Cupsieger, Vize-Meister, Aufsteiger, Absteiger, etc.
+ t.timestamps
+end
+
+ end # block Schema.define
+
+
+ Prop.create!( key: 'db.schema.sport.version', value: SportDB::VERSION )
+
+end # method up
+
+end # class CreateDB
+
+end # module SportDB
View
81 lib/sportdb/templater.rb
@@ -0,0 +1,81 @@
+module SportDB
+
+class Template
+
+ def initialize( path )
+ @path = path
+ end
+
+ def render( binding )
+ ## '<>' means omit newline for lines starting with <% and ending in %>
+ ERB.new( load_template(), 0, '<>' ).result( binding )
+ end
+
+private
+ def load_template
+ puts " Loading template >#{@path}<..."
+ File.read( @path )
+ end
+
+end # class Template
+
+
+class Templater
+
+## make models available in sportdb module by default with namespace
+# e.g. lets you use Team instead of Models::Team
+ include SportDB::Models
+
+
+ def initialize( logger )
+ if logger.nil?
+ @logger = Logger.new(STDOUT)
+ @logger.level = Logger::INFO
+ else
+ @logger = logger
+ end
+ end
+
+ attr_reader :logger
+
+ def run( opts, args )
+
+ ## assume active activerecord connection
+ ##
+
+ args.each do |arg|
+ ## name = File.basename( arg, '.*' )
+ name = arg
+ gen_fixtures( opts.event, name )
+ end
+
+ end # method run
+
+
+ # make props available for template
+ attr_reader :event
+
+ def gen_fixtures( event_key, name )
+
+ @event = Event.find_by_key!( event_key )
+
+ puts "Event #{@event.key} >#{@event.title}<"
+
+ ## todo: honor -o/--output option ??
+
+ dest = "#{name}.rb"
+
+ ## todo: check if path (parent dirs) exits?
+
+ source = "#{SportDB.root}/templates/fixtures.rb.erb"
+
+ puts " Merging template #{source} to #{dest}..."
+
+ out = File.new( dest, 'w+' )
+ out << Template.new( source ).render( binding )
+ out.flush
+ out.close
+ end
+
+end # class Templater
+end # module SportDB
View
243 lib/sportdb/utils.rb
@@ -0,0 +1,243 @@
+
+### some utils moved to worldbdb/utils for reuse
+
+
+module SportDB::FixtureHelpers
+
+ def is_round?( line )
+ line =~ /Spieltag|Runde|Achtelfinale|Viertelfinale|Halbfinale|Finale/
+ end
+
+ def is_group?( line )
+ # NB: check after is_round? (round may contain group reference!)
+ line =~ /Gruppe|Group/
+ end
+
+ def is_knockout_round?( line )
+ if line =~ /Achtelfinale|Viertelfinale|Halbfinale|Spiel um Platz 3|Finale|K\.O\.|Knockout/
+ puts " setting knockout flag to true"
+ true
+ else
+ false
+ end
+ end
+
+ def find_group_title_and_pos!( line )
+ ## group pos - for now support single digit e.g 1,2,3 or letter e.g. A,B,C
+ ## nb: (?:) = is for non-capturing group(ing)
+ regex = /(?:Group|Gruppe)\s+((?:\d{1}|[A-Z]{1}))\b/
+
+ match = regex.match( line )
+
+ return [nil,nil] if match.nil?
+
+ pos = case match[1]
+ when 'A' then 1
+ when 'B' then 2
+ when 'C' then 3
+ when 'D' then 4
+ when 'E' then 5
+ when 'F' then 6
+ when 'G' then 7
+ when 'H' then 8
+ when 'I' then 9
+ when 'J' then 10
+ else match[1].to_i
+ end
+
+ title = match[0]
+
+ puts " title: >#{title}<"
+ puts " pos: >#{pos}<"
+
+ line.sub!( regex, '[GROUP|TITLE+POS]' )
+
+ return [title,pos]
+ end
+
+ def find_round_pos!( line )
+ ## fix/todo:
+ ## if no round found assume last_pos+1 ??? why? why not?
+
+ regex = /\b(\d+)\b/
+
+ if line =~ regex
+ value = $1.to_i
+ puts " pos: >#{value}<"
+
+ line.sub!( regex, '[ROUND|POS]' )
+
+ return value
+ else
+ return nil
+ end
+ end
+
+ def find_date!( line )
+ # extract date from line
+ # and return it
+ # NB: side effect - removes date from line string
+
+ # e.g. 2012-09-14 20:30 => YYYY-MM-DD HH:MM
+ regex_db = /\b(\d{4})-(\d{2})-(\d{2})\s+(\d{2}):(\d{2})\b/
+
+ # e.g. 14.09. 20:30 => DD.MM. HH:MM
+ regex_de = /\b(\d{2})\.(\d{2})\.\s+(\d{2}):(\d{2})\b/
+
+ if line =~ regex_db
+ value = "#{$1}-#{$2}-#{$3} #{$4}:#{$5}"
+ puts " date: >#{value}<"
+
+ ## todo: lets you configure year
+ ## and time zone (e.g. cet, eet, utc, etc.)
+
+ line.sub!( regex_db, '[DATE.DB]' )
+
+ return DateTime.strptime( value, '%Y-%m-%d %H:%M' )
+ elsif line =~ regex_de
+ value = "2012-#{$2}-#{$1} #{$3}:#{$4}"
+ puts " date: >#{value}<"
+
+ ## todo: lets you configure year
+ ## and time zone (e.g. cet, eet, utc, etc.)
+
+ line.sub!( regex_de, '[DATE.DE]' )
+
+ return DateTime.strptime( value, '%Y-%m-%d %H:%M' )
+ else
+ return nil
+ end
+ end
+
+
+ def find_game_pos!( line )
+ # extract optional game pos from line
+ # and return it
+ # NB: side effect - removes pos from line string
+
+ # e.g. (1) - must start line
+ regex = /^[ \t]*\((\d{1,3})\)[ \t]+/
+ if line =~ regex
+ puts " pos: >#{$1}<"
+
+ line.sub!( regex, '[POS] ' )
+ return $1.to_i
+ else
+ return nil
+ end
+
+ end
+
+ def find_scores!( line )
+ # extract score from line
+ # and return it
+ # NB: side effect - removes date from line string
+
+ # e.g. 1:2 or 0:2 or 3:3
+ regex = /\b(\d):(\d)\b/
+
+ # e.g. 1:2nV => overtime
+ regex_ot = /\b(\d):(\d)[ \t]?[nN][vV]\b/
+
+ # e.g. 5:4iE => penalty
+ regex_p = /\b(\d):(\d)[ \t]?[iI][eE]\b/
+
+ scores = []
+
+ if line =~ regex
+ puts " score: >#{$1}-#{$2}<"
+
+ line.sub!( regex, '[SCORE]' )
+
+ scores << $1.to_i
+ scores << $2.to_i
+
+ if line =~ regex_ot
+ puts " score.ot: >#{$1}-#{$2}<"
+
+ line.sub!( regex_ot, '[SCORE.OT]' )
+
+ scores << $1.to_i
+ scores << $2.to_i
+
+ if line =~ regex_p
+ puts " score.p: >#{$1}-#{$2}<"
+
+ line.sub!( regex_p, '[SCORE.P]' )
+
+ scores << $1.to_i
+ scores << $2.to_i
+ end
+ end
+ end
+ scores
+ end # methdod find_scores!
+
+
+ def find_team_worker!( line, index )
+ regex = /@@oo([^@]+?)oo@@/ # e.g. everything in @@ .... @@ (use non-greedy +? plus all chars but not @, that is [^@])
+
+ if line =~ regex
+ value = "#{$1}"
+ puts " team#{index}: >#{value}<"
+
+ line.sub!( regex, "[TEAM#{index}]" )
+
+ return $1
+ else
+ return nil
+ end
+ end
+
+ def find_teams!( line )
+ counter = 1
+ teams = []
+
+ team = find_team_worker!( line, counter )
+ while team.present?
+ teams << team
+ counter += 1
+ team = find_team_worker!( line, counter )
+ end
+
+ teams
+ end
+
+ def find_team1!( line )
+ find_team_worker!( line, 1 )
+ end
+
+ def find_team2!( line )
+ find_team_worker!( line, 2 )
+ end
+
+
+ def match_team_worker!( line, key, values )
+ values.each do |value|
+ ## nb: \b does NOT include space or newline for word boundry (only alphanums e.g. a-z0-9)
+ ## (thus add it, allows match for Benfica Lis. for example - note . at the end)
+
+ ## check add $ e.g. (\b| |\t|$) does this work? - check w/ Benfica Lis.$
+ regex = /\b#{value}(\b| |\t|$)/ # wrap with world boundry (e.g. match only whole words e.g. not wac in wacker)
+ if line =~ regex
+ puts " match for team >#{key}< >#{value}<"
+ # make sure @@oo{key}oo@@ doesn't match itself with other key e.g. wacker, wac, etc.
+ line.sub!( regex, "@@oo#{key}oo@@ " ) # NB: add one space char at end
+ return true # break out after first match (do NOT continue)
+ end
+ end
+ return false
+ end
+
+ ## todo/fix: pass in known_teams as a parameter? why? why not?
+ def match_teams!( line )
+ @known_teams.each do |rec|
+ key = rec[0]
+ values = rec[1]
+ match_team_worker!( line, key, values )
+ end # each known_teams
+ end # method translate_teams!
+
+
+end # module SportDB::FixtureHelpers
+
View
4 lib/sportdb/version.rb
@@ -0,0 +1,4 @@
+
+module SportDB
+ VERSION = '0.9.2'
+end
View
51 templates/fixtures.rb.erb
@@ -0,0 +1,51 @@
+# encoding: utf-8
+
+####################################################################
+# generiert am <%= Time.now %>
+# using <%= SportDB.banner %>
+####################################################################
+
+
+###########################################
+# <%= event.title %>
+
+
+<%# todo: use proper key from event for variable e.g. pl or similar (strip season, etc.) %>
+
+ev = Event.find_by_key!( '<%= event.key %>' )
+
+<% event.teams.each do |team| %>
+<%= '%-18s' % team.key %> = Team.find_by_key!( '<%= team.key %>' )
+<% end %>
+
+
+<% event.rounds.each do |round| %>
+<%= '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') %>'))
+<% end %>
+
+
+<% event.rounds.each do |round| %>
+<%= 'games%02d' % round.pos %> = [
+<% round.games.each_with_index do |game| %>
+ [ <%= '%3d' % game.pos %>, <%= '%-18s' % "#{game.team1.key}," %>
+ [
+ <%= "#{game.score1},#{game.score2}" if game.score1.present? && game.score2.present? %>
+ <%= ",#{game.score3},#{game.score4}" if game.score3.present? && game.score4.present? %>
+ <%= ",#{game.score5},#{game.score6}" if game.score5.present? && game.score6.present? %>
+ ],
+ <%= '%-18s' % "#{game.team2.key}," %> Time.utc('<%= game.play_at.strftime('%Y-%m-%d %H:%M') %>')
+ , <%= game.knockout %>
+ ],
+<%# does comma for last entry matter? ruby ignores it for sure? check %>
+<% end %>
+]
+
+<% end %>
+
+
+<% event.rounds.each do |round| %>
+Game.create_from_ary!( <%= 'games%02d' % round.pos %>, <%= 'r%02d' % round.pos %> )
+<% end %>
+
+<%# todo: what to put for value??? %>
+Prop.create!( key: 'db.<%= event.key %>.fixtures.version', value: '1' )
Please sign in to comment.
Something went wrong with that request. Please try again.