sportdb-readers 0.5.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -2,120 +2,151 @@
2
2
 
3
3
  module SportDb
4
4
 
5
- class MatchReaderV2 ## todo/check: rename to MatchReaderV2 (use plural?) why? why not?
6
-
7
- def self.config() Import.config; end
8
-
9
-
5
+ class MatchReader ## todo/check: rename to MatchReaderV2 (use plural?) why? why not?
10
6
 
11
7
  def self.read( path, season: nil ) ## use - rename to read_file or from_file etc. - why? why not?
12
- txt = File.open( path, 'r:utf-8' ).read
8
+ txt = File.open( path, 'r:utf-8' ) {|f| f.read }
13
9
  parse( txt, season: season )
14
10
  end
15
11
 
16
12
  def self.parse( txt, season: nil )
17
- recs = LeagueOutlineReader.parse( txt, season: season )
18
- pp recs
13
+ new( txt ).parse( season: season )
14
+ end
15
+
19
16
 
20
- recs.each do |rec|
21
- league = Sync::League.find_or_create( rec[:league] )
22
- season = Sync::Season.find_or_create( rec[:season] )
17
+ include Logging
18
+
19
+ def initialize( txt )
20
+ @txt = txt
21
+ end
22
+
23
+ def parse( season: nil )
24
+ secs = LeagueOutlineReader.parse( @txt, season: season )
25
+ pp secs
26
+
27
+
28
+ ###
29
+ ## todo/check/fix: move to LeagueOutlineReader for (re)use - why? why not?
30
+ ## use sec[:lang] or something?
31
+ langs = { ## map country keys to lang codes
32
+ 'de' => 'de', ## de - Deutsch (German)
33
+ 'at' => 'de',
34
+ 'fr' => 'fr', ## fr - French
35
+ 'it' => 'it', ## it - Italian
36
+ 'es' => 'es', ## es - Español (Spanish)
37
+ 'mx' => 'es',
38
+ 'pt' => 'pt', ## pt - Português (Portuguese)
39
+ 'br' => 'br'
40
+ }
41
+
42
+ secs.each do |sec| ## sec(tion)s
43
+ season = sec[:season]
44
+ league = sec[:league]
45
+ stage = sec[:stage]
46
+ lines = sec[:lines]
23
47
 
24
48
  ## hack for now: switch lang
25
- if ['de', 'at'].include?( league.country.key )
26
- SportDb.lang.lang = 'de'
27
- DateFormats.lang = 'de'
28
- elsif ['fr'].include?( league.country.key )
29
- SportDb.lang.lang = 'fr'
30
- DateFormats.lang = 'fr'
31
- elsif ['it'].include?( league.country.key )
32
- SportDb.lang.lang = 'it'
33
- DateFormats.lang = 'it'
34
- elsif ['es', 'mx'].include?( league.country.key )
35
- SportDb.lang.lang = 'es'
36
- DateFormats.lang = 'es'
37
- elsif ['pt', 'br'].include?( league.country.key )
38
- SportDb.lang.lang = 'pt'
39
- DateFormats.lang = 'pt'
40
- else
41
- SportDb.lang.lang = 'en'
42
- DateFormats.lang = 'en'
49
+ ## todo/fix: set lang for now depending on league country!!!
50
+ if league.intl? ## todo/fix: add intl? to ActiveRecord league!!!
51
+ Import.config.lang = 'en'
52
+ else ## assume national/domestic
53
+ Import.config.lang = langs[ league.country.key ] || 'en'
43
54
  end
44
55
 
45
56
 
46
- ## todo/fix:
47
- ## always auto create
48
- ## 1) check for clubs count on event/stage - only if count == 0 use autoconf!!!
49
- ## 2) add lang switch for date/lang too!!!!
57
+ start = if season.year?
58
+ Date.new( season.start_year, 1, 1 )
59
+ else
60
+ Date.new( season.start_year, 7, 1 )
61
+ end
50
62
 
51
- event = Sync::Event.find_or_create( league: league, season: season )
63
+ auto_conf_teams, _ = AutoConfParser.parse( lines,
64
+ start: start )
52
65
 
53
- stage = if rec[:stage]
54
- Sync::Stage.find_or_create( rec[:stage], event: event )
55
- else
56
- nil
57
- end
66
+ ## step 1: map/find teams
58
67
 
