-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcommand.go
144 lines (128 loc) · 3.64 KB
/
command.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
package fssh
import (
"flag"
"fmt"
"io"
"sort"
"strings"
"sync"
"github.com/chzyer/readline"
)
// Command is an interface that defines a shell command.
type Command interface {
// Name returns the name of a command.
Name() string
// Description returns the description of a command.
Description() string
// FlagSet returns the flagSet of a command.
FlagSet() *flag.FlagSet
// Exec executes a command.
Exec(sh *Shell) error
// Usage writes help usage.
Usage(w io.Writer)
// AutoCompleter returns a AutoCompleter if the command supports auto completion.
AutoCompleter() AutoCompleterFunc
// Reset resets the command status. This is called after Exec.
Reset()
}
// AutoCompleterFunc represent a function for auto completion.
type AutoCompleterFunc func(sh *Shell, arg string) ([]string, error)
// NewCommandFunc represents a function to create a new command.
type NewCommandFunc func() Command
var (
commandPools = map[string]*sync.Pool{}
commandNames []string
commandNamesSorted bool
)
// RegisterNewCommandFunc registers a specified NewCommandFunc.
func RegisterNewCommandFunc(fn NewCommandFunc) {
cmd := fn()
commandPools[cmd.Name()] = &sync.Pool{
New: func() any { return fn() },
}
commandNames = append(commandNames, cmd.Name())
commandNamesSorted = false
}
// DeregisterNewCommandFunc deregisters a named NewCommandFunc.
func DeregisterNewCommandFunc(name string) {
delete(commandPools, name)
for i, nm := range commandNames {
if nm == name {
for j := i + 1; j < len(commandNames); j++ {
commandNames[i] = commandNames[j]
}
commandNames = commandNames[:len(commandNames)-1]
break
}
}
}
// AquireCommand returns an Command instance from command pool.
func AquireCommand(name string) Command {
if pool, ok := commandPools[name]; ok {
return pool.Get().(Command)
}
return nil
}
// ReleaseCommand releases a acquired Command via AquireCommand to command pool.
func ReleaseCommand(cmd Command) {
if pool, ok := commandPools[cmd.Name()]; ok {
cmd.Reset()
pool.Put(cmd)
}
}
// SortedCommandNames returns sorted command names from registered commands.
func SortedCommandNames() []string {
if !commandNamesSorted {
sort.Strings(commandNames)
commandNamesSorted = true
}
return commandNames
}
func newReadlineAutoCompleter(sh *Shell) readline.AutoCompleter {
var pcs []readline.PrefixCompleterInterface
for _, name := range SortedCommandNames() {
pcs = append(pcs, newReadlinePrefixCompleter(sh, name))
}
return readline.NewPrefixCompleter(pcs...)
}
func newReadlinePrefixCompleter(sh *Shell, name string) readline.PrefixCompleterInterface {
cmd := AquireCommand(name)
defer ReleaseCommand(cmd)
pc := readline.PcItem(name)
ac := cmd.AutoCompleter()
if ac == nil {
return pc
}
pc.Children = append(pc.Children, readline.PcItemDynamic(func(line string) []string {
cmd := AquireCommand(name)
defer ReleaseCommand(cmd)
return autoComplete(sh, cmd, line)
}))
return pc
}
func autoComplete(sh *Shell, cmd Command, line string) []string {
ac := cmd.AutoCompleter()
args := ParseArgs(line)[1:]
if err := cmd.FlagSet().Parse(args); err != nil {
fmt.Fprintf(sh.Stderr, "%s: failed to parse flags: %v\n", ShellName, err)
return nil
}
fargs := cmd.FlagSet().Args()
lastFarg := ""
if len(fargs) > 0 {
lastFarg = fargs[len(fargs)-1]
}
matches, err := ac(sh, lastFarg)
if err != nil {
fmt.Fprintf(sh.Stderr, "%s: failed to auto complete: %v\n", ShellName, err)
return nil
}
prefix := strings.TrimSpace(line[len(cmd.Name()):])
if len(fargs) > 0 {
prefix = strings.TrimSpace(strings.TrimSuffix(prefix, lastFarg))
}
if prefix != "" {
prefix = prefix + " "
}
return WithPrefixes(matches, prefix)
}