From d28aad564b92483ed98cc7a022bf9132531941b6 Mon Sep 17 00:00:00 2001 From: Naveen Mahalingam Date: Sat, 24 Sep 2022 17:39:50 -0700 Subject: [PATCH] table: option to force text direction for tables with BiDi content (#230) --- table/render.go | 1 + table/render_test.go | 43 +++++++++++++++++++++++++++++++++++++++++++ table/sort_test.go | 3 +++ table/style.go | 7 ++++--- table/table.go | 2 +- text/direction.go | 24 ++++++++++++++++++++++++ 6 files changed, 76 insertions(+), 4 deletions(-) create mode 100644 text/direction.go 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/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" 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 "" +}