lib/archeometer/repo/result.ex

defmodule Archeometer.Repo.Result do
  @moduledoc """
  A query result with a nicer API to manipulate the data.
  """
  defstruct [:headers, :rows]

  defimpl String.Chars do
    @vsep " |"
    @hsep "-"
    @vprefix " |"

    def to_string(%Archeometer.Repo.Result{headers: headers, rows: rows}) do
      widths = widths([headers | rows])
      hsep = Enum.map(widths, fn w -> String.duplicate(@hsep, w) end)

      [headers, hsep | rows]
      |> Enum.map_join(&(@vprefix <> format_row(&1, widths) <> "\n"))
    end

    defp format_row(row, widths) do
      Enum.zip(row, widths)
      |> Enum.map_join(fn {r, w} ->
        r
        |> Kernel.to_string()
        |> String.pad_trailing(w)
        |> Kernel.<>(@vsep)
      end)
    end

    defp widths(rows) do
      rows
      |> Enum.zip()
      |> Enum.map(fn tpl ->
        tpl
        |> Tuple.to_list()
        |> Enum.map(&(Kernel.to_string(&1) |> String.length()))
        |> Enum.max()
      end)
    end
  end
end