68
+ ## note: loop over keys (holding the names); values hold the usage counter!! e.g. 'Arsenal' => 2, etc.
69
+ mods = nil
70
+ if league.clubs? && league.intl? ## todo/fix: add intl? to ActiveRecord league!!!
71
+ ### quick hack mods for popular/known ambigious club names
72
+ ## todo/fix: make more generic / reuseable!!!!
73
+ mods = {}
74
+ ## europa league uses same mods as champions league
75
+ mods[ 'uefa.el' ] = mods[ 'uefa.cl' ] = catalog.clubs.build_mods(
76
+ { 'Liverpool | Liverpool FC' => 'Liverpool FC, ENG',
77
+ 'Arsenal | Arsenal FC' => 'Arsenal FC, ENG',
78
+ 'Barcelona' => 'FC Barcelona, ESP',
79
+ 'Valencia' => 'Valencia CF, ESP' })
80
+ end
59
81
 
60
- auto_conf_clubs, _ = AutoConfParser.parse( rec[:lines],
61
- start: event.start_at )
82
+ teams = catalog.teams.find_by!( name: auto_conf_teams.keys,
83
+ league: league,
84
+ mods: mods )
62
85
 
63
- ## step 1: map/find clubs
64
- club_recs = [] ## array of struct records
65
- club_mapping = {} ## name => database (ActiveRecord) record
86
+ ## build mapping - name => team struct record
87
+ team_mapping = auto_conf_teams.keys.zip( teams ).to_h
66
88
 
67
- ## note: loop over keys (holding the names); values hold the usage counter!! e.g. 'Arsenal' => 2, etc.
68
- country = league.country
69
- auto_conf_clubs.keys.each do |name|
70
- club_rec = config.clubs.find_by!( name: name, country: country )
71
- club_recs << club_rec
72
89
 
73
- club = Sync::Club.find_or_create( club_rec )
74
- club_mapping[ name ] = club
75
- end
90
+ parser = MatchParser.new( lines,
91
+ team_mapping,
92
+ start ) ## note: keep season start_at date for now (no need for more specific stage date need for now)
76
93
 
94
+ matches, rounds, groups = parser.parse
77
95
 
78
- ## todo/fix: check if all clubs are unique
79
- ## check if uniq works for club record (struct) - yes,no ??
80
- clubs = club_mapping.values.uniq
96
+ pp rounds
97
+ pp groups
81
98
 
82
99
 
83
- ## step 2: add to database
84
- teams = stage ? stage.teams : event.teams
85
- team_ids = stage ? stage.team_ids : event.team_ids
100
+ ######################################################
101
+ ## step 2: add to database
86
102
 
87
- clubs.each do |club|
88
- ## add teams to event
89
- ## for now check if team is alreay included
90
- ## todo/fix: clear/destroy_all first - why? why not!!!
103
+ event_rec = Sync::Event.find_or_create_by( league: league,
104
+ season: season )
91
105
 
92
- teams << club unless team_ids.include?( club.id )
93
- end
106
+ stage_rec = if stage
107
+ Sync::Stage.find_or_create( stage, event: event_rec )
108
+ else
109
+ nil
110
+ end
94
111
 
112
+ team_recs = stage_rec ? stage_rec.teams : event_rec.teams
113
+ team_ids = stage_rec ? stage_rec.team_ids : event_rec.team_ids
95
114
 
115
+ ## todo/fix: check if all teams are unique
116
+ ## check if uniq works for club record (struct) - yes,no ??
117
+ new_team_recs = Sync::Team.find_or_create( team_mapping.values.uniq )
96
118
 
97
- ## todo/fix: set lang for now depending on league country!!!
98
- parser = MatchParserSimpleV2.new( rec[:lines],
99
- club_mapping,
100
- event.start_at ) ## note: keep season start_at date for now (no need for more specific stage date need for now)
119
+ new_team_recs.each do |team_rec|
120
+ ## add teams to event
121
+ ## for now check if team is alreay included
122
+ ## todo/fix: clear/destroy_all first - why? why not!!!
123
+ team_recs << team_rec unless team_ids.include?( team_rec.id )
124
+ end
101
125
 
102
- match_recs, round_recs = parser.parse
103
126
 
