4

I am new to cobra and viper.

I wanted to know if there's a way to exclude flag values that aren't set by the user from cli. So my problem is, I have some optional flags in my cobra cmd that have default values. I wonder if there's a way to exclude these optional flags if they aren't set by the user from being processed? Or in another perspective, is there a way to extract flags that are changed by the user explicitly from cli from a cmd flagset?

Here's an example to better illustrate my case:

var cmd = &cobra.Command{
    Use:   "command [operations]",
    Short: "Perform some commands",
    Run: func(cmd *cobra.Command, args []string) {
        var user User
        err := viper.Unmarshal(&user)
        fmt.Println(err)
        fmt.Println(user)
    },
}

cmd.PersistentFlags().String("name", "", "Your Name (Required)")
cmd.PersistentFlags().String("description", "", "Description")
cmd.PersistentFlags().String("address", "", "Address")
cmd.PersistentFlags().Int("age", -1, "Age")
cmd.MarkPersistentFlagRequired("name")
viper.BindPFlags(cmd.PersistentFlags()) 

So suppose for age and address, I want them to be optional and not propagated when they're passed to struct. So this means, in the cli, the user is not required to submit the age and address flags and the default values for these optional flags are nil. However, Int & String flags are strongly typed, so they require default values (eg: "" and -1). As a result, when I bind the command pflags to viper and user don't input age & address values in the cli, viper will receive "" for address and -1 for age. Using the above method, the default values will get propagated to the User struct.

What I want is, to exclude flag values that aren't set by the user. Is there a way for me to exclude User struct from receiving flag values that aren't explicitly set by the user?

My current solution to exclude the unset flags from being processed in the User struct is currently like this:

  1. Have an array of the command flags. Initialize an empty map[string]interface{}
  2. Iterate through the array, and check if the flag is set/changed via flags.Changed("flag"). If flag is changed, then add it to map
  3. Convert map to json
  4. Unmarshal json object to struct

    // inside the Run function of a cobra command
    aMap := make(map[string]interface{})
    flags := cmd.Flags()
    for _, flag := range cmdFlags {
        if flags.Changed(flag) {
            aMap[flag] = viper.Get(flag)
        }
    }
    jsonStr, _ := json.Marshal(aMap)
    var user User
    json.Unmarshal(jsonStr, &user)
    fmt.Println(user)
    

But I was wondering if there is a better alternative than my solution above. Preferably, I don't want to change anything in the User struct as it is imported from an external package.

Thank you. Your help is greatly appreciated.

dekauliya
  • 1,303
  • 2
  • 15
  • 26

1 Answers1

1

Suppose your User struct looks like this:

type User struct {
    ...
    age     int
    address string
    ...
}

Then by default initialization of the User struct will set age => 0, address => 0. So even if viper doesn't set defaults. Go internally will.

What I suggest is:
1. Change how user struct is created

type User struct {
    ...
    age     *int
    address *string
    ...
}
  1. Set the defaults in cobra to nil for both those fields.
    This way you'd know that the default of Cobra is to not set the value. Whenever the value is set, it'll be a non nil value. Hope that helps!
Karthik Nayak
  • 731
  • 3
  • 14
  • 7
    As far as I can tell 2. is not possible, because the various `FlagSet` methods such as `Int64` take a value as an argument, e.g. `func (f *FlagSet) Int64(name string, value int64, usage string) *int64`. So you cannot set `value` to `nil`. – Kurt Peek Jan 16 '20 at 18:44