Pass resource from Command To Subcommands in urfave/cli/v2

Viewed 508

Is is possible, and if so how, to let a Command initialize a resource and pass it down to its Subcommands. Image an application that takes its arguments like

$ mycmd db --connect <...> create <...>
$ mycmd db --connect <...> update <...>

This may not be a great example but it illustrates the concept. Here db is some resource that all the subcommands depend on. I would like a single function to be responsible for the initialization of the db resource and then pass the initialized resource down to the subcommands. I can't figure out how to do this with urfave/cli/v2 .

You could do it by creating two separate cli.Apps, one that parses the db part of the arguments just to create a context.Context with context.WithValue and then use that context to create the second cli.App which would parse the remainder of the arguments. I'm sure there's a better way to do it.

I'm grateful for any help!

1 Answers

You can achieve this with context values. You set the value in the Before callback of the parent Command. Below code is copied and modified from the subcommands example:

package main

import (
    "context"
    "fmt"
    "log"
    "os"

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

func main() {
    app := &cli.App{
        Commands: []*cli.Command{
            {
                Name:   "db",
                Before: func(c *cli.Context) error {
                    db := "example"
                    c.Context = context.WithValue(c.Context, "db", db)
                    return nil
                },
                Subcommands: []*cli.Command{
                    {
                        Name:  "connect",
                        Action: func(c *cli.Context) error {
                            db := c.Context.Value("db").(string) // remember to assert to original type
                            fmt.Println("sub command:", db)
                            return nil
                        },
                    },
                },
            },
        },
    }

    err := app.Run(os.Args)
    if err != nil {
        log.Fatal(err)
    }
}

This main uses a string so that you can copy paste and run it. You can replace string with your DB object.

How to test:

$ go build -o example
$ ./example db connect
sub command: example
Related