Skip to content

Commit

Permalink
Merge pull request #182 from lets-cli/refactor-completions
Browse files Browse the repository at this point in the history
refactor completions, replace docopt.go with custom docopt.go
  • Loading branch information
kindermax authored Jul 11, 2022
2 parents e5f79b6 + f61311c commit 8fd7b31
Show file tree
Hide file tree
Showing 23 changed files with 333 additions and 80 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ jobs:
with:
version: latest
- name: Test bats
run: lets test-bats
run: timeout 120 lets test-bats

lint:
runs-on: ubuntu-latest
Expand Down
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,6 @@ dist
lets
.lets
lets.my.yaml
coverage.out
_lets
coverage.out
node_modules
3 changes: 2 additions & 1 deletion .golangci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ linters:
enable-all: true
disable:
- typecheck
- gomoddirectives
- containedctx
- gochecknoglobals
- goimports
Expand Down Expand Up @@ -41,4 +42,4 @@ issues:
- gomnd
- path: set\.go
linters:
- typecheck
- typecheck
3 changes: 1 addition & 2 deletions checksum/checksum.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package checksum

import (

// #nosec G505
"crypto/sha1"
"fmt"
Expand Down Expand Up @@ -196,7 +195,7 @@ func persistOneChecksum(dotLetsDir string, cmdName string, checksumName string,
return fmt.Errorf("can not create checksum dir at %s: %w", checksumDirPath, err)
}

f, err := os.OpenFile(checksumFilePath, os.O_CREATE|os.O_WRONLY, 0755)
f, err := os.OpenFile(checksumFilePath, os.O_CREATE|os.O_WRONLY, 0o755)
if err != nil {
return fmt.Errorf("can not open file %s to persist checksum: %w", checksumFilePath, err)
}
Expand Down
148 changes: 132 additions & 16 deletions cmd/completion.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,61 @@ import (
"bytes"
"fmt"
"io"
"sort"
"strings"
"text/template"

"github.com/lets-cli/lets/config/config"
"github.com/lets-cli/lets/config/parser"
"github.com/spf13/cobra"
)

const zshCompletionText = `#compdef lets
const zshCompletionText = `#compdef _lets lets
_list () {
LETS_EXECUTABLE=lets
function _lets {
local state
_arguments -C -s \
"1: :->cmds" \
'*::arg:->args'
case $state in
cmds)
_lets_commands
;;
args)
_lets_command_options "${words[1]}"
;;
esac
}
# Check if in folder with correct lets.yaml file
_check_lets_config() {
${LETS_EXECUTABLE} 1>/dev/null 2>/dev/null
echo $?
}
_lets_commands () {
local cmds
# Check if in folder with correct lets.yaml file
lets 1>/dev/null 2>/dev/null
if [ $? -eq 0 ]; then
IFS=$'\n' cmds=($(lets completion --list {{.Verbose}}))
if [ $(_check_lets_config) -eq 0 ]; then
IFS=$'\n' cmds=($(${LETS_EXECUTABLE} completion --commands --verbose))
else
cmds=()
fi
_describe -t commands 'Available commands' cmds
}
_arguments -C -s "1: :{_list}" '*::arg:->args' --
_lets_command_options () {
local cmd=$1
if [ $(_check_lets_config) -eq 0 ]; then
IFS=$'\n'
_arguments -s $(${LETS_EXECUTABLE} completion --options=${cmd} --verbose)
fi
}
`

const bashCompletionText = `_lets_completion() {
Expand All @@ -41,18 +74,18 @@ complete -o filenames -F _lets_completion lets
`

// generate bash completion script.
func genBashCompletion(w io.Writer) error {
func genBashCompletion(out io.Writer) error {
tmpl, err := template.New("Main").Parse(bashCompletionText)
if err != nil {
return fmt.Errorf("error creating zsh completion template: %w", err)
}

return tmpl.Execute(w, nil)
return tmpl.Execute(out, nil)
}

// generate zsh completion script.
// if verbose passed - generate completion with description.
func genZshCompletion(w io.Writer, verbose bool) error {
func genZshCompletion(out io.Writer, verbose bool) error {
tmpl, err := template.New("Main").Parse(zshCompletionText)
if err != nil {
return fmt.Errorf("error creating zsh completion template: %w", err)
Expand All @@ -66,11 +99,11 @@ func genZshCompletion(w io.Writer, verbose bool) error {
data.Verbose = "--verbose"
}

return tmpl.Execute(w, data)
return tmpl.Execute(out, data)
}

// generate string of commands joined with \n.
func getCommandsList(rootCmd *cobra.Command, w io.Writer, verbose bool) error {
func getCommandsList(rootCmd *cobra.Command, out io.Writer, verbose bool) error {
buf := new(bytes.Buffer)

for _, cmd := range rootCmd.Commands() {
Expand All @@ -89,15 +122,67 @@ func getCommandsList(rootCmd *cobra.Command, w io.Writer, verbose bool) error {
}
}

_, err := buf.WriteTo(w)
_, err := buf.WriteTo(out)
if err != nil {
return fmt.Errorf("can not generate commands list: %w", err)
}

return nil
}

func initCompletionCmd(rootCmd *cobra.Command) {
type option struct {
name string
desc string
}

// generate string of command options joined with \n.
func getCommandOptions(command config.Command, out io.Writer, verbose bool) error {
if command.Docopts == "" {
return nil
}

rawOpts, err := parser.ParseDocoptsOptions(command.Docopts, command.Name)
if err != nil {
return fmt.Errorf("can not parse docopts: %w", err)
}

var options []option

for _, opt := range rawOpts {
if strings.HasPrefix(opt.Name, "--") {
options = append(options, option{name: opt.Name, desc: opt.Description})
}
}

sort.SliceStable(options, func(i, j int) bool {
return options[i].name < options[j].name
})

buf := new(bytes.Buffer)

for _, option := range options {
if verbose {
desc := fmt.Sprintf("No description for option %s", option.name)

if option.desc != "" {
desc = strings.TrimSpace(option.desc)
}

buf.WriteString(fmt.Sprintf("%[1]s[%s]\n", option.name, desc))
} else {
buf.WriteString(fmt.Sprintf("%s\n", option.name))
}
}

_, err = buf.WriteTo(out)
if err != nil {
return fmt.Errorf("can not generate command options list: %w", err)
}

return nil
}

func initCompletionCmd(rootCmd *cobra.Command, cfg *config.Config) {
completionCmd := &cobra.Command{
Use: "completion",
Hidden: true,
Expand All @@ -107,16 +192,45 @@ func initCompletionCmd(rootCmd *cobra.Command) {
if err != nil {
return fmt.Errorf("can not get flag 'shell': %w", err)
}

verbose, err := cmd.Flags().GetBool("verbose")
if err != nil {
return fmt.Errorf("can not get flag 'verbose': %w", err)
}

list, err := cmd.Flags().GetBool("list")
if err != nil {
return fmt.Errorf("can not get flag 'list': %w", err)
}

commands, err := cmd.Flags().GetBool("commands")
if err != nil {
return fmt.Errorf("can not get flag 'commands': %w", err)
}

if list {
commands = true
}

optionsForCmd, err := cmd.Flags().GetString("options")
if err != nil {
return fmt.Errorf("can not get flag 'options': %w", err)
}

if optionsForCmd != "" {
if cfg == nil {
return fmt.Errorf("can not read config")
}

command, exists := cfg.Commands[optionsForCmd]
if !exists {
return fmt.Errorf("command %s not declared in config", optionsForCmd)
}

return getCommandOptions(command, cmd.OutOrStdout(), verbose)
}

if commands {
return getCommandsList(rootCmd, cmd.OutOrStdout(), verbose)
}

Expand All @@ -136,8 +250,10 @@ func initCompletionCmd(rootCmd *cobra.Command) {
}

completionCmd.Flags().StringP("shell", "s", "", "The type of shell (bash or zsh)")
completionCmd.Flags().Bool("list", false, "Show list of commands")
completionCmd.Flags().Bool("verbose", false, "Verbose list of commands (with description) (only for zsh)")
completionCmd.Flags().Bool("list", false, "Show list of commands [deprecated, use --commands]")
completionCmd.Flags().Bool("commands", false, "Show list of commands")
completionCmd.Flags().String("options", "", "Show list of options for command")
completionCmd.Flags().Bool("verbose", false, "Verbose list of commands or options (with description) (only for zsh)")

rootCmd.AddCommand(completionCmd)
}
8 changes: 4 additions & 4 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ func newRootCmd(version string) *cobra.Command {
func CreateRootCommandWithConfig(out io.Writer, cfg *config.Config, version string) *cobra.Command {
rootCmd := newRootCmd(version)

initRootCommand(rootCmd)
initRootCommand(rootCmd, cfg)
initSubCommands(rootCmd, cfg, out)

return rootCmd
Expand All @@ -43,7 +43,7 @@ func CreateRootCommandWithConfig(out io.Writer, cfg *config.Config, version stri
func CreateRootCommand(version string) *cobra.Command {
rootCmd := newRootCmd(version)

initRootCommand(rootCmd)
initRootCommand(rootCmd, nil)

return rootCmd
}
Expand All @@ -64,8 +64,8 @@ func ConfigErrorCheck(rootCmd *cobra.Command, err error) {
}
}

func initRootCommand(rootCmd *cobra.Command) {
initCompletionCmd(rootCmd)
func initRootCommand(rootCmd *cobra.Command, cfg *config.Config) {
initCompletionCmd(rootCmd, cfg)
rootCmd.Flags().StringToStringP("env", "E", nil, "set env variable for running command KEY=VALUE")
rootCmd.Flags().StringArray("only", []string{}, "run only specified command(s) described in cmd as map")
rootCmd.Flags().StringArray("exclude", []string{}, "run all but excluded command(s) described in cmd as map")
Expand Down
5 changes: 4 additions & 1 deletion config/config/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,10 @@ func (cmd Command) WithEnv(env map[string]string) Command {
}

func (cmd Command) Pretty() string {
pretty, _ := json.MarshalIndent(cmd, "", " ")
pretty, err := json.MarshalIndent(cmd, "", " ")
if err != nil {
return ""
}

return string(pretty)
}
Expand Down
10 changes: 5 additions & 5 deletions config/parser/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ func parseCmd(cmd interface{}, newCmd *config.Command) error {

cmdList := make([]string, 0, len(cmd)+len(proxyArgs))

for _, v := range cmd {
if v == nil {
for _, value := range cmd {
if value == nil {
return parseError(
"got nil in cmd list",
newCmd.Name,
Expand All @@ -56,11 +56,11 @@ func parseCmd(cmd interface{}, newCmd *config.Command) error {
)
}

cmdList = append(cmdList, fmt.Sprintf("%s", v))
cmdList = append(cmdList, fmt.Sprintf("%s", value))
}

fullCommandList := append(cmdList, escapeArgs(proxyArgs)...)
newCmd.Cmd = strings.TrimSpace(strings.Join(fullCommandList, " "))
cmdList = append(cmdList, escapeArgs(proxyArgs)...)
newCmd.Cmd = strings.TrimSpace(strings.Join(cmdList, " "))
case map[string]interface{}:
cmdMap := make(map[string]string, len(cmd))

Expand Down
Loading

0 comments on commit 8fd7b31

Please sign in to comment.