Repository: jedib0t/go-pretty (go)

Issue:
How to apply text alignment to all header columns?
I love my table header row to have center-aligned text, like this

```
╭───┬──────┬──────────────┬──────────────────────────────┬───────────────┬─────────┬────────────────┬──────────────────────╮
│ # │ Name │ Container ID │             Image            │      Kind     │  State  │  IPv4 Address  │     IPv6 Address     │
├───┼──────┼──────────────┼──────────────────────────────┼───────────────┼─────────┼────────────────┼──────────────────────┤
│ 1 │ srl  │ 7faefde8ee2e │ ghcr.io/nokia/srlinux:latest │ nokia_srlinux │ running │ 172.20.20.2/24 │ 3fff:172:20:20::2/64 │
╰───┴──────┴──────────────┴──────────────────────────────┴───────────────┴─────────┴────────────────┴──────────────────────╯
```

The problem is, that it seems setting the text alignment of the header columns is only possible with 

```
table.SetColumnConfigs()
```

which needs to know either the column names, or indexes. I compile the table dynamically, so I might now know the names of the rows ahead of time.

At the same time, it seems there is no `table.AddColumnConfigs()` that would not overwrite the slice of column configs. How could I dynamically add the column config iterating over the slice of `table.Row`?

Pull request:
table: defaults for row/header/footer align/valign; fixes #340
## Proposed Changes
  - the default align/valign option for every row (header+footer) is text.AlignDefault and text.VAlignDefault
  - expose these as customizable options in `table.Style().Format`

To change the defaults, you'd do something like this:
```golang
		tw := table.NewWriter()
		tw.AppendHeader(table.Row{"#", "First\nName", "Last\nName", "Final\nState", "Misc.\nMulti-line\nNotes"})
		tw.AppendRows([]table.Row{
			{1, "Arya", "Stark", ":) 8)"},
			{20, "Jon", "Snow", ":( :( :(", "You know nothing, Jon Snow!"},
			{300, "Tyrion", "Lannister", ":)"},
		})
		tw.AppendFooter(table.Row{"#", "First\nName", "Last\nName", "Final\nState", "Misc.\nMulti-line\nNotes"})
		tw.SetStyle(table.StyleLight)
		tw.Style().Format.HeaderAlign = text.AlignCenter
		tw.Style().Format.HeaderVAlign = text.VAlignBottom
		tw.Style().Format.FooterAlign = text.AlignRight
		tw.Style().Format.FooterVAlign = text.VAlignTop
		fmt.Println(tw.Render())
```
to get:
```
┌─────┬────────┬───────────┬──────────┬─────────────────────────────┐
│     │        │           │          │            MISC.            │
│     │  FIRST │    LAST   │   FINAL  │          MULTI-LINE         │
│   # │  NAME  │    NAME   │   STATE  │            NOTES            │
├─────┼────────┼───────────┼──────────┼─────────────────────────────┤
│   1 │ Arya   │ Stark     │ :) 8)    │                             │
│  20 │ Jon    │ Snow      │ :( :( :( │ You know nothing, Jon Snow! │
│ 300 │ Tyrion │ Lannister │ :)       │                             │
├─────┼────────┼───────────┼──────────┼─────────────────────────────┤
│   # │  FIRST │      LAST │    FINAL │                       MISC. │
│     │   NAME │      NAME │    STATE │                  MULTI-LINE │
│     │        │           │          │                       NOTES │
└─────┴────────┴───────────┴──────────┴─────────────────────────────┘
```

Remember that column-configs takes precedence and will override the above.

Patch:
diff --git a/table/style.go b/table/style.go
index 23b32b1..aaed2c2 100644
--- a/table/style.go
+++ b/table/style.go
@@ -690,17 +690,29 @@ var (
 
 // FormatOptions defines the text-formatting to perform on parts of the Table.
 type FormatOptions struct {
-	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
+	Direction    text.Direction // (forced) BiDi direction for each Column
+	Footer       text.Format    // default text format
+	FooterAlign  text.Align     // default horizontal align
+	FooterVAlign text.VAlign    // default vertical align
+	Header       text.Format    // default text format
+	HeaderAlign  text.Align     // default horizontal align
+	HeaderVAlign text.VAlign    // default vertical align
+	Row          text.Format    // default text format
+	RowAlign     text.Align     // default horizontal align
+	RowVAlign    text.VAlign    // default vertical align
 }
 
 // FormatOptionsDefault defines sensible formatting options.
 var FormatOptionsDefault = FormatOptions{
-	Footer: text.FormatUpper,
-	Header: text.FormatUpper,
-	Row:    text.FormatDefault,
+	Footer:       text.FormatUpper,
+	FooterAlign:  text.AlignDefault,
+	FooterVAlign: text.VAlignDefault,
+	Header:       text.FormatUpper,
+	HeaderAlign:  text.AlignDefault,
+	HeaderVAlign: text.VAlignDefault,
+	Row:          text.FormatDefault,
+	RowAlign:     text.AlignDefault,
+	RowVAlign:    text.VAlignDefault,
 }
 
 // HTMLOptions defines the global options to control HTML rendering.
diff --git a/table/table.go b/table/table.go
index 34e5a34..35426c5 100644
--- a/table/table.go
+++ b/table/table.go
@@ -356,6 +356,12 @@ func (t *Table) getAlign(colIdx int, hint renderHint) text.Align {
 			align = text.AlignRight
 		} else if hint.isAutoIndexRow {
 			align = text.AlignCenter
+		} else if hint.isHeaderRow {
+			align = t.style.Format.HeaderAlign
+		} else if hint.isFooterRow {
+			align = t.style.Format.FooterAlign
+		} else {
+			align = t.style.Format.RowAlign
 		}
 	}
 	return align
