Hlavní navigace

Vykreslení tabulek do terminálu v jazyce Go

4. 3. 2021
Doba čtení: 25 minut

Sdílet

 Autor: Go lang
Dnes se budeme zabývat zdánlivě triviálním tématem – jak do terminálu vykreslit tabulku s proměnnou šířkou sloupců, popř. s využitím různých textových efektů. Použijeme k tomu balíčky tabwriter, tablewriter a tableprinter.

Obsah

1. Vykreslení tabulek do terminálu v jazyce Go

2. Standardní balíček tabwriter

3. Chování pro záznamy s odlišnou šířkou

4. Specifikace výplňových znaků včetně jejich minimálního počtu

5. Zarovnání hodnot doprava, příznak Debug

6. Skutečný význam znaku Tab

7. Zobrazení tabulky s faktoriály hodnot 0 až 20

8. Postupné přidávání hodnot do výsledné tabulky funkcí fmt.Fprintf

9. Zápis tabulky do souboru

10. HTTP server, který nabízí klientům naformátovanou tabulku

11. Balíček olekukonko/tablewriter

12. Vykreslení tabulky s hlavičkou i s okraji buněk

13. Tabulka bez okrajů

14. Tabulka s patičkou (suma hodnot atd.)

15. Zobrazení tabulky s faktoriály hodnot 0 až 20

16. Výstup do souboru, HTTP server nabízející klientům naformátovanou tabulku

17. Balíček lensesio/tableprinter

18. Ukázky použití balíčku lensesio/tableprinter

19. Repositář s demonstračními příklady

20. Odkazy na Internetu

1. Vykreslení tabulek do terminálu v jazyce Go

V poměrně mnoha aplikacích je někdy nutné dokázat vykreslit do terminálu (tedy neproporcionálním fontem) tabulky, v nichž je použita různá (a mnohdy dopředu neznámá) šířka sloupců. Tento problém má pochopitelně celou řadu řešení. Dnes se zaměříme na programovací jazyk Go, pro který vznikla trojice podobně pojmenovaných balíčků, které dokážou tabulky vykreslovat, a to různými styly: tabwriter, tablewriter a konečně tableprinter. Nejdříve si popíšeme možnosti balíčku nazvaného tabwriter, už jen z toho důvodu, že se jedná o standardní balíček programovacího jazyka Go (i když jeho možnosti jsou v porovnání s konkurencí omezené). tabwriter je založen na algoritmu elastic tabstops, který po analýze dat (zejména jejich šířky) dokáže tabulku korektně naformátovat. Tento algoritmus slouží i ke zobrazení zdrojových kódů – viz následující dnes již pravděpodobně dokonale známou animaci, která ukazuje základní funkcionalitu tohoto algoritmu, která je dostupná na adrese https://nickgravgaard.com/elastic-tabstops/images/columnbloc­ks_coloured.gif:

elastic tabstops
Poznámka: v tomto článku se často budeme zmiňovat o řídicím znaku Tab, takže možná stojí za zmínku, že demonstrační příklady, které budou dnes ukázány v navazujících kapitolách, prošly před importem do redakčního systému konverzí, které znaky Tab nahradilo za mezery (i když se pro programovací jazyk Go oficiálně doporučují Taby). Z tohoto důvodu je lepší zdrojové kódy příkladů získat přímo z repositáře uvedeného v devatenácté kapitole.

2. Standardní balíček tabwriter

V úvodu dnešního článku se zaměříme na standardní balíček nazvaný tabwriter, resp. přesněji (s celou cestou) text/tabwriter. Tento balíček umožňuje zobrazit sloupcová data libovolného typu, přičemž jednotlivé sloupce jsou na vstupu ukončeny (nikoli odděleny) znakem Tab. Použití je ve skutečnosti velmi jednoduché, protože pouze postačuje vytvořit instanci této struktury a zapisovat do ní libovolnými I/O funkcemi, které akceptují rozhraní typu io.Writer, což jsou například mnohé funkce z dalšího standardního balíčku fmt. Konstruktoru se předává několik parametrů popsaných dále:

w := tabwriter.NewWriter(os.Stdout, MinWidth, TabWidth, Padding, PaddingCharacter, Flags)

Samotný zápis libovolných dat s definicí sloupců:

fmt.Fprintln(w, "1\t2\t3")
fmt.Fprintln(w, "4\t5\t6")
fmt.Fprintln(w, "7\t8\t9")

Nakonec je nutné zavolat metodu Flush, která zajistí vykreslení celé tabulky:

w.Flush()

Podívejme se na dnešní první demonstrační příklad, který po svém spuštění vypíše do terminálu dvojici tabulek, každou s odlišnou šířkou sloupců:

package main
 
import (
        "fmt"
        "os"
        "text/tabwriter"
)
 
const (
        MinWidth         = 0
        TabWidth         = 0
        Padding          = 1
        PaddingCharacter = ' '
        Flags            = 0
)
 
func main() {
        w := tabwriter.NewWriter(os.Stdout, MinWidth, TabWidth, Padding, PaddingCharacter, Flags)
        fmt.Fprintln(w, "1\t2\t3")
        fmt.Fprintln(w, "4\t5\t6")
        fmt.Fprintln(w, "7\t8\t9")
        w.Flush()
 
        fmt.Println()
 
        w = tabwriter.NewWriter(os.Stdout, MinWidth, TabWidth, Padding, PaddingCharacter, Flags)
        fmt.Fprintln(w, "foo\tbar\tbaz")
        fmt.Fprintln(w, "foo\tbar\tbaz")
        fmt.Fprintln(w, "foo\tbar\tbaz")
        w.Flush()
}

Výsledek by měl vypadat následovně:

1 2 3
4 5 6
7 8 9
 
foo bar baz
foo bar baz
foo bar baz

3. Chování pro záznamy s odlišnou šířkou

Ve druhém demonstračním příkladu se opět pokusíme vykreslit dvě tabulky, tentokrát ovšem budou mít jednotlivé záznamy odlišnou šířku. Tomu se automaticky přizpůsobí výsledná tabulka (což je velký rozdíl oproti přímočarému použití znaků Tab, které tyto automatické úpravy nepodporují):

package main
 
import (
        "fmt"
        "os"
        "text/tabwriter"
)
 
const (
        MinWidth         = 0
        TabWidth         = 0
        Padding          = 1
        PaddingCharacter = ' '
        Flags            = 0
)
 
func main() {
        w := tabwriter.NewWriter(os.Stdout, MinWidth, TabWidth, Padding, PaddingCharacter, Flags)
        fmt.Fprintln(w, "1\t1\t1")
        fmt.Fprintln(w, "22\t22\t22")
        fmt.Fprintln(w, "333\t333\t333")
        fmt.Fprintln(w, "4444\t4444\t4444")
        w.Flush()
 
        fmt.Println()
 
        w = tabwriter.NewWriter(os.Stdout, MinWidth, TabWidth, Padding, PaddingCharacter, Flags)
        fmt.Fprintln(w, "f\tb\tb")
        fmt.Fprintln(w, "foo\tbar\tbaz")
        fmt.Fprintln(w, "foobar\tbarbaz\tbazfoo")
        w.Flush()
}

Výsledek by měl vypadat takto:

1    1    1
22   22   22
333  333  333
4444 4444 4444
 
f      b      b
foo    bar    baz
foobar barbaz bazfoo

