1 package cli
2
3 import (
4 "bytes"
5 "fmt"
6 "io"
7 "strings"
8 "text/template"
9 )
10
11
12
13 func (a *App) ToFishCompletion() (string, error) {
14 var w bytes.Buffer
15 if err := a.writeFishCompletionTemplate(&w); err != nil {
16 return "", err
17 }
18 return w.String(), nil
19 }
20
21 type fishCompletionTemplate struct {
22 App *App
23 Completions []string
24 AllCommands []string
25 }
26
27 func (a *App) writeFishCompletionTemplate(w io.Writer) error {
28 const name = "cli"
29 t, err := template.New(name).Parse(FishCompletionTemplate)
30 if err != nil {
31 return err
32 }
33 allCommands := []string{}
34
35
36 completions := a.prepareFishFlags(a.VisibleFlags(), allCommands)
37
38
39 if !a.HideHelp {
40 completions = append(
41 completions,
42 a.prepareFishFlags([]Flag{HelpFlag}, allCommands)...,
43 )
44 }
45
46
47 if !a.HideVersion {
48 completions = append(
49 completions,
50 a.prepareFishFlags([]Flag{VersionFlag}, allCommands)...,
51 )
52 }
53
54
55 completions = append(
56 completions,
57 a.prepareFishCommands(a.VisibleCommands(), &allCommands, []string{})...,
58 )
59
60 return t.ExecuteTemplate(w, name, &fishCompletionTemplate{
61 App: a,
62 Completions: completions,
63 AllCommands: allCommands,
64 })
65 }
66
67 func (a *App) prepareFishCommands(commands []*Command, allCommands *[]string, previousCommands []string) []string {
68 completions := []string{}
69 for _, command := range commands {
70 if command.Hidden {
71 continue
72 }
73
74 var completion strings.Builder
75 completion.WriteString(fmt.Sprintf(
76 "complete -r -c %s -n '%s' -a '%s'",
77 a.Name,
78 a.fishSubcommandHelper(previousCommands),
79 strings.Join(command.Names(), " "),
80 ))
81
82 if command.Usage != "" {
83 completion.WriteString(fmt.Sprintf(" -d '%s'",
84 escapeSingleQuotes(command.Usage)))
85 }
86
87 if !command.HideHelp {
88 completions = append(
89 completions,
90 a.prepareFishFlags([]Flag{HelpFlag}, command.Names())...,
91 )
92 }
93
94 *allCommands = append(*allCommands, command.Names()...)
95 completions = append(completions, completion.String())
96 completions = append(
97 completions,
98 a.prepareFishFlags(command.VisibleFlags(), command.Names())...,
99 )
100
101
102 if len(command.Subcommands) > 0 {
103 completions = append(
104 completions,
105 a.prepareFishCommands(
106 command.Subcommands, allCommands, command.Names(),
107 )...,
108 )
109 }
110 }
111
112 return completions
113 }
114
115 func (a *App) prepareFishFlags(flags []Flag, previousCommands []string) []string {
116 completions := []string{}
117 for _, f := range flags {
118 flag, ok := f.(DocGenerationFlag)
119 if !ok {
120 continue
121 }
122
123 completion := &strings.Builder{}
124 completion.WriteString(fmt.Sprintf(
125 "complete -c %s -n '%s'",
126 a.Name,
127 a.fishSubcommandHelper(previousCommands),
128 ))
129
130 fishAddFileFlag(f, completion)
131
132 for idx, opt := range flag.Names() {
133 if idx == 0 {
134 completion.WriteString(fmt.Sprintf(
135 " -l %s", strings.TrimSpace(opt),
136 ))
137 } else {
138 completion.WriteString(fmt.Sprintf(
139 " -s %s", strings.TrimSpace(opt),
140 ))
141
142 }
143 }
144
145 if flag.TakesValue() {
146 completion.WriteString(" -r")
147 }
148
149 if flag.GetUsage() != "" {
150 completion.WriteString(fmt.Sprintf(" -d '%s'",
151 escapeSingleQuotes(flag.GetUsage())))
152 }
153
154 completions = append(completions, completion.String())
155 }
156
157 return completions
158 }
159
160 func fishAddFileFlag(flag Flag, completion *strings.Builder) {
161 switch f := flag.(type) {
162 case *GenericFlag:
163 if f.TakesFile {
164 return
165 }
166 case *StringFlag:
167 if f.TakesFile {
168 return
169 }
170 case *StringSliceFlag:
171 if f.TakesFile {
172 return
173 }
174 case *PathFlag:
175 if f.TakesFile {
176 return
177 }
178 }
179 completion.WriteString(" -f")
180 }
181
182 func (a *App) fishSubcommandHelper(allCommands []string) string {
183 fishHelper := fmt.Sprintf("__fish_%s_no_subcommand", a.Name)
184 if len(allCommands) > 0 {
185 fishHelper = fmt.Sprintf(
186 "__fish_seen_subcommand_from %s",
187 strings.Join(allCommands, " "),
188 )
189 }
190 return fishHelper
191
192 }
193
194 func escapeSingleQuotes(input string) string {
195 return strings.Replace(input, `'`, `\'`, -1)
196 }
197
View as plain text