How to specify "usage" for cli arguments (not flags) - go

For flags I can specify description which appers in --help command
flag.String("a", "", "Is is a flag")
But I don't have flags, only arguments, I use cli like this
mycommand start 4
Is it possible use --help to see description to "start" (and other) arguments?

Since this is not directly supported by flags, I know only of alecthomas/kong which does include argument usage:
package main
import "github.com/alecthomas/kong"
var CLI struct {
Rm struct {
Force bool `help:"Force removal."`
Recursive bool `help:"Recursively remove files."`
Paths []string `arg:"" name:"path" help:"Paths to remove." type:"path"`
} `cmd:"" help:"Remove files."`
Ls struct {
Paths []string `arg:"" optional:"" name:"path" help:"Paths to list." type:"path"`
} `cmd:"" help:"List paths."`
}
func main() {
ctx := kong.Parse(&CLI)
switch ctx.Command() {
case "rm <path>":
case "ls":
default:
panic(ctx.Command())
}
}
You will get with shell --help rm:
$ shell --help rm
usage: shell rm <paths> ...
Remove files.
Arguments:
<paths> ... Paths to remove. <====== "usage" for cli arguments (not flags)!
Flags:
--debug Debug mode.
-f, --force Force removal.
-r, --recursive Recursively remove files.

Related

Golang Argparse not picking correct value for multiple flags

I have a golang binary named test. I have two flags -p and -s using golang argparse library. I want user to pass them like below scenarios:
./test -p
./test -s serviceName
./test -p -s serviceName
Means -p should be passed with empty value and -s should be passed with any service name. 1 and 2 are working fine but in third -p flag is taking value "-s". What i want from 3rd is -p should be empty value ("") and -s should be "serviceName".
Code:
parser := argparse.NewParser("./test", "Check status/version of test")
parser.DisableHelp()
platformFormatVal := parser.String("p", "platform", &argparse.Options{Required: false, Help: "Print status of platform", Default: "Inplatform"})
serviceFormatVal := parser.String("s", "service", &argparse.Options{Required: false, Help: "Print status of service", Default: "Inservice"})
err := parser.Parse(os.Args)
if err != nil {
//generic error
}
platformFormat = *platformFormatVal
serviceFormat = *serviceFormatVal
Haven't used argparse lib, only the standard flag, but according to the docs, you are configuring p as a string argument, while it is actually a boolean flag.
You should configure your arguments similar to this:
sArg := parser.String("s", ...)
pArg := parser.Flag("p", ...)
Then you can use it like this:
if *pArg {
...
}

How to provide the command line args first and then the flags in golang?

Golang's flag package reads the command line flags and args properly if the input provided is of the form : go run main.go -o filename.txt arg1 arg2
But if I try to provide the input like : go run main.go arg1 arg2 -o filename.txt, everything after main.go is read as arguments.
How to make this style work?
My program:
package main
import (
"flag"
"fmt"
)
func main() {
var output string
flag.StringVar(&output, "o", "", "Writes output to the file specified")
flag.Parse()
fmt.Println("Positional Args : ", flag.Args())
fmt.Println("Flag -o : ", output)
}
go run main.go -o filename.txt arg1 arg2
Output:
Positional Args : [arg1 arg2]
Flag -o : filename.txt
go run main.go arg1 arg2 -o filename.txt
Output:
Positional Args : [arg1 arg2 -o filename.txt]
Flag -o :
If you shimmy around with the contents of os.Args, it is possible to accept arg1 arg2 -o filename.txt
Go through the os.Args that is passed in from the command line in the for loop
If a - is seen then set a condition that indicates the first flag has been seen
If the condition is set then populate the "notargs" list. Otherwise, populate the "args" list
There is a bit of extra complication here as the args list that is used to set os.Args to the values that will do the flag processing must include the program name (the original os.Arg[0]) as the first value
This solution does not work with -o filename.txt arg1 arg2
package main
import (
"flag"
"fmt"
"os"
)
func main() {
var output string
var args[]string
var notargs[]string
var in_flags bool=false
for i:=0; i<len(os.Args); i++ {
if os.Args[i][0]=='-' {
in_flags=true
}
if i==0 || in_flags {
notargs=append(notargs,os.Args[i])
} else {
args=append(args,os.Args[i])
}
}
os.Args=notargs
flag.StringVar(&output, "o", "", "Writes output to the file specified")
flag.Parse()
fmt.Println("args ",args)
fmt.Println("Flag -o : ", output)
}

Is a bug: flag.Parse() could not get values after bool arg?

