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 {
|
||||
OptionParser parser = new OptionParser();
|
||||
parser.formatHelpWith(new BisqHelpFormatter());
|
||||
parser.accepts(HELP_KEY, "This help text").forHelp();
|
||||
|
||||
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