module Writer

setup empty leagues (info) hash

Constants

LEAGUES
SOURCES

Public Class Methods

build_stage( matches_by_stage, stages:, name:, lang: ) click to toggle source

def prepare_stages( stages )

if stages.is_a?( Array )
   if stages[0].is_a?( Array )  ## is array of array
     ## convert inner array shortcuts to hash - stage input is same as stage output
     stages.map {|ary| ary.reduce({}) {|h,stage| h[stage]=stage; h }}
   elsif stages[0].is_a?( Hash )  ## assume array of hashes
     stages  ## pass through as is ("canonical") format!!!
   else ## assume array of strings
    ## assume single array shortcut; convert to hash - stage input is same as stage output name
    stages = stages.reduce({}) {|h,stage| h[stage]=stage; h }
    [stages]  ## return hash wrapped in array
   end
else  ## assume (single) hash
  [stages] ## always return array of hashes
end

end

end

# File lib/sportdb/writers/write.rb, line 337
def self.build_stage( matches_by_stage, stages:, name:, lang: )
  buf = String.new('')

  ## note: allow convenience shortcut - assume stage_in is stage_out - auto-convert
  stages = stages.reduce({}) {|h,stage| h[stage]=stage; h }   if stages.is_a?( Array )

  stages.each_with_index do |(stage_in, stage_out),i|
    matches = matches_by_stage[ stage_in ]   ## todo/fix: report error if no matches found!!!

    next if matches.nil? || matches.empty?

    ## (auto-)sort matches by
    ##  1) date
    matches = matches.sort do |l,r|
      result = l.date  <=> r.date
      result
    end

    buf << "\n\n"   if i > 0 && buf.size > 0

    buf << "= #{name}, #{stage_out}\n"
    buf << SportDb::TxtMatchWriter.build( matches, lang: lang )

    puts buf
  end

  buf
end
config() click to toggle source
# File lib/sportdb/writers/config.rb, line 16
def self.config()  @config ||= Configuration.new;  end
configure() { |config| ... } click to toggle source

lets you use

Write.configure do |config|
   config.out_dir = "??"
end
# File lib/sportdb/writers/config.rb, line 14
def self.configure()  yield( config ); end
eng1( season ) click to toggle source

England

# File lib/sportdb/leagues/leagues_eng.rb, line 7
def self.eng1( season )
  case season    ## todo/fix: - use cast e.g. Season(season) - make sure it's a season obj
  when Season('1888/89')..Season('1891/92') ## single league (no divisions)
    {name:     'English Football League',
     basename: '1-footballleague'}
  when Season('1892/93')..Season('1991/92')  ## start of division 1 & 2
    {name:     'English Division One',
     basename: '1-division1'}
  else  ## starts in season 1992/93
    {name:     'English Premier League',
     basename: '1-premierleague'}
  end
end
eng2( season ) click to toggle source
# File lib/sportdb/leagues/leagues_eng.rb, line 21
def self.eng2( season )
  case season
  when Season('1892/93')..Season('1991/92')
    {name:     'English Division Two',  ## or use English Football League Second Division ???
     basename: '2-division2'}
  when Season('1992/93')..Season('2003/04')   ## start of premier league
    {name:     'English Division One',
     basename: '2-division1'}
  else # starts in 2004/05
    {name:     'English Championship',  ## rebranding divsion 1 => championship
     basename: '2-championship'}
  end
end
merge_goals( matches, goals ) click to toggle source
# File lib/sportdb/writers/write.rb, line 23
def self.merge_goals( matches, goals )
  goals_by_match = goals.group_by { |rec| rec.match_id }
  puts "match goal reports - #{goals_by_match.size} records"

  ## lets group by date for easier lookup
  matches_by_date = matches.group_by { |rec| rec.date }


  ## note: "shadow / reuse" matches and goals vars for now in loop
  ##  find better names to avoid confusion!!
  goals_by_match.each_with_index do |(match_id, goals),i|
    ## split match_id
    team_str, more_str   = match_id.split( '|' )
    team1_str, team2_str = team_str.split( ' - ' )

    more_str  = more_str.strip
    team1_str = team1_str.strip
    team2_str = team2_str.strip

    ## for now assume date in more (and not round or something else)
    date_str = more_str  # e.g. in 2019-07-26 format

    puts ">#{team1_str}< - >#{team2_str}< | #{date_str},    #{goals.size} goals"

    ## try a join - find matching match
    matches = matches_by_date[ date_str ]
    if matches.nil?
      puts "!! ERROR: no match found for date >#{date_str}<"
      exit 1
    end

    found_matches = matches.select {|match| match.team1 == team1_str &&
                                            match.team2 == team2_str }

    if found_matches.size == 1
      match = found_matches[0]
      match.goals = SportDb::Import::Goal.build( goals )
    else
      puts "!!! ERROR: found #{found_matches.size} in #{matches.size} matches for date >#{date_str}<:"
      matches.each do |match|
        puts "  >#{match.team1}< - >#{match.team2}<"
      end
      exit 1
    end
  end
