sportdb-formats 1.2.1 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,25 +1,51 @@
1
1
  ## 3rd party gems
2
2
  require 'sportdb/structs'
3
+ require 'sportdb/parser'
4
+ require 'date/formats'
3
5
 
4
6
 
5
7
  require 'zip' ## todo/check: if zip is alreay included in a required module
6
8
 
7
9
 
10
+ ## note - add cocos (code commons)
11
+ ##
12
+ ## pulls in read_csv & parse_csv etc.
13
+ require 'cocos'
14
+
15
+
16
+ require 'logutils'
17
+ module SportDb
18
+ ## logging machinery shortcut; use LogUtils for now
19
+ Logging = LogUtils::Logging
20
+ end
21
+
22
+
8
23
 
9
24
 
10
25
  ###
11
26
  # our own code
12
27
  require_relative 'formats/version' # let version always go first
13
28
 
14
- require_relative 'formats/outline_reader'
15
29
  require_relative 'formats/datafile'
16
30
  require_relative 'formats/datafile_package'
17
31
  require_relative 'formats/package'
18
32
 
33
+ require_relative 'formats/name_helper'
34
+
19
35
 
20
- require_relative 'formats/parser_helper'
21
36
 
22
- require_relative 'formats/lines_reader'
37
+ ## let's put test configuration in its own namespace / module
38
+ module SportDb
39
+ class Test ## todo/check: works with module too? use a module - why? why not?
40
+
41
+ ####
42
+ # todo/fix: find a better way to configure shared test datasets - why? why not?
43
+ # note: use one-up (..) directory for now as default - why? why not?
44
+ def self.data_dir() @data_dir ||= '../test'; end
45
+ def self.data_dir=( path ) @data_dir = path; end
46
+ end
47
+ end # module SportDb
48
+
23
49
 
24
50
 
25
51
  ###
@@ -29,13 +55,10 @@ module SportDb
29
55
  module Import
30
56
 
31
57
  class Configuration
32
- ## note: add more configs (open class), see sportdb-structs for original config!!!
33
-
34
- ## add
35
58
  def world() @world ||= WorldSearch.new( countries: DummyCountrySearch.new ); end
36
59
  def world=(world) @world = world; end
37
60
 
38
- ## tood/fix - add/move catalog here from sportdb-catalogs!!!
61
+ ## todo/fix - add/move catalog here from sportdb-catalogs!!!
39
62
  ## def catalog() @catalog ||= Catalog.new; end
40
63
  ## def catalog(catalog) @catalog = catalog; end
41
64
  end # class Configuration
@@ -43,11 +66,62 @@ end # class Configuration
43
66
  ## e.g. use config.catalog -- keep Import.catalog as a shortcut (for "read-only" access)
44
67
  ## def self.catalog() config.catalog; end
45
68
  def self.world() config.world; end
69
+
70
+ ## lets you use
71
+ ## SportDb::Import.configure do |config|
72
+ ## config.catalog_path = './catalog.db'
73
+ ## end
74
+ def self.configure() yield( config ); end
75
+ def self.config() @config ||= Configuration.new; end
46
76
  end # module Import
47
77
  end # module SportDb
48
78
 
79
+
49
80
  require_relative 'formats/search/world'
50
81
  require_relative 'formats/search/sport'
82
+ require_relative 'formats/search/structs'
83
+
84
+
85
+
86
+ module Sports
87
+ ## note: just forward to SportDb::Import configuration!!!!!
88
+ ## keep Sports module / namespace "clean" - why? why not?
89
+ ## that is, only include data structures (e.g. Match,League,etc) for now - why? why not?
90
+ def self.configure() yield( config ); end
91
+ def self.config() SportDb::Import.config; end
92
+ end # module Sports
93
+
94
+
95
+ ###
96
+ # csv (tabular dataset) support / machinery
97
+ require_relative 'formats/csv/match_status_parser'
98
+ require_relative 'formats/csv/goal'
99
+ require_relative 'formats/csv/goal_parser_csv'
100
+ require_relative 'formats/csv/match_parser_csv'
101
+
102
+
103
+ ### add convenience shortcut helpers
104
+ module Sports
105
+ class Match
106
+ def self.read_csv( path, headers: nil, filters: nil, converters: nil, sep: nil )
107
+ SportDb::CsvMatchParser.read( path,
108
+ headers: headers,
109
+ filters: filters,
110
+ converters: converters,
111
+ sep: sep )
112
+ end
113
+
114
+ def self.parse_csv( txt, headers: nil, filters: nil, converters: nil, sep: nil )
115
+ SportDb::CsvMatchParser.parse( txt,
116
+ headers: headers,
117
+ filters: filters,
118
+ converters: converters,
119
+ sep: sep )
120
+ end
121
+ end # class Match
122
+ end # module Sports
123
+
124
+
51
125
 
