-
Notifications
You must be signed in to change notification settings - Fork 1
/
gorpl.go
169 lines (146 loc) · 3.75 KB
/
gorpl.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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
package gorpl
import (
"fmt"
"log"
"strings"
"github.com/xandout/gorpl/action"
"github.com/chzyer/readline"
)
// Recursive function used to build PrefixCompleter
// I barely understand this...it breaks any time I touch it
func wrFunc(actions []action.Action) []readline.PrefixCompleterInterface {
var pci []readline.PrefixCompleterInterface
for _, a := range actions {
if len(a.Children) > 0 {
pci = append(pci, readline.PcItem(a.Name, wrFunc(a.Children)...))
} else {
pci = append(pci, readline.PcItem(a.Name))
}
}
return pci
}
// Used to build PrefixCompleter...calls wrFunc
func (r *Repl) walkActions() {
var pcis []readline.PrefixCompleterInterface
for _, aa := range r.Actions {
if len(aa.Children) > 0 {
pcis = append(pcis, readline.PcItem(aa.Name, wrFunc(aa.Children)...))
} else {
pcis = append(pcis, readline.PcItem(aa.Name))
}
}
r.RL.Config.AutoComplete = readline.NewPrefixCompleter(pcis...)
}
// Repl houses all of our config data
type Repl struct {
RL *readline.Instance
Actions []action.Action
Default action.Action
Prefix string
Terminator string
}
// New sets up the Repl
func New(term string) Repl {
r, err := readline.NewEx(&readline.Config{
Prompt: "> ",
HistoryFile: "/tmp/repl.history",
DisableAutoSaveHistory: true,
})
if err != nil {
log.Fatal(err)
}
return Repl{
RL: r,
Terminator: term,
}
}
// AddAction registers a named function, Action
func (r *Repl) AddAction(action action.Action) {
r.Actions = append(r.Actions, action)
}
// Start runs the Repl as configured
func (r *Repl) Start() error {
defer r.RL.Close()
// Setup readline completer
r.walkActions()
// History
var cmdHist []string
var lines []string
// If user does not supply default Action then we just echo
if r.Default.Action == nil {
r.Default = action.Action{
Action: func(args ...interface{}) (interface{}, error) {
fmt.Println(args)
return nil, nil
},
}
}
REPL_LOOP:
for {
line, err := r.RL.Readline()
if err != nil {
log.Fatal(err)
}
// Remove extra whitespace
line = strings.TrimSpace(line)
cmd := strings.Split(line, " ")
// Empty input
if len(line) == 0 {
continue
}
cmdHist = append(cmdHist, line)
// Is this a built in command?
for _, a := range r.Actions {
if a.Name == cmd[0] {
// Must be registered. Now we need to work out children
args := make([]interface{}, len(cmd[1:]))
for i, v := range cmd[1:] {
args[i] = v
}
// Determine if child
var childRunner func(parentAction action.Action, children []action.Action, newArgs []interface{})
childRunner = func(parentAction action.Action, children []action.Action, newArgs []interface{}) {
mapped := make(map[string]action.Action, len(children))
for _, a := range children {
mapped[a.Name] = a
}
if len(newArgs) > 0 {
firstarg := newArgs[0].(string)
if act, ok := mapped[firstarg]; ok {
childRunner(act, act.Children, newArgs[1:])
} else {
parentAction.Action(newArgs...)
}
} else {
parentAction.Action()
}
}
childRunner(a, a.Children, args)
r.RL.SetPrompt("> ")
r.RL.SaveHistory(line)
continue REPL_LOOP
}
}
// NOT a registered Action, treat as string with multiline and pass to default.
if !strings.HasSuffix(line, r.Terminator) {
lines = append(lines, line)
r.RL.SetPrompt(">>> ")
r.RL.SaveHistory(line)
continue
} else {
args := make([]interface{}, 1)
lines = append(lines, line)
r.RL.SetPrompt("> ")
r.RL.SaveHistory(line)
args[0] = strings.Join(lines, " ")
ret, err := r.Default.Action(args...)
if err != nil {
log.Println(err)
} else {
fmt.Println(ret)
}
lines = []string{}
continue
}
}
}