launchpad.net/~rogpeppe/juju-core/500-errgo-fix@v0.0.0-20140213181702-000000002356/cmd/output.go (about) 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 }