52
126
 
53
127
 
@@ -77,7 +151,7 @@ module SportDb
77
151
 
78
152
  Player = ::Sports::Player
79
153
 
80
-
154
+
81
155
  class Team
82
156
  ## add convenience lookup helper / method for name by season for now
83
157
  ## use clubs history - for now kept separate from struct - why? why not?
@@ -91,14 +165,7 @@ module SportDb
91
165
  end # module SportDb
92
166
 
93
167
 
94
- require_relative 'formats/goals'
95
-
96
-
97
-
98
- require_relative 'formats/match/mapper'
99
- require_relative 'formats/match/mapper_teams'
100
168
  require_relative 'formats/match/match_parser'
101
- require_relative 'formats/match/match_parser_auto_conf'
102
169
  require_relative 'formats/match/conf_parser'
103
170
 
104
171
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sportdb-formats
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.1
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gerald Bauer
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-06-27 00:00:00.000000000 Z
11
+ date: 2024-08-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: sportdb-structs
@@ -16,28 +16,84 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: 0.1.1
19
+ version: 0.3.0
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: 0.1.1
26
+ version: 0.3.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: sportdb-parser
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 0.2.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: 0.2.0
41
+ - !ruby/object:Gem::Dependency
42
+ name: date-formats
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: 1.0.2
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: 1.0.2
55
+ - !ruby/object:Gem::Dependency
56
+ name: cocos
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: 0.4.0
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: 0.4.0
69
+ - !ruby/object:Gem::Dependency
70
+ name: logutils
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: 0.6.1
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: 0.6.1
27
83
  - !ruby/object:Gem::Dependency
28
84
  name: rubyzip
29
85
  requirement: !ruby/object:Gem::Requirement
30
86
  requirements:
31
87
  - - ">="
32
88
  - !ruby/object:Gem::Version
33
- version: 1.2.4
89
+ version: 2.3.2
34
90
  type: :runtime
35
91
  prerelease: false
36
92
  version_requirements: !ruby/object:Gem::Requirement
37
93
  requirements:
38
94
  - - ">="
39
95
  - !ruby/object:Gem::Version
40
- version: 1.2.4
96
+ version: 2.3.2
41
97
  - !ruby/object:Gem::Dependency
42
98
  name: rdoc
43
99
  requirement: !ruby/object:Gem::Requirement
@@ -87,23 +143,22 @@ files:
87
143
  - Rakefile
88
144
  - lib/sportdb/formats.rb
89
145
  - lib/sportdb/formats/country/country_reader.rb
146
+ - lib/sportdb/formats/csv/goal.rb
147
+ - lib/sportdb/formats/csv/goal_parser_csv.rb
148
+ - lib/sportdb/formats/csv/match_parser_csv.rb
149
+ - lib/sportdb/formats/csv/match_status_parser.rb
90
150
  - lib/sportdb/formats/datafile.rb
91
151
  - lib/sportdb/formats/datafile_package.rb
92
152
  - lib/sportdb/formats/event/event_reader.rb
93
- - lib/sportdb/formats/goals.rb
94
153
  - lib/sportdb/formats/ground/ground_reader.rb
95
154
  - lib/sportdb/formats/league/league_outline_reader.rb
96
155
  - lib/sportdb/formats/league/league_reader.rb
97
- - lib/sportdb/formats/lines_reader.rb
98
156
  - lib/sportdb/formats/match/conf_parser.rb
99
- - lib/sportdb/formats/match/mapper.rb
100
- - lib/sportdb/formats/match/mapper_teams.rb
101
157
  - lib/sportdb/formats/match/match_parser.rb
