github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/cmd/juju/resource/output_tabular.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package resource 5 6 import ( 7 "fmt" 8 "io" 9 "sort" 10 11 "github.com/juju/ansiterm" 12 "github.com/juju/errors" 13 14 "github.com/juju/juju/cmd/output" 15 ) 16 17 // FormatCharmTabular returns a tabular summary of charm resources. 18 func FormatCharmTabular(writer io.Writer, value interface{}) error { 19 resources, valueConverted := value.([]FormattedCharmResource) 20 if !valueConverted { 21 return errors.Errorf("expected value of type %T, got %T", resources, value) 22 } 23 24 // Sort by resource name 25 names, resourcesByName := groupCharmResourcesByName(resources) 26 27 // To format things into columns. 28 tw := output.TabWriter(writer) 29 30 // Write the header. 31 // We do not print a section label. 32 fmt.Fprintln(tw, "Resource\tRevision") 33 34 // Print each info to its own row. 35 for _, name := range names { 36 for _, res := range resourcesByName[name] { 37 // the column headers must be kept in sync with these. 38 fmt.Fprintf(tw, "%s\t%d\n", 39 name, 40 res.Revision, 41 ) 42 } 43 } 44 tw.Flush() 45 46 return nil 47 } 48 49 func groupCharmResourcesByName(resources []FormattedCharmResource) ([]string, map[string][]FormattedCharmResource) { 50 // Sort by resource name 51 names := make([]string, len(resources)) 52 resourcesByName := map[string][]FormattedCharmResource{} 53 for i, r := range resources { 54 names[i] = r.Name 55 allNamedResources, ok := resourcesByName[r.Name] 56 if !ok { 57 allNamedResources = []FormattedCharmResource{} 58 } 59 resourcesByName[r.Name] = append(allNamedResources, r) 60 } 61 sort.Strings(names) 62 return names, resourcesByName 63 } 64 65 // FormatAppTabular returns a tabular summary of resources. 66 func FormatAppTabular(writer io.Writer, value interface{}) error { 67 switch resources := value.(type) { 68 case FormattedApplicationInfo: 69 formatApplicationTabular(writer, resources) 70 return nil 71 case []FormattedAppResource: 72 formatUnitTabular(writer, resources) 73 return nil 74 case FormattedApplicationDetails: 75 formatApplicationDetailTabular(writer, resources) 76 return nil 77 case FormattedUnitDetails: 78 formatUnitDetailTabular(writer, resources) 79 return nil 80 default: 81 return errors.Errorf("unexpected type for data: %T", resources) 82 } 83 } 84 85 func formatApplicationTabular(writer io.Writer, info FormattedApplicationInfo) { 86 // Sort by resource name 87 names, resourcesByName := groupApplicationResourcesByName(info.Resources) 88 89 tw := output.TabWriter(writer) 90 fmt.Fprintln(tw, "Resource\tSupplied by\tRevision") 91 92 // Print each info to its own row. 93 for _, name := range names { 94 for _, r := range resourcesByName[name] { 95 // the column headers must be kept in sync with these. 96 fmt.Fprintf(tw, "%v\t%v\t%v\n", 97 r.Name, 98 r.CombinedOrigin, 99 r.CombinedRevision, 100 ) 101 } 102 } 103 104 // Don't forget to flush! The Tab writer won't actually write to the output 105 // until you flush, which would then have its output incorrectly ordered 106 // with the below fmt.Fprintlns. 107 tw.Flush() 108 109 writeUpdates(info.Updates, writer, tw) 110 } 111 112 func groupApplicationResourcesByName(resources []FormattedAppResource) ([]string, map[string][]FormattedAppResource) { 113 // Sort by resource name 114 names := make([]string, len(resources)) 115 resourcesByName := map[string][]FormattedAppResource{} 116 for i, r := range resources { 117 names[i] = r.Name 118 allNamedResources, ok := resourcesByName[r.Name] 119 if !ok { 120 allNamedResources = []FormattedAppResource{} 121 } 122 resourcesByName[r.Name] = append(allNamedResources, r) 123 } 124 sort.Strings(names) 125 return names, resourcesByName 126 } 127 128 func writeUpdates(updates []FormattedCharmResource, out io.Writer, tw *ansiterm.TabWriter) { 129 names, resourcesByName := groupCharmResourcesByName(updates) 130 131 if len(updates) > 0 { 132 fmt.Fprintln(out, "") 133 fmt.Fprintln(out, "[Updates Available]") 134 fmt.Fprintln(tw, "Resource\tRevision") 135 for _, name := range names { 136 for _, r := range resourcesByName[name] { 137 fmt.Fprintf(tw, "%v\t%v\n", 138 name, 139 r.Revision, 140 ) 141 } 142 } 143 } 144 145 tw.Flush() 146 } 147 148 func formatUnitTabular(writer io.Writer, resources []FormattedAppResource) { 149 names, resourcesByName := groupApplicationResourcesByName(resources) 150 151 // To format things into columns. 152 tw := output.TabWriter(writer) 153 154 // Write the header. 155 // We do not print a section label. 156 fmt.Fprintln(tw, "Resource\tRevision") 157 158 // Print each info to its own row. 159 for _, name := range names { 160 for _, r := range resourcesByName[name] { 161 // the column headers must be kept in sync with these. 162 fmt.Fprintf(tw, "%v\t%v\n", 163 r.Name, 164 r.CombinedRevision, 165 ) 166 } 167 } 168 tw.Flush() 169 } 170 171 func formatApplicationDetailTabular(writer io.Writer, resources FormattedApplicationDetails) { 172 // note that the unit resource can be a zero value here, to indicate that 173 // the unit has not downloaded that resource yet. 174 sort.Sort(byUnitID(resources.Resources)) 175 // To format things into columns. 176 tw := output.TabWriter(writer) 177 178 // Write the header. 179 fmt.Fprintln(tw, "Unit\tResource\tRevision\tExpected") 180 181 for _, r := range resources.Resources { 182 fmt.Fprintf(tw, "%v\t%v\t%v\t%v\n", 183 r.UnitID, 184 r.Expected.Name, 185 r.Unit.CombinedRevision, 186 r.RevProgress, 187 ) 188 } 189 tw.Flush() 190 191 writeUpdates(resources.Updates, writer, tw) 192 } 193 194 func formatUnitDetailTabular(writer io.Writer, resources FormattedUnitDetails) { 195 // note that the unit resource can be a zero value here, to indicate that 196 // the unit has not downloaded that resource yet. 197 sort.Sort(byUnitID(resources)) 198 // To format things into columns. 199 tw := output.TabWriter(writer) 200 201 // Write the header. 202 fmt.Fprintln(tw, "Resource\tRevision\tExpected") 203 204 for _, r := range resources { 205 fmt.Fprintf(tw, "%v\t%v\t%v\n", 206 r.Expected.Name, 207 r.Unit.CombinedRevision, 208 r.RevProgress, 209 ) 210 } 211 tw.Flush() 212 } 213 214 type byUnitID []FormattedDetailResource 215 216 func (b byUnitID) Len() int { return len(b) } 217 func (b byUnitID) Swap(i, j int) { b[i], b[j] = b[j], b[i] } 218 219 func (b byUnitID) Less(i, j int) bool { 220 if b[i].UnitNumber != b[j].UnitNumber { 221 return b[i].UnitNumber < b[j].UnitNumber 222 } 223 return b[i].Expected.Name < b[j].Expected.Name 224 }