complete_zsh.go 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. // Copyright 2019 Yunion
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package promputils
  15. import (
  16. "fmt"
  17. "io"
  18. "strings"
  19. "text/template"
  20. "yunion.io/x/structarg"
  21. )
  22. // ref: https://github.com/spf13/cobra/blob/master/zsh_completions.go
  23. var (
  24. zshCompFuncMap = template.FuncMap{
  25. "genZshFuncName": zshCompGenFuncName,
  26. "extractFlags": zshCompExtractFlag,
  27. "genFlagEntryForZshArguments": zshCompGenFlagEntryForArguments,
  28. "extractArgsCompletions": zshCompExtractArgumentCompletionHintsForRendering,
  29. }
  30. zshCompletionText = `
  31. {{/* should accept Command (that contains subcommands) as parameter */}}
  32. {{define "argumentsC" -}}
  33. {{ $cmdPath := genZshFuncName .}}
  34. function {{$cmdPath}} {
  35. local -a commands
  36. _arguments -C \{{- range extractFlags .}}
  37. {{genFlagEntryForZshArguments .}} \{{- end}}
  38. "1: :->cmnds" \
  39. "*::arg:->args"
  40. case $state in
  41. cmnds)
  42. commands=({{range .SubCmds}}
  43. "{{.Name}}:{{.Desc}}"{{end}}
  44. )
  45. _describe "command" commands
  46. ;;
  47. esac
  48. case "$words[1]" in {{- range .SubCmds}}
  49. {{.Name}})
  50. {{$cmdPath}}_{{.Name}}
  51. ;;{{end}}
  52. esac
  53. }
  54. {{range .SubCmds}}
  55. {{template "selectCmdTemplate" .}}
  56. {{- end}}
  57. {{- end}}
  58. {{/* should accept Command without subcommands as parameter */}}
  59. {{define "arguments" -}}
  60. function {{genZshFuncName .}} {
  61. {{" _arguments"}}{{range extractFlags .}} \
  62. {{genFlagEntryForZshArguments . -}}
  63. {{end}}{{range extractArgsCompletions .}} \
  64. {{.}}{{end}}
  65. }
  66. {{end}}
  67. {{/* dispatcher for commands with or without subcommands */}}
  68. {{define "selectCmdTemplate" -}}
  69. {{if .SubCmds}}{{template "argumentsC" .}}{{else}}{{template "arguments" .}}{{end}}
  70. {{- end}}
  71. {{/* template entry point */}}
  72. {{define "Main" -}}
  73. #compdef _{{.Name}} {{.Name}}
  74. {{template "selectCmdTemplate" .}}
  75. compdef _{{.Name}} {{.Name}}
  76. {{end}}
  77. `
  78. )
  79. func zshCompGenFuncName(c *Cmd) string {
  80. if c.ParentCmd != nil {
  81. return zshCompGenFuncName(c.ParentCmd) + "_" + c.Name
  82. }
  83. return "_" + c.Name
  84. }
  85. func zshCompExtractFlag(c *Cmd) []structarg.Argument {
  86. return c.GetArguments()
  87. }
  88. func zshCompGenFlagEntryForArguments(f structarg.Argument) string {
  89. // not process positional argument and single command
  90. if f.IsPositional() {
  91. return ""
  92. }
  93. if f.ShortToken() == "" {
  94. return zshCompGenFlagEntryForSingleOptionFlag(f)
  95. }
  96. return zshCompGenFlagEntryForMultiOptionFlag(f)
  97. }
  98. func zshCompGenFlagEntryForSingleOptionFlag(f structarg.Argument) string {
  99. var option, multiMark, extras string
  100. if f.IsMulti() {
  101. multiMark = "*"
  102. }
  103. option = "--" + f.Token()
  104. extras = zshCompGenFlagEntryExtras(f)
  105. return fmt.Sprintf(`'%s%s[%s]%s'`, multiMark, option, zshCompQuoteFlagDescription(f.HelpString("")), extras)
  106. }
  107. func zshCompGenFlagEntryForMultiOptionFlag(f structarg.Argument) string {
  108. var options, parenMultiMark, curlyMultiMark, extras string
  109. if f.IsMulti() {
  110. parenMultiMark = "*"
  111. curlyMultiMark = "\\*"
  112. }
  113. options = fmt.Sprintf(`'(%s-%s %s--%s)'{%s-%s,%s--%s}`,
  114. parenMultiMark, f.ShortToken(), parenMultiMark, f.Token(), curlyMultiMark, f.ShortToken(), curlyMultiMark, f.Token())
  115. extras = zshCompGenFlagEntryExtras(f)
  116. return fmt.Sprintf(`%s'[%s]%s'`, options, zshCompQuoteFlagDescription(f.HelpString("")), extras)
  117. }
  118. func zshCompGenFlagEntryExtras(f structarg.Argument) string {
  119. if !f.NeedData() {
  120. return ""
  121. }
  122. // allow options for flag
  123. extras := ":"
  124. type iChoices interface {
  125. Choices() []string
  126. }
  127. if hasChoices, ok := f.(iChoices); ok {
  128. // process choices
  129. var words []string
  130. for _, w := range hasChoices.Choices() {
  131. words = append(words, fmt.Sprintf("%q", w))
  132. }
  133. if len(words) != 0 {
  134. extras = fmt.Sprintf("%s :(%s)", extras, strings.Join(words, " "))
  135. }
  136. }
  137. return extras
  138. }
  139. func zshCompQuoteFlagDescription(s string) string {
  140. s = strings.Replace(s, "'", `'\''`, -1)
  141. s = strings.Replace(s, "[", `\[`, -1)
  142. s = strings.Replace(s, "]", `\]`, -1)
  143. return s
  144. }
  145. func zshCompExtractArgumentCompletionHintsForRendering(c *Cmd) []string {
  146. return nil
  147. }
  148. func (c *Cmd) GenZshCompletion(w io.Writer) error {
  149. tmpl, err := template.New("Main").Funcs(zshCompFuncMap).Parse(zshCompletionText)
  150. if err != nil {
  151. return fmt.Errorf("error creating zsh completion template: %v", err)
  152. }
  153. return tmpl.Execute(w, c.Root())
  154. }