blob: 1e57f8dfd20670f818512588d890cc287764802a [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"
7 "strings"
8 "sync"
9)
10
11// Experiment represents an experimental feature.
12// Experiments are global.
13type Experiment struct {
14 Name string // The name of the experiment used in -x flag
15 Description string // A short description of what the experiment does
16 Enabled bool // Whether the experiment is enabled
17}
18
19var (
20 mu sync.Mutex
21 experiments = []Experiment{
22 {
23 Name: "list",
24 Description: "List all available experiments and exit",
25 },
26 {
27 Name: "all",
28 Description: "Enable all experiments",
29 },
30 }
31 byName = map[string]*Experiment{}
32)
33
34func Enabled(name string) bool {
35 mu.Lock()
36 defer mu.Unlock()
37 return byName[name].Enabled
38}
39
40func init() {
41 for _, e := range experiments {
42 byName[e.Name] = &e
43 }
44}
45
46func (e Experiment) String() string {
47 return fmt.Sprintf("\t%-15s %s\n", e.Name, e.Description)
48}
49
50// Fprint writes a list of all available experiments to w.
51func Fprint(w io.Writer) {
52 mu.Lock()
53 defer mu.Unlock()
54
55 fmt.Fprintln(w, "Available experiments:")
56 for _, e := range experiments {
57 fmt.Fprintln(w, e)
58 }
59}
60
61// Flag is a custom flag type that allows for comma-separated
62// values and can be used multiple times.
63type Flag struct {
64 Value string
65}
66
67// String returns the string representation of the flag value.
68func (f *Flag) String() string {
69 return f.Value
70}
71
72// Set adds a value to the flag.
73func (f *Flag) Set(value string) error {
74 f.Value = f.Value + "," + value // quadratic, doesn't matter, tiny N
75 return nil
76}
77
78// Get returns the flag values.
79func (f *Flag) Get() any {
80 return f.Value
81}
82
83// Process handles all flag values, enabling the appropriate experiments.
84func (f *Flag) Process() error {
85 mu.Lock()
86 defer mu.Unlock()
87
88 for name := range strings.SplitSeq(f.Value, ",") {
89 name = strings.TrimSpace(name)
90 if name == "" {
91 continue
92 }
93 e, ok := byName[name]
94 if !ok {
95 return fmt.Errorf("unknown experiment: %q", name)
96 }
97 e.Enabled = true
98 }
99 if byName["all"].Enabled {
100 for _, e := range experiments {
101 if e.Name == "list" {
102 continue
103 }
104 e.Enabled = true
105 }
106 }
107 return nil
108}