package ansi
// Attribute is a unit type which represents individual ANSI formatting attributes.
type Attribute interface {
Display
isAttribute()
}
const (
// Reset is an ANSI attribute for resetting formatting
Reset attribute = 0
// Bold is an ANSI attribute for setting a bold format
Bold attribute = 1
// Faint is an ANSI attribute for setting a faint format
Faint attribute = 2
// Italice is an ANSI attribute for setting an italic format
Italic attribute = 3
// Underline is an ANSI attribute for setting an underline format
Underline attribute = 4
// DefaultFont is an ANSI attribute for setting the default font
DefaultFont attribute = 10
// FGBlack is an ANSI attribute for the foreground color black
FGBlack attribute = 30
// FGRed is an ANSI attribute for the foreground color red
FGRed attribute = 31
// FGGreen is an ANSI attribute for the foreground color green
FGGreen attribute = 32
// FGYellow is an ANSI attribute for the foreground color yellow
FGYellow attribute = 33
// FGBlue is an ANSI attribute for the foreground color blue
FGBlue attribute = 34
// FGMagenta is an ANSI attribute for the foreground color magenta
FGMagenta attribute = 35
// FGCyan is an ANSI attribute for the foreground color cyan
FGCyan attribute = 36
// FGWhite is an ANSI attribute for the foreground color white
FGWhite attribute = 37
// FGGray is an ANSI attribute for the foreground color gray
FGGray attribute = 90
// FGBrightRed is an ANSI attribute for the foreground color brightred
FGBrightRed attribute = 91
// FGBrightGreen is an ANSI attribute for the foreground color brightgreen
FGBrightGreen attribute = 92
// FGBrightYellow is an ANSI attribute for the foreground color brightyellow
FGBrightYellow attribute = 93
// FGBrightBlue is an ANSI attribute for the foreground color brightblue
FGBrightBlue attribute = 94
// FGBrightMagenta is an ANSI attribute for the foreground color brightmagenta
FGBrightMagenta attribute = 95
// FGBrightCyan is an ANSI attribute for the foreground color brightcyan
FGBrightCyan attribute = 96
// FGBrightWhite is an ANSI attribute for the foreground color brightwhite
FGBrightWhite attribute = 97
// FGDefault is an ANSI attribute for setting the default foreground color
FGDefault attribute = 39
// BGBlack is an ANSI attribute for the background color black
BGBlack attribute = 40
// BGRed is an ANSI attribute for the background color red
BGRed attribute = 41
// BGGreen is an ANSI attribute for the background color green
BGGreen attribute = 42
// BGYellow is an ANSI attribute for the background color yellow
BGYellow attribute = 43
// BGBlue is an ANSI attribute for the background color blue
BGBlue attribute = 44
// BGMagenta is an ANSI attribute for the background color magenta
BGMagenta attribute = 45
// BGCyan is an ANSI attribute for the background color cyan
BGCyan attribute = 46
// BGWhite is an ANSI attribute for the background color white
BGWhite attribute = 47
// BGGray is an ANSI attribute for the background color gray
BGGray attribute = 100
// BGBrightRed is an ANSI attribute for the background color brightred
BGBrightRed attribute = 101
// BGBrightGreen is an ANSI attribute for the background color brightgreen
BGBrightGreen attribute = 102
// BGBrightYellow is an ANSI attribute for the background color brightyellow
BGBrightYellow attribute = 103
// BGBrightBlue is an ANSI attribute for the background color brightblue
BGBrightBlue attribute = 104
// BGBrightMagenta is an ANSI attribute for the background color brightmagenta
BGBrightMagenta attribute = 105
// BGBrightCyan is an ANSI attribute for the background color brightcyan
BGBrightCyan attribute = 106
// BGBrightWhite is an ANSI attribute for the background color brightwhite
BGBrightWhite attribute = 107
// BGDefault is an ANSI attribute for setting the default background color
BGDefault attribute = 49
)
// attribute represents an ANSI attribute formatting
type attribute uint8
func (a attribute) Format(format string, args ...any) string {
return Format(a).Format(format, args...)
}
func (a attribute) String() string {
return createFormatFunc(byte(a))
}
func (a attribute) FormatString() string {
return ansiFormat(byte(a))
}
func (a attribute) codes() []byte {
if !enabled {
return nil
}
return []byte{byte(a)}
}
func (a attribute) len() int {
return 1
}
func (c attribute) isAttribute() {
}
var _ Display = (*attribute)(nil)
var _ Attribute = (*attribute)(nil)
package ansi
import (
"io"
"golang.org/x/term"
)
// ColorWriter creates an io.Writer that forces writing of colors, even if it's
// disabled or not currently allowed.
func ColorWriter(w io.Writer) io.Writer {
if fd, ok := w.(fdWriter); ok && enabled && term.IsTerminal(fd.Fd()) {
return fdColorWriter{fd}
}
return colorWriter{w}
}
type noColorWriter struct {
w io.Writer
}
func (w *noColorWriter) Write(p []byte) (n int, err error) {
return w.w.Write(p)
}
func (w *noColorWriter) Close() error {
if c, ok := w.w.(io.Closer); ok {
return c.Close()
}
return nil
}
// NoColorWriter creates an io.Writer that forces writing of colors to be disabled.
func NoColorWriter(w io.Writer) io.Writer {
return &noColorWriter{w}
}
// IsColorable checks whether the specified Writer is a colorable output destination.
//
// This will return true either if the Writer is a TTY with colors enabled, or
// if the writer is an explicit colorable writer.
func IsColorable(w io.Writer) bool {
if _, ok := w.(interface{ alwaysColor() }); ok {
return true
}
if fd, ok := w.(interface{ Fd() uintptr }); ok && enabled && term.IsTerminal(int(fd.Fd())) {
return true
}
return false
}
type colorWriter struct {
io.Writer
}
func (colorWriter) alwaysColor() {
}
type fdWriter interface {
Fd() int
io.Writer
}
type fdColorWriter struct {
fdWriter
}
func (w fdColorWriter) alwaysColor() {
}
package ansi
import (
"fmt"
"os"
"regexp"
"strconv"
"strings"
)
var (
// enabled is used to detect if
enabled bool = true
createFormatFunc func(...byte) string
formatFunc func(string, ...any) string
stripCodes *regexp.Regexp = regexp.MustCompile("\033\\[([0-9;]+)m")
)
func noFormat(_ ...byte) string {
return ""
}
func ansiFormat(codes ...byte) string {
sb := strings.Builder{}
sb.WriteString(ControlSequenceIntroducer)
for _, f := range codes[:len(codes)-1] {
sb.WriteString(strconv.Itoa(int(f)))
sb.WriteRune(Separator)
}
sb.WriteString(strconv.Itoa(int(codes[len(codes)-1])))
sb.WriteRune(SGRSuffix)
return sb.String()
}
func stripSprintf(format string, args ...any) string {
content := fmt.Sprintf(format, args...)
if !strings.ContainsRune(format, ControlCode) {
return content
}
return string(stripCodes.ReplaceAll([]byte(format), nil))
}
func init() {
enabled = true
createFormatFunc = ansiFormat
formatFunc = fmt.Sprintf
unsetIf("NOCOLOR")
unsetIf("NO_COLOR")
}
func unsetIf(key string) {
if got, ok := os.LookupEnv(key); ok {
if got, err := strconv.ParseBool(got); err == nil && got {
createFormatFunc = noFormat
formatFunc = stripSprintf
enabled = false
}
}
}
package ansi
import (
"fmt"
"io"
"os"
"reflect"
)
// Fsprintf behaves like fmt.Sprintf, except that it will conditionally support
// color formatting depending on whether the Writer is colorable.
func Fsprintf(w io.Writer, format string, args ...any) string {
return fmt.Sprintf(format, formatted(w, args)...)
}
// Fprint behaves like fmt.Fprint, except that it will conditionally support
// color formatting depending on whether the Writer is colorable.
func Fprint(w io.Writer, args ...any) (int, error) {
return fmt.Fprint(w, formatted(w, args)...)
}
// Fprintln behaves like fmt.Fprintln, except that it will conditionally support
// color formatting depending on whether the Writer is colorable.
func Fprintln(w io.Writer, args ...any) (int, error) {
return fmt.Fprintln(w, formatted(w, args)...)
}
// Fprintf behaves like fmt.Fprintf, except that it will conditionally support
// color formatting depending on whether the Writer is colorable.
func Fprintf(w io.Writer, format string, args ...any) (int, error) {
return fmt.Fprintf(w, format, formatted(w, args)...)
}
// print behaves like fmt.Print, except that it will conditionally support
// color formatting depending if os.Stdout is colorable.
func Print(args ...any) (int, error) {
return Fprint(os.Stdout, args...)
}
// Println behaves like fmt.Println, except that it will conditionally support
// color formatting depending if os.Stdout is colorable.
func Println(args ...any) (int, error) {
return Fprintln(os.Stdout, args...)
}
// Printf behaves like fmt.Printf, except that it will conditionally support
// color formatting depending if os.Stdout is colorable.
func Printf(format string, args ...any) (int, error) {
return Fprintf(os.Stdout, format, args...)
}
// Eprint behaves like fmt.Print, except that it will conditionally support
// color formatting depending if os.Stderr is colorable.
func Eprint(args ...any) (int, error) {
return Fprint(os.Stderr, args...)
}
// Eprintln behaves like fmt.Println, except that it will conditionally support
// color formatting depending if os.Stderr is colorable.
func Eprintln(args ...any) (int, error) {
return Fprintln(os.Stderr, args...)
}
// Eprintf behaves like fmt.Printf, except that it will conditionally support
// color formatting depending if os.Stderr is colorable.
func Eprintf(format string, args ...any) (int, error) {
return Fprintf(os.Stderr, format, args...)
}
// Sprintf behaves exactly like fmt.Sprintf, except it will disable color codes
// if NOCOLOR or NO_COLOR are set.
//
// Note: ansi.Sprintf cannot obey explicit colored writers since it coalesces
// down to a single string type. If NOCOLOR is defined, then Sprintf behaves
// identically to fmt.Sprintf.
func Sprintf(format string, args ...any) string {
return formatFunc(format, args...)
}
func formatted(w io.Writer, args []any) []any {
formatter := selectFormatter(w)
newArgs := make([]any, 0, len(args))
for _, arg := range args {
newArgs = append(newArgs, formatter(arg))
}
return newArgs
}
func selectFormatter(w io.Writer) func(any) any {
if _, ok := w.(interface{ alwaysColor() }); ok {
return alwaysFormat
}
if IsColorable(w) {
return defaultFormat
}
return neverFormat
}
func alwaysFormat(one any) any {
if formatter, ok := one.(interface{ formatString() string }); ok {
return formatter.formatString()
}
return one
}
func defaultFormat(one any) any {
return one
}
func neverFormat(one any) any {
if _, ok := one.(interface{ formatString() string }); ok {
return ""
}
if rt := reflect.TypeOf(one); rt.Kind() == reflect.String {
bytes := fmt.Append(nil, one)
rv := reflect.New(rt)
stripped := string(stripCodes.ReplaceAll(bytes, nil))
rv.Elem().SetString(stripped)
return rv.Elem().Interface()
}
return one
}
// StripFormat will remove all ANSI escape codes from the input format string.
func StripFormat(format string) string {
return string(stripCodes.ReplaceAll([]byte(format), nil))
}
package ansi
import (
"fmt"
"strings"
)
const (
// ControlSequenceIntroducer is the prefix for ANSI escape commands.
ControlSequenceIntroducer = "\033["
// ControlCode is a rune representing the ANSI control code (0x1B, or 033).
ControlCode rune = 033
// Separator is the separator between different control codes.
Separator = ';'
// SGR is the suffix code for the Select Graphics Renderer control codes.
SGRSuffix = 'm'
)
// Format is a collection of ANSI displayable formatting attributes.
func Format(displays ...Display) Display {
return format(displays)
}
type format []Display
// Format the input format string as Sprintf would, but wrap it in an ANSI
// format and reset pair.
//
// If color is disabled, this will return only the formatted string
// without any ANSI control codes.
func (f format) Format(format string, args ...any) string {
prefix := f.String()
suffix := Reset.String()
content := formatFunc(format, args...)
if len(prefix) == 0 && len(suffix) == 0 {
return content
}
sb := strings.Builder{}
sb.Grow(len(prefix) + len(suffix) + len(content))
sb.WriteString(prefix)
sb.WriteString(content)
sb.WriteString(suffix)
return sb.String()
}
var _ Formatter = (*format)(nil)
// String implements fmt.Stringer.
//
// If color is disabled, this will return an empty string.
func (f format) String() string {
if len(f) == 0 {
return ""
}
return f.formatString()
}
var _ fmt.Stringer = (*format)(nil)
// FormatString returns this formatted string with escape codes, regardless
// of whether color is enabled.
func (f format) FormatString() string {
return f.formatString()
}
func (f format) formatString() string {
if len(f) == 0 {
return ""
}
numBytes := 0
for _, format := range f {
numBytes += format.len()
}
if numBytes == 0 {
return ""
}
codes := f.codes()
return createFormatFunc(codes...)
}
func (f format) codes() []byte {
if !enabled {
return nil
}
length := 0
for _, display := range f {
length += display.len()
}
codes := make([]byte, 0, length)
for _, display := range f {
codes = append(codes, display.codes()...)
}
return codes
}
func (f format) len() int {
return len(f)
}
var _ Display = (*format)(nil)
package cli
import (
_ "embed"
"fmt"
"io"
"os"
"path/filepath"
"runtime/debug"
"strings"
"sync/atomic"
"text/template"
"github.com/bitwizeshift/protobuild/internal/ansi"
"github.com/spf13/cobra"
)
var (
appName atomic.Pointer[string]
)
func init() {
name := new(string)
*name = filepath.Base(defaultAppName())
appName.Store(name)
}
func defaultAppName() string {
if len(os.Args[0]) == 0 {
return "cli"
}
return filepath.Base(os.Args[0])
}
func AppName() string {
name := appName.Load()
return *name
}
func SetAppName(name string) {
appName.Store(&name)
}
func ErrorPrefix() string {
return ansi.Sprintf("%serror: %s%s:%s",
ansi.FGRed,
ansi.FGWhite,
AppName(),
ansi.Reset,
)
}
func WarningPrefix() string {
return ansi.Sprintf("%swarning:%s",
ansi.FGYellow,
ansi.Reset,
)
}
func NoticePrefix() string {
return ansi.Sprintf("%snotice:%s",
ansi.FGCyan,
ansi.Reset,
)
}
var (
//go:embed usage.template
usageTemplate string
//go:embed help.template
helpTemplate string
//go:embed version.template
versionTemplate string
//go:embed panic.template
panicTemplate string
)
func SetDefaults(cmd *cobra.Command) {
cmd.SetErrPrefix(ErrorPrefix())
cmd.SetHelpTemplate(helpTemplate)
cmd.SetUsageTemplate(usageTemplate)
cmd.SetVersionTemplate(versionTemplate)
}
type payload struct {
Error string
StackTrace []string
}
// HandlePanic is a function that can be deferred to provide a cleaner
// panic handler. This makes use of the `panic.template` file.
func HandlePanic() {
if r := recover(); r != nil {
tracePanic(os.Stderr, r)
os.Exit(2)
}
}
func tracePanic(wr io.Writer, r any) {
template := template.Must(template.New("panic").Funcs(funcs).Parse(panicTemplate))
stack := string(debug.Stack())
payload := &payload{
Error: fmt.Sprintf("%v", r),
StackTrace: strings.Split(stack, "\n"),
}
if err := template.Execute(wr, payload); err != nil {
panic(err)
}
os.Exit(1)
}
package flagset
import (
"sync"
"github.com/spf13/cobra"
)
var (
commands map[*cobra.Command]*entry
lock sync.Mutex
)
func init() {
commands = make(map[*cobra.Command]*entry)
}
type entry struct {
current int
order map[*FlagSet]int
flagsets []*FlagSet
}
// RegisterFlags registers the flagset with the specified command.
func (fs *FlagSet) RegisterFlags(cmd *cobra.Command) {
cmd.Flags().AddFlagSet(fs.FlagSet)
fs.register(cmd)
}
// RegisterLocalFlags registers the flagset with the specified command and
// only that command.
func (fs *FlagSet) RegisterLocalFlags(cmd *cobra.Command) {
cmd.LocalFlags().AddFlagSet(fs.FlagSet)
fs.register(cmd)
}
// RegisterPersistentFlags registers the flagset with the specified command and
// all subcommands.
//
// Note: for this function to take effect correctly, the parent command must
// already have the sub-commands registered.
func (fs *FlagSet) RegisterPersistentFlags(cmd *cobra.Command) {
cmd.PersistentFlags().AddFlagSet(fs.FlagSet)
fs.registerPersistent(cmd)
}
// CommandFlagSets returns the flagsets registered for the specified command.
func CommandFlagSets(cmd *cobra.Command) []*FlagSet {
lock.Lock()
defer lock.Unlock()
if e, ok := commands[cmd]; ok {
flagsets := make([]*FlagSet, len(e.flagsets))
for fs, i := range e.order {
flagsets[i] = fs
}
return flagsets
}
return nil
}
func (fs *FlagSet) register(cmd *cobra.Command) {
lock.Lock()
defer lock.Unlock()
fs.unlockedRegister(cmd)
}
func (fs *FlagSet) registerPersistent(cmd *cobra.Command) {
lock.Lock()
defer lock.Unlock()
fs.registerPersistentAux(cmd)
}
func (fs *FlagSet) registerPersistentAux(cmd *cobra.Command) {
fs.unlockedRegister(cmd)
for _, cmd := range cmd.Commands() {
fs.registerPersistentAux(cmd)
}
}
func (fs *FlagSet) unlockedRegister(cmd *cobra.Command) {
if e, ok := commands[cmd]; ok {
e.flagsets = append(e.flagsets, fs)
e.order[fs] = e.current
e.current++
return
}
commands[cmd] = &entry{
current: 1,
order: map[*FlagSet]int{fs: 0},
flagsets: []*FlagSet{fs},
}
}
package flagset
import (
"bytes"
"fmt"
"strings"
"github.com/bitwizeshift/protobuild/internal/ansi"
"github.com/spf13/pflag"
)
// FlagSet is a wrapper around pflag.FlagSet that provides additional
// functionality for formatting flag usage, and encoding the proper name in a way
// that can be retrieved later.
type FlagSet struct {
name string
*pflag.FlagSet
}
// New creates a new FlagSet with the given name.
func New(name string) *FlagSet {
return &FlagSet{
name: name,
FlagSet: pflag.NewFlagSet(name, pflag.ExitOnError),
}
}
// Name returns the name of the FlagSet.
func (f *FlagSet) Name() string {
return f.name
}
// FlagUsages returns a string containing the usage information for the flags
// in the FlagSet.
func (f *FlagSet) FlagUsages() string {
return f.FormattedFlagUsages(nil)
}
// FormatOptions is used to configure the formatting of the flag usage.
type FormatOptions struct {
ArgFormat ansi.Display
FlagFormat ansi.Display
DeprecatedFormat ansi.Display
}
func (fo *FormatOptions) argFormat() ansi.Display {
if fo == nil || fo.ArgFormat == nil {
return ansi.None
}
return fo.ArgFormat
}
func (fo *FormatOptions) flagFormat() ansi.Display {
if fo == nil || fo.FlagFormat == nil {
return ansi.None
}
return fo.FlagFormat
}
func (fo *FormatOptions) deprecatedFormat() ansi.Display {
if fo == nil || fo.FlagFormat == nil {
return ansi.None
}
return fo.DeprecatedFormat
}
// FormattedFlagUsages returns a string containing the usage information for
// the flags in the FlagSet, formatted according to the provided options.
func (f *FlagSet) FormattedFlagUsages(opts *FormatOptions) string {
buf := new(bytes.Buffer)
var lines []string
maxlen := 0
f.VisitAll(func(flag *pflag.Flag) {
if flag.Hidden {
return
}
line := ""
if flag.Shorthand != "" && flag.ShorthandDeprecated == "" {
line = opts.flagFormat().Format(" -%s, --%s", flag.Shorthand, flag.Name)
} else {
line = opts.flagFormat().Format(" --%s", flag.Name)
}
varname, usage := pflag.UnquoteUsage(flag)
if varname != "" {
line += " " + opts.argFormat().Format(varname)
}
// This special character will be replaced with spacing once the
// correct alignment is calculated
line += "\x00"
maxlen = max(maxlen, len(ansi.StripFormat(line)))
line += usage
if len(flag.Deprecated) != 0 {
line += opts.deprecatedFormat().Format(" (DEPRECATED: %s)", flag.Deprecated)
}
lines = append(lines, line)
})
for _, line := range lines {
sidx := strings.Index(ansi.StripFormat(line), "\x00")
sidx2 := strings.Index(line, "\x00")
spacing := strings.Repeat(" ", maxlen-sidx)
// maxlen + 2 comes from + 1 for the \x00 and + 1 for the (deliberate) off-by-one in maxlen-sidx
fmt.Fprintln(buf, line[:sidx2], spacing, wrap(maxlen+2, 0, line[sidx2+1:]))
}
return buf.String()
}
// Wraps the string `s` to a maximum width `w` with leading indent
// `i`. The first line is not indented (this is assumed to be done by
// caller). Pass `w` == 0 to do no wrapping
func wrap(i, w int, s string) string {
if w == 0 {
return strings.Replace(s, "\n", "\n"+strings.Repeat(" ", i), -1)
}
// space between indent i and end of line width w into which
// we should wrap the text.
wrap := w - i
var r, l string
// Not enough space for sensible wrapping. Wrap as a block on
// the next line instead.
if wrap < 24 {
i = 16
wrap = w - i
r += "\n" + strings.Repeat(" ", i)
}
// If still not enough space then don't even try to wrap.
if wrap < 24 {
return strings.Replace(s, "\n", r, -1)
}
// Try to avoid short orphan words on the final line, by
// allowing wrapN to go a bit over if that would fit in the
// remainder of the line.
slop := 5
wrap = wrap - slop
// Handle first line, which is indented by the caller (or the
// special case above)
l, s = wrapN(wrap, slop, s)
r = r + strings.Replace(l, "\n", "\n"+strings.Repeat(" ", i), -1)
// Now wrap the rest
for s != "" {
var t string
t, s = wrapN(wrap, slop, s)
r = r + "\n" + strings.Repeat(" ", i) + strings.Replace(t, "\n", "\n"+strings.Repeat(" ", i), -1)
}
return r
}
func wrapN(i, slop int, s string) (string, string) {
if i+slop > len(s) {
return s, ""
}
w := strings.LastIndexAny(s[:i], " \t\n")
if w <= 0 {
return s, ""
}
nlPos := strings.LastIndex(s[:i], "\n")
if nlPos > 0 && nlPos < w {
return s[:nlPos], s[nlPos+1:]
}
return s[:w], s[w+1:]
}
package cli
import (
"os"
"github.com/bitwizeshift/protobuild/internal/ansi"
)
func Error(args ...any) {
prefix := ErrorPrefix() + " "
args = append([]any{prefix}, args...)
args = append(args, "\n")
ansi.Fprint(os.Stderr, args...)
}
func Errorf(format string, args ...any) {
Error(ansi.Sprintf(format, args...))
}
func Warning(args ...any) {
prefix := WarningPrefix() + " "
args = append([]any{prefix}, args...)
args = append(args, "\n")
ansi.Fprint(os.Stderr, args...)
}
func Warningf(format string, args ...any) {
Warning(ansi.Sprintf(format, args...))
}
func Notice(args ...any) {
prefix := NoticePrefix() + " "
args = append([]any{prefix}, args...)
args = append(args, "\n")
ansi.Fprint(os.Stderr, args...)
}
func Noticef(format string, args ...any) {
Notice(ansi.Sprintf(format, args...))
}
func Fatal(args ...any) {
Error(args...)
os.Exit(1)
}
func Fatalf(format string, args ...any) {
Errorf(format, args...)
os.Exit(1)
}
package cli
import (
"cmp"
"fmt"
"go/build"
"runtime"
"runtime/debug"
"slices"
"strings"
"text/template"
"github.com/bitwizeshift/protobuild/internal/ansi"
"github.com/bitwizeshift/protobuild/internal/cli/flagset"
"github.com/spf13/cobra"
"golang.org/x/term"
)
var (
FormatLink = ansi.Format(ansi.Underline, ansi.FGWhite)
FormatHeading = ansi.FGYellow
FormatFlag = ansi.FGCyan
FormatArg = ansi.Format(ansi.Bold, ansi.FGWhite)
FormatCommand = ansi.FGGreen
FormatKeyword = ansi.FGCyan
FormatStrong = ansi.Bold
FormatCall = ansi.Format(ansi.FGWhite, ansi.Bold)
FormatQuote = ansi.FGGray
FormatError = ansi.FGRed
FormatWarning = ansi.FGYellow
FormatInfo = ansi.FGBlue
funcs = template.FuncMap{
"AppName": AppName,
"FormatLink": format(FormatLink),
"FormatHeading": format(FormatHeading),
"FormatFlag": format(FormatFlag),
"FormatArg": format(FormatArg),
"FormatCommand": format(FormatCommand),
"FormatKeyword": format(FormatKeyword),
"FormatStrong": format(FormatStrong),
"FormatCall": format(FormatCall),
"FormatQuote": format(FormatQuote),
"FormatError": format(FormatError),
"FormatWarning": format(FormatWarning),
"FormatInfo": format(FormatInfo),
"ToUpper": strings.ToUpper,
"ToLower": strings.ToLower,
"GoVersion": runtime.Version,
"GoArch": variable(build.Default.GOARCH),
"GoOS": variable(build.Default.GOOS),
"GoBuildTags": variable(strings.Join(build.Default.BuildTags, ",")),
"VCS": vcs,
"VCSRevision": vcsRevision,
"VCSTime": vcsTime,
"OrderedGroups": orderedGroups,
"PrefixLines": prefix,
"SuffixLines": suffix,
"Indent": indent,
"FitColumns": fitColumns,
"FitTerm": fitTerm,
"CommandFlagSets": flagset.CommandFlagSets,
"FlagUsages": flagUsages,
"Error": errorMessage,
}
)
func init() {
cobra.AddTemplateFuncs(funcs)
}
func errorMessage(format string, args ...any) string {
return fmt.Sprintf("%s %s", ErrorPrefix(), fmt.Sprintf(format, args...))
}
func fitTerm(content string) string {
width, _, err := term.GetSize(0)
if err != nil {
return content
}
const (
minWidth = 60
maxWidth = 100
)
width = max(minWidth, width)
width = min(maxWidth, width)
return fitColumns(width, content)
}
func fitColumns(columns int, content string) string {
contentLines := strings.Split(content, "\n")
var lines []string
var sb strings.Builder
for _, contentLine := range contentLines {
if strings.TrimSpace(contentLine) == "" {
next := sb.String()
lines = append(lines, next)
if next != "" {
lines = append(lines, "")
}
sb.Reset()
continue
}
words := strings.Fields(contentLine)
for _, word := range words {
if sb.Len()+len(word) > columns {
lines = append(lines, sb.String())
sb.Reset()
}
if sb.Len() > 0 {
sb.WriteByte(' ')
}
sb.WriteString(word)
}
}
if sb.Len() > 0 {
lines = append(lines, sb.String())
}
return strings.Join(lines, "\n")
}
func suffix(suffix, lines string) string {
return strings.ReplaceAll(lines, "\n", suffix+"\n") + suffix
}
func prefix(prefix, lines string) string {
return prefix + strings.ReplaceAll(lines, "\n", "\n"+prefix)
}
func indent(n int, s string) string {
lines := strings.Split(s, "\n")
for i, line := range lines {
lines[i] = strings.Repeat(" ", n) + line
}
return strings.Join(lines, "\n")
}
func format(displays ...ansi.Display) func(string, ...any) string {
return func(format string, args ...any) string {
return ansi.Format(displays...).Format(format, args...)
}
}
func orderedGroups(cmd *cobra.Command) []*cobra.Group {
groups := append([]*cobra.Group{}, cmd.Groups()...)
slices.SortFunc(groups, func(lhs, rhs *cobra.Group) int {
return cmp.Compare(lhs.Title, rhs.Title)
})
return groups
}
func settings() []debug.BuildSetting {
if info, ok := debug.ReadBuildInfo(); ok {
return info.Settings
}
return nil
}
func vcs() string {
for _, setting := range settings() {
if setting.Key == "vcs" {
return setting.Value
}
}
return "unknown"
}
func vcsRevision() string {
for _, setting := range settings() {
if setting.Key == "vcs.revision" {
return setting.Value
}
}
return "unknown"
}
func vcsTime() string {
for _, setting := range settings() {
if setting.Key == "vcs.time" {
return setting.Value
}
}
return "unknown"
}
func variable[T any](v T) func() T {
return func() T { return v }
}
func flagUsages(f *flagset.FlagSet) string {
return f.FormattedFlagUsages(&flagset.FormatOptions{
ArgFormat: FormatArg,
FlagFormat: FormatFlag,
DeprecatedFormat: FormatWarning,
})
}
/*
Package dedent provides a utility for dedenting multiline strings.
This package is really useful for providing multiline string literals as inputs
to cobra commands in Go.
*/
package dedent
import (
"math"
"strings"
"unicode"
)
// String dedents a multiline string and returns the result as a string without
// any common prefix whitespace. This will also trim and remove any leading and
// trailing whitespace lines.
func String(s string) string {
return strings.Join(Lines(strings.Split(s, "\n")...), "\n")
}
// Strings dedents a slice of strings and returns the result as a single string
// with the common prefix whitespace removed. This will also trim any leading
func Strings(lines ...string) string {
return strings.Join(Lines(lines...), "\n")
}
// Lines dedents a slice of strings and returns the result as a slice of strings
// with the common prefix whitespace removed. This will also trim any leading
// and trailing whitespace lines.
func Lines(lines ...string) []string {
lines = trimLeadingBlankLines(lines)
lines = trimTrailingBlankLines(lines)
prefix := commonWhitespacePrefix(lines)
if prefix == 0 {
return lines
}
newlines := make([]string, len(lines))
for i, line := range lines {
if len(line) >= prefix {
newlines[i] = line[prefix:]
}
}
return newlines
}
func trimLeadingBlankLines(lines []string) []string {
for i, line := range lines {
if strings.TrimSpace(line) != "" {
return lines[i:]
}
}
return lines
}
func trimTrailingBlankLines(lines []string) []string {
for i := len(lines) - 1; i >= 0; i-- {
if strings.TrimSpace(lines[i]) != "" {
return lines[:i+1]
}
}
return lines
}
func commonWhitespacePrefix(lines []string) int {
if len(lines) == 0 {
return 0
}
prefix := math.MaxInt
for _, line := range lines {
// Skip empty lines, since they should not contribute to whitespace prefix.
if len(strings.TrimSpace(line)) == 0 {
continue
}
prefix = min(prefix, countLeadingWhitespace(line))
}
if prefix == math.MaxInt {
return 0
}
return prefix
}
func countLeadingWhitespace(s string) int {
for i, ch := range s {
if !unicode.IsSpace(ch) {
return i
}
}
return len(s)
}
/*
Package env provides a way to get environment variables and falling back to
default values if they are not set.
*/
package env
import (
"fmt"
"go/build"
"os"
"path/filepath"
"github.com/bitwizeshift/protobuild/internal/sys"
)
// ErrNoProtobuildPath is returned when the protobuild path could not be
// determined.
var ErrNoProtobuildPath = fmt.Errorf("protobuild path")
// ConfigPath returns the path to the protobuild directory.
func ConfigPath() (string, error) {
if path := os.Getenv("PROTOBUILD_PATH"); path != "" {
return path, nil
}
if home := os.Getenv("HOME"); home != "" {
return filepath.Join(home, ".protobuild"), nil
}
if build.Default.GOOS == "windows" {
if userprofile := os.Getenv("USERPROFILE"); userprofile != "" {
return filepath.Join(userprofile, ".protobuild"), nil
}
}
dirname, err := sys.UserHomeDir()
if err != nil {
return "", fmt.Errorf("%w: %w", ErrNoProtobuildPath, err)
}
return filepath.Join(dirname, ".protobuild"), nil
}
// BinPath the path to where protobuf binaries are stored.
func BinPath() (string, error) {
return configPath("PROTOBUILD_BIN", "bin")
}
// RegistryPath the path to where the protobuild registry is stored.
func RegistryPath() (string, error) {
return configPath("PROTOBUILD_REGISTRY", "registry")
}
// CachePath the path to where the protobuild cache is stored.
func CachePath() (string, error) {
return configPath("PROTOBUILD_CACHE", "cache")
}
func configPath(env, subpath string) (string, error) {
if path := os.Getenv(env); path != "" {
return path, nil
}
path, err := ConfigPath()
if err != nil {
return "", err
}
return filepath.Join(path, subpath), nil
}
// Must returns value if err is nil, otherwise panics with the error.
func Must[T any](value T, err error) T {
if err != nil {
panic(err)
}
return value
}
package glob
import (
"fmt"
"os"
"path/filepath"
"strings"
)
// Pattern represents a globbing pattern that can be used to match against a
// given filepath.
//
// Unlike the builtin [filepath.Match] function, this supports both `**` for
// matching against arbitrary numbers of directories, as well as `!` for
// negating the match.
type Pattern string
// Match is a function that will match a given pattern against a path. This
// function will return a boolean value indicating whether the pattern matched
// the path, as well as an error if one occurred.
func (p Pattern) Match(path string) bool {
status, err := p.match(string(p), path)
return err == nil && status == statusMatched
}
// Filter returns a list of paths that match the pattern.
func (p Pattern) Filter(paths ...string) []string {
var filtered []string
for _, path := range paths {
if p.Match(path) {
filtered = append(filtered, path)
}
}
return filtered
}
// Glob returns a list of paths that match the pattern by recursively searching
// elements in the base directory.
func (p Pattern) Glob(base string) []string {
var paths []string
pattern := p.Prepend(base)
_ = filepath.Walk(base, func(path string, _ os.FileInfo, err error) error {
if err != nil {
return nil
}
if pattern.Match(path) {
paths = append(paths, path)
}
return nil
})
return paths
}
// Abs returns this pattern that was made absolute. If the pattern is already
// absolute, it will return the pattern as is. Any negations are semantically
// replaced so that it continues to work as expected.
func (p Pattern) Abs() (Pattern, error) {
trimmed := strings.TrimLeft(string(p), "!")
if filepath.IsAbs(trimmed) {
return p, nil
}
abs, err := filepath.Abs(trimmed)
if err != nil {
return "", err
}
offset := len(p) - len(trimmed)
prefix := string(p[:offset])
return Pattern(prefix + abs), nil
}
// Prepend returns this pattern that prepends the given base, if the pattern
// is not from an absolute path. If the pattern is already absolute, it will
// return the pattern as is. Any negations are semantically replaced so that it
// continues to work as expected.
func (p Pattern) Prepend(base string) Pattern {
trimmed := strings.TrimLeft(string(p), "!")
if filepath.IsAbs(trimmed) {
return p
}
offset := len(p) - len(trimmed)
prefix := string(p[:offset])
return Pattern(prefix + filepath.Join(base, trimmed))
}
// String converts this pattern to a string.
func (p Pattern) String() string {
return string(p)
}
var _ fmt.Stringer = (*Pattern)(nil)
type status int
const (
statusMatched status = iota
statusRejected
statusUnmatched
)
// match performs the internal matching, and allows for explicit rejection of
// matching paths.
func (p Pattern) match(pattern, path string) (status, error) {
if strings.HasPrefix(pattern, "!") {
return p.matchInverse(pattern[1:], path)
}
matched, err := p.matchParts(pattern, path)
if err != nil {
return statusUnmatched, err
}
if matched {
return statusMatched, nil
}
return statusUnmatched, nil
}
func (p Pattern) matchParts(pattern, path string) (bool, error) {
patternParts := strings.Split(pattern, string(filepath.Separator))
pathParts := strings.Split(path, string(filepath.Separator))
return p.matchRecursive(patternParts, pathParts)
}
func (p Pattern) matchRecursive(patternParts, pathParts []string) (bool, error) {
pathIdx := 0
for patternIdx, patternPart := range patternParts {
if patternPart == "**" {
if patternIdx < len(patternParts) {
matched, err := p.matchRecursive(patternParts[patternIdx+1:], pathParts[pathIdx:])
if matched || err != nil {
return matched, err
}
}
if pathIdx < len(pathParts) {
matched, err := p.matchRecursive(patternParts[patternIdx:], pathParts[pathIdx+1:])
if matched || err != nil {
return matched, err
}
}
return false, nil
}
if pathIdx >= len(pathParts) {
return false, nil
}
matched, err := filepath.Match(patternPart, pathParts[pathIdx])
if !matched || err != nil {
return false, err
}
pathIdx++
}
return pathIdx == len(pathParts), nil
}
func (p Pattern) matchInverse(pattern string, path string) (status, error) {
status, err := p.match(pattern, path)
if err != nil {
return statusUnmatched, err
}
if status == statusMatched {
return statusRejected, nil
}
return status, nil
}
package glob
import (
"os"
"path/filepath"
)
// Patterns represents a list of patterns that can be used to match against a
// given filepath.
// Any negative patterns in the list will take precedence for matching.
type Patterns []Pattern
// NewPatterns is a function that will create a new Patterns object from a
// variadic list of string patterns.
func NewPatterns(patterns ...string) Patterns {
result := make([]Pattern, len(patterns))
for i, pattern := range patterns {
result[i] = Pattern(pattern)
}
return result
}
// Match is a function that will match a given pattern against a name. This
// function will return a boolean value indicating whether the pattern matched
// the name and was valid.
func (p Patterns) Match(name string) bool {
result := statusUnmatched
for _, pattern := range p {
r, err := pattern.match(string(pattern), name)
if err != nil {
return false
}
if r == statusRejected {
return false
}
if r == statusMatched {
result = statusMatched
}
}
return result == statusMatched
}
// Filter is a function that will filter a list of names against a list of patterns.
// This function will return a list of names that matched any of the patterns.
func (p Patterns) Filter(names ...string) []string {
var result []string
for _, name := range names {
if p.Match(name) {
result = append(result, name)
}
}
return result
}
// Glob is a function that will walk a given base directory and return a list of
// paths that match any of the patterns.
func (p Patterns) Glob(base string) []string {
var patterns Patterns
for _, pattern := range p {
patterns = append(patterns, pattern.Prepend(base))
}
var paths []string
_ = filepath.Walk(base, func(path string, _ os.FileInfo, err error) error {
if err != nil {
return nil
}
if patterns.Match(path) {
paths = append(paths, path)
}
return nil
})
return paths
}
// Abs is a function that will make all patterns absolute. If the pattern is
// already absolute, it will return the pattern as is. Any negations are
// semantically replaced so that it continues to work as expected.
func (p Patterns) Abs() (Patterns, error) {
var result Patterns
for _, pattern := range p {
abs, err := pattern.Abs()
if err != nil {
return nil, err
}
result = append(result, abs)
}
return result, nil
}
// Prepend returns all patterns with the specified base prepended to it.
// If the pattern is already absolute, it will return the pattern as is. Any
// negations are semantically replaced so that it continues to work as expected.
func (p Patterns) Prepend(base string) Patterns {
result := make(Patterns, 0, len(p))
for _, pattern := range p {
result = append(result, pattern.Prepend(base))
}
return result
}
/*
Package sys provides replaceable abstractions for
*/
package sys
import (
"os"
"sync/atomic"
)
var (
home atomic.Pointer[func() (string, error)]
)
func init() {
fn := os.UserHomeDir
home.Store(&fn)
}
// SetUserHomeDirFunc sets the function to be used to determine the home directory.
func SetUserHomeDirFunc(f func() (string, error)) {
home.Store(&f)
}
// UserHomeDir returns the home directory for the current user.
func UserHomeDir() (string, error) {
get := home.Load()
return (*get)()
}
package main
func main() {
}