Why could not get the values that following bool type arg in the command line?
Here's my test code:
import (
"flag"
"fmt"
)
var stringVal string
var intVal int
var boolValue bool
func init() {
flag.StringVar(&stringVal, "s", "", "string value")
flag.BoolVar(&boolValue, "b", false, "bool value")
flag.IntVar(&intVal, "i", -1, "int value")
}
func main() {
flag.Parse()
fmt.Println("stringVal:", stringVal, ", boolValue:", boolValue, ", intVal:", intVal)
}
Run it as:
go run flag.go -s test -b true -i 10
Got: stringVal: test , boolValue: true , intVal: -1
go run flag.go -s test -b false -i 10
Got: stringVal: test , boolValue: true , intVal: -1
go run flag.go -b false -s test -i 10
Got: stringVal: , boolValue: true , intVal: -1
go run flag.go -s test -i 10 -b false
Got: stringVal: test , boolValue: true , intVal: 10
Go version: 1.16
Boolean flags are set based on the presence or absence of the flag. They typically default to false, and the presence of the flag is used to modify the default behavior.
For example...
to display the long listing of a directory with ls, you use ls -l, not ls -l true
to make rm safer by having it interactively prompt for deletes, you use rm -i, not rm -i true
to display human-readable output with df, you use df -h, not df -h true
The true and false you're placing after the -b are being provided to your program as arguments, not as flags, and their presence interrupts further processing of flags.
Add the following to the end of your main function:
fmt.Println("Remaining args", flag.Args())
Given an invocation such as go run flag.go -b false -s test -i 10, you'll see that flag processing stopped at false, and that the remaining arguments are passed to your program as non-flag arguments:
$ go run flag.go -b false -s test -i 10
stringVal: , boolValue: true , intVal: -1
Remaining args [false -s test -i 10]
As an aside, the general philosophy behind "boolean" flags is that you give your program some default behavior, and provide two flags: One which modifies that default, and an opposite flag that negates the modification, restoring the default. When processing flags, the last flag will win.
For example, rm provides -i to make the command interactive, and -f to negate the -i flag.
This allows you to set alias rm="rm -i" in your shell, meaning all invocations of rm will have the -i flag applied for safety, prompting you before removing files. However, if you want to run a non-interactive rm, you can still type rm -f, which expands to be rm -i -f, and the last flag (-f) wins. Otherwise, you'd have destroy the alias, run rm and then restore the alias every time you wanted a non-interactive invocation.
A boolean flag tests the existence of the flag, not the value of it. If you want to set a value, use:
go run flag.go -s test -b=false -i 10

How to pass flag values to subcommands in golang urfave cli

I am using urfave at https://github.com/urfave/cli
to create a CLI with two subcommands.
I am able to create a CLI with a subcommand,
but I really have no idea how to define the flags.
What's the difference between the global flag and local flag?
Each command can optionally specify a 'subcommand'. The subcommand is of type Command, which allows for nested / composing commands together.
To achieve something like:
cli-tool command1 command2 --command2flag
you could have a commands structure like:
app := &cli.App{
//...
Commands: []*cli.Command{
{
Name: "command1",
Usage: // ...
Action: //...
SubCommand: []cli.Command{
{
Name: "command2"
Flags: []cli.Flag{
cli.StringFlag{
Name: "command2flag"
// ...
},
},
},
},
},
//...
}
You can see here that command2 is nested in command1's subcommands. And the flags for command2 will only apply to command2. This is an example of a local flag.
Global flags would apply to every command and subcommand. This could be useful for somekind of config that the cli tool might need to use for all commands. e.g. the server address to talk to etc.

How to provide go bin with commands

I use the following code to create command which should run according to some flags that are
passed from the cli.
I use the cobra repo
https://github.com/spf13/cobra
when I run it with go run main.go echo test
I get
Print: test
which works.
Now I run go install open the
bin directory and click on the file newApp (this my name of my app)
and it prints
Usage:
MZR [command]
Available Commands:
echo Echo anything to the screen
help Help about any command
print Print anything to the screen
Flags:
-h, --help help for MZR
Use "MZR [command] --help" for more information about a command.
[Process completed]
And I cannot use any commands (like MZR echo) which I was able when I run it locally with go run main.go echo test
But I want to use it like following MZR -h or MZR echo ,
How I can do it ?
(and also give to my friend the file from the bin that created after go install - which is Unix executable - 3.8 MB )
e.g. like this repo which use the same command line tools and to run it you use hoarder --server
https://github.com/nanopack/hoarder
This is the code for example (to make it more simpler )
package main
import (
"fmt"
"strings"
"github.com/spf13/cobra"
)
func main() {
var echoTimes int
var cmdPrint = &cobra.Command{
Use: "print [string to print]",
Short: "Print anything to the screen",
Long: `print is for printing anything back to the screen.
For many years people have printed back to the screen.`,
Args: cobra.MinimumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("Print: " + strings.Join(args, " "))
},
}
var cmdEcho = &cobra.Command{
Use: "echo [string to echo]",
Short: "Echo anything to the screen",
Long: `echo is for echoing anything back.
Echo works a lot like print, except it has a child command.`,
Args: cobra.MinimumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("Print: " + strings.Join(args, " "))
},
}
var cmdTimes = &cobra.Command{
Use: "times [# times] [string to echo]",
Short: "Echo anything to the screen more times",
Long: `echo things multiple times back to the user by providing
a count and a string.`,
Args: cobra.MinimumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
for i := 0; i < echoTimes; i++ {
fmt.Println("Echo: " + strings.Join(args, " "))
}
},
}
cmdTimes.Flags().IntVarP(&echoTimes, "times", "t", 1, "times to echo the input")
var rootCmd = &cobra.Command{Use: "MZR"}
rootCmd.AddCommand(cmdPrint, cmdEcho)
cmdEcho.AddCommand(cmdTimes)
rootCmd.Execute()
}
The name of the executable is taken from the directory name. Rename the directory newApp to MZR. With this change, the go install command will create a executable with the name MZR. If the executable is on your path, then you can run it from the command line using MZR -h or MZR echo,

Resources