Path: blob/main/components/local-app/pkg/prettyprint/prettyprint.go
2500 views
// Copyright (c) 2023 Gitpod GmbH. All rights reserved.1// Licensed under the GNU Affero General Public License (AGPL).2// See License.AGPL.txt in the project root for license information.34package prettyprint56import (7"fmt"8"io"9"reflect"10"strconv"11"strings"12"text/tabwriter"1314v1 "github.com/gitpod-io/gitpod/components/public-api/go/experimental/v1"15)1617func reflectTabular[T any](data []T) (header []string, rows []map[string]string, err error) {18type field struct {19Name string20Field *reflect.StructField21}22var fields []field2324var dt T25t := reflect.TypeOf(dt)26if t.Kind() == reflect.Ptr {27t = t.Elem()28}29if t.Kind() != reflect.Struct {30return nil, nil, MarkExceptional(fmt.Errorf("can only reflect tabular data from structs"))31}32for i := 0; i < t.NumField(); i++ {33f := t.Field(i)34switch f.Type.Kind() {35case reflect.String, reflect.Bool, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:36default:37continue38}39name := f.Tag.Get("print")40if name == "" {41name = f.Name42}43fields = append(fields, field{Name: name, Field: &f})44header = append(header, name)45}4647rows = make([]map[string]string, 0, len(rows))48for _, row := range data {49r := make(map[string]string)50for _, f := range fields {51v := reflect.ValueOf(row)52if v.Kind() == reflect.Ptr {53v = v.Elem()54}55v = v.FieldByName(f.Field.Name)56switch v.Kind() {57case reflect.String:58r[f.Name] = v.String()59case reflect.Bool:60r[f.Name] = FormatBool(v.Bool())61case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:62r[f.Name] = strconv.FormatInt(v.Int(), 10)63}64}65rows = append(rows, r)66}6768return header, rows, nil69}7071type WriterFormat int7273const (74// WriterFormatWide makes the writer produce wide formatted output, e.g.75// FIELD ONE FIELD TWO FIELD THREE76// valueOne valueTwo valueThree77// valueOne valueTwo valueThree78WriterFormatWide WriterFormat = iota7980// WriterFormatNarrow makes the writer produce narrow formatted output, e.g.81// Field: value82WriterFormatNarrow83)8485type Writer[T any] struct {86Out io.Writer87Format WriterFormat88Field string89}9091// Write writes the given tabular data to the writer92func (w Writer[T]) Write(data []T) error {93header, rows, err := reflectTabular(data)94if err != nil {95return err96}9798tw := tabwriter.NewWriter(w.Out, 0, 4, 1, ' ', 0)99defer tw.Flush()100101switch {102case w.Field != "":103return w.writeField(tw, header, rows)104case w.Format == WriterFormatNarrow:105return w.writeNarrowFormat(tw, header, rows)106default:107return w.writeWideFormat(tw, header, rows)108}109}110111// writeField writes a single field of the given tabular data to the writer112func (w Writer[T]) writeField(tw *tabwriter.Writer, header []string, rows []map[string]string) error {113var found bool114for _, h := range header {115if h == w.Field {116found = true117break118}119}120if !found {121return AddResolution(fmt.Errorf("unknown field: %s", w.Field), "use one of the following fields: "+strings.Join(header, ", "))122}123124for _, row := range rows {125val := row[w.Field]126if val == "" {127continue128}129_, err := tw.Write([]byte(fmt.Sprintf("%s\n", val)))130if err != nil {131return err132}133}134return nil135}136137// writeNarrowFormat writes the given tabular data to the writer in a long format138func (w Writer[T]) writeNarrowFormat(tw *tabwriter.Writer, header []string, rows []map[string]string) error {139for _, row := range rows {140for _, h := range header {141fieldName := Capitalize(h)142fieldName = strings.ReplaceAll(fieldName, "id", "ID")143144_, err := tw.Write([]byte(fmt.Sprintf("%s:\t%s\n", fieldName, row[h])))145if err != nil {146return err147}148}149}150return nil151}152153// writeWideFormat writes the given tabular data to the writer in a short format154func (w Writer[T]) writeWideFormat(tw *tabwriter.Writer, header []string, rows []map[string]string) error {155for _, h := range header {156_, err := tw.Write([]byte(fmt.Sprintf("%s\t", strings.ToUpper(h))))157if err != nil {158return err159}160}161_, _ = tw.Write([]byte("\n"))162for _, row := range rows {163for _, h := range header {164_, err := tw.Write([]byte(fmt.Sprintf("%s\t", row[h])))165if err != nil {166return err167}168}169_, err := tw.Write([]byte("\n"))170if err != nil {171return err172}173}174return nil175}176177// FormatBool returns "true" or "false" depending on the value of b.178func FormatBool(b bool) string {179return strconv.FormatBool(b)180}181182// FormatWorkspacePhase returns a user-facing representation of the given workspace phase183func FormatWorkspacePhase(phase v1.WorkspaceInstanceStatus_Phase) string {184return strings.ToLower(strings.TrimPrefix(phase.String(), "PHASE_"))185}186187// Capitalize capitalizes the first letter of the given string188func Capitalize(s string) string {189if s == "" {190return ""191}192if len(s) == 1 {193return strings.ToUpper(s)194}195196return strings.ToUpper(s[0:1]) + s[1:]197}198199200