diff --git a/initializr-web/pom.xml b/initializr-web/pom.xml index 46fe6046..ae11b5c8 100644 --- a/initializr-web/pom.xml +++ b/initializr-web/pom.xml @@ -44,6 +44,10 @@ org.apache.commons commons-compress + + org.apache.commons + commons-text + javax.cache diff --git a/initializr-web/src/main/java/io/spring/initializr/web/support/CommandLineHelpGenerator.java b/initializr-web/src/main/java/io/spring/initializr/web/support/CommandLineHelpGenerator.java index fb6b6231..fbfc54b8 100644 --- a/initializr-web/src/main/java/io/spring/initializr/web/support/CommandLineHelpGenerator.java +++ b/initializr-web/src/main/java/io/spring/initializr/web/support/CommandLineHelpGenerator.java @@ -18,8 +18,10 @@ package io.spring.initializr.web.support; import java.beans.PropertyDescriptor; import java.io.IOException; +import java.util.ArrayList; import java.util.Comparator; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -28,6 +30,7 @@ import io.spring.initializr.metadata.Dependency; import io.spring.initializr.metadata.InitializrMetadata; import io.spring.initializr.metadata.MetadataElement; import io.spring.initializr.metadata.Type; +import org.apache.commons.text.WordUtils; import org.springframework.beans.BeanWrapperImpl; @@ -43,10 +46,19 @@ public class CommandLineHelpGenerator { + " \\\\/ ___)| |_)| | | | | || (_| | ) ) ) )\n" + " ' |____| .__|_| |_|_| |_\\__, | / / / /\n" + " =========|_|==============|___/=/_/_/_/"; + private static final String NEW_LINE = System.getProperty("line.separator"); + private final TemplateRenderer template; + private final int maxColumnWidth; + public CommandLineHelpGenerator(TemplateRenderer template) { + this(template, 60); + } + + public CommandLineHelpGenerator(TemplateRenderer template, int maxColumnWidth) { this.template = template; + this.maxColumnWidth = maxColumnWidth; } /** @@ -129,7 +141,7 @@ public class CommandLineHelpGenerator { data[2] = (String) defaults.get(id); parameterTable[i++] = data; } - model.put("parameters", TableGenerator.generate(parameterTable)); + model.put("parameters", TableGenerator.generate(parameterTable, false, this.maxColumnWidth)); return model; } @@ -153,7 +165,7 @@ public class CommandLineHelpGenerator { data[2] = (String) defaults.get(id); parameterTable[i++] = data; } - model.put("parameters", TableGenerator.generate(parameterTable)); + model.put("parameters", TableGenerator.generate(parameterTable, false, this.maxColumnWidth)); return model; } @@ -169,7 +181,7 @@ public class CommandLineHelpGenerator { data[2] = dep.getVersionRequirement(); dependencyTable[i++] = data; } - return TableGenerator.generate(dependencyTable); + return TableGenerator.generate(dependencyTable, true, this.maxColumnWidth); } protected String generateTypeTable(InitializrMetadata metadata, String linkHeader, boolean addTags) { @@ -191,7 +203,7 @@ public class CommandLineHelpGenerator { } typeTable[i++] = data; } - return TableGenerator.generate(typeTable); + return TableGenerator.generate(typeTable, false, this.maxColumnWidth); } protected Map buildParametersDescription(InitializrMetadata metadata) { @@ -222,35 +234,48 @@ public class CommandLineHelpGenerator { */ private static class TableGenerator { - static final String NEW_LINE = System.getProperty("line.separator"); - /** * Generate a table description for the specified {@code content}. *

* The {@code content} is a two-dimensional array holding the rows of the table. * The first entry holds the header of the table. * @param content the table content + * @param emptyRow add an empty row separator + * @param maxWidth the width bound for each column * @return the generated table */ - static String generate(String[][] content) { + static String generate(String[][] content, boolean emptyRow, int maxWidth) { StringBuilder sb = new StringBuilder(); - int[] columnsLength = computeColumnsLength(content); + int[] columnsLength = computeColumnsLength(content, maxWidth); appendTableSeparation(sb, columnsLength); - appendRow(sb, content, columnsLength, 0); // Headers + appendRow(sb, content, columnsLength, 0, maxWidth); // Headers appendTableSeparation(sb, columnsLength); for (int i = 1; i < content.length; i++) { - appendRow(sb, content, columnsLength, i); + appendRow(sb, content, columnsLength, i, maxWidth); + if (emptyRow && i < content.length - 1) { + appendEmptyRow(sb, columnsLength); + } } appendTableSeparation(sb, columnsLength); return sb.toString(); } - private static void appendRow(StringBuilder sb, String[][] content, int[] columnsLength, int rowIndex) { - String[] row = content[rowIndex]; - if (row != null) { + private static void appendRow(StringBuilder sb, String[][] content, int[] columnsLength, int rowIndex, + int maxWidth) { + String[] line = content[rowIndex]; + List rows = HelpFormatter.format(line, maxWidth); + for (String[] row : rows) { for (int i = 0; i < row.length; i++) { sb.append("| ").append(fill(row[i], columnsLength[i])).append(" "); } + sb.append("|"); + sb.append(NEW_LINE); + } + } + + private static void appendEmptyRow(StringBuilder sb, int[] columnsLength) { + for (int columnLength : columnsLength) { + sb.append("| ").append(fill(null, columnLength)).append(" "); } sb.append("|"); sb.append(NEW_LINE); @@ -282,16 +307,16 @@ public class CommandLineHelpGenerator { return s.toString(); } - private static int[] computeColumnsLength(String[][] content) { + private static int[] computeColumnsLength(String[][] content, int maxWidth) { int count = content[0].length; int[] result = new int[count]; for (int i = 0; i < count; i++) { - result[i] = largest(content, i); + result[i] = largest(content, i, maxWidth); } return result; } - private static int largest(String[][] content, int column) { + private static int largest(String[][] content, int column, int maxWidth) { int max = 0; for (String[] rows : content) { if (rows != null) { @@ -301,9 +326,65 @@ public class CommandLineHelpGenerator { } } } + return Math.min(max, maxWidth); + } + + } + + private static class HelpFormatter { + + /** + * Formats a given content to a max width. + * @param content the content to format. + * @param maxWidth the max width of each column + * @return the formatted rows. + */ + private static List format(String[] content, int maxWidth) { + List columns = lineWrap(content, maxWidth); + List rows = new ArrayList<>(); + for (int i = 0; i < largest(columns); ++i) { + rows.add(computeRow(columns, i)); + } + return rows; + } + + private static String[] computeRow(List columns, int index) { + String[] line = new String[columns.size()]; + int position = 0; + for (String[] column : columns) { + line[position] = itemOrNull(column, index); + position++; + } + return line; + } + + private static List lineWrap(String[] content, int maxWidth) { + List lineWrapped = new ArrayList<>(); + for (String column : content) { + if (column == null) { + lineWrapped.add(new String[0]); + } + else { + lineWrapped.add(WordUtils.wrap(column, maxWidth).split(NEW_LINE)); + } + } + return lineWrapped; + } + + private static int largest(List columns) { + int max = 0; + for (String[] column : columns) { + if (max < column.length) { + max = column.length; + } + } return max; } + private static String itemOrNull(String[] column, int index) { + return (index >= column.length) ? null : column[index]; + } + } } diff --git a/initializr-web/src/test/java/io/spring/initializr/web/support/CommandLineHelpGeneratorTests.java b/initializr-web/src/test/java/io/spring/initializr/web/support/CommandLineHelpGeneratorTests.java index 6fc094fa..724eb5c5 100755 --- a/initializr-web/src/test/java/io/spring/initializr/web/support/CommandLineHelpGeneratorTests.java +++ b/initializr-web/src/test/java/io/spring/initializr/web/support/CommandLineHelpGeneratorTests.java @@ -18,13 +18,13 @@ package io.spring.initializr.web.support; import java.io.IOException; import java.util.Arrays; +import java.util.List; import io.spring.initializr.generator.io.template.MustacheTemplateRenderer; import io.spring.initializr.generator.spring.test.InitializrMetadataTestBuilder; import io.spring.initializr.metadata.Dependency; import io.spring.initializr.metadata.InitializrMetadata; import io.spring.initializr.metadata.Type; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; @@ -36,12 +36,9 @@ import static org.assertj.core.api.Assertions.assertThat; */ class CommandLineHelpGeneratorTests { - private CommandLineHelpGenerator generator; + private static final MustacheTemplateRenderer template = new MustacheTemplateRenderer("classpath:/templates"); - @BeforeEach - void init() { - this.generator = new CommandLineHelpGenerator(new MustacheTemplateRenderer("classpath:/templates")); - } + private CommandLineHelpGenerator generator = new CommandLineHelpGenerator(template); @Test void generateGenericCapabilities() throws IOException { @@ -94,6 +91,41 @@ class CommandLineHelpGeneratorTests { assertThat(content).contains("curl https://fake-service"); } + @Test + void generateGeneralCapabilitiesWithDefaultLineWrap() throws IOException { + CommandLineHelpGenerator lineWrapTemplateGenerator = new CommandLineHelpGenerator(template); + InitializrMetadata metadata = InitializrMetadataTestBuilder.withDefaults() + .addDependencyGroup("test", createDependency("id-a", "Short description"), createDependency("id-b", + "Version control for your database so you can migrate from any version (incl. an empty database) to the latest version of the schema.")) + .build(); + String content = lineWrapTemplateGenerator.generateGenericCapabilities(metadata, "https://fake-service"); + assertCommandLineCapabilities(content); + assertThat(readAllLines(content)).containsSequence( + "| id-a | Short description | |", + "| | | |", + "| id-b | Version control for your database so you can migrate from | |", + "| | any version (incl. an empty database) to the latest version | |", + "| | of the schema. | |"); + assertThat(content).contains("https://fake-service"); + } + + @Test + void generateGeneralCapabilitiesWithCustomLineWrap() throws IOException { + CommandLineHelpGenerator lineWrapTemplateGenerator = new CommandLineHelpGenerator(template, 100); + InitializrMetadata metadata = InitializrMetadataTestBuilder.withDefaults() + .addDependencyGroup("test", createDependency("id-a", "Short description"), createDependency("id-b", + "Version control for your database so you can migrate from any version (incl. an empty database) to the latest version of the schema.")) + .build(); + String content = lineWrapTemplateGenerator.generateGenericCapabilities(metadata, "https://fake-service"); + assertCommandLineCapabilities(content); + assertThat(readAllLines(content)).containsSequence( + "| id-a | Short description | |", + "| | | |", + "| id-b | Version control for your database so you can migrate from any version (incl. an empty database) to | |", + "| | the latest version of the schema. | |"); + assertThat(content).contains("https://fake-service"); + } + @Test void generateHttpCapabilities() throws IOException { InitializrMetadata metadata = InitializrMetadataTestBuilder.withDefaults().addDependencyGroup("test", @@ -160,4 +192,9 @@ class CommandLineHelpGeneratorTests { return dependency; } + private static List readAllLines(String source) { + String[] lines = source.split("\\r?\\n"); + return Arrays.asList(lines); + } + } diff --git a/pom.xml b/pom.xml index 96f81a66..6bcf3c06 100644 --- a/pom.xml +++ b/pom.xml @@ -42,6 +42,7 @@ ${basedir} UTF-8 1.18 + 1.7 5.4.2 3.6.1 1.3.3 @@ -111,6 +112,11 @@ commons-compress ${commons-compress.version} + + org.apache.commons + commons-text + ${commons-text.version} + org.apache.maven maven-resolver-provider