Skip to content
This repository has been archived by the owner on Jul 12, 2022. It is now read-only.

Commit

Permalink
Merge pull request #21 from ZupIT/feature/autocompletion-local
Browse files Browse the repository at this point in the history
auto completion local
  • Loading branch information
Marcos Guimarães authored Feb 17, 2020
2 parents 09e0746 + 943498b commit 9cfa4ca
Show file tree
Hide file tree
Showing 3 changed files with 304 additions and 20 deletions.
2 changes: 1 addition & 1 deletion cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ func main() {
credManager := credential.NewDefaultManager(env.ServerUrl, http.DefaultClient, sessionManager)
userManager := ruser.NewDefaultManager(env.ServerUrl, http.DefaultClient, sessionManager)
workspaceManager := workspace.NewDefaultManager(ritchieHomePath, env.ServerUrl, http.DefaultClient, treeManager, gitManager, credManager, sessionManager)
autocompleteManager := autocomplete.NewDefaultManager(env.ServerUrl, http.DefaultClient)
autocompleteManager := autocomplete.NewDefaultManager(env.ServerUrl, ritchieHomePath, http.DefaultClient)
ctxManager := context.NewDefaultManager(sessionManager)
metricsManager := metrics.NewDefaultManager(env.ServerUrl, &http.Client{Timeout: 2 * time.Second}, sessionManager)
credResolver := envcredential.NewResolver(credManager)
Expand Down
151 changes: 132 additions & 19 deletions pkg/autocomplete/default_autocomplete.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,47 +3,160 @@ package autocomplete
import (
"errors"
"fmt"
"io/ioutil"
"github.com/ZupIT/ritchie-cli/pkg/tree"
"net/http"
"strings"

"github.com/ZupIT/ritchie-cli/pkg/slice/sliceutil"
)

const pathUrl = "%s/auto-complete/%s"
const (
binaryName string = "rit"
lineCommand string = " commands+=(\"${command}\")"
firstLevel string = "root"
bash string = "bash"
zsh string = "zsh"
)

type defaultManager struct {
serverURL string
httpClient *http.Client
serverURL string
httpClient *http.Client
ritchieHome string
}

func NewDefaultManager(serverURL string, c *http.Client) *defaultManager {
return &defaultManager{serverURL: serverURL, httpClient: c}
type (
BashCommand struct {
LastCommand string
RootCommand string
Commands string
Level int
}

Command struct {
Content []string
Before string
}
)

func NewDefaultManager(serverURL, ritchieHome string, c *http.Client) *defaultManager {
return &defaultManager{serverURL: serverURL, httpClient: c, ritchieHome: ritchieHome}
}

func (d *defaultManager) Handle(shellName string) (string, error) {

if !sliceutil.Contains(supportedAutocomplete(), shellName) {
return "", errors.New("autocomplete for this terminal is not supported")
}

url := fmt.Sprintf(pathUrl, d.serverURL, shellName)
req, _ := http.NewRequest(http.MethodGet, url, nil)

resp, err := d.httpClient.Do(req)
treeManager := tree.NewDefaultManager(d.ritchieHome, "", nil, nil)
tree, err := treeManager.GetLocalTree()
if err != nil {
return "", err
}
autoCompletion := ""
switch shellName {
case bash:
autoCompletion = fmt.Sprintf("%s\n%s", "#!/bin/bash", loadToBash(*tree))
case zsh:
autoCompletion = loadToZsh(*tree)
}
return autoCompletion, nil
}

defer resp.Body.Close()
func supportedAutocomplete() []string {
return []string{bash, zsh}
}

bodyBytes, err := ioutil.ReadAll(resp.Body)
if err != nil {
return "", err
func loadToBash(tree tree.Representation) string {
autoComplete := autoCompletionBash
autoComplete = strings.Replace(autoComplete, "{{BinaryName}}", binaryName, -1)
autoComplete = strings.Replace(autoComplete, "{{DynamicCode}}", loadDynamicCommands(tree), 1)
return autoComplete
}

func loadToZsh(tree tree.Representation) string {
autoComplete := autoCompletionZsh
autoComplete = strings.Replace(autoComplete, "{{BinaryName}}", binaryName, -1)
autoComplete = strings.Replace(autoComplete, "{{AutoCompleteBash}}", loadToBash(tree), 1)
return autoComplete
}

func loadDynamicCommands(tree tree.Representation) string {
commands := tree.Commands
commandString := command
mapCommand := loadCommands(commands)
bashCommands := loadBashCommands(mapCommand)

allCommands := ""
for _, bashCommand := range bashCommands {
functionName := formatterFunctionName(bashCommand.RootCommand)
command := strings.Replace(commandString, "{{RootCommand}}", bashCommand.RootCommand, -1)
command = strings.Replace(command, "{{LastCommand}}", bashCommand.LastCommand, -1)
command = strings.Replace(command, "{{FunctionName}}", functionName, -1)
allCommands += strings.Replace(command, "{{Commands}}", bashCommand.Commands, -1)
}
return allCommands
}

return string(bodyBytes), nil
func formatterFunctionName(functionName string) string {
functionParts := strings.Split(functionName, "_")
if len(functionParts) > 2 {
functionName = functionParts[len(functionParts)-2] + "_" + functionParts[len(functionParts)-1]
}
return functionName
}

func supportedAutocomplete() []string {
return []string{"bash", "zsh"}
func loadCommands(commands []tree.Command) map[string]Command {
commandsMap := make(map[string]Command)
for _, command := range commands {
addValueMap(&commandsMap, command.Parent, command.Usage, command.Parent)
}
commandsMapResponse := make(map[string]Command)
for key, value := range commandsMap {
commandsMapResponse[key] = value
for _, v := range value.Content {
newKey := key + "_" + v
if _, ok := commandsMap[newKey]; !ok {
commandsMapResponse[newKey] = Command{
Content: nil,
Before: newKey,
}
}
}
}
return commandsMapResponse
}

func loadBashCommands(mapCommands map[string]Command) []BashCommand {
var bashCommands []BashCommand
for key, value := range mapCommands {
rootCommand := key
level := len(strings.Split(key, "_"))
commands := ""
for _, valueEntry := range value.Content {
commands += strings.Replace(lineCommand, "${command}", valueEntry, -1) + "\n"
}
if rootCommand == firstLevel {
rootCommand = fmt.Sprintf("%s_%s", binaryName, rootCommand)
}
bashCommands = append(bashCommands, BashCommand{
RootCommand: rootCommand,
Commands: commands,
LastCommand: loadLastCommand(key),
Level: level,
},
)
}
return bashCommands
}

func loadLastCommand(rootCommand string) string {
splitRootCommand := strings.Split(rootCommand, "_")
return splitRootCommand[len(splitRootCommand)-1]
}

func addValueMap(mapCommand *map[string]Command, key string, value string, before string) {
i := *mapCommand
a := i[key]
a.Content = append(a.Content, value)
a.Before = before
i[key] = a
}
171 changes: 171 additions & 0 deletions pkg/autocomplete/template.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
package autocomplete

const (
command string = `_{{FunctionName}}()
{
last_command="{{LastCommand}}"
commands=()
{{Commands}}
}
`



autoCompletionBash string = `
{{DynamicCode}}
__{{BinaryName}}_init_completion()
{
COMPREPLY=()
_get_comp_words_by_ref "$@" cur prev words cword
}
__{{BinaryName}}_index_of_word()
{
local w word=$1
shift
index=0
for w in "$@"; do
[[ $w = "$word" ]] && return
index=$((index+1))
done
index=-1
}
__{{BinaryName}}_contains_word()
{
local w word=$1; shift
for w in "$@"; do
[[ $w = "$word" ]] && return
done
return 1
}
__{{BinaryName}}_handle_command()
{
local next_command
if [[ -n ${last_command} ]]; then
next_command="_${last_command}_${words[c]//:/__}"
else
if [[ $c -eq 0 ]]; then
next_command="_{{BinaryName}}_root"
else
next_command="_${words[c]//:/__}"
fi
fi
c=$((c+1))
declare -F "$next_command" >/dev/null && $next_command
}
__{{BinaryName}}_handle_reply()
{
local completions
completions=("${commands[@]}")
COMPREPLY=( $(compgen -W "${completions[*]}" -- "$cur") )
}
__{{BinaryName}}_handle_word()
{
if [[ $c -ge $cword ]]; then
__{{BinaryName}}_handle_reply
return
fi
if __{{BinaryName}}_contains_word "${words[c]}" "${commands[@]}"; then
__{{BinaryName}}_handle_command
fi
__{{BinaryName}}_handle_word
}
__start_{{BinaryName}}()
{
local cur prev words cword
__{{BinaryName}}_init_completion -n "=" || return
local c=0
local commands=("{{BinaryName}}")
local last_command
__{{BinaryName}}_handle_word
}
complete -F __start_{{BinaryName}} {{BinaryName}}`




autoCompletionZsh string = `#compdef {{BinaryName}}
__{{BinaryName}}_bash_source() {
alias shopt=':'
alias _expand=_bash_expand
alias _complete=_bash_comp
emulate -L sh
setopt kshglob noshglob braceexpand
source "$@"
}
__{{BinaryName}}_compgen() {
local completions w
completions=( $(compgen "$@") ) || return $?
while [[ "$1" = -* && "$1" != -- ]]; do
shift
shift
done
if [[ "$1" == -- ]]; then
shift
fi
for w in "${completions[@]}"; do
if [[ "${w}" = "$1"* ]]; then
echo "${w}"
fi
done
}
__{{BinaryName}}_compopt() {
true
}
__{{BinaryName}}_get_comp_words_by_ref() {
cur="${COMP_WORDS[COMP_CWORD]}"
prev="${COMP_WORDS[${COMP_CWORD}-1]}"
words=("${COMP_WORDS[@]}")
cword=("${COMP_CWORD[@]}")
}
autoload -U +X bashcompinit && bashcompinit
LWORD='[[:<:]]'
RWORD='[[:>:]]'
if sed --help 2>&1 | grep -q GNU; then
LWORD='\<'
RWORD='\>'
fi
__{{BinaryName}}_convert_bash_to_zsh() {
sed \
-e 's/declare -F/whence -w/' \
-e 's/_get_comp_words_by_ref "\$@"/_get_comp_words_by_ref "\$*"/' \
-e 's/local \([a-zA-Z0-9_]*\)=/local \1; \1=/' \
-e "s/${LWORD}_get_comp_words_by_ref${RWORD}/__{{BinaryName}}_get_comp_words_by_ref/g" \
-e "s/${LWORD}compgen${RWORD}/__{{BinaryName}}_compgen/g" \
-e "s/${LWORD}compopt${RWORD}/__{{BinaryName}}_compopt/g" \
-e "s/${LWORD}declare${RWORD}/builtin declare/g" \
<<'BASH_COMPLETION_EOF'
{{AutoCompleteBash}}
# ex: ts=4 sw=4 et filetype=sh
BASH_COMPLETION_EOF
}
__{{BinaryName}}_bash_source <(__{{BinaryName}}_convert_bash_to_zsh)
_complete {{BinaryName}} 2>/dev/null`
)

0 comments on commit 9cfa4ca

Please sign in to comment.