github.com/haraldrudell/parl@v0.4.176/pflags/option-data.go (about)

     1  /*
     2  © 2020–present Harald Rudell <harald.rudell@gmail.com> (https://haraldrudell.github.io/haraldrudell/)
     3  ISC License
     4  */
     5  
     6  package pflags
     7  
     8  import (
     9  	"flag"
    10  	"fmt"
    11  	"time"
    12  
    13  	"github.com/haraldrudell/parl/perrors"
    14  )
    15  
    16  const (
    17  	typeMismatch = "option %s type %T: bad yaml data pointer of type: %T"
    18  )
    19  
    20  var NoOptions []OptionData
    21  
    22  // OptionData contain options data for the flag package
    23  //   - OptionData is used for command-line options to be declarative
    24  //   - instead of flag.BoolVar and similar invocations scattered about the codebase
    25  //     all options are in a single OptionData slice that is iterated so that AddOption is invoked for each element
    26  //   - to invoke [flag.BoolVar]. pointer, name, value and usage is required
    27  //   - [flag.BoolVar] is consumer-provided storage, [flag.Bool] is flag-provided storage
    28  type OptionData struct {
    29  	P     interface{} // P is a reference to where the effective value of this option is stored
    30  	Name  string      // Name is the option name without hyphen, “debug” for -debug
    31  	Value interface{} // Value is the default value for this option
    32  	Usage string      // printable string describing what this option does
    33  	Y     interface{} // reference to effective value in YamlData
    34  }
    35  
    36  // AddOption executes flag.BoolVar and such on the options map
    37  //   - this defines the option to the flag package
    38  //   - all options must be defined prior to invoking [flag.Parse]
    39  //   - options are defined using value pointers, ie [flag.BoolVar] not flag.Bool[],
    40  //     so [flag.Parse] updates effective option values directly
    41  func (o *OptionData) AddOption() {
    42  	switch effectiveValuep := o.P.(type) {
    43  	case *bool:
    44  		if value, ok := o.Value.(bool); !ok {
    45  			o.defaultValuePanic()
    46  		} else {
    47  			flag.BoolVar(effectiveValuep, o.Name, value, o.Usage)
    48  		}
    49  		if y := o.Y; y != nil {
    50  			if _, ok := y.(*bool); !ok {
    51  				o.yamlPointerPanic()
    52  			}
    53  		}
    54  	case *time.Duration:
    55  		if value, ok := o.Value.(time.Duration); !ok {
    56  			o.defaultValuePanic()
    57  		} else {
    58  			flag.DurationVar(effectiveValuep, o.Name, value, o.Usage)
    59  		}
    60  		if y := o.Y; y != nil {
    61  			if _, ok := y.(*time.Duration); !ok {
    62  				o.yamlPointerPanic()
    63  			}
    64  		}
    65  	case *float64:
    66  		if value, ok := o.Value.(float64); !ok {
    67  			o.defaultValuePanic()
    68  		} else {
    69  			flag.Float64Var(effectiveValuep, o.Name, value, o.Usage)
    70  		}
    71  		if y := o.Y; y != nil {
    72  			if _, ok := y.(*float64); !ok {
    73  				o.yamlPointerPanic()
    74  			}
    75  		}
    76  	case *int64:
    77  		if value, ok := o.Value.(int64); !ok {
    78  			o.defaultValuePanic()
    79  		} else {
    80  			flag.Int64Var(effectiveValuep, o.Name, value, o.Usage)
    81  		}
    82  		if y := o.Y; y != nil {
    83  			if _, ok := y.(*int64); !ok {
    84  				o.yamlPointerPanic()
    85  			}
    86  		}
    87  	case *int:
    88  		if value, ok := o.Value.(int); !ok {
    89  			o.defaultValuePanic()
    90  		} else {
    91  			flag.IntVar(effectiveValuep, o.Name, value, o.Usage)
    92  		}
    93  		if y := o.Y; y != nil {
    94  			if _, ok := y.(*int); !ok {
    95  				o.yamlPointerPanic()
    96  			}
    97  		}
    98  	case *string:
    99  		if value, ok := o.Value.(string); !ok {
   100  			o.defaultValuePanic()
   101  		} else {
   102  			flag.StringVar(effectiveValuep, o.Name, value, o.Usage)
   103  		}
   104  		if y := o.Y; y != nil {
   105  			if _, ok := y.(*string); !ok {
   106  				o.yamlPointerPanic()
   107  			}
   108  		}
   109  	case *uint64:
   110  		if value, ok := o.Value.(uint64); !ok {
   111  			o.defaultValuePanic()
   112  		} else {
   113  			flag.Uint64Var(effectiveValuep, o.Name, value, o.Usage)
   114  		}
   115  		if y := o.Y; y != nil {
   116  			if _, ok := y.(*uint64); !ok {
   117  				o.yamlPointerPanic()
   118  			}
   119  		}
   120  	case *uint:
   121  		if value, ok := o.Value.(uint); !ok {
   122  			o.defaultValuePanic()
   123  		} else {
   124  			flag.UintVar(effectiveValuep, o.Name, value, o.Usage)
   125  		}
   126  		if y := o.Y; y != nil {
   127  			if _, ok := y.(*uint); !ok {
   128  				o.yamlPointerPanic()
   129  			}
   130  		}
   131  	case *[]string:
   132  		if value, ok := o.Value.([]string); !ok {
   133  			o.defaultValuePanic()
   134  		} else {
   135  			flag.Var(NewStringSliceValue(effectiveValuep, value), o.Name, o.Usage)
   136  		}
   137  		if y := o.Y; y != nil {
   138  			if _, ok := y.(*[]string); !ok {
   139  				o.yamlPointerPanic()
   140  			}
   141  		}
   142  	default:
   143  		panic(perrors.ErrorfPF("option '%s' Unknown options type: %T", o.Name, effectiveValuep))
   144  	}
   145  }
   146  
   147  // ApplyYaml copies a value read from yaml into the effective value location
   148  func (o *OptionData) ApplyYaml() (err error) {
   149  
   150  	// check if this OptionData is updated from yaml
   151  	var yamlDataPointer = o.Y
   152  	if yamlDataPointer == nil {
   153  		return // does not have YamlValue
   154  	}
   155  
   156  	// assign the effective value with the YamlData field’s value
   157  	switch valuePointer := o.P.(type) {
   158  	case *bool:
   159  		typedPointer, ok := yamlDataPointer.(*bool)
   160  		if !ok {
   161  			return perrors.Errorf(typeMismatch, o.Name, o.P, yamlDataPointer)
   162  		}
   163  		*valuePointer = *typedPointer
   164  	case *time.Duration:
   165  		typedPointer, ok := yamlDataPointer.(*time.Duration)
   166  		if !ok {
   167  			return perrors.Errorf(typeMismatch, o.Name, o.P, yamlDataPointer)
   168  		}
   169  		*valuePointer = *typedPointer
   170  	case *float64:
   171  		typedPointer, ok := yamlDataPointer.(*float64)
   172  		if !ok {
   173  			return perrors.Errorf(typeMismatch, o.Name, o.P, yamlDataPointer)
   174  		}
   175  		*valuePointer = *typedPointer
   176  	case *int64:
   177  		typedPointer, ok := yamlDataPointer.(*int64)
   178  		if !ok {
   179  			return perrors.Errorf(typeMismatch, o.Name, o.P, yamlDataPointer)
   180  		}
   181  		*valuePointer = *typedPointer
   182  	case *int:
   183  		typedPointer, ok := yamlDataPointer.(*int)
   184  		if !ok {
   185  			return perrors.Errorf(typeMismatch, o.Name, o.P, yamlDataPointer)
   186  		}
   187  		*valuePointer = *typedPointer
   188  	case *string:
   189  		typedPointer, ok := yamlDataPointer.(*string)
   190  		if !ok {
   191  			return perrors.Errorf(typeMismatch, o.Name, o.P, yamlDataPointer)
   192  		}
   193  		*valuePointer = *typedPointer
   194  	case *uint64:
   195  		typedPointer, ok := yamlDataPointer.(*uint64)
   196  		if !ok {
   197  			return perrors.Errorf(typeMismatch, o.Name, o.P, yamlDataPointer)
   198  		}
   199  		*valuePointer = *typedPointer
   200  	case *uint:
   201  		typedPointer, ok := yamlDataPointer.(*uint)
   202  		if !ok {
   203  			return perrors.Errorf(typeMismatch, o.Name, o.P, yamlDataPointer)
   204  		}
   205  		*valuePointer = *typedPointer
   206  	case *[]string:
   207  		typedPointer, ok := yamlDataPointer.(*[]string)
   208  		if !ok {
   209  			return perrors.Errorf(typeMismatch, o.Name, o.P, yamlDataPointer)
   210  		}
   211  		*valuePointer = *typedPointer
   212  	default:
   213  		return perrors.Errorf("option %s: unknown value type: %T", o.Name, o.P)
   214  	}
   215  
   216  	return
   217  }
   218  
   219  func (o *OptionData) defaultValuePanic() {
   220  	var expTyp = fmt.Sprintf("%T", o.P)
   221  	panic(perrors.ErrorfPF("option '%s': default value not %s: %T", o.Name, expTyp[1:], o.Value))
   222  }
   223  
   224  func (o *OptionData) yamlPointerPanic() {
   225  	panic(perrors.ErrorfPF("option '%s' yaml pointer not %T: %T", o.Name, o.P, o.Y))
   226  }
   227  
   228  func (o *OptionData) ValueDump() (valueS string) {
   229  	switch effectiveValuep := o.P.(type) {
   230  	case *bool:
   231  		return fmt.Sprintf("%v", *effectiveValuep)
   232  	case *time.Duration:
   233  		return fmt.Sprintf("%v", *effectiveValuep)
   234  	case *float64:
   235  		return fmt.Sprintf("%v", *effectiveValuep)
   236  	case *int64:
   237  		return fmt.Sprintf("%v", *effectiveValuep)
   238  	case *int:
   239  		return fmt.Sprintf("%v", *effectiveValuep)
   240  	case *string:
   241  		return fmt.Sprintf("%v", *effectiveValuep)
   242  	case *uint64:
   243  		return fmt.Sprintf("%v", *effectiveValuep)
   244  	case *uint:
   245  		return fmt.Sprintf("%v", *effectiveValuep)
   246  	case *[]string:
   247  		return fmt.Sprintf("%v", *effectiveValuep)
   248  	default:
   249  		panic(perrors.ErrorfPF("option '%s' Unknown options type: %T", o.Name, effectiveValuep))
   250  	}
   251  }