github.com/dooferlad/cmd@v0.0.0-20150716022859-3edef806220b/output.go (about) 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 "strings" 14 15 goyaml "gopkg.in/yaml.v1" 16 "launchpad.net/gnuflag" 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: 58 if v.Type().Elem().Kind() == reflect.String { 59 slice := reflect.MakeSlice(reflect.TypeOf([]string(nil)), v.Len(), v.Len()) 60 reflect.Copy(slice, v) 61 return []byte(strings.Join(slice.Interface().([]string), "\n")), nil 62 } 63 case reflect.Slice: 64 if v.Type().Elem().Kind() == reflect.String { 65 return []byte(strings.Join(value.([]string), "\n")), nil 66 } 67 case reflect.Bool: 68 if value.(bool) { 69 return []byte("True"), nil 70 } 71 return []byte("False"), nil 72 case reflect.Map, reflect.Float32, reflect.Float64: 73 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 74 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: 75 default: 76 return nil, fmt.Errorf("cannot marshal %#v", value) 77 } 78 return FormatYaml(value) 79 } 80 81 // DefaultFormatters holds the formatters that can be 82 // specified with the --format flag. 83 var DefaultFormatters = map[string]Formatter{ 84 "smart": FormatSmart, 85 "yaml": FormatYaml, 86 "json": FormatJson, 87 } 88 89 // formatterValue implements gnuflag.Value for the --format flag. 90 type formatterValue struct { 91 name string 92 formatters map[string]Formatter 93 } 94 95 // newFormatterValue returns a new formatterValue. The initial Formatter name 96 // must be present in formatters. 97 func newFormatterValue(initial string, formatters map[string]Formatter) *formatterValue { 98 v := &formatterValue{formatters: formatters} 99 if err := v.Set(initial); err != nil { 100 panic(err) 101 } 102 return v 103 } 104 105 // Set stores the chosen formatter name in v.name. 106 func (v *formatterValue) Set(value string) error { 107 if v.formatters[value] == nil { 108 return fmt.Errorf("unknown format %q", value) 109 } 110 v.name = value 111 return nil 112 } 113 114 // String returns the chosen formatter name. 115 func (v *formatterValue) String() string { 116 return v.name 117 } 118 119 // doc returns documentation for the --format flag. 120 func (v *formatterValue) doc() string { 121 choices := make([]string, len(v.formatters)) 122 i := 0 123 for name := range v.formatters { 124 choices[i] = name 125 i++ 126 } 127 sort.Strings(choices) 128 return "specify output format (" + strings.Join(choices, "|") + ")" 129 } 130 131 // format runs the chosen formatter on value. 132 func (v *formatterValue) format(value interface{}) ([]byte, error) { 133 return v.formatters[v.name](value) 134 } 135 136 // Output is responsible for interpreting output-related command line flags 137 // and writing a value to a file or to stdout as directed. 138 type Output struct { 139 formatter *formatterValue 140 outPath string 141 } 142 143 // AddFlags injects the --format and --output command line flags into f. 144 func (c *Output) AddFlags(f *gnuflag.FlagSet, defaultFormatter string, formatters map[string]Formatter) { 145 c.formatter = newFormatterValue(defaultFormatter, formatters) 146 f.Var(c.formatter, "format", c.formatter.doc()) 147 f.StringVar(&c.outPath, "o", "", "specify an output file") 148 f.StringVar(&c.outPath, "output", "", "") 149 } 150 151 // Write formats and outputs the value as directed by the --format and 152 // --output command line flags. 153 func (c *Output) Write(ctx *Context, value interface{}) (err error) { 154 var target io.Writer 155 if c.outPath == "" { 156 target = ctx.Stdout 157 } else { 158 path := ctx.AbsPath(c.outPath) 159 if target, err = os.Create(path); err != nil { 160 return 161 } 162 } 163 bytes, err := c.formatter.format(value) 164 if err != nil { 165 return 166 } 167 if len(bytes) > 0 { 168 _, err = target.Write(bytes) 169 if err == nil { 170 _, err = target.Write([]byte{'\n'}) 171 } 172 } 173 return 174 } 175 176 func (c *Output) Name() string { 177 return c.formatter.name 178 }