Merge branch 'main' into applying-pr1218

This commit is contained in:
Dan Buch 2022-05-07 08:57:31 -04:00 committed by GitHub
commit 6c811a82df
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
36 changed files with 5969 additions and 518 deletions

View File

@ -87,3 +87,8 @@ jobs:
- name: toc
run: go run internal/build/build.go toc docs/v2/manual.md
- name: diff check
run: |
git diff --exit-code
git diff --cached --exit-code

32
Makefile Normal file
View File

@ -0,0 +1,32 @@
# 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.
.PHONY: all
all: generate vet tag-test test check-binary-size tag-check-binary-size gfmrun toc v2diff
# NOTE: this is a special catch-all rule to run any of the commands
# defined in internal/build/build.go with optional arguments passed
# via GFLAGS (global flags) and FLAGS (command-specific flags), e.g.:
#
# $ make test GFLAGS='--packages cli'
%:
go run internal/build/build.go $(GFLAGS) $* $(FLAGS)
.PHONY: tag-test
tag-test:
go run internal/build/build.go -tags urfave_cli_no_docs test
.PHONY: tag-check-binary-size
tag-check-binary-size:
go run internal/build/build.go -tags urfave_cli_no_docs check-binary-size
.PHONY: gfmrun
gfmrun:
go run internal/build/build.go gfmrun docs/v2/manual.md
.PHONY: toc
toc:
go run internal/build/build.go toc docs/v2/manual.md

2
cli.go
View File

@ -20,4 +20,4 @@
// }
package cli
//go:generate go run flag-gen/main.go flag-gen/assets_vfsdata.go
//go:generate go run internal/genflags/cmd/genflags/main.go

View File

