Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Redesign CLI help output #1961

Merged
merged 3 commits into from
Nov 24, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
279 changes: 177 additions & 102 deletions core/src/main/java/bisq/core/app/BisqExecutable.java

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions core/src/main/java/bisq/core/app/BisqHeadlessAppMain.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

import bisq.common.UserThread;
import bisq.common.app.AppModule;
import bisq.common.app.Version;
import bisq.common.setup.CommonSetup;

import joptsimple.OptionSet;
Expand All @@ -36,6 +37,10 @@
public class BisqHeadlessAppMain extends BisqExecutable {
protected HeadlessApp headlessApp;

public BisqHeadlessAppMain() {
super("Bisq Daemon", "bisqd", Version.VERSION);
}

public static void main(String[] args) throws Exception {
if (BisqExecutable.setupInitialOptionParser(args)) {
// For some reason the JavaFX launch process results in us losing the thread context class loader: reset it.
Expand Down
131 changes: 131 additions & 0 deletions core/src/main/java/bisq/core/app/BisqHelpFormatter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
/*
* 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 {

private final String fullName;
private final String scriptName;
private final String version;

public BisqHelpFormatter(String fullName, String scriptName, String version) {
this.fullName = fullName;
this.scriptName = scriptName;
this.version = version;
}

public String format(Map<String, ? extends OptionDescriptor> descriptors) {

StringBuilder output = new StringBuilder();
output.append(String.format("%s version %s\n\n", fullName, version));
output.append(String.format("Usage: %s [options]\n\n", scriptName));
output.append("Options:\n\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());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ public abstract class ExecutableForAppWithP2p extends BisqExecutable implements
private volatile boolean stopped;
private static long maxMemory = MAX_MEMORY_MB_DEFAULT;

public ExecutableForAppWithP2p(String fullName, String scriptName, String version) {
super(fullName, scriptName, version);
}

@Override
protected void configUserThread() {
final ThreadFactory threadFactory = new ThreadFactoryBuilder()
Expand Down
134 changes: 134 additions & 0 deletions core/src/test/java/bisq/core/app/BisqHelpFormatterTest.java
Original file line number Diff line number Diff line change
@@ -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("Bisq Test", "bisq-test", "0.1.0"));

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}
}
57 changes: 57 additions & 0 deletions core/src/test/resources/bisq/core/app/cli-output.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
Bisq Test version 0.1.0

Usage: bisq-test [options]

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

5 changes: 5 additions & 0 deletions desktop/src/main/java/bisq/desktop/app/BisqAppMain.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@

import bisq.common.UserThread;
import bisq.common.app.AppModule;
import bisq.common.app.Version;
import bisq.common.proto.persistable.PersistedDataHost;
import bisq.common.setup.CommonSetup;

Expand All @@ -39,6 +40,10 @@
public class BisqAppMain extends BisqExecutable {
private BisqApp application;

public BisqAppMain() {
super("Bisq Desktop", "bisq-desktop", Version.VERSION);
}

/* @Nullable
private BisqHttpApiServer bisqHttpApiServer;*/
/* @Nullable
Expand Down
Loading