пятница, 15 октября 2010 г.

Автодополнение имён Perl-модулей в Bash

Введение

Автодополнение имён файлов и директорий в Bash - настолько удобная и полезная функция, что пальцы сами тянутся нажать на [TAB] для получения списка вариантов или точного совпадения. Это экономит массу времени и уменьшает вероятность опечатки. Однако, возможности автодополнения в Bash не ограничиваются только лишь именами файлов и директорий: по-сути, автодополнять можно любую команду причём совершенно произвольным образом.

В этой статье рассказывается о том, как научить Bash автодополнять имена Perl-модулей. Вот только несколько возможных применений этой функции:
  • Вывод документации по модулю (perldoc My::Modu [TAB] My::Module)
  • Редактирование модуля (vim Foo::B [TAB] Foo::Bar)
  • Вывод версии модуля (см. pm-version.pl)
По большей части эти преимущества оценят люди, ежедневно занимающиеся разработкой на Perl. Остальные же могут изучить этот пример и адаптировать его для своих нужд.

Получение списка модулей

Во-первых, нам понадобится скрипт, позволяющий получить имена всех Perl-модулей, установленных в системе. Для этой цели прекрасно подходит pmdesc3.pl который, кстати, входит в состав perl-support.vim - отличного VIM-плагина для Perl-разработчиков. Помимо имени модуля, этот скрипт также выводит его версию и заголовок POD документации:
./pmdesc3.pl

...
WWW::Mechanize (1.34) Handy web browsing in a Perl object
WWW::RobotRules::AnyDBM_File (5.810) Persistent RobotRules
DateTime::TimeZone (0.7701) Time zone object base class and factory
DateTime::Locale (0.35) Localization support for DateTime.pm
...
Эта дополнительная информация будет лишней при выводе списка совпадений в Bash-е, поэтому её лучше предварительно убрать:
./pmdesc3.pl 2>/dev/null | awk '{ print $1 }' > /path/to/modules.list

...
WWW::Mechanize
WWW::RobotRules::AnyDBM_File
DateTime::TimeZone
DateTime::Locale
...
Таким образом, в файле /path/to/modules.list окажется список всех установленных модулей. Этот список следует поддерживать в актуальном состоянии: обновлять cron-задачей, а также сразу после установки нового Perl-модуля (читай ниже).

Важный момент: для того чтобы скрипт pmdesc3.pl нашёл не только стандартные модули, но и модули написанные вами, следует добавить соответствующие пути к переменной окружения PERL5LIB:
PERL5LIB="$PERL5LIB:/path/to/my/lib/"
Напомню, что обычно, переменные, определённые в ~/.bashrc не наследуются cron-задачами, поэтому соответствующую переменную необходимо прописать и в crontab-е.

И, наконец, финальное замечание. Как оказалось, скрипт pmdesc3.pl не посещает директории из @INC, являющиеся символическими ссылками на другие директории. К примеру, в Debian Lenny:
$ ls -l /usr/share/perl/5.10
lrwxrwxrwx 1 root root 6 Фев 11 2009 /usr/share/perl/5.10 -> 5.10.0
Но не смотря на то, что в @INC есть /usr/share/perl/5.10, директория /usr/share/perl/5.10.0 скриптом не обрабатывается. Для того чтобы скрипт обошёл и "символические" директории тоже, добавьте их явно к PERL5LIB.

Настройка автодополнения

Механизм автодополнения работает следующим образом. Пользователь определяет специальную функцию и регистрирует её командой complete. В результате, complete связывает конкретную команду (в нашем примере их три: V, v и d) с пользовательской функцией для автодополнения (_perl_module). Почему названия команд такие странные, вскоре станет ясно. Пока же можно считать, что для лаконичности.
PERL_MODULES_LIST="/path/to/modules.list"

_perl_module() {
COMPREPLY=();
cur="${COMP_WORDS[COMP_CWORD]}"

if [[ ${cur} == ?* ]] ; then
COMPREPLY=( $(grep "^${cur}" "$PERL_MODULES_LIST" | sed -e 's/:/\\:/g') )
return 0
fi
}

complete -F _perl_module V
complete -F _perl_module v
complete -F _perl_module d
Функция автодополнения получает в качестве аргумента часть уже введённого текста ($cur) и на его основе должна сохранить в переменной COMPREPLY список вариантов. В нашем случае это результат элементарного grep-а по имени модуля плюс экранирование символа ':'. Экранирование несколько портит вид выводимой подсказки, но к сожалению без этого нельзя.