@ -1,18 +1,121 @@
## Contributing
Use @urfave/cli to ping the maintainers.
Welcome to the `urfave/cli` contributor docs! This goal of this document is to help those
interested in joining the 200+ humans who have contributed to this project over the years.
Feel free to put up a pull request to fix a bug or maybe add a feature. We will
give it a code review and make sure that it does not break backwards
compatibility. If collaborators agree that it is in line with
the vision of the project, we will work with you to get the code into
a mergeable state and merge it into the main branch.
> As a general guiding principle, the current maintainers may be notified via the
> @urfave/cli GitHub team.
If you have contributed something significant to the project, we will most
likely add you as a collaborator. As a collaborator you are given the ability
to merge others pull requests. It is very important that new code does not
break existing code, so be careful about what code you do choose to merge.
All of the current maintainers are *volunteers* who live in various timezones with
different scheduling needs, so please understand that your contribution or question may
not get a response for many days.
If you feel like you have contributed to the project but have not yet been added
as a collaborator, we probably forgot to add you :sweat_smile:. Please open an
issue!
### semantic versioning adherence
The `urfave/cli` project strives to strictly adhere to [semantic
versioning](https://semver.org/spec/v2.0.0.html). The active development branches and the
milestones and import paths to which they correspond are:
#### `main` branch
<https://github.com/urfave/cli/tree/main>
The majority of active development and issue management is targeting the `main` branch,
which **MUST** *only* receive bug fixes and feature *additions*.
- :arrow_right: [`v2.x`](https://github.com/urfave/cli/milestone/16)
- :arrow_right: `github.com/urfave/cli/v2`
The `main` branch in particular includes tooling to help with keeping the `v2.x` series
backward compatible. More details on this process are in the development workflow section
below.
#### `v1` branch
<https://github.com/urfave/cli/tree/v1>
The `v1` branch **MUST** only receive bug fixes in the `v1.22.x` series. There is no
strict rule regarding bug fixes to the `v2.x` series being backported to the `v1.22.x`
series.
- :arrow_right: [`v1.22.x`](https://github.com/urfave/cli/milestone/11)
- :arrow_right: `github.com/urfave/cli`
#### `v3-dev-main` branch
<https://github.com/urfave/cli/tree/v3-dev-main>
The `v3-dev-branch` **MUST** receive all bug fixes and features added to the `main` branch
and **MAY** receive feature *removals* and other changes that are otherwise
*backward-incompatible* with the `v2.x` series.
- :arrow_right: [`v3.x`](https://github.com/urfave/cli/milestone/5)
- unreleased / unsupported
### development workflow
Most of the tooling around the development workflow strives for effective
[dogfooding](https://en.wikipedia.org/wiki/Eating_your_own_dog_food). There is a top-level
`Makefile` that is maintained strictly for the purpose of easing verification of one's
development environment and any changes one may have introduced:
```sh
make
```
Running the default `make` target (`all`) will ensure all of the critical steps are run to
verify one's changes are harmonious in nature. The same steps are also run during the
[continuous integration
phase](https://github.com/urfave/cli/blob/main/.github/workflows/cli.yml).
In the event that the `v2diff` target exits non-zero, this is a signal that the public API
surface area has changed. If the changes adhere to semantic versioning, meaning they are
*additions* or *bug fixes*, then manually running the approval step will "promote" the
current `go doc` output:
```sh
make v2approve
```
Because the `generate` step includes updating `godoc-current.txt` and
`testdata/godoc-v2.x.txt`, these changes *MUST* be part of any proposed pull request so
that reviewers have an opportunity to also make an informed decision about the "promotion"
step.
#### generated code
A significant portion of the project's source code is generated, with the goal being to
eliminate repetetive maintenance where other type-safe abstraction is impractical or
impossible with Go versions `< 1.18`. In a future where the eldest Go version supported is
`1.18.x`, there will likely be efforts to take advantage of
[generics](https://go.dev/doc/tutorial/generics).
The built-in `go generate` command is used to run the commands specified in
`//go:generate` directives. Each such command runs a file that also supports a command
line help system which may be consulted for further information, e.g.:
```sh
go run internal/genflags/cmd/genflags/main.go --help
```
### pull requests
Please feel free to open a pull request to fix a bug or add a feature. The @urfave/cli
team will review it as soon as possible, giving special attention to maintaining backward
compatibility. If the @urfave/cli team agrees that your contribution is in line with the
vision of the project, they will work with you to get the code into a mergeable state,
merged, and then released.
### granting of commit bit / admin mode
Those with a history of contributing to this project will likely be invited to join the
@urfave/cli team. As a member of the @urfave/cli team, you will have the ability to fully
administer pull requests, issues, and other repository bits.
If you feel that you should be a member of the @urfave/cli team but have not yet been
added, the most likely explanation is that this is an accidental oversight! :sweat_smile:.
Please open an issue!
<!--
vim:tw=90
-->

50
flag-spec.yaml Normal file
View File

@ -0,0 +1,50 @@
# NOTE: this file is used by the tool defined in
# ./internal/genflags/cmd/genflags/main.go which uses the
# `genflags.Spec` type that maps to this file structure.
flag_types:
bool: {}
float64: {}
int64: {}
int: {}
time.Duration: {}
uint64: {}
uint: {}
string:
struct_fields:
- { name: TakesFile, type: bool }
Generic:
struct_fields:
- { name: TakesFile, type: bool }
Path:
struct_fields:
- { name: TakesFile, type: bool }
Float64Slice:
value_pointer: true
skip_interfaces:
- fmt.Stringer
Int64Slice:
value_pointer: true
skip_interfaces:
- fmt.Stringer
IntSlice:
value_pointer: true
skip_interfaces:
- fmt.Stringer
StringSlice:
value_pointer: true
skip_interfaces:
- fmt.Stringer
struct_fields:
- { name: TakesFile, type: bool }
Timestamp:
value_pointer: true
struct_fields:
- { name: Layout, type: string }
# TODO: enable UintSlice
# UintSlice: {}
# TODO: enable Uint64Slice once #1334 lands
# Uint64Slice: {}

View File

@ -258,7 +258,7 @@ func withEnvHint(envVars []string, str string) string {
return str + envText
}
func flagNames(name string, aliases []string) []string {
func FlagNames(name string, aliases []string) []string {
var ret []string
for _, part := range append([]string{name}, aliases...) {

View File

@ -6,37 +6,6 @@ import (
"strconv"
)
// BoolFlag is a flag with type bool
type BoolFlag struct {
Name string
Aliases []string
Usage string
EnvVars []string
FilePath string
Required bool
Hidden bool
Value bool
DefaultText string
Destination *bool
HasBeenSet bool
}
// IsSet returns whether or not the flag has been set through env or file
func (f *BoolFlag) IsSet() bool {
return f.HasBeenSet
}
// String returns a readable representation of this value
// (for usage defaults)
func (f *BoolFlag) String() string {
return FlagStringer(f)
}
// Names returns the names of the flag
func (f *BoolFlag) Names() []string {
return flagNames(f.Name, f.Aliases)
}
// IsRequired returns whether or not the flag is required
func (f *BoolFlag) IsRequired() bool {
return f.Required
@ -102,6 +71,11 @@ func (f *BoolFlag) Apply(set *flag.FlagSet) error {
return nil
}
// Get returns the flags value in the given Context.
func (f *BoolFlag) Get(ctx *Context) bool {
return ctx.Bool(f.Name)
}
// Bool looks up the value of a local BoolFlag, returns
// false if not found
func (cCtx *Context) Bool(name string) bool {

View File

@ -6,37 +6,6 @@ import (
"time"
)
// DurationFlag is a flag with type time.Duration (see https://golang.org/pkg/time/#ParseDuration)
type DurationFlag struct {
Name string
Aliases []string
Usage string
EnvVars []string
FilePath string
Required bool
Hidden bool
Value time.Duration
DefaultText string
Destination *time.Duration
HasBeenSet bool
}
// IsSet returns whether or not the flag has been set through env or file
func (f *DurationFlag) IsSet() bool {
return f.HasBeenSet
}
// String returns a readable representation of this value
// (for usage defaults)
func (f *DurationFlag) String() string {
return FlagStringer(f)
}
// Names returns the names of the flag
func (f *DurationFlag) Names() []string {
return flagNames(f.Name, f.Aliases)
}
// IsRequired returns whether or not the flag is required
func (f *DurationFlag) IsRequired() bool {
return f.Required
@ -101,6 +70,11 @@ func (f *DurationFlag) Apply(set *flag.FlagSet) error {
return nil
}
// Get returns the flags value in the given Context.
func (f *DurationFlag) Get(ctx *Context) time.Duration {
return ctx.Duration(f.Name)
}
// Duration looks up the value of a local DurationFlag, returns
// 0 if not found
func (cCtx *Context) Duration(name string) time.Duration {

View File

@ -6,37 +6,6 @@ import (
"strconv"
)
// Float64Flag is a flag with type float64
type Float64Flag struct {
Name string
Aliases []string
Usage string
EnvVars []string
FilePath string
Required bool
Hidden bool
Value float64
DefaultText string
Destination *float64
HasBeenSet bool
}
// IsSet returns whether or not the flag has been set through env or file
func (f *Float64Flag) IsSet() bool {
return f.HasBeenSet
}
// String returns a readable representation of this value
// (for usage defaults)
func (f *Float64Flag) String() string {
return FlagStringer(f)
}
// Names returns the names of the flag
func (f *Float64Flag) Names() []string {
return flagNames(f.Name, f.Aliases)
}
// IsRequired returns whether or not the flag is required
func (f *Float64Flag) IsRequired() bool {
return f.Required
@ -101,6 +70,11 @@ func (f *Float64Flag) Apply(set *flag.FlagSet) error {
return nil
}
// Get returns the flags value in the given Context.
func (f *Float64Flag) Get(ctx *Context) float64 {
return ctx.Float64(f.Name)
}
// Float64 looks up the value of a local Float64Flag, returns
// 0 if not found
func (cCtx *Context) Float64(name string) float64 {

View File

@ -75,36 +75,12 @@ func (f *Float64Slice) Get() interface{} {
return *f
}
// Float64SliceFlag is a flag with type *Float64Slice
type Float64SliceFlag struct {
Name string
Aliases []string
Usage string
EnvVars []string
FilePath string
Required bool
Hidden bool
Value *Float64Slice
DefaultText string
HasBeenSet bool
}
// IsSet returns whether or not the flag has been set through env or file
func (f *Float64SliceFlag) IsSet() bool {
return f.HasBeenSet
}
// String returns a readable representation of this value
// (for usage defaults)
func (f *Float64SliceFlag) String() string {
return withEnvHint(f.GetEnvVars(), stringifyFloat64SliceFlag(f))
}
// Names returns the names of the flag
func (f *Float64SliceFlag) Names() []string {
return flagNames(f.Name, f.Aliases)
}
// IsRequired returns whether or not the flag is required
func (f *Float64SliceFlag) IsRequired() bool {
return f.Required
@ -177,6 +153,11 @@ func (f *Float64SliceFlag) Apply(set *flag.FlagSet) error {
return nil
}
// Get returns the flags value in the given Context.
func (f *Float64SliceFlag) Get(ctx *Context) []float64 {
return ctx.Float64Slice(f.Name)
}
// Float64Slice looks up the value of a local Float64SliceFlag, returns
// nil if not found
func (cCtx *Context) Float64Slice(name string) []float64 {

View File

@ -11,37 +11,6 @@ type Generic interface {
String() string
}
// GenericFlag is a flag with type Generic
type GenericFlag struct {
Name string
Aliases []string
Usage string
EnvVars []string
FilePath string
Required bool
Hidden bool
TakesFile bool
Value Generic
DefaultText string
HasBeenSet bool
}
// IsSet returns whether or not the flag has been set through env or file
func (f *GenericFlag) IsSet() bool {
return f.HasBeenSet
}
// String returns a readable representation of this value
// (for usage defaults)
func (f *GenericFlag) String() string {
return FlagStringer(f)
}
// Names returns the names of the flag
func (f *GenericFlag) Names() []string {
return flagNames(f.Name, f.Aliases)
}
// IsRequired returns whether or not the flag is required
func (f *GenericFlag) IsRequired() bool {
return f.Required
@ -104,6 +73,11 @@ func (f GenericFlag) Apply(set *flag.FlagSet) error {
return nil
}
// Get returns the flags value in the given Context.
func (f *GenericFlag) Get(ctx *Context) interface{} {
return ctx.Generic(f.Name)
}
// Generic looks up the value of a local GenericFlag, returns
// nil if not found
func (cCtx *Context) Generic(name string) interface{} {

View File

@ -6,37 +6,6 @@ import (
"strconv"
)
// IntFlag is a flag with type int
type IntFlag struct {
Name string
Aliases []string
Usage string
EnvVars []string
FilePath string
Required bool
Hidden bool
Value int
DefaultText string
Destination *int
HasBeenSet bool
}
// IsSet returns whether or not the flag has been set through env or file
func (f *IntFlag) IsSet() bool {
return f.HasBeenSet
}
// String returns a readable representation of this value
// (for usage defaults)
func (f *IntFlag) String() string {
return FlagStringer(f)
}
// Names returns the names of the flag
func (f *IntFlag) Names() []string {
return flagNames(f.Name, f.Aliases)
}
// IsRequired returns whether or not the flag is required
func (f *IntFlag) IsRequired() bool {
return f.Required
@ -102,6 +71,11 @@ func (f *IntFlag) Apply(set *flag.FlagSet) error {
return nil
}
// Get returns the flags value in the given Context.
func (f *IntFlag) Get(ctx *Context) int {
return ctx.Int(f.Name)
}
// Int looks up the value of a local IntFlag, returns
// 0 if not found
func (cCtx *Context) Int(name string) int {

View File

@ -6,37 +6,6 @@ import (
"strconv"
)
// Int64Flag is a flag with type int64
type Int64Flag struct {
Name string
Aliases []string
Usage string
EnvVars []string
FilePath string
Required bool
Hidden bool
Value int64
DefaultText string
Destination *int64
HasBeenSet bool
}
// IsSet returns whether or not the flag has been set through env or file
func (f *Int64Flag) IsSet() bool {
return f.HasBeenSet
}
// String returns a readable representation of this value
// (for usage defaults)
func (f *Int64Flag) String() string {
return FlagStringer(f)
}
// Names returns the names of the flag
func (f *Int64Flag) Names() []string {
return flagNames(f.Name, f.Aliases)
}
// IsRequired returns whether or not the flag is required
func (f *Int64Flag) IsRequired() bool {
return f.Required
@ -101,6 +70,11 @@ func (f *Int64Flag) Apply(set *flag.FlagSet) error {
return nil
}
// Get returns the flags value in the given Context.
func (f *Int64Flag) Get(ctx *Context) int64 {
return ctx.Int64(f.Name)
}
// Int64 looks up the value of a local Int64Flag, returns
// 0 if not found
func (cCtx *Context) Int64(name string) int64 {

View File

@ -76,36 +76,12 @@ func (i *Int64Slice) Get() interface{} {
return *i
}
// Int64SliceFlag is a flag with type *Int64Slice
type Int64SliceFlag struct {
Name string
Aliases []string
Usage string
EnvVars []string
FilePath string
Required bool
Hidden bool
Value *Int64Slice
DefaultText string
HasBeenSet bool
}
// IsSet returns whether or not the flag has been set through env or file
func (f *Int64SliceFlag) IsSet() bool {
return f.HasBeenSet
}
// String returns a readable representation of this value
// (for usage defaults)
func (f *Int64SliceFlag) String() string {
return withEnvHint(f.GetEnvVars(), stringifyInt64SliceFlag(f))
}
// Names returns the names of the flag
func (f *Int64SliceFlag) Names() []string {
return flagNames(f.Name, f.Aliases)
}
// IsRequired returns whether or not the flag is required
func (f *Int64SliceFlag) IsRequired() bool {
return f.Required
@ -176,6 +152,11 @@ func (f *Int64SliceFlag) Apply(set *flag.FlagSet) error {
return nil
}
// Get returns the flags value in the given Context.
func (f *Int64SliceFlag) Get(ctx *Context) []int64 {
return ctx.Int64Slice(f.Name)
}
// Int64Slice looks up the value of a local Int64SliceFlag, returns
// nil if not found
func (cCtx *Context) Int64Slice(name string) []int64 {

View File

@ -87,36 +87,12 @@ func (i *IntSlice) Get() interface{} {
return *i
}
// IntSliceFlag is a flag with type *IntSlice
type IntSliceFlag struct {
Name string
Aliases []string
Usage string
EnvVars []string
FilePath string
Required bool
Hidden bool
Value *IntSlice
DefaultText string
HasBeenSet bool
}
// IsSet returns whether or not the flag has been set through env or file
func (f *IntSliceFlag) IsSet() bool {
return f.HasBeenSet
}
// String returns a readable representation of this value
// (for usage defaults)
func (f *IntSliceFlag) String() string {
return withEnvHint(f.GetEnvVars(), stringifyIntSliceFlag(f))
}
// Names returns the names of the flag
func (f *IntSliceFlag) Names() []string {
return flagNames(f.Name, f.Aliases)
}
// IsRequired returns whether or not the flag is required
func (f *IntSliceFlag) IsRequired() bool {
return f.Required
@ -187,6 +163,11 @@ func (f *IntSliceFlag) Apply(set *flag.FlagSet) error {
return nil
}
// Get returns the flags value in the given Context.
func (f *IntSliceFlag) Get(ctx *Context) []int {
return ctx.IntSlice(f.Name)
}
// IntSlice looks up the value of a local IntSliceFlag, returns
// nil if not found
func (cCtx *Context) IntSlice(name string) []int {

View File

@ -5,36 +5,7 @@ import (
"fmt"
)
type PathFlag struct {
Name string
Aliases []string
Usage string
EnvVars []string
FilePath string
Required bool
Hidden bool
TakesFile bool
Value string
DefaultText string
Destination *string
HasBeenSet bool
}
// IsSet returns whether or not the flag has been set through env or file
func (f *PathFlag) IsSet() bool {
return f.HasBeenSet
}
// String returns a readable representation of this value
// (for usage defaults)
func (f *PathFlag) String() string {
return FlagStringer(f)
}
// Names returns the names of the flag
func (f *PathFlag) Names() []string {
return flagNames(f.Name, f.Aliases)
}
type Path = string
// IsRequired returns whether or not the flag is required
func (f *PathFlag) IsRequired() bool {
@ -96,6 +67,11 @@ func (f *PathFlag) Apply(set *flag.FlagSet) error {
return nil
}
// Get returns the flags value in the given Context.
func (f *PathFlag) Get(ctx *Context) string {
return ctx.Path(f.Name)
}
// Path looks up the value of a local PathFlag, returns
// "" if not found
func (cCtx *Context) Path(name string) string {

View File

@ -5,38 +5,6 @@ import (
"fmt"
)
// StringFlag is a flag with type string
type StringFlag struct {
Name string
Aliases []string
Usage string
EnvVars []string
FilePath string
Required bool
Hidden bool
TakesFile bool
Value string
DefaultText string
Destination *string
HasBeenSet bool
}
// IsSet returns whether or not the flag has been set through env or file
func (f *StringFlag) IsSet() bool {
return f.HasBeenSet
}
// String returns a readable representation of this value
// (for usage defaults)
func (f *StringFlag) String() string {
return FlagStringer(f)
}
// Names returns the names of the flag
func (f *StringFlag) Names() []string {
return flagNames(f.Name, f.Aliases)
}
// IsRequired returns whether or not the flag is required
func (f *StringFlag) IsRequired() bool {
return f.Required
@ -97,6 +65,11 @@ func (f *StringFlag) Apply(set *flag.FlagSet) error {
return nil
}
// Get returns the flags value in the given Context.
func (f *StringFlag) Get(ctx *Context) string {
return ctx.String(f.Name)
}
// String looks up the value of a local StringFlag, returns
// "" if not found
func (cCtx *Context) String(name string) string {

View File

@ -70,38 +70,12 @@ func (s *StringSlice) Get() interface{} {
return *s
}
// StringSliceFlag is a flag with type *StringSlice
type StringSliceFlag struct {
Name string
Aliases []string
Usage string
EnvVars []string
FilePath string
Required bool
Hidden bool
TakesFile bool
Value *StringSlice
DefaultText string
HasBeenSet bool
Destination *StringSlice
}
// IsSet returns whether or not the flag has been set through env or file
func (f *StringSliceFlag) IsSet() bool {
return f.HasBeenSet
}
// String returns a readable representation of this value
// (for usage defaults)
func (f *StringSliceFlag) String() string {
return withEnvHint(f.GetEnvVars(), stringifyStringSliceFlag(f))
}
// Names returns the names of the flag
func (f *StringSliceFlag) Names() []string {
return flagNames(f.Name, f.Aliases)
}
// IsRequired returns whether or not the flag is required
func (f *StringSliceFlag) IsRequired() bool {
return f.Required
@ -188,6 +162,11 @@ func (f *StringSliceFlag) Apply(set *flag.FlagSet) error {
return nil
}
// Get returns the flags value in the given Context.
func (f *StringSliceFlag) Get(ctx *Context) []string {
return ctx.StringSlice(f.Name)
}
// StringSlice looks up the value of a local StringSliceFlag, returns
// nil if not found
func (cCtx *Context) StringSlice(name string) []string {

View File

@ -51,6 +51,17 @@ func TestBoolFlagApply_SetsAllNames(t *testing.T) {
expect(t, v, true)
}
func TestBoolFlagValueFromContext(t *testing.T) {
set := flag.NewFlagSet("test", 0)
set.Bool("trueflag", true, "doc")
set.Bool("falseflag", false, "doc")
ctx := NewContext(nil, set, nil)
tf := &BoolFlag{Name: "trueflag"}
ff := &BoolFlag{Name: "falseflag"}
expect(t, tf.Get(ctx), true)
expect(t, ff.Get(ctx), false)
}
func TestFlagsFromEnv(t *testing.T) {
newSetFloat64Slice := func(defaults ...float64) Float64Slice {
s := NewFloat64Slice(defaults...)
@ -439,6 +450,14 @@ func TestStringFlagApply_SetsAllNames(t *testing.T) {
expect(t, v, "YUUUU")
}
func TestStringFlagValueFromContext(t *testing.T) {
set := flag.NewFlagSet("test", 0)
set.String("myflag", "foobar", "doc")
ctx := NewContext(nil, set, nil)
f := &StringFlag{Name: "myflag"}
expect(t, f.Get(ctx), "foobar")
}
var pathFlagTests = []struct {
name string
aliases []string
@ -490,6 +509,14 @@ func TestPathFlagApply_SetsAllNames(t *testing.T) {
expect(t, v, "/path/to/file/PATH")
}
func TestPathFlagValueFromContext(t *testing.T) {
set := flag.NewFlagSet("test", 0)
set.String("myflag", "/my/path", "doc")
ctx := NewContext(nil, set, nil)
f := &PathFlag{Name: "myflag"}
expect(t, f.Get(ctx), "/my/path")
}
var _ = []struct {
name string
env string
@ -603,6 +630,14 @@ func TestStringSliceFlagApply_DefaultValueWithDestination(t *testing.T) {
expect(t, defValue, fl.Destination.Value())
}
func TestStringSliceFlagValueFromContext(t *testing.T) {
set := flag.NewFlagSet("test", 0)
set.Var(NewStringSlice("a", "b", "c"), "myflag", "doc")
ctx := NewContext(nil, set, nil)
f := &StringSliceFlag{Name: "myflag"}
expect(t, f.Get(ctx), []string{"a", "b", "c"})
}
var intFlagTests = []struct {
name string
expected string
@ -652,6 +687,14 @@ func TestIntFlagApply_SetsAllNames(t *testing.T) {
expect(t, v, 5)
}
func TestIntFlagValueFromContext(t *testing.T) {
set := flag.NewFlagSet("test", 0)
set.Int("myflag", 42, "doc")
ctx := NewContext(nil, set, nil)
f := &IntFlag{Name: "myflag"}
expect(t, f.Get(ctx), 42)
}
var int64FlagTests = []struct {
name string
expected string
@ -690,6 +733,14 @@ func TestInt64FlagWithEnvVarHelpOutput(t *testing.T) {
}
}
func TestInt64FlagValueFromContext(t *testing.T) {
set := flag.NewFlagSet("test", 0)
set.Int64("myflag", 42, "doc")
ctx := NewContext(nil, set, nil)
f := &Int64Flag{Name: "myflag"}
expect(t, f.Get(ctx), int64(42))
}
var uintFlagTests = []struct {
name string
expected string
@ -728,6 +779,14 @@ func TestUintFlagWithEnvVarHelpOutput(t *testing.T) {
}
}
func TestUintFlagValueFromContext(t *testing.T) {
set := flag.NewFlagSet("test", 0)
set.Uint("myflag", 42, "doc")
ctx := NewContext(nil, set, nil)
f := &UintFlag{Name: "myflag"}
expect(t, f.Get(ctx), uint(42))
}
var uint64FlagTests = []struct {
name string
expected string
@ -766,6 +825,14 @@ func TestUint64FlagWithEnvVarHelpOutput(t *testing.T) {
}
}
func TestUint64FlagValueFromContext(t *testing.T) {
set := flag.NewFlagSet("test", 0)
set.Uint64("myflag", 42, "doc")
ctx := NewContext(nil, set, nil)
f := &Uint64Flag{Name: "myflag"}
expect(t, f.Get(ctx), uint64(42))
}
var durationFlagTests = []struct {
name string
expected string
@ -815,6 +882,14 @@ func TestDurationFlagApply_SetsAllNames(t *testing.T) {
expect(t, v, time.Hour*30)
}
func TestDurationFlagValueFromContext(t *testing.T) {
set := flag.NewFlagSet("test", 0)
set.Duration("myflag", 42*time.Second, "doc")
ctx := NewContext(nil, set, nil)
f := &DurationFlag{Name: "myflag"}
expect(t, f.Get(ctx), 42*time.Second)
}
var intSliceFlagTests = []struct {
name string
aliases []string
@ -904,6 +979,14 @@ func TestIntSliceFlag_SetFromParentContext(t *testing.T) {
}
}
func TestIntSliceFlagValueFromContext(t *testing.T) {
set := flag.NewFlagSet("test", 0)
set.Var(NewIntSlice(1, 2, 3), "myflag", "doc")
ctx := NewContext(nil, set, nil)
f := &IntSliceFlag{Name: "myflag"}
expect(t, f.Get(ctx), []int{1, 2, 3})
}
var int64SliceFlagTests = []struct {
name string
aliases []string
@ -1000,6 +1083,14 @@ func TestInt64SliceFlag_ReturnNil(t *testing.T) {
}
}
func TestInt64SliceFlagValueFromContext(t *testing.T) {
set := flag.NewFlagSet("test", 0)
set.Var(NewInt64Slice(1, 2, 3), "myflag", "doc")
ctx := NewContext(nil, set, nil)
f := &Int64SliceFlag{Name: "myflag"}
expect(t, f.Get(ctx), []int64{1, 2, 3})
}
var float64FlagTests = []struct {
name string
expected string
@ -1049,6 +1140,14 @@ func TestFloat64FlagApply_SetsAllNames(t *testing.T) {
expect(t, v, float64(43.33333))
}
func TestFloat64FlagValueFromContext(t *testing.T) {
set := flag.NewFlagSet("test", 0)
set.Float64("myflag", 1.23, "doc")
ctx := NewContext(nil, set, nil)
f := &Float64Flag{Name: "myflag"}
expect(t, f.Get(ctx), 1.23)
}
var float64SliceFlagTests = []struct {
name string
aliases []string
@ -1090,6 +1189,14 @@ func TestFloat64SliceFlagWithEnvVarHelpOutput(t *testing.T) {
}
}
func TestFloat64SliceFlagValueFromContext(t *testing.T) {
set := flag.NewFlagSet("test", 0)
set.Var(NewFloat64Slice(1.23, 4.56), "myflag", "doc")
ctx := NewContext(nil, set, nil)
f := &Float64SliceFlag{Name: "myflag"}
expect(t, f.Get(ctx), []float64{1.23, 4.56})
}
var genericFlagTests = []struct {
name string
value Generic
@ -1138,6 +1245,14 @@ func TestGenericFlagApply_SetsAllNames(t *testing.T) {
expect(t, err, nil)
}
func TestGenericFlagValueFromContext(t *testing.T) {
set := flag.NewFlagSet("test", 0)
set.Var(&Parser{"abc", "def"}, "myflag", "doc")
ctx := NewContext(nil, set, nil)
f := &GenericFlag{Name: "myflag"}
expect(t, f.Get(ctx), &Parser{"abc", "def"})
}
func TestParseMultiString(t *testing.T) {
_ = (&App{
Flags: []Flag{
@ -2165,6 +2280,15 @@ func TestTimestampFlagApply_Fail_Parse_Wrong_Time(t *testing.T) {
expect(t, err, fmt.Errorf("invalid value \"2006-01-02T15:04:05Z\" for flag -time: parsing time \"2006-01-02T15:04:05Z\" as \"Jan 2, 2006 at 3:04pm (MST)\": cannot parse \"2006-01-02T15:04:05Z\" as \"Jan\""))
}
func TestTimestampFlagValueFromContext(t *testing.T) {
set := flag.NewFlagSet("test", 0)
now := time.Now()
set.Var(NewTimestamp(now), "myflag", "doc")
ctx := NewContext(nil, set, nil)
f := &TimestampFlag{Name: "myflag"}
expect(t, f.Get(ctx), &now)
}
type flagDefaultTestCase struct {
name string
flag Flag

View File

@ -58,38 +58,6 @@ func (t *Timestamp) Get() interface{} {
return *t
}
// TimestampFlag is a flag with type time
type TimestampFlag struct {
Name string
Aliases []string
Usage string
EnvVars []string
FilePath string
Required bool
Hidden bool
Layout string
Value *Timestamp
DefaultText string
HasBeenSet bool
Destination *Timestamp
}
// IsSet returns whether or not the flag has been set through env or file
func (f *TimestampFlag) IsSet() bool {
return f.HasBeenSet
}
// String returns a readable representation of this value
// (for usage defaults)
func (f *TimestampFlag) String() string {
return FlagStringer(f)
}
// Names returns the names of the flag
func (f *TimestampFlag) Names() []string {
return flagNames(f.Name, f.Aliases)
}
// IsRequired returns whether or not the flag is required
func (f *TimestampFlag) IsRequired() bool {
return f.Required
@ -164,6 +132,11 @@ func (f *TimestampFlag) Apply(set *flag.FlagSet) error {
return nil
}
// Get returns the flags value in the given Context.
func (f *TimestampFlag) Get(ctx *Context) *time.Time {
return ctx.Timestamp(f.Name)
}
// Timestamp gets the timestamp from a flag name
func (cCtx *Context) Timestamp(name string) *time.Time {
if fs := cCtx.lookupFlagSet(name); fs != nil {

View File

@ -6,37 +6,6 @@ import (
"strconv"
)
// UintFlag is a flag with type uint
type UintFlag struct {
Name string
Aliases []string
Usage string
EnvVars []string
FilePath string
Required bool
Hidden bool
Value uint
DefaultText string
Destination *uint
HasBeenSet bool
}
// IsSet returns whether or not the flag has been set through env or file
func (f *UintFlag) IsSet() bool {
return f.HasBeenSet
}
// String returns a readable representation of this value
// (for usage defaults)
func (f *UintFlag) String() string {
return FlagStringer(f)
}
// Names returns the names of the flag
func (f *UintFlag) Names() []string {
return flagNames(f.Name, f.Aliases)
}
// IsRequired returns whether or not the flag is required
func (f *UintFlag) IsRequired() bool {
return f.Required
@ -101,6 +70,11 @@ func (f *UintFlag) GetEnvVars() []string {
return f.EnvVars
}
// Get returns the flags value in the given Context.
func (f *UintFlag) Get(ctx *Context) uint {
return ctx.Uint(f.Name)
}
// Uint looks up the value of a local UintFlag, returns
// 0 if not found
func (cCtx *Context) Uint(name string) uint {

View File

@ -6,37 +6,6 @@ import (
"strconv"
)
// Uint64Flag is a flag with type uint64
type Uint64Flag struct {
Name string
Aliases []string
Usage string
EnvVars []string
FilePath string
Required bool
Hidden bool
Value uint64
DefaultText string
Destination *uint64
HasBeenSet bool
}
// IsSet returns whether or not the flag has been set through env or file
func (f *Uint64Flag) IsSet() bool {
return f.HasBeenSet
}
// String returns a readable representation of this value
// (for usage defaults)
func (f *Uint64Flag) String() string {
return FlagStringer(f)
}
// Names returns the names of the flag
func (f *Uint64Flag) Names() []string {
return flagNames(f.Name, f.Aliases)
}
// IsRequired returns whether or not the flag is required
func (f *Uint64Flag) IsRequired() bool {
return f.Required
@ -101,6 +70,11 @@ func (f *Uint64Flag) GetEnvVars() []string {
return f.EnvVars
}
// Get returns the flags value in the given Context.
func (f *Uint64Flag) Get(ctx *Context) uint64 {
return ctx.Uint64(f.Name)
}
// Uint64 looks up the value of a local Uint64Flag, returns
// 0 if not found
func (cCtx *Context) Uint64(name string) uint64 {

1
go.mod
View File

@ -5,6 +5,7 @@ go 1.18
require (
github.com/BurntSushi/toml v1.1.0
github.com/cpuguy83/go-md2man/v2 v2.0.1
golang.org/x/text v0.3.7
gopkg.in/yaml.v2 v2.4.0
)

6
go.sum
View File

@ -1,11 +1,13 @@
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I=
github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/cpuguy83/go-md2man/v2 v2.0.1 h1:r/myEWzV9lfsM1tFLgDyu0atFtJ1fXn261LKYj/3DxU=
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e h1:FDhOuMEY4JVRztM/gsbk+IKUQ8kj74bxZrgw87eMMVc=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=

2090
godoc-current.txt Normal file

File diff suppressed because it is too large Load Diff

View File

@ -6,19 +6,45 @@ import (
"bufio"
"bytes"
"fmt"
"io/ioutil"
"log"
"math"
"os"
"os/exec"
"path/filepath"
"strings"
"github.com/urfave/cli/v2"
)
var packages = []string{"cli", "altsrc"}
const (
badNewsEmoji = "🚨"
goodNewsEmoji = "✨"
checksPassedEmoji = "✅"
v2diffWarning = `
# The unified diff above indicates that the public API surface area
# has changed. If you feel that the changes are acceptable and adhere
# to the semantic versioning promise of the v2.x series described in
# docs/CONTRIBUTING.md, please run the following command to promote
# the current go docs:
#
# make v2approve
#
`
)
func main() {
top, err := func() (string, error) {
if v, err := sh("git", "rev-parse", "--show-toplevel"); err == nil {
return strings.TrimSpace(v), nil
}
return os.Getwd()
}()
if err != nil {
log.Fatal(err)
}
app := cli.NewApp()
app.Name = "builder"
@ -45,20 +71,52 @@ func main() {
Name: "check-binary-size",
Action: checkBinarySizeActionFunc,
},
{
Name: "generate",
Action: GenerateActionFunc,
},
{
Name: "v2diff",
Flags: []cli.Flag{
&cli.BoolFlag{Name: "color", Value: false},
},
Action: V2Diff,
},
{
Name: "v2approve",
Action: V2Approve,
},
}
app.Flags = []cli.Flag{
&cli.StringFlag{
Name: "tags",
Usage: "set build tags",
},
&cli.PathFlag{
Name: "top",
Value: top,
},
&cli.StringSliceFlag{
Name: "packages",
Value: cli.NewStringSlice("cli", "altsrc", "internal/build", "internal/genflags"),
},
}
err := app.Run(os.Args)
if err != nil {
if err := app.Run(os.Args); err != nil {
log.Fatal(err)
}
}
func sh(exe string, args ...string) (string, error) {
cmd := exec.Command(exe, args...)
cmd.Stdin = os.Stdin
cmd.Stderr = os.Stderr
fmt.Fprintf(os.Stderr, "# ---> %s\n", cmd)
outBytes, err := cmd.Output()
return string(outBytes), err
}
func runCmd(arg string, args ...string) error {
cmd := exec.Command(arg, args...)
@ -66,78 +124,63 @@ func runCmd(arg string, args ...string) error {
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
fmt.Fprintf(os.Stderr, "# ---> %s\n", cmd)
return cmd.Run()
}
func VetActionFunc(_ *cli.Context) error {
return runCmd("go", "vet")
func VetActionFunc(cCtx *cli.Context) error {
return runCmd("go", "vet", cCtx.Path("top")+"/...")
}
func TestActionFunc(c *cli.Context) error {
tags := c.String("tags")
for _, pkg := range packages {
var packageName string
for _, pkg := range c.StringSlice("packages") {
packageName := "github.com/urfave/cli/v2"
if pkg == "cli" {
packageName = "github.com/urfave/cli/v2"
} else {
if pkg != "cli" {
packageName = fmt.Sprintf("github.com/urfave/cli/v2/%s", pkg)
}
coverProfile := fmt.Sprintf("--coverprofile=%s.coverprofile", pkg)
err := runCmd("go", "test", "-tags", tags, "-v", coverProfile, packageName)
if err != nil {
if err := runCmd(
"go", "test",
"-tags", tags,
"-v",
"--coverprofile", pkg+".coverprofile",
"--covermode", "count",
"--cover", packageName,
packageName,
); err != nil {
return err
}
}
return testCleanup()
return testCleanup(c.StringSlice("packages"))
}
func testCleanup() error {
var out bytes.Buffer
func testCleanup(packages []string) error {
out := &bytes.Buffer{}
fmt.Fprintf(out, "mode: count\n")
for _, pkg := range packages {
file, err := os.Open(fmt.Sprintf("%s.coverprofile", pkg))
filename := pkg + ".coverprofile"
lineBytes, err := os.ReadFile(filename)
if err != nil {
return err
}
b, err := ioutil.ReadAll(file)
if err != nil {
return err
}
lines := strings.Split(string(lineBytes), "\n")
out.Write(b)
err = file.Close()
if err != nil {
return err
}
fmt.Fprintf(out, strings.Join(lines[1:], "\n"))
err = os.Remove(fmt.Sprintf("%s.coverprofile", pkg))
if err != nil {
if err := os.Remove(filename); err != nil {
return err
}
}
outFile, err := os.Create("coverage.txt")
if err != nil {
return err
}
_, err = out.WriteTo(outFile)
if err != nil {
return err
}
err = outFile.Close()
if err != nil {
return err
}
return nil
return os.WriteFile("coverage.txt", out.Bytes(), 0644)
}
func GfmrunActionFunc(c *cli.Context) error {
@ -179,17 +222,7 @@ func TocActionFunc(c *cli.Context) error {
filename = "README.md"
}
err := runCmd("markdown-toc", "-i", filename)
if err != nil {
return err
}
err = runCmd("git", "diff", "--exit-code")
if err != nil {
return err
}
return nil
return runCmd("markdown-toc", "-i", filename)
}
// checkBinarySizeActionFunc checks the size of an example binary to ensure that we are keeping size down
@ -201,16 +234,18 @@ func checkBinarySizeActionFunc(c *cli.Context) (err error) {
cliBuiltFilePath = "./internal/example-cli/built-example"
helloSourceFilePath = "./internal/example-hello-world/example-hello-world.go"
helloBuiltFilePath = "./internal/example-hello-world/built-example"
desiredMinBinarySize = 1.675
desiredMaxBinarySize = 2.2
badNewsEmoji = "🚨"
goodNewsEmoji = "✨"
checksPassedEmoji = "✅"
mbStringFormatter = "%.1fMB"
)
desiredMinBinarySize := 1.675
tags := c.String("tags")
if strings.Contains(tags, "urfave_cli_no_docs") {
desiredMinBinarySize = 1.39
}
// get cli example size
cliSize, err := getSize(cliSourceFilePath, cliBuiltFilePath, tags)
if err != nil {
@ -280,6 +315,69 @@ func checkBinarySizeActionFunc(c *cli.Context) (err error) {
return nil
}
func GenerateActionFunc(cCtx *cli.Context) error {
top := cCtx.Path("top")
cliDocs, err := sh("go", "doc", "-all", top)
if err != nil {
return err
}
altsrcDocs, err := sh("go", "doc", "-all", filepath.Join(top, "altsrc"))
if err != nil {
return err
}
if err := os.WriteFile(
filepath.Join(top, "godoc-current.txt"),
[]byte(cliDocs+altsrcDocs),
0644,
); err != nil {
return err
}
return runCmd("go", "generate", cCtx.Path("top")+"/...")
}
func V2Diff(cCtx *cli.Context) error {
os.Chdir(cCtx.Path("top"))
err := runCmd(
"diff",
"--ignore-all-space",
"--minimal",
"--color="+func() string {
if cCtx.Bool("color") {
return "always"
}
return "auto"
}(),
"--unified",
"--label=a/godoc",
filepath.Join("testdata", "godoc-v2.x.txt"),
"--label=b/godoc",
"godoc-current.txt",
)
if err != nil {
fmt.Printf("# %v ---> Hey! <---\n", badNewsEmoji)
fmt.Println(strings.TrimSpace(v2diffWarning))
}
return err
}
func V2Approve(cCtx *cli.Context) error {
top := cCtx.Path("top")
return runCmd(
"cp",
"-v",
filepath.Join(top, "godoc-current.txt"),
filepath.Join(top, "testdata", "godoc-v2.x.txt"),
)
}
func getSize(sourcePath string, builtPath string, tags string) (size int64, err error) {
// build example binary
err = runCmd("go", "build", "-tags", tags, "-o", builtPath, "-ldflags", "-s -w", sourcePath)

View File

@ -0,0 +1,163 @@
package main
import (
"bytes"
"context"
_ "embed"
"log"
"os"
"os/exec"
"os/signal"
"path/filepath"
"strings"
"syscall"
"text/template"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v2/internal/genflags"
"gopkg.in/yaml.v2"
)
const (
defaultPackageName = "cli"
)
func sh(ctx context.Context, exe string, args ...string) (string, error) {
cmd := exec.CommandContext(ctx, exe, args...)
cmd.Stderr = os.Stderr
outBytes, err := cmd.Output()
return string(outBytes), err
}
func main() {
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
defer stop()
top := "../../"
if v, err := sh(ctx, "git", "rev-parse", "--show-toplevel"); err == nil {
top = strings.TrimSpace(v)
}
app := &cli.App{
Name: "genflags",
Usage: "Generate flag types for urfave/cli",
Flags: []cli.Flag{
&cli.PathFlag{
Name: "flag-spec-yaml",
Aliases: []string{"f"},
Value: filepath.Join(top, "flag-spec.yaml"),
},
&cli.PathFlag{
Name: "generated-output",
Aliases: []string{"o"},
Value: filepath.Join(top, "zz_generated.flags.go"),
},
&cli.PathFlag{
Name: "generated-test-output",
Aliases: []string{"t"},
Value: filepath.Join(top, "zz_generated.flags_test.go"),
},
&cli.StringFlag{
Name: "generated-package-name",
Aliases: []string{"p"},
Value: defaultPackageName,
},
&cli.StringFlag{
Name: "generated-test-package-name",
Aliases: []string{"T"},
Value: defaultPackageName + "_test",
},
&cli.StringFlag{
Name: "urfave-cli-namespace",
Aliases: []string{"n"},
Value: "",
},
&cli.StringFlag{
Name: "urfave-cli-test-namespace",
Aliases: []string{"N"},
Value: "cli.",
},
},
Action: runGenFlags,
}
if err := app.RunContext(ctx, os.Args); err != nil {
log.Fatal(err)
}
}
func runGenFlags(cCtx *cli.Context) error {
specBytes, err := os.ReadFile(cCtx.Path("flag-spec-yaml"))
if err != nil {
return err
}
spec := &genflags.Spec{}
if err := yaml.Unmarshal(specBytes, spec); err != nil {
return err
}
if cCtx.IsSet("generated-package-name") {
spec.PackageName = strings.TrimSpace(cCtx.String("generated-package-name"))
}
if strings.TrimSpace(spec.PackageName) == "" {
spec.PackageName = defaultPackageName
}
if cCtx.IsSet("generated-test-package-name") {
spec.TestPackageName = strings.TrimSpace(cCtx.String("generated-test-package-name"))
}
if strings.TrimSpace(spec.TestPackageName) == "" {
spec.TestPackageName = defaultPackageName + "_test"
}
if cCtx.IsSet("urfave-cli-namespace") {
spec.UrfaveCLINamespace = strings.TrimSpace(cCtx.String("urfave-cli-namespace"))
}
if cCtx.IsSet("urfave-cli-test-namespace") {
spec.UrfaveCLITestNamespace = strings.TrimSpace(cCtx.String("urfave-cli-test-namespace"))
} else {
spec.UrfaveCLITestNamespace = "cli."
}
genTmpl, err := template.New("gen").Parse(genflags.TemplateString)
if err != nil {
return err
}
genTestTmpl, err := template.New("gen_test").Parse(genflags.TestTemplateString)
if err != nil {
return err
}
genBuf := &bytes.Buffer{}
if err := genTmpl.Execute(genBuf, spec); err != nil {
return err
}
genTestBuf := &bytes.Buffer{}
if err := genTestTmpl.Execute(genTestBuf, spec); err != nil {
return err
}
if err := os.WriteFile(cCtx.Path("generated-output"), genBuf.Bytes(), 0644); err != nil {
return err
}
if err := os.WriteFile(cCtx.Path("generated-test-output"), genTestBuf.Bytes(), 0644); err != nil {
return err
}
if _, err := sh(cCtx.Context, "goimports", "-w", cCtx.Path("generated-output")); err != nil {
return err
}
if _, err := sh(cCtx.Context, "goimports", "-w", cCtx.Path("generated-test-output")); err != nil {
return err
}
return nil
}

View File

@ -0,0 +1,53 @@
// WARNING: this file is generated. DO NOT EDIT
package {{.PackageName}}
{{range .SortedFlagTypes}}
// {{.TypeName}} is a flag with type {{if .ValuePointer}}*{{end}}{{.GoType}}
type {{.TypeName}} struct {
Name string
DefaultText string
FilePath string
Usage string
Required bool
Hidden bool
HasBeenSet bool
Value {{if .ValuePointer}}*{{end}}{{.GoType}}
Destination *{{.GoType}}
Aliases []string
EnvVars []string
{{range .StructFields}}
{{.Name}} {{.Type}}
{{end}}
}
{{if .GenerateFmtStringerInterface}}
// String returns a readable representation of this value (for usage defaults)
func (f *{{.TypeName}}) String() string {
return {{$.UrfaveCLINamespace}}FlagStringer(f)
}
{{end}}
{{if .GenerateFlagInterface}}
// IsSet returns whether or not the flag has been set through env or file
func (f *{{.TypeName}}) IsSet() bool {
return f.HasBeenSet
}
// Names returns the names of the flag
func (f *{{.TypeName}}) Names() []string {
return {{$.UrfaveCLINamespace}}FlagNames(f.Name, f.Aliases)
}
{{end}}{{/* /if .GenerateFlagInterface */}}
{{end}}{{/* /range .SortedFlagTypes */}}
// vim{{/* 👻 */}}:ro
{{/*
vim:filetype=gotexttmpl
*/}}

View File

@ -0,0 +1,27 @@
// WARNING: this file is generated. DO NOT EDIT
package {{.TestPackageName}}
{{range .SortedFlagTypes}}
{{if .GenerateFlagInterface}}
func Test{{.TypeName}}_SatisfiesFlagInterface(t *testing.T) {
var f {{$.UrfaveCLITestNamespace}}Flag = &{{$.UrfaveCLITestNamespace}}{{.TypeName}}{}
_ = f.IsSet()
_ = f.Names()
}
{{end}}
{{if .GenerateFmtStringerInterface}}
func Test{{.TypeName}}_SatisfiesFmtStringerInterface(t *testing.T) {
var f fmt.Stringer = &{{$.UrfaveCLITestNamespace}}{{.TypeName}}{}
_ = f.String()
}
{{end}}
{{end}}
// vim{{/* 👻 */}}:ro
{{/*
vim:filetype=gotexttmpl
*/}}

View File

@ -0,0 +1,34 @@
package genflags
import (
_ "embed"
"strings"
"golang.org/x/text/cases"
"golang.org/x/text/language"
)
var (
//go:embed generated.gotmpl
TemplateString string
//go:embed generated_test.gotmpl
TestTemplateString string
titler = cases.Title(language.Und, cases.NoLower)
)
func TypeName(goType string, fc *FlagTypeConfig) string {
if fc != nil && strings.TrimSpace(fc.TypeName) != "" {
return strings.TrimSpace(fc.TypeName)
}
dotSplit := strings.Split(goType, ".")
goType = dotSplit[len(dotSplit)-1]
if strings.HasPrefix(goType, "[]") {
return titler.String(strings.TrimPrefix(goType, "[]")) + "SliceFlag"
}
return titler.String(goType) + "Flag"
}

View File

@ -0,0 +1,41 @@
package genflags_test
import (
"fmt"
"testing"
"github.com/urfave/cli/v2/internal/genflags"
)
func TestTypeName(t *testing.T) {
for _, tc := range []struct {
gt string
fc *genflags.FlagTypeConfig
expected string
}{
{gt: "int", fc: nil, expected: "IntFlag"},
{gt: "int", fc: &genflags.FlagTypeConfig{}, expected: "IntFlag"},
{gt: "int", fc: &genflags.FlagTypeConfig{TypeName: "VeryIntyFlag"}, expected: "VeryIntyFlag"},
{gt: "[]bool", fc: nil, expected: "BoolSliceFlag"},
{gt: "[]bool", fc: &genflags.FlagTypeConfig{}, expected: "BoolSliceFlag"},
{gt: "[]bool", fc: &genflags.FlagTypeConfig{TypeName: "ManyTruthsFlag"}, expected: "ManyTruthsFlag"},
{gt: "time.Rumination", fc: nil, expected: "RuminationFlag"},
{gt: "time.Rumination", fc: &genflags.FlagTypeConfig{}, expected: "RuminationFlag"},
{gt: "time.Rumination", fc: &genflags.FlagTypeConfig{TypeName: "PonderFlag"}, expected: "PonderFlag"},
} {
t.Run(
fmt.Sprintf("type=%s,cfg=%v", tc.gt, func() string {
if tc.fc != nil {
return tc.fc.TypeName
}
return "nil"
}()),
func(ct *testing.T) {
actual := genflags.TypeName(tc.gt, tc.fc)
if tc.expected != actual {
ct.Errorf("expected %q, got %q", tc.expected, actual)
}
},
)
}
}

100
internal/genflags/spec.go Normal file
View File

@ -0,0 +1,100 @@
package genflags
import (
"sort"
"strings"
)
type Spec struct {
FlagTypes map[string]*FlagTypeConfig `yaml:"flag_types"`
PackageName string `yaml:"package_name"`
TestPackageName string `yaml:"test_package_name"`
UrfaveCLINamespace string `yaml:"urfave_cli_namespace"`
UrfaveCLITestNamespace string `yaml:"urfave_cli_test_namespace"`
}
func (gfs *Spec) SortedFlagTypes() []*FlagType {
typeNames := []string{}
for name := range gfs.FlagTypes {
if strings.HasPrefix(name, "[]") {
name = strings.TrimPrefix(name, "[]") + "Slice"
}
typeNames = append(typeNames, name)
}
sort.Strings(typeNames)
ret := make([]*FlagType, len(typeNames))
for i, typeName := range typeNames {
ret[i] = &FlagType{
GoType: typeName,
Config: gfs.FlagTypes[typeName],
}
}
return ret
}
type FlagTypeConfig struct {
SkipInterfaces []string `yaml:"skip_interfaces"`
StructFields []*FlagStructField `yaml:"struct_fields"`
TypeName string `yaml:"type_name"`
ValuePointer bool `yaml:"value_pointer"`
}
type FlagStructField struct {
Name string
Type string
}
type FlagType struct {
GoType string
Config *FlagTypeConfig
}
func (ft *FlagType) StructFields() []*FlagStructField {
if ft.Config == nil || ft.Config.StructFields == nil {
return []*FlagStructField{}
}
return ft.Config.StructFields
}
func (ft *FlagType) ValuePointer() bool {
if ft.Config == nil {
return false
}
return ft.Config.ValuePointer
}
func (ft *FlagType) TypeName() string {
return TypeName(ft.GoType, ft.Config)
}
func (ft *FlagType) GenerateFmtStringerInterface() bool {
return ft.skipInterfaceNamed("fmt.Stringer")
}
func (ft *FlagType) GenerateFlagInterface() bool {
return ft.skipInterfaceNamed("Flag")
}
func (ft *FlagType) skipInterfaceNamed(name string) bool {
if ft.Config == nil {
return true
}
lowName := strings.ToLower(name)
for _, interfaceName := range ft.Config.SkipInterfaces {
if strings.ToLower(interfaceName) == lowName {
return false
}
}
return true
}

View File

@ -0,0 +1,112 @@
package genflags_test
import (
"reflect"
"testing"
"github.com/urfave/cli/v2/internal/genflags"
)
func TestSpec_SortedFlagTypes(t *testing.T) {
spec := &genflags.Spec{
FlagTypes: map[string]*genflags.FlagTypeConfig{
"nerf": &genflags.FlagTypeConfig{},
"gerf": nil,
},
}
actual := spec.SortedFlagTypes()
expected := []*genflags.FlagType{
{
GoType: "gerf",
Config: nil,
},
{
GoType: "nerf",
Config: &genflags.FlagTypeConfig{},
},
}
if !reflect.DeepEqual(expected, actual) {
t.Errorf("expected %#v, got %#v", expected, actual)
}
}
func genFlagType() *genflags.FlagType {
return &genflags.FlagType{
GoType: "blerf",
Config: &genflags.FlagTypeConfig{
SkipInterfaces: []string{"fmt.Stringer"},
StructFields: []*genflags.FlagStructField{
{
Name: "Foibles",
Type: "int",
},
{
Name: "Hoopled",
Type: "bool",
},
},
TypeName: "YeOldeBlerfFlag",
ValuePointer: true,
},
}
}
func TestFlagType_StructFields(t *testing.T) {
ft := genFlagType()
sf := ft.StructFields()
if 2 != len(sf) {
t.Errorf("expected 2 struct fields, got %v", len(sf))
return
}
if "Foibles" != sf[0].Name {
t.Errorf("expected struct field order to be retained")
}
}
func TestFlagType_ValuePointer(t *testing.T) {
ft := genFlagType()
if !ft.ValuePointer() {
t.Errorf("expected ValuePointer to be true")
return
}
ft.Config = nil
if ft.ValuePointer() {
t.Errorf("expected ValuePointer to be false")
}
}
func TestFlagType_GenerateFmtStringerInterface(t *testing.T) {
ft := genFlagType()
if ft.GenerateFmtStringerInterface() {
t.Errorf("expected GenerateFmtStringerInterface to be false")
return
}
ft.Config = nil
if !ft.GenerateFmtStringerInterface() {
t.Errorf("expected GenerateFmtStringerInterface to be true")
}
}
func TestFlagType_GenerateFlagInterface(t *testing.T) {
ft := genFlagType()
if !ft.GenerateFlagInterface() {
t.Errorf("expected GenerateFlagInterface to be true")
return
}
ft.Config = nil
if !ft.GenerateFlagInterface() {
t.Errorf("expected GenerateFlagInterface to be true")
}
}

2090
testdata/godoc-v2.x.txt vendored Normal file

File diff suppressed because it is too large Load Diff

507
zz_generated.flags.go Normal file
View File

@ -0,0 +1,507 @@
// WARNING: this file is generated. DO NOT EDIT
package cli
import "time"
// Float64SliceFlag is a flag with type *Float64Slice
type Float64SliceFlag struct {
Name string
DefaultText string
FilePath string
Usage string
Required bool
Hidden bool
HasBeenSet bool
Value *Float64Slice
Destination *Float64Slice
Aliases []string
EnvVars []string
}
// IsSet returns whether or not the flag has been set through env or file
func (f *Float64SliceFlag) IsSet() bool {
return f.HasBeenSet
}
// Names returns the names of the flag
func (f *Float64SliceFlag) Names() []string {
return FlagNames(f.Name, f.Aliases)
}
// GenericFlag is a flag with type Generic
type GenericFlag struct {
Name string
DefaultText string
FilePath string
Usage string
Required bool
Hidden bool
HasBeenSet bool
Value Generic
Destination *Generic
Aliases []string
EnvVars []string
TakesFile bool
}
// String returns a readable representation of this value (for usage defaults)
func (f *GenericFlag) String() string {
return FlagStringer(f)
}
// IsSet returns whether or not the flag has been set through env or file
func (f *GenericFlag) IsSet() bool {
return f.HasBeenSet
}
// Names returns the names of the flag
func (f *GenericFlag) Names() []string {
return FlagNames(f.Name, f.Aliases)
}
// Int64SliceFlag is a flag with type *Int64Slice
type Int64SliceFlag struct {
Name string
DefaultText string
FilePath string
Usage string
Required bool
Hidden bool
HasBeenSet bool
Value *Int64Slice
Destination *Int64Slice
Aliases []string
EnvVars []string
}
// IsSet returns whether or not the flag has been set through env or file
func (f *Int64SliceFlag) IsSet() bool {
return f.HasBeenSet
}
// Names returns the names of the flag
func (f *Int64SliceFlag) Names() []string {
return FlagNames(f.Name, f.Aliases)
}
// IntSliceFlag is a flag with type *IntSlice
type IntSliceFlag struct {
Name string
DefaultText string
FilePath string
Usage string
Required bool
Hidden bool
HasBeenSet bool
Value *IntSlice
Destination *IntSlice
Aliases []string
EnvVars []string
}
// IsSet returns whether or not the flag has been set through env or file
func (f *IntSliceFlag) IsSet() bool {
return f.HasBeenSet
}
// Names returns the names of the flag
func (f *IntSliceFlag) Names() []string {
return FlagNames(f.Name, f.Aliases)
}
// PathFlag is a flag with type Path
type PathFlag struct {
Name string
DefaultText string
FilePath string
Usage string
Required bool
Hidden bool
HasBeenSet bool
Value Path
Destination *Path
Aliases []string
EnvVars []string
TakesFile bool
}
// String returns a readable representation of this value (for usage defaults)
func (f *PathFlag) String() string {
return FlagStringer(f)
}
// IsSet returns whether or not the flag has been set through env or file
func (f *PathFlag) IsSet() bool {
return f.HasBeenSet
}
// Names returns the names of the flag
func (f *PathFlag) Names() []string {
return FlagNames(f.Name, f.Aliases)
}
// StringSliceFlag is a flag with type *StringSlice
type StringSliceFlag struct {
Name string
DefaultText string
FilePath string
Usage string
Required bool
Hidden bool
HasBeenSet bool
Value *StringSlice
Destination *StringSlice
Aliases []string
EnvVars []string
TakesFile bool
}
// IsSet returns whether or not the flag has been set through env or file
func (f *StringSliceFlag) IsSet() bool {
return f.HasBeenSet
}
// Names returns the names of the flag
func (f *StringSliceFlag) Names() []string {
return FlagNames(f.Name, f.Aliases)
}
// TimestampFlag is a flag with type *Timestamp
type TimestampFlag struct {
Name string
DefaultText string
FilePath string
Usage string
Required bool
Hidden bool
HasBeenSet bool
Value *Timestamp
Destination *Timestamp
Aliases []string
EnvVars []string
Layout string
}
// String returns a readable representation of this value (for usage defaults)
func (f *TimestampFlag) String() string {
return FlagStringer(f)
}
// IsSet returns whether or not the flag has been set through env or file
func (f *TimestampFlag) IsSet() bool {
return f.HasBeenSet
}
// Names returns the names of the flag
func (f *TimestampFlag) Names() []string {
return FlagNames(f.Name, f.Aliases)
}
// BoolFlag is a flag with type bool
type BoolFlag struct {
Name string
DefaultText string
FilePath string
Usage string
Required bool
Hidden bool
HasBeenSet bool
Value bool
Destination *bool
Aliases []string
EnvVars []string
}
// String returns a readable representation of this value (for usage defaults)
func (f *BoolFlag) String() string {
return FlagStringer(f)
}
// IsSet returns whether or not the flag has been set through env or file
func (f *BoolFlag) IsSet() bool {
return f.HasBeenSet
}
// Names returns the names of the flag
func (f *BoolFlag) Names() []string {
return FlagNames(f.Name, f.Aliases)
}
// Float64Flag is a flag with type float64
type Float64Flag struct {
Name string
DefaultText string
FilePath string
Usage string
Required bool
Hidden bool
HasBeenSet bool
Value float64
Destination *float64
Aliases []string
EnvVars []string
}
// String returns a readable representation of this value (for usage defaults)
func (f *Float64Flag) String() string {
return FlagStringer(f)
}
// IsSet returns whether or not the flag has been set through env or file
func (f *Float64Flag) IsSet() bool {
return f.HasBeenSet
}
// Names returns the names of the flag
func (f *Float64Flag) Names() []string {
return FlagNames(f.Name, f.Aliases)
}
// IntFlag is a flag with type int
type IntFlag struct {
Name string
DefaultText string
FilePath string
Usage string
Required bool
Hidden bool
HasBeenSet bool
Value int
Destination *int
Aliases []string
EnvVars []string
}
// String returns a readable representation of this value (for usage defaults)
func (f *IntFlag) String() string {
return FlagStringer(f)
}
// IsSet returns whether or not the flag has been set through env or file
func (f *IntFlag) IsSet() bool {
return f.HasBeenSet
}
// Names returns the names of the flag
func (f *IntFlag) Names() []string {
return FlagNames(f.Name, f.Aliases)
}
// Int64Flag is a flag with type int64
type Int64Flag struct {
Name string
DefaultText string
FilePath string
Usage string
Required bool
Hidden bool
HasBeenSet bool
Value int64
Destination *int64
Aliases []string
EnvVars []string
}
// String returns a readable representation of this value (for usage defaults)
func (f *Int64Flag) String() string {
return FlagStringer(f)
}
// IsSet returns whether or not the flag has been set through env or file
func (f *Int64Flag) IsSet() bool {
return f.HasBeenSet
}
// Names returns the names of the flag
func (f *Int64Flag) Names() []string {
return FlagNames(f.Name, f.Aliases)
}
// StringFlag is a flag with type string
type StringFlag struct {
Name string
DefaultText string
FilePath string
Usage string
Required bool
Hidden bool
HasBeenSet bool
Value string
Destination *string
Aliases []string
EnvVars []string
TakesFile bool
}
// String returns a readable representation of this value (for usage defaults)
func (f *StringFlag) String() string {
return FlagStringer(f)
}
// IsSet returns whether or not the flag has been set through env or file
func (f *StringFlag) IsSet() bool {
return f.HasBeenSet
}
// Names returns the names of the flag
func (f *StringFlag) Names() []string {
return FlagNames(f.Name, f.Aliases)
}
// DurationFlag is a flag with type time.Duration
type DurationFlag struct {
Name string
DefaultText string
FilePath string
Usage string
Required bool
Hidden bool
HasBeenSet bool
Value time.Duration
Destination *time.Duration
Aliases []string
EnvVars []string
}
// String returns a readable representation of this value (for usage defaults)
func (f *DurationFlag) String() string {
return FlagStringer(f)
}
// IsSet returns whether or not the flag has been set through env or file
func (f *DurationFlag) IsSet() bool {
return f.HasBeenSet
}
// Names returns the names of the flag
func (f *DurationFlag) Names() []string {
return FlagNames(f.Name, f.Aliases)
}
// UintFlag is a flag with type uint
type UintFlag struct {
Name string
DefaultText string
FilePath string
Usage string
Required bool
Hidden bool
HasBeenSet bool
Value uint
Destination *uint
Aliases []string
EnvVars []string
}
// String returns a readable representation of this value (for usage defaults)
func (f *UintFlag) String() string {
return FlagStringer(f)
}
// IsSet returns whether or not the flag has been set through env or file
func (f *UintFlag) IsSet() bool {
return f.HasBeenSet
}
// Names returns the names of the flag
func (f *UintFlag) Names() []string {
return FlagNames(f.Name, f.Aliases)
}
// Uint64Flag is a flag with type uint64
type Uint64Flag struct {
Name string
DefaultText string
FilePath string
Usage string
Required bool
Hidden bool
HasBeenSet bool
Value uint64
Destination *uint64
Aliases []string
EnvVars []string
}
// String returns a readable representation of this value (for usage defaults)
func (f *Uint64Flag) String() string {
return FlagStringer(f)
}
// IsSet returns whether or not the flag has been set through env or file
func (f *Uint64Flag) IsSet() bool {
return f.HasBeenSet
}
// Names returns the names of the flag
func (f *Uint64Flag) Names() []string {
return FlagNames(f.Name, f.Aliases)
}
// vim:ro

183
zz_generated.flags_test.go Normal file
View File

@ -0,0 +1,183 @@
// WARNING: this file is generated. DO NOT EDIT
package cli_test
import (
"fmt"
"testing"
"github.com/urfave/cli/v2"
)
func TestFloat64SliceFlag_SatisfiesFlagInterface(t *testing.T) {
var f cli.Flag = &cli.Float64SliceFlag{}
_ = f.IsSet()
_ = f.Names()
}
func TestGenericFlag_SatisfiesFlagInterface(t *testing.T) {
var f cli.Flag = &cli.GenericFlag{}
_ = f.IsSet()
_ = f.Names()
}
func TestGenericFlag_SatisfiesFmtStringerInterface(t *testing.T) {
var f fmt.Stringer = &cli.GenericFlag{}
_ = f.String()
}
func TestInt64SliceFlag_SatisfiesFlagInterface(t *testing.T) {
var f cli.Flag = &cli.Int64SliceFlag{}
_ = f.IsSet()
_ = f.Names()
}
func TestIntSliceFlag_SatisfiesFlagInterface(t *testing.T) {
var f cli.Flag = &cli.IntSliceFlag{}
_ = f.IsSet()
_ = f.Names()
}
func TestPathFlag_SatisfiesFlagInterface(t *testing.T) {
var f cli.Flag = &cli.PathFlag{}
_ = f.IsSet()
_ = f.Names()
}
func TestPathFlag_SatisfiesFmtStringerInterface(t *testing.T) {
var f fmt.Stringer = &cli.PathFlag{}
_ = f.String()
}
func TestStringSliceFlag_SatisfiesFlagInterface(t *testing.T) {
var f cli.Flag = &cli.StringSliceFlag{}
_ = f.IsSet()
_ = f.Names()
}
func TestTimestampFlag_SatisfiesFlagInterface(t *testing.T) {
var f cli.Flag = &cli.TimestampFlag{}
_ = f.IsSet()
_ = f.Names()
}
func TestTimestampFlag_SatisfiesFmtStringerInterface(t *testing.T) {
var f fmt.Stringer = &cli.TimestampFlag{}
_ = f.String()
}
func TestBoolFlag_SatisfiesFlagInterface(t *testing.T) {
var f cli.Flag = &cli.BoolFlag{}
_ = f.IsSet()
_ = f.Names()
}
func TestBoolFlag_SatisfiesFmtStringerInterface(t *testing.T) {
var f fmt.Stringer = &cli.BoolFlag{}
_ = f.String()
}
func TestFloat64Flag_SatisfiesFlagInterface(t *testing.T) {
var f cli.Flag = &cli.Float64Flag{}
_ = f.IsSet()
_ = f.Names()
}
func TestFloat64Flag_SatisfiesFmtStringerInterface(t *testing.T) {
var f fmt.Stringer = &cli.Float64Flag{}
_ = f.String()
}
func TestIntFlag_SatisfiesFlagInterface(t *testing.T) {
var f cli.Flag = &cli.IntFlag{}
_ = f.IsSet()
_ = f.Names()
}
func TestIntFlag_SatisfiesFmtStringerInterface(t *testing.T) {
var f fmt.Stringer = &cli.IntFlag{}
_ = f.String()
}
func TestInt64Flag_SatisfiesFlagInterface(t *testing.T) {
var f cli.Flag = &cli.Int64Flag{}
_ = f.IsSet()
_ = f.Names()
}
func TestInt64Flag_SatisfiesFmtStringerInterface(t *testing.T) {
var f fmt.Stringer = &cli.Int64Flag{}
_ = f.String()
}
func TestStringFlag_SatisfiesFlagInterface(t *testing.T) {
var f cli.Flag = &cli.StringFlag{}
_ = f.IsSet()
_ = f.Names()
}
func TestStringFlag_SatisfiesFmtStringerInterface(t *testing.T) {
var f fmt.Stringer = &cli.StringFlag{}
_ = f.String()
}
func TestDurationFlag_SatisfiesFlagInterface(t *testing.T) {
var f cli.Flag = &cli.DurationFlag{}
_ = f.IsSet()
_ = f.Names()
}
func TestDurationFlag_SatisfiesFmtStringerInterface(t *testing.T) {
var f fmt.Stringer = &cli.DurationFlag{}
_ = f.String()
}
func TestUintFlag_SatisfiesFlagInterface(t *testing.T) {
var f cli.Flag = &cli.UintFlag{}
_ = f.IsSet()
_ = f.Names()
}
func TestUintFlag_SatisfiesFmtStringerInterface(t *testing.T) {
var f fmt.Stringer = &cli.UintFlag{}
_ = f.String()
}
func TestUint64Flag_SatisfiesFlagInterface(t *testing.T) {
var f cli.Flag = &cli.Uint64Flag{}
_ = f.IsSet()
_ = f.Names()
}
func TestUint64Flag_SatisfiesFmtStringerInterface(t *testing.T) {
var f fmt.Stringer = &cli.Uint64Flag{}
_ = f.String()
}
// vim:ro