1package command
2
3import (
4	"bytes"
5	"flag"
6	"fmt"
7	"io"
8	"os"
9	"strings"
10
11	"github.com/hashicorp/errwrap"
12	"github.com/hashicorp/go-sockaddr/template"
13	"github.com/mitchellh/cli"
14)
15
16type EvalCommand struct {
17	Ui cli.Ui
18
19	// debugOutput emits framed output vs raw output.
20	debugOutput bool
21
22	// flags is a list of options belonging to this command
23	flags *flag.FlagSet
24
25	// rawInput disables wrapping the string in the text/template {{ }}
26	// handlebars.
27	rawInput bool
28
29	// suppressNewline changes whether or not there's a newline between each
30	// arg passed to the eval subcommand.
31	suppressNewline bool
32}
33
34// Description is the long-form command help.
35func (c *EvalCommand) Description() string {
36	return `Parse the sockaddr template and evaluates the output.
37
38` + "The `sockaddr` library has the potential to be very complex, which is why the " +
39		"`sockaddr` command supports an `eval` subcommand in order to test configurations " +
40		"from the command line.  The `eval` subcommand automatically wraps its input with " +
41		"the `{{` and `}}` template delimiters unless the `-r` command is specified, in " +
42		"which case `eval` parses the raw input.  If the `template` argument passed to " +
43		"`eval` is a dash (`-`), then `sockaddr eval` will read from stdin and " +
44		"automatically sets the `-r` flag."
45
46}
47
48// Help returns the full help output expected by `sockaddr -h cmd`
49func (c *EvalCommand) Help() string {
50	return MakeHelp(c)
51}
52
53// InitOpts is responsible for setup of this command's configuration via the
54// command line.  InitOpts() does not parse the arguments (see parseOpts()).
55func (c *EvalCommand) InitOpts() {
56	c.flags = flag.NewFlagSet("eval", flag.ContinueOnError)
57	c.flags.Usage = func() { c.Ui.Output(c.Help()) }
58	c.flags.BoolVar(&c.debugOutput, "d", false, "Debug output")
59	c.flags.BoolVar(&c.suppressNewline, "n", false, "Suppress newlines between args")
60	c.flags.BoolVar(&c.rawInput, "r", false, "Suppress wrapping the input with {{ }} delimiters")
61}
62
63// Run executes this command.
64func (c *EvalCommand) Run(args []string) int {
65	if len(args) == 0 {
66		c.Ui.Error(c.Help())
67		return 1
68	}
69
70	c.InitOpts()
71	tmpls, err := c.parseOpts(args)
72	if err != nil {
73		if errwrap.Contains(err, "flag: help requested") {
74			return 0
75		}
76		return 1
77	}
78	inputs, outputs := make([]string, len(tmpls)), make([]string, len(tmpls))
79	var rawInput, readStdin bool
80	for i, in := range tmpls {
81		if readStdin {
82			break
83		}
84
85		rawInput = c.rawInput
86		if in == "-" {
87			rawInput = true
88			var f io.Reader = os.Stdin
89			var buf bytes.Buffer
90			if _, err := io.Copy(&buf, f); err != nil {
91				c.Ui.Error(fmt.Sprintf("[ERROR]: Error reading from stdin: %v", err))
92				return 1
93			}
94			in = buf.String()
95			if len(in) == 0 {
96				return 0
97			}
98			readStdin = true
99		}
100		inputs[i] = in
101
102		if !rawInput {
103			in = `{{` + in + `}}`
104			inputs[i] = in
105		}
106
107		out, err := template.Parse(in)
108		if err != nil {
109			c.Ui.Error(fmt.Sprintf("ERROR[%d] in: %q\n[%d] msg: %v\n", i, in, i, err))
110			return 1
111		}
112		outputs[i] = out
113	}
114
115	if c.debugOutput {
116		for i, out := range outputs {
117			c.Ui.Output(fmt.Sprintf("[%d] in: %q\n[%d] out: %q\n", i, inputs[i], i, out))
118			if i != len(outputs)-1 {
119				if c.debugOutput {
120					c.Ui.Output(fmt.Sprintf("---\n"))
121				}
122			}
123		}
124	} else {
125		sep := "\n"
126		if c.suppressNewline {
127			sep = ""
128		}
129		c.Ui.Output(strings.Join(outputs, sep))
130	}
131
132	return 0
133}
134
135// Synopsis returns a terse description used when listing sub-commands.
136func (c *EvalCommand) Synopsis() string {
137	return `Evaluates a sockaddr template`
138}
139
140// Usage is the one-line usage description
141func (c *EvalCommand) Usage() string {
142	return `sockaddr eval [options] [template ...]`
143}
144
145// VisitAllFlags forwards the visitor function to the FlagSet
146func (c *EvalCommand) VisitAllFlags(fn func(*flag.Flag)) {
147	c.flags.VisitAll(fn)
148}
149
150// parseOpts is responsible for parsing the options set in InitOpts().  Returns
151// a list of non-parsed flags.
152func (c *EvalCommand) parseOpts(args []string) ([]string, error) {
153	if err := c.flags.Parse(args); err != nil {
154		return nil, err
155	}
156
157	return c.flags.Args(), nil
158}
159