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