Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 11 additions & 8 deletions cmd/waza/cmd_check.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (
"path/filepath"
"strings"
"time"
"unicode/utf8"

"github.com/mattn/go-runewidth"

Expand Down Expand Up @@ -246,15 +245,17 @@ func printCheckSummaryTable(w interface{ Write([]byte) (int, error) }, reports [
const maxNameWidth = 25
const minNameWidth = 10

// Compute dynamic column width from the longest skill name.
// Compute dynamic column width (in terminal display cells) from the longest
// skill name. Display width, rather than the rune count, is used so the
// column stays aligned with padRight for wide characters (CJK, emoji).
nameWidth := len("Skill")
for _, r := range reports {
n := r.skillName
if n == "" {
n = "unnamed"
}
if runeLen := utf8.RuneCountInString(n); runeLen > nameWidth {
nameWidth = runeLen
if w := runewidth.StringWidth(n); w > nameWidth {
nameWidth = w
}
Comment on lines +257 to 259
}
if nameWidth > maxNameWidth {
Expand Down Expand Up @@ -337,13 +338,15 @@ func printCheckSummaryTable(w interface{ Write([]byte) (int, error) }, reports [
fmt.Fprintf(w, "\n") //nolint:errcheck
}

// truncateName shortens a name to maxLen runes, replacing the last rune with "…" if needed.
// truncateName shortens a name so that its terminal display width is at most
// maxLen cells, appending "…" when truncation happens. Display width is used
// (rather than the rune count) so that wide characters such as CJK and emoji
// stay aligned with padRight in the summary table.
func truncateName(name string, maxLen int) string {
runes := []rune(name)
if len(runes) <= maxLen {
if runewidth.StringWidth(name) <= maxLen {
return name
}
return string(runes[:maxLen-1]) + "…"
return runewidth.Truncate(name, maxLen, "…")
Comment on lines +341 to +349
}

// padRight pads s with spaces so its terminal display width reaches width.
Expand Down
43 changes: 43 additions & 0 deletions cmd/waza/cmd_check_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"testing"
"unicode/utf8"

"github.com/mattn/go-runewidth"
"github.com/microsoft/waza/cmd/waza/tokens"
"github.com/microsoft/waza/internal/scaffold"
"github.com/microsoft/waza/internal/scoring"
Expand Down Expand Up @@ -692,6 +693,48 @@ func TestPrintCheckSummaryTable_DynamicWidth(t *testing.T) {
}
}

func TestPrintCheckSummaryTable_WideCharAlignment(t *testing.T) {
// A skill name with fullwidth (CJK) characters must not break column
// alignment. The table pads by terminal display width (see padRight), so the
// dynamic name column has to be measured by display width too; otherwise a
// wide name overflows its column and shifts every following column right.
reports := []*readinessReport{
{skillName: "日本語スキル", complianceLevel: "Medium-High", tokenCount: 100, tokenLimit: 500},
{skillName: "azure-ai", complianceLevel: "Medium-High", tokenCount: 100, tokenLimit: 500},
}
var buf bytes.Buffer
printCheckSummaryTable(&buf, reports)

// The display column at which the second column begins must be identical on
// the header row ("Compliance") and on every data row ("Medium-High").
displayCol := func(line, token string) (int, bool) {
idx := strings.Index(line, token)
if idx < 0 {
return 0, false
}
return runewidth.StringWidth(line[:idx]), true
}

lines := strings.Split(buf.String(), "\n")
var want int
found := false
for _, line := range lines {
if col, ok := displayCol(line, "Compliance"); ok {
want, found = col, true
break
}
}
require.True(t, found, "header row with the Compliance column was not found")

for _, line := range lines {
if !strings.Contains(line, "Medium-High") {
continue
}
col, _ := displayCol(line, "Medium-High")
assert.Equal(t, want, col, "second column misaligned for row %q", line)
}
Comment on lines +729 to +735
}

func TestCheckCommandJSONOutput(t *testing.T) {
tmpDir := t.TempDir()
skillContent := `---
Expand Down