pkg.re/essentialkaos/ek.10@v12.41.0+incompatible/usage/completion/bash/bash.go (about)

     1  // Package bash provides methods for generating bash completion
     2  package bash
     3  
     4  // ////////////////////////////////////////////////////////////////////////////////// //
     5  //                                                                                    //
     6  //                         Copyright (c) 2022 ESSENTIAL KAOS                          //
     7  //      Apache License, Version 2.0 <https://www.apache.org/licenses/LICENSE-2.0>     //
     8  //                                                                                    //
     9  // ////////////////////////////////////////////////////////////////////////////////// //
    10  
    11  import (
    12  	"fmt"
    13  	"strings"
    14  
    15  	"pkg.re/essentialkaos/ek.v12/usage"
    16  )
    17  
    18  // ////////////////////////////////////////////////////////////////////////////////// //
    19  
    20  // _BASH_TEMPLATE is template used for completion generation
    21  const _BASH_TEMPLATE = `# Completion for {{COMPNAME}}
    22  # This completion is automatically generated
    23  
    24  _{{COMPNAME_SAFE}}() {
    25    local cur prev cmds opts show_files
    26  
    27    COMPREPLY=()
    28    cur="${COMP_WORDS[COMP_CWORD]}"
    29    prev="${COMP_WORDS[COMP_CWORD-1]}"
    30  
    31    cmds="{{COMMANDS}}"
    32    opts="{{GLOBAL_OPTIONS}}"
    33    show_files="{{SHOW_FILES}}"
    34    file_glob="{{FILE_GLOB}}"
    35  
    36  {{COMMANDS_HANDLERS}}
    37  
    38    if [[ $cur == -* ]] ; then
    39      COMPREPLY=($(compgen -W "$opts" -- "$cur"))
    40      return 0
    41    fi
    42  
    43    _{{COMPNAME_SAFE}}_filter "$cmds" "$opts" "$show_files" "$file_glob"
    44  }
    45  
    46  _{{COMPNAME_SAFE}}_filter() {
    47    local cmds="$1"
    48    local opts="$2"
    49    local show_files="$3"
    50    local file_glob="$4"
    51  
    52    local cmd1 cmd2
    53  
    54    for cmd1 in $cmds ; do
    55      for cmd2 in ${COMP_WORDS[*]} ; do
    56        if [[ "$cmd1" == "$cmd2" ]] ; then
    57          if [[ -z "$show_files" ]] ; then
    58            COMPREPLY=($(compgen -W "$opts" -- "$cur"))
    59          else
    60            _filedir "$file_glob"
    61          fi
    62  
    63          return 0
    64        fi
    65      done
    66    done
    67  
    68    if [[ -z "$show_files" ]] ; then
    69      COMPREPLY=($(compgen -W "$cmds" -- "$cur"))
    70      return 0
    71    fi
    72  
    73    _filedir "$file_glob"
    74  }
    75  
    76  complete -F _{{COMPNAME_SAFE}} {{COMPNAME}} {{COMP_OPTS}}
    77  `
    78  
    79  // ////////////////////////////////////////////////////////////////////////////////// //
    80  
    81  // Generate generates Bash completion code
    82  func Generate(info *usage.Info, name string, fileExt ...string) string {
    83  	result := _BASH_TEMPLATE
    84  
    85  	result = strings.Replace(result, "{{COMMANDS}}", genCommandsList(info), -1)
    86  	result = strings.Replace(result, "{{GLOBAL_OPTIONS}}", genGlobalOptionsList(info), -1)
    87  	result = strings.Replace(result, "{{COMMANDS_HANDLERS}}", genCommandsHandlers(info), -1)
    88  	result = strings.Replace(result, "{{COMPNAME}}", name, -1)
    89  
    90  	if len(info.Args) != 0 {
    91  		result = strings.Replace(result, "{{SHOW_FILES}}", "true", -1)
    92  		result = strings.Replace(result, "{{COMP_OPTS}}", "-o filenames", -1)
    93  		if len(fileExt) != 0 {
    94  			result = strings.Replace(result, "{{FILE_GLOB}}", fileExt[0], -1)
    95  		}
    96  	} else {
    97  		result = strings.Replace(result, "{{SHOW_FILES}}", "", -1)
    98  		result = strings.Replace(result, "{{COMP_OPTS}}", "", -1)
    99  		result = strings.Replace(result, "{{FILE_GLOB}}", "", -1)
   100  	}
   101  
   102  	nameSafe := strings.Replace(name, "-", "_", -1)
   103  	result = strings.Replace(result, "{{COMPNAME_SAFE}}", nameSafe, -1)
   104  
   105  	return result
   106  }
   107  
   108  // ////////////////////////////////////////////////////////////////////////////////// //
   109  
   110  // genGlobalOptionsList generates list with global options
   111  func genGlobalOptionsList(info *usage.Info) string {
   112  	var result []string
   113  
   114  	nonGlobalOptions := make(map[string]bool)
   115  
   116  	for _, cmd := range info.Commands {
   117  		for _, opt := range cmd.BoundOptions {
   118  			nonGlobalOptions[opt] = true
   119  		}
   120  	}
   121  
   122  	for _, opt := range info.Options {
   123  		if nonGlobalOptions[opt.Long] {
   124  			continue
   125  		}
   126  
   127  		result = append(result, "--"+opt.Long)
   128  	}
   129  
   130  	return strings.Join(result, " ")
   131  }
   132  
   133  // genCommandsHandlers generates command handler
   134  func genCommandsHandlers(info *usage.Info) string {
   135  	if !isCommandHandlersRequired(info) {
   136  		return ""
   137  	}
   138  
   139  	result := "  case $prev in\n"
   140  
   141  	for _, cmd := range info.Commands {
   142  		if len(cmd.BoundOptions) != 0 {
   143  			result += genCommandHandler(cmd, info)
   144  		}
   145  	}
   146  
   147  	result += "  esac"
   148  
   149  	return result
   150  }
   151  
   152  // genCommandHandler generates handler for given command
   153  func genCommandHandler(cmd *usage.Command, info *usage.Info) string {
   154  	result := fmt.Sprintf("    %s)\n", cmd.Name)
   155  
   156  	var options []string
   157  
   158  	for _, optName := range cmd.BoundOptions {
   159  		opt := info.GetOption(optName)
   160  
   161  		if opt == nil {
   162  			continue
   163  		}
   164  
   165  		options = append(options, "--"+opt.Long)
   166  	}
   167  
   168  	result += fmt.Sprintf("      opts=\"%s\"\n", strings.Join(options, " "))
   169  	result += "      COMPREPLY=($(compgen -W \"$opts\" -- \"$cur\"))\n"
   170  	result += "      return 0\n"
   171  	result += "      ;;\n\n"
   172  
   173  	return result
   174  }
   175  
   176  // getCommandsList returns slice with available commands
   177  func genCommandsList(info *usage.Info) string {
   178  	var result []string
   179  
   180  	for _, command := range info.Commands {
   181  		result = append(result, command.Name)
   182  	}
   183  
   184  	return strings.Join(result, " ")
   185  }
   186  
   187  // isCommandHandlersRequired returns true if commands have bound options
   188  func isCommandHandlersRequired(info *usage.Info) bool {
   189  	for _, cmd := range info.Commands {
   190  		if len(cmd.BoundOptions) != 0 {
   191  			return true
   192  		}
   193  	}
   194  
   195  	return false
   196  }