Hi,
It's great package! I'm not sure it's a problem or I just haven't known how to achieve this task.
I have a 2-level cli tool:
--config xxx global option, which accepts a config file, parses it and need to pass down some subcommand-specific items if needed;monitor subcommand, which need to consume some items in config file;monitor subcommand as a seperate package, now the entrypoint (subcommand action) is in control of cli, I cannot find a opportunity to pass down some information in top-level app into the subcommand.Thanks for your help in advance:-)
In case it's unclear, @renzhengeek I don't know the answer to your question offhand 馃檪
I think the intended way of doing this is leveraging the Metadata field on the top level App, which is always accessible from the *cli.Context in any package:
https://github.com/urfave/cli/blob/db3269ccb900db46f4f9d9c445b416c1a5e882d3/app.go#L80-L81
Here's an example of that in use:
package main
import (
"fmt"
"log"
"os"
"github.com/urfave/cli/v2"
)
func main() {
app := cli.NewApp()
app.Before = func(c *cli.Context) error {
// Parse a config file here to get data
// ...
c.App.Metadata["some key"] = "some value"
return nil
}
app.Commands = []*cli.Command{
{
Name: "cmd",
Action: func(c *cli.Context) error {
val := c.App.Metadata["some key"].(string)
fmt.Println(val)
return nil
},
},
}
if err := app.Run(os.Args); err != nil {
log.Fatal(err)
}
}
While in this case I am doing everything in the main function, you should be able to split this pattern across packages without any issue.
The one consideration is that the type of Metadata is map[string]interface{}, so you have to manage the type of the data yourself. The upside, though, is that any data you need can be placed in there, such as a custom config struct for your application.
I would like to offer an alternative solution.
Although it looks like Action: func(c *cli.Context) error limits you to only use context in your action handlers. This is not true since you can write a function that generates other functions:
func Action(data dType) func(c *cli.Context) error{
return func(c *cli.Context) error{
//use data and c freely here
}
}
You can use this with Action: monitor.Action(data)
This a common pattern for any kind of handler function you may write.
func Action(data dType) func(c *cli.Context) error{
Thanks for the idea. I will give a try later. But, it seems not working: we can only register subcommand with the function signature:
Action: func(c *cli.Context) error {
The problem is, when the wrapper should/can be called instead of calling subcommand action fn directly?
You can use this with Action: monitor.Action(data)
@rliebz Thanks very much for your example. I think the example is good enough to be putted in the doc.
Thanks all for your kind help. Closed:-)
Most helpful comment
I think the intended way of doing this is leveraging the
Metadatafield on the top levelApp, which is always accessible from the*cli.Contextin any package:https://github.com/urfave/cli/blob/db3269ccb900db46f4f9d9c445b416c1a5e882d3/app.go#L80-L81
Here's an example of that in use:
While in this case I am doing everything in the
mainfunction, you should be able to split this pattern across packages without any issue.The one consideration is that the type of
Metadataismap[string]interface{}, so you have to manage the type of the data yourself. The upside, though, is that any data you need can be placed in there, such as a custom config struct for your application.