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