Šířku (přesněji řečeno minimální šířku) lze specifikovat i explicitně, a to konkrétně úpravou druhého parametru konstruktoru NewWriter. V dalším demonstračním příkladu minimální šířku postupně zvětšujeme od nuly až do devíti znaků:

package main
 
import (
        "fmt"
        "os"
        "text/tabwriter"
)
 
const (
        TabWidth         = 0
        Padding          = 1
        PaddingCharacter = ' '
        Flags            = 0
)
 
func main() {
        for minWidth := 0; minWidth < 10; minWidth++ {
                fmt.Printf("Min width = %d\n", minWidth)
                w := tabwriter.NewWriter(os.Stdout, minWidth, TabWidth, Padding, PaddingCharacter, Flags)
                fmt.Fprintln(w, "1\t1\t1")
                fmt.Fprintln(w, "22\t22\t22")
                fmt.Fprintln(w, "333\t333\t333")
                fmt.Fprintln(w, "4444\t4444\t4444")
                w.Flush()
                fmt.Println()
        }
}

Na výsledku je patrné, že zpočátku nemá zadaná šířka žádný vliv na výslednou tabulku, protože záznamy jsou delší, než specifikovaná šířka. Posléze ovšem dojde k roztažení sloupců tabulky na požadovanou šířku:

Min width = 0
1    1    1
22   22   22
333  333  333
4444 4444 4444
 
Min width = 1
1    1    1
22   22   22
333  333  333
4444 4444 4444
 
Min width = 2
1    1    1
22   22   22
333  333  333
4444 4444 4444
 
Min width = 3
1    1    1
22   22   22
333  333  333
4444 4444 4444
 
Min width = 4
1    1    1
22   22   22
333  333  333
4444 4444 4444
 
Min width = 5
1    1    1
22   22   22
333  333  333
4444 4444 4444
 
Min width = 6
1     1     1
22    22    22
333   333   333
4444  4444  4444
 
Min width = 7
1      1      1
22     22     22
333    333    333
4444   4444   4444
 
Min width = 8
1       1       1
22      22      22
333     333     333
4444    4444    4444
 
Min width = 9
1        1        1
22       22       22
333      333      333
4444     4444     4444

4. Specifikace výplňových znaků včetně jejich minimálního počtu

Mezi další parametry konstruktoru NewWriter patří specifikace výplňového znaku (libovolný znak z celého Unicode, jak je ostatně v Go dobrým zvykem) a taktéž minimálního počtu výplňových znaků. Nejprve se podívejme, jak se změní formát výsledných tabulek, pokud budeme postupně měnit počet výplňových znaků:

package main
 
import (
        "fmt"
        "os"
        "text/tabwriter"
)
 
const (
        MinWidth         = 0
        TabWidth         = 0
        PaddingCharacter = ' '
        Flags            = 0
)
 
func main() {
        for padding := 0; padding < 10; padding++ {
                fmt.Printf("padding = %d\n", padding)
                w := tabwriter.NewWriter(os.Stdout, MinWidth, TabWidth, padding, PaddingCharacter, Flags)
                fmt.Fprintln(w, "1\t1\t1")
                fmt.Fprintln(w, "22\t22\t22")
                fmt.Fprintln(w, "333\t333\t333")
                fmt.Fprintln(w, "4444\t4444\t4444")
                w.Flush()
                fmt.Println()
        }
}

Výsledné tabulky:

padding = 0
1   1   1
22  22  22
333 333 333
444444444444
 
padding = 1
1    1    1
22   22   22
333  333  333
4444 4444 4444
 
padding = 2
1     1     1
22    22    22
333   333   333
4444  4444  4444
 
padding = 3
1      1      1
22     22     22
333    333    333
4444   4444   4444
 
padding = 4
1       1       1
22      22      22
333     333     333
4444    4444    4444
 
padding = 5
1        1        1
22       22       22
333      333      333
4444     4444     4444
 
padding = 6
1         1         1
22        22        22
333       333       333
4444      4444      4444
 
padding = 7
1          1          1
22         22         22
333        333        333
4444       4444       4444
 
padding = 8
1           1           1
22          22          22
333         333         333
4444        4444        4444
 
padding = 9
1            1            1
22           22           22
333          333          333
4444         4444         4444

Výplňovým znakem byla až doposud mezera, ovšem ve skutečnosti se může použít libovolný jiný znak, což je ukázáno na dalším demonstračním příkladu:

package main
 
import (
        "fmt"
        "os"
        "text/tabwriter"
)
 
const (
        MinWidth         = 0
        TabWidth         = 0
        PaddingCharacter = '.'
        Flags            = 0
)
 
func main() {
        for padding := 0; padding < 10; padding++ {
                fmt.Printf("padding = %d\n", padding)
                w := tabwriter.NewWriter(os.Stdout, MinWidth, TabWidth, padding, PaddingCharacter, Flags)
                fmt.Fprintln(w, "1\t1\t1")
                fmt.Fprintln(w, "22\t22\t22")
                fmt.Fprintln(w, "333\t333\t333")
                fmt.Fprintln(w, "4444\t4444\t4444")
                w.Flush()
                fmt.Println()
        }
}

Nyní je z výsledků patrné, jak se s výplňovými znaky pracuje v praxi:

padding = 0
1...1...1
22..22..22
333.333.333
444444444444
 
padding = 1
1....1....1
22...22...22
333..333..333
4444.4444.4444
 
padding = 2
1.....1.....1
22....22....22
333...333...333
4444..4444..4444
 
padding = 3
1......1......1
22.....22.....22
333....333....333
4444...4444...4444
 
padding = 4
1.......1.......1
22......22......22
333.....333.....333
4444....4444....4444
 
padding = 5
1........1........1
22.......22.......22
333......333......333
4444.....4444.....4444
 
padding = 6
1.........1.........1
22........22........22
333.......333.......333
4444......4444......4444
 
padding = 7
1..........1..........1
22.........22.........22
333........333........333
4444.......4444.......4444
 
padding = 8
1...........1...........1
22..........22..........22
333.........333.........333
4444........4444........4444
 
padding = 9
1............1............1
22...........22...........22
333..........333..........333
4444.........4444.........4444

5. Zarovnání hodnot doprava, příznak Debug

Posledním parametrem konstruktoru NewWriter je celočíselná hodnota s příznaky, které mění vlastnosti vykreslované tabulky. Jeden z těchto příznaků se jmenuje příznačně AlignRight. Pomocí tohoto příznaku lze hodnoty ve sloupcích zarovnat doprava a nikoli doleva (což je výchozí chování):

package main
 
import (
        "fmt"
        "os"
        "text/tabwriter"
)
 
const (
        TabWidth         = 0
        Padding          = 1
        PaddingCharacter = ' '
        Flags            = tabwriter.AlignRight
)
 
func main() {
        for minWidth := 0; minWidth < 10; minWidth++ {
                fmt.Printf("Min width = %d\n", minWidth)
                w := tabwriter.NewWriter(os.Stdout, minWidth, TabWidth, Padding, PaddingCharacter, Flags)
                fmt.Fprintln(w, "1\t1\t1")
                fmt.Fprintln(w, "22\t22\t22")
                fmt.Fprintln(w, "333\t333\t333")
                fmt.Fprintln(w, "4444\t4444\t4444")
                w.Flush()
                fmt.Println()
        }
}

Výsledkem jsou tabulky se třemi sloupci, které ovšem vypadají „divně“. Proč tomu tak je, si vysvětlíme v dalším textu:

