Java library for creating console commands
Is there a common library which helps to 开发者_如何转开发implement commands entered on a Java console program? I don't mean a library for parsing Java command line options like Commons CLI. I am talking about creating commands which are used in a running Java program entered into the console:
> connect 127.0.01
connected!
> load poem.txt 23
loading....
Something like this - I could write it myself, but the question is, whether there is already a library.
I ended up using inheritance of an abstract class Command. It has execute() and with Java reflections I parse the input and receive the command class from its first input argument:
connect ... -> ConnectCommand
You could use a parser to do this. You want to take some free form text and convert it into appropriate Java objects that represent the expression the user typed in.
There are many libraries available from the heavyweight (AntLR for example) to something simpler (like JParsec). You have also always got the option of doing it manually with regular expressions and so on.
You might want to look at JLine. It is similar to BSD editline and GNU readline.
I once started a similar project when I needed something like this but didn't really finish.
There is naturalcli though. Maybe this works for you.
I've a nice desgin pattern for doing stuff like this, which does not involve any library and is very simple to achieve.
Steps:
1. Create an enum class like below using regex to parse the commands.
2. Create a main-loop, which is reading from the console parsing the command to that enum from 1. over and over.
3. Use a switch-case to dispatch the command to the right method.
ad 1.) The enum-class where you have all your commands. You could also externalize those strings if you want. For the rawCommand it should be immutable. Better would be, creating a class which contains that rawCommand and the enum as fields.
package issue.system.core;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import issue.system.model.IssueState;
/**
* Command enumeration containing plenty of useful methods to parse commands
* @author Alessandro Giusa
* @version 1.0
*
*/
public enum Command {
INIT("^init$"),
ISSUE("^issue #([A-Za-z0-9_])+$"),
OPEN("^open #([A-Za-z0-9_])+$"),
SHOW("^show(( #([A-Za-z0-9_])+( ([0-9])+)?)|( #([A-Za-z0-9_])+ tags)|( t([A-Za-z0-9_])+)(,t([A-Za-z0-9_])+)*)?( -s[0-9](-[0-9]+)?)?( -f (and|or))?$"),
REMOVE("^remove(( #([A-Za-z0-9_])+( t([A-Za-z0-9_])+)?)|(( t([A-Za-z0-9_])+)(,t([A-Za-z0-9_])+)* (-f (and|or)))|( released))$"),
RENAME("^rename( #([A-Za-z0-9_])+ #([A-Za-z0-9_])+)|(( t([A-Za-z0-9_])+ t([A-Za-z0-9_])+))$"),
STATE("^state #([A-Za-z0-9_])+ (" + IssueState.getRegexForCommand() + "$"),
HELP("^help$"),
QUIT("(^quit)|(^q)$"),
QUIT_AND_COMMIT("(^qc)$"),
TAG("^tag(( #([A-Za-z0-9_])+( t([A-Za-z0-9_])+)(,t([A-Za-z0-9_])+)*)|(( t([A-Za-z0-9_])+)(,t([A-Za-z0-9_])+)*)|( -r( t([A-Za-z0-9_])+)(,t([A-Za-z0-9_])+)*))?$"),
CLEAR("^clear$"),
GIT("^git( commit)?$"),
PATCH_NOTES("^patchnotes$")
;
public static final String DELIMITER_COMMAND_TOKEN_COMMAND_TEXT = " ";
private final String regex;
private String rawCommand;
private Command(final String regex) {
this.regex = regex;
}
public String getRegex() {
return this.regex;
}
public String getRawCommand() {
return this.rawCommand;
}
public void setRawCommand(final String rawCommand) {
this.rawCommand = rawCommand;
}
/**
* Parse the given raw string into a command if there is one corresponding to
* @param rawString
* @return option to command
*/
public static Optional<Command> parseCommand(final String rawString) {
Matcher matcher;
for (final Command nextCommand : values()) {
matcher = Pattern.compile(nextCommand.getRegex()).matcher(rawString);
if (matcher.find()) {
final String rawCommand = rawString.substring(
rawString.indexOf(DELIMITER_COMMAND_TOKEN_COMMAND_TEXT, 0)
+ 1, rawString.length());
nextCommand.setRawCommand(rawCommand);
return Optional.of(nextCommand);
}
}
return Optional.empty();
}
}
This is a class I use in my command line issue system.
ad 2.) The main loop which is reading from command line over and over. The Terminal class is just a convenient class for sys-out stuff.
@Override
public void run() {
this.running = true;
while (this.running) {
final String line = Terminal.readLine(CURSOR);
if(line == null) {
Terminal.println("cmd line was empty");
continue;
}
final Optional<Command> optCommand = Command.parseCommand(line);
if (optCommand.isPresent()) {
if (optCommand.get() == Command.INIT || this.loadIssueMetadata(optCommand.get())) {
this.processCommand(optCommand.get());
}
} else {
Terminal.printf("the given command %s is not a vaild. Use <help> to see all commands.", line);
}
}
System.exit(0);
}
ad 3.) And the switch-case part where you dispatch the command to the right method to process.
void processCommand(final Command command) {
switch (command) {
case INIT:
this.init();
break;
case ISSUE:
this.issue(command.getRawCommand());
break;
case SHOW:
this.show(command.getRawCommand());
break;
case QUIT:
this.quit();
break;
case STATE:
this.state(command.getRawCommand());
break;
case REMOVE:
this.remove(command.getRawCommand());
break;
case RENAME:
this.rename(command.getRawCommand());
break;
case HELP:
usage();
break;
case TAG:
this.tag(command.getRawCommand());
break;
case OPEN:
this.open(command.getRawCommand());
break;
case CLEAR:
this.cls();
break;
case GIT:
this.git(command.getRawCommand());
break;
case QUIT_AND_COMMIT:
this.quitAndCommit();
break;
case PATCH_NOTES:
this.patchNotes();
break;
default:
break;
}
}
I hope you got the idea. If you are more interested let me know!
Good programming!
精彩评论