-
Notifications
You must be signed in to change notification settings - Fork 10
Expand file tree
/
Copy pathuconfig.go
More file actions
129 lines (106 loc) · 2.87 KB
/
uconfig.go
File metadata and controls
129 lines (106 loc) · 2.87 KB
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
// Package uconfig provides advanced command line flags supporting defaults, env vars, and config structs.
package uconfig
import (
"context"
"errors"
"fmt"
"os"
"github.com/omeid/uconfig/flat"
"github.com/omeid/uconfig/plugins"
)
// ErrUsage is returned when the user requests usage (e.g. -h flag).
var ErrUsage = plugins.ErrUsage
// PluginProvider is implemented by types that can provide plugins.
// Both file.Files and watchfile.Files implement this interface,
// allowing Classic and Load to accept either.
type PluginProvider interface {
Plugins() []plugins.Plugin
}
// Config is the config manager.
type Config[C any] interface {
// Parse will call the parse method of all the added plugins in the order
// they were registered. It returns early as soon as any plugin fails.
// You must call this before using the config value.
Parse() (*C, error)
// Run calls Parse and checks the error to see if usage was requested,
// otherwise prints the error and usage and exits with os.Exit(1).
Run() *C
// Usage provides a simple usage message based on the meta data registered
// by the plugins.
Usage()
// Watch calls Parse for the initial configuration, then calls fn.
// When any plugin that implements Updater signals a change, fn's
// context is cancelled, the config is re-parsed, and fn is called
// again with the new value.
// If no plugins implement Updater, fn is called once.
//
// fn should block (e.g. <-ctx.Done()) to stay alive until a
// config change. When fn returns, Watch exits with fn's error.
Watch(ctx context.Context, fn func(ctx context.Context, c *C) error) error
}
// New returns a new Config. The conf must be a pointer to a struct.
func New[C any](ps ...plugins.Plugin) Config[C] {
conf := new(C)
fields, err := flat.View(conf)
return &config[C]{
err: err,
conf: conf,
fields: fields,
plugins: ps,
}
}
type config[C any] struct {
plugins []plugins.Plugin
conf *C
fields flat.Fields
err error // lazy error
}
func (c *config[C]) Parse() (*C, error) {
if c.err != nil {
return nil, c.err
}
// first setup plugins.
for _, plug := range c.plugins {
switch plug := plug.(type) {
case plugins.Visitor:
err := plug.Visit(c.fields)
if err != nil {
return nil, err
}
case plugins.Walker:
err := plug.Walk(c.conf)
if err != nil {
return nil, err
}
case plugins.Extension:
err := plug.Extend(c.plugins)
if err != nil {
return nil, err
}
default:
return nil, fmt.Errorf("unsupported plugins. expecting a walker or visitor")
}
}
for _, p := range c.plugins {
err := p.Parse()
if err != nil {
return nil, err
}
}
return c.conf, nil
}
func (c *config[C]) Run() *C {
conf, err := c.Parse()
if err != nil {
usageRequest := errors.Is(err, ErrUsage)
ret := 1
if usageRequest {
ret = 0
} else {
fmt.Println(err)
}
c.Usage()
os.Exit(ret)
}
return conf
}