From 23a4660730a6573f04e0e9e869db8e00c9291106 Mon Sep 17 00:00:00 2001 From: DmitriyMX Date: Mon, 14 Dec 2015 11:24:10 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9F=D0=BE=D0=BB=D0=BD=D0=BE=D1=81=D1=82?= =?UTF-8?q?=D1=8C=D1=8E=20=D0=BF=D0=B5=D1=80=D0=B5=D0=BF=D0=B8=D1=81=D0=B0?= =?UTF-8?q?=D0=BD=20=D0=B0=D0=BB=D0=B3=D0=BE=D1=80=D0=B8=D1=82=D0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ru/dmitriymx/shell/CommandCompleter.java | 26 ++- .../java/ru/dmitriymx/shell/CommandLoop.java | 75 +++++++++ .../ru/dmitriymx/shell/IShellCommand.java | 8 - src/main/java/ru/dmitriymx/shell/Shell.java | 156 ++++++------------ .../ru/dmitriymx/shell/ShellPrintStream.java | 67 ++++++++ .../shell/commands/AbstractCommand.java | 23 +++ .../ru/dmitriymx/shell/commands/Command.java | 17 ++ .../dmitriymx/shell/commands/ExitCommand.java | 19 +++ 8 files changed, 266 insertions(+), 125 deletions(-) create mode 100644 src/main/java/ru/dmitriymx/shell/CommandLoop.java delete mode 100644 src/main/java/ru/dmitriymx/shell/IShellCommand.java create mode 100644 src/main/java/ru/dmitriymx/shell/ShellPrintStream.java create mode 100644 src/main/java/ru/dmitriymx/shell/commands/AbstractCommand.java create mode 100644 src/main/java/ru/dmitriymx/shell/commands/Command.java create mode 100644 src/main/java/ru/dmitriymx/shell/commands/ExitCommand.java diff --git a/src/main/java/ru/dmitriymx/shell/CommandCompleter.java b/src/main/java/ru/dmitriymx/shell/CommandCompleter.java index 7943a58..affff93 100644 --- a/src/main/java/ru/dmitriymx/shell/CommandCompleter.java +++ b/src/main/java/ru/dmitriymx/shell/CommandCompleter.java @@ -5,29 +5,23 @@ import jline.console.completer.Completer; import jline.console.completer.StringsCompleter; import java.util.List; -import java.util.Set; +/** + * @author DmitriyMX + * 2015 + */ public class CommandCompleter implements Completer { - private ArgumentCompleter.ArgumentDelimiter delimiter = new ArgumentCompleter.WhitespaceArgumentDelimiter(); - private Completer commandNamesCompleter; - - public CommandCompleter(Set commandList) { - commandNamesCompleter = new StringsCompleter(commandList); - } + protected StringsCompleter stringsCompleter = new StringsCompleter(); @Override public int complete(String buffer, int cursor, List candidates) { - ArgumentCompleter.ArgumentList parseCommandLine = parseLine(buffer, cursor); - int cursorArgument = parseCommandLine.getCursorArgumentIndex(); + ArgumentCompleter.ArgumentList parseLine = Shell.DELIMITER.delimit(buffer, cursor); + int cursorArgumentIndex = parseLine.getCursorArgumentIndex(); - if (cursorArgument < 0) { + if (cursorArgumentIndex < 0) { return -1; } 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); - } -} \ No newline at end of file +} diff --git a/src/main/java/ru/dmitriymx/shell/CommandLoop.java b/src/main/java/ru/dmitriymx/shell/CommandLoop.java new file mode 100644 index 0000000..4dd786e --- /dev/null +++ b/src/main/java/ru/dmitriymx/shell/CommandLoop.java @@ -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 + * 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 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 + } + } +} diff --git a/src/main/java/ru/dmitriymx/shell/IShellCommand.java b/src/main/java/ru/dmitriymx/shell/IShellCommand.java deleted file mode 100644 index 7187ffa..0000000 --- a/src/main/java/ru/dmitriymx/shell/IShellCommand.java +++ /dev/null @@ -1,8 +0,0 @@ -package ru.dmitriymx.shell; - -public interface IShellCommand { - - public String getName(); - - public void execute(final String[] args); -} diff --git a/src/main/java/ru/dmitriymx/shell/Shell.java b/src/main/java/ru/dmitriymx/shell/Shell.java index 9be3f84..0e2edba 100644 --- a/src/main/java/ru/dmitriymx/shell/Shell.java +++ b/src/main/java/ru/dmitriymx/shell/Shell.java @@ -1,124 +1,78 @@ package ru.dmitriymx.shell; 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.util.HashMap; -import java.util.Map; +import java.io.PrintStream; /** - * Командная оболочка + * @author DmitriyMX + * 2015 */ -public class Shell implements Runnable { - private Thread shellThread; - private String prompt; - private Map commandList = new HashMap<>(); +public class Shell { + public static final ArgumentCompleter.ArgumentDelimiter DELIMITER = new ArgumentCompleter.WhitespaceArgumentDelimiter(); + + private PrintStream sysOut, sysErr; + private ShellPrintStream newErr; + private String promt; + private ConsoleReader console; + private CommandLoop commandLoop; private CommandCompleter commandCompleter; - private final String[] emptyArray = new String[0]; - protected ConsoleReader cReader; - protected boolean runned = false; - /** - * Создание командной оболочки - * @throws IOException - */ - public Shell() throws IOException { - cReader = new ConsoleReader(System.in, System.out); - cReader.setExpandEvents(false); + public void start() throws IOException, InterruptedException { + overrideSysErr(); + + console = new ConsoleReader(System.in, sysErr); + if (promt == null) promt = ":"; + console.setPrompt(ConsoleReader.RESET_LINE + promt); + 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(); } - /** - * Установить текст приглашения - * @param prompt - */ - public void setPrompt(String prompt) { - this.prompt = prompt; + public void shutdown() { + commandLoop.shutdown(); + + newErr.setConsoleReader(null); + console.shutdown(); + System.setOut(sysOut); + System.setErr(sysErr); } - /** - * Добавить команду - * @param commandName имя команды - * @param command команда - */ - 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 setPromt(String promt) { //FIXME коостыли!! + if (console == null) { + this.promt = promt; + } else { + console.setPrompt(ConsoleReader.RESET_LINE + promt); } } - /** - * Остановка командной оболочки - */ - public void stop() { - shellThread.interrupt(); + public void addCommand(Command command) { + command.setShell(this); + String name = command.getName().toLowerCase(); + commandLoop.commandMap.put(name, command); + commandCompleter.stringsCompleter.getStrings().add(name); } /** - * Обработчик входящих комманд + * Подмена стандартных SysErr и SysOut */ - @Override - public void run() { - cReader.setPrompt(prompt); - commandCompleter = new CommandCompleter(commandList.keySet()); - cReader.addCompleter(commandCompleter); - Thread currentThread = Thread.currentThread(); - - 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); + private void overrideSysErr() { + sysOut = System.out; + sysErr = System.err; + newErr = new ShellPrintStream(sysErr); + System.setErr(newErr); + System.setOut(newErr); } } diff --git a/src/main/java/ru/dmitriymx/shell/ShellPrintStream.java b/src/main/java/ru/dmitriymx/shell/ShellPrintStream.java new file mode 100644 index 0000000..a7ebfbe --- /dev/null +++ b/src/main/java/ru/dmitriymx/shell/ShellPrintStream.java @@ -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 + * 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(' '); + } + } + } +} diff --git a/src/main/java/ru/dmitriymx/shell/commands/AbstractCommand.java b/src/main/java/ru/dmitriymx/shell/commands/AbstractCommand.java new file mode 100644 index 0000000..b80bb61 --- /dev/null +++ b/src/main/java/ru/dmitriymx/shell/commands/AbstractCommand.java @@ -0,0 +1,23 @@ +package ru.dmitriymx.shell.commands; + +import ru.dmitriymx.shell.Shell; + +/** + * Шаблон для пользовательских команд + * + * @author DmitriyMX + * 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; + } +} diff --git a/src/main/java/ru/dmitriymx/shell/commands/Command.java b/src/main/java/ru/dmitriymx/shell/commands/Command.java new file mode 100644 index 0000000..8e53429 --- /dev/null +++ b/src/main/java/ru/dmitriymx/shell/commands/Command.java @@ -0,0 +1,17 @@ +package ru.dmitriymx.shell.commands; + +import ru.dmitriymx.shell.Shell; + +/** + * @author DmitriyMX + * 2015 + */ +public interface Command { + String getName(); + + Shell getShell(); + + void setShell(Shell shell); + + void execute(String[] args); +} diff --git a/src/main/java/ru/dmitriymx/shell/commands/ExitCommand.java b/src/main/java/ru/dmitriymx/shell/commands/ExitCommand.java new file mode 100644 index 0000000..1730c9b --- /dev/null +++ b/src/main/java/ru/dmitriymx/shell/commands/ExitCommand.java @@ -0,0 +1,19 @@ +package ru.dmitriymx.shell.commands; + +/** + * Выход из Shell + * + * @author DmitriyMX + * 2015 + */ +public class ExitCommand extends AbstractCommand { + @Override + public String getName() { + return "exit"; + } + + @Override + public void execute(String[] args) { + getShell().shutdown(); + } +}