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

Commit be470b4

Browse files
authored
Merge pull request #168 from pontusmelke/1.2-source-file
Support for :source [file] in cypher-shell
2 parents 89a7004 + 5743995 commit be470b4

File tree

5 files changed

+279
-14
lines changed

5 files changed

+279
-14
lines changed

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

Lines changed: 96 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,20 @@
1414
import org.neo4j.driver.exceptions.ClientException;
1515
import org.neo4j.driver.exceptions.ServiceUnavailableException;
1616
import org.neo4j.shell.cli.CliArgs;
17+
import org.neo4j.shell.commands.CommandHelper;
18+
import org.neo4j.shell.exception.CommandException;
19+
import org.neo4j.shell.exception.ExitException;
1720
import org.neo4j.shell.log.AnsiLogger;
1821
import org.neo4j.shell.log.Logger;
1922
import org.neo4j.shell.prettyprint.LinePrinter;
2023
import org.neo4j.shell.prettyprint.PrettyConfig;
2124
import org.neo4j.shell.prettyprint.ToStringLinePrinter;
2225

2326
import static java.lang.String.format;
27+
import static org.hamcrest.CoreMatchers.isA;
2428
import static org.junit.Assert.assertEquals;
2529
import static org.junit.Assert.assertTrue;
30+
import static org.junit.Assert.fail;
2631
import static org.mockito.Matchers.any;
2732
import static org.mockito.Mockito.mock;
2833
import static org.mockito.Mockito.verify;
@@ -85,7 +90,6 @@ public void promptsOnWrongAuthenticationIfInteractive() throws Exception {
8590
assertEquals( format( "username: neo4j%npassword: ***%n" ), baos.toString() );
8691
assertEquals("neo4j", connectionConfig.username());
8792
assertEquals("neo", connectionConfig.password());
88-
8993
}
9094

9195
@Test
@@ -203,6 +207,75 @@ public void shouldHandleInvalidCypherFromFile() throws Exception {
203207
verifyNoMoreInteractions(logger);
204208
}
205209

210+
@Test
211+
public void shouldReadSingleCypherStatementsFromFileInteractively() throws Exception {
212+
// given
213+
ToStringLinePrinter linePrinter = new ToStringLinePrinter();
214+
CypherShell shell = interactiveShell( linePrinter );
215+
216+
// when
217+
shell.execute( ":source " + fileFromResource( "single.cypher" ));
218+
exit( shell );
219+
220+
// then
221+
assertEquals( format("result%n42%n"), linePrinter.result() );
222+
}
223+
224+
@Test
225+
public void shouldReadMultipleCypherStatementsFromFileInteractively() throws Exception {
226+
// given
227+
ToStringLinePrinter linePrinter = new ToStringLinePrinter();
228+
CypherShell shell = interactiveShell( linePrinter );
229+
230+
// when
231+
shell.execute( ":source " + fileFromResource( "multiple.cypher" ));
232+
exit( shell );
233+
234+
// then
235+
assertEquals(format( "result%n42%n" +
236+
"result%n1337%n" +
237+
"result%n\"done\"%n"), linePrinter.result() );
238+
}
239+
240+
@Test
241+
public void shouldReadEmptyCypherStatementsFromFileInteractively() throws Exception {
242+
// given
243+
ToStringLinePrinter linePrinter = new ToStringLinePrinter();
244+
CypherShell shell = interactiveShell( linePrinter );
245+
246+
// when
247+
shell.execute( ":source " + fileFromResource( "empty.cypher" ));
248+
exit( shell );
249+
250+
// then
251+
assertEquals("", linePrinter.result() );
252+
}
253+
254+
@Test
255+
public void shouldHandleInvalidCypherStatementsFromFileInteractively() throws Exception {
256+
// given
257+
ToStringLinePrinter linePrinter = new ToStringLinePrinter();
258+
CypherShell shell = interactiveShell( linePrinter );
259+
260+
// then
261+
exception.expect( ClientException.class );
262+
exception.expectMessage( "Invalid input 'T':" );
263+
shell.execute( ":source " + fileFromResource( "invalid.cypher" ));
264+
}
265+
266+
@Test
267+
public void shouldFailIfInputFileDoesntExistInteractively() throws Exception {
268+
// given
269+
ToStringLinePrinter linePrinter = new ToStringLinePrinter();
270+
CypherShell shell = interactiveShell( linePrinter );
271+
272+
// expect
273+
exception.expect( CommandException.class);
274+
exception.expectMessage( "Cannot find file: 'what.cypher'" );
275+
exception.expectCause( isA( FileNotFoundException.class ) );
276+
shell.execute( ":source what.cypher" );
277+
}
278+
206279
private String executeFileNonInteractively(String filename) throws Exception {
207280
return executeFileNonInteractively(filename, mock(Logger.class));
208281
}
@@ -228,6 +301,15 @@ private String fileFromResource(String filename)
228301
return getClass().getClassLoader().getResource(filename).getFile();
229302
}
230303

