wand/config.go

245 lines
3.9 KiB
Go
Raw Normal View History

2025-08-24 01:45:25 +02:00
package wand
import (
2025-08-26 03:21:35 +02:00
"bytes"
2025-08-24 01:45:25 +02:00
"errors"
"fmt"
"github.com/iancoleman/strcase"
"io"
2025-08-26 03:21:35 +02:00
"io/ioutil"
2025-08-24 01:45:25 +02:00
"os"
)
type file struct {
filename string
file io.ReadCloser
}
type config struct {
values map[string][]string
discard []string
originalNames map[string]string
}
2025-08-24 04:46:54 +02:00
func fileReader(filename string) *file {
2025-08-24 01:45:25 +02:00
return &file{
filename: filename,
}
}
func (f *file) wrapErr(err error) error {
return fmt.Errorf("%s: %w", f.filename, err)
}
func (f *file) Read(p []byte) (int, error) {
if f.file == nil {
file, err := os.Open(f.filename)
if err != nil {
return 0, f.wrapErr(err)
}
f.file = file
}
n, err := f.file.Read(p)
if err != nil {
return n, f.wrapErr(err)
}
return n, nil
}
func (f *file) Close() error {
if f.file == nil {
return nil
}
if err := f.file.Close(); err != nil {
return f.wrapErr(err)
}
return nil
}
2025-09-05 03:19:00 +02:00
func unescapeConfig(s string) string {
var (
u []rune
escaped bool
)
r := []rune(s)
for _, ri := range r {
if escaped {
u = append(u, ri)
continue
}
if ri == '\\' {
escaped = true
continue
}
u = append(u, ri)
}
return string(u)
}
func unquoteConfig(s string) string {
if len(s) < 2 {
return s
}
s = s[1 : len(s)-1]
return unescapeConfig(s)
}
2025-08-24 01:45:25 +02:00
func readConfigFile(cmd Cmd, conf Config) (config, error) {
2025-08-26 03:21:35 +02:00
var f io.ReadCloser
if conf.test == "" {
f = conf.file(cmd)
defer f.Close()
} else {
f = ioutil.NopCloser(bytes.NewBufferString(conf.test))
}
2025-08-24 01:45:25 +02:00
doc, err := parse(f)
if err != nil {
if conf.optional && (errors.Is(err, os.ErrPermission) || errors.Is(err, os.ErrNotExist)) {
return config{}, nil
}
return config{}, fmt.Errorf("failed to read config file: %w", err)
}
var c config
for _, entry := range doc.Nodes {
if entry.Name != "key-val" {
continue
}
var (
key string
value string
hasValue bool
)
for _, token := range entry.Nodes {
if token.Name == "key" {
key = token.Text()
continue
}
2025-09-05 03:19:00 +02:00
if token.Name != "value" {
2025-08-24 01:45:25 +02:00
continue
}
2025-09-05 03:19:00 +02:00
hasValue = true
for _, valueToken := range token.Nodes {
if valueToken.Name == "value-chars" {
value = unescapeConfig(valueToken.Text())
break
}
if valueToken.Name == "quoted" {
value = unquoteConfig(valueToken.Text())
break
}
}
2025-08-24 01:45:25 +02:00
}
if c.originalNames == nil {
c.originalNames = make(map[string]string)
}
name := strcase.ToKebab(key)
c.originalNames[name] = key
if !hasValue {
2025-09-01 02:07:48 +02:00
delete(c.values, name)
2025-08-24 01:45:25 +02:00
c.discard = append(c.discard, name)
continue
}
if c.values == nil {
c.values = make(map[string][]string)
}
c.values[name] = append(c.values[name], value)
}
return c, nil
}
func readConfigFromOption(cmd Cmd, cl commandLine, conf Config) (config, error) {
f := mapFields(cmd.impl)
if _, ok := f["config"]; ok {
return config{}, nil
}
var c []Config
for _, o := range cl.options {
if o.name != "config" {
continue
}
2025-09-06 21:38:50 +02:00
c = append(c, ConfigFile(o.value.str))
2025-08-24 01:45:25 +02:00
}
2025-09-06 21:38:50 +02:00
return readConfig(cmd, cl, MergeConfig(c...))
2025-08-24 01:45:25 +02:00
}
func readMergeConfig(cmd Cmd, cl commandLine, conf Config) (config, error) {
var c []config
for _, ci := range conf.merge {
cci, err := readConfig(cmd, cl, ci)
if err != nil {
return config{}, err
}
c = append(c, cci)
}
var mc config
for _, ci := range c {
for _, d := range ci.discard {
delete(mc.values, d)
}
for name, values := range ci.values {
if mc.values == nil {
mc.values = make(map[string][]string)
}
mc.values[name] = values
}
}
return mc, nil
}
func readConfig(cmd Cmd, cl commandLine, conf Config) (config, error) {
2025-08-26 03:21:35 +02:00
if conf.file != nil || conf.test != "" {
2025-08-24 01:45:25 +02:00
return readConfigFile(cmd, conf)
}
if conf.fromOption {
return readConfigFromOption(cmd, cl, conf)
}
return readMergeConfig(cmd, cl, conf)
}
func hasConfigFromOption(conf Config) bool {
if conf.fromOption {
return true
}
for _, c := range conf.merge {
if hasConfigFromOption(c) {
return true
}
}
return false
}