arcanis.me/ru/_posts/2014-07-17-writting-own-completions-p1.html~
arcan1s 4449b24cf2 add zsh-comp-tut (ru)
add tag hasTr
2014-07-17 23:15:59 +04:00

147 lines
11 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
category: ru
type: paper
hasTr: true
layout: paper
tags: linux, разработка
title: Написание собственных дополнений для Shell. Zsh
short: writting-own-completions-p1
description: В данных статьях описываются некоторые основы создания файлов автодополнений для собственной программы.
---
<h2><a name="preamble" class="anchor" href="#preamble"><span class="octicon octicon-link"></span></a>Преамбула</h2>
<p>В процессе разработки <a href="/ru/projects/netctl-gui">одного своего проекта</a> возникло желание добавить также файлы автодополнений (только не спрашивайте зачем). Благо я как-то уже брался за написание подобных вещей, но читать что-либо тогда мне было лень, и так и не осилил.</p>
<h2><a name="introduction" class="anchor" href="#introduction"><span class="octicon octicon-link"></span></a>Введение</h2>
<p>Существует несколько возможных вариантов написания файла автодополнения для zsh. В случае данной статьи я остановлюсь только на одном из них, который предоставляет большие возможности и не требует больших затрат (например, работы с регулярными выражениями).</p>
<p>Рассмотрим на примере моего же приложения, часть справки к которому выглядит таким образом:</p>
{% highlight bash %}
netctl-gui [ -h | --help ] [ -e ESSID | --essid ESSID ] [ -с FILE | --config FILE ]
[ -o PROFILE | --open PROFILE ] [ -t NUM | --tab NUM ] [ --set-opts OPTIONS ]
{% endhighlight %}
<p>Список флагов:
<ul>
<li>флаги <code>-h</code> и <code>--help</code> не требуют аргументов;</li>
<li>флаги <code>-e</code> и <code>--essid</code> требуют аргумента в виде строки, без дополнения;</li>
<li>флаги <code>-c</code> и <code>--config</code> требуют аргумента в виде строки, файл с произвольной локацией;</li>
<li>флаги <code>-o</code> и <code>--open</code> требуют аргумента в виде строки, дополнение по файлам из определенной директории;</li>
<li>флаги <code>-t</code> и <code>--tab</code> требуют аргумента в виде строки, дополнение из указанного массива;</li>
<li>флаг <code>--set-opts</code> требует аргумента в виде строки, дополнение из указанного массива, разделены запятыми;</li>
</ul>
</p>
<h2><a name="file" class="anchor" href="#file"><span class="octicon octicon-link"></span></a>Структура файла</h2>
<p>В заголовке должно быть обязательно указано, что это файл дополнений и для каких приложений он служит (можно строкой, если в файле будет содержаться дополнение для нескольких команд):
{% highlight bash %}
#compdef netctl-gui
{% endhighlight %}
Дальше идет описание флагов, вспомогательные функции и переменные. Замечу, что функции и переменные, которые будут использоваться для дополнения <b>должны возвращать массивы</b>, а не строки. В моем случае схема выглядит примерно так (все функции и переменные в этой главе умышленно оставлены пустыми):
{% highlight bash %}
# variables
_netctl_gui_arglist=()
_netctl_gui_settings=()
_netctl_gui_tabs=()
_netctl_profiles() {}
{% endhighlight %}
Затем идут основные функции, которые будут вызываться для дополнения для определенной команды. В моем случае команда одна, и функция одна:
{% highlight bash %}
# work block
_netctl-gui() {}
{% endhighlight %}
Далее <b>без выделения в отдельную функцию</b> идет небольшое шаманство, связанное с соотнесением приложения, которое было декларировано в первой строке, с функцией в теле скрипта:
{% highlight bash %}
case "$service" in
netctl-gui)
_netctl-gui "$@" && return 0
;;
esac
{% endhighlight %}
</p>
<h2><a name="flags" class="anchor" href="#flags"><span class="octicon octicon-link"></span></a>Флаги</h2>
<p>Как я и говорил во введении, существует несколько способов создания подобных файлов. В частности, они различаются декларацией флагов и их дальнейшей обработкой. В данном случае я буду использовать команду <code>_arguments</code>, которая требует специфичный формат переменных. Выглядит он таким образом <code>ФЛАГ[описание]:СООБЩЕНИЕ:ДЕЙСТВИЕ</code>. Последние два поля не обязательны и, как Вы увидите чуть ниже, вовсе и не нужны в некоторых местах. Если Вы предусматриваете два флага (короткий и длинный формат) на одно действие, то формат чуть-чуть усложняется: <code>{(ФЛАГ_2)ФЛАГ_1,(ФЛАГ_1)ФЛАГ_2}[описание]:СООБЩЕНИЕ:ДЕЙСТВИЕ</code>. Замечу, что, если Вы хотите сделать дополнения для двух типов флагов, но некоторые флаги не имеют второй записи, то Вам необходимо продублировать его таким образом: <code>{ФЛАГ,ФЛАГ}[описание]:СООБЩЕНИЕ:ДЕЙСТВИЕ</code>. <code>СООБЩЕНИЕ</code> - сообщение, которое будет показано, <code>ДЕЙСТВИЕ</code> - действие, которое будет выполнено после этого флага. В случае данного туториала, <code>ДЕЙСТВИЕ</code> будет иметь вид <code>->СОСТОЯНИЕ</code>.</p>
<p>Итак, согласно нашим требованиям, получается такое объявление аргументов:
{% highlight bash %}
_netctl_gui_arglist=(
{'(--help)-h','(-h)--help'}'[show help and exit]'
{'(--essid)-e','(-e)--essid'}'[select ESSID]:type ESSID:->essid'
{'(--config)-c','(-c)--config'}'[read configuration from this file]:select file:->files'
{'(--open)-o','(-o)--open'}'[open profile]:select profile:->profiles'
{'(--tab)-t','(-t)--tab'}'[open a tab with specified number]:select tab:->tab'
{'--set-opts','--set-opts'}'[set options for this run, comma separated]:comma separated:->settings'
)
{% endhighlight %}
</p>
<h2><a name="variables" class="anchor" href="#variables"><span class="octicon octicon-link"></span></a>Массивы переменных</h2>
<p>В нашем случае есть два статических массива (не изменятся ни сейчас, ни через пять минут) (массивы умышленно уменьшены):
{% highlight bash %}
_netctl_gui_settings=(
'CTRL_DIR'
'CTRL_GROUP'
)
_netctl_gui_tabs=(
'1'
'2'
)
{% endhighlight %}
И есть динамический массив, который должен каждый раз генерироваться. Он содержит, в данном случае, файлы в указанной директории (это можно сделать и средствами zsh, кстати):
{% highlight bash %}
_netctl_profiles() {
print $(find /etc/netctl -maxdepth 1 -type f -printf "%f\n")
}
{% endhighlight %}
</p>
<h2><a name="body" class="anchor" href="#body"><span class="octicon octicon-link"></span></a>Тело функции</h2>
<p>Помните, там выше было что-то про состояние? Оно хранится в переменной <code>$state</code>, и в теле функции делается проверка на то, чему оно равно, чтобы подобрать соответствующие действия. В начале также нужно не забыть вызвать <code>_arguments</code> с нашими флагами.
{% highlight bash %}
_netctl-gui() {
_arguments $_netctl_gui_arglist
case "$state" in
essid)
# не делать дополнения, ждать введенной строки
;;
files)
# дополнение по существующим файлам
_files
;;
profiles)
# дополнение из функции
# первая переменная описание
# вторая массив для дополнения
_values 'profiles' $(_netctl_profiles)
;;
tab)
# дополнение из массива
_values 'tab' $_netctl_gui_tabs
;;
settings)
# дополнение из массива
# флаг -s устанавливает разделитель и включает мультивыбор
_values -s ',' 'settings' $_netctl_gui_settings
;;
esac
}
{% endhighlight %}
</p>
<h2><a name="conclusion" class="anchor" href="#conclusion"><span class="octicon octicon-link"></span></a>Заключение</h2>
<p>Файл хранится в директории <code>/usr/share/zsh/site-functions</code> с произвольным в общем-то именем с префиксом <code>_</code>. Файл примера полностью может быть найден <a href="https://raw.githubusercontent.com/arcan1s/netctl-gui/master/sources/gui/zsh-completions">в моем репозитории</a>.</p>
<p>Дополнительная информация может быть найдена в репозитории <a href="https://github.com/zsh-users/zsh-completions">zsh-completions</a>. Например, там есть такой <a href="https://github.com/zsh-users/zsh-completions/blob/master/zsh-completions-howto.org">How-To</a>. А еще там есть много примеров.</p>