-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathflags.go
128 lines (117 loc) · 3.07 KB
/
flags.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
package cliff
import (
"errors"
"flag"
"fmt"
"io"
"regexp"
"strings"
"github.com/spf13/pflag"
)
var hasUpper = regexp.MustCompile(`[A-Z]`).FindString
var isAlNum = regexp.MustCompile(`^[a-zA-Z0-9]$`).MatchString
var isValidFlag = regexp.MustCompile(`^[a-zA-Z0-9-]+$`).MatchString
// MustParse parses CLI flags, writes errors and warnings into stderr and exits on error or help.
//
// Typical usage:
//
// cliff.MustParse(os.Stderr, os.Exit, os.Args, flags)
func MustParse[T any](
stderr io.Writer,
exit func(int),
args []string,
init func(c *T) Flags,
) T {
config, err := Parse[T](stderr, args, init)
HandleError(stderr, exit, err)
return config
}
// MustParse parses CLI flags, writes warnings into stderr and returns error on error or help.
//
// Typical usage:
//
// cliff.Parse(os.Stderr, os.Args, flags)
func Parse[T any](stderr io.Writer, args []string, init func(c *T) Flags) (T, error) {
var config T
flags := init(&config)
err := flags.Parse(stderr, args)
return config, err
}
// Flags is a mapping of CLI flag names to the flags.
type Flags map[string]Flag
// Parse the given arguments.
//
// Help and warnings will be written into the given stderr stream.
//
// Typical usage:
//
// flags.Parse(os.Stderr, os.Args)
func (fs Flags) Parse(stderr io.Writer, args []string) error {
pfs, err := fs.PFlagSet(stderr, args[0])
if err != nil {
return err
}
return pfs.Parse(args[1:])
}
func (fs Flags) FlagSet(stderr io.Writer, name string) (*flag.FlagSet, error) {
pfs, err := fs.PFlagSet(stderr, name)
if err != nil {
return nil, err
}
gfs := flag.NewFlagSet(name, flag.ContinueOnError)
pfs.VisitAll(func(pf *pflag.Flag) {
gfs.Var(pf.Value, pf.Name, pf.Usage)
if pf.Shorthand != "" {
gfs.Var(pf.Value, pf.Shorthand, pf.Usage)
}
})
return gfs, nil
}
// PFlagSet returns a [pflag.FlagSet] populated with defined flags.
func (fs Flags) PFlagSet(stderr io.Writer, name string) (*pflag.FlagSet, error) {
pfs := pflag.NewFlagSet(name, pflag.ContinueOnError)
pfs.SetOutput(stderr)
for name, flag := range fs {
err := validateName(name)
if err != nil {
return nil, fmt.Errorf("validate flag name (%s): %v", name, err)
}
err = flag.AddTo(pfs, name)
if err != nil {
return nil, fmt.Errorf("add flag %s: %v", name, err)
}
}
return pfs, nil
}
func validateName(name string) error {
if name == "" {
return errors.New("must not be empty")
}
if hasUpper(name) != "" {
return errors.New("must be lowercase")
}
if !isAlNum(name[:1]) {
return errors.New("must start with alpha-numeric ASCII character")
}
if strings.Contains(name, "--") {
return errors.New("must not contain --")
}
if strings.Contains(name, "=") {
return errors.New("must not contain =")
}
if !isValidFlag(name) {
return errors.New("can contain only alpha-numeric ASCII characters and dashes")
}
return nil
}
// HandleError interrupts the program if an error occurred when parsing arguments.
func HandleError(stderr io.Writer, exit func(int), err error) {
if err == nil {
return
}
if err == pflag.ErrHelp || err == flag.ErrHelp {
exit(0)
}
fmt.Fprintln(stderr, err)
exit(2)
}