Printing Tabular Data

Sometimes when I'm running tests, or pulling information from a database, I want to see the results in table so that it's easy to parse through the information and see what's there. For instance, let's say that I wanted to see the following information pulled from a bank application.

Account    | Owner           | Balance |
2131545422 | John Smith      | 10.00   |
23409834   | Alexander Banks | 230.03  |
987545453  | Smithy Joe      | 150.22  |
3445       | Katie Parks     | 3021.53 |

So, I write a little groovy script to query the database and for each result that's pulled back, I add the mapped data to the printer.

def printer = new TablePrinter()
new Sql(.....).eachRow(....) {
  printer << ['Account#': it.'accountNum',
              'Owner': it.'userId',
              'Balance': it.'balance']
}
printer.printTable()

The keys from the map are used as the title bar and the data is placed into the table. The TablePrinter groovy class will find the largest value from each column and make that the width when printing each record to give it the nice formatting. Here's the source for the TablePrinter.

class TablePrinter {

  def out = System.out
  Collection rowCalcs = []
  Collection<Collection> rows = []

  /**
   * Cycles through the list of entries in the table to assess the largest
   * value for each'column', then prints all the results in a table.
   *
   * @param rows the data to display
   */
  static void printTable(Collection<Collection> pRows) {
    if (pRows) {
      def printer = new TablePrinter()
      pRows.each { printer << it }
      printer.printTable()
    }
  }

  /**
   * Adds a map entry to the data being printed.
   * @see #plus(Collection)
   */
  TablePrinter leftShift(Collection pRow) {
    return plus(pRow)
  }

  /**
   * Adds a map entry to the data being printed.
   * @see #plus(Map)
   */
  TablePrinter leftShift(Map pRow) {
    return plus(pRow)
  }

  /**
   * Adds a new row to the values that will be printed and recalculates column
   * sizes. If no row calculations have yet been added, then key set from the
   * map will be assumed to the title row of the table and added to the rows
   * that will be printed.
   *
   * @param pRow the data being added to the table
   * @return reference to this updated object for chaining
   */
  TablePrinter plus(Map pRow) {
    if (rowCalcs.size() == 0) {
      this.plus(pRow.keySet())
    }
    return this.plus(pRow.values())
  }

  /**
   * Adds a new row to the values that will be printed and recalculates
   * column sizes.
   * @param pRow the data being added to the table
   * @return reference to this updated object for chaining
   */
  TablePrinter plus(Collection pRow) {
    rows << pRow
    calculateColumnSize(pRow)
    return this
  }

  /**
   * Creates a format string (for example: "%-10s | %-6s") using column
   * sizes calculated earlier and prints the values from each row in
   * tabular format.
   */
  void printTable() {
    def formatString = rowCalcs.sum { "%-${it}s | " }
    rows.each { row ->
      out.println sprintf(formatString, row.toList())
    }
  }

  /**
   * Adjusts the size of each 'column' by comparing previous calculations
   * against the size of each value in this row. The larger value is stored
   * in the {@link #rowCalcs} variable to be used later when printing.
   *
   * @param row values being added to the print display that need to be
   * sized to possibly adjust the size of each column
   */
  void calculateColumnSize(Collection row) {
    row.eachWithIndex  { cell, cellNum ->
      //ensure the storage for the row size calculations is large enough
      while (rowCalcs.size() < cellNum + 1) {
        rowCalcs << 0
      }

      //default null into an empty string, checks explicitly for null to
      // prevent 0 -> ''.
      if (row[cellNum] == null) {
        row[cellNum] = ''
      }

      //store the larger of either the previous calculation or the length of
      //current value
      rowCalcs[cellNum] = Math.max(rowCalcs[cellNum],
                                   String.valueOf(row[cellNum]).length())
    }
  }

}

Post a Comment