lib/sportdb/reader.rb in sportdb-0.9.7 vs lib/sportdb/reader.rb in sportdb-1.0.0
- old
+ new
@@ -1,87 +1,296 @@
# encoding: utf-8
module SportDB
+class Reader
-##
-## fix/todo: move to/merge into LineReader itself
+## make models available in sportdb module by default with namespace
+# e.g. lets you use Team instead of Models::Team
+ include SportDB::Models
-class StringLineReader
- def initialize( logger=nil, data )
+ def initialize( logger=nil )
if logger.nil?
- @logger = Logger.new(STDOUT)
- @logger.level = Logger::INFO
+ @logger = LogUtils::Logger.new
+ ## @logger = Logger.new(STDOUT)
+ ## @logger.level = Logger::INFO
else
@logger = logger
end
-
- @data = data
end
attr_reader :logger
+ def load_setup_with_include_path( setup, include_path )
+ ary = load_fixture_setup_with_include_path( setup, include_path )
+ load_with_include_path( ary, include_path )
+ end # method load_setup_with_include_path
- def each_line
- @data.each_line do |line|
-
- if line =~ /^\s*#/
- # skip komments and do NOT copy to result (keep comments secret!)
- logger.debug 'skipping comment line'
- next
+
+ ## fix/todo: rename ??
+ def load_fixture_setup_with_include_path( name, include_path )
+
+ ## todo/fix: cleanup quick and dirty code
+
+ path = "#{include_path}/#{name}.yml"
+
+ logger.info "parsing data '#{name}' (#{path})..."
+
+ text = File.read_utf8( path )
+
+ hash = YAML.load( text )
+
+ ### build up array for fixtures from hash
+
+ ary = []
+
+ hash.each do |key_wild, value_wild|
+ key = key_wild.to_s.strip
+
+ logger.debug "yaml key:#{key_wild.class.name} >>#{key}<<, value:#{value_wild.class.name} >>#{value_wild}<<"
+
+ if value_wild.kind_of?( String ) # assume non-event data
+ ary << value_wild
+ elsif value_wild.kind_of?( Array ) # assume non_event data as array of strings
+ ary = ary + value_wild
+ elsif value_wild.kind_of?( Hash ) # assume event data
+
+ value_wild.each do |event_key, event_values|
+ # e.g.
+ # at.2012/13: at/2012_13/bl, at/2012_13/bl2
+ # becomes
+ # [ 'at.2012/13', 'at/2012_13/bl', 'at/2012_13/bl2' ]
+ ary << ( [ event_key.to_s ] + event_values.split(',') )
+ end
+
+ else
+ logger.error "unknow fixture type in setup (yaml key:#{key_wild.class.name} >>#{key}<<, value:#{value_wild.class.name} >>#{value_wild}<<); skipping"
end
+
+ end
+
+ puts "[debug] fixture setup:"
+ pp ary
+
+ ary
+
+ end # load_fixture_setup_with_include_path
+
+
+ def load_with_include_path( ary, include_path ) # convenience helper for all-in-one reader
+
+ puts "[debug] enter load_with_include_path (include_path=>>#{include_path}<<):"
+ pp ary
+
+ ary.each do |rec|
+ if rec.kind_of?( String )
+ ## assume single fixture name
+ name = rec
- if line =~ /^\s*$/
- # kommentar oder leerzeile überspringen
- logger.debug 'skipping blank line'
- next
+ if name =~ /^seasons/
+ load_seasons_with_include_path( name, include_path )
+ elsif name =~ /^leagues/
+ if name =~ /club/
+ # e.g. leagues_club
+ load_leagues_with_include_path( name, include_path, { club: true } )
+ else
+ # e.g. leagues
+ load_leagues_with_include_path( name, include_path )
+ end
+ elsif name =~ /^([a-z]{2})\/teams/
+ # auto-add country code (from folder structure) for country-specific teams
+ # e.g. at/teams at/teams2 de/teams etc.
+ country_key = $1
+ country = Country.find_by_key!( country_key )
+ load_teams_with_include_path( name, include_path, { club: true, country_id: country.id } )
+ elsif name =~ /\/teams/
+ if name =~ /club/
+ # club teams (many countries)
+ # e.g. club/europe/teams
+ load_teams_with_include_path( name, include_path, { club: true } )
+ else
+ # assume national teams
+ # e.g. world/teams amercia/teams_n
+ load_teams_with_include_path( name, include_path, { national: true } )
+ end
+ else
+ logger.error "unknown sportdb fixture type >#{name}<"
+ # todo/fix: exit w/ error
+ end
+ else # more than one item in record? assume fixture starting w/ event key
+
+ # assume first item is key
+ # assume second item is event plus fixture
+ # assume option third,etc are fixtures (e.g. bl2, etc.)
+ event_key = rec[0] # e.g. at.2012/13
+ event_name = rec[1] # e.g. at/2012_13/bl
+ fixture_names = rec[1..-1] # e.g. at/2012_13/bl, at/2012_13/bl2
+
+ load_event_with_include_path( event_name, include_path )
+ fixture_names.each do |fixture_name|
+ load_fixtures_with_include_path( event_key, fixture_name, include_path )
+ end
end
+
+ end # each ary
+ end # method load_with_include_path
- # remove leading and trailing whitespace
- line = line.strip
-
- yield( line )
- end # each lines
- end # method each_line
-end
+ def load_leagues_with_include_path( name, include_path, more_values={} )
+
+ path = "#{include_path}/#{name}.txt"
+ logger.info "parsing data '#{name}' (#{path})..."
-class Reader
+ reader = ValuesReader.new( logger, path, more_values )
-## make models available in sportdb module by default with namespace
-# e.g. lets you use Team instead of Models::Team
- include SportDB::Models
+ load_leagues_worker( reader )
+
+ ### todo/fix: add prop
+ ### Prop.create!( key: "db.#{fixture_name_to_prop_key(name)}.version", value: "file.txt.#{File.mtime(path).strftime('%Y.%m.%d')}" )
+
+ end # load_leagues_with_include_path
- def initialize( logger=nil )
- if logger.nil?
- @logger = Logger.new(STDOUT)
- @logger.level = Logger::INFO
- else
- @logger = logger
- end
- end
+ def load_seasons_with_include_path( name, include_path )
+ path = "#{include_path}/#{name}.yml"
- attr_reader :logger
+ puts "*** parsing data '#{name}' (#{path})..."
- def run( opts, args )
-
- args.each do |arg|
- name = arg # File.basename( arg, '.*' )
+ reader = HashReader.new( logger, path )
- if opts.load?
- load_fixtures_builtin( opts.event, name )
+ reader.each_typed do |key, value|
+
+ ## puts "processing event attrib >>#{key}<< >>#{value}<<..."
+
+ if key == 'seasons'
+
+ puts "#{value.class.name}: >>#{value}<<"
+
+ ## nb: assume value is an array
+ value.each do |item|
+ season_attribs = {}
+
+ season = Season.find_by_key( item.to_s.strip )
+
+ ## check if it exists
+ if season.present?
+ puts "*** update season #{season.id}-#{season.key}:"
+ else
+ puts "*** create season:"
+ season = Season.new
+ season_attribs[ :key ] = item.to_s.strip
+ end
+
+ season_attribs[:title] = item.to_s.strip
+
+ puts season_attribs.to_json
+
+ season.update_attributes!( season_attribs )
+ end
+
else
- load_fixtures_with_include_path( opts.event, name, opts.data_path )
+ logger.error "unknown seasons key; skipping"
end
- end
+
+ end # each key,value
+
+ ### todo/fix: add prop
+ ### Prop.create_from_sportdb_fixture!( name, path )
+
+ end # load_seasons_with_include_path
- end
+ def load_event_with_include_path( name, include_path )
+ path = "#{include_path}/#{name}.yml"
+
+ logger.info "parsing data '#{name}' (#{path})..."
+
+ reader = HashReader.new( logger, path )
+
+ event_attribs = {}
+
+ reader.each_typed do |key, value|
+
+ ## puts "processing event attrib >>#{key}<< >>#{value}<<..."
+
+ if key == 'league'
+ league = League.find_by_key( value.to_s.strip )
+
+ ## check if it exists
+ if league.present?
+ event_attribs['league_id'] = league.id
+ else
+ logger.error "league with key >>#{value.to_s.strip}<< missing"
+ exit 1
+ end
+
+ elsif key == 'season'
+ season = Season.find_by_key( value.to_s.strip )
+
+ ## check if it exists
+ if season.present?
+ event_attribs['season_id'] = season.id
+ else
+ logger.error "season with key >>#{value.to_s.strip}<< missing"
+ exit 1
+ end
+
+ elsif key == 'start_at'
+
+ if value.is_a?(DateTime) || value.is_a?(Date)
+ start_at = value
+ else # assume it's a string
+ start_at = DateTime.strptime( value.to_s.strip, '%Y-%m-%d %H:%M' )
+ end
+
+ event_attribs['start_at'] = start_at
+
+ elsif key == 'teams'
+
+ ## assume teams value is an array
+
+ team_ids = []
+ value.each do |item|
+ team_key = item.to_s.strip
+ team = Team.find_by_key!( team_key )
+ team_ids << team.id
+ end
+
+ event_attribs['team_ids'] = team_ids
+
+ elsif key == 'team3'
+ ## for now always assume false # todo: fix - use value and convert to boolean if not boolean
+ event_attribs['team3'] = false
+ else
+ ## todo: add a source location struct to_s or similar (file, line, col)
+ logger.error "unknown event attrib; skipping attrib"
+ end
+
+ end # each key,value
+
+ event = Event.find_by_league_id_and_season_id( event_attribs['league_id'], event_attribs['season_id'])
+
+ ## check if it exists
+ if event.present?
+ logger.debug "*** update event #{event.id}-#{event.key}:"
+ else
+ logger.debug "*** create event:"
+ event = Event.new
+ end
+
+ puts event_attribs.to_json
+
+ event.update_attributes!( event_attribs )
+
+ ### todo/fix: add prop
+
+ end # load_event_with_include_path
+
+
def load_fixtures_from_string( event_key, text ) # load from string (e.g. passed in via web form)
## todo/fix: move code into LineReader e.g. use LineReader.fromString() - why? why not?
reader = StringLineReader.new( logger, text )
@@ -90,78 +299,113 @@
## fix add prop
### 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_with_include_path( event_key, name, include_path ) # load from file system
+
path = "#{include_path}/#{name}.txt"
puts "*** parsing data '#{name}' (#{path})..."
+
+ SportDB.lang.lang = LangChecker.new.analyze( name, include_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')}" )
+ ## fix add prop
+ ## 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.data_path}/#{name}.txt"
+ def load_teams_with_include_path( name, include_path, more_values={} )
+ path = "#{include_path}/#{name}.txt"
+
puts "*** parsing data '#{name}' (#{path})..."
- reader = LineReader.new( logger, path )
+ reader = ValuesReader.new( logger, path, more_values )
- load_fixtures_worker( event_key, reader )
+ load_teams_worker( reader )
- Prop.create!( key: "db.#{fixture_name_to_prop_key(name)}.version", value: "sport.txt.#{SportDB::VERSION}" )
- end
+ ## todo/fix: add prop
+ ## Prop.create!( key: "db.#{fixture_name_to_prop_key(name)}.version", value: "sport.txt.#{SportDB::VERSION}" )
+ end # load_teams_with_include_path
+private
- def load_teams_builtin( name, more_values={} )
- path = "#{SportDB.data_path}/#{name}.txt"
+ include SportDB::FixtureHelpers
- puts "*** parsing data '#{name}' (#{path})..."
+ def load_leagues_worker( reader )
- reader = ValuesReader.new( logger, path, more_values )
+ reader.each_line do |attribs, values|
- load_teams_worker( reader )
-
- Prop.create!( key: "db.#{fixture_name_to_prop_key(name)}.version", value: "sport.txt.#{SportDB::VERSION}" )
- end
+ ## check optional values
+ values.each_with_index do |value, index|
+ if value =~ /^club$/ # club flag
+ attribs[ :club ] = true
+ 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 ??
+ ## assume title2 if title2 is empty (not already in use)
+ ## and if it title2 contains at least two letter e.g. [a-zA-Z].*[a-zA-Z]
+ # issue warning: unknown type for value
+ logger.warn "unknown type for value >#{value}<"
+ end
+ end
+ rec = League.find_by_key( attribs[ :key ] )
+ if rec.present?
+ logger.debug "update League #{rec.id}-#{rec.key}:"
+ else
+ logger.debug "create League:"
+ rec = League.new
+ end
+
+ puts attribs.to_json
+
+ rec.update_attributes!( attribs )
-private
+ end # each lines
- include SportDB::FixtureHelpers
+ end # load_leagues_worker
+
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
+ value_city = City.find_by_key( value_city_key )
+ if value_city.present?
+ attribs[ :city_id ] = value_city.id
+ else
+ ## todo/fix: add strict mode flag - fail w/ exit 1 in strict mode
+ logger.warn "city with key #{value_city_key} missing"
+ ## todo: log errors to db log???
+ end
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}<"
+ logger.warn "unknown type for value >#{value}<"
end
end
rec = Team.find_by_key( attribs[ :key ] )
if rec.present?
- puts "*** update Team #{rec.id}-#{rec.key}:"
+ logger.debug "update Team #{rec.id}-#{rec.key}:"
else
- puts "*** create Team:"
+ logger.debug "create Team:"
rec = Team.new
end
puts attribs.to_json
@@ -181,38 +425,38 @@
@round = nil
@event = Event.find_by_key!( event_key )
- puts "Event #{@event.key} >#{@event.title}<"
+ logger.info "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}<"
+ logger.debug "parsing group line: >#{line}<"
match_teams!( line )
team_keys = find_teams!( line )
title, pos = find_group_title_and_pos!( line )
- puts " line: >#{line}<"
+ logger.debug " 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}:"
+ logger.debug "update group #{@group.id}:"
else
- puts "*** create group:"
+ logger.debug "create group:"
@group = Group.new
group_attribs = group_attribs.merge( {
event_id: @event.id,
pos: pos
})
@@ -224,17 +468,17 @@
@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})"
+ logger.debug " adding team #{team.title} (#{team.code})"
@group.teams << team
end
end
def parse_round( line )
- puts "parsing round line: >#{line}<"
+ logger.debug "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 )
@@ -243,10 +487,10 @@
@group = Group.find_by_event_id_and_pos!( @event.id, group_pos )
else
@group = nil # reset group to no group
end
- puts " line: >#{line}<"
+ logger.debug " line: >#{line}<"
## NB: dummy/placeholder start_at, end_at date
## replace/patch after adding all games for round
round_attribs = {