franta-hg@32: /** franta-hg@32: * SQL-DK franta-hg@32: * Copyright © 2013 František Kučera (frantovo.cz) franta-hg@32: * franta-hg@32: * This program is free software: you can redistribute it and/or modify franta-hg@32: * it under the terms of the GNU General Public License as published by franta-hg@32: * the Free Software Foundation, either version 3 of the License, or franta-hg@32: * (at your option) any later version. franta-hg@32: * franta-hg@32: * This program is distributed in the hope that it will be useful, franta-hg@32: * but WITHOUT ANY WARRANTY; without even the implied warranty of franta-hg@32: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the franta-hg@32: * GNU General Public License for more details. franta-hg@32: * franta-hg@32: * You should have received a copy of the GNU General Public License franta-hg@32: * along with this program. If not, see . franta-hg@32: */ franta-hg@32: package info.globalcode.sql.dk.formatting; franta-hg@32: franta-hg@34: import info.globalcode.sql.dk.ColorfulPrintWriter; franta-hg@37: import static info.globalcode.sql.dk.ColorfulPrintWriter.*; franta-hg@39: import static info.globalcode.sql.dk.Functions.lpad; franta-hg@39: import static info.globalcode.sql.dk.Functions.rpad; franta-hg@40: import static info.globalcode.sql.dk.Functions.repeat; franta-hg@88: import java.util.List; franta-hg@34: franta-hg@32: /** franta-hg@32: * franta-hg@32: * @author Ing. František Kučera (frantovo.cz) franta-hg@32: */ franta-hg@32: public class TabularFormatter extends AbstractFormatter { franta-hg@32: franta-hg@79: public static final String NAME = "tabular"; // bash-completion:formatter franta-hg@39: private static final String HEADER_TYPE_PREFIX = " ("; franta-hg@39: private static final String HEADER_TYPE_SUFFIX = ")"; franta-hg@34: private ColorfulPrintWriter out; franta-hg@37: private boolean firstResult = true; franta-hg@39: private int[] columnWidth; franta-hg@87: /** franta-hg@87: * use ASCII borders instead of unicode ones franta-hg@87: */ franta-hg@76: private final boolean asciiNostalgia = false; franta-hg@87: /** franta-hg@87: * Trim values if they are longer than cell size franta-hg@87: */ franta-hg@87: private final boolean trimValues = false; franta-hg@32: franta-hg@32: public TabularFormatter(FormatterContext formatterContext) { franta-hg@32: super(formatterContext); franta-hg@34: out = new ColorfulPrintWriter(formatterContext.getOutputStream()); franta-hg@34: } franta-hg@34: franta-hg@34: @Override franta-hg@37: public void writeStartResultSet() { franta-hg@37: super.writeStartResultSet(); franta-hg@37: printResultSeparator(); franta-hg@37: } franta-hg@37: franta-hg@37: @Override franta-hg@37: public void writeColumnsHeader(ColumnsHeader header) { franta-hg@37: super.writeColumnsHeader(header); franta-hg@37: franta-hg@88: initColumnWidths(header.getColumnCount()); franta-hg@39: franta-hg@40: printTableIndent(); franta-hg@40: printTableBorder("╭"); franta-hg@88: franta-hg@88: List columnDescriptors = header.getColumnDescriptors(); franta-hg@88: franta-hg@88: for (ColumnDescriptor cd : columnDescriptors) { franta-hg@88: // padding: make header cell at least same width as data cells in this column franta-hg@88: int typeWidth = cd.getTypeName().length() + HEADER_TYPE_PREFIX.length() + HEADER_TYPE_SUFFIX.length(); franta-hg@88: cd.setLabel(rpad(cd.getLabel(), getColumnWidth(cd.getColumnNumber()) - typeWidth)); franta-hg@88: updateColumnWidth(cd.getColumnNumber(), cd.getLabel().length() + typeWidth); franta-hg@88: franta-hg@40: if (!cd.isFirstColumn()) { franta-hg@40: printTableBorder("┬"); franta-hg@40: } franta-hg@40: printTableBorder(repeat('─', getColumnWidth(cd.getColumnNumber()) + 2)); franta-hg@40: } franta-hg@40: printTableBorder("╮"); franta-hg@40: out.println(); franta-hg@40: franta-hg@88: for (ColumnDescriptor cd : columnDescriptors) { franta-hg@40: if (cd.isFirstColumn()) { franta-hg@40: printTableIndent(); franta-hg@40: printTableBorder("│ "); franta-hg@40: } else { franta-hg@40: printTableBorder(" │ "); franta-hg@40: } franta-hg@37: out.print(TerminalStyle.Bright, cd.getLabel()); franta-hg@39: out.print(HEADER_TYPE_PREFIX); franta-hg@37: out.print(cd.getTypeName()); franta-hg@39: out.print(HEADER_TYPE_SUFFIX); franta-hg@40: if (cd.isLastColumn()) { franta-hg@40: printTableBorder(" │"); franta-hg@37: } franta-hg@37: } franta-hg@37: out.println(); franta-hg@40: franta-hg@40: printTableIndent(); franta-hg@40: printTableBorder("├"); franta-hg@40: for (int i = 1; i <= header.getColumnCount(); i++) { franta-hg@40: if (i > 1) { franta-hg@40: printTableBorder("┼"); franta-hg@40: } franta-hg@40: printTableBorder(repeat('─', getColumnWidth(i) + 2)); franta-hg@40: } franta-hg@40: printTableBorder("┤"); franta-hg@40: out.println(); franta-hg@40: franta-hg@37: out.flush(); franta-hg@37: } franta-hg@37: franta-hg@88: /** franta-hg@88: * Must be called before franta-hg@88: * {@linkplain #updateColumnWidth(int, int)} franta-hg@88: * and {@linkplain #getColumnWidth(int)} franta-hg@88: * for each result set. franta-hg@88: * franta-hg@88: * @param columnCount number of columns in current result set franta-hg@88: */ franta-hg@88: protected void initColumnWidths(int columnCount) { franta-hg@88: if (columnWidth == null) { franta-hg@88: columnWidth = new int[columnCount]; franta-hg@88: } franta-hg@88: } franta-hg@88: franta-hg@88: protected void cleanColumnWidths() { franta-hg@88: columnWidth = null; franta-hg@88: } franta-hg@88: franta-hg@37: @Override franta-hg@34: public void writeColumnValue(Object value) { franta-hg@34: super.writeColumnValue(value); franta-hg@34: franta-hg@40: if (isCurrentColumnFirst()) { franta-hg@40: printTableIndent(); franta-hg@40: printTableBorder("│ "); franta-hg@40: } else { franta-hg@90: printTableBorder(" │ "); franta-hg@34: } franta-hg@37: franta-hg@103: String[] valueParts = toString(value).split("\n"); franta-hg@103: for (int i = 0; i < valueParts.length; i++) { franta-hg@103: String valuePart = valueParts[i]; franta-hg@103: out.print(TerminalColor.Cyan, valuePart); franta-hg@103: if (i < valueParts.length - 1) { franta-hg@103: out.print(TerminalColor.Red, "↲"); franta-hg@103: } franta-hg@103: } franta-hg@40: franta-hg@40: if (isCurrentColumnLast()) { franta-hg@90: printTableBorder(" │"); franta-hg@40: } franta-hg@40: franta-hg@39: } franta-hg@39: franta-hg@39: private int getColumnWidth(int columnNumber) { franta-hg@39: return columnWidth[columnNumber - 1]; franta-hg@39: } franta-hg@39: franta-hg@39: private void setColumnWidth(int columnNumber, int width) { franta-hg@39: columnWidth[columnNumber - 1] = width; franta-hg@39: } franta-hg@39: franta-hg@88: protected void updateColumnWidth(int columnNumber, int width) { franta-hg@39: int oldWidth = getColumnWidth(columnNumber); franta-hg@39: setColumnWidth(columnNumber, Math.max(width, oldWidth)); franta-hg@39: franta-hg@39: } franta-hg@39: franta-hg@39: protected String toString(Object value) { franta-hg@39: final int width = getColumnWidth(getCurrentColumnsCount()); franta-hg@87: String result; franta-hg@43: if (value instanceof Number || value instanceof Boolean) { franta-hg@98: result = lpad(String.valueOf(value), width); franta-hg@39: } else { franta-hg@98: result = rpad(String.valueOf(value), width); franta-hg@39: } franta-hg@40: // ? value = (boolean) value ? "✔" : "✗"; franta-hg@87: franta-hg@87: if (trimValues && result.length() > width) { franta-hg@87: result = result.substring(0, width - 1) + "…"; franta-hg@87: } franta-hg@87: franta-hg@87: return result; franta-hg@34: } franta-hg@34: franta-hg@34: @Override franta-hg@34: public void writeEndRow() { franta-hg@34: super.writeEndRow(); franta-hg@34: out.println(); franta-hg@34: out.flush(); franta-hg@32: } franta-hg@37: franta-hg@37: @Override franta-hg@37: public void writeEndResultSet() { franta-hg@40: int columnCount = getCurrentColumnsHeader().getColumnCount(); franta-hg@37: super.writeEndResultSet(); franta-hg@40: franta-hg@40: printTableIndent(); franta-hg@40: printTableBorder("╰"); franta-hg@40: for (int i = 1; i <= columnCount; i++) { franta-hg@40: if (i > 1) { franta-hg@40: printTableBorder("┴"); franta-hg@40: } franta-hg@40: printTableBorder(repeat('─', getColumnWidth(i) + 2)); franta-hg@40: } franta-hg@40: printTableBorder("╯"); franta-hg@40: out.println(); franta-hg@40: franta-hg@88: cleanColumnWidths(); franta-hg@40: franta-hg@37: out.print(TerminalColor.Yellow, "Record count: "); franta-hg@37: out.println(getCurrentRowCount()); franta-hg@40: out.bell(); franta-hg@37: out.flush(); franta-hg@37: } franta-hg@37: franta-hg@37: @Override franta-hg@37: public void writeStartUpdatesResult() { franta-hg@37: super.writeStartUpdatesResult(); franta-hg@37: printResultSeparator(); franta-hg@37: } franta-hg@37: franta-hg@37: @Override franta-hg@37: public void writeUpdatedRowsCount(int updatedRowsCount) { franta-hg@37: super.writeUpdatedRowsCount(updatedRowsCount); franta-hg@37: out.print(TerminalColor.Red, "Updated records: "); franta-hg@37: out.println(updatedRowsCount); franta-hg@40: out.bell(); franta-hg@37: out.flush(); franta-hg@37: } franta-hg@37: franta-hg@37: @Override franta-hg@37: public void writeEndDatabase() { franta-hg@37: super.writeEndDatabase(); franta-hg@37: out.flush(); franta-hg@37: } franta-hg@37: franta-hg@37: private void printResultSeparator() { franta-hg@37: if (firstResult) { franta-hg@37: firstResult = false; franta-hg@37: } else { franta-hg@37: out.println(); franta-hg@37: } franta-hg@37: } franta-hg@40: franta-hg@40: private void printTableBorder(String border) { franta-hg@76: if (asciiNostalgia) { franta-hg@76: border = border.replaceAll("─", "-"); franta-hg@76: border = border.replaceAll("│", "|"); franta-hg@76: border = border.replaceAll("[╭┬╮├┼┤╰┴╯]", "+"); franta-hg@76: } franta-hg@87: franta-hg@40: out.print(TerminalColor.Green, border); franta-hg@40: } franta-hg@40: franta-hg@40: private void printTableIndent() { franta-hg@40: out.print(" "); franta-hg@40: } franta-hg@32: }