github.com/saucelabs/saucectl@v0.175.1/internal/cmd/storage/list.go (about)

     1  package storage
     2  
     3  import (
     4  	"encoding/json"
     5  	"errors"
     6  	"fmt"
     7  	"os"
     8  	"time"
     9  
    10  	"github.com/dustin/go-humanize"
    11  	"github.com/jedib0t/go-pretty/v6/table"
    12  	"github.com/jedib0t/go-pretty/v6/text"
    13  	cmds "github.com/saucelabs/saucectl/internal/cmd"
    14  	"github.com/saucelabs/saucectl/internal/segment"
    15  	"github.com/saucelabs/saucectl/internal/storage"
    16  	"github.com/saucelabs/saucectl/internal/usage"
    17  	"github.com/spf13/cobra"
    18  	"golang.org/x/text/cases"
    19  	"golang.org/x/text/language"
    20  )
    21  
    22  var defaultTableStyle = table.Style{
    23  	Name: "saucy",
    24  	Box: table.BoxStyle{
    25  		BottomLeft:       "└",
    26  		BottomRight:      "┘",
    27  		BottomSeparator:  "",
    28  		EmptySeparator:   text.RepeatAndTrim(" ", text.RuneCount("+")),
    29  		Left:             "│",
    30  		LeftSeparator:    "",
    31  		MiddleHorizontal: "─",
    32  		MiddleSeparator:  "",
    33  		MiddleVertical:   "",
    34  		PaddingLeft:      " ",
    35  		PaddingRight:     " ",
    36  		PageSeparator:    "\n",
    37  		Right:            "│",
    38  		RightSeparator:   "",
    39  		TopLeft:          "┌",
    40  		TopRight:         "┐",
    41  		TopSeparator:     "",
    42  		UnfinishedRow:    " ...",
    43  	},
    44  	Color: table.ColorOptionsDefault,
    45  	Format: table.FormatOptions{
    46  		Footer: text.FormatDefault,
    47  		Header: text.FormatDefault,
    48  		Row:    text.FormatDefault,
    49  	},
    50  	HTML: table.DefaultHTMLOptions,
    51  	Options: table.Options{
    52  		DrawBorder:      false,
    53  		SeparateColumns: false,
    54  		SeparateFooter:  true,
    55  		SeparateHeader:  true,
    56  		SeparateRows:    false,
    57  	},
    58  	Title: table.TitleOptionsDefault,
    59  }
    60  
    61  func ListCommand() *cobra.Command {
    62  	var query string
    63  	var name string
    64  	var out string
    65  	var sha256 string
    66  
    67  	cmd := &cobra.Command{
    68  		Use: "list",
    69  		Aliases: []string{
    70  			"ls",
    71  		},
    72  		Short:        "Returns the list of files that have been uploaded to Sauce Storage.",
    73  		SilenceUsage: true,
    74  		PreRun: func(cmd *cobra.Command, args []string) {
    75  			tracker := segment.DefaultTracker
    76  
    77  			go func() {
    78  				tracker.Collect(
    79  					cases.Title(language.English).String(cmds.FullName(cmd)),
    80  					usage.Properties{}.SetFlags(cmd.Flags()),
    81  				)
    82  				_ = tracker.Close()
    83  			}()
    84  		},
    85  		RunE: func(cmd *cobra.Command, args []string) error {
    86  			list, err := appsClient.List(storage.ListOptions{
    87  				Q:      query,
    88  				Name:   name,
    89  				SHA256: sha256,
    90  			})
    91  			if err != nil {
    92  				return fmt.Errorf("failed to retrieve list: %w", err)
    93  			}
    94  			switch out {
    95  			case "text":
    96  				renderTable(list)
    97  			case "json":
    98  				if err := renderJSON(list); err != nil {
    99  					return fmt.Errorf("failed to render output: %w", err)
   100  				}
   101  			default:
   102  				return errors.New("unknown output format")
   103  			}
   104  
   105  			return nil
   106  		},
   107  	}
   108  
   109  	flags := cmd.Flags()
   110  	flags.StringVarP(&query, "query", "q", "",
   111  		"Any search term (such as app name, file name, description, build number or version) by which you want to filter.",
   112  	)
   113  	flags.StringVarP(&name, "name", "n", "",
   114  		"The filename (case-insensitive) by which you want to filter.",
   115  	)
   116  	flags.StringVar(&sha256, "sha256", "",
   117  		"The checksum of the file by which you want to filter.",
   118  	)
   119  	flags.StringVarP(&out, "out", "o", "text",
   120  		"Output format to the console. Options: text, json.",
   121  	)
   122  
   123  	return cmd
   124  }
   125  
   126  func renderTable(list storage.List) {
   127  	if len(list.Items) == 0 {
   128  		println("No files match the search criteria.")
   129  	}
   130  
   131  	t := table.NewWriter()
   132  	t.SetStyle(defaultTableStyle)
   133  	t.SuppressEmptyColumns()
   134  
   135  	t.AppendHeader(table.Row{"Size", "Uploaded", "ID", "Name"})
   136  	t.SetColumnConfigs([]table.ColumnConfig{
   137  		{
   138  			Name:        "Size",
   139  			AlignHeader: text.AlignLeft,
   140  			Align:       text.AlignRight,
   141  			AlignFooter: text.AlignRight,
   142  			Transformer: func(val interface{}) string {
   143  				t, _ := val.(int)
   144  				return humanize.Bytes(uint64(t))
   145  			},
   146  		},
   147  		{
   148  			Name:        "Uploaded",
   149  			Align:       text.AlignRight,
   150  			AlignFooter: text.AlignRight,
   151  			Transformer: func(val interface{}) string {
   152  				t, _ := val.(time.Time)
   153  				return t.Format(time.Stamp)
   154  			},
   155  		},
   156  		{
   157  			Name: "ID",
   158  		},
   159  		{
   160  			Name: "Name",
   161  		},
   162  	})
   163  
   164  	for _, item := range list.Items {
   165  		// the order of values must match the order of the header
   166  		t.AppendRow(table.Row{item.Size, item.Uploaded, item.ID, item.Name})
   167  	}
   168  
   169  	fmt.Println(t.Render())
   170  
   171  	if list.Truncated {
   172  		println("\nYour query returned more files than we can display. Please refine your query.")
   173  	}
   174  }
   175  
   176  func renderJSON(val any) error {
   177  	return json.NewEncoder(os.Stdout).Encode(val)
   178  }