pkg.re/essentialkaos/ek.10@v12.41.0+incompatible/usage/completion/bash/bash.go (about) 1 // Package bash provides methods for generating bash completion 2 package bash 3 4 // ////////////////////////////////////////////////////////////////////////////////// // 5 // // 6 // Copyright (c) 2022 ESSENTIAL KAOS // 7 // Apache License, Version 2.0 <https://www.apache.org/licenses/LICENSE-2.0> // 8 // // 9 // ////////////////////////////////////////////////////////////////////////////////// // 10 11 import ( 12 "fmt" 13 "strings" 14 15 "pkg.re/essentialkaos/ek.v12/usage" 16 ) 17 18 // ////////////////////////////////////////////////////////////////////////////////// // 19 20 // _BASH_TEMPLATE is template used for completion generation 21 const _BASH_TEMPLATE = `# Completion for {{COMPNAME}} 22 # This completion is automatically generated 23 24 _{{COMPNAME_SAFE}}() { 25 local cur prev cmds opts show_files 26 27 COMPREPLY=() 28 cur="${COMP_WORDS[COMP_CWORD]}" 29 prev="${COMP_WORDS[COMP_CWORD-1]}" 30 31 cmds="{{COMMANDS}}" 32 opts="{{GLOBAL_OPTIONS}}" 33 show_files="{{SHOW_FILES}}" 34 file_glob="{{FILE_GLOB}}" 35 36 {{COMMANDS_HANDLERS}} 37 38 if [[ $cur == -* ]] ; then 39 COMPREPLY=($(compgen -W "$opts" -- "$cur")) 40 return 0 41 fi 42 43 _{{COMPNAME_SAFE}}_filter "$cmds" "$opts" "$show_files" "$file_glob" 44 } 45 46 _{{COMPNAME_SAFE}}_filter() { 47 local cmds="$1" 48 local opts="$2" 49 local show_files="$3" 50 local file_glob="$4" 51 52 local cmd1 cmd2 53 54 for cmd1 in $cmds ; do 55 for cmd2 in ${COMP_WORDS[*]} ; do 56 if [[ "$cmd1" == "$cmd2" ]] ; then 57 if [[ -z "$show_files" ]] ; then 58 COMPREPLY=($(compgen -W "$opts" -- "$cur")) 59 else 60 _filedir "$file_glob" 61 fi 62 63 return 0 64 fi 65 done 66 done 67 68 if [[ -z "$show_files" ]] ; then 69 COMPREPLY=($(compgen -W "$cmds" -- "$cur")) 70 return 0 71 fi 72 73 _filedir "$file_glob" 74 } 75 76 complete -F _{{COMPNAME_SAFE}} {{COMPNAME}} {{COMP_OPTS}} 77 ` 78 79 // ////////////////////////////////////////////////////////////////////////////////// // 80 81 // Generate generates Bash completion code 82 func Generate(info *usage.Info, name string, fileExt ...string) string { 83 result := _BASH_TEMPLATE 84 85 result = strings.Replace(result, "{{COMMANDS}}", genCommandsList(info), -1) 86 result = strings.Replace(result, "{{GLOBAL_OPTIONS}}", genGlobalOptionsList(info), -1) 87 result = strings.Replace(result, "{{COMMANDS_HANDLERS}}", genCommandsHandlers(info), -1) 88 result = strings.Replace(result, "{{COMPNAME}}", name, -1) 89 90 if len(info.Args) != 0 { 91 result = strings.Replace(result, "{{SHOW_FILES}}", "true", -1) 92 result = strings.Replace(result, "{{COMP_OPTS}}", "-o filenames", -1) 93 if len(fileExt) != 0 { 94 result = strings.Replace(result, "{{FILE_GLOB}}", fileExt[0], -1) 95 } 96 } else { 97 result = strings.Replace(result, "{{SHOW_FILES}}", "", -1) 98 result = strings.Replace(result, "{{COMP_OPTS}}", "", -1) 99 result = strings.Replace(result, "{{FILE_GLOB}}", "", -1) 100 } 101 102 nameSafe := strings.Replace(name, "-", "_", -1) 103 result = strings.Replace(result, "{{COMPNAME_SAFE}}", nameSafe, -1) 104 105 return result 106 } 107 108 // ////////////////////////////////////////////////////////////////////////////////// // 109 110 // genGlobalOptionsList generates list with global options 111 func genGlobalOptionsList(info *usage.Info) string { 112 var result []string 113 114 nonGlobalOptions := make(map[string]bool) 115 116 for _, cmd := range info.Commands { 117 for _, opt := range cmd.BoundOptions { 118 nonGlobalOptions[opt] = true 119 } 120 } 121 122 for _, opt := range info.Options { 123 if nonGlobalOptions[opt.Long] { 124 continue 125 } 126 127 result = append(result, "--"+opt.Long) 128 } 129 130 return strings.Join(result, " ") 131 } 132 133 // genCommandsHandlers generates command handler 134 func genCommandsHandlers(info *usage.Info) string { 135 if !isCommandHandlersRequired(info) { 136 return "" 137 } 138 139 result := " case $prev in\n" 140 141 for _, cmd := range info.Commands { 142 if len(cmd.BoundOptions) != 0 { 143 result += genCommandHandler(cmd, info) 144 } 145 } 146 147 result += " esac" 148 149 return result 150 } 151 152 // genCommandHandler generates handler for given command 153 func genCommandHandler(cmd *usage.Command, info *usage.Info) string { 154 result := fmt.Sprintf(" %s)\n", cmd.Name) 155 156 var options []string 157 158 for _, optName := range cmd.BoundOptions { 159 opt := info.GetOption(optName) 160 161 if opt == nil { 162 continue 163 } 164 165 options = append(options, "--"+opt.Long) 166 } 167 168 result += fmt.Sprintf(" opts=\"%s\"\n", strings.Join(options, " ")) 169 result += " COMPREPLY=($(compgen -W \"$opts\" -- \"$cur\"))\n" 170 result += " return 0\n" 171 result += " ;;\n\n" 172 173 return result 174 } 175 176 // getCommandsList returns slice with available commands 177 func genCommandsList(info *usage.Info) string { 178 var result []string 179 180 for _, command := range info.Commands { 181 result = append(result, command.Name) 182 } 183 184 return strings.Join(result, " ") 185 } 186 187 // isCommandHandlersRequired returns true if commands have bound options 188 func isCommandHandlersRequired(info *usage.Info) bool { 189 for _, cmd := range info.Commands { 190 if len(cmd.BoundOptions) != 0 { 191 return true 192 } 193 } 194 195 return false 196 }