cricinfo-cli-go 0.1.1 → 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 +126 -2
- package/internal/cli/matches_test.go +82 -0
- package/internal/cli/search.go +156 -0
- package/internal/cricinfo/analysis.go +393 -93
- package/internal/cricinfo/analysis_phase15_test.go +38 -0
- 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/historical_hydration.go +82 -42
- package/internal/cricinfo/matches.go +1641 -88
- package/internal/cricinfo/matches_phase7_test.go +11 -4
- package/internal/cricinfo/normalize_entities.go +83 -35
- package/internal/cricinfo/players.go +236 -2
- package/internal/cricinfo/render_contract.go +191 -49
- package/internal/cricinfo/renderer.go +613 -19
- package/internal/cricinfo/resolver.go +134 -13
- 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
package/internal/cli/matches.go
CHANGED
|
@@ -13,12 +13,16 @@ type matchCommandService interface {
|
|
|
13
13
|
Close() error
|
|
14
14
|
List(ctx context.Context, opts cricinfo.MatchListOptions) (cricinfo.NormalizedResult, error)
|
|
15
15
|
Live(ctx context.Context, opts cricinfo.MatchListOptions) (cricinfo.NormalizedResult, error)
|
|
16
|
+
Lineups(ctx context.Context, query string, opts cricinfo.MatchLookupOptions) (cricinfo.NormalizedResult, error)
|
|
16
17
|
Show(ctx context.Context, query string, opts cricinfo.MatchLookupOptions) (cricinfo.NormalizedResult, error)
|
|
17
18
|
Status(ctx context.Context, query string, opts cricinfo.MatchLookupOptions) (cricinfo.NormalizedResult, error)
|
|
18
19
|
Scorecard(ctx context.Context, query string, opts cricinfo.MatchLookupOptions) (cricinfo.NormalizedResult, error)
|
|
19
20
|
Details(ctx context.Context, query string, opts cricinfo.MatchLookupOptions) (cricinfo.NormalizedResult, error)
|
|
20
21
|
Plays(ctx context.Context, query string, opts cricinfo.MatchLookupOptions) (cricinfo.NormalizedResult, error)
|
|
21
22
|
Situation(ctx context.Context, query string, opts cricinfo.MatchLookupOptions) (cricinfo.NormalizedResult, error)
|
|
23
|
+
LiveView(ctx context.Context, query string, opts cricinfo.MatchLookupOptions) (cricinfo.NormalizedResult, error)
|
|
24
|
+
Duel(ctx context.Context, query string, opts cricinfo.MatchDuelOptions) (cricinfo.NormalizedResult, error)
|
|
25
|
+
Phases(ctx context.Context, query string, opts cricinfo.MatchLookupOptions) (cricinfo.NormalizedResult, error)
|
|
22
26
|
Innings(ctx context.Context, query string, opts cricinfo.MatchInningsOptions) (cricinfo.NormalizedResult, error)
|
|
23
27
|
Partnerships(ctx context.Context, query string, opts cricinfo.MatchInningsOptions) (cricinfo.NormalizedResult, error)
|
|
24
28
|
FallOfWicket(ctx context.Context, query string, opts cricinfo.MatchInningsOptions) (cricinfo.NormalizedResult, error)
|
|
@@ -29,6 +33,8 @@ type matchRuntimeOptions struct {
|
|
|
29
33
|
limit int
|
|
30
34
|
leagueID string
|
|
31
35
|
team string
|
|
36
|
+
batter string
|
|
37
|
+
bowler string
|
|
32
38
|
innings int
|
|
33
39
|
period int
|
|
34
40
|
}
|
|
@@ -51,6 +57,7 @@ func newMatchesCommand(global *globalOptions) *cobra.Command {
|
|
|
51
57
|
" cricinfo matches list",
|
|
52
58
|
" cricinfo matches show <match>",
|
|
53
59
|
" cricinfo matches status <match>",
|
|
60
|
+
" cricinfo matches live-view <match>",
|
|
54
61
|
" cricinfo matches scorecard <match>",
|
|
55
62
|
" cricinfo matches innings <match>",
|
|
56
63
|
}, "\n"),
|
|
@@ -72,12 +79,16 @@ func newMatchesCommand(global *globalOptions) *cobra.Command {
|
|
|
72
79
|
" cricinfo matches show <match>",
|
|
73
80
|
" cricinfo matches status <match>",
|
|
74
81
|
" cricinfo matches scorecard <match>",
|
|
82
|
+
" cricinfo matches live-view <match>",
|
|
75
83
|
" cricinfo matches innings <match>",
|
|
76
84
|
}, "\n"),
|
|
77
85
|
Args: cobra.NoArgs,
|
|
78
86
|
RunE: func(cmd *cobra.Command, _ []string) error {
|
|
79
87
|
return runMatchCommand(cmd, global, func(ctx context.Context, service matchCommandService) (cricinfo.NormalizedResult, error) {
|
|
80
|
-
return service.List(ctx, cricinfo.MatchListOptions{
|
|
88
|
+
return service.List(ctx, cricinfo.MatchListOptions{
|
|
89
|
+
Limit: opts.limit,
|
|
90
|
+
LeagueID: opts.leagueID,
|
|
91
|
+
})
|
|
81
92
|
})
|
|
82
93
|
},
|
|
83
94
|
}
|
|
@@ -93,17 +104,41 @@ func newMatchesCommand(global *globalOptions) *cobra.Command {
|
|
|
93
104
|
" cricinfo matches show <match>",
|
|
94
105
|
" cricinfo matches status <match>",
|
|
95
106
|
" cricinfo matches scorecard <match>",
|
|
107
|
+
" cricinfo matches live-view <match>",
|
|
96
108
|
" cricinfo matches innings <match>",
|
|
97
109
|
}, "\n"),
|
|
98
110
|
Args: cobra.NoArgs,
|
|
99
111
|
RunE: func(cmd *cobra.Command, _ []string) error {
|
|
100
112
|
return runMatchCommand(cmd, global, func(ctx context.Context, service matchCommandService) (cricinfo.NormalizedResult, error) {
|
|
101
|
-
return service.Live(ctx, cricinfo.MatchListOptions{
|
|
113
|
+
return service.Live(ctx, cricinfo.MatchListOptions{
|
|
114
|
+
Limit: opts.limit,
|
|
115
|
+
LeagueID: opts.leagueID,
|
|
116
|
+
})
|
|
102
117
|
})
|
|
103
118
|
},
|
|
104
119
|
}
|
|
105
120
|
liveCmd.Flags().IntVar(&opts.limit, "limit", 20, "Maximum number of live matches to return")
|
|
106
121
|
|
|
122
|
+
lineupCmd := &cobra.Command{
|
|
123
|
+
Use: "lineup <match>",
|
|
124
|
+
Short: "Show starting lineups for both teams in one match",
|
|
125
|
+
Long: strings.Join([]string{
|
|
126
|
+
"Resolve one match and return the match-scoped roster entries for both teams.",
|
|
127
|
+
"",
|
|
128
|
+
"Next steps:",
|
|
129
|
+
" cricinfo matches scorecard <match>",
|
|
130
|
+
" cricinfo matches status <match>",
|
|
131
|
+
" cricinfo matches deliveries <match> --team <team> --innings <n> --period <n>",
|
|
132
|
+
}, "\n"),
|
|
133
|
+
Args: cobra.MinimumNArgs(1),
|
|
134
|
+
RunE: func(cmd *cobra.Command, args []string) error {
|
|
135
|
+
query := strings.TrimSpace(strings.Join(args, " "))
|
|
136
|
+
return runMatchCommand(cmd, global, func(ctx context.Context, service matchCommandService) (cricinfo.NormalizedResult, error) {
|
|
137
|
+
return service.Lineups(ctx, query, cricinfo.MatchLookupOptions{LeagueID: opts.leagueID})
|
|
138
|
+
})
|
|
139
|
+
},
|
|
140
|
+
}
|
|
141
|
+
|
|
107
142
|
showCmd := &cobra.Command{
|
|
108
143
|
Use: "show <match>",
|
|
109
144
|
Short: "Show one match summary",
|
|
@@ -113,6 +148,7 @@ func newMatchesCommand(global *globalOptions) *cobra.Command {
|
|
|
113
148
|
"Next steps:",
|
|
114
149
|
" cricinfo matches status <match>",
|
|
115
150
|
" cricinfo matches scorecard <match>",
|
|
151
|
+
" cricinfo matches live-view <match>",
|
|
116
152
|
" cricinfo matches innings <match>",
|
|
117
153
|
}, "\n"),
|
|
118
154
|
Args: cobra.MinimumNArgs(1),
|
|
@@ -133,6 +169,7 @@ func newMatchesCommand(global *globalOptions) *cobra.Command {
|
|
|
133
169
|
"Next steps:",
|
|
134
170
|
" cricinfo matches show <match>",
|
|
135
171
|
" cricinfo matches scorecard <match>",
|
|
172
|
+
" cricinfo matches live-view <match>",
|
|
136
173
|
" cricinfo matches innings <match>",
|
|
137
174
|
}, "\n"),
|
|
138
175
|
Args: cobra.MinimumNArgs(1),
|
|
@@ -153,6 +190,7 @@ func newMatchesCommand(global *globalOptions) *cobra.Command {
|
|
|
153
190
|
"Next steps:",
|
|
154
191
|
" cricinfo matches details <match>",
|
|
155
192
|
" cricinfo matches plays <match>",
|
|
193
|
+
" cricinfo matches live-view <match>",
|
|
156
194
|
" cricinfo matches situation <match>",
|
|
157
195
|
}, "\n"),
|
|
158
196
|
Args: cobra.MinimumNArgs(1),
|
|
@@ -193,6 +231,7 @@ func newMatchesCommand(global *globalOptions) *cobra.Command {
|
|
|
193
231
|
"Next steps:",
|
|
194
232
|
" cricinfo matches details <match>",
|
|
195
233
|
" cricinfo matches scorecard <match>",
|
|
234
|
+
" cricinfo matches live-view <match>",
|
|
196
235
|
" cricinfo matches situation <match>",
|
|
197
236
|
}, "\n"),
|
|
198
237
|
Args: cobra.MinimumNArgs(1),
|
|
@@ -213,6 +252,7 @@ func newMatchesCommand(global *globalOptions) *cobra.Command {
|
|
|
213
252
|
"Next steps:",
|
|
214
253
|
" cricinfo matches status <match>",
|
|
215
254
|
" cricinfo matches details <match>",
|
|
255
|
+
" cricinfo matches live-view <match>",
|
|
216
256
|
" cricinfo matches scorecard <match>",
|
|
217
257
|
}, "\n"),
|
|
218
258
|
Args: cobra.MinimumNArgs(1),
|
|
@@ -224,6 +264,86 @@ func newMatchesCommand(global *globalOptions) *cobra.Command {
|
|
|
224
264
|
},
|
|
225
265
|
}
|
|
226
266
|
|
|
267
|
+
liveViewCmd := &cobra.Command{
|
|
268
|
+
Use: "live-view <match>",
|
|
269
|
+
Short: "Show fan-first live snapshot (batters, bowlers, figures, recent balls)",
|
|
270
|
+
Long: strings.Join([]string{
|
|
271
|
+
"Resolve a match and render a synthesized live snapshot from delivery details and roster metadata.",
|
|
272
|
+
"",
|
|
273
|
+
"Shows:",
|
|
274
|
+
" - current batters with runs/balls/strike-rate/boundaries",
|
|
275
|
+
" - current bowlers with figures and economy",
|
|
276
|
+
" - recent balls with over.ball and running score",
|
|
277
|
+
"",
|
|
278
|
+
"Next steps:",
|
|
279
|
+
" cricinfo matches plays <match>",
|
|
280
|
+
" cricinfo matches scorecard <match>",
|
|
281
|
+
" cricinfo matches situation <match>",
|
|
282
|
+
}, "\n"),
|
|
283
|
+
Args: cobra.MinimumNArgs(1),
|
|
284
|
+
RunE: func(cmd *cobra.Command, args []string) error {
|
|
285
|
+
query := strings.TrimSpace(strings.Join(args, " "))
|
|
286
|
+
return runMatchCommand(cmd, global, func(ctx context.Context, service matchCommandService) (cricinfo.NormalizedResult, error) {
|
|
287
|
+
return service.LiveView(ctx, query, cricinfo.MatchLookupOptions{LeagueID: opts.leagueID})
|
|
288
|
+
})
|
|
289
|
+
},
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
duelCmd := &cobra.Command{
|
|
293
|
+
Use: "duel <match>",
|
|
294
|
+
Short: "Show batter-vs-bowler matchup summary in one match",
|
|
295
|
+
Long: strings.Join([]string{
|
|
296
|
+
"Resolve a match and summarize the head-to-head duel between one batter and one bowler.",
|
|
297
|
+
"",
|
|
298
|
+
"Required flags:",
|
|
299
|
+
" --batter <player>",
|
|
300
|
+
" --bowler <player>",
|
|
301
|
+
"",
|
|
302
|
+
"Next steps:",
|
|
303
|
+
" cricinfo matches plays <match>",
|
|
304
|
+
" cricinfo players deliveries <player> --match <match>",
|
|
305
|
+
}, "\n"),
|
|
306
|
+
Args: cobra.MinimumNArgs(1),
|
|
307
|
+
RunE: func(cmd *cobra.Command, args []string) error {
|
|
308
|
+
if strings.TrimSpace(opts.batter) == "" {
|
|
309
|
+
return fmt.Errorf("--batter is required")
|
|
310
|
+
}
|
|
311
|
+
if strings.TrimSpace(opts.bowler) == "" {
|
|
312
|
+
return fmt.Errorf("--bowler is required")
|
|
313
|
+
}
|
|
314
|
+
query := strings.TrimSpace(strings.Join(args, " "))
|
|
315
|
+
return runMatchCommand(cmd, global, func(ctx context.Context, service matchCommandService) (cricinfo.NormalizedResult, error) {
|
|
316
|
+
return service.Duel(ctx, query, cricinfo.MatchDuelOptions{
|
|
317
|
+
LeagueID: opts.leagueID,
|
|
318
|
+
BatterQuery: opts.batter,
|
|
319
|
+
BowlerQuery: opts.bowler,
|
|
320
|
+
})
|
|
321
|
+
})
|
|
322
|
+
},
|
|
323
|
+
}
|
|
324
|
+
duelCmd.Flags().StringVar(&opts.batter, "batter", "", "Required: batter ID/ref/alias")
|
|
325
|
+
duelCmd.Flags().StringVar(&opts.bowler, "bowler", "", "Required: bowler ID/ref/alias")
|
|
326
|
+
|
|
327
|
+
phasesCmd := &cobra.Command{
|
|
328
|
+
Use: "phases <match>",
|
|
329
|
+
Short: "Show powerplay, middle, and death-over phase splits for each innings",
|
|
330
|
+
Long: strings.Join([]string{
|
|
331
|
+
"Resolve a match and show fan-friendly phase splits (powerplay/middle/death) with momentum markers.",
|
|
332
|
+
"",
|
|
333
|
+
"Next steps:",
|
|
334
|
+
" cricinfo matches scorecard <match>",
|
|
335
|
+
" cricinfo matches deliveries <match> --team <team> --innings <n> --period <n>",
|
|
336
|
+
" cricinfo matches fow <match> --team <team> --innings <n> --period <n>",
|
|
337
|
+
}, "\n"),
|
|
338
|
+
Args: cobra.MinimumNArgs(1),
|
|
339
|
+
RunE: func(cmd *cobra.Command, args []string) error {
|
|
340
|
+
query := strings.TrimSpace(strings.Join(args, " "))
|
|
341
|
+
return runMatchCommand(cmd, global, func(ctx context.Context, service matchCommandService) (cricinfo.NormalizedResult, error) {
|
|
342
|
+
return service.Phases(ctx, query, cricinfo.MatchLookupOptions{LeagueID: opts.leagueID})
|
|
343
|
+
})
|
|
344
|
+
},
|
|
345
|
+
}
|
|
346
|
+
|
|
227
347
|
inningsCmd := &cobra.Command{
|
|
228
348
|
Use: "innings <match>",
|
|
229
349
|
Short: "Show innings summaries with over and wicket timelines",
|
|
@@ -363,12 +483,16 @@ func newMatchesCommand(global *globalOptions) *cobra.Command {
|
|
|
363
483
|
cmd.AddCommand(
|
|
364
484
|
liveCmd,
|
|
365
485
|
listCmd,
|
|
486
|
+
lineupCmd,
|
|
366
487
|
showCmd,
|
|
367
488
|
statusCmd,
|
|
368
489
|
scorecardCmd,
|
|
369
490
|
detailsCmd,
|
|
370
491
|
playsCmd,
|
|
371
492
|
situationCmd,
|
|
493
|
+
liveViewCmd,
|
|
494
|
+
duelCmd,
|
|
495
|
+
phasesCmd,
|
|
372
496
|
inningsCmd,
|
|
373
497
|
partnershipsCmd,
|
|
374
498
|
fowCmd,
|
|
@@ -13,12 +13,16 @@ import (
|
|
|
13
13
|
type fakeMatchService struct {
|
|
14
14
|
listResult cricinfo.NormalizedResult
|
|
15
15
|
liveResult cricinfo.NormalizedResult
|
|
16
|
+
lineupResult cricinfo.NormalizedResult
|
|
16
17
|
showResult cricinfo.NormalizedResult
|
|
17
18
|
statusResult cricinfo.NormalizedResult
|
|
18
19
|
scorecardResult cricinfo.NormalizedResult
|
|
19
20
|
detailsResult cricinfo.NormalizedResult
|
|
20
21
|
playsResult cricinfo.NormalizedResult
|
|
21
22
|
situationResult cricinfo.NormalizedResult
|
|
23
|
+
liveViewResult cricinfo.NormalizedResult
|
|
24
|
+
duelResult cricinfo.NormalizedResult
|
|
25
|
+
phasesResult cricinfo.NormalizedResult
|
|
22
26
|
inningsResult cricinfo.NormalizedResult
|
|
23
27
|
partnerships cricinfo.NormalizedResult
|
|
24
28
|
fowResult cricinfo.NormalizedResult
|
|
@@ -44,6 +48,10 @@ func (f *fakeMatchService) Live(context.Context, cricinfo.MatchListOptions) (cri
|
|
|
44
48
|
return f.liveResult, nil
|
|
45
49
|
}
|
|
46
50
|
|
|
51
|
+
func (f *fakeMatchService) Lineups(context.Context, string, cricinfo.MatchLookupOptions) (cricinfo.NormalizedResult, error) {
|
|
52
|
+
return f.lineupResult, nil
|
|
53
|
+
}
|
|
54
|
+
|
|
47
55
|
func (f *fakeMatchService) Show(context.Context, string, cricinfo.MatchLookupOptions) (cricinfo.NormalizedResult, error) {
|
|
48
56
|
return f.showResult, nil
|
|
49
57
|
}
|
|
@@ -68,6 +76,18 @@ func (f *fakeMatchService) Situation(context.Context, string, cricinfo.MatchLook
|
|
|
68
76
|
return f.situationResult, nil
|
|
69
77
|
}
|
|
70
78
|
|
|
79
|
+
func (f *fakeMatchService) LiveView(context.Context, string, cricinfo.MatchLookupOptions) (cricinfo.NormalizedResult, error) {
|
|
80
|
+
return f.liveViewResult, nil
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
func (f *fakeMatchService) Duel(context.Context, string, cricinfo.MatchDuelOptions) (cricinfo.NormalizedResult, error) {
|
|
84
|
+
return f.duelResult, nil
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
func (f *fakeMatchService) Phases(context.Context, string, cricinfo.MatchLookupOptions) (cricinfo.NormalizedResult, error) {
|
|
88
|
+
return f.phasesResult, nil
|
|
89
|
+
}
|
|
90
|
+
|
|
71
91
|
func (f *fakeMatchService) Innings(_ context.Context, query string, opts cricinfo.MatchInningsOptions) (cricinfo.NormalizedResult, error) {
|
|
72
92
|
f.inningsQueries = append(f.inningsQueries, query)
|
|
73
93
|
f.inningsOpts = append(f.inningsOpts, opts)
|
|
@@ -144,12 +164,45 @@ func TestMatchesCommandsRenderTextAndJSON(t *testing.T) {
|
|
|
144
164
|
service := &fakeMatchService{
|
|
145
165
|
listResult: cricinfo.NewListResult(cricinfo.EntityMatch, []any{match}),
|
|
146
166
|
liveResult: cricinfo.NewListResult(cricinfo.EntityMatch, []any{match}),
|
|
167
|
+
lineupResult: cricinfo.NewListResult(cricinfo.EntityTeamRoster, []any{cricinfo.TeamRosterEntry{DisplayName: "Player One", TeamName: "BOOST"}}),
|
|
147
168
|
showResult: cricinfo.NewDataResult(cricinfo.EntityMatch, match),
|
|
148
169
|
statusResult: cricinfo.NewDataResult(cricinfo.EntityMatch, match),
|
|
149
170
|
scorecardResult: cricinfo.NewDataResult(cricinfo.EntityMatchScorecard, scorecard),
|
|
150
171
|
detailsResult: cricinfo.NewListResult(cricinfo.EntityDeliveryEvent, []any{delivery}),
|
|
151
172
|
playsResult: cricinfo.NewListResult(cricinfo.EntityDeliveryEvent, []any{delivery}),
|
|
152
173
|
situationResult: cricinfo.NewDataResult(cricinfo.EntityMatchSituation, situation),
|
|
174
|
+
liveViewResult: cricinfo.NewDataResult(cricinfo.EntityMatchSituation, cricinfo.MatchSituation{
|
|
175
|
+
MatchID: "1529474",
|
|
176
|
+
Live: &cricinfo.MatchLiveView{
|
|
177
|
+
Score: "69/2",
|
|
178
|
+
Batters: []cricinfo.LiveBatterView{
|
|
179
|
+
{PlayerName: "Numan Shah", Runs: 52, Balls: 41, StrikeRate: 126.82, OnStrike: true},
|
|
180
|
+
},
|
|
181
|
+
Bowlers: []cricinfo.LiveBowlerView{
|
|
182
|
+
{PlayerName: "Hayatullah Noori", Overs: 3, Conceded: 21, Wickets: 1, Economy: 7.0},
|
|
183
|
+
},
|
|
184
|
+
RecentBalls: []cricinfo.DeliveryEvent{delivery},
|
|
185
|
+
},
|
|
186
|
+
}),
|
|
187
|
+
duelResult: cricinfo.NewDataResult(cricinfo.EntityMatchDuel, cricinfo.MatchDuel{
|
|
188
|
+
MatchID: "1529474",
|
|
189
|
+
BatterName: "Numan Shah",
|
|
190
|
+
BowlerName: "Hayatullah Noori",
|
|
191
|
+
Runs: 10,
|
|
192
|
+
Balls: 7,
|
|
193
|
+
Dots: 2,
|
|
194
|
+
Fours: 2,
|
|
195
|
+
StrikeRate: 142.86,
|
|
196
|
+
RecentBalls: []cricinfo.DeliveryEvent{
|
|
197
|
+
delivery,
|
|
198
|
+
},
|
|
199
|
+
}),
|
|
200
|
+
phasesResult: cricinfo.NewDataResult(cricinfo.EntityMatchPhases, cricinfo.MatchPhases{
|
|
201
|
+
MatchID: "1529474",
|
|
202
|
+
Innings: []cricinfo.MatchPhaseInning{
|
|
203
|
+
{TeamName: "BOOST", InningsNumber: 1, Period: 3, Score: "69/2 (19 ov)"},
|
|
204
|
+
},
|
|
205
|
+
}),
|
|
153
206
|
inningsResult: cricinfo.NewListResult(cricinfo.EntityInnings, []any{
|
|
154
207
|
cricinfo.Innings{
|
|
155
208
|
TeamName: "BOOST",
|
|
@@ -285,6 +338,15 @@ func TestMatchesCommandsRenderTextAndJSON(t *testing.T) {
|
|
|
285
338
|
t.Fatalf("expected plays text output to include normalized short text, got %q", playsOut.String())
|
|
286
339
|
}
|
|
287
340
|
|
|
341
|
+
var lineupOut bytes.Buffer
|
|
342
|
+
var lineupErr bytes.Buffer
|
|
343
|
+
if err := Run([]string{"matches", "lineup", "1529474", "--format", "text"}, &lineupOut, &lineupErr); err != nil {
|
|
344
|
+
t.Fatalf("Run matches lineup --format text error: %v", err)
|
|
345
|
+
}
|
|
346
|
+
if !strings.Contains(lineupOut.String(), "Player One") {
|
|
347
|
+
t.Fatalf("expected lineup text to include roster names, got %q", lineupOut.String())
|
|
348
|
+
}
|
|
349
|
+
|
|
288
350
|
var situationOut bytes.Buffer
|
|
289
351
|
var situationErr bytes.Buffer
|
|
290
352
|
if err := Run([]string{"matches", "situation", "1529474", "--format", "json"}, &situationOut, &situationErr); err != nil {
|
|
@@ -298,6 +360,26 @@ func TestMatchesCommandsRenderTextAndJSON(t *testing.T) {
|
|
|
298
360
|
t.Fatalf("expected kind %q in situation output, got %#v", cricinfo.EntityMatchSituation, situationPayload["kind"])
|
|
299
361
|
}
|
|
300
362
|
|
|
363
|
+
var liveViewOut bytes.Buffer
|
|
364
|
+
var liveViewErr bytes.Buffer
|
|
365
|
+
if err := Run([]string{"matches", "live-view", "1529474", "--format", "text"}, &liveViewOut, &liveViewErr); err != nil {
|
|
366
|
+
t.Fatalf("Run matches live-view --format text error: %v", err)
|
|
367
|
+
}
|
|
368
|
+
liveViewText := liveViewOut.String()
|
|
369
|
+
if !strings.Contains(liveViewText, "Batters") || !strings.Contains(liveViewText, "Bowlers") {
|
|
370
|
+
t.Fatalf("expected batters/bowlers sections in live-view output, got %q", liveViewText)
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
var duelOut bytes.Buffer
|
|
374
|
+
var duelErr bytes.Buffer
|
|
375
|
+
if err := Run([]string{"matches", "duel", "1529474", "--batter", "Numan Shah", "--bowler", "Hayatullah Noori", "--format", "text"}, &duelOut, &duelErr); err != nil {
|
|
376
|
+
t.Fatalf("Run matches duel --format text error: %v", err)
|
|
377
|
+
}
|
|
378
|
+
duelText := duelOut.String()
|
|
379
|
+
if !strings.Contains(duelText, "Duel:") || !strings.Contains(duelText, "Numan Shah") {
|
|
380
|
+
t.Fatalf("expected duel output to include matchup summary, got %q", duelText)
|
|
381
|
+
}
|
|
382
|
+
|
|
301
383
|
var inningsOut bytes.Buffer
|
|
302
384
|
var inningsErr bytes.Buffer
|
|
303
385
|
if err := Run([]string{"matches", "innings", "1529474", "--team", "BOOST", "--format", "text"}, &inningsOut, &inningsErr); err != nil {
|
package/internal/cli/search.go
CHANGED
|
@@ -95,6 +95,13 @@ func newSearchEntityCommand(name string, kind cricinfo.EntityKind, global *globa
|
|
|
95
95
|
for _, entity := range searchResult.Entities {
|
|
96
96
|
items = append(items, entity.ToRenderable())
|
|
97
97
|
}
|
|
98
|
+
if kind == cricinfo.EntityMatch && len(items) == 0 && query != "" {
|
|
99
|
+
fallbackItems, fallbackWarnings := fallbackMatchSearchItems(cmd.Context(), query, searchOpts.leagueID, searchOpts.limit)
|
|
100
|
+
if len(fallbackItems) > 0 {
|
|
101
|
+
items = fallbackItems
|
|
102
|
+
}
|
|
103
|
+
searchResult.Warnings = append(searchResult.Warnings, fallbackWarnings...)
|
|
104
|
+
}
|
|
98
105
|
|
|
99
106
|
result := cricinfo.NewListResult(kind, items)
|
|
100
107
|
if len(searchResult.Warnings) > 0 {
|
|
@@ -117,3 +124,152 @@ func newSearchEntityCommand(name string, kind cricinfo.EntityKind, global *globa
|
|
|
117
124
|
|
|
118
125
|
return command
|
|
119
126
|
}
|
|
127
|
+
|
|
128
|
+
func fallbackMatchSearchItems(ctx context.Context, query string, leagueID string, limit int) ([]any, []string) {
|
|
129
|
+
service, err := cricinfo.NewMatchService(cricinfo.MatchServiceConfig{})
|
|
130
|
+
if err != nil {
|
|
131
|
+
return nil, []string{fmt.Sprintf("match fallback init failed: %v", err)}
|
|
132
|
+
}
|
|
133
|
+
defer func() {
|
|
134
|
+
_ = service.Close()
|
|
135
|
+
}()
|
|
136
|
+
|
|
137
|
+
listLimit := limit
|
|
138
|
+
if listLimit < 20 {
|
|
139
|
+
listLimit = 20
|
|
140
|
+
}
|
|
141
|
+
if listLimit > 80 {
|
|
142
|
+
listLimit = 80
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
result, err := service.Live(ctx, cricinfo.MatchListOptions{Limit: listLimit, LeagueID: strings.TrimSpace(leagueID)})
|
|
146
|
+
if err != nil {
|
|
147
|
+
return nil, []string{fmt.Sprintf("match fallback live list failed: %v", err)}
|
|
148
|
+
}
|
|
149
|
+
if len(result.Items) == 0 {
|
|
150
|
+
result, err = service.List(ctx, cricinfo.MatchListOptions{Limit: listLimit, LeagueID: strings.TrimSpace(leagueID)})
|
|
151
|
+
if err != nil {
|
|
152
|
+
return nil, []string{fmt.Sprintf("match fallback list failed: %v", err)}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
queryNorm := normalizeMatchSearchText(query)
|
|
157
|
+
queryTokens := strings.Fields(queryNorm)
|
|
158
|
+
if queryNorm == "" {
|
|
159
|
+
return nil, nil
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
items := make([]any, 0, limitOrDefault(limit, 10))
|
|
163
|
+
for _, raw := range result.Items {
|
|
164
|
+
match, ok := raw.(cricinfo.Match)
|
|
165
|
+
if !ok {
|
|
166
|
+
continue
|
|
167
|
+
}
|
|
168
|
+
if matchSearchScore(match, queryNorm, queryTokens) == 0 {
|
|
169
|
+
continue
|
|
170
|
+
}
|
|
171
|
+
items = append(items, match)
|
|
172
|
+
if len(items) >= limitOrDefault(limit, 10) {
|
|
173
|
+
break
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if len(items) == 0 {
|
|
178
|
+
return nil, nil
|
|
179
|
+
}
|
|
180
|
+
return items, []string{"match search fallback used list/live aliases"}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
func matchSearchScore(match cricinfo.Match, query string, queryTokens []string) int {
|
|
184
|
+
score := 0
|
|
185
|
+
for _, value := range []string{
|
|
186
|
+
match.ID,
|
|
187
|
+
match.Description,
|
|
188
|
+
match.ShortDescription,
|
|
189
|
+
match.Note,
|
|
190
|
+
match.ScoreSummary,
|
|
191
|
+
} {
|
|
192
|
+
score = maxInt(score, aliasScore(normalizeMatchSearchText(value), query, queryTokens))
|
|
193
|
+
}
|
|
194
|
+
for _, team := range match.Teams {
|
|
195
|
+
for _, value := range []string{
|
|
196
|
+
team.ID,
|
|
197
|
+
team.Name,
|
|
198
|
+
team.ShortName,
|
|
199
|
+
team.Abbreviation,
|
|
200
|
+
} {
|
|
201
|
+
score = maxInt(score, aliasScore(normalizeMatchSearchText(value), query, queryTokens))
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return score
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
func aliasScore(alias, query string, queryTokens []string) int {
|
|
208
|
+
if alias == "" || query == "" {
|
|
209
|
+
return 0
|
|
210
|
+
}
|
|
211
|
+
if alias == query {
|
|
212
|
+
return 1000
|
|
213
|
+
}
|
|
214
|
+
if strings.HasPrefix(alias, query) {
|
|
215
|
+
return 800
|
|
216
|
+
}
|
|
217
|
+
if strings.Contains(alias, query) {
|
|
218
|
+
return 650
|
|
219
|
+
}
|
|
220
|
+
aliasTokens := strings.Fields(alias)
|
|
221
|
+
if len(aliasTokens) == 0 || len(queryTokens) == 0 {
|
|
222
|
+
return 0
|
|
223
|
+
}
|
|
224
|
+
matched := 0
|
|
225
|
+
for _, queryToken := range queryTokens {
|
|
226
|
+
for _, aliasToken := range aliasTokens {
|
|
227
|
+
if aliasToken == queryToken || strings.HasPrefix(aliasToken, queryToken) || (len(aliasToken) >= 2 && strings.HasPrefix(queryToken, aliasToken)) {
|
|
228
|
+
matched++
|
|
229
|
+
break
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
if matched == 0 {
|
|
234
|
+
return 0
|
|
235
|
+
}
|
|
236
|
+
return 300 + (matched * 60)
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
func normalizeMatchSearchText(raw string) string {
|
|
240
|
+
raw = strings.ToLower(strings.TrimSpace(raw))
|
|
241
|
+
if raw == "" {
|
|
242
|
+
return ""
|
|
243
|
+
}
|
|
244
|
+
var builder strings.Builder
|
|
245
|
+
lastSpace := false
|
|
246
|
+
for _, r := range raw {
|
|
247
|
+
if (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9') {
|
|
248
|
+
builder.WriteRune(r)
|
|
249
|
+
lastSpace = false
|
|
250
|
+
continue
|
|
251
|
+
}
|
|
252
|
+
if !lastSpace {
|
|
253
|
+
builder.WriteRune(' ')
|
|
254
|
+
lastSpace = true
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
return strings.Join(strings.Fields(builder.String()), " ")
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
func maxInt(a, b int) int {
|
|
261
|
+
if a > b {
|
|
262
|
+
return a
|
|
263
|
+
}
|
|
264
|
+
return b
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
func limitOrDefault(value int, fallback int) int {
|
|
268
|
+
if value > 0 {
|
|
269
|
+
return value
|
|
270
|
+
}
|
|
271
|
+
if fallback <= 0 {
|
|
272
|
+
return 10
|
|
273
|
+
}
|
|
274
|
+
return fallback
|
|
275
|
+
}
|