cli icon indicating copy to clipboard operation
cli copied to clipboard

Accessing global flags from subcommand

Open makew0rld opened this issue 3 years ago • 12 comments

Here is how I am accessing global flags from a subcommand. This func also handles accessing a global flag when the provided context is also global, because it simplifies code.

func globalFlag(name string, c *cli.Context) interface{} {
	ancestor := c.Lineage()[len(c.Lineage())-1]
	if len(ancestor.Args().Slice()) == 0 {
		// When the global context calls this func, the last in the lineage
		// has no args for some reason. So return the second-last instead.
		return c.Lineage()[len(c.Lineage())-2].Value(name)
	}
	return ancestor.Value(name)
}

This has been working for me. Is there a better way or built-in way to do this? Also, do you know why I have to handle that special case? Is that a bug?

makew0rld avatar Apr 22 '21 14:04 makew0rld

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.

stale[bot] avatar Jul 22 '21 02:07 stale[bot]

Bump

makew0rld avatar Jul 22 '21 18:07 makew0rld

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.

stale[bot] avatar Jul 22 '21 18:07 stale[bot]

Duplicate of #427? Looks like this has been asked about several times over the span of years.

dargueta avatar Aug 03 '21 17:08 dargueta

@dargueta no, I'm interested in global flags staying in their usual place, but being accessible from within subcommand code.

makew0rld avatar Aug 03 '21 18:08 makew0rld

Oh, I misunderstood. Ignore me! 😅

dargueta avatar Aug 04 '21 22:08 dargueta

@makeworld-the-better-one The way you do it is fine. I dont think there is a better way.

dearchap avatar Aug 28 '22 13:08 dearchap

@dearchap Well this is obviously kind of a hack, and not something simple for a library user to figure out on their own. If what I'm doing is the best way, then it should be added to the library API as function for getting global args.

And there is even potentially a bug in the library code, as you can see from my original issue comment.

makew0rld avatar Aug 28 '22 18:08 makew0rld

[...] this is obviously kind of a hack, and not something simple for a library user to figure out on their own. If what I'm doing is the best way, then it should be added to the library API as function for getting global args.

@makeworld-the-better-one I agree and I would love too see an addition to the library API to help with this 🤘🏼 Is this something you're up for contributing?

meatballhat avatar Sep 05 '22 14:09 meatballhat

Not right at the moment, but maybe some time in the next couple weeks. I'll comment here if I take it on, so if anyone else wants to before then feel free.

makew0rld avatar Sep 07 '22 13:09 makew0rld

@makeworld-the-better-one couldnt you just use context.Value('globalFlagName') ?

dearchap avatar Sep 07 '22 13:09 dearchap

@makeworld-the-better-one after thinking about this a bit more, I'm recalling the Global{Type} functions that used to be available on *cli.Context in v1. The example code you provide in the description is doing a similar dance to what's in the v1 code:

https://github.com/urfave/cli/blob/c24c9f398d5d925f8c3d990509e8c7b3ae942646/context.go#L245-L255

It seems to me now that dropping the Global{Type} functions in favor of a unified interface that automatically traverses the lineage may not have been sufficient:

https://github.com/urfave/cli/blob/d62ac9c02e3224397c92e27a0d973477092f58d4/context.go#L165-L176

In your usage, are you finding that the internal traversal of context lineage isn't working because there's a flag name conflict or similar?

meatballhat avatar Sep 10 '22 15:09 meatballhat

@meatballhat it's been a while since I used this code, so I'm not sure right now. I don't believe there was a conflict, just that I couldn't easily access the value of a global flag from within a subcommand context.

makew0rld avatar Jan 16 '23 19:01 makew0rld

@makew0rld I really am not seeing the issue.

package main

import (
	"log"
	"os"

	"github.com/urfave/cli/v2"
)

func dummyAction(c *cli.Context) error {
	log.Printf("%d", c.NumFlags())
	log.Printf("val %v", c.Value("hello"))
	for index, ctx := range c.Lineage() {
		log.Printf("%d %v", index, ctx.Value("hello"))
		if ctx.App != nil {
			log.Printf("%v", ctx.App.Flags)
		}
	}
	return nil
}

var (
	command1 = cli.Command{
		Name:        "command1",
		Usage:       "command1.Usage",
		Description: "command1.Description",
		Subcommands: []*cli.Command{
			{
				Action:      dummyAction,
				Name:        "action1",
				Usage:       "command1action1.Usage",
				ArgsUsage:   "<arg1>",
				Description: "command1action1.Description",
			},
			{
				Action:      dummyAction,
				Name:        "action2",
				Usage:       "command1action2.Usage",
				ArgsUsage:   "<arg1> [<arg2>]",
				Description: "command1action2.Description",
			},
			{
				Action:      dummyAction,
				Name:        "action3",
				Usage:       "command1action3.Usage",
				ArgsUsage:   "<arg1> [<arg2>]...",
				Description: "command1action3.Description",
			},
		},
	}
	command2 = cli.Command{
		Action:      dummyAction,
		Name:        "command2",
		Usage:       "command2.Usage",
		Description: "command2.Description",
		Subcommands: []*cli.Command{
			{
				Action:      dummyAction,
				Name:        "action1",
				Usage:       "command2action1.Usage",
				ArgsUsage:   "<arg1>",
				Description: "command2action1.Description",
			},
			{
				Action:      dummyAction,
				Name:        "action2",
				Usage:       "command2action2.Usage",
				ArgsUsage:   "<arg1> [<arg2>]",
				Description: "command2action2.Description",
			},
			{
				Action:      dummyAction,
				Name:        "action3",
				Usage:       "command2action3.Usage",
				ArgsUsage:   "<arg1> [<arg2>]...",
				Description: "command2action3.Description",
			},
		},
	}
	command3 = cli.Command{
		Action:      dummyAction,
		Name:        "command3",
		Usage:       "command3.Usage",
		ArgsUsage:   "<arg1> [<arg2>]...",
		Description: "command3.Description",
	}
)

func main() {
	app := cli.NewApp()
	app.Name = "app.Name"
	//app.Usage = "app.Usage"
	app.Version = "app.Version"
	app.EnableBashCompletion = true
	app.Flags = []cli.Flag{
		&cli.StringFlag{
			Name: "hello",
		},
	}
	app.Commands = []*cli.Command{
		&command1,
		&command2,
		&command3,
	}
	app.Run(os.Args)
}
$ ./main --hello foo command2 
2023/06/07 20:45:42 0
2023/06/07 20:45:42 val foo

You can get the value of a global flag from a subcommand action. If you mean set the value of a global flag in a subcommand that support is available in v3 via marking a flag as persistent

dearchap avatar Jun 08 '23 00:06 dearchap

can this tun into a feature? I have a cli im working on soon where every sub-command will need a --env flag

acidjazz avatar Jun 10 '23 01:06 acidjazz

This is a feature in v3. It's already implemented and working. You can try it out right now

dearchap avatar Jun 10 '23 01:06 dearchap

@dearchap can you point me to v3? i see no branch/tag/release/docs/etc

acidjazz avatar Jun 11 '23 05:06 acidjazz

@acidjazz v3 is what's in progress in the main branch. There are a few alpha tags available which may be installed/used via the /v3 suffix like:

go get github.com/urfave/cli/v3

I hope this helps! 😁

meatballhat avatar Jun 11 '23 15:06 meatballhat