From 117f71ecfd2ecf8f42e202aecb6f6b6c171a6107 Mon Sep 17 00:00:00 2001 From: Achille Roussel Date: Mon, 29 May 2023 17:42:18 -0700 Subject: [PATCH] order table output of get command Signed-off-by: Achille Roussel --- internal/cmd/get.go | 127 +++++++++++++++++------------- internal/print/textprint/table.go | 91 +++++++++++++-------- 2 files changed, 127 insertions(+), 91 deletions(-) diff --git a/internal/cmd/get.go b/internal/cmd/get.go index 12aa34eb..a05d6319 100644 --- a/internal/cmd/get.go +++ b/internal/cmd/get.go @@ -38,8 +38,7 @@ Examples: "digest": "sha256:9d7b7563baf3702cf24ed3688dc9a58faef2d0ac586041cb2dc95df919f5e5f2", "size": 7150231, "annotations": { - "timecraft.module.name": "app.wasm", - "timecraft.object.created-at": "2023-05-28T21:52:26Z" + "timecraft.module.name": "app.wasm" } } @@ -146,88 +145,104 @@ Did you mean?%s`, resourceTypeLookup, joinResourceTypes(matchingResources, "\n return err } -func getConfigs(ctx context.Context, w io.Writer, r *timemachine.Registry) stream.WriteCloser[*format.Descriptor] { +func getConfigs(ctx context.Context, w io.Writer, reg *timemachine.Registry) stream.WriteCloser[*format.Descriptor] { type config struct { ID string `text:"CONFIG ID"` Runtime string `text:"RUNTIME"` Modules int `text:"MODULES"` Size human.Bytes `text:"SIZE"` } - return newDescTableWriter(w, func(desc *format.Descriptor) (config, error) { - c, err := r.LookupConfig(ctx, desc.Digest) - if err != nil { - return config{}, err - } - r, err := r.LookupRuntime(ctx, c.Runtime.Digest) - if err != nil { - return config{}, err - } - return config{ - ID: desc.Digest.Short(), - Runtime: r.Runtime + " (" + r.Version + ")", - Modules: len(c.Modules), - Size: human.Bytes(desc.Size), - }, nil - }) + return newDescTableWriter(w, + func(c1, c2 config) bool { + return c1.ID < c2.ID + }, + func(desc *format.Descriptor) (config, error) { + c, err := reg.LookupConfig(ctx, desc.Digest) + if err != nil { + return config{}, err + } + r, err := reg.LookupRuntime(ctx, c.Runtime.Digest) + if err != nil { + return config{}, err + } + return config{ + ID: desc.Digest.Short(), + Runtime: r.Runtime + " (" + r.Version + ")", + Modules: len(c.Modules), + Size: human.Bytes(desc.Size), + }, nil + }) } -func getModules(ctx context.Context, w io.Writer, r *timemachine.Registry) stream.WriteCloser[*format.Descriptor] { +func getModules(ctx context.Context, w io.Writer, reg *timemachine.Registry) stream.WriteCloser[*format.Descriptor] { type module struct { ID string `text:"MODULE ID"` Name string `text:"MODULE NAME"` Size human.Bytes `text:"SIZE"` } - return newDescTableWriter(w, func(desc *format.Descriptor) (module, error) { - name := desc.Annotations["timecraft.module.name"] - if name == "" { - name = "(none)" - } - return module{ - ID: desc.Digest.Short(), - Name: name, - Size: human.Bytes(desc.Size), - }, nil - }) + return newDescTableWriter(w, + func(m1, m2 module) bool { + return m1.ID < m2.ID + }, + func(desc *format.Descriptor) (module, error) { + name := desc.Annotations["timecraft.module.name"] + if name == "" { + name = "(none)" + } + return module{ + ID: desc.Digest.Short(), + Name: name, + Size: human.Bytes(desc.Size), + }, nil + }) } -func getProcesses(ctx context.Context, w io.Writer, r *timemachine.Registry) stream.WriteCloser[*format.Descriptor] { +func getProcesses(ctx context.Context, w io.Writer, reg *timemachine.Registry) stream.WriteCloser[*format.Descriptor] { type process struct { ID format.UUID `text:"PROCESS ID"` StartTime human.Time `text:"STARTED"` } - return newDescTableWriter(w, func(desc *format.Descriptor) (process, error) { - p, err := r.LookupProcess(ctx, desc.Digest) - if err != nil { - return process{}, err - } - return process{ - ID: p.ID, - StartTime: human.Time(p.StartTime), - }, nil - }) + return newDescTableWriter(w, + func(p1, p2 process) bool { + return time.Time(p1.StartTime).Before(time.Time(p2.StartTime)) + }, + func(desc *format.Descriptor) (process, error) { + p, err := reg.LookupProcess(ctx, desc.Digest) + if err != nil { + return process{}, err + } + return process{ + ID: p.ID, + StartTime: human.Time(p.StartTime), + }, nil + }) } -func getRuntimes(ctx context.Context, w io.Writer, r *timemachine.Registry) stream.WriteCloser[*format.Descriptor] { +func getRuntimes(ctx context.Context, w io.Writer, reg *timemachine.Registry) stream.WriteCloser[*format.Descriptor] { type runtime struct { ID string `text:"RUNTIME ID"` Runtime string `text:"RUNTIME NAME"` Version string `text:"VERSION"` } - return newDescTableWriter(w, func(desc *format.Descriptor) (runtime, error) { - r, err := r.LookupRuntime(ctx, desc.Digest) - if err != nil { - return runtime{}, err - } - return runtime{ - ID: desc.Digest.Short(), - Runtime: r.Runtime, - Version: r.Version, - }, nil - }) + return newDescTableWriter(w, + func(r1, r2 runtime) bool { + return r1.ID < r2.ID + }, + func(desc *format.Descriptor) (runtime, error) { + r, err := reg.LookupRuntime(ctx, desc.Digest) + if err != nil { + return runtime{}, err + } + return runtime{ + ID: desc.Digest.Short(), + Runtime: r.Runtime, + Version: r.Version, + }, nil + }) } -func newDescTableWriter[T any](w io.Writer, conv func(*format.Descriptor) (T, error)) stream.WriteCloser[*format.Descriptor] { - tw := textprint.NewTableWriter[T](w) +func newDescTableWriter[T any](w io.Writer, orderBy func(T, T) bool, conv func(*format.Descriptor) (T, error)) stream.WriteCloser[*format.Descriptor] { + tw := textprint.NewTableWriter[T](w, textprint.OrderBy(orderBy)) cw := stream.ConvertWriter[T](tw, conv) return stream.NewWriteCloser(cw, tw) } diff --git a/internal/print/textprint/table.go b/internal/print/textprint/table.go index c90031e7..bae5efda 100644 --- a/internal/print/textprint/table.go +++ b/internal/print/textprint/table.go @@ -7,35 +7,64 @@ import ( "text/tabwriter" "github.com/stealthrocket/timecraft/internal/stream" + "golang.org/x/exp/slices" ) -func NewTableWriter[T any](w io.Writer) stream.WriteCloser[T] { +type TableOption[T any] func(*tableWriter[T]) + +func OrderBy[T any](f func(T, T) bool) TableOption[T] { + return func(t *tableWriter[T]) { + t.orderBy = f + } +} + +func NewTableWriter[T any](w io.Writer, opts ...TableOption[T]) stream.WriteCloser[T] { t := &tableWriter[T]{ - writer: tabwriter.NewWriter(w, 0, 4, 2, ' ', 0), - valueOf: func(values []T, index int) reflect.Value { - return reflect.ValueOf(&values[index]).Elem() - }, + output: w, } + for _, opt := range opts { + opt(t) + } + return t +} - writeString := func(w io.Writer, s string) { - _, err := io.WriteString(w, s) - if err != nil { - panic(err) - } +type tableWriter[T any] struct { + output io.Writer + values []T + orderBy func(T, T) bool +} + +func (t *tableWriter[T]) Write(values []T) (int, error) { + t.values = append(t.values, values...) + return len(values), nil +} + +func (t *tableWriter[T]) Close() error { + tw := tabwriter.NewWriter(t.output, 0, 4, 2, ' ', 0) + + if t.orderBy != nil { + slices.SortFunc(t.values, t.orderBy) + } + + valueOf := func(values []T, index int) reflect.Value { + return reflect.ValueOf(&values[index]).Elem() } var v T valueType := reflect.TypeOf(v) if valueType.Kind() == reflect.Pointer { valueType = valueType.Elem() - t.valueOf = func(values []T, index int) reflect.Value { + valueOf = func(values []T, index int) reflect.Value { return reflect.ValueOf(values[index]).Elem() } } + var encoders []encodeFunc for i, f := range reflect.VisibleFields(valueType) { if i != 0 { - writeString(t.writer, "\t") + if _, err := io.WriteString(tw, "\t"); err != nil { + return err + } } name := f.Name @@ -53,44 +82,36 @@ func NewTableWriter[T any](w io.Writer) stream.WriteCloser[T] { continue } - writeString(t.writer, name) - t.encoders = append(t.encoders, encodeFuncOfStructField(f.Type, f.Index)) + if _, err := io.WriteString(tw, name); err != nil { + return err + } + encoders = append(encoders, encodeFuncOfStructField(f.Type, f.Index)) } - writeString(t.writer, "\n") - return t -} - -type tableWriter[T any] struct { - writer *tabwriter.Writer - encoders []encodeFunc - valueOf func([]T, int) reflect.Value -} + if _, err := io.WriteString(tw, "\n"); err != nil { + return err + } -func (t *tableWriter[T]) Write(values []T) (int, error) { - for n := range values { - v := t.valueOf(values, n) - w := io.Writer(t.writer) + for n := range t.values { + v := valueOf(t.values, n) + w := io.Writer(tw) - for i, enc := range t.encoders { + for i, enc := range encoders { if i != 0 { _, err := io.WriteString(w, "\t") if err != nil { - return n, err + return err } } if err := enc(w, v); err != nil { - return n, err + return err } } if _, err := io.WriteString(w, "\n"); err != nil { - return n, err + return err } } - return len(values), nil -} -func (t *tableWriter[T]) Close() error { - return t.writer.Flush() + return tw.Flush() }