sportdb-readers 0.5.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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