Repository: jedib0t/go-pretty
Pull request #287 (2024-01-03 09:03:52)

feat: add suppressTrailingSpaces to remove all trailing spaces in final column
## Proposed Changes
  - Add new `SuppressTrailingSpaces` that removes all the trailing spaces in the final column of a row. This is useful when using the `OptionsNoBordersAndSeparators` options to reduce the number of spaces at the end of the column.

shreddedbacon: I hope this is OK for me to submit. It isn't super important that it gets merged, I can deal with the trailing spaces. But it would be nice to be able to disable them as required for cleaner output.
coveralls: ## Pull Request Test Coverage Report for [Build 7395335843](https://coveralls.io/builds/64848574)


* **0** of **0**   changed or added relevant lines in **0** files are covered.
* No unchanged relevant lines lost coverage.
* Overall coverage remained the same at **100.0%**

---



|  Totals | [![Coverage Status](https://coveralls.io/builds/64848574/badge)](https://coveralls.io/builds/64848574) |
| :-- | --: |
| Change from base [Build 6674369350](https://coveralls.io/builds/63600729): |  0.0% |
| Covered Lines: | 3366 |
| Relevant Lines: | 3366 |

---
##### 💛  - [Coveralls](https://coveralls.io)

sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=jedib0t_go-pretty&pullRequest=287) **Quality Gate passed**  
The SonarCloud Quality Gate passed, but some issues were introduced.

[1 New issue](https://sonarcloud.io/project/issues?id=jedib0t_go-pretty&pullRequest=287&resolved=false&inNewCodePeriod=true)  
[0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=jedib0t_go-pretty&pullRequest=287&resolved=false&inNewCodePeriod=true)  
No data about Coverage  
[0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=jedib0t_go-pretty&pullRequest=287&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=jedib0t_go-pretty&pullRequest=287)


jedib0t: Can you show what happens when `OptionsNoBordersAndSeparators` is false and `SuppressLastColumnSpaces` is true?
sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=jedib0t_go-pretty&pullRequest=287) **Quality Gate passed**  
The SonarCloud Quality Gate passed, but some issues were introduced.

[1 New issue](https://sonarcloud.io/project/issues?id=jedib0t_go-pretty&pullRequest=287&resolved=false&inNewCodePeriod=true)  
[0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=jedib0t_go-pretty&pullRequest=287&resolved=false&inNewCodePeriod=true)  
No data about Coverage  
[0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=jedib0t_go-pretty&pullRequest=287&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=jedib0t_go-pretty&pullRequest=287)


shreddedbacon: > Can you show what happens when `OptionsNoBordersAndSeparators` is false and `SuppressLastColumnSpaces` is true?

I added coverage in the tests to show how this looks when borders are turned on (they look bad as expected)
jedib0t: > > Can you show what happens when `OptionsNoBordersAndSeparators` is false and `SuppressLastColumnSpaces` is true?
> 
> I added coverage in the tests to show how this looks when borders are turned on (they look bad as expected)

Yeah this code cannot be merged in this state. How about just adding a block of code after https://github.com/jedib0t/go-pretty/blob/main/table/table.go#L681? If the suppressTrailingSpaces is set to true, split the outStr into lines, and then trim each line before returning that?
sonarcloud[bot]: ## [![Quality Gate Failed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-failed-20px.png 'Quality Gate Failed')](https://sonarcloud.io/dashboard?id=jedib0t_go-pretty&pullRequest=287) **Quality Gate failed**  
Failed conditions

[40.7% Duplication on New Code](https://sonarcloud.io/component_measures?id=jedib0t_go-pretty&pullRequest=287&metric=new_duplicated_lines_density&view=list) (required ≤ 15%)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=jedib0t_go-pretty&pullRequest=287)


sonarcloud[bot]: ## [![Quality Gate Failed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-failed-20px.png 'Quality Gate Failed')](https://sonarcloud.io/dashboard?id=jedib0t_go-pretty&pullRequest=287) **Quality Gate failed**  
Failed conditions

[44.9% Duplication on New Code](https://sonarcloud.io/component_measures?id=jedib0t_go-pretty&pullRequest=287&metric=new_duplicated_lines_density&view=list) (required ≤ 15%)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=jedib0t_go-pretty&pullRequest=287)


shreddedbacon: > > > Can you show what happens when `OptionsNoBordersAndSeparators` is false and `SuppressLastColumnSpaces` is true?
> > 
> > 
> > I added coverage in the tests to show how this looks when borders are turned on (they look bad as expected)
> 
> Yeah this code cannot be merged in this state. How about just adding a block of code after [`main`/table/table.go#L681](https://github.com/jedib0t/go-pretty/blob/main/table/table.go?rgh-link-date=2024-01-04T01%3A09%3A03Z#L681)? If the suppressTrailingSpaces is set to true, split the outStr into lines, and then trim each line before returning that?

Ahh, that works even better, thanks for the tip. And it retains the table shape when borders are present which is better than what I originally proposed.

The sonarcloud code analysis complains about duplicated code in the `render_test` file though, most of it seems unrelated to my PR specifically here, so I don't think I need to address this.
sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=jedib0t_go-pretty&pullRequest=287) **Quality Gate passed**  
The SonarCloud Quality Gate passed, but some issues were introduced.

[1 New issue](https://sonarcloud.io/project/issues?id=jedib0t_go-pretty&pullRequest=287&resolved=false&inNewCodePeriod=true)  
[0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=jedib0t_go-pretty&pullRequest=287&resolved=false&inNewCodePeriod=true)  
No data about Coverage  
[0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=jedib0t_go-pretty&pullRequest=287&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=jedib0t_go-pretty&pullRequest=287)



Patch:
diff --git a/table/README.md b/table/README.md
index 406532a..6d89da4 100644
--- a/table/README.md
+++ b/table/README.md
@@ -21,6 +21,7 @@ Pretty-print tables into ASCII/Unicode strings.
   - Mirror output to an `io.Writer` (ex. `os.StdOut`) (`SetOutputMirror`)
   - Sort by one or more Columns (`SortBy`)
   - Suppress/hide columns with no content (`SuppressEmptyColumns`) 
+  - Suppress trailing spaces in the last column (`SupressTrailingSpaces`) 
   - Customizable Cell rendering per Column (`ColumnConfig.Transformer*`)
   - Hide any columns that you don't want displayed (`ColumnConfig.Hidden`)
   - Reset Headers/Rows/Footers at will to reuse the same Table Writer (`Reset*`)
diff --git a/table/table.go b/table/table.go
index 6b7c906..42e7129 100644
--- a/table/table.go
+++ b/table/table.go
@@ -1,6 +1,7 @@
 package table
 
 import (
+	"bufio"
 	"fmt"
 	"io"
 	"strings"
@@ -106,6 +107,8 @@ type Table struct {
 	// suppressEmptyColumns hides columns which have no content on all regular
 	// rows
 	suppressEmptyColumns bool
+	// supressTrailingSpaces removes all trailing spaces from the end of the last column
+	supressTrailingSpaces bool
 	// title contains the text to appear above the table
 	title string
 }
@@ -297,6 +300,12 @@ func (t *Table) SuppressEmptyColumns() {
 	t.suppressEmptyColumns = true
 }
 
+// SupressTrailingSpaces removes all trailing spaces from the end of the last column
+// this is useful when OptionsNoBordersAndSeparators is used
+func (t *Table) SupressTrailingSpaces() {
+	t.supressTrailingSpaces = true
+}
+
 func (t *Table) getAlign(colIdx int, hint renderHint) text.Align {
 	align := text.AlignDefault
 	if cfg, ok := t.columnConfigMap[colIdx]; ok {
@@ -679,6 +688,14 @@ func (t *Table) isIndexColumn(colIdx int, hint renderHint) bool {
 
 func (t *Table) render(out *strings.Builder) string {
 	outStr := out.String()
+	if t.supressTrailingSpaces {
+		var trimmed []string
+		sc := bufio.NewScanner(strings.NewReader(outStr))
+		for sc.Scan() {
+			trimmed = append(trimmed, strings.TrimSpace(sc.Text()))
+		}
+		outStr = strings.Join(trimmed, "\n")
+	}
 	if t.outputMirror != nil && len(outStr) > 0 {
 		_, _ = t.outputMirror.Write([]byte(outStr))
 		_, _ = t.outputMirror.Write([]byte("\n"))
diff --git a/table/writer.go b/table/writer.go
index c08195b..7b57b3e 100644
--- a/table/writer.go
+++ b/table/writer.go
@@ -33,6 +33,7 @@ type Writer interface {
 	SortBy(sortBy []SortBy)
 	Style() *Style
 	SuppressEmptyColumns()
+	SupressTrailingSpaces()
 
 	// deprecated; in favor of Style().HTML.CSSClass
 	SetHTMLCSSClass(cssClass string)


Test patch:
diff --git a/table/render_test.go b/table/render_test.go
index abc1a72..b2400e4 100644
--- a/table/render_test.go
+++ b/table/render_test.go
@@ -1175,3 +1175,67 @@ func TestTable_Render_WidthEnforcer(t *testing.T) {
 | R123 | Small name           | 2021-04-19 13:37 | Abcdefghijklmnopqrstuvwxyz |
 +------+----------------------+------------------+----------------------------+`)
 }
+
+func TestTable_Render_SupressTrailingSpaces(t *testing.T) {
+	tw := NewWriter()
+	tw.AppendHeader(testHeader2)
+	tw.AppendRows([]Row{
+		{"U2", "Hey", "2021-04-19 13:37", "Yuh yuh yuh"},
+		{"S12", "Uhhhh", "2021-04-19 13:37", "Some dummy data here"},
+		{"R123", "Lobsters", "2021-04-19 13:37", "I like lobsters"},
+		{"R123", "Some big name here and it's pretty big", "2021-04-19 13:37", "Abcdefghijklmnopqrstuvwxyz"},
+		{"R123", "Small name", "2021-04-19 13:37", "Abcdefghijklmnopqrstuvwxyz"},
+	})
+
+	t.Run("borders and separators", func(t *testing.T) {
+		tw.Style().Options = OptionsDefault
+		compareOutput(t, tw.Render(), `
++------+----------------------------------------+------------------+----------------------------+
+| ID   | TEXT1                                  | DATE             | TEXT2                      |
++------+----------------------------------------+------------------+----------------------------+
+| U2   | Hey                                    | 2021-04-19 13:37 | Yuh yuh yuh                |
+| S12  | Uhhhh                                  | 2021-04-19 13:37 | Some dummy data here       |
+| R123 | Lobsters                               | 2021-04-19 13:37 | I like lobsters            |
+| R123 | Some big name here and it's pretty big | 2021-04-19 13:37 | Abcdefghijklmnopqrstuvwxyz |
+| R123 | Small name                             | 2021-04-19 13:37 | Abcdefghijklmnopqrstuvwxyz |
++------+----------------------------------------+------------------+----------------------------+`)
+	})
+
+	t.Run("no borders and separators", func(t *testing.T) {
+		tw.Style().Options = OptionsNoBordersAndSeparators
+		compareOutput(t, tw.Render(), `
+ ID    TEXT1                                   DATE              TEXT2                      
+ U2    Hey                                     2021-04-19 13:37  Yuh yuh yuh                
+ S12   Uhhhh                                   2021-04-19 13:37  Some dummy data here       
+ R123  Lobsters                                2021-04-19 13:37  I like lobsters            
+ R123  Some big name here and it's pretty big  2021-04-19 13:37  Abcdefghijklmnopqrstuvwxyz 
+ R123  Small name                              2021-04-19 13:37  Abcdefghijklmnopqrstuvwxyz `)
+	})
+
+	tw.SupressTrailingSpaces()
+
+	t.Run("borders and separators suppressed spaces", func(t *testing.T) {
+		tw.Style().Options = OptionsDefault
+		compareOutput(t, tw.Render(), `
++------+----------------------------------------+------------------+----------------------------+
+| ID   | TEXT1                                  | DATE             | TEXT2                      |
++------+----------------------------------------+------------------+----------------------------+
+| U2   | Hey                                    | 2021-04-19 13:37 | Yuh yuh yuh                |
+| S12  | Uhhhh                                  | 2021-04-19 13:37 | Some dummy data here       |
+| R123 | Lobsters                               | 2021-04-19 13:37 | I like lobsters            |
+| R123 | Some big name here and it's pretty big | 2021-04-19 13:37 | Abcdefghijklmnopqrstuvwxyz |
+| R123 | Small name                             | 2021-04-19 13:37 | Abcdefghijklmnopqrstuvwxyz |
++------+----------------------------------------+------------------+----------------------------+`)
+	})
+
+	t.Run("no borders and separators suppressed spaces", func(t *testing.T) {
+		tw.Style().Options = OptionsNoBordersAndSeparators
+		compareOutput(t, tw.Render(), `
+ID    TEXT1                                   DATE              TEXT2
+U2    Hey                                     2021-04-19 13:37  Yuh yuh yuh
+S12   Uhhhh                                   2021-04-19 13:37  Some dummy data here
+R123  Lobsters                                2021-04-19 13:37  I like lobsters
+R123  Some big name here and it's pretty big  2021-04-19 13:37  Abcdefghijklmnopqrstuvwxyz
+R123  Small name                              2021-04-19 13:37  Abcdefghijklmnopqrstuvwxyz`)
+	})
+}
diff --git a/table/table_test.go b/table/table_test.go
index d4f9955..9addf88 100644
--- a/table/table_test.go
+++ b/table/table_test.go
@@ -19,6 +19,7 @@ var (
 	testFooterMultiLine = Row{"", "", "Total\nSalary", 10000}
 	testHeader          = Row{"#", "First Name", "Last Name", "Salary"}
 	testHeaderMultiLine = Row{"#", "First\nName", "Last\nName", "Salary"}
+	testHeader2         = Row{"ID", "Text1", "Date", "Text2"}
 	testRows            = []Row{
 		{1, "Arya", "Stark", 3000},
 		{20, "Jon", "Snow", 2000, "You know nothing, Jon Snow!"},