Fix cmdutil.PrintTable to handle non-simple strings

This commit is contained in:
Fraser Waters 2021-11-03 10:13:31 +00:00
parent 8fae881a81
commit 83e2cd7e4a
6 changed files with 121 additions and 23 deletions

View file

@ -574,6 +574,8 @@ github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7z
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rjeczalik/notify v0.9.2 h1:MiTWrPj55mNDHEiIX5YUSKefw/+lCQVoAFmD6oQm5w8=
github.com/rjeczalik/notify v0.9.2/go.mod h1:aErll2f0sUX9PXZnVNyeiObbmTlk5jnMoCa4QEjJeqM=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=

View file

@ -61,6 +61,7 @@ require (
github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/spf13/pflag v1.0.3 // indirect
github.com/src-d/gcfg v1.4.0 // indirect
github.com/xanzy/ssh-agent v0.2.1 // indirect

View file

@ -164,6 +164,8 @@ github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y8
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sabhiram/go-gitignore v0.0.0-20180611051255-d3107576ba94 h1:G04eS0JkAIVZfaJLjla9dNxkJCPiKIGZlw9AfOhzOD0=

View file

@ -17,10 +17,11 @@ package cmdutil
import (
"fmt"
"os"
"regexp"
"runtime"
"strconv"
"strings"
"github.com/rivo/uniseg"
"golang.org/x/crypto/ssh/terminal"
"github.com/pulumi/pulumi/sdk/v3/go/common/util/ciutil"
@ -152,6 +153,9 @@ func (table *Table) ToStringWithGap(columnGap string) string {
allRows = append(allRows, table.Rows...)
// 7-bit C1 ANSI sequences
ansiEscape := regexp.MustCompile(`\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])`)
for rowIndex, row := range allRows {
columns := row.Columns
if len(columns) != len(preferredColumnWidths) {
@ -161,36 +165,42 @@ func (table *Table) ToStringWithGap(columnGap string) string {
}
for columnIndex, val := range columns {
preferredColumnWidths[columnIndex] = max(preferredColumnWidths[columnIndex], len(val))
// Strip ansi escape sequences
clean := ansiEscape.ReplaceAllString(val, "")
// Need to count graphemes not runes or bytes
preferredColumnWidths[columnIndex] = max(preferredColumnWidths[columnIndex], uniseg.GraphemeClusterCount(clean))
}
}
format := ""
for i, maxWidth := range preferredColumnWidths {
if i < len(preferredColumnWidths)-1 {
format += "%-" + strconv.Itoa(maxWidth+len(columnGap)) + "s"
} else {
result := ""
for _, row := range allRows {
result += table.Prefix
for columnIndex, val := range row.Columns {
result += val
if columnIndex < columnCount-1 {
// Work out how much whitespace we need to add to this string to bring it up to the
// preferredColumnWidth for this column. Remembering to ignore ansiEscapes
// Strip ansi escape sequences
clean := ansiEscape.ReplaceAllString(val, "")
maxWidth := preferredColumnWidths[columnIndex]
padding := maxWidth - uniseg.GraphemeClusterCount(clean)
result += strings.Repeat(" ", padding)
// Now, ensure we have the requested gap between columns as well.
result += columnGap
}
// do not want whitespace appended to the last column. It would cause wrapping on lines
// that were not actually long if some other line was very long.
format += "%s"
}
}
format += "\n"
result := ""
columns := make([]interface{}, columnCount)
for _, row := range allRows {
for columnIndex, value := range row.Columns {
// Now, ensure we have the requested gap between columns as well.
if columnIndex < columnCount-1 {
value += columnGap
}
columns[columnIndex] = value
}
result += fmt.Sprintf(table.Prefix+format, columns...)
result += "\n"
if row.AdditionalInfo != "" {
result += fmt.Sprint(row.AdditionalInfo)
result += row.AdditionalInfo
}
}
return result

View file

@ -0,0 +1,81 @@
// Copyright 2016-2021, Pulumi Corporation.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cmdutil
import (
"regexp"
"testing"
"github.com/pulumi/pulumi/sdk/v3/go/common/diag/colors"
"github.com/stretchr/testify/assert"
)
func TestTablePrinting(t *testing.T) {
rows := []TableRow{
{Columns: []string{"A", "B", "C"}},
{Columns: []string{"Some A", "B", "Some C"}},
}
table := &Table{
Headers: []string{"ColumnA", "Long column B", "C"},
Rows: rows,
Prefix: " ",
}
expected := "" +
" ColumnA Long column B C\n" +
" A B C\n" +
" Some A B Some C\n"
assert.Equal(t, expected, table.ToStringWithGap(" "))
}
func TestColorTablePrinting(t *testing.T) {
greenText := func(msg string) string {
return colors.Always.Colorize(colors.Green + msg + colors.Reset)
}
rows := []TableRow{
{Columns: []string{greenText("+"), "pulumi:pulumi:Stack", "aws-cs-webserver-test", greenText("create")}},
{Columns: []string{greenText("+"), "├─ aws:ec2/instance:Instance", "web-server-www", greenText("create")}},
{Columns: []string{greenText("+"), "├─ aws:ec2/securityGroup:SecurityGroup", "web-secgrp", greenText("create")}},
{Columns: []string{greenText("+"), "└─ pulumi:providers:aws", "default_4_25_0", greenText("create")}},
}
columnHeader := func(msg string) string {
return colors.Always.Colorize(colors.Underline + colors.BrightBlue + msg + colors.Reset)
}
table := &Table{
Headers: []string{"", columnHeader("Type"), columnHeader("Name"), columnHeader("Plan")},
Rows: rows,
Prefix: " ",
}
expected := "" +
" Type Name Plan\n" +
" + pulumi:pulumi:Stack aws-cs-webserver-test create\n" +
" + ├─ aws:ec2/instance:Instance web-server-www create\n" +
" + ├─ aws:ec2/securityGroup:SecurityGroup web-secgrp create\n" +
" + └─ pulumi:providers:aws default_4_25_0 create\n"
colorTable := table.ToStringWithGap(" ")
// 7-bit C1 ANSI sequences
ansiEscape := regexp.MustCompile(`\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])`)
cleanTable := ansiEscape.ReplaceAllString(colorTable, "")
assert.Equal(t, expected, cleanTable)
}

View file

@ -567,6 +567,8 @@ github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7z
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rjeczalik/notify v0.9.2 h1:MiTWrPj55mNDHEiIX5YUSKefw/+lCQVoAFmD6oQm5w8=
github.com/rjeczalik/notify v0.9.2/go.mod h1:aErll2f0sUX9PXZnVNyeiObbmTlk5jnMoCa4QEjJeqM=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=