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.
@@ -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{Limit: opts.limit})
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{Limit: opts.limit})
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 {
@@ -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
+ }