~nskaggs/+junk/xenial-test

« back to all changes in this revision

Viewing changes to src/github.com/juju/cmd/output.go

  • Committer: Nicholas Skaggs
  • Date: 2016-10-24 20:56:05 UTC
  • Revision ID: nicholas.skaggs@canonical.com-20161024205605-z8lta0uvuhtxwzwl
Initi with beta15

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
// Copyright 2012, 2013 Canonical Ltd.
 
2
// Licensed under the LGPLv3, see LICENSE file for details.
 
3
 
 
4
package cmd
 
5
 
 
6
import (
 
7
        "encoding/json"
 
8
        "fmt"
 
9
        "io"
 
10
        "os"
 
11
        "reflect"
 
12
        "sort"
 
13
        "strconv"
 
14
        "strings"
 
15
 
 
16
        goyaml "gopkg.in/yaml.v2"
 
17
        "launchpad.net/gnuflag"
 
18
)
 
19
 
 
20
// Formatter converts an arbitrary object into a []byte.
 
21
type Formatter func(value interface{}) ([]byte, error)
 
22
 
 
23
// FormatYaml marshals value to a yaml-formatted []byte, unless value is nil.
 
24
func FormatYaml(value interface{}) ([]byte, error) {
 
25
        if value == nil {
 
26
                return nil, nil
 
27
        }
 
28
        result, err := goyaml.Marshal(value)
 
29
        if err != nil {
 
30
                return nil, err
 
31
        }
 
32
        for i := len(result) - 1; i > 0; i-- {
 
33
                if result[i] != '\n' {
 
34
                        break
 
35
                }
 
36
                result = result[:i]
 
37
        }
 
38
        return result, nil
 
39
}
 
40
 
 
41
// FormatJson marshals value to a json-formatted []byte.
 
42
var FormatJson = json.Marshal
 
43
 
 
44
// FormatSmart marshals value into a []byte according to the following rules:
 
45
//   * string:        untouched
 
46
//   * bool:          converted to `True` or `False` (to match pyjuju)
 
47
//   * int or float:  converted to sensible strings
 
48
//   * []string:      joined by `\n`s into a single string
 
49
//   * anything else: delegate to FormatYaml
 
50
func FormatSmart(value interface{}) ([]byte, error) {
 
51
        if value == nil {
 
52
                return nil, nil
 
53
        }
 
54
        v := reflect.ValueOf(value)
 
55
        switch kind := v.Kind(); kind {
 
56
        case reflect.String:
 
57
                return []byte(value.(string)), nil
 
58
        case reflect.Array:
 
59
                if v.Type().Elem().Kind() == reflect.String {
 
60
                        slice := reflect.MakeSlice(reflect.TypeOf([]string(nil)), v.Len(), v.Len())
 
61
                        reflect.Copy(slice, v)
 
62
                        return []byte(strings.Join(slice.Interface().([]string), "\n")), nil
 
63
                }
 
64
        case reflect.Slice:
 
65
                if v.Type().Elem().Kind() == reflect.String {
 
66
                        return []byte(strings.Join(value.([]string), "\n")), nil
 
67
                }
 
68
        case reflect.Bool:
 
69
                if value.(bool) {
 
70
                        return []byte("True"), nil
 
71
                }
 
72
                return []byte("False"), nil
 
73
        case reflect.Float32, reflect.Float64:
 
74
                sv := strconv.FormatFloat(value.(float64), 'f', -1, 64)
 
75
                return []byte(sv), nil
 
76
        case reflect.Map:
 
77
        case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
 
78
        case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
 
79
        default:
 
80
                return nil, fmt.Errorf("cannot marshal %#v", value)
 
81
        }
 
82
        return FormatYaml(value)
 
83
}
 
84
 
 
85
// DefaultFormatters holds the formatters that can be
 
86
// specified with the --format flag.
 
87
var DefaultFormatters = map[string]Formatter{
 
88
        "smart": FormatSmart,
 
89
        "yaml":  FormatYaml,
 
90
        "json":  FormatJson,
 
91
}
 
92
 
 
93
// formatterValue implements gnuflag.Value for the --format flag.
 
94
type formatterValue struct {
 
95
        name       string
 
96
        formatters map[string]Formatter
 
97
}
 
98
 
 
99
// newFormatterValue returns a new formatterValue. The initial Formatter name
 
100
// must be present in formatters.
 
101
func newFormatterValue(initial string, formatters map[string]Formatter) *formatterValue {
 
102
        v := &formatterValue{formatters: formatters}
 
103
        if err := v.Set(initial); err != nil {
 
104
                panic(err)
 
105
        }
 
106
        return v
 
107
}
 
108
 
 
109
// Set stores the chosen formatter name in v.name.
 
110
func (v *formatterValue) Set(value string) error {
 
111
        if v.formatters[value] == nil {
 
112
                return fmt.Errorf("unknown format %q", value)
 
113
        }
 
114
        v.name = value
 
115
        return nil
 
116
}
 
117
 
 
118
// String returns the chosen formatter name.
 
119
func (v *formatterValue) String() string {
 
120
        return v.name
 
121
}
 
122
 
 
123
// doc returns documentation for the --format flag.
 
124
func (v *formatterValue) doc() string {
 
125
        choices := make([]string, len(v.formatters))
 
126
        i := 0
 
127
        for name := range v.formatters {
 
128
                choices[i] = name
 
129
                i++
 
130
        }
 
131
        sort.Strings(choices)
 
132
        return "Specify output format (" + strings.Join(choices, "|") + ")"
 
133
}
 
134
 
 
135
// format runs the chosen formatter on value.
 
136
func (v *formatterValue) format(value interface{}) ([]byte, error) {
 
137
        return v.formatters[v.name](value)
 
138
}
 
139
 
 
140
// Output is responsible for interpreting output-related command line flags
 
141
// and writing a value to a file or to stdout as directed.
 
142
type Output struct {
 
143
        formatter *formatterValue
 
144
        outPath   string
 
145
}
 
146
 
 
147
// AddFlags injects the --format and --output command line flags into f.
 
148
func (c *Output) AddFlags(f *gnuflag.FlagSet, defaultFormatter string, formatters map[string]Formatter) {
 
149
        c.formatter = newFormatterValue(defaultFormatter, formatters)
 
150
        f.Var(c.formatter, "format", c.formatter.doc())
 
151
        f.StringVar(&c.outPath, "o", "", "Specify an output file")
 
152
        f.StringVar(&c.outPath, "output", "", "")
 
153
}
 
154
 
 
155
// Write formats and outputs the value as directed by the --format and
 
156
// --output command line flags.
 
157
func (c *Output) Write(ctx *Context, value interface{}) (err error) {
 
158
        var target io.Writer
 
159
        if c.outPath == "" {
 
160
                target = ctx.Stdout
 
161
        } else {
 
162
                path := ctx.AbsPath(c.outPath)
 
163
                var f *os.File
 
164
                if f, err = os.Create(path); err != nil {
 
165
                        return
 
166
                }
 
167
                defer f.Close()
 
168
                target = f
 
169
        }
 
170
        bytes, err := c.formatter.format(value)
 
171
        if err != nil {
 
172
                return
 
173
        }
 
174
        if len(bytes) > 0 {
 
175
                _, err = target.Write(bytes)
 
176
                if err == nil {
 
177
                        _, err = target.Write([]byte{'\n'})
 
178
                }
 
179
        }
 
180
        return
 
181
}
 
182
 
 
183
func (c *Output) Name() string {
 
184
        return c.formatter.name
 
185
}