Min width = 0
    1    11
   22   2222
  333  333333
 4444 44444444
 
Min width = 1
    1    11
   22   2222
  333  333333
 4444 44444444
 
Min width = 2
    1    11
   22   2222
  333  333333
 4444 44444444
 
Min width = 3
    1    11
   22   2222
  333  333333
 4444 44444444
 
Min width = 4
    1    11
   22   2222
  333  333333
 4444 44444444
 
Min width = 5
    1    11
   22   2222
  333  333333
 4444 44444444
 
Min width = 6
     1     11
    22    2222
   333   333333
  4444  44444444
 
Min width = 7
      1      11
     22     2222
    333    333333
   4444   44444444
 
Min width = 8
       1       11
      22      2222
     333     333333
    4444    44444444
 
Min width = 9
        1        11
       22       2222
      333      333333
     4444     44444444

Druhým užitečným příznakem je příznak nazvaný Debug. Tímto příznakem se povoluje zobrazení oddělovačů mezi sloupci. Oddělovačem je ve výchozím nastavení znak „|“:

package main
 
import (
        "fmt"
        "os"
        "text/tabwriter"
)
 
const (
        TabWidth         = 0
        Padding          = 1
        PaddingCharacter = ' '
        Flags            = tabwriter.AlignRight | tabwriter.Debug
)
 
func main() {
        for minWidth := 0; minWidth < 10; minWidth++ {
                fmt.Printf("Min width = %d\n", minWidth)
                w := tabwriter.NewWriter(os.Stdout, minWidth, TabWidth, Padding, PaddingCharacter, Flags)
                fmt.Fprintln(w, "1\t1\t1")
                fmt.Fprintln(w, "22\t22\t22")
                fmt.Fprintln(w, "333\t333\t333")
                fmt.Fprintln(w, "4444\t4444\t4444")
                w.Flush()
                fmt.Println()
        }
}

Nyní jsou – alespoň teoreticky – sloupce zarovnané doprava a současně jsou od sebe oddělené znakem „|“:

Min width = 0
    1|    1|1
   22|   22|22
  333|  333|333
 4444| 4444|4444
 
Min width = 1
    1|    1|1
   22|   22|22
  333|  333|333
 4444| 4444|4444
 
Min width = 2
    1|    1|1
   22|   22|22
  333|  333|333
 4444| 4444|4444
 
Min width = 3
    1|    1|1
   22|   22|22
  333|  333|333
 4444| 4444|4444
 
Min width = 4
    1|    1|1
   22|   22|22
  333|  333|333
 4444| 4444|4444
 
Min width = 5
    1|    1|1
   22|   22|22
  333|  333|333
 4444| 4444|4444
 
Min width = 6
     1|     1|1
    22|    22|22
   333|   333|333
  4444|  4444|4444
 
Min width = 7
      1|      1|1
     22|     22|22
    333|    333|333
   4444|   4444|4444
 
Min width = 8
       1|       1|1
      22|      22|22
     333|     333|333
    4444|    4444|4444
 
Min width = 9
        1|        1|1
       22|       22|22
      333|      333|333
     4444|     4444|4444

6. Skutečný význam znaku Tab

Předchozí dvojice demonstračních příkladů nepracovala korektně, protože poslední (třetí) sloupec ve skutečnosti nebyl zarovnán doprava, ale doleva. Je tomu tak z toho důvodu, že znak Tab slouží pro ukončení sloupců, nikoli pro jejich oddělení. V praxi to znamená to, že je nutné uvést znak Tab i na konci vypisovaného řetězce (přesněji řečeno před znakem pro konec řádku):

package main
 
import (
        "fmt"
        "os"
        "text/tabwriter"
)
 
const (
        TabWidth         = 0
        Padding          = 1
        PaddingCharacter = ' '
        Flags            = tabwriter.AlignRight
)
 
func main() {
        for minWidth := 0; minWidth < 10; minWidth++ {
                fmt.Printf("Min width = %d\n", minWidth)
                w := tabwriter.NewWriter(os.Stdout, minWidth, TabWidth, Padding, PaddingCharacter, Flags)
                fmt.Fprintln(w, "1\t1\t1\t")
                fmt.Fprintln(w, "22\t22\t22\t")
                fmt.Fprintln(w, "333\t333\t333\t")
                fmt.Fprintln(w, "4444\t4444\t4444\t")
                w.Flush()
                fmt.Println()
        }
}

Tento demonstrační příklad již zobrazí všechny sloupce s korektním zarovnáním:

Min width = 0
    1    1    1
   22   22   22
  333  333  333
 4444 4444 4444
 
Min width = 1
    1    1    1
   22   22   22
  333  333  333
 4444 4444 4444
 
Min width = 2
    1    1    1
   22   22   22
  333  333  333
 4444 4444 4444
 
Min width = 3
    1    1    1
   22   22   22
  333  333  333
 4444 4444 4444
 
Min width = 4
    1    1    1
   22   22   22
  333  333  333
 4444 4444 4444
 
Min width = 5
    1    1    1
   22   22   22
  333  333  333
 4444 4444 4444
 
Min width = 6
     1     1     1
    22    22    22
   333   333   333
  4444  4444  4444
 
Min width = 7
      1      1      1
     22     22     22
    333    333    333
   4444   4444   4444
 
Min width = 8
       1       1       1
      22      22      22
     333     333     333
    4444    4444    4444
 
Min width = 9
        1        1        1
       22       22       22
      333      333      333
     4444     4444     4444

Totéž bude platit i při využití příznaku Debug, kterým zajistíme oddělení hodnot ve sloupcích znakem „|“:

package main
 
import (
        "fmt"
        "os"
        "text/tabwriter"
)
 
const (
        TabWidth         = 0
        Padding          = 1
        PaddingCharacter = ' '
        Flags            = tabwriter.AlignRight | tabwriter.Debug
)
 
func main() {
        for minWidth := 0; minWidth < 10; minWidth++ {
                fmt.Printf("Min width = %d\n", minWidth)
                w := tabwriter.NewWriter(os.Stdout, minWidth, TabWidth, Padding, PaddingCharacter, Flags)
                fmt.Fprintln(w, "1\t1\t1\t")
                fmt.Fprintln(w, "22\t22\t22\t")
                fmt.Fprintln(w, "333\t333\t333\t")
                fmt.Fprintln(w, "4444\t4444\t4444\t")
                w.Flush()
                fmt.Println()
        }
}

S výsledkem:

Min width = 0
    1|    1|    1|
   22|   22|   22|
  333|  333|  333|
 4444| 4444| 4444|
 
Min width = 1
    1|    1|    1|
   22|   22|   22|
  333|  333|  333|
 4444| 4444| 4444|
 
Min width = 2
    1|    1|    1|
   22|   22|   22|
  333|  333|  333|
 4444| 4444| 4444|
 
Min width = 3
    1|    1|    1|
   22|   22|   22|
  333|  333|  333|
 4444| 4444| 4444|
 
Min width = 4
    1|    1|    1|
   22|   22|   22|
  333|  333|  333|
 4444| 4444| 4444|
 
Min width = 5
    1|    1|    1|
   22|   22|   22|
  333|  333|  333|
 4444| 4444| 4444|
 
Min width = 6
     1|     1|     1|
    22|    22|    22|
   333|   333|   333|
  4444|  4444|  4444|
 
Min width = 7
      1|      1|      1|
     22|     22|     22|
    333|    333|    333|
   4444|   4444|   4444|
 
