Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hide columns in the list command #1266

Merged
merged 2 commits into from
Feb 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 19 additions & 1 deletion cmd/limactl/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@ package main
import (
"errors"
"fmt"
"os"
"reflect"
"sort"
"strings"

"github.com/cheggaaa/pb/v3/termutil"
"github.com/lima-vm/lima/pkg/store"
"github.com/mattn/go-isatty"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
Expand Down Expand Up @@ -54,6 +57,7 @@ func newListCommand() *cobra.Command {
listCommand.Flags().Bool("list-fields", false, "List fields available for format")
listCommand.Flags().Bool("json", false, "JSONify output")
listCommand.Flags().BoolP("quiet", "q", false, "Only show names")
listCommand.Flags().Bool("all-fields", false, "Show all fields")

return listCommand
}
Expand Down Expand Up @@ -155,7 +159,21 @@ func listAction(cmd *cobra.Command, args []string) error {
}
}

return store.PrintInstances(cmd.OutOrStdout(), instances, format)
allFields, err := cmd.Flags().GetBool("all-fields")
if err != nil {
return err
}

options := store.PrintOptions{AllFields: allFields}
out := cmd.OutOrStdout()
if out == os.Stdout {
if isatty.IsTerminal(os.Stdout.Fd()) || isatty.IsCygwinTerminal(os.Stdout.Fd()) {
if w, err := termutil.TerminalWidth(); err == nil {
options.TerminalWidth = w
}
}
}
return store.PrintInstances(out, instances, format, &options)
}

