mirror of
https://github.com/bisq-network/bisq.git
synced 2025-02-24 07:07:43 +01:00
Introduce BisqHelpFormatter
Prior to this commit, help output for Bisq executables, e.g. Bisq Desktop itself used JOptSimple's default HelpFormatter implementation, which creates a quite cramped and hard-to-read output. This commit introduces a custom HelpFormatter implementation modeled after bitcoind's own help output. It maximizes readability while making full use of an 80-character width.
This commit is contained in:
parent
fc0491d8da
commit
2a537a601b
4 changed files with 308 additions and 0 deletions
|
@ -114,6 +114,7 @@ public abstract class BisqExecutable implements GracefulShutDownHandler {
|
||||||
|
|
||||||
public void execute(String[] args) throws Exception {
|
public void execute(String[] args) throws Exception {
|
||||||
OptionParser parser = new OptionParser();
|
OptionParser parser = new OptionParser();
|
||||||
|
parser.formatHelpWith(new BisqHelpFormatter());
|
||||||
parser.accepts(HELP_KEY, "This help text").forHelp();
|
parser.accepts(HELP_KEY, "This help text").forHelp();
|
||||||
|
|
||||||
this.customizeOptionParsing(parser);
|
this.customizeOptionParsing(parser);
|
||||||
|
|
120
core/src/main/java/bisq/core/app/BisqHelpFormatter.java
Normal file
120
core/src/main/java/bisq/core/app/BisqHelpFormatter.java
Normal file
|
@ -0,0 +1,120 @@
|
||||||
|
/*
|
||||||
|
* This file is part of Bisq.
|
||||||
|
*
|
||||||
|
* Bisq is free software: you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU Affero General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or (at
|
||||||
|
* your option) any later version.
|
||||||
|
*
|
||||||
|
* Bisq is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||||
|
* License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package bisq.core.app;
|
||||||
|
|
||||||
|
import joptsimple.HelpFormatter;
|
||||||
|
import joptsimple.OptionDescriptor;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
public class BisqHelpFormatter implements HelpFormatter {
|
||||||
|
|
||||||
|
public String format(Map<String, ? extends OptionDescriptor> descriptors) {
|
||||||
|
|
||||||
|
StringBuilder output = new StringBuilder();
|
||||||
|
output.append("Options:\n");
|
||||||
|
output.append("\n");
|
||||||
|
|
||||||
|
for (Map.Entry<String, ? extends OptionDescriptor> entry : descriptors.entrySet()) {
|
||||||
|
String optionName = entry.getKey();
|
||||||
|
OptionDescriptor optionDesc = entry.getValue();
|
||||||
|
|
||||||
|
if (optionDesc.representsNonOptions())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
output.append(String.format("%s\n", formatOptionSyntax(optionName, optionDesc)));
|
||||||
|
output.append(String.format("%s\n", formatOptionDescription(optionDesc)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return output.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String formatOptionSyntax(String optionName, OptionDescriptor optionDesc) {
|
||||||
|
StringBuilder result = new StringBuilder(String.format(" --%s", optionName));
|
||||||
|
|
||||||
|
if (optionDesc.acceptsArguments())
|
||||||
|
result.append(String.format("=<%s>", formatArgDescription(optionDesc)));
|
||||||
|
|
||||||
|
List<?> defaultValues = optionDesc.defaultValues();
|
||||||
|
if (defaultValues.size() > 0)
|
||||||
|
result.append(String.format(" (default: %s)", formatDefaultValues(defaultValues)));
|
||||||
|
|
||||||
|
return result.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String formatArgDescription(OptionDescriptor optionDesc) {
|
||||||
|
String argDescription = optionDesc.argumentDescription();
|
||||||
|
|
||||||
|
if (argDescription.length() > 0)
|
||||||
|
return argDescription;
|
||||||
|
|
||||||
|
String typeIndicator = optionDesc.argumentTypeIndicator();
|
||||||
|
|
||||||
|
if (typeIndicator == null)
|
||||||
|
return "value";
|
||||||
|
|
||||||
|
try {
|
||||||
|
Class<?> type = Class.forName(typeIndicator);
|
||||||
|
return type.isEnum() ?
|
||||||
|
Arrays.stream(type.getEnumConstants()).map(Object::toString).collect(Collectors.joining("|")) :
|
||||||
|
typeIndicator.substring(typeIndicator.lastIndexOf('.') + 1);
|
||||||
|
} catch (ClassNotFoundException ex) {
|
||||||
|
// typeIndicator is something other than a class name, which can occur
|
||||||
|
// in certain cases e.g. where OptionParser.withValuesConvertedBy is used.
|
||||||
|
return typeIndicator;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Object formatDefaultValues(List<?> defaultValues) {
|
||||||
|
return defaultValues.size() == 1 ?
|
||||||
|
defaultValues.get(0) :
|
||||||
|
defaultValues.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String formatOptionDescription(OptionDescriptor optionDesc) {
|
||||||
|
StringBuilder output = new StringBuilder();
|
||||||
|
|
||||||
|
String remainder = optionDesc.description().trim();
|
||||||
|
|
||||||
|
// Wrap description text at 80 characters with 8 spaces of indentation and a
|
||||||
|
// maximum of 72 chars of text, wrapping on spaces. Strings longer than 72 chars
|
||||||
|
// without any spaces (e.g. a URL) are allowed to overflow the 80-char margin.
|
||||||
|
while (remainder.length() > 72) {
|
||||||
|
int idxFirstSpace = remainder.indexOf(' ');
|
||||||
|
int chunkLen = idxFirstSpace == -1 ? remainder.length() : idxFirstSpace > 73 ? idxFirstSpace : 73;
|
||||||
|
String chunk = remainder.substring(0, chunkLen);
|
||||||
|
int idxLastSpace = chunk.lastIndexOf(' ');
|
||||||
|
int idxBreak = idxLastSpace > 0 ? idxLastSpace : chunk.length();
|
||||||
|
String line = remainder.substring(0, idxBreak);
|
||||||
|
output.append(formatLine(line));
|
||||||
|
remainder = remainder.substring(chunk.length() - (chunk.length() - idxBreak)).trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (remainder.length() > 0)
|
||||||
|
output.append(formatLine(remainder));
|
||||||
|
|
||||||
|
return output.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String formatLine(String line) {
|
||||||
|
return String.format(" %s\n", line.trim());
|
||||||
|
}
|
||||||
|
}
|
134
core/src/test/java/bisq/core/app/BisqHelpFormatterTest.java
Normal file
134
core/src/test/java/bisq/core/app/BisqHelpFormatterTest.java
Normal file
|
@ -0,0 +1,134 @@
|
||||||
|
/*
|
||||||
|
* This file is part of Bisq.
|
||||||
|
*
|
||||||
|
* Bisq is free software: you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU Affero General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or (at
|
||||||
|
* your option) any later version.
|
||||||
|
*
|
||||||
|
* Bisq is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||||
|
* License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package bisq.core.app;
|
||||||
|
|
||||||
|
import joptsimple.OptionParser;
|
||||||
|
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.PrintStream;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import static org.hamcrest.CoreMatchers.equalTo;
|
||||||
|
import static org.junit.Assert.assertThat;
|
||||||
|
|
||||||
|
public class BisqHelpFormatterTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testHelpFormatter() throws IOException, URISyntaxException {
|
||||||
|
|
||||||
|
OptionParser parser = new OptionParser();
|
||||||
|
|
||||||
|
parser.formatHelpWith(new BisqHelpFormatter());
|
||||||
|
|
||||||
|
parser.accepts("name",
|
||||||
|
"The name of the Bisq node")
|
||||||
|
.withRequiredArg()
|
||||||
|
.ofType(String.class)
|
||||||
|
.defaultsTo("Bisq");
|
||||||
|
|
||||||
|
parser.accepts("another-option",
|
||||||
|
"This is a long description which will need to break over multiple linessssssssssss such " +
|
||||||
|
"that no line is longer than 80 characters in the help output.")
|
||||||
|
.withRequiredArg()
|
||||||
|
.ofType(String.class)
|
||||||
|
.defaultsTo("WAT");
|
||||||
|
|
||||||
|
parser.accepts("exactly-72-char-description",
|
||||||
|
"012345678911234567892123456789312345678941234567895123456789612345678971")
|
||||||
|
.withRequiredArg()
|
||||||
|
.ofType(String.class);
|
||||||
|
|
||||||
|
parser.accepts("exactly-72-char-description-with-spaces",
|
||||||
|
" 123456789 123456789 123456789 123456789 123456789 123456789 123456789 1")
|
||||||
|
.withRequiredArg()
|
||||||
|
.ofType(String.class);
|
||||||
|
|
||||||
|
parser.accepts("90-char-description-without-spaces",
|
||||||
|
"-123456789-223456789-323456789-423456789-523456789-623456789-723456789-823456789-923456789")
|
||||||
|
.withRequiredArg()
|
||||||
|
.ofType(String.class);
|
||||||
|
|
||||||
|
parser.accepts("90-char-description-with-space-at-char-80",
|
||||||
|
"-123456789-223456789-323456789-423456789-523456789-623456789-723456789-823456789 923456789")
|
||||||
|
.withRequiredArg()
|
||||||
|
.ofType(String.class);
|
||||||
|
|
||||||
|
parser.accepts("90-char-description-with-spaces-at-chars-5-and-80",
|
||||||
|
"-123 56789-223456789-323456789-423456789-523456789-623456789-723456789-823456789 923456789")
|
||||||
|
.withRequiredArg()
|
||||||
|
.ofType(String.class);
|
||||||
|
|
||||||
|
parser.accepts("90-char-description-with-space-at-char-73",
|
||||||
|
"-123456789-223456789-323456789-423456789-523456789-623456789-723456789-8 3456789-923456789")
|
||||||
|
.withRequiredArg()
|
||||||
|
.ofType(String.class);
|
||||||
|
|
||||||
|
parser.accepts("1-char-description-with-only-a-space", " ")
|
||||||
|
.withRequiredArg()
|
||||||
|
.ofType(String.class);
|
||||||
|
|
||||||
|
parser.accepts("empty-description", "")
|
||||||
|
.withRequiredArg()
|
||||||
|
.ofType(String.class);
|
||||||
|
|
||||||
|
parser.accepts("no-description")
|
||||||
|
.withRequiredArg()
|
||||||
|
.ofType(String.class);
|
||||||
|
|
||||||
|
parser.accepts("no-arg", "Some description");
|
||||||
|
|
||||||
|
parser.accepts("optional-arg",
|
||||||
|
"Option description")
|
||||||
|
.withOptionalArg();
|
||||||
|
|
||||||
|
parser.accepts("with-default-value",
|
||||||
|
"Some option with a default value")
|
||||||
|
.withRequiredArg()
|
||||||
|
.ofType(String.class)
|
||||||
|
.defaultsTo("Wat");
|
||||||
|
|
||||||
|
parser.accepts("data-dir",
|
||||||
|
"Application data directory")
|
||||||
|
.withRequiredArg()
|
||||||
|
.ofType(File.class)
|
||||||
|
.defaultsTo(new File("/Users/cbeams/Library/Applicaton Support/Bisq"));
|
||||||
|
|
||||||
|
parser.accepts("enum-opt",
|
||||||
|
"Some option that accepts an enum value as an argument")
|
||||||
|
.withRequiredArg()
|
||||||
|
.ofType(AnEnum.class)
|
||||||
|
.defaultsTo(AnEnum.foo);
|
||||||
|
|
||||||
|
ByteArrayOutputStream actual = new ByteArrayOutputStream();
|
||||||
|
String expected = new String(Files.readAllBytes(Paths.get(getClass().getResource("cli-output.txt").toURI())));
|
||||||
|
|
||||||
|
parser.printHelpOn(new PrintStream(actual));
|
||||||
|
assertThat(actual.toString(), equalTo(expected));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
enum AnEnum {foo, bar, baz}
|
||||||
|
}
|
53
core/src/test/resources/bisq/core/app/cli-output.txt
Normal file
53
core/src/test/resources/bisq/core/app/cli-output.txt
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
Options:
|
||||||
|
|
||||||
|
--name=<String> (default: Bisq)
|
||||||
|
The name of the Bisq node
|
||||||
|
|
||||||
|
--another-option=<String> (default: WAT)
|
||||||
|
This is a long description which will need to break over multiple
|
||||||
|
linessssssssssss such that no line is longer than 80 characters in the
|
||||||
|
help output.
|
||||||
|
|
||||||
|
--exactly-72-char-description=<String>
|
||||||
|
012345678911234567892123456789312345678941234567895123456789612345678971
|
||||||
|
|
||||||
|
--exactly-72-char-description-with-spaces=<String>
|
||||||
|
123456789 123456789 123456789 123456789 123456789 123456789 123456789 1
|
||||||
|
|
||||||
|
--90-char-description-without-spaces=<String>
|
||||||
|
-123456789-223456789-323456789-423456789-523456789-623456789-723456789-823456789-923456789
|
||||||
|
|
||||||
|
--90-char-description-with-space-at-char-80=<String>
|
||||||
|
-123456789-223456789-323456789-423456789-523456789-623456789-723456789-823456789
|
||||||
|
923456789
|
||||||
|
|
||||||
|
--90-char-description-with-spaces-at-chars-5-and-80=<String>
|
||||||
|
-123
|
||||||
|
56789-223456789-323456789-423456789-523456789-623456789-723456789-823456789
|
||||||
|
923456789
|
||||||
|
|
||||||
|
--90-char-description-with-space-at-char-73=<String>
|
||||||
|
-123456789-223456789-323456789-423456789-523456789-623456789-723456789-8
|
||||||
|
3456789-923456789
|
||||||
|
|
||||||
|
--1-char-description-with-only-a-space=<String>
|
||||||
|
|
||||||
|
--empty-description=<String>
|
||||||
|
|
||||||
|
--no-description=<String>
|
||||||
|
|
||||||
|
--no-arg
|
||||||
|
Some description
|
||||||
|
|
||||||
|
--optional-arg=<value>
|
||||||
|
Option description
|
||||||
|
|
||||||
|
--with-default-value=<String> (default: Wat)
|
||||||
|
Some option with a default value
|
||||||
|
|
||||||
|
--data-dir=<File> (default: /Users/cbeams/Library/Applicaton Support/Bisq)
|
||||||
|
Application data directory
|
||||||
|
|
||||||
|
--enum-opt=<foo|bar|baz> (default: foo)
|
||||||
|
Some option that accepts an enum value as an argument
|
||||||
|
|
Loading…
Add table
Reference in a new issue