sportdb-formats 1.2.0 → 2.0.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.
@@ -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.0
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-22 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,313 +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
- # note: use ^ for start of string only!!!
213
- # - for now slurp everything up to digits (inlc. spaces - use strip to remove)
214
- # todo/check: use/rename to NAME_UNTIL_REGEX ??? ( add lookahead for spaces?)
215
- NAME_REGEX = /^
216
- [^0-9]+
217
- /x
218
-
219
-
220
- # todo/check: change to MINUTE_REGEX ??
221
- # add MINUTE_SKIP_REGEX or MINUTE_SEP_REGEX /^[ ,]+/
222
- # todo/fix: split out penalty and owngoal flag in PATTERN constant for reuse
223
- MINUTES_REGEX = /^ # note: use ^ for start of string only!!!
224
- (?<minute>[0-9]{1,3})
225
- (?:\+
226
- (?<offset>[1-9]{1})
227
- )?
228
- '
229
- (?:[ ]*
230
- \(
231
- (?<type>P|pen\.|o\.g\.)
232
- \)
233
- )?
234
- /x
235
-
236
-
237
-
238
- def initialize
239
- # nothing here for now
240
- end
241
-
242
- def parse!( line, opts={} )
243
-
244
- ## for now assume
245
- ## everything up-to 0-9 and , and () is part of player name
246
-
247
- ## try parsing lhs
248
- ## todo: check for empty - remove (make it same as empty string)
249
-
250
- players = []
251
-
252
- name = get_player_name!( line )
253
- while name
254
- logger.debug " found player name >#{name}< - remaining >#{line}<"
255
-
256
- player = GoalsPlayerStruct.new
257
- player.name = name
258
-
259
- minute_hash = get_minute_hash!( line )
260
- while minute_hash
261
- logger.debug " found minutes >#{minute_hash.inspect}< - remaining >#{line}<"
262
-
263
- minute = GoalsMinuteStruct.new
264
- minute.minute = minute_hash[:minute].to_i
265
- minute.offset = minute_hash[:offset].to_i if minute_hash[:offset]
266
- if minute_hash[:type]
267
- minute.owngoal = true if minute_hash[:type] =~ /o\.g\./
268
- minute.penalty = true if minute_hash[:type] =~ /P|pen\./
269
- end
270
- player.minutes << minute
271
-
272
- # remove commas and spaces (note: use ^ for start of string only!!!)
273
- line.sub!( /^[ ,]+/, '' )
274
- minute_hash = get_minute_hash!( line )
275
- end
276
-
277
- players << player
278
- name = get_player_name!( line )
279
- end
280
-
281
- players
282
- end # method parse!
283
-
284
- private
285
- def get_player_name!( line )
286
- m = NAME_REGEX.match( line )
287
- if m
288
- ## remove from line
289
- line.slice!( 0...m[0].length )
290
- m[0].strip # remove leading and trailing spaces
291
- else
292
- nil
293
- end
294
- end
295
-
296
- def get_minute_hash!( line )
297
- m = MINUTES_REGEX.match( line ) # note: use ^ for start of string only!!!
298
- if m
299
- h = {}
300
- # - note: do NOT forget to turn name into symbol for lookup in new hash (name.to_sym)
301
- m.names.each { |n| h[n.to_sym] = m[n] } # or use match_data.names.zip( match_data.captures ) - more cryptic but "elegant"??
302
-
303
- ## remove matched string from line
304
- line.slice!( 0...m[0].length )
305
- h
306
- else
307
- nil
308
- end
309
- end
310
-
311
- end # class GoalsParser
312
-
313
- 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