package features
import (
"flag"
"fmt"
"sort"
"strings"
)
type Feature string
const setFlagName = "enable-features"
func Register(fs *flag.FlagSet, ff []Feature) {
var (
cache = make(map[Feature]struct{}, len(ff))
names = make([]string, len(ff))
)
for i, f := range ff {
normalized := normalize(f)
if _, found := cache[normalized]; found {
panic(fmt.Sprintf("case-insensitive feature %q registered twice", normalized))
}
cache[normalized] = struct{}{}
names[i] = string(normalized)
}
help := fmt.Sprintf("Comma-delimited list of features to enable. Valid values: %s", strings.Join(names, ", "))
s := set{valid: cache, validString: strings.Join(names, ", ")}
fs.Var(&s, setFlagName, help)
}
func normalize(f Feature) Feature {
return Feature(strings.ToLower(string(f)))
}
func Enabled(fs *flag.FlagSet, name Feature) bool {
name = normalize(name)
f := fs.Lookup(setFlagName)
if f == nil {
panic("feature flag not registered to fs")
}
s, ok := f.Value.(*set)
if !ok {
panic("registered feature flag not appropriate type")
}
if _, valid := s.valid[name]; !valid {
panic(fmt.Sprintf("unknown feature %q", name))
}
_, enabled := s.enabled[name]
return enabled
}
type Dependency struct {
Flag string
Feature Feature
}
func Validate(fs *flag.FlagSet, deps []Dependency) error {
depLookup := make(map[string]Dependency, len(deps))
for _, dep := range deps {
if fs.Lookup(dep.Flag) == nil {
panic(fmt.Sprintf("flag %q does not exist in fs", dep.Flag))
}
depLookup[dep.Flag] = dep
_ = Enabled(fs, dep.Feature)
}
var err error
fs.Visit(func(f *flag.Flag) {
if err != nil {
return
}
dep, ok := depLookup[f.Name]
if !ok {
return
}
if !Enabled(fs, dep.Feature) {
err = fmt.Errorf("flag %q requires feature %q to be provided in --%s", f.Name, dep.Feature, setFlagName)
}
})
return err
}
func GetAllEnabled(fs *flag.FlagSet) []string {
f := fs.Lookup(setFlagName)
if f == nil {
panic("feature flag not registered to fs")
}
s, ok := f.Value.(*set)
if !ok {
panic("registered feature flag not appropriate type")
}
var enabled []string
for feature := range s.enabled {
enabled = append(enabled, string(feature))
}
return enabled
}
type set struct {
valid map[Feature]struct{}
validString string
enabled map[Feature]struct{}
}
func (s *set) String() string {
res := make([]string, 0, len(s.enabled))
for k := range s.enabled {
res = append(res, string(k))
}
sort.Strings(res)
return strings.Join(res, ",")
}
func (s *set) Set(in string) error {
slice := strings.Split(in, ",")
m := make(map[Feature]struct{}, len(slice))
for _, input := range slice {
f := normalize(Feature(input))
if _, valid := s.valid[f]; !valid {
return fmt.Errorf("unknown feature %q. possible options: %s", f, s.validString)
} else if _, ok := m[f]; ok {
return fmt.Errorf("%q already set", f)
}
m[f] = struct{}{}
}
s.enabled = m
return nil
}