string.go raw

   1  package text
   2  
   3  import (
   4  	"encoding/json"
   5  	"fmt"
   6  	"strings"
   7  	"sync/atomic"
   8  
   9  	"github.com/p9c/p9/pkg/opts/meta"
  10  	"github.com/p9c/p9/pkg/opts/opt"
  11  	"github.com/p9c/p9/pkg/opts/sanitizers"
  12  )
  13  
  14  // Opt stores a string configuration value
  15  type Opt struct {
  16  	meta.Data
  17  	hook  []Hook
  18  	Value *atomic.Value
  19  	Def   string
  20  }
  21  
  22  type Hook func(s []byte) error
  23  
  24  // New creates a new Opt with a given default value set
  25  func New(m meta.Data, def string, hook ...Hook) *Opt {
  26  	v := &atomic.Value{}
  27  	v.Store([]byte(def))
  28  	return &Opt{Value: v, Data: m, Def: def, hook: hook}
  29  }
  30  
  31  // SetName sets the name for the generator
  32  func (x *Opt) SetName(name string) {
  33  	x.Data.Option = strings.ToLower(name)
  34  	x.Data.Name = name
  35  }
  36  
  37  // Type returns the receiver wrapped in an interface for identifying its type
  38  func (x *Opt) Type() interface{} {
  39  	return x
  40  }
  41  
  42  // GetMetadata returns the metadata of the opt type
  43  func (x *Opt) GetMetadata() *meta.Data {
  44  	return &x.Data
  45  }
  46  
  47  // ReadInput sets the value from a string
  48  func (x *Opt) ReadInput(input string) (o opt.Option, e error) {
  49  	if input == "" {
  50  		e = fmt.Errorf("string opt %s %v may not be empty", x.Name(), x.Data.Aliases)
  51  		return
  52  	}
  53  	if strings.HasPrefix(input, "=") {
  54  		// the following removes leading `=` and retains any following instances of `=`
  55  		input = strings.Join(strings.Split(input, "=")[1:], "=")
  56  	}
  57  	if x.Data.Options != nil {
  58  		var matched string
  59  		e = fmt.Errorf("option value not found '%s'", input)
  60  		for _, i := range x.Data.Options {
  61  			op := i
  62  			if len(i) >= len(input) {
  63  				op = i[:len(input)]
  64  			}
  65  			if input == op {
  66  				if e == nil {
  67  					return x, fmt.Errorf("ambiguous short option value '%s' matches multiple options: %s, %s", input, matched, i)
  68  				}
  69  				matched = i
  70  				e = nil
  71  			} else {
  72  				continue
  73  			}
  74  		}
  75  		if E.Chk(e) {
  76  			return
  77  		}
  78  		input = matched
  79  	} else {
  80  		var cleaned string
  81  		if cleaned, e = sanitizers.StringType(x.Data.Type, input, x.Data.DefaultPort); E.Chk(e) {
  82  			return
  83  		}
  84  		if cleaned != "" {
  85  			I.Ln("setting value for", x.Data.Name, cleaned)
  86  			input = cleaned
  87  		}
  88  	}
  89  	e = x.Set(input)
  90  	return x, e
  91  }
  92  
  93  // LoadInput sets the value from a string
  94  func (x *Opt) LoadInput(input string) (o opt.Option, e error) {
  95  	return x.ReadInput(input)
  96  }
  97  
  98  // Name returns the name of the opt
  99  func (x *Opt) Name() string {
 100  	return x.Data.Option
 101  }
 102  
 103  // AddHooks appends callback hooks to be run when the value is changed
 104  func (x *Opt) AddHooks(hook ...Hook) {
 105  	x.hook = append(x.hook, hook...)
 106  }
 107  
 108  // SetHooks sets a new slice of hooks
 109  func (x *Opt) SetHooks(hook ...Hook) {
 110  	x.hook = hook
 111  }
 112  
 113  // V returns the stored string
 114  func (x *Opt) V() string {
 115  	return string(x.Value.Load().([]byte))
 116  }
 117  
 118  // Empty returns true if the string is empty
 119  func (x *Opt) Empty() bool {
 120  	return len(x.Value.Load().([]byte)) == 0
 121  }
 122  
 123  // Bytes returns the raw bytes in the underlying storage
 124  // note that this returns a copy because anything done to the slice affects
 125  // all accesses afterwards, thus there is also a zero function
 126  // todo: make an option for the byte buffer to be MMU fenced to prevent
 127  //  elevated privilege processes from accessing this memory.
 128  func (x *Opt) Bytes() []byte {
 129  	byt := x.Value.Load().([]byte)
 130  	o := make([]byte, len(byt))
 131  	copy(o,byt)
 132  	return o
 133  }
 134  
 135  // Zero the bytes
 136  func(x *Opt) Zero() {
 137  	byt := x.Value.Load().([]byte)
 138  	for i := range byt {
 139  		byt[i]=0
 140  	}
 141  	x.Value.Store(byt)
 142  }
 143  
 144  func (x *Opt) runHooks(s []byte) (e error) {
 145  	for i := range x.hook {
 146  		if e = x.hook[i](s); E.Chk(e) {
 147  			break
 148  		}
 149  	}
 150  	return
 151  }
 152  
 153  // Set the value stored
 154  func (x *Opt) Set(s string) (e error) {
 155  	if e = x.runHooks([]byte(s)); !E.Chk(e) {
 156  		x.Value.Store([]byte(s))
 157  	}
 158  	return
 159  }
 160  
 161  // SetBytes sets the string from bytes
 162  func (x *Opt) SetBytes(s []byte) (e error) {
 163  	if e = x.runHooks(s); !E.Chk(e) {
 164  		x.Value.Store(s)
 165  	}
 166  	return
 167  }
 168  
 169  // Opt returns a string representation of the value
 170  func (x *Opt) String() string {
 171  	return fmt.Sprintf("%s: '%s'", x.Data.Option, x.V())
 172  }
 173  
 174  // MarshalJSON returns the json representation
 175  func (x *Opt) MarshalJSON() (b []byte, e error) {
 176  	v := string(x.Value.Load().([]byte))
 177  	return json.Marshal(&v)
 178  }
 179  
 180  // UnmarshalJSON decodes a JSON representation
 181  func (x *Opt) UnmarshalJSON(data []byte) (e error) {
 182  	v := x.Value.Load().([]byte)
 183  	e = json.Unmarshal(data, &v)
 184  	x.Value.Store(v)
 185  	return
 186  }
 187