По ходу написания небольшого проекта для себя на Groovy & Grails встала острая необходимость в использовании все различных shell-скриптов. То сервер перезапустить, предварительно передеплоив только что созданный проект, то логи ото всюду собрать, то новые версии конфигов залить и прочее. По началу писал всё на bash-скриптах, но, в силу того, что использую его, помимо своего pet-проекта, крайне редко, каждый раз сталкивался с долгими поисками в интернетах необходимых функций, правил синтаксиса и т.д., что, несомненно, сильно тормозило разработку собственно самого проекта.
И так как с языком программирования Groovy уже хорошо ознакомился, решил писать на нём. Но разводить огромное количество *.groovy файлов очень не хотелось, а хотелось как раз наоборот — иметь один скрипт управления back-end'ом, который уже включал бы в себя все необходимые команды, что бы можно было как во «взрослой» консольной программе иметь возможность и историю команд посмотреть и цепочку последовательно выполняемых команд задать и легко добавить, при необходимости, новые. Это хотелка ещё появилась потому, что вспомнил я про cmd, которым некогда пользовался осваивая Python. Но оказалось что для Groovy такого cmd никто не написал (позже я даже понял почему), что и подтолкнуло меня к очередному велосипедостроению, а именно к созданию небольшого Фреймворка Cli приложений на Groovy.
Целью данного поста является — получить объективную критику и предложения по дополнению моего «Фреймворка», возможно кто то захочет им воспользоваться, благо весь код, а также всю документацию я выложил на свой bitbacket аккаунт.
Началось всё с одной статьи, где автор описывал как пользоваться Python'овским cmd, собственно я не далеко ушёл в своём начинании и реализовал примерно тот же функционал, где присутствует абстрактный класс Cmd.groovy, наследовавшись от которого, мы получаем среду выполнения своего приложения. Простой пример:
файл cli.groovy:
Каждый метод, который мы хотим задействовать в качестве консольной команды должен иметь строгую сигнатуру:
В случае, если метод начинается с префикса "do_", но не выполняет одно из выше описанных условий, программа выдаст предупреждение, где будет описано почему метод не вошёл в список команд и как это это исправить.
Создав экземпляр нашего класса запустить его можно двумя способами:
1) Вызвав метод "execute(String[])", передав туда аргументы пользователя. В данном случае объект выполнит все команды, полученные при вызове, и завершит выполнение;
2) Запустив цикл интерактивного ввода, вызвав метод "start()". При этом в терминал будет выведено приветствие и «приглашение» ввести команду. Выполнение цикла ввода будет продолжаться до тех пор, пока пользователь не введёт "exit".
Также, к нашему методу можно добавить аннотацию @Description, которая будет содержать краткое (brief) и полное (full) описание метода:
Запустив такую программу, появится возможность запросить подсказку по команде:
При желании команды можно объединить в цепочку, простым добавлением знака "+" между ними:
Знак "+" стал использовать, вместо канонического двойного амперсанда из-за того, что при мгновенном запуске программы shell думал, что я пытаюсь вызвать другую команду, а не просто сеттю аргумент:
Ожидаемо, что при возвращении командой значения false, цепочка оборвётся
«Hello Artem!» такой запуск не выведет.
Весь текст вывода распихан по переменным и при желании их значения можно изменить, как например здесь:
Запустив такую программу, мы получим изменённый приглашающий к вводу префикс, не стандартный "cmd:> ", а "$> ":
Более подробно о том какие переменные для локализации существуют и какие у них значения по умолчанию можно посмотреть а специальной страничке в вики.
Также имеется возможность использовать класс-хелпер Shell, который имеет всего один статический метод Response execute(String). Передав этому методу класса строку, в ответ мы получим экземпляр класса Response, который имеет два поля:
Например, так могла бы выглядеть команда по отображению пути до текущей директории:
Это было самое сложное. На сколько я понял, Java (а соответственно и Groovy) имеют, так сказать, очень натянутые отношения с I/O в терминал, по крайне мере на Linux версиях JVM (на других не пробовал). Например, стандартными средствами невозможно перемещать курсор ввода стрелками на клавиатуре, будет печататься мусор аля "^[[C^[[D^[[A^[[B", примерно тоже самое ждёт Вас при нажатии, например, Tab и прочего.
Есть обходной манёвр, в виде все различных Java библиотек, которые, как я понял, содержат просто код на C\C++ для работы с вводом из терминала и классы-обёртки на Java. Для небольших скриптов тянуть с собой jar'ник файлов, причём зависящего от ОС — это как то чересчур, но в тоже время хотелось иметь функциональность, например просмотра истории команд и изменения списка аргументов в выбранной из истории команде. Для этого я написал велосипед в велосипеде.
Знак "|" говорит нам о том что мы можем отредактировать список аргументов команды. Мы можем просто нажать Enter и тогда интерпретатор выполнит команду. Можем ввести "q", тогда редактирование будет отменено. А можем сделать так:
Наигравшись с изменениями аргументов, можно, в конце концов, нажать Enter и интерпретатор выполнит команду, также записав её в историю команд.
На этом я закончу этот бесконечно длинный пост, скажу лишь, что сорцы лежат в свободном доступе в моём репозитории по ссылке.
Очень хотелось бы услышать от Вас объективную критику и, возможно, предложения по дополнению «Фреймворка».
P.S.: Написал я, кстати, при помощи этого «Фреймворка» не только скрипт управления своим сервером, но также и на работе пригодился, для похожих задач.
P.S.S.: Если кто знает как можно решить проблему ввода с консоли на Java под Linux, просьба — поделитесь.
И так как с языком программирования 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, просьба — поделитесь.