blob: 068f9d7b35c6b458d91af7bc6c5645d29fb23500 [file] [log] [blame]
Josh Bleecher Snyderb4782142025-05-05 19:16:54 +00001// Package experiment provides support for experimental features.
2package experiment
3
4import (
5 "fmt"
6 "io"
Josh Bleecher Snyder6f446fa2025-05-05 14:51:49 -07007 "log/slog"
Josh Bleecher Snyderb4782142025-05-05 19:16:54 +00008 "strings"
9 "sync"
10)
11
12// Experiment represents an experimental feature.
13// Experiments are global.
14type Experiment struct {
15 Name string // The name of the experiment used in -x flag
16 Description string // A short description of what the experiment does
17 Enabled bool // Whether the experiment is enabled
18}
19
20var (
21 mu sync.Mutex
22 experiments = []Experiment{
23 {
24 Name: "list",
25 Description: "List all available experiments and exit",
26 },
27 {
28 Name: "all",
29 Description: "Enable all experiments",
30 },
Josh Bleecher Snyder503b5e32025-05-05 13:30:55 -070031 {
Josh Bleecher Snydere2518e52025-04-29 11:13:40 -070032 Name: "llm_review",
33 Description: "Add an LLM step to the codereview tool",
34 },
Josh Bleecher Snyderb4782142025-05-05 19:16:54 +000035 }
36 byName = map[string]*Experiment{}
37)
38
39func Enabled(name string) bool {
40 mu.Lock()
41 defer mu.Unlock()
Josh Bleecher Snyder6f446fa2025-05-05 14:51:49 -070042 e, ok := byName[name]
43 if !ok {
44 slog.Error("unknown experiment", "name", name)
45 return false
46 }
47 return e.Enabled
Josh Bleecher Snyderb4782142025-05-05 19:16:54 +000048}
49
50func init() {
Josh Bleecher Snydere01ea0e2025-05-06 02:03:26 +000051 for i := range experiments {
52 e := &experiments[i]
53 byName[e.Name] = e
Josh Bleecher Snyderb4782142025-05-05 19:16:54 +000054 }
55}
56
57func (e Experiment) String() string {
58 return fmt.Sprintf("\t%-15s %s\n", e.Name, e.Description)
59}
60
61// Fprint writes a list of all available experiments to w.
62func Fprint(w io.Writer) {
63 mu.Lock()
64 defer mu.Unlock()
65
66 fmt.Fprintln(w, "Available experiments:")
67 for _, e := range experiments {
68 fmt.Fprintln(w, e)
69 }
70}
71
72// Flag is a custom flag type that allows for comma-separated
73// values and can be used multiple times.
74type Flag struct {
75 Value string
76}
77
78// String returns the string representation of the flag value.
79func (f *Flag) String() string {
80 return f.Value
81}
82
83// Set adds a value to the flag.
84func (f *Flag) Set(value string) error {
Josh Bleecher Snyder085f74c2025-05-06 01:58:25 +000085 if f.Value == "" {
86 f.Value = value
87 } else {
88 f.Value = f.Value + "," + value // quadratic, doesn't matter, tiny N
89 }
Josh Bleecher Snyderb4782142025-05-05 19:16:54 +000090 return nil
91}
92
93// Get returns the flag values.
94func (f *Flag) Get() any {
95 return f.Value
96}
97
98// Process handles all flag values, enabling the appropriate experiments.
99func (f *Flag) Process() error {
100 mu.Lock()
101 defer mu.Unlock()
102
103 for name := range strings.SplitSeq(f.Value, ",") {
104 name = strings.TrimSpace(name)
105 if name == "" {
106 continue
107 }
108 e, ok := byName[name]
109 if !ok {
110 return fmt.Errorf("unknown experiment: %q", name)
111 }
112 e.Enabled = true
113 }
114 if byName["all"].Enabled {
Josh Bleecher Snyderd42577f2025-05-05 17:59:00 -0700115 for i := range experiments {
116 e := &experiments[i]
Josh Bleecher Snyderb4782142025-05-05 19:16:54 +0000117 if e.Name == "list" {
118 continue
119 }
120 e.Enabled = true
121 }
122 }
123 return nil
124}