pflag icon indicating copy to clipboard operation
pflag copied to clipboard

Add missing compatibility for flag.boolFlag.IsBoolFlag()

Open squ94wk opened this issue 4 years ago • 1 comments

In the standard flag package the flag.boolValue interface with its special IsBoolFlag() bool method is honored by the parser and allows creating flag.Value types that don't require an argument.

In pflag creating such a flag is only possible by overwriting the pflag.Flag's NoOptDefVal field with "true" or an equivalent (see strconv.ParseBool). The behavior of custom Value types that also implement IsBoolFlag() bool (hence are flag.boolValue or pflag.boolValue) is not compatible to the flag package, since passing no argument for the flag results in an error.

This snippet demonstrates it:

package main

import (
	"flag"
	"log"
	"strconv"

	"github.com/spf13/pflag"
)

/* straight from bool.go */
// -- bool Value
type boolValue bool

func newBoolValue(val bool, p *bool) *boolValue {
	*p = val
	return (*boolValue)(p)
}

func (b *boolValue) Set(s string) error {
	v, err := strconv.ParseBool(s)
	*b = boolValue(v)
	return err
}

func (b *boolValue) Type() string {
	return "bool"
}

func (b *boolValue) String() string { return strconv.FormatBool(bool(*b)) }

func (b *boolValue) IsBoolFlag() bool { return true }


func main() {
	var flagValue, pflagValue, pflagValue2 bool
	flagSet := flag.NewFlagSet("flag", flag.ContinueOnError)
	flagSet.Var(newBoolValue(false, &flagValue), "bool", "may omit argument")

	pflagSet := pflag.NewFlagSet("pflag", pflag.ContinueOnError)
	pflagSet.Var(newBoolValue(false, &pflagValue), "bool", "can't omit argument")
	boolFlag := pflagSet.VarPF(newBoolValue(false, &pflagValue2), "bool2", "", "has default argument if none exists")
	boolFlag.NoOptDefVal = "true"

	fErr := flagSet.Parse([]string{"--bool"})
	pfErr := pflagSet.Parse([]string{"--bool2", "--bool"})

	if (fErr == nil) != (pfErr == nil) {
		log.Println("flag and pflag handle boolValue differently")
	}

	if !pflagValue && pflagValue2 {
		log.Println("have to modify pflag.Flag value to get same behavior as with flag")
	}
}

Is this an aspect that is supposed to be compatible to flag? I can understand that it makes sense to place the possibility to omit an argument to the Flag type and not the Value type. But I also think it's good if I can specify that my Value type can be set without a value.

If this should be "fixed", I'd be happy to help.

squ94wk avatar Aug 30 '20 17:08 squ94wk

I would love to see this fixed also, but I did discover a work around, slightly hacky, but works, and with limitations.

By using pflag.AddGoFlag() or pflag.AddGoFlagSet(), when pflag converts each flag.Flag, it will check for IsBoolValue() , and if true, it will set NoOptDefVal = true, giving the equivalent behavior.

Using pflag.Var() you cannot use --foo, you still must do --foo=true

	pflag.Var(&xxx, "foo", "foo help").     // even if xxx implements IsBoolValue() { return true}

This will allow the --foo without --foo=true

	flag.Var(&xxx, "foo", "foo help")
	pflag.CommandLine.AddGoFlagSet(flag.CommandLine)

cameronelliott avatar Dec 01 '21 02:12 cameronelliott