add vendor

parent e79e6c0b
The MIT License (MIT)
Copyright (c) 2013 Fatih Arslan
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
# color [![](https://github.com/fatih/color/workflows/build/badge.svg)](https://github.com/fatih/color/actions) [![PkgGoDev](https://pkg.go.dev/badge/github.com/fatih/color)](https://pkg.go.dev/github.com/fatih/color)
Color lets you use colorized outputs in terms of [ANSI Escape
Codes](http://en.wikipedia.org/wiki/ANSI_escape_code#Colors) in Go (Golang). It
has support for Windows too! The API can be used in several ways, pick one that
suits you.
![Color](https://user-images.githubusercontent.com/438920/96832689-03b3e000-13f4-11eb-9803-46f4c4de3406.jpg)
## Install
```
go get github.com/fatih/color
```
## Examples
### Standard colors
```go
// Print with default helper functions
color.Cyan("Prints text in cyan.")
// A newline will be appended automatically
color.Blue("Prints %s in blue.", "text")
// These are using the default foreground colors
color.Red("We have red")
color.Magenta("And many others ..")
```
### RGB colors
If your terminal supports 24-bit colors, you can use RGB color codes.
```go
color.RGB(255, 128, 0).Println("foreground orange")
color.RGB(230, 42, 42).Println("foreground red")
color.BgRGB(255, 128, 0).Println("background orange")
color.BgRGB(230, 42, 42).Println("background red")
```
### Mix and reuse colors
```go
// Create a new color object
c := color.New(color.FgCyan).Add(color.Underline)
c.Println("Prints cyan text with an underline.")
// Or just add them to New()
d := color.New(color.FgCyan, color.Bold)
d.Printf("This prints bold cyan %s\n", "too!.")
// Mix up foreground and background colors, create new mixes!
red := color.New(color.FgRed)
boldRed := red.Add(color.Bold)
boldRed.Println("This will print text in bold red.")
whiteBackground := red.Add(color.BgWhite)
whiteBackground.Println("Red text with white background.")
// Mix with RGB color codes
color.RGB(255, 128, 0).AddBgRGB(0, 0, 0).Println("orange with black background")
color.BgRGB(255, 128, 0).AddRGB(255, 255, 255).Println("orange background with white foreground")
```
### Use your own output (io.Writer)
```go
// Use your own io.Writer output
color.New(color.FgBlue).Fprintln(myWriter, "blue color!")
blue := color.New(color.FgBlue)
blue.Fprint(writer, "This will print text in blue.")
```
### Custom print functions (PrintFunc)
```go
// Create a custom print function for convenience
red := color.New(color.FgRed).PrintfFunc()
red("Warning")
red("Error: %s", err)
// Mix up multiple attributes
notice := color.New(color.Bold, color.FgGreen).PrintlnFunc()
notice("Don't forget this...")
```
### Custom fprint functions (FprintFunc)
```go
blue := color.New(color.FgBlue).FprintfFunc()
blue(myWriter, "important notice: %s", stars)
// Mix up with multiple attributes
success := color.New(color.Bold, color.FgGreen).FprintlnFunc()
success(myWriter, "Don't forget this...")
```
### Insert into noncolor strings (SprintFunc)
```go
// Create SprintXxx functions to mix strings with other non-colorized strings:
yellow := color.New(color.FgYellow).SprintFunc()
red := color.New(color.FgRed).SprintFunc()
fmt.Printf("This is a %s and this is %s.\n", yellow("warning"), red("error"))
info := color.New(color.FgWhite, color.BgGreen).SprintFunc()
fmt.Printf("This %s rocks!\n", info("package"))
// Use helper functions
fmt.Println("This", color.RedString("warning"), "should be not neglected.")
fmt.Printf("%v %v\n", color.GreenString("Info:"), "an important message.")
// Windows supported too! Just don't forget to change the output to color.Output
fmt.Fprintf(color.Output, "Windows support: %s", color.GreenString("PASS"))
```
### Plug into existing code
```go
// Use handy standard colors
color.Set(color.FgYellow)
fmt.Println("Existing text will now be in yellow")
fmt.Printf("This one %s\n", "too")
color.Unset() // Don't forget to unset
// You can mix up parameters
color.Set(color.FgMagenta, color.Bold)
defer color.Unset() // Use it in your function
fmt.Println("All text will now be bold magenta.")
```
### Disable/Enable color
There might be a case where you want to explicitly disable/enable color output. the
`go-isatty` package will automatically disable color output for non-tty output streams
(for example if the output were piped directly to `less`).
The `color` package also disables color output if the [`NO_COLOR`](https://no-color.org) environment
variable is set to a non-empty string.
`Color` has support to disable/enable colors programmatically both globally and
for single color definitions. For example suppose you have a CLI app and a
`-no-color` bool flag. You can easily disable the color output with:
```go
var flagNoColor = flag.Bool("no-color", false, "Disable color output")
if *flagNoColor {
color.NoColor = true // disables colorized output
}
```
It also has support for single color definitions (local). You can
disable/enable color output on the fly:
```go
c := color.New(color.FgCyan)
c.Println("Prints cyan text")
c.DisableColor()
c.Println("This is printed without any color")
c.EnableColor()
c.Println("This prints again cyan...")
```
## GitHub Actions
To output color in GitHub Actions (or other CI systems that support ANSI colors), make sure to set `color.NoColor = false` so that it bypasses the check for non-tty output streams.
## Credits
* [Fatih Arslan](https://github.com/fatih)
* Windows support via @mattn: [colorable](https://github.com/mattn/go-colorable)
## License
The MIT License (MIT) - see [`LICENSE.md`](https://github.com/fatih/color/blob/master/LICENSE.md) for more details
package color
import (
"os"
"golang.org/x/sys/windows"
)
func init() {
// Opt-in for ansi color support for current process.
// https://learn.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences#output-sequences
var outMode uint32
out := windows.Handle(os.Stdout.Fd())
if err := windows.GetConsoleMode(out, &outMode); err != nil {
return
}
outMode |= windows.ENABLE_PROCESSED_OUTPUT | windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING
_ = windows.SetConsoleMode(out, outMode)
}
/*
Package color is an ANSI color package to output colorized or SGR defined
output to the standard output. The API can be used in several way, pick one
that suits you.
Use simple and default helper functions with predefined foreground colors:
color.Cyan("Prints text in cyan.")
// a newline will be appended automatically
color.Blue("Prints %s in blue.", "text")
// More default foreground colors..
color.Red("We have red")
color.Yellow("Yellow color too!")
color.Magenta("And many others ..")
// Hi-intensity colors
color.HiGreen("Bright green color.")
color.HiBlack("Bright black means gray..")
color.HiWhite("Shiny white color!")
However, there are times when custom color mixes are required. Below are some
examples to create custom color objects and use the print functions of each
separate color object.
// Create a new color object
c := color.New(color.FgCyan).Add(color.Underline)
c.Println("Prints cyan text with an underline.")
// Or just add them to New()
d := color.New(color.FgCyan, color.Bold)
d.Printf("This prints bold cyan %s\n", "too!.")
// Mix up foreground and background colors, create new mixes!
red := color.New(color.FgRed)
boldRed := red.Add(color.Bold)
boldRed.Println("This will print text in bold red.")
whiteBackground := red.Add(color.BgWhite)
whiteBackground.Println("Red text with White background.")
// Use your own io.Writer output
color.New(color.FgBlue).Fprintln(myWriter, "blue color!")
blue := color.New(color.FgBlue)
blue.Fprint(myWriter, "This will print text in blue.")
You can create PrintXxx functions to simplify even more:
// Create a custom print function for convenient
red := color.New(color.FgRed).PrintfFunc()
red("warning")
red("error: %s", err)
// Mix up multiple attributes
notice := color.New(color.Bold, color.FgGreen).PrintlnFunc()
notice("don't forget this...")
You can also FprintXxx functions to pass your own io.Writer:
blue := color.New(FgBlue).FprintfFunc()
blue(myWriter, "important notice: %s", stars)
// Mix up with multiple attributes
success := color.New(color.Bold, color.FgGreen).FprintlnFunc()
success(myWriter, don't forget this...")
Or create SprintXxx functions to mix strings with other non-colorized strings:
yellow := New(FgYellow).SprintFunc()
red := New(FgRed).SprintFunc()
fmt.Printf("this is a %s and this is %s.\n", yellow("warning"), red("error"))
info := New(FgWhite, BgGreen).SprintFunc()
fmt.Printf("this %s rocks!\n", info("package"))
Windows support is enabled by default. All Print functions work as intended.
However, only for color.SprintXXX functions, user should use fmt.FprintXXX and
set the output to color.Output:
fmt.Fprintf(color.Output, "Windows support: %s", color.GreenString("PASS"))
info := New(FgWhite, BgGreen).SprintFunc()
fmt.Fprintf(color.Output, "this %s rocks!\n", info("package"))
Using with existing code is possible. Just use the Set() method to set the
standard output to the given parameters. That way a rewrite of an existing
code is not required.
// Use handy standard colors.
color.Set(color.FgYellow)
fmt.Println("Existing text will be now in Yellow")
fmt.Printf("This one %s\n", "too")
color.Unset() // don't forget to unset
// You can mix up parameters
color.Set(color.FgMagenta, color.Bold)
defer color.Unset() // use it in your function
fmt.Println("All text will be now bold magenta.")
There might be a case where you want to disable color output (for example to
pipe the standard output of your app to somewhere else). `Color` has support to
disable colors both globally and for single color definition. For example
suppose you have a CLI app and a `--no-color` bool flag. You can easily disable
the color output with:
var flagNoColor = flag.Bool("no-color", false, "Disable color output")
if *flagNoColor {
color.NoColor = true // disables colorized output
}
You can also disable the color by setting the NO_COLOR environment variable to any value.
It also has support for single color definitions (local). You can
disable/enable color output on the fly:
c := color.New(color.FgCyan)
c.Println("Prints cyan text")
c.DisableColor()
c.Println("This is printed without any color")
c.EnableColor()
c.Println("This prints again cyan...")
*/
package color
The MIT License (MIT)
Copyright (c) 2016 Yasuhiro Matsumoto
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
# go-colorable
[![Build Status](https://github.com/mattn/go-colorable/workflows/test/badge.svg)](https://github.com/mattn/go-colorable/actions?query=workflow%3Atest)
[![Codecov](https://codecov.io/gh/mattn/go-colorable/branch/master/graph/badge.svg)](https://codecov.io/gh/mattn/go-colorable)
[![GoDoc](https://godoc.org/github.com/mattn/go-colorable?status.svg)](http://godoc.org/github.com/mattn/go-colorable)
[![Go Report Card](https://goreportcard.com/badge/mattn/go-colorable)](https://goreportcard.com/report/mattn/go-colorable)
Colorable writer for windows.
For example, most of logger packages doesn't show colors on windows. (I know we can do it with ansicon. But I don't want.)
This package is possible to handle escape sequence for ansi color on windows.
## Too Bad!
![](https://raw.githubusercontent.com/mattn/go-colorable/gh-pages/bad.png)
## So Good!
![](https://raw.githubusercontent.com/mattn/go-colorable/gh-pages/good.png)
## Usage
```go
logrus.SetFormatter(&logrus.TextFormatter{ForceColors: true})
logrus.SetOutput(colorable.NewColorableStdout())
logrus.Info("succeeded")
logrus.Warn("not correct")
logrus.Error("something error")
logrus.Fatal("panic")
```
You can compile above code on non-windows OSs.
## Installation
```
$ go get github.com/mattn/go-colorable
```
# License
MIT
# Author
Yasuhiro Matsumoto (a.k.a mattn)
//go:build appengine
// +build appengine
package colorable
import (
"io"
"os"
_ "github.com/mattn/go-isatty"
)
// NewColorable returns new instance of Writer which handles escape sequence.
func NewColorable(file *os.File) io.Writer {
if file == nil {
panic("nil passed instead of *os.File to NewColorable()")
}
return file
}
// NewColorableStdout returns new instance of Writer which handles escape sequence for stdout.
func NewColorableStdout() io.Writer {
return os.Stdout
}
// NewColorableStderr returns new instance of Writer which handles escape sequence for stderr.
func NewColorableStderr() io.Writer {
return os.Stderr
}
// EnableColorsStdout enable colors if possible.
func EnableColorsStdout(enabled *bool) func() {
if enabled != nil {
*enabled = true
}
return func() {}
}
//go:build !windows && !appengine
// +build !windows,!appengine
package colorable
import (
"io"
"os"
_ "github.com/mattn/go-isatty"
)
// NewColorable returns new instance of Writer which handles escape sequence.
func NewColorable(file *os.File) io.Writer {
if file == nil {
panic("nil passed instead of *os.File to NewColorable()")
}
return file
}
// NewColorableStdout returns new instance of Writer which handles escape sequence for stdout.
func NewColorableStdout() io.Writer {
return os.Stdout
}
// NewColorableStderr returns new instance of Writer which handles escape sequence for stderr.
func NewColorableStderr() io.Writer {
return os.Stderr
}
// EnableColorsStdout enable colors if possible.
func EnableColorsStdout(enabled *bool) func() {
if enabled != nil {
*enabled = true
}
return func() {}
}
#!/usr/bin/env bash
set -e
echo "" > coverage.txt
for d in $(go list ./... | grep -v vendor); do
go test -race -coverprofile=profile.out -covermode=atomic "$d"
if [ -f profile.out ]; then
cat profile.out >> coverage.txt
rm profile.out
fi
done
package colorable
import (
"bytes"
"io"
)
// NonColorable holds writer but removes escape sequence.
type NonColorable struct {
out io.Writer
}
// NewNonColorable returns new instance of Writer which removes escape sequence from Writer.
func NewNonColorable(w io.Writer) io.Writer {
return &NonColorable{out: w}
}
// Write writes data on console
func (w *NonColorable) Write(data []byte) (n int, err error) {
er := bytes.NewReader(data)
var plaintext bytes.Buffer
loop:
for {
c1, err := er.ReadByte()
if err != nil {
plaintext.WriteTo(w.out)
break loop
}
if c1 != 0x1b {
plaintext.WriteByte(c1)
continue
}
_, err = plaintext.WriteTo(w.out)
if err != nil {
break loop
}
c2, err := er.ReadByte()
if err != nil {
break loop
}
if c2 != 0x5b {
continue
}
for {
c, err := er.ReadByte()
if err != nil {
break loop
}
if ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || c == '@' {
break
}
}
}
return len(data), nil
}
Copyright (c) Yasuhiro MATSUMOTO <mattn.jp@gmail.com>
MIT License (Expat)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
# go-isatty
[![Godoc Reference](https://godoc.org/github.com/mattn/go-isatty?status.svg)](http://godoc.org/github.com/mattn/go-isatty)
[![Codecov](https://codecov.io/gh/mattn/go-isatty/branch/master/graph/badge.svg)](https://codecov.io/gh/mattn/go-isatty)
[![Coverage Status](https://coveralls.io/repos/github/mattn/go-isatty/badge.svg?branch=master)](https://coveralls.io/github/mattn/go-isatty?branch=master)
[![Go Report Card](https://goreportcard.com/badge/mattn/go-isatty)](https://goreportcard.com/report/mattn/go-isatty)
isatty for golang
## Usage
```go
package main
import (
"fmt"
"github.com/mattn/go-isatty"
"os"
)
func main() {
if isatty.IsTerminal(os.Stdout.Fd()) {
fmt.Println("Is Terminal")
} else if isatty.IsCygwinTerminal(os.Stdout.Fd()) {
fmt.Println("Is Cygwin/MSYS2 Terminal")
} else {
fmt.Println("Is Not Terminal")
}
}
```
## Installation
```
$ go get github.com/mattn/go-isatty
```
## License
MIT
## Author
Yasuhiro Matsumoto (a.k.a mattn)
## Thanks
* k-takata: base idea for IsCygwinTerminal
https://github.com/k-takata/go-iscygpty
// Package isatty implements interface to isatty
package isatty
#!/usr/bin/env bash
set -e
echo "" > coverage.txt
for d in $(go list ./... | grep -v vendor); do
go test -race -coverprofile=profile.out -covermode=atomic "$d"
if [ -f profile.out ]; then
cat profile.out >> coverage.txt
rm profile.out
fi
done
//go:build (darwin || freebsd || openbsd || netbsd || dragonfly || hurd) && !appengine && !tinygo
// +build darwin freebsd openbsd netbsd dragonfly hurd
// +build !appengine
// +build !tinygo
package isatty
import "golang.org/x/sys/unix"
// IsTerminal return true if the file descriptor is terminal.
func IsTerminal(fd uintptr) bool {
_, err := unix.IoctlGetTermios(int(fd), unix.TIOCGETA)
return err == nil
}
// IsCygwinTerminal return true if the file descriptor is a cygwin or msys2
// terminal. This is also always false on this environment.
func IsCygwinTerminal(fd uintptr) bool {
return false
}
//go:build (appengine || js || nacl || tinygo || wasm) && !windows
// +build appengine js nacl tinygo wasm
// +build !windows
package isatty
// IsTerminal returns true if the file descriptor is terminal which
// is always false on js and appengine classic which is a sandboxed PaaS.
func IsTerminal(fd uintptr) bool {
return false
}
// IsCygwinTerminal() return true if the file descriptor is a cygwin or msys2
// terminal. This is also always false on this environment.
func IsCygwinTerminal(fd uintptr) bool {
return false
}
//go:build plan9
// +build plan9
package isatty
import (
"syscall"
)
// IsTerminal returns true if the given file descriptor is a terminal.
func IsTerminal(fd uintptr) bool {
path, err := syscall.Fd2path(int(fd))
if err != nil {
return false
}
return path == "/dev/cons" || path == "/mnt/term/dev/cons"
}
// IsCygwinTerminal return true if the file descriptor is a cygwin or msys2
// terminal. This is also always false on this environment.
func IsCygwinTerminal(fd uintptr) bool {
return false
}
//go:build solaris && !appengine
// +build solaris,!appengine
package isatty
import (
"golang.org/x/sys/unix"
)
// IsTerminal returns true if the given file descriptor is a terminal.
// see: https://src.illumos.org/source/xref/illumos-gate/usr/src/lib/libc/port/gen/isatty.c
func IsTerminal(fd uintptr) bool {
_, err := unix.IoctlGetTermio(int(fd), unix.TCGETA)
return err == nil
}
// IsCygwinTerminal return true if the file descriptor is a cygwin or msys2
// terminal. This is also always false on this environment.
func IsCygwinTerminal(fd uintptr) bool {
return false
}
//go:build (linux || aix || zos) && !appengine && !tinygo
// +build linux aix zos
// +build !appengine
// +build !tinygo
package isatty
import "golang.org/x/sys/unix"
// IsTerminal return true if the file descriptor is terminal.
func IsTerminal(fd uintptr) bool {
_, err := unix.IoctlGetTermios(int(fd), unix.TCGETS)
return err == nil
}
// IsCygwinTerminal return true if the file descriptor is a cygwin or msys2
// terminal. This is also always false on this environment.
func IsCygwinTerminal(fd uintptr) bool {
return false
}
//go:build windows && !appengine
// +build windows,!appengine
package isatty
import (
"errors"
"strings"
"syscall"
"unicode/utf16"
"unsafe"
)
const (
objectNameInfo uintptr = 1
fileNameInfo = 2
fileTypePipe = 3
)
var (
kernel32 = syscall.NewLazyDLL("kernel32.dll")
ntdll = syscall.NewLazyDLL("ntdll.dll")
procGetConsoleMode = kernel32.NewProc("GetConsoleMode")
procGetFileInformationByHandleEx = kernel32.NewProc("GetFileInformationByHandleEx")
procGetFileType = kernel32.NewProc("GetFileType")
procNtQueryObject = ntdll.NewProc("NtQueryObject")
)
func init() {
// Check if GetFileInformationByHandleEx is available.
if procGetFileInformationByHandleEx.Find() != nil {
procGetFileInformationByHandleEx = nil
}
}
// IsTerminal return true if the file descriptor is terminal.
func IsTerminal(fd uintptr) bool {
var st uint32
r, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, fd, uintptr(unsafe.Pointer(&st)), 0)
return r != 0 && e == 0
}
// Check pipe name is used for cygwin/msys2 pty.
// Cygwin/MSYS2 PTY has a name like:
// \{cygwin,msys}-XXXXXXXXXXXXXXXX-ptyN-{from,to}-master
func isCygwinPipeName(name string) bool {
token := strings.Split(name, "-")
if len(token) < 5 {
return false
}
if token[0] != `\msys` &&
token[0] != `\cygwin` &&
token[0] != `\Device\NamedPipe\msys` &&
token[0] != `\Device\NamedPipe\cygwin` {
return false
}
if token[1] == "" {
return false
}
if !strings.HasPrefix(token[2], "pty") {
return false
}
if token[3] != `from` && token[3] != `to` {
return false
}
if token[4] != "master" {
return false
}
return true
}
// getFileNameByHandle use the undocomented ntdll NtQueryObject to get file full name from file handler
// since GetFileInformationByHandleEx is not available under windows Vista and still some old fashion
// guys are using Windows XP, this is a workaround for those guys, it will also work on system from
// Windows vista to 10
// see https://stackoverflow.com/a/18792477 for details
func getFileNameByHandle(fd uintptr) (string, error) {
if procNtQueryObject == nil {
return "", errors.New("ntdll.dll: NtQueryObject not supported")
}
var buf [4 + syscall.MAX_PATH]uint16
var result int
r, _, e := syscall.Syscall6(procNtQueryObject.Addr(), 5,
fd, objectNameInfo, uintptr(unsafe.Pointer(&buf)), uintptr(2*len(buf)), uintptr(unsafe.Pointer(&result)), 0)
if r != 0 {
return "", e
}
return string(utf16.Decode(buf[4 : 4+buf[0]/2])), nil
}
// IsCygwinTerminal() return true if the file descriptor is a cygwin or msys2
// terminal.
func IsCygwinTerminal(fd uintptr) bool {
if procGetFileInformationByHandleEx == nil {
name, err := getFileNameByHandle(fd)
if err != nil {
return false
}
return isCygwinPipeName(name)
}
// Cygwin/msys's pty is a pipe.
ft, _, e := syscall.Syscall(procGetFileType.Addr(), 1, fd, 0, 0)
if ft != fileTypePipe || e != 0 {
return false
}
var buf [2 + syscall.MAX_PATH]uint16
r, _, e := syscall.Syscall6(procGetFileInformationByHandleEx.Addr(),
4, fd, fileNameInfo, uintptr(unsafe.Pointer(&buf)),
uintptr(len(buf)*2), 0, 0)
if r == 0 || e != 0 {
return false
}
l := *(*uint32)(unsafe.Pointer(&buf))
return isCygwinPipeName(string(utf16.Decode(buf[2 : 2+l/2])))
}
*.coverprofile
*.exe
*.orig
.*envrc
.envrc
.idea
/.local/
/site/
coverage.txt
examples/*/built-example
vendor
version: "2"
formatters:
enable:
- gofumpt
linters:
enable:
- makezero
- misspell
exclusions:
presets:
- std-error-handling
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, gender identity and expression, level of experience,
education, socio-economic status, nationality, personal appearance, race,
religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting urfave-governance@googlegroups.com, a members-only group
that is world-postable. All complaints will be reviewed and investigated and
will result in a response that is deemed necessary and appropriate to the
circumstances. The project team is obligated to maintain confidentiality with
regard to the reporter of an incident. Further details of specific enforcement
policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
[homepage]: https://www.contributor-covenant.org
MIT License
Copyright (c) 2023 urfave/cli maintainers
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
# NOTE: this Makefile is meant to provide a simplified entry point for humans to
# run all of the critical steps to verify one's changes are harmonious in
# nature. Keeping target bodies to one line each and abstaining from make magic
# are very important so that maintainers and contributors can focus their
# attention on files that are primarily Go.
GO_RUN_BUILD := go run scripts/build.go
.PHONY: all
all: generate vet test check-binary-size gfmrun
# NOTE: this is a special catch-all rule to run any of the commands
# defined in scripts/build.go with optional arguments passed
# via GFLAGS (global flags) and FLAGS (command-specific flags), e.g.:
#
# $ make test GFLAGS='--packages cli'
%:
$(GO_RUN_BUILD) $(GFLAGS) $* $(FLAGS)
.PHONY: docs
docs:
mkdocs build
.PHONY: serve-docs
serve-docs:
mkdocs serve
# Welcome to urfave/cli
[![Go Reference][goreference_badge]][goreference_link]
[![Go Report Card][goreportcard_badge]][goreportcard_link]
[![codecov][codecov_badge]][codecov_link]
[![Tests status][test_badge]][test_link]
urfave/cli is a **declarative**, simple, fast, and fun package for building
command line tools in Go featuring:
- commands and subcommands with alias and prefix match support
- flexible and permissive help system
- dynamic shell completion for `bash`, `zsh`, `fish`, and `powershell`
- no dependencies except Go standard library
- input flags for simple types, slices of simple types, time, duration, and
others
- compound short flag support (`-a` `-b` `-c` can be shortened to `-abc`)
- documentation generation in `man` and Markdown (supported via the
[`urfave/cli-docs`][urfave/cli-docs] module)
- input lookup from:
- environment variables
- plain text files
- structured file formats (supported via the
[`urfave/cli-altsrc`][urfave/cli-altsrc] module)
## Documentation
See the hosted documentation website at <https://cli.urfave.org>. Contents of
this website are built from the [`./docs`](./docs) directory.
## Support
Check the [Q&A discussions]. If you don't find answer to your question, [create
a new discussion].
If you found a bug or have a feature request, [create a new issue].
Please keep in mind that this project is run by unpaid volunteers.
### License
See [`LICENSE`](./LICENSE).
[test_badge]: https://github.com/urfave/cli/actions/workflows/test.yml/badge.svg
[test_link]: https://github.com/urfave/cli/actions/workflows/test.yml
[goreference_badge]: https://pkg.go.dev/badge/github.com/urfave/cli/v3.svg
[goreference_link]: https://pkg.go.dev/github.com/urfave/cli/v3
[goreportcard_badge]: https://goreportcard.com/badge/github.com/urfave/cli/v3
[goreportcard_link]: https://goreportcard.com/report/github.com/urfave/cli/v3
[codecov_badge]: https://codecov.io/gh/urfave/cli/branch/main/graph/badge.svg?token=t9YGWLh05g
[codecov_link]: https://codecov.io/gh/urfave/cli
[Q&A discussions]: https://github.com/urfave/cli/discussions/categories/q-a
[create a new discussion]: https://github.com/urfave/cli/discussions/new?category=q-a
[urfave/cli-docs]: https://github.com/urfave/cli-docs
[urfave/cli-altsrc]: https://github.com/urfave/cli-altsrc
[create a new issue]: https://github.com/urfave/cli/issues/new/choose
#!/bin/bash
# This is a shell completion script auto-generated by https://github.com/urfave/cli for bash.
# Macs have bash3 for which the bash-completion package doesn't include
# _init_completion. This is a minimal version of that function.
__%[1]s_init_completion() {
COMPREPLY=()
_get_comp_words_by_ref "$@" cur prev words cword
}
__%[1]s_bash_autocomplete() {
if [[ "${COMP_WORDS[0]}" != "source" ]]; then
local cur opts base words
COMPREPLY=()
cur="${COMP_WORDS[COMP_CWORD]}"
if declare -F _init_completion >/dev/null 2>&1; then
_init_completion -n "=:" || return
else
__%[1]s_init_completion -n "=:" || return
fi
words=("${words[@]:0:$cword}")
if [[ "$cur" == "-"* ]]; then
requestComp="${words[*]} ${cur} --generate-shell-completion"
else
requestComp="${words[*]} --generate-shell-completion"
fi
opts=$(eval "${requestComp}" 2>/dev/null)
COMPREPLY=($(compgen -W "${opts}" -- ${cur}))
return 0
fi
}
complete -o bashdefault -o default -o nospace -F __%[1]s_bash_autocomplete %[1]s
$fn = $($MyInvocation.MyCommand.Name)
$name = $fn -replace "(.*)\.ps1$", '$1'
Register-ArgumentCompleter -Native -CommandName $name -ScriptBlock {
param($commandName, $wordToComplete, $cursorPosition)
$other = "$wordToComplete --generate-shell-completion"
Invoke-Expression $other | ForEach-Object {
[System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_)
}
}
#compdef %[1]s
compdef _%[1]s %[1]s
# This is a shell completion script auto-generated by https://github.com/urfave/cli for zsh.
_%[1]s() {
local -a opts # Declare a local array
local current
current=${words[-1]} # -1 means "the last element"
if [[ "$current" == "-"* ]]; then
# Current word starts with a hyphen, so complete flags/options
opts=("${(@f)$(${words[@]:0:#words[@]-1} ${current} --generate-shell-completion)}")
else
# Current word does not start with a hyphen, so complete subcommands
opts=("${(@f)$(${words[@]:0:#words[@]-1} --generate-shell-completion)}")
fi
if [[ "${opts[1]}" != "" ]]; then
_describe 'values' opts
else
_files
fi
}
# Don't run the completion function when being source-ed or eval-ed.
# See https://github.com/urfave/cli/issues/1874 for discussion.
if [ "$funcstack[1]" = "_%[1]s" ]; then
_%[1]s
fi
package cli
import "sort"
// CommandCategories interface allows for category manipulation
type CommandCategories interface {
// AddCommand adds a command to a category, creating a new category if necessary.
AddCommand(category string, command *Command)
// Categories returns a slice of categories sorted by name
Categories() []CommandCategory
}
type commandCategories []*commandCategory
func newCommandCategories() CommandCategories {
ret := commandCategories([]*commandCategory{})
return &ret
}
func (c *commandCategories) Less(i, j int) bool {
return lexicographicLess((*c)[i].Name(), (*c)[j].Name())
}
func (c *commandCategories) Len() int {
return len(*c)
}
func (c *commandCategories) Swap(i, j int) {
(*c)[i], (*c)[j] = (*c)[j], (*c)[i]
}
func (c *commandCategories) AddCommand(category string, command *Command) {
for _, commandCategory := range []*commandCategory(*c) {
if commandCategory.name == category {
commandCategory.commands = append(commandCategory.commands, command)
return
}
}
newVal := append(*c,
&commandCategory{name: category, commands: []*Command{command}})
*c = newVal
}
func (c *commandCategories) Categories() []CommandCategory {
ret := make([]CommandCategory, len(*c))
for i, cat := range *c {
ret[i] = cat
}
return ret
}
// CommandCategory is a category containing commands.
type CommandCategory interface {
// Name returns the category name string
Name() string
// VisibleCommands returns a slice of the Commands with Hidden=false
VisibleCommands() []*Command
}
type commandCategory struct {
name string
commands []*Command
}
func (c *commandCategory) Name() string {
return c.name
}
func (c *commandCategory) VisibleCommands() []*Command {
if c.commands == nil {
c.commands = []*Command{}
}
var ret []*Command
for _, command := range c.commands {
if !command.Hidden {
ret = append(ret, command)
}
}
return ret
}
// FlagCategories interface allows for category manipulation
type FlagCategories interface {
// AddFlags adds a flag to a category, creating a new category if necessary.
AddFlag(category string, fl Flag)
// VisibleCategories returns a slice of visible flag categories sorted by name
VisibleCategories() []VisibleFlagCategory
}
type defaultFlagCategories struct {
m map[string]*defaultVisibleFlagCategory
}
func newFlagCategories() FlagCategories {
return &defaultFlagCategories{
m: map[string]*defaultVisibleFlagCategory{},
}
}
func newFlagCategoriesFromFlags(fs []Flag) FlagCategories {
fc := newFlagCategories()
var categorized bool
for _, fl := range fs {
if cf, ok := fl.(CategorizableFlag); ok {
visible := false
if vf, ok := fl.(VisibleFlag); ok {
visible = vf.IsVisible()
}
if cat := cf.GetCategory(); cat != "" && visible {
fc.AddFlag(cat, fl)
categorized = true
}
}
}
if categorized {
for _, fl := range fs {
if cf, ok := fl.(CategorizableFlag); ok {
visible := false
if vf, ok := fl.(VisibleFlag); ok {
visible = vf.IsVisible()
}
if cf.GetCategory() == "" && visible {
fc.AddFlag("", fl)
}
}
}
}
return fc
}
func (f *defaultFlagCategories) AddFlag(category string, fl Flag) {
if _, ok := f.m[category]; !ok {
f.m[category] = &defaultVisibleFlagCategory{name: category, m: map[string]Flag{}}
}
f.m[category].m[fl.String()] = fl
}
func (f *defaultFlagCategories) VisibleCategories() []VisibleFlagCategory {
catNames := []string{}
for name := range f.m {
catNames = append(catNames, name)
}
sort.Strings(catNames)
ret := make([]VisibleFlagCategory, len(catNames))
for i, name := range catNames {
ret[i] = f.m[name]
}
return ret
}
// VisibleFlagCategory is a category containing flags.
type VisibleFlagCategory interface {
// Name returns the category name string
Name() string
// Flags returns a slice of VisibleFlag sorted by name
Flags() []Flag
}
type defaultVisibleFlagCategory struct {
name string
m map[string]Flag
}
func (fc *defaultVisibleFlagCategory) Name() string {
return fc.name
}
func (fc *defaultVisibleFlagCategory) Flags() []Flag {
vfNames := []string{}
for flName, fl := range fc.m {
if vf, ok := fl.(VisibleFlag); ok {
if vf.IsVisible() {
vfNames = append(vfNames, flName)
}
}
}
sort.Strings(vfNames)
ret := make([]Flag, len(vfNames))
for i, flName := range vfNames {
ret[i] = fc.m[flName]
}
return ret
}
// Package cli provides a minimal framework for creating and organizing command line
// Go applications. cli is designed to be easy to understand and write, the most simple
// cli application can be written as follows:
//
// func main() {
// (&cli.Command{}).Run(context.Background(), os.Args)
// }
//
// Of course this application does not do much, so let's make this an actual application:
//
// func main() {
// cmd := &cli.Command{
// Name: "greet",
// Usage: "say a greeting",
// Action: func(c *cli.Context) error {
// fmt.Println("Greetings")
// return nil
// },
// }
//
// cmd.Run(context.Background(), os.Args)
// }
package cli
import (
"fmt"
"os"
"runtime"
"strings"
)
var isTracingOn = os.Getenv("URFAVE_CLI_TRACING") == "on"
func tracef(format string, a ...any) {
if !isTracingOn {
return
}
if !strings.HasSuffix(format, "\n") {
format = format + "\n"
}
pc, file, line, _ := runtime.Caller(1)
cf := runtime.FuncForPC(pc)
fmt.Fprintf(
os.Stderr,
strings.Join([]string{
"## URFAVE CLI TRACE ",
file,
":",
fmt.Sprintf("%v", line),
" ",
fmt.Sprintf("(%s)", cf.Name()),
" ",
format,
}, ""),
a...,
)
}
package cli
import (
"fmt"
"strings"
"unicode"
)
const (
providedButNotDefinedErrMsg = "flag provided but not defined: -"
argumentNotProvidedErrMsg = "flag needs an argument: "
)
// flagFromError tries to parse a provided flag from an error message. If the
// parsing fails, it returns the input error and an empty string
func flagFromError(err error) (string, error) {
errStr := err.Error()
trimmed := strings.TrimPrefix(errStr, providedButNotDefinedErrMsg)
if errStr == trimmed {
return "", err
}
return trimmed, nil
}
func (cmd *Command) parseFlags(args Args) (Args, error) {
tracef("parsing flags from arguments %[1]q (cmd=%[2]q)", args, cmd.Name)
cmd.setFlags = map[Flag]struct{}{}
cmd.appliedFlags = cmd.allFlags()
tracef("walking command lineage for persistent flags (cmd=%[1]q)", cmd.Name)
for pCmd := cmd.parent; pCmd != nil; pCmd = pCmd.parent {
tracef(
"checking ancestor command=%[1]q for persistent flags (cmd=%[2]q)",
pCmd.Name, cmd.Name,
)
for _, fl := range pCmd.Flags {
flNames := fl.Names()
pfl, ok := fl.(LocalFlag)
if !ok || pfl.IsLocal() {
tracef("skipping non-persistent flag %[1]q (cmd=%[2]q)", flNames, cmd.Name)
continue
}
tracef(
"checking for applying persistent flag=%[1]q pCmd=%[2]q (cmd=%[3]q)",
flNames, pCmd.Name, cmd.Name,
)
applyPersistentFlag := true
for _, name := range flNames {
if cmd.lFlag(name) != nil {
applyPersistentFlag = false
break
}
}
if !applyPersistentFlag {
tracef("not applying as persistent flag=%[1]q (cmd=%[2]q)", flNames, cmd.Name)
continue
}
tracef("applying as persistent flag=%[1]q (cmd=%[2]q)", flNames, cmd.Name)
tracef("appending to applied flags flag=%[1]q (cmd=%[2]q)", flNames, cmd.Name)
cmd.appliedFlags = append(cmd.appliedFlags, fl)
}
}
tracef("parsing flags iteratively tail=%[1]q (cmd=%[2]q)", args.Tail(), cmd.Name)
defer tracef("done parsing flags (cmd=%[1]q)", cmd.Name)
posArgs := []string{}
for rargs := args.Slice(); len(rargs) > 0; rargs = rargs[1:] {
tracef("rearrange:1 (cmd=%[1]q) %[2]q", cmd.Name, rargs)
firstArg := strings.TrimSpace(rargs[0])
if len(firstArg) == 0 {
break
}
// stop parsing once we see a "--"
if firstArg == "--" {
posArgs = append(posArgs, rargs[1:]...)
return &stringSliceArgs{posArgs}, nil
}
// handle positional args
if firstArg[0] != '-' {
// positional argument probably
tracef("rearrange-3 (cmd=%[1]q) check %[2]q", cmd.Name, firstArg)
// if there is a command by that name let the command handle the
// rest of the parsing
if cmd.Command(firstArg) != nil {
posArgs = append(posArgs, rargs...)
return &stringSliceArgs{posArgs}, nil
}
posArgs = append(posArgs, firstArg)
continue
}
numMinuses := 1
// this is same as firstArg == "-"
if len(firstArg) == 1 {
posArgs = append(posArgs, firstArg)
break
}
shortOptionHandling := cmd.useShortOptionHandling()
// stop parsing -- as short flags
if firstArg[1] == '-' {
numMinuses++
shortOptionHandling = false
} else if !unicode.IsLetter(rune(firstArg[1])) {
// this is not a flag
tracef("parseFlags not a unicode letter. Stop parsing")
posArgs = append(posArgs, rargs...)
return &stringSliceArgs{posArgs}, nil
}
tracef("parseFlags (shortOptionHandling=%[1]q)", shortOptionHandling)
flagName := firstArg[numMinuses:]
flagVal := ""
tracef("flagName:1 (fName=%[1]q)", flagName)
if index := strings.Index(flagName, "="); index != -1 {
flagVal = flagName[index+1:]
flagName = flagName[:index]
}
tracef("flagName:2 (fName=%[1]q) (fVal=%[2]q)", flagName, flagVal)
f := cmd.lookupFlag(flagName)
// found a flag matching given flagName
if f != nil {
tracef("Trying flag type (fName=%[1]q) (type=%[2]T)", flagName, f)
if fb, ok := f.(boolFlag); ok && fb.IsBoolFlag() {
if flagVal == "" {
flagVal = "true"
}
tracef("parse Apply bool flag (fName=%[1]q) (fVal=%[2]q)", flagName, flagVal)
if err := cmd.set(flagName, f, flagVal); err != nil {
return &stringSliceArgs{posArgs}, err
}
continue
}
tracef("processing non bool flag (fName=%[1]q)", flagName)
// not a bool flag so need to get the next arg
if flagVal == "" {
if len(rargs) == 1 {
return &stringSliceArgs{posArgs}, fmt.Errorf("%s%s", argumentNotProvidedErrMsg, firstArg)
}
flagVal = rargs[1]
rargs = rargs[1:]
}
tracef("setting non bool flag (fName=%[1]q) (fVal=%[2]q)", flagName, flagVal)
if err := cmd.set(flagName, f, flagVal); err != nil {
return &stringSliceArgs{posArgs}, err
}
continue
}
// no flag lookup found and short handling is disabled
if !shortOptionHandling {
return &stringSliceArgs{posArgs}, fmt.Errorf("%s%s", providedButNotDefinedErrMsg, flagName)
}
// try to split the flags
for index, c := range flagName {
tracef("processing flag (fName=%[1]q)", string(c))
if sf := cmd.lookupFlag(string(c)); sf == nil {
return &stringSliceArgs{posArgs}, fmt.Errorf("%s%s", providedButNotDefinedErrMsg, flagName)
} else if fb, ok := sf.(boolFlag); ok && fb.IsBoolFlag() {
fv := flagVal
if index == (len(flagName)-1) && flagVal == "" {
fv = "true"
}
if fv == "" {
fv = "true"
}
if err := cmd.set(flagName, sf, fv); err != nil {
tracef("processing flag.2 (fName=%[1]q)", string(c))
return &stringSliceArgs{posArgs}, err
}
} else if index == len(flagName)-1 { // last flag can take an arg
if flagVal == "" {
if len(rargs) == 1 {
return &stringSliceArgs{posArgs}, fmt.Errorf("%s%s", argumentNotProvidedErrMsg, string(c))
}
flagVal = rargs[1]
}
tracef("parseFlags (flagName %[1]q) (flagVal %[2]q)", flagName, flagVal)
if err := cmd.set(flagName, sf, flagVal); err != nil {
tracef("processing flag.4 (fName=%[1]q)", string(c))
return &stringSliceArgs{posArgs}, err
}
}
}
}
tracef("returning-2 (cmd=%[1]q) args %[2]q", cmd.Name, posArgs)
return &stringSliceArgs{posArgs}, nil
}
package cli
import (
"bufio"
"context"
"fmt"
"io"
"reflect"
"slices"
"unicode"
)
func (cmd *Command) parseArgsFromStdin() ([]string, error) {
type state int
const (
stateSearchForToken state = -1
stateSearchForString state = 0
)
st := stateSearchForToken
linenum := 1
token := ""
args := []string{}
breader := bufio.NewReader(cmd.Reader)
outer:
for {
ch, _, err := breader.ReadRune()
if err == io.EOF {
switch st {
case stateSearchForToken:
if token != "--" {
args = append(args, token)
}
case stateSearchForString:
// make sure string is not empty
for _, t := range token {
if !unicode.IsSpace(t) {
args = append(args, token)
}
}
}
break outer
}
if err != nil {
return nil, err
}
switch st {
case stateSearchForToken:
if unicode.IsSpace(ch) || ch == '"' {
if ch == '\n' {
linenum++
}
if token != "" {
// end the processing here
if token == "--" {
break outer
}
args = append(args, token)
token = ""
}
if ch == '"' {
st = stateSearchForString
}
continue
}
token += string(ch)
case stateSearchForString:
if ch != '"' {
token += string(ch)
} else {
if token != "" {
args = append(args, token)
token = ""
}
/*else {
//TODO. Should we pass in empty strings ?
}*/
st = stateSearchForToken
}
}
}
tracef("parsed stdin args as %v (cmd=%[2]q)", args, cmd.Name)
return args, nil
}
// Run is the entry point to the command graph. The positional
// arguments are parsed according to the Flag and Command
// definitions and the matching Action functions are run.
func (cmd *Command) Run(ctx context.Context, osArgs []string) (deferErr error) {
_, deferErr = cmd.run(ctx, osArgs)
return
}
func (cmd *Command) run(ctx context.Context, osArgs []string) (_ context.Context, deferErr error) {
tracef("running with arguments %[1]q (cmd=%[2]q)", osArgs, cmd.Name)
cmd.setupDefaults(osArgs)
if v, ok := ctx.Value(commandContextKey).(*Command); ok {
tracef("setting parent (cmd=%[1]q) command from context.Context value (cmd=%[2]q)", v.Name, cmd.Name)
cmd.parent = v
}
if cmd.parent == nil {
if cmd.ReadArgsFromStdin {
if args, err := cmd.parseArgsFromStdin(); err != nil {
return ctx, err
} else {
osArgs = append(osArgs, args...)
}
}
// handle the completion flag separately from the flagset since
// completion could be attempted after a flag, but before its value was put
// on the command line. this causes the flagset to interpret the completion
// flag name as the value of the flag before it which is undesirable
// note that we can only do this because the shell autocomplete function
// always appends the completion flag at the end of the command
tracef("checking osArgs %v (cmd=%[2]q)", osArgs, cmd.Name)
cmd.shellCompletion, osArgs = checkShellCompleteFlag(cmd, osArgs)
tracef("setting cmd.shellCompletion=%[1]v from checkShellCompleteFlag (cmd=%[2]q)", cmd.shellCompletion && cmd.EnableShellCompletion, cmd.Name)
cmd.shellCompletion = cmd.EnableShellCompletion && cmd.shellCompletion
}
tracef("using post-checkShellCompleteFlag arguments %[1]q (cmd=%[2]q)", osArgs, cmd.Name)
tracef("setting self as cmd in context (cmd=%[1]q)", cmd.Name)
ctx = context.WithValue(ctx, commandContextKey, cmd)
if cmd.parent == nil {
cmd.setupCommandGraph()
}
var rargs Args = &stringSliceArgs{v: osArgs}
for _, f := range cmd.allFlags() {
if err := f.PreParse(); err != nil {
return ctx, err
}
}
var args Args = &stringSliceArgs{rargs.Tail()}
var err error
if cmd.SkipFlagParsing {
tracef("skipping flag parsing (cmd=%[1]q)", cmd.Name)
cmd.parsedArgs = args
} else {
cmd.parsedArgs, err = cmd.parseFlags(args)
}
tracef("using post-parse arguments %[1]q (cmd=%[2]q)", args, cmd.Name)
if checkCompletions(ctx, cmd) {
return ctx, nil
}
if err != nil {
tracef("setting deferErr from %[1]q (cmd=%[2]q)", err, cmd.Name)
deferErr = err
cmd.isInError = true
if cmd.OnUsageError != nil {
err = cmd.OnUsageError(ctx, cmd, err, cmd.parent != nil)
err = cmd.handleExitCoder(ctx, err)
return ctx, err
}
fmt.Fprintf(cmd.Root().ErrWriter, "Incorrect Usage: %s\n\n", err.Error())
if cmd.Suggest {
if suggestion, err := cmd.suggestFlagFromError(err, ""); err == nil {
fmt.Fprintf(cmd.Root().ErrWriter, "%s", suggestion)
}
}
if !cmd.hideHelp() {
if cmd.parent == nil {
tracef("running ShowRootCommandHelp")
if err := ShowRootCommandHelp(cmd); err != nil {
tracef("SILENTLY IGNORING ERROR running ShowRootCommandHelp %[1]v (cmd=%[2]q)", err, cmd.Name)
}
} else {
tracef("running ShowCommandHelp with %[1]q", cmd.Name)
if err := ShowCommandHelp(ctx, cmd, cmd.Name); err != nil {
tracef("SILENTLY IGNORING ERROR running ShowCommandHelp with %[1]q %[2]v", cmd.Name, err)
}
}
}
return ctx, err
}
if cmd.checkHelp() {
return ctx, helpCommandAction(ctx, cmd)
} else {
tracef("no help is wanted (cmd=%[1]q)", cmd.Name)
}
if cmd.parent == nil && !cmd.HideVersion && checkVersion(cmd) {
ShowVersion(cmd)
return ctx, nil
}
for _, flag := range cmd.allFlags() {
if err := flag.PostParse(); err != nil {
return ctx, err
}
}
if cmd.After != nil && !cmd.Root().shellCompletion {
defer func() {
if err := cmd.After(ctx, cmd); err != nil {
err = cmd.handleExitCoder(ctx, err)
if deferErr != nil {
deferErr = newMultiError(deferErr, err)
} else {
deferErr = err
}
}
}()
}
for _, grp := range cmd.MutuallyExclusiveFlags {
if err := grp.check(cmd); err != nil {
if cmd.OnUsageError != nil {
err = cmd.OnUsageError(ctx, cmd, err, cmd.parent != nil)
} else {
_ = ShowSubcommandHelp(cmd)
}
return ctx, err
}
}
var subCmd *Command
if cmd.parsedArgs.Present() {
tracef("checking positional args %[1]q (cmd=%[2]q)", cmd.parsedArgs, cmd.Name)
name := cmd.parsedArgs.First()
tracef("using first positional argument as sub-command name=%[1]q (cmd=%[2]q)", name, cmd.Name)
if cmd.SuggestCommandFunc != nil && name != "--" {
name = cmd.SuggestCommandFunc(cmd.Commands, name)
}
subCmd = cmd.Command(name)
if subCmd == nil {
hasDefault := cmd.DefaultCommand != ""
isFlagName := slices.Contains(cmd.FlagNames(), name)
if hasDefault {
tracef("using default command=%[1]q (cmd=%[2]q)", cmd.DefaultCommand, cmd.Name)
}
if isFlagName || hasDefault {
argsWithDefault := cmd.argsWithDefaultCommand(args)
tracef("using default command args=%[1]q (cmd=%[2]q)", argsWithDefault, cmd.Name)
if !reflect.DeepEqual(args, argsWithDefault) {
subCmd = cmd.Command(argsWithDefault.First())
}
}
}
} else if cmd.parent == nil && cmd.DefaultCommand != "" {
tracef("no positional args present; checking default command %[1]q (cmd=%[2]q)", cmd.DefaultCommand, cmd.Name)
if dc := cmd.Command(cmd.DefaultCommand); dc != cmd {
subCmd = dc
}
}
// If a subcommand has been resolved, let it handle the remaining execution.
if subCmd != nil {
tracef("running sub-command %[1]q with arguments %[2]q (cmd=%[3]q)", subCmd.Name, cmd.Args(), cmd.Name)
// It is important that we overwrite the ctx variable in the current
// function so any defer'd functions use the new context returned
// from the sub command.
ctx, err = subCmd.run(ctx, cmd.Args().Slice())
return ctx, err
}
// This code path is the innermost command execution. Here we actually
// perform the command action.
//
// First, resolve the chain of nested commands up to the parent.
var cmdChain []*Command
for p := cmd; p != nil; p = p.parent {
cmdChain = append(cmdChain, p)
}
slices.Reverse(cmdChain)
// Run Before actions in order.
for _, cmd := range cmdChain {
if cmd.Before == nil {
continue
}
if bctx, err := cmd.Before(ctx, cmd); err != nil {
deferErr = cmd.handleExitCoder(ctx, err)
return ctx, deferErr
} else if bctx != nil {
ctx = bctx
}
}
// Run flag actions in order.
// These take a context, so this has to happen after Before actions.
for _, cmd := range cmdChain {
tracef("running flag actions (cmd=%[1]q)", cmd.Name)
if err := cmd.runFlagActions(ctx); err != nil {
deferErr = cmd.handleExitCoder(ctx, err)
return ctx, deferErr
}
}
if err := cmd.checkAllRequiredFlags(); err != nil {
cmd.isInError = true
if cmd.OnUsageError != nil {
err = cmd.OnUsageError(ctx, cmd, err, cmd.parent != nil)
} else {
_ = ShowSubcommandHelp(cmd)
}
return ctx, err
}
// Run the command action.
if len(cmd.Arguments) > 0 {
rargs := cmd.Args().Slice()
tracef("calling argparse with %[1]v", rargs)
for _, arg := range cmd.Arguments {
var err error
rargs, err = arg.Parse(rargs)
if err != nil {
tracef("calling with %[1]v (cmd=%[2]q)", err, cmd.Name)
if cmd.OnUsageError != nil {
err = cmd.OnUsageError(ctx, cmd, err, cmd.parent != nil)
}
err = cmd.handleExitCoder(ctx, err)
return ctx, err
}
}
cmd.parsedArgs = &stringSliceArgs{v: rargs}
}
if err := cmd.Action(ctx, cmd); err != nil {
tracef("calling handleExitCoder with %[1]v (cmd=%[2]q)", err, cmd.Name)
deferErr = cmd.handleExitCoder(ctx, err)
}
tracef("returning deferErr (cmd=%[1]q) %[2]q", cmd.Name, deferErr)
return ctx, deferErr
}
package cli
import (
"flag"
"os"
"path/filepath"
"sort"
"strings"
)
func (cmd *Command) setupDefaults(osArgs []string) {
if cmd.didSetupDefaults {
tracef("already did setup (cmd=%[1]q)", cmd.Name)
return
}
cmd.didSetupDefaults = true
isRoot := cmd.parent == nil
tracef("isRoot? %[1]v (cmd=%[2]q)", isRoot, cmd.Name)
if cmd.ShellComplete == nil {
tracef("setting default ShellComplete (cmd=%[1]q)", cmd.Name)
cmd.ShellComplete = DefaultCompleteWithFlags
}
if cmd.Name == "" && isRoot {
name := filepath.Base(osArgs[0])
tracef("setting cmd.Name from first arg basename (cmd=%[1]q)", name)
cmd.Name = name
}
if cmd.Usage == "" && isRoot {
tracef("setting default Usage (cmd=%[1]q)", cmd.Name)
cmd.Usage = "A new cli application"
}
if cmd.Version == "" {
tracef("setting HideVersion=true due to empty Version (cmd=%[1]q)", cmd.Name)
cmd.HideVersion = true
}
if cmd.Action == nil {
tracef("setting default Action as help command action (cmd=%[1]q)", cmd.Name)
cmd.Action = helpCommandAction
}
if cmd.Reader == nil {
tracef("setting default Reader as os.Stdin (cmd=%[1]q)", cmd.Name)
cmd.Reader = os.Stdin
}
if cmd.Writer == nil {
tracef("setting default Writer as os.Stdout (cmd=%[1]q)", cmd.Name)
cmd.Writer = os.Stdout
}
if cmd.ErrWriter == nil {
tracef("setting default ErrWriter as os.Stderr (cmd=%[1]q)", cmd.Name)
cmd.ErrWriter = os.Stderr
}
if cmd.AllowExtFlags {
tracef("visiting all flags given AllowExtFlags=true (cmd=%[1]q)", cmd.Name)
// add global flags added by other packages
flag.VisitAll(func(f *flag.Flag) {
// skip test flags
if !strings.HasPrefix(f.Name, ignoreFlagPrefix) {
cmd.Flags = append(cmd.Flags, &extFlag{f})
}
})
}
for _, subCmd := range cmd.Commands {
tracef("setting sub-command (cmd=%[1]q) parent as self (cmd=%[2]q)", subCmd.Name, cmd.Name)
subCmd.parent = cmd
}
cmd.ensureHelp()
if !cmd.HideVersion && isRoot {
tracef("appending version flag (cmd=%[1]q)", cmd.Name)
cmd.appendFlag(VersionFlag)
}
if cmd.PrefixMatchCommands && cmd.SuggestCommandFunc == nil {
tracef("setting default SuggestCommandFunc (cmd=%[1]q)", cmd.Name)
cmd.SuggestCommandFunc = suggestCommand
}
if isRoot && cmd.EnableShellCompletion || cmd.ConfigureShellCompletionCommand != nil {
completionCommand := buildCompletionCommand(cmd.Name)
if cmd.ShellCompletionCommandName != "" {
tracef(
"setting completion command name (%[1]q) from "+
"cmd.ShellCompletionCommandName (cmd=%[2]q)",
cmd.ShellCompletionCommandName, cmd.Name,
)
completionCommand.Name = cmd.ShellCompletionCommandName
}
tracef("appending completionCommand (cmd=%[1]q)", cmd.Name)
cmd.appendCommand(completionCommand)
if cmd.ConfigureShellCompletionCommand != nil {
cmd.ConfigureShellCompletionCommand(completionCommand)
}
}
tracef("setting command categories (cmd=%[1]q)", cmd.Name)
cmd.categories = newCommandCategories()
for _, subCmd := range cmd.Commands {
cmd.categories.AddCommand(subCmd.Category, subCmd)
}
tracef("sorting command categories (cmd=%[1]q)", cmd.Name)
sort.Sort(cmd.categories.(*commandCategories))
tracef("setting category on mutually exclusive flags (cmd=%[1]q)", cmd.Name)
for _, grp := range cmd.MutuallyExclusiveFlags {
grp.propagateCategory()
}
tracef("setting flag categories (cmd=%[1]q)", cmd.Name)
cmd.flagCategories = newFlagCategoriesFromFlags(cmd.allFlags())
if cmd.Metadata == nil {
tracef("setting default Metadata (cmd=%[1]q)", cmd.Name)
cmd.Metadata = map[string]any{}
}
if len(cmd.SliceFlagSeparator) != 0 {
tracef("setting defaultSliceFlagSeparator from cmd.SliceFlagSeparator (cmd=%[1]q)", cmd.Name)
defaultSliceFlagSeparator = cmd.SliceFlagSeparator
}
tracef("setting disableSliceFlagSeparator from cmd.DisableSliceFlagSeparator (cmd=%[1]q)", cmd.Name)
disableSliceFlagSeparator = cmd.DisableSliceFlagSeparator
cmd.setFlags = map[Flag]struct{}{}
}
func (cmd *Command) setupCommandGraph() {
tracef("setting up command graph (cmd=%[1]q)", cmd.Name)
for _, subCmd := range cmd.Commands {
subCmd.parent = cmd
subCmd.setupSubcommand()
subCmd.setupCommandGraph()
}
}
func (cmd *Command) setupSubcommand() {
tracef("setting up self as sub-command (cmd=%[1]q)", cmd.Name)
cmd.ensureHelp()
tracef("setting command categories (cmd=%[1]q)", cmd.Name)
cmd.categories = newCommandCategories()
for _, subCmd := range cmd.Commands {
cmd.categories.AddCommand(subCmd.Category, subCmd)
}
tracef("sorting command categories (cmd=%[1]q)", cmd.Name)
sort.Sort(cmd.categories.(*commandCategories))
tracef("setting category on mutually exclusive flags (cmd=%[1]q)", cmd.Name)
for _, grp := range cmd.MutuallyExclusiveFlags {
grp.propagateCategory()
}
tracef("setting flag categories (cmd=%[1]q)", cmd.Name)
cmd.flagCategories = newFlagCategoriesFromFlags(cmd.allFlags())
}
func (cmd *Command) hideHelp() bool {
tracef("hide help (cmd=%[1]q)", cmd.Name)
for c := cmd; c != nil; c = c.parent {
if c.HideHelp {
return true
}
}
return false
}
func (cmd *Command) ensureHelp() {
tracef("ensuring help (cmd=%[1]q)", cmd.Name)
helpCommand := buildHelpCommand(true)
if !cmd.hideHelp() {
if cmd.Command(helpCommand.Name) == nil {
if !cmd.HideHelpCommand {
tracef("appending helpCommand (cmd=%[1]q)", cmd.Name)
cmd.appendCommand(helpCommand)
}
}
if HelpFlag != nil {
// TODO need to remove hack
if hf, ok := HelpFlag.(*BoolFlag); ok {
hf.applied = false
hf.hasBeenSet = false
hf.Value = false
hf.value = nil
}
tracef("appending HelpFlag (cmd=%[1]q)", cmd.Name)
cmd.appendFlag(HelpFlag)
}
}
}
package cli
import (
"context"
"embed"
"fmt"
"sort"
"strings"
)
const (
completionCommandName = "completion"
// This flag is supposed to only be used by the completion script itself to generate completions on the fly.
completionFlag = "--generate-shell-completion"
)
type renderCompletion func(cmd *Command, appName string) (string, error)
var (
//go:embed autocomplete
autoCompleteFS embed.FS
shellCompletions = map[string]renderCompletion{
"bash": func(c *Command, appName string) (string, error) {
b, err := autoCompleteFS.ReadFile("autocomplete/bash_autocomplete")
return fmt.Sprintf(string(b), appName), err
},
"zsh": func(c *Command, appName string) (string, error) {
b, err := autoCompleteFS.ReadFile("autocomplete/zsh_autocomplete")
return fmt.Sprintf(string(b), appName), err
},
"fish": func(c *Command, appName string) (string, error) {
return c.Root().ToFishCompletion()
},
"pwsh": func(c *Command, appName string) (string, error) {
b, err := autoCompleteFS.ReadFile("autocomplete/powershell_autocomplete.ps1")
return string(b), err
},
}
)
const completionDescription = `Output shell completion script for bash, zsh, fish, or Powershell.
Source the output to enable completion.
# .bashrc
source <($COMMAND completion bash)
# .zshrc
source <($COMMAND completion zsh)
# fish
$COMMAND completion fish > ~/.config/fish/completions/$COMMAND.fish
# Powershell
Output the script to path/to/autocomplete/$COMMAND.ps1 an run it.
`
func buildCompletionCommand(appName string) *Command {
return &Command{
Name: completionCommandName,
Hidden: true,
Usage: "Output shell completion script for bash, zsh, fish, or Powershell",
Description: strings.ReplaceAll(completionDescription, "$COMMAND", appName),
Action: func(ctx context.Context, cmd *Command) error {
return printShellCompletion(ctx, cmd, appName)
},
}
}
func printShellCompletion(_ context.Context, cmd *Command, appName string) error {
var shells []string
for k := range shellCompletions {
shells = append(shells, k)
}
sort.Strings(shells)
if cmd.Args().Len() == 0 {
return Exit(fmt.Sprintf("no shell provided for completion command. available shells are %+v", shells), 1)
}
s := cmd.Args().First()
renderCompletion, ok := shellCompletions[s]
if !ok {
return Exit(fmt.Sprintf("unknown shell %s, available shells are %+v", s, shells), 1)
}
completionScript, err := renderCompletion(cmd, appName)
if err != nil {
return Exit(err, 1)
}
_, err = cmd.Writer.Write([]byte(completionScript))
if err != nil {
return Exit(err, 1)
}
return nil
}
package cli
import (
"fmt"
"os"
"runtime"
"strings"
)
func prefixFor(name string) (prefix string) {
if len(name) == 1 {
prefix = "-"
} else {
prefix = "--"
}
return
}
// Returns the placeholder, if any, and the unquoted usage string.
func unquoteUsage(usage string) (string, string) {
for i := 0; i < len(usage); i++ {
if usage[i] == '`' {
for j := i + 1; j < len(usage); j++ {
if usage[j] == '`' {
name := usage[i+1 : j]
usage = usage[:i] + name + usage[j+1:]
return name, usage
}
}
break
}
}
return "", usage
}
func prefixedNames(names []string, placeholder string) string {
var prefixed string
for i, name := range names {
if name == "" {
continue
}
prefixed += prefixFor(name) + name
if placeholder != "" {
prefixed += " " + placeholder
}
if i < len(names)-1 {
prefixed += ", "
}
}
return prefixed
}
func envFormat(envVars []string, prefix, sep, suffix string) string {
if len(envVars) > 0 {
return fmt.Sprintf(" [%s%s%s]", prefix, strings.Join(envVars, sep), suffix)
}
return ""
}
func defaultEnvFormat(envVars []string) string {
return envFormat(envVars, "$", ", $", "")
}
func withEnvHint(envVars []string, str string) string {
envText := ""
if runtime.GOOS != "windows" || os.Getenv("PSHOME") != "" {
envText = defaultEnvFormat(envVars)
} else {
envText = envFormat(envVars, "%", "%, %", "%")
}
return str + envText
}
func withFileHint(filePath, str string) string {
fileText := ""
if filePath != "" {
fileText = fmt.Sprintf(" [%s]", filePath)
}
return str + fileText
}
func formatDefault(format string) string {
return " (default: " + format + ")"
}
func stringifyFlag(f Flag) string {
// enforce DocGeneration interface on flags to avoid reflection
df, ok := f.(DocGenerationFlag)
if !ok {
return ""
}
placeholder, usage := unquoteUsage(df.GetUsage())
needsPlaceholder := df.TakesValue()
// if needsPlaceholder is true, placeholder is empty
if needsPlaceholder && placeholder == "" {
// try to get type from flag
if tname := df.TypeName(); tname != "" {
placeholder = tname
} else {
placeholder = defaultPlaceholder
}
}
defaultValueString := ""
// don't print default text for required flags
if rf, ok := f.(RequiredFlag); !ok || !rf.IsRequired() {
isVisible := df.IsDefaultVisible()
if s := df.GetDefaultText(); isVisible && s != "" {
defaultValueString = fmt.Sprintf(formatDefault("%s"), s)
}
}
usageWithDefault := strings.TrimSpace(usage + defaultValueString)
pn := prefixedNames(f.Names(), placeholder)
sliceFlag, ok := f.(DocGenerationMultiValueFlag)
if ok && sliceFlag.IsMultiValueFlag() {
pn = pn + " [ " + pn + " ]"
}
return withEnvHint(df.GetEnvVars(), fmt.Sprintf("%s\t%s", pn, usageWithDefault))
}
package cli
import (
"fmt"
"io"
"os"
"strings"
)
// OsExiter is the function used when the app exits. If not set defaults to os.Exit.
var OsExiter = os.Exit
// ErrWriter is used to write errors to the user. This can be anything
// implementing the io.Writer interface and defaults to os.Stderr.
var ErrWriter io.Writer = os.Stderr
// MultiError is an error that wraps multiple errors.
type MultiError interface {
error
Errors() []error
}
// newMultiError creates a new MultiError. Pass in one or more errors.
func newMultiError(err ...error) MultiError {
ret := multiError(err)
return &ret
}
type multiError []error
// Error implements the error interface.
func (m *multiError) Error() string {
errs := make([]string, len(*m))
for i, err := range *m {
errs[i] = err.Error()
}
return strings.Join(errs, "\n")
}
// Errors returns a copy of the errors slice
func (m *multiError) Errors() []error {
errs := make([]error, len(*m))
copy(errs, *m)
return errs
}
type requiredFlagsErr interface {
error
}
type errRequiredFlags struct {
missingFlags []string
}
func (e *errRequiredFlags) Error() string {
if len(e.missingFlags) == 1 {
return fmt.Sprintf("Required flag %q not set", e.missingFlags[0])
}
joinedMissingFlags := strings.Join(e.missingFlags, ", ")
return fmt.Sprintf("Required flags %q not set", joinedMissingFlags)
}
type mutuallyExclusiveGroup struct {
flag1Name string
flag2Name string
}
func (e *mutuallyExclusiveGroup) Error() string {
return fmt.Sprintf("option %s cannot be set along with option %s", e.flag1Name, e.flag2Name)
}
type mutuallyExclusiveGroupRequiredFlag struct {
flags *MutuallyExclusiveFlags
}
func (e *mutuallyExclusiveGroupRequiredFlag) Error() string {
var missingFlags []string
for _, grpf := range e.flags.Flags {
var grpString []string
for _, f := range grpf {
grpString = append(grpString, f.Names()...)
}
missingFlags = append(missingFlags, strings.Join(grpString, " "))
}
return fmt.Sprintf("one of these flags needs to be provided: %s", strings.Join(missingFlags, ", "))
}
// ErrorFormatter is the interface that will suitably format the error output
type ErrorFormatter interface {
Format(s fmt.State, verb rune)
}
// ExitCoder is the interface checked by `Command` for a custom exit code.
type ExitCoder interface {
error
ExitCode() int
}
type exitError struct {
exitCode int
err error
}
// Exit wraps a message and exit code into an error, which by default is
// handled with a call to os.Exit during default error handling.
//
// This is the simplest way to trigger a non-zero exit code for a Command without
// having to call os.Exit manually. During testing, this behavior can be avoided
// by overriding the ExitErrHandler function on a Command or the package-global
// OsExiter function.
func Exit(message any, exitCode int) ExitCoder {
var err error
switch e := message.(type) {
case ErrorFormatter:
err = fmt.Errorf("%+v", message)
case error:
err = e
default:
err = fmt.Errorf("%+v", message)
}
return &exitError{
err: err,
exitCode: exitCode,
}
}
func (ee *exitError) Error() string {
return ee.err.Error()
}
func (ee *exitError) ExitCode() int {
return ee.exitCode
}
// HandleExitCoder handles errors implementing ExitCoder by printing their
// message and calling OsExiter with the given exit code.
//
// If the given error instead implements MultiError, each error will be checked
// for the ExitCoder interface, and OsExiter will be called with the last exit
// code found, or exit code 1 if no ExitCoder is found.
//
// This function is the default error-handling behavior for a Command.
func HandleExitCoder(err error) {
if err == nil {
return
}
if exitErr, ok := err.(ExitCoder); ok {
if err.Error() != "" {
if _, ok := exitErr.(ErrorFormatter); ok {
_, _ = fmt.Fprintf(ErrWriter, "%+v\n", err)
} else {
_, _ = fmt.Fprintln(ErrWriter, err)
}
}
OsExiter(exitErr.ExitCode())
return
}
if multiErr, ok := err.(MultiError); ok {
code := handleMultiError(multiErr)
OsExiter(code)
return
}
}
func handleMultiError(multiErr MultiError) int {
code := 1
for _, merr := range multiErr.Errors() {
if multiErr2, ok := merr.(MultiError); ok {
code = handleMultiError(multiErr2)
} else if merr != nil {
fmt.Fprintln(ErrWriter, merr)
if exitErr, ok := merr.(ExitCoder); ok {
code = exitErr.ExitCode()
}
}
}
return code
}
package cli
import (
"bytes"
"fmt"
"io"
"strings"
"text/template"
)
// ToFishCompletion creates a fish completion string for the `*Command`
// The function errors if either parsing or writing of the string fails.
func (cmd *Command) ToFishCompletion() (string, error) {
var w bytes.Buffer
if err := cmd.writeFishCompletionTemplate(&w); err != nil {
return "", err
}
return w.String(), nil
}
type fishCommandCompletionTemplate struct {
Command *Command
Completions []string
AllCommands []string
}
func (cmd *Command) writeFishCompletionTemplate(w io.Writer) error {
const name = "cli"
t, err := template.New(name).Parse(FishCompletionTemplate)
if err != nil {
return err
}
// Add global flags
completions := prepareFishFlags(cmd.Name, cmd)
// Add commands and their flags
completions = append(
completions,
prepareFishCommands(cmd.Name, cmd)...,
)
toplevelCommandNames := []string{}
for _, child := range cmd.Commands {
toplevelCommandNames = append(toplevelCommandNames, child.Names()...)
}
return t.ExecuteTemplate(w, name, &fishCommandCompletionTemplate{
Command: cmd,
Completions: completions,
AllCommands: toplevelCommandNames,
})
}
func prepareFishCommands(binary string, parent *Command) []string {
commands := parent.Commands
completions := []string{}
for _, command := range commands {
if !command.Hidden {
var completion strings.Builder
fmt.Fprintf(&completion,
"complete -x -c %s -n '%s' -a '%s'",
binary,
fishSubcommandHelper(binary, parent, commands),
command.Name,
)
if command.Usage != "" {
fmt.Fprintf(&completion,
" -d '%s'",
escapeSingleQuotes(command.Usage))
}
completions = append(completions, completion.String())
}
completions = append(
completions,
prepareFishFlags(binary, command)...,
)
// recursively iterate subcommands
completions = append(
completions,
prepareFishCommands(binary, command)...,
)
}
return completions
}
func prepareFishFlags(binary string, owner *Command) []string {
flags := owner.VisibleFlags()
completions := []string{}
for _, f := range flags {
completion := &strings.Builder{}
fmt.Fprintf(completion,
"complete -c %s -n '%s'",
binary,
fishFlagHelper(binary, owner),
)
fishAddFileFlag(f, completion)
for idx, opt := range f.Names() {
if idx == 0 {
fmt.Fprintf(completion,
" -l %s", strings.TrimSpace(opt),
)
} else {
fmt.Fprintf(completion,
" -s %s", strings.TrimSpace(opt),
)
}
}
if flag, ok := f.(DocGenerationFlag); ok {
if flag.TakesValue() {
completion.WriteString(" -r")
}
if flag.GetUsage() != "" {
fmt.Fprintf(completion,
" -d '%s'",
escapeSingleQuotes(flag.GetUsage()))
}
}
completions = append(completions, completion.String())
}
return completions
}
func fishAddFileFlag(flag Flag, completion *strings.Builder) {
switch f := flag.(type) {
case *StringFlag:
if f.TakesFile {
return
}
case *StringSliceFlag:
if f.TakesFile {
return
}
}
completion.WriteString(" -f")
}
func fishSubcommandHelper(binary string, command *Command, siblings []*Command) string {
fishHelper := fmt.Sprintf("__fish_%s_no_subcommand", binary)
if len(command.Lineage()) > 1 {
var siblingNames []string
for _, sibling := range siblings {
siblingNames = append(siblingNames, sibling.Names()...)
}
ancestry := commandAncestry(command)
fishHelper = fmt.Sprintf(
"%s; and not __fish_seen_subcommand_from %s",
ancestry,
strings.Join(siblingNames, " "),
)
}
return fishHelper
}
func fishFlagHelper(binary string, command *Command) string {
fishHelper := fmt.Sprintf("__fish_%s_no_subcommand", binary)
if len(command.Lineage()) > 1 {
fishHelper = commandAncestry(command)
}
return fishHelper
}
func commandAncestry(command *Command) string {
var ancestry []string
ancestors := command.Lineage()
for i := len(ancestors) - 2; i >= 0; i-- {
ancestry = append(
ancestry,
fmt.Sprintf(
"__fish_seen_subcommand_from %s",
strings.Join(ancestors[i].Names(), " "),
),
)
}
return strings.Join(ancestry, "; and ")
}
func escapeSingleQuotes(input string) string {
return strings.ReplaceAll(input, `'`, `\'`)
}
package cli
import (
"context"
"fmt"
"regexp"
"strings"
"time"
)
const defaultPlaceholder = "value"
var (
defaultSliceFlagSeparator = ","
defaultMapFlagKeyValueSeparator = "="
disableSliceFlagSeparator = false
)
var (
slPfx = fmt.Sprintf("sl:::%d:::", time.Now().UTC().UnixNano())
commaWhitespace = regexp.MustCompile("[, ]+.*")
)
// GenerateShellCompletionFlag enables shell completion
var GenerateShellCompletionFlag Flag = &BoolFlag{
Name: "generate-shell-completion",
Hidden: true,
}
// VersionFlag prints the version for the application
var VersionFlag Flag = &BoolFlag{
Name: "version",
Aliases: []string{"v"},
Usage: "print the version",
HideDefault: true,
Local: true,
}
// HelpFlag prints the help for all commands and subcommands.
// Set to nil to disable the flag. The subcommand
// will still be added unless HideHelp or HideHelpCommand is set to true.
var HelpFlag Flag = &BoolFlag{
Name: "help",
Aliases: []string{"h"},
Usage: "show help",
HideDefault: true,
Local: true,
}
// FlagStringer converts a flag definition to a string. This is used by help
// to display a flag.
var FlagStringer FlagStringFunc = stringifyFlag
// Serializer is used to circumvent the limitations of flag.FlagSet.Set
type Serializer interface {
Serialize() string
}
// FlagNamePrefixer converts a full flag name and its placeholder into the help
// message flag prefix. This is used by the default FlagStringer.
var FlagNamePrefixer FlagNamePrefixFunc = prefixedNames
// FlagEnvHinter annotates flag help message with the environment variable
// details. This is used by the default FlagStringer.
var FlagEnvHinter FlagEnvHintFunc = withEnvHint
// FlagFileHinter annotates flag help message with the environment variable
// details. This is used by the default FlagStringer.
var FlagFileHinter FlagFileHintFunc = withFileHint
// FlagsByName is a slice of Flag.
type FlagsByName []Flag
func (f FlagsByName) Len() int {
return len(f)
}
func (f FlagsByName) Less(i, j int) bool {
if len(f[j].Names()) == 0 {
return false
} else if len(f[i].Names()) == 0 {
return true
}
return lexicographicLess(f[i].Names()[0], f[j].Names()[0])
}
func (f FlagsByName) Swap(i, j int) {
f[i], f[j] = f[j], f[i]
}
// ActionableFlag is an interface that wraps Flag interface and RunAction operation.
type ActionableFlag interface {
RunAction(context.Context, *Command) error
}
// Flag is a common interface related to parsing flags in cli.
// For more advanced flag parsing techniques, it is recommended that
// this interface be implemented.
type Flag interface {
fmt.Stringer
// Retrieve the value of the Flag
Get() any
// Lifecycle methods.
// flag callback prior to parsing
PreParse() error
// flag callback post parsing
PostParse() error
// Apply Flag settings to the given flag set
Set(string, string) error
// All possible names for this flag
Names() []string
// Whether the flag has been set or not
IsSet() bool
}
// RequiredFlag is an interface that allows us to mark flags as required
// it allows flags required flags to be backwards compatible with the Flag interface
type RequiredFlag interface {
// whether the flag is a required flag or not
IsRequired() bool
}
// DocGenerationFlag is an interface that allows documentation generation for the flag
type DocGenerationFlag interface {
// TakesValue returns true if the flag takes a value, otherwise false
TakesValue() bool
// GetUsage returns the usage string for the flag
GetUsage() string
// GetValue returns the flags value as string representation and an empty
// string if the flag takes no value at all.
GetValue() string
// GetDefaultText returns the default text for this flag
GetDefaultText() string
// GetEnvVars returns the env vars for this flag
GetEnvVars() []string
// IsDefaultVisible returns whether the default value should be shown in
// help text
IsDefaultVisible() bool
// TypeName to detect if a flag is a string, bool, etc.
TypeName() string
}
// DocGenerationMultiValueFlag extends DocGenerationFlag for slice/map based flags.
type DocGenerationMultiValueFlag interface {
DocGenerationFlag
// IsMultiValueFlag returns true for flags that can be given multiple times.
IsMultiValueFlag() bool
}
// Countable is an interface to enable detection of flag values which support
// repetitive flags
type Countable interface {
Count() int
}
// VisibleFlag is an interface that allows to check if a flag is visible
type VisibleFlag interface {
// IsVisible returns true if the flag is not hidden, otherwise false
IsVisible() bool
}
// CategorizableFlag is an interface that allows us to potentially
// use a flag in a categorized representation.
type CategorizableFlag interface {
// Returns the category of the flag
GetCategory() string
// Sets the category of the flag
SetCategory(string)
}
// LocalFlag is an interface to enable detection of flags which are local
// to current command
type LocalFlag interface {
IsLocal() bool
}
func visibleFlags(fl []Flag) []Flag {
var visible []Flag
for _, f := range fl {
if vf, ok := f.(VisibleFlag); ok && vf.IsVisible() {
visible = append(visible, f)
}
}
return visible
}
func FlagNames(name string, aliases []string) []string {
var ret []string
for _, part := range append([]string{name}, aliases...) {
// v1 -> v2 migration warning zone:
// Strip off anything after the first found comma or space, which
// *hopefully* makes it a tiny bit more obvious that unexpected behavior is
// caused by using the v1 form of stringly typed "Name".
ret = append(ret, commaWhitespace.ReplaceAllString(part, ""))
}
return ret
}
func hasFlag(flags []Flag, fl Flag) bool {
for _, existing := range flags {
if fl == existing {
return true
}
}
return false
}
func flagSplitMultiValues(val string) []string {
if disableSliceFlagSeparator {
return []string{val}
}
return strings.Split(val, defaultSliceFlagSeparator)
}
package cli
import (
"errors"
"strconv"
)
type BoolFlag = FlagBase[bool, BoolConfig, boolValue]
// BoolConfig defines the configuration for bool flags
type BoolConfig struct {
Count *int
}
// boolValue needs to implement the boolFlag internal interface in flag
// to be able to capture bool fields and values
//
// type boolFlag interface {
// Value
// IsBoolFlag() bool
// }
type boolValue struct {
destination *bool
count *int
}
func (cmd *Command) Bool(name string) bool {
if v, ok := cmd.Value(name).(bool); ok {
tracef("bool available for flag name %[1]q with value=%[2]v (cmd=%[3]q)", name, v, cmd.Name)
return v
}
tracef("bool NOT available for flag name %[1]q (cmd=%[2]q)", name, cmd.Name)
return false
}
// Below functions are to satisfy the ValueCreator interface
// Create creates the bool value
func (b boolValue) Create(val bool, p *bool, c BoolConfig) Value {
*p = val
if c.Count == nil {
c.Count = new(int)
}
return &boolValue{
destination: p,
count: c.Count,
}
}
// ToString formats the bool value
func (b boolValue) ToString(value bool) string {
return strconv.FormatBool(value)
}
// Below functions are to satisfy the flag.Value interface
func (b *boolValue) Set(s string) error {
v, err := strconv.ParseBool(s)
if err != nil {
err = errors.New("parse error")
return err
}
*b.destination = v
if b.count != nil {
*b.count = *b.count + 1
}
return err
}
func (b *boolValue) Get() interface{} { return *b.destination }
func (b *boolValue) String() string {
return strconv.FormatBool(*b.destination)
}
func (b *boolValue) IsBoolFlag() bool { return true }
func (b *boolValue) Count() int {
return *b.count
}
package cli
import (
"context"
"fmt"
"slices"
"strings"
)
var DefaultInverseBoolPrefix = "no-"
type BoolWithInverseFlag struct {
Name string `json:"name"` // name of the flag
Category string `json:"category"` // category of the flag, if any
DefaultText string `json:"defaultText"` // default text of the flag for usage purposes
HideDefault bool `json:"hideDefault"` // whether to hide the default value in output
Usage string `json:"usage"` // usage string for help output
Sources ValueSourceChain `json:"-"` // sources to load flag value from
Required bool `json:"required"` // whether the flag is required or not
Hidden bool `json:"hidden"` // whether to hide the flag in help output
Local bool `json:"local"` // whether the flag needs to be applied to subcommands as well
Value bool `json:"defaultValue"` // default value for this flag if not set by from any source
Destination *bool `json:"-"` // destination pointer for value when set
Aliases []string `json:"aliases"` // Aliases that are allowed for this flag
TakesFile bool `json:"takesFileArg"` // whether this flag takes a file argument, mainly for shell completion purposes
Action func(context.Context, *Command, bool) error `json:"-"` // Action callback to be called when flag is set
OnlyOnce bool `json:"onlyOnce"` // whether this flag can be duplicated on the command line
Validator func(bool) error `json:"-"` // custom function to validate this flag value
ValidateDefaults bool `json:"validateDefaults"` // whether to validate defaults or not
Config BoolConfig `json:"config"` // Additional/Custom configuration associated with this flag type
InversePrefix string `json:"invPrefix"` // The prefix used to indicate a negative value. Default: `env` becomes `no-env`
// unexported fields for internal use
count int // number of times the flag has been set
hasBeenSet bool // whether the flag has been set from env or file
applied bool // whether the flag has been applied to a flag set already
value Value // value representing this flag's value
pset bool
nset bool
}
func (bif *BoolWithInverseFlag) IsSet() bool {
return bif.hasBeenSet
}
func (bif *BoolWithInverseFlag) Get() any {
return bif.value.Get()
}
func (bif *BoolWithInverseFlag) RunAction(ctx context.Context, cmd *Command) error {
if bif.Action != nil {
return bif.Action(ctx, cmd, bif.Get().(bool))
}
return nil
}
func (bif *BoolWithInverseFlag) IsLocal() bool {
return bif.Local
}
func (bif *BoolWithInverseFlag) inversePrefix() string {
if bif.InversePrefix == "" {
bif.InversePrefix = DefaultInverseBoolPrefix
}
return bif.InversePrefix
}
func (bif *BoolWithInverseFlag) PreParse() error {
count := bif.Config.Count
if count == nil {
count = &bif.count
}
dest := bif.Destination
if dest == nil {
dest = new(bool)
}
*dest = bif.Value
bif.value = &boolValue{
destination: dest,
count: count,
}
// Validate the given default or values set from external sources as well
if bif.Validator != nil && bif.ValidateDefaults {
if err := bif.Validator(bif.value.Get().(bool)); err != nil {
return err
}
}
bif.applied = true
return nil
}
func (bif *BoolWithInverseFlag) PostParse() error {
tracef("postparse (flag=%[1]q)", bif.Name)
if !bif.hasBeenSet {
if val, source, found := bif.Sources.LookupWithSource(); found {
if val == "" {
val = "false"
}
if err := bif.Set(bif.Name, val); err != nil {
return fmt.Errorf(
"could not parse %[1]q as %[2]T value from %[3]s for flag %[4]s: %[5]s",
val, bif.Value, source, bif.Name, err,
)
}
bif.hasBeenSet = true
}
}
return nil
}
func (bif *BoolWithInverseFlag) Set(name, val string) error {
if bif.count > 0 && bif.OnlyOnce {
return fmt.Errorf("cant duplicate this flag")
}
bif.hasBeenSet = true
if slices.Contains(append([]string{bif.Name}, bif.Aliases...), name) {
if bif.nset {
return fmt.Errorf("cannot set both flags `--%s` and `--%s`", bif.Name, bif.inversePrefix()+bif.Name)
}
if err := bif.value.Set(val); err != nil {
return err
}
bif.pset = true
} else {
if bif.pset {
return fmt.Errorf("cannot set both flags `--%s` and `--%s`", bif.Name, bif.inversePrefix()+bif.Name)
}
if err := bif.value.Set("false"); err != nil {
return err
}
bif.nset = true
}
if bif.Validator != nil {
return bif.Validator(bif.value.Get().(bool))
}
return nil
}
func (bif *BoolWithInverseFlag) Names() []string {
names := append([]string{bif.Name}, bif.Aliases...)
for _, name := range names {
names = append(names, bif.inversePrefix()+name)
}
return names
}
func (bif *BoolWithInverseFlag) IsRequired() bool {
return bif.Required
}
func (bif *BoolWithInverseFlag) IsVisible() bool {
return !bif.Hidden
}
// String implements the standard Stringer interface.
//
// Example for BoolFlag{Name: "env"}
// --[no-]env (default: false)
func (bif *BoolWithInverseFlag) String() string {
out := FlagStringer(bif)
i := strings.Index(out, "\t")
prefix := "--"
// single character flags are prefixed with `-` instead of `--`
if len(bif.Name) == 1 {
prefix = "-"
}
return fmt.Sprintf("%s[%s]%s%s", prefix, bif.inversePrefix(), bif.Name, out[i:])
}
// IsBoolFlag returns whether the flag doesnt need to accept args
func (bif *BoolWithInverseFlag) IsBoolFlag() bool {
return true
}
// Count returns the number of times this flag has been invoked
func (bif *BoolWithInverseFlag) Count() int {
return bif.count
}
// GetDefaultText returns the default text for this flag
func (bif *BoolWithInverseFlag) GetDefaultText() string {
if bif.Required {
return bif.DefaultText
}
return boolValue{}.ToString(bif.Value)
}
// GetCategory returns the category of the flag
func (bif *BoolWithInverseFlag) GetCategory() string {
return bif.Category
}
func (bif *BoolWithInverseFlag) SetCategory(c string) {
bif.Category = c
}
// GetUsage returns the usage string for the flag
func (bif *BoolWithInverseFlag) GetUsage() string {
return bif.Usage
}
// GetEnvVars returns the env vars for this flag
func (bif *BoolWithInverseFlag) GetEnvVars() []string {
return bif.Sources.EnvKeys()
}
// GetValue returns the flags value as string representation and an empty
// string if the flag takes no value at all.
func (bif *BoolWithInverseFlag) GetValue() string {
return ""
}
func (bif *BoolWithInverseFlag) TakesValue() bool {
return false
}
// IsDefaultVisible returns true if the flag is not hidden, otherwise false
func (bif *BoolWithInverseFlag) IsDefaultVisible() bool {
return !bif.HideDefault
}
// TypeName is used for stringify/docs. For bool its a no-op
func (bif *BoolWithInverseFlag) TypeName() string {
return "bool"
}
package cli
import (
"fmt"
"time"
)
type DurationFlag = FlagBase[time.Duration, NoConfig, durationValue]
// -- time.Duration Value
type durationValue time.Duration
// Below functions are to satisfy the ValueCreator interface
func (d durationValue) Create(val time.Duration, p *time.Duration, c NoConfig) Value {
*p = val
return (*durationValue)(p)
}
func (d durationValue) ToString(val time.Duration) string {
return fmt.Sprintf("%v", val)
}
// Below functions are to satisfy the flag.Value interface
func (d *durationValue) Set(s string) error {
v, err := time.ParseDuration(s)
if err != nil {
return err
}
*d = durationValue(v)
return err
}
func (d *durationValue) Get() any { return time.Duration(*d) }
func (d *durationValue) String() string { return (*time.Duration)(d).String() }
func (cmd *Command) Duration(name string) time.Duration {
if v, ok := cmd.Value(name).(time.Duration); ok {
tracef("duration available for flag name %[1]q with value=%[2]v (cmd=%[3]q)", name, v, cmd.Name)
return v
}
tracef("bool NOT available for flag name %[1]q (cmd=%[2]q)", name, cmd.Name)
return 0
}
package cli
import "flag"
type extFlag struct {
f *flag.Flag
}
func (e *extFlag) PreParse() error {
if e.f.DefValue != "" {
return e.Set("", e.f.DefValue)
}
return nil
}
func (e *extFlag) PostParse() error {
return nil
}
func (e *extFlag) Set(_ string, val string) error {
return e.f.Value.Set(val)
}
func (e *extFlag) Get() any {
return e.f.Value.(flag.Getter).Get()
}
func (e *extFlag) Names() []string {
return []string{e.f.Name}
}
func (e *extFlag) IsSet() bool {
return false
}
func (e *extFlag) String() string {
return FlagStringer(e)
}
func (e *extFlag) IsVisible() bool {
return true
}
func (e *extFlag) TakesValue() bool {
return false
}
func (e *extFlag) GetUsage() string {
return e.f.Usage
}
func (e *extFlag) GetValue() string {
return e.f.Value.String()
}
func (e *extFlag) GetDefaultText() string {
return e.f.DefValue
}
func (e *extFlag) GetEnvVars() []string {
return nil
}
package cli
import (
"strconv"
"unsafe"
)
type (
FloatFlag = FlagBase[float64, NoConfig, floatValue[float64]]
Float32Flag = FlagBase[float32, NoConfig, floatValue[float32]]
Float64Flag = FlagBase[float64, NoConfig, floatValue[float64]]
)
// -- float Value
type floatValue[T float32 | float64] struct {
val *T
}
// Below functions are to satisfy the ValueCreator interface
func (f floatValue[T]) Create(val T, p *T, c NoConfig) Value {
*p = val
return &floatValue[T]{val: p}
}
func (f floatValue[T]) ToString(b T) string {
return strconv.FormatFloat(float64(b), 'g', -1, int(unsafe.Sizeof(T(0))*8))
}
// Below functions are to satisfy the flag.Value interface
func (f *floatValue[T]) Set(s string) error {
v, err := strconv.ParseFloat(s, int(unsafe.Sizeof(T(0))*8))
if err != nil {
return err
}
*f.val = T(v)
return nil
}
func (f *floatValue[T]) Get() any { return *f.val }
func (f *floatValue[T]) String() string {
return strconv.FormatFloat(float64(*f.val), 'g', -1, int(unsafe.Sizeof(T(0))*8))
}
// Float looks up the value of a local FloatFlag, returns
// 0 if not found
func (cmd *Command) Float(name string) float64 {
return getFloat[float64](cmd, name)
}
// Float32 looks up the value of a local Float32Flag, returns
// 0 if not found
func (cmd *Command) Float32(name string) float32 {
return getFloat[float32](cmd, name)
}
// Float64 looks up the value of a local Float64Flag, returns
// 0 if not found
func (cmd *Command) Float64(name string) float64 {
return getFloat[float64](cmd, name)
}
func getFloat[T float32 | float64](cmd *Command, name string) T {
if v, ok := cmd.Value(name).(T); ok {
tracef("float available for flag name %[1]q with value=%[2]v (cmd=%[3]q)", name, v, cmd.Name)
return v
}
tracef("float NOT available for flag name %[1]q (cmd=%[2]q)", name, cmd.Name)
return 0
}
package cli
type (
FloatSlice = SliceBase[float64, NoConfig, floatValue[float64]]
Float32Slice = SliceBase[float32, NoConfig, floatValue[float32]]
Float64Slice = SliceBase[float64, NoConfig, floatValue[float64]]
FloatSliceFlag = FlagBase[[]float64, NoConfig, FloatSlice]
Float32SliceFlag = FlagBase[[]float32, NoConfig, Float32Slice]
Float64SliceFlag = FlagBase[[]float64, NoConfig, Float64Slice]
)
var (
NewFloatSlice = NewSliceBase[float64, NoConfig, floatValue[float64]]
NewFloat32Slice = NewSliceBase[float32, NoConfig, floatValue[float32]]
NewFloat64Slice = NewSliceBase[float64, NoConfig, floatValue[float64]]
)
// FloatSlice looks up the value of a local FloatSliceFlag, returns
// nil if not found
func (cmd *Command) FloatSlice(name string) []float64 {
return getNumberSlice[float64](cmd, name)
}
// Float32Slice looks up the value of a local Float32Slice, returns
// nil if not found
func (cmd *Command) Float32Slice(name string) []float32 {
return getNumberSlice[float32](cmd, name)
}
// Float64Slice looks up the value of a local Float64SliceFlag, returns
// nil if not found
func (cmd *Command) Float64Slice(name string) []float64 {
return getNumberSlice[float64](cmd, name)
}
package cli
type GenericFlag = FlagBase[Value, NoConfig, genericValue]
// -- Value Value
type genericValue struct {
val Value
}
// Below functions are to satisfy the ValueCreator interface
func (f genericValue) Create(val Value, p *Value, c NoConfig) Value {
*p = val
return &genericValue{
val: *p,
}
}
func (f genericValue) ToString(b Value) string {
if b != nil {
return b.String()
}
return ""
}
// Below functions are to satisfy the flag.Value interface
func (f *genericValue) Set(s string) error {
if f.val != nil {
return f.val.Set(s)
}
return nil
}
func (f *genericValue) Get() any {
if f.val != nil {
return f.val.Get()
}
return nil
}
func (f *genericValue) String() string {
if f.val != nil {
return f.val.String()
}
return ""
}
func (f *genericValue) IsBoolFlag() bool {
if f.val == nil {
return false
}
bf, ok := f.val.(boolFlag)
return ok && bf.IsBoolFlag()
}
// Generic looks up the value of a local GenericFlag, returns
// nil if not found
func (cmd *Command) Generic(name string) Value {
if v, ok := cmd.Value(name).(Value); ok {
tracef("generic available for flag name %[1]q with value=%[2]v (cmd=%[3]q)", name, v, cmd.Name)
return v
}
tracef("generic NOT available for flag name %[1]q (cmd=%[2]q)", name, cmd.Name)
return nil
}
package cli
import (
"context"
"flag"
"fmt"
"reflect"
"strings"
)
// Value represents a value as used by cli.
// For now it implements the golang flag.Value interface
type Value interface {
flag.Value
flag.Getter
}
type boolFlag interface {
IsBoolFlag() bool
}
// ValueCreator is responsible for creating a flag.Value emulation
// as well as custom formatting
//
// T specifies the type
// C specifies the config for the type
type ValueCreator[T any, C any] interface {
Create(T, *T, C) Value
ToString(T) string
}
// NoConfig is for flags which dont need a custom configuration
type NoConfig struct{}
// FlagBase [T,C,VC] is a generic flag base which can be used
// as a boilerplate to implement the most common interfaces
// used by urfave/cli.
//
// T specifies the type
// C specifies the configuration required(if any for that flag type)
// VC specifies the value creator which creates the flag.Value emulation
type FlagBase[T any, C any, VC ValueCreator[T, C]] struct {
Name string `json:"name"` // name of the flag
Category string `json:"category"` // category of the flag, if any
DefaultText string `json:"defaultText"` // default text of the flag for usage purposes
HideDefault bool `json:"hideDefault"` // whether to hide the default value in output
Usage string `json:"usage"` // usage string for help output
Sources ValueSourceChain `json:"-"` // sources to load flag value from
Required bool `json:"required"` // whether the flag is required or not
Hidden bool `json:"hidden"` // whether to hide the flag in help output
Local bool `json:"local"` // whether the flag needs to be applied to subcommands as well
Value T `json:"defaultValue"` // default value for this flag if not set by from any source
Destination *T `json:"-"` // destination pointer for value when set
Aliases []string `json:"aliases"` // Aliases that are allowed for this flag
TakesFile bool `json:"takesFileArg"` // whether this flag takes a file argument, mainly for shell completion purposes
Action func(context.Context, *Command, T) error `json:"-"` // Action callback to be called when flag is set
Config C `json:"config"` // Additional/Custom configuration associated with this flag type
OnlyOnce bool `json:"onlyOnce"` // whether this flag can be duplicated on the command line
Validator func(T) error `json:"-"` // custom function to validate this flag value
ValidateDefaults bool `json:"validateDefaults"` // whether to validate defaults or not
// unexported fields for internal use
count int // number of times the flag has been set
hasBeenSet bool // whether the flag has been set from env or file
applied bool // whether the flag has been applied to a flag set already
creator VC // value creator for this flag type
value Value // value representing this flag's value
}
// GetValue returns the flags value as string representation and an empty
// string if the flag takes no value at all.
func (f *FlagBase[T, C, V]) GetValue() string {
if !f.TakesValue() {
return ""
}
return fmt.Sprintf("%v", f.Value)
}
// TypeName returns the type of the flag.
func (f *FlagBase[T, C, V]) TypeName() string {
ty := reflect.TypeOf(f.Value)
if ty == nil {
return ""
}
// convert the typename to generic type
convertToGenericType := func(name string) string {
prefixMap := map[string]string{
"float": "float",
"int": "int",
"uint": "uint",
}
for prefix, genericType := range prefixMap {
if strings.HasPrefix(name, prefix) {
return genericType
}
}
return strings.ToLower(name)
}
switch ty.Kind() {
// if it is a Slice, then return the slice's inner type. Will nested slices be used in the future?
case reflect.Slice:
elemType := ty.Elem()
return convertToGenericType(elemType.Name())
// if it is a Map, then return the map's key and value types.
case reflect.Map:
keyType := ty.Key()
valueType := ty.Elem()
return fmt.Sprintf("%s=%s", convertToGenericType(keyType.Name()), convertToGenericType(valueType.Name()))
default:
return convertToGenericType(ty.Name())
}
}
// PostParse populates the flag given the flag set and environment
func (f *FlagBase[T, C, V]) PostParse() error {
tracef("postparse (flag=%[1]q)", f.Name)
if !f.hasBeenSet {
if val, source, found := f.Sources.LookupWithSource(); found {
if val != "" || reflect.TypeOf(f.Value).Kind() == reflect.String {
if err := f.Set(f.Name, val); err != nil {
return fmt.Errorf(
"could not parse %[1]q as %[2]T value from %[3]s for flag %[4]s: %[5]s",
val, f.Value, source, f.Name, err,
)
}
} else if val == "" && reflect.TypeOf(f.Value).Kind() == reflect.Bool {
_ = f.Set(f.Name, "false")
}
f.hasBeenSet = true
}
}
return nil
}
func (f *FlagBase[T, C, V]) PreParse() error {
newVal := f.Value
if f.Destination == nil {
f.value = f.creator.Create(newVal, new(T), f.Config)
} else {
f.value = f.creator.Create(newVal, f.Destination, f.Config)
}
// Validate the given default or values set from external sources as well
if f.Validator != nil && f.ValidateDefaults {
if err := f.Validator(f.value.Get().(T)); err != nil {
return err
}
}
f.applied = true
return nil
}
// Set applies given value from string
func (f *FlagBase[T, C, V]) Set(_ string, val string) error {
tracef("apply (flag=%[1]q)", f.Name)
// TODO move this phase into a separate flag initialization function
// if flag has been applied previously then it would have already been set
// from env or file. So no need to apply the env set again. However
// lots of units tests prior to persistent flags assumed that the
// flag can be applied to different flag sets multiple times while still
// keeping the env set.
if !f.applied || f.Local {
if err := f.PreParse(); err != nil {
return err
}
f.applied = true
}
if f.count == 1 && f.OnlyOnce {
return fmt.Errorf("cant duplicate this flag")
}
f.count++
if err := f.value.Set(val); err != nil {
return err
}
f.hasBeenSet = true
if f.Validator != nil {
if err := f.Validator(f.value.Get().(T)); err != nil {
return err
}
}
return nil
}
func (f *FlagBase[T, C, V]) Get() any {
if f.value != nil {
return f.value.Get()
}
return f.Value
}
// IsDefaultVisible returns true if the flag is not hidden, otherwise false
func (f *FlagBase[T, C, V]) IsDefaultVisible() bool {
return !f.HideDefault
}
// String returns a readable representation of this value (for usage defaults)
func (f *FlagBase[T, C, V]) String() string {
return FlagStringer(f)
}
// IsSet returns whether or not the flag has been set through env or file
func (f *FlagBase[T, C, V]) IsSet() bool {
return f.hasBeenSet
}
// Names returns the names of the flag
func (f *FlagBase[T, C, V]) Names() []string {
return FlagNames(f.Name, f.Aliases)
}
// IsRequired returns whether or not the flag is required
func (f *FlagBase[T, C, V]) IsRequired() bool {
return f.Required
}
// IsVisible returns true if the flag is not hidden, otherwise false
func (f *FlagBase[T, C, V]) IsVisible() bool {
return !f.Hidden
}
// GetCategory returns the category of the flag
func (f *FlagBase[T, C, V]) GetCategory() string {
return f.Category
}
func (f *FlagBase[T, C, V]) SetCategory(c string) {
f.Category = c
}
// GetUsage returns the usage string for the flag
func (f *FlagBase[T, C, V]) GetUsage() string {
return f.Usage
}
// GetEnvVars returns the env vars for this flag
func (f *FlagBase[T, C, V]) GetEnvVars() []string {
return f.Sources.EnvKeys()
}
// TakesValue returns true if the flag takes a value, otherwise false
func (f *FlagBase[T, C, V]) TakesValue() bool {
var t T
return reflect.TypeOf(t) == nil || reflect.TypeOf(t).Kind() != reflect.Bool
}
// GetDefaultText returns the default text for this flag
func (f *FlagBase[T, C, V]) GetDefaultText() string {
if f.DefaultText != "" {
return f.DefaultText
}
var v V
return v.ToString(f.Value)
}
// RunAction executes flag action if set
func (f *FlagBase[T, C, V]) RunAction(ctx context.Context, cmd *Command) error {
if f.Action != nil {
return f.Action(ctx, cmd, f.value.Get().(T))
}
return nil
}
// IsMultiValueFlag returns true if the value type T can take multiple
// values from cmd line. This is true for slice and map type flags
func (f *FlagBase[T, C, VC]) IsMultiValueFlag() bool {
// TBD how to specify
if reflect.TypeOf(f.Value) == nil {
return false
}
kind := reflect.TypeOf(f.Value).Kind()
return kind == reflect.Slice || kind == reflect.Map
}
// IsLocal returns false if flag needs to be persistent across subcommands
func (f *FlagBase[T, C, VC]) IsLocal() bool {
return f.Local
}
// IsBoolFlag returns whether the flag doesnt need to accept args
func (f *FlagBase[T, C, VC]) IsBoolFlag() bool {
bf, ok := f.value.(boolFlag)
return ok && bf.IsBoolFlag()
}
// Count returns the number of times this flag has been invoked
func (f *FlagBase[T, C, VC]) Count() int {
return f.count
}
package cli
import (
"strconv"
"unsafe"
)
type (
IntFlag = FlagBase[int, IntegerConfig, intValue[int]]
Int8Flag = FlagBase[int8, IntegerConfig, intValue[int8]]
Int16Flag = FlagBase[int16, IntegerConfig, intValue[int16]]
Int32Flag = FlagBase[int32, IntegerConfig, intValue[int32]]
Int64Flag = FlagBase[int64, IntegerConfig, intValue[int64]]
)
// IntegerConfig is the configuration for all integer type flags
type IntegerConfig struct {
Base int
}
// -- int Value
type intValue[T int | int8 | int16 | int32 | int64] struct {
val *T
base int
}
// Below functions are to satisfy the ValueCreator interface
func (i intValue[T]) Create(val T, p *T, c IntegerConfig) Value {
*p = val
return &intValue[T]{
val: p,
base: c.Base,
}
}
func (i intValue[T]) ToString(b T) string {
if i.base == 0 {
i.base = 10
}
return strconv.FormatInt(int64(b), i.base)
}
// Below functions are to satisfy the flag.Value interface
func (i *intValue[T]) Set(s string) error {
v, err := strconv.ParseInt(s, i.base, int(unsafe.Sizeof(T(0))*8))
if err != nil {
return err
}
*i.val = T(v)
return err
}
func (i *intValue[T]) Get() any { return *i.val }
func (i *intValue[T]) String() string {
base := i.base
if base == 0 {
base = 10
}
return strconv.FormatInt(int64(*i.val), base)
}
// Int looks up the value of a local Int64Flag, returns
// 0 if not found
func (cmd *Command) Int(name string) int {
return getInt[int](cmd, name)
}
// Int8 looks up the value of a local Int8Flag, returns
// 0 if not found
func (cmd *Command) Int8(name string) int8 {
return getInt[int8](cmd, name)
}
// Int16 looks up the value of a local Int16Flag, returns
// 0 if not found
func (cmd *Command) Int16(name string) int16 {
return getInt[int16](cmd, name)
}
// Int32 looks up the value of a local Int32Flag, returns
// 0 if not found
func (cmd *Command) Int32(name string) int32 {
return getInt[int32](cmd, name)
}
// Int64 looks up the value of a local Int64Flag, returns
// 0 if not found
func (cmd *Command) Int64(name string) int64 {
return getInt[int64](cmd, name)
}
func getInt[T int | int8 | int16 | int32 | int64](cmd *Command, name string) T {
if v, ok := cmd.Value(name).(T); ok {
tracef("int available for flag name %[1]q with value=%[2]v (cmd=%[3]q)", name, v, cmd.Name)
return v
}
tracef("int NOT available for flag name %[1]q (cmd=%[2]q)", name, cmd.Name)
return 0
}
package cli
type (
IntSlice = SliceBase[int, IntegerConfig, intValue[int]]
Int8Slice = SliceBase[int8, IntegerConfig, intValue[int8]]
Int16Slice = SliceBase[int16, IntegerConfig, intValue[int16]]
Int32Slice = SliceBase[int32, IntegerConfig, intValue[int32]]
Int64Slice = SliceBase[int64, IntegerConfig, intValue[int64]]
IntSliceFlag = FlagBase[[]int, IntegerConfig, IntSlice]
Int8SliceFlag = FlagBase[[]int8, IntegerConfig, Int8Slice]
Int16SliceFlag = FlagBase[[]int16, IntegerConfig, Int16Slice]
Int32SliceFlag = FlagBase[[]int32, IntegerConfig, Int32Slice]
Int64SliceFlag = FlagBase[[]int64, IntegerConfig, Int64Slice]
)
var (
NewIntSlice = NewSliceBase[int, IntegerConfig, intValue[int]]
NewInt8Slice = NewSliceBase[int8, IntegerConfig, intValue[int8]]
NewInt16Slice = NewSliceBase[int16, IntegerConfig, intValue[int16]]
NewInt32Slice = NewSliceBase[int32, IntegerConfig, intValue[int32]]
NewInt64Slice = NewSliceBase[int64, IntegerConfig, intValue[int64]]
)
// IntSlice looks up the value of a local IntSliceFlag, returns
// nil if not found
func (cmd *Command) IntSlice(name string) []int {
return getNumberSlice[int](cmd, name)
}
// Int8Slice looks up the value of a local Int8SliceFlag, returns
// nil if not found
func (cmd *Command) Int8Slice(name string) []int8 {
return getNumberSlice[int8](cmd, name)
}
// Int16Slice looks up the value of a local Int16SliceFlag, returns
// nil if not found
func (cmd *Command) Int16Slice(name string) []int16 {
return getNumberSlice[int16](cmd, name)
}
// Int32Slice looks up the value of a local Int32SliceFlag, returns
// nil if not found
func (cmd *Command) Int32Slice(name string) []int32 {
return getNumberSlice[int32](cmd, name)
}
// Int64Slice looks up the value of a local Int64SliceFlag, returns
// nil if not found
func (cmd *Command) Int64Slice(name string) []int64 {
return getNumberSlice[int64](cmd, name)
}
package cli
import (
"encoding/json"
"fmt"
"reflect"
"sort"
"strings"
)
// MapBase wraps map[string]T to satisfy flag.Value
type MapBase[T any, C any, VC ValueCreator[T, C]] struct {
dict *map[string]T
hasBeenSet bool
value Value
}
func (i MapBase[T, C, VC]) Create(val map[string]T, p *map[string]T, c C) Value {
*p = map[string]T{}
for k, v := range val {
(*p)[k] = v
}
var t T
np := new(T)
var vc VC
return &MapBase[T, C, VC]{
dict: p,
value: vc.Create(t, np, c),
}
}
// NewMapBase makes a *MapBase with default values
func NewMapBase[T any, C any, VC ValueCreator[T, C]](defaults map[string]T) *MapBase[T, C, VC] {
return &MapBase[T, C, VC]{
dict: &defaults,
}
}
// Set parses the value and appends it to the list of values
func (i *MapBase[T, C, VC]) Set(value string) error {
if !i.hasBeenSet {
*i.dict = map[string]T{}
i.hasBeenSet = true
}
if strings.HasPrefix(value, slPfx) {
// Deserializing assumes overwrite
_ = json.Unmarshal([]byte(strings.Replace(value, slPfx, "", 1)), &i.dict)
i.hasBeenSet = true
return nil
}
for _, item := range flagSplitMultiValues(value) {
key, value, ok := strings.Cut(item, defaultMapFlagKeyValueSeparator)
if !ok {
return fmt.Errorf("item %q is missing separator %q", item, defaultMapFlagKeyValueSeparator)
}
if err := i.value.Set(value); err != nil {
return err
}
(*i.dict)[key] = i.value.Get().(T)
}
return nil
}
// String returns a readable representation of this value (for usage defaults)
func (i *MapBase[T, C, VC]) String() string {
v := i.Value()
var t T
if reflect.TypeOf(t).Kind() == reflect.String {
return fmt.Sprintf("%v", v)
}
return fmt.Sprintf("%T{%s}", v, i.ToString(v))
}
// Serialize allows MapBase to fulfill Serializer
func (i *MapBase[T, C, VC]) Serialize() string {
jsonBytes, _ := json.Marshal(i.dict)
return fmt.Sprintf("%s%s", slPfx, string(jsonBytes))
}
// Value returns the mapping of values set by this flag
func (i *MapBase[T, C, VC]) Value() map[string]T {
if i.dict == nil {
return map[string]T{}
}
return *i.dict
}
// Get returns the mapping of values set by this flag
func (i *MapBase[T, C, VC]) Get() interface{} {
return *i.dict
}
func (i MapBase[T, C, VC]) ToString(t map[string]T) string {
var defaultVals []string
var vc VC
for _, k := range sortedKeys(t) {
defaultVals = append(defaultVals, k+defaultMapFlagKeyValueSeparator+vc.ToString(t[k]))
}
return strings.Join(defaultVals, ", ")
}
func sortedKeys[T any](dict map[string]T) []string {
keys := make([]string, 0, len(dict))
for k := range dict {
keys = append(keys, k)
}
sort.Strings(keys)
return keys
}
package cli
// MutuallyExclusiveFlags defines a mutually exclusive flag group
// Multiple option paths can be provided out of which
// only one can be defined on cmdline
// So for example
// [ --foo | [ --bar something --darth somethingelse ] ]
type MutuallyExclusiveFlags struct {
// Flag list
Flags [][]Flag
// whether this group is required
Required bool
// Category to apply to all flags within group
Category string
}
func (grp MutuallyExclusiveFlags) check(_ *Command) error {
oneSet := false
e := &mutuallyExclusiveGroup{}
for _, grpf := range grp.Flags {
for _, f := range grpf {
if f.IsSet() {
if oneSet {
e.flag2Name = f.Names()[0]
return e
}
e.flag1Name = f.Names()[0]
oneSet = true
break
}
if oneSet {
break
}
}
}
if !oneSet && grp.Required {
return &mutuallyExclusiveGroupRequiredFlag{flags: &grp}
}
return nil
}
func (grp MutuallyExclusiveFlags) propagateCategory() {
for _, grpf := range grp.Flags {
for _, f := range grpf {
if cf, ok := f.(CategorizableFlag); ok {
cf.SetCategory(grp.Category)
}
}
}
}
package cli
type numberType interface {
int | int8 | int16 | int32 | int64 | float32 | float64
}
func getNumberSlice[T numberType](cmd *Command, name string) []T {
if v, ok := cmd.Value(name).([]T); ok {
tracef("%T slice available for flag name %[1]q with value=%[2]v (cmd=%[3]q)", *new(T), name, v, cmd.Name)
return v
}
tracef("%T slice NOT available for flag name %[1]q (cmd=%[2]q)", *new(T), name, cmd.Name)
return nil
}
package cli
import (
"encoding/json"
"fmt"
"reflect"
"strings"
)
// SliceBase wraps []T to satisfy flag.Value
type SliceBase[T any, C any, VC ValueCreator[T, C]] struct {
slice *[]T
hasBeenSet bool
value Value
}
func (i SliceBase[T, C, VC]) Create(val []T, p *[]T, c C) Value {
*p = []T{}
*p = append(*p, val...)
var t T
np := new(T)
var vc VC
return &SliceBase[T, C, VC]{
slice: p,
value: vc.Create(t, np, c),
}
}
// NewSliceBase makes a *SliceBase with default values
func NewSliceBase[T any, C any, VC ValueCreator[T, C]](defaults ...T) *SliceBase[T, C, VC] {
return &SliceBase[T, C, VC]{
slice: &defaults,
}
}
// Set parses the value and appends it to the list of values
func (i *SliceBase[T, C, VC]) Set(value string) error {
if !i.hasBeenSet {
*i.slice = []T{}
i.hasBeenSet = true
}
if strings.HasPrefix(value, slPfx) {
// Deserializing assumes overwrite
_ = json.Unmarshal([]byte(strings.Replace(value, slPfx, "", 1)), &i.slice)
i.hasBeenSet = true
return nil
}
trimSpace := true
// hack. How do we know if we should trim spaces?
// it makes sense only for string slice flags which have
// an option to not trim spaces. So by default we trim spaces
// otherwise we let the underlying value type handle it.
var t T
if reflect.TypeOf(t).Kind() == reflect.String {
trimSpace = false
}
for _, s := range flagSplitMultiValues(value) {
if trimSpace {
s = strings.TrimSpace(s)
}
if err := i.value.Set(s); err != nil {
return err
}
*i.slice = append(*i.slice, i.value.Get().(T))
}
return nil
}
// String returns a readable representation of this value (for usage defaults)
func (i *SliceBase[T, C, VC]) String() string {
v := i.Value()
var t T
if reflect.TypeOf(t).Kind() == reflect.String {
return fmt.Sprintf("%v", v)
}
return fmt.Sprintf("%T{%s}", v, i.ToString(v))
}
// Serialize allows SliceBase to fulfill Serializer
func (i *SliceBase[T, C, VC]) Serialize() string {
jsonBytes, _ := json.Marshal(i.slice)
return fmt.Sprintf("%s%s", slPfx, string(jsonBytes))
}
// Value returns the slice of values set by this flag
func (i *SliceBase[T, C, VC]) Value() []T {
if i.slice == nil {
return nil
}
return *i.slice
}
// Get returns the slice of values set by this flag
func (i *SliceBase[T, C, VC]) Get() interface{} {
return *i.slice
}
func (i SliceBase[T, C, VC]) ToString(t []T) string {
var defaultVals []string
var v VC
for _, s := range t {
defaultVals = append(defaultVals, v.ToString(s))
}
return strings.Join(defaultVals, ", ")
}
package cli
import (
"fmt"
"strings"
)
type StringFlag = FlagBase[string, StringConfig, stringValue]
// StringConfig defines the configuration for string flags
type StringConfig struct {
// Whether to trim whitespace of parsed value
TrimSpace bool
}
// -- string Value
type stringValue struct {
destination *string
trimSpace bool
}
// Below functions are to satisfy the ValueCreator interface
func (s stringValue) Create(val string, p *string, c StringConfig) Value {
*p = val
return &stringValue{
destination: p,
trimSpace: c.TrimSpace,
}
}
func (s stringValue) ToString(val string) string {
if val == "" {
return val
}
return fmt.Sprintf("%q", val)
}
// Below functions are to satisfy the flag.Value interface
func (s *stringValue) Set(val string) error {
if s.trimSpace {
val = strings.TrimSpace(val)
}
*s.destination = val
return nil
}
func (s *stringValue) Get() any { return *s.destination }
func (s *stringValue) String() string {
if s.destination != nil {
return *s.destination
}
return ""
}
func (cmd *Command) String(name string) string {
if v, ok := cmd.Value(name).(string); ok {
tracef("string available for flag name %[1]q with value=%[2]v (cmd=%[3]q)", name, v, cmd.Name)
return v
}
tracef("string NOT available for flag name %[1]q (cmd=%[2]q)", name, cmd.Name)
return ""
}
package cli
type (
StringMap = MapBase[string, StringConfig, stringValue]
StringMapFlag = FlagBase[map[string]string, StringConfig, StringMap]
)
var NewStringMap = NewMapBase[string, StringConfig, stringValue]
// StringMap looks up the value of a local StringMapFlag, returns
// nil if not found
func (cmd *Command) StringMap(name string) map[string]string {
if v, ok := cmd.Value(name).(map[string]string); ok {
tracef("string map available for flag name %[1]q with value=%[2]v (cmd=%[3]q)", name, v, cmd.Name)
return v
}
tracef("string map NOT available for flag name %[1]q (cmd=%[2]q)", name, cmd.Name)
return nil
}
package cli
type (
StringSlice = SliceBase[string, StringConfig, stringValue]
StringSliceFlag = FlagBase[[]string, StringConfig, StringSlice]
)
var NewStringSlice = NewSliceBase[string, StringConfig, stringValue]
// StringSlice looks up the value of a local StringSliceFlag, returns
// nil if not found
func (cmd *Command) StringSlice(name string) []string {
if v, ok := cmd.Value(name).([]string); ok {
tracef("string slice available for flag name %[1]q with value=%[2]v (cmd=%[3]q)", name, v, cmd.Name)
return v
}
tracef("string slice NOT available for flag name %[1]q (cmd=%[2]q)", name, cmd.Name)
return nil
}
package cli
import (
"errors"
"fmt"
"time"
)
type TimestampFlag = FlagBase[time.Time, TimestampConfig, timestampValue]
// TimestampConfig defines the config for timestamp flags
type TimestampConfig struct {
Timezone *time.Location
// Available layouts for flag value.
//
// Note that value for formats with missing year/date will be interpreted as current year/date respectively.
//
// Read more about time layouts: https://pkg.go.dev/time#pkg-constants
Layouts []string
}
// timestampValue wrap to satisfy golang's flag interface.
type timestampValue struct {
timestamp *time.Time
hasBeenSet bool
layouts []string
location *time.Location
}
var _ ValueCreator[time.Time, TimestampConfig] = timestampValue{}
// Below functions are to satisfy the ValueCreator interface
func (t timestampValue) Create(val time.Time, p *time.Time, c TimestampConfig) Value {
*p = val
return &timestampValue{
timestamp: p,
layouts: c.Layouts,
location: c.Timezone,
}
}
func (t timestampValue) ToString(b time.Time) string {
if b.IsZero() {
return ""
}
return fmt.Sprintf("%v", b)
}
// Below functions are to satisfy the Value interface
// Parses the string value to timestamp
func (t *timestampValue) Set(value string) error {
var timestamp time.Time
var err error
if t.location == nil {
t.location = time.UTC
}
if len(t.layouts) == 0 {
return errors.New("got nil/empty layouts slice")
}
for _, layout := range t.layouts {
var locErr error
timestamp, locErr = time.ParseInLocation(layout, value, t.location)
if locErr != nil {
if err == nil {
err = locErr
continue
}
err = newMultiError(err, locErr)
continue
}
err = nil
break
}
if err != nil {
return err
}
defaultTS, _ := time.ParseInLocation(time.TimeOnly, time.TimeOnly, timestamp.Location())
n := time.Now().In(timestamp.Location())
// If format is missing date (or year only), set it explicitly to current
if timestamp.Truncate(time.Hour*24).UnixNano() == defaultTS.Truncate(time.Hour*24).UnixNano() {
timestamp = time.Date(
n.Year(),
n.Month(),
n.Day(),
timestamp.Hour(),
timestamp.Minute(),
timestamp.Second(),
timestamp.Nanosecond(),
timestamp.Location(),
)
} else if timestamp.Year() == 0 {
timestamp = time.Date(
n.Year(),
timestamp.Month(),
timestamp.Day(),
timestamp.Hour(),
timestamp.Minute(),
timestamp.Second(),
timestamp.Nanosecond(),
timestamp.Location(),
)
}
if t.timestamp != nil {
*t.timestamp = timestamp
}
t.hasBeenSet = true
return nil
}
// String returns a readable representation of this value (for usage defaults)
func (t *timestampValue) String() string {
return fmt.Sprintf("%#v", t.timestamp)
}
// Get returns the flag structure
func (t *timestampValue) Get() any {
return *t.timestamp
}
// Timestamp gets the timestamp from a flag name
func (cmd *Command) Timestamp(name string) time.Time {
if v, ok := cmd.Value(name).(time.Time); ok {
tracef("time.Time available for flag name %[1]q with value=%[2]v (cmd=%[3]q)", name, v, cmd.Name)
return v
}
tracef("time.Time NOT available for flag name %[1]q (cmd=%[2]q)", name, cmd.Name)
return time.Time{}
}
package cli
import (
"strconv"
"unsafe"
)
type (
UintFlag = FlagBase[uint, IntegerConfig, uintValue[uint]]
Uint8Flag = FlagBase[uint8, IntegerConfig, uintValue[uint8]]
Uint16Flag = FlagBase[uint16, IntegerConfig, uintValue[uint16]]
Uint32Flag = FlagBase[uint32, IntegerConfig, uintValue[uint32]]
Uint64Flag = FlagBase[uint64, IntegerConfig, uintValue[uint64]]
)
// -- uint Value
type uintValue[T uint | uint8 | uint16 | uint32 | uint64] struct {
val *T
base int
}
// Below functions are to satisfy the ValueCreator interface
func (i uintValue[T]) Create(val T, p *T, c IntegerConfig) Value {
*p = val
return &uintValue[T]{
val: p,
base: c.Base,
}
}
func (i uintValue[T]) ToString(b T) string {
base := i.base
if base == 0 {
base = 10
}
return strconv.FormatUint(uint64(b), base)
}
// Below functions are to satisfy the flag.Value interface
func (i *uintValue[T]) Set(s string) error {
v, err := strconv.ParseUint(s, i.base, int(unsafe.Sizeof(T(0))*8))
if err != nil {
return err
}
*i.val = T(v)
return err
}
func (i *uintValue[T]) Get() any { return *i.val }
func (i *uintValue[T]) String() string {
base := i.base
if base == 0 {
base = 10
}
return strconv.FormatUint(uint64(*i.val), base)
}
// Uint looks up the value of a local Uint64Flag, returns
// 0 if not found
func (cmd *Command) Uint(name string) uint {
return getUint[uint](cmd, name)
}
// Uint8 looks up the value of a local Uint8Flag, returns
// 0 if not found
func (cmd *Command) Uint8(name string) uint8 {
return getUint[uint8](cmd, name)
}
// Uint16 looks up the value of a local Uint16Flag, returns
// 0 if not found
func (cmd *Command) Uint16(name string) uint16 {
return getUint[uint16](cmd, name)
}
// Uint32 looks up the value of a local Uint32Flag, returns
// 0 if not found
func (cmd *Command) Uint32(name string) uint32 {
return getUint[uint32](cmd, name)
}
// Uint64 looks up the value of a local Uint64Flag, returns
// 0 if not found
func (cmd *Command) Uint64(name string) uint64 {
return getUint[uint64](cmd, name)
}
func getUint[T uint | uint8 | uint16 | uint32 | uint64](cmd *Command, name string) T {
if v, ok := cmd.Value(name).(T); ok {
tracef("uint available for flag name %[1]q with value=%[2]v (cmd=%[3]q)", name, v, cmd.Name)
return v
}
tracef("uint NOT available for flag name %[1]q (cmd=%[2]q)", name, cmd.Name)
return 0
}
package cli
type (
UintSlice = SliceBase[uint, IntegerConfig, uintValue[uint]]
Uint8Slice = SliceBase[uint8, IntegerConfig, uintValue[uint8]]
Uint16Slice = SliceBase[uint16, IntegerConfig, uintValue[uint16]]
Uint32Slice = SliceBase[uint32, IntegerConfig, uintValue[uint32]]
Uint64Slice = SliceBase[uint64, IntegerConfig, uintValue[uint64]]
UintSliceFlag = FlagBase[[]uint, IntegerConfig, UintSlice]
Uint8SliceFlag = FlagBase[[]uint8, IntegerConfig, Uint8Slice]
Uint16SliceFlag = FlagBase[[]uint16, IntegerConfig, Uint16Slice]
Uint32SliceFlag = FlagBase[[]uint32, IntegerConfig, Uint32Slice]
Uint64SliceFlag = FlagBase[[]uint64, IntegerConfig, Uint64Slice]
)
var (
NewUintSlice = NewSliceBase[uint, IntegerConfig, uintValue[uint]]
NewUint8Slice = NewSliceBase[uint8, IntegerConfig, uintValue[uint8]]
NewUint16Slice = NewSliceBase[uint16, IntegerConfig, uintValue[uint16]]
NewUint32Slice = NewSliceBase[uint32, IntegerConfig, uintValue[uint32]]
NewUint64Slice = NewSliceBase[uint64, IntegerConfig, uintValue[uint64]]
)
// UintSlice looks up the value of a local UintSliceFlag, returns
// nil if not found
func (cmd *Command) UintSlice(name string) []uint {
return getUintSlice[uint](cmd, name)
}
// Uint8Slice looks up the value of a local Uint8SliceFlag, returns
// nil if not found
func (cmd *Command) Uint8Slice(name string) []uint8 {
return getUintSlice[uint8](cmd, name)
}
// Uint16Slice looks up the value of a local Uint16SliceFlag, returns
// nil if not found
func (cmd *Command) Uint16Slice(name string) []uint16 {
return getUintSlice[uint16](cmd, name)
}
// Uint32Slice looks up the value of a local Uint32SliceFlag, returns
// nil if not found
func (cmd *Command) Uint32Slice(name string) []uint32 {
return getUintSlice[uint32](cmd, name)
}
// Uint64Slice looks up the value of a local Uint64SliceFlag, returns
// nil if not found
func (cmd *Command) Uint64Slice(name string) []uint64 {
return getUintSlice[uint64](cmd, name)
}
func getUintSlice[T uint | uint8 | uint16 | uint32 | uint64](cmd *Command, name string) []T {
if v, ok := cmd.Value(name).([]T); ok {
tracef("uint slice available for flag name %[1]q with value=%[2]v (cmd=%[3]q)", name, v, cmd.Name)
return v
}
tracef("uint slice NOT available for flag name %[1]q (cmd=%[2]q)", name, cmd.Name)
return nil
}
package cli
import "context"
// ShellCompleteFunc is an action to execute when the shell completion flag is set
type ShellCompleteFunc func(context.Context, *Command)
// BeforeFunc is an action that executes prior to any subcommands being run once
// the context is ready. If a non-nil error is returned, no subcommands are
// run.
type BeforeFunc func(context.Context, *Command) (context.Context, error)
// AfterFunc is an action that executes after any subcommands are run and have
// finished. The AfterFunc is run even if Action() panics.
type AfterFunc func(context.Context, *Command) error
// ActionFunc is the action to execute when no subcommands are specified
type ActionFunc func(context.Context, *Command) error
// CommandNotFoundFunc is executed if the proper command cannot be found
type CommandNotFoundFunc func(context.Context, *Command, string)
// ConfigureShellCompletionCommand is a function to configure a shell completion command
type ConfigureShellCompletionCommand func(*Command)
// OnUsageErrorFunc is executed if a usage error occurs. This is useful for displaying
// customized usage error messages. This function is able to replace the
// original error messages. If this function is not set, the "Incorrect usage"
// is displayed and the execution is interrupted.
type OnUsageErrorFunc func(ctx context.Context, cmd *Command, err error, isSubcommand bool) error
// InvalidFlagAccessFunc is executed when an invalid flag is accessed from the context.
type InvalidFlagAccessFunc func(context.Context, *Command, string)
// ExitErrHandlerFunc is executed if provided in order to handle exitError values
// returned by Actions and Before/After functions.
type ExitErrHandlerFunc func(context.Context, *Command, error)
// FlagStringFunc is used by the help generation to display a flag, which is
// expected to be a single line.
type FlagStringFunc func(Flag) string
// FlagNamePrefixFunc is used by the default FlagStringFunc to create prefix
// text for a flag's full name.
type FlagNamePrefixFunc func(fullName []string, placeholder string) string
// FlagEnvHintFunc is used by the default FlagStringFunc to annotate flag help
// with the environment variable details.
type FlagEnvHintFunc func(envVars []string, str string) string
// FlagFileHintFunc is used by the default FlagStringFunc to annotate flag help
// with the file path details.
type FlagFileHintFunc func(filePath, str string) string
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment