Skip to content

Commit

Permalink
chore: autocomplete bash and zsh (#93)
Browse files Browse the repository at this point in the history
* chore: Fix autocomplete

Signed-off-by: Ce Gao <cegao@tensorchord.ai>

* fix: Update

Signed-off-by: Ce Gao <cegao@tensorchord.ai>
  • Loading branch information
gaocegege authored May 1, 2022
1 parent 3d3677e commit b0d47fe
Show file tree
Hide file tree
Showing 6 changed files with 209 additions and 167 deletions.
161 changes: 3 additions & 158 deletions cmd/midi/bootstrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,12 @@
package main

import (
"fmt"
"os"
"os/user"
"path/filepath"
"runtime"
"strings"

"github.com/cockroachdb/errors"
"github.com/containerd/containerd/log"
"github.com/sirupsen/logrus"
cli "github.com/urfave/cli/v2"

ac "github.com/tensorchord/MIDI/pkg/autocomplete"
"github.com/tensorchord/MIDI/pkg/buildkitd"
"github.com/tensorchord/MIDI/pkg/util/fileutil"
)

var CommandBootstrap = &cli.Command{
Expand All @@ -55,12 +47,12 @@ func bootstrap(clicontext *cli.Context) error {
autocomplete := clicontext.Bool("with-autocomplete")
if autocomplete {
// Because this requires sudo, it should warn and not fail the rest of it.
err := insertBashCompleteEntry()
err := ac.InsertBashCompleteEntry()
if err != nil {
logrus.Warnf("Warning: %s\n", err.Error())
err = nil
}
err = insertZSHCompleteEntry()
err = ac.InsertZSHCompleteEntry()
if err != nil {
logrus.Warnf("Warning: %s\n", err.Error())
err = nil
Expand All @@ -82,150 +74,3 @@ func bootstrap(clicontext *cli.Context) error {
}
return nil
}

// If debugging this, it might be required to run `rm ~/.zcompdump*` to remove the cache
func insertZSHCompleteEntry() error {
// should be the same on linux and macOS
path := "/usr/local/share/zsh/site-functions/_midi"
dirPath := filepath.Dir(path)

dirPathExists, err := fileutil.DirExists(dirPath)
if err != nil {
return errors.Wrapf(err, "failed to check if %s exists", dirPath)
}
if !dirPathExists {
log.L.Warnf("Warning: unable to enable zsh-completion: %s does not exist", dirPath)
return nil // zsh-completion isn't available, silently fail.
}

pathExists, err := fileutil.FileExists(path)
if err != nil {
return errors.Wrapf(err, "failed to check if %s exists", path)
}
if pathExists {
return nil // file already exists, don't update it.
}

// create the completion file
f, err := os.Create(path)
if err != nil {
return err
}
defer f.Close()

compEntry, err := zshCompleteEntry()
if err != nil {
fmt.Fprintf(os.Stderr, "Warning: unable to enable zsh-completion: %s\n", err)
return nil // zsh-completion isn't available, silently fail.
}

_, err = f.Write([]byte(compEntry))
if err != nil {
return errors.Wrapf(err, "failed writing to %s", path)
}

return deleteZcompdump()
}

func zshCompleteEntry() (string, error) {
template := `#compdef _midi midi
function _midi {
autoload -Uz bashcompinit
bashcompinit
complete -o nospace -C '__midi__' midi
}
`
return renderEntryTemplate(template)
}

func insertBashCompleteEntry() error {
var path string
if runtime.GOOS == "darwin" {
path = "/usr/local/etc/bash_completion.d/midi"
} else {
path = "/usr/share/bash-completion/completions/midi"
}
dirPath := filepath.Dir(path)

dirPathExists, err := fileutil.DirExists(dirPath)
if err != nil {
return errors.Wrapf(err, "failed checking if %s exists", dirPath)
}
if !dirPathExists {
fmt.Fprintf(os.Stderr, "Warning: unable to enable bash-completion: %s does not exist\n", dirPath)
return nil // bash-completion isn't available, silently fail.
}

pathExists, err := fileutil.FileExists(path)
if err != nil {
return errors.Wrapf(err, "failed checking if %s exists", path)
}
if pathExists {
return nil // file already exists, don't update it.
}

// create the completion file
f, err := os.Create(path)
if err != nil {
return err
}
defer f.Close()

bashEntry, err := bashCompleteEntry()
if err != nil {
fmt.Fprintf(os.Stderr, "Warning: unable to enable bash-completion: %s\n", err)
return nil // bash-completion isn't available, silently fail.
}

_, err = f.Write([]byte(bashEntry))
if err != nil {
return errors.Wrapf(err, "failed writing to %s", path)
}
return nil
}

func bashCompleteEntry() (string, error) {
template := "complete -o nospace -C '__midi__' midi\n"
return renderEntryTemplate(template)
}

func renderEntryTemplate(template string) (string, error) {
midiPath, err := os.Executable()
if err != nil {
return "", errors.Wrapf(err, "failed to determine midi path: %s", err)
}
return strings.ReplaceAll(template, "__midi__", midiPath), nil
}

func deleteZcompdump() error {
var homeDir string
sudoUser, found := os.LookupEnv("SUDO_USER")
if !found {
var err error
homeDir, err = os.UserHomeDir()
if err != nil {
return errors.Wrapf(err, "failed to lookup current user home dir")
}
} else {
currentUser, err := user.Lookup(sudoUser)
if err != nil {
return errors.Wrapf(err, "failed to lookup user %s", sudoUser)
}
homeDir = currentUser.HomeDir
}
files, err := os.ReadDir(homeDir)
if err != nil {
return errors.Wrapf(err, "failed to read dir %s", homeDir)
}
for _, f := range files {
if strings.HasPrefix(f.Name(), ".zcompdump") {
path := filepath.Join(homeDir, f.Name())
err := os.Remove(path)
if err != nil {
return errors.Wrapf(err, "failed to remove %s", path)
}
}
}
return nil
}
1 change: 1 addition & 0 deletions cmd/midi/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ func main() {

// TODO(gaocegege): Enclose the app, maybe create the struct MIDIApp.
app := cli.NewApp()
app.EnableBashCompletion = true
app.Name = "midi"
app.Usage = "Build tools for data scientists"
app.Version = version.Version
Expand Down
2 changes: 0 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -82,12 +82,10 @@ require (
go.opentelemetry.io/otel/sdk v1.4.1 // indirect
go.opentelemetry.io/otel/trace v1.4.1 // indirect
go.opentelemetry.io/proto/otlp v0.12.0 // indirect
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect
golang.org/x/net v0.0.0-20220225172249-27dd8689420f // indirect
golang.org/x/sys v0.0.0-20220422013727-9388b58f7150 // indirect
golang.org/x/text v0.3.7 // indirect
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect
golang.org/x/tools v0.1.10 // indirect
golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f // indirect
google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa // indirect
google.golang.org/grpc v1.45.0 // indirect
Expand Down
7 changes: 0 additions & 7 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -600,8 +600,6 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 h1:kQgndtyPBW/JIYERgdxfwMYh3AVStj88WQTlNDi2a+o=
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
Expand Down Expand Up @@ -735,8 +733,6 @@ golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8 h1:OH54vjqzRWmbJ62fjuhxy7AxFFgoHN0/DPc/UrL8cAs=
golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220422013727-9388b58f7150 h1:xHms4gcpe1YE7A3yIllJXP16CMAGuqwO2lX1mTyyRRc=
golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
Expand Down Expand Up @@ -814,12 +810,9 @@ golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4f
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.10 h1:QjFRCZxdOhBJ/UNgnBZLbNV13DlbnK0quyivTnXJM20=
golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f h1:GGU+dLjvlC3qDwqYgL6UgRmHXhOOgns0bZu2Ty5mm6U=
golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
Expand Down
86 changes: 86 additions & 0 deletions pkg/autocomplete/bash.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package autocomplete

import (
"fmt"
"os"
"path/filepath"
"runtime"

"github.com/cockroachdb/errors"

"github.com/tensorchord/MIDI/pkg/util/fileutil"
)

var autocompleteBASH = `
#! /bin/bash
$PROG=midi
: ${PROG:=$(basename ${BASH_SOURCE})}
_cli_bash_autocomplete() {
if [[ "${COMP_WORDS[0]}" != "source" ]]; then
local cur opts base
COMPREPLY=()
cur="${COMP_WORDS[COMP_CWORD]}"
if [[ "$cur" == "-"* ]]; then
opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} ${cur} --generate-bash-completion )
else
opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} --generate-bash-completion )
fi
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
return 0
fi
}
complete -o bashdefault -o default -o nospace -F _cli_bash_autocomplete $PROG
unset PROG
`

func InsertBashCompleteEntry() error {
var path string
if runtime.GOOS == "darwin" {
path = "/usr/local/etc/bash_completion.d/midi"
} else {
path = "/usr/share/bash-completion/completions/midi"
}
dirPath := filepath.Dir(path)

dirPathExists, err := fileutil.DirExists(dirPath)
if err != nil {
return errors.Wrapf(err, "failed checking if %s exists", dirPath)
}
if !dirPathExists {
fmt.Fprintf(os.Stderr, "Warning: unable to enable bash-completion: %s does not exist\n", dirPath)
return nil // bash-completion isn't available, silently fail.
}

pathExists, err := fileutil.FileExists(path)
if err != nil {
return errors.Wrapf(err, "failed checking if %s exists", path)
}
if pathExists {
return nil // file already exists, don't update it.
}

// create the completion file
f, err := os.Create(path)
if err != nil {
return err
}
defer f.Close()

bashEntry, err := bashCompleteEntry()
if err != nil {
fmt.Fprintf(os.Stderr, "Warning: unable to enable bash-completion: %s\n", err)
return nil // bash-completion isn't available, silently fail.
}

_, err = f.Write([]byte(bashEntry))
if err != nil {
return errors.Wrapf(err, "failed writing to %s", path)
}
return nil
}

func bashCompleteEntry() (string, error) {
return autocompleteBASH, nil
}
Loading

0 comments on commit b0d47fe

Please sign in to comment.