score-formats 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 0ce70736d26500e8d3866ae96a2f75e6ce8c1725
4
+ data.tar.gz: 16e29461a44bb4c232039b82f38d805596b47acf
5
+ SHA512:
6
+ metadata.gz: 28ed77625082a2f1b29713b03cf92354df0799c8b0189f5e40e4ccddbad34cb6e14b286b580adee97304cb3b84e8c14064dd26c33efcfea80e94e4caa7354d6e
7
+ data.tar.gz: bf023b724232b5ca987615d223c8d8aef928a3afcf7b9807e1f1b9167fbabfc2a80d78404220cb5de6633585be885ea5e9f05c0770d28da59a1cb89d827b63b7
@@ -0,0 +1,3 @@
1
+ ### 0.0.1 / 2020-08-17
2
+
3
+ * Everything is new. First release.
@@ -0,0 +1,13 @@
1
+ CHANGELOG.md
2
+ Manifest.txt
3
+ README.md
4
+ Rakefile
5
+ lib/score-formats.rb
6
+ lib/score-formats/formats.rb
7
+ lib/score-formats/parser.rb
8
+ lib/score-formats/score.rb
9
+ lib/score-formats/version.rb
10
+ lib/score/formats.rb
11
+ test/helper.rb
12
+ test/test_formats.rb
13
+ test/test_score.rb
@@ -0,0 +1,29 @@
1
+ # score-formats - read / parse and print sports match scores (incl. half time, full time, extra time, penalties and more)
2
+
3
+
4
+ * home :: [github.com/sportdb/sport.db](https://github.com/sportdb/sport.db)
5
+ * bugs :: [github.com/sportdb/sport.db/issues](https://github.com/sportdb/sport.db/issues)
6
+ * gem :: [rubygems.org/gems/score-formats](https://rubygems.org/gems/score-formats)
7
+ * rdoc :: [rubydoc.info/gems/score-formats](http://rubydoc.info/gems/score-formats)
8
+ * forum :: [opensport](http://groups.google.com/group/opensport)
9
+
10
+
11
+ ## Usage
12
+
13
+
14
+ to be done
15
+
16
+
17
+
18
+
19
+ ## License
20
+
21
+ The `score-formats` scripts are dedicated to the public domain.
22
+ Use it as you please with no restrictions whatsoever.
23
+
24
+
25
+ ## Questions? Comments?
26
+
27
+ Send them along to the
28
+ [Open Sports & Friends Forum/Mailing List](http://groups.google.com/group/opensport).
29
+ Thanks!
@@ -0,0 +1,27 @@
1
+ require 'hoe'
2
+ require './lib/score-formats/version.rb'
3
+
4
+ Hoe.spec 'score-formats' do
5
+
6
+ self.version = ScoreFormats::VERSION
7
+
8
+ self.summary = "score-formats - read / parse and print sports match scores (incl. half time, full time, extra time, penalties and more)"
9
+ self.description = summary
10
+
11
+ self.urls = ['https://github.com/sportdb/sport.db']
12
+
13
+ self.author = 'Gerald Bauer'
14
+ self.email = 'opensport@googlegroups.com'
15
+
16
+ # switch extension to .markdown for gihub formatting
17
+ self.readme_file = 'README.md'
18
+ self.history_file = 'CHANGELOG.md'
19
+
20
+ self.licenses = ['Public Domain']
21
+
22
+ self.extra_deps = []
23
+
24
+ self.spec_extras = {
25
+ required_ruby_version: '>= 2.2.2'
26
+ }
27
+ end
@@ -0,0 +1,72 @@
1
+ require 'pp'
2
+ require 'date'
3
+ require 'time'
4
+
5
+
6
+ ###
7
+ # our own code
8
+ require 'score-formats/version' # let version always go first
9
+
10
+ ## todo/fix: make logging class configurable - lets you use logutils etc.
11
+ module ScoreFormats
12
+ module Logging
13
+ def logger() @logger ||= Logger.new; end
14
+
15
+ class Logger ## for now use quick "dummy" logger to
16
+ def debug( msg ) puts "[debug] #{msg}"; end
17
+ end # class Logger
18
+ end # module Logging
19
+ end # module ScoreFormats
20
+
21
+
22
+
23
+ require 'score-formats/score'
24
+ require 'score-formats/formats'
25
+ require 'score-formats/parser'
26
+
27
+
28
+
29
+ module ScoreFormats
30
+ def self.lang
31
+ @@lang ||= :en ## defaults to english (:en)
32
+ end
33
+ def self.lang=( value )
34
+ @@lang = value.to_sym ## note: make sure lang is always a symbol for now (NOT a string)
35
+ @@lang ## todo/check: remove =() method always returns passed in value? double check
36
+ end
37
+
38
+ def self.parser( lang: ) ## find parser
39
+ lang = lang.to_sym ## note: make sure lang is always a symbol for now (NOT a string)
40
+
41
+ ## note: cache all "built-in" lang versions (e.g. formats == nil)
42
+ @@parser ||= {}
43
+ parser = @@parser[ lang ] ||= ScoreParser.new( lang: lang )
44
+ end
45
+
46
+ def self.parse( line, lang: ScoreFormats.lang )
47
+ parser( lang: lang ).parse( line )
48
+ end
49
+
50
+ def self.find!( line, lang: ScoreFormats.lang )
51
+ parser( lang: lang ).find!( line )
52
+ end
53
+ end
54
+
55
+
56
+
57
+ ##
58
+ # add more convenience / shortcut helpers / named ctors to score class itself
59
+
60
+ class Score
61
+ def self.parse( line, lang: ScoreFormats.lang )
62
+ ScoreFormats.parse( line, lang: lang )
63
+ end
64
+
65
+ def self.find!( line, lang: ScoreFormats.lang )
66
+ ScoreFormats.find!( line, lang: lang )
67
+ end
68
+ end
69
+
70
+
71
+
72
+ puts ScoreFormats.banner # say hello
@@ -0,0 +1,239 @@
1
+
2
+ module ScoreFormats
3
+
4
+ ## todo/check: use ‹› (unicode chars) to mark optional parts in regex constant name - why? why not?
5
+
6
+ #####
7
+ # english helpers (penalty, extra time, ...)
8
+ P_EN = '(?: p | pen\.? | pso )' # e.g. p, pen, pen., PSO, etc.
9
+ ET_EN = '(?: aet | a\.e\.t\.? )' # note: make last . optional (e.g a.e.t) allowed too
10
+
11
+
12
+ ## note: allow SPECIAL cases WITHOUT full time scores (just a.e.t or pen. + a.e.t.)
13
+ ## 3-4 pen. 2-2 a.e.t.
14
+ ## 2-2 a.e.t.
15
+ EN__P_ET__RE = /\b
16
+ (?:
17
+ (?<score1p>\d{1,2})
18
+ [ ]* - [ ]* # note: sep in optional block; CANNOT use a reference
19
+ (?<score2p>\d{1,2})
20
+ [ ]* #{P_EN} [ ]*
21
+ )? # note: make penalty (P) score optional for now
22
+ (?<score1et>\d{1,2})
23
+ [ ]* - [ ]*
24
+ (?<score2et>\d{1,2})
25
+ [ ]* #{ET_EN}
26
+ (?=[ \]]|$)/xi ## todo/check: remove loakahead assertion here - why require space?
27
+ ## note: \b works only after non-alphanum e.g. )
28
+
29
+
30
+ ## e.g. 3-4 pen. 2-2 a.e.t. (1-1, 1-1) or
31
+ ## 3-4 pen. 2-2 a.e.t. (1-1, ) or
32
+ ## 3-4 pen. 2-2 a.e.t. (1-1) or
33
+ ## 2-2 a.e.t. (1-1, 1-1) or
34
+ ## 2-2 a.e.t. (1-1, ) or
35
+ ## 2-2 a.e.t. (1-1)
36
+
37
+ EN__P_ET_FT_HT__RE = /\b
38
+ (?:
39
+ (?<score1p>\d{1,2})
40
+ [ ]* - [ ]* # note: sep in optional block; CANNOT use a reference
41
+ (?<score2p>\d{1,2})
42
+ [ ]* #{P_EN} [ ]*
43
+ )? # note: make penalty (P) score optional for now
44
+ (?<score1et>\d{1,2})
45
+ [ ]* - [ ]*
46
+ (?<score2et>\d{1,2})
47
+ [ ]* #{ET_EN} [ ]*
48
+ \(
49
+ [ ]*
50
+ (?<score1>\d{1,2})
51
+ [ ]* - [ ]*
52
+ (?<score2>\d{1,2})
53
+ [ ]*
54
+ (?:
55
+ , [ ]*
56
+ (?: (?<score1i>\d{1,2})
57
+ [ ]* - [ ]*
58
+ (?<score2i>\d{1,2})
59
+ [ ]*
60
+ )?
61
+ )? # note: make half time (HT) score optional for now
62
+ \)
63
+ (?=[ \]]|$)/xi ## todo/check: remove loakahead assertion here - why require space?
64
+ ## note: \b works only after non-alphanum e.g. )
65
+
66
+ ###
67
+ ## special case for case WITHOUT extra time!!
68
+ ## same as above (but WITHOUT extra time and pen required)
69
+ EN__P_FT_HT__RE = /\b
70
+ (?<score1p>\d{1,2})
71
+ [ ]* - [ ]* # note: sep in optional block; CANNOT use a reference
72
+ (?<score2p>\d{1,2})
73
+ [ ]* #{P_EN} [ ]*
74
+ \(
75
+ [ ]*
76
+ (?<score1>\d{1,2})
77
+ [ ]* - [ ]*
78
+ (?<score2>\d{1,2})
79
+ [ ]*
80
+ (?:
81
+ , [ ]*
82
+ (?: (?<score1i>\d{1,2})
83
+ [ ]* - [ ]*
84
+ (?<score2i>\d{1,2})
85
+ [ ]*
86
+ )?
87
+ )? # note: make half time (HT) score optional for now
88
+ \)
89
+ (?=[ \]]|$)/xi ## todo/check: remove loakahead assertion here - why require space?
90
+ ## note: \b works only after non-alphanum e.g. )
91
+
92
+
93
+
94
+ ## e.g. 2-1 (1-1) or
95
+ ## 2-1
96
+ ## note: for now add here used in Brazil / Portugal
97
+ ## e.g 1x1 or 1X1 or 0x2 or 3x3 too
98
+ ## todo/check/fix: move to its own use PT__FT_HT etc!!!!
99
+
100
+ EN__FT_HT__RE = /\b
101
+ (?<score1>\d{1,2})
102
+ [ ]* (?<sep>[x-]) [ ]*
103
+ (?<score2>\d{1,2})
104
+ (?:
105
+ [ ]* \( [ ]*
106
+ (?<score1i>\d{1,2})
107
+ [ ]* \k<sep> [ ]*
108
+ (?<score2i>\d{1,2})
109
+ [ ]* \)
110
+ )? # note: make half time (HT) score optional for now
111
+ (?=[ \]]|$)/xi ## todo/check: remove loakahead assertion here - why require space?
112
+ ## note: \b works only after non-alphanum e.g. )
113
+
114
+
115
+ #####
116
+ # deutsch / german helpers (penalty, extra time, ...)
117
+ ## todo add more marker e.g. im Elf. or such!!!
118
+ P_DE = '(?: ie | i\.e\.? )' # e.g. iE, i.E., i.E etc.
119
+ ET_DE = '(?: nv | n\.v\.? )' # e.g. nV, n.V., n.V etc.
120
+
121
+
122
+ ## support alternate all-in-one score e.g.
123
+ ## i.E. 2:4, n.V. 3:3 (1:1, 1:1) or
124
+ ## n.V. 3:2 (2:2, 1:2)
125
+ DE__P_ET_FT_HT__RE = /\b
126
+ (?:
127
+ #{P_DE}
128
+ [ ]*
129
+ (?<score1p>\d{1,2})
130
+ [ ]* : [ ]*
131
+ (?<score2p>\d{1,2})
132
+ [ ]* (?:, [ ]*)?
133
+ )? # note: make penalty (P) score optional for now
134
+ #{ET_DE}
135
+ [ ]*
136
+ (?<score1et>\d{1,2})
137
+ [ ]* : [ ]*
138
+ (?<score2et>\d{1,2})
139
+ [ ]*
140
+ \(
141
+ [ ]*
142
+ (?<score1>\d{1,2})
143
+ [ ]* : [ ]*
144
+ (?<score2>\d{1,2})
145
+ [ ]*
146
+ (?:
147
+ , [ ]*
148
+ (?:
149
+ (?<score1i>\d{1,2})
150
+ [ ]* : [ ]*
151
+ (?<score2i>\d{1,2})
152
+ [ ]*
153
+ )?
154
+ )? # note: make half time (HT) score optional for now
155
+ \)
156
+ (?=[ \]]|$)
157
+ /xi
158
+
159
+ ## support all-in-one "literal form e.g.
160
+ # 2:2 (1:1, 1:0) n.V. 5:1 i.E. or
161
+ # 2-2 (1-1, 1-0) n.V. 5-1 i.E.
162
+ DE__ET_FT_HT_P__RE = /\b
163
+ (?<score1et>\d{1,2})
164
+ [ ]* (?<sep>[:-]) [ ]* ## note: for now allow : or - as separator!!
165
+ (?<score2et>\d{1,2})
166
+ [ ]*
167
+ \(
168
+ [ ]*
169
+ (?<score1>\d{1,2})
170
+ [ ]* \k<sep> [ ]*
171
+ (?<score2>\d{1,2})
172
+ [ ]*
173
+ (?:
174
+ , [ ]*
175
+ (?:
176
+ (?<score1i>\d{1,2})
177
+ [ ]* \k<sep> [ ]*
178
+ (?<score2i>\d{1,2})
179
+ [ ]*
180
+ )?
181
+ )? # note: make half time (HT) score optional for now
182
+ \)
183
+ [ ]*
184
+ #{ET_DE}
185
+ (?:
186
+ [ ]*
187
+ (?<score1p>\d{1,2})
188
+ [ ]* \k<sep> [ ]*
189
+ (?<score2p>\d{1,2})
190
+ [ ]*
191
+ #{P_DE}
192
+ )? # note: make penalty (P) score optional for now
193
+ (?=[ \]]|$)
194
+ /xi ## todo/check: remove loakahead assertion here - why require space?
195
+ ## note: \b works only after non-alphanum e.g. )
196
+
197
+
198
+ ## e.g. 2:1 (1:1) or
199
+ ## 2-1 (1-1) or
200
+ ## 2:1 or
201
+ ## 2-1
202
+ DE__FT_HT__RE = /\b
203
+ (?<score1>\d{1,2})
204
+ [ ]* (?<sep>[:-]) [ ]*
205
+ (?<score2>\d{1,2})
206
+ (?:
207
+ [ ]* \( [ ]*
208
+ (?<score1i>\d{1,2})
209
+ [ ]* \k<sep> [ ]*
210
+ (?<score2i>\d{1,2})
211
+ [ ]* \)
212
+ )? # note: make half time (HT) score optional for now
213
+ (?=[ \]]|$)/x ## todo/check: remove loakahead assertion here - why require space?
214
+ ## note: \b works only after non-alphanum e.g. )
215
+
216
+
217
+ #############################################
218
+ # map tables - 1) regex, 2) tag - note: order matters; first come-first matched/served
219
+
220
+
221
+ FORMATS_EN = [
222
+ [ EN__P_ET_FT_HT__RE, '[SCORE.EN__P?_ET_(FT_HT?)]' ], # e.g. 5-1 pen. 2-2 a.e.t. (1-1, 1-0)
223
+ [ EN__P_FT_HT__RE, '[SCORE.EN__P_(FT_HT?)]' ], # e.g. 5-1 pen. (1-1)
224
+ [ EN__P_ET__RE, '[SCORE.EN__P?_ET]' ], # e.g. 2-2 a.e.t. or 5-1 pen. 2-2 a.e.t.
225
+ [ EN__FT_HT__RE, '[SCORE.EN__FT_(HT)?]' ], # e.g. 1-1 (1-0)
226
+ ]
227
+
228
+ FORMATS_DE = [
229
+ [ DE__ET_FT_HT_P__RE, '[SCORE.DE__ET_(FT_HT?)_P?]' ], # e.g. 2:2 (1:1, 1:0) n.V. 5:1 i.E.
230
+ [ DE__P_ET_FT_HT__RE, '[SCORE.DE__P?_ET_(FT_HT?)]' ], # e.g. i.E. 2:4, n.V. 3:3 (1:1, 1:1)
231
+ [ DE__FT_HT__RE, '[SCORE.DE__FT_(HT)?]' ], # e.g. 1:1 (1:0)
232
+ ]
233
+
234
+ FORMATS = {
235
+ en: FORMATS_EN,
236
+ de: FORMATS_DE,
237
+ }
238
+
239
+ end # module ScoreFormats
@@ -0,0 +1,129 @@
1
+
2
+ module ScoreFormats
3
+
4
+ class ScoreParser
5
+
6
+ include Logging
7
+
8
+ def initialize( lang: )
9
+ @lang = lang.to_sym ## note: make sure lang is always a symbol for now (NOT a string)
10
+
11
+ ## fallback to english if lang not available
12
+ ## todo/fix: add/issue warning - why? why not?
13
+ @formats = FORMATS[ @lang ] || FORMATS[ :en ]
14
+ end
15
+
16
+
17
+ def parse( line )
18
+
19
+ ##########
20
+ ## todo/fix/check: add unicode to regular dash conversion - why? why not?
21
+ ## e.g. – becomes - (yes, the letters a different!!!)
22
+ #############
23
+
24
+ score = nil
25
+ @formats.each do |format|
26
+ re = format[0]
27
+ m = re.match( line )
28
+ if m
29
+ score = parse_matchdata( m )
30
+ break
31
+ end
32
+ # no match; continue; try next regex pattern
33
+ end
34
+
35
+ ## todo/fix - raise ArgumentError - invalid score; no format match found
36
+ score # note: nil if no match found
37
+ end # method parse
38
+
39
+
40
+ def find!( line )
41
+ ### fix: add and match all-in-one literal first, followed by
42
+
43
+ # note: always call after find_dates !!!
44
+ # scores match date-like patterns!! e.g. 10-11 or 10:00 etc.
45
+ # -- note: score might have two digits too
46
+
47
+ ### fix: depending on language allow 1:1 or 1-1
48
+ ## do NOT allow mix and match
49
+ ## e.g. default to en is 1-1
50
+ ## de is 1:1 etc.
51
+
52
+
53
+ # extract score from line
54
+ # and return it
55
+ # note: side effect - removes date from line string
56
+
57
+ score = nil
58
+ @formats.each do |format|
59
+ re = format[0]
60
+ tag = format[1]
61
+ m = re.match( line )
62
+ if m
63
+ score = parse_matchdata( m )
64
+ line.sub!( m[0], tag )
65
+ break
66
+ end
67
+ # no match; continue; try next regex pattern
68
+ end
69
+
70
+ score # note: nil if no match found
71
+ end # method find!
72
+
73
+ private
74
+ def parse_matchdata( m )
75
+ # convert regex match_data captures to hash
76
+ # - note: cannont use match_data like a hash (e.g. raises exception if key/name not present/found)
77
+ h = {}
78
+ # - note: do NOT forget to turn name into symbol for lookup in new hash (name.to_sym)
79
+ m.names.each { |name| h[name.to_sym] = m[name] } # or use match_data.names.zip( match_data.captures ) - more cryptic but "elegant"??
80
+
81
+ ## puts "[parse_date_time] match_data:"
82
+ ## pp h
83
+ logger.debug " [parse_matchdata] hash: >#{h.inspect}<"
84
+
85
+ score1i = nil # half time (ht) scores
86
+ score2i = nil
87
+
88
+ score1 = nil # full time (ft) scores
89
+ score2 = nil
90
+
91
+ score1et = nil # extra time (et) scores
92
+ score2et = nil
93
+
94
+ score1p = nil # penalty (p) scores
95
+ score2p = nil
96
+
97
+
98
+ if h[:score1i] && h[:score2i] ## note: half time (HT) score is optional now
99
+ score1i = h[:score1i].to_i
100
+ score2i = h[:score2i].to_i
101
+ end
102
+
103
+ if h[:score1] && h[:score2] ## note: full time (FT) score can be optional too!!!
104
+ score1 = h[:score1].to_i
105
+ score2 = h[:score2].to_i
106
+ end
107
+
108
+ if h[:score1et] && h[:score2et]
109
+ score1et = h[:score1et].to_i
110
+ score2et = h[:score2et].to_i
111
+ end
112
+
113
+ if h[:score1p] && h[:score2p]
114
+ score1p = h[:score1p].to_i
115
+ score2p = h[:score2p].to_i
116
+ end
117
+
118
+ score = Score.new( score1i, score2i,
119
+ score1, score2,
120
+ score1et, score2et,
121
+ score1p, score2p )
122
+ score
123
+ end # method parse_matchdata
124
+
125
+
126
+
127
+ end # class ScoreParser
128
+ end # module ScoreFormats
129
+
@@ -0,0 +1,165 @@
1
+
2
+
3
+ ## note: make Score top-level and use like Date - yes, yes, yes - why? why not?
4
+ class Score
5
+
6
+ SCORE_SPLIT_RE = %r{^ [ ]*
7
+ ([0-9]+)
8
+ [ ]*
9
+ [:x–-] ## note: allow some unicode dashes too
10
+ [ ]*
11
+ ([0-9]+)
12
+ [ ]* $}xi
13
+
14
+ def self.split( str ) ## note: return array of two integers or empty array
15
+ ## e.g. allow/support
16
+ ## 1-1 or 1 - 1 - "english" style
17
+ ## 1:1 - "german / deutsch" style
18
+ ## 1x1 1X1 - "brazil/portugese" style
19
+
20
+ ## note: add unicode "fancy" dash too (e.g. –)
21
+ ## add some more - why? why not?
22
+
23
+ if m=SCORE_SPLIT_RE.match(str)
24
+ [m[1].to_i, m[2].to_i]
25
+ else
26
+ # no match - warn if str is not empty? why? why not?
27
+ ##
28
+ ## todo/fix:
29
+ ## do NOT warn on
30
+ ## assert_equal [], Score.split( '-' )
31
+ ## assert_equal [], Score.split( '-:-' )
32
+ ## assert_equal [], Score.split( '?' )
33
+ ## for now - add more?
34
+
35
+ puts "!! WARN - cannot match (split) score format >#{str}<" unless str.empty?
36
+ []
37
+ end
38
+ end
39
+
40
+
41
+
42
+
43
+ attr_reader :score1i, :score2i, # half time (ht) score
44
+ :score1, :score2, # full time (ft) score
45
+ :score1et, :score2et, # extra time (et) score
46
+ :score1p, :score2p # penalty (p) score
47
+ ## todo/fix: add :score1agg, score2agg too - why? why not?!!!
48
+ ## add state too e.g. canceled or abadoned etc - why? why not?
49
+
50
+ ## alternate accessor via array e.g. ft[0] and ft[1]
51
+ def ft() [@score1, @score2]; end ## e.g. 90 mins (in football)
52
+ def ht() [@score1i, @score2i]; end ## e.g. 45 mins
53
+ def et() [@score1et, @score2et]; end ## e.g. 90+15mins
54
+ def p() [@score1p, @score2p]; end ## e.g. note - starts "fresh" score from 0-0
55
+ alias_method :pen, :p ## add alias - why? why not?
56
+
57
+ ## todo/check: allow one part missing why? why not?
58
+ ## e.g. 1-nil or nil-1 - why? why not?
59
+ def ft?() @score1 && @score2; end
60
+ def ht?() @score1i && @score2i; end
61
+ def et?() @score1et && @score2et; end
62
+ def p?() @score1p && @score2p; end
63
+ alias_method :pen?, :p?
64
+
65
+
66
+
67
+ def initialize( *values )
68
+ ## note: for now always assumes integers
69
+ ## todo/check - check/require integer args - why? why not?
70
+
71
+ ### todo/fix: add more init options
72
+ ## allow kwargs (keyword args) via hash - why? why not?
73
+ ## use kwargs for "perfect" init where you can only set the half time (ht) score
74
+ ## or only the penalty or other "edge" cases
75
+ ## allow int pairs e.g. [1,2], [2,2]
76
+ ## allow values array MUST be of size 8 (or 4 or 6) - why? why not?
77
+
78
+ raise ArgumentError, "expected even integer number (pairs), but got #{values.size}" if values.size % 2 == 1
79
+
80
+ if values.size == 2
81
+ @score1 = values[0] # full time (ft) score
82
+ @score2 = values[1]
83
+
84
+ @score1i = @score2i = nil
85
+ @score1et = @score2et = nil
86
+ @score1p = @score2p = nil
87
+ else
88
+ @score1i = values[0] # half time (ht) score
89
+ @score2i = values[1]
90
+
91
+ @score1 = values[2] # full time (ft) score
92
+ @score2 = values[3]
93
+
94
+ @score1et = values[4] # extra time (et) score
95
+ @score2et = values[5]
96
+
97
+ @score1p = values[6] # penalty (p) score
98
+ @score2p = values[7]
99
+ end
100
+ end
101
+
102
+
103
+
104
+ def to_h( format = :default )
105
+ case format.to_sym
106
+ when :default, :std
107
+ ## check/todo: only add entries if ft, ht, etc. have values (non-null) or always - why? why not?
108
+ h = {}
109
+ h[:ht] = [@score1i, @score2i] if @score1i || @score2i
110
+ h[:ft] = [@score1, @score2] if @score1 || @score2
111
+ h[:et] = [@score1et, @score2et] if @score1et || @score2et
112
+ h[:p] = [@score1p, @score2p] if @score1p || @score2p
113
+ h
114
+ when :db
115
+ ## use a "flat" structure with "internal" std names
116
+ { score1i: @score1i, score2i: @score2i,
117
+ score1: @score1, score2: @score2,
118
+ score1et: @score1et, score2et: @score2et,
119
+ score1p: @score1p, score2p: @score2p
120
+ }
121
+ else
122
+ puts "!! ERROR: unknown score to_h format >#{format}<"
123
+ exit 1
124
+ end
125
+ end
126
+ alias_method :to_hash, :to_h ## add alias - why? why not?
127
+
128
+
129
+ def values
130
+ ## todo/ fix: always return complete array
131
+ ## e.g. [score1i, score2i, score1, score2, score1et, score2et, score1p, score2p]
132
+
133
+ ## todo: how to handle game w/o extra time
134
+ # but w/ optional penalty ??? e.g. used in copa liberatores, for example
135
+ # retrun 0,0 or nil,nil for extra time score ?? or -1, -1 ??
136
+ # for now use nil,nil
137
+ score = []
138
+ score += [@score1i, @score2i] if @score1p || @score2p || @score1et || @score2et || @score1 || score2 || score1i || score2i
139
+ score += [@score1, @score2] if @score1p || @score2p || @score1et || @score2et || @score1 || score2
140
+ score += [@score1et, @score2et] if @score1p || @score2p || @score1et || @score2et
141
+ score += [@score1p, @score2p] if @score1p || @score2p
142
+ score
143
+ end
144
+
145
+ def to_a
146
+ ## pairs with values
147
+ pairs = []
148
+ ## note: allow 1-nil, nil-1 for now in pairs (or use && and NOT ||) - why? why not?
149
+ pairs << [@score1i, @score2i] if @score1i || @score2i
150
+ pairs << [@score1, @score2] if @score1 || @score2
151
+ pairs << [@score1et, @score2et] if @score1et || @score2et
152
+ pairs << [@score1p, @score2p] if @score1p || @score2p
153
+
154
+ if pairs.empty?
155
+ pairs # e.g. return []
156
+ elsif pairs.size == 1
157
+ pairs[0] # return single pair "unwrapped" e.g. [0,1] instead of [[0,1]] - why? why not?
158
+ else
159
+ pairs
160
+ end
161
+ end
162
+
163
+ end # class Score
164
+
165
+
@@ -0,0 +1,19 @@
1
+
2
+ module ScoreFormats
3
+ MAJOR = 0 ## todo: namespace inside version or something - why? why not??
4
+ MINOR = 0
5
+ PATCH = 1
6
+ VERSION = [MAJOR,MINOR,PATCH].join('.')
7
+
8
+ def self.version
9
+ VERSION
10
+ end
11
+
12
+ def self.banner
13
+ "score-formats/#{VERSION} on Ruby #{RUBY_VERSION} (#{RUBY_RELEASE_DATE}) [#{RUBY_PLATFORM}]"
14
+ end
15
+
16
+ def self.root
17
+ File.expand_path( File.dirname(File.dirname(File.dirname(__FILE__))) )
18
+ end
19
+ end # module ScoreFormats
@@ -0,0 +1,5 @@
1
+ # note: allow require 'score/formats' too
2
+ # (in addition to require 'score-formats')
3
+
4
+ require_relative '../score-formats'
5
+
@@ -0,0 +1,12 @@
1
+ ## $:.unshift(File.dirname(__FILE__))
2
+
3
+ ## minitest setup
4
+
5
+ require 'minitest/autorun'
6
+
7
+
8
+ ## our own code
9
+
10
+ require 'score/formats' ## or require 'score-formats'
11
+
12
+
@@ -0,0 +1,129 @@
1
+ ###
2
+ # to run use
3
+ # ruby -I ./lib -I ./test test/test_formats.rb
4
+
5
+
6
+ require 'helper'
7
+
8
+ class TestFormats < MiniTest::Test
9
+
10
+ def test_de
11
+ ScoreFormats.lang = :de
12
+ data = [
13
+ [ '10:0', [nil,nil, 10,0]],
14
+ [ '1:22', [nil,nil, 1,22]],
15
+ [ '1-22', [nil,nil, 1,22]],
16
+
17
+ ## do not support three digits for now - why? why not?
18
+ [ '1:222', nil],
19
+ [ '111:0', nil],
20
+ [ '1-222', nil],
21
+ [ '111-0', nil],
22
+
23
+ [ '2:2 (1:1, 1:0) n.V. 5:1 i.E.', [1,0, 1,1, 2,2, 5,1]],
24
+ [ '2:2 (1:1, 1:0) n.V.', [1,0, 1,1, 2,2]],
25
+ [ '2:2 (1:1, ) n.V. 5:1 i.E.', [nil,nil, 1,1, 2,2, 5,1]],
26
+ [ '2:2 (1:1, ) n.V.', [nil,nil, 1,1, 2,2]],
27
+
28
+ [ '2:2 (1:1) n.V. 5:1 i.E.', [nil,nil, 1,1, 2,2, 5,1]],
29
+ [ '2:2 (1:1) n.V.', [nil,nil, 1,1, 2,2]],
30
+
31
+ [ '2-2 (1-1, 1-0) n.V. 5-1 i.E.', [1,0, 1,1, 2,2, 5,1]],
32
+ [ '2-2 (1-1, 1-0) n.V.', [1,0, 1,1, 2,2]],
33
+ [ '2-2 (1-1, ) n.V. 5-1 i.E.', [nil,nil, 1,1, 2,2, 5,1]],
34
+ [ '2-2 (1-1, ) n.V.', [nil,nil, 1,1, 2,2]],
35
+
36
+ [ '2 : 2 ( 1 : 1 , 1 : 0 ) n.V. 5 : 1 i.E.', [1,0, 1,1, 2,2, 5,1]],
37
+ [ '2 : 2 ( 1 : 1 , 1 : 0 ) n.V.', [1,0, 1,1, 2,2]],
38
+ [ '2 : 2 ( 1 : 1 , ) n.V. 5 : 1 i.E.', [nil,nil, 1,1, 2,2, 5,1]],
39
+ [ '2 : 2 ( 1 : 1 , ) n.V.', [nil,nil, 1,1, 2,2]],
40
+
41
+ ## alternate format
42
+ ['i.E. 2:4, n.V. 3:3 (1:1, 1:0)', [1,0, 1,1, 3,3, 2,4]],
43
+ ['iE 2:4 nV 3:3 (1:1, 1:0)', [1,0, 1,1, 3,3, 2,4]],
44
+ ['i.E. 2:4 n.V. 3:3 (1:1, 1:0)', [1,0, 1,1, 3,3, 2,4]],
45
+ ['i.E. 2:4, n.V. 3:3 (1:1)', [nil, nil, 1,1, 3,3, 2,4]],
46
+ ['i.E. 2:4 n.V. 3:3 (1:1)', [nil, nil, 1,1, 3,3, 2,4]],
47
+ ['n.V. 3:2 (2:2, 1:2)', [1,2, 2,2, 3,2]],
48
+ ['n.V. 3:2 (2:2)', [nil, nil, 2,2, 3,2]],
49
+ ]
50
+
51
+ assert_score( data )
52
+ end
53
+
54
+ def test_en
55
+ ScoreFormats.lang = :en
56
+
57
+ data = [
58
+ [ '1-22', [nil,nil, 1, 22]],
59
+ [ '1x22', [nil,nil, 1, 22]],
60
+ [ '1X22', [nil,nil, 1,22]],
61
+
62
+ ## do not support three digits for now - why? why not?
63
+ [ '1-222', nil],
64
+ [ '111-0', nil],
65
+ [ '111x0', nil],
66
+ [ '111X0', nil],
67
+
68
+ ## do not support colon sep for now in en locale - why? why not?
69
+ [ '2:1', nil],
70
+ [ '2:1 (1:1)', nil],
71
+
72
+
73
+ [ '2-1 (1-1)', [1,1, 2,1]],
74
+ [ '2x1 (1x1)', [1,1, 2,1]],
75
+ [ '2X1 (1X1)', [1,1, 2,1]],
76
+
77
+ [ '2-1 a.e.t. (1-1, 0-0)', [0,0, 1,1, 2,1]],
78
+ [ '2-1aet (1-1, 0-0)', [0,0, 1,1, 2,1]],
79
+ [ '2-1 A.E.T. (1-1, 0-0)', [0,0, 1,1, 2,1]],
80
+ [ '2-1AET (1-1, 0-0)', [0,0, 1,1, 2,1]],
81
+ [ '2-1 a.e.t.', [nil,nil,nil,nil, 2,1]],
82
+
83
+ [ '3-4 pen. 2-2 a.e.t. (1-1, 1-1)', [1,1, 1,1, 2,2, 3,4]],
84
+ [ '3-4 pen 2-2 a.e.t. (1-1, 1-1)', [1,1, 1,1, 2,2, 3,4]],
85
+ [ '3-4 pen 2-2 a.e.t. (1-1, 1-1)', [1,1, 1,1, 2,2, 3,4]],
86
+ [ '3-4p 2-2aet (1-1, 1-1)', [1,1, 1,1, 2,2, 3,4]],
87
+ [ '3-4PSO 2-2AET (1-1, 1-1)', [1,1, 1,1, 2,2, 3,4]],
88
+ [ '3-4 pen. 2-2 a.e.t.', [nil,nil,nil,nil, 2,2, 3,4]],
89
+
90
+ [ '4-3 pen. 1-0 a.e.t. (1-0, )', [nil,nil, 1,0, 1,0, 4,3]],
91
+ [ '3-4 pen. 2-1 a.e.t. (2-1, )', [nil,nil, 2,1, 2,1, 3,4]],
92
+ [ '4-1 a.e.t. (3-1, )', [nil,nil, 3,1, 4,1]],
93
+ [ '3-4aet (1-1,)', [nil,nil, 1,1, 3,4]],
94
+ [ '3-4 a.e.t. (1-1,)', [nil,nil, 1,1, 3,4]],
95
+
96
+ [ '4-3 pen. 1-0 a.e.t. (1-0)', [nil,nil, 1,0, 1,0, 4,3]],
97
+ [ '3-4 pen. 2-1 a.e.t. (2-1)', [nil,nil, 2,1, 2,1, 3,4]],
98
+ [ '4-1 a.e.t. (3-1)', [nil,nil, 3,1, 4,1]],
99
+ [ '3-4aet (1-1)', [nil,nil, 1,1, 3,4]],
100
+ [ '3-4 a.e.t. (1-1)', [nil,nil, 1,1, 3,4]],
101
+
102
+ [ '3-1 pen (1-1)', [nil,nil, 1,1, nil, nil, 3,1]],
103
+
104
+ ## try with more "liberal" spaces
105
+ [ '2 - 1 ( 1 - 1 )', [1,1, 2,1]],
106
+ [ '2 - 1 a.e.t. ( 1 - 1 , 0 - 0 )', [0,0, 1,1, 2,1]],
107
+ [ '4 - 1 a.e.t. ( 3 - 1, )', [nil,nil, 3,1, 4,1]],
108
+ ]
109
+
110
+ assert_score( data )
111
+ end
112
+
113
+ private
114
+ def assert_score( data )
115
+ data.each do |rec|
116
+ line = rec[0].dup
117
+ exp = rec[1]
118
+
119
+ score = ScoreFormats.find!( line )
120
+ ## pp score
121
+
122
+ if exp.nil?
123
+ assert_nil score, "failed >#{rec[0]}< - >#{line}<"
124
+ else
125
+ assert_equal exp, score.values, "failed >#{rec[0]}< - >#{line}<"
126
+ end
127
+ end
128
+ end
129
+ end # class TestScores
@@ -0,0 +1,76 @@
1
+ ###
2
+ # to run use
3
+ # ruby -I ./lib -I ./test test/test_score.rb
4
+
5
+
6
+ require 'helper'
7
+
8
+ class TestScore < MiniTest::Test
9
+
10
+ def test_split
11
+ assert_equal [], Score.split( '' )
12
+ assert_equal [], Score.split( '-' )
13
+ assert_equal [], Score.split( '-:-' )
14
+ assert_equal [], Score.split( '?' )
15
+
16
+ assert_equal [0,1], Score.split( '0-1' )
17
+ assert_equal [0,1], Score.split( ' 0 - 1 ' )
18
+ assert_equal [0,1], Score.split( '0:1' )
19
+ assert_equal [0,1], Score.split( ' 0 : 1 ' )
20
+ assert_equal [0,1], Score.split( '0x1' )
21
+ assert_equal [0,1], Score.split( '0X1' )
22
+
23
+ assert_equal [10,11], Score.split( '10 - 11' )
24
+ assert_equal [10,11], Score.split( '10 : 11' )
25
+ end
26
+
27
+ def test_to_h
28
+ assert_equal Hash( ft: [1,0] ), Score.parse( '1-0' ).to_h
29
+ assert_equal Hash( ht: [1,0],
30
+ ft: [3,2] ), Score.parse( '3-2 (1-0)' ).to_h
31
+ end
32
+
33
+
34
+ def test_new_et_al
35
+ score = Score.new
36
+ assert_equal [], score.to_a
37
+ assert_equal [], score.values
38
+ assert_equal [nil,nil], score.ft
39
+ assert_equal [nil,nil], score.ht
40
+ assert_equal Hash({}), score.to_h ## e.g. {} - empty hash
41
+ assert_equal Hash( score1i: nil, score2i: nil,
42
+ score1: nil, score2: nil,
43
+ score1et: nil, score2et: nil,
44
+ score1p: nil, score2p: nil ), score.to_h( :db )
45
+
46
+
47
+ [
48
+ Score.new( 0, 1 ),
49
+ Score.new( nil, nil, 0, 1 ),
50
+ ].each do |score|
51
+ assert_equal [0,1], score.to_a
52
+ assert_equal [nil,nil,0,1], score.values
53
+ assert_equal [nil,nil], score.ht
54
+ assert_equal [0,1], score.ft
55
+ assert_equal Hash(ft: [0,1]), score.to_h
56
+ assert_equal Hash( score1i: nil, score2i: nil,
57
+ score1: 0, score2: 1,
58
+ score1et: nil, score2et: nil,
59
+ score1p: nil, score2p: nil ), score.to_h( :db )
60
+ end
61
+
62
+
63
+ score = Score.new( 0, 1, 2, 3 )
64
+ assert_equal [[0,1],[2,3]], score.to_a
65
+ assert_equal [0,1,2,3], score.values
66
+ assert_equal [0,1], score.ht
67
+ assert_equal [2,3], score.ft
68
+ assert_equal Hash(ht: [0,1],
69
+ ft: [2,3]), score.to_h
70
+ assert_equal Hash( score1i: 0, score2i: 1,
71
+ score1: 2, score2: 3,
72
+ score1et: nil, score2et: nil,
73
+ score1p: nil, score2p: nil ), score.to_h( :db )
74
+ end
75
+
76
+ end
metadata ADDED
@@ -0,0 +1,91 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: score-formats
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Gerald Bauer
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2020-08-17 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rdoc
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '4.0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '4.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: hoe
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '3.16'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '3.16'
41
+ description: score-formats - read / parse and print sports match scores (incl. half
42
+ time, full time, extra time, penalties and more)
43
+ email: opensport@googlegroups.com
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files:
47
+ - CHANGELOG.md
48
+ - Manifest.txt
49
+ - README.md
50
+ files:
51
+ - CHANGELOG.md
52
+ - Manifest.txt
53
+ - README.md
54
+ - Rakefile
55
+ - lib/score-formats.rb
56
+ - lib/score-formats/formats.rb
57
+ - lib/score-formats/parser.rb
58
+ - lib/score-formats/score.rb
59
+ - lib/score-formats/version.rb
60
+ - lib/score/formats.rb
61
+ - test/helper.rb
62
+ - test/test_formats.rb
63
+ - test/test_score.rb
64
+ homepage: https://github.com/sportdb/sport.db
65
+ licenses:
66
+ - Public Domain
67
+ metadata: {}
68
+ post_install_message:
69
+ rdoc_options:
70
+ - "--main"
71
+ - README.md
72
+ require_paths:
73
+ - lib
74
+ required_ruby_version: !ruby/object:Gem::Requirement
75
+ requirements:
76
+ - - ">="
77
+ - !ruby/object:Gem::Version
78
+ version: 2.2.2
79
+ required_rubygems_version: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ version: '0'
84
+ requirements: []
85
+ rubyforge_project:
86
+ rubygems_version: 2.5.2
87
+ signing_key:
88
+ specification_version: 4
89
+ summary: score-formats - read / parse and print sports match scores (incl. half time,
90
+ full time, extra time, penalties and more)
91
+ test_files: []