How to specify positional arguments with the flag package in Golang? - go

Sometimes I want to pass an argument on the command line with no name, say a command like wc, which takes a filename as input:
wc filename.txt
With the flag package, it looks like every flag has to be given a name, with a default value if unspecified.
filename := flag.String("filename", "foo.txt", "Which file to count the words for")
However I don't want a default value, I want the program to exit with an error code if an argument is not specified. How would I add a required argument to a Go binary?
I would also like to be able to parse arguments with type information, so just checking the Args() directly doesn't quite do it.

You just have to check flag.NArg().
From https://golang.org/pkg/flag/#NArg:
NArg is the number of arguments remaining after flags have been processed.
flag.Parse()
if flag.NArg() == 0 {
flag.Usage()
os.Exit(1)
}

You can also use the flag.Narg() function to ensure you have the required number of positional arguments, though I don't know what it gives you over len(flag.Args())
if flag.NArg() < minArgs {
// do something
...
}

In case anyone is unsatisfied with the standard flag package behavior of stopping the parse as soon as it sees the first positional arg, you can use these small library functions to parse flags in a way that flags can come before or after positional args.
// ParseFlags parses the command line args, allowing flags to be
// specified after positional args.
func ParseFlags() error {
return ParseFlagSet(flag.CommandLine, os.Args[1:])
}
// ParseFlagSet works like flagset.Parse(), except positional arguments are not
// required to come after flag arguments.
func ParseFlagSet(flagset *flag.FlagSet, args []string) error {
var positionalArgs []string
for {
if err := flagset.Parse(args); err != nil {
return err
}
// Consume all the flags that were parsed as flags.
args = args[len(args)-flagset.NArg():]
if len(args) == 0 {
break
}
// There's at least one flag remaining and it must be a positional arg since
// we consumed all args that were parsed as flags. Consume just the first
// one, and retry parsing, since subsequent args may be flags.
positionalArgs = append(positionalArgs, args[0])
args = args[1:]
}
// Parse just the positional args so that flagset.Args()/flagset.NArgs()
// return the expected value.
// Note: This should never return an error.
return flagset.Parse(positionalArgs)
}

Related

How to unset flags Visited on command line in GoLang for Tests

I'm trying to run tests which call the same function multiple times with different arguments each time. This is an application that accepts different command line flags. If no commandline flag is supplied then the default value is used.
flagset = make(map[string]bool)
flagset["flag1"] = false
flagset["flag2"] = false
flagset["flag3"] = false
flagset["flag4"] = false
func LoadCommandLineArguments(args []string) error{
err := flag.CommandLine.Parse(args)
/*Do error handling
*/
flag.Visit(func(f *flag.Flag) { flagset[f.Name] = true })
// Iterate through all the flags and set their default values whatever was not passed on the commandline.
}
func resetFlags(){
/* Reset flag variables to their original default values and set map values to false */
flagset["flag1"] = false
flagset["flag2"] = false
flagset["flag3"] = false
flagset["flag4"] = false
}
I have different test functions each of which supply different flags for tests. Eg: This is how my test file looks like
func TestFlag1(t *testing.T) {
resetFlags()
err := LoadCommandLineArguments([]string{"-flag1=somevalue1"})
}
func TestFlag2(t *testing.T) {
resetFlags()
err := LoadCommandLineArguments([]string{"-flag2=somevalue2"})
}
func TestFlag3(t *testing.T) {
resetFlags()
err := LoadCommandLineArguments([]string{"-flag3=somevalue3"})
}
func TestFlag4(t *testing.T) {
resetFlags()
err := LoadCommandLineArguments([]string{""})
}
Whenever I run my test file and each unit test separately all of them seem to work fine.
But when I run the whole file test file together, by the time it reaches testFlag4, in LoadCommandLineArguments, it Visits all the flags and thinks all of them were passed on the commandline even though I'm resetting them at the beginning of each test function.
In TestFlag1 it thinks flag1 was passed.
In TestFlag2 it thinks flag1 and flag2 were passed.
In TestFlag3, it thinks flag1, flag2, flag3 were passed.
Similarly, in TestFlag4, it thinks flag1 was also passed, flag2 was also passed, flag3 was also passed, when I didn't pass anything. I want this test to have no command line flags passed. The flags.Visit function should not be able to get true for any of the flags.
I know by setting my map values to false, I'm not actually unsetting the command line values passed but then whats the way to do this?
Is there a way to Unvisit / Unset commandline flags in Golang?
Any help will be appreciated.
Thanks.
Based on the source code for flag this should work (you'll need to add os to your imports):
flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ExitOnError) //flags are now reset
Also, a couple things, as a matter of style:
Maps return the default value when a key is not found. So all of these init / reset lines can be deleted.
flagset = make(map[string]bool)
// the initialization lines are not needed; bool already initalizes to false:
//flagset["flag1"] = false
//...
func LoadCommandLineArguments(args []string) error{
//...
}
func resetFlags(){
/* Reset flag variables */
flagset = make(map[string]bool)//just make a new map
}