@@ -671,6 +677,15 @@ func (t *Table) getVAlign(colIdx int, hint renderHint) text.VAlign {
 			vAlign = cfg.VAlign
 		}
 	}
+	if vAlign == text.VAlignDefault {
+		if hint.isHeaderRow {
+			vAlign = t.style.Format.HeaderVAlign
+		} else if hint.isFooterRow {
+			vAlign = t.style.Format.FooterVAlign
+		} else {
+			vAlign = t.style.Format.RowVAlign
+		}
+	}
 	return vAlign
 }
 


Test patch:
diff --git a/table/render_test.go b/table/render_test.go
index c7134c6..4451cc3 100644
--- a/table/render_test.go
+++ b/table/render_test.go
@@ -112,20 +112,64 @@ A Song of Ice and Fire`)
 }
 
 func TestTable_Render_Align(t *testing.T) {
-	tw := NewWriter()
-	tw.AppendHeader(testHeader)
-	tw.AppendRows(testRows)
-	tw.AppendRow(Row{500, "Jamie", "Lannister", "Kingslayer", "The things I do for love."})
-	tw.AppendRow(Row{1000, "Tywin", "Lannister", nil})
-	tw.AppendFooter(testFooter)
-	tw.SetColumnConfigs([]ColumnConfig{
-		{Name: "First Name", Align: text.AlignLeft, AlignHeader: text.AlignLeft, AlignFooter: text.AlignLeft},
-		{Name: "Last Name", Align: text.AlignRight, AlignHeader: text.AlignRight, AlignFooter: text.AlignRight},
-		{Name: "Salary", Align: text.AlignAuto, AlignHeader: text.AlignRight, AlignFooter: text.AlignAuto},
-		{Number: 5, Align: text.AlignJustify, AlignHeader: text.AlignJustify, AlignFooter: text.AlignJustify},
+	t.Run("defaults", func(t *testing.T) {
+		tw := NewWriter()
+		tw.AppendHeader(Row{"#", "First\nName", "Last\nName", "Final\nState", "Misc.\nMulti-line\nNotes"})
+		tw.AppendRows([]Row{
+			{1, "Arya", "Stark", ":) 8)"},
+			{20, "Jon", "Snow", ":( :( :(", "You know nothing, Jon Snow!"},
+			{300, "Tyrion", "Lannister", ":)"},
+		})
+		tw.AppendFooter(Row{"#", "First\nName", "Last\nName", "Final\nState", "Misc.\nMulti-line\nNotes"})
+		tw.Style().Format = FormatOptions{
+			FooterAlign:  text.AlignRight,
+			FooterVAlign: text.VAlignTop,
+			HeaderAlign:  text.AlignLeft,
+			HeaderVAlign: text.VAlignBottom,
+			RowAlign:     text.AlignCenter,
+			RowVAlign:    text.VAlignMiddle,
+		}
+		tw.SetColumnConfigs([]ColumnConfig{ // takes precedence
+			{
+				Name:  "Final\nState",
+				Align: text.AlignLeft, VAlign: text.VAlignTop,
+				AlignHeader: text.AlignLeft, VAlignHeader: text.VAlignTop,
+				AlignFooter: text.AlignLeft, VAlignFooter: text.VAlignBottom,
+			},
+		})
+
+		compareOutput(t, tw.Render(), `
++-----+--------+-----------+----------+-----------------------------+
+|     |        |           | Final    | Misc.                       |
+|     | First  | Last      | State    | Multi-line                  |
+|   # | Name   | Name      |          | Notes                       |
++-----+--------+-----------+----------+-----------------------------+
+|   1 |  Arya  |   Stark   | :) 8)    |                             |
+|  20 |   Jon  |    Snow   | :( :( :( | You know nothing, Jon Snow! |
+| 300 | Tyrion | Lannister | :)       |                             |
++-----+--------+-----------+----------+-----------------------------+
+|   # |  First |      Last |          |                       Misc. |
+|     |   Name |      Name | Final    |                  Multi-line |
+|     |        |           | State    |                       Notes |
++-----+--------+-----------+----------+-----------------------------+`)
+
 	})
 
-	compareOutput(t, tw.Render(), `
+	t.Run("column overrides", func(t *testing.T) {
+		tw := NewWriter()
+		tw.AppendHeader(testHeader)
+		tw.AppendRows(testRows)
+		tw.AppendRow(Row{500, "Jamie", "Lannister", "Kingslayer", "The things I do for love."})
+		tw.AppendRow(Row{1000, "Tywin", "Lannister", nil})
+		tw.AppendFooter(testFooter)
+		tw.SetColumnConfigs([]ColumnConfig{
+			{Name: "First Name", Align: text.AlignLeft, AlignHeader: text.AlignLeft, AlignFooter: text.AlignLeft},
+			{Name: "Last Name", Align: text.AlignRight, AlignHeader: text.AlignRight, AlignFooter: text.AlignRight},
+			{Name: "Salary", Align: text.AlignAuto, AlignHeader: text.AlignRight, AlignFooter: text.AlignAuto},
+			{Number: 5, Align: text.AlignJustify, AlignHeader: text.AlignJustify, AlignFooter: text.AlignJustify},
+		})
+
+		compareOutput(t, tw.Render(), `
 +------+------------+-----------+------------+-----------------------------+
 |    # | FIRST NAME | LAST NAME |     SALARY |                             |
 +------+------------+-----------+------------+-----------------------------+
@@ -137,6 +181,7 @@ func TestTable_Render_Align(t *testing.T) {
 +------+------------+-----------+------------+-----------------------------+
 |      |            |     TOTAL |      10000 |                             |
 +------+------------+-----------+------------+-----------------------------+`)
+	})
 }
 
 func TestTable_Render_AutoIndex(t *testing.T) {