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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f4a900ae50e920da9b842d72e975577d098dbedd
4
- data.tar.gz: 9576ab392dcadb0d672535d59614c7d93ed29387
3
+ metadata.gz: f8c01094bf348c410e2d95343b8a5a3a660c84d4
4
+ data.tar.gz: 8983fe5aa77546edf2d4d31a64e9cd30a80d7320
5
5
  SHA512:
6
- metadata.gz: e1585ce4281e1725b0452194c1c34858714dc5a40ed4b25ffe598182be9878ce46f544ee66b3d43dc64ca5480b3e00195ae6c08de013a4f3c26962c1cb920484
7
- data.tar.gz: fd04ee65cde3a82a144473b1d63eb8a2cba1cf63c5dc0d4e0a98122ece942f2bef980ef751a6752273acf01c7adaaab68e5202d35bc86c8dc09356dde39b1b84
6
+ metadata.gz: 93f591316bb2a1d3e0111039757ada38294a744479388f0b868d81fb88e36b6f61f7036d0f4124dd49efb74679e8aee4d19bab9b2673649a955e6dc83757893a
7
+ data.tar.gz: 1de11267bcf199e67d82ccd07d40c2709413aeea985545b164131a0ae1a42851335133e4d0434f77c63fb6ab55b64ea4f77209118c6a8bc45117bd9148a14279
@@ -1,17 +1,19 @@
1
1
  CHANGELOG.md
2
2
  Manifest.txt
3
+ NOTES.md
3
4
  README.md
4
5
  Rakefile
5
6
  lib/sportdb/readers.rb
6
- lib/sportdb/readers/conf_linter.rb
7
7
  lib/sportdb/readers/conf_reader.rb
8
- lib/sportdb/readers/league_outline_reader.rb
9
- lib/sportdb/readers/match_linter.rb
10
8
  lib/sportdb/readers/match_reader.rb
11
9
  lib/sportdb/readers/package.rb
12
10
  lib/sportdb/readers/version.rb
13
11
  test/helper.rb
12
+ test/test_conf_reader.rb
13
+ test/test_match_reader_champs.rb
14
14
  test/test_match_reader_eng.rb
15
+ test/test_match_reader_euro.rb
15
16
  test/test_match_reader_mu.rb
16
17
  test/test_read.rb
17
18
  test/test_reader.rb
19
+ test/test_reader_champs.rb
@@ -0,0 +1,35 @@
1
+ # Notes
2
+
3
+ ## Todos
4
+
5
+
6
+ todo/fix: use config / configurations; remove all globals e.g.:
7
+ - COUNTRIES
8
+ - CLUBS
9
+ - LEAGUES !!!!!
10
+
11
+
12
+
13
+ fix/todo: move find_league to sportdb-league index use find_by! and find_by !!!!
14
+ in sportdb-readers (and others?)
15
+
16
+ ```
17
+ def self.find_league( name )
18
+ league = nil
19
+ m = config.leagues.match( name )
20
+ # pp m
21
+
22
+ if m.nil?
23
+ puts "** !!! ERROR !!! no league match found for >#{name}<, add to leagues table; sorry"
24
+ exit 1
25
+ elsif m.size > 1
26
+ puts "** !!! ERROR !!! ambigious league name; too many leagues (#{m.size}) found:"
27
+ pp m
28
+ exit 1
29
+ else
30
+ league = m[0]
31
+ end
32
+
33
+ league
34
+ end
35
+ ```
data/README.md CHANGED
@@ -31,50 +31,116 @@ Let's read in some leagues, seasons, clubs, and match schedules and results.
31
31
  Let's use the public domain football.db datasets for England (see [`openfootball/england`](https://github.com/openfootball/england)), as an example:
32
32
 
33
33
 
34
- ``` ruby
35
- ## turn on logging to console
36
- ActiveRecord::Base.logger = Logger.new( STDOUT )
34
+ ```
35
+ = English Premier League 2015/16
36
+
37
+ Matchday 1
38
+
39
+ [Sat Aug 8]
40
+ Manchester United 1-0 Tottenham Hotspur
41
+ AFC Bournemouth 0-1 Aston Villa
42
+ Everton FC 2-2 Watford FC
43
+ Leicester City 4-2 Sunderland AFC
44
+ Norwich City 1-3 Crystal Palace
45
+ Chelsea FC 2-2 Swansea City
46
+ [Sun Aug 9]
47
+ Arsenal FC 0-2 West Ham United
48
+ Newcastle United 2-2 Southampton FC
49
+ Stoke City 0-1 Liverpool FC
50
+ [Mon Aug 10]
51
+ West Bromwich Albion 0-3 Manchester City
52
+
53
+ ...
54
+ ```
55
+
56
+ (Source: [england/2015-16/1-premierleague-i.txt](https://github.com/openfootball/england/blob/master/2015-16/1-premierleague-i.txt))
37
57
 
58
+ and let's try:
59
+
60
+ ``` ruby
38
61
  ## assumes football.db datasets for England in ./england directory
