И так как с языком программирования Groovy уже хорошо ознакомился, решил писать на нём. Но разводить огромное количество *.groovy файлов очень не хотелось, а хотелось как раз наоборот — иметь один скрипт управления back-end’ом, который уже включал бы в себя все необходимые команды, что бы можно было как во «взрослой» консольной программе иметь возможность и историю команд посмотреть и цепочку последовательно выполняемых команд задать и легко добавить, при необходимости, новые. Это хотелка ещё появилась потому, что вспомнил я про cmd, которым некогда пользовался осваивая Python. Но оказалось что для Groovy такого cmd никто не написал (позже я даже понял почему), что и подтолкнуло меня к очередному велосипедостроению, а именно к созданию небольшого Фреймворка Cli приложений на Groovy.
Целью данного поста является — получить объективную критику и предложения по дополнению моего «Фреймворка», возможно кто то захочет им воспользоваться, благо весь код, а также всю документацию я выложил на свой bitbacket аккаунт.
Основы
Началось всё с одной статьи, где автор описывал как пользоваться Python’овским cmd, собственно я не далеко ушёл в своём начинании и реализовал примерно тот же функционал, где присутствует абстрактный класс Cmd.groovy, наследовавшись от которого, мы получаем среду выполнения своего приложения. Простой пример:
файл cli.groovy:
class MyCli extends Cmd { boolean do_hello(String[] args) { if (args.length == 0) { println('Hello world!'); } else { args.each { println("Hello ${it}!"); } } return true; } } def cli = new MyCli(); if (this.args.length > 0) { cli.execute(this.args); } else { cli.start(); }
Каждый метод, который мы хотим задействовать в качестве консольной команды должен иметь строгую сигнатуру:
- Возвращаемый тип — boolean. Это сделано для объединения команд в цепочки, что бы если хоть одна команда завершится некорректно (вернёт false), то цепочка прерывалась;
- Имя метода должно начинаться с префикса "do_", как и в cmd для Python’а;
- Аргумент метода должен быть только один и типа "String[]".
В случае, если метод начинается с префикса "do_", но не выполняет одно из выше описанных условий, программа выдаст предупреждение, где будет описано почему метод не вошёл в список команд и как это это исправить.
Создав экземпляр нашего класса запустить его можно двумя способами:
1) Вызвав метод "execute(String[])", передав туда аргументы пользователя. В данном случае объект выполнит все команды, полученные при вызове, и завершит выполнение;
$ groovy -cp . cli.groovy help List of all available commands: (for reference on command print "help <command_name>") history - prints history of commands hello - No description help - prints commands info exit - for exit from CMD
2) Запустив цикл интерактивного ввода, вызвав метод "start()". При этом в терминал будет выведено приветствие и «приглашение» ввести команду. Выполнение цикла ввода будет продолжаться до тех пор, пока пользователь не введёт "exit".
$ groovy -cp . cli.groovy Welcome Print "help" for reference cmd:> help List of all available commands: (for reference on command print "help <command_name>") history - prints history of commands hello - No description help - prints commands info exit - for exit from CMD cmd:> exit Goodbye! $ _
Также, к нашему методу можно добавить аннотацию @Description, которая будет содержать краткое (brief) и полное (full) описание метода:
class MyCli extends Cmd { @Description( brief='prints greetings', full='prints greetins for all setted arguments, if arguments list is empty prints default message "Hello world!"' ) boolean do_hello(String[] args) { if (args.length == 0) { println('Hello world!'); } else { args.each { println("Hello ${it}!"); } } return true; } }
Запустив такую программу, появится возможность запросить подсказку по команде:
cmd:> help hello Command info ('hello'): prints greetins for all setted arguments, if arguments list is empty prints default message "Hello world!"
Цепочки команд
При желании команды можно объединить в цепочку, простым добавлением знака "+" между ними:
cmd:> hello Liza + hello Artem Hello Liza! Hello Artem! cmd:> _
Знак "+" стал использовать, вместо канонического двойного амперсанда из-за того, что при мгновенном запуске программы shell думал, что я пытаюсь вызвать другую команду, а не просто сеттю аргумент:
// так не сработает, shell будет искать программу hello в окружении $ groovy -cp . cli.groovy hello Liza && hello Artem // а это уже другое дело $ groovy -cp . cli.groovy hello Liza + hello Artem
Ожидаемо, что при возвращении командой значения false, цепочка оборвётся
$ groovy -cp . cli.groovy hello Liza + popa + hello Artem Hello Liza! ERROR: No such command 'popa' List of all available commands: (for reference on command print "help <command_name>") history - prints history of commands hello - No description help - prints commands info exit - for exit from CMD
«Hello Artem!» такой запуск не выведет.
Локализация
Весь текст вывода распихан по переменным и при желании их значения можно изменить, как например здесь:
class MyCli extends Cmd { MyCli() { super(); PROMPT = '$> '; } boolean do_hello(String[] args) { if (args.length == 0) { println('Hello world!'); } else { args.each { println("Hello ${it}!"); } } return true; } } def cli = new MyCli(); if (this.args.length > 0) { cli.execute(this.args); } else { cli.start(); }
Запустив такую программу, мы получим изменённый приглашающий к вводу префикс, не стандартный "cmd:> ", а "$> ":
$ groovy -cp . cli.groovy Welcome Print "help" for reference $> _
Более подробно о том какие переменные для локализации существуют и какие у них значения по умолчанию можно посмотреть а специальной страничке в вики.
Исполнение команд оболочки
Также имеется возможность использовать класс-хелпер Shell, который имеет всего один статический метод Response execute(String). Передав этому методу класса строку, в ответ мы получим экземпляр класса Response, который имеет два поля:
- hasError — флаг, говорящий о успехе выполнения команды в оболочке
- out — результат выполнения команды
Например, так могла бы выглядеть команда по отображению пути до текущей директории:
boolean do_pwd(String[] args) { if (args.length != 0) { println('ERROR: this method doesn\'t have any arguments'); return false; } Response response = Shell.execute('pwd'); if (!response.hasError) { println(response.out); return true; } else { println("ERROR: ${response.out}"); return false; } }
История ввода команд
Это было самое сложное. На сколько я понял, Java (а соответственно и Groovy) имеют, так сказать, очень натянутые отношения с I/O в терминал, по крайне мере на Linux версиях JVM (на других не пробовал). Например, стандартными средствами невозможно перемещать курсор ввода стрелками на клавиатуре, будет печататься мусор аля "^[[C^[[D^[[A^[[B", примерно тоже самое ждёт Вас при нажатии, например, Tab и прочего.
Есть обходной манёвр, в виде все различных Java библиотек, которые, как я понял, содержат просто код на C\C++ для работы с вводом из терминала и классы-обёртки на Java. Для небольших скриптов тянуть с собой jar’ник файлов, причём зависящего от ОС — это как то чересчур, но в тоже время хотелось иметь функциональность, например просмотра истории команд и изменения списка аргументов в выбранной из истории команде. Для этого я написал велосипед в велосипеде.
Welcome Print "help" for reference cmd:> help List of all available commands: (for reference on command print "help <command_name>") help - prints commands info exit - for exit from CMD cmd:> !! <- как и в nix'ах, вызываем последнюю команду cmd:> help | _
Знак "|" говорит нам о том что мы можем отредактировать список аргументов команды. Мы можем просто нажать Enter и тогда интерпретатор выполнит команду. Можем ввести "q", тогда редактирование будет отменено. А можем сделать так:
cmd:> help | exit <- добавляем новый аргумент к команде cmd:> help exit | +1=hello <- изменяем первый аргумент на новое значение cmd:> help hello | -1 <- удаляем первый аргумент
Наигравшись с изменениями аргументов, можно, в конце концов, нажать Enter и интерпретатор выполнит команду, также записав её в историю команд.
На этом я закончу этот бесконечно длинный пост, скажу лишь, что сорцы лежат в свободном доступе в моём репозитории по ссылке.
Очень хотелось бы услышать от Вас объективную критику и, возможно, предложения по дополнению «Фреймворка».
P.S.: Написал я, кстати, при помощи этого «Фреймворка» не только скрипт управления своим сервером, но также и на работе пригодился, для похожих задач.
P.S.S.: Если кто знает как можно решить проблему ввода с консоли на Java под Linux, просьба — поделитесь.
ссылка на оригинал статьи http://habrahabr.ru/post/212457/
Добавить комментарий