188 lines
4.2 KiB
Go
188 lines
4.2 KiB
Go
package text
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"strings"
|
|
"sync/atomic"
|
|
|
|
"git.mleku.dev/mleku/prevara/pkg/opts/meta"
|
|
"git.mleku.dev/mleku/prevara/pkg/opts/opt"
|
|
"git.mleku.dev/mleku/prevara/pkg/opts/sanitizers"
|
|
)
|
|
|
|
// Opt stores a string configuration value
|
|
type Opt struct {
|
|
meta.Data
|
|
hook []Hook
|
|
Value *atomic.Value
|
|
Def string
|
|
}
|
|
|
|
type Hook func(s []byte) error
|
|
|
|
// New creates a new Opt with a given default value set
|
|
func New(m meta.Data, def string, hook ...Hook) *Opt {
|
|
v := &atomic.Value{}
|
|
v.Store([]byte(def))
|
|
return &Opt{Value: v, Data: m, Def: def, hook: hook}
|
|
}
|
|
|
|
// SetName sets the name for the generator
|
|
func (x *Opt) SetName(name string) {
|
|
x.Data.Option = strings.ToLower(name)
|
|
x.Data.Name = name
|
|
}
|
|
|
|
// Type returns the receiver wrapped in an interface for identifying its type
|
|
func (x *Opt) Type() interface{} {
|
|
return x
|
|
}
|
|
|
|
// GetMetadata returns the metadata of the opt type
|
|
func (x *Opt) GetMetadata() *meta.Data {
|
|
return &x.Data
|
|
}
|
|
|
|
// ReadInput sets the value from a string
|
|
func (x *Opt) ReadInput(input string) (o opt.Option, e error) {
|
|
if input == "" {
|
|
e = fmt.Errorf("string opt %s %v may not be empty", x.Name(), x.Data.Aliases)
|
|
return
|
|
}
|
|
if strings.HasPrefix(input, "=") {
|
|
// the following removes leading `=` and retains any following instances of `=`
|
|
input = strings.Join(strings.Split(input, "=")[1:], "=")
|
|
}
|
|
if x.Data.Options != nil {
|
|
var matched string
|
|
e = fmt.Errorf("option value not found '%s'", input)
|
|
for _, i := range x.Data.Options {
|
|
op := i
|
|
if len(i) >= len(input) {
|
|
op = i[:len(input)]
|
|
}
|
|
if input == op {
|
|
if e == nil {
|
|
return x, fmt.Errorf("ambiguous short option value '%s' matches multiple options: %s, %s", input, matched, i)
|
|
}
|
|
matched = i
|
|
e = nil
|
|
} else {
|
|
continue
|
|
}
|
|
}
|
|
if E.Chk(e) {
|
|
return
|
|
}
|
|
input = matched
|
|
} else {
|
|
var cleaned string
|
|
if cleaned, e = sanitizers.StringType(x.Data.Type, input, x.Data.DefaultPort); E.Chk(e) {
|
|
return
|
|
}
|
|
if cleaned != "" {
|
|
I.Ln("setting value for", x.Data.Name, cleaned)
|
|
input = cleaned
|
|
}
|
|
}
|
|
e = x.Set(input)
|
|
return x, e
|
|
}
|
|
|
|
// LoadInput sets the value from a string
|
|
func (x *Opt) LoadInput(input string) (o opt.Option, e error) {
|
|
return x.ReadInput(input)
|
|
}
|
|
|
|
// Name returns the name of the opt
|
|
func (x *Opt) Name() string {
|
|
return x.Data.Option
|
|
}
|
|
|
|
// AddHooks appends callback hooks to be run when the value is changed
|
|
func (x *Opt) AddHooks(hook ...Hook) {
|
|
x.hook = append(x.hook, hook...)
|
|
}
|
|
|
|
// SetHooks sets a new slice of hooks
|
|
func (x *Opt) SetHooks(hook ...Hook) {
|
|
x.hook = hook
|
|
}
|
|
|
|
// V returns the stored string
|
|
func (x *Opt) V() string {
|
|
return string(x.Value.Load().([]byte))
|
|
}
|
|
|
|
// Empty returns true if the string is empty
|
|
func (x *Opt) Empty() bool {
|
|
return len(x.Value.Load().([]byte)) == 0
|
|
}
|
|
|
|
// Bytes returns the raw bytes in the underlying storage
|
|
// note that this returns a copy because anything done to the slice affects
|
|
// all accesses afterwards, thus there is also a zero function
|
|
// todo: make an option for the byte buffer to be MMU fenced to prevent
|
|
//
|
|
// elevated privilege processes from accessing this memory.
|
|
func (x *Opt) Bytes() []byte {
|
|
byt := x.Value.Load().([]byte)
|
|
o := make([]byte, len(byt))
|
|
copy(o, byt)
|
|
return o
|
|
}
|
|
|
|
// Zero the bytes
|
|
func (x *Opt) Zero() {
|
|
byt := x.Value.Load().([]byte)
|
|
for i := range byt {
|
|
byt[i] = 0
|
|
}
|
|
x.Value.Store(byt)
|
|
}
|
|
|
|
func (x *Opt) runHooks(s []byte) (e error) {
|
|
for i := range x.hook {
|
|
if e = x.hook[i](s); E.Chk(e) {
|
|
break
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// Set the value stored
|
|
func (x *Opt) Set(s string) (e error) {
|
|
if e = x.runHooks([]byte(s)); !E.Chk(e) {
|
|
x.Value.Store([]byte(s))
|
|
}
|
|
return
|
|
}
|
|
|
|
// SetBytes sets the string from bytes
|
|
func (x *Opt) SetBytes(s []byte) (e error) {
|
|
if e = x.runHooks(s); !E.Chk(e) {
|
|
x.Value.Store(s)
|
|
}
|
|
return
|
|
}
|
|
|
|
// Opt returns a string representation of the value
|
|
func (x *Opt) String() string {
|
|
return fmt.Sprintf("%s: '%s'", x.Data.Option, x.V())
|
|
}
|
|
|
|
// MarshalJSON returns the json representation
|
|
func (x *Opt) MarshalJSON() (b []byte, e error) {
|
|
v := string(x.Value.Load().([]byte))
|
|
return json.Marshal(&v)
|
|
}
|
|
|
|
// UnmarshalJSON decodes a JSON representation
|
|
func (x *Opt) UnmarshalJSON(data []byte) (e error) {
|
|
v := x.Value.Load().([]byte)
|
|
e = json.Unmarshal(data, &v)
|
|
x.Value.Store(v)
|
|
return
|
|
}
|