# HG changeset patch # User František Kučera # Date 1707059437 -3600 # Node ID 099bb96f8d8d9dfb648b5f87e39feef739100893 # Parent c4b901ff07038c151326c75e8e8d97b52aacdc5c tabular formatter: new option 'separateBy' to print horizontal separator on each change of given column diff -r c4b901ff0703 -r 099bb96f8d8d java/sql-dk/src/main/java/info/globalcode/sql/dk/configuration/Properties.java --- a/java/sql-dk/src/main/java/info/globalcode/sql/dk/configuration/Properties.java Fri Sep 23 18:05:50 2022 +0200 +++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/configuration/Properties.java Sun Feb 04 16:10:37 2024 +0100 @@ -20,15 +20,16 @@ import javax.xml.bind.annotation.XmlTransient; import static info.globalcode.sql.dk.Functions.findByName; import java.util.Collections; +import java.util.regex.Pattern; /** *

* List of configurables.

* *

- * Can be backed by defaults – if value for given name is nof found in this instance, we will - * look into defaults. Methods also accept defaultValue parameter – is used if property is nof found - * even in default properties.

+ * Can be backed by defaults – if value for given name is nof found in this + * instance, we will look into defaults. Methods also accept defaultValue + * parameter – is used if property is nof found even in default properties.

* *

* Typical use:

@@ -94,6 +95,11 @@ return p == null ? defaultValue : Integer.valueOf(p.getValue()); } + public Pattern getPattern(String name, Pattern defaultValue) { + Property p = findProperty(name); + return p == null ? defaultValue : Pattern.compile(p.getValue()); + } + public boolean hasProperty(String name) { return findByName(this, name) != null; } diff -r c4b901ff0703 -r 099bb96f8d8d java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/TabularFormatter.java --- a/java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/TabularFormatter.java Fri Sep 23 18:05:50 2022 +0200 +++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/TabularFormatter.java Sun Feb 04 16:10:37 2024 +0100 @@ -27,9 +27,12 @@ import static info.globalcode.sql.dk.formatting.CommonProperties.COLORFUL_DESCRIPTION; import java.sql.SQLException; import java.sql.SQLXML; +import java.util.ArrayList; import java.util.List; +import java.util.Objects; import java.util.logging.Level; import java.util.logging.Logger; +import java.util.regex.Pattern; /** *

@@ -49,6 +52,7 @@ @PropertyDeclaration(name = TabularFormatter.PROPERTY_ASCII, defaultValue = "false", type = Boolean.class, description = "whether to use ASCII table borders instead of unicode ones") @PropertyDeclaration(name = TabularFormatter.PROPERTY_TRIM, defaultValue = "false", type = Boolean.class, description = "whether to trim the values to fit the column width") @PropertyDeclaration(name = TabularFormatter.PROPERTY_HEADER_TYPE, defaultValue = "true", type = Boolean.class, description = "whether to print data types in column headers") +@PropertyDeclaration(name = TabularFormatter.PROPERTY_SEPARATE_BY, defaultValue = "", type = String.class, description = "colum(s) whose change triggers separator printing") public class TabularFormatter extends AbstractFormatter { private static final Logger log = Logger.getLogger(TabularFormatter.class.getName()); @@ -58,6 +62,7 @@ public static final String PROPERTY_ASCII = "ascii"; public static final String PROPERTY_TRIM = "trim"; public static final String PROPERTY_HEADER_TYPE = "headerTypes"; + public static final String PROPERTY_SEPARATE_BY = "separateBy"; protected ColorfulPrintWriter out; private boolean firstResult = true; private int[] columnWidth; @@ -73,6 +78,27 @@ * Print data type of each column in the header */ private final boolean printHeaderTypes; + /** + * When values of columns (names specified by this pattern) changes + * between two consecutive rows, horizontal separator is printed. + */ + private final Pattern separateBy; + /** + * Indexes of columns that matches {@linkplain #separateBy} + */ + private final List separators = new ArrayList<>(); + /** + * Internal counter for buffered data. + */ + private int currentColumnsCount = 0; + /** + * Buffered data to be compared and printed later. + */ + private final List currentRow = new ArrayList<>(); + /** + * Buffered data to be compared later. + */ + private final List previousRow = new ArrayList<>(); public TabularFormatter(FormatterContext formatterContext) { super(formatterContext); @@ -81,6 +107,7 @@ trimValues = formatterContext.getProperties().getBoolean(PROPERTY_TRIM, false); printHeaderTypes = formatterContext.getProperties().getBoolean(PROPERTY_HEADER_TYPE, true); out.setColorful(formatterContext.getProperties().getBoolean(COLORFUL, true)); + separateBy = formatterContext.getProperties().getPattern(PROPERTY_SEPARATE_BY, null); } @Override @@ -89,12 +116,21 @@ printResultSeparator(); initColumnWidths(header.getColumnCount()); + currentRow.clear(); + previousRow.clear(); + separators.clear(); printTableIndent(); printTableBorder("╭"); List columnDescriptors = header.getColumnDescriptors(); + if (separateBy != null) { + for (ColumnDescriptor cd : columnDescriptors) { + separators.add(separateBy.matcher(cd.getLabel()).matches()); + } + } + for (ColumnDescriptor cd : columnDescriptors) { // padding: make header cell at least same width as data cells in this column int typeWidth = printHeaderTypes ? cd.getTypeName().length() + HEADER_TYPE_PREFIX.length() + HEADER_TYPE_SUFFIX.length() : 0; @@ -160,11 +196,49 @@ @Override public void writeColumnValue(Object value) { - super.writeColumnValue(value); + if (separateBy == null) super.writeColumnValue(value); writeColumnValueInternal(value); } protected void writeColumnValueInternal(Object value) { + if (separateBy == null) { + printColumnValue(value); + } else { + currentRow.add(value); + currentColumnsCount++; + int columnsCount = getCurrentColumnsHeader().getColumnCount(); + if (currentColumnsCount % columnsCount == 0) { + if (!previousRow.isEmpty()) { + boolean hasChanges = false; + for (int i = 0; i < previousRow.size(); i++) { + Object previous = previousRow.get(i); + Object current = currentRow.get(i); + boolean isSepating = separators.get(i); + if (isSepating && !Objects.equals(previous, current)) { + hasChanges = true; + break; + } + } + + if (hasChanges) { + printRecordSeparator(); + } + } + + for (Object o : currentRow) { + super.writeColumnValue(value); + printColumnValue(o); + } + + previousRow.clear(); + previousRow.addAll(currentRow); + currentRow.clear(); + currentColumnsCount = 0; + } + } + } + + protected void printColumnValue(Object value) { if (isCurrentColumnFirst()) { printTableIndent(); @@ -181,6 +255,20 @@ } + protected void printRecordSeparator() { + int columnCount = getCurrentColumnsHeader().getColumnCount(); + printTableIndent(); + printTableBorder("├"); + for (int i = 1; i <= columnCount; i++) { + if (i > 1) { + printTableBorder("┼"); + } + printTableBorder(repeat('─', getColumnWidth(i) + 2)); + } + printTableBorder("┤"); + out.println(); + } + protected void printValueWithWhitespaceReplaced(String text) { Functions.printValueWithWhitespaceReplaced(out, text, TerminalColor.Cyan, TerminalColor.Red); } @@ -226,6 +314,12 @@ } @Override + public void writeStartRow() { + super.writeStartRow(); + currentColumnsCount = 0; + } + + @Override public void writeEndRow() { super.writeEndRow(); writeEndRowInternal();