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
package/internal/cli/matches.go
CHANGED
|
@@ -13,12 +13,15 @@ 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)
|
|
22
25
|
Phases(ctx context.Context, query string, opts cricinfo.MatchLookupOptions) (cricinfo.NormalizedResult, error)
|
|
23
26
|
Innings(ctx context.Context, query string, opts cricinfo.MatchInningsOptions) (cricinfo.NormalizedResult, error)
|
|
24
27
|
Partnerships(ctx context.Context, query string, opts cricinfo.MatchInningsOptions) (cricinfo.NormalizedResult, error)
|
|
@@ -30,6 +33,8 @@ type matchRuntimeOptions struct {
|
|
|
30
33
|
limit int
|
|
31
34
|
leagueID string
|
|
32
35
|
team string
|
|
36
|
+
batter string
|
|
37
|
+
bowler string
|
|
33
38
|
innings int
|
|
34
39
|
period int
|
|
35
40
|
}
|
|
@@ -52,6 +57,7 @@ func newMatchesCommand(global *globalOptions) *cobra.Command {
|
|
|
52
57
|
" cricinfo matches list",
|
|
53
58
|
" cricinfo matches show <match>",
|
|
54
59
|
" cricinfo matches status <match>",
|
|
60
|
+
" cricinfo matches live-view <match>",
|
|
55
61
|
" cricinfo matches scorecard <match>",
|
|
56
62
|
" cricinfo matches innings <match>",
|
|
57
63
|
}, "\n"),
|
|
@@ -73,6 +79,7 @@ func newMatchesCommand(global *globalOptions) *cobra.Command {
|
|
|
73
79
|
" cricinfo matches show <match>",
|
|
74
80
|
" cricinfo matches status <match>",
|
|
75
81
|
" cricinfo matches scorecard <match>",
|
|
82
|
+
" cricinfo matches live-view <match>",
|
|
76
83
|
" cricinfo matches innings <match>",
|
|
77
84
|
}, "\n"),
|
|
78
85
|
Args: cobra.NoArgs,
|
|
@@ -97,6 +104,7 @@ func newMatchesCommand(global *globalOptions) *cobra.Command {
|
|
|
97
104
|
" cricinfo matches show <match>",
|
|
98
105
|
" cricinfo matches status <match>",
|
|
99
106
|
" cricinfo matches scorecard <match>",
|
|
107
|
+
" cricinfo matches live-view <match>",
|
|
100
108
|
" cricinfo matches innings <match>",
|
|
101
109
|
}, "\n"),
|
|
102
110
|
Args: cobra.NoArgs,
|
|
@@ -111,6 +119,26 @@ func newMatchesCommand(global *globalOptions) *cobra.Command {
|
|
|
111
119
|
}
|
|
112
120
|
liveCmd.Flags().IntVar(&opts.limit, "limit", 20, "Maximum number of live matches to return")
|
|
113
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
|
+
|
|
114
142
|
showCmd := &cobra.Command{
|
|
115
143
|
Use: "show <match>",
|
|
116
144
|
Short: "Show one match summary",
|
|
@@ -120,6 +148,7 @@ func newMatchesCommand(global *globalOptions) *cobra.Command {
|
|
|
120
148
|
"Next steps:",
|
|
121
149
|
" cricinfo matches status <match>",
|
|
122
150
|
" cricinfo matches scorecard <match>",
|
|
151
|
+
" cricinfo matches live-view <match>",
|
|
123
152
|
" cricinfo matches innings <match>",
|
|
124
153
|
}, "\n"),
|
|
125
154
|
Args: cobra.MinimumNArgs(1),
|
|
@@ -140,6 +169,7 @@ func newMatchesCommand(global *globalOptions) *cobra.Command {
|
|
|
140
169
|
"Next steps:",
|
|
141
170
|
" cricinfo matches show <match>",
|
|
142
171
|
" cricinfo matches scorecard <match>",
|
|
172
|
+
" cricinfo matches live-view <match>",
|
|
143
173
|
" cricinfo matches innings <match>",
|
|
144
174
|
}, "\n"),
|
|
145
175
|
Args: cobra.MinimumNArgs(1),
|
|
@@ -160,6 +190,7 @@ func newMatchesCommand(global *globalOptions) *cobra.Command {
|
|
|
160
190
|
"Next steps:",
|
|
161
191
|
" cricinfo matches details <match>",
|
|
162
192
|
" cricinfo matches plays <match>",
|
|
193
|
+
" cricinfo matches live-view <match>",
|
|
163
194
|
" cricinfo matches situation <match>",
|
|
164
195
|
}, "\n"),
|
|
165
196
|
Args: cobra.MinimumNArgs(1),
|
|
@@ -200,6 +231,7 @@ func newMatchesCommand(global *globalOptions) *cobra.Command {
|
|
|
200
231
|
"Next steps:",
|
|
201
232
|
" cricinfo matches details <match>",
|
|
202
233
|
" cricinfo matches scorecard <match>",
|
|
234
|
+
" cricinfo matches live-view <match>",
|
|
203
235
|
" cricinfo matches situation <match>",
|
|
204
236
|
}, "\n"),
|
|
205
237
|
Args: cobra.MinimumNArgs(1),
|
|
@@ -220,6 +252,7 @@ func newMatchesCommand(global *globalOptions) *cobra.Command {
|
|
|
220
252
|
"Next steps:",
|
|
221
253
|
" cricinfo matches status <match>",
|
|
222
254
|
" cricinfo matches details <match>",
|
|
255
|
+
" cricinfo matches live-view <match>",
|
|
223
256
|
" cricinfo matches scorecard <match>",
|
|
224
257
|
}, "\n"),
|
|
225
258
|
Args: cobra.MinimumNArgs(1),
|
|
@@ -231,6 +264,66 @@ func newMatchesCommand(global *globalOptions) *cobra.Command {
|
|
|
231
264
|
},
|
|
232
265
|
}
|
|
233
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
|
+
|
|
234
327
|
phasesCmd := &cobra.Command{
|
|
235
328
|
Use: "phases <match>",
|
|
236
329
|
Short: "Show powerplay, middle, and death-over phase splits for each innings",
|
|
@@ -390,12 +483,15 @@ func newMatchesCommand(global *globalOptions) *cobra.Command {
|
|
|
390
483
|
cmd.AddCommand(
|
|
391
484
|
liveCmd,
|
|
392
485
|
listCmd,
|
|
486
|
+
lineupCmd,
|
|
393
487
|
showCmd,
|
|
394
488
|
statusCmd,
|
|
395
489
|
scorecardCmd,
|
|
396
490
|
detailsCmd,
|
|
397
491
|
playsCmd,
|
|
398
492
|
situationCmd,
|
|
493
|
+
liveViewCmd,
|
|
494
|
+
duelCmd,
|
|
399
495
|
phasesCmd,
|
|
400
496
|
inningsCmd,
|
|
401
497
|
partnershipsCmd,
|
|
@@ -13,12 +13,15 @@ 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
|
|
22
25
|
phasesResult cricinfo.NormalizedResult
|
|
23
26
|
inningsResult cricinfo.NormalizedResult
|
|
24
27
|
partnerships cricinfo.NormalizedResult
|
|
@@ -45,6 +48,10 @@ func (f *fakeMatchService) Live(context.Context, cricinfo.MatchListOptions) (cri
|
|
|
45
48
|
return f.liveResult, nil
|
|
46
49
|
}
|
|
47
50
|
|
|
51
|
+
func (f *fakeMatchService) Lineups(context.Context, string, cricinfo.MatchLookupOptions) (cricinfo.NormalizedResult, error) {
|
|
52
|
+
return f.lineupResult, nil
|
|
53
|
+
}
|
|
54
|
+
|
|
48
55
|
func (f *fakeMatchService) Show(context.Context, string, cricinfo.MatchLookupOptions) (cricinfo.NormalizedResult, error) {
|
|
49
56
|
return f.showResult, nil
|
|
50
57
|
}
|
|
@@ -69,6 +76,14 @@ func (f *fakeMatchService) Situation(context.Context, string, cricinfo.MatchLook
|
|
|
69
76
|
return f.situationResult, nil
|
|
70
77
|
}
|
|
71
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
|
+
|
|
72
87
|
func (f *fakeMatchService) Phases(context.Context, string, cricinfo.MatchLookupOptions) (cricinfo.NormalizedResult, error) {
|
|
73
88
|
return f.phasesResult, nil
|
|
74
89
|
}
|
|
@@ -149,12 +164,39 @@ func TestMatchesCommandsRenderTextAndJSON(t *testing.T) {
|
|
|
149
164
|
service := &fakeMatchService{
|
|
150
165
|
listResult: cricinfo.NewListResult(cricinfo.EntityMatch, []any{match}),
|
|
151
166
|
liveResult: cricinfo.NewListResult(cricinfo.EntityMatch, []any{match}),
|
|
167
|
+
lineupResult: cricinfo.NewListResult(cricinfo.EntityTeamRoster, []any{cricinfo.TeamRosterEntry{DisplayName: "Player One", TeamName: "BOOST"}}),
|
|
152
168
|
showResult: cricinfo.NewDataResult(cricinfo.EntityMatch, match),
|
|
153
169
|
statusResult: cricinfo.NewDataResult(cricinfo.EntityMatch, match),
|
|
154
170
|
scorecardResult: cricinfo.NewDataResult(cricinfo.EntityMatchScorecard, scorecard),
|
|
155
171
|
detailsResult: cricinfo.NewListResult(cricinfo.EntityDeliveryEvent, []any{delivery}),
|
|
156
172
|
playsResult: cricinfo.NewListResult(cricinfo.EntityDeliveryEvent, []any{delivery}),
|
|
157
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
|
+
}),
|
|
158
200
|
phasesResult: cricinfo.NewDataResult(cricinfo.EntityMatchPhases, cricinfo.MatchPhases{
|
|
159
201
|
MatchID: "1529474",
|
|
160
202
|
Innings: []cricinfo.MatchPhaseInning{
|
|
@@ -296,6 +338,15 @@ func TestMatchesCommandsRenderTextAndJSON(t *testing.T) {
|
|
|
296
338
|
t.Fatalf("expected plays text output to include normalized short text, got %q", playsOut.String())
|
|
297
339
|
}
|
|
298
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
|
+
|
|
299
350
|
var situationOut bytes.Buffer
|
|
300
351
|
var situationErr bytes.Buffer
|
|
301
352
|
if err := Run([]string{"matches", "situation", "1529474", "--format", "json"}, &situationOut, &situationErr); err != nil {
|
|
@@ -309,6 +360,26 @@ func TestMatchesCommandsRenderTextAndJSON(t *testing.T) {
|
|
|
309
360
|
t.Fatalf("expected kind %q in situation output, got %#v", cricinfo.EntityMatchSituation, situationPayload["kind"])
|
|
310
361
|
}
|
|
311
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
|
+
|
|
312
383
|
var inningsOut bytes.Buffer
|
|
313
384
|
var inningsErr bytes.Buffer
|
|
314
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
|
+
}
|