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  }