Repository: jedib0t/go-pretty
Pull request #230 (2022-09-25 00:32:50)

table: option to force direction for tables with BiDi text
Provides a way to overcome issue reported in #229.


sonarcloud[bot]: SonarCloud Quality Gate failed.&nbsp; &nbsp; [![Quality Gate failed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/failed-16px.png 'Quality Gate failed')](https://sonarcloud.io/dashboard?id=jedib0t_go-pretty&pullRequest=230)

[![Bug](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug-16px.png 'Bug')](https://sonarcloud.io/project/issues?id=jedib0t_go-pretty&pullRequest=230&resolved=false&types=BUG) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=jedib0t_go-pretty&pullRequest=230&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=jedib0t_go-pretty&pullRequest=230&resolved=false&types=BUG)  
[![Vulnerability](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability-16px.png 'Vulnerability')](https://sonarcloud.io/project/issues?id=jedib0t_go-pretty&pullRequest=230&resolved=false&types=VULNERABILITY) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=jedib0t_go-pretty&pullRequest=230&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=jedib0t_go-pretty&pullRequest=230&resolved=false&types=VULNERABILITY)  
[![Security Hotspot](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot-16px.png 'Security Hotspot')](https://sonarcloud.io/project/security_hotspots?id=jedib0t_go-pretty&pullRequest=230&resolved=false&types=SECURITY_HOTSPOT) [![E](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/E-16px.png 'E')](https://sonarcloud.io/project/security_hotspots?id=jedib0t_go-pretty&pullRequest=230&resolved=false&types=SECURITY_HOTSPOT) [2 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=jedib0t_go-pretty&pullRequest=230&resolved=false&types=SECURITY_HOTSPOT)  
[![Code Smell](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell-16px.png 'Code Smell')](https://sonarcloud.io/project/issues?id=jedib0t_go-pretty&pullRequest=230&resolved=false&types=CODE_SMELL) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=jedib0t_go-pretty&pullRequest=230&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=jedib0t_go-pretty&pullRequest=230&resolved=false&types=CODE_SMELL)

[![No Coverage information](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/NoCoverageInfo-16px.png 'No Coverage information')](https://sonarcloud.io/component_measures?id=jedib0t_go-pretty&pullRequest=230) No Coverage information  
[![0.0%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/3-16px.png '0.0%')](https://sonarcloud.io/component_measures?id=jedib0t_go-pretty&pullRequest=230&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=jedib0t_go-pretty&pullRequest=230&metric=new_duplicated_lines_density&view=list)


coveralls: ## Pull Request Test Coverage Report for [Build 3120337455](https://coveralls.io/builds/52733430)

* **2** of **9**   **(22.22%)**  changed or added relevant lines in **3** files are covered.
* No unchanged relevant lines lost coverage.
* Overall coverage decreased (**-0.2%**) to **99.778%**

---

|  Changes Missing Coverage | Covered Lines | Changed/Added Lines | % |
| :-----|--------------|--------|---: |
| [text/direction.go](https://coveralls.io/builds/52733430/source?filename=text%2Fdirection.go#L16) | 0 | 7 | 0.0%
<!-- | **Total:** | **2** | **9** | **22.22%** | -->


|  Totals | [![Coverage Status](https://coveralls.io/builds/52733430/badge)](https://coveralls.io/builds/52733430) |
| :-- | --: |
| Change from base [Build 2985673163](https://coveralls.io/builds/52187137): |  -0.2% |
| Covered Lines: | 3142 |
| Relevant Lines: | 3149 |

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

sonarcloud[bot]: Kudos, SonarCloud Quality Gate passed!&nbsp; &nbsp; [![Quality Gate passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/passed-16px.png 'Quality Gate passed')](https://sonarcloud.io/dashboard?id=jedib0t_go-pretty&pullRequest=230)

[![Bug](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug-16px.png 'Bug')](https://sonarcloud.io/project/issues?id=jedib0t_go-pretty&pullRequest=230&resolved=false&types=BUG) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=jedib0t_go-pretty&pullRequest=230&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=jedib0t_go-pretty&pullRequest=230&resolved=false&types=BUG)  
[![Vulnerability](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability-16px.png 'Vulnerability')](https://sonarcloud.io/project/issues?id=jedib0t_go-pretty&pullRequest=230&resolved=false&types=VULNERABILITY) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=jedib0t_go-pretty&pullRequest=230&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=jedib0t_go-pretty&pullRequest=230&resolved=false&types=VULNERABILITY)  
[![Security Hotspot](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot-16px.png 'Security Hotspot')](https://sonarcloud.io/project/security_hotspots?id=jedib0t_go-pretty&pullRequest=230&resolved=false&types=SECURITY_HOTSPOT) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/security_hotspots?id=jedib0t_go-pretty&pullRequest=230&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=jedib0t_go-pretty&pullRequest=230&resolved=false&types=SECURITY_HOTSPOT)  
[![Code Smell](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell-16px.png 'Code Smell')](https://sonarcloud.io/project/issues?id=jedib0t_go-pretty&pullRequest=230&resolved=false&types=CODE_SMELL) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=jedib0t_go-pretty&pullRequest=230&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=jedib0t_go-pretty&pullRequest=230&resolved=false&types=CODE_SMELL)

[![No Coverage information](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/NoCoverageInfo-16px.png 'No Coverage information')](https://sonarcloud.io/component_measures?id=jedib0t_go-pretty&pullRequest=230) No Coverage information  
[![0.0%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/3-16px.png '0.0%')](https://sonarcloud.io/component_measures?id=jedib0t_go-pretty&pullRequest=230&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=jedib0t_go-pretty&pullRequest=230&metric=new_duplicated_lines_density&view=list)



Patch:
diff --git a/table/render.go b/table/render.go
index 79c60bf..dc928f6 100644
--- a/table/render.go
+++ b/table/render.go
@@ -236,6 +236,7 @@ func (t *Table) renderLineMergeOutputs(out *strings.Builder, outLine *strings.Bu
 }
 
 func (t *Table) renderMarginLeft(out *strings.Builder, hint renderHint) {
+	out.WriteString(t.style.Format.Direction.Modifier())
 	if t.style.Options.DrawBorder {
 		border := t.getBorderLeft(hint)
 		colors := t.getBorderColors(hint)
diff --git a/table/style.go b/table/style.go
index f440672..2c1795c 100644
--- a/table/style.go
+++ b/table/style.go
@@ -667,9 +667,10 @@ var (
 
 // FormatOptions defines the text-formatting to perform on parts of the Table.
 type FormatOptions struct {
-	Footer text.Format // footer row(s) text format
-	Header text.Format // header row(s) text format
-	Row    text.Format // (data) row(s) text format
+	Direction text.Direction // (forced) BiDi direction for each Column
+	Footer    text.Format    // footer row(s) text format
+	Header    text.Format    // header row(s) text format
+	Row       text.Format    // (data) row(s) text format
 }
 
 var (
diff --git a/table/table.go b/table/table.go
index 335df26..06153ed 100644
--- a/table/table.go
+++ b/table/table.go
@@ -338,7 +338,7 @@ func (t *Table) analyzeAndStringifyColumn(colIdx int, col interface{}, hint rend
 	if strings.Contains(colStr, "\r") {
 		colStr = strings.Replace(colStr, "\r", "", -1)
 	}
-	return colStr
+	return fmt.Sprintf("%s%s", t.style.Format.Direction.Modifier(), colStr)
 }
 
 func (t *Table) getAlign(colIdx int, hint renderHint) text.Align {
diff --git a/text/direction.go b/text/direction.go
new file mode 100644
index 0000000..25eccc2
--- /dev/null
+++ b/text/direction.go
@@ -0,0 +1,24 @@
+package text
+
+// Direction defines the overall flow of text. Similar to bidi.Direction, but
+// simplified and specific to this package.
+type Direction int
+
+// Available Directions.
+const (
+	Default Direction = iota
+	LeftToRight
+	RightToLeft
+)
+
+// Modifier returns a character to force the given direction for the text that
+// follows the modifier.
+func (d Direction) Modifier() string {
+	switch d {
+	case LeftToRight:
+		return "\u202a"
+	case RightToLeft:
+		return "\u202b"
+	}
+	return ""
+}


Test patch:
diff --git a/table/render_test.go b/table/render_test.go
index 8b41588..0bc337f 100644
--- a/table/render_test.go
+++ b/table/render_test.go
@@ -506,6 +506,49 @@ func TestTable_Render_AutoMerge_Wrapped(t *testing.T) {
 └───┴──────────────────────────────┴───────────┴───────────────────┘`)
 }
 
+func TestTable_Render_BiDiText(t *testing.T) {
+	table := Table{}
+	table.AppendHeader(Row{"תאריך", "סכום", "מחלקה", "תגים"})
+	table.AppendRow(Row{"2020-01-01", 5.0, "מחלקה1", []string{"תג1", "תג2"}})
+	table.AppendRow(Row{"2021-02-01", 5.0, "מחלקה1", []string{"תג1"}})
+	table.AppendRow(Row{"2022-03-01", 5.0, "מחלקה2", []string{"תג1"}})
+	table.AppendFooter(Row{"סהכ", 30})
+	table.SetAutoIndex(true)
+
+	//table.Style().Format.Direction = text.Default
+	compareOutput(t, table.Render(), `+---+------------+------+--------+-----------+
+|   | תאריך      | סכום | מחלקה  | תגים      |
++---+------------+------+--------+-----------+
+| 1 | 2020-01-01 |    5 | מחלקה1 | [תג1 תג2] |
+| 2 | 2021-02-01 |    5 | מחלקה1 | [תג1]     |
+| 3 | 2022-03-01 |    5 | מחלקה2 | [תג1]     |
++---+------------+------+--------+-----------+
+|   | סהכ        |   30 |        |           |
++---+------------+------+--------+-----------+`)
+
+	table.Style().Format.Direction = text.LeftToRight
+	compareOutput(t, table.Render(), `‪+---+------------+------+--------+-----------+
+‪|   | ‪תאריך      | ‪סכום | ‪מחלקה  | ‪תגים      |
+‪+---+------------+------+--------+-----------+
+‪| 1 | ‪2020-01-01 |    ‪5 | ‪מחלקה1 | ‪[תג1 תג2] |
+‪| 2 | ‪2021-02-01 |    ‪5 | ‪מחלקה1 | ‪[תג1]     |
+‪| 3 | ‪2022-03-01 |    ‪5 | ‪מחלקה2 | ‪[תג1]     |
+‪+---+------------+------+--------+-----------+
+‪|   | ‪סהכ        |   ‪30 |        |           |
+‪+---+------------+------+--------+-----------+`)
+
+	table.Style().Format.Direction = text.RightToLeft
+	compareOutput(t, table.Render(), `‫+---+------------+------+--------+-----------+
+‫|   | ‫תאריך      | ‫סכום | ‫מחלקה  | ‫תגים      |
+‫+---+------------+------+--------+-----------+
+‫| 1 | ‫2020-01-01 |    ‫5 | ‫מחלקה1 | ‫[תג1 תג2] |
+‫| 2 | ‫2021-02-01 |    ‫5 | ‫מחלקה1 | ‫[תג1]     |
+‫| 3 | ‫2022-03-01 |    ‫5 | ‫מחלקה2 | ‫[תג1]     |
+‫+---+------------+------+--------+-----------+
+‫|   | ‫סהכ        |   ‫30 |        |           |
+‫+---+------------+------+--------+-----------+`)
+}
+
 func TestTable_Render_BorderAndSeparators(t *testing.T) {
 	table := Table{}
 	table.AppendHeader(testHeader)
diff --git a/table/sort_test.go b/table/sort_test.go
index bd8f9a4..d63164d 100644
--- a/table/sort_test.go
+++ b/table/sort_test.go
@@ -15,6 +15,7 @@ func TestTable_sortRows_WithName(t *testing.T) {
 		{20, "Jon", "Snow", 2000, "You know nothing, Jon Snow!"},
 		{300, "Tyrion", "Lannister", 5000},
 	})
+	table.SetStyle(StyleDefault)
 	table.initForRenderRows()
 
 	// sort by nothing
@@ -76,6 +77,7 @@ func TestTable_sortRows_WithoutName(t *testing.T) {
 		{20, "Jon", "Snow", 2000, "You know nothing, Jon Snow!"},
 		{300, "Tyrion", "Lannister", 5000},
 	})
+	table.SetStyle(StyleDefault)
 	table.initForRenderRows()
 
 	// sort by nothing
@@ -137,6 +139,7 @@ func TestTable_sortRows_InvalidMode(t *testing.T) {
 		{20, "Jon", "Snow", 2000, "You know nothing, Jon Snow!"},
 		{300, "Tyrion", "Lannister", 5000},
 	})
+	table.SetStyle(StyleDefault)
 	table.initForRenderRows()
 
 	// sort by "First Name"