Min width = 8
       1|       1|       1|
      22|      22|      22|
     333|     333|     333|
    4444|    4444|    4444|
 
Min width = 9
        1|        1|        1|
       22|       22|       22|
      333|      333|      333|
     4444|     4444|     4444|

7. Zobrazení tabulky s faktoriály hodnot 0 až 20

V dalším demonstračním příkladu, který bude ještě několikrát zopakován, ovšem pokaždé při použití odlišného balíčku, je ukázán způsob zobrazení tabulky s faktoriály hodnot 0 až 20. Výsledky velmi rychle rostou, ovšem tabulka se bez problémů přizpůsobí různé šířce hodnot:

package main
 
import (
        "fmt"
        "os"
        "text/tabwriter"
)
 
const (
        MinWidth         = 5
        TabWidth         = 0
        Padding          = 1
        PaddingCharacter = ' '
        Flags            = tabwriter.AlignRight | tabwriter.Debug
)
 
// Factorial computes factorial for given n that might be positive integer
func Factorial(n int64) int64 {
        switch {
        case n < 0:
                return 1
        case n == 0:
                return 1
        default:
                return n * Factorial(n-1)
        }
}
 
func main() {
        w := tabwriter.NewWriter(os.Stdout, MinWidth, TabWidth, Padding, PaddingCharacter, Flags)
        fmt.Fprintln(w, "n\tn!\t")
 
        for n := 0; n <= 20; n++ {
                fmt.Fprintf(w, "%d\t%d\t\n", n, Factorial(int64(n)))
        }
 
        w.Flush()
}

Výsledná tabulka zobrazená na terminálu by měla vypadat následovně:

    n|                  n!|
    0|                   1|
    1|                   1|
    2|                   2|
    3|                   6|
    4|                  24|
    5|                 120|
    6|                 720|
    7|                5040|
    8|               40320|
    9|              362880|
   10|             3628800|
   11|            39916800|
   12|           479001600|
   13|          6227020800|
   14|         87178291200|
   15|       1307674368000|
   16|      20922789888000|
   17|     355687428096000|
   18|    6402373705728000|
   19|  121645100408832000|
   20| 2432902008176640000|

8. Postupné přidávání hodnot do výsledné tabulky funkcí fmt.Fprintf

Při konstrukci výsledné tabulky se balíček text/tabwriter ovládá dvěma řídicími znaky – Tab a Newline, tedy v programovém zápisu ‚\t‘ a ‚\n‘. Znak nového řádku vede k vykreslení jednoho řádku tabulky, znak Tab pak k ukončení položky záznamu. Co to ovšem znamená v praxi? Není nutné celý řádek tabulky vypsat jedinou funkcí fmt.Println či nějakou podobnou funkcí, která na výstup posílá i znak pro konec řádku. Naopak – jednotlivé položky záznamu je možné přidávat postupně, což může být i přehlednější. Viz též následující demonstrační příklad:

package main
 
import (
        "fmt"
        "os"
        "text/tabwriter"
)
 
const (
        MinWidth         = 5
        TabWidth         = 0
        Padding          = 1
        PaddingCharacter = ' '
        Flags            = tabwriter.AlignRight | tabwriter.Debug
)
 
// Factorial computes factorial for given n that might be positive integer
func Factorial(n int64) int64 {
        switch {
        case n < 0:
                return 1
        case n == 0:
                return 1
        default:
                return n * Factorial(n-1)
        }
}
 
func main() {
        w := tabwriter.NewWriter(os.Stdout, MinWidth, TabWidth, Padding, PaddingCharacter, Flags)
        fmt.Fprintln(w, "n\tn!\t")
 
        for n := 0; n <= 20; n++ {
                fmt.Fprintf(w, "%d\t", n)
                result := Factorial(int64(n))
                fmt.Fprintf(w, "%d\t", result)
                fmt.Fprintln(w)
        }
 
        w.Flush()
}

Výsledek by měl být totožný s příkladem, který byl ukázán v předchozí kapitole:

    n|                  n!|
    0|                   1|
    1|                   1|
    2|                   2|
    3|                   6|
    4|                  24|
    5|                 120|
    6|                 720|
    7|                5040|
    8|               40320|
    9|              362880|
   10|             3628800|
   11|            39916800|
   12|           479001600|
   13|          6227020800|
   14|         87178291200|
   15|       1307674368000|
   16|      20922789888000|
   17|     355687428096000|
   18|    6402373705728000|
   19|  121645100408832000|
   20| 2432902008176640000|

9. Zápis tabulky do souboru

Tabulku je možné ve skutečnosti poslat (neboli zobrazit) s využitím libovolné struktury, která implementuje rozhraní Writer. Zcela triviálně je tak podporován zápis do souboru, což je ostatně ukázáno v dnešním dvanáctém demonstračním příkladu:

package main
 
import (
        "fmt"
        "log"
        "os"
        "text/tabwriter"
)
 
const (
        MinWidth         = 5
        TabWidth         = 0
        Padding          = 1
        PaddingCharacter = ' '
        Flags            = tabwriter.AlignRight | tabwriter.Debug
)
 
// Factorial computes factorial for given n that might be positive integer
func Factorial(n int64) int64 {
        switch {
        case n < 0:
                return 1
        case n == 0:
                return 1
        default:
                return n * Factorial(n-1)
        }
}
 
func main() {
        file, err := os.Create("table1.txt")
        if err != nil {
                log.Fatal(err)
        }
 
        defer file.Close()
 
        w := tabwriter.NewWriter(file, MinWidth, TabWidth, Padding, PaddingCharacter, Flags)
        fmt.Fprintln(w, "n\tn!\t")
 
        for n := 0; n <= 20; n++ {
                fmt.Fprintf(w, "%d\t", n)
                result := Factorial(int64(n))
                fmt.Fprintf(w, "%d\t", result)
                fmt.Fprintln(w)
        }
 
        w.Flush()
}

Po spuštění tohoto příkladu se vytvoří soubor „table1.txt“, jehož obsah je následující:

    n|                  n!|
    0|                   1|
    1|                   1|
    2|                   2|
    3|                   6|
    4|                  24|
    5|                 120|
    6|                 720|
    7|                5040|
    8|               40320|
    9|              362880|
   10|             3628800|
   11|            39916800|
   12|           479001600|
   13|          6227020800|
   14|         87178291200|
   15|       1307674368000|
   16|      20922789888000|
   17|     355687428096000|
   18|    6402373705728000|
   19|  121645100408832000|
   20| 2432902008176640000|

10. HTTP server, který nabízí klientům naformátovanou tabulku

Poslední demonstrační příklad založený na balíčku text/tabwriter se od předchozích příkladů odlišuje. Jedná se totiž o implementaci HTTP serveru, jehož jediný endpoint slouží pro získání naformátované tabulky. Základní funkcionalita tohoto příkladu je založena na tom, že obsluha (handler) zajišťující přístup k endpointu pracuje s datovou strukturou implementující rozhraní io.Writer (přesněji řečeno s datovou strukturou, která danému rozhraní vyhovuje, abychom se drželi ustálené terminologie):

package main
 
import (
        "fmt"
        "net/http"
        "text/tabwriter"
)
 
const (
        MinWidth         = 5
        TabWidth         = 0
        Padding          = 1
        PaddingCharacter = ' '
        Flags            = tabwriter.AlignRight | tabwriter.Debug
)
 
