github.com/shipa-corp/ketch@v0.6.0/cmd/ketch/output/column.go (about) 1 package output 2 3 import ( 4 "bytes" 5 "fmt" 6 "io" 7 "reflect" 8 "sort" 9 "strings" 10 "text/tabwriter" 11 ) 12 13 // columnOutput represents data and a writer for standard output type 14 type columnOutput struct { 15 data interface{} 16 writer io.Writer 17 } 18 19 // val is a structure for storing a Value and it's column struct tag together 20 type val struct { 21 tag reflect.StructTag 22 value reflect.Value 23 } 24 25 type valSet []val 26 27 // write implements Writer for type Column 28 func (c *columnOutput) write() error { 29 d, err := c.marshal(c.data) 30 if err != nil { 31 return err 32 } 33 _, err = fmt.Fprintln(c.writer, string(d)) 34 return err 35 } 36 37 // marshal creates valSets from v, depending on whether it is a slice, struct, map, or pointer. 38 // Column headings are pulled from the "column" struct tag or spaced-and-capitalized from the field name 39 // if the tag does not exist. Data is then printed to the tabwriter. Fields with the dash struct tag, e.g. `column"-"` 40 // are omitted. The 'omitempty' tag directive has not been implemented. 41 func (c *columnOutput) marshal(v interface{}) ([]byte, error) { 42 // create valSets from the ValueOf v, depending on v's underlying kind 43 var valSets []valSet 44 value := reflect.ValueOf(v) 45 switch value.Kind() { 46 case reflect.Struct: 47 valSet := newValSet(value) 48 valSets = append(valSets, valSet) 49 50 case reflect.Slice: 51 for i := 0; i < reflect.Value.Len(value); i++ { 52 valSet := newValSet(value.Index(i)) 53 valSets = append(valSets, valSet) 54 } 55 56 case reflect.Ptr: 57 valSet := newValSet(value.Elem()) 58 valSets = append(valSets, valSet) 59 60 case reflect.Map: 61 var vs valSet 62 keys := value.MapKeys() 63 sort.Slice(keys, func(i, j int) bool { 64 return keys[i].String() < keys[j].String() 65 }) 66 for _, key := range keys { 67 v := value.MapIndex(key) 68 vs = append(vs, val{tag: reflect.StructTag(fmt.Sprintf("column:\"%s\"", key.String())), value: v}) 69 } 70 valSets = append(valSets, vs) 71 72 default: 73 return nil, fmt.Errorf("unsupported kind: %s", value.Kind()) 74 } 75 76 // no data 77 if len(valSets) < 1 { 78 return nil, nil 79 } 80 81 var buf bytes.Buffer 82 w := tabwriter.NewWriter(&buf, 0, 4, 4, ' ', 0) 83 84 // write header columns using tags from first item in valSets 85 for i, val := range valSets[0] { 86 tag := val.tag.Get("column") 87 // omit? 88 if tag == "-" { 89 continue 90 } 91 92 fmt.Fprint(w, tag) 93 // tab 94 if i+1 < len(valSets[0]) { 95 fmt.Fprint(w, "\t") 96 } 97 } 98 fmt.Fprint(w, "\n") 99 100 // write field columns from valSets values 101 for i, valSet := range valSets { 102 for j, val := range valSet { 103 // omit if column tag is '-' 104 if val.tag.Get("column") == "-" { 105 continue 106 } 107 108 fmt.Fprint(w, val.value) 109 110 // tab 111 if j+1 < len(valSet) { 112 fmt.Fprint(w, "\t") 113 } 114 } 115 // newline 116 if i+1 < len(valSets) { 117 fmt.Fprint(w, "\n") 118 } 119 } 120 err := w.Flush() 121 if err != nil { 122 return nil, err 123 } 124 125 return buf.Bytes(), nil 126 } 127 128 // newValSet iterates over a value's fields and assigns the Value and StructTag to a valSet 129 func newValSet(value reflect.Value) valSet { 130 var valSet valSet 131 for i := 0; i < value.NumField(); i++ { 132 tag := value.Type().Field(i).Tag 133 // use Field Type as StructTag if column tag is not set 134 if tag.Get("column") == "" { 135 fieldName := value.Type().Field(i).Name 136 // split and uppercase field name 137 var builder strings.Builder 138 for i, r := range fieldName { 139 if r > 96 && r < 123 { // lowercase -> uppercase 140 builder.WriteString(string(r - 32)) 141 } else { 142 if i > 0 { // prepend space if not first value 143 builder.WriteString(" ") 144 } 145 builder.WriteString(string(r)) // write currently uppercase runes 146 } 147 } 148 tag = reflect.StructTag(fmt.Sprintf("column:\"%s\"", builder.String())) 149 } 150 valSet = append(valSet, val{tag: tag, value: value.Field(i)}) 151 } 152 return valSet 153 }