Полностью переписан алгоритм
This commit is contained in:
@@ -5,29 +5,23 @@ import jline.console.completer.Completer;
|
|||||||
import jline.console.completer.StringsCompleter;
|
import jline.console.completer.StringsCompleter;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author DmitriyMX <mail@dmitriymx.ru>
|
||||||
|
* 2015
|
||||||
|
*/
|
||||||
public class CommandCompleter implements Completer {
|
public class CommandCompleter implements Completer {
|
||||||
private ArgumentCompleter.ArgumentDelimiter delimiter = new ArgumentCompleter.WhitespaceArgumentDelimiter();
|
protected StringsCompleter stringsCompleter = new StringsCompleter();
|
||||||
private Completer commandNamesCompleter;
|
|
||||||
|
|
||||||
public CommandCompleter(Set<String> commandList) {
|
|
||||||
commandNamesCompleter = new StringsCompleter(commandList);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int complete(String buffer, int cursor, List<CharSequence> candidates) {
|
public int complete(String buffer, int cursor, List<CharSequence> candidates) {
|
||||||
ArgumentCompleter.ArgumentList parseCommandLine = parseLine(buffer, cursor);
|
ArgumentCompleter.ArgumentList parseLine = Shell.DELIMITER.delimit(buffer, cursor);
|
||||||
int cursorArgument = parseCommandLine.getCursorArgumentIndex();
|
int cursorArgumentIndex = parseLine.getCursorArgumentIndex();
|
||||||
|
|
||||||
if (cursorArgument < 0) {
|
if (cursorArgumentIndex < 0) {
|
||||||
return -1;
|
return -1;
|
||||||
} else {
|
} else {
|
||||||
return commandNamesCompleter.complete(buffer, cursor, candidates);
|
return stringsCompleter.complete(buffer, cursor, candidates);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public ArgumentCompleter.ArgumentList parseLine(String buffer, int cursor) {
|
|
||||||
return delimiter.delimit(buffer, cursor);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
75
src/main/java/ru/dmitriymx/shell/CommandLoop.java
Normal file
75
src/main/java/ru/dmitriymx/shell/CommandLoop.java
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
package ru.dmitriymx.shell;
|
||||||
|
|
||||||
|
import jline.console.ConsoleReader;
|
||||||
|
import org.fusesource.jansi.Ansi;
|
||||||
|
import ru.dmitriymx.shell.commands.Command;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author DmitriyMX <mail@dmitriymx.ru>
|
||||||
|
* 2015
|
||||||
|
*/
|
||||||
|
public class CommandLoop implements Runnable {
|
||||||
|
private static final String[] EMPTY_ARGS = new String[0];
|
||||||
|
private static final String RED = Ansi.ansi().fgBright(Ansi.Color.RED).toString();
|
||||||
|
private ConsoleReader console;
|
||||||
|
private boolean run;
|
||||||
|
protected Map<String, Command> commandMap = new HashMap<>();
|
||||||
|
|
||||||
|
public CommandLoop(ConsoleReader consoleReader) {
|
||||||
|
console = consoleReader;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
Thread loop = Thread.currentThread();
|
||||||
|
run = true;
|
||||||
|
|
||||||
|
while (run && !loop.isInterrupted()) {
|
||||||
|
try {
|
||||||
|
String line;
|
||||||
|
if ((line = console.readLine()) != null) {
|
||||||
|
String[] parseLine = Shell.DELIMITER.delimit(line.trim(), console.getCursorBuffer().cursor).getArguments();
|
||||||
|
if (parseLine.length == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
String comandName = parseLine[0].toLowerCase();
|
||||||
|
if (commandMap.containsKey(comandName)) {
|
||||||
|
Command command = commandMap.get(comandName);
|
||||||
|
|
||||||
|
if (parseLine.length == 1) {
|
||||||
|
command.execute(EMPTY_ARGS);
|
||||||
|
} else {
|
||||||
|
String[] args = new String[parseLine.length - 1];
|
||||||
|
System.arraycopy(parseLine, 1, args, 0, args.length);
|
||||||
|
command.execute(args);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
System.err.println(RED + "Unknown command \"" + comandName + "\"" + Ansi.ansi().reset());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
// чтобы не нагружать процессор
|
||||||
|
safeSleep(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void shutdown() {
|
||||||
|
run = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void safeSleep(long millis) {
|
||||||
|
try {
|
||||||
|
Thread.sleep(millis);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
//ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
package ru.dmitriymx.shell;
|
|
||||||
|
|
||||||
public interface IShellCommand {
|
|
||||||
|
|
||||||
public String getName();
|
|
||||||
|
|
||||||
public void execute(final String[] args);
|
|
||||||
}
|
|
||||||
@@ -1,124 +1,78 @@
|
|||||||
package ru.dmitriymx.shell;
|
package ru.dmitriymx.shell;
|
||||||
|
|
||||||
import jline.console.ConsoleReader;
|
import jline.console.ConsoleReader;
|
||||||
|
import jline.console.completer.ArgumentCompleter;
|
||||||
|
import ru.dmitriymx.shell.commands.Command;
|
||||||
|
import ru.dmitriymx.shell.commands.ExitCommand;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.HashMap;
|
import java.io.PrintStream;
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Командная оболочка
|
* @author DmitriyMX <mail@dmitriymx.ru>
|
||||||
|
* 2015
|
||||||
*/
|
*/
|
||||||
public class Shell implements Runnable {
|
public class Shell {
|
||||||
private Thread shellThread;
|
public static final ArgumentCompleter.ArgumentDelimiter DELIMITER = new ArgumentCompleter.WhitespaceArgumentDelimiter();
|
||||||
private String prompt;
|
|
||||||
private Map<String, IShellCommand> commandList = new HashMap<>();
|
private PrintStream sysOut, sysErr;
|
||||||
|
private ShellPrintStream newErr;
|
||||||
|
private String promt;
|
||||||
|
private ConsoleReader console;
|
||||||
|
private CommandLoop commandLoop;
|
||||||
private CommandCompleter commandCompleter;
|
private CommandCompleter commandCompleter;
|
||||||
private final String[] emptyArray = new String[0];
|
|
||||||
protected ConsoleReader cReader;
|
|
||||||
protected boolean runned = false;
|
|
||||||
|
|
||||||
/**
|
public void start() throws IOException, InterruptedException {
|
||||||
* Создание командной оболочки
|
overrideSysErr();
|
||||||
* @throws IOException
|
|
||||||
*/
|
console = new ConsoleReader(System.in, sysErr);
|
||||||
public Shell() throws IOException {
|
if (promt == null) promt = ":";
|
||||||
cReader = new ConsoleReader(System.in, System.out);
|
console.setPrompt(ConsoleReader.RESET_LINE + promt);
|
||||||
cReader.setExpandEvents(false);
|
console.addCompleter((commandCompleter = new CommandCompleter()));
|
||||||
|
newErr.setConsoleReader(console);
|
||||||
|
commandLoop = new CommandLoop(console);
|
||||||
|
|
||||||
|
if (!commandLoop.commandMap.containsKey("exit")) {
|
||||||
|
addCommand(new ExitCommand());
|
||||||
|
}
|
||||||
|
|
||||||
|
Thread loopCommandReader = new Thread(commandLoop, "Command reader loop");
|
||||||
|
loopCommandReader.join();
|
||||||
|
loopCommandReader.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public void shutdown() {
|
||||||
* Установить текст приглашения
|
commandLoop.shutdown();
|
||||||
* @param prompt
|
|
||||||
*/
|
newErr.setConsoleReader(null);
|
||||||
public void setPrompt(String prompt) {
|
console.shutdown();
|
||||||
this.prompt = prompt;
|
System.setOut(sysOut);
|
||||||
|
System.setErr(sysErr);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public void setPromt(String promt) { //FIXME коостыли!!
|
||||||
* Добавить команду
|
if (console == null) {
|
||||||
* @param commandName имя команды
|
this.promt = promt;
|
||||||
* @param command команда
|
} else {
|
||||||
*/
|
console.setPrompt(ConsoleReader.RESET_LINE + promt);
|
||||||
public void addCommand(String commandName, IShellCommand command) {
|
|
||||||
commandList.put(commandName.toLowerCase(), command);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Удаление команды
|
|
||||||
* @param command удаляемая команда
|
|
||||||
*/
|
|
||||||
public void removeCommand(String commandName, IShellCommand command) {
|
|
||||||
commandList.remove(commandName);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Запуск командной оболочки
|
|
||||||
*/
|
|
||||||
public void start() {
|
|
||||||
shellThread = new Thread(this, "Shell Thread");
|
|
||||||
try {
|
|
||||||
runned = true;
|
|
||||||
shellThread.join();
|
|
||||||
shellThread.start();
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public void addCommand(Command command) {
|
||||||
* Остановка командной оболочки
|
command.setShell(this);
|
||||||
*/
|
String name = command.getName().toLowerCase();
|
||||||
public void stop() {
|
commandLoop.commandMap.put(name, command);
|
||||||
shellThread.interrupt();
|
commandCompleter.stringsCompleter.getStrings().add(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Обработчик входящих комманд
|
* Подмена стандартных SysErr и SysOut
|
||||||
*/
|
*/
|
||||||
@Override
|
private void overrideSysErr() {
|
||||||
public void run() {
|
sysOut = System.out;
|
||||||
cReader.setPrompt(prompt);
|
sysErr = System.err;
|
||||||
commandCompleter = new CommandCompleter(commandList.keySet());
|
newErr = new ShellPrintStream(sysErr);
|
||||||
cReader.addCompleter(commandCompleter);
|
System.setErr(newErr);
|
||||||
Thread currentThread = Thread.currentThread();
|
System.setOut(newErr);
|
||||||
|
|
||||||
String line;
|
|
||||||
try {
|
|
||||||
while (!currentThread.isInterrupted() && (line = cReader.readLine()) != null) {
|
|
||||||
String[] parseLine = commandCompleter.parseLine(line, cReader.getCursorBuffer().cursor).getArguments();
|
|
||||||
if (parseLine.length == 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
String commandName = parseLine[0].toLowerCase();
|
|
||||||
if (commandList.containsKey(commandName)) {
|
|
||||||
IShellCommand command = commandList.get(commandName);
|
|
||||||
|
|
||||||
if (parseLine.length == 1) {
|
|
||||||
command.execute(emptyArray);
|
|
||||||
} else {
|
|
||||||
String[] args = new String[parseLine.length - 1];
|
|
||||||
System.arraycopy(parseLine, 1, args, 0, args.length);
|
|
||||||
command.execute(args);
|
|
||||||
}
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
System.err.println(String.format("Unknow command \"%s\"", commandName));
|
|
||||||
|
|
||||||
try {
|
|
||||||
Thread.sleep(1);
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
System.err.println("Shell exception");
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
|
|
||||||
cReader.removeCompleter(commandCompleter);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
67
src/main/java/ru/dmitriymx/shell/ShellPrintStream.java
Normal file
67
src/main/java/ru/dmitriymx/shell/ShellPrintStream.java
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
package ru.dmitriymx.shell;
|
||||||
|
|
||||||
|
import jline.console.ConsoleReader;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.io.PrintStream;
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Обертка над SysOut и SysErr
|
||||||
|
*
|
||||||
|
* @author DmitriyMX <mail@dmitriymx.ru>
|
||||||
|
* 2015
|
||||||
|
*/
|
||||||
|
public class ShellPrintStream extends PrintStream {
|
||||||
|
private ConsoleReader consoleReader;
|
||||||
|
private PrintWriter writer;
|
||||||
|
|
||||||
|
public ShellPrintStream(OutputStream outputStream) {
|
||||||
|
super(outputStream, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setConsoleReader(ConsoleReader consoleReader) {
|
||||||
|
this.consoleReader = consoleReader;
|
||||||
|
|
||||||
|
if (consoleReader != null) {
|
||||||
|
this.writer = new PrintWriter(consoleReader.getOutput());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void print(String s) {
|
||||||
|
println(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void println(String s) {
|
||||||
|
if (consoleReader != null) {
|
||||||
|
writer.print(ConsoleReader.RESET_LINE);
|
||||||
|
writer.print(s);
|
||||||
|
cleanTrashLine(s);
|
||||||
|
writer.println();
|
||||||
|
try {
|
||||||
|
consoleReader.drawLine();
|
||||||
|
} catch (IOException e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
writer.flush();
|
||||||
|
} else {
|
||||||
|
super.print(ConsoleReader.RESET_LINE);
|
||||||
|
super.print(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Очистка печатной строки от мусора
|
||||||
|
*/
|
||||||
|
private void cleanTrashLine(String string) {
|
||||||
|
// очищает полностью строку
|
||||||
|
if (consoleReader.getCursorBuffer().buffer.length() + consoleReader.getPrompt().length() > string.length()) {
|
||||||
|
for (int i = string.length(); i <= consoleReader.getCursorBuffer().buffer.length() + 2; i++) {
|
||||||
|
writer.print(' ');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package ru.dmitriymx.shell.commands;
|
||||||
|
|
||||||
|
import ru.dmitriymx.shell.Shell;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Шаблон для пользовательских команд
|
||||||
|
*
|
||||||
|
* @author DmitriyMX <mail@dmitriymx.ru>
|
||||||
|
* 2015
|
||||||
|
*/
|
||||||
|
public abstract class AbstractCommand implements Command {
|
||||||
|
private Shell shell;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Shell getShell() {
|
||||||
|
return shell;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setShell(Shell shell) {
|
||||||
|
this.shell = shell;
|
||||||
|
}
|
||||||
|
}
|
||||||
17
src/main/java/ru/dmitriymx/shell/commands/Command.java
Normal file
17
src/main/java/ru/dmitriymx/shell/commands/Command.java
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
package ru.dmitriymx.shell.commands;
|
||||||
|
|
||||||
|
import ru.dmitriymx.shell.Shell;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author DmitriyMX <mail@dmitriymx.ru>
|
||||||
|
* 2015
|
||||||
|
*/
|
||||||
|
public interface Command {
|
||||||
|
String getName();
|
||||||
|
|
||||||
|
Shell getShell();
|
||||||
|
|
||||||
|
void setShell(Shell shell);
|
||||||
|
|
||||||
|
void execute(String[] args);
|
||||||
|
}
|
||||||
19
src/main/java/ru/dmitriymx/shell/commands/ExitCommand.java
Normal file
19
src/main/java/ru/dmitriymx/shell/commands/ExitCommand.java
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
package ru.dmitriymx.shell.commands;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Выход из Shell
|
||||||
|
*
|
||||||
|
* @author DmitriyMX <mail@dmitriymx.ru>
|
||||||
|
* 2015
|
||||||
|
*/
|
||||||
|
public class ExitCommand extends AbstractCommand {
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "exit";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(String[] args) {
|
||||||
|
getShell().shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user