~rogpeppe/juju-core/azure

« back to all changes in this revision

Viewing changes to cmd/output.go

  • Committer: William Reade
  • Date: 2012-01-20 21:32:53 UTC
  • mto: This revision was merged to the branch mainline in revision 47.
  • Revision ID: fwereade@gmail.com-20120120213253-csks5e12opl8t1rq
hefty rearrangement, few actual changes

Show diffs side-by-side

added added

removed removed

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