Написание своих дополнений для Shell. Часть 1: zsh

1ffece11b63ae0c70703d4546a42e704.pngВ данных статьях (решил, что тематически лучше разбить на две части) описываются некоторые основы создания файлов дополнений для собственной программы.ПреамбулаВ процессе разработки одного своего проекта возникло желание добавить также файлы дополнений (только не спрашивайте зачем). Благо я как-то уже брался за написание подобных вещей, но читать что-либо тогда мне было лень, и так и не осилил.Введение Существует несколько возможных вариантов написания файла автодополнения для zsh. В случае данной статьи я остановлюсь только на одном из них, который предоставляет большие возможности и не требует больших затрат (например, работы с регулярными выражениями).Рассмотрим на примере моего же приложения, часть справки к которому выглядит таким образом:

proga [ -h | --help ] [ -e ESSID | --essid ESSID ] [ -с FILE | --config FILE ] [ -o PROFILE | --open PROFILE ] [ -t NUM | --tab NUM ] [ --set-opts OPTIONS ] Список флагов: флаги -h и --help не требуют аргументов; флаги -e и --essid требуют аргумента в виде строки, без дополнения; флаги -c и --config требуют аргумента в виде строки, файл с произвольной локацией; флаги -o и --open требуют аргумента в виде строки, дополнение по файлам из определенной директории; флаги -t и --tab требуют аргумента в виде строки, дополнение из указанного массива; флаг --set-opts требует аргумента в виде строки, дополнение из указанного массива, разделены запятыми; Структура файла В заголовке должно быть обязательно указано, что это файл дополнений и для каких приложений он служит (можно строкой, если в файле будет содержаться дополнение для нескольких команд): #compdef proga Дальше идет описание флагов, вспомогательные функции и переменные. Замечу, что функции и переменные, которые будут использоваться для дополнения должны возвращать массивы, а не строки. В моем случае схема выглядит примерно так (все функции и переменные в этой главе умышленно оставлены пустыми): # variables _proga_arglist=() _proga_settings=() _proga_tabs=() _proga_profiles () {} Затем идут основные функции, которые будут вызываться для дополнения для определенной команды. В моем случае команда одна, и функция одна: # work block _proga () {} Далее без выделения в отдельную функцию идет небольшое шаманство, связанное с соотнесением приложения, которое было декларировано в первой строке, с функцией в теле скрипта: case »$service» in proga) _proga »$@» && return 0 ;; esac Флаги Как я и говорил во введении, существует несколько способов создания подобных файлов. В частности, они различаются декларацией флагов и их дальнейшей обработкой. В данном случае я буду использовать команду _arguments, которая требует специфичный формат переменных. Выглядит он таким образом: ФЛАГ[описание]: СООБЩЕНИЕ: ДЕЙСТВИЕ Последние два поля не обязательны и, как Вы увидите чуть ниже, вовсе и не нужны в некоторых местах. Если Вы предусматриваете два флага (короткий и длинный формат) на одно действие, то формат чуть-чуть усложняется: {(ФЛАГ_2)ФЛАГ_1,(ФЛАГ_1)ФЛАГ_2}[описание]: СООБЩЕНИЕ: ДЕЙСТВИЕ Замечу, что, если Вы хотите сделать дополнения для двух типов флагов, но некоторые флаги не имеют второй записи, то Вам необходимо продублировать его таким образом: {ФЛАГ, ФЛАГ}[описание]: СООБЩЕНИЕ: ДЕЙСТВИЕ СООБЩЕНИЕ — сообщение, которое будет показано, ДЕЙСТВИЕ — действие, которое будет выполнено после этого флага. В случае данного туториала, ДЕЙСТВИЕ будет иметь вид →СОСТОЯНИЕ.Итак, согласно нашим требованиям, получается такое объявление аргументов:

_proga_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' ) Массивы переменных В нашем случае есть два статических массива (не изменятся ни сейчас, ни через пять минут) (массивы умышленно уменьшены): _proga_settings=( 'CTRL_DIR' 'CTRL_GROUP' )

_proga_tabs=( '1' '2' ) И есть динамический массив, который должен каждый раз генерироваться. Он содержит, в данном случае, файлы в указанной директории (это можно сделать и средствами zsh, кстати): _proga_profiles () { print $(find /some/path -maxdepth 1 -type f -printf »%f\n») } Тело функции Помните, там выше было что-то про состояние? Оно хранится в переменной $state, и в теле функции делается проверка на то, чему оно равно, чтобы подобрать соответствующие действия. В начале также нужно не забыть вызвать _arguments с нашими флагами. _proga () { _arguments $_proga_gui_arglist case »$state» in essid) # не делать дополнения, ждать введенной строки ;; files) # дополнение по существующим файлам _files ;; profiles) # дополнение из функции # первая переменная описание # вторая массив для дополнения _values 'profiles' $(_proga_profiles) ;; tab) # дополнение из массива _values 'tab' $_proga_tabs ;; settings) # дополнение из массива # флаг -s устанавливает разделитель и включает мультивыбор _values -s ',' 'settings' $_proga_settings ;; esac } Заключение Файл хранится в директории /usr/share/zsh/site-functions/ с произвольным, в общем-то, именем с префиксом _. Файл примера полностью может быть найден в моем репозитории (не сочтите за рекламу, я специально все имена повырезал).Дополнительная информация может быть найдена в репозитории zsh-completions. Например, там есть такой How-To. А еще там есть много примеров.

© Habrahabr.ru