// Factorial computes factorial for given n that might be positive integer
func Factorial(n int64) int64 {
        switch {
        case n < 0:
                return 1
        case n == 0:
                return 1
        default:
                return n * Factorial(n-1)
        }
}
 
func mainEndpoint(writer http.ResponseWriter, request *http.Request) {
        w := tabwriter.NewWriter(writer, MinWidth, TabWidth, Padding, PaddingCharacter, Flags)
        fmt.Fprintln(w, "n\tn!\t")
 
        for n := 0; n <= 20; n++ {
                fmt.Fprintf(w, "%d\t", n)
                result := Factorial(int64(n))
                fmt.Fprintf(w, "%d\t", result)
                fmt.Fprintln(w)
        }
 
        w.Flush()
}
 
func main() {
        http.HandleFunc("/", mainEndpoint)
        http.ListenAndServe(":8000", nil)
}

Po spuštění tohoto příkladu je možné v jiném terminálu přistoupit na adresu localhost:8000, například nástrojem curl:

$ curl localhost:8000/
 
    n|                  n!|
    0|                   1|
    1|                   1|
    2|                   2|
    3|                   6|
    4|                  24|
    5|                 120|
    6|                 720|
    7|                5040|
    8|               40320|
    9|              362880|
   10|             3628800|
   11|            39916800|
   12|           479001600|
   13|          6227020800|
   14|         87178291200|
   15|       1307674368000|
   16|      20922789888000|
   17|     355687428096000|
   18|    6402373705728000|
   19|  121645100408832000|
   20| 2432902008176640000|
Poznámka: výsledek tedy odpovídá předchozí dvojici demonstračních příkladů.

11. Balíček olekukonko/tablewriter

Druhý balíček určený pro práci s tabulkami v programovacím jazyku Go se jmenuje olekukonko/tablewriter. Jedná se o nestandardní balíček, který je nutné explicitně nainstalovat:

$ go get github.com/olekukonko/tablewriter

Od této chvíle je možné balíček používat v jakémkoli projektu naprogramovaného v jazyce Go.

S tímto balíčkem se pracuje poněkud odlišným způsobem, než se standardním balíčkem text/tabwriter. Hlavní rozdíl spočívá v konstrukci tabulek – nepoužívají se zde řetězce se znaky Tab, ale tabulka je konstruována z řezů řetězců (string slice), což vyžaduje poněkud odlišný přístup ke struktuře kódu, který tabulky generuje. Možnosti balíčku tablewriter jsou větší, než u standardního balíčku tabwriter, protože je podporován například výstup s využitím barev, orámováním tabulky, tabulka může obsahovat hlavičku a patičku atd. Některé možnosti jsou ukázány v navazujících kapitolách.

Poznámka: dnes si popíšeme pouze základní možnosti tohoto balíčku. Podrobnostmi, například nastavením barev, se budeme zabývat příště.

12. Vykreslení tabulky s hlavičkou i s okraji buněk

V následujícím kódu, který je mimochodem převzat prakticky beze změny přímo ze stránky balíčku tablewriter, je ukázána konstrukce tabulky. Nejprve je nutné zkonstruovat objekt reprezentující tabulku; k tomuto účelu slouží konstruktor NewWriter, jemuž se opět předává libovolná struktura vyhovující rozhraní io.Writer. Následně je možné specifikovat hlavičku tabulky metodou SetHeader, přidat do tabulky jednotlivé řádky metodou Append a na konci tabulku vykreslit metodou Render:

package main
 
import (
        "os"
 
        "github.com/olekukonko/tablewriter"
)
 
func main() {
        data := [][]string{
                []string{"A", "The Good", "500"},
                []string{"B", "The Very very Bad Man", "288"},
                []string{"C", "The Ugly", "120"},
                []string{"D", "The Gopher", "800"},
        }
 
        table := tablewriter.NewWriter(os.Stdout)
        table.SetHeader([]string{"Name", "Sign", "Rating"})
 
        for _, v := range data {
                table.Append(v)
        }
        table.Render()
}

Výsledná tabulka vypadá značně odlišně od předchozích tabulek:

+------+-----------------------+--------+
| NAME |         SIGN          | RATING |
+------+-----------------------+--------+
| A    | The Good              |    500 |
| B    | The Very very Bad Man |    288 |
| C    | The Ugly              |    120 |
| D    | The Gopher            |    800 |
+------+-----------------------+--------+

13. Tabulka bez okrajů

Po konstrukci datové struktury představující tabulku konstruktorem NewWriter je možné modifikovat některé vlastnosti tabulky. Týká se to například zobrazení tabulky bez okrajů – pokud okraje z nějakého důvodu nepotřebujete zobrazit, postačuje zavolat metodu SetBorder a předat jí pravdivostní hodnotu false:

package main
 
import (
        "os"

        "github.com/olekukonko/tablewriter"
)
 
func main() {
        data := [][]string{
                []string{"A", "The Good", "500"},
                []string{"B", "The Very very Bad Man", "288"},
                []string{"C", "The Ugly", "120"},
                []string{"D", "The Gopher", "800"},
        }
 
        table := tablewriter.NewWriter(os.Stdout)
        table.SetHeader([]string{"Name", "Sign", "Rating"})
        table.SetBorder(false)
 
        for _, v := range data {
                table.Append(v)
        }
        table.Render()
}

Výsledná tabulka by měla vypadat následovně:

  NAME |         SIGN          | RATING
-------+-----------------------+---------
  A    | The Good              |    500
  B    | The Very very Bad Man |    288
  C    | The Ugly              |    120
  D    | The Gopher            |    800

14. Tabulka s patičkou (suma hodnot atd.)

Poměrně užitečná je možnost zobrazit tabulku nejenom s hlavičkou, ale i s patičkou. K přidání patičky slouží metoda SetFooter, která akceptuje stejné parametry, jako metody SetHeaderAppend:

package main
 
import (
        "os"

        "github.com/olekukonko/tablewriter"
)
 
func main() {
        data := [][]string{
                []string{"A", "The Good", "500"},
                []string{"B", "The Very very Bad Man", "288"},
                []string{"C", "The Ugly", "120"},
                []string{"D", "The Gopher", "800"},
        }
 
        table := tablewriter.NewWriter(os.Stdout)
        table.SetHeader([]string{"Name", "Sign", "Rating"})
        table.SetFooter([]string{"", "Sum", "1708"})
 
        for _, v := range data {
                table.Append(v)
        }
        table.Render()
}

Tabulka bude zobrazena následujícím způsobem (povšimněte si buňky s chybějící hodnotou v patičce):

+------+-----------------------+--------+
| NAME |         SIGN          | RATING |
+------+-----------------------+--------+
| A    | The Good              |    500 |
| B    | The Very very Bad Man |    288 |
| C    | The Ugly              |    120 |
| D    | The Gopher            |    800 |
+------+-----------------------+--------+
|                 SUM          |  1708  |
+------+-----------------------+--------+

Prakticky stejný příklad, ovšem s tím nepatrným rozdílem, že tabulka nebude obsahovat okraje:

package main
 
import (
        "os"

        "github.com/olekukonko/tablewriter"
)
 
func main() {
        data := [][]string{
                []string{"A", "The Good", "500"},
                []string{"B", "The Very very Bad Man", "288"},
                []string{"C", "The Ugly", "120"},
                []string{"D", "The Gopher", "800"},
        }
 
        table := tablewriter.NewWriter(os.Stdout)
        table.SetHeader([]string{"Name", "Sign", "Rating"})
        table.SetFooter([]string{"", "Sum", "1708"})
        table.SetBorder(false)
 
        for _, v := range data {
                table.Append(v)
        }
        table.Render()
}

