Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Accessing global flags from subcommand #1268

Closed
makew0rld opened this issue Apr 22, 2021 · 18 comments
Closed

Accessing global flags from subcommand #1268

makew0rld opened this issue Apr 22, 2021 · 18 comments
Labels
area/v2 relates to / is being considered for v2 kind/question someone asking a question status/triage maintainers still need to look into this
Milestone

Comments

@makew0rld
Copy link

makew0rld commented Apr 22, 2021

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 makew0rld added area/v2 relates to / is being considered for v2 kind/question someone asking a question status/triage maintainers still need to look into this labels Apr 22, 2021
@stale
Copy link

stale bot commented Jul 22, 2021

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 stale bot added the status/stale stale due to the age of it's last update label Jul 22, 2021
@makew0rld
Copy link
Author

Bump

@stale
Copy link

stale bot commented Jul 22, 2021

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 stale bot removed the status/stale stale due to the age of it's last update label Jul 22, 2021
@dargueta
Copy link

dargueta commented Aug 3, 2021

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

@makew0rld
Copy link
Author

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

@dargueta
Copy link

dargueta commented Aug 4, 2021

Oh, I misunderstood. Ignore me! 😅

@meatballhat meatballhat changed the title q: Accessing global flags from subcommand Accessing global flags from subcommand Apr 21, 2022
@dearchap
Copy link
Contributor

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

@makew0rld
Copy link
Author

makew0rld commented Aug 28, 2022

@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.

@dearchap dearchap reopened this Aug 28, 2022
@meatballhat
Copy link
Member

[...] 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?

@makew0rld
Copy link
Author

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.

@dearchap
Copy link
Contributor

dearchap commented Sep 7, 2022

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

@meatballhat
Copy link
Member

@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:

cli/context.go

Lines 245 to 255 in c24c9f3

func lookupGlobalFlagSet(name string, ctx *Context) *flag.FlagSet {
if ctx.parentContext != nil {
ctx = ctx.parentContext
}
for ; ctx != nil; ctx = ctx.parentContext {
if f := ctx.flagSet.Lookup(name); f != nil {
return ctx.flagSet
}
}
return nil
}

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:

cli/context.go

Lines 165 to 176 in d62ac9c

func (cCtx *Context) lookupFlagSet(name string) *flag.FlagSet {
for _, c := range cCtx.Lineage() {
if c.flagSet == nil {
continue
}
if f := c.flagSet.Lookup(name); f != nil {
return c.flagSet
}
}
cCtx.onInvalidFlag(name)
return nil
}

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?

@makew0rld
Copy link
Author

@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.

huiyifyj added a commit to Pengxn/go-xn that referenced this issue Mar 26, 2023
- Move '--config' flag from global options to 'web' subcommand.
- Set default '--config' flag value to 'fyj.ini'.

[cli](github.com/urfave/cli) library requires global flags before subcommands/arguments.
For refer to urfave/cli#1113, urfave/cli#1268 and urfave/cli#1391.
@dearchap
Copy link
Contributor

dearchap commented Jun 8, 2023

@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

@acidjazz
Copy link

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

@dearchap
Copy link
Contributor

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

@acidjazz
Copy link

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

@meatballhat
Copy link
Member

@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! 😁

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area/v2 relates to / is being considered for v2 kind/question someone asking a question status/triage maintainers still need to look into this
Projects
None yet
Development

No branches or pull requests

5 participants