39
62
  ## see github.com/openfootball/england
40
- SportDb::ConfReaderV2.read( './england/2015-16/.conf.txt' )
41
- SportDb::MatchReaderV2.read( './england/2015-16/1-premierleague-i.txt' )
42
- SportDb::MatchReaderV2.read( './england/2015-16/1-premierleague-ii.txt' )
63
+ SportDb.read( './england/2015-16/1-premierleague-i.txt' )
64
+ SportDb.read( './england/2015-16/1-premierleague-ii.txt' )
43
65
 
44
66
  ## let's try another season
45
- SportDb::ConfReaderV2.read( './england/2019-20/.conf.txt' )
46
- SportDb::MatchReaderV2.read( './england/2019-20/1-premierleague.txt' )
67
+ SportDb.read( './england/2019-20/1-premierleague.txt' )
47
68
  ```
48
69
 
49
70
  All leagues, seasons, clubs, match days and rounds, match fixtures and results,
50
71
  and more are now in your (SQL) database of choice.
51
72
 
52
-
53
- Or as an alternative use the `read` convenience all-in-one shortcut helper:
73
+ The proof of the pudding - Let's query the (SQL) database using the sport.db ActiveRecord models:
54
74
 
55
75
  ``` ruby
56
- ## assumes football.db datasets for England in ./england directory
57
- ## see github.com/openfootball/england
58
- SportDb.read( './england/2015-16/.conf.txt' )
59
- SportDb.read( './england/2015-16/1-premierleague-i.txt' )
60
- SportDb.read( './england/2015-16/1-premierleague-ii.txt' )
76
+ include SportDb::Models
61
77
 
62
- ## let's try another season
63
- SportDb.read( './england/2019-20/.conf.txt' )
64
- SportDb.read( './england/2019-20/1-premierleague.txt' )
78
+ pl_2015_16 = Event.find_by( key: 'eng.1.2015/16' )
79
+ #=> SELECT * FROM events WHERE key = 'eng.1.2015/16' LIMIT 1
80
+
81
+ pl_2015_16.teams.count #=> 20
82
+ #=> SELECT COUNT(*) FROM teams
83
+ # INNER JOIN events_teams ON teams.id = events_teams.team_id
84
+ # WHERE events_teams.event_id = 1
85
+
86
+ pl_2015_16.games.count #=> 380
87
+ #=> SELECT COUNT(*) FROM games
88
+ # INNER JOIN rounds ON games.round_id = rounds.id
89
+ # WHERE rounds.event_id = 1
90
+
91
+ pl_2019_20 = Event.find_by( key: 'eng.1.2019/20' )
92
+ pl_2015_16.teams.count #=> 20
93
+ pl_2015_16.games.count #=> 380
94
+
95
+ # -or-
96
+
97
+ pl = League.find_by( key: 'eng.1' )
98
+ #=> SELECT * FROM leagues WHERE key = 'eng.1' LIMIT 1
99
+
100
+ pl.seasons.count #=> 2
101
+ #=> SELECT COUNT(*) FROM seasons
102
+ # INNER JOIN events ON seasons.id = events.season_id
103
+ # WHERE events.league_id = 1
104
+
105
+ # and so on and so forth.
65
106
  ```
66
107
 
67
- Or as an alternative pass in the "package" directory and let `read` figure
68
- out what datafiles to read:
108
+ Bonus: As an alternative pass in the "package" directory or a zip archive and let `read` figure
109
+ out what datafiles to read in:
69
110
 
70
111
  ``` ruby
71
112
  ## assumes football.db datasets for England in ./england directory
72
113
  ## see github.com/openfootball/england
73
114
  SportDb.read( './england' )