Výsledná tabulka vygenerovaná předchozím příkladem:

  NAME |         SIGN          | RATING
-------+-----------------------+---------
  A    | The Good              |    500
  B    | The Very very Bad Man |    288
  C    | The Ugly              |    120
  D    | The Gopher            |    800
-------+-----------------------+---------
                  SUM          |  1708
       ------------------------+---------

15. Zobrazení tabulky s faktoriály hodnot 0 až 20

Opět se pokusme zobrazit tabulku s faktoriály hodnot 0 až 20, tentokrát ovšem s využitím balíčku tablewriter a nikoli tabwriter. Programový kód bude v tomto případě složitější, protože je nutné převádět celočíselné hodnoty na řetězce. Je tomu tak z toho důvodu, že metoda Append akceptuje pole řetězců – viz též dokumentaci:

package main
 
import (
        "os"
        "strconv"

        "github.com/olekukonko/tablewriter"
)
 
// Factorial computes factorial for given n that might be positive integer
func Factorial(n int64) int64 {
        switch {
        case n < 0:
                return 1
        case n == 0:
                return 1
        default:
                return n * Factorial(n-1)
        }
}
 
func main() {
        table := tablewriter.NewWriter(os.Stdout)
        table.SetHeader([]string{"n", "n!"})
 
        for n := 0; n <= 20; n++ {
                f := Factorial(int64(n))
                row := []string{strconv.Itoa(n), strconv.FormatInt(f, 10)}
                table.Append(row)
        }
 
        table.Render()
}

Výsledná tabulka:

+----+---------------------+
| N  |         N!          |
+----+---------------------+
|  0 |                   1 |
|  1 |                   1 |
|  2 |                   2 |
|  3 |                   6 |
|  4 |                  24 |
|  5 |                 120 |
|  6 |                 720 |
|  7 |                5040 |
|  8 |               40320 |
|  9 |              362880 |
| 10 |             3628800 |
| 11 |            39916800 |
| 12 |           479001600 |
| 13 |          6227020800 |
| 14 |         87178291200 |
| 15 |       1307674368000 |
| 16 |      20922789888000 |
| 17 |     355687428096000 |
| 18 |    6402373705728000 |
| 19 |  121645100408832000 |
| 20 | 2432902008176640000 |
+----+---------------------+

16. Výstup do souboru, HTTP server nabízející klientům naformátovanou tabulku

Protože balíček tablewriter dokáže pracovat s každou datovou strukturou vyhovující rozhraní io.Writer, je podporováno i vykreslení tabulky do textového souboru, což je ukázáno v dalším demonstračním příkladu. Ten po svém spuštění vytvoří soubor „table2.txt“ a zapíše do něj příslušnou naformátovanou tabulku s hodnotami faktoriálů:

package main
 
import (
        "log"
        "os"
        "strconv"
 
        "github.com/olekukonko/tablewriter"
)
 
// Factorial computes factorial for given n that might be positive integer
func Factorial(n int64) int64 {
        switch {
        case n < 0:
                return 1
        case n == 0:
                return 1
        default:
                return n * Factorial(n-1)
        }
}
 
func main() {
        file, err := os.Create("table2.txt")
        if err != nil {
                log.Fatal(err)
        }
 
        defer file.Close()
 
        table := tablewriter.NewWriter(file)
        table.SetHeader([]string{"n", "n!"})
 
        for n := 0; n <= 20; n++ {
                f := Factorial(int64(n))
                row := []string{strconv.Itoa(n), strconv.FormatInt(f, 10)}
                table.Append(row)
        }
 
        table.Render()
}
+----+---------------------+
| N  |         N!          |
+----+---------------------+
|  0 |                   1 |
|  1 |                   1 |
|  2 |                   2 |
|  3 |                   6 |
|  4 |                  24 |
|  5 |                 120 |
|  6 |                 720 |
|  7 |                5040 |
|  8 |               40320 |
|  9 |              362880 |
| 10 |             3628800 |
| 11 |            39916800 |
| 12 |           479001600 |
| 13 |          6227020800 |
| 14 |         87178291200 |
| 15 |       1307674368000 |
| 16 |      20922789888000 |
| 17 |     355687428096000 |
| 18 |    6402373705728000 |
| 19 |  121645100408832000 |
| 20 | 2432902008176640000 |
+----+---------------------+

Vzhledem k tomu, že balíček tablewriter dokáže tabulku vypsat do libovolné struktury vyhovující rozhraní io.Writer, je možné (opět) relativně snadno implementovat HTTP server, jehož obslužný kód endpointu takovou tabulku vytvoří a pošle zpět klientovi. Jedná se tedy o kombinaci příkladu z předchozí kapitoly z příkladem z kapitoly desáté:

package main
 
import (
        "net/http"
        "strconv"
 
        "github.com/olekukonko/tablewriter"
)
 
// Factorial computes factorial for given n that might be positive integer
func Factorial(n int64) int64 {
        switch {
        case n < 0:
                return 1
        case n == 0:
                return 1
        default:
                return n * Factorial(n-1)
        }
}
 
func mainEndpoint(writer http.ResponseWriter, request *http.Request) {
        table := tablewriter.NewWriter(writer)
        table.SetHeader([]string{"n", "n!"})
 
        for n := 0; n <= 20; n++ {
                f := Factorial(int64(n))
                row := []string{strconv.Itoa(n), strconv.FormatInt(f, 10)}
                table.Append(row)
        }
 
        table.Render()
}
 
func main() {
        http.HandleFunc("/", mainEndpoint)
        http.ListenAndServe(":8000", nil)
}

Po spuštění HTTP serveru přistoupíme k (jedinému) koncovému bodu a necháme si serverem poslat naformátovanou tabulku:

$ curl localhost:8000/
 
+----+---------------------+
| N  |         N!          |
+----+---------------------+
|  0 |                   1 |
|  1 |                   1 |
|  2 |                   2 |
|  3 |                   6 |
|  4 |                  24 |
|  5 |                 120 |
|  6 |                 720 |
|  7 |                5040 |
|  8 |               40320 |
|  9 |              362880 |
| 10 |             3628800 |
| 11 |            39916800 |
| 12 |           479001600 |
| 13 |          6227020800 |
| 14 |         87178291200 |
| 15 |       1307674368000 |
| 16 |      20922789888000 |
| 17 |     355687428096000 |
| 18 |    6402373705728000 |
| 19 |  121645100408832000 |
| 20 | 2432902008176640000 |
+----+---------------------+

17. Balíček lensesio/tableprinter

Poslední, v pořadí již třetí balíček, o němž se v dnešním článku zmíníme, se jmenuje tableprinter, resp. celým jménem lensesio/tableprinter. Tento balíček je založen na balíčku tablewriter, ovšem rozšiřuje jeho možnosti, a to hned několika způsoby. Dnes se seznámíme jen se základními možnostmi nabízenými tímto balíčkem; případné podrobnosti (barevný výstup atd.) budou vysvětleny v navazujícím článku.

Opět se jedná o nestandardní balíček, takže je nutná jeho explicitní instalace:

$ go get -u github.com/lensesio/tableprinter

Od této chvíle je možné balíček používat v jakémkoli projektu naprogramovaného v jazyce Go.

