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  }