1 package list
2 3 import (
4 "encoding/json"
5 "fmt"
6 "strings"
7 "sync/atomic"
8 9 "github.com/p9c/p9/pkg/opts/normalize"
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 a string slice configuration value
17 type Opt struct {
18 meta.Data
19 hook []Hook
20 Value *atomic.Value
21 Def []string
22 }
23 24 type Hook func(s []string) error
25 26 // New creates a new Opt with default values set
27 func New(m meta.Data, def []string, hook ...Hook) *Opt {
28 as := &atomic.Value{}
29 as.Store(def)
30 return &Opt{Value: as, Data: m, Def: def, hook: hook}
31 }
32 33 // SetName sets the name for the generator
34 func (x *Opt) SetName(name string) {
35 x.Data.Option = strings.ToLower(name)
36 x.Data.Name = name
37 }
38 39 // Type returns the receiver wrapped in an interface for identifying its type
40 func (x *Opt) Type() interface{} {
41 return x
42 }
43 44 // GetMetadata returns the metadata of the opt type
45 func (x *Opt) GetMetadata() *meta.Data {
46 return &x.Data
47 }
48 49 // ReadInput adds the value from a string. For this opt this means appending to the list
50 func (x *Opt) ReadInput(input string) (o opt.Option, e error) {
51 if input == "" {
52 e = fmt.Errorf("string opt %s %v may not be empty", x.Name(), x.Data.Aliases)
53 return
54 }
55 if strings.HasPrefix(input, "=") {
56 input = strings.Join(strings.Split(input, "=")[1:], "=")
57 }
58 // if value has a comma in it, it's a list of items, so split them and append them
59 slice := x.S()
60 if strings.Contains(input, ",") {
61 split := strings.Split(input, ",")
62 for i := range split {
63 var cleaned string
64 if cleaned, e = sanitizers.StringType(x.Data.Type, split[i], x.Data.DefaultPort); E.Chk(e) {
65 return
66 }
67 if cleaned != "" {
68 I.Ln("setting value for", x.Data.Name, cleaned)
69 split[i] = cleaned
70 }
71 }
72 e = x.Set(append(slice, split...))
73 } else {
74 var cleaned string
75 if cleaned, e = sanitizers.StringType(x.Data.Type, input, x.Data.DefaultPort); E.Chk(e) {
76 return
77 }
78 if cleaned != "" {
79 I.Ln("setting value for", x.Data.Name, cleaned)
80 input = cleaned
81 }
82 if e = x.Set(append(slice, input)); E.Chk(e) {
83 }
84 85 }
86 // ensure there is no duplicates
87 e = x.Set(normalize.RemoveDuplicateAddresses(x.V()))
88 return x, e
89 }
90 91 // LoadInput sets the value from a string. For this opt this replacing the list
92 func (x *Opt) LoadInput(input string) (o opt.Option, e error) {
93 old := x.V()
94 _ = x.Set([]string{})
95 if o, e = x.ReadInput(input); E.Chk(e) {
96 // if input failed to parse, restore its prior state
97 _ = x.Set(old)
98 }
99 return
100 }
101 102 // Name returns the name of the opt
103 func (x *Opt) Name() string {
104 return x.Data.Option
105 }
106 107 // AddHooks appends callback hooks to be run when the value is changed
108 func (x *Opt) AddHooks(hook ...Hook) {
109 x.hook = append(x.hook, hook...)
110 }
111 112 // SetHooks sets a new slice of hooks
113 func (x *Opt) SetHooks(hook ...Hook) {
114 x.hook = hook
115 }
116 117 // V returns the stored value
118 func (x *Opt) V() []string {
119 return x.Value.Load().([]string)
120 }
121 122 // Len returns the length of the slice of strings
123 func (x *Opt) Len() int {
124 return len(x.S())
125 }
126 127 func (x *Opt) runHooks(s []string) (e error) {
128 for i := range x.hook {
129 if e = x.hook[i](s); E.Chk(e) {
130 break
131 }
132 }
133 return
134 }
135 136 // Set the slice of strings stored
137 func (x *Opt) Set(ss []string) (e error) {
138 if e = x.runHooks(ss); !E.Chk(e) {
139 x.Value.Store(ss)
140 }
141 return
142 }
143 144 // S returns the value as a slice of string
145 func (x *Opt) S() []string {
146 return x.Value.Load().([]string)
147 }
148 149 // String returns a string representation of the value
150 func (x *Opt) String() string {
151 return fmt.Sprint(x.Data.Option, ": ", x.S())
152 }
153 154 // MarshalJSON returns the json representation of
155 func (x *Opt) MarshalJSON() (b []byte, e error) {
156 xs := x.Value.Load().([]string)
157 return json.Marshal(xs)
158 }
159 160 // UnmarshalJSON decodes a JSON representation of
161 func (x *Opt) UnmarshalJSON(data []byte) (e error) {
162 var v []string
163 e = json.Unmarshal(data, &v)
164 x.Value.Store(v)
165 return
166 }
167