Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: added better formatting in messages for screen width #413

Closed
wants to merge 1 commit into from
Closed
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
2 changes: 1 addition & 1 deletion bluemix/terminal/prompt.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ type Prompt struct {
// NewPrompt returns a single prompt
func NewPrompt(message string, options *PromptOptions) *Prompt {
p := &Prompt{
message: message,
message: FormatWidth(message, 0),
Reader: os.Stdin,
Writer: os.Stdout,
}
Expand Down
2 changes: 1 addition & 1 deletion bluemix/terminal/table.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ func (t *PrintableTable) printRow(row []string) {
value = TableContentHeaderColor(value)
}

output = output + t.cellValue(columnIndex, value)
output = FormatWidth(output+t.cellValue(columnIndex, value), 0)
}
fmt.Fprintln(t.writer, output)
}
Expand Down
11 changes: 11 additions & 0 deletions bluemix/terminal/table_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,3 +137,14 @@ func TestEmptyTable(t *testing.T) {
assert.Equal(t, err, nil)
assert.Equal(t, len(strings.TrimSpace(buf.String())), 0)
}

// Text wrapping
func TestTableWrapRows(t *testing.T) {
buf := bytes.Buffer{}
testTable := NewTable(&buf, []string{"col1"})
longString := " 2. Reserved Enterprise : Enterprise plan for this offering has been deprecated. Please see the announcement here: https://www-01.ibm.com/common/ssi/ShowDoc.wss?docURL=/common/ssi/rep_ca/3/897/ENUS918-103/index.html&request_locale=en. Analytics Engine provides the ability to spin up and manage Spark clusters. We recommend using this for any production Spark workloads."
testTable.Add(longString)
testTable.Print()
formattedString := "2. Reserved Enterprise : Enterprise plan for this offering has been \ndeprecated. Please see the announcement here: \nhttps://www-01.ibm.com/common/ssi/ShowDoc.wss?docURL=/common/ssi/rep_ca/3/897/ENUS918-103/index.html&request_locale=en.\nAnalytics Engine provides the ability to spin up and manage Spark clusters. We \nrecommend using this for any production Spark workloads."
assert.Contains(t, buf.String(), formattedString)
}
114 changes: 114 additions & 0 deletions bluemix/terminal/util.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package terminal

import (
"os"
"strings"

"golang.org/x/term"
)

func terminalWidth() (width int) {

var err error
width, _, err = term.GetSize(int(os.Stdin.Fd()))

if err != nil {
// Assume normal 80 char width line
width = 80
}

return
}

func complexFormatWidth(stringToFormat string, indent int) string {
lengthOfStringToFormat := len(stringToFormat)
returnString := ""

if stringToFormat[lengthOfStringToFormat-1:] == "\n" {
// remove newline at end of string before it is formatted
stringToFormat = stringToFormat[:lengthOfStringToFormat-1]
}

stringLines := strings.Split(stringToFormat, "\n")

for key, line := range stringLines {
lineLen := len(line)
if lineLen > terminalWidth() {
// Format the string
line = FormatWidth(line[:lineLen], indent) + "\n"
} else if key != len(stringLines)-1 {
// add newline except if the line is the last one
line += "\n"
}

returnString += line
}

return returnString
}

// FormatWidth formats the width of a string to the max of the terminal width
func FormatWidth(stringToFormat string, indent int) string {

// if the string is already formatted, handle as a more complex formatting
// confirm that if a newline character exists that the only one is not at the end of the string
if strings.Contains(stringToFormat, "\n") && strings.Index(stringToFormat, "\n") < len(stringToFormat)-2 {
return complexFormatWidth(stringToFormat, indent)
}

moreToProcess := true
startIdx := 0
width := terminalWidth() - 1
endIdx := width
returnString := ""

runesToFormat := []rune(stringToFormat)

for moreToProcess {

if endIdx < len(runesToFormat) {
if string(runesToFormat[endIdx-1:endIdx]) == " " {
stringToAdd := string(runesToFormat[startIdx:endIdx])
if strings.TrimSpace(stringToAdd) == "" {
// next space is after 80 characters, so just find the next space and append that "word"
var index int
for index = startIdx; index < len(runesToFormat); index++ {
if string(runesToFormat[index:index+1]) == " " {
break
}
}
stringToAdd = string(runesToFormat[startIdx:index])
// set endIdx to the index after the index of the space that we found
endIdx = index + 1
}
returnString += stringToAdd + "\n"

// Indent the next line
for i := 0; i < indent; i++ {
returnString += " "
}

startIdx = endIdx
endIdx += width - indent // Decrease number of characters by the indention
} else {
endIdx--
if endIdx < 1 {
moreToProcess = false
}
}
} else {
if startIdx < len(runesToFormat) {
// Add the remainder to the returnString
returnString += string(runesToFormat[startIdx:])
}
moreToProcess = false
}
}

// If there are no spaces in the string to break it up, just return the original string
if returnString == "" {
returnString = string(runesToFormat)
}

return returnString
}
52 changes: 52 additions & 0 deletions bluemix/terminal/util_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package terminal_test

