Skip to content

Commit 7ec11cd

Browse files
committed
feat(cmd): Enable setting flags via env vars
1 parent 4aa3a26 commit 7ec11cd

File tree

5 files changed

+145
-44
lines changed

5 files changed

+145
-44
lines changed

go.mod

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,4 @@ module github.com/cluttrdev/deepl-go
22

33
go 1.21
44

5-
require (
6-
github.com/pkg/errors v0.9.1
7-
golang.org/x/text v0.7.0
8-
)
5+
require golang.org/x/text v0.7.0

go.sum

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,2 @@
1-
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
2-
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
31
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
42
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=

internal/cmd/root.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,12 @@ func Execute() error {
115115
return errors.New("failed to configure root command")
116116
}
117117

118-
if err := rootCmd.Parse(os.Args[1:]); err != nil {
118+
args := os.Args[1:]
119+
opts := []command.ParseOption{
120+
command.WithEnvVarPrefix("DEEPL"),
121+
}
122+
123+
if err := rootCmd.Parse(args, opts...); err != nil {
119124
if errors.Is(err, flag.ErrHelp) {
120125
return nil
121126
} else {

internal/command/command.go

Lines changed: 0 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import (
55
"errors"
66
"flag"
77
"fmt"
8-
"strings"
98
)
109

1110
type Command struct {
@@ -24,42 +23,6 @@ type Command struct {
2423
args []string
2524
}
2625

27-
func (cmd *Command) Parse(args []string) error {
28-
if cmd.Name == "" {
29-
return errors.New("name is required")
30-
}
31-
if cmd.Flags == nil {
32-
cmd.Flags = flag.NewFlagSet(cmd.Name, flag.ContinueOnError)
33-
}
34-
35-
cmd.Flags.Usage = func() {
36-
fmt.Fprintln(cmd.Flags.Output(), DefaultUsage(cmd))
37-
}
38-
39-
if err := cmd.Flags.Parse(args); err != nil {
40-
return fmt.Errorf("%s: %w", cmd.Name, err)
41-
}
42-
43-
cmd.args = cmd.Flags.Args()
44-
45-
// check for subcommands
46-
if len(cmd.args) > 0 {
47-
arg0 := cmd.args[0]
48-
for _, subcmd := range cmd.Subcommands {
49-
if strings.EqualFold(arg0, subcmd.Name) {
50-
cmd.selected = subcmd
51-
subcmd.parent = cmd
52-
return subcmd.Parse(cmd.args[1:])
53-
}
54-
}
55-
}
56-
57-
// select self if no subcommand was found
58-
cmd.selected = cmd
59-
60-
return nil
61-
}
62-
6326
func (cmd *Command) Run(ctx context.Context) (err error) {
6427
if !cmd.Flags.Parsed() {
6528
return errors.New("not parsed")

internal/command/parse.go

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
package command
2+
3+
import (
4+
"errors"
5+
"flag"
6+
"fmt"
7+
"os"
8+
"strings"
9+
)
10+
11+
type ParseOptions struct {
12+
envVarEnabled bool
13+
envVarPrefix string
14+
}
15+
16+
type ParseOption func(*ParseOptions) error
17+
18+
func WithEnvVars() ParseOption {
19+
return func(po *ParseOptions) error {
20+
po.envVarEnabled = true
21+
return nil
22+
}
23+
}
24+
25+
func WithEnvVarPrefix(prefix string) ParseOption {
26+
return func(po *ParseOptions) error {
27+
po.envVarEnabled = true
28+
po.envVarPrefix = prefix
29+
return nil
30+
}
31+
}
32+
33+
func (cmd *Command) Parse(args []string, options ...ParseOption) error {
34+
if cmd.Name == "" {
35+
return errors.New("name is required")
36+
}
37+
if cmd.Flags == nil {
38+
cmd.Flags = flag.NewFlagSet(cmd.Name, flag.ContinueOnError)
39+
}
40+
41+
cmd.Flags.Usage = func() {
42+
fmt.Fprintln(cmd.Flags.Output(), DefaultUsage(cmd))
43+
}
44+
45+
if err := parse(cmd.Flags, args, options...); err != nil {
46+
return fmt.Errorf("%s: %w", cmd.Name, err)
47+
}
48+
49+
cmd.args = cmd.Flags.Args()
50+
51+
// check for subcommands
52+
if len(cmd.args) > 0 {
53+
for _, subcmd := range cmd.Subcommands {
54+
if strings.EqualFold(args[0], subcmd.Name) {
55+
cmd.selected = subcmd
56+
subcmd.parent = cmd
57+
58+
return subcmd.Parse(cmd.args[1:], options...)
59+
}
60+
}
61+
}
62+
63+
// select self if no subcommand was found
64+
cmd.selected = cmd
65+
66+
return nil
67+
}
68+
69+
func parse(fs *flag.FlagSet, args []string, options ...ParseOption) error {
70+
var opts ParseOptions
71+
for _, option := range options {
72+
if err := option(&opts); err != nil {
73+
return err
74+
}
75+
}
76+
77+
provided := map[string]bool{}
78+
79+
// command-line flags first
80+
{
81+
if err := fs.Parse(args); err != nil {
82+
return fmt.Errorf("parse args: %w", err)
83+
}
84+
85+
// mark set flags as provided
86+
fs.Visit(func(f *flag.Flag) {
87+
provided[f.Name] = true
88+
})
89+
}
90+
91+
// environment variables next
92+
if opts.envVarEnabled {
93+
var visitErr error
94+
fs.VisitAll(func(f *flag.Flag) {
95+
// skip flags already provided
96+
if provided[f.Name] {
97+
return
98+
}
99+
100+
key := getEnvVarKey(f.Name, opts.envVarPrefix)
101+
102+
val := os.Getenv(key)
103+
if val == "" {
104+
return
105+
}
106+
107+
if err := fs.Set(f.Name, val); err != nil {
108+
visitErr = err
109+
}
110+
})
111+
if visitErr != nil {
112+
return fmt.Errorf("parse env: %w", visitErr)
113+
}
114+
115+
// mark set flags as provided
116+
fs.Visit(func(f *flag.Flag) {
117+
provided[f.Name] = true
118+
})
119+
}
120+
121+
return nil
122+
}
123+
124+
func getEnvVarKey(name string, prefix string) string {
125+
replacer := strings.NewReplacer(
126+
"-", "_",
127+
".", "_",
128+
"/", "_",
129+
)
130+
131+
key := strings.ToUpper(name)
132+
key = replacer.Replace(key)
133+
if prefix != "" {
134+
key = strings.ToUpper(prefix) + "_" + key
135+
}
136+
137+
return key
138+
}

0 commit comments

Comments
 (0)