parent
0d98d7ee19
commit
5fdb1adfd7
9
args.go
9
args.go
|
@ -57,11 +57,20 @@ func newArgs(line string) Args {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// splitFields returns a list of fields from the given command line.
|
||||||
|
// If the last character is space, it appends an empty field in the end
|
||||||
|
// indicating that the field before it was completed.
|
||||||
|
// If the last field is of the form "a=b", it splits it to two fields: "a", "b",
|
||||||
|
// So it can be completed.
|
||||||
func splitFields(line string) []string {
|
func splitFields(line string) []string {
|
||||||
parts := strings.Fields(line)
|
parts := strings.Fields(line)
|
||||||
|
|
||||||
|
// Add empty field if the last field was completed.
|
||||||
if len(line) > 0 && unicode.IsSpace(rune(line[len(line)-1])) {
|
if len(line) > 0 && unicode.IsSpace(rune(line[len(line)-1])) {
|
||||||
parts = append(parts, "")
|
parts = append(parts, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Treat the last field if it is of the form "a=b"
|
||||||
parts = splitLastEqual(parts)
|
parts = splitLastEqual(parts)
|
||||||
return parts
|
return parts
|
||||||
}
|
}
|
||||||
|
|
30
complete.go
30
complete.go
|
@ -10,14 +10,16 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"github.com/posener/complete/cmd"
|
"github.com/posener/complete/cmd"
|
||||||
"github.com/posener/complete/match"
|
"github.com/posener/complete/match"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
envComplete = "COMP_LINE"
|
envLine = "COMP_LINE"
|
||||||
envDebug = "COMP_DEBUG"
|
envPoint = "COMP_POINT"
|
||||||
|
envDebug = "COMP_DEBUG"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Complete structs define completion for a command with CLI options
|
// Complete structs define completion for a command with CLI options
|
||||||
|
@ -55,14 +57,17 @@ func (c *Complete) Run() bool {
|
||||||
// For installation: it assumes that flags were added and parsed before
|
// For installation: it assumes that flags were added and parsed before
|
||||||
// it was called.
|
// it was called.
|
||||||
func (c *Complete) Complete() bool {
|
func (c *Complete) Complete() bool {
|
||||||
line, ok := getLine()
|
line, point, ok := getEnv()
|
||||||
if !ok {
|
if !ok {
|
||||||
// make sure flags parsed,
|
// make sure flags parsed,
|
||||||
// in case they were not added in the main program
|
// in case they were not added in the main program
|
||||||
return c.CLI.Run()
|
return c.CLI.Run()
|
||||||
}
|
}
|
||||||
Log("Completing line: %s", line)
|
|
||||||
a := newArgs(line)
|
completePhrase := line[:point]
|
||||||
|
|
||||||
|
Log("Completing phrase: %s", completePhrase)
|
||||||
|
a := newArgs(completePhrase)
|
||||||
Log("Completing last field: %s", a.Last)
|
Log("Completing last field: %s", a.Last)
|
||||||
options := c.Command.Predict(a)
|
options := c.Command.Predict(a)
|
||||||
Log("Options: %s", options)
|
Log("Options: %s", options)
|
||||||
|
@ -79,12 +84,19 @@ func (c *Complete) Complete() bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func getLine() (string, bool) {
|
func getEnv() (line string, point int, ok bool) {
|
||||||
line := os.Getenv(envComplete)
|
line = os.Getenv(envLine)
|
||||||
if line == "" {
|
if line == "" {
|
||||||
return "", false
|
return
|
||||||
}
|
}
|
||||||
return line, true
|
point, err := strconv.Atoi(os.Getenv(envPoint))
|
||||||
|
if err != nil {
|
||||||
|
// If failed parsing point for some reason, set it to point
|
||||||
|
// on the end of the line.
|
||||||
|
Log("Failed parsing point %s: %v", os.Getenv(envPoint), err)
|
||||||
|
point = len(line)
|
||||||
|
}
|
||||||
|
return line, point, true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Complete) output(options []string) {
|
func (c *Complete) output(options []string) {
|
||||||
|
|
279
complete_test.go
279
complete_test.go
|
@ -2,14 +2,15 @@ package complete
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"sort"
|
"sort"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestCompleter_Complete(t *testing.T) {
|
func TestCompleter_Complete(t *testing.T) {
|
||||||
t.Parallel()
|
|
||||||
initTests()
|
initTests()
|
||||||
|
|
||||||
c := Command{
|
c := Command{
|
||||||
|
@ -39,166 +40,229 @@ func TestCompleter_Complete(t *testing.T) {
|
||||||
cmp := New("cmd", c)
|
cmp := New("cmd", c)
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
args string
|
line string
|
||||||
want []string
|
point int // -1 indicates len(line)
|
||||||
|
want []string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
args: "",
|
line: "cmd ",
|
||||||
want: []string{"sub1", "sub2"},
|
point: -1,
|
||||||
|
want: []string{"sub1", "sub2"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
args: "-",
|
line: "cmd -",
|
||||||
want: []string{"-h", "-global1", "-o"},
|
point: -1,
|
||||||
|
want: []string{"-h", "-global1", "-o"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
args: "-h ",
|
line: "cmd -h ",
|
||||||
want: []string{"sub1", "sub2"},
|
point: -1,
|
||||||
|
want: []string{"sub1", "sub2"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
args: "-global1 ", // global1 is known follow flag
|
line: "cmd -global1 ", // global1 is known follow flag
|
||||||
want: []string{},
|
point: -1,
|
||||||
|
want: []string{},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
args: "sub",
|
line: "cmd sub",
|
||||||
want: []string{"sub1", "sub2"},
|
point: -1,
|
||||||
|
want: []string{"sub1", "sub2"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
args: "sub1",
|
line: "cmd sub1",
|
||||||
want: []string{"sub1"},
|
point: -1,
|
||||||
|
want: []string{"sub1"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
args: "sub2",
|
line: "cmd sub2",
|
||||||
want: []string{"sub2"},
|
point: -1,
|
||||||
|
want: []string{"sub2"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
args: "sub1 ",
|
line: "cmd sub1 ",
|
||||||
want: []string{},
|
point: -1,
|
||||||
|
want: []string{},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
args: "sub1 -",
|
line: "cmd sub1 -",
|
||||||
want: []string{"-flag1", "-flag2", "-h", "-global1"},
|
point: -1,
|
||||||
|
want: []string{"-flag1", "-flag2", "-h", "-global1"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
args: "sub2 ",
|
line: "cmd sub2 ",
|
||||||
want: []string{"./", "dir/", "outer/", "readme.md"},
|
point: -1,
|
||||||
|
want: []string{"./", "dir/", "outer/", "readme.md"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
args: "sub2 ./",
|
line: "cmd sub2 ./",
|
||||||
want: []string{"./", "./readme.md", "./dir/", "./outer/"},
|
point: -1,
|
||||||
|
want: []string{"./", "./readme.md", "./dir/", "./outer/"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
args: "sub2 re",
|
line: "cmd sub2 re",
|
||||||
want: []string{"readme.md"},
|
point: -1,
|
||||||
|
want: []string{"readme.md"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
args: "sub2 ./re",
|
line: "cmd sub2 ./re",
|
||||||
want: []string{"./readme.md"},
|
point: -1,
|
||||||
|
want: []string{"./readme.md"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
args: "sub2 -flag2 ",
|
line: "cmd sub2 -flag2 ",
|
||||||
want: []string{"./", "dir/", "outer/", "readme.md"},
|
point: -1,
|
||||||
|
want: []string{"./", "dir/", "outer/", "readme.md"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
args: "sub1 -fl",
|
line: "cmd sub1 -fl",
|
||||||
want: []string{"-flag1", "-flag2"},
|
point: -1,
|
||||||
|
want: []string{"-flag1", "-flag2"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
args: "sub1 -flag1",
|
line: "cmd sub1 -flag1",
|
||||||
want: []string{"-flag1"},
|
point: -1,
|
||||||
|
want: []string{"-flag1"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
args: "sub1 -flag1 ",
|
line: "cmd sub1 -flag1 ",
|
||||||
want: []string{}, // flag1 is unknown follow flag
|
point: -1,
|
||||||
|
want: []string{}, // flag1 is unknown follow flag
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
args: "sub1 -flag2 -",
|
line: "cmd sub1 -flag2 -",
|
||||||
want: []string{"-flag1", "-flag2", "-h", "-global1"},
|
point: -1,
|
||||||
|
want: []string{"-flag1", "-flag2", "-h", "-global1"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
args: "-no-such-flag",
|
line: "cmd -no-such-flag",
|
||||||
want: []string{},
|
point: -1,
|
||||||
|
want: []string{},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
args: "-no-such-flag ",
|
line: "cmd -no-such-flag ",
|
||||||
want: []string{"sub1", "sub2"},
|
point: -1,
|
||||||
|
want: []string{"sub1", "sub2"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
args: "-no-such-flag -",
|
line: "cmd -no-such-flag -",
|
||||||
want: []string{"-h", "-global1", "-o"},
|
point: -1,
|
||||||
|
want: []string{"-h", "-global1", "-o"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
args: "no-such-command",
|
line: "cmd no-such-command",
|
||||||
want: []string{},
|
point: -1,
|
||||||
|
want: []string{},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
args: "no-such-command ",
|
line: "cmd no-such-command ",
|
||||||
want: []string{"sub1", "sub2"},
|
point: -1,
|
||||||
|
want: []string{"sub1", "sub2"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
args: "-o ",
|
line: "cmd -o ",
|
||||||
want: []string{"a.txt", "b.txt", "c.txt", ".dot.txt", "./", "dir/", "outer/"},
|
point: -1,
|
||||||
|
want: []string{"a.txt", "b.txt", "c.txt", ".dot.txt", "./", "dir/", "outer/"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
args: "-o ./no-su",
|
line: "cmd -o ./no-su",
|
||||||
want: []string{},
|
point: -1,
|
||||||
|
want: []string{},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
args: "-o ./",
|
line: "cmd -o ./",
|
||||||
want: []string{"./a.txt", "./b.txt", "./c.txt", "./.dot.txt", "./", "./dir/", "./outer/"},
|
point: -1,
|
||||||
|
want: []string{"./a.txt", "./b.txt", "./c.txt", "./.dot.txt", "./", "./dir/", "./outer/"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
args: "-o=./",
|
line: "cmd -o=./",
|
||||||
want: []string{"./a.txt", "./b.txt", "./c.txt", "./.dot.txt", "./", "./dir/", "./outer/"},
|
point: -1,
|
||||||
|
want: []string{"./a.txt", "./b.txt", "./c.txt", "./.dot.txt", "./", "./dir/", "./outer/"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
args: "-o .",
|
line: "cmd -o .",
|
||||||
want: []string{"./a.txt", "./b.txt", "./c.txt", "./.dot.txt", "./", "./dir/", "./outer/"},
|
point: -1,
|
||||||
|
want: []string{"./a.txt", "./b.txt", "./c.txt", "./.dot.txt", "./", "./dir/", "./outer/"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
args: "-o ./b",
|
line: "cmd -o ./b",
|
||||||
want: []string{"./b.txt"},
|
point: -1,
|
||||||
|
want: []string{"./b.txt"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
args: "-o=./b",
|
line: "cmd -o=./b",
|
||||||
want: []string{"./b.txt"},
|
point: -1,
|
||||||
|
want: []string{"./b.txt"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
args: "-o ./read",
|
line: "cmd -o ./read",
|
||||||
want: []string{},
|
point: -1,
|
||||||
|
want: []string{},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
args: "-o=./read",
|
line: "cmd -o=./read",
|
||||||
want: []string{},
|
point: -1,
|
||||||
|
want: []string{},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
args: "-o ./readme.md",
|
line: "cmd -o ./readme.md",
|
||||||
want: []string{},
|
point: -1,
|
||||||
|
want: []string{},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
args: "-o ./readme.md ",
|
line: "cmd -o ./readme.md ",
|
||||||
want: []string{"sub1", "sub2"},
|
point: -1,
|
||||||
|
want: []string{"sub1", "sub2"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
args: "-o=./readme.md ",
|
line: "cmd -o=./readme.md ",
|
||||||
want: []string{"sub1", "sub2"},
|
point: -1,
|
||||||
|
want: []string{"sub1", "sub2"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
args: "-o sub2 -flag3 ",
|
line: "cmd -o sub2 -flag3 ",
|
||||||
want: []string{"opt1", "opt2", "opt12"},
|
point: -1,
|
||||||
|
want: []string{"opt1", "opt2", "opt12"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
args: "-o sub2 -flag3 opt1",
|
line: "cmd -o sub2 -flag3 opt1",
|
||||||
want: []string{"opt1", "opt12"},
|
point: -1,
|
||||||
|
want: []string{"opt1", "opt12"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
args: "-o sub2 -flag3 opt",
|
line: "cmd -o sub2 -flag3 opt",
|
||||||
want: []string{"opt1", "opt2", "opt12"},
|
point: -1,
|
||||||
|
want: []string{"opt1", "opt2", "opt12"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
line: "cmd -o ./b foo",
|
||||||
|
// ^
|
||||||
|
point: 10,
|
||||||
|
want: []string{"./b.txt"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
line: "cmd -o=./b foo",
|
||||||
|
// ^
|
||||||
|
point: 10,
|
||||||
|
want: []string{"./b.txt"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
line: "cmd -o sub2 -flag3 optfoo",
|
||||||
|
// ^
|
||||||
|
point: 22,
|
||||||
|
want: []string{"opt1", "opt2", "opt12"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
line: "cmd -o ",
|
||||||
|
// ^
|
||||||
|
point: 4,
|
||||||
|
want: []string{"sub1", "sub2"},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.args, func(t *testing.T) {
|
t.Run(fmt.Sprintf("%s@%d", tt.line, tt.point), func(t *testing.T) {
|
||||||
got := runComplete(cmp, tt.args)
|
got := runComplete(cmp, tt.line, tt.point)
|
||||||
|
|
||||||
sort.Strings(tt.want)
|
sort.Strings(tt.want)
|
||||||
sort.Strings(got)
|
sort.Strings(got)
|
||||||
|
@ -211,7 +275,6 @@ func TestCompleter_Complete(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCompleter_Complete_SharedPrefix(t *testing.T) {
|
func TestCompleter_Complete_SharedPrefix(t *testing.T) {
|
||||||
t.Parallel()
|
|
||||||
initTests()
|
initTests()
|
||||||
|
|
||||||
c := Command{
|
c := Command{
|
||||||
|
@ -243,42 +306,50 @@ func TestCompleter_Complete_SharedPrefix(t *testing.T) {
|
||||||
cmp := New("cmd", c)
|
cmp := New("cmd", c)
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
args string
|
line string
|
||||||
want []string
|
point int // -1 indicates len(line)
|
||||||
|
want []string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
args: "",
|
line: "cmd ",
|
||||||
want: []string{"status", "job"},
|
point: -1,
|
||||||
|
want: []string{"status", "job"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
args: "-",
|
line: "cmd -",
|
||||||
want: []string{"-h", "-global1", "-o"},
|
point: -1,
|
||||||
|
want: []string{"-h", "-global1", "-o"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
args: "j",
|
line: "cmd j",
|
||||||
want: []string{"job"},
|
point: -1,
|
||||||
|
want: []string{"job"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
args: "job ",
|
line: "cmd job ",
|
||||||
want: []string{"status"},
|
point: -1,
|
||||||
|
want: []string{"status"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
args: "job -",
|
line: "cmd job -",
|
||||||
want: []string{"-h", "-global1"},
|
point: -1,
|
||||||
|
want: []string{"-h", "-global1"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
args: "job status ",
|
line: "cmd job status ",
|
||||||
want: []string{},
|
point: -1,
|
||||||
|
want: []string{},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
args: "job status -",
|
line: "cmd job status -",
|
||||||
want: []string{"-f4", "-h", "-global1"},
|
point: -1,
|
||||||
|
want: []string{"-f4", "-h", "-global1"},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.args, func(t *testing.T) {
|
t.Run(tt.line, func(t *testing.T) {
|
||||||
got := runComplete(cmp, tt.args)
|
got := runComplete(cmp, tt.line, tt.point)
|
||||||
|
|
||||||
sort.Strings(tt.want)
|
sort.Strings(tt.want)
|
||||||
sort.Strings(got)
|
sort.Strings(got)
|
||||||
|
@ -293,8 +364,12 @@ func TestCompleter_Complete_SharedPrefix(t *testing.T) {
|
||||||
// runComplete runs the complete login for test purposes
|
// runComplete runs the complete login for test purposes
|
||||||
// it gets the complete struct and command line arguments and returns
|
// it gets the complete struct and command line arguments and returns
|
||||||
// the complete options
|
// the complete options
|
||||||
func runComplete(c *Complete, args string) (completions []string) {
|
func runComplete(c *Complete, line string, point int) (completions []string) {
|
||||||
os.Setenv(envComplete, "cmd "+args)
|
if point == -1 {
|
||||||
|
point = len(line)
|
||||||
|
}
|
||||||
|
os.Setenv(envLine, line)
|
||||||
|
os.Setenv(envPoint, strconv.Itoa(point))
|
||||||
b := bytes.NewBuffer(nil)
|
b := bytes.NewBuffer(nil)
|
||||||
c.Out = b
|
c.Out = b
|
||||||
c.Complete()
|
c.Complete()
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
module gocomplete
|
module gocomplete
|
||||||
|
|
||||||
require (
|
require github.com/posener/complete v1.1.2
|
||||||
github.com/hashicorp/go-multierror v1.0.0 // indirect
|
|
||||||
github.com/posener/complete v1.1.2
|
replace github.com/posener/complete v1.1.2 => ./..
|
||||||
)
|
|
||||||
|
|
Loading…
Reference in New Issue