Skip to content

Commit 8be32de

Browse files
authored
Merge pull request #277 from hugoh/flags
fix(cli): replace urfave/cli with stdlib flag
2 parents 8b74cc9 + a2e2084 commit 8be32de

4 files changed

Lines changed: 76 additions & 61 deletions

File tree

go.mod

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ require (
66
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
77
github.com/pelletier/go-toml/v2 v2.3.1
88
github.com/stretchr/testify v1.11.1
9-
github.com/urfave/cli/v3 v3.9.0
109
go.uber.org/goleak v1.3.0
1110
)
1211

go.sum

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,6 @@ github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0t
2020
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
2121
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
2222
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
23-
github.com/urfave/cli/v3 v3.9.0 h1:AV9lIiPv3ukYnxunaCUsHnEozptYmDN2F0+yWqLMn/c=
24-
github.com/urfave/cli/v3 v3.9.0/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso=
2523
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
2624
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
2725
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

internal/cmd.go

Lines changed: 70 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ package internal
66

77
import (
88
"context"
9+
"errors"
10+
"flag"
911
"fmt"
1012
"os"
1113
"os/signal"
@@ -15,7 +17,6 @@ import (
1517
"github.com/hugoh/upd/internal/logger"
1618
"github.com/hugoh/upd/internal/logic"
1719
"github.com/hugoh/upd/internal/version"
18-
"github.com/urfave/cli/v3"
1920
)
2021

2122
const (
@@ -36,6 +37,12 @@ const (
3637
ConfigDebug string = "debug"
3738
)
3839

40+
// Flags holds the parsed command-line flags.
41+
type Flags struct {
42+
ConfigPath string
43+
Debug bool
44+
}
45+
3946
// SetupLoop initializes the loop with configuration from the given file.
4047
func SetupLoop(loop *logic.Loop, configPath string) (*config.Configuration, error) {
4148
newConf, err := config.ReadConf(configPath)
@@ -60,8 +67,8 @@ func SetupLoop(loop *logic.Loop, configPath string) (*config.Configuration, erro
6067
}
6168

6269
// Run is the main application entry point handling signals and configuration reload.
63-
func Run(appCtx context.Context, cmd *cli.Command) error {
64-
logger.LogSetup(cmd.Bool(ConfigDebug))
70+
func Run(appCtx context.Context, flags Flags) error {
71+
logger.LogSetup(flags.Debug)
6572

6673
rootCtx, stopSignalHandlers := signal.NotifyContext(appCtx, syscall.SIGINT, syscall.SIGTERM)
6774
defer stopSignalHandlers()
@@ -88,7 +95,7 @@ func Run(appCtx context.Context, cmd *cli.Command) error {
8895
go func(ctx context.Context) {
8996
defer close(done)
9097

91-
newConf, err := SetupLoop(loop, cmd.String(ConfigConfig))
98+
newConf, err := SetupLoop(loop, flags.ConfigPath)
9299
if err != nil {
93100
errCh <- fmt.Errorf("cannot configure app: %w", err)
94101

@@ -157,34 +164,70 @@ func waitForWorker(
157164
}
158165
}
159166

160-
// Cmd creates and runs the CLI application.
161-
func Cmd() error {
162-
flags := []cli.Flag{
163-
&cli.StringFlag{
164-
Name: ConfigConfig,
165-
Aliases: []string{"c"},
166-
Usage: "use the specified TOML configuration file",
167-
Value: config.DefaultConfig,
168-
TakesFile: true,
169-
},
170-
&cli.BoolFlag{
171-
Name: ConfigDebug,
172-
Aliases: []string{"d"},
173-
Value: false,
174-
Usage: "display debugging output in the console",
175-
},
167+
// ParseFlags parses the given command-line arguments (excluding the program
168+
// name) into Flags. It returns flag.ErrHelp when --help or --version was
169+
// requested and already handled, in which case the caller should exit
170+
// without running the app.
171+
func ParseFlags(args []string) (Flags, error) {
172+
var (
173+
flags Flags
174+
showVersion bool
175+
)
176+
177+
flagSet := flag.NewFlagSet(AppName, flag.ContinueOnError)
178+
flagSet.Usage = func() {
179+
usage := fmt.Sprintf(
180+
"%s - %s\n\nUsage:\n %s [flags]\n\nFlags:\n",
181+
AppName,
182+
AppShort,
183+
AppName,
184+
)
185+
if _, err := fmt.Fprint(flagSet.Output(), usage); err != nil {
186+
return
187+
}
188+
189+
flagSet.PrintDefaults()
190+
}
191+
192+
flagSet.StringVar(
193+
&flags.ConfigPath,
194+
ConfigConfig,
195+
config.DefaultConfig,
196+
"use the specified TOML configuration file",
197+
)
198+
flagSet.StringVar(&flags.ConfigPath, "c", config.DefaultConfig, "shorthand for --"+ConfigConfig)
199+
flagSet.BoolVar(&flags.Debug, ConfigDebug, false, "display debugging output in the console")
200+
flagSet.BoolVar(&flags.Debug, "d", false, "shorthand for --"+ConfigDebug)
201+
flagSet.BoolVar(&showVersion, "version", false, "print the version and exit")
202+
203+
if err := flagSet.Parse(args); err != nil {
204+
return Flags{}, fmt.Errorf("failed to parse flags: %w", err)
176205
}
177206

178-
app := &cli.Command{
179-
Name: AppName,
180-
Usage: AppShort,
181-
Version: version.Version(),
182-
Flags: flags,
183-
Action: Run,
207+
if showVersion {
208+
versionLine := fmt.Sprintf("%s version %s\n", AppName, version.Version())
209+
if _, err := fmt.Fprint(os.Stdout, versionLine); err != nil {
210+
return Flags{}, fmt.Errorf("failed to print version: %w", err)
211+
}
212+
213+
return Flags{}, flag.ErrHelp
184214
}
185215

186-
err := app.Run(context.Background(), os.Args)
216+
return flags, nil
217+
}
218+
219+
// Cmd creates and runs the CLI application.
220+
func Cmd() error {
221+
flags, err := ParseFlags(os.Args[1:])
187222
if err != nil {
223+
if errors.Is(err, flag.ErrHelp) {
224+
return nil
225+
}
226+
227+
return err
228+
}
229+
230+
if err := Run(context.Background(), flags); err != nil {
188231
return fmt.Errorf("failed to run app: %w", err)
189232
}
190233

internal/cmd_test.go

Lines changed: 6 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import (
1212
"github.com/hugoh/upd/internal/logic"
1313
"github.com/stretchr/testify/assert"
1414
"github.com/stretchr/testify/require"
15-
"github.com/urfave/cli/v3"
1615
)
1716

1817
// Must match internal/config/config_test.go.
@@ -22,16 +21,8 @@ func TestRun_NoMultipleRestartsOnSuccess(t *testing.T) {
2221
ctx, cancel := context.WithTimeout(t.Context(), 500*time.Millisecond)
2322
defer cancel()
2423

25-
cmd := &cli.Command{
26-
Flags: []cli.Flag{
27-
&cli.StringFlag{
28-
Name: ConfigConfig,
29-
Value: testConfigDir + "/upd_test_minimal.toml",
30-
},
31-
&cli.BoolFlag{
32-
Name: ConfigDebug,
33-
},
34-
},
24+
cmd := Flags{
25+
ConfigPath: testConfigDir + "/upd_test_minimal.toml",
3526
}
3627

3728
startTime := time.Now()
@@ -44,16 +35,8 @@ func TestRun_NoMultipleRestartsOnSuccess(t *testing.T) {
4435
func TestRun_WaitsForWorkerCompletion(t *testing.T) {
4536
ctx, cancel := context.WithCancel(t.Context())
4637

47-
cmd := &cli.Command{
48-
Flags: []cli.Flag{
49-
&cli.StringFlag{
50-
Name: ConfigConfig,
51-
Value: testConfigDir + "/upd_test_minimal.toml",
52-
},
53-
&cli.BoolFlag{
54-
Name: ConfigDebug,
55-
},
56-
},
38+
cmd := Flags{
39+
ConfigPath: testConfigDir + "/upd_test_minimal.toml",
5740
}
5841

5942
done := make(chan struct{})
@@ -87,16 +70,8 @@ func TestRun_SighupReloadsConfig(t *testing.T) {
8770
ctx, cancel := context.WithCancel(t.Context())
8871
defer cancel()
8972

90-
cmd := &cli.Command{
91-
Flags: []cli.Flag{
92-
&cli.StringFlag{
93-
Name: ConfigConfig,
94-
Value: cfgPath,
95-
},
96-
&cli.BoolFlag{
97-
Name: ConfigDebug,
98-
},
99-
},
73+
cmd := Flags{
74+
ConfigPath: cfgPath,
10075
}
10176

10277
errResult := make(chan error, 1)

0 commit comments

Comments
 (0)