duration.go raw

   1  package duration
   2  
   3  import (
   4  	"encoding/json"
   5  	"fmt"
   6  	"strings"
   7  	"time"
   8  
   9  	uberatomic "go.uber.org/atomic"
  10  
  11  	"github.com/p9c/p9/pkg/opts/meta"
  12  	"github.com/p9c/p9/pkg/opts/opt"
  13  	"github.com/p9c/p9/pkg/opts/sanitizers"
  14  )
  15  
  16  // Opt stores an time.Duration configuration value
  17  type Opt struct {
  18  	meta.Data
  19  	hook     []Hook
  20  	clamp    func(input time.Duration) (result time.Duration)
  21  	Min, Max time.Duration
  22  	Value    *uberatomic.Duration
  23  	Def      time.Duration
  24  }
  25  
  26  type Hook func(d time.Duration) error
  27  
  28  // New creates a new Opt with a given default value set
  29  func New(m meta.Data, def time.Duration, min, max time.Duration, hook ...Hook) *Opt {
  30  	return &Opt{
  31  		Value: uberatomic.NewDuration(def),
  32  		Data:  m,
  33  		Def:   def,
  34  		Min:   min,
  35  		Max:   max,
  36  		hook:  hook,
  37  		clamp: sanitizers.ClampDuration(min, max),
  38  	}
  39  }
  40  
  41  // SetName sets the name for the generator
  42  func (x *Opt) SetName(name string) {
  43  	x.Data.Option = strings.ToLower(name)
  44  	x.Data.Name = name
  45  }
  46  
  47  // Type returns the receiver wrapped in an interface for identifying its type
  48  func (x *Opt) Type() interface{} {
  49  	return x
  50  }
  51  
  52  // GetMetadata returns the metadata of the opt type
  53  func (x *Opt) GetMetadata() *meta.Data {
  54  	return &x.Data
  55  }
  56  
  57  // ReadInput sets the value from a string
  58  func (x *Opt) ReadInput(input string) (o opt.Option, e error) {
  59  	if input == "" {
  60  		e = fmt.Errorf("duration opt %s %v may not be empty", x.Name(), x.Data.Aliases)
  61  		return
  62  	}
  63  	if strings.HasPrefix(input, "=") {
  64  		// the following removes leading and trailing '='
  65  		input = strings.Join(strings.Split(input, "=")[1:], "=")
  66  	}
  67  	var v time.Duration
  68  	if v, e = time.ParseDuration(input); E.Chk(e) {
  69  		return
  70  	}
  71  	if e = x.Set(v); E.Chk(e) {
  72  	}
  73  	return
  74  }
  75  
  76  // LoadInput sets the value from a string (this is the same as the above but differs for Strings)
  77  func (x *Opt) LoadInput(input string) (o opt.Option, e error) {
  78  	return x.ReadInput(input)
  79  }
  80  
  81  // Name returns the name of the opt
  82  func (x *Opt) Name() string {
  83  	return x.Data.Option
  84  }
  85  
  86  // AddHooks appends callback hooks to be run when the value is changed
  87  func (x *Opt) AddHooks(hook ...Hook) {
  88  	x.hook = append(x.hook, hook...)
  89  }
  90  
  91  // SetHooks sets a new slice of hooks
  92  func (x *Opt) SetHooks(hook ...Hook) {
  93  	x.hook = hook
  94  }
  95  
  96  // V returns the value stored
  97  func (x *Opt) V() time.Duration {
  98  	return x.Value.Load()
  99  }
 100  
 101  func (x *Opt) runHooks(d time.Duration) (e error) {
 102  	for i := range x.hook {
 103  		if e = x.hook[i](d); E.Chk(e) {
 104  			break
 105  		}
 106  	}
 107  	return
 108  }
 109  
 110  // Set the value stored
 111  func (x *Opt) Set(d time.Duration) (e error) {
 112  	d = x.clamp(d)
 113  	if e = x.runHooks(d); !E.Chk(e) {
 114  		x.Value.Store(d)
 115  	}
 116  	return
 117  }
 118  
 119  // String returns a string representation of the value
 120  func (x *Opt) String() string {
 121  	return fmt.Sprintf("%s: %v", x.Data.Option, x.V())
 122  }
 123  
 124  // MarshalJSON returns the json representation
 125  func (x *Opt) MarshalJSON() (b []byte, e error) {
 126  	v := x.Value.Load()
 127  	return json.Marshal(&v)
 128  }
 129  
 130  // UnmarshalJSON decodes a JSON representation
 131  func (x *Opt) UnmarshalJSON(data []byte) (e error) {
 132  	v := x.Value.Load()
 133  	e = json.Unmarshal(data, &v)
 134  	e = x.Set(v)
 135  	return
 136  }
 137