April 6, 2020

Using the tabwriter package

Let’s say you’re writing output to the command line in a tabular format, and you want the columns to align nicely. The tabwriter package does a nice job acheiving this task.

Let’s say you’re writing output like so:

package main

import (
	"fmt"
)

type hotel struct {
	name string
	rate int
}

func (h hotel) String() string {
	return fmt.Sprintf("%s $%d", h.name, h.rate)
}

var (
	hotels = []hotel{
		hotel{"Loews", 100},
		hotel{"Swiss", 120},
		hotel{"Radisson", 110},
		hotel{"Grand Sheridan", 200},
		hotel{"Continental", 120},
	}
)

func main() {
	for _, hotel := range hotels {
		fmt.Println(hotel)
	}
}

You get

Loews $100
Swiss $120
Radisson $110
Grand Sheridan $200
Continental $120

which looks a bit messy since the length of the hotel name isn’t aligned. To fix this, let’s use tabwriter.Writer and initialize it to write to os.Stdout

var w tabwriter.Writer
w.Init(os.Stdout, 16, 2, 0, ' ', 0)
defer flush.Flush()

This gives us a writer configured to write its output to the command line. The minimum width of columns is set to 16, padded by 2 space ' ' characters. The two zero parameters are for additional padding and a format control flag, which we’ll not use here.

To use this, we’ll want to use tabs in our Stringer implementation

func (h hotel) String() string {
  return fmt.Sprintf("%s\t$%d", h.name, h.rate)
}

and create a printer function that accepts a writer as a dependency

func Fprint(w io.Writer, hotels ...hotel) (n int, err error) {
  for _, hotel := range hotels {
    m, err = fmt.Fprintf(w, "%s\n", hotel)
    n += m
    if err != nil {
      return
    }
  }
}

Our main function can then be refactored to simply configure the tabwriter and use the Fprint function. The full program then looks like this:

package main

import (
	"fmt"
	"io"
	"os"
	"text/tabwriter"
)

type hotel struct {
	name string
	rate int
}

func (h hotel) String() string {
	return fmt.Sprintf("%s\t$%d", h.name, h.rate)
}

func Fprint(w io.Writer, hotels ...hotel) (n int, err error) {
	for _, hotel := range hotels {
		var m int
		m, err = fmt.Fprintf(w, "%s\n", hotel)
		n += m
		if err != nil {
			return
		}
	}
	return
}

var (
	hotels = []hotel{
		hotel{"Loews", 100},
		hotel{"Swiss", 120},
		hotel{"Radisson", 110},
		hotel{"Grand Sheridan", 200},
		hotel{"Continental", 120},
	}
)

func main() {
	var w tabwriter.Writer
	w.Init(os.Stdout, 16, 2, 0, ' ', 0)
	defer w.Flush()

	Fprint(&w, hotels...)
}
Loews           $100
Swiss           $120
Radisson        $110
Grand Sheridan  $200
Continental     $120

Try it out on the Go Playground

Content by © Jared Davis 2019-2020

Powered by Hugo & Kiss.