104
- pp round_recs
127
+ rounds.each do |round|
128
+ round_rec = Sync::Round.find_or_create( round, event: event_rec ) ## check: use/rename to EventRound why? why not?
129
+ end
105
130
 
106
- round_recs.each do |round_rec|
107
- ## quick hack: if pos missing fill with dummy 999 for now
108
- round_rec.pos = 999 if round_rec.pos.nil?
109
- round = Sync::Round.find_or_create( round_rec, event: event ) ## check: use/rename to EventRound why? why not?
131
+ groups.each do |group|
132
+ group_rec = Sync::Group.find_or_create( group, event: event_rec ) ## check: use/rename to EventGroup why? why not?
110
133
  end
111
134
 
112
- match_recs.each do |match_rec|
113
- ## todo/fix: pass along stage (if present): stage - optional!!!!
114
- match = Sync::Match.create_or_update( match_rec, event: event )
135
+ matches.each do |match|
136
+ ## note: pass along stage (if present): stage - optional from heading!!!!
137
+ match = match.update( stage: stage ) if stage
138
+ match_rec = Sync::Match.create_or_update( match, event: event_rec )
115
139
  end
116
140
  end
117
141
 
118
- recs
119
- end # method read
120
- end # class MatchReaderV2
142
+ true ## success/ok
143
+ end # method parse
144
+
145
+
146
+ ######################
147
+ # (convenience) helpers
148
+
149
+ def catalog() Import.catalog; end
150
+
151
+ end # class MatchReader
121
152
  end # module SportDb
@@ -2,130 +2,70 @@
2
2
  module SportDb
3
3
  class Package
4
4
 
5
- CONF_RE = Datafile::CONF_RE
6
- CLUB_PROPS_RE = Datafile::CLUB_PROPS_RE
7
- LEAGUES_RE = Datafile::LEAGUES_RE
8
- CLUBS_RE = Datafile::CLUBS_RE
9
-
10
-
11
- ## note: if pattern includes directory add here (otherwise move to more "generic" datafile) - why? why not?
12
- MATCH_RE = %r{ /(?: \d{4}-\d{2} ## season folder e.g. /2019-20
13
- | \d{4} ## season year-only folder e.g. /2019
14
- )
15
- /[a-z0-9_-]+\.txt$ ## txt e.g /1-premierleague.txt
16
- }x
17
-
18
-
19
- attr_reader :pack ## allow access to embedded ("low-level") delegate package
20
-
21
- def initialize( path_or_pack )
22
- if path_or_pack.is_a?( Datafile::Package )
23
- @pack = path_or_pack
24
- else ## assume it's a (string) path
25
- path = path_or_pack
26
- if !File.exist?( path ) ## file or directory
27
- puts "** !!! ERROR !!! file NOT found >#{path}<; cannot open package"
28
- exit 1
29
- end
30
-
31
- if File.directory?( path )
32
- @pack = Datafile::DirPackage.new( path ) ## delegate to "generic" package
33
- elsif File.file?( path ) && File.extname( path ) == '.zip' # note: includes dot (.) eg .zip
34
- @pack = Datafile::ZipPackage.new( path )
35
- else
36
- puts "** !!! ERROR !!! cannot open package - directory or file with .zip extension required"
37
- exit 1
38
- end
39
- end
40
- end
41
-
42
- def each_conf( &blk ) @pack.each( pattern: CONF_RE, &blk ); end
43
- def each_match( &blk ) @pack.each( pattern: MATCH_RE, &blk ); end
44
- def each_club_props( &blk ) @pack.each( pattern: CLUB_PROPS_RE, &blk ); end
45
-
46
- def each_leagues( &blk ) @pack.each( pattern: LEAGUES_RE, &blk ); end
47
- def each_clubs( &blk ) @pack.each( pattern: CLUBS_RE, &blk ); end
48
-
5
+ ## note: add readers here; for full class def see the sourcein sportdb-formats!!!
49
6
 
50
7
  def read_leagues
51
- each_leagues do |entry|
52
- SportDb.parse_leagues( entry.read )
53
- end
8
+ each_leagues { |entry| SportDb.parse_leagues( entry.read ) }
54
9
  end
55
10
 
56
11
  def read_clubs
