sportdb-writers 0.4.3 → 0.5.1
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 +4 -4
- data/CHANGELOG.md +1 -2
- data/Manifest.txt +4 -3
- data/lib/sportdb/writers/{txt_writer_v2.rb → build.rb} +51 -28
- data/lib/sportdb/writers/{goals.rb → build_goals.rb} +46 -0
- data/lib/sportdb/writers/build_stats.rb +95 -0
- data/lib/sportdb/writers/rounds.rb +26 -0
- data/lib/sportdb/writers/version.rb +2 -2
- data/lib/sportdb/writers.rb +23 -23
- metadata +6 -5
- data/lib/sportdb/writers/txt_writer.rb +0 -344
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8d17c7dec2e2be1aa0de35fa8701c7983998ba9e3ccf06d63d8a0428294b0350
|
|
4
|
+
data.tar.gz: 1365f21386f582ea89b9f8e58e6a01566022d8f1cdb843a1046f337461622c8f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 308e428f1ef26bd54f27e2dc49d1e1fa059417ae022ee094f37405b6df7b150a1b4a916e3bcc225057d46249c772f704b06f2c97e1c0a8c830b2b7d76e791fb8
|
|
7
|
+
data.tar.gz: 34f3100368f4f2404d4ab03afdb4bf36fa545dfc01bb1de259bcb3b0aa4a728123747980dfe9c8d86a95564c21a7df6820d956ec612bbd183bc726a862900227
|
data/CHANGELOG.md
CHANGED
data/Manifest.txt
CHANGED
|
@@ -3,7 +3,8 @@ Manifest.txt
|
|
|
3
3
|
README.md
|
|
4
4
|
Rakefile
|
|
5
5
|
lib/sportdb/writers.rb
|
|
6
|
-
lib/sportdb/writers/
|
|
7
|
-
lib/sportdb/writers/
|
|
8
|
-
lib/sportdb/writers/
|
|
6
|
+
lib/sportdb/writers/build.rb
|
|
7
|
+
lib/sportdb/writers/build_goals.rb
|
|
8
|
+
lib/sportdb/writers/build_stats.rb
|
|
9
|
+
lib/sportdb/writers/rounds.rb
|
|
9
10
|
lib/sportdb/writers/version.rb
|
|
@@ -4,7 +4,7 @@ class TxtMatchWriter
|
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
## note: build returns buf - an (in-memory) string buf(fer)
|
|
7
|
-
def self.
|
|
7
|
+
def self.build( matches, rounds: true )
|
|
8
8
|
## note: make sure rounds is a bool, that is, true or false (do NOT pass in strings etc.)
|
|
9
9
|
raise ArgumentError, "rounds flag - bool expected; got: #{rounds.inspect}" unless rounds.is_a?( TrueClass ) || rounds.is_a?( FalseClass )
|
|
10
10
|
|
|
@@ -19,18 +19,18 @@ def self.build_v2( matches, rounds: true )
|
|
|
19
19
|
buf << _build_stats( stats )
|
|
20
20
|
buf << "\n\n"
|
|
21
21
|
|
|
22
|
-
buf <<
|
|
22
|
+
buf << _build_batch( matches, rounds: rounds )
|
|
23
23
|
|
|
24
24
|
buf
|
|
25
25
|
end
|
|
26
26
|
|
|
27
27
|
|
|
28
|
-
def self.
|
|
28
|
+
def self._build_batch( matches, rounds: true )
|
|
29
29
|
## note: make sure rounds is a bool, that is, true or false (do NOT pass in strings etc.)
|
|
30
30
|
raise ArgumentError, "rounds flag - bool expected; got: #{rounds.inspect}" unless rounds.is_a?( TrueClass ) || rounds.is_a?( FalseClass )
|
|
31
31
|
|
|
32
32
|
last_round = nil
|
|
33
|
-
last_year = nil
|
|
33
|
+
last_year = nil
|
|
34
34
|
last_date = nil
|
|
35
35
|
last_time = nil
|
|
36
36
|
|
|
@@ -43,31 +43,46 @@ def self._build_batch_v2( matches, rounds: true )
|
|
|
43
43
|
## note: make rounds optional (set rounds flag to false to turn off)
|
|
44
44
|
if rounds
|
|
45
45
|
## build round string
|
|
46
|
-
round = if match.round
|
|
47
|
-
|
|
46
|
+
round = if match.round
|
|
47
|
+
if match.round.is_a?( Integer ) ||
|
|
48
|
+
match.round =~ /^[0-9]+$/ ## all numbers/digits
|
|
48
49
|
## default "class format
|
|
49
50
|
## e.g. Runde 1, Spieltag 1, Matchday 1, Week 1
|
|
50
51
|
"Matchday #{match.round}"
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
## warn
|
|
55
|
-
puts "!! ERROR - match with round nil?"
|
|
56
|
-
pp match
|
|
57
|
-
exit 1
|
|
52
|
+
else ## use as is from match
|
|
53
|
+
## note: for now assume english names
|
|
54
|
+
(ROUND_TRANSLATIONS[match.round] || match.round)
|
|
58
55
|
end
|
|
59
|
-
|
|
56
|
+
else
|
|
57
|
+
nil
|
|
60
58
|
end
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
if match.stage
|
|
62
|
+
if ['Regular', 'Regular stage'].include?(match.stage)
|
|
63
|
+
## skip stage
|
|
64
|
+
else
|
|
65
|
+
## note - only stage present is possible
|
|
66
|
+
## if stage present - (auto-)add upfront
|
|
67
|
+
round = round ? "#{match.stage}, #{round}" : "#{match.stage}"
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
if round.nil?
|
|
73
|
+
puts "!! ERROR - match with round nil (no round and no stage)"
|
|
74
|
+
pp match
|
|
75
|
+
exit 1
|
|
76
|
+
end
|
|
77
|
+
|
|
64
78
|
|
|
65
79
|
|
|
66
80
|
if round != last_round
|
|
67
81
|
buf << (i == 0 ? "\n" : "\n\n") ## start with single empty line
|
|
68
|
-
buf << "
|
|
69
|
-
|
|
82
|
+
buf << "▪ #{round}\n"
|
|
83
|
+
|
|
70
84
|
## note - reset last_date & last_time on every new round header
|
|
85
|
+
## kind of starting "new scope" (no date/time inheritance)
|
|
71
86
|
last_date = nil
|
|
72
87
|
last_time = nil
|
|
73
88
|
last_round = round
|
|
@@ -81,6 +96,13 @@ def self._build_batch_v2( matches, rounds: true )
|
|
|
81
96
|
match.date
|
|
82
97
|
end
|
|
83
98
|
|
|
99
|
+
###
|
|
100
|
+
## fix - fix - fix
|
|
101
|
+
## ruby has no TIME type/class
|
|
102
|
+
## Time.strptime will include date (plus timezone etc.) e.g.!!!
|
|
103
|
+
## 12:00 => 2026-05-25 12:00:00 +0200
|
|
104
|
+
## do NOT use object; keep time as a string!!!!
|
|
105
|
+
|
|
84
106
|
time = if match.time.is_a?( String )
|
|
85
107
|
Time.strptime( match.time, '%H:%M')
|
|
86
108
|
else ## assume it's already a time (object) or nil
|
|
@@ -90,10 +112,10 @@ def self._build_batch_v2( matches, rounds: true )
|
|
|
90
112
|
|
|
91
113
|
## note - date might be NIL!!!!!
|
|
92
114
|
date_yyyymmdd = date ? date.strftime( '%Y-%m-%d' ) : nil
|
|
93
|
-
|
|
115
|
+
|
|
94
116
|
## note: time is OPTIONAL for now
|
|
95
|
-
## note: use 17
|
|
96
|
-
time_hhmm = time ? time.strftime( '%H
|
|
117
|
+
## note: use 17:00 always as time format (do NOT use 17.00!!!)
|
|
118
|
+
time_hhmm = time ? time.strftime( '%H:%M' ) : nil
|
|
97
119
|
|
|
98
120
|
|
|
99
121
|
if date_yyyymmdd
|
|
@@ -104,9 +126,9 @@ def self._build_batch_v2( matches, rounds: true )
|
|
|
104
126
|
buf << " "
|
|
105
127
|
## note: only add year if different for last date header
|
|
106
128
|
buf << if (date ? date.year : nil) != last_year
|
|
107
|
-
"#{date.strftime( '%a %b
|
|
108
|
-
else
|
|
109
|
-
"#{date.strftime( '%a %b
|
|
129
|
+
"#{date.strftime( '%a %b %-d %Y' )}"
|
|
130
|
+
else
|
|
131
|
+
"#{date.strftime( '%a %b %-d' )}"
|
|
110
132
|
end
|
|
111
133
|
buf << "\n"
|
|
112
134
|
last_time = nil
|
|
@@ -142,7 +164,7 @@ def self._build_batch_v2( matches, rounds: true )
|
|
|
142
164
|
score = match.score.to_s( lang: 'en' )
|
|
143
165
|
if score != '-'
|
|
144
166
|
## note: separate by at least two spaces for now
|
|
145
|
-
line << " #{score} "
|
|
167
|
+
line << " #{score} "
|
|
146
168
|
end
|
|
147
169
|
|
|
148
170
|
if match.status
|
|
@@ -160,9 +182,10 @@ def self._build_batch_v2( matches, rounds: true )
|
|
|
160
182
|
line << '[postponed]'
|
|
161
183
|
## was -- note: add NOTHING for postponed for now
|
|
162
184
|
else
|
|
163
|
-
puts "!!
|
|
185
|
+
puts "!! ERROR - unknown match status >#{match.status}<:"
|
|
164
186
|
pp match
|
|
165
187
|
line << "[#{match.status.downcase}]" ## print "literal" downcased for now
|
|
188
|
+
exit 1
|
|
166
189
|
end
|
|
167
190
|
end
|
|
168
191
|
|
|
@@ -173,7 +196,7 @@ def self._build_batch_v2( matches, rounds: true )
|
|
|
173
196
|
if match.goals
|
|
174
197
|
buf << ' ' # 4 space indent
|
|
175
198
|
buf << ' ' if time # 7 (5+2) space indent (for hour e.g. 17.30)
|
|
176
|
-
buf << "
|
|
199
|
+
buf << "(#{build_goals(match.goals)})"
|
|
177
200
|
buf << "\n"
|
|
178
201
|
end
|
|
179
202
|
|
|
@@ -55,4 +55,50 @@ def self.merge_goals( matches, goals )
|
|
|
55
55
|
end
|
|
56
56
|
|
|
57
57
|
|
|
58
|
+
|
|
59
|
+
def self.build_goals( goals )
|
|
60
|
+
## todo/fix: for now assumes always minutes (without offset) - add offset support
|
|
61
|
+
|
|
62
|
+
## note: "fold" multiple goals by players
|
|
63
|
+
team1_goals = {}
|
|
64
|
+
team2_goals = {}
|
|
65
|
+
goals.each do |goal|
|
|
66
|
+
team_goals = goal.team == 1 ? team1_goals : team2_goals
|
|
67
|
+
player_goals = team_goals[ goal.player ] ||= []
|
|
68
|
+
player_goals << goal
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
buf = String.new
|
|
72
|
+
if team1_goals.size > 0
|
|
73
|
+
buf << build_goals_for_team( team1_goals )
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
## note: only add a separator (;) if BOTH teams have goal scores
|
|
77
|
+
if team1_goals.size > 0 && team2_goals.size > 0
|
|
78
|
+
buf << '; '
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
if team2_goals.size > 0
|
|
82
|
+
buf << build_goals_for_team( team2_goals )
|
|
83
|
+
end
|
|
84
|
+
buf
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def self.build_goals_for_team( team_goals )
|
|
89
|
+
buf = String.new
|
|
90
|
+
team_goals.each_with_index do |(player_name, goals),i|
|
|
91
|
+
buf << ' ' if i > 0
|
|
92
|
+
buf << "#{player_name} "
|
|
93
|
+
buf << goals.map do |goal|
|
|
94
|
+
str = "#{goal.minute}'"
|
|
95
|
+
str << "(og)" if goal.owngoal?
|
|
96
|
+
str << "(p)" if goal.penalty?
|
|
97
|
+
str
|
|
98
|
+
end.join( ', ' )
|
|
99
|
+
end
|
|
100
|
+
buf
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
|
|
58
104
|
end # module Writer
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
module SportDb
|
|
2
|
+
class TxtMatchWriter
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
## helper - to calculate match stats e.g.
|
|
7
|
+
## how many stages, start and end dates, etc.
|
|
8
|
+
def self._calc_stats( matches )
|
|
9
|
+
### check for stages & stats
|
|
10
|
+
stats = { 'stage' => Hash.new(0),
|
|
11
|
+
'date' => { 'start_date' => nil,
|
|
12
|
+
'end_date' => nil, },
|
|
13
|
+
'teams' => Hash.new(0),
|
|
14
|
+
'matches' => 0,
|
|
15
|
+
'use_stages' => false,
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
## add matches played stats too??
|
|
19
|
+
## for now only simply count used
|
|
20
|
+
stats['matches'] = matches.size
|
|
21
|
+
|
|
22
|
+
matches.each do |match|
|
|
23
|
+
stage = match.stage
|
|
24
|
+
stage = 'Regular Season' if stage.nil? || stage.empty?
|
|
25
|
+
stats['stage'][ stage ] += 1
|
|
26
|
+
|
|
27
|
+
## note - date for now optional (not required)!!!!
|
|
28
|
+
## only teams for match
|
|
29
|
+
if match.date
|
|
30
|
+
## todo/fix - norm date (parse as Date)
|
|
31
|
+
## check format etc.
|
|
32
|
+
date = if match.date.is_a?( String )
|
|
33
|
+
Date.strptime( match.date, '%Y-%m-%d' )
|
|
34
|
+
else ## assume it's already a date (object)
|
|
35
|
+
match.date
|
|
36
|
+
end
|
|
37
|
+
stats['date']['start_date'] ||= date
|
|
38
|
+
stats['date']['end_date'] ||= date
|
|
39
|
+
|
|
40
|
+
stats['date']['start_date'] = date if date < stats['date']['start_date']
|
|
41
|
+
stats['date']['end_date'] = date if date > stats['date']['end_date']
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
[match.team1, match.team2].each do |team|
|
|
45
|
+
stats['teams'][ team ] += 1 if team && !['N.N.'].include?( team )
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
## add & update use_stage flag
|
|
50
|
+
stats['use_stages'] = if stats['stage'].size >= 2 ||
|
|
51
|
+
(stats['stage'].size == 1 &&
|
|
52
|
+
stats['stage'].keys[0] != 'Regular Season')
|
|
53
|
+
true
|
|
54
|
+
else
|
|
55
|
+
false
|
|
56
|
+
end
|
|
57
|
+
stats
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def self._build_stats( stats )
|
|
61
|
+
### add comment header
|
|
62
|
+
buf = String.new
|
|
63
|
+
# e.g. 13 April – 25 September 2024
|
|
64
|
+
# or 16 August 2024 – 25 May 2025
|
|
65
|
+
## note - date is optional!!
|
|
66
|
+
if stats['date']['start_date']
|
|
67
|
+
buf << "# Date "
|
|
68
|
+
start_date = stats['date']['start_date']
|
|
69
|
+
end_date = stats['date']['end_date']
|
|
70
|
+
if start_date.year != end_date.year
|
|
71
|
+
buf << "#{start_date.strftime('%a %b %-d %Y')} - #{end_date.strftime('%a %b %-d %Y')}"
|
|
72
|
+
else
|
|
73
|
+
buf << "#{start_date.strftime('%a %b %-d')} - #{end_date.strftime('%a %b %-d %Y')}"
|
|
74
|
+
end
|
|
75
|
+
buf << " (#{end_date.jd-start_date.jd}d)" ## add days
|
|
76
|
+
buf << "\n"
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
buf << "# Teams #{stats['teams'].size}\n"
|
|
80
|
+
buf << "# Matches #{stats['matches']}\n"
|
|
81
|
+
|
|
82
|
+
if stats['use_stages']
|
|
83
|
+
buf << "# Stages "
|
|
84
|
+
stages = stats['stage'].map { |name,count| "#{name} (#{count})" }.join( ' ' )
|
|
85
|
+
buf << stages
|
|
86
|
+
buf << "\n"
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
buf
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
end # class TxtMatchWriter
|
|
95
|
+
end # module SportDb
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
module SportDb
|
|
2
|
+
class TxtMatchWriter
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
## translate from lang x (german, etc) to english
|
|
7
|
+
ROUND_TRANSLATIONS = {
|
|
8
|
+
# de/german
|
|
9
|
+
'1. Runde' => 'Round 1',
|
|
10
|
+
'2. Runde' => 'Round 2',
|
|
11
|
+
'Achtelfinale' => 'Round of 16',
|
|
12
|
+
'Viertelfinale' => 'Quarterfinals',
|
|
13
|
+
'Halbfinale' => 'Semifinals',
|
|
14
|
+
'Finale' => 'Final',
|
|
15
|
+
|
|
16
|
+
'Gruppe A' => 'Group A',
|
|
17
|
+
'Gruppe B' => 'Group B',
|
|
18
|
+
'Gruppe C' => 'Group C',
|
|
19
|
+
'Gruppe D' => 'Group D',
|
|
20
|
+
'Gruppe E' => 'Group E',
|
|
21
|
+
'Gruppe F' => 'Group F',
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
end # class TxtMatchWriter
|
|
26
|
+
end # module SportDb
|
data/lib/sportdb/writers.rb
CHANGED
|
@@ -26,43 +26,43 @@ end # module Writer
|
|
|
26
26
|
###
|
|
27
27
|
# our own code
|
|
28
28
|
require_relative 'writers/version'
|
|
29
|
-
|
|
30
|
-
require_relative 'writers/
|
|
31
|
-
|
|
29
|
+
|
|
30
|
+
require_relative 'writers/rounds'
|
|
31
|
+
|
|
32
|
+
require_relative 'writers/build_stats'
|
|
33
|
+
require_relative 'writers/build_goals'
|
|
34
|
+
|
|
35
|
+
require_relative 'writers/build'
|
|
32
36
|
|
|
33
37
|
|
|
34
38
|
|
|
35
39
|
module SportDb
|
|
36
40
|
class TxtMatchWriter
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
## fix - change name: to title: !!!!
|
|
46
|
+
## fix: remove rounds: true|false - make it works without rounds without flag!!!!!
|
|
47
|
+
|
|
37
48
|
def self.write( path, matches, name:, rounds: true)
|
|
38
49
|
|
|
39
50
|
buf = build( matches, rounds: rounds )
|
|
40
|
-
|
|
41
|
-
## for convenience - make sure parent folders/directories exist
|
|
42
|
-
FileUtils.mkdir_p( File.dirname( path) ) unless Dir.exist?( File.dirname( path ))
|
|
43
|
-
|
|
51
|
+
|
|
44
52
|
puts "==> writing to >#{path}<..."
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
end
|
|
53
|
+
|
|
54
|
+
txt = "= #{name}\n\n" + buf
|
|
55
|
+
write_text( path, txt )
|
|
49
56
|
end # method self.write
|
|
50
57
|
|
|
51
|
-
def self.write_v2( path, matches, name:, rounds: true)
|
|
52
58
|
|
|
53
|
-
buf = build_v2( matches, rounds: rounds )
|
|
54
|
-
|
|
55
|
-
## for convenience - make sure parent folders/directories exist
|
|
56
|
-
FileUtils.mkdir_p( File.dirname( path) ) unless Dir.exist?( File.dirname( path ))
|
|
57
|
-
|
|
58
|
-
puts "==> writing to >#{path}<..."
|
|
59
|
-
File.open( path, 'w:utf-8' ) do |f|
|
|
60
|
-
f.write( "= #{name}\n\n" )
|
|
61
|
-
f.write( buf )
|
|
62
|
-
end
|
|
63
|
-
end # method self.write_v2
|
|
64
59
|
|
|
65
60
|
|
|
61
|
+
class << self
|
|
62
|
+
alias_method :write_v2, :write
|
|
63
|
+
alias_method :build_v2, :build
|
|
64
|
+
end
|
|
65
|
+
|
|
66
66
|
end # class TxtMatchWriter
|
|
67
67
|
end # module SportDb
|
|
68
68
|
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: sportdb-writers
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.5.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Gerald Bauer
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2026-05-25 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: sportdb-structs
|
|
@@ -73,9 +73,10 @@ files:
|
|
|
73
73
|
- README.md
|
|
74
74
|
- Rakefile
|
|
75
75
|
- lib/sportdb/writers.rb
|
|
76
|
-
- lib/sportdb/writers/
|
|
77
|
-
- lib/sportdb/writers/
|
|
78
|
-
- lib/sportdb/writers/
|
|
76
|
+
- lib/sportdb/writers/build.rb
|
|
77
|
+
- lib/sportdb/writers/build_goals.rb
|
|
78
|
+
- lib/sportdb/writers/build_stats.rb
|
|
79
|
+
- lib/sportdb/writers/rounds.rb
|
|
79
80
|
- lib/sportdb/writers/version.rb
|
|
80
81
|
homepage: https://github.com/sportdb/sport.db
|
|
81
82
|
licenses:
|
|
@@ -1,344 +0,0 @@
|
|
|
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
|
-
'Gruppe A' => 'Group A',
|
|
16
|
-
'Gruppe B' => 'Group B',
|
|
17
|
-
'Gruppe C' => 'Group C',
|
|
18
|
-
'Gruppe D' => 'Group D',
|
|
19
|
-
'Gruppe E' => 'Group E',
|
|
20
|
-
'Gruppe F' => 'Group F',
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
## helper - to calculate match stats e.g.
|
|
25
|
-
## how many stages, start and end dates, etc.
|
|
26
|
-
def self._calc_stats( matches )
|
|
27
|
-
### check for stages & stats
|
|
28
|
-
stats = { 'stage' => Hash.new(0),
|
|
29
|
-
'date' => { 'start_date' => nil,
|
|
30
|
-
'end_date' => nil, },
|
|
31
|
-
'teams' => Hash.new(0),
|
|
32
|
-
'matches' => 0,
|
|
33
|
-
'use_stages' => false,
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
## add matches played stats too??
|
|
37
|
-
## for now only simply count used
|
|
38
|
-
stats['matches'] = matches.size
|
|
39
|
-
|
|
40
|
-
matches.each do |match|
|
|
41
|
-
stage = match.stage
|
|
42
|
-
stage = 'Regular Season' if stage.nil? || stage.empty?
|
|
43
|
-
stats['stage'][ stage ] += 1
|
|
44
|
-
|
|
45
|
-
## note - date for now optional (not required)!!!!
|
|
46
|
-
## only teams for match
|
|
47
|
-
if match.date
|
|
48
|
-
## todo/fix - norm date (parse as Date)
|
|
49
|
-
## check format etc.
|
|
50
|
-
date = if match.date.is_a?( String )
|
|
51
|
-
Date.strptime( match.date, '%Y-%m-%d' )
|
|
52
|
-
else ## assume it's already a date (object)
|
|
53
|
-
match.date
|
|
54
|
-
end
|
|
55
|
-
stats['date']['start_date'] ||= date
|
|
56
|
-
stats['date']['end_date'] ||= date
|
|
57
|
-
|
|
58
|
-
stats['date']['start_date'] = date if date < stats['date']['start_date']
|
|
59
|
-
stats['date']['end_date'] = date if date > stats['date']['end_date']
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
[match.team1, match.team2].each do |team|
|
|
63
|
-
stats['teams'][ team ] += 1 if team && !['N.N.'].include?( team )
|
|
64
|
-
end
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
## add & update use_stage flag
|
|
68
|
-
stats['use_stages'] = if stats['stage'].size >= 2 ||
|
|
69
|
-
(stats['stage'].size == 1 &&
|
|
70
|
-
stats['stage'].keys[0] != 'Regular Season')
|
|
71
|
-
true
|
|
72
|
-
else
|
|
73
|
-
false
|
|
74
|
-
end
|
|
75
|
-
stats
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
def self._build_stats( stats )
|
|
79
|
-
### add comment header
|
|
80
|
-
buf = String.new
|
|
81
|
-
# e.g. 13 April – 25 September 2024
|
|
82
|
-
# or 16 August 2024 – 25 May 2025
|
|
83
|
-
## note - date is optional!!
|
|
84
|
-
if stats['date']['start_date']
|
|
85
|
-
buf << "# Date "
|
|
86
|
-
start_date = stats['date']['start_date']
|
|
87
|
-
end_date = stats['date']['end_date']
|
|
88
|
-
if start_date.year != end_date.year
|
|
89
|
-
buf << "#{start_date.strftime('%a %b/%-d %Y')} - #{end_date.strftime('%a %b/%-d %Y')}"
|
|
90
|
-
else
|
|
91
|
-
buf << "#{start_date.strftime('%a %b/%-d')} - #{end_date.strftime('%a %b/%-d %Y')}"
|
|
92
|
-
end
|
|
93
|
-
buf << " (#{end_date.jd-start_date.jd}d)" ## add days
|
|
94
|
-
buf << "\n"
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
buf << "# Teams #{stats['teams'].size}\n"
|
|
98
|
-
buf << "# Matches #{stats['matches']}\n"
|
|
99
|
-
|
|
100
|
-
if stats['use_stages']
|
|
101
|
-
buf << "# Stages "
|
|
102
|
-
stages = stats['stage'].map { |name,count| "#{name} (#{count})" }.join( ' ' )
|
|
103
|
-
buf << stages
|
|
104
|
-
buf << "\n"
|
|
105
|
-
end
|
|
106
|
-
|
|
107
|
-
buf
|
|
108
|
-
end
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
## note: build returns buf - an (in-memory) string buf(fer)
|
|
112
|
-
def self.build( matches, rounds: true )
|
|
113
|
-
## note: make sure rounds is a bool, that is, true or false (do NOT pass in strings etc.)
|
|
114
|
-
raise ArgumentError, "rounds flag - bool expected; got: #{rounds.inspect}" unless rounds.is_a?( TrueClass ) || rounds.is_a?( FalseClass )
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
### calc stats and check for stages
|
|
118
|
-
stats = _calc_stats( matches )
|
|
119
|
-
|
|
120
|
-
buf = String.new
|
|
121
|
-
|
|
122
|
-
### add comment header
|
|
123
|
-
buf << _build_stats( stats )
|
|
124
|
-
buf << "\n\n"
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
use_stages = stats['use_stages' ]
|
|
128
|
-
if use_stages
|
|
129
|
-
## split matches by stage
|
|
130
|
-
matches_by_stage = {}
|
|
131
|
-
matches.each do |match|
|
|
132
|
-
stage = match.stage || ''
|
|
133
|
-
matches_by_stage[stage] ||= []
|
|
134
|
-
matches_by_stage[stage] << match
|
|
135
|
-
end
|
|
136
|
-
|
|
137
|
-
## todo/fix
|
|
138
|
-
## note - empty stage must go first!!!!
|
|
139
|
-
matches_by_stage.each_with_index do |(name, matches),i|
|
|
140
|
-
buf << "\n" if i != 0 # add extra new line (if not first stage)
|
|
141
|
-
if name.empty?
|
|
142
|
-
buf << "# Regular Season\n" ## empty stage
|
|
143
|
-
else
|
|
144
|
-
buf << "== #{name}\n"
|
|
145
|
-
end
|
|
146
|
-
buf += _build_batch( matches, rounds: rounds )
|
|
147
|
-
buf << "\n" if i+1 != matches_by_stage.size
|
|
148
|
-
end
|
|
149
|
-
buf
|
|
150
|
-
else
|
|
151
|
-
buf += _build_batch( matches, rounds: rounds )
|
|
152
|
-
buf
|
|
153
|
-
end
|
|
154
|
-
end
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
def self._build_batch( matches, rounds: true )
|
|
158
|
-
## note: make sure rounds is a bool, that is, true or false (do NOT pass in strings etc.)
|
|
159
|
-
raise ArgumentError, "rounds flag - bool expected; got: #{rounds.inspect}" unless rounds.is_a?( TrueClass ) || rounds.is_a?( FalseClass )
|
|
160
|
-
|
|
161
|
-
buf = String.new
|
|
162
|
-
|
|
163
|
-
last_round = nil
|
|
164
|
-
last_date = nil
|
|
165
|
-
last_time = nil
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
matches.each_with_index do |match,i|
|
|
169
|
-
|
|
170
|
-
## note: make rounds optional (set rounds flag to false to turn off)
|
|
171
|
-
if rounds
|
|
172
|
-
if match.round != last_round
|
|
173
|
-
buf << (i == 0 ? "\n" : "\n\n") ## start with single empty line
|
|
174
|
-
if match.round.is_a?( Integer ) ||
|
|
175
|
-
match.round =~ /^[0-9]+$/ ## all numbers/digits
|
|
176
|
-
## default "class format
|
|
177
|
-
## e.g. Runde 1, Spieltag 1, Matchday 1, Week 1
|
|
178
|
-
buf << "Matchday #{match.round}"
|
|
179
|
-
else ## use as is from match
|
|
180
|
-
## note: for now assume english names
|
|
181
|
-
if match.round.nil?
|
|
182
|
-
## warn
|
|
183
|
-
puts "!! ERROR - match with round nil?"
|
|
184
|
-
pp match
|
|
185
|
-
exit 1
|
|
186
|
-
end
|
|
187
|
-
|
|
188
|
-
buf << (ROUND_TRANSLATIONS[match.round] || match.round)
|
|
189
|
-
end
|
|
190
|
-
## note - reset last_date & last_time on every new round header
|
|
191
|
-
last_date = nil
|
|
192
|
-
last_time = nil
|
|
193
|
-
|
|
194
|
-
buf << "\n"
|
|
195
|
-
end
|
|
196
|
-
end
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
date = if match.date.is_a?( String )
|
|
200
|
-
Date.strptime( match.date, '%Y-%m-%d' )
|
|
201
|
-
else ## assume it's already a date (object) or nil!!!!
|
|
202
|
-
match.date
|
|
203
|
-
end
|
|
204
|
-
|
|
205
|
-
time = if match.time.is_a?( String )
|
|
206
|
-
Time.strptime( match.time, '%H:%M')
|
|
207
|
-
else ## assume it's already a time (object) or nil
|
|
208
|
-
match.time
|
|
209
|
-
end
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
## note - date might be NIL!!!!!
|
|
213
|
-
date_yyyymmdd = date ? date.strftime( '%Y-%m-%d' ) : nil
|
|
214
|
-
|
|
215
|
-
## note: time is OPTIONAL for now
|
|
216
|
-
## note: use 17.00 and NOT 17:00 for now
|
|
217
|
-
time_hhmm = time ? time.strftime( '%H.%M' ) : nil
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
if date_yyyymmdd
|
|
221
|
-
if date_yyyymmdd != last_date
|
|
222
|
-
## note: add an extra leading blank line (if no round headings printed)
|
|
223
|
-
buf << "\n" unless rounds
|
|
224
|
-
buf << "[#{date.strftime( '%a %b/%-d' )}]\n"
|
|
225
|
-
last_time = nil
|
|
226
|
-
end
|
|
227
|
-
end
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
## allow strings and structs for team names
|
|
231
|
-
team1 = match.team1.is_a?( String ) ? match.team1 : match.team1.name
|
|
232
|
-
team2 = match.team2.is_a?( String ) ? match.team2 : match.team2.name
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
line = String.new
|
|
236
|
-
line << ' '
|
|
237
|
-
|
|
238
|
-
if time
|
|
239
|
-
if last_time != time_hhmm
|
|
240
|
-
line << "%5s" % time_hhmm
|
|
241
|
-
else
|
|
242
|
-
line << ' '
|
|
243
|
-
end
|
|
244
|
-
line << ' '
|
|
245
|
-
else
|
|
246
|
-
line << ' '
|
|
247
|
-
end
|
|
248
|
-
|
|
249
|
-
line << "%-23s" % team1 ## note: use %-s for left-align
|
|
250
|
-
|
|
251
|
-
## note: separate by at least two spaces for now
|
|
252
|
-
line << " #{match.score.to_s( lang: 'en' )} "
|
|
253
|
-
|
|
254
|
-
line << "%-23s" % team2
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
if match.status
|
|
258
|
-
line << ' '
|
|
259
|
-
case match.status
|
|
260
|
-
when Status::CANCELLED
|
|
261
|
-
line << '[cancelled]'
|
|
262
|
-
when Status::AWARDED
|
|
263
|
-
line << '[awarded]'
|
|
264
|
-
when Status::ABANDONED
|
|
265
|
-
line << '[abandoned]'
|
|
266
|
-
when Status::REPLAY
|
|
267
|
-
line << '[replay]'
|
|
268
|
-
when Status::POSTPONED
|
|
269
|
-
line << '[postponed]'
|
|
270
|
-
## was -- note: add NOTHING for postponed for now
|
|
271
|
-
else
|
|
272
|
-
puts "!! WARN - unknown match status >#{match.status}<:"
|
|
273
|
-
pp match
|
|
274
|
-
line << "[#{match.status.downcase}]" ## print "literal" downcased for now
|
|
275
|
-
end
|
|
276
|
-
end
|
|
277
|
-
|
|
278
|
-
## add match line
|
|
279
|
-
buf << line.rstrip ## remove possible right trailing spaces before adding
|
|
280
|
-
buf << "\n"
|
|
281
|
-
|
|
282
|
-
if match.goals
|
|
283
|
-
buf << ' ' # 4 space indent
|
|
284
|
-
buf << ' ' if time # 7 (5+2) space indent (for hour e.g. 17.30)
|
|
285
|
-
buf << "[#{build_goals(match.goals)}]"
|
|
286
|
-
buf << "\n"
|
|
287
|
-
end
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
last_round = match.round
|
|
291
|
-
last_date = date_yyyymmdd
|
|
292
|
-
last_time = time_hhmm
|
|
293
|
-
end
|
|
294
|
-
buf
|
|
295
|
-
end
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
def self.build_goals( goals )
|
|
299
|
-
## todo/fix: for now assumes always minutes (without offset) - add offset support
|
|
300
|
-
|
|
301
|
-
## note: "fold" multiple goals by players
|
|
302
|
-
team1_goals = {}
|
|
303
|
-
team2_goals = {}
|
|
304
|
-
goals.each do |goal|
|
|
305
|
-
team_goals = goal.team == 1 ? team1_goals : team2_goals
|
|
306
|
-
player_goals = team_goals[ goal.player ] ||= []
|
|
307
|
-
player_goals << goal
|
|
308
|
-
end
|
|
309
|
-
|
|
310
|
-
buf = String.new
|
|
311
|
-
if team1_goals.size > 0
|
|
312
|
-
buf << build_goals_for_team( team1_goals )
|
|
313
|
-
end
|
|
314
|
-
|
|
315
|
-
## note: only add a separator (;) if BOTH teams have goal scores
|
|
316
|
-
if team1_goals.size > 0 && team2_goals.size > 0
|
|
317
|
-
buf << '; '
|
|
318
|
-
end
|
|
319
|
-
|
|
320
|
-
if team2_goals.size > 0
|
|
321
|
-
buf << build_goals_for_team( team2_goals )
|
|
322
|
-
end
|
|
323
|
-
buf
|
|
324
|
-
end
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
def self.build_goals_for_team( team_goals )
|
|
328
|
-
buf = String.new
|
|
329
|
-
team_goals.each_with_index do |(player_name, goals),i|
|
|
330
|
-
buf << ' ' if i > 0
|
|
331
|
-
buf << "#{player_name} "
|
|
332
|
-
buf << goals.map do |goal|
|
|
333
|
-
str = "#{goal.minute}'"
|
|
334
|
-
str << " (o.g.)" if goal.owngoal?
|
|
335
|
-
str << " (pen.)" if goal.penalty?
|
|
336
|
-
str
|
|
337
|
-
end.join( ', ' )
|
|
338
|
-
end
|
|
339
|
-
buf
|
|
340
|
-
end
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
end # class TxtMatchWriter
|
|
344
|
-
end # module SportDb
|