mirror of
https://github.com/docker/cli.git
synced 2026-11-06 13:10:41 -04:00
Allow plugins to return informational messages without the next-steps heading or indentation. Invoke each hook once and render generic messages before the shared next-steps block. Centralize grouping and formatting in PrintMessages, with a callback for template evaluation and error handling. This avoids the need for an intermediate EvaluatedMessage type and simplifies hook collection to return the original responses. Skip messages containing only whitespace while preserving empty lines within non-empty messages. Keep PrintNextSteps as a deprecated wrapper to preserve existing callers and output. Update the printer tests and remove the obsolete appendMessages helper and its tests. Co-authored-by: Nicolas Beck <nicolas.beck@docker.com> Signed-off-by: Nicolas Beck <nicolas.beck@docker.com> Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
106 lines
2.9 KiB
Go
106 lines
2.9 KiB
Go
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
|
//go:build go1.26
|
|
|
|
package hooks
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"fmt"
|
|
"slices"
|
|
"strings"
|
|
"text/template"
|
|
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
const maxMessages = 10
|
|
|
|
func ParseTemplate(hookTemplate string, cmd *cobra.Command) ([]string, error) {
|
|
out := hookTemplate
|
|
if strings.Contains(hookTemplate, "{{") {
|
|
// Message may be a template.
|
|
msgContext := commandInfo{cmd: cmd}
|
|
|
|
tmpl, err := template.New("").Funcs(template.FuncMap{
|
|
"command": msgContext.command,
|
|
"flagValue": msgContext.flagValue,
|
|
"argValue": msgContext.argValue,
|
|
|
|
// kept for backward-compatibility with old templates.
|
|
"flag": func(_ any, flagName string) (string, error) { return msgContext.flagValue(flagName) },
|
|
"arg": func(_ any, i int) (string, error) { return msgContext.argValue(i) },
|
|
}).Parse(hookTemplate)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var b bytes.Buffer
|
|
err = tmpl.Execute(&b, msgContext)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
out = b.String()
|
|
}
|
|
if n := strings.Count(out, "\n"); n > maxMessages {
|
|
return nil, fmt.Errorf("hook template contains too many messages (%d): maximum is %d", n, maxMessages)
|
|
}
|
|
lines := strings.SplitN(out, "\n", maxMessages)
|
|
|
|
// Skip messages containing only whitespace, but preserve empty lines
|
|
// in messages that have content (e.g., for paragraph spacing).
|
|
if !slices.ContainsFunc(lines, func(line string) bool {
|
|
return strings.TrimSpace(line) != ""
|
|
}) {
|
|
return nil, nil
|
|
}
|
|
return lines, nil
|
|
}
|
|
|
|
var ErrHookTemplateParse = errors.New("failed to parse hook template")
|
|
|
|
// commandInfo provides info about the command for which the hook was invoked.
|
|
// It is used for templated hook-messages.
|
|
type commandInfo struct {
|
|
cmd *cobra.Command
|
|
}
|
|
|
|
// Name returns the name of the (sub)command for which the hook was invoked.
|
|
//
|
|
// It's used for backward-compatibility with old templates.
|
|
func (c commandInfo) Name() string {
|
|
return c.command()
|
|
}
|
|
|
|
// command returns the name of the (sub)command for which the hook was invoked.
|
|
func (c commandInfo) command() string {
|
|
if c.cmd == nil {
|
|
return ""
|
|
}
|
|
return c.cmd.Name()
|
|
}
|
|
|
|
// flagValue returns the value that was set for the given flag when the hook was invoked.
|
|
func (c commandInfo) flagValue(flagName string) (string, error) {
|
|
if c.cmd == nil {
|
|
return "", fmt.Errorf("%w: flagValue: cmd is nil", ErrHookTemplateParse)
|
|
}
|
|
f := c.cmd.Flag(flagName)
|
|
if f == nil {
|
|
return "", fmt.Errorf("%w: flagValue: no flags found", ErrHookTemplateParse)
|
|
}
|
|
return f.Value.String(), nil
|
|
}
|
|
|
|
// argValue returns the value of the nth argument.
|
|
func (c commandInfo) argValue(n int) (string, error) {
|
|
if c.cmd == nil {
|
|
return "", fmt.Errorf("%w: arg: cmd is nil", ErrHookTemplateParse)
|
|
}
|
|
flags := c.cmd.Flags()
|
|
v := flags.Arg(n)
|
|
if v == "" && n >= flags.NArg() {
|
|
return "", fmt.Errorf("%w: arg: %dth argument not set", ErrHookTemplateParse, n)
|
|
}
|
|
return v, nil
|
|
}
|