Cli: v2 bug: how to pass down global information into subcommand

Created on 22 Dec 2019  路  5Comments  路  Source: urfave/cli

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:

  • there is a --config xxx global option, which accepts a config file, parses it and need to pass down some subcommand-specific items if needed;
  • there is monitor subcommand, which need to consume some items in config file;
  • there problem is, I made the 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:-)

arev2 help wanted kinquestion statuconfirmed

Most helpful comment

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.

All 5 comments

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

Was this page helpful?
0 / 5 - 0 ratings

Related issues

efairon picture efairon  路  5Comments

Zyko0 picture Zyko0  路  6Comments

lynncyrin picture lynncyrin  路  3Comments

blackrez picture blackrez  路  5Comments

costela picture costela  路  3Comments