56 [[ $w = "$word" ]] && return |
56 [[ $w = "$word" ]] && return |
57 done |
57 done |
58 return 1 |
58 return 1 |
59 } |
59 } |
60 |
60 |
|
61 __%[1]s_handle_go_custom_completion() |
|
62 { |
|
63 __%[1]s_debug "${FUNCNAME[0]}: cur is ${cur}, words[*] is ${words[*]}, #words[@] is ${#words[@]}" |
|
64 |
|
65 local shellCompDirectiveError=%[3]d |
|
66 local shellCompDirectiveNoSpace=%[4]d |
|
67 local shellCompDirectiveNoFileComp=%[5]d |
|
68 local shellCompDirectiveFilterFileExt=%[6]d |
|
69 local shellCompDirectiveFilterDirs=%[7]d |
|
70 |
|
71 local out requestComp lastParam lastChar comp directive args |
|
72 |
|
73 # Prepare the command to request completions for the program. |
|
74 # Calling ${words[0]} instead of directly %[1]s allows to handle aliases |
|
75 args=("${words[@]:1}") |
|
76 requestComp="${words[0]} %[2]s ${args[*]}" |
|
77 |
|
78 lastParam=${words[$((${#words[@]}-1))]} |
|
79 lastChar=${lastParam:$((${#lastParam}-1)):1} |
|
80 __%[1]s_debug "${FUNCNAME[0]}: lastParam ${lastParam}, lastChar ${lastChar}" |
|
81 |
|
82 if [ -z "${cur}" ] && [ "${lastChar}" != "=" ]; then |
|
83 # If the last parameter is complete (there is a space following it) |
|
84 # We add an extra empty parameter so we can indicate this to the go method. |
|
85 __%[1]s_debug "${FUNCNAME[0]}: Adding extra empty parameter" |
|
86 requestComp="${requestComp} \"\"" |
|
87 fi |
|
88 |
|
89 __%[1]s_debug "${FUNCNAME[0]}: calling ${requestComp}" |
|
90 # Use eval to handle any environment variables and such |
|
91 out=$(eval "${requestComp}" 2>/dev/null) |
|
92 |
|
93 # Extract the directive integer at the very end of the output following a colon (:) |
|
94 directive=${out##*:} |
|
95 # Remove the directive |
|
96 out=${out%%:*} |
|
97 if [ "${directive}" = "${out}" ]; then |
|
98 # There is not directive specified |
|
99 directive=0 |
|
100 fi |
|
101 __%[1]s_debug "${FUNCNAME[0]}: the completion directive is: ${directive}" |
|
102 __%[1]s_debug "${FUNCNAME[0]}: the completions are: ${out[*]}" |
|
103 |
|
104 if [ $((directive & shellCompDirectiveError)) -ne 0 ]; then |
|
105 # Error code. No completion. |
|
106 __%[1]s_debug "${FUNCNAME[0]}: received error from custom completion go code" |
|
107 return |
|
108 else |
|
109 if [ $((directive & shellCompDirectiveNoSpace)) -ne 0 ]; then |
|
110 if [[ $(type -t compopt) = "builtin" ]]; then |
|
111 __%[1]s_debug "${FUNCNAME[0]}: activating no space" |
|
112 compopt -o nospace |
|
113 fi |
|
114 fi |
|
115 if [ $((directive & shellCompDirectiveNoFileComp)) -ne 0 ]; then |
|
116 if [[ $(type -t compopt) = "builtin" ]]; then |
|
117 __%[1]s_debug "${FUNCNAME[0]}: activating no file completion" |
|
118 compopt +o default |
|
119 fi |
|
120 fi |
|
121 fi |
|
122 |
|
123 if [ $((directive & shellCompDirectiveFilterFileExt)) -ne 0 ]; then |
|
124 # File extension filtering |
|
125 local fullFilter filter filteringCmd |
|
126 # Do not use quotes around the $out variable or else newline |
|
127 # characters will be kept. |
|
128 for filter in ${out[*]}; do |
|
129 fullFilter+="$filter|" |
|
130 done |
|
131 |
|
132 filteringCmd="_filedir $fullFilter" |
|
133 __%[1]s_debug "File filtering command: $filteringCmd" |
|
134 $filteringCmd |
|
135 elif [ $((directive & shellCompDirectiveFilterDirs)) -ne 0 ]; then |
|
136 # File completion for directories only |
|
137 local subDir |
|
138 # Use printf to strip any trailing newline |
|
139 subdir=$(printf "%%s" "${out[0]}") |
|
140 if [ -n "$subdir" ]; then |
|
141 __%[1]s_debug "Listing directories in $subdir" |
|
142 __%[1]s_handle_subdirs_in_dir_flag "$subdir" |
|
143 else |
|
144 __%[1]s_debug "Listing directories in ." |
|
145 _filedir -d |
|
146 fi |
|
147 else |
|
148 while IFS='' read -r comp; do |
|
149 COMPREPLY+=("$comp") |
|
150 done < <(compgen -W "${out[*]}" -- "$cur") |
|
151 fi |
|
152 } |
|
153 |
61 __%[1]s_handle_reply() |
154 __%[1]s_handle_reply() |
62 { |
155 { |
63 __%[1]s_debug "${FUNCNAME[0]}" |
156 __%[1]s_debug "${FUNCNAME[0]}" |
|
157 local comp |
64 case $cur in |
158 case $cur in |
65 -*) |
159 -*) |
66 if [[ $(type -t compopt) = "builtin" ]]; then |
160 if [[ $(type -t compopt) = "builtin" ]]; then |
67 compopt -o nospace |
161 compopt -o nospace |
68 fi |
162 fi |
293 local two_word_flags=() |
398 local two_word_flags=() |
294 local local_nonpersistent_flags=() |
399 local local_nonpersistent_flags=() |
295 local flags_with_completion=() |
400 local flags_with_completion=() |
296 local flags_completion=() |
401 local flags_completion=() |
297 local commands=("%[1]s") |
402 local commands=("%[1]s") |
|
403 local command_aliases=() |
298 local must_have_one_flag=() |
404 local must_have_one_flag=() |
299 local must_have_one_noun=() |
405 local must_have_one_noun=() |
|
406 local has_completion_function |
300 local last_command |
407 local last_command |
301 local nouns=() |
408 local nouns=() |
|
409 local noun_aliases=() |
302 |
410 |
303 __%[1]s_handle_word |
411 __%[1]s_handle_word |
304 } |
412 } |
305 |
413 |
306 `, name)) |
414 `, name)) |
307 buf.WriteString(fmt.Sprintf(`if [[ $(type -t compopt) = "builtin" ]]; then |
415 WriteStringAndCheck(buf, fmt.Sprintf(`if [[ $(type -t compopt) = "builtin" ]]; then |
308 complete -o default -F __start_%s %s |
416 complete -o default -F __start_%s %s |
309 else |
417 else |
310 complete -o default -o nospace -F __start_%s %s |
418 complete -o default -o nospace -F __start_%s %s |
311 fi |
419 fi |
312 |
420 |
313 `, name, name, name, name)) |
421 `, name, name, name, name)) |
314 buf.WriteString("# ex: ts=4 sw=4 et filetype=sh\n") |
422 WriteStringAndCheck(buf, "# ex: ts=4 sw=4 et filetype=sh\n") |
315 } |
423 } |
316 |
424 |
317 func writeCommands(buf *bytes.Buffer, cmd *Command) { |
425 func writeCommands(buf io.StringWriter, cmd *Command) { |
318 buf.WriteString(" commands=()\n") |
426 WriteStringAndCheck(buf, " commands=()\n") |
319 for _, c := range cmd.Commands() { |
427 for _, c := range cmd.Commands() { |
320 if !c.IsAvailableCommand() || c == cmd.helpCommand { |
428 if !c.IsAvailableCommand() && c != cmd.helpCommand { |
321 continue |
429 continue |
322 } |
430 } |
323 buf.WriteString(fmt.Sprintf(" commands+=(%q)\n", c.Name())) |
431 WriteStringAndCheck(buf, fmt.Sprintf(" commands+=(%q)\n", c.Name())) |
324 writeCmdAliases(buf, c) |
432 writeCmdAliases(buf, c) |
325 } |
433 } |
326 buf.WriteString("\n") |
434 WriteStringAndCheck(buf, "\n") |
327 } |
435 } |
328 |
436 |
329 func writeFlagHandler(buf *bytes.Buffer, name string, annotations map[string][]string, cmd *Command) { |
437 func writeFlagHandler(buf io.StringWriter, name string, annotations map[string][]string, cmd *Command) { |
330 for key, value := range annotations { |
438 for key, value := range annotations { |
331 switch key { |
439 switch key { |
332 case BashCompFilenameExt: |
440 case BashCompFilenameExt: |
333 buf.WriteString(fmt.Sprintf(" flags_with_completion+=(%q)\n", name)) |
441 WriteStringAndCheck(buf, fmt.Sprintf(" flags_with_completion+=(%q)\n", name)) |
334 |
442 |
335 var ext string |
443 var ext string |
336 if len(value) > 0 { |
444 if len(value) > 0 { |
337 ext = fmt.Sprintf("__%s_handle_filename_extension_flag ", cmd.Root().Name()) + strings.Join(value, "|") |
445 ext = fmt.Sprintf("__%s_handle_filename_extension_flag ", cmd.Root().Name()) + strings.Join(value, "|") |
338 } else { |
446 } else { |
339 ext = "_filedir" |
447 ext = "_filedir" |
340 } |
448 } |
341 buf.WriteString(fmt.Sprintf(" flags_completion+=(%q)\n", ext)) |
449 WriteStringAndCheck(buf, fmt.Sprintf(" flags_completion+=(%q)\n", ext)) |
342 case BashCompCustom: |
450 case BashCompCustom: |
343 buf.WriteString(fmt.Sprintf(" flags_with_completion+=(%q)\n", name)) |
451 WriteStringAndCheck(buf, fmt.Sprintf(" flags_with_completion+=(%q)\n", name)) |
|
452 |
344 if len(value) > 0 { |
453 if len(value) > 0 { |
345 handlers := strings.Join(value, "; ") |
454 handlers := strings.Join(value, "; ") |
346 buf.WriteString(fmt.Sprintf(" flags_completion+=(%q)\n", handlers)) |
455 WriteStringAndCheck(buf, fmt.Sprintf(" flags_completion+=(%q)\n", handlers)) |
347 } else { |
456 } else { |
348 buf.WriteString(" flags_completion+=(:)\n") |
457 WriteStringAndCheck(buf, " flags_completion+=(:)\n") |
349 } |
458 } |
350 case BashCompSubdirsInDir: |
459 case BashCompSubdirsInDir: |
351 buf.WriteString(fmt.Sprintf(" flags_with_completion+=(%q)\n", name)) |
460 WriteStringAndCheck(buf, fmt.Sprintf(" flags_with_completion+=(%q)\n", name)) |
352 |
461 |
353 var ext string |
462 var ext string |
354 if len(value) == 1 { |
463 if len(value) == 1 { |
355 ext = fmt.Sprintf("__%s_handle_subdirs_in_dir_flag ", cmd.Root().Name()) + value[0] |
464 ext = fmt.Sprintf("__%s_handle_subdirs_in_dir_flag ", cmd.Root().Name()) + value[0] |
356 } else { |
465 } else { |
357 ext = "_filedir -d" |
466 ext = "_filedir -d" |
358 } |
467 } |
359 buf.WriteString(fmt.Sprintf(" flags_completion+=(%q)\n", ext)) |
468 WriteStringAndCheck(buf, fmt.Sprintf(" flags_completion+=(%q)\n", ext)) |
360 } |
469 } |
361 } |
470 } |
362 } |
471 } |
363 |
472 |
364 func writeShortFlag(buf *bytes.Buffer, flag *pflag.Flag, cmd *Command) { |
473 const cbn = "\")\n" |
|
474 |
|
475 func writeShortFlag(buf io.StringWriter, flag *pflag.Flag, cmd *Command) { |
365 name := flag.Shorthand |
476 name := flag.Shorthand |
366 format := " " |
477 format := " " |
367 if len(flag.NoOptDefVal) == 0 { |
478 if len(flag.NoOptDefVal) == 0 { |
368 format += "two_word_" |
479 format += "two_word_" |
369 } |
480 } |
370 format += "flags+=(\"-%s\")\n" |
481 format += "flags+=(\"-%s" + cbn |
371 buf.WriteString(fmt.Sprintf(format, name)) |
482 WriteStringAndCheck(buf, fmt.Sprintf(format, name)) |
372 writeFlagHandler(buf, "-"+name, flag.Annotations, cmd) |
483 writeFlagHandler(buf, "-"+name, flag.Annotations, cmd) |
373 } |
484 } |
374 |
485 |
375 func writeFlag(buf *bytes.Buffer, flag *pflag.Flag, cmd *Command) { |
486 func writeFlag(buf io.StringWriter, flag *pflag.Flag, cmd *Command) { |
376 name := flag.Name |
487 name := flag.Name |
377 format := " flags+=(\"--%s" |
488 format := " flags+=(\"--%s" |
378 if len(flag.NoOptDefVal) == 0 { |
489 if len(flag.NoOptDefVal) == 0 { |
379 format += "=" |
490 format += "=" |
380 } |
491 } |
381 format += "\")\n" |
492 format += cbn |
382 buf.WriteString(fmt.Sprintf(format, name)) |
493 WriteStringAndCheck(buf, fmt.Sprintf(format, name)) |
383 if len(flag.NoOptDefVal) == 0 { |
494 if len(flag.NoOptDefVal) == 0 { |
384 format = " two_word_flags+=(\"--%s\")\n" |
495 format = " two_word_flags+=(\"--%s" + cbn |
385 buf.WriteString(fmt.Sprintf(format, name)) |
496 WriteStringAndCheck(buf, fmt.Sprintf(format, name)) |
386 } |
497 } |
387 writeFlagHandler(buf, "--"+name, flag.Annotations, cmd) |
498 writeFlagHandler(buf, "--"+name, flag.Annotations, cmd) |
388 } |
499 } |
389 |
500 |
390 func writeLocalNonPersistentFlag(buf *bytes.Buffer, flag *pflag.Flag) { |
501 func writeLocalNonPersistentFlag(buf io.StringWriter, flag *pflag.Flag) { |
391 name := flag.Name |
502 name := flag.Name |
392 format := " local_nonpersistent_flags+=(\"--%s" |
503 format := " local_nonpersistent_flags+=(\"--%[1]s" + cbn |
393 if len(flag.NoOptDefVal) == 0 { |
504 if len(flag.NoOptDefVal) == 0 { |
394 format += "=" |
505 format += " local_nonpersistent_flags+=(\"--%[1]s=" + cbn |
395 } |
506 } |
396 format += "\")\n" |
507 WriteStringAndCheck(buf, fmt.Sprintf(format, name)) |
397 buf.WriteString(fmt.Sprintf(format, name)) |
508 if len(flag.Shorthand) > 0 { |
398 } |
509 WriteStringAndCheck(buf, fmt.Sprintf(" local_nonpersistent_flags+=(\"-%s\")\n", flag.Shorthand)) |
399 |
510 } |
400 func writeFlags(buf *bytes.Buffer, cmd *Command) { |
511 } |
401 buf.WriteString(` flags=() |
512 |
|
513 // Setup annotations for go completions for registered flags |
|
514 func prepareCustomAnnotationsForFlags(cmd *Command) { |
|
515 flagCompletionMutex.RLock() |
|
516 defer flagCompletionMutex.RUnlock() |
|
517 for flag := range flagCompletionFunctions { |
|
518 // Make sure the completion script calls the __*_go_custom_completion function for |
|
519 // every registered flag. We need to do this here (and not when the flag was registered |
|
520 // for completion) so that we can know the root command name for the prefix |
|
521 // of __<prefix>_go_custom_completion |
|
522 if flag.Annotations == nil { |
|
523 flag.Annotations = map[string][]string{} |
|
524 } |
|
525 flag.Annotations[BashCompCustom] = []string{fmt.Sprintf("__%[1]s_handle_go_custom_completion", cmd.Root().Name())} |
|
526 } |
|
527 } |
|
528 |
|
529 func writeFlags(buf io.StringWriter, cmd *Command) { |
|
530 prepareCustomAnnotationsForFlags(cmd) |
|
531 WriteStringAndCheck(buf, ` flags=() |
402 two_word_flags=() |
532 two_word_flags=() |
403 local_nonpersistent_flags=() |
533 local_nonpersistent_flags=() |
404 flags_with_completion=() |
534 flags_with_completion=() |
405 flags_completion=() |
535 flags_completion=() |
406 |
536 |
443 case BashCompOneRequiredFlag: |
575 case BashCompOneRequiredFlag: |
444 format := " must_have_one_flag+=(\"--%s" |
576 format := " must_have_one_flag+=(\"--%s" |
445 if flag.Value.Type() != "bool" { |
577 if flag.Value.Type() != "bool" { |
446 format += "=" |
578 format += "=" |
447 } |
579 } |
448 format += "\")\n" |
580 format += cbn |
449 buf.WriteString(fmt.Sprintf(format, flag.Name)) |
581 WriteStringAndCheck(buf, fmt.Sprintf(format, flag.Name)) |
450 |
582 |
451 if len(flag.Shorthand) > 0 { |
583 if len(flag.Shorthand) > 0 { |
452 buf.WriteString(fmt.Sprintf(" must_have_one_flag+=(\"-%s\")\n", flag.Shorthand)) |
584 WriteStringAndCheck(buf, fmt.Sprintf(" must_have_one_flag+=(\"-%s"+cbn, flag.Shorthand)) |
453 } |
585 } |
454 } |
586 } |
455 } |
587 } |
456 }) |
588 }) |
457 } |
589 } |
458 |
590 |
459 func writeRequiredNouns(buf *bytes.Buffer, cmd *Command) { |
591 func writeRequiredNouns(buf io.StringWriter, cmd *Command) { |
460 buf.WriteString(" must_have_one_noun=()\n") |
592 WriteStringAndCheck(buf, " must_have_one_noun=()\n") |
461 sort.Sort(sort.StringSlice(cmd.ValidArgs)) |
593 sort.Strings(cmd.ValidArgs) |
462 for _, value := range cmd.ValidArgs { |
594 for _, value := range cmd.ValidArgs { |
463 buf.WriteString(fmt.Sprintf(" must_have_one_noun+=(%q)\n", value)) |
595 // Remove any description that may be included following a tab character. |
464 } |
596 // Descriptions are not supported by bash completion. |
465 } |
597 value = strings.Split(value, "\t")[0] |
466 |
598 WriteStringAndCheck(buf, fmt.Sprintf(" must_have_one_noun+=(%q)\n", value)) |
467 func writeCmdAliases(buf *bytes.Buffer, cmd *Command) { |
599 } |
|
600 if cmd.ValidArgsFunction != nil { |
|
601 WriteStringAndCheck(buf, " has_completion_function=1\n") |
|
602 } |
|
603 } |
|
604 |
|
605 func writeCmdAliases(buf io.StringWriter, cmd *Command) { |
468 if len(cmd.Aliases) == 0 { |
606 if len(cmd.Aliases) == 0 { |
469 return |
607 return |
470 } |
608 } |
471 |
609 |
472 sort.Sort(sort.StringSlice(cmd.Aliases)) |
610 sort.Strings(cmd.Aliases) |
473 |
611 |
474 buf.WriteString(fmt.Sprint(` if [[ -z "${BASH_VERSION}" || "${BASH_VERSINFO[0]}" -gt 3 ]]; then`, "\n")) |
612 WriteStringAndCheck(buf, fmt.Sprint(` if [[ -z "${BASH_VERSION}" || "${BASH_VERSINFO[0]}" -gt 3 ]]; then`, "\n")) |
475 for _, value := range cmd.Aliases { |
613 for _, value := range cmd.Aliases { |
476 buf.WriteString(fmt.Sprintf(" command_aliases+=(%q)\n", value)) |
614 WriteStringAndCheck(buf, fmt.Sprintf(" command_aliases+=(%q)\n", value)) |
477 buf.WriteString(fmt.Sprintf(" aliashash[%q]=%q\n", value, cmd.Name())) |
615 WriteStringAndCheck(buf, fmt.Sprintf(" aliashash[%q]=%q\n", value, cmd.Name())) |
478 } |
616 } |
479 buf.WriteString(` fi`) |
617 WriteStringAndCheck(buf, ` fi`) |
480 buf.WriteString("\n") |
618 WriteStringAndCheck(buf, "\n") |
481 } |
619 } |
482 func writeArgAliases(buf *bytes.Buffer, cmd *Command) { |
620 func writeArgAliases(buf io.StringWriter, cmd *Command) { |
483 buf.WriteString(" noun_aliases=()\n") |
621 WriteStringAndCheck(buf, " noun_aliases=()\n") |
484 sort.Sort(sort.StringSlice(cmd.ArgAliases)) |
622 sort.Strings(cmd.ArgAliases) |
485 for _, value := range cmd.ArgAliases { |
623 for _, value := range cmd.ArgAliases { |
486 buf.WriteString(fmt.Sprintf(" noun_aliases+=(%q)\n", value)) |
624 WriteStringAndCheck(buf, fmt.Sprintf(" noun_aliases+=(%q)\n", value)) |
487 } |
625 } |
488 } |
626 } |
489 |
627 |
490 func gen(buf *bytes.Buffer, cmd *Command) { |
628 func gen(buf io.StringWriter, cmd *Command) { |
491 for _, c := range cmd.Commands() { |
629 for _, c := range cmd.Commands() { |
492 if !c.IsAvailableCommand() || c == cmd.helpCommand { |
630 if !c.IsAvailableCommand() && c != cmd.helpCommand { |
493 continue |
631 continue |
494 } |
632 } |
495 gen(buf, c) |
633 gen(buf, c) |
496 } |
634 } |
497 commandName := cmd.CommandPath() |
635 commandName := cmd.CommandPath() |
498 commandName = strings.Replace(commandName, " ", "_", -1) |
636 commandName = strings.Replace(commandName, " ", "_", -1) |
499 commandName = strings.Replace(commandName, ":", "__", -1) |
637 commandName = strings.Replace(commandName, ":", "__", -1) |
500 |
638 |
501 if cmd.Root() == cmd { |
639 if cmd.Root() == cmd { |
502 buf.WriteString(fmt.Sprintf("_%s_root_command()\n{\n", commandName)) |
640 WriteStringAndCheck(buf, fmt.Sprintf("_%s_root_command()\n{\n", commandName)) |
503 } else { |
641 } else { |
504 buf.WriteString(fmt.Sprintf("_%s()\n{\n", commandName)) |
642 WriteStringAndCheck(buf, fmt.Sprintf("_%s()\n{\n", commandName)) |
505 } |
643 } |
506 |
644 |
507 buf.WriteString(fmt.Sprintf(" last_command=%q\n", commandName)) |
645 WriteStringAndCheck(buf, fmt.Sprintf(" last_command=%q\n", commandName)) |
508 buf.WriteString("\n") |
646 WriteStringAndCheck(buf, "\n") |
509 buf.WriteString(" command_aliases=()\n") |
647 WriteStringAndCheck(buf, " command_aliases=()\n") |
510 buf.WriteString("\n") |
648 WriteStringAndCheck(buf, "\n") |
511 |
649 |
512 writeCommands(buf, cmd) |
650 writeCommands(buf, cmd) |
513 writeFlags(buf, cmd) |
651 writeFlags(buf, cmd) |
514 writeRequiredFlag(buf, cmd) |
652 writeRequiredFlag(buf, cmd) |
515 writeRequiredNouns(buf, cmd) |
653 writeRequiredNouns(buf, cmd) |
516 writeArgAliases(buf, cmd) |
654 writeArgAliases(buf, cmd) |
517 buf.WriteString("}\n\n") |
655 WriteStringAndCheck(buf, "}\n\n") |
518 } |
656 } |
519 |
657 |
520 // GenBashCompletion generates bash completion file and writes to the passed writer. |
658 // GenBashCompletion generates bash completion file and writes to the passed writer. |
521 func (c *Command) GenBashCompletion(w io.Writer) error { |
659 func (c *Command) GenBashCompletion(w io.Writer) error { |
522 buf := new(bytes.Buffer) |
660 buf := new(bytes.Buffer) |