Skip to content

Commit 8db6076

Browse files
authored
Add better support for command line arguments (#702)
1 parent 8317182 commit 8db6076

File tree

8 files changed

+342
-34
lines changed

8 files changed

+342
-34
lines changed

build.gradle

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,7 @@ project(":core") {
260260
testCompile group: 'org.apache.httpcomponents', name: 'httpclient', version: '4.5.2'
261261
testCompile group: 'org.apache.httpcomponents', name: 'httpcore', version: '4.2.2'
262262
testCompile group: 'org.apache.httpcomponents', name: 'httpmime', version: '4.5.2'
263+
compile group: 'commons-cli', name: 'commons-cli', version: '1.3.1'
263264
// We use the no_aop version of Guice because the aop isn't avaiable in arm java
264265
// http://stackoverflow.com/a/15235190/3708426
265266
// https://github.com/google/guice/wiki/OptionalAOP
@@ -275,6 +276,12 @@ project(":core") {
275276

276277
mainClassName = 'edu.wpi.grip.core.Main'
277278

279+
if (project.hasProperty('args')) {
280+
run {
281+
args = (project.args.split("\\s+") as List)
282+
}
283+
}
284+
278285
jar {
279286
manifest {
280287
attributes 'Implementation-Version': version, 'Main-Class': mainClassName
@@ -438,6 +445,12 @@ project(":ui") {
438445
}
439446
}
440447

448+
if (project.hasProperty('args')) {
449+
run {
450+
args = (project.args.split("\\s+") as List)
451+
}
452+
}
453+
441454
task testSharedLib() {
442455
description 'Compiles the shared library used by c++ generation testing.'
443456
doLast {
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
package edu.wpi.grip.core;
2+
3+
import com.google.common.annotations.VisibleForTesting;
4+
5+
import org.apache.commons.cli.CommandLine;
6+
import org.apache.commons.cli.DefaultParser;
7+
import org.apache.commons.cli.HelpFormatter;
8+
import org.apache.commons.cli.Option;
9+
import org.apache.commons.cli.Options;
10+
import org.apache.commons.cli.ParseException;
11+
12+
import java.util.List;
13+
14+
/**
15+
* A helper class for command line options for GRIP.
16+
*/
17+
public class CoreCommandLineHelper {
18+
19+
public static final String FILE_OPTION = "f"; // "f" for "file"
20+
public static final String PORT_OPTION = "p"; // "p" for "port"
21+
public static final String HELP_OPTION = "h"; // "h" for "help" (this is standard)
22+
public static final String VERSION_OPTION = "v"; // "v" for "version" (this is standard)
23+
24+
private final Options options = new Options();
25+
private static final Option saveOption =
26+
Option.builder(FILE_OPTION)
27+
.longOpt("file")
28+
.desc("Set the GRIP save file to load")
29+
.hasArg()
30+
.numberOfArgs(1)
31+
.argName("path")
32+
.build();
33+
private static final Option portOption =
34+
Option.builder(PORT_OPTION)
35+
.longOpt("port")
36+
.desc("Set the port to run the HTTP server on")
37+
.hasArg()
38+
.numberOfArgs(1)
39+
.argName("port")
40+
.build();
41+
private static final Option helpOption
42+
= new Option(HELP_OPTION, "help", false, "Prints the command line options");
43+
private static final Option versionOption
44+
= new Option(VERSION_OPTION, "version", false, "Prints the version of GRIP");
45+
46+
/**
47+
* Creates a new core commandline helper with the standard options.
48+
*/
49+
public CoreCommandLineHelper() {
50+
options.addOption(saveOption);
51+
options.addOption(portOption);
52+
options.addOption(helpOption);
53+
options.addOption(versionOption);
54+
}
55+
56+
/**
57+
* Creates a command line helper with all the standard options, plus any additional options
58+
* required by subclasses.
59+
*
60+
* @param additionalOptions additional command line arguments
61+
*/
62+
protected CoreCommandLineHelper(Option... additionalOptions) {
63+
this();
64+
for (Option o : additionalOptions) {
65+
options.addOption(o);
66+
}
67+
}
68+
69+
/**
70+
* Parses an array of command line arguments. If there are unknown arguments or the arguments are
71+
* otherwise malformed, a help message will be printed and the application will exit without
72+
* returning from this method. This will also occur if the help option is specified.
73+
*
74+
* @param args the command line arguments to parse
75+
*
76+
* @return a CommandLine object that can be queried for command line options and their values
77+
*/
78+
@SuppressWarnings({"checkstyle:regexp", "PMD.SystemPrintln"})
79+
public CommandLine parse(String... args) {
80+
try {
81+
DefaultParser parser = new DefaultParser();
82+
CommandLine parsed = parser.parse(options, args);
83+
if (parsed.hasOption(HELP_OPTION)) {
84+
printHelpAndExit();
85+
} else if (parsed.hasOption(VERSION_OPTION)) {
86+
printVersionAndExit();
87+
}
88+
return parsed;
89+
} catch (ParseException e) {
90+
System.out.println("Incorrect command line arguments: " + e.getMessage());
91+
printHelpAndExit();
92+
}
93+
return null; // This is OK -- will only happen in tests
94+
}
95+
96+
/**
97+
* Parses a list of command line arguments. This coverts the list to an array and passes it to
98+
* {@link #parse(String[])}.
99+
*
100+
* @see #parse(String[])
101+
*/
102+
public CommandLine parse(List<String> args) {
103+
return parse(args.toArray(new String[args.size()]));
104+
}
105+
106+
/**
107+
* Prints a help message for the command line arguments and exits the application.
108+
*/
109+
@VisibleForTesting
110+
void printHelpAndExit() {
111+
HelpFormatter hf = new HelpFormatter();
112+
hf.printHelp("GRIP", options);
113+
exit();
114+
}
115+
116+
/**
117+
* Prints the app version and exits the application.
118+
*/
119+
@VisibleForTesting
120+
@SuppressWarnings({"checkstyle:regexp", "PMD.SystemPrintln"})
121+
void printVersionAndExit() {
122+
System.out.printf(
123+
"GRIP version %s%n",
124+
CoreCommandLineHelper.class.getPackage().getImplementationVersion()
125+
);
126+
exit();
127+
}
128+
129+
/**
130+
* Exits the app. This method only exists so testing can work.
131+
*/
132+
@VisibleForTesting
133+
void exit() {
134+
System.exit(0);
135+
}
136+
137+
}

core/src/main/java/edu/wpi/grip/core/Main.java

Lines changed: 41 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,13 @@
1212

1313
import com.google.common.eventbus.EventBus;
1414
import com.google.common.eventbus.Subscribe;
15+
import com.google.common.util.concurrent.Service;
1516
import com.google.inject.Guice;
1617
import com.google.inject.Injector;
1718
import com.google.inject.util.Modules;
1819

20+
import org.apache.commons.cli.CommandLine;
21+
1922
import java.io.File;
2023
import java.io.IOException;
2124
import java.util.logging.Level;
@@ -47,30 +50,58 @@ public class Main {
4750

4851
@SuppressWarnings("JavadocMethod")
4952
public static void main(String[] args) throws IOException, InterruptedException {
53+
new CoreCommandLineHelper().parse(args); // Check for help or version before doing anything else
5054
final Injector injector = Guice.createInjector(Modules.override(new GripCoreModule(),
5155
new GripFileModule(), new GripSourcesHardwareModule()).with(new GripNetworkModule()));
5256
injector.getInstance(Main.class).start(args);
5357
}
5458

5559
@SuppressWarnings("JavadocMethod")
5660
public void start(String[] args) throws IOException, InterruptedException {
57-
String projectPath = null;
58-
if (args.length == 1) {
59-
logger.log(Level.INFO, "Loading file " + args[0]);
60-
projectPath = args[0];
61-
}
6261

6362
operations.addOperations();
6463
cvOperations.addOperations();
6564
gripServer.addHandler(pipelineSwitcher);
66-
gripServer.start();
6765

68-
// Open a project from a .grip file specified on the command line
69-
if (projectPath != null) {
70-
project.open(new File(projectPath));
66+
CoreCommandLineHelper commandLineHelper = new CoreCommandLineHelper();
67+
CommandLine parsedArgs = commandLineHelper.parse(args);
68+
69+
// Parse the save file option
70+
if (parsedArgs.hasOption(CoreCommandLineHelper.FILE_OPTION)) {
71+
// Open a project from a .grip file specified on the command line
72+
String file = parsedArgs.getOptionValue(CoreCommandLineHelper.FILE_OPTION);
73+
logger.log(Level.INFO, "Loading file " + file);
74+
project.open(new File(file));
7175
}
7276

73-
pipelineRunner.startAsync();
77+
// Set the port AFTER loading the project to override the saved port number
78+
if (parsedArgs.hasOption(CoreCommandLineHelper.PORT_OPTION)) {
79+
try {
80+
int port = Integer.parseInt(parsedArgs.getOptionValue(CoreCommandLineHelper.PORT_OPTION));
81+
if (port < 1024 || port > 65535) {
82+
logger.warning("Not a valid port: " + port);
83+
} else {
84+
// Valid port; set it (Note: this doesn't check to see if the port is available)
85+
logger.info("Running server on port " + port);
86+
gripServer.setPort(port);
87+
}
88+
} catch (NumberFormatException e) {
89+
logger.warning(
90+
"Not a valid port: " + parsedArgs.getOptionValue(CoreCommandLineHelper.PORT_OPTION));
91+
}
92+
}
93+
94+
// This will throw an exception if the port specified by the save file or command line
95+
// argument is already taken. Since we have to have the server running to handle remotely
96+
// loading pipelines and uploading images, as well as potential HTTP publishing operations,
97+
// this will cause the program to exit.
98+
gripServer.start();
99+
100+
if (pipelineRunner.state() == Service.State.NEW) {
101+
// Loading a project will start the pipeline, so only start it if a project wasn't specified
102+
// as a command line argument.
103+
pipelineRunner.startAsync();
104+
}
74105

75106
// This is done in order to indicate to the user using the deployment UI that this is running
76107
logger.log(Level.INFO, "SUCCESS! The project is running in headless mode!");

core/src/main/java/edu/wpi/grip/core/http/GripServer.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -212,10 +212,12 @@ public State getState() {
212212
*
213213
* @param port the new port to run on.
214214
*/
215-
private void setPort(int port) {
215+
public void setPort(int port) {
216216
stop();
217217
server = serverFactory.create(port);
218+
server.setHandler(handlers);
218219
state = State.PRE_RUN;
220+
start();
219221
}
220222

221223
/**
@@ -230,7 +232,6 @@ public void settingsChanged(ProjectSettingsChangedEvent event) {
230232
int port = event.getProjectSettings().getServerPort();
231233
if (port != getPort()) {
232234
setPort(port);
233-
server.setHandler(handlers);
234235
start();
235236
}
236237
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package edu.wpi.grip.core;
2+
3+
import org.junit.Test;
4+
5+
import static org.junit.Assert.assertTrue;
6+
7+
public class CoreCommandLineHelperTest {
8+
9+
private static class MockHelper extends CoreCommandLineHelper {
10+
@Override
11+
void exit() {
12+
// NOP
13+
}
14+
}
15+
16+
@Test
17+
public void testHelp() {
18+
boolean[] printed = {false};
19+
boolean[] exited = {false};
20+
MockHelper m = new MockHelper() {
21+
@Override
22+
void printHelpAndExit() {
23+
printed[0] = true;
24+
super.printHelpAndExit();
25+
}
26+
27+
@Override
28+
void exit() {
29+
exited[0] = true;
30+
}
31+
};
32+
m.parse("-h");
33+
assertTrue("Help text was not printed", printed[0]);
34+
assertTrue("The application didn't exit", exited[0]);
35+
printed[0] = false;
36+
exited[0] = false;
37+
m.parse("--help");
38+
assertTrue("Help text was not printed", printed[0]);
39+
assertTrue("The application didn't exit", exited[0]);
40+
printed[0] = false;
41+
exited[0] = false;
42+
m.parse("--port"); // No value given, should print help text and exit
43+
assertTrue("Help text was not printed", printed[0]);
44+
assertTrue("The application didn't exit", exited[0]);
45+
}
46+
47+
@Test
48+
public void testVersion() {
49+
boolean[] printed = {false};
50+
boolean[] exited = {false};
51+
MockHelper m = new MockHelper() {
52+
@Override
53+
void printVersionAndExit() {
54+
printed[0] = true;
55+
super.printVersionAndExit();
56+
}
57+
58+
@Override
59+
void exit() {
60+
exited[0] = true;
61+
}
62+
};
63+
m.parse("-v");
64+
assertTrue("Version text was not printed", printed[0]);
65+
assertTrue("The application didn't exit", exited[0]);
66+
printed[0] = false;
67+
exited[0] = false;
68+
m.parse("--version");
69+
assertTrue("Version text was not printed", printed[0]);
70+
assertTrue("The application didn't exit", exited[0]);
71+
}
72+
73+
}

core/src/test/java/edu/wpi/grip/core/CoreSanityTest.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ public CoreSanityTest() {
2626
AddOperation.class,
2727
ManualPipelineRunner.class,
2828
SubtractionOperation.class,
29-
Main.class
29+
Main.class,
30+
CoreCommandLineHelper.class
3031
).contains(c));
3132
setDefault(OutputSocket.class, new MockOutputSocket("Mock Out"));
3233
setDefault(InputSocket.class, new MockInputSocket("Mock In"));

0 commit comments

Comments
 (0)