What exactly is args ...interface{} means for a parameter of method? [duplicate]

This question already has answers here:
What does "..." mean when next to a parameter in a go function declaration?
(3 answers)
Closed 3 years ago.
I am referring to following method that takes last argument as args ...interfact{})
func (*sqlx.DB).Select(dest interface{}, query string, args ...interface{}) error
https://godoc.org/github.com/jmoiron/sqlx#DB.Select
From my understanding that the method accepts last parameter of any type which is variadic ..
So
selectStmt = 'Select * FROM users where user_id IN (?)'
selectStmt, userArgs, err := sqlx.In(selectStmt, userIDs)// userIDs is a slice
if err != nil {
return nil, errors.Wrap(err, "")
}
selectStmt = s.db.Rebind(selectStmt)
var users []User
err = s.db.Select(&users, selectStmt, userArgs) // wrong one .. Line A
err = s.db.Select(&users, selectStmt, userArgs... ) // right one .. Line B
In the aforementioned code if i comment out Line B , but not Line A it doesn't work. I get following error.
sql: converting argument $1 type: unsupported type []interface {}, a slice of interface *
Question
What exactly happening here , why can't go infer the variadic automatically ?? What is the need of passing extra '...' to the third argument?
What exactly happening here , why can't go infer the variadic automatically ?? What is the need of passing extra '...' to the third argument?
Go doesn't infer the variadic automatically - in fact, Go intentionally infers very little and does very little automatically. You need the ... because it does not infer. It also makes clear, when you pass a slice to a variadic, whether you mean for it to be exploded, or you mean for the slice itself to be a single argument; either could be a valid use case, and rather than making assumptions, Go expects you to be explicit.

how to get caller arguments string in golang? is it impossible?

How can I return user.Class.Name string in reflectCallArgs. Is it impossible?
type User struct {
Class struct {
Name string
}
}
var user = &User{}
reflectCallArgs := func(src interface{}) string {
//how to get this string?
return "user.Class.Name"
}
reflectCallArgs(user.Class.Name)
The nearest thing you can get is the filename and line number where the function is being called, by querying runtime.Caller. https://golang.org/pkg/runtime/#Caller
func Caller(skip int) (pc uintptr, file string, line int, ok bool)
Caller reports file and line number information about function invocations on the calling goroutine's stack. The argument skip is the number of stack frames to ascend, with 0 identifying the caller of Caller. (For historical reasons the meaning of skip differs between Caller and Callers.) The return values report the program counter, file name, and line number within the file of the corresponding call. The boolean ok is false if it was not possible to recover the information.
However, you need to have access to that file and be able to parse the file (or that single line) to get "caller arguments string".

Pass a result from multi-returing function to another one taking only one argument in Go

