commands: Formatted code
Matt Bell committed
Oct 10, 2014 at 14:24 UTC
43670971d802d7d381c87aeb61b22ff84fd5453f
6 files changed
+314
-310
commands/command.go
+98
-98
@@ -1,120 +1,120 @@
1
package commands
2
3
import (
4
- "fmt"
5
- "strings"
6
- "reflect"
4
+ "fmt"
5
+ "reflect"
6
+ "strings"
7
)
8
9
type Command struct {
10
- Help string
11
- Options []Option
12
- f func(*Request, *Response)
13
- subcommands map[string]*Command
10
+ Help string
11
+ Options []Option
12
+ f func(*Request, *Response)
13
+ subcommands map[string]*Command
14
}
15
16
// Register adds a subcommand
17
func (c *Command) Register(id string, sub *Command) error {
18
- if c.subcommands == nil {
19
- c.subcommands = make(map[string]*Command)
20
- }
21
-
22
- // check for duplicate option names (only checks downwards)
23
- names := make(map[string]bool)
24
- globalCommand.checkOptions(names)
25
- c.checkOptions(names)
26
- err := sub.checkOptions(names)
27
- if err != nil {
28
- return err
29
- }
30
-
31
- if _, ok := c.subcommands[id]; ok {
32
- return fmt.Errorf("There is already a subcommand registered with id '%s'", id)
33
- }
34
-
35
- c.subcommands[id] = sub
36
- return nil
18
+ if c.subcommands == nil {
19
+ c.subcommands = make(map[string]*Command)
20
+ }
21
+
22
+ // check for duplicate option names (only checks downwards)
23
+ names := make(map[string]bool)
24
+ globalCommand.checkOptions(names)
25
+ c.checkOptions(names)
26
+ err := sub.checkOptions(names)
27
+ if err != nil {
28
+ return err
29
+ }
30
+
31
+ if _, ok := c.subcommands[id]; ok {
32
+ return fmt.Errorf("There is already a subcommand registered with id '%s'", id)
33
+ }
34
+
35
+ c.subcommands[id] = sub
36
+ return nil
37
}
38
39
// Call invokes the command at the given subcommand path
40
func (c *Command) Call(path []string, req *Request) *Response {
41
- options := make([]Option, len(c.Options))
42
- copy(options, c.Options)
43
- options = append(options, globalOptions...)
44
- cmd := c
45
- res := &Response{ req: req }
46
-
47
- if path != nil {
48
- for i, id := range path {
49
- cmd = c.Sub(id)
50
-
51
- if cmd == nil {
52
- pathS := strings.Join(path[0:i], "/")
53
- res.SetError(fmt.Errorf("Undefined command: '%s'", pathS), Client)
54
- return res
55
- }
56
-
57
- options = append(options, cmd.Options...)
58
- }
59
- }
60
-
61
- optionsMap := make(map[string]Option)
62
- for _, opt := range options {
63
- for _, name := range opt.Names {
64
- optionsMap[name] = opt
65
- }
66
- }
67
-
68
- for k, v := range req.options {
69
- opt, ok := optionsMap[k]
70
-
71
- if !ok {
72
- res.SetError(fmt.Errorf("Unrecognized command option: '%s'", k), Client)
73
- return res
74
- }
75
-
76
- for _, name := range opt.Names {
77
- if _, ok = req.options[name]; name != k && ok {
78
- res.SetError(fmt.Errorf("Duplicate command options were provided ('%s' and '%s')",
79
- k, name), Client)
80
- return res
81
- }
82
- }
83
-
84
- kind := reflect.TypeOf(v).Kind()
85
- if kind != opt.Type {
86
- res.SetError(fmt.Errorf("Option '%s' should be type '%s', but got type '%s'",
87
- k, opt.Type.String(), kind.String()), Client)
88
- return res
89
- }
90
- }
91
-
92
- cmd.f(req, res)
93
-
94
- return res
41
+ options := make([]Option, len(c.Options))
42
+ copy(options, c.Options)
43
+ options = append(options, globalOptions...)
44
+ cmd := c
45
+ res := &Response{req: req}
46
+
47
+ if path != nil {
48
+ for i, id := range path {
49
+ cmd = c.Sub(id)
50
+
51
+ if cmd == nil {
52
+ pathS := strings.Join(path[0:i], "/")
53
+ res.SetError(fmt.Errorf("Undefined command: '%s'", pathS), Client)
54
+ return res
55
+ }
56
+
57
+ options = append(options, cmd.Options...)
58
+ }
59
+ }
60
+
61
+ optionsMap := make(map[string]Option)
62
+ for _, opt := range options {
63
+ for _, name := range opt.Names {
64
+ optionsMap[name] = opt
65
+ }
66
+ }
67
+
68
+ for k, v := range req.options {
69
+ opt, ok := optionsMap[k]
70
+
71
+ if !ok {
72
+ res.SetError(fmt.Errorf("Unrecognized command option: '%s'", k), Client)
73
+ return res
74
+ }
75
+
76
+ for _, name := range opt.Names {
77
+ if _, ok = req.options[name]; name != k && ok {
78
+ res.SetError(fmt.Errorf("Duplicate command options were provided ('%s' and '%s')",
79
+ k, name), Client)
80
+ return res
81
+ }
82
+ }
83
+
84
+ kind := reflect.TypeOf(v).Kind()
85
+ if kind != opt.Type {
86
+ res.SetError(fmt.Errorf("Option '%s' should be type '%s', but got type '%s'",
87
+ k, opt.Type.String(), kind.String()), Client)
88
+ return res
89
+ }
90
+ }
91
+
92
+ cmd.f(req, res)
93
+
94
+ return res
95
}
96
97
// Sub returns the subcommand with the given id
98
func (c *Command) Sub(id string) *Command {
99
- return c.subcommands[id]
99
+ return c.subcommands[id]
100
}
101
102
func (c *Command) checkOptions(names map[string]bool) error {
103
- for _, opt := range c.Options {
104
- for _, name := range opt.Names {
105
- if _, ok := names[name]; ok {
106
- return fmt.Errorf("Multiple options are using the same name ('%s')", name)
107
- }
108
- names[name] = true
109
- }
110
- }
111
-
112
- for _, cmd := range c.subcommands {
113
- err := cmd.checkOptions(names)
114
- if err != nil {
115
- return err
116
- }
117
- }
118
-
119
- return nil
103
+ for _, opt := range c.Options {
104
+ for _, name := range opt.Names {
105
+ if _, ok := names[name]; ok {
106
+ return fmt.Errorf("Multiple options are using the same name ('%s')", name)
107
+ }
108
+ names[name] = true
109
+ }
110
+ }
111
+
112
+ for _, cmd := range c.subcommands {
113
+ err := cmd.checkOptions(names)
114
+ if err != nil {
115
+ return err
116
+ }
117
+ }
118
+
119
+ return nil
120
}
commands/command_test.go
+115
-115
@@ -3,122 +3,122 @@ package commands
3
import "testing"
4
5
func TestOptionValidation(t *testing.T) {
6
- cmd := Command{
7
- Options: []Option{
8
- Option{ []string{ "b", "beep" }, Int },
9
- Option{ []string{ "B", "boop" }, String },
10
- },
11
- f: func(req *Request, res *Response) {},
12
- }
13
-
14
- req := NewRequest()
15
- req.options["foo"] = 5
16
- res := cmd.Call(nil, req)
17
- if res.Error == nil {
18
- t.Error("Should have failed (unrecognized command)")
19
- }
20
-
21
- req = NewRequest()
22
- req.options["beep"] = 5
23
- req.options["b"] = 10
24
- res = cmd.Call(nil, req)
25
- if res.Error == nil {
26
- t.Error("Should have failed (duplicate options)")
27
- }
28
-
29
- req = NewRequest()
30
- req.options["beep"] = "foo"
31
- res = cmd.Call(nil, req)
32
- if res.Error == nil {
33
- t.Error("Should have failed (incorrect type)")
34
- }
35
-
36
- req = NewRequest()
37
- req.options["beep"] = 5
38
- res = cmd.Call(nil, req)
39
- if res.Error != nil {
40
- t.Error("Should have passed")
41
- }
42
-
43
- req = NewRequest()
44
- req.options["beep"] = 5
45
- req.options["boop"] = "test"
46
- res = cmd.Call(nil, req)
47
- if res.Error != nil {
48
- t.Error("Should have passed")
49
- }
50
-
51
- req = NewRequest()
52
- req.options["b"] = 5
53
- req.options["B"] = "test"
54
- res = cmd.Call(nil, req)
55
- if res.Error != nil {
56
- t.Error("Should have passed")
57
- }
58
-
59
- req = NewRequest()
60
- req.options["enc"] = "json"
61
- res = cmd.Call(nil, req)
62
- if res.Error != nil {
63
- t.Error("Should have passed")
64
- }
6
+ cmd := Command{
7
+ Options: []Option{
8
+ Option{[]string{"b", "beep"}, Int},
9
+ Option{[]string{"B", "boop"}, String},
10
+ },
11
+ f: func(req *Request, res *Response) {},
12
+ }
13
+
14
+ req := NewRequest()
15
+ req.options["foo"] = 5
16
+ res := cmd.Call(nil, req)
17
+ if res.Error == nil {
18
+ t.Error("Should have failed (unrecognized command)")
19
+ }
20
+
21
+ req = NewRequest()
22
+ req.options["beep"] = 5
23
+ req.options["b"] = 10
24
+ res = cmd.Call(nil, req)
25
+ if res.Error == nil {
26
+ t.Error("Should have failed (duplicate options)")
27
+ }
28
+
29
+ req = NewRequest()
30
+ req.options["beep"] = "foo"
31
+ res = cmd.Call(nil, req)
32
+ if res.Error == nil {
33
+ t.Error("Should have failed (incorrect type)")
34
+ }
35
+
36
+ req = NewRequest()
37
+ req.options["beep"] = 5
38
+ res = cmd.Call(nil, req)
39
+ if res.Error != nil {
40
+ t.Error("Should have passed")
41
+ }
42
+
43
+ req = NewRequest()
44
+ req.options["beep"] = 5
45
+ req.options["boop"] = "test"
46
+ res = cmd.Call(nil, req)
47
+ if res.Error != nil {
48
+ t.Error("Should have passed")
49
+ }
50
+
51
+ req = NewRequest()
52
+ req.options["b"] = 5
53
+ req.options["B"] = "test"
54
+ res = cmd.Call(nil, req)
55
+ if res.Error != nil {
56
+ t.Error("Should have passed")
57
+ }
58
+
59
+ req = NewRequest()
60
+ req.options["enc"] = "json"
61
+ res = cmd.Call(nil, req)
62
+ if res.Error != nil {
63
+ t.Error("Should have passed")
64
+ }
65
}
66
67
func TestRegistration(t *testing.T) {
68
- cmds := []*Command{
69
- &Command{
70
- Options: []Option{
71
- Option{ []string{ "beep" }, Int },
72
- },
73
- f: func(req *Request, res *Response) {},
74
- },
75
-
76
- &Command{
77
- Options: []Option{
78
- Option{ []string{ "boop" }, Int },
79
- },
80
- f: func(req *Request, res *Response) {},
81
- },
82
-
83
- &Command{
84
- Options: []Option{
85
- Option{ []string{ "boop" }, String },
86
- },
87
- f: func(req *Request, res *Response) {},
88
- },
89
-
90
- &Command{
91
- Options: []Option{
92
- Option{ []string{ "bop" }, String },
93
- },
94
- f: func(req *Request, res *Response) {},
95
- },
96
-
97
- &Command{
98
- Options: []Option{
99
- Option{ []string{ "enc" }, String },
100
- },
101
- f: func(req *Request, res *Response) {},
102
- },
103
- }
104
-
105
- err := cmds[0].Register("foo", cmds[1])
106
- if err != nil {
107
- t.Error("Should have passed")
108
- }
109
-
110
- err = cmds[0].Register("bar", cmds[2])
111
- if err == nil {
112
- t.Error("Should have failed (option name collision)")
113
- }
114
-
115
- err = cmds[0].Register("foo", cmds[3])
116
- if err == nil {
117
- t.Error("Should have failed (subcommand name collision)")
118
- }
119
-
120
- err = cmds[0].Register("baz", cmds[4])
121
- if err == nil {
122
- t.Error("Should have failed (option name collision with global options)")
123
- }
68
+ cmds := []*Command{
69
+ &Command{
70
+ Options: []Option{
71
+ Option{[]string{"beep"}, Int},
72
+ },
73
+ f: func(req *Request, res *Response) {},
74
+ },
75
+
76
+ &Command{
77
+ Options: []Option{
78
+ Option{[]string{"boop"}, Int},
79
+ },
80
+ f: func(req *Request, res *Response) {},
81
+ },
82
+
83
+ &Command{
84
+ Options: []Option{
85
+ Option{[]string{"boop"}, String},
86
+ },
87
+ f: func(req *Request, res *Response) {},
88
+ },
89
+
90
+ &Command{
91
+ Options: []Option{
92
+ Option{[]string{"bop"}, String},
93
+ },
94
+ f: func(req *Request, res *Response) {},
95
+ },
96
+
97
+ &Command{
98
+ Options: []Option{
99
+ Option{[]string{"enc"}, String},
100
+ },
101
+ f: func(req *Request, res *Response) {},
102
+ },
103
+ }
104
+
105
+ err := cmds[0].Register("foo", cmds[1])
106
+ if err != nil {
107
+ t.Error("Should have passed")
108
+ }
109
+
110
+ err = cmds[0].Register("bar", cmds[2])
111
+ if err == nil {
112
+ t.Error("Should have failed (option name collision)")
113
+ }
114
+
115
+ err = cmds[0].Register("foo", cmds[3])
116
+ if err == nil {
117
+ t.Error("Should have failed (subcommand name collision)")
118
+ }
119
+
120
+ err = cmds[0].Register("baz", cmds[4])
121
+ if err == nil {
122
+ t.Error("Should have failed (option name collision with global options)")
123
+ }
124
}
commands/option.go
+14
-13
@@ -3,29 +3,30 @@ package commands
3
import "reflect"
4
5
const (
6
- Invalid = reflect.Invalid
7
- Bool = reflect.Bool
8
- Int = reflect.Int
9
- Uint = reflect.Uint
10
- Float = reflect.Float32
11
- String = reflect.String
6
+ Invalid = reflect.Invalid
7
+ Bool = reflect.Bool
8
+ Int = reflect.Int
9
+ Uint = reflect.Uint
10
+ Float = reflect.Float32
11
+ String = reflect.String
12
)
13
14
// Option is used to specify a field that will be provided by a consumer
15
type Option struct {
16
- Names []string // a list of unique names to
17
- Type reflect.Kind // value must be this type
16
+ Names []string // a list of unique names to
17
+ Type reflect.Kind // value must be this type
18
19
- // TODO: add more features(?):
20
- //Default interface{} // the default value (ignored if `Required` is true)
21
- //Required bool // whether or not the option must be provided
19
+ // TODO: add more features(?):
20
+ //Default interface{} // the default value (ignored if `Required` is true)
21
+ //Required bool // whether or not the option must be provided
22
}
23
24
// options that are used by this package
25
var globalOptions []Option = []Option{
26
- Option{ []string{ "enc", "encoding" }, String },
26
+ Option{[]string{"enc", "encoding"}, String},
27
}
28
+
29
// the above array of Options, wrapped in a Command
30
var globalCommand *Command = &Command{
30
- Options: globalOptions,
31
+ Options: globalOptions,
32
}
commands/request.go
+13
-13
@@ -2,29 +2,29 @@ package commands
2
3
// Request represents a call to a command from a consumer
4
type Request struct {
5
- options map[string]interface{}
6
- arguments []string
5
+ options map[string]interface{}
6
+ arguments []string
7
}
8
9
func (r *Request) Option(name string) interface{} {
10
- return r.options[name]
10
+ return r.options[name]
11
}
12
13
func (r *Request) SetOption(option Option, value interface{}) {
14
- // saves the option value in the map, indexed by each name
15
- // (so commands can retrieve it using any of the names)
16
- for _, name := range option.Names {
17
- r.options[name] = value
18
- }
14
+ // saves the option value in the map, indexed by each name
15
+ // (so commands can retrieve it using any of the names)
16
+ for _, name := range option.Names {
17
+ r.options[name] = value
18
+ }
19
}
20
21
func (r *Request) Arguments() []string {
22
- return r.arguments
22
+ return r.arguments
23
}
24
25
func NewRequest() *Request {
26
- return &Request{
27
- make(map[string]interface{}),
28
- make([]string, 0),
29
- }
26
+ return &Request{
27
+ make(map[string]interface{}),
28
+ make([]string, 0),
29
+ }
30
}
commands/response.go
+43
-40
@@ -1,74 +1,77 @@
1
package commands
2
3
import (
4
- "fmt"
5
- "strings"
6
- "encoding/json"
7
- "encoding/xml"
4
+ "encoding/json"
5
+ "encoding/xml"
6
+ "fmt"
7
+ "strings"
8
)
9
10
type ErrorType uint
11
+
12
const (
12
- Normal ErrorType = iota // general errors
13
- Client // error was caused by the client, (e.g. invalid CLI usage)
14
- // TODO: add more types of errors for better error-specific handling
13
+ Normal ErrorType = iota // general errors
14
+ Client // error was caused by the client, (e.g. invalid CLI usage)
15
+ // TODO: add more types of errors for better error-specific handling
16
)
17
18
// Error is a struct for marshalling errors
19
type Error struct {
19
- Message string
20
- Code ErrorType
20
+ Message string
21
+ Code ErrorType
22
}
23
24
type EncodingType string
25
+
26
const (
25
- Json = "json"
26
- Xml = "xml"
27
- // TODO: support more encoding types
27
+ Json = "json"
28
+ Xml = "xml"
29
+ // TODO: support more encoding types
30
)
31
30
-type Marshaller func(v interface{})([]byte, error)
32
+type Marshaller func(v interface{}) ([]byte, error)
33
+
34
var marshallers = map[EncodingType]Marshaller{
32
- Json: json.Marshal,
33
- Xml: xml.Marshal,
35
+ Json: json.Marshal,
36
+ Xml: xml.Marshal,
37
}
38
39
type Response struct {
37
- req *Request
38
- Error error
39
- ErrorType ErrorType
40
- Value interface{}
40
+ req *Request
41
+ Error error
42
+ ErrorType ErrorType
43
+ Value interface{}
44
}
45
46
func (r *Response) SetError(err error, errType ErrorType) {
44
- r.Error = err
45
- r.ErrorType = errType
47
+ r.Error = err
48
+ r.ErrorType = errType
49
}
50
51
func (r *Response) FormatError() Error {
49
- return Error{ r.Error.Error(), r.ErrorType }
52
+ return Error{r.Error.Error(), r.ErrorType}
53
}
54
55
func (r *Response) Marshal() ([]byte, error) {
53
- if r.Error == nil && r.Value == nil {
54
- return nil, fmt.Errorf("No error or value set, there is nothing to marshal")
55
- }
56
+ if r.Error == nil && r.Value == nil {
57
+ return nil, fmt.Errorf("No error or value set, there is nothing to marshal")
58
+ }
59
57
- enc := r.req.Option("enc")
58
- if enc == nil {
59
- return nil, fmt.Errorf("No encoding type was specified")
60
- }
61
- encType := EncodingType(strings.ToLower(enc.(string)))
60
+ enc := r.req.Option("enc")
61
+ if enc == nil {
62
+ return nil, fmt.Errorf("No encoding type was specified")
63
+ }
64
+ encType := EncodingType(strings.ToLower(enc.(string)))
65
63
- marshaller, ok := marshallers[encType]
64
- if !ok {
65
- return nil, fmt.Errorf("No marshaller found for encoding type '%s'", enc)
66
- }
66
+ marshaller, ok := marshallers[encType]
67
+ if !ok {
68
+ return nil, fmt.Errorf("No marshaller found for encoding type '%s'", enc)
69
+ }
70
68
- if r.Error != nil {
69
- err := r.FormatError()
70
- return marshaller(err)
71
- } else {
72
- return marshaller(r.Value)
73
- }
71
+ if r.Error != nil {
72
+ err := r.FormatError()
73
+ return marshaller(err)
74
+ } else {
75
+ return marshaller(r.Value)
76
+ }
77
}
commands/response_test.go
+31
-31
@@ -1,45 +1,45 @@
1
package commands
2
3
import (
4
- "testing"
5
- "fmt"
4
+ "fmt"
5
+ "testing"
6
)
7
8
type TestOutput struct {
9
- Foo, Bar string
10
- Baz int
9
+ Foo, Bar string
10
+ Baz int
11
}
12
13
func TestMarshalling(t *testing.T) {
14
- req := NewRequest()
14
+ req := NewRequest()
15
16
- res := Response{
17
- req: req,
18
- Value: TestOutput{ "beep", "boop", 1337 },
19
- }
16
+ res := Response{
17
+ req: req,
18
+ Value: TestOutput{"beep", "boop", 1337},
19
+ }
20
21
- _, err := res.Marshal()
22
- if err == nil {
23
- t.Error("Should have failed (no encoding type specified in request)")
24
- }
21
+ _, err := res.Marshal()
22
+ if err == nil {
23
+ t.Error("Should have failed (no encoding type specified in request)")
24
+ }
25
26
- req.SetOption(globalOptions[0], Json)
27
- bytes, err := res.Marshal()
28
- if err != nil {
29
- t.Error("Should have passed")
30
- }
31
- output := string(bytes)
32
- if output != "{\"Foo\":\"beep\",\"Bar\":\"boop\",\"Baz\":1337}" {
33
- t.Error("Incorrect JSON output")
34
- }
26
+ req.SetOption(globalOptions[0], Json)
27
+ bytes, err := res.Marshal()
28
+ if err != nil {
29
+ t.Error("Should have passed")
30
+ }
31
+ output := string(bytes)
32
+ if output != "{\"Foo\":\"beep\",\"Bar\":\"boop\",\"Baz\":1337}" {
33
+ t.Error("Incorrect JSON output")
34
+ }
35
36
- res.SetError(fmt.Errorf("You broke something!"), Client)
37
- bytes, err = res.Marshal()
38
- if err != nil {
39
- t.Error("Should have passed")
40
- }
41
- output = string(bytes)
42
- if output != "{\"Message\":\"You broke something!\",\"Code\":1}" {
43
- t.Error("Incorrect JSON output")
44
- }
36
+ res.SetError(fmt.Errorf("You broke something!"), Client)
37
+ bytes, err = res.Marshal()
38
+ if err != nil {
39
+ t.Error("Should have passed")
40
+ }
41
+ output = string(bytes)
42
+ if output != "{\"Message\":\"You broke something!\",\"Code\":1}" {
43
+ t.Error("Incorrect JSON output")
44
+ }
45
}