github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/cmd/snap/cmd_list.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2016 Canonical Ltd
     5   *
     6   * This program is free software: you can redistribute it and/or modify
     7   * it under the terms of the GNU General Public License version 3 as
     8   * published by the Free Software Foundation.
     9   *
    10   * This program is distributed in the hope that it will be useful,
    11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13   * GNU General Public License for more details.
    14   *
    15   * You should have received a copy of the GNU General Public License
    16   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17   *
    18   */
    19  
    20  package main
    21  
    22  import (
    23  	"errors"
    24  	"fmt"
    25  	"sort"
    26  	"strings"
    27  	"text/tabwriter"
    28  
    29  	"github.com/jessevdk/go-flags"
    30  
    31  	"github.com/snapcore/snapd/client"
    32  	"github.com/snapcore/snapd/i18n"
    33  )
    34  
    35  var shortListHelp = i18n.G("List installed snaps")
    36  var longListHelp = i18n.G(`
    37  The list command displays a summary of snaps installed in the current system.
    38  
    39  A green check mark (given color and unicode support) after a publisher name
    40  indicates that the publisher has been verified.
    41  `)
    42  
    43  type cmdList struct {
    44  	clientMixin
    45  	Positional struct {
    46  		Snaps []installedSnapName `positional-arg-name:"<snap>"`
    47  	} `positional-args:"yes"`
    48  
    49  	All bool `long:"all"`
    50  	colorMixin
    51  }
    52  
    53  func init() {
    54  	addCommand("list", shortListHelp, longListHelp, func() flags.Commander { return &cmdList{} },
    55  		colorDescs.also(map[string]string{
    56  			// TRANSLATORS: This should not start with a lowercase letter.
    57  			"all": i18n.G("Show all revisions"),
    58  		}), nil)
    59  }
    60  
    61  type snapsByName []*client.Snap
    62  
    63  func (s snapsByName) Len() int           { return len(s) }
    64  func (s snapsByName) Less(i, j int) bool { return s[i].Name < s[j].Name }
    65  func (s snapsByName) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }
    66  
    67  var ErrNoMatchingSnaps = errors.New(i18n.G("no matching snaps installed"))
    68  
    69  // snapd will give us  and we want
    70  // "" (local snap)     "-"
    71  // latest/risk         latest/risk
    72  // track/risk          track/risk
    73  // track/risk/branch   track/risk/…
    74  // anything else       SISO
    75  func fmtChannel(ch string) string {
    76  	if ch == "" {
    77  		// "" -> "-" (local snap)
    78  		return "-"
    79  	}
    80  	if strings.Count(ch, "/") != 2 {
    81  		return ch
    82  	}
    83  	idx := strings.LastIndexByte(ch, '/')
    84  	return ch[:idx+1] + "…"
    85  }
    86  
    87  func (x *cmdList) Execute(args []string) error {
    88  	if len(args) > 0 {
    89  		return ErrExtraArgs
    90  	}
    91  
    92  	names := installedSnapNames(x.Positional.Snaps)
    93  	snaps, err := x.client.List(names, &client.ListOptions{All: x.All})
    94  	if err != nil {
    95  		if err == client.ErrNoSnapsInstalled {
    96  			if len(names) == 0 {
    97  				fmt.Fprintln(Stderr, i18n.G("No snaps are installed yet. Try 'snap install hello-world'."))
    98  				return nil
    99  			} else {
   100  				return ErrNoMatchingSnaps
   101  			}
   102  		}
   103  		return err
   104  	} else if len(snaps) == 0 {
   105  		return ErrNoMatchingSnaps
   106  	}
   107  	sort.Sort(snapsByName(snaps))
   108  
   109  	esc := x.getEscapes()
   110  	w := tabWriter()
   111  
   112  	// TRANSLATORS: the %s is to insert a filler escape sequence (please keep it flush to the column header, with no extra spaces)
   113  	fmt.Fprintf(w, i18n.G("Name\tVersion\tRev\tTracking\tPublisher%s\tNotes\n"), fillerPublisher(esc))
   114  
   115  	for _, snap := range snaps {
   116  		// doing it this way because otherwise it's a sea of %s\t%s\t%s
   117  		line := []string{
   118  			snap.Name,
   119  			snap.Version,
   120  			snap.Revision.String(),
   121  			fmtChannel(snap.TrackingChannel),
   122  			shortPublisher(esc, snap.Publisher),
   123  			NotesFromLocal(snap).String(),
   124  		}
   125  		fmt.Fprintln(w, strings.Join(line, "\t"))
   126  	}
   127  	w.Flush()
   128  
   129  	return nil
   130  }
   131  
   132  func tabWriter() *tabwriter.Writer {
   133  	return tabwriter.NewWriter(Stdout, 5, 3, 2, ' ', 0)
   134  }