-
Notifications
You must be signed in to change notification settings - Fork 0
/
cfgutil.go
130 lines (113 loc) · 3.04 KB
/
cfgutil.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
// Package cfgutil provides utilities for working with configuration files and
// environment variables. It supports parsing TOML and JSON files.
package cfgutil
import (
"bytes"
"encoding/json"
"fmt"
"io"
"log/slog"
"os"
"path/filepath"
"strings"
"time"
"github.com/pkg/errors"
)
// ParseJSON parses a JSON config from an io.Reader.
func ParseJSON(r io.Reader, dst any) error {
return json.NewDecoder(r).Decode(dst)
}
// Parse parses a reader.
func Parse(f io.Reader, configType string, dst any) error {
switch configType {
case "json":
return ParseJSON(f, dst)
default:
return fmt.Errorf("unsupported config type %s", configType)
}
}
// ParseMany parses b into multiple destinations.
func ParseMany(b []byte, configType string, dsts ...any) error {
for _, dst := range dsts {
if err := Parse(bytes.NewReader(b), configType, dst); err != nil {
return fmt.Errorf("cannot parse into %T: %w", dst, err)
}
}
return nil
}
// ParseFile parses a config file from a path. The file extension is used to
// determine the config format.
func ParseFile[T any](path string) (*T, error) {
ext := filepath.Ext(path)
f, err := os.Open(path)
if err != nil {
return nil, errors.Wrap(err, "failed to open config file")
}
defer f.Close()
var v T
err = Parse(f, strings.TrimPrefix(ext, "."), &v)
return &v, err
}
// Duration is a type that describes a duration in a config.
type Duration time.Duration
func (d *Duration) UnmarshalText(b []byte) error {
v, err := time.ParseDuration(string(b))
if err != nil {
return err
}
*d = Duration(v)
return nil
}
func (d *Duration) UnmarshalJSON(b []byte) error {
var v string
if err := json.Unmarshal(b, &v); err != nil {
return err
}
return d.UnmarshalText([]byte(v))
}
func (d Duration) AsDuration() time.Duration {
return time.Duration(d)
}
// VerbosityToLevel converts a base log level and a verbosity integer to a new
// log level. The higher the verbosity, the lower the log level.
func VerbosityToLevel(base slog.Level, verbosity int) slog.Level {
base -= slog.Level(verbosity * 4)
return base
}
// MaybeArray is a type that describes a value that can be either a single
// value or an array of values.
type MaybeArray[T any] []T
func (a *MaybeArray[T]) UnmarshalJSON(b []byte) error {
if bytes.HasPrefix(b, []byte{'['}) {
var items []T
if err := json.Unmarshal(b, &items); err != nil {
return err
}
*a = items
} else {
var item T
if err := json.Unmarshal(b, &item); err != nil {
return err
}
*a = []T{item}
}
return nil
}
// TransformSlice applies a function to each element of a slice and returns a new
// slice with the results.
func TransformSlice[T any, U any](slice []T, f func(T) U) []U {
result := make([]U, len(slice))
for i, v := range slice {
result[i] = f(v)
}
return result
}
// TransformMapValues applies a function to each value of a map and returns a new
// map with the results.
func TransformMapValues[K comparable, V any, U any](m map[K]V, f func(V) U) map[K]U {
result := make(map[K]U, len(m))
for k, v := range m {
result[k] = f(v)
}
return result
}