mirror of
https://github.com/wavetermdev/inshellisense.git
synced 2026-08-05 13:43:30 -07:00
feat: implement initial terminal command parsing
Signed-off-by: cpendery <cpendery@vt.edu>
This commit is contained in:
@@ -1,17 +1,29 @@
|
||||
package autocomplete
|
||||
|
||||
import "strings"
|
||||
import (
|
||||
"log"
|
||||
"regexp"
|
||||
|
||||
"github.com/cpendery/clac/autocomplete/specs"
|
||||
)
|
||||
|
||||
type Suggestion struct {
|
||||
Name string
|
||||
Description string
|
||||
}
|
||||
|
||||
var (
|
||||
cmdDelimiter = regexp.MustCompile(`(\|\|)|(&&)|(;)`)
|
||||
)
|
||||
|
||||
func LoadSuggestions(cmd string) []Suggestion {
|
||||
params := strings.Split(cmd, " ")
|
||||
lastParam := params[len(params)-1]
|
||||
if lastParam == "git" {
|
||||
return []Suggestion{{"go", "the only way to go"}, {"get", "the right way to get"}, {"gone", "here today, bye tomorrow"}}
|
||||
activeCmd := ParseCommand(cmd)
|
||||
if len(activeCmd) <= 0 {
|
||||
return []Suggestion{}
|
||||
}
|
||||
log.Println(activeCmd, activeCmd[0].token)
|
||||
if spec, ok := specs.Specs[activeCmd[0].token]; ok {
|
||||
log.Println(spec.Description)
|
||||
}
|
||||
return []Suggestion{}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import * as fsAsync from "fs/promises";
|
||||
import * as fs from "fs";
|
||||
import * as path from "path";
|
||||
import * as process from "process";
|
||||
import * as child_process from "child_process";
|
||||
|
||||
const main = async () => {
|
||||
const basePath = path.join(process.cwd(), ".fig");
|
||||
@@ -28,12 +29,73 @@ const main = async () => {
|
||||
return;
|
||||
}
|
||||
|
||||
const filenameWithoutExtension = path.parse(directoryItem.name).name;
|
||||
const subcommand = spec as unknown as Fig.Subcommand;
|
||||
console.log(subcommand?.description);
|
||||
const generatedCode = generateGolang(
|
||||
subcommand,
|
||||
filenameWithoutExtension
|
||||
);
|
||||
await fsAsync.writeFile(
|
||||
path.join(
|
||||
process.cwd(),
|
||||
"..",
|
||||
"specs",
|
||||
`${filenameWithoutExtension}.go`
|
||||
),
|
||||
generatedCode
|
||||
);
|
||||
})
|
||||
);
|
||||
|
||||
const generatedFilesPath = path.join(process.cwd(), "..", "specs");
|
||||
child_process.exec(`gofmt -w ${generatedFilesPath}`);
|
||||
};
|
||||
|
||||
const generateGolang = (subcommand: Fig.Subcommand) => {};
|
||||
const generateArgs = (args: Fig.SingleOrArray<Fig.Arg>): string => {
|
||||
return "";
|
||||
};
|
||||
|
||||
const generateOptions = (options: Fig.Option[]): string => {
|
||||
return "";
|
||||
};
|
||||
|
||||
const generateSubcommand = (subcommand: Fig.Subcommand): string => {
|
||||
const name = Array.isArray(subcommand.name)
|
||||
? `Name: []string{${subcommand.name.map((n) => `"${n}"`).join(",")}},`
|
||||
: `Name: "${subcommand.name}",`;
|
||||
|
||||
const description =
|
||||
subcommand.description != null
|
||||
? `Description: "${subcommand.description}",`
|
||||
: "";
|
||||
|
||||
const args = subcommand.args != null ? generateArgs(subcommand.args) : "";
|
||||
const options =
|
||||
subcommand.options != null ? generateOptions(subcommand.options) : "";
|
||||
|
||||
return `model.Subcommand{
|
||||
${name}
|
||||
${description}
|
||||
${args}
|
||||
${options}
|
||||
}`.replaceAll(/\s/g, "");
|
||||
};
|
||||
|
||||
const generateGolang = (
|
||||
subcommand: Fig.Subcommand,
|
||||
filenameWithoutExtension: string
|
||||
) => {
|
||||
return `// Code generated by autocomplete/extract/extract.ts. DO NOT EDIT.
|
||||
|
||||
package specs
|
||||
|
||||
import (
|
||||
"github.com/cpendery/clac/autocomplete/model"
|
||||
)
|
||||
|
||||
func init() {
|
||||
Specs["${filenameWithoutExtension}"] = ${generateSubcommand(subcommand)}
|
||||
}`;
|
||||
};
|
||||
|
||||
main();
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
package model
|
||||
|
||||
type Subcommand struct {
|
||||
Name interface{} //single or array string, required
|
||||
Description string
|
||||
Args interface{} //single or array Arg, optional
|
||||
Options []Option
|
||||
}
|
||||
|
||||
type Option struct {
|
||||
Name interface{} //single or array string, required
|
||||
Args interface{} //single or array Arg, optional
|
||||
Description string //single, optional
|
||||
}
|
||||
|
||||
type Arg struct {
|
||||
Name string //single, optional
|
||||
Description string //single, optional
|
||||
Template Template //single, optional
|
||||
}
|
||||
|
||||
type Template string
|
||||
|
||||
const (
|
||||
TemplateFilepaths Template = "filepaths"
|
||||
TemplateFolders Template = "folders"
|
||||
TemplateHistory Template = "history"
|
||||
)
|
||||
|
||||
var (
|
||||
Templates = []Template{TemplateFilepaths, TemplateFolders, TemplateHistory}
|
||||
)
|
||||
@@ -0,0 +1,62 @@
|
||||
package autocomplete
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
var (
|
||||
quoteRegex = regexp.MustCompile(`['"]`)
|
||||
)
|
||||
|
||||
type commandToken struct {
|
||||
token string
|
||||
complete bool
|
||||
}
|
||||
|
||||
func ParseCommand(cmd string) []commandToken {
|
||||
commands := cmdDelimiter.Split(cmd, -1)
|
||||
command := commands[len(commands)-1]
|
||||
cleanCommand := strings.TrimLeftFunc(command, unicode.IsSpace)
|
||||
return parse([]rune(cleanCommand))
|
||||
}
|
||||
|
||||
func parse(cmd []rune) []commandToken {
|
||||
results := []commandToken{}
|
||||
readingQuotedStr := false
|
||||
readingFlag := false
|
||||
readingCmd := false
|
||||
readingIdx := 0
|
||||
var reading bool
|
||||
|
||||
for idx, r := range cmd {
|
||||
reading = readingQuotedStr || readingFlag || readingCmd
|
||||
if !reading && quoteRegex.MatchString(string(r)) {
|
||||
readingQuotedStr, readingIdx = true, idx
|
||||
continue
|
||||
} else if !reading && r == '-' {
|
||||
readingFlag, readingIdx = true, idx
|
||||
continue
|
||||
} else if !reading && !unicode.IsSpace(r) {
|
||||
readingCmd, readingIdx = true, idx
|
||||
continue
|
||||
}
|
||||
if readingQuotedStr && quoteRegex.MatchString(string(r)) {
|
||||
readingQuotedStr = false
|
||||
complete := idx+1 < len(cmd) && unicode.IsSpace(cmd[idx+1])
|
||||
results = append(results, commandToken{complete: complete, token: string(cmd[readingIdx : idx+1])})
|
||||
} else if readingFlag && (unicode.IsSpace(r) || r == '=') {
|
||||
readingFlag = false
|
||||
results = append(results, commandToken{complete: true, token: string(cmd[readingIdx:idx])})
|
||||
} else if readingCmd && unicode.IsSpace(r) {
|
||||
readingCmd = false
|
||||
results = append(results, commandToken{complete: true, token: string(cmd[readingIdx:idx])})
|
||||
}
|
||||
}
|
||||
reading = readingQuotedStr || readingFlag || readingCmd
|
||||
if reading {
|
||||
results = append(results, commandToken{complete: false, token: string(cmd[readingIdx:])})
|
||||
}
|
||||
return results
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package autocomplete
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestParseCommand(t *testing.T) {
|
||||
tests := []struct {
|
||||
cmd string
|
||||
expected []commandToken
|
||||
}{
|
||||
{"cmd --flag value ", []commandToken{{"cmd", true}, {"--flag", true}, {"value", true}}},
|
||||
{"cmd --flag 'value' ", []commandToken{{"cmd", true}, {"--flag", true}, {"'value'", true}}},
|
||||
{"cmd --flag=value ", []commandToken{{"cmd", true}, {"--flag", true}, {"value", true}}},
|
||||
{"cmd --flag='value' ", []commandToken{{"cmd", true}, {"--flag", true}, {"'value'", true}}},
|
||||
{"cmd 'value' ", []commandToken{{"cmd", true}, {"'value'", true}}},
|
||||
{"cmd value ", []commandToken{{"cmd", true}, {"value", true}}},
|
||||
{"cmd -f", []commandToken{{"cmd", true}, {"-f", false}}},
|
||||
{"cmd -f=value ", []commandToken{{"cmd", true}, {"-f", true}, {"value", true}}},
|
||||
{"cmd -f value ", []commandToken{{"cmd", true}, {"-f", true}, {"value", true}}},
|
||||
{"cmd -f 'value' ", []commandToken{{"cmd", true}, {"-f", true}, {"'value'", true}}},
|
||||
{"cmd -f='value' ", []commandToken{{"cmd", true}, {"-f", true}, {"'value'", true}}},
|
||||
{"cmd -f='val", []commandToken{{"cmd", true}, {"-f", true}, {"'val", false}}},
|
||||
{"cmd -f", []commandToken{{"cmd", true}, {"-f", false}}},
|
||||
{"cmd -f=", []commandToken{{"cmd", true}, {"-f", true}}},
|
||||
{"cmd -f ", []commandToken{{"cmd", true}, {"-f", true}}},
|
||||
{"cmd", []commandToken{{"cmd", false}}},
|
||||
{"cmd ", []commandToken{{"cmd", true}}},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.cmd, func(tc *testing.T) {
|
||||
result := ParseCommand(test.cmd)
|
||||
assert.Equal(tc, test.expected, result)
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// Code generated by autocomplete/extract/extract.ts. DO NOT EDIT.
|
||||
|
||||
package specs
|
||||
|
||||
import (
|
||||
"github.com/cpendery/clac/autocomplete/model"
|
||||
)
|
||||
|
||||
func init() {
|
||||
Specs["afplay"] = model.Subcommand{Name: "afplay", Description: "AudioFilePlay"}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package specs
|
||||
|
||||
import "github.com/cpendery/clac/autocomplete/model"
|
||||
|
||||
var (
|
||||
Specs = make(map[string]model.Subcommand)
|
||||
)
|
||||
@@ -8,6 +8,7 @@ require (
|
||||
github.com/charmbracelet/lipgloss v0.8.0
|
||||
github.com/mattn/go-runewidth v0.0.14
|
||||
github.com/muesli/reflow v0.3.0
|
||||
github.com/stretchr/testify v1.8.4
|
||||
golang.org/x/term v0.6.0
|
||||
)
|
||||
|
||||
@@ -15,14 +16,17 @@ require (
|
||||
github.com/atotto/clipboard v0.1.4 // indirect
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.18 // indirect
|
||||
github.com/mattn/go-localereader v0.0.1 // indirect
|
||||
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b // indirect
|
||||
github.com/muesli/cancelreader v0.2.2 // indirect
|
||||
github.com/muesli/termenv v0.15.2 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
golang.org/x/sync v0.1.0 // indirect
|
||||
golang.org/x/sys v0.7.0 // indirect
|
||||
golang.org/x/text v0.3.8 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
@@ -10,6 +10,8 @@ github.com/charmbracelet/lipgloss v0.8.0 h1:IS00fk4XAHcf8uZKc3eHeMUTCxUH6NkaTrdy
|
||||
github.com/charmbracelet/lipgloss v0.8.0/go.mod h1:p4eYUZZJ/0oXTuCQKFF8mqyKCz0ja6y+7DniDDw5KKU=
|
||||
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 h1:q2hJAaP1k2wIvVRd/hEHD7lacgqrCPS+k8g1MndzfWY=
|
||||
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98=
|
||||
@@ -27,9 +29,13 @@ github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
|
||||
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
|
||||
github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo=
|
||||
github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
@@ -40,3 +46,7 @@ golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw=
|
||||
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
||||
golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY=
|
||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
||||
Reference in New Issue
Block a user