Closed vadmeste closed 2 years ago
Ouch, definitely feels like a bug.
What's happening in your example is that the flags are being refedined at each level so that c.Bool/c.GlobalBool
are going to look at the flag defined at the status
level (which is why ... admin service status -d
works). Theoretically, you should be able to write code like:
package main
import (
"fmt"
"os"
"strconv"
"github.com/urfave/cli"
)
func main() {
globalFlags := []cli.Flag{
cli.BoolFlag{Name: "debug, d", Usage: "Run in debug mode"},
}
adminServiceStatusCmd := cli.Command{
Name: "status",
Action: func(c *cli.Context) {
global := strconv.FormatBool(c.GlobalBool("debug"))
local := strconv.FormatBool(c.Bool("debug"))
fmt.Printf("%s: => local (%s), global (%s)\n", c.Command.Name, local, global)
},
}
adminServiceCmd := cli.Command{
Name: "service",
Subcommands: []cli.Command{adminServiceStatusCmd},
}
adminCmd := cli.Command{
Name: "admin",
Subcommands: []cli.Command{adminServiceCmd},
}
app := cli.NewApp()
app.Name = "lookup"
app.Flags = append([]cli.Flag{}, globalFlags...)
app.Commands = []cli.Command{adminCmd}
app.Run(os.Args)
}
Where -d
would be valid at any level, but this is not currently supported by the library. In this example ... -d admin service status
works, but it doesn't permit the -d
flag to be injected in-between the other commands as it should.
Far from ideal, but you can do something like:
package main
import (
"fmt"
"os"
"github.com/urfave/cli"
)
var debug = false
func setDebug(c *cli.Context) error {
if c.IsSet("debug") {
debug = true
}
return nil
}
func main() {
globalFlags := []cli.Flag{
cli.BoolFlag{Name: "debug, d", Usage: "Run in debug mode"},
}
adminServiceStatusCmd := cli.Command{
Name: "status",
Before: setDebug,
Flags: append([]cli.Flag{}, globalFlags...),
Action: func(c *cli.Context) {
fmt.Printf("%s\n", debug)
},
}
adminServiceCmd := cli.Command{
Name: "service",
Before: setDebug,
Flags: append([]cli.Flag{}, globalFlags...),
Subcommands: []cli.Command{adminServiceStatusCmd},
}
adminCmd := cli.Command{
Name: "admin",
Before: setDebug,
Flags: append([]cli.Flag{}, globalFlags...),
Subcommands: []cli.Command{adminServiceCmd},
}
app := cli.NewApp()
app.Name = "lookup"
app.Flags = append([]cli.Flag{}, globalFlags...)
app.Commands = []cli.Command{adminCmd}
app.Before = setDebug
app.Run(os.Args)
}
as a workaround (or use c.App.Metadata["debug"]
rather than a global debug
variable).
I took a brief look and solving this one unfortunately doesn't appear very straight forward. I think we'll need to curry along the "global" flagset to each subcommand that includes the app flagset as well as any flags defined on its parent.
Pretty old issue but is still this the only way to handle a global flag under different levels of commands/subcommands?
The example above is probably still true! I would be very much in favor of someone creating a PR to make this better 🙏
This issue or PR has been automatically marked as stale because it has not had recent activity. Please add a comment bumping this if you're still interested in it's resolution! Thanks for your help, please let us know if you need anything else.
I think this issue is fixed as of v2?
This issue or PR has been bumped and is no longer marked as stale! Feel free to bump it again in the future, if it's still relevant.
This issue or PR has been automatically marked as stale because it has not had recent activity. Please add a comment bumping this if you're still interested in it's resolution! Thanks for your help, please let us know if you need anything else.
Could this perhaps be documented? Took me some time to figure out this behaviour. Many cli libraries seem to be having problems with this. Thanks for the workaround though. 🙂
Edit: on further inspection. This seems to be very finicky though. It is very sensitive to the placement of the global flags.
This issue or PR has been bumped and is no longer marked as stale! Feel free to bump it again in the future, if it's still relevant.
Could this perhaps be documented?
@AndreasBackx this issue is currently marked as help wanted
and available for anyone who wants to add documentation about it
This issue or PR has been automatically marked as stale because it has not had recent activity. Please add a comment bumping this if you're still interested in it's resolution! Thanks for your help, please let us know if you need anything else.
Closing this as it has become stale.
@AndreasBackx I know I'm necrobumping this and I hope you've found some workaround, but for other devs who are seeing this and thinking of XKCD 979, here's something I've cobbled together which actually works for finding flag values from variously nested levels of cli.Context
s.
import (
"time"
"github.com/urfave/cli/v2"
)
func flagExistsInContext(c *cli.Context, flagName string) bool {
for _, f := range c.LocalFlagNames() {
if f == flagName {
return true
}
}
return false
}
func contextWithFlag(c *cli.Context, flagName string) (*cli.Context, bool) {
var (
ctx = c
ok = false
)
lineage := c.Lineage()
if len(lineage) == 1 {
return c, flagExistsInContext(c, flagName)
}
for i := range lineage {
if flagExistsInContext(lineage[i], flagName) {
ctx = lineage[i]
ok = true
break
}
}
return ctx, ok
}
func GetInt64Slice(c *cli.Context, flagName string) (val []int64) {
flagCtx, ok := contextWithFlag(c, flagName)
if ok {
val = flagCtx.Int64Slice(flagName)
} else {
val = make([]int64, 0)
}
return
}
func GetStringSlice(c *cli.Context, flagName string) (val []string) {
flagCtx, ok := contextWithFlag(c, flagName)
if ok {
val = flagCtx.StringSlice(flagName)
} else {
val = make([]string, 0)
}
return
}
func GetString(c *cli.Context, flagName string, defaultValue ...string) (val string) {
flagCtx, ok := contextWithFlag(c, flagName)
if ok {
val = flagCtx.String(flagName)
} else if len(defaultValue) > 0 {
val = defaultValue[0]
}
return
}
func GetInt64(c *cli.Context, flagName string, defaultValue ...int64) (val int64) {
flagCtx, ok := contextWithFlag(c, flagName)
if ok {
val = flagCtx.Int64(flagName)
} else if len(defaultValue) > 0 {
val = defaultValue[0]
}
return
}
func GetUint64(c *cli.Context, flagName string, defaultValue ...uint64) (val uint64) {
flagCtx, ok := contextWithFlag(c, flagName)
if ok {
val = flagCtx.Uint64(flagName)
} else if len(defaultValue) > 0 {
val = defaultValue[0]
}
return
}
func GetDuration(c *cli.Context, flagName string, defaultValue ...time.Duration) (val time.Duration) {
flagCtx, ok := contextWithFlag(c, flagName)
if ok {
val = flagCtx.Duration(flagName)
} else if len(defaultValue) > 0 {
val = defaultValue[0]
}
return
}
func GetBool(c *cli.Context, flagName string, defaultValue ...bool) (val bool) {
flagCtx, ok := contextWithFlag(c, flagName)
if ok {
val = flagCtx.Bool(flagName)
} else if len(defaultValue) > 0 {
val = defaultValue[0]
}
return
}
func GetPath(c *cli.Context, flagName string, defaultValue ...string) (val string) {
flagCtx, ok := contextWithFlag(c, flagName)
if ok {
val = flagCtx.Path(flagName)
} else if (len(defaultValue)) > 0 {
val = defaultValue[0]
}
return
}
This behaviour is not present in v2
package main
import (
"fmt"
"log"
"os"
"github.com/urfave/cli/v2"
)
func main() {
app := cli.NewApp()
app.Name = "myprogramname"
app.Action = func(c *cli.Context) error {
fmt.Println("c.App.Name for app.Action is", c.App.Name)
return nil
}
app.Flags = []cli.Flag{
&cli.Int64Flag{
Name: "myi",
Value: 10,
},
}
app.Commands = []*cli.Command{
{
Name: "foo",
Action: func(c *cli.Context) error {
fmt.Println("c.App.Name for app.Commands.Action is", c.App.Name)
return nil
},
Subcommands: []*cli.Command{
{
Name: "bar",
/*Before: func(c *cli.Context) error {
return fmt.Errorf("before error")
},*/
Action: func(c *cli.Context) error {
log.Printf("%v", c.Int64("myi"))
fmt.Println("c.App.Name for App.Commands.Subcommands.Action is", c.App.Name)
return nil
},
},
},
},
}
err := app.Run(os.Args)
if err != nil {
log.Fatal()
}
}
$ go run main.go foo bar
2022/10/21 18:06:57 10
c.App.Name for App.Commands.Subcommands.Action is myprogramname
$ go run main.go -myi 11 foo bar
2022/10/21 18:07:06 11
c.App.Name for App.Commands.Subcommands.Action is myprogramname
Since there are workaround for v1 I am closing this issue
Hello community,
My aim is to have some global flags that can be inserted anywhere. The code below registers the
admin
command which has a sub-commandservice
which itself has another sub-command calledstatus
. Debug flag is global. However, when I type the following command, debug flag is not activatedThe code:
Is this a bug ? or am I just misunderstanding the global flag concept ?
Thanks,