1package cbflag
2
3import (
4	"fmt"
5	"strings"
6
7	"github.com/couchbase/cbflag/pwd"
8)
9
10// |                                 TOTAL_LEN (80)                                 |
11// | PREFIX_LEN (2) |       FLAGS_LEN (25)       | POSTFIX_LEN (3) | USAGE_LEN (50) |
12// | PREFIX_LEN (2) | flags definition | padding | POSTFIX_LEN (3) | USAGE_LEN (50) |
13
14const PREFIX_LEN = 2
15const POSTFIX_LEN = 3
16const FLAGS_LEN int = 25
17const USAGE_LEN int = 50
18const TOTAL_LEN int = 80
19
20type ValidatorFn func(Value) error
21type OptionHandler func(string, string) (string, bool, error)
22
23type Flag struct {
24	short      string
25	long       string
26	env        string
27	deprecated []string
28	desc       string
29	value      Value
30	validator  ValidatorFn
31	optHandler OptionHandler
32	foundLong  bool
33	foundShort bool
34	foundEnv   bool
35	foundDepr  bool
36	required   bool
37	hidden     bool
38	isFlag     bool
39}
40
41func BoolFlag(result *bool, def bool, short, long, env, usage string, deprecated []string, hidden bool) *Flag {
42	return varFlag(newBoolValue(def, result), short, long, env, usage, deprecated, nil,
43		DefaultOptionHandler, false, hidden, true)
44}
45
46func Float64Flag(result *float64, def float64, short, long, env, usage string, deprecated []string,
47	validator ValidatorFn, required, hidden bool) *Flag {
48	return varFlag(newFloat64Value(def, result), short, long, env, usage, deprecated, validator,
49		DefaultOptionHandler, required, hidden, false)
50}
51
52func IntFlag(result *int, def int, short, long, env, usage string, deprecated []string, validator ValidatorFn,
53	required, hidden bool) *Flag {
54	return varFlag(newIntValue(def, result), short, long, env, usage, deprecated, validator,
55		DefaultOptionHandler, required, hidden, false)
56}
57
58func IntArrayFlag(result *[]int, def []int, short, long, env, usage string, deprecated []string, validator ValidatorFn,
59	required, hidden bool) *Flag {
60	return varFlag(newIntArray(def, result), short, long, env, usage, deprecated, validator,
61		DefaultOptionHandler, required, hidden, false)
62}
63
64func Int64Flag(result *int64, def int64, short, long, env, usage string, deprecated []string,
65	validator ValidatorFn, required, hidden bool) *Flag {
66	return varFlag(newInt64Value(def, result), short, long, env, usage, deprecated, validator,
67		DefaultOptionHandler, required, hidden, false)
68}
69
70func RuneFlag(result *rune, def rune, short, long, env, usage string, deprecated []string,
71	validator ValidatorFn, required, hidden bool) *Flag {
72	return varFlag(newRuneValue(def, result), short, long, env, usage, deprecated, validator,
73		DefaultOptionHandler, required, hidden, false)
74}
75
76func StringFlag(result *string, def, short, long, env, usage string, deprecated []string,
77	validator ValidatorFn, required, hidden bool) *Flag {
78	return varFlag(newStringValue(def, result), short, long, env, usage, deprecated, validator,
79		DefaultOptionHandler, required, hidden, false)
80}
81
82func StringMapFlag(result *map[string]string, def map[string]string, short, long, env, usage string,
83	deprecated []string, validator ValidatorFn, required, hidden bool) *Flag {
84	return varFlag(newStringMapValue(def, result), short, long, env, usage, deprecated, validator,
85		DefaultOptionHandler, required, hidden, false)
86}
87
88func UintFlag(result *uint, def uint, short, long, env, usage string, deprecated []string,
89	validator ValidatorFn, required, hidden bool) *Flag {
90	return varFlag(newUintValue(def, result), short, long, env, usage, deprecated, validator,
91		DefaultOptionHandler, required, hidden, false)
92}
93
94func Uint64Flag(result *uint64, def uint64, short, long, env, usage string, deprecated []string,
95	validator ValidatorFn, required, hidden bool) *Flag {
96	return varFlag(newUint64Value(def, result), short, long, env, usage, deprecated, validator,
97		DefaultOptionHandler, required, hidden, false)
98}
99
100func HostFlag(result *string, def string, deprecated []string, required, hidden bool) *Flag {
101	return varFlag(newStringValue(def, result), "c", "cluster", "CB_CLUSTER",
102		"The hostname of the Couchbase cluster", deprecated, HostValidator, DefaultOptionHandler,
103		required, hidden, false)
104}
105
106func UsernameFlag(result *string, def string, deprecated []string, required, hidden bool) *Flag {
107	return varFlag(newStringValue(def, result), "u", "username", "CB_USERNAME",
108		"The username of the Couchbase cluster", deprecated, nil, DefaultOptionHandler, required,
109		hidden, false)
110}
111
112func PasswordFlag(result *string, def string, deprecated []string, required, hidden bool) *Flag {
113	return varFlag(newStringValue(def, result), "p", "password", "CB_PASSWORD",
114		"The password of the Couchbase cluster", deprecated, nil, PasswordOptionHandler, required,
115		hidden, false)
116}
117
118func CACertFlag(result *string, def string, deprecated []string, required, hidden bool) *Flag {
119	return varFlag(newStringValue(def, result), "", "cacert", "",
120		"Verifies the cluster identity with this certificate", deprecated, nil, DefaultOptionHandler,
121		required, hidden, false)
122}
123
124func NoSSLVerifyFlag(result *bool, deprecated []string, required, hidden bool) *Flag {
125	return varFlag(newBoolValue(false, result), "", "no-ssl-verify", "",
126		"Skips SSL verification of certificates against CA", deprecated, nil, DefaultOptionHandler,
127		required, hidden, true)
128}
129
130func helpFlag(result *bool) *Flag {
131	return varFlag(newBoolValue(false, result), "h", "help", "", "Prints the help message",
132		make([]string, 0), nil, DefaultOptionHandler, false, false, true)
133}
134
135func varFlag(value Value, short, long, env, usage string, deprecated []string, validator ValidatorFn,
136	optHandler OptionHandler, required, hidden, isFlag bool) *Flag {
137	return &Flag{
138		short:      short,
139		long:       long,
140		env:        env,
141		deprecated: deprecated,
142		desc:       usage,
143		value:      value,
144		validator:  validator,
145		optHandler: optHandler,
146		foundLong:  false,
147		foundShort: false,
148		foundEnv:   false,
149		foundDepr:  false,
150		required:   required,
151		hidden:     hidden,
152		isFlag:     isFlag,
153	}
154}
155
156func (f *Flag) found() bool {
157	return f.foundLong || f.foundShort || f.foundEnv || f.foundDepr
158}
159
160func (f *Flag) foundNonEnv() bool {
161	return f.foundLong || f.foundShort || f.foundDepr
162}
163
164func (f *Flag) deprecatedFlagSpecified() bool {
165	return f.foundDepr
166}
167
168func (f *Flag) markFound(value string, environment, deprecated bool) {
169	if deprecated {
170		f.foundDepr = true
171	} else if environment {
172		f.foundEnv = true
173	} else if strings.HasPrefix(value, "--") {
174		f.foundLong = true
175	} else if strings.HasPrefix(value, "-") {
176		f.foundShort = true
177	}
178}
179
180func (f *Flag) validate() error {
181	if f.validator == nil {
182		return nil
183	}
184
185	return f.validator(f.value)
186}
187
188func (f *Flag) usageString() string {
189	if f.hidden {
190		return ""
191	}
192
193	s := ""
194	lines := f.splitDescription()
195
196	flagsStr := f.flagsHelpString()
197	if len(f.long) > FLAGS_LEN {
198		prePadding := strings.Repeat(" ", PREFIX_LEN)
199		s += fmt.Sprintf("%s%s\n", prePadding, flagsStr)
200		s += fmt.Sprintf("%s%s\n", strings.Repeat(" ", TOTAL_LEN-USAGE_LEN), lines[0])
201	} else {
202		prePadding := strings.Repeat(" ", PREFIX_LEN)
203		postPadding := strings.Repeat(" ", FLAGS_LEN+POSTFIX_LEN-len(flagsStr))
204		s += fmt.Sprintf("%s%s%s%s\n", prePadding, flagsStr, postPadding, lines[0])
205	}
206
207	for i := 1; i < len(lines); i++ {
208		s += fmt.Sprintf("%s%s\n", strings.Repeat(" ", TOTAL_LEN-USAGE_LEN), lines[i])
209	}
210
211	return s
212}
213
214func (f *Flag) flagsHelpString() string {
215	if f.short != "" && f.long != "" {
216		return fmt.Sprintf("-%s,--%s", f.short, f.long)
217	} else if f.short == "" {
218		return fmt.Sprintf("   --%s", f.long)
219	} else if f.long == "" {
220		return fmt.Sprintf("-%s", f.short)
221	} else {
222		return ""
223	}
224}
225
226func (f *Flag) deprecatedFlagsString() string {
227	rv := ""
228	for _, depr := range f.deprecated {
229		if len(rv) > 0 {
230			rv += ","
231		}
232
233		if len(depr) == 1 {
234			rv += "-" + depr
235		} else {
236			rv += "--" + depr
237		}
238	}
239
240	return rv
241}
242
243func (f *Flag) splitDescription() []string {
244	desc := f.desc
245	lines := make([]string, 0)
246	for len(desc) > 50 {
247		i := 50
248		for ; i >= 0; i-- {
249			if desc[i] == ' ' {
250				break
251			}
252		}
253		lines = append(lines, desc[0:i])
254		desc = desc[i+1:]
255	}
256
257	return append(lines, desc)
258}
259
260func DefaultOptionHandler(opt, value string) (string, bool, error) {
261	if value == "" || strings.HasPrefix(value, "-") {
262		return value, false, fmt.Errorf("Expected argument for option: %s", opt)
263	}
264
265	return value, true, nil
266}
267
268func PasswordOptionHandler(opt, value string) (string, bool, error) {
269	if value == "" || strings.HasPrefix(value, "-") {
270		fmt.Print("Password: ")
271		password, err := pwd.GetPasswd()
272		return string(password), false, err
273	}
274
275	return value, true, nil
276}
277