end
normalize( matches, league:, season: nil ) click to toggle source

helpers

 normalize team names

todo/fix:  for reuse move to sportdb-catalogs
              use normalize  - add to module/class ??

 todo/fix: check league - if is national_team or clubs or intl etc.!!!!
# File lib/sportdb/writers/write.rb, line 83
def self.normalize( matches, league:, season: nil )
    league = SportDb::Import.catalog.leagues.find!( league )
    country = league.country

    ## todo/fix: cache name lookups - why? why not?
    matches.each do |match|
       team1 = SportDb::Import.catalog.clubs.find_by!( name: match.team1,
                                                       country: country )
       team2 = SportDb::Import.catalog.clubs.find_by!( name: match.team2,
                                                       country: country )

       if season
         team1_name = team1.name_by_season( season )
         team2_name = team2.name_by_season( season )
       else
         team1_name = team1.name
         team2_name = team2.name
       end

       puts "#{match.team1} => #{team1_name}"  if match.team1 != team1_name
       puts "#{match.team2} => #{team2_name}"  if match.team2 != team2_name

       match.update( team1: team1_name )
       match.update( team2: team2_name )
    end
    matches
end
split_matches( matches, season: ) click to toggle source
# File lib/sportdb/writers/write.rb, line 114
def self.split_matches( matches, season: )
  matches_i  = []
  matches_ii = []
  matches.each do |match|
    date = Date.strptime( match.date, '%Y-%m-%d' )
    if date.year == season.start_year
      matches_i << match
    elsif date.year == season.end_year
      matches_ii << match
    else
      puts "!! ERROR: match date-out-of-range for season:"
      pp season
      pp date
      pp match
      exit 1
    end
  end
  [matches_i, matches_ii]
