Skip to content

Commit

Permalink
text: AlignAuto to align numbers Right and rest Left
Browse files Browse the repository at this point in the history
  • Loading branch information
jedib0t committed Mar 14, 2024
1 parent 7206de6 commit 54f0831
Show file tree
Hide file tree
Showing 3 changed files with 85 additions and 25 deletions.
28 changes: 28 additions & 0 deletions table/render_test.go
Expand Up @@ -111,6 +111,34 @@ func TestTable_Render(t *testing.T) {
A Song of Ice and Fire`)
}

func TestTable_Render_Align(t *testing.T) {
tw := NewWriter()
tw.AppendHeader(testHeader)
tw.AppendRows(testRows)
tw.AppendRow(Row{500, "Jamie", "Lannister", "Kingslayer", "The things I do for love."})
tw.AppendRow(Row{1000, "Tywin", "Lannister", nil})
tw.AppendFooter(testFooter)
tw.SetColumnConfigs([]ColumnConfig{
{Name: "First Name", Align: text.AlignLeft, AlignHeader: text.AlignLeft, AlignFooter: text.AlignLeft},
{Name: "Last Name", Align: text.AlignRight, AlignHeader: text.AlignRight, AlignFooter: text.AlignRight},
{Name: "Salary", Align: text.AlignAuto, AlignHeader: text.AlignRight, AlignFooter: text.AlignAuto},
{Number: 5, Align: text.AlignJustify, AlignHeader: text.AlignJustify, AlignFooter: text.AlignJustify},
})

compareOutput(t, tw.Render(), `
+------+------------+-----------+------------+-----------------------------+
| # | FIRST NAME | LAST NAME | SALARY | |
+------+------------+-----------+------------+-----------------------------+
| 1 | Arya | Stark | 3000 | |
| 20 | Jon | Snow | 2000 | You know nothing, Jon Snow! |
| 300 | Tyrion | Lannister | 5000 | |
| 500 | Jamie | Lannister | Kingslayer | The things I do for love. |
| 1000 | Tywin | Lannister | <nil> | |
+------+------------+-----------+------------+-----------------------------+
| | | TOTAL | 10000 | |
+------+------------+-----------+------------+-----------------------------+`)
}

func TestTable_Render_AutoIndex(t *testing.T) {
tw := NewWriter()
for rowIdx := 0; rowIdx < 10; rowIdx++ {
Expand Down
67 changes: 42 additions & 25 deletions text/align.go
Expand Up @@ -2,6 +2,7 @@ package text

import (
"fmt"
"regexp"
"strconv"
"strings"
"unicode/utf8"
Expand All @@ -17,6 +18,12 @@ const (
AlignCenter // " center "
AlignJustify // "justify it"
AlignRight // " right"
AlignAuto // AlignRight for numbers, AlignLeft for the rest
)

var (
// reNumericText - Regular Expression to match numbers.
reNumericText = regexp.MustCompile(`^\s*[+\-]?\d*[.]?\d+\s*$`)
)

// Apply aligns the text as directed. For ex.:
Expand All @@ -25,14 +32,24 @@ const (
// - AlignCenter.Apply("Jon Snow", 12) returns " Jon Snow "
// - AlignJustify.Apply("Jon Snow", 12) returns "Jon Snow"
// - AlignRight.Apply("Jon Snow", 12) returns " Jon Snow"
// - AlignAuto.Apply("Jon Snow", 12) returns "Jon Snow "
func (a Align) Apply(text string, maxLength int) string {
text = a.trimString(text)
aComputed := a
if aComputed == AlignAuto {
if reNumericText.MatchString(text) {
aComputed = AlignRight
} else {
aComputed = AlignLeft
}
}

text = aComputed.trimString(text)
sLen := utf8.RuneCountInString(text)
sLenWoE := RuneWidthWithoutEscSequences(text)
numEscChars := sLen - sLenWoE

// now, align the text
switch a {
switch aComputed {
case AlignDefault, AlignLeft:
return fmt.Sprintf("%-"+strconv.Itoa(maxLength+numEscChars)+"s", text)
case AlignCenter:
Expand All @@ -42,7 +59,7 @@ func (a Align) Apply(text string, maxLength int) string {
text+strings.Repeat(" ", (maxLength-sLenWoE)/2))
}
case AlignJustify:
return a.justifyText(text, sLenWoE, maxLength)
return justifyText(text, sLenWoE, maxLength)
}
return fmt.Sprintf("%"+strconv.Itoa(maxLength+numEscChars)+"s", text)
}
Expand Down Expand Up @@ -77,16 +94,34 @@ func (a Align) MarkdownProperty() string {
}
}

func (a Align) justifyText(text string, textLength int, maxLength int) string {
func (a Align) trimString(text string) string {
switch a {
case AlignDefault, AlignLeft:
if strings.HasSuffix(text, " ") {
return strings.TrimRight(text, " ")
}
case AlignRight:
if strings.HasPrefix(text, " ") {
return strings.TrimLeft(text, " ")
}
default:
if strings.HasPrefix(text, " ") || strings.HasSuffix(text, " ") {
return strings.Trim(text, " ")
}
}
return text
}

func justifyText(text string, textLength int, maxLength int) string {
// split the text into individual words
wordsUnfiltered := strings.Split(text, " ")
words := Filter(wordsUnfiltered, func(item string) bool {
words := Filter(strings.Split(text, " "), func(item string) bool {
return item != ""
})
// empty string implies spaces for maxLength
// empty string implies result is just spaces for maxLength
if len(words) == 0 {
return strings.Repeat(" ", maxLength)
}

// get the number of spaces to insert into the text
numSpacesNeeded := maxLength - textLength + strings.Count(text, " ")
numSpacesNeededBetweenWords := 0
Expand Down Expand Up @@ -117,21 +152,3 @@ func (a Align) justifyText(text string, textLength int, maxLength int) string {
}
return outText.String()
}

func (a Align) trimString(text string) string {
switch a {
case AlignDefault, AlignLeft:
if strings.HasSuffix(text, " ") {
return strings.TrimRight(text, " ")
}
case AlignRight:
if strings.HasPrefix(text, " ") {
return strings.TrimLeft(text, " ")
}
default:
if strings.HasPrefix(text, " ") || strings.HasSuffix(text, " ") {
return strings.Trim(text, " ")
}
}
return text
}
15 changes: 15 additions & 0 deletions text/align_test.go
Expand Up @@ -13,12 +13,16 @@ func ExampleAlign_Apply() {
fmt.Printf("AlignCenter : '%s'\n", AlignCenter.Apply("Jon Snow", 12))
fmt.Printf("AlignJustify: '%s'\n", AlignJustify.Apply("Jon Snow", 12))
fmt.Printf("AlignRight : '%s'\n", AlignRight.Apply("Jon Snow", 12))
fmt.Printf("AlignAuto : '%s'\n", AlignAuto.Apply("Jon Snow", 12))
fmt.Printf("AlignAuto : '%s'\n", AlignAuto.Apply("-5.43", 12))

// Output: AlignDefault: 'Jon Snow '
// AlignLeft : 'Jon Snow '
// AlignCenter : ' Jon Snow '
// AlignJustify: 'Jon Snow'
// AlignRight : ' Jon Snow'
// AlignAuto : 'Jon Snow '
// AlignAuto : ' -5.43'
}

func TestAlign_Apply(t *testing.T) {
Expand Down Expand Up @@ -50,6 +54,17 @@ func TestAlign_Apply(t *testing.T) {
assert.Equal(t, " Jon Snow ", AlignRight.Apply("Jon Snow ", 12))
assert.Equal(t, " Jon Snow ", AlignRight.Apply(" Jon Snow ", 12))
assert.Equal(t, " ", AlignRight.Apply("", 12))

// Align Auto
assert.Equal(t, "Jon Snow ", AlignAuto.Apply("Jon Snow", 12))
assert.Equal(t, "Jon Snow ", AlignAuto.Apply("Jon Snow ", 12))
assert.Equal(t, " Jon Snow ", AlignAuto.Apply(" Jon Snow ", 12))
assert.Equal(t, " ", AlignAuto.Apply("", 12))
assert.Equal(t, " 13", AlignAuto.Apply("13", 12))
assert.Equal(t, " -5.43", AlignAuto.Apply("-5.43", 12))
assert.Equal(t, " +.43", AlignAuto.Apply("+.43", 12))
assert.Equal(t, " +5.43", AlignAuto.Apply("+5.43", 12))
assert.Equal(t, "+5.43x ", AlignAuto.Apply("+5.43x", 12))
}

func TestAlign_Apply_ColoredText(t *testing.T) {
Expand Down

0 comments on commit 54f0831

Please sign in to comment.