cricinfo-cli-go 0.1.2 → 0.1.4
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.
- package/internal/cli/matches.go +96 -0
- package/internal/cli/matches_test.go +71 -0
- package/internal/cli/search.go +156 -0
- package/internal/cricinfo/analysis.go +177 -47
- package/internal/cricinfo/client.go +23 -2
- package/internal/cricinfo/coverage_ledger_test.go +2 -22
- package/internal/cricinfo/entity_index.go +27 -0
- package/internal/cricinfo/matches.go +1036 -22
- package/internal/cricinfo/matches_phase7_test.go +11 -4
- package/internal/cricinfo/normalize_entities.go +67 -35
- package/internal/cricinfo/players.go +236 -2
- package/internal/cricinfo/render_contract.go +139 -37
- package/internal/cricinfo/renderer.go +422 -13
- package/internal/cricinfo/resolver.go +92 -15
- package/internal/cricinfo/teams.go +109 -6
- package/internal/cricinfo/testdata/coverage/cricinfo-field-path-catalog.txt +2536 -0
- package/internal/cricinfo/testdata/coverage/cricinfo-working-templates.tsv +56 -0
- package/package.json +1 -1
|
@@ -6,6 +6,7 @@ import (
|
|
|
6
6
|
"io"
|
|
7
7
|
"regexp"
|
|
8
8
|
"sort"
|
|
9
|
+
"strconv"
|
|
9
10
|
"strings"
|
|
10
11
|
)
|
|
11
12
|
|
|
@@ -201,6 +202,15 @@ func renderText(w io.Writer, result NormalizedResult, opts RenderOptions) error
|
|
|
201
202
|
lines = append(lines, formatInningsTimelines(itemMap)...)
|
|
202
203
|
return writeTextLines(w, lines)
|
|
203
204
|
}
|
|
205
|
+
if result.Kind == EntityMatchSituation {
|
|
206
|
+
itemMap, err := toMap(result.Data, opts.AllFields)
|
|
207
|
+
if err != nil {
|
|
208
|
+
return err
|
|
209
|
+
}
|
|
210
|
+
lines = append(lines, "Match Situation")
|
|
211
|
+
lines = append(lines, formatMatchSituation(itemMap)...)
|
|
212
|
+
return writeTextLines(w, lines)
|
|
213
|
+
}
|
|
204
214
|
if result.Kind == EntityPlayerStats {
|
|
205
215
|
itemMap, err := toMap(result.Data, opts.AllFields)
|
|
206
216
|
if err != nil {
|
|
@@ -264,6 +274,15 @@ func renderText(w io.Writer, result NormalizedResult, opts RenderOptions) error
|
|
|
264
274
|
lines = append(lines, formatMatchPhases(itemMap)...)
|
|
265
275
|
return writeTextLines(w, lines)
|
|
266
276
|
}
|
|
277
|
+
if result.Kind == EntityMatchDuel {
|
|
278
|
+
itemMap, err := toMap(result.Data, opts.AllFields)
|
|
279
|
+
if err != nil {
|
|
280
|
+
return err
|
|
281
|
+
}
|
|
282
|
+
lines = append(lines, "Match Duel")
|
|
283
|
+
lines = append(lines, formatMatchDuel(itemMap)...)
|
|
284
|
+
return writeTextLines(w, lines)
|
|
285
|
+
}
|
|
267
286
|
|
|
268
287
|
itemMap, err := toMap(result.Data, opts.AllFields)
|
|
269
288
|
if err != nil {
|
|
@@ -283,8 +302,35 @@ func renderText(w io.Writer, result NormalizedResult, opts RenderOptions) error
|
|
|
283
302
|
return writeTextLines(w, lines)
|
|
284
303
|
}
|
|
285
304
|
|
|
305
|
+
if result.Kind == EntityTeamStatistics || result.Kind == EntityTeamRecords {
|
|
306
|
+
title := "Team Statistics"
|
|
307
|
+
if result.Kind == EntityTeamRecords {
|
|
308
|
+
title = "Team Records"
|
|
309
|
+
}
|
|
310
|
+
lines = append(lines, fmt.Sprintf("%s Categories (%d)", title, len(result.Items)))
|
|
311
|
+
lines = append(lines, formatStatCategoryList(result.Items)...)
|
|
312
|
+
return writeTextLines(w, lines)
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
if result.Kind == EntityStandingsGroup {
|
|
316
|
+
lines = append(lines, formatStandingsGroupList(result.Items)...)
|
|
317
|
+
return writeTextLines(w, lines)
|
|
318
|
+
}
|
|
319
|
+
|
|
286
320
|
if result.Kind == EntityDeliveryEvent || result.Kind == EntityPlayerDelivery {
|
|
287
321
|
lines = append(lines, fmt.Sprintf("%s (%d)", titleize(kindPlural(result.Kind)), len(result.Items)))
|
|
322
|
+
if result.Kind == EntityDeliveryEvent {
|
|
323
|
+
source := ""
|
|
324
|
+
switch {
|
|
325
|
+
case strings.Contains(result.RequestedRef, "/details"):
|
|
326
|
+
source = "details"
|
|
327
|
+
case strings.Contains(result.RequestedRef, "/plays"):
|
|
328
|
+
source = "plays"
|
|
329
|
+
}
|
|
330
|
+
if source != "" {
|
|
331
|
+
lines = append(lines, "Source: "+source)
|
|
332
|
+
}
|
|
333
|
+
}
|
|
288
334
|
for i, item := range result.Items {
|
|
289
335
|
summary := summarizeDeliveryListItem(item, result.Kind)
|
|
290
336
|
if strings.TrimSpace(summary) == "" {
|
|
@@ -339,19 +385,26 @@ func summarizeDeliveryListItem(item any, kind EntityKind) string {
|
|
|
339
385
|
if short == "" {
|
|
340
386
|
short = joinParts("over "+intString(typed.OverNumber), "ball "+intString(typed.BallNumber))
|
|
341
387
|
}
|
|
388
|
+
lead := firstNonEmpty(overBallString(typed.OverNumber, typed.BallNumber), "")
|
|
389
|
+
score := firstNonEmpty(scoreLabel(typed.HomeScore), scoreLabel(typed.AwayScore))
|
|
342
390
|
if kind == EntityPlayerDelivery {
|
|
343
|
-
return joinParts(short, strings.Join(typed.Involvement, ",")
|
|
391
|
+
return joinParts(lead, short, score, strings.Join(typed.Involvement, ","))
|
|
344
392
|
}
|
|
345
|
-
return short
|
|
393
|
+
return joinParts(lead, short, score)
|
|
346
394
|
case map[string]any:
|
|
347
395
|
short := firstNonEmpty(valueString(typed, "shortText"), valueString(typed, "text"))
|
|
348
396
|
if short == "" {
|
|
349
397
|
short = joinParts("over "+valueString(typed, "overNumber"), "ball "+valueString(typed, "ballNumber"))
|
|
350
398
|
}
|
|
399
|
+
if strings.TrimSpace(short) == "/" || strings.TrimSpace(short) == "-" {
|
|
400
|
+
return ""
|
|
401
|
+
}
|
|
402
|
+
lead := overBallLabel(typed)
|
|
403
|
+
score := firstNonEmpty(scoreLabel(valueString(typed, "homeScore")), scoreLabel(valueString(typed, "awayScore")))
|
|
351
404
|
if kind == EntityPlayerDelivery {
|
|
352
|
-
return joinParts(short,
|
|
405
|
+
return joinParts(lead, short, score, involvementLabel(typed))
|
|
353
406
|
}
|
|
354
|
-
return short
|
|
407
|
+
return joinParts(lead, short, score)
|
|
355
408
|
default:
|
|
356
409
|
return ""
|
|
357
410
|
}
|
|
@@ -361,7 +414,7 @@ func overBallString(over, ball int) string {
|
|
|
361
414
|
if over <= 0 || ball <= 0 {
|
|
362
415
|
return ""
|
|
363
416
|
}
|
|
364
|
-
return fmt.Sprintf("
|
|
417
|
+
return fmt.Sprintf("%d.%d", over, ball)
|
|
365
418
|
}
|
|
366
419
|
|
|
367
420
|
func intString(value int) string {
|
|
@@ -379,6 +432,17 @@ func defaultNumeric(raw string) string {
|
|
|
379
432
|
return raw
|
|
380
433
|
}
|
|
381
434
|
|
|
435
|
+
func scoreLabel(raw string) string {
|
|
436
|
+
raw = strings.TrimSpace(raw)
|
|
437
|
+
if raw == "" {
|
|
438
|
+
return ""
|
|
439
|
+
}
|
|
440
|
+
if strings.Contains(raw, "/") {
|
|
441
|
+
return raw
|
|
442
|
+
}
|
|
443
|
+
return ""
|
|
444
|
+
}
|
|
445
|
+
|
|
382
446
|
func sanitizeValue(value any, allFields bool) (any, error) {
|
|
383
447
|
blob, err := json.Marshal(value)
|
|
384
448
|
if err != nil {
|
|
@@ -423,6 +487,25 @@ func toMap(value any, allFields bool) (map[string]any, error) {
|
|
|
423
487
|
return mapped, nil
|
|
424
488
|
}
|
|
425
489
|
|
|
490
|
+
func mapFromAny(value any) map[string]any {
|
|
491
|
+
if value == nil {
|
|
492
|
+
return nil
|
|
493
|
+
}
|
|
494
|
+
if mapped, ok := value.(map[string]any); ok {
|
|
495
|
+
return mapped
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
blob, err := json.Marshal(value)
|
|
499
|
+
if err != nil {
|
|
500
|
+
return nil
|
|
501
|
+
}
|
|
502
|
+
var mapped map[string]any
|
|
503
|
+
if err := json.Unmarshal(blob, &mapped); err != nil {
|
|
504
|
+
return nil
|
|
505
|
+
}
|
|
506
|
+
return mapped
|
|
507
|
+
}
|
|
508
|
+
|
|
426
509
|
func summarizeEntity(entity map[string]any, kind EntityKind, verbose bool) string {
|
|
427
510
|
switch kind {
|
|
428
511
|
case EntityMatch:
|
|
@@ -464,6 +547,15 @@ func summarizeEntity(entity map[string]any, kind EntityKind, verbose bool) strin
|
|
|
464
547
|
return joinParts("situation", data)
|
|
465
548
|
}
|
|
466
549
|
return joinParts("situation", valueString(entity, "competitionId"))
|
|
550
|
+
case EntityMatchDuel:
|
|
551
|
+
duelLabel := strings.TrimSpace(fmt.Sprintf("%s vs %s",
|
|
552
|
+
firstNonEmpty(valueString(entity, "batterName"), valueString(entity, "batterId")),
|
|
553
|
+
firstNonEmpty(valueString(entity, "bowlerName"), valueString(entity, "bowlerId")),
|
|
554
|
+
))
|
|
555
|
+
return joinParts(
|
|
556
|
+
duelLabel,
|
|
557
|
+
fmt.Sprintf("%s off %s", valueString(entity, "runs"), valueString(entity, "balls")),
|
|
558
|
+
)
|
|
467
559
|
case EntityMatchPhases:
|
|
468
560
|
return joinParts(
|
|
469
561
|
"match "+firstNonEmpty(valueString(entity, "matchId"), valueString(entity, "competitionId")),
|
|
@@ -520,7 +612,15 @@ func summarizeEntity(entity map[string]any, kind EntityKind, verbose bool) strin
|
|
|
520
612
|
name := firstNonEmpty(valueString(entity, "name"), valueString(entity, "shortName"), valueString(entity, "id"))
|
|
521
613
|
return joinParts(name, bracket(valueString(entity, "homeAway")))
|
|
522
614
|
case EntityTeamRoster:
|
|
523
|
-
name :=
|
|
615
|
+
name := strings.TrimSpace(valueString(entity, "displayName"))
|
|
616
|
+
if name == "" {
|
|
617
|
+
playerID := firstNonEmpty(valueString(entity, "playerId"), valueString(entity, "athleteId"))
|
|
618
|
+
if playerID != "" {
|
|
619
|
+
name = "Unknown player (" + playerID + ")"
|
|
620
|
+
} else {
|
|
621
|
+
name = "Unknown player"
|
|
622
|
+
}
|
|
623
|
+
}
|
|
524
624
|
badges := []string{}
|
|
525
625
|
if valueString(entity, "captain") == "true" {
|
|
526
626
|
badges = append(badges, "captain")
|
|
@@ -528,7 +628,7 @@ func summarizeEntity(entity map[string]any, kind EntityKind, verbose bool) strin
|
|
|
528
628
|
if valueString(entity, "active") == "true" {
|
|
529
629
|
badges = append(badges, "active")
|
|
530
630
|
}
|
|
531
|
-
return joinParts(name, strings.Join(badges, ", "))
|
|
631
|
+
return joinParts(name, firstNonEmpty(valueString(entity, "teamName"), valueString(entity, "teamId")), strings.Join(badges, ", "))
|
|
532
632
|
case EntityTeamScore:
|
|
533
633
|
return joinParts(valueString(entity, "displayValue"), valueString(entity, "value"), bracket(valueString(entity, "source")))
|
|
534
634
|
case EntityTeamLeaders:
|
|
@@ -915,7 +1015,7 @@ func formatPlayerProfile(entity map[string]any) []string {
|
|
|
915
1015
|
}
|
|
916
1016
|
|
|
917
1017
|
func formatPlayerMatchView(entity map[string]any) []string {
|
|
918
|
-
lines := make([]string, 0,
|
|
1018
|
+
lines := make([]string, 0, 48)
|
|
919
1019
|
if player := valueString(entity, "playerName"); player != "" {
|
|
920
1020
|
lines = append(lines, "Player: "+player)
|
|
921
1021
|
}
|
|
@@ -931,13 +1031,21 @@ func formatPlayerMatchView(entity map[string]any) []string {
|
|
|
931
1031
|
}
|
|
932
1032
|
}
|
|
933
1033
|
if batting := sliceValue(entity, "batting"); len(batting) > 0 {
|
|
934
|
-
lines = append(lines,
|
|
1034
|
+
lines = append(lines, "Batting")
|
|
1035
|
+
lines = append(lines, formatStatCategoryList(batting)...)
|
|
935
1036
|
}
|
|
936
1037
|
if bowling := sliceValue(entity, "bowling"); len(bowling) > 0 {
|
|
937
|
-
lines = append(lines,
|
|
1038
|
+
lines = append(lines, "Bowling")
|
|
1039
|
+
lines = append(lines, formatStatCategoryList(bowling)...)
|
|
938
1040
|
}
|
|
939
1041
|
if fielding := sliceValue(entity, "fielding"); len(fielding) > 0 {
|
|
940
|
-
lines = append(lines,
|
|
1042
|
+
lines = append(lines, "Fielding")
|
|
1043
|
+
lines = append(lines, formatStatCategoryList(fielding)...)
|
|
1044
|
+
}
|
|
1045
|
+
if len(sliceValue(entity, "batting")) == 0 &&
|
|
1046
|
+
len(sliceValue(entity, "bowling")) == 0 &&
|
|
1047
|
+
len(sliceValue(entity, "fielding")) == 0 {
|
|
1048
|
+
lines = append(lines, "No match statistics categories available.")
|
|
941
1049
|
}
|
|
942
1050
|
return lines
|
|
943
1051
|
}
|
|
@@ -992,6 +1100,164 @@ func formatMatchView(entity map[string]any) []string {
|
|
|
992
1100
|
return lines
|
|
993
1101
|
}
|
|
994
1102
|
|
|
1103
|
+
func formatMatchSituation(entity map[string]any) []string {
|
|
1104
|
+
lines := make([]string, 0, 64)
|
|
1105
|
+
if matchID := firstNonEmpty(valueString(entity, "matchId"), valueString(entity, "competitionId")); matchID != "" {
|
|
1106
|
+
lines = append(lines, "Match: "+matchID)
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
live, _ := entity["live"].(map[string]any)
|
|
1110
|
+
if live == nil {
|
|
1111
|
+
if oddsRef := valueString(entity, "oddsRef"); oddsRef != "" {
|
|
1112
|
+
lines = append(lines, "Odds Ref: "+oddsRef)
|
|
1113
|
+
}
|
|
1114
|
+
if data, ok := entity["data"].(map[string]any); ok && len(data) > 0 {
|
|
1115
|
+
lines = append(lines, "Data: "+printableValue(data))
|
|
1116
|
+
}
|
|
1117
|
+
if len(lines) <= 1 {
|
|
1118
|
+
lines = append(lines, "No situation data available for this match.")
|
|
1119
|
+
}
|
|
1120
|
+
return lines
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
if fixture := valueString(live, "fixture"); fixture != "" {
|
|
1124
|
+
lines = append(lines, "Fixture: "+fixture)
|
|
1125
|
+
}
|
|
1126
|
+
if status := valueString(live, "status"); status != "" {
|
|
1127
|
+
lines = append(lines, "Status: "+status)
|
|
1128
|
+
}
|
|
1129
|
+
scoreLine := joinParts(valueString(live, "score"), valueString(live, "overs"))
|
|
1130
|
+
if scoreLine != "" {
|
|
1131
|
+
lines = append(lines, "Score: "+scoreLine)
|
|
1132
|
+
}
|
|
1133
|
+
teamsLine := joinParts(
|
|
1134
|
+
"Batting "+valueString(live, "battingTeam"),
|
|
1135
|
+
"Bowling "+valueString(live, "bowlingTeam"),
|
|
1136
|
+
)
|
|
1137
|
+
if teamsLine != "" {
|
|
1138
|
+
lines = append(lines, teamsLine)
|
|
1139
|
+
}
|
|
1140
|
+
if snapshotAt := valueString(live, "snapshotAt"); snapshotAt != "" {
|
|
1141
|
+
lines = append(lines, "Snapshot: "+snapshotAt)
|
|
1142
|
+
}
|
|
1143
|
+
if stale := valueString(live, "stale"); stale == "true" {
|
|
1144
|
+
lines = append(lines, "Stale: true")
|
|
1145
|
+
if reason := valueString(live, "staleReason"); reason != "" {
|
|
1146
|
+
lines = append(lines, "Stale Reason: "+reason)
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
batters := sliceValue(live, "batters")
|
|
1151
|
+
if len(batters) > 0 {
|
|
1152
|
+
lines = append(lines, "Batters")
|
|
1153
|
+
for i, raw := range batters {
|
|
1154
|
+
batter, ok := raw.(map[string]any)
|
|
1155
|
+
if !ok {
|
|
1156
|
+
continue
|
|
1157
|
+
}
|
|
1158
|
+
score := fmt.Sprintf("%s(%s)", defaultNumeric(valueString(batter, "runs")), defaultNumeric(valueString(batter, "balls")))
|
|
1159
|
+
boundaries := joinParts("4s "+defaultNumeric(valueString(batter, "fours")), "6s "+defaultNumeric(valueString(batter, "sixes")))
|
|
1160
|
+
row := joinParts(
|
|
1161
|
+
firstNonEmpty(valueString(batter, "playerName"), valueString(batter, "playerId")),
|
|
1162
|
+
score,
|
|
1163
|
+
"SR "+defaultNumeric(valueString(batter, "strikeRate")),
|
|
1164
|
+
boundaries,
|
|
1165
|
+
)
|
|
1166
|
+
if strings.EqualFold(valueString(batter, "onStrike"), "true") {
|
|
1167
|
+
row = joinParts(row, "*")
|
|
1168
|
+
}
|
|
1169
|
+
lines = append(lines, fmt.Sprintf(" %d. %s", i+1, row))
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
bowlers := sliceValue(live, "bowlers")
|
|
1174
|
+
if len(bowlers) > 0 {
|
|
1175
|
+
lines = append(lines, "Bowlers")
|
|
1176
|
+
for i, raw := range bowlers {
|
|
1177
|
+
bowler, ok := raw.(map[string]any)
|
|
1178
|
+
if !ok {
|
|
1179
|
+
continue
|
|
1180
|
+
}
|
|
1181
|
+
figures := fmt.Sprintf("%s-%s-%s-%s",
|
|
1182
|
+
oversLabelFromFields(valueString(bowler, "overs"), valueString(bowler, "balls")),
|
|
1183
|
+
defaultNumeric(valueString(bowler, "maidens")),
|
|
1184
|
+
defaultNumeric(valueString(bowler, "conceded")),
|
|
1185
|
+
defaultNumeric(valueString(bowler, "wickets")),
|
|
1186
|
+
)
|
|
1187
|
+
row := joinParts(
|
|
1188
|
+
firstNonEmpty(valueString(bowler, "playerName"), valueString(bowler, "playerId")),
|
|
1189
|
+
figures,
|
|
1190
|
+
"Econ "+defaultNumeric(valueString(bowler, "economy")),
|
|
1191
|
+
)
|
|
1192
|
+
lines = append(lines, fmt.Sprintf(" %d. %s", i+1, row))
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
balls := sliceValue(live, "recentBalls")
|
|
1197
|
+
if len(balls) == 0 {
|
|
1198
|
+
balls = sliceValue(live, "currentOverBalls")
|
|
1199
|
+
}
|
|
1200
|
+
if len(balls) > 0 {
|
|
1201
|
+
lines = append(lines, "Recent Balls")
|
|
1202
|
+
for i, raw := range balls {
|
|
1203
|
+
lines = append(lines, fmt.Sprintf(" %d. %s", i+1, summarizeDeliveryListItem(raw, EntityDeliveryEvent)))
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
return lines
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
func formatMatchDuel(entity map[string]any) []string {
|
|
1210
|
+
lines := make([]string, 0, 48)
|
|
1211
|
+
if matchID := valueString(entity, "matchId"); matchID != "" {
|
|
1212
|
+
lines = append(lines, "Match: "+matchID)
|
|
1213
|
+
}
|
|
1214
|
+
if fixture := valueString(entity, "fixture"); fixture != "" {
|
|
1215
|
+
lines = append(lines, "Fixture: "+fixture)
|
|
1216
|
+
}
|
|
1217
|
+
if score := valueString(entity, "score"); score != "" {
|
|
1218
|
+
lines = append(lines, "Score: "+score)
|
|
1219
|
+
}
|
|
1220
|
+
lines = append(lines, "Duel: "+firstNonEmpty(valueString(entity, "batterName"), valueString(entity, "batterId"))+" vs "+firstNonEmpty(valueString(entity, "bowlerName"), valueString(entity, "bowlerId")))
|
|
1221
|
+
summary := joinParts(
|
|
1222
|
+
fmt.Sprintf("%s off %s", defaultNumeric(valueString(entity, "runs")), defaultNumeric(valueString(entity, "balls"))),
|
|
1223
|
+
"SR "+defaultNumeric(valueString(entity, "strikeRate")),
|
|
1224
|
+
"dots "+defaultNumeric(valueString(entity, "dots")),
|
|
1225
|
+
"4s "+defaultNumeric(valueString(entity, "fours")),
|
|
1226
|
+
"6s "+defaultNumeric(valueString(entity, "sixes")),
|
|
1227
|
+
"wkts "+defaultNumeric(valueString(entity, "wickets")),
|
|
1228
|
+
)
|
|
1229
|
+
if strings.TrimSpace(summary) != "" {
|
|
1230
|
+
lines = append(lines, "Summary: "+summary)
|
|
1231
|
+
}
|
|
1232
|
+
if snapshot := valueString(entity, "snapshotAt"); snapshot != "" {
|
|
1233
|
+
lines = append(lines, "Snapshot: "+snapshot)
|
|
1234
|
+
}
|
|
1235
|
+
balls := sliceValue(entity, "recentBalls")
|
|
1236
|
+
if len(balls) > 0 {
|
|
1237
|
+
lines = append(lines, "Recent Duel Balls")
|
|
1238
|
+
for i, raw := range balls {
|
|
1239
|
+
lines = append(lines, fmt.Sprintf(" %d. %s", i+1, summarizeDeliveryListItem(raw, EntityDeliveryEvent)))
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
return lines
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
func oversLabelFromFields(overs string, balls string) string {
|
|
1246
|
+
overs = strings.TrimSpace(overs)
|
|
1247
|
+
if overs != "" && overs != "0" {
|
|
1248
|
+
return overs
|
|
1249
|
+
}
|
|
1250
|
+
balls = strings.TrimSpace(balls)
|
|
1251
|
+
if balls == "" || balls == "0" {
|
|
1252
|
+
return "0.0"
|
|
1253
|
+
}
|
|
1254
|
+
b, err := strconv.Atoi(balls)
|
|
1255
|
+
if err != nil || b < 0 {
|
|
1256
|
+
return "0.0"
|
|
1257
|
+
}
|
|
1258
|
+
return fmt.Sprintf("%d.%d", b/6, b%6)
|
|
1259
|
+
}
|
|
1260
|
+
|
|
995
1261
|
func formatMatchPhases(entity map[string]any) []string {
|
|
996
1262
|
lines := make([]string, 0, 64)
|
|
997
1263
|
if matchID := firstNonEmpty(valueString(entity, "matchId"), valueString(entity, "competitionId")); matchID != "" {
|
|
@@ -1266,6 +1532,149 @@ func formatTeamLeaders(entity map[string]any) []string {
|
|
|
1266
1532
|
return lines
|
|
1267
1533
|
}
|
|
1268
1534
|
|
|
1535
|
+
func formatStatCategoryList(items []any) []string {
|
|
1536
|
+
lines := make([]string, 0, len(items)*4)
|
|
1537
|
+
for i, rawCategory := range items {
|
|
1538
|
+
category := mapFromAny(rawCategory)
|
|
1539
|
+
if category == nil {
|
|
1540
|
+
continue
|
|
1541
|
+
}
|
|
1542
|
+
|
|
1543
|
+
categoryName := firstNonEmpty(valueString(category, "displayName"), valueString(category, "name"), "Category")
|
|
1544
|
+
lines = append(lines, fmt.Sprintf(" %d. %s", i+1, categoryName))
|
|
1545
|
+
|
|
1546
|
+
stats := sliceValue(category, "stats")
|
|
1547
|
+
if len(stats) == 0 {
|
|
1548
|
+
continue
|
|
1549
|
+
}
|
|
1550
|
+
|
|
1551
|
+
limit := len(stats)
|
|
1552
|
+
if limit > 16 {
|
|
1553
|
+
limit = 16
|
|
1554
|
+
}
|
|
1555
|
+
for j := 0; j < limit; j++ {
|
|
1556
|
+
statMap, ok := stats[j].(map[string]any)
|
|
1557
|
+
if !ok {
|
|
1558
|
+
continue
|
|
1559
|
+
}
|
|
1560
|
+
label := firstNonEmpty(valueString(statMap, "displayName"), valueString(statMap, "name"), valueString(statMap, "abbreviation"))
|
|
1561
|
+
value := firstNonEmpty(valueString(statMap, "displayValue"), valueString(statMap, "value"))
|
|
1562
|
+
row := joinParts(label, value, bracket(valueString(statMap, "abbreviation")))
|
|
1563
|
+
if row == "" {
|
|
1564
|
+
continue
|
|
1565
|
+
}
|
|
1566
|
+
lines = append(lines, fmt.Sprintf(" - %s", row))
|
|
1567
|
+
}
|
|
1568
|
+
if len(stats) > limit {
|
|
1569
|
+
lines = append(lines, fmt.Sprintf(" - ... %d more", len(stats)-limit))
|
|
1570
|
+
}
|
|
1571
|
+
}
|
|
1572
|
+
return lines
|
|
1573
|
+
}
|
|
1574
|
+
|
|
1575
|
+
func formatStandingsGroupList(items []any) []string {
|
|
1576
|
+
lines := make([]string, 0, len(items)*8+1)
|
|
1577
|
+
lines = append(lines, fmt.Sprintf("Standings Groups (%d)", len(items)))
|
|
1578
|
+
|
|
1579
|
+
for i, rawGroup := range items {
|
|
1580
|
+
group := mapFromAny(rawGroup)
|
|
1581
|
+
if group == nil {
|
|
1582
|
+
continue
|
|
1583
|
+
}
|
|
1584
|
+
|
|
1585
|
+
groupID := firstNonEmpty(valueString(group, "groupId"), valueString(group, "id"), fmt.Sprintf("%d", i+1))
|
|
1586
|
+
seasonID := valueString(group, "seasonId")
|
|
1587
|
+
header := "Group " + groupID
|
|
1588
|
+
if seasonID != "" {
|
|
1589
|
+
header = joinParts(header, "Season "+seasonID)
|
|
1590
|
+
}
|
|
1591
|
+
lines = append(lines, header)
|
|
1592
|
+
|
|
1593
|
+
entries := sliceValue(group, "entries")
|
|
1594
|
+
if len(entries) == 0 {
|
|
1595
|
+
lines = append(lines, " No standings entries available.")
|
|
1596
|
+
continue
|
|
1597
|
+
}
|
|
1598
|
+
|
|
1599
|
+
for entryIndex, rawEntry := range entries {
|
|
1600
|
+
team := mapFromAny(rawEntry)
|
|
1601
|
+
if team == nil {
|
|
1602
|
+
continue
|
|
1603
|
+
}
|
|
1604
|
+
|
|
1605
|
+
rank := standingsStatValueFromTeam(team, "rank", "position")
|
|
1606
|
+
if rank == "" {
|
|
1607
|
+
rank = fmt.Sprintf("%d", entryIndex+1)
|
|
1608
|
+
}
|
|
1609
|
+
teamName := firstNonEmpty(valueString(team, "shortName"), valueString(team, "name"), valueString(team, "id"))
|
|
1610
|
+
played := standingsStatValueFromTeam(team, "matchesplayed", "played", "matches")
|
|
1611
|
+
won := standingsStatValueFromTeam(team, "wins", "won")
|
|
1612
|
+
lost := standingsStatValueFromTeam(team, "losses", "lost")
|
|
1613
|
+
points := standingsStatValueFromTeam(team, "matchpoints", "points", "pts")
|
|
1614
|
+
nrr := standingsStatValueFromTeam(team, "netrunrate", "nrr", "runrate")
|
|
1615
|
+
|
|
1616
|
+
row := joinParts(
|
|
1617
|
+
fmt.Sprintf("#%s %s", rank, teamName),
|
|
1618
|
+
nonEmptyLabel("P", played),
|
|
1619
|
+
nonEmptyLabel("W", won),
|
|
1620
|
+
nonEmptyLabel("L", lost),
|
|
1621
|
+
nonEmptyLabel("Pts", points),
|
|
1622
|
+
nonEmptyLabel("NRR", nrr),
|
|
1623
|
+
)
|
|
1624
|
+
if strings.TrimSpace(row) == "" {
|
|
1625
|
+
row = joinParts(fmt.Sprintf("#%s %s", rank, teamName), valueString(team, "scoreSummary"))
|
|
1626
|
+
}
|
|
1627
|
+
lines = append(lines, " "+row)
|
|
1628
|
+
}
|
|
1629
|
+
}
|
|
1630
|
+
return lines
|
|
1631
|
+
}
|
|
1632
|
+
|
|
1633
|
+
func standingsStatValueFromTeam(team map[string]any, names ...string) string {
|
|
1634
|
+
if len(names) == 0 || team == nil {
|
|
1635
|
+
return ""
|
|
1636
|
+
}
|
|
1637
|
+
|
|
1638
|
+
targets := map[string]struct{}{}
|
|
1639
|
+
for _, name := range names {
|
|
1640
|
+
key := normalizeStatName(name)
|
|
1641
|
+
if key != "" {
|
|
1642
|
+
targets[key] = struct{}{}
|
|
1643
|
+
}
|
|
1644
|
+
}
|
|
1645
|
+
|
|
1646
|
+
extensions, ok := team["extensions"].(map[string]any)
|
|
1647
|
+
if !ok || extensions == nil {
|
|
1648
|
+
return ""
|
|
1649
|
+
}
|
|
1650
|
+
records, ok := extensions["records"].([]any)
|
|
1651
|
+
if !ok || len(records) == 0 {
|
|
1652
|
+
return ""
|
|
1653
|
+
}
|
|
1654
|
+
|
|
1655
|
+
for _, rawRecord := range records {
|
|
1656
|
+
record := mapFromAny(rawRecord)
|
|
1657
|
+
if record == nil {
|
|
1658
|
+
continue
|
|
1659
|
+
}
|
|
1660
|
+
for _, rawStat := range sliceValue(record, "stats") {
|
|
1661
|
+
stat := mapFromAny(rawStat)
|
|
1662
|
+
if stat == nil {
|
|
1663
|
+
continue
|
|
1664
|
+
}
|
|
1665
|
+
nameKey := normalizeStatName(firstNonEmpty(valueString(stat, "name"), valueString(stat, "type")))
|
|
1666
|
+
if _, ok := targets[nameKey]; !ok {
|
|
1667
|
+
continue
|
|
1668
|
+
}
|
|
1669
|
+
value := firstNonEmpty(valueString(stat, "displayValue"), valueString(stat, "value"))
|
|
1670
|
+
if value != "" {
|
|
1671
|
+
return value
|
|
1672
|
+
}
|
|
1673
|
+
}
|
|
1674
|
+
}
|
|
1675
|
+
return ""
|
|
1676
|
+
}
|
|
1677
|
+
|
|
1269
1678
|
func formatAnalysisView(entity map[string]any) []string {
|
|
1270
1679
|
lines := make([]string, 0, 64)
|
|
1271
1680
|
if command := valueString(entity, "command"); command != "" {
|
|
@@ -1535,9 +1944,9 @@ func overBallLabel(entity map[string]any) string {
|
|
|
1535
1944
|
return ""
|
|
1536
1945
|
}
|
|
1537
1946
|
if ball == "" {
|
|
1538
|
-
return
|
|
1947
|
+
return over
|
|
1539
1948
|
}
|
|
1540
|
-
return
|
|
1949
|
+
return over + "." + ball
|
|
1541
1950
|
}
|
|
1542
1951
|
|
|
1543
1952
|
func involvementLabel(entity map[string]any) string {
|