Отмечу, что более сложная функция автодополнения может использовать контекст (предыдущие введённые слова) для более интеллектуальной подсказки. К примеру, предыдущее слово можно получить так:
prev="${COMP_WORDS[COMP_CWORD-1]}"
Приведённый выше файл необходимо поместить туда, где Bash сможет его найти и загрузить. В Debian Lenny есть специальная директория для функций автодополнения: /etc/bash_completion.d/

Bash-алиасы

Алиасы d и V, упомянутые выше, названы так только для краткости. Для того чтобы они заработали необходимо добавить в ~/.bashrc следующие строки:
alias d="perldoc"
alias V="/path/to/pm-version.pl"
Скрипт pm-version.pl показывает версию модуля и сообщает является ли он core-модулем и если да, то начиная с какой версии Perl.
alias v="PAGER=$EDITOR perldoc -m"
Алиас v - немного хитрее. Он позволяет открывать Perl-модули на редактирование по имени, а не по пути, т.е. v Foo::Bar вместо vim /path/to/lib/Foo/Bar.pm

Для достижения этой цели используется малоизвестная опция -m команды perldoc. С этой опцией perldoc открывает код Perl-модуля (не POD, а именно код!) пейджером (обычно less). Подставив в PAGER ваш любимый $EDITOR, исходный код модуля чудесным образом откроется на редактирование.

Обновление списка модулей при установке нового пакета

После того как в базовом варианте всё заработает, возникает естественное желание обновлять список модулей не только по расписанию, но и сразу после установки очередного модуля.

Обычно, во всех дистрибутивах имена пакетов для Perl-модулей имеют определённую структуру. В Debian, к примеру, за очень редким исключением, пакет для Perl-модуля Foo::Bar будет называться libfoo-bar-perl. Используя этот факт несложно написать небольшую обёртку для менеджера пакетов, которая будет выполнять следующие действия:
  • Преобразование имени Perl-модуля в имя пакета
  • Установку пакета
  • Пересоздание или обновление списка установленных модулей
Интеграция с VIM-ом

Как известно, VIM имеет встроенный механизм автодополнения по словарю (CRTL+n). Таким образом, если подключить уже имеющийся у нас список модулей в качестве словаря, то можно будет автодополнять выражения вида use и require:
use Foo::B [CTRL+n]
| Foo::Bar
| Foo::Bar::Baz
Вот необходимое заклинание для ~/.vimrc:
set dictionary+=/path/to/perl-modules.list
Консольный скрин-каст: сёрфинг по Perl-модулям

Работу автодополнения наглядно демонстрирует следующий консольный ролик: surf-perl-modules.txt. Для его воспроизведения потребуется программа ttyplay:
ttyplay surf-perl-modules.txt
Приятного просмотра!

Ссылки

8 комментариев:

  1. Класс! Очень интересно, большое спасибо.

    ОтветитьУдалить
  2. А у вас есть какой-то алиал просто для запуска vim? V и v заняты, что вы используете?

    ОтветитьУдалить
  3. А что у вас прячется за алиасом I? apt-get же не ставит perl модули. Какая-то обертка чтобы перевести модули в вид libfoo-bar-perl?

    А что делать если модуля нет в репозитарии? Вы используете свой репозитарий куда помещаете все нужные модули собранные вручную?

    ОтветитьУдалить
  4. Спасибо за отзыв)

    Для vim-а у меня нет алиаса, непосредственно командой vim я редактирую всё что не является Perl-модулями.

    ОтветитьУдалить
  5. ... а по поводу отсутствующих пакетов в репозитории, могу посоветовать программу dh-make-perl (Debian):

    dh-make-perl --cpan Dancer::Plugin
    cd ./Dancer-Plugin-XXX
    debuild

    ОтветитьУдалить
  6. Олег, а что именно не работает?
    Список не генерируется или автодополнение не работает? Для начала, можно попробовать

    1. не использовать алисы
    2. загрузить perl_modules.txt явно, при помощи команды source

    ОтветитьУдалить
  7. Да, I - это алиас для программки (pm-install.pl), которая переводит имена Perl-модулей в имена deb-пакетов, ставит их, а затем обновляет список установленных Perl-модулей.

    Вот ссылка: http://entropyware.info/blog/perl_tools/pm-install.tar.gz

    Я упомянул об этом в статье, но сам скрипт приводить не стал, т.к. во-первых, он очень простой, а во-вторых, он сильно завязан на конкретное окружение.

    Там, в частности, вызывается команда update-modules-list.sh, которая обновляет список модулей при помощи pmdesc3.pl и складывает его в мой хоум, в одну из подпапок vim-а. Мне показалось, что такие интимные подробности моей жизни наврядли будут интересны широкой общественности, а приводить полуработающий (да ещё и такой простой) скрипт в статье было бы по меньшей мере неопрятно.

    ОтветитьУдалить