| package cli |
| |
| import ( |
| "bytes" |
| "fmt" |
| "io" |
| "strings" |
| "text/template" |
| ) |
| |
| // ToFishCompletion creates a fish completion string for the `*App` |
| // The function errors if either parsing or writing of the string fails. |
| func (a *App) ToFishCompletion() (string, error) { |
| var w bytes.Buffer |
| if err := a.writeFishCompletionTemplate(&w); err != nil { |
| return "", err |
| } |
| return w.String(), nil |
| } |
| |
| type fishCompletionTemplate struct { |
| App *App |
| Completions []string |
| AllCommands []string |
| } |
| |
| func (a *App) writeFishCompletionTemplate(w io.Writer) error { |
| const name = "cli" |
| t, err := template.New(name).Parse(FishCompletionTemplate) |
| if err != nil { |
| return err |
| } |
| allCommands := []string{} |
| |
| // Add global flags |
| completions := a.prepareFishFlags(a.VisibleFlags(), allCommands) |
| |
| // Add help flag |
| if !a.HideHelp { |
| completions = append( |
| completions, |
| a.prepareFishFlags([]Flag{HelpFlag}, allCommands)..., |
| ) |
| } |
| |
| // Add version flag |
| if !a.HideVersion { |
| completions = append( |
| completions, |
| a.prepareFishFlags([]Flag{VersionFlag}, allCommands)..., |
| ) |
| } |
| |
| // Add commands and their flags |
| completions = append( |
| completions, |
| a.prepareFishCommands(a.VisibleCommands(), &allCommands, []string{})..., |
| ) |
| |
| return t.ExecuteTemplate(w, name, &fishCompletionTemplate{ |
| App: a, |
| Completions: completions, |
| AllCommands: allCommands, |
| }) |
| } |
| |
| func (a *App) prepareFishCommands(commands []*Command, allCommands *[]string, previousCommands []string) []string { |
| completions := []string{} |
| for _, command := range commands { |
| if command.Hidden { |
| continue |
| } |
| |
| var completion strings.Builder |
| completion.WriteString(fmt.Sprintf( |
| "complete -r -c %s -n '%s' -a '%s'", |
| a.Name, |
| a.fishSubcommandHelper(previousCommands), |
| strings.Join(command.Names(), " "), |
| )) |
| |
| if command.Usage != "" { |
| completion.WriteString(fmt.Sprintf(" -d '%s'", |
| escapeSingleQuotes(command.Usage))) |
| } |
| |
| if !command.HideHelp { |
| completions = append( |
| completions, |
| a.prepareFishFlags([]Flag{HelpFlag}, command.Names())..., |
| ) |
| } |
| |
| *allCommands = append(*allCommands, command.Names()...) |
| completions = append(completions, completion.String()) |
| completions = append( |
| completions, |
| a.prepareFishFlags(command.Flags, command.Names())..., |
| ) |
| |
| // recursevly iterate subcommands |
| if len(command.Subcommands) > 0 { |
| completions = append( |
| completions, |
| a.prepareFishCommands( |
| command.Subcommands, allCommands, command.Names(), |
| )..., |
| ) |
| } |
| } |
| |
| return completions |
| } |
| |
| func (a *App) prepareFishFlags(flags []Flag, previousCommands []string) []string { |
| completions := []string{} |
| for _, f := range flags { |
| flag, ok := f.(DocGenerationFlag) |
| if !ok { |
| continue |
| } |
| |
| completion := &strings.Builder{} |
| completion.WriteString(fmt.Sprintf( |
| "complete -c %s -n '%s'", |
| a.Name, |
| a.fishSubcommandHelper(previousCommands), |
| )) |
| |
| fishAddFileFlag(f, completion) |
| |
| for idx, opt := range flag.Names() { |
| if idx == 0 { |
| completion.WriteString(fmt.Sprintf( |
| " -l %s", strings.TrimSpace(opt), |
| )) |
| } else { |
| completion.WriteString(fmt.Sprintf( |
| " -s %s", strings.TrimSpace(opt), |
| )) |
| |
| } |
| } |
| |
| if flag.TakesValue() { |
| completion.WriteString(" -r") |
| } |
| |
| if flag.GetUsage() != "" { |
| completion.WriteString(fmt.Sprintf(" -d '%s'", |
| escapeSingleQuotes(flag.GetUsage()))) |
| } |
| |
| completions = append(completions, completion.String()) |
| } |
| |
| return completions |
| } |
| |
| func fishAddFileFlag(flag Flag, completion *strings.Builder) { |
| switch f := flag.(type) { |
| case *GenericFlag: |
| if f.TakesFile { |
| return |
| } |
| case *StringFlag: |
| if f.TakesFile { |
| return |
| } |
| case *StringSliceFlag: |
| if f.TakesFile { |
| return |
| } |
| } |
| completion.WriteString(" -f") |
| } |
| |
| func (a *App) fishSubcommandHelper(allCommands []string) string { |
| fishHelper := fmt.Sprintf("__fish_%s_no_subcommand", a.Name) |
| if len(allCommands) > 0 { |
| fishHelper = fmt.Sprintf( |
| "__fish_seen_subcommand_from %s", |
| strings.Join(allCommands, " "), |
| ) |
| } |
| return fishHelper |
| |
| } |
| |
| func escapeSingleQuotes(input string) string { |
| return strings.Replace(input, `'`, `\'`, -1) |
| } |