github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/cmd/juju/storage/listformatters.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package storage 5 6 import ( 7 "fmt" 8 "io" 9 "sort" 10 "strconv" 11 "strings" 12 13 "github.com/juju/juju/cmd/output" 14 ) 15 16 // formatListTabular writes a tabular summary of storage instances. 17 func formatStorageListTabular(writer io.Writer, storageInfo map[string]StorageInfo) error { 18 tw := output.TabWriter(writer) 19 p := func(values ...interface{}) { 20 for _, v := range values { 21 fmt.Fprintf(tw, "%v\t", v) 22 } 23 fmt.Fprintln(tw) 24 } 25 p("[Storage]") 26 p("UNIT\tID\tLOCATION\tSTATUS\tMESSAGE") 27 28 byUnit := make(map[string]map[string]storageAttachmentInfo) 29 for storageId, storageInfo := range storageInfo { 30 if storageInfo.Attachments == nil { 31 byStorage := byUnit[""] 32 if byStorage == nil { 33 byStorage = make(map[string]storageAttachmentInfo) 34 byUnit[""] = byStorage 35 } 36 byStorage[storageId] = storageAttachmentInfo{ 37 storageId: storageId, 38 kind: storageInfo.Kind, 39 persistent: storageInfo.Persistent, 40 status: storageInfo.Status, 41 } 42 continue 43 } 44 for unitId, a := range storageInfo.Attachments.Units { 45 byStorage := byUnit[unitId] 46 if byStorage == nil { 47 byStorage = make(map[string]storageAttachmentInfo) 48 byUnit[unitId] = byStorage 49 } 50 byStorage[storageId] = storageAttachmentInfo{ 51 storageId: storageId, 52 unitId: unitId, 53 kind: storageInfo.Kind, 54 persistent: storageInfo.Persistent, 55 location: a.Location, 56 status: storageInfo.Status, 57 } 58 } 59 } 60 61 // First sort by units 62 units := make([]string, 0, len(storageInfo)) 63 for unit := range byUnit { 64 units = append(units, unit) 65 } 66 sort.Strings(slashSeparatedIds(units)) 67 68 for _, unit := range units { 69 // Then sort by storage ids 70 byStorage := byUnit[unit] 71 storageIds := make([]string, 0, len(byStorage)) 72 for storageId := range byStorage { 73 storageIds = append(storageIds, storageId) 74 } 75 sort.Strings(slashSeparatedIds(storageIds)) 76 77 for _, storageId := range storageIds { 78 info := byStorage[storageId] 79 p(info.unitId, info.storageId, info.location, info.status.Current, info.status.Message) 80 } 81 } 82 tw.Flush() 83 84 return nil 85 } 86 87 type storageAttachmentInfo struct { 88 storageId string 89 unitId string 90 kind string 91 persistent bool 92 location string 93 status EntityStatus 94 } 95 96 type slashSeparatedIds []string 97 98 func (s slashSeparatedIds) Len() int { 99 return len(s) 100 } 101 102 func (s slashSeparatedIds) Swap(a, b int) { 103 s[a], s[b] = s[b], s[a] 104 } 105 106 func (s slashSeparatedIds) Less(a, b int) bool { 107 return compareSlashSeparated(s[a], s[b]) == -1 108 } 109 110 // compareSlashSeparated compares a with b, first the string before 111 // "/", and then the integer or string after. Empty strings are sorted 112 // after all others. 113 func compareSlashSeparated(a, b string) int { 114 switch { 115 case a == "" && b == "": 116 return 0 117 case a == "": 118 return 1 119 case b == "": 120 return -1 121 } 122 123 sa := strings.SplitN(a, "/", 2) 124 sb := strings.SplitN(b, "/", 2) 125 if sa[0] < sb[0] { 126 return -1 127 } 128 if sa[0] > sb[0] { 129 return 1 130 } 131 132 getInt := func(suffix string) (bool, int) { 133 num, err := strconv.Atoi(suffix) 134 if err != nil { 135 return false, 0 136 } 137 return true, num 138 } 139 140 naIsNumeric, na := getInt(sa[1]) 141 if !naIsNumeric { 142 return compareStrings(sa[1], sb[1]) 143 } 144 nbIsNumeric, nb := getInt(sb[1]) 145 if !nbIsNumeric { 146 return compareStrings(sa[1], sb[1]) 147 } 148 149 switch { 150 case na < nb: 151 return -1 152 case na == nb: 153 return 0 154 } 155 return 1 156 } 157 158 // compareStrings does what strings.Compare does, but without using 159 // strings.Compare as it does not exist in Go 1.2. 160 func compareStrings(a, b string) int { 161 if a == b { 162 return 0 163 } 164 if a < b { 165 return -1 166 } 167 return 1 168 }