Skip to content

Commit

Permalink
Merge pull request #426 from IBM-Cloud/dev
Browse files Browse the repository at this point in the history
* feat: added AlphaCommandsEnabled configuration

* chore: move up dependencies (#423)

* feat: increase token expiration check visibility (#424)

* feat: used go-pretty to improve table formatting on screen width (#414)
G
* chore: bumped version to v1.6.0

* chore: updated golang.org/x/crypto to v0.32.0

---------

Co-authored-by: steveclay <[email protected]>
  • Loading branch information
Aerex and steveclay authored Jan 28, 2025
2 parents 4a59214 + 9d8ea65 commit 8965968
Show file tree
Hide file tree
Showing 9 changed files with 244 additions and 70 deletions.
20 changes: 17 additions & 3 deletions bluemix/configuration/core_config/bx_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ type BXConfigData struct {
LastSessionUpdateTime int64
Trace string
ColorEnabled string
AlphaCommandsEnabled string
HTTPTimeout int
TypeOfSSO string
FallbackIAMTokens struct {
Expand Down Expand Up @@ -312,18 +313,18 @@ func (c *bxConfig) IAMID() (guid string) {
func (c *bxConfig) IsLoggedIn() bool {
if token, refresh := c.IAMToken(), c.IAMRefreshToken(); token != "" || refresh != "" {
iamTokenInfo := NewIAMTokenInfo(token)
if iamTokenInfo.hasExpired() && refresh != "" {
if iamTokenInfo.HasExpired() && refresh != "" {
repo := newRepository(c)
if _, err := repo.RefreshIAMToken(); err != nil {
return false
}
// Check again to make sure that the new token has not expired
if iamTokenInfo = NewIAMTokenInfo(c.IAMToken()); iamTokenInfo.hasExpired() {
if iamTokenInfo = NewIAMTokenInfo(c.IAMToken()); iamTokenInfo.HasExpired() {
return false
}

return true
} else if iamTokenInfo.hasExpired() && refresh == "" {
} else if iamTokenInfo.HasExpired() && refresh == "" {
return false
} else {
return true
Expand Down Expand Up @@ -415,6 +416,13 @@ func (c *bxConfig) ColorEnabled() (enabled string) {
return
}

func (c *bxConfig) AlphaCommandsEnabled() (enabled string) {
c.read(func() {
enabled = c.data.AlphaCommandsEnabled
})
return
}

func (c *bxConfig) TypeOfSSO() (style string) {
c.read(func() {
style = c.data.TypeOfSSO
Expand Down Expand Up @@ -723,6 +731,12 @@ func (c *bxConfig) SetColorEnabled(enabled string) {
})
}

func (c *bxConfig) SetAlphaCommandsEnabled(enabled string) {
c.write(func() {
c.data.AlphaCommandsEnabled = enabled
})
}

func (c *bxConfig) SetLocale(locale string) {
c.write(func() {
c.data.Locale = locale
Expand Down
2 changes: 1 addition & 1 deletion bluemix/configuration/core_config/iam_token.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ func (t IAMTokenInfo) exists() bool {
return t.ID != ""
}

func (t IAMTokenInfo) hasExpired() bool {
func (t IAMTokenInfo) HasExpired() bool {
if !t.exists() {
return true
}
Expand Down
2 changes: 1 addition & 1 deletion bluemix/configuration/core_config/iam_token_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ func TestIAMTokenHasExpired(t *testing.T) {
for _, testCase := range TestIAMTokenHasExpiredTestCases {
t.Run(testCase.name, func(t *testing.T) {
tokenInfo := NewIAMTokenInfo(testCase.token)
assert.Equal(t, testCase.isExpired, tokenInfo.hasExpired())
assert.Equal(t, testCase.isExpired, tokenInfo.HasExpired())
})
}
}
2 changes: 2 additions & 0 deletions bluemix/configuration/core_config/repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ type Repository interface {
PluginRepo(string) (models.PluginRepo, bool)
IsSSLDisabled() bool
TypeOfSSO() string
AlphaCommandsEnabled() string
AssumedTrustedProfileId() string
FallbackIAMToken() string
FallbackIAMRefreshToken() string
Expand Down Expand Up @@ -118,6 +119,7 @@ type Repository interface {
SetLocale(string)
SetTrace(string)
SetColorEnabled(string)
SetAlphaCommandsEnabled(string)

CheckMessageOfTheDay() bool
SetMessageOfTheDayTime()
Expand Down
183 changes: 146 additions & 37 deletions bluemix/terminal/table.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,18 @@ package terminal
import (
"encoding/csv"
"fmt"
"io"
"os"
"strconv"
"strings"

"golang.org/x/term"

. "github.com/IBM-Cloud/ibm-cloud-cli-sdk/i18n"

"io"

"github.com/jedib0t/go-pretty/v6/table"
"github.com/jedib0t/go-pretty/v6/text"
"github.com/mattn/go-runewidth"
)

Expand All @@ -24,11 +32,10 @@ type Table interface {
}

type PrintableTable struct {
writer io.Writer
headers []string
headerPrinted bool
maxSizes []int
rows [][]string //each row is single line
writer io.Writer
headers []string
maxSizes []int
rows [][]string //each row is single line
}

func NewTable(w io.Writer, headers []string) Table {
Expand Down Expand Up @@ -69,58 +76,160 @@ func (t *PrintableTable) Add(row ...string) {
}
}

func isWideColumn(col string) bool {
// list of common columns that are usually wide
largeColumnTypes := []string{T("ID"), T("Description")}

for _, largeColn := range largeColumnTypes {
if strings.Contains(largeColn, col) {
return true
}
}

return false

}

func terminalWidth() int {
var err error
terminalWidth, _, err := term.GetSize(int(os.Stdin.Fd()))

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

testTerminalWidth, envSet := os.LookupEnv("TEST_TERMINAL_WIDTH")
if envSet {
envWidth, err := strconv.Atoi(testTerminalWidth)
if err == nil {
terminalWidth = envWidth
}
}
return terminalWidth
}

func (t *PrintableTable) Print() {
for _, row := range append(t.rows, t.headers) {
t.calculateMaxSize(row)
}

if t.headerPrinted == false {
t.printHeader()
t.headerPrinted = true
tbl := table.NewWriter()
tbl.SetOutputMirror(t.writer)
tbl.SuppressTrailingSpaces()
// remove padding from the left to keep the table aligned to the left
tbl.Style().Box.PaddingLeft = ""
tbl.Style().Box.PaddingRight = strings.Repeat(" ", minSpace)
// remove all border and column and row separators
tbl.Style().Options.DrawBorder = false
tbl.Style().Options.SeparateColumns = false
tbl.Style().Options.SeparateFooter = false
tbl.Style().Options.SeparateHeader = false
tbl.Style().Options.SeparateRows = false
tbl.Style().Format.Header = text.FormatDefault

headerRow, rows := t.createPrettyRowsAndHeaders()
columnConfig := t.createColumnConfigs()

tbl.SetColumnConfigs(columnConfig)
tbl.AppendHeader(headerRow)
tbl.AppendRows(rows)
tbl.Render()
}

func (t *PrintableTable) createColumnConfigs() []table.ColumnConfig {
// there must be at row in order to configure column
if len(t.rows) == 0 {
return []table.ColumnConfig{}
}

for _, line := range t.rows {
t.printRow(line)
colCount := len(t.rows[0])
var (
widestColIndicies []int
terminalWidth = terminalWidth()
// total amount padding space that a row will take up
totalPaddingSpace = (colCount - 1) * minSpace
remainingSpace = max(0, terminalWidth-totalPaddingSpace)
// the estimated max column width by dividing the remaining space evenly across the columns
maxColWidth = remainingSpace / colCount
)
columnConfig := make([]table.ColumnConfig, colCount)

for colIndex := range columnConfig {
columnConfig[colIndex] = table.ColumnConfig{
AlignHeader: text.AlignLeft,
Align: text.AlignLeft,
WidthMax: maxColWidth,
Number: colIndex + 1,
}

// assuming the table has headers: store columns with wide content where the max width may need to be adjusted
// using the remaining space
if t.maxSizes[colIndex] > maxColWidth && (colIndex < len(t.headers) && isWideColumn(t.headers[colIndex])) {
widestColIndicies = append(widestColIndicies, colIndex)
} else if t.maxSizes[colIndex] < maxColWidth {
// use the max column width instead of the estimated max column width
// if it is shorter
columnConfig[colIndex].WidthMax = t.maxSizes[colIndex]
remainingSpace -= t.maxSizes[colIndex]
} else {
remainingSpace -= maxColWidth
}
}

t.rows = [][]string{}
}
// if only one wide column use the remaining space as the max column width
if len(widestColIndicies) == 1 {
widestColIndx := widestColIndicies[0]
columnConfig[widestColIndx].WidthMax = remainingSpace
}

func (t *PrintableTable) calculateMaxSize(row []string) {
for index, value := range row {
cellLength := runewidth.StringWidth(Decolorize(value))
if t.maxSizes[index] < cellLength {
t.maxSizes[index] = cellLength
// if more than one wide column, spread the remaining space between the columns
if len(widestColIndicies) > 1 {
remainingSpace /= len(widestColIndicies)
for _, columnCfgIdx := range widestColIndicies {
columnConfig[columnCfgIdx].WidthMax = remainingSpace
}

origRemainingSpace := remainingSpace
moreRemainingSpace := origRemainingSpace % len(widestColIndicies)
if moreRemainingSpace != 0 {
columnConfig[0].WidthMax += moreRemainingSpace
}
}

return columnConfig
}

func (t *PrintableTable) printHeader() {
output := ""
for col, value := range t.headers {
output = output + t.cellValue(col, HeaderColor(value))
func (t *PrintableTable) createPrettyRowsAndHeaders() (headerRow table.Row, rows []table.Row) {
for _, header := range t.headers {
headerRow = append(headerRow, header)
}
fmt.Fprintln(t.writer, output)
}

func (t *PrintableTable) printRow(row []string) {
output := ""
for columnIndex, value := range row {
if columnIndex == 0 {
value = TableContentHeaderColor(value)
for i := range t.rows {
var row, emptyRow table.Row
for j, cell := range t.rows[i] {
if j == 0 {
cell = TableContentHeaderColor(cell)
}
row = append(row, cell)
emptyRow = append(emptyRow, "")
}

output = output + t.cellValue(columnIndex, value)
if i == 0 && len(t.headers) == 0 {
rows = append(rows, emptyRow)
}
rows = append(rows, row)
}
fmt.Fprintln(t.writer, output)

return
}

func (t *PrintableTable) cellValue(col int, value string) string {
padding := ""
if col < len(t.maxSizes)-1 {
padding = strings.Repeat(" ", t.maxSizes[col]-runewidth.StringWidth(Decolorize(value))+minSpace)
func (t *PrintableTable) calculateMaxSize(row []string) {
for index, value := range row {
cellLength := runewidth.StringWidth(Decolorize(value))
if t.maxSizes[index] < cellLength {
t.maxSizes[index] = cellLength
}
}
return fmt.Sprintf("%s%s", value, padding)
}

// Prints out a nicely/human formatted Json string instead of a table structure
Expand Down
47 changes: 45 additions & 2 deletions bluemix/terminal/table_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package terminal_test

import (
"bytes"
"os"
"strings"
"testing"

Expand Down Expand Up @@ -38,7 +39,7 @@ func TestEmptyHeaderTable(t *testing.T) {
testTable.Add("row1", "row2")
testTable.Print()
assert.Contains(t, buf.String(), "row1")
assert.Equal(t, " \nrow1 row2\n", buf.String())
assert.Equal(t, "\nrow1 row2\n", buf.String())
}

func TestEmptyHeaderTableJson(t *testing.T) {
Expand Down Expand Up @@ -79,7 +80,49 @@ func TestNotEnoughRowEntires(t *testing.T) {
testTable.Add("", "row2")
testTable.Print()
assert.Contains(t, buf.String(), "row1")
assert.Equal(t, "col1 col2\nrow1 \n row2\n", buf.String())
assert.Equal(t, "col1 col2\nrow1\n row2\n", buf.String())
}

func TestMoreColThanTerminalWidth(t *testing.T) {
os.Setenv("TEST_TERMINAL_WIDTH", "1")
buf := bytes.Buffer{}
testTable := NewTable(&buf, []string{"col1"})
testTable.Add("row1", "row2")
testTable.Print()
assert.Contains(t, buf.String(), "row1")
assert.Equal(t, "col1\nrow1 row2\n", buf.String())
os.Unsetenv("TEST_TERMINAL_WIDTH")
}

func TestWideHeaderNames(t *testing.T) {
buf := bytes.Buffer{}
testTable := NewTable(&buf, []string{"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt u", "NAME"})
testTable.Add("col1", "col2")
testTable.Print()
assert.Contains(t, buf.String(), "Lorem ipsum dolor sit amet, consectetu")
assert.Equal(t, "Lorem ipsum dolor sit amet, consectetu NAME\nr adipiscing elit, sed do eiusmod temp\nor incididunt u\ncol1 col2\n", buf.String())
}

func TestWidestColumn(t *testing.T) {
buf := bytes.Buffer{}
id := "ABCDEFG-9b8babbd-f2ed-4371-b817-a839e4130332"
testTable := NewTable(&buf, []string{"ID", "Name"})
testTable.Add(id, "row2")
testTable.Print()
assert.Contains(t, buf.String(), id)
assert.Equal(t, buf.String(), "ID Name\nABCDEFG-9b8babbd-f2ed-4371-b817-a839e4130332 row2\n")
}

func TestMultiWideColumns(t *testing.T) {
buf := bytes.Buffer{}
id := "ABCDEFG-9b8babbd-f2ed-4371-b817-a839e4130332"
desc := "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut"
testTable := NewTable(&buf, []string{"ID", "Description", "Name"})
testTable.Add(id, desc, "col3")
testTable.Print()
assert.Contains(t, buf.String(), "ABCDEFG-9b8babbd-f2ed-4371-b817-a839")
assert.Contains(t, buf.String(), "e4130332")
assert.Equal(t, buf.String(), "ID Description Name\nABCDEFG-9b8babbd-f2ed-4371-b817-a839 Lorem ipsum dolor sit amet, consect col3\ne4130332 etur adipiscing elit, sed do eiusmo\n d tempor incididunt ut\n")
}

func TestNotEnoughRowEntiresJson(t *testing.T) {
Expand Down
2 changes: 1 addition & 1 deletion bluemix/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package bluemix
import "fmt"

// Version is the SDK version
var Version = VersionType{Major: 1, Minor: 5, Build: 0}
var Version = VersionType{Major: 1, Minor: 6, Build: 0}

// VersionType describe version info
type VersionType struct {
Expand Down
Loading

0 comments on commit 8965968

Please sign in to comment.