import (
"strings"
"testing"

. "github.com/IBM-Cloud/ibm-cloud-cli-sdk/bluemix/terminal"
"github.com/stretchr/testify/assert"
)

func TestFormatWidthWithSpace(t *testing.T) {
stringOf70 := "0123456789012345678901234567890123456789012345678901234567890123456789"
stringOf10 := "abcdefghij"

formattedString := FormatWidth(stringOf70+" "+stringOf10, 0)
finalStrings := strings.Split(formattedString, "\n")
assert.Equal(t, 2, len(finalStrings))
assert.Equal(t, finalStrings[1], stringOf10)

formattedString = FormatWidth(stringOf10+" "+stringOf70, 0)
finalStrings = strings.Split(formattedString, "\n")
assert.Equal(t, 2, len(finalStrings))
assert.Equal(t, finalStrings[1], stringOf70)

formattedString = FormatWidth(stringOf10+" "+stringOf70, 5)
finalStrings = strings.Split(formattedString, "\n")
assert.Equal(t, 2, len(finalStrings))
assert.Equal(t, strings.Repeat(" ", 5)+stringOf70, finalStrings[1])

}

func TestFormatWidthLongString(t *testing.T) {
longString := " 2. Reserved Enterprise : Enterprise plan for this offering has been deprecated. Please see the announcement here: https://www-01.ibm.com/common/ssi/ShowDoc.wss?docURL=/common/ssi/rep_ca/3/897/ENUS918-103/index.html&request_locale=en. Analytics Engine provides the ability to spin up and manage Spark clusters. We recommend using this for any production Spark workloads."

formattedString := FormatWidth(longString, 4)
expected := " 2. Reserved Enterprise : Enterprise plan for this offering has been \n deprecated. Please see the announcement here: \n https://www-01.ibm.com/common/ssi/ShowDoc.wss?docURL=/common/ssi/rep_ca/3/897/ENUS918-103/index.html&request_locale=en.\n Analytics Engine provides the ability to spin up and manage Spark \n clusters. We recommend using this for any production Spark workloads."
assert.Equal(t, expected, formattedString)
}

func TestFuncWidithWithBreaks(t *testing.T) {
longString := "this is a string that is so long that it is longer than 80 characters and contains a newline character at the end\n"
expected := "this is a string that is so long that it is longer than 80 characters and \n contains a newline character at the end\n"
assert.Equal(t, expected, FormatWidth(longString, 4))

anotherLongString := "this is a string that is so long that it is\n longer than 80 characters and contains a newline character in the middle and at the end\n"
expected = "this is a string that is so long that it is\n longer than 80 characters and contains a newline character in the middle and \n at the end\n"
assert.Equal(t, expected, FormatWidth(anotherLongString, 4))

lastLongString := "this is a string that is so long that it is\n longer than 80 characters and contains\n multiple newline characters in the middle and at the end\n"
expected = "this is a string that is so long that it is\n longer than 80 characters and contains\n multiple newline characters in the middle and at the end"
assert.Equal(t, expected, FormatWidth(lastLongString, 4))
}
8 changes: 4 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@ require (
github.com/spf13/cobra v1.6.1
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.2.2
golang.org/x/crypto v0.21.0
golang.org/x/text v0.14.0
golang.org/x/crypto v0.28.0
golang.org/x/term v0.25.0
golang.org/x/text v0.19.0
gopkg.in/cheggaaa/pb.v1 v1.0.15
gopkg.in/yaml.v2 v2.4.0
)
Expand All @@ -27,7 +28,6 @@ require (
github.com/mattn/go-isatty v0.0.5-0.20180830101745-3fb116b82035 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
golang.org/x/net v0.23.0 // indirect
golang.org/x/sys v0.18.0 // indirect
golang.org/x/term v0.18.0 // indirect
golang.org/x/sys v0.26.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
20 changes: 10 additions & 10 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -41,20 +41,20 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8=
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24=
golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc=
golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/cheggaaa/pb.v1 v1.0.15 h1:1WP0I1XIkfylrOuo3YeOAt4QXsvESM1enkg3vH6FDmI=
Expand Down