From b6470abca809b51fb1e51c1b17b12c7380f94b65 Mon Sep 17 00:00:00 2001 From: joobisb Date: Mon, 18 Nov 2024 10:49:25 +0530 Subject: [PATCH] feat: add a field on command to deprecate --- command.go | 12 ++++++++++++ command_test.go | 21 +++++++++++++++++++++ help.go | 20 +++++++++++--------- help.tpl | 5 +++++ 4 files changed, 49 insertions(+), 9 deletions(-) diff --git a/command.go b/command.go index 9cf25ea..115c16a 100644 --- a/command.go +++ b/command.go @@ -43,6 +43,11 @@ type Command struct { // Hidden determines whether the command should be hidden from help. Hidden bool + // Deprecated indicates whether this command is deprecated. + // If empty, the command is not deprecated. + // If set, the value is used as the deprecation message. + Deprecated string `json:"deprecated,omitempty"` + // RawArgs determines whether the command should receive unparsed arguments. // No flags are parsed when set, and the command is responsible for parsing // its own flags. @@ -316,6 +321,13 @@ func (inv *Invocation) CurWords() (prev string, cur string) { // allArgs is wired through the stack so that global flags can be accepted // anywhere in the command invocation. func (inv *Invocation) run(state *runState) error { + if inv.Command.Deprecated != "" { + fmt.Fprintf(inv.Stderr, "%s %q is deprecated!. %s\n", + prettyHeader("warning"), + inv.Command.FullName(), + inv.Command.Deprecated, + ) + } err := inv.Command.Options.ParseEnv(inv.Environ) if err != nil { return xerrors.Errorf("parsing env: %w", err) diff --git a/command_test.go b/command_test.go index 638e4ac..f4242ae 100644 --- a/command_test.go +++ b/command_test.go @@ -376,6 +376,27 @@ func TestCommand(t *testing.T) { err := i.Run() require.NoError(t, err, fio.Stdout.String()) }) + + t.Run("DeprecatedCommand", func(t *testing.T) { + t.Parallel() + + deprecatedCmd := &serpent.Command{ + Use: "deprecated-cmd", + Deprecated: "This command is deprecated and will be removed in the future.", + Handler: func(i *serpent.Invocation) error { + _, _ = i.Stdout.Write([]byte("Running deprecated command")) + return nil + }, + } + + i := deprecatedCmd.Invoke() + io := fakeIO(i) + err := i.Run() + require.NoError(t, err) + expectedWarning := fmt.Sprintf("WARNING: %q is deprecated!. %s\n", deprecatedCmd.Use, deprecatedCmd.Deprecated) + require.Equal(t, io.Stderr.String(), expectedWarning) + require.Contains(t, io.Stdout.String(), "Running deprecated command") + }) } func TestCommand_DeepNest(t *testing.T) { diff --git a/help.go b/help.go index 2ec4201..1753945 100644 --- a/help.go +++ b/help.go @@ -62,14 +62,21 @@ func helpColor(s string) termenv.Color { return helpColorProfile.Color(s) } +// prettyHeader formats a header string with consistent styling. +// It uppercases the text, adds a colon, and applies the header color. +func prettyHeader(s string) string { + headerFg := pretty.FgColor(helpColor("#337CA0")) + s = strings.ToUpper(s) + txt := pretty.String(s, ":") + headerFg.Format(txt) + return txt.String() +} + var defaultHelpTemplate = func() *template.Template { var ( optionFg = pretty.FgColor( helpColor("#04A777"), ) - headerFg = pretty.FgColor( - helpColor("#337CA0"), - ) ) return template.Must( template.New("usage").Funcs( @@ -85,12 +92,7 @@ var defaultHelpTemplate = func() *template.Template { optionFg.Format(txt) return txt.String() }, - "prettyHeader": func(s string) string { - s = strings.ToUpper(s) - txt := pretty.String(s, ":") - headerFg.Format(txt) - return txt.String() - }, + "prettyHeader": prettyHeader, "typeHelper": func(opt *Option) string { switch v := opt.Value.(type) { case *Enum: diff --git a/help.tpl b/help.tpl index bcda2d6..3d4f1c9 100644 --- a/help.tpl +++ b/help.tpl @@ -8,6 +8,11 @@ {{"\n"}} {{- end}} +{{- with .Deprecated }} +{{- indent (printf "DEPRECATED: %s" .) 2 | wrapTTY }} +{{"\n"}} +{{- end }} + {{ with .Aliases }} {{" Aliases: "}} {{- joinStrings .}} {{- end }}