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}