Skip to content

Commit

Permalink
HelpConfigCommand: Help for configs #27
Browse files Browse the repository at this point in the history
* CONFIGURATION section
  • Loading branch information
andrus committed Dec 27, 2016
1 parent 76800bf commit a4e059a
Show file tree
Hide file tree
Showing 13 changed files with 350 additions and 35 deletions.
2 changes: 1 addition & 1 deletion bootique/src/main/java/io/bootique/Bootique.java
Expand Up @@ -429,7 +429,7 @@ public String name() {
public BQModule.Builder moduleBuilder() {
return BQModuleProvider.super
.moduleBuilder()
.description("Bootique core module");
.description("Bootique core module.");
}
};
}
Expand Down
Expand Up @@ -2,7 +2,7 @@

import io.bootique.annotation.BQConfig;
import io.bootique.annotation.BQConfigProperty;
import io.bootique.module.ConfigMetadata;
import io.bootique.module.ConfigObjectMetadata;
import io.bootique.module.ConfigPropertyMetadata;
import io.bootique.module.ModuleMetadata;

Expand Down Expand Up @@ -53,14 +53,14 @@ private ModuleMetadata toModuleMetadata(BQModule module) {
.build();
}

private Collection<ConfigMetadata> toConfigs(BQModule module) {
private Collection<ConfigObjectMetadata> toConfigs(BQModule module) {

Map<String, Class<?>> configTypes = module.getConfigs();
if (configTypes.isEmpty()) {
return Collections.emptyList();
}

Collection<ConfigMetadata> configs = new ArrayList<>();
Collection<ConfigObjectMetadata> configs = new ArrayList<>();

configTypes.forEach((prefix, type) -> {
configs.add(toConfig(prefix, type));
Expand All @@ -69,9 +69,9 @@ private Collection<ConfigMetadata> toConfigs(BQModule module) {
return configs;
}

private ConfigMetadata toConfig(String name, Class<?> type) {
private ConfigObjectMetadata toConfig(String name, Class<?> type) {

ConfigMetadata.Builder builder = ConfigMetadata.builder()
ConfigObjectMetadata.Builder builder = ConfigObjectMetadata.builder()
.name(name)
.type(type);

Expand Down
71 changes: 64 additions & 7 deletions bootique/src/main/java/io/bootique/help/FormattedAppender.java
Expand Up @@ -30,27 +30,69 @@ public class FormattedAppender {

private Appendable out;
private int lineWidth;
private String baseOffset;

private int sectionCount;
private int subsectionCount;
// TODO: get rid of appender state in favor of "withOffset"
private transient int sectionCount;
private transient int subsectionCount;

public FormattedAppender(Appendable out, int lineWidth) {

if (lineWidth < MIN_LINE_WIDTH) {
throw new IllegalArgumentException("Line width is too small. Minimal supported width is " + MIN_LINE_WIDTH);
}

this.baseOffset = "";
this.out = out;
this.lineWidth = lineWidth;
}

protected FormattedAppender(FormattedAppender proto) {
out = proto.out;
lineWidth = proto.lineWidth;
baseOffset = proto.baseOffset;

// do not copy "transient" properties
}

private static List<String> asList(String... parts) {
return parts != null ? Arrays.asList(parts) : Collections.emptyList();
}

/**
* Creates and returns a new appender with base offset equals to default text offset.
*
* @return a new appender with base offset equals to default text offset.
* @since 0.21
*/
public FormattedAppender withOffset() {
return withOffset(TEXT_OFFSET.length());
}

/**
* Creates and returns a new appender with the specified base offset.
*
* @return a new appender with the specified base offset.
* @since 0.21
*/
public FormattedAppender withOffset(int offset) {
FormattedAppender offsetAppender = new FormattedAppender(this);

if (offset > 0) {
StringBuilder padding = new StringBuilder(baseOffset);
for (int i = 0; i < offset; i++) {
padding.append(" ");
}

offsetAppender.baseOffset = padding.toString();
}

return offsetAppender;
}

public void printSectionName(String name) {

// line break between sections
if (sectionCount++ > 0) {
subsectionCount = 0;
println(NO_OFFSET, Collections.emptyList());
Expand All @@ -64,13 +106,16 @@ public void printSubsectionHeader(String... parts) {
}

public void printSubsectionHeader(Collection<String> parts) {

// line break between subsections
if (subsectionCount++ > 0) {
println(NO_OFFSET, Collections.emptyList());
}

println(TEXT_OFFSET, parts);
}


public void printText(String... parts) {
println(TEXT_OFFSET, asList(parts));
}
Expand All @@ -80,31 +125,43 @@ public void printText(Collection<String> parts) {
}

public void printDescription(String... parts) {
foldWithOffset(DESCRIPTION_OFFSET, parts)
foldWithOffset(DESCRIPTION_OFFSET.length(), parts)
.forEach(s -> println(DESCRIPTION_OFFSET, Collections.singleton(s)));
}

public void println() {
try {
out.append(NEWLINE);
} catch (IOException e) {
throw new RuntimeException("Error printing help", e);
}
}

protected void println(String offset, Collection<String> parts) {

try {
out.append(baseOffset);
out.append(offset);
for (String p : parts) {
out.append(p);
}
out.append(NEWLINE);
} catch (IOException e) {
throw new RuntimeException("Error printing help", e);
}

println();
}

private Collection<String> foldWithOffset(String offset, String... parts) {
protected Collection<String> foldWithOffset(int offset, String... parts) {

offset += baseOffset.length();

if (offset.length() > DESCRIPTION_OFFSET.length()) {
if (offset > DESCRIPTION_OFFSET.length()) {
throw new IllegalArgumentException("Offset is too big: " + offset
+ ". Can't fit the text in remaining space.");
}

int maxLength = lineWidth - offset.length();
int maxLength = lineWidth - offset;

List<String> folded = new ArrayList<>();
StringBuilder line = new StringBuilder();
Expand Down
@@ -0,0 +1,143 @@
package io.bootique.help.config;

import io.bootique.help.FormattedAppender;
import io.bootique.module.ConfigMetadataNode;
import io.bootique.module.ConfigObjectMetadata;
import io.bootique.module.ConfigMetadataVisitor;
import io.bootique.module.ConfigPropertyMetadata;

import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

/**
* @since 0.21
*/
class ConfigSectionGenerator implements ConfigMetadataVisitor<Object> {

private FormattedAppender out;

public ConfigSectionGenerator(FormattedAppender out) {
this.out = Objects.requireNonNull(out);
}

@Override
public Object visitConfigMetadata(ConfigObjectMetadata metadata) {
printTypeHeader(metadata);
out.printText(metadata.getName(), ":");

List<ConfigMetadataNode> sortedChildren = metadata.getProperties()
.stream()
.sorted(Comparator.comparing(ConfigPropertyMetadata::getName))
.collect(Collectors.toList());

if (sortedChildren.isEmpty()) {
return null;
}

ConfigMetadataNode last = sortedChildren.get(sortedChildren.size() - 1);

ConfigSectionGenerator childGenerator = withOffset();
sortedChildren.forEach(p -> {
p.accept(childGenerator);

if (p != last) {
out.println();
}
});

return null;
}

@Override
public Object visitConfigPropertyMetadata(ConfigPropertyMetadata metadata) {

printTypeHeader(metadata);

if (metadata.getType() != null) {
out.printText(metadata.getName(), ": ", sampleValue(metadata.getType()));
} else {
out.printText(metadata.getName(), ": ?");
}
return null;
}

protected ConfigSectionGenerator withOffset() {
return new ConfigSectionGenerator(out.withOffset());
}


protected void printTypeHeader(ConfigPropertyMetadata metadata) {
Class<?> valueType = metadata.getType();

if (valueType != null) {
out.printText("# Type: ", typeLabel(valueType));
}

if (metadata.getDescription() != null) {
out.printText("# ", metadata.getDescription());
}
}

protected String sampleValue(Class<?> type) {

// TODO: allow to provide sample values in metadata, so that we can display something useful

String typeName = type.getTypeName();

switch (typeName) {
case "boolean":
case "java.lang.Boolean":
return "false";
case "int":
case "java.lang.Integer":
return "100";
case "byte":
case "java.lang.Byte":
return "1";
case "double":
case "java.lang.Double":
return "double";
case "float":
case "java.lang.Float":
return "1.1";
case "short":
case "java.lang.Short":
return "1";
case "long":
case "java.lang.Long":
return "10000000";
case "java.lang.String":
return "'string'";
default:
return "value";
}
}

protected String typeLabel(Class<?> type) {

String typeName = type.getTypeName();

switch (typeName) {
case "java.lang.Boolean":
return "boolean";
case "java.lang.Integer":
return "int";
case "java.lang.Byte":
return "byte";
case "java.lang.Double":
return "double";
case "java.lang.Float":
return "float";
case "java.lang.Short":
return "short";
case "java.lang.Long":
return "long";
case "java.lang.String":
return "String";
default:
return typeName;
}
}
}

0 comments on commit a4e059a

Please sign in to comment.