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

Making StringMap/StringMapVar optional? #4

Closed
SlowPokeInTexas opened this issue Sep 11, 2019 · 2 comments
Closed

Making StringMap/StringMapVar optional? #4

SlowPokeInTexas opened this issue Sep 11, 2019 · 2 comments
Assignees
Labels

Comments

@SlowPokeInTexas
Copy link

SlowPokeInTexas commented Sep 11, 2019

Greetings, this is my favorite of the Go command-line parsing mechanisms. One question though: I noticed that StringMap/StringMapVar have a minimum of 1 entry. In my case I intend to fall back to a configuration file for map[string]string values that aren't explicitly provided on the command-line. Does this sound reasonable or am I making an incorrect usage assumption?

@DavidGamba
Copy link
Owner

Hi @SlowPokeInTexas it is awesome to know that someone else besides me and my team are using the tool.

I noticed that StringMap/StringMapVar have a minimum of 1 entry. In my case I intend to fall back to a configuration file for map[string]string values that aren't explicitly provided on the command-line.

I normally accomplish what I believe you are trying to say in the following way:

Given this program main.txt:

package main

import (
	"fmt"
	"io/ioutil"
	"log"
	"os"

	"github.com/DavidGamba/go-getoptions"
)

var logger = log.New(ioutil.Discard, "", log.LstdFlags)

func main() {
	config := map[string]string{
		"region":  "us-east-1",
		"profile": "default",
	}
	opt := getoptions.New()
	opt.Bool("help", false, opt.Alias("?"))
	opt.Bool("debug", false)
	opt.StringMapVar(&config, "config", 1, 99)
	remaining, err := opt.Parse(os.Args[1:])
	if err != nil {
		fmt.Fprintf(os.Stderr, "ERROR: %s\n", err)
		os.Exit(1)
	}
	if opt.Called("help") {
		fmt.Fprintln(os.Stderr, opt.Help())
		os.Exit(1)
	}
	if opt.Called("debug") {
		logger.SetOutput(os.Stderr)
	}
	log.Println(remaining)

	fmt.Printf("%#v\n", config)
}

You can run the following on the cli:

$ go run main.go                                                                                                                                                                                                                                          
2019/09/12 08:42:22 []
map[string]string{"profile":"default", "region":"us-east-1"}

$ go run main.go -config profile=hello    
2019/09/12 08:42:32 []                                                                                                                                                                                                                                    
map[string]string{"profile":"hello", "region":"us-east-1"}   

$ go run main.go -config profile=hello region=us-west-2                                                                                                                                                                                                   
2019/09/12 08:42:48 []
map[string]string{"profile":"hello", "region":"us-west-2"}

So you can see that if you always pre-load the config map, its entries will not be overwritten unless explicitly passed.

Another option to do the same, is to have two maps, one for the config unmarshaling and one for the config overrides, then you merge them by iterating over the second and overriding the first one.

Something like main2.txt:

package main

import (
	"fmt"
	"io/ioutil"
	"log"
	"os"

	"github.com/DavidGamba/go-getoptions"
)

var logger = log.New(ioutil.Discard, "", log.LstdFlags)

func main() {
	var configFile string
	var configOverride map[string]string

	opt := getoptions.New()
	opt.Bool("help", false, opt.Alias("?"))
	opt.Bool("debug", false)
	opt.StringVar(&configFile, "file", "config.yaml")
	opt.StringMapVar(&configOverride, "config", 1, 99)
	remaining, err := opt.Parse(os.Args[1:])
	if err != nil {
		fmt.Fprintf(os.Stderr, "ERROR: %s\n", err)
		os.Exit(1)
	}
	if opt.Called("help") {
		fmt.Fprintln(os.Stderr, opt.Help())
		os.Exit(1)
	}
	if opt.Called("debug") {
		logger.SetOutput(os.Stderr)
	}
	log.Println(remaining)

	// TODO: Unmarshall configFile into config var
	// ...
	config := map[string]string{
		"region":  "us-east-1",
		"profile": "default",
	}

	// Override defaults with configOverride
	for k, v := range configOverride {
		config[k] = v
	}

	fmt.Printf("%#v\n", config)
}

With the same results:

$ go run main.go
2019/09/12 08:55:29 []
map[string]string{"profile":"default", "region":"us-east-1"}

$ go run main.go -config profile=hello
2019/09/12 08:55:42 []
map[string]string{"profile":"hello", "region":"us-east-1"}

$ go run main.go -config profile=hello region=us-west-2
2019/09/12 08:55:52 []
map[string]string{"profile":"hello", "region":"us-west-2"}

Let me you what you think of this approach.

@DavidGamba DavidGamba self-assigned this Sep 12, 2019
@SlowPokeInTexas
Copy link
Author

Thank you David. Just now getting back to this project; what you suggest works fine.

Best Regards!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants