golang check if cli integer flag exists - go

I'm able to see if a flag exists when its a bool. I'm trying to figure out how to see if a flag exits if its integer, if it does exist use the value if not ignore.
For example, the code below does something if either Bool flags are used. BUT I want to set a flag for -month ie go run appname.go -month=01 should then save the value of 01 but if -month is not used then it should be ignored like a bool flag. In the code below I got around this by making a default value of 0 so if no -month flag is used the value is 0. Is there a better way to do this?
package main
import (
"flag"
"fmt"
"time"
)
func main() {
//Adding flags
useCron := flag.Bool("cron", false, "-cron, uses cron flags and defaults to true")
useNow := flag.Bool("now", false, "-now, uses cron flags and defaults to true")
useMonth := flag.Int("month", 0, "-month, specify a month")
flag.Parse()
//If -cron flag is used
if *useCron {
fmt.Printf("Cron set using cron run as date")
return
} else if *useNow {
fmt.Printf("Use Current date")
return
}
if *useMonth != 0 {
fmt.Printf("month %v\n", *useMonth)
}
}

See the documentation and example for the flag.Value interface :
You may define a custom type, which implements flag.Value, for example :
type CliInt struct {
IsSet bool
Val int
}
func (i *CliInt) String() string {
if !i.IsSet {
return "<not set>"
}
return strconv.Itoa(i.Val)
}
func (i *CliInt) Set(value string) error {
v, err := strconv.Atoi(value)
if err != nil {
return err
}
i.IsSet = true
i.Val = v
return nil
}
and then use flag.Var() to bind it :
flag.Var(&i1, "i1", "1st int to parse")
flag.Var(&i2, "i2", "2nd int to parse")
after calling flag.Parse(), you may check the .IsSet flag to see if that flag was set.
playground
Another way is to call flag.Visit() after calling flag.Parse() :
var isSet1, isSet2 bool
i1 := flag.Int("i1", 0, "1st int to parse")
i2 := flag.Int("i2", 0, "2nd int to parse")
flag.Parse([]string{"-i1=123"})
// flag.Visit will only visit flags which have been explicitly set :
flag.Visit(func(fl *flag.Flag) {
if fl.Name == "i1" {
isSet1 = true
}
if fl.Name == "i2" {
isSet2 = true
}
})
playground

In the bool flag also you can't check if exist or not because you are checking the default false value. If you will change it to true you will always get it as exists.
useCron := flag.Bool("cron", true, "-cron, uses cron flags and defaults to true")
useNow := flag.Bool("now", true, "-now, uses cron flags and defaults to true")
The same goes for any other data type, we can only check the default values.
You can use flag.NFlag().
This function returns a number of flags set by the CLI.
This option makes sense when all the options are required.
#LeGEC answer fulfills your requirement.

Related

Golang function default value

I'm trying to assign default value using struct, but how do I distinguish if it is zero value or the actual input value without using pointer?
type Options struct {
a bool `default:"true"`
// other options
}
func processOptions(options *Options) {
if !options.a { // zero value - false
options.a = true // what if the user actually input false?
}
}
func test(options Options) {
processOptions(&options)
// ...
}
the pointer approach will be
type Options struct ...
func processOptions(options *Options) {
if options.a == nil {
aDefault := true
options.a = &aDefault
}
}
func test(options Options) ...
a := false
options := Options{&a}
test(options)
functional options mentioned by #maja takes advantages of variadic functions and struct type to make the 'constructor' initialize options safely regarding the types of each option.
type Options struct {
a bool
// other options
}
type Option func(*Options)
func WithA(a bool) Option {
return func(options *Options) {
options.a = a
}
}
func NewOptions(opts ...Option) *Options {
options := &Options{
a: true // default value
}
for _, opt := range opts {
opt(options)
}
return options
}
func test(options Options) {
// ...
}
options := NewOptions(
WithA(false),
)
test(*options)
In many cases there is no need to distinguish between a default value, or the value being explicitly set to zero.
Otherwise, you could use a pointer, or another field as a presence flag
type Option struct {
a bool
aProvided bool
}
func processOption(option *Option) {
if !option.aProvided
option.a = true
}
}
This would require callers to initialize this extra field as well as a. To reiterate my above advice, it is often easier if you don't need to distinguish.
Looks like you already solve it your self using *string. But you want to avoid that ...
Why don't you make your all your options are default as it is. for example you have options auto=true and repeat=false. You can rename your options for default value like auto to disableAuto
type Option struct {
Repeat bool // because repeat by default false
DisableAuto bool // because auto by default true, this options purpose is to disabled auto
}
Updated:
or maybe you want functional options patterns, here my example https://play.golang.org/p/4DsSaQQkWg0
for more reference you can see here https://golang.cafe/blog/golang-functional-options-pattern.html

Unable to read terraform variables.tf files into may go program

I am attempting to write a go program that reads in a terraform variables.tf and populates a struct for later manipulation. However, I am getting errors when attempting to "parse" the file. I Am hoping someone can tell me what I am doing wrong:
Code:
package main
import (
"fmt"
"io/ioutil"
"log"
"os"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/gohcl"
"github.com/hashicorp/hcl/v2/hclsyntax"
)
type Config struct {
Upstreams []*TfVariable `hcl:"variable,block"`
}
type TfVariable struct {
Name string `hcl:",label"`
// Default string `hcl:"default,optional"`
Type string `hcl:"type"`
Description string `hcl:"description,attr"`
// validation block
Sensitive bool `hcl:"sensitive,optional"`
}
func main() {
readHCLFile("examples/string.tf")
}
// Exits program by sending error message to standard error and specified error code.
func abort(errorMessage string, exitcode int) {
fmt.Fprintln(os.Stderr, errorMessage)
os.Exit(exitcode)
}
func readHCLFile(filePath string) {
content, err := ioutil.ReadFile(filePath)
if err != nil {
log.Fatal(err)
}
fmt.Printf("File contents: %s", content) // TODO: Remove me
file, diags := hclsyntax.ParseConfig(content, filePath, hcl.Pos{Line: 1, Column: 1})
if diags.HasErrors() {
log.Fatal(fmt.Errorf("ParseConfig: %w", diags))
}
c := &Config{}
diags = gohcl.DecodeBody(file.Body, nil, c)
if diags.HasErrors() {
log.Fatal(fmt.Errorf("DecodeBody: %w", diags))
}
fmt.Println(c) // TODO: Remove me
}
ERROR
File contents: variable "image_id" {
type = string
description = "The id of the machine image (AMI) to use for the server."
sensitive = false
}
variable "other_id" {
type = string
description = "The id of the machine image (AMI) to use for the server."
sensitive = true
}
2021/03/13 19:55:49 DecodeBody: examples/string.tf:2,17-23: Variables not allowed; Variables may not be used here., and 3 other diagnostic(s)
exit status 1
Stack driver question is sadly for hcl1
Blog post I am referencing.
It looks like it's a bug/feature of the library, since as soon as you change string to "string", e.g.,
variable "image_id" {
type = string
...
to
variable "image_id" {
type = "string"
...
gohcl.DecodeBody succeeds.
--- UPDATE ---
So, they do use this package in Terraform, BUT they custom-parse configs, i.e., they don't use gohcl.DecodeBody. They also custom-treat type attributes by using hcl.ExprAsKeyword (compare with description). As you assumed, they do use a custom type for type, but with custom parsing you don't have to.
Below is a working example:
package main
import (
"fmt"
"log"
"os"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/gohcl"
"github.com/hashicorp/hcl/v2/hclsyntax"
)
var (
configFileSchema = &hcl.BodySchema{
Blocks: []hcl.BlockHeaderSchema{
{
Type: "variable",
LabelNames: []string{"name"},
},
},
}
variableBlockSchema = &hcl.BodySchema{
Attributes: []hcl.AttributeSchema{
{
Name: "description",
},
{
Name: "type",
},
{
Name: "sensitive",
},
},
}
)
type Config struct {
Variables []*Variable
}
type Variable struct {
Name string
Description string
Type string
Sensitive bool
}
func main() {
config := configFromFile("examples/string.tf")
for _, v := range config.Variables {
fmt.Printf("%+v\n", v)
}
}
func configFromFile(filePath string) *Config {
content, err := os.ReadFile(filePath) // go 1.16
if err != nil {
log.Fatal(err)
}
file, diags := hclsyntax.ParseConfig(content, filePath, hcl.Pos{Line: 1, Column: 1})
if diags.HasErrors() {
log.Fatal("ParseConfig", diags)
}
bodyCont, diags := file.Body.Content(configFileSchema)
if diags.HasErrors() {
log.Fatal("file content", diags)
}
res := &Config{}
for _, block := range bodyCont.Blocks {
v := &Variable{
Name: block.Labels[0],
}
blockCont, diags := block.Body.Content(variableBlockSchema)
if diags.HasErrors() {
log.Fatal("block content", diags)
}
if attr, exists := blockCont.Attributes["description"]; exists {
diags := gohcl.DecodeExpression(attr.Expr, nil, &v.Description)
if diags.HasErrors() {
log.Fatal("description attr", diags)
}
}
if attr, exists := blockCont.Attributes["sensitive"]; exists {
diags := gohcl.DecodeExpression(attr.Expr, nil, &v.Sensitive)
if diags.HasErrors() {
log.Fatal("sensitive attr", diags)
}
}
if attr, exists := blockCont.Attributes["type"]; exists {
v.Type = hcl.ExprAsKeyword(attr.Expr)
if v.Type == "" {
log.Fatal("type attr", "invalid value")
}
}
res.Variables = append(res.Variables, v)
}
return res
}
Add for completeness, example/string.tf:
variable "image_id" {
type = string
description = "The id of the machine image (AMI) to use for the server."
sensitive = false
}
variable "other_id" {
type = string
description = "The id of the machine image (AMI) to use for the server."
sensitive = true
}
Since the Terraform language makes extensive use of various HCL features that require custom programming with the low-level HCL API, the Terraform team maintains a Go library terraform-config-inspect which understands the Terraform language enough to extract static metadata about top-level objects, including variables. It also deals with the fact that Terraform allows variable definitions in any .tf or .tf.json file interleaved with other declarations; putting them in variables.tf is only a convention.
For example:
mod, diags := tfconfig.LoadModule("examples")
if diags.HasErrors() {
log.Fatalf(diags.Error())
}
for _, variable := range mod.Variables {
fmt.Printf("%#v\n", variable)
}
This library is the same code used by Terraform Registry to produce the documentation about module input variables, so it supports all Terraform language versions that the Terraform Registry does (at the time of writing, going back to the Terraform v0.10 language, since that's the first version that can install modules from a registry) and supports both the HCL native syntax and JSON representations of the Terraform language.

Can I set default max length for string fields in struct?

I have multiple structs in my application using golang. Some fields in a struct have maxsize tags, some does not have.
for e.g:
type structone struct {
fieldone string `valid:MaxSize(2)`
fieldtwo string
}
type structtwo struct {
fieldone string `valid:MaxSize(2)`
fieldtwo string
}
So I want to set default maxsize for all fields, if does not contain any valid max size tags in run time. Is it possible? Can somebody help.
Can I set default max length for string fields in struct?
No.
The string predeclared type does not allow you to limit the length of the string value it may hold.
What you may do is use an unexported field so it cannot be accessed (set) outside of your package, and provide a setter method in which you check the length, and refuse to set it if it does not meet your requirements (or cap the value to the allowed max).
For example:
func (s *structone) SetFieldone(v string) error {
if len(v) > 2 {
return errors.New("too long")
}
s.fieldone = v
return nil
}
The other answers seem to assume you are using vanilla strings in Go and asking if you could limit their max size. That could be achieved with some of the suggestions made.
However, from the code snippet you have provided, I infer that you are asking whether the validate go package can specify a default max size of all fields in a struct using tags.
Unfortunately, that library does not currently support specifying a default validation tag for all fields. You have to explicitly define the validation tag for all fields of a struct.
What you are trying to achieve is possible, however, but the library needs to be extended.
One suggestion is extending it to support syntax such as:
type MyStruct struct {
valid.Default `valid:MaxSize(5)`
field1 string
field2 string
}
this program read itself, add the tag valid:MaxSize(2) to the property structone.fieldone, then prints the updated program to os.Stdout.
package main
import (
"go/ast"
"go/printer"
"go/token"
"log"
"os"
"strings"
"golang.org/x/tools/go/ast/astutil"
"golang.org/x/tools/go/loader"
)
type structone struct {
fieldone string
fieldtwo string
}
func main() {
var conf loader.Config
_, err := conf.FromArgs([]string{"."}, false)
if err != nil {
log.Fatal(err)
}
prog, err := conf.Load()
if err != nil {
log.Fatal(err)
}
astutil.Apply(prog.InitialPackages()[0].Files[0], addTag("structone.fieldone", "`valid:MaxSize(2)`"), nil)
printer.Fprint(os.Stdout, prog.Fset, prog.InitialPackages()[0].Files[0])
}
func addTag(p string, tag string) func(*astutil.Cursor) bool {
pp := strings.Split(p, ".")
sName := pp[0]
pName := pp[1]
return func(cursor *astutil.Cursor) bool {
n := cursor.Node()
if x, ok := n.(*ast.TypeSpec); ok {
return x.Name.Name == sName
} else if x, ok := n.(*ast.Field); ok {
for _, v := range x.Names {
if v.Name == pName {
x.Tag = &ast.BasicLit{
Value: tag,
Kind: token.STRING,
}
}
}
} else if _, ok := n.(*ast.File); ok {
return true
} else if _, ok := n.(*ast.GenDecl); ok {
return true
} else if _, ok := n.(*ast.TypeSpec); ok {
return true
} else if _, ok := n.(*ast.StructType); ok {
return true
} else if _, ok := n.(*ast.FieldList); ok {
return true
}
return false
}
}

Check if Flag Was Provided in Go

With the flag package, is there a good way to distinguish if a string flag was passed?
For example, when the flag is not passed, I want to set it to a dynamic default value. However, I want to set it to empty if the flag was provided but with a value of "".
Current I am doing the following:
flagHost = flag.String(flagHostFlagKey, "", "...")
...
setHostname := false
for _, arg := range os.Args {
if arg == "-"+flagHostFlagKey {
setHostname = true
}
}
if !setHostname {
...
Which seems to work fine, but is kind of ugly. Is there a better way while staying with the standard flag package?
Use the flag.Visit()
Description:
Visit visits the command-line flags in lexicographical order, calling fn for each. It visits only those flags that have been set.
use:
func isFlagPassed(name string) bool {
found := false
flag.Visit(func(f *flag.Flag) {
if f.Name == name {
found = true
}
})
return found
}
The built-in flag types don't support distinguishing default values and explicit assignment to the default value. However, the flag package is quite flexible, and allows you to roll your own type that does, using the flag.Value interface.
Here's a full example that contains a string flag which records if it's been assigned to.
package main
import (
"flag"
"fmt"
)
type stringFlag struct {
set bool
value string
}
func (sf *stringFlag) Set(x string) error {
sf.value = x
sf.set = true
return nil
}
func (sf *stringFlag) String() string {
return sf.value
}
var filename stringFlag
func init() {
flag.Var(&filename, "filename", "the filename")
}
func main() {
flag.Parse()
if !filename.set {
fmt.Println("--filename not set")
} else {
fmt.Printf("--filename set to %q\n", filename.value)
}
}
Here's some example runs:
$ go run a.go -filename=abc
--filename set to "abc"
$ go run a.go -filename=
--filename set to ""
$ go run a.go
--filename not set
The issue with using a custom flag type (the stringFlag example in this thread) is you'll slightly upset the PrintDefaults output (i.e. --help). For example, with a string username flag and a stringFlag servername flag, --help looks like this:
-server value
server:port (default localhost:1234)
-username string
username (default "kimmi")
Note these are both string arguments as far as the user is concerned, but presented differently as a stringFlag is not a string.
flag's Flagset has an internal map that includes the flags that were declared ('formal') and those actually set ('actual'). The former is available via Lookup(), though alas the latter is not exposed, or you could just write:
var servername = flag.String("server", "localhost:8129", "server:port")
flag.Parse()
if f := flag.CommandLine.LookupActual("server"); f != nil {
fmt.Printf("server set to %#v\n", f)
} else {
fmt.Printf("server not set\n")
}
Seems like the best you can do, if you want consistent PrintDefaults() output, is to use Visit to extract your own view of 'actual' (VisitAll does the same thing with 'formal'):
var servername = flag.String("server", "localhost:8129", "server:port")
flag.Parse()
flagset := make(map[string]bool)
flag.Visit(func(f *flag.Flag) { flagset[f.Name]=true } )
if flagset["server"] {
fmt.Printf("server set via flags\n")
} else {
fmt.Printf("server not explicitly set, using default\n")
}
To use a dynamic default value for a flag, create the flag with the default set to the dynamic value:
func main() {
flagHost = flag.String(flagHostFlagKey, computedHostFlag(), "...")
flag.Parse()
// *flagHost equals the return value from computedHostFlag() if
// the flag is not specified on the command line.
...
}
With this approach, it's not necessary to detect if the flag is specified on the command line. Also, help shows the correct default.
If the computed value depends on other flags or is expensive to calculate, then use the approach suggested in one of the other answers.
Face with same problem, but have even complex case with bool flag, in this case computedHostFlag() not working, since you can provide to flag creation only true or false. "type stringFlag struct" solution also not the best, since ruin idea of default values.
Solve it in this way: create two sets of flags, with different default values, after parse - just check - if flag in first flagset have the same value that flag from second flagset - that it means that flag value was provided by user from command line. If they different - than this mean that flag was set by default.
package main
import (
"fmt"
"flag"
)
func main() {
args := []string{"-foo="}
flagSet1 := flag.NewFlagSet("flagSet1", flag.ContinueOnError)
foo1 := flagSet1.String("foo", "-", ``)
boolFoo1 := flagSet1.Bool("boolfoo", false, ``)
flagSet1.Parse(args)
flagSet2 := flag.NewFlagSet("flagSet2", flag.ContinueOnError)
foo2 := flagSet2.String("foo", "+", ``)
boolFoo2 := flagSet2.Bool("boolfoo", true, ``)
flagSet2.Parse(args)
if *foo1 != *foo2 {
fmt.Println("foo flag set by default")
} else {
fmt.Println("foo flag provided by user")
}
if *boolFoo1 != *boolFoo2 {
fmt.Println("boolfoo flag set by default")
} else {
fmt.Println("boolfoo flag provided by user")
}
}
playground: https://play.golang.org/p/BVceE_pN5PO , for real CLI execution, you can do something like that: https://play.golang.org/p/WNvDaaPj585
Same as https://stackoverflow.com/a/35809400/3567989 but with a pointer to a string instead of a custom struct. The *string is nil if unset, non-nil if set.
package main
import (
"flag"
"fmt"
)
type stringPtrFlag struct {
ptr **string
}
func (f stringPtrFlag) String() string {
if *f.ptr == nil {
return ""
}
return **f.ptr
}
func (f stringPtrFlag) Set(s string) error {
*f.ptr = &s
return nil
}
var filename *string
func init() {
flag.Var(stringPtrFlag{&filename}, "filename", "the filename")
}
func main() {
flag.Parse()
if filename == nil {
fmt.Println("--filename not set")
} else {
fmt.Printf("--filename set to %q\n", *filename)
}
}
I think a more reliable way is to check whether any flag in the command-line parameters (os.Args[1:]) is prefixed by "prefix" + str, so the function:
func isInSlice(str string, list []string, prefix string) bool {
for _, v := range list {
if strings.HasPrefix(v, prefix + str) {
return true
}
}
return false
}
I found that we have the Lookup() method:
func isFlagPassed(name string) bool {
rs := flag.Lookup(name)
return rs != nil
}
Full docs
The FlagSet does not have a function LookupActual() in my environment (go version go1.13.4 windows/amd64), and the internal map actual mentioned in Ben L's answer can not be accessed directly.
I have an approach to check if a flag is set using reflect:
import "reflect"
fs := flag.NewFlagSet("the flags", flag.ExitOnError)
flag_name := "host"
host := fs.String(flag_name, "localhost", "specify the host address")
// other flags
fs.Parse(os.Args[1:])
if reflect.Indirect(reflect.ValueOf(fs)).FieldByName("actual").MapIndex(reflect.ValueOf(flag_name)).IsValid() {
fmt.Printf("the host flag is set with value %v", *host)
} else {
fmt.Printf("the host flag is not set")
}

3 variable map in Go

I am trying to make a 3 variable map in go so that you can do something like.
var postlist = make(map[int][int]bool)
postlist[postid][userid] = true
if postid[postid][userid] = true {
//do something
}
I have tried to make my own using a struct like
var postlist = make(map[int]cusmap)
type cusmap struct {
userid int
seen bool
}
but then I don't know how to check to check both the userid and seen bool condition.
I am not sure what you are trying to do, but a map is only a key/value. You can't have key/key/value. In order to do this, you need the value to be a map, so you would do:
http://play.golang.org/p/dOAXNAI4CO
package main
func main() {
var postlist = make(map[int]map[int]bool)
postid, userid := 0, 0
postlist[postid] = make(map[int]bool)
postlist[postid][userid] = true
if postlist[postid][userid] == true {
println("ok")
} else {
println("ko")
}
}
If you want to implement a set of int pairs, you can use a structure as the map key:
type postData struct {
userID int
postID int
}
Then make a map with postData keys and bool values:
postSet := map[postData]bool{
postData{1234, 7284}: true,
postData{7777, 1212}: true}
We can exploit the fact that if we give a non-existent index to the map, it will just return a zero-value.
For bool, the zero-value is false.
if postSet[postData{7777, 1212}] {
fmt.Println("post found.")
} else {
fmt.Println("no such post!")
}
Here's a full working example: http://play.golang.org/p/VJw9Vm8gHA
An alternative to #creack's approach is to define funcs on the map itself that way you don't have to manually check every time you want to set / unset something:
func main() {
cm := CustMap{}
pid, uid := 0, 0
cm.Set(pid, uid)
fmt.Println(cm.Exists(pid, uid))
fmt.Println(cm.Exists(pid, 10))
fmt.Println(cm.Exists(10, 10))
}
type CustMap map[int]map[int]struct{} //struct makes the map use 0 bytes for values instead of 1 bytes for bools, handy as the map grows
func (cm CustMap) Set(pid, uid int) {
if _, ok := cm[pid]; !ok {
cm[pid] = make(map[int]struct{})
}
cm[pid][uid] = struct{}{}
}
func (cm CustMap) Exists(pid, uid int) (ok bool) {
if u := cm[pid]; u != nil {
_, ok = u[uid]
}
return
}
playground

Resources