304+
private CypherShell interactiveShell( LinePrinter linePrinter ) throws Exception
305+
{
306+
PrettyConfig prettyConfig = new PrettyConfig( new CliArgs() );
307+
CypherShell shell = new CypherShell( linePrinter, prettyConfig, true, new ShellParameterMap() );
308+
main.connectMaybeInteractively( shell, connectionConfig, true, true );
309+
shell.setCommandHelper( new CommandHelper( mock( Logger.class ), Historian.empty, shell) );
310+
return shell;
311+
}
312+
231313
private ShellAndConnection getShell( CliArgs cliArgs )
232314
{
233315
Logger logger = new AnsiLogger( cliArgs.getDebugMode() );
@@ -248,4 +330,17 @@ private ShellAndConnection getShell( CliArgs cliArgs, LinePrinter linePrinter )
248330

249331
return new ShellAndConnection( new CypherShell( linePrinter, prettyConfig, true, new ShellParameterMap() ), connectionConfig );
250332
}
333+
334+
private void exit( CypherShell shell ) throws CommandException
335+
{
336+
try
337+
{
338+
shell.execute( ":exit" );
339+
fail("Should have exited");
340+
}
341+
catch ( ExitException e )
342+
{
343+
//do nothing
344+
}
345+
}
251346
}

cypher-shell/src/main/java/org/neo4j/shell/commands/CommandHelper.java

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,12 @@
77
import javax.annotation.Nullable;
88

99
import org.neo4j.shell.CypherShell;
10-
import org.neo4j.shell.DatabaseManager;
1110
import org.neo4j.shell.Historian;
12-
import org.neo4j.shell.TransactionHandler;
13-
import org.neo4j.shell.ParameterMap;
1411
import org.neo4j.shell.exception.CommandException;
1512
import org.neo4j.shell.exception.DuplicateCommandException;
1613
import org.neo4j.shell.log.AnsiFormattedText;
1714
import org.neo4j.shell.log.Logger;
15+
import org.neo4j.shell.parser.ShellStatementParser;
1816

