sportdb-writers 0.0.1 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,407 +1,225 @@
1
- module SportDb
2
- class TxtMatchWriter
3
-
4
-
5
- DE_WEEKDAY = {
6
- 1 => 'Mo', ## Montag
7
- 2 => 'Di', ## Dienstag
8
- 3 => 'Mi', ## Mittwoch
9
- 4 => 'Do', ## Donnerstag
10
- 5 => 'Fr', ## Freitag
11
- 6 => 'Sa', ## Samstag
12
- 7 => 'So', ## Sonntag
13
- }
14
-
15
- ##
16
- # https://en.wikipedia.org/wiki/Date_and_time_notation_in_Spain
17
- ES_WEEKDAY = {
18
- 1 => 'Lun', ## lunes ## todo: fix!! was Lue - why?
19
- 2 => 'Mar', ## martes
20
- 3 => 'Mié', ## miércoles
21
- 4 => 'Jue', ## jueves
22
- 5 => 'Vie', ## viernes
23
- 6 => 'Sáb', ## sábado ## todo: fix!! was Sab - why?
24
- 7 => 'Dom', ## domingo
25
- }
26
-
27
-
28
- PT_WEEKDAY = {
29
- 1 => 'Seg',
30
- 2 => 'Ter',
31
- 3 => 'Qua',
32
- 4 => 'Qui',
33
- 5 => 'Sex',
34
- 6 => 'Sáb',
35
- 7 => 'Dom',
36
- }
37
-
38
- PT_MONTH = {
39
- 1 => 'Jan',
40
- 2 => 'Fev',
41
- 3 => 'Março',
42
- 4 => 'Abril',
43
- 5 => 'Maio',
44
- 6 => 'Junho',
45
- 7 => 'Julho',
46
- 8 => 'Agosto',
47
- 9 => 'Set',
48
- 10 => 'Out',
49
- 11 => 'Nov',
50
- 12 => 'Dez',
51
- }
52
-
53
- ##
54
- # https://en.wikipedia.org/wiki/Date_and_time_notation_in_Italy
55
- IT_WEEKDAY = {
56
- 1 => 'Lun', ## Lunedì
57
- 2 => 'Mar', ## Martedì
58
- 3 => 'Mer', ## Mercoledì
59
- 4 => 'Gio', ## Giovedì
60
- 5 => 'Ven', ## Venerdì
61
- 6 => 'Sab', ## Sabato
62
- 7 => 'Dom', ## Domenica
63
- }
64
-
65
- FR_WEEKDAY = {
66
- 1 => 'Lun',
67
- 2 => 'Mar ',
68
- 3 => 'Mer',
69
- 4 => 'Jeu',
70
- 5 => 'Ven',
71
- 6 => 'Sam',
72
- 7 => 'Dim',
73
- }
74
-
75
- FR_MONTH = {
76
- 1 => 'Jan',
77
- 2 => 'Fév',
78
- 3 => 'Mars',
79
- 4 => 'Avril',
80
- 5 => 'Mai',
81
- 6 => 'Juin',
82
- 7 => 'Juil',
83
- 8 => 'Août',
84
- 9 => 'Sept',
85
- 10 => 'Oct',
86
- 11 => 'Nov',
87
- 12 => 'Déc',
88
- }
89
-
90
-
91
- EN_WEEKDAY = {
92
- 1 => 'Mon',
93
- 2 => 'Tue',
94
- 3 => 'Wed',
95
- 4 => 'Thu',
96
- 5 => 'Fri',
97
- 6 => 'Sat',
98
- 7 => 'Sun',
99
- }
100
-
101
-
102
-
103
- DATES =
104
- {
105
- ## english (en) -- e.g. Mon Aug/11
106
- 'en' => ->(date) { buf = String.new('')
107
- buf << EN_WEEKDAY[date.cwday]
108
- buf << ' '
109
- buf << date.strftime( '%b/%-d' )
110
- buf
111
- },
112
- ## portuguese (pt) -- e.g. Sáb, 13/Maio or Sáb 13 Maio
113
- 'pt' => ->(date) { buf = String.new('')
114
- buf << PT_WEEKDAY[date.cwday]
115
- buf << ", #{date.day}/"
116
- buf << PT_MONTH[date.month]
117
- buf
118
- },
119
- ## german / deutsch (de) -- e.g. Mo 11.8.
120
- 'de' => ->(date) { buf = String.new('')
121
- buf << DE_WEEKDAY[date.cwday]
122
- buf << ' '
123
- buf << date.strftime( '%-d.%-m.' )
124
- buf
125
- },
126
- ## italian / italiano (it) -- e.g. Lun. 11.8.
127
- 'it' => ->(date) { buf = String.new('')
128
- buf << IT_WEEKDAY[date.cwday]
129
- buf << '. '
130
- buf << date.strftime( '%-d.%-m.' )
131
- buf
132
- },
133
- ## spanish / espanol (es) -- e.g. Lun. 11.8.
134
- 'es' => ->(date) { buf = String.new('')
135
- buf << ES_WEEKDAY[date.cwday]
136
- buf << '. '
137
- buf << date.strftime( '%-d.%-m.' )
138
- buf
139
- },
140
- ## french / francais (fr)
141
- 'fr' => ->( date ) { buf = String.new('')
142
- buf << FR_WEEKDAY[date.cwday]
143
- buf << " #{date.day}. "
144
- buf << FR_MONTH[date.month]
145
- buf
146
- },
147
- }
148
-
149
-
150
- SCORES =
151
- {
152
- 'en' => ->( match ) { match.score.to_s( lang: 'en' ) },
153
- 'de' => ->( match ) { match.score.to_s( lang: 'de' ) },
154
- }
155
-
156
-
157
- LANGS =
158
- {
159
- 'en' => { round: 'Matchday', date: DATES['en'], score: SCORES['en'] },
160
- 'en_AU' => { round: 'Round', date: DATES['en'], score: SCORES['en'] },
161
- 'pt' => { round: 'Jornada', date: DATES['pt'], score: SCORES['en'] },
162
- 'pt_BR' => { round: 'Rodada', date: DATES['pt'], score: SCORES['en'] },
163
- 'it' => { round: ->(round) { "%s^ Giornata" % round }, date: DATES['it'], score: SCORES['en'] },
164
- 'fr' => { round: 'Journée', date: DATES['fr'], score: SCORES['en'] },
165
- 'es' => { round: 'Jornada',
166
- date: DATES['es'],
167
- score: SCORES['en'],
168
- ## add simple en-to-es translation for now
169
- translations: {
170
- 'Quarterfinals' => 'Cuartos de Final',
171
- 'Semifinals' => 'Semifinales',
172
- 'Finals' => 'Final',
173
- },
174
- },
175
- 'de' => { round: 'Spieltag', date: DATES['de'], score: SCORES['de'] },
176
- 'de_AT' => { round: ->(round) { "%s. Runde" % round }, date: DATES['de'], score: SCORES['de'] },
177
- }
178
-
179
-
180
-
181
-
182
-
183
- ## add 1:1 (more) convenience aliases
184
- LANGS[ 'de_DE' ] = LANGS[ 'de']
185
- LANGS[ 'de_CH' ] = LANGS[ 'de']
186
- LANGS[ 'pt_PT' ] = LANGS[ 'pt']
187
- LANGS[ 'es_AR' ] = LANGS[ 'es']
188
-
189
-
190
-
191
-
192
- ## note: build returns buf - an (in-memory) string buf(fer)
193
- def self.build( matches, lang: 'en', rounds: true )
194
- ## note: make sure rounds is a bool, that is, true or false (do NOT pass in strings etc.)
195
- raise ArgumentError, "rounds flag - bool expected; got: #{rounds.inspect}" unless rounds.is_a?( TrueClass ) || rounds.is_a?( FalseClass )
196
-
197
-
198
- defaults = LANGS[ lang ] || LANGS[ 'en' ] ## note: fallback for now to english if no defaults defined for lang
199
-
200
- round = defaults[ :round ]
201
- format_date = defaults[ :date ]
202
- format_score = defaults[ :score ]
203
- round_translations = defaults[ :translations ]
204
-
205
-
206
- buf = String.new('')
207
-
208
- last_round = nil
209
- last_date = nil
210
- last_time = nil
211
-
212
-
213
- matches.each do |match|
214
-
215
- ## note: make rounds optional (set rounds flag to false to turn off)
216
- if rounds
217
- if match.round != last_round
218
- buf << "\n\n"
219
- if match.round.is_a?( Integer ) ||
220
- match.round =~ /^[0-9]+$/ ## all numbers/digits
221
- if round.is_a?( Proc )
222
- buf << round.call( match.round )
223
- else
224
- ## default "class format
225
- ## e.g. Runde 1, Spieltag 1, Matchday 1, Week 1
226
- buf << "#{round} #{match.round}"
227
- end
228
- else ## use as is from match
229
- ## note: for now assume english names
230
- if round_translations
231
- buf << "#{round_translations[match.round] || match.round}"
232
- else
233
- buf << "#{match.round}"
234
- end
235
- end
236
- buf << "\n"
237
- end
238
- end
239
-
240
-
241
- date = if match.date.is_a?( String )
242
- Date.strptime( match.date, '%Y-%m-%d' )
243
- else ## assume it's already a date (object)
244
- match.date
245
- end
246
-
247
- time = if match.time.is_a?( String )
248
- Time.strptime( match.time, '%H:%M')
249
- else ## assume it's already a time (object) or nil
250
- match.time
251
- end
252
-
253
-
254
- date_yyyymmdd = date.strftime( '%Y-%m-%d' )
255
-
256
- ## note: time is OPTIONAL for now
257
- ## note: use 17.00 and NOT 17:00 for now
258
- time_hhmm = time ? time.strftime( '%H.%M' ) : nil
259
-
260
-
261
- if rounds
262
- if match.round != last_round || date_yyyymmdd != last_date
263
- buf << "[#{format_date.call( date )}]\n"
264
- last_time = nil ## note: reset time for new date
265
- end
266
- else
267
- if date_yyyymmdd != last_date
268
- buf << "\n" ## note: add an extra leading blank line (if no round headings printed)
269
- buf << "[#{format_date.call( date )}]\n"
270
- last_time = nil
271
- end
272
- end
273
-
274
-
275
- ## allow strings and structs for team names
276
- team1 = match.team1.is_a?( String ) ? match.team1 : match.team1.name
277
- team2 = match.team2.is_a?( String ) ? match.team2 : match.team2.name
278
-
279
-
280
- line = String.new('')
281
- line << ' '
282
-
283
- if time
284
- if last_time.nil? || last_time != time_hhmm
285
- line << "%5s" % time_hhmm
286
- else
287
- line << ' '
288
- end
289
- line << ' '
290
- else
291
- ## do nothing for now
292
- end
293
-
294
-
295
- line << "%-23s" % team1 ## note: use %-s for left-align
296
-
297
- line << " #{format_score.call( match )} " ## note: separate by at least two spaces for now
298
-
299
- line << "%-23s" % team2
300
-
301
-
302
- if match.status
303
- line << ' '
304
- case match.status
305
- when Status::CANCELLED
306
- line << '[cancelled]'
307
- when Status::AWARDED
308
- line << '[awarded]'
309
- when Status::ABANDONED
310
- line << '[abandoned]'
311
- when Status::REPLAY
312
- line << '[replay]'
313
- when Status::POSTPONED
314
- ## note: add NOTHING for postponed for now
315
- else
316
- puts "!! WARN - unknown match status >#{match.status}<:"
317
- pp match
318
- line << "[#{match.status.downcase}]" ## print "literal" downcased for now
319
- end
320
- end
321
-
322
- ## add match line
323
- buf << line.rstrip ## remove possible right trailing spaces before adding
324
- buf << "\n"
325
-
326
- if match.goals
327
- buf << ' ' # 4 space indent
328
- buf << ' ' if time # 7 (5+2) space indent (for hour e.g. 17.30)
329
- buf << "[#{build_goals(match.goals, lang: lang )}]"
330
- buf << "\n"
331
- end
332
-
333
-
334
- last_round = match.round
335
- last_date = date_yyyymmdd
336
- last_time = time_hhmm
337
- end
338
- buf
339
- end
340
-
341
-
342
- def self.write( path, matches, name:, lang: 'en', rounds: true)
343
-
344
- buf = build( matches, lang: lang, rounds: rounds )
345
-
346
- ## for convenience - make sure parent folders/directories exist
347
- FileUtils.mkdir_p( File.dirname( path) ) unless Dir.exists?( File.dirname( path ))
348
-
349
- File.open( path, 'w:utf-8' ) do |f|
350
- f.write( "= #{name}\n" )
351
- f.write( buf )
352
- end
353
- end # method self.write
354
-
355
-
356
- def self.build_goals( goals, lang: )
357
- ## todo/fix: for now assumes always minutes (without offset) - add offset support
358
-
359
- ## note: "fold" multiple goals by players
360
- team1_goals = {}
361
- team2_goals = {}
362
- goals.each do |goal|
363
- team_goals = goal.team == 1 ? team1_goals : team2_goals
364
- player_goals = team_goals[ goal.player ] ||= []
365
- player_goals << goal
366
- end
367
-
368
- buf = String.new('')
369
- if team1_goals.size > 0
370
- buf << build_goals_for_team( team1_goals, lang: lang )
371
- end
372
-
373
- ## note: only add a separator (;) if BOTH teams have goal scores
374
- if team1_goals.size > 0 && team2_goals.size > 0
375
- buf << '; '
376
- end
377
-
378
- if team2_goals.size > 0
379
- buf << build_goals_for_team( team2_goals, lang: lang )
380
- end
381
- buf
382
- end
383
-
384
-
385
- def self.build_goals_for_team( team_goals, lang: )
386
- buf = String.new('')
387
- team_goals.each_with_index do |(player_name, goals),i|
388
- buf << ' ' if i > 0
389
- buf << "#{player_name} "
390
- buf << goals.map do |goal|
391
- str = "#{goal.minute}'"
392
- if ['de', 'de_AT', 'de_DE', 'de_CH'].include?( lang )
393
- str << " (Eigentor)" if goal.owngoal?
394
- str << " (Elf.)" if goal.penalty?
395
- else ## fallback to english (by default)
396
- str << " (o.g.)" if goal.owngoal?
397
- str << " (pen.)" if goal.penalty?
398
- end
399
- str
400
- end.join( ', ' )
401
- end
402
- buf
403
- end
404
-
405
-
406
- end # class TxtMatchWriter
407
- end # module SportDb
1
+ module SportDb
2
+ class TxtMatchWriter
3
+
4
+
5
+ ## translate from lang x (german, etc) to english
6
+ ROUND_TRANSLATIONS = {
7
+ # de/german
8
+ '1. Runde' => 'Round 1',
9
+ '2. Runde' => 'Round 2',
10
+ 'Achtelfinale' => 'Round of 16',
11
+ 'Viertelfinale' => 'Quarterfinals',
12
+ 'Halbfinale' => 'Semifinals',
13
+ 'Finale' => 'Final',
14
+ }
15
+
16
+
17
+ ## note: build returns buf - an (in-memory) string buf(fer)
18
+ def self.build( matches, rounds: true )
19
+ ## note: make sure rounds is a bool, that is, true or false (do NOT pass in strings etc.)
20
+ raise ArgumentError, "rounds flag - bool expected; got: #{rounds.inspect}" unless rounds.is_a?( TrueClass ) || rounds.is_a?( FalseClass )
21
+
22
+ ## note: for now always english
23
+ round = 'Matchday'
24
+ format_date = ->(date) { date.strftime( '%a %b/%-d' ) }
25
+ format_score = ->(match) { match.score.to_s( lang: 'en' ) }
26
+ round_translations = ROUND_TRANSLATIONS
27
+
28
+
29
+ buf = String.new
30
+
31
+ last_round = nil
32
+ last_date = nil
33
+ last_time = nil
34
+
35
+
36
+ matches.each do |match|
37
+
38
+ ## note: make rounds optional (set rounds flag to false to turn off)
39
+ if rounds
40
+ if match.round != last_round
41
+ buf << "\n\n"
42
+ if match.round.is_a?( Integer ) ||
43
+ match.round =~ /^[0-9]+$/ ## all numbers/digits
44
+ ## default "class format
45
+ ## e.g. Runde 1, Spieltag 1, Matchday 1, Week 1
46
+ buf << "#{round} #{match.round}"
47
+ else ## use as is from match
48
+ ## note: for now assume english names
49
+ if match.round.nil?
50
+ ## warn
51
+ puts "!! ERROR - match with round nil?"
52
+ pp match
53
+ exit 1
54
+ end
55
+
56
+ buf << (round_translations[match.round] || match.round)
57
+ end
58
+ buf << "\n"
59
+ end
60
+ end
61
+
62
+
63
+ date = if match.date.is_a?( String )
64
+ Date.strptime( match.date, '%Y-%m-%d' )
65
+ else ## assume it's already a date (object)
66
+ match.date
67
+ end
68
+
69
+ time = if match.time.is_a?( String )
70
+ Time.strptime( match.time, '%H:%M')
71
+ else ## assume it's already a time (object) or nil
72
+ match.time
73
+ end
74
+
75
+
76
+ date_yyyymmdd = date.strftime( '%Y-%m-%d' )
77
+
78
+ ## note: time is OPTIONAL for now
79
+ ## note: use 17.00 and NOT 17:00 for now
80
+ time_hhmm = time ? time.strftime( '%H.%M' ) : nil
81
+
82
+
83
+ if rounds
84
+ if match.round != last_round || date_yyyymmdd != last_date
85
+ buf << "[#{format_date.call( date )}]\n"
86
+ last_time = nil ## note: reset time for new date
87
+ end
88
+ else
89
+ if date_yyyymmdd != last_date
90
+ buf << "\n" ## note: add an extra leading blank line (if no round headings printed)
91
+ buf << "[#{format_date.call( date )}]\n"
92
+ last_time = nil
93
+ end
94
+ end
95
+
96
+
97
+ ## allow strings and structs for team names
98
+ team1 = match.team1.is_a?( String ) ? match.team1 : match.team1.name
99
+ team2 = match.team2.is_a?( String ) ? match.team2 : match.team2.name
100
+
101
+
102
+ line = String.new
103
+ line << ' '
104
+
105
+ if time
106
+ if last_time.nil? || last_time != time_hhmm
107
+ line << "%5s" % time_hhmm
108
+ else
109
+ line << ' '
110
+ end
111
+ line << ' '
112
+ else
113
+ ## do nothing for now
114
+ end
115
+
116
+
117
+ line << "%-23s" % team1 ## note: use %-s for left-align
118
+
119
+ line << " #{format_score.call( match )} " ## note: separate by at least two spaces for now
120
+
121
+ line << "%-23s" % team2
122
+
123
+
124
+ if match.status
125
+ line << ' '
126
+ case match.status
127
+ when Status::CANCELLED
128
+ line << '[cancelled]'
129
+ when Status::AWARDED
130
+ line << '[awarded]'
131
+ when Status::ABANDONED
132
+ line << '[abandoned]'
133
+ when Status::REPLAY
134
+ line << '[replay]'
135
+ when Status::POSTPONED
136
+ ## note: add NOTHING for postponed for now
137
+ else
138
+ puts "!! WARN - unknown match status >#{match.status}<:"
139
+ pp match
140
+ line << "[#{match.status.downcase}]" ## print "literal" downcased for now
141
+ end
142
+ end
143
+
144
+ ## add match line
145
+ buf << line.rstrip ## remove possible right trailing spaces before adding
146
+ buf << "\n"
147
+
148
+ if match.goals
149
+ buf << ' ' # 4 space indent
150
+ buf << ' ' if time # 7 (5+2) space indent (for hour e.g. 17.30)
151
+ buf << "[#{build_goals(match.goals)}]"
152
+ buf << "\n"
153
+ end
154
+
155
+
156
+ last_round = match.round
157
+ last_date = date_yyyymmdd
158
+ last_time = time_hhmm
159
+ end
160
+ buf
161
+ end
162
+
163
+
164
+ def self.write( path, matches, name:, rounds: true)
165
+
166
+ buf = build( matches, rounds: rounds )
167
+
168
+ ## for convenience - make sure parent folders/directories exist
169
+ FileUtils.mkdir_p( File.dirname( path) ) unless Dir.exist?( File.dirname( path ))
170
+
171
+ puts "==> writing to >#{path}<..."
172
+ File.open( path, 'w:utf-8' ) do |f|
173
+ f.write( "= #{name}\n" )
174
+ f.write( buf )
175
+ end
176
+ end # method self.write
177
+
178
+
179
+ def self.build_goals( goals )
180
+ ## todo/fix: for now assumes always minutes (without offset) - add offset support
181
+
182
+ ## note: "fold" multiple goals by players
183
+ team1_goals = {}
184
+ team2_goals = {}
185
+ goals.each do |goal|
186
+ team_goals = goal.team == 1 ? team1_goals : team2_goals
187
+ player_goals = team_goals[ goal.player ] ||= []
188
+ player_goals << goal
189
+ end
190
+
191
+ buf = String.new
192
+ if team1_goals.size > 0
193
+ buf << build_goals_for_team( team1_goals )
194
+ end
195
+
196
+ ## note: only add a separator (;) if BOTH teams have goal scores
197
+ if team1_goals.size > 0 && team2_goals.size > 0
198
+ buf << '; '
199
+ end
200
+
201
+ if team2_goals.size > 0
202
+ buf << build_goals_for_team( team2_goals )
203
+ end
204
+ buf
205
+ end
206
+
207
+
208
+ def self.build_goals_for_team( team_goals )
209
+ buf = String.new
210
+ team_goals.each_with_index do |(player_name, goals),i|
211
+ buf << ' ' if i > 0
212
+ buf << "#{player_name} "
213
+ buf << goals.map do |goal|
214
+ str = "#{goal.minute}'"
215
+ str << " (o.g.)" if goal.owngoal?
216
+ str << " (pen.)" if goal.penalty?
217
+ str
218
+ end.join( ', ' )
219
+ end
220
+ buf
221
+ end
222
+
223
+
224
+ end # class TxtMatchWriter
225
+ end # module SportDb