-
Notifications
You must be signed in to change notification settings - Fork 46
/
Copy pathexecutor.go
129 lines (107 loc) · 3.02 KB
/
executor.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
package cmds
import (
"context"
"os"
)
type Executor interface {
Execute(req *Request, re ResponseEmitter, env Environment) error
}
// Environment is the environment passed to commands.
type Environment interface{}
// MakeEnvironment takes a context and the request to construct the environment
// that is passed to the command's Run function.
// The user can define a function like this to pass it to cli.Run.
type MakeEnvironment func(context.Context, *Request) (Environment, error)
// MakeExecutor takes the request and environment variable to construct the
// executor that determines how to call the command - i.e. by calling Run or
// making an API request to a daemon.
// The user can define a function like this to pass it to cli.Run.
type MakeExecutor func(*Request, interface{}) (Executor, error)
func NewExecutor(root *Command) Executor {
return &executor{
root: root,
}
}
type executor struct {
root *Command
}
func (x *executor) Execute(req *Request, re ResponseEmitter, env Environment) error {
cmd := req.Command
if cmd.Run == nil {
return ErrNotCallable
}
err := cmd.CheckArguments(req)
if err != nil {
return err
}
if cmd.PreRun != nil {
err = cmd.PreRun(req, env)
if err != nil {
return err
}
}
return EmitResponse(cmd.Run, req, re, env)
}
// Helper for Execute that handles post-Run emitter logic
func EmitResponse(run Function, req *Request, re ResponseEmitter, env Environment) error {
// Keep track of the lowest emitter to select the correct
// PostRun method.
lowest := re
cmd := req.Command
// contains the error returned by DisplayCLI or PostRun
errCh := make(chan error, 1)
if cmd.DisplayCLI != nil && GetEncoding(req, "json") == "text" {
var res Response
// This overwrites the emitter provided as an
// argument. Maybe it's better to provide the
// 'DisplayCLI emitter' as an argument to Execute.
re, res = NewChanResponsePair(req)
go func() {
defer close(errCh)
errCh <- cmd.DisplayCLI(res, os.Stdout, os.Stderr)
}()
}
maybeStartPostRun := func(formatters PostRunMap) <-chan error {
var (
postRun func(Response, ResponseEmitter) error
postRunCh = make(chan error)
)
// check if we have a formatter for this emitter type
typer, isTyper := lowest.(interface {
Type() PostRunType
})
if isTyper &&
formatters[typer.Type()] != nil {
postRun = formatters[typer.Type()]
} else {
close(postRunCh)
return postRunCh
}
// redirect emitter to us
// and start waiting for emissions
var (
postRes Response
postEmitter = re
)
re, postRes = NewChanResponsePair(req)
go func() {
defer close(postRunCh)
postRunCh <- postEmitter.CloseWithError(postRun(postRes, postEmitter))
}()
return postRunCh
}
postRunCh := maybeStartPostRun(cmd.PostRun)
runCloseErr := re.CloseWithError(run(req, re, env))
postCloseErr := <-postRunCh
switch runCloseErr {
case ErrClosingClosedEmitter, nil:
default:
return runCloseErr
}
switch postCloseErr {
case ErrClosingClosedEmitter, nil:
default:
return postCloseErr
}
return nil
}