1917
/**
2018
* Utility methods for dealing with commands
@@ -23,23 +21,22 @@ public class CommandHelper {
2321
private final TreeMap<String, Command> commands = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
2422

2523
public CommandHelper(Logger logger, Historian historian, CypherShell cypherShell) {
26-
registerAllCommands(logger, historian, cypherShell, cypherShell, cypherShell.getParameterMap());
24+
registerAllCommands(logger, historian, cypherShell);
2725
}
2826

2927
private void registerAllCommands(Logger logger,
3028
Historian historian,
31-
DatabaseManager databaseManager,
32-
TransactionHandler transactionHandler,
33-
ParameterMap parameterMap) {
29+
CypherShell cypherShell) {
3430
registerCommand(new Exit(logger));
3531
registerCommand(new Help(logger, this));
3632
registerCommand(new History(logger, historian));
37-
registerCommand(new Use(databaseManager));
38-
registerCommand(new Begin(transactionHandler));
39-
registerCommand(new Commit(transactionHandler));
40-
registerCommand(new Rollback(transactionHandler));
41-
registerCommand(new Param(parameterMap));
42-
registerCommand(new Params(logger, parameterMap));
33+
registerCommand(new Use(cypherShell));
34+
registerCommand(new Begin(cypherShell));
35+
registerCommand(new Commit(cypherShell));
36+
registerCommand(new Rollback(cypherShell));
37+
registerCommand(new Param(cypherShell.getParameterMap()));
38+
registerCommand(new Params(logger, cypherShell.getParameterMap()));
39+
registerCommand(new Source(cypherShell, new ShellStatementParser() ));
4340
}
4441

4542
private void registerCommand(@Nonnull final Command command) throws DuplicateCommandException {
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package org.neo4j.shell.commands;
2+
3+
import java.io.BufferedReader;
4+
import java.io.File;
5+
import java.io.FileInputStream;
6+
import java.io.IOException;
7+
import java.io.InputStreamReader;
8+
import java.util.Collections;
9+
import java.util.List;
10+
import javax.annotation.Nonnull;
11+
12+
import org.neo4j.shell.CypherShell;
13+
import org.neo4j.shell.exception.CommandException;
14+
import org.neo4j.shell.exception.ExitException;
15+
import org.neo4j.shell.parser.StatementParser;
16+
17+
import static java.lang.String.format;
18+
import static org.neo4j.shell.commands.CommandHelper.simpleArgParse;
19+
20+
/**
21+
* This command reads a cypher file frome the filesystem and executes the statements therein.
22+
*/
23+
public class Source implements Command {
24+
private static final String COMMAND_NAME = ":source";
25+
private final CypherShell cypherShell;
26+
private final StatementParser statementParser;
27+
28+
public Source( CypherShell cypherShell, StatementParser statementParser ) {
29+
this.cypherShell = cypherShell;
30+
this.statementParser = statementParser;
31+
}
32+
33+
@Nonnull
34+
@Override
35+
public String getName() {
36+
return COMMAND_NAME;
37+
}
38+
39+
@Nonnull
40+
@Override
41+
public String getDescription() {
42+
return "Interactively executes cypher statements from a file";
43+
}
44+
45+
@Nonnull
46+
@Override
47+
public String getUsage() {
48+
return "[filename]";
49+
}
50+
51+
@Nonnull
52+
@Override
53+
public String getHelp() {
54+
return "Executes Cypher statements from a file";
55+
}
56+
57+
@Nonnull
58+
@Override
59+
public List<String> getAliases() {
60+
return Collections.emptyList();
61+
}
62+
63+
@Override
64+
public void execute(@Nonnull final String argString) throws ExitException, CommandException {
65+
String filename = simpleArgParse(argString, 1, 1, COMMAND_NAME, getUsage())[0];
66+
67+
try ( BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(new FileInputStream( new File(filename) )))) {
68+
bufferedReader.lines()
69+
.forEach(line -> statementParser.parseMoreText(line + "\n"));
70+
List<String> statements = statementParser.consumeStatements();
71+
for ( String statement : statements )
72+
{
73+
cypherShell.execute( statement );
74+
}
75+
}
76+
catch ( IOException e )
77+
{
78+
throw new CommandException( format("Cannot find file: '%s'", filename), e);
79+
}
80+
}
81+
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
package org.neo4j.shell.commands;
2+
3+
import org.junit.Before;
4+
import org.junit.Rule;
5+
import org.junit.Test;
6+
import org.junit.rules.ExpectedException;
7+
8+
import java.io.FileNotFoundException;
9+
10+
import org.neo4j.shell.CypherShell;
11+
import org.neo4j.shell.exception.CommandException;
12+
import org.neo4j.shell.log.Logger;
13+
import org.neo4j.shell.parser.ShellStatementParser;
14+
15+
import static org.hamcrest.CoreMatchers.containsString;
16+
import static org.hamcrest.CoreMatchers.isA;
17+
import static org.junit.Assert.assertNotNull;
18+
import static org.mockito.Mockito.mock;
19+
import static org.mockito.Mockito.verify;
20+
import static org.mockito.Mockito.verifyNoMoreInteractions;
21+
22+
public class SourceTest
23+
{
24+
@Rule
25+
public final ExpectedException thrown = ExpectedException.none();
26+
27+
private Logger logger;
28+
private Source cmd;
29+
private CypherShell shell;
30+
31+
@Before
32+
public void setup()
33+
{
34+
logger = mock(Logger.class);
35+
shell = mock(CypherShell.class);
36+
cmd = new Source(shell, new ShellStatementParser() );
37+
}
38+
39+
@Test
40+
public void descriptionNotNull() {
41+
assertNotNull(cmd.getDescription());
42+
}
43+
44+
@Test
45+
public void usageNotNull() {
46+
assertNotNull(cmd.getUsage());
47+
}
48+
49+
@Test
50+
public void helpNotNull() {
51+
assertNotNull(cmd.getHelp());
52+
}
53+
54+
@Test
55+
public void runCommand() throws CommandException {
56+
// given
57+
cmd.execute( fileFromResource( "test.cypher" ) );
58+
verify(shell).execute( "RETURN 42;" );
59+
verifyNoMoreInteractions( shell );
60+
}
61+
62+
@Test
63+
public void shouldFailIfFileNotThere() throws CommandException
64+
{
65+
thrown.expect( CommandException.class );
66+
thrown.expectMessage(containsString("Cannot find file: 'not.there'"));
67+
thrown.expectCause(isA(FileNotFoundException.class));
68+
cmd.execute( "not.there" );
69+
}
70+
71+
@Test
72+
public void shouldNotAcceptMoreThanOneArgs() throws CommandException {
73+
thrown.expect(CommandException.class);
74+
thrown.expectMessage(containsString("Incorrect number of arguments"));
75+
76+
cmd.execute("bob sob");
77+
}
78+
79+
@Test
80+
public void shouldNotAcceptZeroArgs() throws CommandException {
81+
thrown.expect(CommandException.class);
82+
thrown.expectMessage(containsString("Incorrect number of arguments"));
83+
84+
cmd.execute("");
85+
}
86+
87+
private String fileFromResource(String filename)
88+
{
89+
return getClass().getResource(filename).getFile();
90+
}
91+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
RETURN 42;

0 commit comments

Comments
 (0)