Struktura dat, která se mají vykreslit v tabulce, je definována novým datovým typem, v němž lze u jednotlivých položek specifikovat i názvy sloupců (podobně se ostatně pracuje se soubory TOML, JSON atd.):

type Record struct {
        Rank  string `header:"rank"`
        Title string `header:"title"`
        Value int    `header:"value"`
}

Samotné vytvoření a vykreslení tabulky je jednoduché – nejprve se vytvoří instance objektu představujícího tabulku a poté se metodou Print tomuto objektu předají data, která se mají vykreslit:

table := tableprinter.New(os.Stdout)
 
table.Print(data)

18. Ukázky použití balíčku lensesio/tableprinter

V této kapitole si – prozatím ovšem pouze ve stručnosti – ukážeme některé možnosti, které nám nabízí balíček tableprinter při tisku tabulek. První demonstrační příklad je nejjednodušší, protože vykreslí tabulku výchozím stylem bez dalších úprav:

package main
 
import (
        "os"
 
        "github.com/lensesio/tableprinter"
)
 
type Record struct {
        Rank  string `header:"rank"`
        Title string `header:"title"`
        Value int    `header:"value"`
}
 
func main() {
        data := []Record{
                {"A", "The Good", 500},
                {"B", "The Very very Bad Man", 288},
                {"C", "The Ugly", 120},
                {"D", "The Gopher", 800},
        }
 
        table := tableprinter.New(os.Stdout)
 
        table.Print(data)
}

Výsledkem bude tato tabulka:

  RANK (4)    TITLE                   VALUE
 ----------- ----------------------- -------
  A           The Good                  500
  B           The Very very Bad Man     288
  C           The Ugly                  120
  D           The Gopher                800

Můžeme ovšem taktéž explicitně specifikovat styl okrajů tabulky:

package main
 
import (
        "os"
 
        "github.com/lensesio/tableprinter"
)
 
type Record struct {
        Rank  string `header:"rank"`
        Title string `header:"title"`
        Value int    `header:"value"`
}
 
func main() {
        data := []Record{
                {"A", "The Good", 500},
                {"B", "The Very very Bad Man", 288},
                {"C", "The Ugly", 120},
                {"D", "The Gopher", 800},
        }
 
        table := tableprinter.New(os.Stdout)
        table.BorderTop, table.BorderBottom, table.BorderLeft, table.BorderRight = true, true, true, true
 
        table.Print(data)
}

S výsledkem:

 ----------- ----------------------- -------
  RANK (4)    TITLE                   VALUE
 ----------- ----------------------- -------
  A           The Good                  500
  B           The Very very Bad Man     288
  C           The Ugly                  120
  D           The Gopher                800
 ----------- ----------------------- -------

Určit je možné i znaky použité pro tisk okrajů. Podporováno je celé Unicode, samozřejmě pouze v případě použití fontu se všemi potřebnými znaky:

package main
 
import (
        "os"
 
        "github.com/lensesio/tableprinter"
)
 
type Record struct {
        Rank  string `header:"rank"`
        Title string `header:"title"`
        Value int    `header:"value"`
}
 
func main() {
        data := []Record{
                {"A", "The Good", 500},
                {"B", "The Very very Bad Man", 288},
                {"C", "The Ugly", 120},
                {"D", "The Gopher", 800},
        }
 
        table := tableprinter.New(os.Stdout)
        table.BorderTop, table.BorderBottom, table.BorderLeft, table.BorderRight = true, true, true, true
        table.CenterSeparator = "│"
        table.ColumnSeparator = "│"
        table.RowSeparator = "─"
 
        table.Print(data)
}

S výsledkem:

│───────────│───────────────────────│───────│
│ RANK (4)  │ TITLE                 │ VALUE │
│───────────│───────────────────────│───────│
│ A         │ The Good              │   500 │
│ B         │ The Very very Bad Man │   288 │
│ C         │ The Ugly              │   120 │
│ D         │ The Gopher            │   800 │
│───────────│───────────────────────│───────│

Tisk tabulky s faktoriály:

package main
 
import (
        "os"
 
        "github.com/lensesio/tableprinter"
)
 
type Record struct {
        N int   `header:"n"`
        F int64 `header:"n!"`
}
 
// Factorial computes factorial for given n that might be positive integer
func Factorial(n int64) int64 {
        switch {
        case n < 0:
                return 1
        case n == 0:
                return 1
        default:
                return n * Factorial(n-1)
        }
}
 
const MaxN = 17
 
func main() {
        data := make([]Record, 0)
 
        for n := 0; n <= MaxN; n++ {
                data = append(data, Record{n, Factorial(int64(n))})
        }
 
        table := tableprinter.New(os.Stdout)
        table.BorderTop, table.BorderBottom, table.BorderLeft, table.BorderRight = true, true, true, true
        table.CenterSeparator = "│"
        table.ColumnSeparator = "│"
        table.RowSeparator = "─"
 
        table.Print(data)
}

Povšimněte si, jak se velké hodnoty implicitně zkracují (což nemusí být vždy výhodné):

│─────────│────────│
│ N (18)  │ N!     │
│─────────│────────│
│       0 │      1 │
│       1 │      1 │
│       2 │      2 │
│       3 │      6 │
│       4 │     24 │
│       5 │    120 │
│       6 │    720 │
│       7 │   5.4K │
│       8 │  40.3K │
│       9 │ 362.8K │
│      10 │   3.6M │
│      11 │  39.9M │
│      12 │ 479.1M │
│      13 │   6.2B │
│      14 │  87.1B │
│      15 │   1.3T │
│      16 │  20.9T │
│      17 │ 355.6T │
│─────────│────────│

Výstup do souboru:

package main
 
import (
        "log"
        "os"
 
        "github.com/lensesio/tableprinter"
)
 
type Record struct {
        N int   `header:"n"`
        F int64 `header:"n!"`
}
 
// Factorial computes factorial for given n that might be positive integer
func Factorial(n int64) int64 {
        switch {
        case n < 0:
                return 1
        case n == 0:
                return 1
        default:
                return n * Factorial(n-1)
        }
}
 
const MaxN = 17
 
func main() {
        file, err := os.Create("table3.txt")
        if err != nil {
                log.Fatal(err)
        }
 
        defer file.Close()
 
        data := make([]Record, 0)
 
        for n := 0; n <= MaxN; n++ {
                data = append(data, Record{n, Factorial(int64(n))})
        }
 
        table := tableprinter.New(file)
        table.BorderTop, table.BorderBottom, table.BorderLeft, table.BorderRight = true, true, true, true
        table.CenterSeparator = "│"
        table.ColumnSeparator = "│"
        table.RowSeparator = "─"
 
        table.Print(data)
}

Výsledkem bude soubor „table3.txt“ s tímto obsahem:

│─────────│────────│
│ N (18)  │ N!     │
│─────────│────────│
│       0 │      1 │
│       1 │      1 │
│       2 │      2 │
│       3 │      6 │
│       4 │     24 │
│       5 │    120 │
│       6 │    720 │
│       7 │   5.4K │
│       8 │  40.3K │
│       9 │ 362.8K │
│      10 │   3.6M │
│      11 │  39.9M │
│      12 │ 479.1M │
│      13 │   6.2B │
│      14 │  87.1B │
│      15 │   1.3T │
│      16 │  20.9T │
│      17 │ 355.6T │
│─────────│────────│

A konečně obdoba HTTP serveru z předchozích kapitol:

package main
 
