Files
job-search/internal/config/modify.go
T
bwilliams 465358609a Naming, layout and comments: descriptive full-word names, one item per line in wrapped calls and literals, and doc comments on exported names
Short and single-letter names in application code become full words (tests
keep theirs). Wrapped calls, signatures and composite literals put one item
per line, and one-line function bodies are expanded. Exported names and
groups of constants get doc comments, and comments refer to the renamed
identifiers.
2026-10-15 16:12:56 +01:00

118 lines
3.3 KiB
Go

package config
import (
"errors"
"fmt"
"reflect"
"strconv"
"github.com/spf13/viper"
)
// StoreValue parses value as the type of key, checks conf accepts it, sets it
// in conf and Viper and writes it to the config file Viper is using. Only the
// file's own settings are written back, not defaults or environment
// variables. It returns the stored value (as ValueOf gives it) and its kind.
func StoreValue(
conf *JobSearchConfig, key string, value string,
) (any, reflect.Kind, error) {
return storeValue(conf, key, value)
}
// storeValue is StoreValue for any section, so key types that
// JobSearchConfig doesn't have yet can be tested.
func storeValue(section Section, key string, value string) (any, reflect.Kind, error) {
confType, err := section.TypeOf(key)
if err != nil {
return nil, confType, err
}
// Check the value is of the right kind first, for a clearer error than
// SetValue gives.
if _, err := parseValue(confType, value); err != nil {
return nil, confType, fmt.Errorf(
"config value type not accepted, expected <%s>", confType,
)
}
// SetValue parses the string into the field's own type (e.g. int8,
// float32 or time.Duration) and rejects values the configuration itself
// would not accept, e.g. an unknown log level or an unparseable duration.
if err := section.SetValue(key, value); err != nil {
return nil, confType, err
}
confValue, err := section.ValueOf(key)
if err != nil {
return nil, confType, err
}
viper.Set(key, confValue)
file, err := readUsedConfigFile()
if err != nil {
return nil, confType, err
}
file.Set(key, confValue)
if err := file.WriteConfig(); err != nil {
return nil, confType, err
}
return confValue, confType, nil
}
// parseValue parses value as a value of kind, returning
// errors.ErrUnsupported for a kind it can't parse.
func parseValue(kind reflect.Kind, value string) (any, error) {
switch kind {
case reflect.Bool:
return strconv.ParseBool(value)
case reflect.String:
return value, nil
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return strconv.ParseInt(value, 10, 64)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return strconv.ParseUint(value, 10, 64)
case reflect.Float32, reflect.Float64:
return strconv.ParseFloat(value, 64)
case reflect.Slice:
return splitList(value), nil
}
return nil, errors.ErrUnsupported
}
// PurgeValue rewrites the config file Viper is using without key. The
// file's other settings are kept as they are; defaults and environment
// variables are not written to it.
func PurgeValue(conf *JobSearchConfig, key string) error {
if _, err := conf.TypeOf(key); err != nil {
return err
}
file, err := readUsedConfigFile()
if err != nil {
return err
}
out := viper.New()
out.SetConfigFile(file.ConfigFileUsed())
for _, fileKey := range file.AllKeys() {
if fileKey != key {
out.Set(fileKey, file.Get(fileKey))
}
}
return out.WriteConfig()
}
// readUsedConfigFile reads the config file Viper is using into a new Viper, so
// it holds only the file's settings.
func readUsedConfigFile() (*viper.Viper, error) {
path := viper.ConfigFileUsed()
if path == "" {
return nil, errors.New("no configuration file in use")
}
file := viper.New()
file.SetConfigFile(path)
if err := file.ReadInConfig(); err != nil {
return nil, err
}
return file, nil
}