github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/cmd/juju/crossmodel/showformatter.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package crossmodel 5 6 import ( 7 "fmt" 8 "io" 9 "sort" 10 "strings" 11 12 "github.com/juju/errors" 13 14 "github.com/juju/juju/cmd/output" 15 "github.com/juju/juju/core/crossmodel" 16 ) 17 18 const ( 19 // To wrap long lines within a column. 20 maxColumnLength = 180 21 truncatedSuffix = "..." 22 maxFieldLength = maxColumnLength - len(truncatedSuffix) 23 columnWidth = 45 24 ) 25 26 // formatShowTabular returns a tabular summary of remote applications or 27 // errors out if parameter is not of expected type. 28 func formatShowTabular(writer io.Writer, value interface{}) error { 29 endpoints, ok := value.(map[string]ShowOfferedApplication) 30 if !ok { 31 return errors.Errorf("expected value of type %T, got %T", endpoints, value) 32 } 33 return formatOfferedEndpointsTabular(writer, endpoints) 34 } 35 36 // formatOfferedEndpointsTabular returns a tabular summary of offered applications' endpoints. 37 func formatOfferedEndpointsTabular(writer io.Writer, all map[string]ShowOfferedApplication) error { 38 tw := output.TabWriter(writer) 39 w := output.Wrapper{tw} 40 41 w.Println("Store", "URL", "Access", "Description", "Endpoint", "Interface", "Role") 42 43 for urlStr, one := range all { 44 url, err := crossmodel.ParseOfferURL(urlStr) 45 if err != nil { 46 return err 47 } 48 store := url.Source 49 url.Source = "" 50 offerURL := url.String() 51 offerAccess := one.Access 52 offerDesc := one.Description 53 54 // truncate long description for now. 55 if len(offerDesc) > maxColumnLength { 56 offerDesc = fmt.Sprintf("%v%v", offerDesc[:maxFieldLength], truncatedSuffix) 57 } 58 descLines := breakLines(offerDesc) 59 60 // Find the maximum amount of iterations required: 61 // it will be either endpoints or description lines length 62 maxIterations := max(len(one.Endpoints), len(descLines)) 63 64 names := []string{} 65 for name := range one.Endpoints { 66 names = append(names, name) 67 } 68 sort.Strings(names) 69 70 for i := 0; i < maxIterations; i++ { 71 descLine := descAt(descLines, i) 72 name, endpoint := endpointAt(one.Endpoints, names, i) 73 w.Println(store, offerURL, offerAccess, descLine, name, endpoint.Interface, endpoint.Role) 74 // Only print once. 75 store = "" 76 offerURL = "" 77 offerAccess = "" 78 } 79 } 80 tw.Flush() 81 return nil 82 } 83 84 func descAt(lines []string, i int) string { 85 if i < len(lines) { 86 return lines[i] 87 } 88 return "" 89 } 90 91 func endpointAt(endpoints map[string]RemoteEndpoint, names []string, i int) (string, RemoteEndpoint) { 92 if i < len(endpoints) { 93 name := names[i] 94 return name, endpoints[name] 95 } 96 return "", RemoteEndpoint{} 97 } 98 99 func breakLines(text string) []string { 100 words := strings.Fields(text) 101 102 // if there is one very long word, break it 103 if len(words) == 1 { 104 return breakOneWord(words[0]) 105 } 106 107 numLines := len(text)/columnWidth + 1 108 lines := make([]string, numLines) 109 110 index := 0 111 for _, aWord := range words { 112 if len(lines[index]) == 0 { 113 lines[index] = aWord 114 continue 115 } 116 tp := fmt.Sprintf("%v %v", lines[index], aWord) 117 if len(tp) > columnWidth { 118 index++ 119 lines[index] = aWord 120 continue 121 } 122 lines[index] = tp 123 } 124 125 return lines 126 } 127 128 func breakOneWord(one string) []string { 129 if len(one) <= columnWidth { 130 return []string{one} 131 } 132 133 numParts := (len(one) / columnWidth) + 1 134 parts := make([]string, numParts) 135 136 for i := 0; i < numParts; i++ { 137 start := i * columnWidth 138 end := start + columnWidth 139 if end > len(one) { 140 parts[i] = one[start:] 141 continue 142 } 143 parts[i] = one[start:end] 144 } 145 return parts 146 } 147 148 func max(one, two int) int { 149 if one > two { 150 return one 151 } 152 return two 153 }