...

Source file src/github.com/urfave/cli/v2/fish.go

Documentation: github.com/urfave/cli/v2

     1  package cli
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io"
     7  	"strings"
     8  	"text/template"
     9  )
    10  
    11  // ToFishCompletion creates a fish completion string for the `*App`
    12  // The function errors if either parsing or writing of the string fails.
    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  	// Add global flags
    36  	completions := a.prepareFishFlags(a.VisibleFlags(), allCommands)
    37  
    38  	// Add help flag
    39  	if !a.HideHelp {
    40  		completions = append(
    41  			completions,
    42  			a.prepareFishFlags([]Flag{HelpFlag}, allCommands)...,
    43  		)
    44  	}
    45  
    46  	// Add version flag
    47  	if !a.HideVersion {
    48  		completions = append(
    49  			completions,
    50  			a.prepareFishFlags([]Flag{VersionFlag}, allCommands)...,
    51  		)
    52  	}
    53  
    54  	// Add commands and their flags
    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  		// recursively iterate subcommands
   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