fang.go

  1// Package fang provides styling for cobra commands.
  2package fang
  3
  4import (
  5	"context"
  6	"fmt"
  7	"os"
  8	"runtime/debug"
  9
 10	"github.com/charmbracelet/colorprofile"
 11	"github.com/charmbracelet/lipgloss/v2"
 12	mango "github.com/muesli/mango-cobra"
 13	"github.com/muesli/roff"
 14	"github.com/spf13/cobra"
 15)
 16
 17const shaLen = 7
 18
 19type settings struct {
 20	completions bool
 21	manpages    bool
 22	version     string
 23	commit      string
 24	theme       *ColorScheme
 25}
 26
 27// Option changes fang settings.
 28type Option func(*settings)
 29
 30// WithoutCompletions disables completions.
 31func WithoutCompletions() Option {
 32	return func(s *settings) {
 33		s.completions = false
 34	}
 35}
 36
 37// WithoutManpage disables man pages.
 38func WithoutManpage() Option {
 39	return func(s *settings) {
 40		s.manpages = false
 41	}
 42}
 43
 44// WithTheme sets the colorscheme.
 45func WithTheme(theme ColorScheme) Option {
 46	return func(s *settings) {
 47		s.theme = &theme
 48	}
 49}
 50
 51// WithVersion sets the version.
 52func WithVersion(version string) Option {
 53	return func(s *settings) {
 54		s.version = version
 55	}
 56}
 57
 58// WithCommit sets the commit SHA.
 59func WithCommit(commit string) Option {
 60	return func(s *settings) {
 61		s.commit = commit
 62	}
 63}
 64
 65// Execute applies fang to the command and executes it.
 66func Execute(ctx context.Context, root *cobra.Command, options ...Option) error {
 67	opts := settings{
 68		manpages:    true,
 69		completions: true,
 70	}
 71	for _, option := range options {
 72		option(&opts)
 73	}
 74
 75	if opts.theme == nil {
 76		isDark := lipgloss.HasDarkBackground(os.Stdin, os.Stderr)
 77		t := DefaultTheme(isDark)
 78		opts.theme = &t
 79	}
 80
 81	styles := makeStyles(*opts.theme)
 82
 83	root.SetHelpFunc(func(c *cobra.Command, _ []string) {
 84		w := colorprofile.NewWriter(c.OutOrStdout(), os.Environ())
 85		helpFn(c, w, styles)
 86	})
 87	root.SilenceUsage = true
 88	root.SilenceErrors = true
 89
 90	if opts.manpages {
 91		root.AddCommand(&cobra.Command{
 92			Use:                   "man",
 93			Short:                 "Generates manpages",
 94			SilenceUsage:          true,
 95			DisableFlagsInUseLine: true,
 96			Hidden:                true,
 97			Args:                  cobra.NoArgs,
 98			RunE: func(cmd *cobra.Command, _ []string) error {
 99				page, err := mango.NewManPage(1, cmd.Root())
100				if err != nil {
101					//nolint:wrapcheck
102					return err
103				}
104				_, err = fmt.Fprint(os.Stdout, page.Build(roff.NewDocument()))
105				//nolint:wrapcheck
106				return err
107			},
108		})
109	}
110
111	if opts.completions {
112		root.InitDefaultCompletionCmd()
113	} else {
114		root.CompletionOptions.DisableDefaultCmd = true
115	}
116
117	if opts.version == "" {
118		if info, ok := debug.ReadBuildInfo(); ok && info.Main.Sum != "" {
119			opts.version = info.Main.Version
120			opts.commit = getKey(info, "vcs.revision")
121		} else {
122			opts.version = "unknown (built from source)"
123		}
124	}
125	if len(opts.commit) >= shaLen {
126		opts.version += " (" + opts.commit[:shaLen] + ")"
127	}
128
129	root.Version = opts.version
130
131	if err := root.ExecuteContext(ctx); err != nil {
132		w := colorprofile.NewWriter(root.ErrOrStderr(), os.Environ())
133		writeError(w, styles, err)
134		return err //nolint:wrapcheck
135	}
136	return nil
137}
138
139func getKey(info *debug.BuildInfo, key string) string {
140	if info == nil {
141		return ""
142	}
143	for _, iter := range info.Settings {
144		if iter.Key == key {
145			return iter.Value
146		}
147	}
148	return ""
149}