115
+ ## -or- use a zip archive download
116
+ SportDb.read( './england.zip' )
74
117
  ```
75
118
 
76
119
  That's it.
77
120
 
121
+ ## Frequently Asked Questions (F.A.Q.s) and Answers
122
+
123
+ Q: What about reading in datasets in comma-separated values (CSV) format?
124
+ Example:
125
+
126
+ ```
127
+ Round, Date, Team 1, FT, HT, Team 2
128
+ 1, (Fri) 9 Aug 2019, Liverpool FC, 4-1, 4-0, Norwich City FC
129
+ 1, (Sat) 10 Aug 2019, West Ham United FC, 0-5, 0-1, Manchester City FC
130
+ 1, (Sat) 10 Aug 2019, AFC Bournemouth, 1-1, 0-0, Sheffield United FC
131
+ 1, (Sat) 10 Aug 2019, Burnley FC, 3-0, 0-0, Southampton FC
132
+ 1, (Sat) 10 Aug 2019, Crystal Palace FC, 0-0, 0-0, Everton FC
133
+ 1, (Sat) 10 Aug 2019, Watford FC, 0-3, 0-1, Brighton & Hove Albion FC
134
+ 1, (Sat) 10 Aug 2019, Tottenham Hotspur FC, 3-1, 0-1, Aston Villa FC
135
+ 1, (Sun) 11 Aug 2019, Leicester City FC, 0-0, 0-0, Wolverhampton Wanderers FC
136
+ 1, (Sun) 11 Aug 2019, Newcastle United FC, 0-1, 0-0, Arsenal FC
137
+ 1, (Sun) 11 Aug 2019, Manchester United FC, 4-0, 1-0, Chelsea FC
138
+ ...
139
+ ```
140
+ (Source: [england/2019-20/eng.1.csv](https://github.com/footballcsv/england/blob/master/2010s/2019-20/eng.1.csv))
141
+
142
+ Yes, you can. See the [sportdb-importers library / gem »](https://github.com/sportdb/sport.db/tree/master/sportdb-importers)
143
+
78
144
 
79
145
 
80
146
  ## License
data/Rakefile CHANGED
@@ -20,9 +20,7 @@ Hoe.spec 'sportdb-readers' do
20
20
  self.licenses = ['Public Domain']
21
21
 
22
22
  self.extra_deps = [
23
- ['sportdb-config', '>= 0.9.0'],
24
- ['sportdb-models', '>= 1.18.6'],
25
- ['sportdb-sync', '>= 0.1.0'],
23
+ ['sportdb-sync', '>= 1.1.0'],
26
24
  ]
27
25
 
28
26
  self.spec_extras = {
@@ -1,19 +1,13 @@
1
1
  # encoding: utf-8
2
2
 
3
-
4
- require 'sportdb/config'
5
- require 'sportdb/models' ## add sql database support
6
3
  require 'sportdb/sync'
7
4
 
8
5
 
9
6
  ###
10
7
  # our own code
11
8
  require 'sportdb/readers/version' # let version always go first
12
- require 'sportdb/readers/league_outline_reader'
13
9
  require 'sportdb/readers/conf_reader'
14
- require 'sportdb/readers/conf_linter'
15
10
  require 'sportdb/readers/match_reader'
16
- require 'sportdb/readers/match_linter'
17
11
  require 'sportdb/readers/package'
18
12
 
19
13
 
@@ -22,76 +16,43 @@ require 'sportdb/readers/package'
22
16
  ##
23
17
  ## add convenience shortcut helpers
24
18
  module SportDb
19
+ def self.read_conf( path, season: nil ) ConfReader.read( path, season: season ); end
20
+ def self.parse_conf( txt, season: nil ) ConfReader.parse( txt, season: season ); end
21
+
22
+ ### todo/check: add alias read_matches - why? why not?
23
+ def self.read_match( path, season: nil ) MatchReader.read( path, season: season ); end
24
+ def self.parse_match( txt, season: nil ) MatchReader.parse( txt, season: season ); end
25
+
26
+ def self.read_club_props( path ) Import::ClubPropsReader.read( path ); end
27
+ def self.parse_club_props( txt ) Import::ClubPropsReader.parse( txt ); end
28
+
29
+ def self.parse_leagues( txt ) recs = Import::LeagueReader.parse( txt ); Import.catalog.leagues.add( recs ); end
30
+ def self.parse_clubs( txt ) recs = Import::ClubReader.parse( txt ); Import.catalog.clubs.add( recs ); end
25
31
 
26
- ## note: sync is dry run (for lint checking)
27
- def self.read_conf( path, season: nil, sync: true )
28
- sync ? ConfReaderV2.read( path, season: season )
29
- : ConfLinter.read( path, season: season )
30
- end
31
- def self.parse_conf( txt, season: nil, sync: true )
32
- sync ? ConfReaderV2.parse( txt, season: season )
33
- : ConfLinter.parse( txt, season: season )
34
- end
35
-
36
- def self.read_match( path, season: nil, sync: true ) ### todo/check: add alias read_matches - why? why not?
37
- sync ? MatchReaderV2.read( path, season: season )
38
- : MatchLinter.read( path, season: season )
39
- end
40
- def self.parse_match( txt, season: nil, sync: true ) ### todo/check: add alias read_matches - why? why not?
41
- sync ? MatchReaderV2.parse( txt, season: season )
42
- : MatchLinter.parse( txt, season: season )
43
- end
44
-
45
- def self.read_club_props( path, sync: true )
46
- ## note: for now run only if sync (e.g. run with db updates)
47
- SportDb::Import::ClubPropsReader.read( path ) if sync
48
- end
49
- def self.parse_club_props( txt, sync: true )
50
- ## note: for now run only if sync (e.g. run with db updates)
51
- SportDb::Import::ClubPropsReader.parse( txt ) if sync
52
- end
53
-
54
-
55
- def self.parse_leagues( txt )
56
- recs = SportDb::Import::LeagueReader.parse( txt )
57
- Import::config.leagues.add( recs )
58
- end
59
-
60
- def self.parse_clubs( txt )
61
- recs = SportDb::Import::ClubReader.parse( txt )
62
- Import::config.clubs.add( recs )
63
- end
64
-
65
-
66
- def self.read( path, season: nil, sync: true )
32
+
33
+ def self.read( path, season: nil )
67
34
  pack = if File.directory?( path ) ## if directory assume "unzipped" package
68
35
  DirPackage.new( path )
69
- elsif File.file?( path ) && Datafile.match_zip( path ) ## check if file is a .zip (archive) file
36
+ elsif File.file?( path ) && File.extname( path ) == '.zip' ## check if file is a .zip (archive) file
70
37
  ZipPackage.new( path )
71
38
  else ## no package; assume single (standalone) datafile
72
39
  nil
73
40
  end
74
41
 
75
42
  if pack
76
- pack.read( season: season, sync: sync )
43
+ pack.read( season: season )
77
44
  else
78
- if Datafile.match_conf( path ) ## check if datafile matches conf(iguration) naming (e.g. .conf.txt)
79
- read_conf( path, season: season, sync: sync )
80
- elsif Datafile.match_club_props( path )
81
- read_club_props( path, sync: sync )
45
+ if Package.conf?( path ) ## check if datafile matches conf(iguration) naming (e.g. .conf.txt)
46
+ read_conf( path, season: season )
47
+ elsif Package.club_props?( path )
48
+ read_club_props( path )
82
49
  else ## assume "regular" match datafile
83
- read_match( path, season: season, sync: sync )
50
+ read_match( path, season: season )
84
51
  end
85
52
  end
86
53
  end # method read
87
54
 
88
55
 
89
-
90
- ## (more) convenience helpers for lint(ing)
91
- def self.lint( path, season: nil ) read( path, season: season, sync: false ); end
92
- def self.lint_conf( path, season: nil ) read_conf( path, season: season, sync: false ); end
93
- def self.lint_match( path, season: nil ) read_match( path, season: season, sync: false ); end
94
-
95
56
  end # module SportDb
96
57
 
97
58
 
@@ -3,86 +3,98 @@
3
3
  module SportDb
4
4
 
5
5
 
6
- class ConfReaderV2 ## todo/check: rename to EventsReaderV2 (use plural?) why? why not?
7
-
8
- def self.config() Import.config; end ## shortcut convenience helper
9
-
6
+ class ConfReader ## todo/check: rename to EventsReaderV2 (use plural?) why? why not?
10
7
 
11
8
  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
9
+ txt = File.open( path, 'r:utf-8' ) {|f| f.read }
13
10
  parse( txt, season: season )
14
11
  end
15
12
 
16
13
  def self.parse( txt, season: nil )
17
- recs = LeagueOutlineReader.parse( txt, season: season )
18
- pp recs
19
-
20
- ## pass 2 - check & map; replace inline (string with record)
21
- recs.each do |rec|
22
- league = rec[:league]
23
- clubs = [] ## convert lines to clubs
24
- rec[:lines].each do |line|
25
-
26
- next if line =~ /^[ -]+$/ ## skip decorative lines with dash only (e.g. ---- or - - - -) etc.
27
-
28
- scan = StringScanner.new( line )
29
-
30
- if scan.check( /\d{1,2}[ ]+/ ) ## entry with standaning starts with ranking e.g. 1,2,3, etc.
31
- puts " table entry >#{line}<"
32
- rank = scan.scan( /\d{1,2}[ ]+/ ).strip # note: strip trailing spaces
33
-
34
- ## note: uses look ahead scan until we hit at least two spaces
35
- ## or the end of string (standing records for now optional)
36
- name = scan.scan_until( /(?=\s{2})|$/ )
37
- if scan.eos?
38
- standing = nil
39
- else
40
- standing = scan.rest.strip # note: strip leading and trailing spaces
41
- end
42
- puts " rank: >#{rank}<, name: >#{name}<, standing: >#{standing}<"
14
+ new( txt ).parse( season: season )
15
+ end
43
16
 
44
- ## note: rank and standing gets ignored (not used) for now
17
+
18
+ include Logging
19
+
20
+ def initialize( txt )
21
+ @txt = txt
22
+ end
23
+
24
+ def parse( season: nil )
25
+ secs = LeagueOutlineReader.parse( @txt, season: season )
26
+ pp secs
27
+
28
+ ## pass 1 - check & map; replace inline (string with record)
29
+ secs.each do |sec| # sec(tion)s
30
+
31
+ conf = ConfParser.parse( sec[:lines] )
32
+
33
+ league = sec[:league]
34
+ teams = [] ## convert lines to teams
35
+
36
+ if league.clubs?
37
+ if league.intl?
38
+ conf.each do |name, rec|
39
+ country_key = rec[:country]
40
+ teams << catalog.clubs.find_by!( name: name,
41
+ country: country_key )
42
+ end
45
43
  else
46
- ## assume club is full line
47
- name = line
44
+ conf.each do |name, _|
45
+ ## note: rank and standing gets ignored (not used) for now
46
+ teams << catalog.clubs.find_by!( name: name,
47
+ country: league.country )
48
+ end
49
+ end
50
+ else ### assume national teams
51
+ conf.each do |name, _|
52
+ ## note: rank and standing gets ignored (not used) for now
53
+ teams << catalog.national_teams.find!( name )
48
54
  end
49
-
50
- clubs << config.clubs.find_by( name: name, country: league.country )
51
55
  end
52
56
 
53
- rec[:clubs] = clubs
54
- rec.delete( :lines ) ## remove lines entry
57
+
58
+ sec[:teams] = teams
59
+
60
+ sec.delete( :lines ) ## remove lines entry
55
61
  end
56
62
 
57
- ## pass 3 - import (insert/update) into db
58
- recs.each do |rec|
59
- league = Sync::League.find_or_create( rec[:league] )
60
- season = Sync::Season.find_or_create( rec[:season] )
61
63
 
64
+ ## pass 2 - import (insert/update) into db
65
+ secs.each do |sec| # sec(tion)s
66
+ ## todo/fix: always return Season struct record in LeagueReader - why? why not?
67
+ event_rec = Sync::Event.find_or_create_by( league: sec[:league],
68
+ season: sec[:season] )
62
69
 
63
- event = Sync::Event.find_or_create( league: league, season: season )
64
- if rec[:stage]
65
- stage = Sync::Stage.find_or_create( rec[:stage], event: event )
66
- else
67
- stage = nil
68
- end
70
+ stage_rec = if sec[:stage]
71
+ Sync::Stage.find_or_create( sec[:stage], event: event_rec )
72
+ else
73
+ nil
74
+ end
69
75
 
76
+ ## todo/fix: check if all teams are unique
77
+ ## check if uniq works for club/national_team record (struct) - yes,no ??
78
+ teams = sec[:teams]
79
+ teams = teams.uniq
70
80
 
71
- rec[:clubs].each do |club_rec|
72
- club = Sync::Club.find_or_create( club_rec )
81
+ ## add to database
82
+ team_recs = stage_rec ? stage_rec.teams : event_rec.teams
83
+ team_ids = stage_rec ? stage_rec.team_ids : event_rec.team_ids
84
+
85
+ new_team_recs = Sync::Team.find_or_create( teams )
86
+ new_team_recs.each do |team_rec|
73
87
  ## add teams to event
74
- ## todo/fix: check if team is alreay included?
75
- ## or clear/destroy_all first!!!
76
- if stage
77
- stage.teams << club
78
- else
79
- event.teams << club
80
- end
88
+ ## for now check if team is alreay included
89
+ ## todo/fix: clear/destroy_all first - why? why not!!!
90
+ team_recs << team_rec unless team_ids.include?( team_rec.id )
81
91
  end
82
92
  end
83
93
 
84
- recs
94
+ true ## todo/fix: return true/false or something
85
95
  end # method read
86
96
 
87
- end # class ConfReaderV2
97
+ def catalog() Import.catalog; end ## shortcut convenience helper
98
+
99
+ end # class ConfReader
88
100
  end # module SportDb