Refactor formatting of output

This change is almost perfectly compatible with the existing code,
except it's a little shorter because it uses a list of a available
formatters that must implement a `command.Formatter` interface.

Also added some basic formatting tests.
This commit is contained in:
Ron Kuris 2016-02-12 10:21:42 -08:00
parent 4e896ca0d2
commit c4c6bbf33c
2 changed files with 162 additions and 95 deletions

View File

@ -3,6 +3,7 @@ package command
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"sort"
"strconv"
@ -15,86 +16,102 @@ import (
)
func OutputSecret(ui cli.Ui, format string, secret *api.Secret) int {
switch format {
case "json":
return outputFormatJSON(ui, secret)
case "yaml":
return outputFormatYAML(ui, secret)
case "table":
return outputFormatTable(ui, secret, true)
default:
ui.Error(fmt.Sprintf("Invalid output format: %s", format))
return 1
}
return outputWithFormat(ui, format, secret)
}
func OutputList(ui cli.Ui, format string, secret *api.Secret) int {
switch format {
case "json":
return outputFormatJSONList(ui, secret)
case "yaml":
return outputFormatYAMLList(ui, secret)
case "table":
return outputFormatTableList(ui, secret)
default:
return outputWithFormat(ui, format, secret.Data["keys"])
}
func outputWithFormat(ui cli.Ui, format string, data interface{}) int {
formatter, ok := Formatters[strings.ToLower(format)]
if !ok {
ui.Error(fmt.Sprintf("Invalid output format: %s", format))
return 1
}
}
func outputFormatJSON(ui cli.Ui, s *api.Secret) int {
b, err := json.Marshal(s)
if err != nil {
ui.Error(fmt.Sprintf(
"Error formatting secret: %s", err))
if err := formatter.Output(ui, data); err != nil {
ui.Error(fmt.Sprintf("Could not output secret: %s", err.Error()))
return 1
}
var out bytes.Buffer
json.Indent(&out, b, "", "\t")
ui.Output(out.String())
return 0
}
func outputFormatJSONList(ui cli.Ui, s *api.Secret) int {
b, err := json.Marshal(s.Data["keys"])
if err != nil {
ui.Error(fmt.Sprintf(
"Error formatting keys: %s", err))
return 1
}
var out bytes.Buffer
json.Indent(&out, b, "", "\t")
ui.Output(out.String())
return 0
type Formatter interface {
Output(ui cli.Ui, data interface{}) error
}
func outputFormatYAML(ui cli.Ui, s *api.Secret) int {
b, err := yaml.Marshal(s)
if err != nil {
ui.Error(fmt.Sprintf(
"Error formatting secret: %s", err))
return 1
}
ui.Output(strings.TrimSpace(string(b)))
return 0
var Formatters = map[string]Formatter{
"json": JsonFormatter{},
"table": TableFormatter{},
"yaml": YamlFormatter{},
}
func outputFormatYAMLList(ui cli.Ui, s *api.Secret) int {
b, err := yaml.Marshal(s.Data["keys"])
if err != nil {
ui.Error(fmt.Sprintf(
"Error formatting secret: %s", err))
return 1
}
ui.Output(strings.TrimSpace(string(b)))
return 0
// An output formatter for json output of an object
type JsonFormatter struct {
}
func outputFormatTable(ui cli.Ui, s *api.Secret, whitespace bool) int {
func (j JsonFormatter) Output(ui cli.Ui, data interface{}) error {
b, err := json.Marshal(data)
if err == nil {
var out bytes.Buffer
json.Indent(&out, b, "", "\t")
ui.Output(out.String())
}
return err
}
// An output formatter for yaml output format of an object
type YamlFormatter struct {
}
func (y YamlFormatter) Output(ui cli.Ui, data interface{}) error {
b, err := yaml.Marshal(data)
if err == nil {
ui.Output(strings.TrimSpace(string(b)))
}
return err
}
// An output formatter for table output of an object
type TableFormatter struct {
}
func (t TableFormatter) Output(ui cli.Ui, data interface{}) error {
// TODO: this should really use reflection like the other formatters do
if s, ok := data.(*api.Secret); ok {
return t.OutputSecret(ui, s)
}
if s, ok := data.([]interface{}); ok {
return t.OutputList(ui, s)
}
return errors.New("Cannot use the table formatter for this type")
}
func (t TableFormatter) OutputList(ui cli.Ui, list []interface{}) error {
config := columnize.DefaultConfig()
config.Delim = "♨"
config.Glue = "\t"
config.Prefix = ""
input := make([]string, 0, 5)
input = append(input, "Keys")
keys := make([]string, 0, len(list))
for _, k := range list {
keys = append(keys, k.(string))
}
sort.Strings(keys)
for _, k := range keys {
input = append(input, fmt.Sprintf("%s", k))
}
ui.Output(columnize.Format(input, config))
return nil
}
func (t TableFormatter) OutputSecret(ui cli.Ui, s *api.Secret) error {
config := columnize.DefaultConfig()
config.Delim = "♨"
config.Glue = "\t"
@ -145,36 +162,5 @@ func outputFormatTable(ui cli.Ui, s *api.Secret, whitespace bool) int {
}
ui.Output(columnize.Format(input, config))
return 0
}
func outputFormatTableList(ui cli.Ui, s *api.Secret) int {
config := columnize.DefaultConfig()
config.Delim = "♨"
config.Glue = "\t"
config.Prefix = ""
input := make([]string, 0, 5)
input = append(input, "Keys")
keys := make([]string, 0, len(s.Data["keys"].([]interface{})))
for _, k := range s.Data["keys"].([]interface{}) {
keys = append(keys, k.(string))
}
sort.Strings(keys)
for _, k := range keys {
input = append(input, fmt.Sprintf("%s", k))
}
if len(s.Warnings) != 0 {
input = append(input, "")
for _, warning := range s.Warnings {
input = append(input, fmt.Sprintf("* %s", warning))
}
}
ui.Output(columnize.Format(input, config))
return 0
return nil
}

81
command/format_test.go Normal file
View File

@ -0,0 +1,81 @@
package command
import (
"encoding/json"
"github.com/ghodss/yaml"
"github.com/hashicorp/vault/api"
"strings"
"testing"
)
var output string
type mockUi struct {
t *testing.T
SampleData string
}
func (m mockUi) Ask(_ string) (string, error) {
m.t.FailNow()
return "", nil
}
func (m mockUi) AskSecret(_ string) (string, error) {
m.t.FailNow()
return "", nil
}
func (m mockUi) Output(s string) {
output = s
}
func (m mockUi) Info(s string) {
m.t.Log(s)
}
func (m mockUi) Error(s string) {
m.t.Log(s)
}
func (m mockUi) Warn(s string) {
m.t.Log(s)
}
func TestJsonFormatter(t *testing.T) {
ui := mockUi{t: t, SampleData: "something"}
if err := outputWithFormat(ui, "json", ui); err != 0 {
t.Fatal(err)
}
var newUi mockUi
if err := json.Unmarshal([]byte(output), &newUi); err != nil {
t.Fatal(err)
}
if newUi.SampleData != ui.SampleData {
t.Fatalf(`values not equal ("%s" != "%s")`,
newUi.SampleData,
ui.SampleData)
}
}
func TestYamlFormatter(t *testing.T) {
ui := mockUi{t: t, SampleData: "something"}
if err := outputWithFormat(ui, "yaml", ui); err != 0 {
t.Fatal(err)
}
var newUi mockUi
err := yaml.Unmarshal([]byte(output), &newUi)
if err != nil {
t.Fatal(err)
}
if newUi.SampleData != ui.SampleData {
t.Fatalf(`values not equal ("%s" != "%s")`,
newUi.SampleData,
ui.SampleData)
}
}
func TestTableFormatter(t *testing.T) {
ui := mockUi{t: t}
s := api.Secret{Data: map[string]interface{}{"k": "something"}}
if err := outputWithFormat(ui, "table", &s); err != 0 {
t.Fatal(err)
}
if !strings.Contains(output, "something") {
t.Fatal("did not find something")
}
}