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