io.spring.initializr.web.support.CommandLineHelpGenerator Maven / Gradle / Ivy
Show all versions of initializr-web Show documentation
/*
* Copyright 2012-2019 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
*
* https://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 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.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import io.spring.initializr.generator.io.template.TemplateRenderer;
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;
/**
* Generate help pages for command-line clients.
*
* @author Stephane Nicoll
*/
public class CommandLineHelpGenerator {
private static final String LOGO = " . ____ _ __ _ _\n"
+ " /\\\\ / ___'_ __ _ _(_)_ __ __ _ \\ \\ \\ \\\n" + "( ( )\\___ | '_ | '_| | '_ \\/ _` | \\ \\ \\ \\\n"
+ " \\\\/ ___)| |_)| | | | | || (_| | ) ) ) )\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;
}
/**
* Generate the capabilities of the service as a generic plain text document. Used
* when no particular agent was detected.
* @param metadata the initializr metadata
* @param serviceUrl the service URL
* @return the generic capabilities text document
* @throws IOException if rendering the capabilities failed
*/
public String generateGenericCapabilities(InitializrMetadata metadata, String serviceUrl) throws IOException {
Map model = initializeCommandLineModel(metadata, serviceUrl);
model.put("hasExamples", false);
return this.template.render("cli/cli-capabilities", model);
}
/**
* Generate the capabilities of the service using "curl" as a plain text document.
* @param metadata the initializr metadata
* @param serviceUrl the service URL
* @return the generic capabilities text document
* @throws IOException if rendering the capabilities failed
*/
public String generateCurlCapabilities(InitializrMetadata metadata, String serviceUrl) throws IOException {
Map model = initializeCommandLineModel(metadata, serviceUrl);
model.put("examples", this.template.render("cli/curl-examples", model));
model.put("hasExamples", true);
return this.template.render("cli/cli-capabilities", model);
}
/**
* Generate the capabilities of the service using "HTTPie" as a plain text document.
* @param metadata the initializr metadata
* @param serviceUrl the service URL
* @return the generic capabilities text document
* @throws IOException if rendering the capabilities failed
*/
public String generateHttpieCapabilities(InitializrMetadata metadata, String serviceUrl) throws IOException {
Map model = initializeCommandLineModel(metadata, serviceUrl);
model.put("examples", this.template.render("cli/httpie-examples", model));
model.put("hasExamples", true);
return this.template.render("cli/cli-capabilities", model);
}
/**
* Generate the capabilities of the service using Spring Boot CLI as a plain text
* document.
* @param metadata the initializr metadata
* @param serviceUrl the service URL
* @return the generic capabilities text document
* @throws IOException if rendering the capabilities failed
*/
public String generateSpringBootCliCapabilities(InitializrMetadata metadata, String serviceUrl) throws IOException {
Map model = initializeSpringBootCliModel(metadata, serviceUrl);
model.put("hasExamples", false);
return this.template.render("cli/boot-cli-capabilities", model);
}
protected Map initializeCommandLineModel(InitializrMetadata metadata, String serviceUrl) {
Map model = new LinkedHashMap<>();
model.put("logo", LOGO);
model.put("serviceUrl", serviceUrl);
model.put("dependencies", generateDependencyTable(metadata));
model.put("types", generateTypeTable(metadata, "Rel", false));
Map defaults = metadata.defaults();
defaults.put("applicationName",
metadata.getConfiguration().generateApplicationName(metadata.getName().getContent()));
defaults.put("baseDir", "no base dir");
defaults.put("dependencies", "none");
Map parametersDescription = buildParametersDescription(metadata);
String[][] parameterTable = new String[defaults.size() + 1][];
parameterTable[0] = new String[] { "Parameter", "Description", "Default value" };
int i = 1;
for (String id : defaults.keySet().stream().sorted().collect(Collectors.toList())) {
String[] data = new String[3];
data[0] = id;
data[1] = (String) parametersDescription.get(id);
data[2] = (String) defaults.get(id);
parameterTable[i++] = data;
}
model.put("parameters", TableGenerator.generate(parameterTable, this.maxColumnWidth));
return model;
}
protected Map initializeSpringBootCliModel(InitializrMetadata metadata, String serviceUrl) {
Map model = new LinkedHashMap<>();
model.put("logo", LOGO);
model.put("serviceUrl", serviceUrl);
model.put("dependencies", generateDependencyTable(metadata));
model.put("types", generateTypeTable(metadata, "Id", true));
Map defaults = metadata.defaults();
Map parametersDescription = buildParametersDescription(metadata);
String[][] parameterTable = new String[defaults.size() + 1][];
parameterTable[0] = new String[] { "Id", "Description", "Default value" };
int i = 1;
for (String id : defaults.keySet().stream().sorted().collect(Collectors.toList())) {
String[] data = new String[3];
data[0] = id;
data[1] = (String) parametersDescription.get(id);
data[2] = (String) defaults.get(id);
parameterTable[i++] = data;
}
model.put("parameters", TableGenerator.generate(parameterTable, this.maxColumnWidth));
return model;
}
protected String generateDependencyTable(InitializrMetadata metadata) {
String[][] dependencyTable = new String[metadata.getDependencies().getAll().size() + 1][];
dependencyTable[0] = new String[] { "Id", "Description", "Required version" };
int i = 1;
for (Dependency dep : metadata.getDependencies().getAll().stream()
.sorted(Comparator.comparing(MetadataElement::getId)).collect(Collectors.toList())) {
String[] data = new String[3];
data[0] = dep.getId();
data[1] = (dep.getDescription() != null) ? dep.getDescription() : dep.getName();
data[2] = dep.getVersionRequirement();
dependencyTable[i++] = data;
}
return TableGenerator.generate(dependencyTable, this.maxColumnWidth);
}
protected String generateTypeTable(InitializrMetadata metadata, String linkHeader, boolean addTags) {
String[][] typeTable = new String[metadata.getTypes().getContent().size() + 1][];
if (addTags) {
typeTable[0] = new String[] { linkHeader, "Description", "Tags" };
}
else {
typeTable[0] = new String[] { linkHeader, "Description" };
}
int i = 1;
for (Type type : metadata.getTypes().getContent().stream().sorted(Comparator.comparing(MetadataElement::getId))
.collect(Collectors.toList())) {
String[] data = new String[typeTable[0].length];
data[0] = (type.isDefault() ? type.getId() + " *" : type.getId());
data[1] = (type.getDescription() != null) ? type.getDescription() : type.getName();
if (addTags) {
data[2] = buildTagRepresentation(type);
}
typeTable[i++] = data;
}
return TableGenerator.generate(typeTable, this.maxColumnWidth);
}
protected Map buildParametersDescription(InitializrMetadata metadata) {
Map result = new LinkedHashMap<>();
BeanWrapperImpl wrapper = new BeanWrapperImpl(metadata);
for (PropertyDescriptor descriptor : wrapper.getPropertyDescriptors()) {
Object value = wrapper.getPropertyValue(descriptor.getName());
BeanWrapperImpl nested = new BeanWrapperImpl(value);
if (nested.isReadableProperty("description") && nested.isReadableProperty("id")) {
result.put((String) nested.getPropertyValue("id"), nested.getPropertyValue("description"));
}
}
result.put("applicationName", "application name");
result.put("baseDir", "base directory to create in the archive");
return result;
}
private static String buildTagRepresentation(Type type) {
if (type.getTags().isEmpty()) {
return "";
}
return String.join(",", type.getTags().entrySet().stream()
.map((entry) -> entry.getKey() + ":" + entry.getValue()).toArray(String[]::new));
}
/**
* Utility to generate a text table.
*/
private static class TableGenerator {
/**
* 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 maxWidth the width bound for each column
* @return the generated table
*/
static String generate(String[][] content, int maxWidth) {
StringBuilder sb = new StringBuilder();
boolean emptyRow = false;
int[] columnsLength = computeColumnsLength(content, maxWidth);
List> formattedContent = new LinkedList<>();
for (int i = 0; i < content.length; i++) {
List rows = computeRow(content, i, maxWidth);
formattedContent.add(rows);
if (rows.size() > 1) {
emptyRow = true;
}
}
appendTableSeparation(sb, columnsLength);
appendRow(sb, formattedContent, columnsLength, 0); // Headers
appendTableSeparation(sb, columnsLength);
for (int i = 1; i < formattedContent.size(); i++) {
appendRow(sb, formattedContent, columnsLength, i);
if (emptyRow && i < content.length - 1) {
appendEmptyRow(sb, columnsLength);
}
}
appendTableSeparation(sb, columnsLength);
return sb.toString();
}
private static void appendRow(StringBuilder sb, List> formattedContent, int[] columnsLength,
int rowIndex) {
List rows = formattedContent.get(rowIndex);
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 List computeRow(String[][] content, int rowIndex, int maxWidth) {
String[] line = content[rowIndex];
List row = HelpFormatter.format(line, maxWidth);
return row;
}
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);
}
private static void appendTableSeparation(StringBuilder sb, int[] headersLength) {
for (int headerLength : headersLength) {
sb.append("+").append(multiply("-", headerLength + 2));
}
sb.append("+");
sb.append(NEW_LINE);
}
private static String fill(String data, int columnSize) {
if (data == null) {
return multiply(" ", columnSize);
}
else {
int i = columnSize - data.length();
return data + multiply(" ", i);
}
}
private static String multiply(String value, int size) {
StringBuilder s = new StringBuilder();
for (int i = 0; i < size; i++) {
s.append(value);
}
return s.toString();
}
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, maxWidth);
}
return result;
}
private static int largest(String[][] content, int column, int maxWidth) {
int max = 0;
for (String[] rows : content) {
if (rows != null) {
String s = rows[column];
if (s != null && s.length() > max) {
max = s.length();
}
}
}
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];
}
}
}