Пользуюсь Gentoo. Частенько приходится писать ебилды для пакетов, которых нет в Portage. Все ебилды похожи, пишу я их в Емаксе. В таком случае определённо стоит призвать на помощь скелетов.
Скелеты, описанные в доке «Autotype» — это, в общем-то, обычные шаблоны на стероидах. Помимо обычной вставки текста, скелет может вежливо спросить через минибуфер, что именно вставить, через минибуфер, а то и сплясать как нам захочется, выполнив произвольную конструкцию на Emacs Lisp. Главной фичей является возможность рекурсивного вызова скелетом самого себя. Собственно, для описываемой задачи скелеты были выбранны именно по этому; для более простых вещей шаблонные расширения Emacs типа yasnippet дают более компактное решение. Скелет также может обернуть уже написанное в буфере. То есть, скелет — интерактивный динамический шаблон. Можно сделать так, чтобы скелет вставлялся в буфер, когда создаётся новый файл определённого типа (который определяется по расширению или текущему режиму).
Полностью несложное дело конструирования нежити раскрыто в разделе «Skeleton Language», я покажу простой пример.
Скелет представляет собой обычный список, каждый элемент трактуется
определённым образом. Например, строковые элементы тупо вставляются в
буфер, символ > делает отступ текущий строчки буфера согласно режиму
и т. д.
Первый элемент имеет особое значение для скелетов, когда-либо задающих
вопросы — если это строка, то она используется как приглашение при
вводе, при этом вопрос задаётся не сразу, а тогда, когда в скелете
встретится специальный элемент str (вместо которого и вставится то,
что мы ответим скелету). Брутальные же молчаливые скелеты имеют nil
в качестве первого элемента.
Частью скелета в том числе может быть и другой скелет :-)
Я хотел бы иметь скелета, который вставлял бы в начало буфера
стандартный заголовок ебилда (/usr/portage/header.txt), спрашивал
меня, как заполнить DESCRIPTION, HOMEPAGE, SRC_URI, LICENSE
(при этом я хочу иметь мудрый комплишен по списку лицензий), IUSE,
узнавал у меня поочерёдно элементы DEPEND, гламурно форматируя их
список, а также вставлял простые предопределённые заготовки
src_unpack(), src_compile(), src_install() и ещё некоторые
конструкции.
Функция define-skeleton определяет команду для вставки скелета.
Первым аргументом идёт имя скелета, потом дока, а потом собственно
содержимое скелетика.
(define-skeleton ebuild-skeleton
"Insert new ebuild skeleton.
Ask for package description, homepage, source URL, license, USE
flags and dependencies interactively."
nil
'(insert-file-contents "/usr/portage/header.txt")
'(goto-char (point-max))
"inherit eutils" \n \n
"DESCRIPTION=\"" (read-string "Enter description: ") "\"" \n
"HOMEPAGE=\"" (read-string "Enter homepage URL: " "http://") "\"" \n
"SRC_URI=\"" (read-string "Enter source code URL: " "http://") "\"" \n
"LICENSE=\"" (ebuild-read-license "Enter package license: ") "\"" \n \n
"SLOT=\"0\"" \n
"KEYWORDS=\"~x86\"" \n
"IUSE=\"" (read-string "Enter USE flags: ") "\"" \n \n
"DEPEND=\"" ("Enter dependency atom: " str & (nil ?\n " "))
& (if skeleton-untabify -5 -2) "\"" ?\n
"RDEPEND=\"${DEPEND}\"" \n \n
"src_unpack() {" \n _ "unpack ${A}" > _ ?\n "}" \n \n
"src_compile() {" \n "emake || die \"emake failed\"" > ?\n "}" \n \n
"src_install() {" \n "emake DESTDIR=\"${D}\" install || die \"Install failed\"" > ?\n "}" \n \n)
Голова скелета здесь nil — сам он не будет задавать вопросов.
Поехали.
'(insert-file-contents "/usr/portage/header.txt")
'(goto-char (point-max))
Когда встречается цитированная конструкция, она выполняется только ради своего побочного эффекта — вставить хидер из файла, переместиться в конец буфера.
"inherit eutils" \n \n
"DESCRIPTION=\"" (read-string "Enter description: ") "\"" \n
"HOMEPAGE=\"" (read-string "Enter homepage URL: " "http://") "\"" \n
"SRC_URI=\"" (read-string "Enter source code URL: " "http://") "\"" \n
"LICENSE=\"" (ebuild-read-license "Enter package license: ") "\"" \n \n
"SLOT=\"0\"" \n
"KEYWORDS=\"~x86\"" \n
"IUSE=\"" (read-string "Enter USE flags: ") "\"" \n \n
Строки просто вставляются (кавычки " нужно экранировать). Когда
встречается просто выражение (как здесь read-string), его результат
(в данном случае — считанная из минибуфера строка) вновь
интерпретируется как часть скелета. Тело функции
ebuild-read-license, которая считывает имя лицензии, покажу попозже.
Элементы \n вставляют перевод строки с выравниванием по предыдущей.
"DEPEND=\"" ("Enter dependency atom (RET to finish): " str & (nil ?\n " "))
& (if skeleton-untabify -5 -2) "\"" ?\n
После вставки строки DEPEND=" встречается подскелет! Подскелеты
хитры, и вставляются до тех пор, пока пользователь что-нибудь им
говорит. В данном случае первый элемент субскелета используется как
строка приглашения для ввода очередной зависимости. Появление str
собственно заставляет скелет задать вопрос. & работает как
логическое «И» — если предыдущий элемент перемещал текущую точку в
буфере, вставляется очередной субскелет — (nil ?\n " "). В нём
используется ?\n, который вставляет перевод строки без выравнивания,
и обычная табуляция (приходится вручную вставлять табуляцию, потому
что сейчас в ebuild-mode нет правил расставления отступов в
DEPEND). Так продолжается до тех пор, пока скелет получает непустые
ответы на свои вопросы.
Если подскелет вызвал вставку хотя бы одной зависимости, на конце
получится лишний перевод строки с табом, которые сжираются при помощи
(if skeleton-untabify -5 -2) — элементы вида -N удаляют N
предыдущих символов.
"RDEPEND=\"${DEPEND}\"" \n \n
"src_unpack() {" \n _ "unpack ${A}" > _ ?\n "}" \n \n
"src_compile() {" \n "emake || die \"emake failed\"" > ?\n "}" \n \n
"src_install() {" \n "emake DESTDIR=\"${D}\" install || die \"Install failed\"" > ?\n "}" \n \n
Тупо вставка строк (RDEPEND, конечно, в ебилдах не всегда такой, но его
достоверное определение может потребовать установки пакета, а там
всякие ldd(1), yolk(1) и пр. помогают).
Потом три заготовки функций. Элемент > делает выравнивание текущий
строки согласно режиму (тела функций в gentoo-mode смещаются
нормально, на таб), а _ указывает на положение точки после того, как
скелет закончит работу (скорее всего доводку ебилда придётся начать с
src_unpack, поэтому туда).
Все лицензии, доступные в Portage, лежат в /usr/portage/licenses,
поэтому удобно считывать имя лицензии с комплишеном по доступным.
(defun ebuild-read-license (prompt)
"Read a license name from the minibuffer.
PROMPT is used as minibuffer prompt, input is completed it to one
of licenses in Portage if possible."
(completing-read prompt
(file-name-all-completions "" "/usr/portage/licenses")
nil
nil
"GPL-2"))
После определения скелета можно, например, вызвать функцию
ebuild-skeleton, которая задаст нужные вопросы и выведет в буфер
что-то вроде этого:
# Copyright 1999-2008 Gentoo Foundation
# Distributed under the terms of the GNU General Public License v2
# $Header: $
DESCRIPTION="libfoobar is an ultimate solution for extensive foobaring"
HOMEPAGE="http://libfoobar.org"
SRC_URI="http://libfoobar.org/download/libfoobar-1.3.tgz"
LICENSE="MIT"
SLOT="0"
KEYWORDS="~x86"
IUSE="doc emacs vim"
DEPEND="dev-libs/nettle
emacs? ( virtual/emacs )
vim? ( app-editors/vim )"
RDEPEND="${DEPEND}"
inherit eutils
src_unpack() {
unpack ${A}
}
src_compile() {
emake || die "emake failed"
}
src_install() {
emake DESTDIR="${D}" install || die "Install failed"
}
Средство «auto-insert» поможет сделать так, чтобы созданная нежить прибегала по первому свисту.
Функция auto-insert вставляет в текущий буфер некоторое содержимое
(скелета или простой текстовый шаблон) согласно режиму или маске
файла. Например, для latex-mode скелет спрашивает \documentclass и
какие пакеты с какими опциями подключить.
Сопоставление режима/маски файла и содержимого, включаемого по
M-x auto-insert, выполняется при помощи define-auto-insert (это
нужно написать куда-нибудь в .emacs.el):
(define-auto-insert '("\\.ebuild$" "Ebuild") 'ebuild-skeleton)
Чтобы автоматическая вставка фрагментов выполнялась каждый раз, когда
я открываю новый файл с подходящим расширением, нужно включить и
сохранить опцию auto-insert-mode.
Собственно, это включит автовставку содержимого и для других типов
файлов (см. доки). Можно настроить переменную
auto-insert-query, чтобы вставка происходила с подтверждением (будет
задаваться вопрос типа «Perform (Ebuild) auto-insertion? (y or n)»)
Комментарии:
загонять шаблон в елисповый код не совсем хорошо - лучше воспользоваться подстановками, которые будут раскрываться в соответствующий код. я использую более-менее статические шаблоны для auto-insert, а затем уже пользуюсь msf-abbrevs для вставки больших кусков кода
и уж лучше
yasnippetНе могу сказать, что для
msf-abbrevsполучился бы более компактный код в тех местах, которые действительно представляют собой мешанину (запросы DEPEND); для небольших же кусочков кода языкиmsf-abbrevsили того жеyasnippetопределённо выглядят приятнее скелетов.