import (
        "net/http"
 
        "github.com/lensesio/tableprinter"
)
 
type Record struct {
        N int   `header:"n"`
        F int64 `header:"n!"`
}
 
// Factorial computes factorial for given n that might be positive integer
func Factorial(n int64) int64 {
        switch {
        case n < 0:
                return 1
        case n == 0:
                return 1
        default:
                return n * Factorial(n-1)
        }
}
 
const MaxN = 17
 
func mainEndpoint(writer http.ResponseWriter, request *http.Request) {
        data := make([]Record, 0)
 
        for n := 0; n <= MaxN; n++ {
                data = append(data, Record{n, Factorial(int64(n))})
        }
 
        table := tableprinter.New(writer)
        table.BorderTop, table.BorderBottom, table.BorderLeft, table.BorderRight = true, true, true, true
        table.CenterSeparator = "│"
        table.ColumnSeparator = "│"
        table.RowSeparator = "─"
 
        table.Print(data)
}
 
func main() {
        http.HandleFunc("/", mainEndpoint)
        http.ListenAndServe(":8000", nil)
}

19. Repositář s demonstračními příklady

Zdrojové kódy všech dnes použitých demonstračních příkladů byly uloženy do nového Git repositáře, který je dostupný na adrese https://github.com/tisnik/go-root (stále na GitHubu :-). V případě, že nebudete chtít klonovat celý repositář (ten je ovšem – alespoň prozatím – velmi malý, dnes má přibližně stovku kilobajtů), můžete namísto toho použít odkazy na jednotlivé demonstrační příklady, které naleznete v následující tabulce:

# Příklad Stručný popis Cesta
1 01-tabwriter-basic-usage.go vykreslení dvou jednoduchých tabulek s využitím tabwriter https://github.com/tisnik/go-root/blob/master/article70/01-tabwriter-basic-usage.go
2 02-tabwriter-different-column-widths.go chování pro záznamy s odlišnou šířkou https://github.com/tisnik/go-root/blob/master/article70/02-tabwriter-different-column-widths.go
3 03-tabwriter-min-width.go změna minimální šířky sloupců https://github.com/tisnik/go-root/blob/master/article70/03-tabwriter-min-width.go
4 04-tabwriter-padding.go změna minimálního počtu výplňových znaků https://github.com/tisnik/go-root/blob/master/article70/04-tabwriter-padding.go
5 05-tabwriter-padding-character.go změna výplňového znaku https://github.com/tisnik/go-root/blob/master/article70/05-tabwriter-padding-character.go
6 06-tabwriter-align-right.go zarovnání sloupců doprava https://github.com/tisnik/go-root/blob/master/article70/06-tabwriter-align-right.go
7 07-tabwriter-align-right-debug.go zarovnání sloupců doprava + výstup s oddělovači mezi sloupci https://github.com/tisnik/go-root/blob/master/article70/07-tabwriter-align-right-debug.go
8 08-tabwriter-align-right-trailing-tab.go korektní použití „trailing tab“ https://github.com/tisnik/go-root/blob/master/article70/08-tabwriter-align-right-trailing-tab.go
9 09-tabwriter-align-right-trailing-tab-debug.go korektní použití „trailing tab“ https://github.com/tisnik/go-root/blob/master/article70/09-tabwriter-align-right-trailing-tab-debug.go
10 10-tabwriter-factorial.go zobrazení tabulky s faktoriály hodnot 0 až 20 https://github.com/tisnik/go-root/blob/master/article70/10-tabwriter-factorial.go
11 11-tabwriter-factorial-multiprint.go postupné přidávání hodnot do výsledné tabulky funkcí fmt.Fprintf https://github.com/tisnik/go-root/blob/master/article70/11-tabwriter-factorial-multiprint.go
12 12-tabwriter-write-to-file.go zápis tabulky do souboru https://github.com/tisnik/go-root/blob/master/article70/12-tablewriter-basic-usage.go
13 13-tabwriter-http-server.go HTTP server, který nabízí klientům naformátovanou tabulku https://github.com/tisnik/go-root/blob/master/article70/13-tabwriter-http-server.go
       
14 14-tablewriter-basic-usage.go základní použití balíčku tablewriter https://github.com/tisnik/go-root/blob/master/article70/14-tablewriter-basic-usage.go
15 15-tablewriter-no-border.go zobrazení tabulky bez okrajů https://github.com/tisnik/go-root/blob/master/article70/15-tablewriter-no-border.go
16 16-tablewriter-set-footer.go tabulka s patičkou https://github.com/tisnik/go-root/blob/master/article70/16-tablewriter-set-footer.go
17 17-tablewriter-set-footer-no-border.go kombinace předchozích dvou příkladů https://github.com/tisnik/go-root/blob/master/article70/17-tablewriter-set-footer-no-border.go
18 18-tablewriter-factorial.go zobrazení tabulky s faktoriály hodnot 0 až 20 https://github.com/tisnik/go-root/blob/master/article70/18-tablewriter-factorial.go
19 19-tablewriter-write-to-file.go zápis tabulky do souboru https://github.com/tisnik/go-root/blob/master/article70/19-tablewriter-write-to-file.go
20 20-tablewriter-http-server.go HTTP server, který nabízí klientům naformátovanou tabulku https://github.com/tisnik/go-root/blob/master/article70/20-tablewriter-http-server.go
       
21 21-tableprinter-basic-usage.go základní použití balíčku tableprinter https://github.com/tisnik/go-root/blob/master/article70/21-tableprinter-basic-usage.go
22 22-tableprinter-borders.go nastavení okrajů tabulky https://github.com/tisnik/go-root/blob/master/article70/22-tableprinter-borders.go
23 23-tableprinter-borders.go nastavení okrajů tabulky https://github.com/tisnik/go-root/blob/master/article70/23-tableprinter-borders.go
24 24-tableprinter-factorial.go zobrazení tabulky s faktoriály hodnot 0 až 20 https://github.com/tisnik/go-root/blob/master/article70/24-tableprinter-factorial.go
25 25-tableprinter-write-to-file.go zápis tabulky do souboru https://github.com/tisnik/go-root/blob/master/article70/25-tableprinter-write-to-file.go
26 26-tableprinter-http-server.go HTTP server, který nabízí klientům naformátovanou tabulku https://github.com/tisnik/go-root/blob/master/article70/26-tableprinter-http-server.go

20. Odkazy na Internetu

  1. Standardní balíček text/tabwriter
    https://golang.org/pkg/tex­t/tabwriter/
  2. Elastic tabstops: A better way to indent and align code
    https://nickgravgaard.com/elastic-tabstops/
  3. ASCII Table Writer
    https://github.com/olekukon­ko/tablewriter
  4. TablePrinter
    https://github.com/lensesi­o/tableprinter
  5. go-pretty
    https://github.com/jedib0t/go-pretty
  6. What are the drawbacks of elastic tabstops?
    https://softwareengineerin­g.stackexchange.com/questi­ons/137290/what-are-the-drawbacks-of-elastic-tabstops
  7. Elastic tabstop editors and plugins
    https://stackoverflow.com/qu­estions/28652/elastic-tabstop-editors-and-plugins
  8. Příkaz gofmt
    https://golang.org/cmd/gofmt/
  9. Spaces vs. Tabs: A 20-Year Debate Reignited by Google’s Golang
    https://thenewstack.io/spaces-vs-tabs-a-20-year-debate-and-now-this-what-the-hell-is-wrong-with-go/