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
14	"github.com/posener/complete/cmd"
15	"github.com/posener/complete/match"
16)
17
18const (
19	envComplete = "COMP_LINE"
20	envDebug    = "COMP_DEBUG"
21)
22
23// Complete structs define completion for a command with CLI options
24type Complete struct {
25	Command Command
26	cmd.CLI
27	Out io.Writer
28}
29
30// New creates a new complete command.
31// name is the name of command we want to auto complete.
32// IMPORTANT: it must be the same name - if the auto complete
33// completes the 'go' command, name must be equal to "go".
34// command is the struct of the command completion.
35func New(name string, command Command) *Complete {
36	return &Complete{
37		Command: command,
38		CLI:     cmd.CLI{Name: name},
39		Out:     os.Stdout,
40	}
41}
42
43// Run runs the completion and add installation flags beforehand.
44// The flags are added to the main flag CommandLine variable.
45func (c *Complete) Run() bool {
46	c.AddFlags(nil)
47	flag.Parse()
48	return c.Complete()
49}
50
51// Complete a command from completion line in environment variable,
52// and print out the complete options.
53// returns success if the completion ran or if the cli matched
54// any of the given flags, false otherwise
55// For installation: it assumes that flags were added and parsed before
56// it was called.
57func (c *Complete) Complete() bool {
58	line, ok := getLine()
59	if !ok {
60		// make sure flags parsed,
61		// in case they were not added in the main program
62		return c.CLI.Run()
63	}
64	Log("Completing line: %s", line)
65	a := newArgs(line)
66	Log("Completing last field: %s", a.Last)
67	options := c.Command.Predict(a)
68	Log("Options: %s", options)
69
70	// filter only options that match the last argument
71	matches := []string{}
72	for _, option := range options {
73		if match.Prefix(option, a.Last) {
74			matches = append(matches, option)
75		}
76	}
77	Log("Matches: %s", matches)
78	c.output(matches)
79	return true
80}
81
82func getLine() (string, bool) {
83	line := os.Getenv(envComplete)
84	if line == "" {
85		return "", false
86	}
87	return line, true
88}
89
90func (c *Complete) output(options []string) {
91	// stdout of program defines the complete options
92	for _, option := range options {
93		fmt.Fprintln(c.Out, option)
94	}
95}
96