github.com/kat-co/cmd@v0.0.0-20140616103059-5da365f9d57e/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:
    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  }