57
- each_clubs do |entry|
58
- SportDb.parse_clubs( entry.read )
59
- end
12
+ each_clubs { |entry| SportDb.parse_clubs( entry.read ) }
60
13
  end
61
14
 
62
-
63
- def read_club_props( sync: true ) ## todo/fix: remove sync!! - why? why not?
64
- each_club_props do |entry|
65
- SportDb.parse_club_props( entry.read, sync: sync )
66
- end
15
+ def read_club_props
16
+ each_club_props { |entry| SportDb.parse_club_props( entry.read ) }
67
17
  end
68
18
 
69
- def read_conf( *names,
70
- season: nil, sync: true )
19
+
20
+ def read_conf( *names, season: nil )
71
21
  if names.empty? ## no (entry) names passed in; read in all
72
22
  each_conf do |entry|
73
- SportDb.parse_conf( entry.read, season: season, sync: sync )
23
+ SportDb.parse_conf( entry.read, season: season )
74
24
  end
75
25
  else
76
26
  names.each do |name|
77
27
  entry = @pack.find( name )
78
- SportDb.parse_conf( entry.read, season: season, sync: sync )
28
+ SportDb.parse_conf( entry.read, season: season )
79
29
  end
80
30
  end
81
31
  end
82
32
 
83
- def read_match( *names,
84
- season: nil, sync: true )
33
+ def read_match( *names, season: nil )
85
34
  if names.empty? ## no (entry) names passed in; read in all
86
35
  each_match do |entry|
87
- SportDb.parse_match( entry.read, season: season, sync: sync )
36
+ SportDb.parse_match( entry.read, season: season )
88
37
  end
89
38
  else
90
39
  names.each do |name|
91
40
  entry = @pack.find( name )
92
- SportDb.parse_match( entry.read, season: season, sync: sync )
41
+ SportDb.parse_match( entry.read, season: season )
93
42
  end
94
43
  end
95
44
  end
96
45
 
97
46
 
98
- def read( *names,
99
- season: nil, sync: true )
47
+ def read( *names, season: nil )
100
48
  if names.empty? ## read all datafiles
101
49
  read_leagues()
102
50
  read_clubs()
103
- read_club_props( sync: sync ) ## todo/fix: remove sync - why? why not?
104
- read_conf( season: season, sync: sync )
105
- read_match( season: season, sync: sync )
51
+ read_club_props()
52
+ ## note: skip conf(iguration)s for now!!!!!!!
53
+ ## read_conf( season: season )
54
+ read_match( season: season )
106
55
  else
107
56
  names.each do |name|
108
57
  entry = @pack.find( name )
109
58
  ## fix/todo: add read_leagues, read_clubs too!!!
110
- if Datafile.match_conf( name ) ## check if datafile matches conf(iguration) naming (e.g. .conf.txt)
111
- SportDb.parse_conf( entry.read, season: season, sync: sync )
112
- elsif Datafile.match_club_props( name )
113
- SportDb.parse_club_props( entry.read, sync: sync )
114
- else ## assume "regular" match datafile
115
- SportDb.parse_match( entry.read, season: season, sync: sync )
59
+ if match_conf?( name ) ## check if datafile matches conf(iguration) naming (e.g. .conf.txt)
60
+ SportDb.parse_conf( entry.read, season: season )
61
+ elsif match_club_props?( name )
62
+ SportDb.parse_club_props( entry.read )
63
+ else ## assume "regular" match datafile or check pattern and report error on fail - why? why not?
64
+ SportDb.parse_match( entry.read, season: season )
116
65
  end
117
66
  end
118
67
  end
119
68
  end
120
69
  end # class Package
121
70
 
122
-
123
- class DirPackage < Package
124
- def initialize( path ) super( Datafile::DirPackage.new( path ) ); end
125
- end
126
-
127
- class ZipPackage < Package
128
- def initialize( path ) super( Datafile::ZipPackage.new( path ) ); end
129
- end
130
-
131
71
  end # module SportDb
@@ -4,8 +4,8 @@
4
4
  module SportDb
5
5
  module Readers
6
6
 
7
- MAJOR = 0 ## todo: namespace inside version or something - why? why not??
8
- MINOR = 5
7
+ MAJOR = 1 ## todo: namespace inside version or something - why? why not??
8
+ MINOR = 1
9
9
  PATCH = 0
