···11-import cac from 'cac'
11+import cac, { Command } from "cac";
22+33+const cli = cac("cac");
44+import mri, { Options } from "mri";
55+66+cli.option("--type [type]", "Choose a project type", {
77+ default: "node",
88+});
99+cli.option("--name <name>", "Provide your name");
1010+1111+cli.command("start").option("--port <port>", "your port");
1212+1313+cli.command("help");
1414+1515+cli.command("help config").option("--foo", "foo option");
1616+1717+cli
1818+ .command("complete")
1919+ .action(() => console.log(genZshComp("my_command", true)));
2020+2121+cli
2222+ .command("__complete [...args]")
2323+ .action((_, { ["--"]: args }: { ["--"]: string[] }) => {
2424+ const toComplete = args[args.length - 1];
2525+ const trimmedArgs = args.slice(0, args.length - 1);
2626+2727+ // console.log("found", cli.parse(trimmedArgs))
2828+ const parsed = mri(trimmedArgs);
2929+ const commandName = parsed._.join(" ");
3030+3131+ let command: Command | null = null;
3232+ for (const c of cli.commands) {
3333+ if (c.isMatched(commandName)) {
3434+ command = c;
3535+ }
3636+ }
3737+ const completions: string[] = [];
3838+3939+ if (toComplete.startsWith("-")) {
4040+ const flag = toComplete.slice("--".length);
4141+ const options: Command["options"] = [
4242+ ...(command?.options ?? []),
4343+ ...cli.globalCommand.options,
4444+ ];
4545+ if (flag) {
4646+ completions.push(
4747+ ...options
4848+ .filter((o) => o.names.some((name) => name.includes(flag)))
4949+ .map((o) => `--${o.name}`),
5050+ );
5151+ } else {
5252+ }
5353+5454+ }
5555+5656+ // console.log(args, toComplete, commandName);
5757+ // console.log(cli);
5858+ console.log(`${completions.join("\n")}\n`);
5959+ });
6060+6161+cli.version("0.0.0");
6262+cli.help();
6363+6464+// console.log('cli', cli)
6565+6666+// ShellCompRequestCmd is the name of the hidden command that is used to request
6767+// completion results from the program. It is used by the shell completion scripts.
6868+const ShellCompRequestCmd: string = "__complete";
6969+7070+// ShellCompNoDescRequestCmd is the name of the hidden command that is used to request
7171+// completion results without their description. It is used by the shell completion scripts.
7272+const ShellCompNoDescRequestCmd: string = "__completeNoDesc";
7373+7474+// ShellCompDirective is a bit map representing the different behaviors the shell
7575+// can be instructed to have once completions have been provided.
7676+enum ShellCompDirective {
7777+ // ShellCompDirectiveError indicates an error occurred and completions should be ignored.
7878+ ShellCompDirectiveError = 1 << 0,
7979+8080+ // ShellCompDirectiveNoSpace indicates that the shell should not add a space
8181+ // after the completion even if there is a single completion provided.
8282+ ShellCompDirectiveNoSpace = 1 << 1,
8383+8484+ // ShellCompDirectiveNoFileComp indicates that the shell should not provide
8585+ // file completion even when no completion is provided.
8686+ ShellCompDirectiveNoFileComp = 1 << 2,
8787+8888+ // ShellCompDirectiveFilterFileExt indicates that the provided completions
8989+ // should be used as file extension filters.
9090+ // For flags, using Command.MarkFlagFilename() and Command.MarkPersistentFlagFilename()
9191+ // is a shortcut to using this directive explicitly. The BashCompFilenameExt
9292+ // annotation can also be used to obtain the same behavior for flags.
9393+ ShellCompDirectiveFilterFileExt = 1 << 3,
9494+9595+ // ShellCompDirectiveFilterDirs indicates that only directory names should
9696+ // be provided in file completion. To request directory names within another
9797+ // directory, the returned completions should specify the directory within
9898+ // which to search. The BashCompSubdirsInDir annotation can be used to
9999+ // obtain the same behavior but only for flags.
100100+ ShellCompDirectiveFilterDirs = 1 << 4,
101101+102102+ // ShellCompDirectiveKeepOrder indicates that the shell should preserve the order
103103+ // in which the completions are provided.
104104+ ShellCompDirectiveKeepOrder = 1 << 5,
105105+106106+ // ===========================================================================
107107+108108+ // All directives using iota (or equivalent in Go) should be above this one.
109109+ // For internal use.
110110+ shellCompDirectiveMaxValue = 1 << 6,
111111+112112+ // ShellCompDirectiveDefault indicates to let the shell perform its default
113113+ // behavior after completions have been provided.
114114+ // This one must be last to avoid messing up the iota count.
115115+ ShellCompDirectiveDefault = 0,
116116+}
117117+118118+const execPath = process.execPath;
119119+const args = process.argv.slice(1);
120120+const x = `${execPath} ${process.execArgv.join(" ")} ${args[0]}`;
121121+// console.error(x)
122122+123123+function genZshComp(name: string, includeDesc: boolean) {
124124+ let compCmd = ShellCompRequestCmd;
125125+ if (!includeDesc) {
126126+ compCmd = ShellCompNoDescRequestCmd;
127127+ }
128128+129129+ return `#compdef ${name}
130130+compdef _${name} ${name}
131131+132132+# zsh completion for ${name} -*- shell-script -*-
133133+134134+__${name}_debug() {
135135+ local file="$BASH_COMP_DEBUG_FILE"
136136+ if [[ -n \${file} ]]; then
137137+ echo "$*" >> "\${file}"
138138+ fi
139139+}
140140+141141+_${name}() {
142142+ local shellCompDirectiveError=${ShellCompDirective.ShellCompDirectiveError}
143143+ local shellCompDirectiveNoSpace=${ShellCompDirective.ShellCompDirectiveNoSpace}
144144+ local shellCompDirectiveNoFileComp=${ShellCompDirective.ShellCompDirectiveNoFileComp}
145145+ local shellCompDirectiveFilterFileExt=${ShellCompDirective.ShellCompDirectiveFilterFileExt}
146146+ local shellCompDirectiveFilterDirs=${ShellCompDirective.ShellCompDirectiveFilterDirs}
147147+ local shellCompDirectiveKeepOrder=${ShellCompDirective.ShellCompDirectiveKeepOrder}
148148+149149+ local lastParam lastChar flagPrefix requestComp out directive comp lastComp noSpace keepOrder
150150+ local -a completions
151151+152152+ __${name}_debug "\\n========= starting completion logic =========="
153153+ __${name}_debug "CURRENT: \${CURRENT}, words[*]: \${words[*]}"
154154+155155+ # The user could have moved the cursor backwards on the command-line.
156156+ # We need to trigger completion from the \$CURRENT location, so we need
157157+ # to truncate the command-line (\$words) up to the \$CURRENT location.
158158+ # (We cannot use \$CURSOR as its value does not work when a command is an alias.)
159159+ words=( "\${=words[1,CURRENT]}" )
160160+ __${name}_debug "Truncated words[*]: \${words[*]},"
161161+162162+ lastParam=\${words[-1]}
163163+ lastChar=\${lastParam[-1]}
164164+ __${name}_debug "lastParam: \${lastParam}, lastChar: \${lastChar}"
165165+166166+ # For zsh, when completing a flag with an = (e.g., ${name} -n=<TAB>)
167167+ # completions must be prefixed with the flag
168168+ setopt local_options BASH_REMATCH
169169+ if [[ "\${lastParam}" =~ '-.*=' ]]; then
170170+ # We are dealing with a flag with an =
171171+ flagPrefix="-P \${BASH_REMATCH}"
172172+ fi
173173+174174+ # Prepare the command to obtain completions
175175+ requestComp="${x} ${compCmd} -- \${words[2,-1]}"
176176+ if [ "\${lastChar}" = "" ]; then
177177+ # If the last parameter is complete (there is a space following it)
178178+ # We add an extra empty parameter so we can indicate this to the go completion code.
179179+ __${name}_debug "Adding extra empty parameter"
180180+ requestComp="\${requestComp} \"\""
181181+ fi
218233-const cli = cac('cac')
183183+ __${name}_debug "About to call: eval \${requestComp}"
418455-cli.option('--type [type]', 'Choose a project type', {
66- default: 'node',
77-})
88-cli.option('--name <name>', 'Provide your name')
185185+ # Use eval to handle any environment variables and such
186186+ out=\$(eval \${requestComp} 2>/dev/null)
187187+ __${name}_debug "completion output: \${out}"
188188+189189+ # Extract the directive integer following a : from the last line
190190+ local lastLine
191191+ while IFS='\n' read -r line; do
192192+ lastLine=\${line}
193193+ done < <(printf "%s\n" "\${out[@]}")
194194+ __${name}_debug "last line: \${lastLine}"
195195+196196+ if [ "\${lastLine[1]}" = : ]; then
197197+ directive=\${lastLine[2,-1]}
198198+ # Remove the directive including the : and the newline
199199+ local suffix
200200+ (( suffix=\${#lastLine}+2))
201201+ out=\${out[1,-\$suffix]}
202202+ else
203203+ # There is no directive specified. Leave \$out as is.
204204+ __${name}_debug "No directive found. Setting to default"
205205+ directive=0
206206+ fi
207207+208208+ __${name}_debug "directive: \${directive}"
209209+ __${name}_debug "completions: \${out}"
210210+ __${name}_debug "flagPrefix: \${flagPrefix}"
211211+212212+ if [ \$((directive & shellCompDirectiveError)) -ne 0 ]; then
213213+ __${name}_debug "Completion received error. Ignoring completions."
214214+ return
215215+ fi
92161010-cli.command('lint [...files]', 'Lint files').action((files, options) => {
1111- console.log(files, options)
1212-})
217217+ local activeHelpMarker="%"
218218+ local endIndex=\${#activeHelpMarker}
219219+ local startIndex=\$((\${#activeHelpMarker}+1))
220220+ local hasActiveHelp=0
221221+ while IFS='\n' read -r comp; do
222222+ # Check if this is an activeHelp statement (i.e., prefixed with \$activeHelpMarker)
223223+ if [ "\${comp[1,\$endIndex]}" = "\$activeHelpMarker" ];then
224224+ __${name}_debug "ActiveHelp found: \$comp"
225225+ comp="\${comp[\$startIndex,-1]}"
226226+ if [ -n "\$comp" ]; then
227227+ compadd -x "\${comp}"
228228+ __${name}_debug "ActiveHelp will need delimiter"
229229+ hasActiveHelp=1
230230+ fi
231231+ continue
232232+ fi
132331414-// Display help message when `-h` or `--help` appears
1515-cli.help()
1616-// Display version number when `-v` or `--version` appears
1717-// It's also used in help message
1818-cli.version('0.0.0')
234234+ if [ -n "\$comp" ]; then
235235+ # If requested, completions are returned with a description.
236236+ # The description is preceded by a TAB character.
237237+ # For zsh's _describe, we need to use a : instead of a TAB.
238238+ # We first need to escape any : as part of the completion itself.
239239+ comp=\${comp//:/\\:}
192402020-console.log('cli', cli)
241241+ local tab="\$(printf '\\t')"
242242+ comp=\${comp//\$tab/:}
21243244244+ __${name}_debug "Adding completion: \${comp}"
245245+ completions+=\${comp}
246246+ lastComp=\$comp
247247+ fi
248248+ done < <(printf "%s\n" "\${out[@]}")
22249250250+ # Add a delimiter after the activeHelp statements, but only if:
251251+ # - there are completions following the activeHelp statements, or
252252+ # - file completion will be performed (so there will be choices after the activeHelp)
253253+ if [ \$hasActiveHelp -eq 1 ]; then
254254+ if [ \${#completions} -ne 0 ] || [ \$((directive & shellCompDirectiveNoFileComp)) -eq 0 ]; then
255255+ __${name}_debug "Adding activeHelp delimiter"
256256+ compadd -x "--"
257257+ hasActiveHelp=0
258258+ fi
259259+ fi
260260+261261+ if [ \$((directive & shellCompDirectiveNoSpace)) -ne 0 ]; then
262262+ __${name}_debug "Activating nospace."
263263+ noSpace="-S ''"
264264+ fi
265265+266266+ if [ \$((directive & shellCompDirectiveKeepOrder)) -ne 0 ]; then
267267+ __${name}_debug "Activating keep order."
268268+ keepOrder="-V"
269269+ fi
270270+271271+ if [ \$((directive & shellCompDirectiveFilterFileExt)) -ne 0 ]; then
272272+ # File extension filtering
273273+ local filteringCmd
274274+ filteringCmd='_files'
275275+ for filter in \${completions[@]}; do
276276+ if [ \${filter[1]} != '*' ]; then
277277+ # zsh requires a glob pattern to do file filtering
278278+ filter="\\*.\$filter"
279279+ fi
280280+ filteringCmd+=" -g \$filter"
281281+ done
282282+ filteringCmd+=" \${flagPrefix}"
283283+284284+ __${name}_debug "File filtering command: \$filteringCmd"
285285+ _arguments '*:filename:'"\$filteringCmd"
286286+ elif [ \$((directive & shellCompDirectiveFilterDirs)) -ne 0 ]; then
287287+ # File completion for directories only
288288+ local subdir
289289+ subdir="\${completions[1]}"
290290+ if [ -n "\$subdir" ]; then
291291+ __${name}_debug "Listing directories in \$subdir"
292292+ pushd "\${subdir}" >/dev/null 2>&1
293293+ else
294294+ __${name}_debug "Listing directories in ."
295295+ fi
296296+297297+ local result
298298+ _arguments '*:dirname:_files -/'" \${flagPrefix}"
299299+ result=\$?
300300+ if [ -n "\$subdir" ]; then
301301+ popd >/dev/null 2>&1
302302+ fi
303303+ return \$result
304304+ else
305305+ __${name}_debug "Calling _describe"
306306+ if eval _describe \$keepOrder "completions" completions \${flagPrefix} \${noSpace}; then
307307+ __${name}_debug "_describe found some completions"
308308+309309+ # Return the success of having called _describe
310310+ return 0
311311+ else
312312+ __${name}_debug "_describe did not find completions."
313313+ __${name}_debug "Checking if we should do file completion."
314314+ if [ \$((directive & shellCompDirectiveNoFileComp)) -ne 0 ]; then
315315+ __${name}_debug "deactivating file completion"
316316+317317+ # We must return an error code here to let zsh know that there were no
318318+ # completions found by _describe; this is what will trigger other
319319+ # matching algorithms to attempt to find completions.
320320+ # For example zsh can match letters in the middle of words.
321321+ return 1
322322+ else
323323+ # Perform file completion
324324+ __${name}_debug "Activating file completion"
325325+326326+ # We must return the result of this command, so it must be the
327327+ # last command, or else we must store its result to return it.
328328+ _arguments '*:filename:_files'" \${flagPrefix}"
329329+ fi
330330+ fi
331331+ fi
332332+}
333333+334334+# don't run the completion function when being sourced or eval-ed
335335+if [ "\${funcstack[1]}" = "_${name}" ]; then
336336+ _${name}
337337+fi
338338+`;
339339+}
340340+341341+cli.parse()