Add pretty table rendering
* Add some border combinations + helper methods * Introducing TableBuilder * Fix tests * Add deprecation tags for old table
This commit is contained in:
committed by
Mark Pollack
parent
21a993d6bf
commit
29e0d6ae73
@@ -78,11 +78,15 @@ javadoc {
|
||||
]
|
||||
|
||||
//exclude "org/springframework/data/redis/config/**"
|
||||
if (JavaVersion.current().isJava8Compatible()) {
|
||||
options.addStringOption('Xdoclint:none', '-quiet')
|
||||
}
|
||||
}
|
||||
|
||||
title = "${rootProject.description} ${version} API"
|
||||
}
|
||||
|
||||
|
||||
jar {
|
||||
manifest.attributes['Implementation-Title'] = 'spring-shell'
|
||||
manifest.attributes['Implementation-Version'] = project.version
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
package org.springframework.shell;
|
||||
|
||||
/**
|
||||
* To be implemented by command result objects that can adapt to the terminal size when they are being rendered.
|
||||
*
|
||||
* <p>An object which does not implement this interface will simply be rendered by invoking its {@link #toString()}
|
||||
* method.</p>
|
||||
*
|
||||
* @author Eric Bottard
|
||||
*/
|
||||
public interface TerminalSizeAware {
|
||||
|
||||
CharSequence render(int terminalWidth);
|
||||
}
|
||||
@@ -19,6 +19,9 @@ import java.io.File;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import jline.TerminalFactory;
|
||||
|
||||
import org.springframework.shell.TerminalSizeAware;
|
||||
import org.springframework.shell.core.annotation.CliCommand;
|
||||
import org.springframework.shell.event.AbstractShellStatusPublisher;
|
||||
import org.springframework.shell.event.ParseResult;
|
||||
@@ -141,6 +144,7 @@ public abstract class AbstractShell extends AbstractShellStatusPublisher impleme
|
||||
return new CommandResult(true, result, null);
|
||||
} catch (RuntimeException e) {
|
||||
setShellStatus(Status.EXECUTION_FAILED, line, parseResult);
|
||||
logger.log(Level.WARNING, e.getMessage(), e);
|
||||
// We rely on execution strategy to log it
|
||||
try {
|
||||
logCommandIfRequired(line, false);
|
||||
@@ -294,8 +298,11 @@ public abstract class AbstractShell extends AbstractShellStatusPublisher impleme
|
||||
protected void handleExecutionResult(Object result) {
|
||||
if (result instanceof Iterable<?>) {
|
||||
for (Object o : (Iterable<?>) result) {
|
||||
logger.info(o.toString());
|
||||
handleExecutionResult(o);
|
||||
}
|
||||
} else if (result instanceof TerminalSizeAware) {
|
||||
int width = TerminalFactory.get().getWidth();
|
||||
logger.info(((TerminalSizeAware) result).render(width).toString());
|
||||
} else {
|
||||
logger.info(result.toString());
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ import java.util.TreeMap;
|
||||
* @see TableRenderer
|
||||
*
|
||||
* @author Gunnar Hillert
|
||||
*
|
||||
* @deprecated In favor of {@link org.springframework.shell.table.TableBuilder}
|
||||
*/
|
||||
public class Table {
|
||||
|
||||
|
||||
@@ -22,7 +22,8 @@ package org.springframework.shell.support.table;
|
||||
* @see TableRenderer
|
||||
*
|
||||
* @author Gunnar Hillert
|
||||
*
|
||||
* @deprecated In favor of {@link org.springframework.shell.table.TableBuilder}
|
||||
*
|
||||
*/
|
||||
public class TableHeader {
|
||||
|
||||
|
||||
@@ -20,17 +20,18 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import org.springframework.shell.support.util.StringUtils;
|
||||
|
||||
import com.google.common.base.Splitter;
|
||||
|
||||
import org.springframework.shell.support.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Contains utility methods for rendering data to a formatted console output.
|
||||
* E.g. it provides helper methods for rendering ASCII-based data tables.
|
||||
*
|
||||
* @author Gunnar Hillert
|
||||
* @author Thomas Risberg
|
||||
*
|
||||
* @deprecated In favor of {@link org.springframework.shell.table.TableBuilder}
|
||||
*
|
||||
*/
|
||||
public final class TableRenderer {
|
||||
|
||||
|
||||
@@ -26,7 +26,8 @@ import java.util.Map;
|
||||
*
|
||||
* @author Gunnar Hillert
|
||||
* @author Ilayaperumal Gopinathan
|
||||
*
|
||||
* @deprecated In favor of {@link org.springframework.shell.table.TableBuilder}
|
||||
*
|
||||
*/
|
||||
public class TableRow {
|
||||
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.shell.table;
|
||||
|
||||
/**
|
||||
* A cell sizing strategy that forces a fixed width, expressed in number of characters.
|
||||
*
|
||||
* @author Eric Bottard
|
||||
*/
|
||||
public class AbsoluteWidthSizeConstraints implements SizeConstraints {
|
||||
|
||||
private final int width;
|
||||
|
||||
public AbsoluteWidthSizeConstraints(int width) {
|
||||
this.width = width;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Extent width(String[] raw, int previous, int tableWidth) {
|
||||
return new Extent(width, width);
|
||||
}
|
||||
}
|
||||
34
src/main/java/org/springframework/shell/table/Aligner.java
Normal file
34
src/main/java/org/springframework/shell/table/Aligner.java
Normal file
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.shell.table;
|
||||
|
||||
/**
|
||||
* A strategy interface for performing text alignment.
|
||||
*
|
||||
* @author Eric Bottard
|
||||
*/
|
||||
public interface Aligner {
|
||||
|
||||
/**
|
||||
* Perform text alignment, returning a String array that MUST contain
|
||||
* {@code cellHeight} lines, each of which MUST be {@code cellWidth} chars in length.
|
||||
*
|
||||
* <p>Input array is guaranteed to contain lines that have length equal to {@cellWidth}. There
|
||||
* is no guarantee on the input number of lines though.</p>
|
||||
*/
|
||||
String[] align(String[] text, int cellWidth, int cellHeight);
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.shell.table;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A TableModel backed by a row-first array.
|
||||
*
|
||||
* @author Eric Bottard
|
||||
*/
|
||||
public class ArrayTableModel extends TableModel {
|
||||
|
||||
private Object[][] data;
|
||||
|
||||
public ArrayTableModel(Object[][] data) {
|
||||
this.data = data;
|
||||
int width = data.length > 0 ? data[0].length : 0;
|
||||
for (int row = 0; row < data.length; row++) {
|
||||
Assert.isTrue(width == data[row].length, "All rows of array data must be of same length");
|
||||
}
|
||||
}
|
||||
|
||||
public int getRowCount() {
|
||||
return data.length;
|
||||
}
|
||||
|
||||
public int getColumnCount() {
|
||||
return data.length > 0 ? data[0].length : 0;
|
||||
}
|
||||
|
||||
public Object getValue(int row, int column) {
|
||||
return data[row][column];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.shell.table;
|
||||
|
||||
/**
|
||||
* A SizeConstraints implementation that splits lines at space boundaries
|
||||
* and returns an extent with minimal and maximal width requirements.
|
||||
*
|
||||
* @author Eric Bottard
|
||||
*/
|
||||
public class AutoSizeConstraints implements SizeConstraints {
|
||||
|
||||
@Override
|
||||
public Extent width(String[] raw, int tableWidth, int nbColumns) {
|
||||
int max = 0;
|
||||
int min = 0;
|
||||
for (String line : raw) {
|
||||
String[] words = line.split(" ");
|
||||
for (String word : words) {
|
||||
min = Math.max(min, word.length());
|
||||
}
|
||||
max = Math.max(max, line.length());
|
||||
}
|
||||
return new Extent(min, max);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.shell.table;
|
||||
|
||||
import java.beans.PropertyDescriptor;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.BeanWrapper;
|
||||
import org.springframework.beans.BeanWrapperImpl;
|
||||
|
||||
/**
|
||||
* A table model that is backed by a list of beans.
|
||||
*
|
||||
* <p>One can control which properties are exposed (and their order). There is also
|
||||
* a convenience constructor for adding a special header row.</p>
|
||||
*
|
||||
* @author Eric Bottard
|
||||
*/
|
||||
public class BeanListTableModel<T> extends TableModel {
|
||||
|
||||
private final List<BeanWrapper> data;
|
||||
|
||||
private final List<String> propertyNames;
|
||||
|
||||
private final List<Object> headerRow;
|
||||
|
||||
public BeanListTableModel(Class<T> clazz, Iterable<T> list) {
|
||||
this.data = new ArrayList<BeanWrapper>();
|
||||
for (T bean : list) {
|
||||
this.data.add(new BeanWrapperImpl(bean));
|
||||
}
|
||||
this.headerRow = null;
|
||||
propertyNames = new ArrayList<String>();
|
||||
for (PropertyDescriptor propertyName : BeanUtils.getPropertyDescriptors(clazz)) {
|
||||
if ("class".equals(propertyName.getName())) {
|
||||
continue;
|
||||
}
|
||||
propertyNames.add(propertyName.getName());
|
||||
}
|
||||
}
|
||||
|
||||
public BeanListTableModel(Iterable<T> list, String... propertyNames) {
|
||||
this.data = new ArrayList<BeanWrapper>();
|
||||
for (T bean : list) {
|
||||
this.data.add(new BeanWrapperImpl(bean));
|
||||
}
|
||||
this.headerRow = null;
|
||||
this.propertyNames = Arrays.asList(propertyNames);
|
||||
}
|
||||
|
||||
public BeanListTableModel(Iterable<T> list, LinkedHashMap<String, Object> header) {
|
||||
this.data = new ArrayList<BeanWrapper>();
|
||||
for (T bean : list) {
|
||||
this.data.add(new BeanWrapperImpl(bean));
|
||||
}
|
||||
this.headerRow = new ArrayList<Object>(header.values());
|
||||
propertyNames = new ArrayList<String>(header.keySet());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRowCount() {
|
||||
return headerRow == null ? data.size() : 1 + data.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getColumnCount() {
|
||||
return propertyNames.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getValue(int row, int column) {
|
||||
if (headerRow != null && row == 0) {
|
||||
return headerRow.get(column);
|
||||
}
|
||||
else {
|
||||
int rowToUse = headerRow == null ? row : row - 1;
|
||||
String propertyName = propertyNames.get(column);
|
||||
return data.get(rowToUse).getPropertyValue(propertyName);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.shell.table;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* This represents a directive to set some borders on cells of a table.
|
||||
* Multiple specifications can be combined on a single table.
|
||||
*
|
||||
* @author Eric Bottard
|
||||
*/
|
||||
public class BorderSpecification {
|
||||
|
||||
public static final int NONE = 0;
|
||||
|
||||
public static final int TOP = 1;
|
||||
|
||||
public static final int BOTTOM = 2;
|
||||
|
||||
public static final int LEFT = 4;
|
||||
|
||||
public static final int RIGHT = 8;
|
||||
|
||||
public static final int INNER_VERTICAL = 16;
|
||||
|
||||
public static final int INNER_HORIZONTAL = 32;
|
||||
|
||||
public static final int OUTLINE = TOP | BOTTOM | LEFT | RIGHT;
|
||||
|
||||
public static final int FULL = OUTLINE | INNER_HORIZONTAL | INNER_VERTICAL;
|
||||
|
||||
public static final int INNER = INNER_HORIZONTAL | INNER_VERTICAL;
|
||||
|
||||
private final int row1, row2, column1, column2;
|
||||
|
||||
private final int match;
|
||||
|
||||
private final BorderStyle style;
|
||||
|
||||
/**
|
||||
* Specifications are created by {@link Table#addBorder(int, int, int, int, int, BorderStyle)}.
|
||||
*/
|
||||
/*default*/ BorderSpecification(int row1, int column1, int row2, int column2, int match, BorderStyle style) {
|
||||
this.row1 = row1;
|
||||
this.row2 = row2;
|
||||
this.column1 = column1;
|
||||
this.column2 = column2;
|
||||
this.match = match;
|
||||
this.style = style;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does this specification result in the need to paint a vertical bar at row,column?
|
||||
*/
|
||||
/*default*/ char verticals(int row, int column) {
|
||||
boolean result = (match & LEFT) == LEFT && column == column1;
|
||||
result |= (match & INNER_VERTICAL) == INNER_VERTICAL && column > column1 && column < column2;
|
||||
result |= (match & RIGHT) == RIGHT && column == column2;
|
||||
|
||||
result &= row >= row1;
|
||||
result &= row < row2;
|
||||
return result ? style.verticalGlyph() : BorderStyle.NONE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does this specification result in the need to paint an horizontal bar at row,column?
|
||||
*/
|
||||
/*default*/ char horizontals(int row, int column) {
|
||||
boolean result = (match & TOP) == TOP && row == row1;
|
||||
result |= (match & INNER_HORIZONTAL) == INNER_HORIZONTAL && row > row1 && row < row2;
|
||||
result |= (match & BOTTOM) == BOTTOM && row == row2;
|
||||
|
||||
result &= column >= column1;
|
||||
result &= column < column2;
|
||||
return result ? style.horizontalGlyph() : BorderStyle.NONE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("%s[(%d, %d)->(%d, %d), %s, %s]", getClass().getSimpleName(), row1, column1, row2, column2, style, matchConstants());
|
||||
}
|
||||
|
||||
private String matchConstants() {
|
||||
try {
|
||||
for (String field : new String[] {"NONE", "INNER", "FULL", "OUTLINE"}) {
|
||||
int value = ReflectionUtils.findField(getClass(), field).getInt(null);
|
||||
if (match == value) {
|
||||
return field;
|
||||
}
|
||||
}
|
||||
List<String> constants = new ArrayList<String>();
|
||||
for (String field : new String[] {"TOP", "BOTTOM", "LEFT", "RIGHT", "INNER_HORIZONTAL", "INNER_VERTICAL"}) {
|
||||
int value = ReflectionUtils.findField(getClass(), field).getInt(null);
|
||||
if ((match & value) == value) {
|
||||
constants.add(field);
|
||||
}
|
||||
}
|
||||
return StringUtils.collectionToDelimitedString(constants, "|");
|
||||
}
|
||||
catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
242
src/main/java/org/springframework/shell/table/BorderStyle.java
Normal file
242
src/main/java/org/springframework/shell/table/BorderStyle.java
Normal file
@@ -0,0 +1,242 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.shell.table;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Provides support for different styles of borders, using simple or fancy ascii art.
|
||||
*
|
||||
* @see <a href="https://en.wikipedia.org/wiki/Box-drawing_character">https://en.wikipedia.org/wiki/Box-drawing_character</a>
|
||||
*
|
||||
* @author Eric Bottard
|
||||
*/
|
||||
public enum BorderStyle {
|
||||
|
||||
/**
|
||||
* A simplistic style, using characters that ought to always be available in all systems (pipe and minus).
|
||||
*/
|
||||
oldschool('|', '-'),
|
||||
|
||||
/**
|
||||
* A border style that uses dedicated light box drawing characters from the unicode set.
|
||||
*/
|
||||
fancy_light('│', '─'),
|
||||
|
||||
/**
|
||||
* A border style that uses dedicated fat box drawing characters from the unicode set.
|
||||
*/
|
||||
fancy_heavy('┃', '━'),
|
||||
|
||||
/**
|
||||
* A border style that uses dedicated double-light box drawing characters from the unicode set.
|
||||
*/
|
||||
fancy_double('║', '═'),
|
||||
|
||||
/**
|
||||
* A border style that uses space characters, giving some space between columns.
|
||||
*/
|
||||
air(' ', ' '),
|
||||
|
||||
/**
|
||||
* A border style that uses dedicated double dash light box drawing characters from the unicode set.
|
||||
*/
|
||||
fancy_light_double_dash('╎', '╌'),
|
||||
|
||||
/**
|
||||
* A border style that uses dedicated double dash light box drawing characters from the unicode set.
|
||||
*/
|
||||
fancy_light_triple_dash('┆', '┄'),
|
||||
|
||||
/**
|
||||
* A border style that uses dedicated double dash light box drawing characters from the unicode set.
|
||||
*/
|
||||
fancy_light_quadruple_dash('┊', '┈'),
|
||||
|
||||
/**
|
||||
* A border style that uses dedicated double dash heavy box drawing characters from the unicode set.
|
||||
*/
|
||||
fancy_heavy_double_dash('╏', '╍'),
|
||||
|
||||
/**
|
||||
* A border style that uses dedicated double dash heavy box drawing characters from the unicode set.
|
||||
*/
|
||||
fancy_heavy_triple_dash('┇', '┅'),
|
||||
|
||||
/**
|
||||
* A border style that uses dedicated double dash heavy box drawing characters from the unicode set.
|
||||
*/
|
||||
fancy_heavy_quadruple_dash('┋', '┉'),
|
||||
|
||||
;
|
||||
|
||||
private char vertical;
|
||||
|
||||
private char horizontal;
|
||||
|
||||
public static final char NONE = '\u0000';
|
||||
|
||||
private static Map<Long, Character> CORNERS = new HashMap<Long, Character>();
|
||||
|
||||
private static Map<Character, Character> EQUIVALENTS = new HashMap<Character, Character>();
|
||||
|
||||
public char verticalGlyph() {
|
||||
return vertical;
|
||||
}
|
||||
|
||||
public char horizontalGlyph() {
|
||||
return horizontal;
|
||||
}
|
||||
|
||||
static {
|
||||
registerCorners("─│┌┐└┘├┤┬┴┼");
|
||||
registerCorners("━┃┏┓┗┛┣┫┳┻╋");
|
||||
|
||||
// double dashes
|
||||
registerCorners("╌╎┌┐└┘├┤┬┴┼");
|
||||
registerCorners("╍╏┏┓┗┛┣┫┳┻╋");
|
||||
|
||||
// triple dashes
|
||||
registerCorners("┈┆┌┐└┘├┤┬┴┼");
|
||||
registerCorners("┅┇┏┓┗┛┣┫┳┻╋");
|
||||
|
||||
// quad dashes
|
||||
registerCorners("┈┊┌┐└┘├┤┬┴┼");
|
||||
registerCorners("┉┋┏┓┗┛┣┫┳┻╋");
|
||||
|
||||
// double lines
|
||||
registerCorners("═║╔╗╚╝╠╣╦╩╬");
|
||||
// oldschool
|
||||
registerCorners("-|+++++++++");
|
||||
// air style
|
||||
registerCorners(" ");
|
||||
|
||||
// Register some mixed-style combinations
|
||||
// light + heavy
|
||||
registerCorner('│', '│', '━', NONE, '┥');
|
||||
registerCorner('│', '│', NONE, '━', '┝');
|
||||
registerCorner('┃', NONE, '─', '─', '┸');
|
||||
registerCorner(NONE, '┃', '─', '─', '┰');
|
||||
// heavy + light
|
||||
registerCorner('┃', '┃', '─', NONE, '┨');
|
||||
registerCorner('┃', '┃', NONE, '─', '┠');
|
||||
registerCorner('│', NONE, '━', '━', '┷');
|
||||
registerCorner(NONE, '│', '━', '━', '┯');
|
||||
// double + single
|
||||
registerCorner('║', '║', '─', NONE, '╢');
|
||||
registerCorner('║', '║', NONE, '─', '╟');
|
||||
registerCorner('│', NONE, '═', '═', '╧');
|
||||
registerCorner(NONE, '│', '═', '═', '╤');
|
||||
// single + double
|
||||
registerCorner('│', '│', '═', NONE, '╡');
|
||||
registerCorner('│', '│', NONE, '═', '╞');
|
||||
registerCorner('║', NONE, '─', '─', '╨');
|
||||
registerCorner(NONE, '║', '─', '─', '╥');
|
||||
// heavy + light, 90°
|
||||
registerCorner('┃', '│', '━', '─', '╃');
|
||||
registerCorner('│', '┃', '─', '━', '╆');
|
||||
registerCorner('┃', '│', '─', '━', '╄');
|
||||
registerCorner('│', '┃', '━', '─', '╅');
|
||||
// light crossing (heavy or double)
|
||||
registerCorner('│', '│', '━', '━', '┿');
|
||||
registerCorner('│', '│', '═', '═', '╪');
|
||||
registerCorner('┃', '┃', '─', '─', '╂');
|
||||
registerCorner('║', '║', '─', '─', '╫');
|
||||
|
||||
// Dashed variants crossing others behave like regular corners
|
||||
registerSameCorners(fancy_light_double_dash, fancy_light);
|
||||
registerSameCorners(fancy_light_triple_dash, fancy_light);
|
||||
registerSameCorners(fancy_light_quadruple_dash, fancy_light);
|
||||
registerSameCorners(fancy_heavy_double_dash, fancy_heavy);
|
||||
registerSameCorners(fancy_heavy_triple_dash, fancy_heavy);
|
||||
registerSameCorners(fancy_heavy_quadruple_dash, fancy_heavy);
|
||||
|
||||
|
||||
// Air-style glyphs are easy to combine with others. Register some combinations
|
||||
registerMixedWithAirCombinations(oldschool.vertical, oldschool.horizontal);
|
||||
registerMixedWithAirCombinations(fancy_light.vertical, fancy_light.horizontal);
|
||||
registerMixedWithAirCombinations(fancy_double.vertical, fancy_double.horizontal);
|
||||
registerMixedWithAirCombinations(fancy_heavy.vertical, fancy_heavy.horizontal);
|
||||
|
||||
registerMixedWithAirCombinations(fancy_light_double_dash.vertical, fancy_light_double_dash.horizontal);
|
||||
registerMixedWithAirCombinations(fancy_light_triple_dash.vertical, fancy_light_triple_dash.horizontal);
|
||||
registerMixedWithAirCombinations(fancy_light_quadruple_dash.vertical, fancy_light_quadruple_dash.horizontal);
|
||||
registerMixedWithAirCombinations(fancy_heavy_double_dash.vertical, fancy_heavy_double_dash.horizontal);
|
||||
registerMixedWithAirCombinations(fancy_heavy_triple_dash.vertical, fancy_heavy_triple_dash.horizontal);
|
||||
registerMixedWithAirCombinations(fancy_heavy_quadruple_dash.vertical, fancy_heavy_quadruple_dash.horizontal);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the fact that for corner purposes, style1 behaves like style2.
|
||||
*/
|
||||
private static void registerSameCorners(BorderStyle style1, BorderStyle style2) {
|
||||
EQUIVALENTS.put(style1.horizontal, style2.horizontal);
|
||||
EQUIVALENTS.put(style1.vertical, style2.vertical);
|
||||
}
|
||||
|
||||
private static void registerMixedWithAirCombinations(char vertical, char horizontal) {
|
||||
registerCorner(vertical, vertical, ' ', NONE, vertical);
|
||||
registerCorner(vertical, vertical, NONE, ' ', vertical);
|
||||
registerCorner(vertical, vertical, ' ', ' ', vertical);
|
||||
registerCorner(' ', NONE, horizontal, horizontal, horizontal);
|
||||
registerCorner(NONE, ' ', horizontal, horizontal, horizontal);
|
||||
registerCorner(' ', ' ', horizontal, horizontal, horizontal);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Register corner glyphs for a given set, not taking care of mixed style intersections.
|
||||
*/
|
||||
private static void registerCorners(String list) {
|
||||
char horizontal = list.charAt(0);
|
||||
char vertical = list.charAt(1);
|
||||
registerCorner(NONE, vertical, NONE, horizontal, list.charAt(2));
|
||||
registerCorner(NONE, vertical, horizontal, NONE, list.charAt(3));
|
||||
registerCorner(vertical, NONE, NONE, horizontal, list.charAt(4));
|
||||
registerCorner(vertical, NONE, horizontal, NONE, list.charAt(5));
|
||||
registerCorner(vertical, vertical, NONE, horizontal, list.charAt(6));
|
||||
registerCorner(vertical, vertical, horizontal, NONE, list.charAt(7));
|
||||
registerCorner(NONE, vertical, horizontal, horizontal, list.charAt(8));
|
||||
registerCorner(vertical, NONE, horizontal, horizontal, list.charAt(9));
|
||||
registerCorner(vertical, vertical, horizontal, horizontal, list.charAt(10));
|
||||
|
||||
}
|
||||
|
||||
private static void registerCorner(char above, char below, char left, char right, char corner) {
|
||||
long key = key(above, below, left, right);
|
||||
CORNERS.put(key, corner);
|
||||
}
|
||||
|
||||
public static char intersection(char above, char below, char left, char right) {
|
||||
above = EQUIVALENTS.get(above) != null ? EQUIVALENTS.get(above) : above;
|
||||
below = EQUIVALENTS.get(below) != null ? EQUIVALENTS.get(below) : below;
|
||||
left = EQUIVALENTS.get(left) != null ? EQUIVALENTS.get(left) : left;
|
||||
right = EQUIVALENTS.get(right) != null ? EQUIVALENTS.get(right) : right;
|
||||
Character character = CORNERS.get(key(above, below, left, right));
|
||||
return character != null ? character : NONE;
|
||||
}
|
||||
|
||||
private static long key(char above, char below, char left, char right) {
|
||||
return (long) above << 48 | (long) below << 32 | (long) left << 16 | (long) right;
|
||||
}
|
||||
|
||||
BorderStyle(char vertical, char horizontal) {
|
||||
this.vertical = vertical;
|
||||
this.horizontal = horizontal;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.shell.table;
|
||||
|
||||
/**
|
||||
* This is used to specify where some components of a Table may be applied.
|
||||
*
|
||||
* <p>Some commonly used matchers can be created <i>via</i> {@link CellMatchers}.</p>
|
||||
*
|
||||
* @author Eric Bottard
|
||||
*/
|
||||
public interface CellMatcher {
|
||||
|
||||
/**
|
||||
* Return whether a given cell of the table should match.
|
||||
*/
|
||||
public boolean matches(int row, int column, TableModel model);
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.shell.table;
|
||||
|
||||
/**
|
||||
* Contains factory methods for commonly used {@link CellMatcher}s.
|
||||
*
|
||||
* @author Eric Bottard
|
||||
*/
|
||||
public class CellMatchers {
|
||||
|
||||
/**
|
||||
* Return a matcher that applies to every cell of the table.
|
||||
*/
|
||||
public static CellMatcher table() {
|
||||
return new CellMatcher() {
|
||||
public boolean matches(int row, int column, TableModel model) {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a matcher that applies to every cell of some column of the table.
|
||||
*/
|
||||
public static CellMatcher column(final int col) {
|
||||
return new CellMatcher() {
|
||||
public boolean matches(int row, int column, TableModel model) {
|
||||
return col == column;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a matcher that applies to every cell of some row of the table.
|
||||
*/
|
||||
public static CellMatcher row(final int theRow) {
|
||||
return new CellMatcher() {
|
||||
public boolean matches(int row, int column, TableModel model) {
|
||||
return theRow == row;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static CellMatcher ofType(final Class<?> clazz) {
|
||||
return new CellMatcher() {
|
||||
@Override
|
||||
public boolean matches(int row, int column, TableModel model) {
|
||||
Object value = model.getValue(row, column);
|
||||
if (value == null) {
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
return clazz.isAssignableFrom(value.getClass());
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.shell.table;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A decorator Aligner that checks the Aligner invariants contract, useful for debugging.
|
||||
*
|
||||
* @author Eric Bottard
|
||||
*/
|
||||
public class DebugAligner implements Aligner {
|
||||
|
||||
private final Aligner delegate;
|
||||
|
||||
public DebugAligner(Aligner delegate) {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] align(String[] text, int cellWidth, int cellHeight) {
|
||||
String[] result = delegate.align(text, cellWidth, cellHeight);
|
||||
Assert.isTrue(result.length == cellHeight, String.format("%s had the wrong number of lines (%d), expected %d",
|
||||
Arrays.asList(result), result.length, cellHeight));
|
||||
for (String s : result) {
|
||||
Assert.isTrue(s.length() == cellWidth, String.format("'%s' had wrong length (%d), expected %d", s, s.length(),
|
||||
cellWidth));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.shell.table;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A TextWrapper that delegates to another but makes sure that the contract is not violated.
|
||||
*
|
||||
* @author Eric Bottard
|
||||
*/
|
||||
public class DebugTextWrapper implements TextWrapper {
|
||||
|
||||
private final TextWrapper delegate;
|
||||
|
||||
public DebugTextWrapper(TextWrapper delegate) {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] wrap(String[] original, int columnWidth) {
|
||||
String[] result = delegate.wrap(original, columnWidth);
|
||||
for (String s : result) {
|
||||
Assert.isTrue(s.length() == columnWidth, String.format("'%s' has the wrong length (%d), expected %d", s, s.length(), columnWidth));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.shell.table;
|
||||
|
||||
/**
|
||||
* A very simple formatter that uses {@link Object#toString()} and splits on newlines.
|
||||
*
|
||||
* @author Eric Bottard
|
||||
*/
|
||||
public class DefaultFormatter implements Formatter {
|
||||
|
||||
public String[] format(Object value) {
|
||||
return value == null ? new String[] {""} : value.toString().split("\n");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.shell.table;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A Text wrapper that wraps at "word" boundaries. The default delimiter is the space character.
|
||||
*
|
||||
* @author Eric Bottard
|
||||
*/
|
||||
public class DelimiterTextWrapper implements TextWrapper {
|
||||
|
||||
private final char delimiter;
|
||||
|
||||
public DelimiterTextWrapper() {
|
||||
this(' ');
|
||||
}
|
||||
|
||||
public DelimiterTextWrapper(char delimiter) {
|
||||
this.delimiter = delimiter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] wrap(String[] original, int columnWidth) {
|
||||
List<String> result = new ArrayList<String>(original.length);
|
||||
for (String line : original) {
|
||||
while (line.length() > columnWidth) {
|
||||
int split = line.lastIndexOf(delimiter, columnWidth);
|
||||
String toAdd = split == -1 ? line.substring(0, columnWidth) : line.substring(0, split);
|
||||
result.add(String.format("%-" + columnWidth + "s", toAdd));
|
||||
line = line.substring(split == -1 ? columnWidth : split + 1);
|
||||
}
|
||||
result.add(String.format("%-" + columnWidth + "s", line)); // right pad if necessary
|
||||
}
|
||||
return result.toArray(new String[result.size()]);
|
||||
}
|
||||
}
|
||||
31
src/main/java/org/springframework/shell/table/Formatter.java
Normal file
31
src/main/java/org/springframework/shell/table/Formatter.java
Normal file
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.shell.table;
|
||||
|
||||
/**
|
||||
* A Formatter is responsible for the initial rendering of a value to lines of text.
|
||||
*
|
||||
* <p>Note that this representation is likely to be altered later in the pipeline, for the
|
||||
* purpose of text wrapping and aligning. The role of a formatter is merely to give the
|
||||
* raw text representation (<i>e.g.</i> format numbers).</p>
|
||||
*
|
||||
* @author Eric Bottard
|
||||
*/
|
||||
public interface Formatter {
|
||||
|
||||
public String[] format(Object value);
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.shell.table;
|
||||
|
||||
/**
|
||||
* A text alignment strategy that aligns text horizontally so that all instances of some special character(s)
|
||||
* line up perfectly in a column.
|
||||
*
|
||||
* <p>Typically used to render numbers which may or may not have a decimal point, or series of key-value pairs</p>
|
||||
*
|
||||
* @author Eric Bottard
|
||||
*/
|
||||
public class KeyValueHorizontalAligner implements Aligner {
|
||||
|
||||
private final String delimiter;
|
||||
|
||||
public KeyValueHorizontalAligner(String delimiter) {
|
||||
this.delimiter = delimiter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] align(String[] text, int cellWidth, int cellHeight) {
|
||||
|
||||
String[] result = new String[cellHeight];
|
||||
int alignOffset = 0;
|
||||
for (String line : text) {
|
||||
alignOffset = Math.max(alignOffset, line.trim().indexOf(delimiter));
|
||||
}
|
||||
int i = 0;
|
||||
for (String line : text) {
|
||||
String trimmed = line.trim();
|
||||
int offset = trimmed.indexOf(delimiter);
|
||||
if (offset >= 0) {
|
||||
// It is possible that aligning would trigger overflow
|
||||
// Make sure not to
|
||||
int offsetToUse = Math.min(alignOffset - offset, cellWidth - trimmed.length());
|
||||
result[i++] = pad(offsetToUse, cellWidth - trimmed.length() - offsetToUse, trimmed);
|
||||
}
|
||||
else {
|
||||
result[i++] = pad(0, cellWidth - line.length(), line);
|
||||
}
|
||||
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private String pad(int left, int right, String original) {
|
||||
StringBuilder sb = new StringBuilder(left + original.length() + right);
|
||||
for (int i = 0; i < left; i++) {
|
||||
sb.append(' ');
|
||||
}
|
||||
sb.append(original);
|
||||
for (int i = 0; i < right; i++) {
|
||||
sb.append(' ');
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.shell.table;
|
||||
|
||||
/**
|
||||
* A SizeConstraints implementation that is tailored to rendering a series
|
||||
* of {@literal key = value} pairs. Computes extents so that equal signs (or any other
|
||||
* configurable delimiter) line up vertically.
|
||||
*
|
||||
* @author Eric Bottard
|
||||
*/
|
||||
public class KeyValueSizeConstraints implements SizeConstraints {
|
||||
|
||||
private final String delimiter;
|
||||
|
||||
public KeyValueSizeConstraints(String delimiter) {
|
||||
this.delimiter = delimiter;
|
||||
}
|
||||
|
||||
private static String leftTrim(String raw) {
|
||||
int start = 0;
|
||||
int length = raw.length();
|
||||
while (start < length && raw.charAt(start) == ' ') {
|
||||
start++;
|
||||
}
|
||||
return raw.substring(start);
|
||||
}
|
||||
|
||||
private static String rightTrim(String raw) {
|
||||
int end = raw.length();
|
||||
while (end > 0 && raw.charAt(end - 1) == ' ') {
|
||||
end--;
|
||||
}
|
||||
return raw.substring(0, end);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Extent width(String[] raw, int tableWidth, int nbColumns) {
|
||||
|
||||
// We need to make sure we take care of the case where we have
|
||||
// k = long-value
|
||||
// long-key = v
|
||||
// as the real maximal extent is size(long-key) + size( = ) + size(long-value)
|
||||
|
||||
// The minimal extent in the example above is size(long-value)
|
||||
|
||||
int maxLeft = 0;
|
||||
int maxRight = 0;
|
||||
int min = 0;
|
||||
for (String line : raw) {
|
||||
String lineToConsider = line.trim();
|
||||
int offset = lineToConsider.indexOf(delimiter);
|
||||
|
||||
if (offset != -1) {
|
||||
// Compute minimal case (line can be split, decide where to put the delimiter)
|
||||
String minimalLeftPart = lineToConsider.substring(0, offset).trim();
|
||||
String minimalRightPart = lineToConsider.substring(offset + delimiter.length()).trim();
|
||||
int left = minimalLeftPart.length();
|
||||
int right = minimalRightPart.length();
|
||||
int case1 = Math.max(left, right + leftTrim(delimiter).length());
|
||||
int case2 = Math.max(left + rightTrim(delimiter).length(), right);
|
||||
int bestMin = Math.min(case1, case2);
|
||||
min = Math.max(min, bestMin);
|
||||
|
||||
// Compute maximal case (sum of worst case on left and right)
|
||||
maxLeft = Math.max(maxLeft, offset);
|
||||
int after = lineToConsider.length() - offset - delimiter.length();
|
||||
maxRight = Math.max(maxRight, after);
|
||||
}
|
||||
else {
|
||||
min = Math.max(min, lineToConsider.length());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return new Extent(min, maxLeft + delimiter.length() + maxRight);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.shell.table;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A TextWrapper implementation tailored for key-value rendering (working in concert
|
||||
* with {@link KeyValueSizeConstraints}, {@link KeyValueHorizontalAligner}), that tries its
|
||||
* best to vertically align some delimiter character (default '=').
|
||||
*/
|
||||
public class KeyValueTextWrapper implements TextWrapper {
|
||||
|
||||
private final String delimiter;
|
||||
|
||||
public KeyValueTextWrapper() {
|
||||
this("=");
|
||||
}
|
||||
|
||||
public KeyValueTextWrapper(String delimiter) {
|
||||
this.delimiter = delimiter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] wrap(String[] original, int columnWidth) {
|
||||
List<String> result = new ArrayList<String>();
|
||||
for (String line : original) {
|
||||
line = line.trim();
|
||||
while (line.length() > columnWidth) {
|
||||
int cut = line.lastIndexOf(delimiter, columnWidth);
|
||||
if (cut == -1) {
|
||||
cut = columnWidth;
|
||||
}
|
||||
else if (cut + delimiter.length() <= columnWidth) {
|
||||
cut = cut + delimiter.length();
|
||||
}
|
||||
result.add(rightPad(line.substring(0, cut), columnWidth));
|
||||
line = line.substring(cut).trim();
|
||||
}
|
||||
if (line.length() > 0) {
|
||||
result.add(rightPad(line, columnWidth));
|
||||
}
|
||||
}
|
||||
return result.toArray(new String[result.size()]);
|
||||
}
|
||||
|
||||
private String rightPad(String raw, int width) {
|
||||
StringBuilder result = new StringBuilder(raw);
|
||||
for (int i = raw.length(); i < width; i++) {
|
||||
result.append(' ');
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.shell.table;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* A formatter suited for key-value pairs, that renders each mapping on a new line.
|
||||
*
|
||||
* @author Eric Bottard
|
||||
*/
|
||||
public class MapFormatter implements Formatter {
|
||||
|
||||
private final String separator;
|
||||
|
||||
public MapFormatter(String separator) {
|
||||
this.separator = separator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] format(Object value) {
|
||||
Map<?, ?> map = (Map<?, ?>) value;
|
||||
String[] result = new String[map.size()];
|
||||
int i = 0;
|
||||
for (Map.Entry<?, ?> kv : map.entrySet()) {
|
||||
result[i++] = kv.getKey() + separator + kv.getValue();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.shell.table;
|
||||
|
||||
/**
|
||||
* A sizing strategy that will impose the longest line width on cells.
|
||||
*
|
||||
* @author Eric Bottard
|
||||
*/
|
||||
public class NoWrapSizeConstraints implements SizeConstraints {
|
||||
|
||||
@Override
|
||||
public Extent width(String[] raw, int tableWidth, int nbColumns) {
|
||||
int max = 0;
|
||||
for (String line : raw) {
|
||||
max = Math.max(max, line.length());
|
||||
}
|
||||
return new Extent(max, max);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.shell.table;
|
||||
|
||||
/**
|
||||
* An horizontal alignment strategy that allows alignment to the left, center or right.
|
||||
*
|
||||
* @author Eric Bottard
|
||||
*/
|
||||
public enum SimpleHorizontalAligner implements Aligner {
|
||||
|
||||
left, center, right;
|
||||
|
||||
@Override
|
||||
public String[] align(String[] text, int cellWidth, int cellHeight) {
|
||||
String[] result = new String[cellHeight];
|
||||
for (int i = 0; i < cellHeight; i++) {
|
||||
String line = (i < text.length && text[i] != null) ? text[i].trim() : "";
|
||||
|
||||
int paddingToDistribute = cellWidth - line.length();
|
||||
|
||||
int padLeft;
|
||||
int padRight;
|
||||
|
||||
switch (this) {
|
||||
case center: {
|
||||
int carry = paddingToDistribute % 2;
|
||||
paddingToDistribute = paddingToDistribute - carry;
|
||||
padLeft = padRight = paddingToDistribute / 2;
|
||||
padRight += carry;
|
||||
break;
|
||||
}
|
||||
case right: {
|
||||
padLeft = paddingToDistribute;
|
||||
padRight = 0;
|
||||
break;
|
||||
}
|
||||
case left: {
|
||||
padLeft = 0;
|
||||
padRight = paddingToDistribute;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new AssertionError();
|
||||
}
|
||||
StringBuilder sb = new StringBuilder(cellWidth);
|
||||
for (int j = 0; j < padLeft; j++) {
|
||||
sb.append(' ');
|
||||
}
|
||||
sb.append(line);
|
||||
for (int j = 0; j < padRight; j++) {
|
||||
sb.append(' ');
|
||||
}
|
||||
|
||||
result[i] = sb.toString();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.shell.table;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Alignment strategy that allows simple vertical alignment to top, middle or bottom.
|
||||
*
|
||||
* @author Eric Bottard
|
||||
*/
|
||||
public enum SimpleVerticalAligner implements Aligner {
|
||||
|
||||
top, middle, bottom;
|
||||
|
||||
@Override
|
||||
public String[] align(String[] text, int cellWidth, int cellHeight) {
|
||||
String[] result = new String[cellHeight];
|
||||
int blanksBefore = 0;
|
||||
int blanksAfter = 0;
|
||||
for (int row = 0; row < text.length; row++) {
|
||||
if (text[row] == null || text[row].trim().equals("")) {
|
||||
blanksBefore++;
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (int row = text.length - 1; row >= 0; row--) {
|
||||
if (text[row] == null || text[row].trim().equals("")) {
|
||||
blanksAfter++;
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
String filler = spaces(cellWidth);
|
||||
|
||||
int padBefore;
|
||||
int padAfter;
|
||||
int nonBlankLines = text.length - blanksAfter - blanksBefore;
|
||||
int paddingToDistribute = cellHeight - nonBlankLines;
|
||||
|
||||
switch (this) {
|
||||
case middle: {
|
||||
int carry = paddingToDistribute % 2;
|
||||
paddingToDistribute = paddingToDistribute - carry;
|
||||
padBefore = padAfter = paddingToDistribute / 2;
|
||||
padAfter += carry;
|
||||
break;
|
||||
}
|
||||
case bottom: {
|
||||
padBefore = paddingToDistribute;
|
||||
padAfter = 0;
|
||||
break;
|
||||
}
|
||||
case top: {
|
||||
padBefore = 0;
|
||||
padAfter = paddingToDistribute;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
Arrays.fill(result, 0, padBefore, filler);
|
||||
System.arraycopy(text, blanksBefore, result, padBefore, nonBlankLines);
|
||||
Arrays.fill(result, result.length - padAfter, result.length, filler);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private String spaces(int width) {
|
||||
char[] data = new char[width];
|
||||
Arrays.fill(data, ' ');
|
||||
return new String(data);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.shell.table;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Strategy for computing the dimensions of a table cell.
|
||||
*
|
||||
* @author Eric Bottard
|
||||
*/
|
||||
public interface SizeConstraints {
|
||||
|
||||
/**
|
||||
* Return the minimum and maximum width of the cell, given its raw content.
|
||||
* @param raw the raw String representation of the cell contents (may be reformatted later, eg wrapped)
|
||||
* @param tableWidth the whole available width for the table
|
||||
* @param nbColumns the number of columns in the table
|
||||
*/
|
||||
Extent width(String[] raw, int tableWidth, int nbColumns);
|
||||
|
||||
/**
|
||||
* Holds both a minimum and maximum width.
|
||||
*
|
||||
* @author Eric Bottard
|
||||
*/
|
||||
class Extent {
|
||||
|
||||
public final int min;
|
||||
|
||||
public final int max;
|
||||
|
||||
public Extent(int min, int max) {
|
||||
Assert.isTrue(min <= max, "min must be less than max");
|
||||
Assert.isTrue(0 <= min, "min and max must be positive");
|
||||
this.min = min;
|
||||
this.max = max;
|
||||
}
|
||||
}
|
||||
}
|
||||
361
src/main/java/org/springframework/shell/table/Table.java
Normal file
361
src/main/java/org/springframework/shell/table/Table.java
Normal file
@@ -0,0 +1,361 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.shell.table;
|
||||
|
||||
import static org.springframework.shell.table.BorderSpecification.NONE;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.shell.TerminalSizeAware;
|
||||
|
||||
/**
|
||||
* This is the central API for table rendering. A Table object is constructed with a given
|
||||
* TableModel, which holds raw table contents. Its rendering logic is then altered by applying
|
||||
* various customizations, in a fashion very similar to what is used <i>e.g.</i> in a spreadsheet
|
||||
* program:<ol>
|
||||
* <li>{@link #format(CellMatcher, Formatter) formatters} know how to derive character data out of raw data. For
|
||||
* example, numbers are
|
||||
* formatted according to a Locale, or Maps are emitted as a series of {@literal key=value} lines</li>
|
||||
* <li>{@link #size(CellMatcher, SizeConstraints) size constraints} are then applied, which decide how
|
||||
* much column real estate to allocate to cells</li>
|
||||
* <li>{@link #wrap(CellMatcher, TextWrapper) text wrapping policies} are applied once the column sizes
|
||||
* are known</li>
|
||||
* <li>finally, {@link #align(CellMatcher, Aligner) alignment} strategies actually render
|
||||
* text as a series of space-padded strings that draw nicely on screen.</li>
|
||||
* </ol>
|
||||
* All those customizations are applied selectively on the Table cells thanks to a {@link CellMatcher}: One can
|
||||
* decide to right pad column number 3, or to format in a certain way all instances of {@literal java.util.Map}.
|
||||
*
|
||||
* <p>Of course, all of those customizations often work hand in hand, and not all combinations make sense:
|
||||
* one needs to anticipate the fact that text will be split using the ' ' (space) character to properly
|
||||
* calculate column sizes.</p>
|
||||
* @author Eric Bottard
|
||||
*/
|
||||
public class Table implements TerminalSizeAware {
|
||||
|
||||
private final int rows;
|
||||
|
||||
private final int columns;
|
||||
|
||||
private TableModel model;
|
||||
|
||||
private Map<CellMatcher, Formatter> formatters = new LinkedHashMap<CellMatcher, Formatter>();
|
||||
|
||||
private Map<CellMatcher, SizeConstraints> sizeConstraints = new LinkedHashMap<CellMatcher, SizeConstraints>();
|
||||
|
||||
private Map<CellMatcher, TextWrapper> wrappers = new LinkedHashMap<CellMatcher, TextWrapper>();
|
||||
|
||||
private Map<CellMatcher, Aligner> aligners = new LinkedHashMap<CellMatcher, Aligner>();
|
||||
|
||||
private List<BorderSpecification> borderSpecifications = new ArrayList<BorderSpecification>();
|
||||
|
||||
/**
|
||||
* Construct a new Table with the given model and customizers.
|
||||
* The passed in LinkedHashMap should be in reverse-insertion order (<i>i.e.</i> the first CellMatcher
|
||||
* found in iteration order will "win").
|
||||
*
|
||||
* @see TableBuilder#build()
|
||||
*/
|
||||
/*package*/ Table(TableModel model,
|
||||
LinkedHashMap<CellMatcher, Formatter> formatters,
|
||||
LinkedHashMap<CellMatcher, SizeConstraints> sizeConstraints,
|
||||
LinkedHashMap<CellMatcher, TextWrapper> wrappers,
|
||||
LinkedHashMap<CellMatcher, Aligner> aligners,
|
||||
List<BorderSpecification> borderSpecifications) {
|
||||
this.model = model;
|
||||
this.formatters = formatters;
|
||||
this.sizeConstraints = sizeConstraints;
|
||||
this.wrappers = wrappers;
|
||||
this.aligners = aligners;
|
||||
this.borderSpecifications = borderSpecifications;
|
||||
rows = model.getRowCount();
|
||||
columns = model.getColumnCount();
|
||||
|
||||
}
|
||||
|
||||
public TableModel getModel() {
|
||||
return model;
|
||||
}
|
||||
|
||||
public String render(int totalAvailableWidth) {
|
||||
StringBuilder result = new StringBuilder();
|
||||
|
||||
int[] cellHeights = new int[rows];
|
||||
int[] cellWidths;
|
||||
int[] minCellWidths = new int[columns];
|
||||
int[] maxCellWidths = new int[columns];
|
||||
|
||||
String[][][] subLines = new String[rows][columns][];
|
||||
|
||||
Borders borders = new Borders();
|
||||
int widthAvailableForContents = totalAvailableWidth - borders.getNumberOfVerticalBorders();
|
||||
|
||||
// First, compute desired column widths
|
||||
for (int row = 0; row < rows; row++) {
|
||||
for (int column = 0; column < columns; column++) {
|
||||
Object value = model.getValue(row, column);
|
||||
String[] lines = getFormatter(row, column).format(value);
|
||||
subLines[row][column] = lines;
|
||||
|
||||
SizeConstraints.Extent extent = getSizeConstraints(row, column).width(lines, widthAvailableForContents, columns);
|
||||
|
||||
minCellWidths[column] = Math.max(minCellWidths[column], extent.min);
|
||||
maxCellWidths[column] = Math.max(maxCellWidths[column], extent.max);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
cellWidths = computeColumnWidths(widthAvailableForContents, minCellWidths, maxCellWidths);
|
||||
// Now that widths are known, apply wrapping & render
|
||||
for (int row = 0; row < rows; row++) {
|
||||
for (int column = 0; column < columns; column++) {
|
||||
subLines[row][column] = getWrapper(row, column).wrap(subLines[row][column], cellWidths[column]);
|
||||
cellHeights[row] = Math.max(cellHeights[row], subLines[row][column].length);
|
||||
}
|
||||
for (int column = 0; column < columns; column++) {
|
||||
for (Map.Entry<CellMatcher, Aligner> kv : aligners.entrySet()) {
|
||||
if (kv.getKey().matches(row, column, model)) {
|
||||
subLines[row][column] = kv.getValue().align(subLines[row][column], cellWidths[column], cellHeights[row]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
for (int row = 0; row < rows; row++) {
|
||||
|
||||
// TOP CELL BORDER
|
||||
int before = result.length();
|
||||
for (int column = 0; column < columns; column++) {
|
||||
borders.paintCorner(row, column, result);
|
||||
borders.paintHorizontal(row, column, cellWidths[column], result);
|
||||
}
|
||||
borders.paintCorner(row, columns, result);
|
||||
if (result.length() > before) {
|
||||
result.append('\n');
|
||||
}
|
||||
|
||||
for (int subRow = 0; subRow < cellHeights[row]; subRow++) {
|
||||
for (int column = 0; column < columns; column++) {
|
||||
// LEFT CELL BORDER
|
||||
borders.paintVertical(row, column, result);
|
||||
String[] lines = subLines[row][column];
|
||||
result.append(lines[subRow]);
|
||||
}
|
||||
// TABLE RIGHT BORDER
|
||||
borders.paintVertical(row, columns, result);
|
||||
result.append("\n");
|
||||
}
|
||||
}
|
||||
|
||||
// TABLE BOTTOM BORDER
|
||||
int before = result.length();
|
||||
for (int column = 0; column < columns; column++) {
|
||||
borders.paintCorner(rows, column, result);
|
||||
borders.paintHorizontal(rows, column, cellWidths[column], result);
|
||||
}
|
||||
|
||||
// TABLE BOTTOM RIGHT CORNER
|
||||
borders.paintCorner(rows, columns, result);
|
||||
if (result.length() > before) {
|
||||
result.append('\n');
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
private int[] computeColumnWidths(int availableWidth, int[] minCellWidths, int[] maxCellWidths) {
|
||||
|
||||
int[] cellWidths;
|
||||
int minTableWidth = 0, maxTableWidth = 0;
|
||||
for (int column = 0; column < columns; column++) {
|
||||
minTableWidth += minCellWidths[column];
|
||||
maxTableWidth += maxCellWidths[column];
|
||||
}
|
||||
|
||||
// Can use max desired width
|
||||
if (maxTableWidth <= availableWidth) {
|
||||
cellWidths = maxCellWidths;
|
||||
} // will overflow
|
||||
else if (minTableWidth >= availableWidth) {
|
||||
cellWidths = minCellWidths;
|
||||
} // Redistribute nicely
|
||||
else {
|
||||
int W = availableWidth - minTableWidth;
|
||||
int D = maxTableWidth - minTableWidth;
|
||||
cellWidths = new int[columns];
|
||||
for (int column = 0; column < columns; column++) {
|
||||
cellWidths[column] = minCellWidths[column] + W * (maxCellWidths[column] - minCellWidths[column]) / D;
|
||||
}
|
||||
}
|
||||
return cellWidths;
|
||||
}
|
||||
|
||||
private TextWrapper getWrapper(int row, int column) {
|
||||
for (Map.Entry<CellMatcher, TextWrapper> kv : wrappers.entrySet()) {
|
||||
if (kv.getKey().matches(row, column, model)) {
|
||||
return kv.getValue();
|
||||
}
|
||||
}
|
||||
throw new AssertionError("Can't be reached thanks to the whole-table default");
|
||||
}
|
||||
|
||||
private SizeConstraints getSizeConstraints(int row, int column) {
|
||||
for (Map.Entry<CellMatcher, SizeConstraints> kv : sizeConstraints.entrySet()) {
|
||||
if (kv.getKey().matches(row, column, model)) {
|
||||
return kv.getValue();
|
||||
}
|
||||
}
|
||||
throw new AssertionError("Can't be reached thanks to the whole-table default");
|
||||
}
|
||||
|
||||
private Formatter getFormatter(int row, int column) {
|
||||
for (Map.Entry<CellMatcher, Formatter> kv : formatters.entrySet()) {
|
||||
if (kv.getKey().matches(row, column, model)) {
|
||||
return kv.getValue();
|
||||
}
|
||||
}
|
||||
throw new AssertionError("Can't be reached thanks to the whole-table default");
|
||||
}
|
||||
|
||||
/**
|
||||
* An instance of this class knows where to paint border glyphs.
|
||||
*
|
||||
* <p>In all instance arrays, 'row' and 'column' are actually indices in-between
|
||||
* table rows and columns. Hence, sizes are larger by one.</p>
|
||||
* @author Eric Bottard
|
||||
*/
|
||||
private class Borders {
|
||||
|
||||
/**
|
||||
* Glyph to paint a vertical line at row,col.
|
||||
*/
|
||||
private char[][] verticals;
|
||||
|
||||
/**
|
||||
* Glyph to paint a horizontal line at row,col.
|
||||
*/
|
||||
private char[][] horizontals;
|
||||
|
||||
/**
|
||||
* The type of corner, if any, to paint at row,col.
|
||||
*/
|
||||
private char[][] corners;
|
||||
|
||||
/**
|
||||
* True if at least one vertical bar exists in that col.
|
||||
*/
|
||||
private boolean[] vFillers;
|
||||
|
||||
/**
|
||||
* True if at least one horizontal bar exists in that row.
|
||||
*/
|
||||
private boolean[] hFillers;
|
||||
|
||||
public Borders() {
|
||||
verticals = new char[rows][columns + 1];
|
||||
horizontals = new char[rows + 1][columns];
|
||||
corners = new char[rows + 1][columns + 1];
|
||||
vFillers = new boolean[columns + 1];
|
||||
hFillers = new boolean[rows + 1];
|
||||
init();
|
||||
}
|
||||
|
||||
private void init() {
|
||||
|
||||
for (int row = 0; row <= rows; row++) {
|
||||
for (int column = 0; column <= columns; column++) {
|
||||
for (BorderSpecification bs : borderSpecifications) {
|
||||
if (row < rows) {
|
||||
char verticalThere = bs.verticals(row, column);
|
||||
if (verticalThere != BorderStyle.NONE) {
|
||||
this.verticals[row][column] = verticalThere;
|
||||
vFillers[column] |= true;
|
||||
}
|
||||
}
|
||||
if (column < columns) {
|
||||
char horizontalThere = bs.horizontals(row, column);
|
||||
if (horizontalThere != BorderStyle.NONE) {
|
||||
this.horizontals[row][column] = horizontalThere;
|
||||
hFillers[row] |= true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Compute corners when horizontals & verticals intersect
|
||||
for (int row = 0; row <= rows; row++) {
|
||||
for (int column = 0; column <= columns; column++) {
|
||||
char left = (column - 1 >= 0) ? horizontals[row][column - 1] : NONE;
|
||||
char right = (column < columns) ? horizontals[row][column] : NONE;
|
||||
char above = (row - 1 >= 0) ? verticals[row - 1][column] : NONE;
|
||||
char below = (row < rows) ? verticals[row][column] : NONE;
|
||||
corners[row][column] = BorderStyle.intersection(above, below, left, right);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void paintCorner(int row, int column, StringBuilder stringBuilder) {
|
||||
if (corners[row][column] != NONE) {
|
||||
stringBuilder.append(corners[row][column]);
|
||||
} // If there is a border in same row|column, paint filler
|
||||
else if (vFillers[column] && hFillers[row]) {
|
||||
stringBuilder.append(' ');
|
||||
}
|
||||
}
|
||||
|
||||
private void paintVertical(int row, int column, StringBuilder stringBuilder) {
|
||||
if (verticals[row][column] != NONE) {
|
||||
stringBuilder.append(verticals[row][column]);
|
||||
}
|
||||
else if (vFillers[column]) {
|
||||
stringBuilder.append(' ');
|
||||
}
|
||||
}
|
||||
|
||||
private void paintHorizontal(int row, int column, int width, StringBuilder stringBuilder) {
|
||||
if (horizontals[row][column] != NONE) {
|
||||
for (int i = 0; i < width; i++) {
|
||||
stringBuilder.append(horizontals[row][column]);
|
||||
}
|
||||
}
|
||||
else if (hFillers[row]) {
|
||||
for (int i = 0; i < width; i++) {
|
||||
stringBuilder.append(' ');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number of vertical borders, and hence the space consumed by those.
|
||||
*/
|
||||
public int getNumberOfVerticalBorders() {
|
||||
int result = 0;
|
||||
for (boolean b : vFillers) {
|
||||
if (b) {
|
||||
result++;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
242
src/main/java/org/springframework/shell/table/TableBuilder.java
Normal file
242
src/main/java/org/springframework/shell/table/TableBuilder.java
Normal file
@@ -0,0 +1,242 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.shell.table;
|
||||
|
||||
import static org.springframework.shell.table.BorderSpecification.FULL;
|
||||
import static org.springframework.shell.table.BorderSpecification.INNER;
|
||||
import static org.springframework.shell.table.BorderSpecification.INNER_VERTICAL;
|
||||
import static org.springframework.shell.table.BorderSpecification.OUTLINE;
|
||||
import static org.springframework.shell.table.SimpleHorizontalAligner.left;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A builder class to incrementally configure a Table.
|
||||
* @author Eric Bottard
|
||||
*/
|
||||
public class TableBuilder {
|
||||
|
||||
private final TableModel model;
|
||||
|
||||
private final Map<CellMatcher, Formatter> formatters = new LinkedHashMap<CellMatcher, Formatter>();
|
||||
|
||||
private final Map<CellMatcher, SizeConstraints> sizeConstraints = new LinkedHashMap<CellMatcher, SizeConstraints>();
|
||||
|
||||
private final Map<CellMatcher, TextWrapper> wrappers = new LinkedHashMap<CellMatcher, TextWrapper>();
|
||||
|
||||
private final LinkedHashMap<CellMatcher, Aligner> aligners = new LinkedHashMap<CellMatcher, Aligner>();
|
||||
|
||||
private final List<BorderSpecification> borderSpecifications = new ArrayList<BorderSpecification>();
|
||||
|
||||
private final int rows;
|
||||
|
||||
private final int columns;
|
||||
|
||||
/**
|
||||
* Construct a table with the given model. The table will use the following
|
||||
* strategies for all cells, unless overridden:<ul>
|
||||
* <li>{@link DefaultFormatter default formatting} using {@literal toString()}</li>
|
||||
* <li>{@link AutoSizeConstraints sizing strategy} trying to use the maximum space, resorting to splitting lines on
|
||||
* spaces</li>
|
||||
* <li>{@link DelimiterTextWrapper wrapping text} on space characters</li>
|
||||
* <li>{@link SimpleHorizontalAligner left aligning} text.</li>
|
||||
* </ul>
|
||||
*/
|
||||
|
||||
public TableBuilder(TableModel model) {
|
||||
this.model = model;
|
||||
rows = model.getRowCount();
|
||||
columns = model.getColumnCount();
|
||||
|
||||
formatters.put(CellMatchers.table(), new DefaultFormatter());
|
||||
sizeConstraints.put(CellMatchers.table(), new AutoSizeConstraints());
|
||||
wrappers.put(CellMatchers.table(), new DelimiterTextWrapper());
|
||||
aligners.put(CellMatchers.table(), left);
|
||||
|
||||
}
|
||||
|
||||
private TableBuilder addBorder(int top, int left, int bottom, int right, int match, BorderStyle style) {
|
||||
Assert.isTrue(top >= 0 && top < rows, "top row must be positive and less than total number of rows");
|
||||
Assert.isTrue(left >= 0 && left < columns, "left column must be positive and less than total number of columns");
|
||||
Assert.isTrue(bottom > top && bottom <= rows, "bottom row must be greater than top and less than total number of rows");
|
||||
Assert.isTrue(right >= left && right <= columns, "right column must be greater than left and less than total number of columns");
|
||||
Assert.notNull(style, "style cannot be null");
|
||||
borderSpecifications.add(new BorderSpecification(top, left, bottom, right, match, style));
|
||||
return this;
|
||||
}
|
||||
|
||||
public TableModel getModel() {
|
||||
return model;
|
||||
}
|
||||
|
||||
public CellMatcherStub on(CellMatcher matcher) {
|
||||
return new CellMatcherStub(matcher);
|
||||
}
|
||||
|
||||
public Table build() {
|
||||
return new Table(model,
|
||||
reverse(formatters),
|
||||
reverse(sizeConstraints),
|
||||
reverse(wrappers),
|
||||
aligners,
|
||||
borderSpecifications);
|
||||
}
|
||||
|
||||
public BorderStub paintBorder(BorderStyle style, int match) {
|
||||
return new BorderStub(style, match);
|
||||
}
|
||||
|
||||
// Convenience methods for borders
|
||||
|
||||
/**
|
||||
* Set a border on the outline of the whole table.
|
||||
*/
|
||||
public TableBuilder addOutlineBorder(BorderStyle style) {
|
||||
this.addBorder(0, 0, model.getRowCount(), model.getColumnCount(), OUTLINE, style);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a border on the outline of the whole table, as well as around the first row.
|
||||
*/
|
||||
public TableBuilder addHeaderBorder(BorderStyle style) {
|
||||
this.addBorder(0, 0, 1, model.getColumnCount(), OUTLINE, style);
|
||||
return addOutlineBorder(style);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a border around each and every cell of the table.
|
||||
*/
|
||||
public TableBuilder addFullBorder(BorderStyle style) {
|
||||
this.addBorder(0, 0, model.getRowCount(), model.getColumnCount(), FULL, style);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a border on the outline of the whole table, around the first row and draw vertical lines
|
||||
* around each column.
|
||||
*/
|
||||
public TableBuilder addHeaderAndVerticalsBorders(BorderStyle style) {
|
||||
this.addBorder(0, 0, 1, model.getColumnCount(), OUTLINE, style);
|
||||
this.addBorder(0, 0, model.getRowCount(), model.getColumnCount(), OUTLINE | INNER_VERTICAL, style);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a border on the inner verticals and horizontals of the table, but not on the outline.
|
||||
*/
|
||||
public TableBuilder addInnerBorder(BorderStyle style) {
|
||||
this.addBorder(0, 0, model.getRowCount(), model.getColumnCount(), INNER, style);
|
||||
return this;
|
||||
}
|
||||
|
||||
private <K, V> LinkedHashMap<K, V> reverse(Map<K, V> original) {
|
||||
LinkedHashMap<K, V> result = new LinkedHashMap<K, V>(original.size());
|
||||
List<Map.Entry<K, V>> entries = new ArrayList<Map.Entry<K, V>>(original.entrySet());
|
||||
for (int i = entries.size() - 1; i >= 0; i--) {
|
||||
Map.Entry<K, V> entry = entries.get(i);
|
||||
result.put(entry.getKey(), entry.getValue());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public class CellMatcherStub {
|
||||
|
||||
private final CellMatcher cellMatcher;
|
||||
|
||||
private CellMatcherStub(CellMatcher cellMatcher) {
|
||||
this.cellMatcher = cellMatcher;
|
||||
}
|
||||
|
||||
public CellMatcherStub addFormatter(Formatter formatter) {
|
||||
formatters.put(this.cellMatcher, formatter);
|
||||
return this;
|
||||
}
|
||||
|
||||
public CellMatcherStub addSizer(SizeConstraints sizer) {
|
||||
sizeConstraints.put(this.cellMatcher, sizer);
|
||||
return this;
|
||||
}
|
||||
|
||||
public CellMatcherStub addWrapper(TextWrapper textWrapper) {
|
||||
wrappers.put(this.cellMatcher, textWrapper);
|
||||
return this;
|
||||
}
|
||||
|
||||
public CellMatcherStub addAligner(Aligner aligner) {
|
||||
aligners.put(this.cellMatcher, aligner);
|
||||
return this;
|
||||
}
|
||||
|
||||
public CellMatcherStub on(CellMatcher other) {
|
||||
return TableBuilder.this.on(other);
|
||||
}
|
||||
|
||||
public TableBuilder and() {
|
||||
return TableBuilder.this;
|
||||
}
|
||||
|
||||
public Table build() {
|
||||
return TableBuilder.this.build();
|
||||
}
|
||||
}
|
||||
|
||||
public class BorderStub {
|
||||
|
||||
private final BorderStyle style;
|
||||
|
||||
private final int match;
|
||||
|
||||
private BorderStub(BorderStyle style, int match) {
|
||||
this.style = style;
|
||||
this.match = match;
|
||||
}
|
||||
|
||||
public TopLeft fromRowColumn(int row, int column) {
|
||||
return new TopLeft(row, column);
|
||||
}
|
||||
|
||||
public TopLeft fromTopLeft() {
|
||||
return new TopLeft(0, 0);
|
||||
}
|
||||
|
||||
public class TopLeft {
|
||||
private final int row;
|
||||
|
||||
private final int column;
|
||||
|
||||
private TopLeft(int row, int column) {
|
||||
this.row = row;
|
||||
this.column = column;
|
||||
}
|
||||
|
||||
public TableBuilder toRowColumn(int row, int column) {
|
||||
TableBuilder.this.addBorder(TopLeft.this.row, TopLeft.this.column, row, column, BorderStub.this.match, BorderStub.this.style);
|
||||
return TableBuilder.this;
|
||||
}
|
||||
|
||||
public TableBuilder toBottomRight() {
|
||||
return toRowColumn(model.getRowCount(), model.getColumnCount());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.shell.table;
|
||||
|
||||
/**
|
||||
* Abstracts away the contract a {@link Table} will use to retrieve tabular data.
|
||||
*
|
||||
* @author Eric Bottard
|
||||
*/
|
||||
public abstract class TableModel {
|
||||
|
||||
/**
|
||||
* Return the number of rows that can be queried.
|
||||
* Values between 0 and {@code rowCount-1} inclusive are valid values.
|
||||
*/
|
||||
public abstract int getRowCount();
|
||||
|
||||
/**
|
||||
* Return the number of columns that can be queried.
|
||||
* Values between 0 and {@code columnCount-1} inclusive are valid values.
|
||||
*/
|
||||
public abstract int getColumnCount();
|
||||
|
||||
/**
|
||||
* Return the data value to be displayed at a given row and column, which may be null.
|
||||
*/
|
||||
public abstract Object getValue(int row, int column);
|
||||
|
||||
/**
|
||||
* Return a transposed view of this model, where rows become columns and vice-versa.
|
||||
*/
|
||||
public TableModel transpose() {
|
||||
return new TableModel() {
|
||||
@Override
|
||||
public int getRowCount() {
|
||||
return TableModel.this.getColumnCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getColumnCount() {
|
||||
return TableModel.this.getRowCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getValue(int row, int column) {
|
||||
return TableModel.this.getValue(column, row);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.shell.table;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Helper class to build a TableModel incrementally.
|
||||
*
|
||||
* @author Eric Bottard
|
||||
*/
|
||||
public class TableModelBuilder<T> {
|
||||
|
||||
public static final int DEFAULT_ROW_CAPACITY = 3;
|
||||
|
||||
private List<List<T>> rows = new ArrayList<List<T>>();
|
||||
|
||||
private int previousRowSize = -1;
|
||||
|
||||
private boolean frozen;
|
||||
|
||||
public TableModelBuilder<T> addRow() {
|
||||
Assert.isTrue(!frozen, "TableModel has already been built, builder can't be altered anymore");
|
||||
int nbRows = rows.size();
|
||||
if (previousRowSize != -1) {
|
||||
int currentRowSize = rows.get(nbRows - 1).size();
|
||||
Assert.isTrue(currentRowSize == previousRowSize,
|
||||
"Can't switch to next row, as the current one does not have as many elements as the previous one");
|
||||
}
|
||||
if (rows.size() > 0) {
|
||||
previousRowSize = rows.get(0).size();
|
||||
}
|
||||
rows.add(new ArrayList<T>(previousRowSize == -1 ? DEFAULT_ROW_CAPACITY : previousRowSize));
|
||||
return this;
|
||||
}
|
||||
|
||||
public TableModelBuilder<T> addValue(T value) {
|
||||
Assert.isTrue(!frozen, "TableModel has already been built, builder can't be altered anymore");
|
||||
if (previousRowSize != -1 && rows.get(rows.size() - 1).size() == previousRowSize) {
|
||||
throw new IllegalArgumentException("Can't add another value to current row");
|
||||
}
|
||||
rows.get(rows.size() - 1).add(value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public TableModel build() {
|
||||
frozen = true;
|
||||
return new TableModel() {
|
||||
@Override
|
||||
public int getRowCount() {
|
||||
return rows.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getColumnCount() {
|
||||
return rows.isEmpty() ? 0 : rows.get(0).size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getValue(int row, int column) {
|
||||
return rows.get(row).get(column);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
38
src/main/java/org/springframework/shell/table/Tables.java
Normal file
38
src/main/java/org/springframework/shell/table/Tables.java
Normal file
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.shell.table;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Utility class used to create and configure typical Tables.
|
||||
*
|
||||
* @author Eric Bottard
|
||||
*/
|
||||
public class Tables {
|
||||
|
||||
/**
|
||||
* Install all the necessary formatters, aligners, etc for key-value rendering of Maps.
|
||||
*/
|
||||
public static TableBuilder configureKeyValueRendering(TableBuilder builder, String delimiter) {
|
||||
return builder.on(CellMatchers.ofType(Map.class))
|
||||
.addFormatter(new MapFormatter(delimiter))
|
||||
.addAligner(new KeyValueHorizontalAligner(delimiter.trim()))
|
||||
.addSizer(new KeyValueSizeConstraints(delimiter))
|
||||
.addWrapper(new KeyValueTextWrapper(delimiter)).and();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.shell.table;
|
||||
|
||||
/**
|
||||
* A strategy for applying text wrapping/cropping given a cell width.
|
||||
*/
|
||||
public interface TextWrapper {
|
||||
|
||||
/**
|
||||
* Return a list of lines where each line length MUST be equal to {@code columnWidth} (padding with spaces if
|
||||
* appropriate). There is no constraint on the number of lines returned however (typically, will be greater than
|
||||
* the input number if wrapping occurred).
|
||||
*/
|
||||
String[] wrap(String[] original, int columnWidth);
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.shell.table;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.rules.TestName;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
|
||||
/**
|
||||
* Base class that allows reading a sample result rendering of a table, based on the actual
|
||||
* class and method name of the test.
|
||||
*
|
||||
* @author Eric Bottard
|
||||
*/
|
||||
public class AbstractTestWithSample {
|
||||
|
||||
@Rule
|
||||
public TestName testName = new TestName();
|
||||
|
||||
protected String sample() throws IOException {
|
||||
String sampleName = String.format("%s-%s.txt",
|
||||
this.getClass().getSimpleName(), testName.getMethodName());
|
||||
InputStream stream = TableTest.class.getResourceAsStream(sampleName);
|
||||
Assert.notNull(stream, "Can't find expected rendering result at " + sampleName);
|
||||
return FileCopyUtils.copyToString(new InputStreamReader(stream)).replace("&", "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a simple rows x columns model made of chars.
|
||||
*/
|
||||
protected TableModel generate(int rows, int columns) {
|
||||
Character[][] data = new Character[rows][columns];
|
||||
for (int row = 0; row < rows; row++) {
|
||||
data[row] = new Character[columns];
|
||||
for (int column = 0; column < columns; column++) {
|
||||
data[row][column] = (char) ('a' + row * columns + column);
|
||||
}
|
||||
}
|
||||
return new ArrayTableModel(data);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.shell.table;
|
||||
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Tests for ArrayTableModel.
|
||||
*
|
||||
* @author Eric Bottard
|
||||
*/
|
||||
public class ArrayTableModelTest {
|
||||
|
||||
@Test
|
||||
public void testValid() {
|
||||
TableModel model = new ArrayTableModel(new String[][] {{"a", "b"}, {"c", "d"}});
|
||||
assertThat(model.getColumnCount(), equalTo(2));
|
||||
assertThat(model.getRowCount(), equalTo(2));
|
||||
assertThat(model.getValue(0, 1), equalTo((Object) "b"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEmpty() {
|
||||
TableModel model = new ArrayTableModel(new String[][] {});
|
||||
assertThat(model.getColumnCount(), equalTo(0));
|
||||
assertThat(model.getRowCount(), equalTo(0));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testInvalidDimensions() {
|
||||
new ArrayTableModel(new String[][] {{"a", "b"}, {"c", "d", "e"}});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.shell.table;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Test for BeanListTableModel.
|
||||
*
|
||||
* @author Eric Bottard
|
||||
*/
|
||||
public class BeanListTableModelTest extends AbstractTestWithSample {
|
||||
|
||||
@Test
|
||||
public void testSimpleConstructor() throws IOException {
|
||||
|
||||
List<Person> data = data();
|
||||
|
||||
Table table = new TableBuilder(new BeanListTableModel<Person>(Person.class, data)).build();
|
||||
String result = table.render(80);
|
||||
assertThat(result, equalTo(sample()));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExplicitPropertyNames() throws IOException {
|
||||
|
||||
List<Person> data = data();
|
||||
|
||||
Table table = new TableBuilder(new BeanListTableModel<Person>(data, "lastName", "firstName")).build();
|
||||
String result = table.render(80);
|
||||
assertThat(result, equalTo(sample()));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHeaderRow() throws IOException {
|
||||
|
||||
List<Person> data = data();
|
||||
|
||||
LinkedHashMap<String, Object> header = new LinkedHashMap<String, Object>();
|
||||
header.put("lastName", "Last Name");
|
||||
header.put("firstName", "First Name");
|
||||
|
||||
Table table = new TableBuilder(new BeanListTableModel<Person>(data, header)).build();
|
||||
String result = table.render(80);
|
||||
assertThat(result, equalTo(sample()));
|
||||
|
||||
}
|
||||
|
||||
private List<Person> data() {
|
||||
List<Person> data = new ArrayList<Person>();
|
||||
data.add(new Person("Alice", "Clark", 12));
|
||||
data.add(new Person("Bob", "Smith", 42));
|
||||
data.add(new Person("Sarah", "Connor", 38));
|
||||
return data;
|
||||
}
|
||||
|
||||
public static class Person {
|
||||
private int age;
|
||||
|
||||
private String firstName;
|
||||
|
||||
private String lastName;
|
||||
|
||||
public Person(String firstName, String lastName, int age) {
|
||||
this.age = age;
|
||||
this.firstName = firstName;
|
||||
this.lastName = lastName;
|
||||
}
|
||||
|
||||
public int getAge() {
|
||||
return age;
|
||||
}
|
||||
|
||||
public String getFirstName() {
|
||||
return firstName;
|
||||
}
|
||||
|
||||
public String getLastName() {
|
||||
return lastName;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.shell.table;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.equalTo;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.springframework.shell.table.BorderStyle.fancy_double;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Tests for convenience borders factory.
|
||||
*
|
||||
* @author Eric Bottard
|
||||
*/
|
||||
public class BorderFactoryTest extends AbstractTestWithSample {
|
||||
|
||||
@Test
|
||||
public void testOutlineBorder() throws IOException {
|
||||
TableModel model = generate(3, 3);
|
||||
Table table = new TableBuilder(model).addOutlineBorder(fancy_double).build();
|
||||
String result = table.render(80);
|
||||
assertThat(result, equalTo(sample()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFullBorder() throws IOException {
|
||||
TableModel model = generate(3, 3);
|
||||
Table table = new TableBuilder(model).addFullBorder(fancy_double).build();
|
||||
String result = table.render(80);
|
||||
assertThat(result, equalTo(sample()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHeaderBorder() throws IOException {
|
||||
TableModel model = generate(3, 3);
|
||||
Table table = new TableBuilder(model).addHeaderBorder(fancy_double).build();
|
||||
String result = table.render(80);
|
||||
assertThat(result, equalTo(sample()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHeaderAndVerticalsBorder() throws IOException {
|
||||
TableModel model = generate(3, 3);
|
||||
Table table = new TableBuilder(model).addHeaderAndVerticalsBorders(fancy_double).build();
|
||||
String result = table.render(80);
|
||||
assertThat(result, equalTo(sample()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.shell.table;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Tests for BorderStyle rendering and combinations.
|
||||
*
|
||||
* @author Eric Bottard
|
||||
*/
|
||||
public class BorderStyleTests extends AbstractTestWithSample {
|
||||
|
||||
@Test
|
||||
public void testOldSchool() throws IOException {
|
||||
Table table = new TableBuilder(generate(2, 2)).addFullBorder(BorderStyle.oldschool).build();
|
||||
assertThat(table.render(10), is(sample()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFancySimple() throws IOException {
|
||||
Table table = new TableBuilder(generate(2, 2)).addFullBorder(BorderStyle.fancy_light).build();
|
||||
assertThat(table.render(10), is(sample()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFancyHeavy() throws IOException {
|
||||
Table table = new TableBuilder(generate(2, 2)).addFullBorder(BorderStyle.fancy_heavy).build();
|
||||
assertThat(table.render(10), is(sample()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFancyDouble() throws IOException {
|
||||
Table table = new TableBuilder(generate(2, 2)).addFullBorder(BorderStyle.fancy_double).build();
|
||||
assertThat(table.render(10), is(sample()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAir() throws IOException {
|
||||
Table table = new TableBuilder(generate(2, 2)).addFullBorder(BorderStyle.air).build();
|
||||
assertThat(table.render(10), is(sample()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMixedOldSchoolWithAir() throws IOException {
|
||||
Table table = new TableBuilder(generate(2, 2))
|
||||
.addFullBorder(BorderStyle.air)
|
||||
.addOutlineBorder(BorderStyle.oldschool)
|
||||
.build();
|
||||
assertThat(table.render(10), is(sample()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMixedFancyLightAndHeavy() throws IOException {
|
||||
Table table = new TableBuilder(generate(2, 2))
|
||||
.addFullBorder(BorderStyle.fancy_heavy)
|
||||
.addOutlineBorder(BorderStyle.fancy_light)
|
||||
.build();
|
||||
assertThat(table.render(10), is(sample()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMixedFancyHeavyAndLight() throws IOException {
|
||||
Table table = new TableBuilder(generate(2, 2))
|
||||
.addFullBorder(BorderStyle.fancy_light)
|
||||
.addOutlineBorder(BorderStyle.fancy_heavy)
|
||||
.build();
|
||||
assertThat(table.render(10), is(sample()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMixedDoubleAndSingle() throws IOException {
|
||||
Table table = new TableBuilder(generate(2, 2))
|
||||
.addFullBorder(BorderStyle.fancy_light)
|
||||
.addOutlineBorder(BorderStyle.fancy_double)
|
||||
.build();
|
||||
assertThat(table.render(10), is(sample()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMixedSingleAndDouble() throws IOException {
|
||||
Table table = new TableBuilder(generate(2, 2))
|
||||
.addFullBorder(BorderStyle.fancy_double)
|
||||
.addOutlineBorder(BorderStyle.fancy_light)
|
||||
.build();
|
||||
assertThat(table.render(10), is(sample()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMixedLightInternalAndHeavy() throws IOException {
|
||||
Table table = new TableBuilder(generate(3, 3))
|
||||
.addFullBorder(BorderStyle.fancy_heavy)
|
||||
.paintBorder(BorderStyle.fancy_light, BorderSpecification.OUTLINE).fromRowColumn(1, 1).toRowColumn(2, 2)
|
||||
.build();
|
||||
assertThat(table.render(10), is(sample()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMixedHeavyInternalAndLight() throws IOException {
|
||||
Table table = new TableBuilder(generate(3, 3))
|
||||
.addFullBorder(BorderStyle.fancy_light)
|
||||
.paintBorder(BorderStyle.fancy_heavy, BorderSpecification.OUTLINE).fromRowColumn(1, 1).toRowColumn(2, 2)
|
||||
.build();
|
||||
assertThat(table.render(10), is(sample()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHeavyOutlineAndHeader_LightVerticals_AirHorizontals() throws IOException {
|
||||
Table table = new TableBuilder(generate(4, 4))
|
||||
.addOutlineBorder(BorderStyle.fancy_heavy)
|
||||
.paintBorder(BorderStyle.fancy_light, BorderSpecification.INNER_VERTICAL).fromTopLeft().toBottomRight()
|
||||
.paintBorder(BorderStyle.air, BorderSpecification.INNER_HORIZONTAL).fromTopLeft().toBottomRight()
|
||||
.paintBorder(BorderStyle.fancy_heavy, BorderSpecification.OUTLINE).fromTopLeft().toRowColumn(1, 4)
|
||||
.build();
|
||||
assertThat(table.render(10), is(sample()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.shell.table;
|
||||
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Tests for DelimiterTextWrapper.
|
||||
*
|
||||
* @author Eric Bottard
|
||||
*/
|
||||
public class DelimiterTextWrapperTest {
|
||||
|
||||
private TextWrapper wrapper = new DelimiterTextWrapper();
|
||||
|
||||
@Test
|
||||
public void testNoWordSplit() {
|
||||
String[] text = new String[] {"the quick brown fox jumps over the lazy dog."};
|
||||
assertThat(wrapper.wrap(text, 10),
|
||||
arrayContaining("the quick ", "brown fox ", "jumps over", "the lazy ", "dog. "));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWordSplit() {
|
||||
String[] text = new String[] {"the quick brown fox jumps over the lazy dog."};
|
||||
assertThat(wrapper.wrap(text, 4),
|
||||
arrayContaining("the ", "quic", "k ", "brow", "n ", "fox ", "jump", "s ", "over", "the ", "lazy", "dog."));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.shell.table;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Tests related to rendering Maps.
|
||||
*
|
||||
* @author Eric Bottard
|
||||
*/
|
||||
public class KeyValueRenderingTests extends AbstractTestWithSample {
|
||||
|
||||
@Test
|
||||
public void testRenderConstrained() throws IOException {
|
||||
Map<String, String> values = new LinkedHashMap<String, String>();
|
||||
values.put("a", "b");
|
||||
values.put("long-key", "c");
|
||||
values.put("d", "long-value");
|
||||
TableModel model = new ArrayTableModel(new Object[][] {{"Thing", "Properties"}, {"Something", values}});
|
||||
TableBuilder tableBuilder = new TableBuilder(model)
|
||||
.addHeaderAndVerticalsBorders(BorderStyle.fancy_light);
|
||||
Tables.configureKeyValueRendering(tableBuilder, " = ");
|
||||
Table table = tableBuilder.build();
|
||||
String result = table.render(10);
|
||||
assertThat(result, is(sample()));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRenderUnconstrained() throws IOException {
|
||||
Map<String, String> values = new LinkedHashMap<String, String>();
|
||||
values.put("a", "b");
|
||||
values.put("long-key", "c");
|
||||
values.put("d", "long-value");
|
||||
TableModel model = new ArrayTableModel(new Object[][] {{"Thing", "Properties"}, {"Something", values}});
|
||||
TableBuilder tableBuilder = new TableBuilder(model)
|
||||
.addHeaderAndVerticalsBorders(BorderStyle.fancy_light);
|
||||
Tables.configureKeyValueRendering(tableBuilder, " = ");
|
||||
Table table = tableBuilder.build();
|
||||
String result = table.render(80);
|
||||
assertThat(result, is(sample()));
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.shell.table;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Tests for TableModelBuilder.
|
||||
*
|
||||
* @author Eric Bottard
|
||||
*/
|
||||
public class TableModelBuilderTests {
|
||||
|
||||
@Test
|
||||
public void emptyModel() {
|
||||
TableModelBuilder<Number> builder = new TableModelBuilder<Number>();
|
||||
TableModel model = builder.build();
|
||||
Assert.assertThat(model.getColumnCount(), is(0));
|
||||
Assert.assertThat(model.getRowCount(), is(0));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testFrozen() {
|
||||
TableModelBuilder<Number> builder = new TableModelBuilder<Number>();
|
||||
builder.addRow().addValue(5);
|
||||
builder.build();
|
||||
builder.addRow();
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testAddingTooManyValues() {
|
||||
TableModelBuilder<Number> builder = new TableModelBuilder<Number>();
|
||||
builder.addRow().addValue(5);
|
||||
builder.addRow().addValue(1).addValue(2);
|
||||
builder.build();
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testAddingLessValues() {
|
||||
TableModelBuilder<Number> builder = new TableModelBuilder<Number>();
|
||||
builder.addRow().addValue(1).addValue(2);
|
||||
builder.addRow().addValue(5);
|
||||
builder.addRow();
|
||||
builder.build();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void simpleBuild() {
|
||||
TableModelBuilder<Number> builder = new TableModelBuilder<Number>();
|
||||
builder
|
||||
.addRow()
|
||||
.addValue(7).addValue(2)
|
||||
.addRow()
|
||||
.addValue(3).addValue(5.5)
|
||||
.addRow()
|
||||
.addValue(1).addValue(4);
|
||||
|
||||
TableModel model = builder.build();
|
||||
Assert.assertThat(model.getColumnCount(), is(2));
|
||||
Assert.assertThat(model.getRowCount(), is(3));
|
||||
Assert.assertThat(model.getValue(1, 1), is((Object)5.5));
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.shell.table;
|
||||
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Tests for TableModel.
|
||||
*
|
||||
* @author Eric Bottard
|
||||
*/
|
||||
public class TableModelTest {
|
||||
|
||||
@Test
|
||||
public void testTranspose() {
|
||||
TableModel model = new ArrayTableModel(new String[][] {{"a", "b", "c"}, {"d", "e", "f"}});
|
||||
|
||||
assertThat(model.transpose().getColumnCount(), equalTo(2));
|
||||
assertThat(model.transpose().getRowCount(), equalTo(3));
|
||||
assertThat(model.transpose().getValue(2, 1), equalTo((Object) "f"));
|
||||
}
|
||||
|
||||
}
|
||||
93
src/test/java/org/springframework/shell/table/TableTest.java
Normal file
93
src/test/java/org/springframework/shell/table/TableTest.java
Normal file
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.shell.table;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.springframework.shell.table.SimpleHorizontalAligner.*;
|
||||
import static org.springframework.shell.table.SimpleVerticalAligner.*;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Tests for Table rendering.
|
||||
*
|
||||
* @author Eric Bottard
|
||||
*/
|
||||
public class TableTest extends AbstractTestWithSample {
|
||||
|
||||
@Test
|
||||
public void testEmptyModel() {
|
||||
TableModel model = new ArrayTableModel(new Object[0][0]);
|
||||
Table table = new TableBuilder(model).build();
|
||||
String result = table.render(80);
|
||||
assertThat(result, equalTo(""));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPreformattedModel() {
|
||||
TableModel model = generate(2, 2);
|
||||
Table table = new TableBuilder(model).build();
|
||||
String result = table.render(80);
|
||||
assertThat(result, equalTo("ab\ncd\n"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExpandingColumns() throws IOException {
|
||||
TableModel model = new ArrayTableModel(new String[][] {{"a", "b"}, {"ccc", "d"}});
|
||||
Table table = new TableBuilder(model).build();
|
||||
String result = table.render(80);
|
||||
assertThat(result, equalTo(sample()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRightAlignment() throws IOException {
|
||||
TableModel model = new ArrayTableModel(new String[][] {{"a\na\na", "bbb"}, {"ccc", "d"}});
|
||||
Table table = new TableBuilder(model).on(CellMatchers.column(1)).addAligner(right).build();
|
||||
String result = table.render(80);
|
||||
assertThat(result, equalTo(sample()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVerticalAlignment() throws IOException {
|
||||
TableModel model = new ArrayTableModel(new String[][] {{"a\na\na", "bbb"}, {"ccc", "d"}});
|
||||
Table table = new TableBuilder(model).on(CellMatchers.row(0)).addAligner(middle).build();
|
||||
String result = table.render(80);
|
||||
assertThat(result, equalTo(sample()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAutoWrapping() throws IOException {
|
||||
TableModel model = new ArrayTableModel(new String[][] {{"this is a long line", "bbb"}, {"ccc", "d"}});
|
||||
Table table = new TableBuilder(model).build();
|
||||
String result = table.render(10);
|
||||
assertThat(result, equalTo(sample()));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOverflow() throws IOException {
|
||||
TableModel model = new ArrayTableModel(new String[][] {{"this is a long line", "bbb"}, {"ccc", "d"}});
|
||||
Table table = new TableBuilder(model).build();
|
||||
String result = table.render(3);
|
||||
assertThat(result, equalTo(sample()));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
Clark Alice&
|
||||
Smith Bob &
|
||||
ConnorSarah&
|
||||
@@ -0,0 +1,4 @@
|
||||
Last NameFirst Name&
|
||||
Clark Alice &
|
||||
Smith Bob &
|
||||
Connor Sarah &
|
||||
@@ -0,0 +1,3 @@
|
||||
12AliceClark &
|
||||
42Bob Smith &
|
||||
38SarahConnor&
|
||||
@@ -0,0 +1,7 @@
|
||||
╔═╦═╦═╗
|
||||
║a║b║c║
|
||||
╠═╬═╬═╣
|
||||
║d║e║f║
|
||||
╠═╬═╬═╣
|
||||
║g║h║i║
|
||||
╚═╩═╩═╝
|
||||
@@ -0,0 +1,6 @@
|
||||
╔═╦═╦═╗
|
||||
║a║b║c║
|
||||
╠═╬═╬═╣
|
||||
║d║e║f║
|
||||
║g║h║i║
|
||||
╚═╩═╩═╝
|
||||
@@ -0,0 +1,6 @@
|
||||
╔═══╗
|
||||
║abc║
|
||||
╠═══╣
|
||||
║def║
|
||||
║ghi║
|
||||
╚═══╝
|
||||
@@ -0,0 +1,5 @@
|
||||
╔═══╗
|
||||
║abc║
|
||||
║def║
|
||||
║ghi║
|
||||
╚═══╝
|
||||
@@ -0,0 +1,5 @@
|
||||
&
|
||||
a b &
|
||||
&
|
||||
c d &
|
||||
&
|
||||
@@ -0,0 +1,5 @@
|
||||
╔═╦═╗
|
||||
║a║b║
|
||||
╠═╬═╣
|
||||
║c║d║
|
||||
╚═╩═╝
|
||||
@@ -0,0 +1,5 @@
|
||||
┏━┳━┓
|
||||
┃a┃b┃
|
||||
┣━╋━┫
|
||||
┃c┃d┃
|
||||
┗━┻━┛
|
||||
@@ -0,0 +1,5 @@
|
||||
┌─┬─┐
|
||||
│a│b│
|
||||
├─┼─┤
|
||||
│c│d│
|
||||
└─┴─┘
|
||||
@@ -0,0 +1,9 @@
|
||||
┏━┯━┯━┯━┓
|
||||
┃a│b│c│d┃
|
||||
┣━┿━┿━┿━┫
|
||||
┃e│f│g│h┃
|
||||
┃ │ │ │ ┃
|
||||
┃i│j│k│l┃
|
||||
┃ │ │ │ ┃
|
||||
┃m│n│o│p┃
|
||||
┗━┷━┷━┷━┛
|
||||
@@ -0,0 +1,5 @@
|
||||
╔═╤═╗
|
||||
║a│b║
|
||||
╟─┼─╢
|
||||
║c│d║
|
||||
╚═╧═╝
|
||||
@@ -0,0 +1,5 @@
|
||||
┏━┯━┓
|
||||
┃a│b┃
|
||||
┠─┼─┨
|
||||
┃c│d┃
|
||||
┗━┷━┛
|
||||
@@ -0,0 +1,5 @@
|
||||
┌─┰─┐
|
||||
│a┃b│
|
||||
┝━╋━┥
|
||||
│c┃d│
|
||||
└─┸─┘
|
||||
@@ -0,0 +1,7 @@
|
||||
┌─┬─┬─┐
|
||||
│a│b│c│
|
||||
├─╆━╅─┤
|
||||
│d┃e┃f│
|
||||
├─╄━╃─┤
|
||||
│g│h│i│
|
||||
└─┴─┴─┘
|
||||
@@ -0,0 +1,7 @@
|
||||
┏━┳━┳━┓
|
||||
┃a┃b┃c┃
|
||||
┣━╃─╄━┫
|
||||
┃d│e│f┃
|
||||
┣━╅─╆━┫
|
||||
┃g┃h┃i┃
|
||||
┗━┻━┻━┛
|
||||
@@ -0,0 +1,5 @@
|
||||
+---+
|
||||
|a b|
|
||||
| |
|
||||
|c d|
|
||||
+---+
|
||||
@@ -0,0 +1,5 @@
|
||||
┌─╥─┐
|
||||
│a║b│
|
||||
╞═╬═╡
|
||||
│c║d│
|
||||
└─╨─┘
|
||||
@@ -0,0 +1,5 @@
|
||||
+-+-+
|
||||
|a|b|
|
||||
+-+-+
|
||||
|c|d|
|
||||
+-+-+
|
||||
@@ -0,0 +1,9 @@
|
||||
┌─────────┬──────────┐
|
||||
│Thing │Properties│
|
||||
├─────────┼──────────┤
|
||||
│Something│a = b │
|
||||
│ │long-key │
|
||||
│ │ = c │
|
||||
│ │d = │
|
||||
│ │long-value│
|
||||
└─────────┴──────────┘
|
||||
@@ -0,0 +1,7 @@
|
||||
┌─────────┬─────────────────────┐
|
||||
│Thing │Properties │
|
||||
├─────────┼─────────────────────┤
|
||||
│Something│ a = b │
|
||||
│ │long-key = c │
|
||||
│ │ d = long-value│
|
||||
└─────────┴─────────────────────┘
|
||||
@@ -0,0 +1,4 @@
|
||||
this isbbb&
|
||||
a long &
|
||||
line &
|
||||
ccc d &
|
||||
@@ -0,0 +1,2 @@
|
||||
a b&
|
||||
cccd&
|
||||
@@ -0,0 +1,5 @@
|
||||
thisbbb&
|
||||
is a &
|
||||
long &
|
||||
line &
|
||||
ccc d &
|
||||
@@ -0,0 +1,4 @@
|
||||
a bbb&
|
||||
a &
|
||||
a &
|
||||
ccc d&
|
||||
@@ -0,0 +1,4 @@
|
||||
a &
|
||||
a bbb&
|
||||
a &
|
||||
cccd &
|
||||
Reference in New Issue
Block a user