From 5697d05eb08c86a5c43c0c3b9db68d7aa78d537a Mon Sep 17 00:00:00 2001 From: Thomas Mitchell Date: Thu, 11 Mar 2021 10:03:59 -0500 Subject: [PATCH] update versions to show created at when available --- go.mod | 3 +- go.sum | 6 +++ main.go | 26 +++++++++-- ui.go | 140 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 170 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index ff5c311..f2c35bf 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.14 require ( github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 - github.com/cloudfoundry-community/vaultkv v0.1.1 + github.com/cloudfoundry-community/vaultkv v0.2.0 github.com/jhunt/go-ansi v0.0.0-20180630013815-403d5f0d9ccb github.com/jhunt/go-cli v0.0.0-20170503201019-f04a1744b5e3 github.com/jhunt/go-envirotron v0.0.0-20171017043611-8bdb90f72b39 @@ -12,6 +12,7 @@ require ( github.com/mattn/go-isatty v0.0.0-20151211000621-56b76bdf51f7 github.com/mitchellh/gox v1.0.1 // indirect github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect + github.com/olekukonko/tablewriter v0.0.5 github.com/onsi/ginkgo v1.13.0 github.com/onsi/gomega v1.10.1 github.com/pborman/uuid v1.2.1 diff --git a/go.sum b/go.sum index ab1f3b0..a6b0b09 100644 --- a/go.sum +++ b/go.sum @@ -2,6 +2,8 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPd github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/cloudfoundry-community/vaultkv v0.1.1 h1:z4N2Ze/N5o+R3d7oWQ1n+tQMWAw+V9SDKaZyDmTX/ms= github.com/cloudfoundry-community/vaultkv v0.1.1/go.mod h1:qjEGtHytd0cvF0m9LxA2oJlkP1k2CwRMimDeiW7iYgE= +github.com/cloudfoundry-community/vaultkv v0.2.0 h1:dOHzMDtkuDQprUrFupPF7XEnCDs/HgvnrcVTwIGUU6c= +github.com/cloudfoundry-community/vaultkv v0.2.0/go.mod h1:qjEGtHytd0cvF0m9LxA2oJlkP1k2CwRMimDeiW7iYgE= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= @@ -35,6 +37,8 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/mattn/go-isatty v0.0.0-20151211000621-56b76bdf51f7 h1:owMyzMR4QR+jSdlfkX9jPU3rsby4++j99BfbtgVr6ZY= github.com/mattn/go-isatty v0.0.0-20151211000621-56b76bdf51f7/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mitchellh/gox v1.0.1 h1:x0jD3dcHk9a9xPSDN6YEL4xL6Qz0dvNYm8yZqui5chI= github.com/mitchellh/gox v1.0.1/go.mod h1:ED6BioOGXMswlXa2zxfh/xdd5QhwYliBFn9V18Ap4z4= github.com/mitchellh/iochan v1.0.0 h1:C+X3KsSTLFVBr/tK1eYN/vs4rJcvsiLU338UhYPJWeY= @@ -43,6 +47,8 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWb github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= +github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.13.0 h1:M76yO2HkZASFjXL0HSoZJ1AYEmQxNJmY41Jx1zNUq1Y= diff --git a/main.go b/main.go index 8e7fdd1..e0dcc37 100644 --- a/main.go +++ b/main.go @@ -1938,18 +1938,36 @@ paths/keys. fmt.Printf("@B{%s}:\n", args[i]) } + const numColumns = 3 + table := table{} + + table.setHeader("version", "status", "created at") + for j := range versions { //Destroyed needs to be first because things can come back as both deleted _and_ destroyed. // destroyed is objectively more interesting. + statusString := "@G{alive}" if versions[j].Destroyed { - fmt.Printf("%d\t@R{destroyed}\n", versions[j].Version) + statusString = "@R{destroyed}" } else if versions[j].Deleted { - fmt.Printf("%d\t@Y{deleted}\n", versions[j].Version) - } else { - fmt.Printf("%d\t@G{alive}\n", versions[j].Version) + statusString = "@Y{deleted}" + } + + createdAtString := "unknown" + + if !versions[j].CreatedAt.IsZero() { + createdAtString = versions[j].CreatedAt.Local().Format(time.RFC822) } + + table.addRow( + fmt.Sprintf("%d", versions[j].Version), + fmt.Sprintf(statusString), + createdAtString, + ) } + table.print() + if len(args) > 1 && i != len(args)-1 { fmt.Printf("\n") } diff --git a/ui.go b/ui.go index 9536b6a..491070f 100644 --- a/ui.go +++ b/ui.go @@ -5,6 +5,7 @@ import ( "io/ioutil" "os" "strings" + "unicode" "github.com/jhunt/go-ansi" "github.com/starkandwayne/safe/prompt" @@ -79,3 +80,142 @@ func pr(label string, confirm bool, secure bool) string { ansi.Fprintf(os.Stderr, "\n@Y{oops, try again }(Ctrl-C to cancel)\n\n") } } + +type table struct { + headers []string + rows [][]string + numCols int +} + +func (t *table) setHeader(headers ...string) { + t._assertValidRowWidth(len(headers)) + t.headers = headers + t._formatHeaders() +} + +func (t *table) addRow(cols ...string) { + t._assertValidRowWidth(len(cols)) + t.rows = append(t.rows, cols) +} + +func (t *table) print() { + if t._getNumCols() == 0 { + return + } + + colWidths := t._calcColWidths() + + if len(t.headers) > 0 { + t._printRow(t.headers, colWidths) + } + + for rowNum := range t.rows { + t._printRow(t.rows[rowNum], colWidths) + } +} + +func (t *table) _assertValidRowWidth(numCols int) { + if numCols == 0 { + panic("Cannot append row with zero columns") + } + + existingCols := t._getNumCols() + if existingCols != 0 && numCols != existingCols { + panic("Number of columns in each row must be consistent") + } + +} + +func (t *table) _getNumCols() int { + if len(t.headers) != 0 { + return len(t.headers) + } + if len(t.rows) != 0 { + return len(t.rows[0]) + } + return 0 +} + +func (t *table) _calcColWidths() []int { + ret := make([]int, t._getNumCols()) + for i := 0; i < len(ret); i++ { + ret[i] = t._calcColWidth(i) + } + + return ret +} + +func (t *table) _calcColWidth(colNum int) int { + maxWidth := 0 + if len(t.headers) != 0 { + maxWidth = t._calcDisplayWidth(t.headers[colNum]) + } + + for rowNum := range t.rows { + cellWidth := t._calcDisplayWidth(t.rows[rowNum][colNum]) + if cellWidth > maxWidth { + maxWidth = cellWidth + } + } + + return maxWidth +} + +func (t *table) _calcDisplayWidth(cell string) int { + const asciiEscapeStart = '\033' + const asciiEscapeEnd = 'm' + count := 0 + state := 0 + for _, c := range cell { + switch state { + case 0: //not ascii escape + if c == asciiEscapeStart { + state = 1 + } else if unicode.IsGraphic(c) { + count++ + } + + case 1: //in ascii escape + if c == asciiEscapeEnd { + state = 0 + } + } + } + + return count +} + +func (t *table) _formatHeaders() { + for colNum := range t.headers { + t.headers[colNum] = ansi.Sprintf("@M{%s}", t.headers[colNum]) + } +} + +func (t *table) _printRow(row []string, widths []int) { + const colBuffer = 2 //two spaces min between cols + //print every col except last, inserting buffer spaces + for colNum := 0; colNum < len(row)-1; colNum++ { + t._printCell( + row[colNum], + widths[colNum]+colBuffer-t._calcDisplayWidth(row[colNum])) + } + + //no spaces at the end of the last col + t._printCell(row[len(row)-1], 0) + os.Stdout.Write([]byte{'\n'}) +} + +func (t *table) _printCell(cell string, spaces int) { + os.Stdout.Write([]byte(cell)) + + if spaces == 0 { + return + } + + spaceBuf := make([]byte, spaces) + for idx := 0; idx < spaces; idx++ { + spaceBuf[idx] = ' ' + } + + os.Stdout.Write(spaceBuf) +}