tabular formatter: new option 'separateBy' to print horizontal separator on each change of given column
1.1 --- a/java/sql-dk/src/main/java/info/globalcode/sql/dk/configuration/Properties.java Fri Sep 23 18:05:50 2022 +0200
1.2 +++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/configuration/Properties.java Sun Feb 04 16:10:37 2024 +0100
1.3 @@ -20,15 +20,16 @@
1.4 import javax.xml.bind.annotation.XmlTransient;
1.5 import static info.globalcode.sql.dk.Functions.findByName;
1.6 import java.util.Collections;
1.7 +import java.util.regex.Pattern;
1.8
1.9 /**
1.10 * <p>
1.11 * List of configurables.</p>
1.12 *
1.13 * <p>
1.14 - * Can be backed by defaults – if value for given name is nof found in this instance, we will
1.15 - * look into defaults. Methods also accept defaultValue parameter – is used if property is nof found
1.16 - * even in default properties.</p>
1.17 + * Can be backed by defaults – if value for given name is nof found in this
1.18 + * instance, we will look into defaults. Methods also accept defaultValue
1.19 + * parameter – is used if property is nof found even in default properties.</p>
1.20 *
1.21 * <p>
1.22 * Typical use: </p>
1.23 @@ -94,6 +95,11 @@
1.24 return p == null ? defaultValue : Integer.valueOf(p.getValue());
1.25 }
1.26
1.27 + public Pattern getPattern(String name, Pattern defaultValue) {
1.28 + Property p = findProperty(name);
1.29 + return p == null ? defaultValue : Pattern.compile(p.getValue());
1.30 + }
1.31 +
1.32 public boolean hasProperty(String name) {
1.33 return findByName(this, name) != null;
1.34 }
2.1 --- a/java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/TabularFormatter.java Fri Sep 23 18:05:50 2022 +0200
2.2 +++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/formatting/TabularFormatter.java Sun Feb 04 16:10:37 2024 +0100
2.3 @@ -27,9 +27,12 @@
2.4 import static info.globalcode.sql.dk.formatting.CommonProperties.COLORFUL_DESCRIPTION;
2.5 import java.sql.SQLException;
2.6 import java.sql.SQLXML;
2.7 +import java.util.ArrayList;
2.8 import java.util.List;
2.9 +import java.util.Objects;
2.10 import java.util.logging.Level;
2.11 import java.util.logging.Logger;
2.12 +import java.util.regex.Pattern;
2.13
2.14 /**
2.15 * <p>
2.16 @@ -49,6 +52,7 @@
2.17 @PropertyDeclaration(name = TabularFormatter.PROPERTY_ASCII, defaultValue = "false", type = Boolean.class, description = "whether to use ASCII table borders instead of unicode ones")
2.18 @PropertyDeclaration(name = TabularFormatter.PROPERTY_TRIM, defaultValue = "false", type = Boolean.class, description = "whether to trim the values to fit the column width")
2.19 @PropertyDeclaration(name = TabularFormatter.PROPERTY_HEADER_TYPE, defaultValue = "true", type = Boolean.class, description = "whether to print data types in column headers")
2.20 +@PropertyDeclaration(name = TabularFormatter.PROPERTY_SEPARATE_BY, defaultValue = "", type = String.class, description = "colum(s) whose change triggers separator printing")
2.21 public class TabularFormatter extends AbstractFormatter {
2.22
2.23 private static final Logger log = Logger.getLogger(TabularFormatter.class.getName());
2.24 @@ -58,6 +62,7 @@
2.25 public static final String PROPERTY_ASCII = "ascii";
2.26 public static final String PROPERTY_TRIM = "trim";
2.27 public static final String PROPERTY_HEADER_TYPE = "headerTypes";
2.28 + public static final String PROPERTY_SEPARATE_BY = "separateBy";
2.29 protected ColorfulPrintWriter out;
2.30 private boolean firstResult = true;
2.31 private int[] columnWidth;
2.32 @@ -73,6 +78,27 @@
2.33 * Print data type of each column in the header
2.34 */
2.35 private final boolean printHeaderTypes;
2.36 + /**
2.37 + * When values of columns (names specified by this pattern) changes
2.38 + * between two consecutive rows, horizontal separator is printed.
2.39 + */
2.40 + private final Pattern separateBy;
2.41 + /**
2.42 + * Indexes of columns that matches {@linkplain #separateBy}
2.43 + */
2.44 + private final List<Boolean> separators = new ArrayList<>();
2.45 + /**
2.46 + * Internal counter for buffered data.
2.47 + */
2.48 + private int currentColumnsCount = 0;
2.49 + /**
2.50 + * Buffered data to be compared and printed later.
2.51 + */
2.52 + private final List<Object> currentRow = new ArrayList<>();
2.53 + /**
2.54 + * Buffered data to be compared later.
2.55 + */
2.56 + private final List<Object> previousRow = new ArrayList<>();
2.57
2.58 public TabularFormatter(FormatterContext formatterContext) {
2.59 super(formatterContext);
2.60 @@ -81,6 +107,7 @@
2.61 trimValues = formatterContext.getProperties().getBoolean(PROPERTY_TRIM, false);
2.62 printHeaderTypes = formatterContext.getProperties().getBoolean(PROPERTY_HEADER_TYPE, true);
2.63 out.setColorful(formatterContext.getProperties().getBoolean(COLORFUL, true));
2.64 + separateBy = formatterContext.getProperties().getPattern(PROPERTY_SEPARATE_BY, null);
2.65 }
2.66
2.67 @Override
2.68 @@ -89,12 +116,21 @@
2.69 printResultSeparator();
2.70
2.71 initColumnWidths(header.getColumnCount());
2.72 + currentRow.clear();
2.73 + previousRow.clear();
2.74 + separators.clear();
2.75
2.76 printTableIndent();
2.77 printTableBorder("╭");
2.78
2.79 List<ColumnDescriptor> columnDescriptors = header.getColumnDescriptors();
2.80
2.81 + if (separateBy != null) {
2.82 + for (ColumnDescriptor cd : columnDescriptors) {
2.83 + separators.add(separateBy.matcher(cd.getLabel()).matches());
2.84 + }
2.85 + }
2.86 +
2.87 for (ColumnDescriptor cd : columnDescriptors) {
2.88 // padding: make header cell at least same width as data cells in this column
2.89 int typeWidth = printHeaderTypes ? cd.getTypeName().length() + HEADER_TYPE_PREFIX.length() + HEADER_TYPE_SUFFIX.length() : 0;
2.90 @@ -160,11 +196,49 @@
2.91
2.92 @Override
2.93 public void writeColumnValue(Object value) {
2.94 - super.writeColumnValue(value);
2.95 + if (separateBy == null) super.writeColumnValue(value);
2.96 writeColumnValueInternal(value);
2.97 }
2.98
2.99 protected void writeColumnValueInternal(Object value) {
2.100 + if (separateBy == null) {
2.101 + printColumnValue(value);
2.102 + } else {
2.103 + currentRow.add(value);
2.104 + currentColumnsCount++;
2.105 + int columnsCount = getCurrentColumnsHeader().getColumnCount();
2.106 + if (currentColumnsCount % columnsCount == 0) {
2.107 + if (!previousRow.isEmpty()) {
2.108 + boolean hasChanges = false;
2.109 + for (int i = 0; i < previousRow.size(); i++) {
2.110 + Object previous = previousRow.get(i);
2.111 + Object current = currentRow.get(i);
2.112 + boolean isSepating = separators.get(i);
2.113 + if (isSepating && !Objects.equals(previous, current)) {
2.114 + hasChanges = true;
2.115 + break;
2.116 + }
2.117 + }
2.118 +
2.119 + if (hasChanges) {
2.120 + printRecordSeparator();
2.121 + }
2.122 + }
2.123 +
2.124 + for (Object o : currentRow) {
2.125 + super.writeColumnValue(value);
2.126 + printColumnValue(o);
2.127 + }
2.128 +
2.129 + previousRow.clear();
2.130 + previousRow.addAll(currentRow);
2.131 + currentRow.clear();
2.132 + currentColumnsCount = 0;
2.133 + }
2.134 + }
2.135 + }
2.136 +
2.137 + protected void printColumnValue(Object value) {
2.138
2.139 if (isCurrentColumnFirst()) {
2.140 printTableIndent();
2.141 @@ -181,6 +255,20 @@
2.142
2.143 }
2.144
2.145 + protected void printRecordSeparator() {
2.146 + int columnCount = getCurrentColumnsHeader().getColumnCount();
2.147 + printTableIndent();
2.148 + printTableBorder("├");
2.149 + for (int i = 1; i <= columnCount; i++) {
2.150 + if (i > 1) {
2.151 + printTableBorder("┼");
2.152 + }
2.153 + printTableBorder(repeat('─', getColumnWidth(i) + 2));
2.154 + }
2.155 + printTableBorder("┤");
2.156 + out.println();
2.157 + }
2.158 +
2.159 protected void printValueWithWhitespaceReplaced(String text) {
2.160 Functions.printValueWithWhitespaceReplaced(out, text, TerminalColor.Cyan, TerminalColor.Red);
2.161 }
2.162 @@ -226,6 +314,12 @@
2.163 }
2.164
2.165 @Override
2.166 + public void writeStartRow() {
2.167 + super.writeStartRow();
2.168 + currentColumnsCount = 0;
2.169 + }
2.170 +
2.171 + @Override
2.172 public void writeEndRow() {
2.173 super.writeEndRow();
2.174 writeEndRowInternal();