Skip to content
This repository was archived by the owner on Jul 6, 2023. It is now read-only.

Commit 4e94b2e

Browse files
authored
Merge pull request #161 from sherfert/1.1-prompt
Prompt silently if the output is redirected.
2 parents f4c4954 + 78c1500 commit 4e94b2e

File tree

3 files changed

+184
-54
lines changed

3 files changed

+184
-54
lines changed

cypher-shell/src/integration-test/java/org/neo4j/shell/MainIntegrationTest.java

Lines changed: 39 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package org.neo4j.shell;
22

3+
import org.junit.Before;
34
import org.junit.Test;
45
import org.neo4j.shell.cli.CliArgs;
56
import org.neo4j.shell.log.AnsiLogger;
@@ -16,39 +17,47 @@
1617

1718
public class MainIntegrationTest {
1819

19-
@Test
20-
public void connectInteractivelyPromptsOnWrongAuthentication() throws Exception {
20+
private String inputString = String.format( "neo4j%nneo%n" );
21+
private ByteArrayOutputStream baos;
22+
private ConnectionConfig connectionConfig;
23+
private CypherShell shell;
24+
private Main main;
25+
26+
@Before
27+
public void setup() {
2128
// given
22-
// what the user inputs when prompted
23-
String inputString = String.format( "neo4j%nneo%n" );
24-
InputStream inputStream = new ByteArrayInputStream(inputString.getBytes());
29+
InputStream inputStream = new ByteArrayInputStream( inputString.getBytes() );
2530

26-
ByteArrayOutputStream baos = new ByteArrayOutputStream();
27-
PrintStream ps = new PrintStream(baos);
31+
baos = new ByteArrayOutputStream();
32+
PrintStream ps = new PrintStream( baos );
2833

29-
Main main = new Main(inputStream, ps);
34+
main = new Main( inputStream, ps );
3035

3136
CliArgs cliArgs = new CliArgs();
3237
cliArgs.setUsername("", "");
3338
cliArgs.setPassword( "", "" );
3439

3540
Logger logger = new AnsiLogger(cliArgs.getDebugMode());
3641
PrettyConfig prettyConfig = new PrettyConfig(cliArgs);
37-
ConnectionConfig connectionConfig = new ConnectionConfig(
42+
connectionConfig = new ConnectionConfig(
3843
cliArgs.getScheme(),
3944
cliArgs.getHost(),
4045
cliArgs.getPort(),
4146
cliArgs.getUsername(),
4247
cliArgs.getPassword(),
4348
cliArgs.getEncryption());
4449

45-
CypherShell shell = new CypherShell(logger, prettyConfig);
50+
shell = new CypherShell(logger, prettyConfig);
51+
}
4652

53+
54+
@Test
55+
public void promptsOnWrongAuthenticationIfInteractive() throws Exception {
4756
// when
4857
assertEquals("", connectionConfig.username());
4958
assertEquals("", connectionConfig.password());
5059

51-
main.connectMaybeInteractively(shell, connectionConfig, true);
60+
main.connectMaybeInteractively(shell, connectionConfig, true, true);
5261

5362
// then
5463
// should be connected
@@ -60,4 +69,23 @@ public void connectInteractivelyPromptsOnWrongAuthentication() throws Exception
6069
String out = baos.toString();
6170
assertEquals( String.format( "username: neo4j%npassword: ***%n" ), out );
6271
}
72+
73+
@Test
74+
public void promptsSilentlyOnWrongAuthenticationIfOutputRedirected() throws Exception {
75+
// when
76+
assertEquals("", connectionConfig.username());
77+
assertEquals("", connectionConfig.password());
78+
79+
main.connectMaybeInteractively(shell, connectionConfig, true, false);
80+
81+
// then
82+
// should be connected
83+
assertTrue(shell.isConnected());
84+
// should have prompted silently and set the username and password
85+
assertEquals("neo4j", connectionConfig.username());
86+
assertEquals("neo", connectionConfig.password());
87+
88+
String out = baos.toString();
89+
assertEquals( "", out );
90+
}
6391
}

cypher-shell/src/main/java/org/neo4j/shell/Main.java

Lines changed: 40 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
import org.neo4j.shell.build.Build;
66
import org.neo4j.shell.cli.CliArgHelper;
77
import org.neo4j.shell.cli.CliArgs;
8-
import org.neo4j.shell.cli.Format;
98
import org.neo4j.shell.commands.CommandHelper;
109
import org.neo4j.shell.exception.CommandException;
1110
import org.neo4j.shell.log.AnsiLogger;
@@ -15,9 +14,11 @@
1514
import javax.annotation.Nonnull;
1615
import javax.annotation.Nullable;
1716
import java.io.InputStream;
17+
import java.io.OutputStream;
1818
import java.io.PrintStream;
1919

2020
import static org.neo4j.shell.ShellRunner.isInputInteractive;
21+
import static org.neo4j.shell.ShellRunner.isOutputInteractive;
2122

2223
public class Main {
2324
static final String NEO_CLIENT_ERROR_SECURITY_UNAUTHORIZED = "Neo.ClientError.Security.Unauthorized";
@@ -72,7 +73,7 @@ void startShell(@Nonnull CliArgs cliArgs) {
7273
try {
7374
CypherShell shell = new CypherShell(logger, prettyConfig);
7475
// Can only prompt for password if input has not been redirected
75-
connectMaybeInteractively(shell, connectionConfig, isInputInteractive());
76+
connectMaybeInteractively(shell, connectionConfig, isInputInteractive(), isOutputInteractive());
7677

7778
// Construct shellrunner after connecting, due to interrupt handling
7879
ShellRunner shellRunner = ShellRunner.getShellRunner(cliArgs, shell, logger, connectionConfig);
@@ -92,9 +93,20 @@ void startShell(@Nonnull CliArgs cliArgs) {
9293
/**
9394
* Connect the shell to the server, and try to handle missing passwords and such
9495
*/
95-
void connectMaybeInteractively(@Nonnull CypherShell shell, @Nonnull ConnectionConfig connectionConfig,
96-
boolean interactively)
96+
void connectMaybeInteractively(@Nonnull CypherShell shell,
97+
@Nonnull ConnectionConfig connectionConfig,
98+
boolean inputInteractive,
99+
boolean outputInteractive)
97100
throws Exception {
101+
102+
OutputStream outputStream = outputInteractive ? out : new ThrowawayOutputStream();
103+
104+
ConsoleReader consoleReader = new ConsoleReader(in, outputStream);
105+
// Disable expansion of bangs: !
106+
consoleReader.setExpandEvents(false);
107+
// Ensure Reader does not handle user input for ctrl+C behaviour
108+
consoleReader.setHandleUserInterrupt(false);
109+
98110
try {
99111
shell.connect(connectionConfig);
100112
} catch (AuthenticationException e) {
@@ -103,19 +115,24 @@ void connectMaybeInteractively(@Nonnull CypherShell shell, @Nonnull ConnectionCo
103115
throw e;
104116
}
105117
// else need to prompt for username and password
106-
if (interactively) {
118+
if (inputInteractive) {
107119
if (connectionConfig.username().isEmpty()) {
108-
connectionConfig.setUsername(promptForNonEmptyText("username", null));
120+
String username = outputInteractive ?
121+
promptForNonEmptyText("username", consoleReader, null) :
122+
promptForText("username", consoleReader, null);
123+
connectionConfig.setUsername(username);
109124
}
110125
if (connectionConfig.password().isEmpty()) {
111-
connectionConfig.setPassword(promptForText("password", '*'));
126+
connectionConfig.setPassword(promptForText("password", consoleReader, '*'));
112127
}
113128
// try again
114129
shell.connect(connectionConfig);
115130
} else {
116131
// Can't prompt because input has been redirected
117132
throw e;
118133
}
134+
} finally {
135+
consoleReader.close();
119136
}
120137
}
121138

@@ -129,40 +146,41 @@ void connectMaybeInteractively(@Nonnull CypherShell shell, @Nonnull ConnectionCo
129146
* in case of errors
130147
*/
131148
@Nonnull
132-
private String promptForNonEmptyText(@Nonnull String prompt, @Nullable Character mask) throws Exception {
133-
String text = promptForText(prompt, mask);
149+
private String promptForNonEmptyText(@Nonnull String prompt, @Nonnull ConsoleReader consoleReader, @Nullable Character mask) throws Exception {
150+
String text = promptForText(prompt, consoleReader, mask);
134151
if (!text.isEmpty()) {
135152
return text;
136153
}
137-
out.println(prompt + " cannot be empty");
138-
out.println();
139-
return promptForNonEmptyText(prompt, mask);
154+
consoleReader.println( prompt + " cannot be empty" );
155+
consoleReader.println();
156+
return promptForNonEmptyText(prompt, consoleReader, mask);
140157
}
141158

142159
/**
143160
* @param prompt
144161
* to display to the user
145162
* @param mask
146163
* single character to display instead of what the user is typing, use null if text is not secret
164+
* @param consoleReader
165+
* the reader
147166
* @return the text which was entered
148167
* @throws Exception
149168
* in case of errors
150169
*/
151170
@Nonnull
152-
private String promptForText(@Nonnull String prompt, @Nullable Character mask) throws Exception {
153-
String line;
154-
ConsoleReader consoleReader = new ConsoleReader(in, out);
155-
// Disable expansion of bangs: !
156-
consoleReader.setExpandEvents(false);
157-
// Ensure Reader does not handle user input for ctrl+C behaviour
158-
consoleReader.setHandleUserInterrupt(false);
159-
line = consoleReader.readLine(prompt + ": ", mask);
160-
consoleReader.close();
161-
171+
private String promptForText(@Nonnull String prompt, @Nonnull ConsoleReader consoleReader, @Nullable Character mask) throws Exception {
172+
String line = consoleReader.readLine(prompt + ": ", mask);
162173
if (line == null) {
163174
throw new CommandException("No text could be read, exiting...");
164175
}
165176

166177
return line;
167178
}
179+
180+
private static class ThrowawayOutputStream extends OutputStream {
181+
@Override
182+
public void write( int b )
183+
{
184+
}
185+
}
168186
}

0 commit comments

Comments
 (0)