github.com/gechr/complete@v0.0.0-20191016221035-401475e3ce1e/complete.go (about)

     1  package complete
     2  
     3  import (
     4  	"flag"
     5  	"fmt"
     6  	"io"
     7  	"os"
     8  	"strconv"
     9  
    10  	"github.com/gechr/complete/cmd"
    11  	"github.com/sahilm/fuzzy"
    12  )
    13  
    14  const (
    15  	envLine  = "COMP_LINE"
    16  	envPoint = "COMP_POINT"
    17  	envDebug = "COMP_DEBUG"
    18  )
    19  
    20  // Complete structs define completion for a command with CLI options
    21  type Complete struct {
    22  	Command Command
    23  	cmd.CLI
    24  	Out io.Writer
    25  }
    26  
    27  // New creates a new complete command.
    28  // name is the name of command we want to auto complete.
    29  // IMPORTANT: it must be the same name - if the auto complete
    30  // completes the 'go' command, name must be equal to "go".
    31  // command is the struct of the command completion.
    32  func New(name string, command Command) *Complete {
    33  	return &Complete{
    34  		Command: command,
    35  		CLI:     cmd.CLI{Name: name},
    36  		Out:     os.Stdout,
    37  	}
    38  }
    39  
    40  // Run runs the completion and add installation flags beforehand.
    41  // The flags are added to the main flag CommandLine variable.
    42  func (c *Complete) Run() bool {
    43  	c.AddFlags(nil)
    44  	flag.Parse()
    45  	return c.Complete()
    46  }
    47  
    48  // Complete a command from completion line in environment variable,
    49  // and print out the complete options.
    50  // returns success if the completion ran or if the cli matched
    51  // any of the given flags, false otherwise
    52  // For installation: it assumes that flags were added and parsed before
    53  // it was called.
    54  func (c *Complete) Complete() bool {
    55  	line, point, ok := getEnv()
    56  	if !ok {
    57  		// make sure flags parsed,
    58  		// in case they were not added in the main program
    59  		return c.CLI.Run()
    60  	}
    61  
    62  	if point >= 0 && point < len(line) {
    63  		line = line[:point]
    64  	}
    65  
    66  	Log("Completing phrase: %s", line)
    67  	a := newArgs(line)
    68  	Log("Completing last field: %s", a.Last)
    69  	options := c.Command.Predict(a)
    70  	Log("Options: %s", options)
    71  
    72  	// filter only options that match the last argument
    73  	if a.Last == "" {
    74  		Log("Matches: %s", options)
    75  		c.output(options)
    76  		return true
    77  	}
    78  	fuzzyMatches := fuzzy.Find(a.Last, options)
    79  	matches := make([]string, len(fuzzyMatches))
    80  	for i, fm := range fuzzyMatches {
    81  		matches[i] = fm.Str
    82  	}
    83  	Log("Matches: %s", matches)
    84  	c.output(matches)
    85  	return true
    86  }
    87  
    88  func getEnv() (line string, point int, ok bool) {
    89  	line = os.Getenv(envLine)
    90  	if line == "" {
    91  		return
    92  	}
    93  	point, err := strconv.Atoi(os.Getenv(envPoint))
    94  	if err != nil {
    95  		// If failed parsing point for some reason, set it to point
    96  		// on the end of the line.
    97  		Log("Failed parsing point %s: %v", os.Getenv(envPoint), err)
    98  		point = len(line)
    99  	}
   100  	return line, point, true
   101  }
   102  
   103  func (c *Complete) output(options []string) {
   104  	// stdout of program defines the complete options
   105  	for _, option := range options {
   106  		fmt.Fprintln(c.Out, option)
   107  	}
   108  }