github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/cmd/juju/resource/output_tabular.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package resource
     5  
     6  import (
     7  	"fmt"
     8  	"io"
     9  	"sort"
    10  
    11  	"github.com/juju/ansiterm"
    12  	"github.com/juju/errors"
    13  
    14  	"github.com/juju/juju/cmd/output"
    15  )
    16  
    17  // FormatCharmTabular returns a tabular summary of charm resources.
    18  func FormatCharmTabular(writer io.Writer, value interface{}) error {
    19  	resources, valueConverted := value.([]FormattedCharmResource)
    20  	if !valueConverted {
    21  		return errors.Errorf("expected value of type %T, got %T", resources, value)
    22  	}
    23  
    24  	// Sort by resource name
    25  	names, resourcesByName := groupCharmResourcesByName(resources)
    26  
    27  	// To format things into columns.
    28  	tw := output.TabWriter(writer)
    29  
    30  	// Write the header.
    31  	// We do not print a section label.
    32  	fmt.Fprintln(tw, "Resource\tRevision")
    33  
    34  	// Print each info to its own row.
    35  	for _, name := range names {
    36  		for _, res := range resourcesByName[name] {
    37  			// the column headers must be kept in sync with these.
    38  			fmt.Fprintf(tw, "%s\t%d\n",
    39  				name,
    40  				res.Revision,
    41  			)
    42  		}
    43  	}
    44  	tw.Flush()
    45  
    46  	return nil
    47  }
    48  
    49  func groupCharmResourcesByName(resources []FormattedCharmResource) ([]string, map[string][]FormattedCharmResource) {
    50  	// Sort by resource name
    51  	names := make([]string, len(resources))
    52  	resourcesByName := map[string][]FormattedCharmResource{}
    53  	for i, r := range resources {
    54  		names[i] = r.Name
    55  		allNamedResources, ok := resourcesByName[r.Name]
    56  		if !ok {
    57  			allNamedResources = []FormattedCharmResource{}
    58  		}
    59  		resourcesByName[r.Name] = append(allNamedResources, r)
    60  	}
    61  	sort.Strings(names)
    62  	return names, resourcesByName
    63  }
    64  
    65  // FormatAppTabular returns a tabular summary of resources.
    66  func FormatAppTabular(writer io.Writer, value interface{}) error {
    67  	switch resources := value.(type) {
    68  	case FormattedApplicationInfo:
    69  		formatApplicationTabular(writer, resources)
    70  		return nil
    71  	case []FormattedAppResource:
    72  		formatUnitTabular(writer, resources)
    73  		return nil
    74  	case FormattedApplicationDetails:
    75  		formatApplicationDetailTabular(writer, resources)
    76  		return nil
    77  	case FormattedUnitDetails:
    78  		formatUnitDetailTabular(writer, resources)
    79  		return nil
    80  	default:
    81  		return errors.Errorf("unexpected type for data: %T", resources)
    82  	}
    83  }
    84  
    85  func formatApplicationTabular(writer io.Writer, info FormattedApplicationInfo) {
    86  	// Sort by resource name
    87  	names, resourcesByName := groupApplicationResourcesByName(info.Resources)
    88  
    89  	tw := output.TabWriter(writer)
    90  	fmt.Fprintln(tw, "Resource\tSupplied by\tRevision")
    91  
    92  	// Print each info to its own row.
    93  	for _, name := range names {
    94  		for _, r := range resourcesByName[name] {
    95  			// the column headers must be kept in sync with these.
    96  			fmt.Fprintf(tw, "%v\t%v\t%v\n",
    97  				r.Name,
    98  				r.CombinedOrigin,
    99  				r.CombinedRevision,
   100  			)
   101  		}
   102  	}
   103  
   104  	// Don't forget to flush!  The Tab writer won't actually write to the output
   105  	// until you flush, which would then have its output incorrectly ordered
   106  	// with the below fmt.Fprintlns.
   107  	tw.Flush()
   108  
   109  	writeUpdates(info.Updates, writer, tw)
   110  }
   111  
   112  func groupApplicationResourcesByName(resources []FormattedAppResource) ([]string, map[string][]FormattedAppResource) {
   113  	// Sort by resource name
   114  	names := make([]string, len(resources))
   115  	resourcesByName := map[string][]FormattedAppResource{}
   116  	for i, r := range resources {
   117  		names[i] = r.Name
   118  		allNamedResources, ok := resourcesByName[r.Name]
   119  		if !ok {
   120  			allNamedResources = []FormattedAppResource{}
   121  		}
   122  		resourcesByName[r.Name] = append(allNamedResources, r)
   123  	}
   124  	sort.Strings(names)
   125  	return names, resourcesByName
   126  }
   127  
   128  func writeUpdates(updates []FormattedCharmResource, out io.Writer, tw *ansiterm.TabWriter) {
   129  	names, resourcesByName := groupCharmResourcesByName(updates)
   130  
   131  	if len(updates) > 0 {
   132  		fmt.Fprintln(out, "")
   133  		fmt.Fprintln(out, "[Updates Available]")
   134  		fmt.Fprintln(tw, "Resource\tRevision")
   135  		for _, name := range names {
   136  			for _, r := range resourcesByName[name] {
   137  				fmt.Fprintf(tw, "%v\t%v\n",
   138  					name,
   139  					r.Revision,
   140  				)
   141  			}
   142  		}
   143  	}
   144  
   145  	tw.Flush()
   146  }
   147  
   148  func formatUnitTabular(writer io.Writer, resources []FormattedAppResource) {
   149  	names, resourcesByName := groupApplicationResourcesByName(resources)
   150  
   151  	// To format things into columns.
   152  	tw := output.TabWriter(writer)
   153  
   154  	// Write the header.
   155  	// We do not print a section label.
   156  	fmt.Fprintln(tw, "Resource\tRevision")
   157  
   158  	// Print each info to its own row.
   159  	for _, name := range names {
   160  		for _, r := range resourcesByName[name] {
   161  			// the column headers must be kept in sync with these.
   162  			fmt.Fprintf(tw, "%v\t%v\n",
   163  				r.Name,
   164  				r.CombinedRevision,
   165  			)
   166  		}
   167  	}
   168  	tw.Flush()
   169  }
   170  
   171  func formatApplicationDetailTabular(writer io.Writer, resources FormattedApplicationDetails) {
   172  	// note that the unit resource can be a zero value here, to indicate that
   173  	// the unit has not downloaded that resource yet.
   174  	sort.Sort(byUnitID(resources.Resources))
   175  	// To format things into columns.
   176  	tw := output.TabWriter(writer)
   177  
   178  	// Write the header.
   179  	fmt.Fprintln(tw, "Unit\tResource\tRevision\tExpected")
   180  
   181  	for _, r := range resources.Resources {
   182  		fmt.Fprintf(tw, "%v\t%v\t%v\t%v\n",
   183  			r.UnitID,
   184  			r.Expected.Name,
   185  			r.Unit.CombinedRevision,
   186  			r.RevProgress,
   187  		)
   188  	}
   189  	tw.Flush()
   190  
   191  	writeUpdates(resources.Updates, writer, tw)
   192  }
   193  
   194  func formatUnitDetailTabular(writer io.Writer, resources FormattedUnitDetails) {
   195  	// note that the unit resource can be a zero value here, to indicate that
   196  	// the unit has not downloaded that resource yet.
   197  	sort.Sort(byUnitID(resources))
   198  	// To format things into columns.
   199  	tw := output.TabWriter(writer)
   200  
   201  	// Write the header.
   202  	fmt.Fprintln(tw, "Resource\tRevision\tExpected")
   203  
   204  	for _, r := range resources {
   205  		fmt.Fprintf(tw, "%v\t%v\t%v\n",
   206  			r.Expected.Name,
   207  			r.Unit.CombinedRevision,
   208  			r.RevProgress,
   209  		)
   210  	}
   211  	tw.Flush()
   212  }
   213  
   214  type byUnitID []FormattedDetailResource
   215  
   216  func (b byUnitID) Len() int      { return len(b) }
   217  func (b byUnitID) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
   218  
   219  func (b byUnitID) Less(i, j int) bool {
   220  	if b[i].UnitNumber != b[j].UnitNumber {
   221  		return b[i].UnitNumber < b[j].UnitNumber
   222  	}
   223  	return b[i].Expected.Name < b[j].Expected.Name
   224  }