10
10
  VERSION = [MAJOR,MINOR,PATCH].join('.')
11
11
 
@@ -1,19 +1,23 @@
1
- ## $:.unshift(File.dirname(__FILE__))
1
+ ## note: use the local version of sportdb gems
2
+ $LOAD_PATH.unshift( File.expand_path( '../sportdb-formats/lib' ))
3
+ $LOAD_PATH.unshift( File.expand_path( '../sportdb-config/lib' ))
4
+ $LOAD_PATH.unshift( File.expand_path( '../sportdb-models/lib' ))
5
+ $LOAD_PATH.unshift( File.expand_path( '../sportdb-sync/lib' ))
6
+
2
7
 
3
8
  ## minitest setup
4
9
  require 'minitest/autorun'
5
10
 
6
11
 
7
- ## note: use the local version of sportdb gems
8
- $LOAD_PATH.unshift( File.expand_path( '../sportdb-match-formats/lib' ))
9
-
10
12
 
11
13
  ## our own code
12
14
  require 'sportdb/readers'
13
15
 
14
-
15
-
16
-
17
16
  ## use (switch to) "external" datasets
18
17
  SportDb::Import.config.leagues_dir = "../../../openfootball/leagues"
19
18
  SportDb::Import.config.clubs_dir = "../../../openfootball/clubs"
19
+
20
+
21
+ COUNTRIES = SportDb::Import.catalog.countries
22
+ LEAGUES = SportDb::Import.catalog.leagues
23
+ CLUBS = SportDb::Import.catalog.clubs
@@ -0,0 +1,78 @@
1
+ # encoding: utf-8
2
+
3
+ ###
4
+ # to run use
5
+ # ruby -I ./lib -I ./test test/test_conf_reader.rb
6
+
7
+
8
+ require 'helper'
9
+
10
+
11
+ class TestConfReader < MiniTest::Test
12
+
13
+ def setup
14
+ SportDb.connect( adapter: 'sqlite3',
15
+ database: ':memory:' )
16
+ SportDb.create_all ## build schema
17
+
18
+ ## turn on logging to console
19
+ ActiveRecord::Base.logger = Logger.new(STDOUT)
20
+ end
21
+
22
+ def test_read
23
+ # path = "../../../openfootball/austria/2018-19/.conf.txt"
24
+ path = "../../../openfootball/england/2015-16/.conf.txt"
25
+ # path = "../../../openfootball/england/2017-18/.conf.txt"
26
+ # path = "../../../openfootball/england/2018-19/.conf.txt"
27
+ # path = "../../../openfootball/england/2019-20/.conf.txt"
28
+ SportDb::ConfReader.read( path )
29
+ end # method test_read
30
+
31
+
32
+ def test_read_champs
33
+ txt =<<TXT
34
+ = UEFA Champions League 2017/18
35
+
36
+ Manchester United › ENG
37
+ Liverpool › ENG
38
+ Chelsea › ENG
39
+ Manchester City › ENG
40
+ Tottenham Hotspur › ENG
41
+
42
+ Atlético Madrid › ESP
43
+ Barcelona › ESP
44
+ Sevilla › ESP
45
+ Real Madrid › ESP
46
+
47
+ Roma › ITA
48
+ Juventus › ITA
49
+ Napoli › ITA
50
+
51
+ Bayern München › GER
52
+ Borussia Dortmund › GER
53
+ RB Leipzig › GER
54
+
55
+ Benfica › POR
56
+ Sporting CP › POR
57
+ Porto › POR
58
+
59
+ CSKA Moscow › RUS
60
+ Spartak Moscow › RUS
61
+
62
+ Paris Saint-Germain › FRA
63
+ Basel › SUI
64
+ Celtic › SCO
65
+ Anderlecht › BEL
66
+ Qarabağ › AZE
67
+ Olympiacos › GRE
68
+ Maribor › SVN
69
+ Shakhtar Donetsk › UKR
70
+ Feyenoord › NED
71
+ Beşiktaş › TUR
72
+ Monaco › MCO
73
+ APOEL › CYP
74
+ TXT
75
+
76
+ SportDb::ConfReader.parse( txt )
77
+ end
78
+ end # class TestConfReader