end
write( league, season, source:, extra: nil, split: false, normalize: true, rounds: true ) click to toggle source
# File lib/sportdb/writers/write.rb, line 149
def self.write( league, season, source:,
                                extra: nil,
                                split: false,
                                normalize: true,
                                rounds: true )
  season = Season( season )  ## normalize season

  league_info = LEAGUES[ league ]
  if league_info.nil?
    puts "!! ERROR - no league found for >#{league}<; sorry"
    exit 1
  end

  ## check - if source is directory (assume if starting ./ or ../ or /)
  if source.start_with?( './')  ||
     source.start_with?( '../') ||
     source.start_with?( '/')
     ## check if directory exists
     unless File.exist?( source )
       puts "!! ERROR: source dir >#{source}< does not exist"
       exit 1
     end
     source_info = { path: source }   ## wrap in "plain" source dir in source info
  else
    source_info = SOURCES[ source ]
    if source_info.nil?
      puts "!! ERROR - no source found for >#{source}<; sorry"
      exit 1
    end
  end

  source_path = source_info[:path]

  ## format lets you specify directory layout
  ##   default   = 1888-89
  ##   century   = 1800s/1888-89
  ##   ...
  season_path = season.to_path( (source_info[:format] || 'default').to_sym )
  in_path = "#{source_path}/#{season_path}/#{league}.csv"   # e.g. ../stage/one/2020/br.1.csv


  matches = SportDb::CsvMatchParser.read( in_path )
  puts "matches- #{matches.size} records"


  ## check for goals
  in_path_goals = "#{source_path}/#{season_path}/#{league}~goals.csv"   # e.g. ../stage/one/2020/br.1~goals.csv
  if File.exist?( in_path_goals )
    goals = SportDb::CsvGoalParser.read( in_path_goals )
    puts "goals - #{goals.size} records"
    pp goals[0]

    puts
    puts "merge goals:"
    merge_goals( matches, goals )
  end


  pp matches[0]


  matches = normalize( matches, league: league, season: season )   if normalize



  league_name  = league_info[ :name ]      # e.g. Brasileiro Série A
  basename     = league_info[ :basename]   #.e.g  1-seriea

  league_name =  league_name.call( season )   if league_name.is_a?( Proc )  ## is proc/func - name depends on season
  basename    =  basename.call( season )      if basename.is_a?( Proc )  ## is proc/func - name depends on season

  lang         = league_info[ :lang ] || 'en_AU'  ## default / fallback to en_AU (always use rounds NOT matchday for now)
  repo_path    = league_info[ :path ]      # e.g. brazil or world/europe/portugal etc.


  season_path = String.new('')    ## note: allow extra path for output!!!! e.g. archive/2000s etc.
  season_path << "#{extra}/"   if extra
  season_path << season.path


  ## check for stages
  stages = league_info[ :stages ]
  stages = stages.call( season )    if stages.is_a?( Proc )  ## is proc/func - stages depends on season


  if stages

  ## split into four stages / two files
  ## - Grunddurchgang
  ## - Finaldurchgang - Meister
  ## - Finaldurchgang - Qualifikation
  ## - Europa League Play-off

  matches_by_stage = matches.group_by { |match| match.stage }
  pp matches_by_stage.keys


  ## stages = prepare_stages( stages )
  pp stages


  romans = %w[I II III IIII V VI VII VIII VIIII X XI]  ## note: use "simple" romans without -1 rule e.g. iv or ix

  stages.each_with_index do |stage, i|

    ## assume "extended" style / syntax
    if stage.is_a?( Hash ) && stage.has_key?( :names )
      stage_names    = stage[ :names ]
      stage_basename = stage[ :basename ]
      ## add search/replace {basename} - why? why not?
      stage_basename = stage_basename.sub( '{basename}', basename )
    else  ## assume simple style (array of strings OR hash mapping of string => string)
      stage_names    = stage
      stage_basename =  if stages.size == 1
                            "#{basename}"  ## use basename as is 1:1
                         else
                            "#{basename}-#{romans[i].downcase}"  ## append i,ii,etc.
                         end
    end

    buf = build_stage( matches_by_stage, stages: stage_names,
                                         name: "#{league_name} #{season.key}",
                                         lang: lang )

    ## note: might be empty!!! if no matches skip (do NOT write)
    write_buf( "#{config.out_dir}/#{repo_path}/#{season_path}/#{stage_basename}.txt", buf )   unless buf.empty?
  end
  else  ## no stages - assume "regular" plain vanilla season

## always (auto-) sort for now - why? why not?
matches = matches.sort do |l,r|
  ## first by date (older first)
  ## next by matchday (lower first)
  res =   l.date <=> r.date
  res =   l.time <=> r.time     if res == 0 && l.time && r.time
  res =   l.round <=> r.round   if res == 0 && rounds
  res
end

  if split
    matches_i, matches_ii = split_matches( matches, season: season )

    out_path = "#{config.out_dir}/#{repo_path}/#{season_path}/#{basename}-i.txt"

    SportDb::TxtMatchWriter.write( out_path, matches_i,
                                   name: "#{league_name} #{season.key}",
                                   lang:  lang,
                                   rounds: rounds )

    out_path = "#{config.out_dir}/#{repo_path}/#{season_path}/#{basename}-ii.txt"

    SportDb::TxtMatchWriter.write( out_path, matches_ii,
                                   name: "#{league_name} #{season.key}",
                                   lang:  lang,
                                   rounds: rounds )
  else
    out_path = "#{config.out_dir}/#{repo_path}/#{season_path}/#{basename}.txt"

    SportDb::TxtMatchWriter.write( out_path, matches,
                                   name: "#{league_name} #{season.key}",
                                   lang:  lang,
                                   rounds: rounds )
  end
  end
end
write_buf( path, buf ) click to toggle source

todo/check: use Writer.open() or FileWriter.open() or such - why? why not?

# File lib/sportdb/writers/write.rb, line 138
def self.write_buf( path, buf )  ## write buffer helper
  ## for convenience - make sure parent folders/directories exist
  FileUtils.mkdir_p( File.dirname( path ))  unless Dir.exist?( File.dirname( path ))

  File.open( path, 'w:utf-8' ) do |f|
    f.write( buf )
  end
end