Skip to content
This repository was archived by the owner on Jul 6, 2023. It is now read-only.
Closed
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
2 changes: 1 addition & 1 deletion cypher-shell/src/dist/cypher-shell
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,6 @@ if [ -z "${JARPATH}" ]; then
exit 1
fi

exec "$JAVA_CMD" ${JAVA_OPTS:-} \
exec "$JAVA_CMD" ${JAVA_OPTS:-} -Dterminal.columns=${COLUMNS:-} \
-jar "$JARPATH" \
"$@"
11 changes: 8 additions & 3 deletions cypher-shell/src/main/java/org/neo4j/shell/CypherShell.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

Expand All @@ -32,7 +33,7 @@ public class CypherShell implements StatementExecuter, Connector, TransactionHan
protected CommandHelper commandHelper;

public CypherShell(@Nonnull Logger logger) {
this(logger, new BoltStateHandler(), new PrettyPrinter(logger.getFormat()));
this(logger, new BoltStateHandler(), new PrettyPrinter(logger.getFormat(), logger.getWidth(), logger.getWrap()));
}

protected CypherShell(@Nonnull Logger logger,
Expand Down Expand Up @@ -81,7 +82,11 @@ public void execute(@Nonnull final String cmdString) throws ExitException, Comma
*/
protected void executeCypher(@Nonnull final String cypher) throws CommandException {
final Optional<BoltResult> result = boltStateHandler.runCypher(cypher, queryParams);
result.ifPresent(boltResult -> logger.printOut(prettyPrinter.format(boltResult)));
result.ifPresent(boltResult -> prettyPrinter.format(boltResult, printer()));
}

private Consumer<String> printer() {
return (text) -> {if (text!=null && !text.trim().isEmpty()) logger.printOut(text);};
}

@Override
Expand Down Expand Up @@ -136,7 +141,7 @@ public void beginTransaction() throws CommandException {
@Override
public Optional<List<BoltResult>> commitTransaction() throws CommandException {
Optional<List<BoltResult>> results = boltStateHandler.commitTransaction();
results.ifPresent(boltResult -> boltResult.forEach(result -> logger.printOut(prettyPrinter.format(result))));
results.ifPresent(boltResult -> boltResult.forEach(result -> prettyPrinter.format(result, printer())));
return results;
}

Expand Down
2 changes: 2 additions & 0 deletions cypher-shell/src/main/java/org/neo4j/shell/Main.java
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ private Logger instantiateLogger(@Nonnull CliArgs cliArgs) {
if (cliArgs.isStringShell() && Format.AUTO.equals(cliArgs.getFormat())) {
logger.setFormat(Format.PLAIN);
}
logger.setWidth(cliArgs.getWidth());
logger.setWrap(cliArgs.getWrap());
return logger;
}

Expand Down
21 changes: 18 additions & 3 deletions cypher-shell/src/main/java/org/neo4j/shell/ShellRunner.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,7 @@
import javax.annotation.Nonnull;
import java.io.IOException;

import static org.fusesource.jansi.internal.CLibrary.STDIN_FILENO;
import static org.fusesource.jansi.internal.CLibrary.STDOUT_FILENO;
import static org.fusesource.jansi.internal.CLibrary.isatty;
import static org.fusesource.jansi.internal.CLibrary.*;
import static org.neo4j.shell.system.Utils.isWindows;

public interface ShellRunner {
Expand Down Expand Up @@ -93,6 +91,23 @@ static boolean isInputInteractive() {
}
}

static int ttyColumns() {
String cols = System.getProperty("terminal.columns");
if (cols != null && !cols.trim().isEmpty()) return Integer.parseInt(cols);
if (isOutputInteractive()) {
try {
WinSize winSize = new WinSize();
if (ioctl(STDOUT_FILENO, TIOCSWINSZ, winSize) == 0) {
System.err.printf("row %d col %d px x %d px y %d%n",winSize.ws_row,winSize.ws_col,winSize.ws_xpixel,winSize.ws_ypixel);
if (winSize.ws_col > 0) return winSize.ws_col;
}
} catch (Throwable ignored) {
// system is not using libc (like Alpine Linux)
}
}
return -1;
}

/**
* Checks if STDOUT is a TTY. In case TTY checking is not possible (lack of libc), then the check falls back to
* the built in Java {@link System#console()} which checks if EITHER STDIN or STDOUT has been redirected.
Expand Down
30 changes: 25 additions & 5 deletions cypher-shell/src/main/java/org/neo4j/shell/cli/CliArgHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,7 @@
import net.sourceforge.argparse4j.impl.action.StoreTrueArgumentAction;
import net.sourceforge.argparse4j.impl.choice.CollectionArgumentChoice;
import net.sourceforge.argparse4j.impl.type.BooleanArgumentType;
import net.sourceforge.argparse4j.inf.ArgumentGroup;
import net.sourceforge.argparse4j.inf.ArgumentParser;
import net.sourceforge.argparse4j.inf.ArgumentParserException;
import net.sourceforge.argparse4j.inf.MutuallyExclusiveGroup;
import net.sourceforge.argparse4j.inf.Namespace;
import net.sourceforge.argparse4j.inf.*;

import java.io.PrintWriter;
import java.util.regex.Matcher;
Expand Down Expand Up @@ -88,6 +84,9 @@ public static CliArgs parse(@Nonnull String... args) {

cliArgs.setNonInteractive(ns.getBoolean("force-non-interactive"));

cliArgs.setWidth(ns.getInt("width"));
cliArgs.setWrap(ns.getBoolean("wrap"));

cliArgs.setVersion(ns.getBoolean("version"));

return cliArgs;
Expand Down Expand Up @@ -165,6 +164,15 @@ private static ArgumentParser setupParser()
.dest("force-non-interactive")
.action(new StoreTrueArgumentAction());

parser.addArgument("--width")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deeply disagree with this design decision. The program should output a sensible width automatically

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

neo4j-client has a similar option, :set width=??? (a cli option, allowing width changes at runtime, rather than a command line switch). It also accepts :set width=auto, which checks the terminal window size and sets the size based on that (auto is the default). Assuming Java supports finding the termwidth, perhaps a width option (with default) is a neat solution here too?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

well if I were designing this, I'd have a single switch: --wrap[=[false|true|INT]] where true would be some kind of automatic width (or sensible value).

.help("terminal width, only for table format")
.type(new WidthArgumentType())
.setDefault(-1);
parser.addArgument("--wrap")
.help("wrap table colum values if table is too narrow, default true")
.type(new BooleanArgumentType())
.setDefault(true);

parser.addArgument("-v", "--version")
.help("print version of cypher-shell and exit")
.action(new StoreTrueArgumentAction());
Expand All @@ -177,4 +185,16 @@ private static ArgumentParser setupParser()
}


private static class WidthArgumentType implements ArgumentType<Integer> {
@Override
public Integer convert(ArgumentParser parser, Argument arg, String value) throws ArgumentParserException {
try {
int result = Integer.parseInt(value);
if (result < 1) throw new NumberFormatException(value);
return result;
} catch (NumberFormatException nfe) {
throw new ArgumentParserException("Invalid width value: "+value,parser);
}
}
}
}
23 changes: 23 additions & 0 deletions cypher-shell/src/main/java/org/neo4j/shell/cli/CliArgs.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package org.neo4j.shell.cli;

import org.neo4j.shell.ShellRunner;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Optional;
Expand All @@ -17,6 +19,8 @@ public class CliArgs {
private boolean debugMode;
private boolean nonInteractive = false;
private boolean version = false;
private int width = ShellRunner.ttyColumns();
private boolean wrap = true;

/**
* Set the scheme to the primary value, or if null, the fallback value.
Expand Down Expand Up @@ -158,4 +162,23 @@ public void setVersion(boolean version) {
public boolean isStringShell() {
return cypher.isPresent();
}

public void setWidth(Integer width) {
if (width != null && width > 0) {
this.width = width;
}
}

@Nonnull
public int getWidth() {
return width;
}

public boolean getWrap() {
return wrap;
}

public void setWrap(boolean wrap) {
this.wrap = wrap;
}
}
27 changes: 24 additions & 3 deletions cypher-shell/src/main/java/org/neo4j/shell/log/AnsiLogger.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import org.neo4j.shell.exception.AnsiFormattedException;

import javax.annotation.Nonnull;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.nio.charset.StandardCharsets;
Expand All @@ -23,6 +24,8 @@ public class AnsiLogger implements Logger {
private final PrintStream err;
private final boolean debug;
private Format format;
private int width;
private boolean wrap = true;

public AnsiLogger(final boolean debug) {
this(debug, Format.VERBOSE, System.out, System.err);
Expand Down Expand Up @@ -66,6 +69,19 @@ private static boolean isOutputInteractive() {
return 1 == isatty(STDOUT_FILENO) && 1 == isatty(STDERR_FILENO);
}

public int getWidth() {
return width != -1 || isOutputInteractive() ? width : -1;
}

@Override
public boolean getWrap() {
return wrap;
}

public void setWrap(boolean wrap) {
this.wrap = wrap;
}

@Nonnull
@Override
public PrintStream getOutputStream() {
Expand Down Expand Up @@ -94,6 +110,11 @@ public boolean isDebugEnabled() {
return debug;
}

@Override
public void setWidth(int width) {
this.width = width;
}

@Override
public void printError(@Nonnull Throwable throwable) {
printError(getFormattedMessage(throwable));
Expand Down Expand Up @@ -131,9 +152,9 @@ String getFormattedMessage(@Nonnull final Throwable e) {
cause.getMessage() != null && cause.getMessage().contains("Missing username")) {
// Username and password was not specified
msg = msg.append(cause.getMessage())
.append("\nPlease specify --username, and optionally --password, as argument(s)")
.append("\nor as environment variable(s), NEO4J_USERNAME, and NEO4J_PASSWORD respectively.")
.append("\nSee --help for more info.");
.append("\nPlease specify --username, and optionally --password, as argument(s)")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

System.lineBreak()

.append("\nor as environment variable(s), NEO4J_USERNAME, and NEO4J_PASSWORD respectively.")
.append("\nSee --help for more info.");
} else {
if (cause.getMessage() != null) {
msg = msg.append(cause.getMessage());
Expand Down
7 changes: 7 additions & 0 deletions cypher-shell/src/main/java/org/neo4j/shell/log/Logger.java
Original file line number Diff line number Diff line change
Expand Up @@ -92,4 +92,11 @@ default void printIfPlain(@Nonnull String text) {
printOut(text);
}
}

void setWidth(int width);
int getWidth();

boolean getWrap();

void setWrap(boolean wrap);
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import org.neo4j.shell.state.BoltResult;

import java.util.*;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import javax.annotation.Nonnull;

Expand All @@ -24,15 +25,18 @@
*/
public interface OutputFormatter {

enum Capablities {info, plan, result, footer, statistics}

String COMMA_SEPARATOR = ", ";
String COLON_SEPARATOR = ": ";
String COLON = ":";
String SPACE = " ";
String NEWLINE = System.getProperty("line.separator");

@Nonnull String format(@Nonnull BoltResult result);
void format(@Nonnull BoltResult result, @Nonnull Consumer<String> output);

@Nonnull default String formatValue(@Nonnull final Value value) {
@Nonnull default String formatValue(final Value value) {
if (value == null) return "";
TypeRepresentation type = (TypeRepresentation) value.type();
switch (type.constructor()) {
case LIST_TyCon:
Expand Down Expand Up @@ -161,6 +165,7 @@ static boolean isNotBlank(String string) {
return "";
}

Set<Capablities> capabilities();

List<String> INFO = asList("Version", "Planner", "Runtime");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@

import javax.annotation.Nonnull;

import static java.util.Arrays.asList;
import java.util.Set;
import java.util.function.Consumer;

/**
* Print the result from neo4j in a intelligible fashion.
Expand All @@ -14,17 +15,19 @@ public class PrettyPrinter {
private final StatisticsCollector statisticsCollector;
private final OutputFormatter outputFormatter;

public PrettyPrinter(@Nonnull Format format) {
public PrettyPrinter(@Nonnull Format format, int width, boolean wrap) {
this.statisticsCollector = new StatisticsCollector(format);
this.outputFormatter = format == Format.VERBOSE ? new TableOutputFormatter() : new SimpleOutputFormatter();
this.outputFormatter = format == Format.VERBOSE ? new TableOutputFormatter(width, wrap) : new SimpleOutputFormatter();
}

public String format(@Nonnull final BoltResult result) {
String infoOutput = outputFormatter.formatInfo(result.getSummary());
String planOutput = outputFormatter.formatPlan(result.getSummary());
String statistics = statisticsCollector.collect(result.getSummary());
String resultOutput = outputFormatter.format(result);
String footer = outputFormatter.formatFooter(result);
return OutputFormatter.joinNonBlanks(OutputFormatter.NEWLINE, asList(infoOutput, planOutput, resultOutput, footer, statistics));
public void format(@Nonnull final BoltResult result, Consumer<String> outputConsumer) {
Set<OutputFormatter.Capablities> capabilities = outputFormatter.capabilities();

if (capabilities.contains(OutputFormatter.Capablities.result)) outputFormatter.format(result,outputConsumer);

if (capabilities.contains(OutputFormatter.Capablities.info)) outputConsumer.accept(outputFormatter.formatInfo(result.getSummary()));
if (capabilities.contains(OutputFormatter.Capablities.plan)) outputConsumer.accept(outputFormatter.formatPlan(result.getSummary()));
if (capabilities.contains(OutputFormatter.Capablities.footer)) outputConsumer.accept(outputFormatter.formatFooter(result));
if (capabilities.contains(OutputFormatter.Capablities.statistics)) outputConsumer.accept(statisticsCollector.collect(result.getSummary()));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,26 @@
import org.neo4j.driver.v1.summary.ResultSummary;
import org.neo4j.shell.state.BoltResult;

import java.util.List;
import javax.annotation.Nonnull;
import java.util.EnumSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Collectors;

import javax.annotation.Nonnull;

public class SimpleOutputFormatter implements OutputFormatter {

@Override
@Nonnull
public String format(@Nonnull final BoltResult result) {
StringBuilder sb = new StringBuilder();
List<Record> records = result.getRecords();
if (!records.isEmpty()) {
sb.append(records.get(0).keys().stream().collect(Collectors.joining(COMMA_SEPARATOR)));
sb.append("\n");
sb.append(records.stream().map(this::formatRecord).collect(Collectors.joining("\n")));
public void format(@Nonnull BoltResult result, @Nonnull Consumer<String> output) {
Iterator<Record> records = result.iterate();
if (records.hasNext()) {
Record firstRow = records.next();
output.accept(String.join(COMMA_SEPARATOR,firstRow.keys()));
output.accept(formatRecord(firstRow));
while (records.hasNext()) {
output.accept(formatRecord(records.next()));
}
}
return sb.toString();
}

@Nonnull
Expand All @@ -38,4 +39,9 @@ public String formatInfo(@Nonnull ResultSummary summary) {
Map<String, Value> info = OutputFormatter.info(summary);
return info.entrySet().stream().map( e -> String.format("%s: %s",e.getKey(),e.getValue())).collect(Collectors.joining(NEWLINE));
}

@Override
public Set<Capablities> capabilities() {
return EnumSet.of(Capablities.info, Capablities.statistics, Capablities.result);
}
}
Loading