github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/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  }