2022-11-14 22:56:28 +01:00
|
|
|
package shparse
|
|
|
|
|
|
|
|
import (
|
2022-11-15 04:57:29 +01:00
|
|
|
"fmt"
|
2022-11-14 22:56:28 +01:00
|
|
|
"testing"
|
2022-11-19 04:05:03 +01:00
|
|
|
|
2023-07-26 23:24:14 +02:00
|
|
|
"github.com/commandlinedev/prompt-server/pkg/utilfn"
|
2022-11-14 22:56:28 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
// $(ls f[*]); ./x
|
2022-11-15 04:57:29 +01:00
|
|
|
// ls f => raw["ls f"] -> lit["ls f"] -> lit["ls"] lit["f"]
|
2022-11-14 22:56:28 +01:00
|
|
|
// w; ls foo; => raw["w; ls foo;"]
|
|
|
|
// ls&"ls" => raw["ls&ls"] => lit["ls&"] dq["ls"] => lit["ls"] key["&"] dq["ls"]
|
|
|
|
// ls $x; echo `ls f => raw["ls $x; echo `ls f"]
|
|
|
|
// > echo $foo{x,y}
|
|
|
|
|
|
|
|
func testParse(t *testing.T, s string) {
|
2022-11-15 09:36:30 +01:00
|
|
|
words := Tokenize(s)
|
2022-11-15 04:57:29 +01:00
|
|
|
|
2022-11-18 23:26:52 +01:00
|
|
|
fmt.Printf("parse <<\n%s\n>>\n", s)
|
|
|
|
dumpWords(words, " ", 8)
|
2022-11-15 09:39:53 +01:00
|
|
|
outStr := wordsToStr(words)
|
|
|
|
if outStr != s {
|
|
|
|
t.Errorf("tokenization output does not match input: %q => %q", s, outStr)
|
|
|
|
}
|
2022-11-18 23:26:52 +01:00
|
|
|
fmt.Printf("------\n\n")
|
2022-11-14 22:56:28 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func Test1(t *testing.T) {
|
|
|
|
testParse(t, "ls")
|
|
|
|
testParse(t, "ls 'foo'")
|
|
|
|
testParse(t, `ls "hello" $'\''`)
|
2022-11-15 04:57:29 +01:00
|
|
|
testParse(t, `ls "foo`)
|
|
|
|
testParse(t, `echo $11 $xyz $ `)
|
2022-11-15 19:27:36 +01:00
|
|
|
testParse(t, `echo $(ls ${x:"hello"} foo`)
|
|
|
|
testParse(t, `ls ${x:"hello"} $[2+2] $((5 * 10)) $(ls; ls&)`)
|
|
|
|
testParse(t, `ls;ls&./foo > out 2> "out2"`)
|
|
|
|
testParse(t, `(( x = 5)); ls& cd ~/work/"hello again"`)
|
2022-11-16 09:37:22 +01:00
|
|
|
testParse(t, `echo "hello"abc$(ls)$x${y:foo}`)
|
|
|
|
testParse(t, `echo $(ls; ./x "foo")`)
|
|
|
|
testParse(t, `echo $(ls; (cd foo; ls); (cd bar; ls))xyz`)
|
|
|
|
testParse(t, `echo "$x ${y:-foo}"`)
|
|
|
|
testParse(t, `command="$(echo "$input" | sed -e "s/^[ \t]*\([^ \t]*\)[ \t]*.*$/\1/g")"`)
|
|
|
|
testParse(t, `echo $(ls $)`)
|
|
|
|
testParse(t, `echo ${x:-hello\}"}"} 2nd`)
|
|
|
|
testParse(t, `echo "$(ls "foo") more $x"`)
|
2022-11-16 21:00:44 +01:00
|
|
|
testParse(t, "echo `ls $x \"hello $x\" \\`ls\\`; ./foo`")
|
|
|
|
testParse(t, `echo $"hello $x $(ls)"`)
|
2022-11-18 09:09:18 +01:00
|
|
|
testParse(t, "echo 'hello'\nls\n")
|
2022-11-18 23:26:52 +01:00
|
|
|
testParse(t, "echo 'hello'abc$'\a'")
|
2022-11-14 22:56:28 +01:00
|
|
|
}
|
2022-11-17 08:52:10 +01:00
|
|
|
|
2022-11-18 09:09:18 +01:00
|
|
|
func lastWord(words []*WordType) *WordType {
|
2022-11-17 08:52:10 +01:00
|
|
|
if len(words) == 0 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return words[len(words)-1]
|
|
|
|
}
|
|
|
|
|
2022-11-22 04:06:59 +01:00
|
|
|
func testExtend(t *testing.T, startStr string, extendStr string, complete bool, expStr string) {
|
|
|
|
startSP := utilfn.ParseToSP(startStr)
|
|
|
|
words := Tokenize(startSP.Str)
|
|
|
|
word := findCompletionWordAtPos(words, startSP.Pos, true)
|
|
|
|
if word == nil {
|
|
|
|
word = MakeEmptyWord(WordTypeLit, nil, startSP.Pos, true)
|
2022-11-17 08:52:10 +01:00
|
|
|
}
|
2022-11-22 04:06:59 +01:00
|
|
|
outSP := Extend(word, startSP.Pos-word.Offset, extendStr, complete)
|
|
|
|
expSP := utilfn.ParseToSP(expStr)
|
2022-11-22 08:06:58 +01:00
|
|
|
fmt.Printf("extend: [%s] + %q => [%s]\n", startStr, extendStr, outSP)
|
2022-11-22 04:06:59 +01:00
|
|
|
if outSP != expSP {
|
2022-11-22 08:06:58 +01:00
|
|
|
t.Errorf("extension does not match: [%s] + %q => [%s] expected [%s]\n", startStr, extendStr, outSP, expSP)
|
2022-11-17 08:52:10 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func Test2(t *testing.T) {
|
2022-11-22 04:06:59 +01:00
|
|
|
testExtend(t, `he[*]`, "llo", false, "hello[*]")
|
|
|
|
testExtend(t, `he[*]`, "llo", true, "hello [*]")
|
|
|
|
testExtend(t, `'mi[*]e`, "k", false, "'mik[*]e")
|
|
|
|
testExtend(t, `'mi[*]e`, "k", true, "'mik[*]e")
|
|
|
|
testExtend(t, `'mi[*]'`, "ke", true, "'mike' [*]")
|
|
|
|
testExtend(t, `'mi'[*]`, "ke", true, "'mike' [*]")
|
|
|
|
testExtend(t, `'mi[*]'`, "ke", false, "'mike[*]'")
|
|
|
|
testExtend(t, `'mi'[*]`, "ke", false, "'mike[*]'")
|
|
|
|
testExtend(t, `$f[*]`, "oo", false, "$foo[*]")
|
|
|
|
testExtend(t, `${f}[*]`, "oo", false, "${foo[*]}")
|
|
|
|
testExtend(t, `${f[*]}`, "oo", true, "${foo} [*]")
|
2022-11-22 08:06:58 +01:00
|
|
|
testExtend(t, `[*]`, "more stuff", false, `more\ stuff[*]`)
|
|
|
|
testExtend(t, `[*]`, "hello\amike", false, `hello$'\a'mike[*]`)
|
|
|
|
testExtend(t, `$'he[*]'`, "\x01\x02\x0a", true, `$'he\x01\x02\n' [*]`)
|
|
|
|
testExtend(t, `${x}\ [*]ll$y`, "e", false, `${x}\ e[*]ll$y`)
|
|
|
|
testExtend(t, `"he[*]"`, "$$o", true, `"he\$\$o" [*]`)
|
|
|
|
testExtend(t, `"h[*]llo"`, "e", false, `"he[*]llo"`)
|
|
|
|
testExtend(t, `"h[*]llo"`, "e", true, `"he[*]llo"`)
|
|
|
|
testExtend(t, `"[*]${h}llo"`, "e\x01", true, `"e"$'\x01'[*]"${h}llo"`)
|
|
|
|
testExtend(t, `"${h}llo[*]"`, "e\x01", true, `"${h}lloe"$'\x01' [*]`)
|
|
|
|
testExtend(t, `"${h}llo[*]"`, "e\x01", false, `"${h}lloe"$'\x01'[*]`)
|
|
|
|
testExtend(t, `"${h}ll[*]o"`, "e\x01", false, `"${h}lle"$'\x01'[*]"o"`)
|
|
|
|
testExtend(t, `"ab[*]c${x}def"`, "\x01", false, `"ab"$'\x01'[*]"c${x}def"`)
|
|
|
|
testExtend(t, `'ab[*]ef'`, "\x01", false, `'ab'$'\x01'[*]'ef'`)
|
2022-11-22 04:06:59 +01:00
|
|
|
|
|
|
|
// testExtend(t, `'he'`, "llo", `'hello'`)
|
|
|
|
// testExtend(t, `'he'`, "'", `'he'\'''`)
|
|
|
|
// testExtend(t, `'he'`, "'\x01", `'he'\'$'\x01'''`)
|
|
|
|
// testExtend(t, `he`, "llo", `hello`)
|
|
|
|
// testExtend(t, `he`, "l*l'\x01\x07o", `hel\*l\'$'\x01'$'\a'o`)
|
|
|
|
// testExtend(t, `$x`, "fo|o", `$xfoo`)
|
|
|
|
// testExtend(t, `${x`, "fo|o", `${xfoo`)
|
|
|
|
// testExtend(t, `$'f`, "oo", `$'foo`)
|
|
|
|
// testExtend(t, `$'f`, "'\x01\x07o", `$'f\'\x01\ao`)
|
|
|
|
// testExtend(t, `"f"`, "oo", `"foo"`)
|
|
|
|
// testExtend(t, `"mi"`, "ke's \"hello\"", `"mike's \"hello\""`)
|
|
|
|
// testExtend(t, `"t"`, "t\x01\x07", `"tt"$'\x01'$'\a'""`)
|
2022-11-18 09:09:18 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func testParseCommands(t *testing.T, str string) {
|
|
|
|
fmt.Printf("parse: %q\n", str)
|
|
|
|
words := Tokenize(str)
|
|
|
|
cmds := ParseCommands(words)
|
2022-11-19 01:16:31 +01:00
|
|
|
dumpCommands(cmds, " ", -1)
|
2022-11-18 09:09:18 +01:00
|
|
|
fmt.Printf("\n")
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestCmd(t *testing.T) {
|
|
|
|
testParseCommands(t, "ls foo")
|
2022-11-18 23:26:52 +01:00
|
|
|
testParseCommands(t, "function foo () { echo hello; }")
|
2022-11-18 09:09:18 +01:00
|
|
|
testParseCommands(t, "ls foo && ls bar; ./run $x hello | xargs foo; ")
|
|
|
|
testParseCommands(t, "if [[ 2 > 1 ]]; then echo hello\nelse echo world; echo next; done")
|
|
|
|
testParseCommands(t, "case lots of stuff; i don\\'t know how to parse; esac; ls foo")
|
2022-11-18 23:57:25 +01:00
|
|
|
testParseCommands(t, "(ls & ./x \n \n); for x in $vars 3; do { echo $x; ls foo ; } done")
|
2022-11-18 23:26:52 +01:00
|
|
|
testParseCommands(t, `ls f"oo" "${x:"hello$y"}"`)
|
|
|
|
testParseCommands(t, `x="foo $y" z=10 ls`)
|
2022-11-17 08:52:10 +01:00
|
|
|
}
|
2022-11-19 04:05:03 +01:00
|
|
|
|
2022-11-22 09:26:41 +01:00
|
|
|
func testCompPos(t *testing.T, cmdStr string, compType string, hasCommand bool, cmdWordPos int, hasWord bool, superOffset int) {
|
2022-11-19 04:05:03 +01:00
|
|
|
cmdSP := utilfn.ParseToSP(cmdStr)
|
|
|
|
words := Tokenize(cmdSP.Str)
|
|
|
|
cmds := ParseCommands(words)
|
2022-11-23 23:34:49 +01:00
|
|
|
cpos := FindCompletionPos(cmds, cmdSP.Pos)
|
2022-11-21 21:55:53 +01:00
|
|
|
fmt.Printf("testCompPos [%d] %q => [%s] %v\n", cmdSP.Pos, cmdStr, cpos.CompType, cpos)
|
|
|
|
if cpos.CompType != compType {
|
|
|
|
t.Errorf("testCompPos %q => invalid comp-type %q, expected %q", cmdStr, cpos.CompType, compType)
|
|
|
|
}
|
|
|
|
if cpos.CompWord != nil {
|
|
|
|
fmt.Printf(" found-word: %d %s\n", cpos.CompWordOffset, cpos.CompWord.stringWithPos(cpos.CompWordOffset))
|
2022-11-19 04:05:03 +01:00
|
|
|
}
|
|
|
|
if cpos.Cmd != nil {
|
|
|
|
fmt.Printf(" found-cmd: ")
|
|
|
|
dumpCommands([]*CmdType{cpos.Cmd}, " ", cpos.RawPos)
|
|
|
|
}
|
|
|
|
dumpCommands(cmds, " ", cmdSP.Pos)
|
|
|
|
fmt.Printf("\n")
|
|
|
|
if cpos.RawPos+cpos.SuperOffset != cmdSP.Pos {
|
|
|
|
t.Errorf("testCompPos %q => bad rawpos:%d superoffset:%d expected:%d", cmdStr, cpos.RawPos, cpos.SuperOffset, cmdSP.Pos)
|
|
|
|
}
|
|
|
|
if (cpos.Cmd != nil) != hasCommand {
|
|
|
|
t.Errorf("testCompPos %q => bad has-command exp:%v", cmdStr, hasCommand)
|
|
|
|
}
|
2022-11-21 21:55:53 +01:00
|
|
|
if (cpos.CompWord != nil) != hasWord {
|
2022-11-19 04:05:03 +01:00
|
|
|
t.Errorf("testCompPos %q => bad has-word exp:%v", cmdStr, hasWord)
|
|
|
|
}
|
|
|
|
if cpos.CmdWordPos != cmdWordPos {
|
|
|
|
t.Errorf("testCompPos %q => bad cmd-word-pos got:%d exp:%d", cmdStr, cpos.CmdWordPos, cmdWordPos)
|
|
|
|
}
|
2022-11-22 09:26:41 +01:00
|
|
|
if cpos.SuperOffset != superOffset {
|
|
|
|
t.Errorf("testCompPos %q => bad super-offset got:%d exp:%d", cmdStr, cpos.SuperOffset, superOffset)
|
|
|
|
}
|
2022-11-19 04:05:03 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestCompPos(t *testing.T) {
|
2022-11-22 09:26:41 +01:00
|
|
|
testCompPos(t, "ls [*]foo", CompTypeArg, true, 1, false, 0)
|
|
|
|
testCompPos(t, "ls foo [*];", CompTypeArg, true, 2, false, 0)
|
|
|
|
testCompPos(t, "ls foo ;[*]", CompTypeCommand, false, 0, false, 0)
|
|
|
|
testCompPos(t, "ls foo >[*]> ./bar", CompTypeInvalid, true, 2, true, 0)
|
|
|
|
testCompPos(t, "l[*]s", CompTypeCommand, true, 0, true, 0)
|
|
|
|
testCompPos(t, "ls[*]", CompTypeCommand, true, 0, true, 0)
|
|
|
|
testCompPos(t, "x=10 { (ls ./f[*] more); ls }", CompTypeArg, true, 1, true, 0)
|
|
|
|
testCompPos(t, "for x in 1[*] 2 3; do ", CompTypeBasic, false, 0, true, 0)
|
|
|
|
testCompPos(t, "for[*] x in 1 2 3;", CompTypeInvalid, false, 0, true, 0)
|
|
|
|
testCompPos(t, `ls "abc $(ls -l t[*])" && foo`, CompTypeArg, true, 2, true, 10)
|
|
|
|
testCompPos(t, "ls ${abc:$(ls -l [*])}", CompTypeVar, false, 0, true, 0) // we don't sub-parse inside of ${} (so this returns "var" right now)
|
|
|
|
testCompPos(t, `ls abc"$(ls $"echo $(ls ./[*]x) foo)" `, CompTypeArg, true, 1, true, 21)
|
|
|
|
testCompPos(t, `ls "abc$d[*]"`, CompTypeVar, false, 0, true, 4)
|
|
|
|
testCompPos(t, `ls "abc$d$'a[*]`, CompTypeArg, true, 1, true, 0)
|
|
|
|
testCompPos(t, `ls $[*]'foo`, CompTypeArg, true, 1, true, 0)
|
|
|
|
testCompPos(t, `echo $TE[*]`, CompTypeVar, false, 0, true, 0)
|
2022-11-19 04:05:03 +01:00
|
|
|
}
|
2022-11-19 23:05:38 +01:00
|
|
|
|
|
|
|
func testExpand(t *testing.T, str string, pos int, expStr string, expInfo *ExpandInfo) {
|
|
|
|
ectx := ExpandContext{HomeDir: "/Users/mike"}
|
|
|
|
words := Tokenize(str)
|
|
|
|
if len(words) == 0 {
|
|
|
|
t.Errorf("could not tokenize any words from %q", str)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
word := words[0]
|
|
|
|
output, info := SimpleExpandPrefix(ectx, word, pos)
|
|
|
|
if output != expStr {
|
|
|
|
t.Errorf("error expanding %q, output:%q exp:%q", str, output, expStr)
|
|
|
|
} else {
|
|
|
|
fmt.Printf("expand: %q (%d) => %q\n", str, pos, output)
|
|
|
|
}
|
|
|
|
if expInfo != nil {
|
|
|
|
if info != *expInfo {
|
|
|
|
t.Errorf("error expanding %q, info:%v exp:%v", str, info, expInfo)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestExpand(t *testing.T) {
|
|
|
|
testExpand(t, "hello", 3, "hel", nil)
|
|
|
|
testExpand(t, "he\\$xabc", 6, "he$xa", nil)
|
2022-11-21 21:55:53 +01:00
|
|
|
testExpand(t, "he${x}abc", 6, "he${x}", nil)
|
2022-11-19 23:05:38 +01:00
|
|
|
testExpand(t, "'hello\"mike'", 8, "hello\"m", nil)
|
|
|
|
testExpand(t, `$'abc\x01def`, 10, "abc\x01d", nil)
|
|
|
|
testExpand(t, `$((2 + 2))`, 6, "$((2 +", &ExpandInfo{HasSpecial: true})
|
|
|
|
testExpand(t, `abc"def"`, 6, "abcde", nil)
|
2022-11-21 21:55:53 +01:00
|
|
|
testExpand(t, `"abc$x$'"'""`, 12, "abc$x\"", nil)
|
|
|
|
testExpand(t, `'he'\''s'`, 9, "he's", nil)
|
2022-11-19 23:05:38 +01:00
|
|
|
}
|