func listBashComplete(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
Expand Down
91 changes: 85 additions & 6 deletions pkg/store/instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -232,17 +232,80 @@ func AddGlobalFields(inst *Instance) (FormatData, error) {
return data, nil
}

type PrintOptions struct {
AllFields bool
TerminalWidth int
}

// PrintInstances prints instances in a requested format to a given io.Writer.
// Supported formats are "json", "yaml", "table", or a go template
func PrintInstances(w io.Writer, instances []*Instance, format string) error {
func PrintInstances(w io.Writer, instances []*Instance, format string, options *PrintOptions) error {
switch format {
case "json":
format = "{{json .}}"
case "yaml":
format = "{{yaml .}}"
case "table":
types := map[string]int{}
archs := map[string]int{}
for _, instance := range instances {
types[instance.VMType]++
archs[instance.Arch]++
}
all := options != nil && options.AllFields
width := 0
if options != nil {
width = options.TerminalWidth
}
columnWidth := 8
hideType := false
hideArch := false
hideDir := false

columns := 1 // NAME
columns += 2 // STATUS
columns += 2 // SSH
// can we still fit the remaining columns (7)
if width == 0 || (columns+7)*columnWidth > width && !all {
hideType = len(types) == 1
}
if !hideType {
columns++ // VMTYPE
}
// only hide arch if it is the same as the host arch
goarch := limayaml.NewArch(runtime.GOARCH)
// can we still fit the remaining columns (6)
if width == 0 || (columns+6)*columnWidth > width && !all {
hideArch = len(archs) == 1 && instances[0].Arch == goarch
}
if !hideArch {
columns++ // ARCH
}
columns++ // CPUS
columns++ // MEMORY
columns++ // DISK
// can we still fit the remaining columns (2)
if width != 0 && (columns+2)*columnWidth > width && !all {
hideDir = true
}
if !hideDir {
columns += 2 // DIR
}
_ = columns

w := tabwriter.NewWriter(w, 4, 8, 4, ' ', 0)
fmt.Fprintln(w, "NAME\tSTATUS\tSSH\tVMTYPE\tARCH\tCPUS\tMEMORY\tDISK\tDIR")
fmt.Fprint(w, "NAME\tSTATUS\tSSH")
if !hideType {
fmt.Fprint(w, "\tVMTYPE")
}
if !hideArch {
fmt.Fprint(w, "\tARCH")
}
fmt.Fprint(w, "\tCPUS\tMEMORY\tDISK")
if !hideDir {
fmt.Fprint(w, "\tDIR")
}
fmt.Fprintln(w)

u, err := user.Current()
if err != nil {
Expand All @@ -255,17 +318,33 @@ func PrintInstances(w io.Writer, instances []*Instance, format string) error {
if strings.HasPrefix(dir, homeDir) {
dir = strings.Replace(dir, homeDir, "~", 1)
}
fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%d\t%s\t%s\t%s\n",
fmt.Fprintf(w, "%s\t%s\t%s",
instance.Name,
instance.Status,
fmt.Sprintf("127.0.0.1:%d", instance.SSHLocalPort),
instance.VMType,
instance.Arch,
)
if !hideType {
fmt.Fprintf(w, "\t%s",
instance.VMType,
)
}
if !hideArch {
fmt.Fprintf(w, "\t%s",
instance.Arch,
)
}
fmt.Fprintf(w, "\t%d\t%s\t%s",
instance.CPUs,
units.BytesSize(float64(instance.Memory)),
units.BytesSize(float64(instance.Disk)),
dir,
)
if !hideDir {
fmt.Fprintf(w, "\t%s",
dir,
)
}
fmt.Fprint(w, "\n")

}
return w.Flush()
default:
Expand Down
143 changes: 143 additions & 0 deletions pkg/store/instance_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
package store

import (
"bytes"
"os/user"
"path/filepath"
"runtime"
"testing"

"github.com/lima-vm/lima/pkg/limayaml"
"gotest.tools/v3/assert"
)

const separator = string(filepath.Separator)

var vmtype = limayaml.QEMU
var goarch = limayaml.NewArch(runtime.GOARCH)

var instance = Instance{
Name: "foo",
Status: StatusStopped,
VMType: vmtype,
Arch: goarch,
Dir: "dir",
}

var table = "NAME STATUS SSH CPUS MEMORY DISK DIR\n" +
"foo Stopped 127.0.0.1:0 0 0B 0B dir\n"

var tableEmu = "NAME STATUS SSH ARCH CPUS MEMORY DISK DIR\n" +
"foo Stopped 127.0.0.1:0 unknown 0 0B 0B dir\n"

var tableHome = "NAME STATUS SSH CPUS MEMORY DISK DIR\n" +
"foo Stopped 127.0.0.1:0 0 0B 0B ~" + separator + "dir\n"

var tableAll = "NAME STATUS SSH VMTYPE ARCH CPUS MEMORY DISK DIR\n" +
"foo Stopped 127.0.0.1:0 " + vmtype + " " + goarch + " 0 0B 0B dir\n"

// for width 60, everything is hidden
var table60 = "NAME STATUS SSH CPUS MEMORY DISK\n" +
"foo Stopped 127.0.0.1:0 0 0B 0B\n"

// for width 80, identical is hidden (type/arch)
var table80i = "NAME STATUS SSH CPUS MEMORY DISK DIR\n" +
"foo Stopped 127.0.0.1:0 0 0B 0B dir\n"

// for width 80, different arch is still shown (not dir)
var table80d = "NAME STATUS SSH ARCH CPUS MEMORY DISK\n" +
"foo Stopped 127.0.0.1:0 unknown 0 0B 0B\n"

// for width 100, nothing is hidden
var table100 = "NAME STATUS SSH VMTYPE ARCH CPUS MEMORY DISK DIR\n" +
"foo Stopped 127.0.0.1:0 " + vmtype + " " + goarch + " 0 0B 0B dir\n"

// for width 80, directory is hidden (if not identical)
var tableTwo = "NAME STATUS SSH VMTYPE ARCH CPUS MEMORY DISK\n" +
"foo Stopped 127.0.0.1:0 qemu x86_64 0 0B 0B\n" +
"bar Stopped 127.0.0.1:0 vz aarch64 0 0B 0B\n"

func TestPrintInstanceTable(t *testing.T) {
var buf bytes.Buffer
instances := []*Instance{&instance}
PrintInstances(&buf, instances, "table", nil)
assert.Equal(t, table, buf.String())
}

func TestPrintInstanceTableEmu(t *testing.T) {
var buf bytes.Buffer
instance1 := instance
instance1.Arch = "unknown"
instances := []*Instance{&instance1}
PrintInstances(&buf, instances, "table", nil)
assert.Equal(t, tableEmu, buf.String())
}

func TestPrintInstanceTableHome(t *testing.T) {
var buf bytes.Buffer
u, err := user.Current()
assert.NilError(t, err)
instance1 := instance
instance1.Dir = filepath.Join(u.HomeDir, "dir")
instances := []*Instance{&instance1}
PrintInstances(&buf, instances, "table", nil)
assert.Equal(t, tableHome, buf.String())
}

func TestPrintInstanceTable60(t *testing.T) {
var buf bytes.Buffer
instances := []*Instance{&instance}
options := PrintOptions{TerminalWidth: 60}
PrintInstances(&buf, instances, "table", &options)
assert.Equal(t, table60, buf.String())
}

func TestPrintInstanceTable80SameArch(t *testing.T) {
var buf bytes.Buffer
instances := []*Instance{&instance}
options := PrintOptions{TerminalWidth: 80}
PrintInstances(&buf, instances, "table", &options)
assert.Equal(t, table80i, buf.String())
}

func TestPrintInstanceTable80DiffArch(t *testing.T) {
var buf bytes.Buffer
instance1 := instance
instance1.Arch = limayaml.NewArch("unknown")
instances := []*Instance{&instance1}
options := PrintOptions{TerminalWidth: 80}
PrintInstances(&buf, instances, "table", &options)
assert.Equal(t, table80d, buf.String())
}

func TestPrintInstanceTable100(t *testing.T) {
var buf bytes.Buffer
instances := []*Instance{&instance}
options := PrintOptions{TerminalWidth: 100}
PrintInstances(&buf, instances, "table", &options)
assert.Equal(t, table100, buf.String())
}

func TestPrintInstanceTableAll(t *testing.T) {
var buf bytes.Buffer
instances := []*Instance{&instance}
options := PrintOptions{TerminalWidth: 40, AllFields: true}
PrintInstances(&buf, instances, "table", &options)
assert.Equal(t, tableAll, buf.String())
}

func TestPrintInstanceTableTwo(t *testing.T) {
var buf bytes.Buffer
instance1 := instance
instance1.Name = "foo"
instance1.VMType = limayaml.QEMU
instance1.Arch = limayaml.X8664
instance2 := instance
instance2.Name = "bar"
instance2.VMType = limayaml.VZ
instance2.Arch = limayaml.AARCH64
instances := []*Instance{&instance1, &instance2}
options := PrintOptions{TerminalWidth: 80}
PrintInstances(&buf, instances, "table", &options)
assert.Equal(t, tableTwo, buf.String())
}