master
go 243 lines 6.24 KB
Raw
1 package commands
2
3 import (
4 "io"
5 "slices"
6 "strings"
7 "text/template"
8
9 cmds "github.com/ipfs/go-ipfs-cmds"
10 )
11
12 type completionCommand struct {
13 Name string
14 FullName string
15 Description string
16 Subcommands []*completionCommand
17 Flags []*singleOption
18 Options []*singleOption
19 ShortFlags []string
20 ShortOptions []string
21 LongFlags []string
22 LongOptions []string
23 IsFinal bool
24 }
25
26 type singleOption struct {
27 LongNames []string
28 ShortNames []string
29 Description string
30 }
31
32 func commandToCompletions(name string, fullName string, cmd *cmds.Command) *completionCommand {
33 parsed := &completionCommand{
34 Name: name,
35 FullName: fullName,
36 Description: cmd.Helptext.Tagline,
37 IsFinal: len(cmd.Subcommands) == 0,
38 }
39 for name, subCmd := range cmd.Subcommands {
40 parsed.Subcommands = append(parsed.Subcommands,
41 commandToCompletions(name, fullName+" "+name, subCmd))
42 }
43 slices.SortFunc(parsed.Subcommands, func(a, b *completionCommand) int {
44 return strings.Compare(a.Name, b.Name)
45 })
46
47 for _, opt := range cmd.Options {
48 flag := &singleOption{Description: opt.Description()}
49 flag.LongNames = append(flag.LongNames, opt.Name())
50 if opt.Type() == cmds.Bool {
51 parsed.LongFlags = append(parsed.LongFlags, opt.Name())
52 for _, name := range opt.Names() {
53 if len(name) == 1 {
54 parsed.ShortFlags = append(parsed.ShortFlags, name)
55 flag.ShortNames = append(flag.ShortNames, name)
56 break
57 }
58 }
59 parsed.Flags = append(parsed.Flags, flag)
60 } else {
61 parsed.LongOptions = append(parsed.LongOptions, opt.Name())
62 for _, name := range opt.Names() {
63 if len(name) == 1 {
64 parsed.ShortOptions = append(parsed.ShortOptions, name)
65 flag.ShortNames = append(flag.ShortNames, name)
66 break
67 }
68 }
69 parsed.Options = append(parsed.Options, flag)
70 }
71 }
72 slices.Sort(parsed.LongFlags)
73 slices.Sort(parsed.ShortFlags)
74 slices.Sort(parsed.LongOptions)
75 slices.Sort(parsed.ShortOptions)
76 return parsed
77 }
78
79 var bashCompletionTemplate, fishCompletionTemplate, zshCompletionTemplate *template.Template
80
81 func init() {
82 commandTemplate := template.Must(template.New("command").Parse(`
83 while [[ ${index} -lt ${COMP_CWORD} ]]; do
84 case "${COMP_WORDS[index]}" in
85 -*)
86 let index++
87 continue
88 ;;
89 {{ range .Subcommands }}
90 "{{ .Name }}")
91 let index++
92 {{ template "command" . }}
93 return 0
94 ;;
95 {{ end }}
96 esac
97 break
98 done
99
100 if [[ "${word}" == -* ]]; then
101 {{ if .ShortFlags -}}
102 _ipfs_compgen -W $'{{ range .ShortFlags }}-{{.}} \n{{end}}' -- "${word}"
103 {{ end -}}
104 {{- if .ShortOptions -}}
105 _ipfs_compgen -S = -W $'{{ range .ShortOptions }}-{{.}}\n{{end}}' -- "${word}"
106 {{ end -}}
107 {{- if .LongFlags -}}
108 _ipfs_compgen -W $'{{ range .LongFlags }}--{{.}} \n{{end}}' -- "${word}"
109 {{ end -}}
110 {{- if .LongOptions -}}
111 _ipfs_compgen -S = -W $'{{ range .LongOptions }}--{{.}}\n{{end}}' -- "${word}"
112 {{ end -}}
113 return 0
114 fi
115
116 while [[ ${index} -lt ${COMP_CWORD} ]]; do
117 if [[ "${COMP_WORDS[index]}" != -* ]]; then
118 let argidx++
119 fi
120 let index++
121 done
122
123 {{- if .Subcommands }}
124 if [[ "${argidx}" -eq 0 ]]; then
125 _ipfs_compgen -W $'{{ range .Subcommands }}{{.Name}} \n{{end}}' -- "${word}"
126 fi
127 {{ end -}}
128 `))
129
130 bashCompletionTemplate = template.Must(commandTemplate.New("root").Parse(`#!/bin/bash
131
132 _ipfs_compgen() {
133 local oldifs="$IFS"
134 IFS=$'\n'
135 while read -r line; do
136 COMPREPLY+=("$line")
137 done < <(compgen "$@")
138 IFS="$oldifs"
139 }
140
141 _ipfs() {
142 COMPREPLY=()
143 local index=1
144 local argidx=0
145 local word="${COMP_WORDS[COMP_CWORD]}"
146 {{ template "command" . }}
147 }
148 complete -o nosort -o nospace -o default -F _ipfs ipfs
149 `))
150
151 zshCompletionTemplate = template.Must(commandTemplate.New("root").Parse(`#!bin/zsh
152 autoload bashcompinit
153 bashcompinit
154 _ipfs_compgen() {
155 local oldifs="$IFS"
156 IFS=$'\n'
157 while read -r line; do
158 COMPREPLY+=("$line")
159 done < <(compgen "$@")
160 IFS="$oldifs"
161 }
162
163 _ipfs() {
164 COMPREPLY=()
165 local index=1
166 local argidx=0
167 local word="${COMP_WORDS[COMP_CWORD]}"
168 {{ template "command" . }}
169 }
170 complete -o nosort -o nospace -o default -F _ipfs ipfs
171 `))
172
173 fishCommandTemplate := template.Must(template.New("command").Parse(`
174 {{- if .IsFinal -}}
175 complete -c ipfs -n '__fish_ipfs_seen_all_subcommands_from{{ .FullName }}' -F
176 {{ end -}}
177 {{- range .Flags -}}
178 complete -c ipfs -n '__fish_ipfs_seen_all_subcommands_from{{ $.FullName }}' {{ range .ShortNames }}-s {{.}} {{end}}{{ range .LongNames }}-l {{.}} {{end}}-d "{{ .Description }}"
179 {{ end -}}
180 {{- range .Options -}}
181 complete -c ipfs -n '__fish_ipfs_seen_all_subcommands_from{{ $.FullName }}' -r {{ range .ShortNames }}-s {{.}} {{end}}{{ range .LongNames }}-l {{.}} {{end}}-d "{{ .Description }}"
182 {{ end -}}
183
184 {{- range .Subcommands }}
185 #{{ .FullName }}
186 complete -c ipfs -n '__fish_ipfs_use_subcommand{{ .FullName }}' -a {{ .Name }} -d "{{ .Description }}"
187 {{ template "command" . }}
188 {{ end -}}
189 `))
190 fishCompletionTemplate = template.Must(fishCommandTemplate.New("root").Parse(`#!/usr/bin/env fish
191 function __fish_ipfs_seen_all_subcommands_from
192 set -l cmd (commandline -poc)
193 set -e cmd[1]
194 for c in $argv
195 if not contains -- $c $cmd
196 return 1
197 end
198 end
199 return 0
200 end
201
202 function __fish_ipfs_use_subcommand
203 set -e argv[-1]
204 set -l cmd (commandline -poc)
205 set -e cmd[1]
206 for i in $cmd
207 switch $i
208 case '-*'
209 continue
210 case $argv[1]
211 set argv $argv[2..]
212 continue
213 case '*'
214 return 1
215 end
216 end
217 test -z "$argv"
218 end
219
220 complete -c ipfs -l help -d "Show the full command help text."
221
222 complete -c ipfs --keep-order --no-files
223
224 {{ template "command" . }}
225 `))
226 }
227
228 // writeBashCompletions generates a bash completion script for the given command tree.
229 func writeBashCompletions(cmd *cmds.Command, out io.Writer) error {
230 cmds := commandToCompletions("ipfs", "", cmd)
231 return bashCompletionTemplate.Execute(out, cmds)
232 }
233
234 // writeFishCompletions generates a fish completion script for the given command tree.
235 func writeFishCompletions(cmd *cmds.Command, out io.Writer) error {
236 cmds := commandToCompletions("ipfs", "", cmd)
237 return fishCompletionTemplate.Execute(out, cmds)
238 }
239
240 func writeZshCompletions(cmd *cmds.Command, out io.Writer) error {
241 cmds := commandToCompletions("ipfs", "", cmd)
242 return zshCompletionTemplate.Execute(out, cmds)
243 }