Skip to content

Commit 93e843e

Browse files
committed
extract opts, args and helptext to cmdsutils subpackage
this way we can share the code with the legace commands package and don't have to convert everything
0 parents  commit 93e843e

File tree

5 files changed

+340
-0
lines changed

5 files changed

+340
-0
lines changed

argument.go

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package cmdsutil
2+
3+
type ArgumentType int
4+
5+
const (
6+
ArgString ArgumentType = iota
7+
ArgFile
8+
)
9+
10+
type Argument struct {
11+
Name string
12+
Type ArgumentType
13+
Required bool // error if no value is specified
14+
Variadic bool // unlimited values can be specfied
15+
SupportsStdin bool // can accept stdin as a value
16+
Recursive bool // supports recursive file adding (with '-r' flag)
17+
Description string
18+
}
19+
20+
func StringArg(name string, required, variadic bool, description string) Argument {
21+
return Argument{
22+
Name: name,
23+
Type: ArgString,
24+
Required: required,
25+
Variadic: variadic,
26+
Description: description,
27+
}
28+
}
29+
30+
func FileArg(name string, required, variadic bool, description string) Argument {
31+
return Argument{
32+
Name: name,
33+
Type: ArgFile,
34+
Required: required,
35+
Variadic: variadic,
36+
Description: description,
37+
}
38+
}
39+
40+
// TODO: modifiers might need a different API?
41+
// e.g. passing enum values into arg constructors variadically
42+
// (`FileArg("file", ArgRequired, ArgStdin, ArgRecursive)`)
43+
44+
func (a Argument) EnableStdin() Argument {
45+
a.SupportsStdin = true
46+
return a
47+
}
48+
49+
func (a Argument) EnableRecursive() Argument {
50+
if a.Type != ArgFile {
51+
panic("Only FileArgs can enable recursive")
52+
}
53+
54+
a.Recursive = true
55+
return a
56+
}

error.go

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package cmdsutil
2+
3+
// ErrorType signfies a category of errors
4+
type ErrorType uint
5+
6+
// ErrorTypes convey what category of error ocurred
7+
const (
8+
ErrNormal ErrorType = iota // general errors
9+
ErrClient // error was caused by the client, (e.g. invalid CLI usage)
10+
ErrImplementation // programmer error in the server
11+
// TODO: add more types of errors for better error-specific handling
12+
)
13+
14+
// Error is a struct for marshalling errors
15+
type Error struct {
16+
Message string
17+
Code ErrorType
18+
}
19+
20+
func (e Error) Error() string {
21+
return e.Message
22+
}

helptext.go

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package cmdsutil
2+
3+
// HelpText is a set of strings used to generate command help text. The help
4+
// text follows formats similar to man pages, but not exactly the same.
5+
type HelpText struct {
6+
// required
7+
Tagline string // used in <cmd usage>
8+
ShortDescription string // used in DESCRIPTION
9+
SynopsisOptionsValues map[string]string // mappings for synopsis generator
10+
11+
// optional - whole section overrides
12+
Usage string // overrides USAGE section
13+
LongDescription string // overrides DESCRIPTION section
14+
Options string // overrides OPTIONS section
15+
Arguments string // overrides ARGUMENTS section
16+
Subcommands string // overrides SUBCOMMANDS section
17+
Synopsis string // overrides SYNOPSIS field
18+
}

option.go

