completion-main.go 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114
  1. // License: GPLv3 Copyright: 2022, Kovid Goyal, <kovid at kovidgoyal.net>
  2. package cli
  3. import (
  4. "encoding/json"
  5. "fmt"
  6. "io"
  7. "os"
  8. "strings"
  9. )
  10. func json_input_parser(data []byte, shell_state map[string]string) ([][]string, error) {
  11. ans := make([][]string, 0, 32)
  12. err := json.Unmarshal(data, &ans)
  13. return ans, err
  14. }
  15. func json_output_serializer(completions []*Completions, shell_state map[string]string) ([]byte, error) {
  16. return json.Marshal(completions)
  17. }
  18. type completion_script_func func(commands []string) (string, error)
  19. type parser_func func(data []byte, shell_state map[string]string) ([][]string, error)
  20. type serializer_func func(completions []*Completions, shell_state map[string]string) ([]byte, error)
  21. var completion_scripts = make(map[string]completion_script_func, 4)
  22. var input_parsers = make(map[string]parser_func, 4)
  23. var output_serializers = make(map[string]serializer_func, 4)
  24. var init_completions = make(map[string]func(*Completions), 4)
  25. func init() {
  26. input_parsers["json"] = json_input_parser
  27. output_serializers["json"] = json_output_serializer
  28. }
  29. var registered_exes []func(root *Command)
  30. func RegisterExeForCompletion(x func(root *Command)) {
  31. if registered_exes == nil {
  32. registered_exes = make([]func(root *Command), 0, 4)
  33. }
  34. registered_exes = append(registered_exes, x)
  35. }
  36. func GenerateCompletions(args []string) error {
  37. output_type := "json"
  38. if len(args) > 0 {
  39. output_type = args[0]
  40. args = args[1:]
  41. }
  42. n := len(args)
  43. if n < 1 {
  44. n = 1
  45. }
  46. if output_type == "setup" {
  47. if len(args) == 0 {
  48. return fmt.Errorf("The shell must be specified")
  49. }
  50. shell_name := args[0]
  51. args = args[1:]
  52. completion_script := completion_scripts[shell_name]
  53. if completion_script == nil {
  54. return fmt.Errorf("Unsupported shell: %s", shell_name)
  55. }
  56. output, err := completion_script(args)
  57. if err == nil {
  58. _, err = os.Stdout.WriteString(output)
  59. }
  60. return err
  61. }
  62. shell_state := make(map[string]string, n)
  63. for _, arg := range args {
  64. k, v, found := strings.Cut(arg, "=")
  65. if !found {
  66. return fmt.Errorf("Invalid shell state specification: %s", arg)
  67. }
  68. shell_state[k] = v
  69. }
  70. input_parser := input_parsers[output_type]
  71. output_serializer := output_serializers[output_type]
  72. if input_parser == nil || output_serializer == nil {
  73. return fmt.Errorf("Unknown output type: %s", output_type)
  74. }
  75. data, err := io.ReadAll(os.Stdin)
  76. if err != nil {
  77. return err
  78. }
  79. // debugf("%#v", string(data))
  80. all_argv, err := input_parser(data, shell_state)
  81. if err != nil {
  82. return err
  83. }
  84. var root = NewRootCommand()
  85. for _, re := range registered_exes {
  86. re(root)
  87. }
  88. err = root.Validate()
  89. if err != nil {
  90. return err
  91. }
  92. all_completions := make([]*Completions, 0, 1)
  93. for _, argv := range all_argv {
  94. all_completions = append(all_completions, root.GetCompletions(argv, init_completions[output_type]))
  95. root.ResetAfterParseArgs()
  96. }
  97. output, err := output_serializer(all_completions, shell_state)
  98. if err == nil {
  99. _, err = os.Stdout.Write(output)
  100. }
  101. return err
  102. }