Is it possible to pass a result form function which returns multiple values directly to function which accepts only one? Example:
func MarshallCommandMap(mapToMarshall map[string]string) string {
return string(json.Marshal(mapToMarshall))
}
The example above will cause compilation error:multiple-value json.Marshal() in single-value context. I know it is possible to get same result with additional variable:
func MarshallCommandMap(mapToMarshall map[string]string) string {
marshaledBytes, marshalingError := json.Marshal(mapToMarshall)
if (marshalingError != nil) {
panic(marshalingError)
}
return string(marshaledBytes)
}
But is it possible to pass only first value direclty without any variable?
I think you mean doing something like python's tuple unpacking.
Unfortunately this is not possible in Go (AFAIK).
No you can't, however 2 things with your code.
Shouldn't panic, either return an error or return an empty string.
You can make it shorter.
Example :
func MarshallCommandMap(mapToMarshall map[string]string) string {
js, _ := json.Marshal(mapToMarshall) //ignore the error
return string(js)
}

Golang Flag gets interpreted as first os.Args argument

I would like to run my program like this:
go run launch.go http://example.com --m=2 --strat=par
"http://example.com" gets interpreted as the first command line argument, which is ok, but the flags are not parsed after that and stay at the default value. If I put it like this:
go run launch.go --m=2 --strat=par http://example.com
then "--m=2" is interpreted as the first argument (which should be the URL).
I could also just remove the os.Args completely, but then I would have only optional flags and I want one (the URL) to be mandatory.
Here's my code:
package main
import (
"fmt"
"webcrawler/crawler"
"webcrawler/model"
"webcrawler/urlutils"
"os"
"flag"
)
func main() {
if len(os.Args) < 2 {
log.Fatal("Url must be provided as first argument")
}
strategy := flag.String("strat", "par", "par for parallel OR seq for sequential crawling strategy")
routineMultiplier := flag.Int("m", 1, "Goroutine multiplier. Default 1x logical CPUs. Only works in parallel strategy")
page := model.NewBasePage(os.Args[1])
urlutils.BASE_URL = os.Args[1]
flag.Parse()
pages := crawler.Crawl(&page, *strategy, *routineMultiplier)
fmt.Printf("Crawled: %d\n", len(pages))
}
I am pretty sure that this should be possible, but I can't figure out how.
EDIT:
Thanks justinas for the hint with the flag.Args(). I now adapted it like this and it works:
...
flag.Parse()
args := flag.Args()
if len(args) != 1 {
log.Fatal("Only one argument (URL) allowed.")
}
page := model.NewBasePage(args[0])
...
os.Args doesn't really know anything about the flag package and contains all command-line arguments. Try flag.Args() (after calling flag.Parse(), of course).
As a followup, to parse flags that follow a command like
runme init -m thisis
You can create your own flagset to skip the first value like
var myValue string
mySet := flag.NewFlagSet("",flag.ExitOnError)
mySet.StringVar(&myValue,"m","mmmmm","something")
mySet.Parse(os.Args[2:])
This tripped me up too, and since I call flag.String/flag.Int64/etc in a couple of places in my app, I didn't want to have to pass around a new flag.FlagSet all over the place.
// If a commandline app works like this: ./app subcommand -flag -flag2
// `flag.Parse` won't parse anything after `subcommand`.
// To still be able to use `flag.String/flag.Int64` etc without creating
// a new `flag.FlagSet`, we need this hack to find the first arg that has a dash
// so we know when to start parsing
firstArgWithDash := 1
for i := 1; i < len(os.Args); i++ {
firstArgWithDash = i
if len(os.Args[i]) > 0 && os.Args[i][0] == '-' {
break
}
}
flag.CommandLine.Parse(os.Args[firstArgWithDash:])
The reason I went with this is because flag.Parse just calls flag.CommandLine.Parse(os.Args[1:]) under the hood anyway.
You can check if the Arg starts with "--" or "-" and avoid using that Arg in a loop.
For example:
for _, file := range os.Args[1:] {
if strings.HasPrefix(file, "--") {
continue
}
//do stuff
}

Resources