102
- - lib/sportdb/formats/match/match_parser_auto_conf.rb
103
- - lib/sportdb/formats/outline_reader.rb
158
+ - lib/sportdb/formats/name_helper.rb
104
159
  - lib/sportdb/formats/package.rb
105
- - lib/sportdb/formats/parser_helper.rb
106
160
  - lib/sportdb/formats/search/sport.rb
161
+ - lib/sportdb/formats/search/structs.rb
107
162
  - lib/sportdb/formats/search/world.rb
108
163
  - lib/sportdb/formats/team/club_index_history.rb
109
164
  - lib/sportdb/formats/team/club_reader.rb
@@ -125,7 +180,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
125
180
  requirements:
126
181
  - - ">="
127
182
  - !ruby/object:Gem::Version
128
- version: 2.2.2
183
+ version: 3.1.0
129
184
  required_rubygems_version: !ruby/object:Gem::Requirement
130
185
  requirements:
131
186
  - - ">="
@@ -1,321 +0,0 @@
1
-
2
- module SportDb
3
-
4
-
5
-
6
- class GoalsPlayerStruct
7
- ##
8
- # note: player with own goal (o.g) gets listed on other team
9
- # (thus, player might have two entries if also scored for its own team)
10
- #
11
- attr_accessor :name
12
- attr_accessor :minutes # ary of minutes e.g. 30', 45+2', 72'
13
-
14
- def initialize
15
- @minutes = []
16
- end
17
-
18
- def pretty_print( printer )
19
- buf = String.new
20
- buf << "<GoalsPlayerStruct: #{@name} "
21
- buf << @minutes.pretty_print_inspect
22
- buf << ">"
23
-
24
- printer.text( buf )
25
- end
26
- end
27
-
28
-
29
- class GoalsMinuteStruct
30
- attr_accessor :minute, :offset
31
- attr_accessor :penalty, :owngoal # flags
32
-
33
- def initialize
34
- @offset = 0
35
- @penalty = false
36
- @owngoal = false
37
- end
38
-
39
- def pretty_print( printer )
40
- buf = String.new
41
- buf << "<GoalsMinuteStruct: #{@minute}"
42
- buf << "+#{@offset}" if @offset && @offset > 0
43
- buf << "'"
44
- buf << " (o.g.)" if @owngoal
45
- buf << " (pen.)" if @penalty
46
- buf << ">"
47
-
48
- printer.text( buf )
49
- end
50
- end
51
-
52
-
53
- class GoalStruct
54
- ######
55
- # flat struct for goals - one entry per goals
56
- attr_accessor :name
57
- attr_accessor :team # 1 or 2 ? check/todo: add team1 or team2 flag?
58
- attr_accessor :minute, :offset
59
- attr_accessor :penalty, :owngoal
60
- attr_accessor :score1, :score2 # gets calculated
61
-
62
- ## add pos for sequence number? e.g. 1,2,3,4 (1st goald, 2nd goal, etc.) ???
63
-
64
-
65
- def initialize( **kwargs ) ## add/allow quick and dirty quick init with keywords
66
- if kwargs.empty?
67
- # do nothing
68
- else
69
- kwargs.each do |key,value|
70
- send( "#{key}=", value )
71
- end
72
- end
73
- end
74
-
75
- def ==(o)
76
- o.class == self.class && o.state == state
77
- end
78
-
79
- def state
80
- [@name, @team, @minute, @offset, @penalty, @owngoal, @score1, @score2]
81
- end
82
-
83
-
84
- def pretty_print( printer )
85
- buf = String.new
86
- buf << "<GoalStruct: #{@score1}-#{@score2} #{@name} #{@minute}"
87
- buf << "+#{@offset}" if @offset && @offset > 0
88
- buf << "'"
89
- buf << " (o.g.)" if @owngoal
90
- buf << " (pen.)" if @penalty
91
- buf << " for #{@team}" ### team 1 or 2 - use home/away
92
- buf << ">"
93
-
94
- printer.text( buf )
95
- end
96
-
97
-
98
- end
99
-
100
-
101
-
102
- # todo: find a better name? to avoid confusing w/ GoalsParser? use MatchGoalsParser or similar?
103
- class GoalsFinder
104
- include LogUtils::Logging
105
-
106
-
107
- def initialize
108
- # nothing here for now
109
- end
110
-
111
- def find!( line, opts={} )
112
- # remove end-of-line comments
113
- line = line.sub( /#.*$/ ) do |_|
114
- logger.debug " cutting off end of line comment - >>#{$&}<<"
115
- ''
116
- end
117
-
118
- # remove [] if presents e.g. [Neymar 12']
119
- line = line.gsub( /[\[\]]/, '' )
120
- # remove (single match) if line starts w/ - (allow spaces) e.g. [-;Neymar 12'] or [ - ;Neymar 12']
121
- line = line.sub( /^[ ]*-[ ]*/, '' )
122
-
123
- # split into left hand side (lhs) for team1 and
124
- # right hand side (rhs) for team2
125
-
126
- values = line.split( ';' )
127
-
128
- # note: allow empty right hand side (e.g. team2 did NOT score any goals e.g. 3-0 etc.)
129
- lhs = values[0]
130
- rhs = values[1]
131
-
132
- lhs = lhs.strip unless lhs.nil?
133
- rhs = rhs.strip unless rhs.nil?
134
-
135
- parser = GoalsParser.new
136
- ## todo/check: only call if not nil?
137
-
138
- logger.debug " lhs (team1): >#{lhs}<"
139
- lhs_data = parser.parse!( lhs )
140
- pp lhs_data
141
-
142
- logger.debug " rhs (team2): >#{rhs}<"
143
- rhs_data = parser.parse!( rhs )
144
- pp rhs_data
145
-
146
- ### merge into flat goal structs
147
- goals = []
148
- lhs_data.each do |player|
149
- player.minutes.each do |minute|
150
- goal = GoalStruct.new
151
- goal.name = player.name
152
- goal.team = 1
153
- goal.minute = minute.minute
154
- goal.offset = minute.offset
155
- goal.penalty = minute.penalty
156
- goal.owngoal = minute.owngoal
157
- goals << goal
158
- end
159
- end
160
-
161
- rhs_data.each do |player|
162
- player.minutes.each do |minute|
163
- goal = GoalStruct.new
164
- goal.name = player.name
165
- goal.team = 2
166
- goal.minute = minute.minute
167
- goal.offset = minute.offset
168
- goal.penalty = minute.penalty
169
- goal.owngoal = minute.owngoal
170
- goals << goal
171
- end
172
- end
173
-
174
-
175
- # sort by minute + offset
176
- goals = goals.sort do |l,r|
177
- res = l.minute <=> r.minute
178
- if res == 0
179
- res = l.offset <=> r.offset # pass 2: sort by offset
180
- end
181
- res
182
- end
183
-
184
- ## calc score1,score2
185
- score1 = 0
186
- score2 = 0
187
- goals.each do |goal|
188
- if goal.team == 1
189
- score1 += 1
190
- elsif goal.team == 2
191
- score2 += 1
192
- else
193
- # todo: should not happen: issue warning
194
- end
195
- goal.score1 = score1
196
- goal.score2 = score2
197
- end
198
-
199
- logger.debug " #{goals.size} goals:"
200
- pp goals
201
-
202
- goals
203
- end
204
-
205
- end # class GoalsFinder
206
-
207
-
208
- class GoalsParser
209
- include LogUtils::Logging
210
-
211
-
212
- ### todo/fix:
213
- ## let's use stringscanner for parsing line - why? why not?
214
-
215
-
216
- # note: use ^ for start of string only!!!
217
- # - for now slurp everything up to digits (inlc. spaces - use strip to remove)
218
- # todo/check: use/rename to NAME_UNTIL_REGEX ??? ( add lookahead for spaces?)
219
- NAME_REGEX = /^
220
- [^0-9]+
221
- /x
222
-
223
-
224
- # todo/check: change to MINUTE_REGEX ??
225
- # add MINUTE_SKIP_REGEX or MINUTE_SEP_REGEX /^[ ,]+/
226
- # todo/fix: split out penalty and owngoal flag in PATTERN constant for reuse
227
- # note - offset 90+10 possible!!!!
228
- # note - allow p/pen./pen or o.g. or og
229
- MINUTES_REGEX = /^ # note: use ^ for start of string only!!!
230
- (?<minute>[0-9]{1,3})
231
- (?:\+
232
- (?<offset>[0-9]{1,2})
233
- )?
234
- '
235
- (?:[ ]*
236
- \(
237
- (?<type>p|pen\.?|
238
- og|o\.g\.)
239
- \)
240
- )?
241
- /ix
242
-
243
-
244
-
245
- def initialize
246
- # nothing here for now
247
- end
248
-
249
- def parse!( line, opts={} )
250
-
251
- ## for now assume
252
- ## everything up-to 0-9 and , and () is part of player name
253
-
254
- ## try parsing lhs
255
- ## todo: check for empty - remove (make it same as empty string)
256
-
257
- players = []
258
-
259
- name = get_player_name!( line )
260
- while name
261
- logger.debug " found player name >#{name}< - remaining >#{line}<"
262
-
263
- player = GoalsPlayerStruct.new
264
- player.name = name
265
-
266
- minute_hash = nil
267
- while minute_hash=get_minute_hash!( line ) ## note: returns nil if no (regex) match
268
- logger.debug " found minutes >#{minute_hash.inspect}< - remaining >#{line}<"
269
-
270
- minute = GoalsMinuteStruct.new
271
- minute.minute = minute_hash[:minute].to_i
272
- minute.offset = minute_hash[:offset].to_i if minute_hash[:offset]
273
- if minute_hash[:type]
274
- minute.owngoal = true if minute_hash[:type] =~ /og|o\.g\./i
275
- minute.penalty = true if minute_hash[:type] =~ /p|pen\.?/i
276
- end
277
- player.minutes << minute
278
-
279
- # remove commas and spaces (note: use ^ for start of string only!!!)
280
- line.sub!( /^[ ,]+/, '' )
281
- end
282
-
283
- players << player
284
- name = get_player_name!( line )
285
- end
286
-
287
- players
288
- end # method parse!
289
-
290
- private
291
- def get_player_name!( line )
292
- m = NAME_REGEX.match( line )
293
- if m
294
- ## remove from line
295
- line.slice!( 0...m[0].length )
296
- m[0].strip # remove leading and trailing spaces
297
- else
298
- nil
299
- end
300
- end
301
-
302
- def get_minute_hash!( line )
303
- m = MINUTES_REGEX.match( line ) # note: use ^ for start of string only!!!
304
- if m
305
- h = {}
306
- ## todo/fix - hash conversion no longer need in ruby 3+!!
307
- ## double check - and remove (simplify) !!!!
308
- # - note: do NOT forget to turn name into symbol for lookup in new hash (name.to_sym)
309
- m.names.each { |n| h[n.to_sym] = m[n] } # or use match_data.names.zip( match_data.captures ) - more cryptic but "elegant"??
310
-
311
- ## remove matched string from line
312
- line.slice!( 0...m[0].length )
313
- h
314
- else
315
- nil
316
- end
317
- end
318
-
319
- end # class GoalsParser
320
-
321
- end # module SportDb
@@ -1,47 +0,0 @@
1
-
2
- module SportDb
3
-
4
- class LinesReader ## change to LinesEnumerator - why? why not?
5
- def initialize( lines )
6
- @iter = lines.each ## get (external) enumerator (same as to_enum)
7
- @lineno = 0
8
- end
9
-
10
- def each( &blk )
11
- ## note - StopIteration is rescued (automagically) by Kernel#loop.
12
- ## no need to rescue ourselves here
13
- loop do
14
- line = @iter.next ## note - raises StopIteration
15
- blk.call( line )
16
- end
17
- end
18
-
19
- def each_with_index( &blk )
20
- ## note - StopIteration is rescued (automagically) by Kernel#loop.
21
- loop do
22
- line = @iter.next ## note - raises StopIteration
23
- blk.call( line, @lineno )
24
- @lineno += 1
25
- end
26
- end
27
-
28
- def peek
29
- begin
30
- @iter.peek
31
- rescue StopIteration
32
- nil
33
- end
34
- end
35
-
36
- def next
37
- ## todo/check - do NOT catch StopIteration for next - why? why not?
38
- begin
39
- line = @iter.next
40
- @lineno += 1
41
- line
42
- rescue StopIteration
43
- nil
44
- end
45
- end
46
- end # class LinesReader
47
- end # module SportDb