+199
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
package cmdsutil
2+
3+
import (
4+
"fmt"
5+
"reflect"
6+
"strings"
7+
8+
"gx/ipfs/Qmb912gdngC1UWwTkhuW8knyRbcWeu5kqkxBpveLmW8bSr/go-ipfs-util"
9+
)
10+
11+
// Types of Command options
12+
const (
13+
Invalid = reflect.Invalid
14+
Bool = reflect.Bool
15+
Int = reflect.Int
16+
Uint = reflect.Uint
17+
Float = reflect.Float64
18+
String = reflect.String
19+
)
20+
21+
type OptMap map[string]interface{}
22+
23+
// Option is used to specify a field that will be provided by a consumer
24+
type Option interface {
25+
Names() []string // a list of unique names matched with user-provided flags
26+
Type() reflect.Kind // value must be this type
27+
Description() string // a short string that describes this option
28+
Default(interface{}) Option // sets the default value of the option
29+
DefaultVal() interface{}
30+
}
31+
32+
type option struct {
33+
names []string
34+
kind reflect.Kind
35+
description string
36+
defaultVal interface{}
37+
}
38+
39+
func (o *option) Names() []string {
40+
return o.names
41+
}
42+
43+
func (o *option) Type() reflect.Kind {
44+
return o.kind
45+
}
46+
47+
func (o *option) Description() string {
48+
if len(o.description) == 0 {
49+
return ""
50+
}
51+
if !strings.HasSuffix(o.description, ".") {
52+
o.description += "."
53+
}
54+
if o.defaultVal != nil {
55+
if strings.Contains(o.description, "<<default>>") {
56+
return strings.Replace(o.description, "<<default>>",
57+
fmt.Sprintf("Default: %v.", o.defaultVal), -1)
58+
} else {
59+
return fmt.Sprintf("%s Default: %v.", o.description, o.defaultVal)
60+
}
61+
}
62+
return o.description
63+
}
64+
65+
// constructor helper functions
66+
func NewOption(kind reflect.Kind, names ...string) Option {
67+
if len(names) < 2 {
68+
// FIXME(btc) don't panic (fix_before_merge)
69+
panic("Options require at least two string values (name and description)")
70+
}
71+
72+
desc := names[len(names)-1]
73+
names = names[:len(names)-1]
74+
75+
return &option{
76+
names: names,
77+
kind: kind,
78+
description: desc,
79+
}
80+
}
81+
82+
func (o *option) Default(v interface{}) Option {
83+
o.defaultVal = v
84+
return o
85+
}
86+
87+
func (o *option) DefaultVal() interface{} {
88+
return o.defaultVal
89+
}
90+
91+
// TODO handle description separately. this will take care of the panic case in
92+
// NewOption
93+
94+
// For all func {Type}Option(...string) functions, the last variadic argument
95+
// is treated as the description field.
96+
97+
func BoolOption(names ...string) Option {
98+
return NewOption(Bool, names...)
99+
}
100+
func IntOption(names ...string) Option {
101+
return NewOption(Int, names...)
102+
}
103+
func UintOption(names ...string) Option {
104+
return NewOption(Uint, names...)
105+
}
106+
func FloatOption(names ...string) Option {
107+
return NewOption(Float, names...)
108+
}
109+
func StringOption(names ...string) Option {
110+
return NewOption(String, names...)
111+
}
112+
113+
type OptionValue struct {
114+
Value interface{}
115+
ValueFound bool
116+
Def Option
117+
}
118+
119+
// Found returns true if the option value was provided by the user (not a default value)
120+
func (ov OptionValue) Found() bool {
121+
return ov.ValueFound
122+
}
123+
124+
// Definition returns the option definition for the provided value
125+
func (ov OptionValue) Definition() Option {
126+
return ov.Def
127+
}
128+
129+
// value accessor methods, gets the value as a certain type
130+
func (ov OptionValue) Bool() (value bool, found bool, err error) {
131+
if !ov.ValueFound && ov.Value == nil {
132+
return false, false, nil
133+
}
134+
val, ok := ov.Value.(bool)
135+
if !ok {
136+
err = util.ErrCast()
137+
}
138+
return val, ov.ValueFound, err
139+
}
140+
141+
func (ov OptionValue) Int() (value int, found bool, err error) {
142+
if !ov.ValueFound && ov.Value == nil {
143+
return 0, false, nil
144+
}
145+
val, ok := ov.Value.(int)
146+
if !ok {
147+
err = util.ErrCast()
148+
}
149+
return val, ov.ValueFound, err
150+
}
151+
152+
func (ov OptionValue) Uint() (value uint, found bool, err error) {
153+
if !ov.ValueFound && ov.Value == nil {
154+
return 0, false, nil
155+
}
156+
val, ok := ov.Value.(uint)
157+
if !ok {
158+
err = util.ErrCast()
159+
}
160+
return val, ov.ValueFound, err
161+
}
162+
163+
func (ov OptionValue) Float() (value float64, found bool, err error) {
164+
if !ov.ValueFound && ov.Value == nil {
165+
return 0, false, nil
166+
}
167+
val, ok := ov.Value.(float64)
168+
if !ok {
169+
err = util.ErrCast()
170+
}
171+
return val, ov.ValueFound, err
172+
}
173+
174+
func (ov OptionValue) String() (value string, found bool, err error) {
175+
if !ov.ValueFound && ov.Value == nil {
176+
return "", false, nil
177+
}
178+
val, ok := ov.Value.(string)
179+
if !ok {
180+
err = util.ErrCast()
181+
}
182+
return val, ov.ValueFound, err
183+
}
184+
185+
// Flag names
186+
const (
187+
EncShort = "enc"
188+
EncLong = "encoding"
189+
RecShort = "r"
190+
RecLong = "recursive"
191+
ChanOpt = "stream-channels"
192+
TimeoutOpt = "timeout"
193+
)
194+
195+
// options that are used by this package
196+
var OptionEncodingType = StringOption(EncLong, EncShort, "The encoding type the output should be encoded with (json, xml, or text)")
197+
var OptionRecursivePath = BoolOption(RecLong, RecShort, "Add directory paths recursively").Default(false)
198+
var OptionStreamChannels = BoolOption(ChanOpt, "Stream channel output")
199+
var OptionTimeout = StringOption(TimeoutOpt, "set a global timeout on the command")

option_test.go

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package cmdsutil
2+
3+
import (
4+
"strings"
5+
"testing"
6+
)
7+
8+
func TestOptionValueExtractBoolNotFound(t *testing.T) {
9+
t.Log("ensure that no error is returned when value is not found")
10+
optval := &OptionValue{found: false}
11+
_, _, err := optval.Bool()
12+
if err != nil {
13+
t.Fatal("Found was false. Err should have been nil")
14+
}
15+
}
16+
17+
func TestOptionValueExtractWrongType(t *testing.T) {
18+
19+
t.Log("ensure that error is returned when value if of wrong type")
20+
21+
optval := &OptionValue{value: "wrong type: a string", found: true}
22+
_, _, err := optval.Bool()
23+
if err == nil {
24+
t.Fatal("No error returned. Failure.")
25+
}
26+
27+
optval = &OptionValue{value: "wrong type: a string", found: true}
28+
_, _, err = optval.Int()
29+
if err == nil {
30+
t.Fatal("No error returned. Failure.")
31+
}
32+
}
33+
34+
func TestLackOfDescriptionOfOptionDoesNotPanic(t *testing.T) {
35+
opt := BoolOption("a", "")
36+
opt.Description()
37+
}
38+
39+
func TestDotIsAddedInDescripton(t *testing.T) {
40+
opt := BoolOption("a", "desc without dot")
41+
dest := opt.Description()
42+
if !strings.HasSuffix(dest, ".") {
43+
t.Fatal("dot should have been added at the end of description")
44+
}
45+
}

0 commit comments

Comments
 (0)