# Klish 3
## Обзор
Программа klish предназначенa для организации интерфейса командной строки,
в котором оператору доступен только строго определенный набор команд. Это
отличает klish от стандартных shell интерпретаторов, где оператор может
использовать любые команды, существующие в системе и возможность их выполнения
зависит только от прав доступа. Набор доступных в klish команд определяется на
этапе конфигурирования и может быть задан, в общем случае, различными способами,
основным из которых являются XML файлы конфигурирования. Команды в klish не
являются системными командами, а полностью определены в файлах конфигурации,
со всеми возможными параметрами, синтаксисом и действиями, которые они
выполняют.
Основным применением klish являются встроенные системы, где оператору доступны
только определенные, специфические для конкретного устройства, команды, а не
произвольный набор команд, как в системах общего назначения. В таких встроенных
системах klish может подменить собой shell интерпретатор, недоступный для
оператора.
Примером встроенных систем может служить управляемое сетевое оборудование.
Исторически в этом сегменте сложилось два основных подхода к организации
интерфейса командной строки. Условно их можно назвать подход "Cisco" и подход
"Juniper". И Cisco и Juniper имеют два режима работы - командный режим и
режим конфигурирования. В командном режиме введенные команды немедленно
исполняются, но не влияют на конфигурацию системы. Это команды просмотра
состояния, команды управления устройством, но не изменения его конфигурации.
В режиме конфигурирования
идет настройка оборудования и сервисов. В Cisco команды конфигурирования также
исполняются немедленно, меняя конфигурацию системы. В Juniper конфигурацию
можно условно представить себе, как текстовый файл, изменения в котором
производятся с помощью стандартных команд редактирования. При редактировании
изменения не применяются в системе. И только по специальной команде весь
накопленный комплекс изменений применяется разом, чем обеспечивается
согласованность изменений. При Cisco подходе похожее поведение также можно
эмулировать, проектируя команды определенным образом, но для Cisco это менее
естественный способ.
Какой из подходов лучше и проще в конкретном случае - определяется задачей.
Klish в первую очередь рассчитан на подход Cisco, т.е. на немедленно выполняемые
команды. Однако проект имеет систему плагинов, что позволяет расширять его
возможности. Так плагин klish-plugin-sysrepo, реализованный отдельным проектом,
работая на основе хранилища sysrepo, позволяет организовать Juniper подход.
## Основные сведения
![Клиент-серверная модель Klish](/klish-client-server.ru.png "Клиент-серверная модель Klish")
Проект klish использует клиент-серверную модель. Слушающий сервер klishd
загружает конфигурацию команд и ожидает запросов от клиентов на UNIX-сокете (1).
При соединении от клиента слушающий сервер klishd порождает (fork()) отдельный
процесс (2), который будет заниматься обслуживанием одного конкретного клиента.
Порожденный процесс называется "обслуживающий сервер klishd". Слушающий сервер
klishd продолжает ожидать новых соединений от клиентов. Взаимодействие между
клиентами и обслуживающим сервером происходит по UNIX-сокетам с использованием
специально разработанного для этой цели протокола KTP (Klish Transfer Protocol)
(3).
Задача клиента - передача ввода от оператора на сервер и получение от него
результата для показа оператору. Клиент не знает, какие команды существуют,
как их выполнять. Выполнением занимается серверная сторона. Так как клиент имеет
относительно простой код, нетрудно реализовать альтернативные программы -
клиенты, например графический клиент или клиент для автоматизированного
управления. Сейчас существует только текстовый клиент командной строки klish.
![Библиотеки Klish](/klish-libs.ru.png "Библиотеки Klish")
Основа проекта klish - библиотека libklish. На ее основе построены клиент klish
и сервер klishd. Библиотека реализует все основные механизмы работы и
взаимодействия. Библиотека входит в состав проекта klish.
Текстовый клиент использует еще одну встроенную библиотеку -
libtinyrl (Tiny ReadLine). Библиотека обеспечивает взаимодействие с пользователем
при работе в командной строке.
Все исполняемые файлы проекта klish, а также все его встроенные библиотеки
используют в своей работе библиотеку libfaux (Library of Auxiliary Functions) -
библиотеку вспомогательных функций. В предыдущих версиях проекта klish библиотека
входила в состав исходных кодов klish, сейчас она выделена в отдельный проект и
включает функции, облегчающие работу со строками, файлами, сетью, стандартными
структурами данных и т.д. Библиотека libfaux является обязательной внешней
зависимостью для проекта klish.
Кроме этого, плагины klish, о которых будет сказано ниже, могут использовать и
другие библиотеки для своей работы.
Klish имеет два типа плагинов. Плагины для загрузки конфигурации команд
(назовем их плагинами баз данных) и плагины, реализующие действия для
команд от пользователя (назовем их плагинами функций). Klish не
имеет встроенных команд и средств загрузки конфигурации (кроме специального
случая ischeme). Вся эта функциональность реализована в плагинах. Несколько
плагинов с базовой функциональностью входят в состав исходных кодов klish и
находятся в директориях "dbs/" и "plugins/".
Все необходимые плагины загружает слушающий сервер klishd в начале своей работы.
Какие именно плагины для загрузки конфигурации пользовательских команд нужно
использовать он узнает из своего базового файла конфигурации (специальный файл в
формате INI). Затем с помощью плагина базы данных сервер klishd загружает
конфигурацию пользовательских команд (например из XML файлов). Из этой
конфигурации он узнает список необходимых плагинов функций, и так же загружает
эти плагины в память. Однако использовать функции из плагинов функций будет
только обслуживающий (не слушающий) сервер klishd по запросам от пользователя.
Для настройки параметров сервера klishd используется конфигурационный файл
`/etc/klish/klishd.conf`. Альтернативный конфигурационный файл можно указать при
запуске сервера klishd.
Для настройки параметров клиента используется конфигурационный файл
`/etc/klish/klish.conf`. Альтернативный конфигурационный файл можно указать при
запуске клиента klish.
## Загрузка конфигурации команд
![Загрузка конфигурации команд](/klish-plugin-db.ru.png "Загрузка конфигурации команд")
Внутренним представлением конфигурации команд в klish является kscheme. Kscheme -
это набор C-структур, представляющих все дерево доступных пользователю команд,
областей видимости, параметров и т.д. Именно по этим структурам производится
вся внутренняя работа - поиск команд, автодополнение, формирование подсказок при
взаимодействии с пользователем.
В klish существует промежуточное представление конфигурации команд в виде
С-структур, называемое ischeme. Это представление вцелом похоже на kscheme, но
отличается от него тем, что все поля конфигурации представлены в текстовом виде,
все ссылки на другие объекты также являются текстовыми именами объектов, на
которые ссылаются, а не указателями, как в kscheme. Существуют и другие отличия.
Таким образом ischeme можно назвать "неоткомпилированной схемой", а kscheme -
"откомпилированной". "Компиляция", т.е. преобразование ischeme в kscheme
производится внутренними механизмами klish. Ischeme может быть вручную задано
пользователем в виде C-структур и собрано в виде отдельного плагина базы данных.
В этом случае другие плагины баз данных не понадобятся, вся конфигурацию уже
готова к преобразованию в kscheme.
В составе klish (см. dbs/) существуют следующие плагины баз данных для загрузки
конфигурации команд, т.е. схемы:
* expat - Использует библиотеку expat для загрузки конфигурации из XML.
* libxml2 - Использует библиотеку libxml2 для загрузки конфигурации из XML.
* roxml - Использует библиотеку roxml для загрузки конфигурации из XML.
* ischeme - Использует встроенную в C-код конфигурацию (Internal Scheme).
Все плагины баз данных переводят внешнюю конфигурацию, полученную например из
XML файлов, в ischeme. В случае ischeme, дополнительный этап преобразования не
требуется, т.к. ischeme уже готово.
Установленные плагины dbs находятся в `/usr/lib` (если конфигурировать
сборку с --prefix=/usr). Их имена `libklish-db-<имя>.so`, например
`/usr/lib/libklish-db-libxml2.so`.
## Плагины исполняемых функций
Каждая команда klish выполняет какое-либо действие или несколько действий сразу.
Эти действия надо как-то описывать. Если смотреть внутрь реализации, то klish
может запускать только исполняемый откомпилированный код из плагина. Плагины
содержат так называемые символы (symbol, sym), которые, по-сути, представляют
собой функции с единым зафиксированным API. На эти символы могут ссылаться
команды klish. В свою очередь символ может выполнять сложный код, например
запускать интерпретатор shell со скриптом, определяемым при описании команды
klish в конфигурационном файле. Или же другой символ может исполнять Lua скрипт.
Klish умеет получать символы только из плагинов. Стандартные символы реализованы
в плагинах включенных в состав проекта klish. В состав klish входят следующие
плагины:
* klish - Базовый плагин. Навигация, типы данных (для параметров),
вспомогательные функции.
* lua - Исполнение скриптов Lua. Механизм встроен в плагин и не использует
внешнюю программу для интерпретации.
* script - Исполнение скриптов. Учитывает шебанг (shebang) для определения
какой интерпретатор использовать. Таким образом могут выполняться не только
shell скрипты, но и скрипты на других интерпретируемых языках. По умолчанию
используется интерпретатор shell.
Пользователи могут писать свои плагины и использовать собственные символы в
командах klish. Установленные плагины находятся в `/usr/lib`
(если конфигурировать сборку с --prefix=/usr). Их имена
`libklish-plugin-<имя>.so`, например `/usr/lib/libklish-plugin-script.so`.
![Исполнение команд](/klish-exec.ru.png "Исполнение команд")
Символы бывают "синхронные" и "асинхронные". Синхронные символы исполняются
в адресном пространстве klishd, для асинхронных порождается отдельный процесс.
Асинхронные символы могут принимать на вход (stdin) пользовательский ввод,
получаемый обслуживающим сервером klishd от клиента по протоколу KTP и
пересылаемый порожденному процессу, в рамках которого выполняется асинхронный
символ. В свою очередь асинхронный символ может писать в потоки stdout, stderr.
Данные из этих потоков получает обслуживающий сервер klishd и пересылает
клиенту по протоколу KTP.
Синхронные символы (с выводом в stdout, stderr) не могут получать данные через
входной поток stdin, так как они выполняются в рамках обслуживающего сервера
klishd и, на время выполнения кода символа, другие функции сервера
временно останавливаются. Останавливается прием данных от клиента. Обслуживающий
сервер klishd является однопоточной программой, поэтому синхронные символы надо
использовать с осторожностью. Если символ будет выполняться слишком долго, то
сервер зависнет на это время. Кроме этого синхронные символы выполняются в
адресном пространстве сервера. Это означает, что выполнение неотлаженного
символа, содержащего ошибки, может привести к падению обслуживающего сервера.
Хотя синхронный символ не имеет входного потока stdin, он может использовать
выходные потоки stdout, stderr. Для получения данных из этих потоков и
последующей отправки их клиенту, обслуживающий сервер порождает служебный
процесс, который получает и временно хранит данные, поступающие от символа. При
завершении выполнения символа, служебный процесс пересылает сохраненные данные
обратно обслуживающему серверу, который, в свою очередь, отправляет их клиенту.
Синхронные символы рекомендуется использовать только тогда, когда символ меняет
состояние обслуживающего сервера и поэтому не может быть выполнен в рамках
другого процесса. Например, навигация по секциям (см. Области видимости)
реализована синхронным символом, т.к. положение пользователя в дереве секций
сохраняется в обслуживающем сервере.
Облегченные синхронные символы не имеют входного и выходных потоков данных
stdin, stdout, stderr. За счет этого, выполнение такого символа не требует
порождения новых процессов, как это делается в случае обычного синхронного
символа, либо асинхронного символа. Таким образом облегченный синхронный символ
выполняется максимально быстро. Хотя облегченный синхронный символ не имеет
потока stdout, он может сформировать выходную текстовую строку.
Строка формируется вручную и, с помощью специальной функции, передается
обслуживающему серверу, который может отправить её клиенту в качестве stdout.
В остальном облегченный синхронный символ имеет теже особенности, что и обычный
синхронный символ. Облегченные синхронные символы рекомендуется использовать
тогда, когда требуется максимально быстрое выполнение. Примером такой задачи
может служить формирования приглашения (prompt) пользователя. Это служебная
функция, которая выполняется каждый раз, когда пользователь нажимает кнопку
ввода. Еще один пример использования - функции проверки, является ли
введенный пользователем аргумент допустимым (см. PTYPE), а также функции для
формирования автодополнения и подсказок.
Символ, или, вернее сказать, команда klish, так как команда может состоять из
несколько последовательно выполняемых символов, может быть обычной, либо
являться фильтром. Для простоты предположим, что команда выполняет только один
символ, и поэтому будем говорить о символах, а не командах. Обычный символ
выполняет кокое-либо полезное действие и, часто, дает на выходе некоторый
текстовый вывод. А фильтр получает на вход результат работы, т.е., в данном
случае, текстовый вывод обычного символа и обрабатывает его. Чтобы указать, что
к обычной команде должен быть применен фильтр, пользователь использует цепочки
команд, разделенных знаком "|". Во многом это аналогично цепочкам в стандартной
пользовательской оболочке shell.
![Фильтры](/klish-filters.ru.png "Фильтры")
Синхронные символы не имеют входного потока stdin, т.е. не получают на вход
никаких данных и поэтому не могут быть полноценными фильтрами, а могут
использоваться только в качестве первой команды в цепочке команд. Облегченные
синхронные символы не имеют и выходного потока stdout, поэтому их нельзя
использовать ни только как фильтры, но и как команду на первом месте в цепочке
команд, потому что фильтр не сможет получить от них никаких данных. Типичное
использование облегченных синхронных символов - это служебные внутренние функции,
которые не взаимодействуют с пользователем напрямую и не используются в цепочках
команд.
Первая команда в цепочке команд получает на вход потока stdin данные от
пользователя, если это требуется. Назовем поток stdin от пользователя -
глобальным потоком stdin. Затем поток stdout предыдущего символа в цепочке
поступает на вход потока stdin последующего символа. Выходной поток stdout
последнего в цепочке символа считается глобальным и отправляется обслуживающему
серверу, а затем пользователю.
Любой символ в цепочке имеет возможность писать в поток stderr. У всей цепочки
единый поток stderr, т.е. один для всех символов. Этот поток считается глобальным
stderr и отправляется обслуживающему серверу, а затем пользователю.
Количество команд в цепочке команд не ограничено. Команда klish должна быть
объявлена фильтром, чтобы ее можно было использовать после знака "|".
## Протокол KTP
Протокол KTP (Klish Transfer Protocol) используется для взаимодействия клиента
klish с обслуживающим
сервером klishd. Основная задача - передача команд и пользовательского ввода
от клиента - серверу, передача текстового вывода выполненной команды и статуса
выполнения команды - в обратную сторону. Кроме этого в протоколе предусмотрены
средства для получения клиентом от сервера вариантов автодополнения для
незавершенных команд и подсказок для команд и параметров.
Протокол KTP реализован средствами из библиотеки libfaux, которая упрощает
создание специализированного сетевого протокола на базе шаблона обобщенного
сетевого протокола. Протокол построен поверх потокового сокета. Сейчас в проекте
klish используются UNIX-сокеты для взаимодействия клиента и сервера.
Пакет протокола KTP состоит из заголовка фиксированной длины и набора
"параметров". В специальном поле заголовка хранится количество параметров.
Каждый параметр имеет свой небольшой заголовок, в котором указан тип параметра и
его длина. Таким образом в начале KTP пакета расположен KTP заголовок, за ним
заголовок первого параметра, данные первого параметра, заголовок второго
параметра, данные второго параметра и т.д. Все поля в заголовках передаются с
использованием сетевого (big-endian) порядка байт.
KTP заголовок:
|Размер, байт|Название поля |Значение поля|
|------------|---------------------------------|-------------|
|4 |Magic номер |0x4b545020 |
|1 |Версия протокола (major) |0x01 |
|1 |Версия протокола (minor) |0x00 |
|2 |Код команды | |
|4 |Статус | |
|4 |Не используется | |
|4 |Количество параметров | |
|4 |Длина всего пакета (с заголовком)| |
Коды команд:
|Код|Название |Направление|Описание |
|---|--------------|-----------|-----------------------------------------------|
|'i'|STDIN |-> |stdin пользовательский ввод (PARAM_LINE) |
|'o'|STDOUT |<- |stdout вывод команды (PARAM_LINE) |
|'e'|STDERR |<- |stderr вывод команды (PARAM_LINE) |
|'c'|CMD |-> |Команда на выполнение (PARAM_LINE) |
|'C'|CMD_ACK |<- |Результат выполнения команды |
|'v'|COMPLETION |-> |Запрос на автодополнение (PARAM_LINE) |
|'V'|COMPLETION_ACK|<- |Возможные дополнения (PARAM_PREFIX, PARAM_LINE)|
|'h'|HELP |-> |Запрос подсказки (PARAM_LINE) |
|'H'|HELP_ACK |<- |Подсказка (PARAM_PREFIX, PARAM_LINE) |
|'n'|NOTIFICATION |<-> |Асинхронное сообщение (PARAM_LINE) |
|'a'|AUTH |-> |Запрос на аутентификацию |
|'A'|AUTH_ACK |<- |Подтверждение аутентификации |
|'I'|STDIN_CLOSE |-> |stdin был закрыт |
|'O'|STDOUT_CLOSE |-> |stdout был закрыт |
|'E'|STDERR_CLOSE |-> |stderr был закрыт |
Колонка "Направление" показывает в каком направлении передается команда. Стрелка
вправо означает передачу от клиента к серверу, стрелка влево - от сервера
клиенту. В колонке "Описание" в скобках приведены названия параметров, в которых
передаются данные.
Поле статуса в KTP заголовке представляет собой 32-битное поле. Значения битов
поля статуса:
|Маска бита|Название |Значение |
|----------|------------------|-----------------------------------------------|
|0x00000001|STATUS_ERROR |Ошибка |
|0x00000002|STATUS_INCOMPLETED|Выполнение команды не завершено |
|0x00000100|STATUS_TTY_STDIN |stdin клиента является терминалом |
|0x00000200|STATUS_TTY_STDOUT |stdout клиента является терминалом |
|0x00000400|STATUS_TTY_STDERR |stderr клиента является терминалом |
|0x00001000|STATUS_NEED_STDIN |Команда принимает пользовательский ввод |
|0x00002000|STATUS_INTERACTIVE|Вывод команды предназначен для терминала |
|0x00010000|STATUS_DRY_RUN |Холостой запуск. Команду выполнять не требуется|
|0x80000000|STATUS_EXIT |Завершение сессии |
Заголовок параметра:
|Размер, байт|Название поля |
|------------|--------------------------------------|
|2 |Тип параметра |
|2 |Не используется |
|1 |Длина данных параметра (без заголовка)|
Типы параметров:
|Код|Название |Направление|Описание |
|---|-------------|-----------|---------------------------------------------|
|'L'|PARAM_LINE |<-> |Строка. Многофункциональна |
|'P'|PARAM_PREFIX |<- |Строка. Для автодополнения и подсказки |
|'$'|PARAM_PROMPT |<- |Строка. Пользовательское приглашение |
|'H'|PARAM_HOTKEY |<- |Функциональная клавиша и ее значение |
|'W'|PARAM_WINCH |-> |Размеры пользовательского окна. При изменении|
|'E'|PARAM_ERROR |<- |Строка. Сообщение об ошибке |
|'R'|PARAM_RETCODE|<- |Код возврата выполненной команды |
От сервера к клиенту, вместе с командой и соответствующими команде параметрами,
могут передаваться дополнительные параметры. Например с командой CMD_ACK,
сообщающей о завершении выполнения пользовательской команды, может посылаться
параметр PARAM_PROMPT, сообщающий клиенту о том, что пользовательское
приглашение изменилось.
## Структура XML конфигурации
Основным способом описания klish команд на сегодняшний день являются XML файлы.
В данном разделе все примеры будут основаны на XML элементах.
### Области видимости
Команды организованы в "области видимости", называемые VIEW. Т.е. каждая команда
принадлежит какому-либо VIEW и в нем определена. При работе в klish всегда
существует "текущий путь". По умолчанию, при запуске klish текущим путем
назначается VIEW с именем "main". Текущий путь определяет какие команды в данный
момент видны оператору. Текущий путь можно менять
[командами навигации](#навигация). Например
перейти в "соседний" VIEW, тогда текущим путем станет этот соседний VIEW,
вытеснив старый. Другая возможность - "уйти вглубь", т.е. зайти во вложенный
VIEW. Тогда текущий путь станет двух-уровневым, подобно тому, как можно зайти во
вложенную директорию в файловой системе. Когда текущий путь имеет более одного
уровня, оператору доступны команды самого "глубокого" VIEW, а также команды всех
вышележащих уровней. С помощью команд навигации можно выходить из вложенных
уровней. При смене текущего пути используемая команда навигации определяет будет
ли VIEW текущего пути вытеснен на том же уровне вложенности другим VIEW, либо
новый VIEW станет вложенным и в текущем пути появится еще один уровень. То, как
VIEW определены в XML файлах не влияет на то, может ли VIEW быть вложенным.
При определении VIEW в XML файлах, одни VIEW могут быть вложены в другие VIEW.
Не надо путать эту вложенность с вложенностью при формировании текущего пути.
VIEW, определенный внутри другого VIEW добавляет свои команды к командам
родительского VIEW, но при этом может адресоваться и отдельно от родительского.
Кроме этого существуют ссылки на VIEW. Объявив такую ссылку внутри VIEW, мы
добавляем команды того VIEW, на который указывает ссылка, к командам текущего
VIEW. Можно определить VIEW со "стандартными" командами и включать ссылку на
этот VIEW в другие VIEW, где требуются эти команды, не переопределяя их заново.
В XML файлах конфигурации для объявления VIEW используется тег `VIEW`.
### Команды и параметры
Команды (тег `COMMAND`) могут иметь параметры (тег `PARAM`). Команда определяется
внутри какого-либо VIEW и принадлежит ему. Параметры определяются внутри команды
и, в свою очередь, принадлежат ей. Команда может состоять только из одного слова,
в отличие от команд в klish предыдущих версий. Если нужно определить многословную
команду, то создаются вложенные команды. Т.е. внутри команды, идентифицируемой по
первому слову многословной команды, создается команда, идентифицируемая по
второму слову многословной команды и т.д.
Строго говоря, команда отличается от параметра только тем, что ее значением может
быть только заранее определенное слово, в то время, как значением параметра может
быть что угодно. Только тип параметра определяет его возможные значения. Таким
образом команду можно рассматривать как параметр с типом, который говорит о том,
что допустимым значением является само имя параметра. Внутренняя реализация
команд именно такая.
В общем случае параметр может определяться непосредственно во VIEW, а не внутри
команды, но это скорее нетипичный случай.
Как и VIEW, команды и параметры могут быть ссылками. В этом случае ссылку можно
рассматривать просто как подстановку того объекта, на который указывает ссылка.
Параметры могут быть обязательными, опциональными, либо являться обязательным
выбором среди нескольких параметров - кандидатов. Таким образом при вводе команды
оператором некоторые параметры могут быть указаны, а некоторые нет. При разборе
командной строки составляется последовательность выбранных параметров.
### Тип параметра
Тип параметра определяет допустимые значения этого параметра. Обычно типы
определяются отдельно с помощью тега `PTYPE`, а параметр ссылается на определенный
тип по его имени. Также тип может быть определен прямо внутри параметра, но это
не типичный случай, т.к. стандартными типами удается покрыть большую часть
потребностей.
Тип PTYPE, как и команда, выполняет определенное действие, заданное тегом
`ACTION`. Действие, заданное для типа, проверяет введенное оператором значение
параметра и возвращает результат - успех или ошибка.
### Действие
Каждая команда должна определять действие, выполняемое при вводе этой команды
оператором. Действие может быть одно, либо несколько действий для одной команды.
Действие объявляется тегом `ACTION` внутри команды. В ACTION указывается ссылка
на символ (функцию) из плагина функций, которая будет исполнена в данном случае.
Все данные внутри тега ACTION доступны символу. Символ по своему усмотрению может
использовать эту информацию. В качестве данных, например, может быть задан
скрипт, который будет выполнен символом.
Результатом выполнения действия является "код возврата". Он определяет успешность
или неуспешность выполнения команды в целом. Если для одной команды определено
более одного действия, то вычисление кода возврата становится более сложной
задачей. Каждое действие имеет флаг, определяющий влияет ли код возврата
текущего действия на общий код возврата. Так же действия имеют настройки,
определяющие будет ли выполняться действие при условии, что предыдущее действие
завершилось с ошибкой. Если последовательно выполняются несколько действий,
имеющих флаг влияния на общий код возврата, то общим кодом возврата будет код
возврата последнего такого действия.
### Фильтры
Фильтры представляют собой команды, которые обрабатывают вывод других команд.
Фильтр может быть указан в командной строке после основной команды и знака `|`.
Фильтр не может быть самостоятельной командой и использоваться без основной
команды. Примером типичного фильтра может служить аналог UNIX утилиты `grep`.
Фильтры могут использоваться один за другим, с разделителем `|`, как это
делается в shell.
Если команда не является фильтром, то она не может использоваться после символа
`|`.
Фильтр задается в файлах конфигурации с помощью тега `FILTER`.
### Контейнеры параметров
Контейнеры SWITCH и SEQ используются для агрегации вложенных в них параметров и
определяют правила по которым происходит разбор командной строки относительно
этих параметров.
По умолчанию считается, что если внутри команды определено последовательно
несколько параметров, то все эти параметры так же последовательно должны
присутствовать в командной строке. Однако иногда возникает необходимость, чтобы
оператор ввел лишь один параметр по выбору из набора возможных параметров.
В таком случае может использоваться контейнер SWITCH. Если при разборе командной
строки внутри команды встречается контейнер SWITCH, то для соответствия
следующему введенному оператором слова должен быть выбран только один параметр
из параметров, вложенных в SWITCH. Т.е. с помощью контейнера SWITCH происходит
ветвление разбора.
Контейнер SEQ определяет последовательность параметров. Все они должны быть
последовательно сопоставлены с командной строкой. Хотя, как было отмечено ранее,
вложенные в команду параметры и так разбираются последовательно, однако
контейнер может быть полезен в более сложных конструкциях. Например контейнер
SEQ сам может быть элементом контейнера SWITCH.
### Приглашения командной строки
Для формирования приглашения командной строки, которую видит оператор,
используется конструкция `PROMPT`. Тег PROMPT должен быть вложен внутрь VIEW.
Разные VIEW могут иметь разные приглашения. Т.е. в зависимости от текущего
пути, оператор видит разное приглашение. Приглашение может быть динамическим
и генерироваться действиями `ACTION`, заданными внутри PROMPT.
### Автодополнение и подсказки
Для удобства работы оператора для команд и параметров могут быть реализованы
подсказки и автодополнение. Подсказки (help), поясняющие предназначение и
формат возможных параметров, отображаются в клиенте klish по нажатию клавиши
`?`. Список возможных дополнений печатается при нажатии оператором клавиши `Tab`.
Для задания подсказок и списка возможных дополнений в конфигурации используются
теги `HELP` и `COMPL`. Эти теги должны быть вложенными относительно
соответствующих команд и параметров. Для простоты подсказки для параметра или
команды могут быть заданы атрибутом основного тега, если подсказка является
статическим текстом и ее не нужно генерировать. Если подсказка динамическая, то
ее содержимое генерируется действиями ACTION, вложенными внутрь HELP. Для
дополнений COMPL действия ACTION генерируют список возможных значений
параметра, разделенных переводом строки.
### Условный элемент
Команды и параметры могут быть скрыты от оператора на основании динамических
условий. Условие задается с помощью тега `COND`, вложенного внутрь
соответствующей команды или параметра. Внутри COND находятся одно или несколько
действий ACTION. Если код возврата ACTION `0`, т.е. успех, то параметр доступен
оператору. Если ACTION вернули ошибку, то элемент будет скрыт.
### Плагин
Процесс klishd загружает не все существующие плагины функций подряд, а только
те, которые указаны в конфигурации с помощью тега `PLUGIN`. Данные внутри тега
могут интерпретироваться функцией инициализации плагина по своему усмотрению,
в частности, как конфигурация для плагина.
### Горячие клавиши
Для удобства оператора в конфигурации команд klish могут быть заданы "горячие
клавиши". Тег для задания горячих клавиш - `HOTKEY`. Список горячих клавиш
передается сервером klishd клиенту. Клиент на свое усмотрение использует эту
информацию или не использует. Например для клиента автоматизированного управления
информация о горячих клавишах не имеет смысла.
При определении горячей клавиши указывается текстовая команда, которая должна
быть выполнена при нажатии горячей клавиши оператором. Это может быть быстрый
показ текущей конфигурации устройства, выход из текущего VIEW, либо любая другая
команда. Клиент klish "ловит" нажатие горячих клавиш и передает на сервер
команду, соответствующую нажатой горячей клавише.
### Ссылки на элементы
Некоторые теги имеют атрибуты, которые являются ссылками на другие, определенные
в файлах конфигурации, элементы. Например `VIEW` имеет атрибут `ref`, с помощью
которого в текущем месте схемы создается "копия" стороннего `VIEW`. Или тег
`PARAM` имеет атрибут `ptype`, с помощью которого ссылается на `PTYPE`, определяя
тип параметра. Для организации ссылок в klish предусмотрен свой "язык". Можно
сказать это сильно упрощенный аналог путей в файловой системе или XPath.
Путь до элемента в klish набирается, указывая все его родительские элементы с
разделителем `/`. Именем элемента считается значение его атрибута `name`. Путь
начинается также с символа `/`, обозначая корневой элемент.
> Относительные пути в данный момент не поддерживаются
```
```
Параметр "par1" ссылается на `PTYPE`, используя путь `/PTYPE1`. Имена типов
принято обозначать большими буквами, чтобы проще отличать типы от других
элементов. Здесь тип определен на самом верхнем уровне схемы. Базовые типы
обычно так и определяются, но `PTYPE` не обязан находится на верхнем уровне и
может быть вложен во `VIEW` или даже `PARAM`. В таком случае он будет иметь
более сложный путь.
`VIEW` с именем "view2" импортирует команды из `VIEW` с именем "view1_2",
используя путь `/view1/view1_2`.
Если, предположим, понадобится ссылка на параметр "par1", то путь будет выглядеть
так - `/view1/view1_2/cmd1/par1`.
На следующие элементы нельзя ссылаться. Они не имеют пути:
* `KLISH`
* `PLUGIN`
* `HOTKEY`
* `ACTION`
> Нельзя путать [текущий путь сессии](#области-видимости) с путем для создания
> ссылок. Путь при создании ссылок использует внутреннюю структуру схемы,
> заданную при написании файлов конфигурации. Это путь элемента внутри схемы,
> уникально его идентифицирующий. Это статический путь. Вложенность элементов
> при определении схемы лишь формирует необходимые наборы команд, эта вложенность
> для оператора не видна и не является вложенностью в терминах его работы с
> командной строкой. Он видит только лишь готовые линейные наборы команд.
>
> Текущий путь сессии является динамическим. Он
> формируется командами оператора и подразумевает возможность войти глубже, т.е.
> добавить к пути еще один уровень вложенности и получить доступ к
> дополнительному набору команд, либо подняться выше. С помощью
> текущего пути можно комбинировать созданные на этапе написания схемы линейные
> наборы команд. Также команды навигации позволяют полностью заменить текущий
> набор команд на другой, сменив `VIEW` на текущем уровне пути. Таким образом,
> существование текущего пути сессии может создать для оператора видимость
> ветвистого дерева конфигурации.
## Теги
### KLISH
Любой XML файл с конфигурацией klish должен начинаться открывающим тегом `KLISH`
и заканчиваться закрывающим тегом `KLISH`.
```
```
Заголовок вводит свое пространство имен XML по умолчанию
"https://klish.libcode.org/klish3". Заголовок стандартный и чаще всего нет смысла
его менять.
### PLUGIN
Самостоятельно klish не загружает никаких плагинов исполняемых функций.
Соответственно пользователю недоступны никакие символы для использования в
действиях ACTION. Загрузку плагинов нужно в явном виде прописывать в файлах
конфигурации. Для этого используется тег `PLUGIN`.
Обратите внимание, что даже базовый плагин с именем "klish" также не загружается
автоматически и его надо прописывать в файлах конфигурации. В этом плагине, в
частности, реализована навигация. Типичная конфигурация будет содержать строку:
```
```
Тег `PLUGIN` не может содержать других вложенных тегов.
#### Атрибут `name`
Атрибут определяет имя, под которым плагин будет распознаваться внутри файлов
конфигурации. Когда `ACTION` ссылается на символ, то может быть указано просто
имя символа, а может быть уточнено, в каком плагине искать символ.
```
```
В первом случае klish будет искать "my_sym" во всех плагинах и использует первый
найденный. Во втором случае поиск будет производиться только в плагине
"my_plugin". Кроме того, разные плагины могут содержать одноименные символы и
указание плагина позволит узнать, какой из символов имелся в виду.
Если не указаны атрибуты `id` и `file`, то `name` используется также для
формирования имени файла плагина. Плагин должен иметь имя `kplugin-.so` и
находиться в директории `/usr/lib/klish/plugins` (если klish был сконфигурирован
с `--prefix=/usr`).
#### Атрибут `id`
Атрибут используется для формирования имени файла плагина, если не указан атрибут
`file`. Плагин должен иметь имя `kplugin-.so` и находиться в директории
`/usr/lib/klish/plugins` (если klish был сконфигурирован
с `--prefix=/usr`).
Если указан атрибут `id`, то `name` не будет использоваться для формирования
имени файла плагина, а только для идентификации внутри файлов конфигурации.
```
```
#### Атрибут `file`
Полное имя файла плагина (разделяемой библиотеки) может быть указано в явном
виде. Если указан атрибут `file`, то никакие другие атрибуты не будут
использоваться для формирования имени файла плагина, а будет взято значение
`file` "как есть" и передано функции dlopen(). Это означает, что может быть
указан как абсолютный путь, так и просто имя файла, но в таком случае файл
должен находиться по стандартным путям, используемым при поиске разделяемой
библиотеки.
```
```
#### Данные внутри тега
Данные внутри тега `PLUGIN` могут быть обработаны функцией инициализации
плагина. Формат данных остается на усмотрение самого плагина. Например,
в качестве данных могут быть указаны настройки для плагина.
```
JuniperLikeShow = y
FirstKeyWithStatement = n
MultiKeysWithStatement = y
Colorize = y
```
### HOTKEY
Для более удобной работы в интерфейсе командной строки, для часто используемых
команд могут быть заданы "горячие клавиши". Горячая клавиша определяется с
помощью тега `HOTKEY`.
Для работы горячих клавиш нужна поддержка в клиентской программе, которая
подключается к серверу klishd. Клиент "klish" имеет такую поддержку.
Тег `HOTKEY` не может иметь вложенных тегов. Какие либо дополнительные данные
внутри тега не предусмотрены.
#### Атрибут `key`
Атрибут определяет, что именно должен нажать оператор для активации горячей
клавиши. Поддерживаются комбинации с клавишей "Ctrl". Например `^Z` означает,
что должна быть нажата комбинация клавиш "Ctrl" и "Z".
```
```
#### Атрибут `cmd`
Атрибут определяет какое именно действие будет выполнено при нажатии оператором
горячей клавиши. Значение атрибута `cmd` разбирается по тем же правилам, как
любая другая команда вручную введенная оператором.
```
pop
```
Команда, используемая в качестве значения атрибута `cmd` должно быть определена
в файлах конфигурации. Приведенный пример выполнит ранее определенную команду
`exit` при нажатии сочетания клавиш "Ctrl^Z".
### ACTION
Тег `ACTION` определяет действие, которое необходимо выполнить. Типичное
использование тега - внутри команды `COMMAND`. При вводе оператором
соответствующей команды, действия, определенные в `ACTION` будут выполнены.
Основным атрибутом `ACTION` является `sym`. Действие может выполнять только
символы (функции), определенные в плагинах. Атрибут `sym` ссылается на такой
символ.
Действия могут выполняться не только командой. Далее приведен список тегов,
внутри которых может встречаться `ACTION`:
* `ENTRY` - для чего будет использован `ACTION`, определяется параметрами `ENTRY`.
* `COMMAND` - выполняется действие, определенное в `ACTION`, при вводе оператором
соответствующей команды.
* `PARAM` - тоже, что и для `COMMAND`.
* `PTYPE` - `ACTION` определяет действия для проверки значения введенного
оператором параметра, имеющего соответствующий тип.
Внутри перечисленных элементов может быть одновременно несколько элементов
`ACTION`. Назовем это блоком элементов `ACTION`. Действия выполняются
последовательно, одно за другим, если иное не определено атрибутом `exec_on`.
Внутри одной команды может быть определено несколько блоков действий. Это
возможно, если команда имеет ветвление параметров или опциональные параметры.
Блоком считаются действия, определенные внутри одного элемента. Действия,
определенные в разных элементах, включая вложенные, принадлежат разным блокам.
Всегда выполняется только один блок действий.
```
```
В примере объявлена команда "cmd1", имеющая три альтернативных (указан может быть
только один из трех) опциональных параметра. Поиск действий для выполнения идет
с конца к началу при разборе введенной командной строки.
Так если оператор ввел
команду `cmd1`, то механизм разбора распознает команду с именем "cmd1" и будет
искать `ACTION` непосредственно в этом элементе. Будет найден `ACTION` с символом
"sym1".
Если оператор ввел команду `cmd1 opt1`, то строка "opt1" будет распознана,
как параметр (он же подкоманда) с именем "opt1". Поиск идет с конца, поэтому
сначала будет найден `ACTION` с символом "sym2". Как только блок действий найден,
больше поиск действий производиться не будет и "sym1" найден не будет.
Если оператор ввел команду `cmd1 opt2`, то будет найдено действие с символом
"sym1", так как элемент "opt2" не имеет собственных вложенных действий и поиск
уходит наверх к родительским элементам.
Если оператор ввел команду `cmd1 arbitrary_string`, то будет найдено действие с
символом "sym3".
#### Атрибут `sym`
Атрибут ссылается на символ (функцию) из плагина. Эта функция будет выполнена при
выполнении `ACTION`. В качестве значения может быть указано имя символа, например
`sym="my_sym"`. В таком случае поиск символа будет происходить по всем
загруженным плагинам. Если указан плагин, в котором надо искать символ, например
`sym="my_sym@my_plugin"`, то в других плагинах поиск производиться не будет.
В разных ситуациях может быть выгодно использовать разные подходы, относительно
того, указывать ли имя плагина для символа. С одной стороны, несколько плагинов
могут содержать символы с одинаковым именем и для однозначной идентификации
символа указание плагина будет обязательным. Кроме этого при указании плагина
поиск символа будет проходить немного быстрее. С другой стороны, можно написать
некие универсальные команды, в которых указываются символы без принадлежности к
плагину. Тогда несколько плагинов могут реализовать "интерфейс", т.е. все
используемые символы, а их реальное содержание будет зависеть от того, какой
плагин загружен.
#### Атрибут `lock`
> Внимание: атрибут пока не реализован
Некоторые действия требуют атомарности и эксклюзивного доступа к ресурсу. При
работе в klish это не обеспечивается автоматически. Два оператора могут
независимо, но одновременно запустить на выполнение одну и ту-же команду. Для
обеспечения атомарности или/и эксклюзивного доступа к ресурсу могут
использоваться блокировки `lock`. Блокировки в klish являются именованными.
Атрибут `lock` указывает, блокировку с каким именем захватит `ACTION` при
выполнении. Например `lock="my_lock"`, где "my_lock" - имя блокировки. Захват
блокировки с одним именем никак не влияет на блокировки с другим именем. Таким
образом в системе может быть реализована не одна глобальная блокировка, а
несколько отдельных, основанных, например на том, какой именно ресурс защищает
блокировка.
Если блокировка захвачена одним процессом, то другой процесс, при попытке
захватить ту же блокировку, приостановит свое выполнение до освобождения
блокировки.
#### Атрибут `in`
Атрибут показывает, может ли действие принимать данные через стандартный ввод
(stdin) от пользователя. Возможные значения - `true`, `false`, `tty`.
Значение по умолчанию `false`.
Значение `false` означает, что действие не принимает данные через
стандартный ввод. Значение `true` означает, что действие принимает данные через
стандартный ввод. Значение `tty` означает, что действие принимает данные через
стандартный ввод и, при этом, стандартный ввод является терминалом.
При выполнении действия, клиент ставится в известность о статусе стандартного
ввода и, в соответствии с ним, ждет от пользователя ввода или нет.
Если команда подразумевает последовательное выполнение сразу нескольких действий,
и хотя бы одно действие имеет атрибут со значением `tty`, то считается, что вся
команда имеет атрибут `tty`. Если в команде нет действий со значением атрибута
`tty`, то ищется атрибут со значением `true`. Если хоть одно действие имеет
значение атрибута `true`, то и вся команда имеет атрибут `true`. В противном
случае, команда имеет атрибут `false`.
Если пользователь отправил на выполнение не одну команду, а цепочку команд
(через знак `|`), то применяется следующая логика для уведомления клиента,
требуется ли получение стандартного ввода. Если хоть одно команда в цепочке имеет
значение атрибута `tty`, то стандартный ввод требуется. Если первая команда в
цепочке имеет атрибут `true`, то стандартный ввод требуется. В противном случае
стандартный ввод не требуется.
При выполнении действия, обслуживающий сервер может создать псевдотерминал и
предоставить его действию в качестве стандартного ввода. Будет ли стандартный
ввод псевдотерминалом или нет, определяется тем, является ли стандартный ввод на
клиентской стороне терминалом или нет. Эта информация передается от клиента
серверу в статусном слове.
#### Атрибут `out`
Атрибут показывает, выводит ли действие данные через стандартный вывод (stdout).
Возможные значения - `true`, `false`, `tty`.
Значение по умолчанию `true`.
Значение `false` означает, что действие не выводит данные через
стандартный вывод. Значение `true` означает, что действие выводит данные через
стандартный вывод. Значение `tty` означает, что действие выводит данные через
стандартный вывод и, при этом, стандартный вывод является терминалом.
При выполнении действия, клиент ставится в известность о статусе стандартного
вывода и, в соответствии с ним, вводит стандартный вывод в режим
необрабатываемого (raw) терминала или нет.
Обычно текстовый клиент запускает на своей стороне пейджер для обработки вывода
каждой команды. Если атрибут команды имеет значение `tty`, то пейджер не
запускается. Предполагается, что выполняется интерактивная команда.
Если команда подразумевает последовательное выполнение сразу нескольких действий,
и хотя бы одно действие имеет атрибут со значением `tty`, то считается, что вся
команда имеет атрибут `tty`. Если в команде нет действий со значением атрибута
`tty`, то ищется атрибут со значением `true`. Если хоть одно действие имеет
значение атрибута `true`, то и вся команда имеет атрибут `true`. В противном
случае, команда имеет атрибут `false`.
Если пользователь отправил на выполнение не одну команду, а цепочку команд
(через знак `|`), то применяется следующая логика для уведомления клиента о
статусе стандартного вывода. Если хоть одна команда в цепочке имеет
значение атрибута `tty`, то клиенту передается флаг "интерактивности". В
противном случае клиент работает в обычном режиме.
При выполнении действия, обслуживающий сервер может создать псевдотерминал и
предоставить его действию в качестве стандартного вывода. Будет ли стандартный
вывод псевдотерминалом или нет, определяется тем, является ли стандартный вывод
на клиентской стороне терминалом и установлен ли атрибут команды в значение
`tty`. Информация о том, является ли стандартный вывод со стороны клиента
терминалом передается от клиента серверу в статусном слове.
#### Атрибут `permanent`
Система klish может быть запущена в режиме "dry-run", когда все действия не
будут в реальности выполняться, а их код возврата всегда будет иметь значение
"успех". Такой режим может использоваться для тестирования, для проверки
корректности входящих данных и т.д.
Однако некоторые символы должны исполняться всегда, независимо от режима.
Примером такого символа может выступать функция навигации. Т.е. менять
текущий путь сессии нужно всегда, в независимости от режима работы.
Флаг `permanent` может менять поведение действия в режиме "dry-run".
Возможные значения атрибута - `true` и `false`. По умолчанию `false`, т.е.
действие не является "постоянным" и будет отключено в режиме "dry-run".
Символы, при объявлении их в плагине, уже имеют признак постоянства. Символ
может быть принудительно объявлен постоянным, принудительно объявлен
непостоянным, либо плагин может отдать решение о постоянстве на откуп
пользователю. Если флаг постоянства объявлен принудительно в плагине, то
настройка атрибута `permanent` не будет влиять ни на что. Она не может
переопределить флаг постоянства, принудительно определенный внутри плагина.
#### Атрибут `sync`
Символ может выполняться "синхронно" или "асинхронно". В синхронном режиме код
символа будет запущен прямо в контексте текущего процесса - сервера klishd. В
асинхронном режиме для запуска кода символа будет порожден (fork()) отдельный
процесс. Запуск символа в асинхронном режиме является более безопасным, так как
ошибки в коде не повлияют на процесс klishd. Рекомендуется использовать
асинхронный режим.
Возможные значения атрибута - `true` и `false`. По умолчанию `false`, т.е.
символ будет выполняться асинхронно.
Символы, при объявлении их в плагине, уже имеют признак синхронности. Символ
может быть принудительно объявлен синхронным, асинхронным, либо плагин может
отдать решение о синхронности на откуп пользователю. Если флаг постоянства
объявлен принудительно в плагине, то настройка атрибута `sync` не будет влиять
ни на что. Она не может переопределить флаг синхронности, принудительно
определенный внутри плагина.
#### Атрибут `update_retcode`
В одной команде может содержаться несколько элементов `ACTION`. Это называется
"блок действий". Каждое из действий имеет свой код
возврата. Однако команда в целом тоже должна иметь код возврата и этот код
должен быть одним значением, а не массивом.
По умолчанию действия `ACTION` выполняются последовательно и как только одно из
действий вернет ошибку, выполнение блока останавливается и общим кодом возврата
считается ошибка. Если ни одно действие из блока не вернуло ошибку, то кодом
возврата считается код возврата последнего действия в блоке.
Иногда требуется, чтобы в независимости от кода возврата определенного действия
выполнение блока продолжилось. Для этого может использоваться атрибут
`update_retcode`. Атрибут может принимать значение `true` или `false`. По
умолчанию используется `true`. Это означает, что код возврата текущего действия
влияет на общий код возврата. На этом этапе общему коду возврата будет присвоено
значение текущего кода возврата. Если значение флага установлено в значение
`false`, то текущий код возврата игнорируется и никак не будет влиять на
формирование общего кода возврата. Также и выполнение блока действий не будет
прервано в случае ошибки на этапе выполнения текущего действия.
#### Атрибут `exec_on`
При выполнении блока действий (несколько `ACTION` внутри одного элемента),
действия выполняются последовательно, пока не будут выполнены все действия,
либо пока одно из действий не вернет ошибку. В таком случае выполнение блока
прерывается. Однако это поведение можно регулировать атрибутом `exec_on`.
Атрибут может принимать следующие значения:
* `success` - текущее действие будет отправлено на выполнение, если значение
общего кода возврата на данный момент - "успех".
* `fail` - текущее действие будет отправлено на выполнение, если значение
общего кода возврата на данный момент - "ошибка".
* `always` - текущее действие будет выполнено вне зависимости от общего кода
возврата.
* `never` - действие не будет выполняться ни при каких условиях.
По умолчанию используется значение `success`, т.е. действия выполняются, если
до этого не было ошибок.
```
1
2
3
4
/bin/false
6
7
8
9
```
Данный пример выведет на экран:
```
1
3
7
8
```
Строка "1" выведется, потому что вначале выполнения блока действий общий код
возврата принимается равным значению "успех", а так же значение `exec_on` по
умолчанию равно `success`.
Строка "2" не выведется, потому что `on_exec="never"`, т.е. не выполнять ни при
каких условиях.
Строка "3" выполнится, потому что предыдущее действие (строка "1") выполнилось
успешно.
Строка "4" не выполнится, потому что стоит условие `on_exec="fail"`, а при этом
предыдущее действие "3" выполнилось успешно и установило общий код возврата в
значение "успех".
Строка "5" выполнится и переведет общий код возврата в значение "ошибка".
Строка "6" не выполнится, потому что текущий общий код возврата равен значению
"ошибка", а строка должна выполнится, только если общий код возврата успешен.
Строка "7" выведется, так как стоит условие `on_exec="fail"`, текущий общий код
возврата действительно равен "ошибке". Обратите внимание, что хотя само действие
выполнится успешно, общий код возврата не будет изменен, так как использован
атрибут `update_retcode="false"`.
Строка "8" выведется, потому что стоит условие `on_exec="always"`, что означает
выполнить действие вне зависимости от текущего общего кода возврата.
Строка "9" не выведется, потому что строка "8" изменила общий код возврата на
значение "успех".
#### Данные внутри тега
Данные внутри тега `ACTION` используются по усмотрению самого символа,
указанного атрибутом `sym`. Как пример можно привести символ `script` из плагина
`script`. Этот символ использует данные внутри тега, как код скрипта, который
он должен выполнить.
```
ls /
#!/usr/bin/python
import os
print('ENV', os.getenv("KLISH_COMMAND"))
```
Обратите внимание, что в команде "pytest" указан шебанг `#!/usr/bin/python`,
который указывает с помощью какого интерпретатора нужно выполнять скрипт.
### ENTRY
> Обычно тег `ENTRY` не используется в файлах конфигурации в явном виде.
> Однако тег является базовым для большинства других тегов и большая часть его
> атрибутов наследуется.
Если смотреть на внутреннюю реализация klish, то там не найти всего множества
тегов, доступных при написании XML конфигурации. На самом деле существует базовый
элемент `ENTRY`, который реализует функции большинства других тегов. Элемент
"превращается" в другие теги в зависимости от значения своих атрибутов. Следующие
теги по внутренней реализации являются элементом `ENTRY`:
* [`VIEW`](#view)
* [`COMMAND`](#command)
* [`FILTER`](#filter)
* [`PARAM`](#param)
* [`PTYPE`](#ptype)
* [`COND`](#cond)
* [`HELP`](#help)
* [`COMPL`](#compl)
* [`PROMPT`](#prompt)
* [`SWITCH`](#switch)
* [`SEQ`](#seq)
В данном разделе будут довольно подробно рассмотрены атрибуты элемента `ENTRY`,
зачастую являющиеся атрибутами также и других элементов. Другие элементы будут
ссылаться на эти описания в разделе `ENTRY`. Примеры конфигурации, при описании
атрибутов, не обязательно основаны на элементе `ENTRY`, а используют другие,
наиболее типичные теги - "обёртки".
Основа элемента `ENTRY` - атрибуты, определяющие особенности его поведения и
возможность вложить внутрь элемента `ENTRY` другие элементы `ENTRY`. Таким
образом строится вся схема конфигурации.
#### Атрибут `name`
Атрибут `name` является идентификатором элемента. Среди элементов текущего уровня
вложенности, идентификатор должен быть уникальным. В разных ветках схемы могут
присутствовать элементы с одинаковым именем. Важно, чтобы был уникален абсолютный
путь элемента, т.е. комбинация имени самого элемента и имен всех его "предков".
Для тега `COMMAND` атрибут также служит значением позиционного параметра, если не
определен атрибут `value`. Т.е. оператор вводит строку, равную имени элемента
`COMMAND`, чтобы вызвать эту команду.
#### Атрибут `value`
Если идентификатор команды (атрибут `name`) отличается от имени команды для
оператора, то атрибут `value` содержит имя команды, каким оно представляется
оператору.
Используется для следующих тегов:
* `COMMAND`
* `PARAM`
* `PTYPE`
```
```
В примере идентификатор команды равен "cmdid". Он будет использоваться, если
нужно создать ссылку на этот элемент внутри XML конфигов. Но пользователь, чтобы
запустить команду на выполнение, в командной строке вводит текст `next`.
#### Атрибут `help`
При работе с командной строкой оператор может получить подсказку по возможным
командам, параметрам и их назначению. В клиенте "klish" подсказка будет показана
при нажатии клавиши `?`. Самый простой способ задать текст подсказки для элемента
- это указать значение атрибута `help`.
Следующие теги поддерживают атрибут `help`:
* `COMMAND`
* `PARAM`
* `PTYPE`
Подсказка, заданная с помощью атрибута `help`, является статической. Другой
способ задать подсказку для элемента - это создать вложенный элемент `HELP`.
Элемент `HELP` генерирует текст подсказки динамически.
```
ls -la "/etc/passwd"
```
Если для элемента одновременно заданы и атрибут `help` и вложенный элемент
`HELP`, то будет использоваться динамический вложенный элемент `HELP`, а атрибут
будет проигнорирован.
Элемент `PTYPE` имеет свои подсказки. Как статический атрибут, так и динамический
элемент. Эти подсказки будут использованы для параметра `PARAM`, использующего
этот тип данных, в том случае, если для параметра не задан ни атрибут, ни
динамический элемент `HELP`.
#### Атрибут `container`
"Контейнером" в терминах klish является элемент, который содержит другие
элементы, но сам не является видимым для оператора. Т.е. этот элемент не
сопоставляется никаким позиционным параметрам при разборе введенной командной
строки. Он только лишь организует другие элементы. Примером контейнеров являются
`VIEW`, `SWITCH`, `SEQ`. Тег `VIEW` организует команды в области видимости, но
сам элемент не является для оператора командой или параметром. Оператор не может
ввести в командной строке имя этого элемента, он может обратиться сразу к
вложенным в контейнер элементам (если они сами не являются контейнерами).
Элементы `SWITCH` и `SEQ` также не видны оператору. Они определяют, как должны
обрабатываться вложенные в них элементы.
Атрибут `container` может принимать значения `true` или `false`. По умолчанию
используется `false`, т.е. элемент не является контейнером. При этом надо
заметить, что для всех элементов - обёрток, наследованных от `ENTRY`, значение
атрибута заранее прописано в правильное значение. Т.е. использовать атрибут в
реальных конфигах обычно не приходится. Только в специфических случаях он
реально требуется.
#### Атрибут `mode`
Атрибут `mode` определяет то, как будут обрабатываться вложенные элементы при
разборе введенной командной строки. Возможные значения:
* `sequence` - вложенные элементы будут сопоставляться введенным оператором
"словам" последовательно, один за другим. Таким образом все вложенные элементы
могут принять значения при разборе.
* `switch` - только один из вложенных элементов должен быть сопоставлен вводу от
оператора. Это выбор одного из многих. Элементы, которые не были выбраны не
получат значений.
* `empty` - элемент не может иметь вложенных элементов.
Так элемент `VIEW` принудительно имеет атрибут `mode="switch"`, предполагая, что
он содержит внутри себя команды и не должно быть возможности в одной строке
ввести сразу много команд друг за другом. Оператор выбирает в данный момент
только одну команду.
Элементы `COMMAND` и `PARAM` по умолчанию имеют настройку `mode="sequence"`, так
как чаще всего внутри них размещены параметры, которые должны быть введены один
за другим. Однако есть возможность переопределить атрибут `mode` в тегах
`COMMAND`, `PARAM`.
Элементы `SEQ` и `SWITCH` являются контейнерами и созданы только для того, чтобы
менять способ обработки вложенных элементов. Элемент `SEQ` имеет
`mode="sequence"`, элемент `SWITCH` имеет `mode="switch"`.
Далее приведены примеры ветвлений внутри команды:
```
```
Команда по умолчанию имеет `mod="sequence"`, поэтому оператор должен будет ввести
оба параметра, один за другим.
```
```
Значение атрибута `mode` переопределено, поэтому оператор должен будет ввести
только один из параметров. Какому из параметров соответствуют введенные символы
в данном случае будет определяться следующим образом. Сначала проверяется на
корректность первый параметр "param1". Если строка соответствует формату целого
числа, то параметр принимает свое значение и второй параметр не проверяется на
соответствие, хотя он бы тоже подошел, так как тип `STRING` может содержать и
числа. Таким образом при выборе одного параметра из многих, порядок указания
параметров важен.
```
```
Данный пример идентичен примеру "2". Только вместо атрибута `mode` используется
вложенный тег `SWITCH`. Запись в примере "2" - короче, в примере "3" - нагляднее.
```
```
Тут демонстрируется, как идет разбор аргументов командной строки. Если выбран
параметр "param1", то далее используются его вложенные элементы "param3" и
"param4", а затем только следующий за `SWITCH` элемент "param5". Параметр
"param2" никак не используется.
Если вначале был выбран "param2", то затем обрабатывается "param5" и на этом
процесс завершается.
```
```
Пример полностью аналогичен поведению примера "4". Только вместо вложенности
используется тег `SEQ`, который говорит, что если уж был выбран первый параметр
из блока последовательных параметров, то остальные тоже необходимо ввести один
за другим.
```
```
Пример демонстрирует вложенную "подкоманду" с именем "cmd1_6". Тут подкоманда
ничем не отличается от параметра, кроме того, что критерий сопоставления
аргументов командной строки команде `COMMAND` состоит в том, чтобы введенный
аргумент совпадал с именем команды.
#### Атрибут `purpose`
Некоторые вложенные элементы должны иметь специальное значение. Например внутри
`VIEW` может быть определен элемент который генерирует текст приглашения для
оператора. Чтобы отделить элемент для генерации приглашения от вложенных команд,
необходимо придать ему специальный признак. Позже, когда сервер klishd должен
будет получить приглашение пользователя для этого `VIEW`, код просмотрит
вложенные элементы `VIEW` и выберет элемент, который специально для этого
предназначен.
Атрибут `purpose` устанавливает элементу специальное назначение. Возможные
назначения:
* `common` - специальное назначение отсутствует. Обычные теги имеет именно это
значение атрибута.
* `ptype` - элемент определяет тип родительского параметра. Тег `PTYPE`.
* `prompt` - элемент служит для генерации приглашения пользователя для
родительского элемента. Тег `PROMPT`. Родительским элементом является `VIEW`.
* `cond` - элемент проверяет условие и, в случае неудачи, родительский элемент
становится недоступен для оператора. Тег `COND`. На данный момент не реализован.
* `completion` - элемент генерирует возможные автодополнения для родительского
элемента. Тег `COMPL`.
* `help` - элемент генерирует подсказку для родительского элемента. Тег `HELP`.
Обычно атрибут `purpose` не используется в файлах конфигурации напрямую, так
как для каждого специального назначения введен свой тег, что более наглядно.
#### Атрибут `ref`
Элемент схемы может быть ссылкой на другой элемент схемы. Создание элемента "#1",
являющегося ссылкой на элемент "#2" эквивалентно тому, что элемент "#2" будет
объявлен в том месте схемы, где расположен элемент "#1". Вернее будет сказать,
что это эквивалентно созданию копии элемента "#2" на том месте, где определен
элемент "#1".
Если элемент является ссылкой, то в нем определен атрибут `ref`. Значение атрибута
- ссылка на целевой элемент схемы.
```
```
В примере "view2" содержит ссылку на "view1", что эквивалентно объявлению копии
"view1" внутри "view2". А это в свою очередь означает, что команда "cmd1" станет
доступна во "view2".
В другом `VIEW` с именем "view3" объявлена ссылка на команду "cmd2". Таким
образом отдельная команда становится доступна внутри "view3".
Параметры "param1" и "param2" имеют одинаковый тип `/ptype1`. Ссылка на тип
может быть прописана с помощью атрибута `ptype`, либо с помощью вложенного
тега `PTYPE`, который является ссылкой на ранее объявленный `PTYPE`. В итоге
типы двух объявленных параметров полностью эквивалентны.
В последнем примере ссылка на `VIEW` заключена внутрь тега `COMMAND`. В данном
случае это будет означать, что если мы работаем с "view4", то команды из
"view1" будут доступны с префиксом "do". Т.е. если оператор находится во
`VIEW` с именем "view4", то он должен написать в командной строке `do cmd1`,
чтобы выполнить команду "cmd1".
С помощью ссылок можно организовать повторное использование кода. Например
объявить блок "стандартных" параметров и потом с помощью ссылок вставлять
этот блок во все команды, где параметры повторяются.
#### Атрибут `restore`
Предположим текущий путь состоит из нескольких уровней. Т.е. оператор вошел во
вложенный `VIEW`. Находясь во вложенном `VIEW`, оператору доступны команды не
только текущего `VIEW`, но и всех родительских. Оператор вводит команду,
определенную в родительском `VIEW`. Атрибут `restore` определяет, будет ли
изменен текущий путь перед выполнением команды.
Возможные значения атрибута `restore` - `true` или `false`. По умолчанию
используется `false`. Это означает, что команда будет выполнена, а текущий путь
останется прежним. Если `restore="true"`, то перед выполнением команды текущий
путь будет изменен. Вложенные уровни пути будут сняты до уровня того `VIEW`, в
котором определена введенная команда.
Атрибут `restore` используется в элементе `COMMAND`.
Поведение с восстановлением "родного" пути команды может быть использовано в
системе, где режим конфигурирования реализован по принципу маршрутизаторов
"Cisco". В таком режиме переход в одну секцию конфигурирования возможен не
выходя заранее из другой - соседней секции. Для этого требуется предварительно
сменить текущий путь, подняться до уровня вводимой команды, а затем сразу же
перейти в новую секцию, но уже на основании текущего пути, соответствующего
команде входа в новую секцию.
#### Атрибут `order`
Атрибут `order` определяет важен ли порядок при вводе объявленных друг за другом
опциональных параметров. Предположим объявлены три идущих подряд опциональных
параметра. По умолчанию `order="false"` и это означает, что оператор может
ввести эти три параметра в произвольном порядке. Скажем, сначала третий, потом
первый и за ним второй. Если на втором параметре стоит флаг `order="true"`, то
введя сначала второй параметр, оператор уже не сможет ввести после него первый.
Ему останется доступен только третий параметр.
Атрибут `order` используется в элементе `PARAM`.
#### Атрибут `filter`
> Внимание: Фильтры пока не работают.
Некоторые команды являются фильтрами. Это означает, что они не могут
использоваться самостоятельно. Фильтры только обрабатывают вывод других команд.
Фильтры указываются после основной команды и символа разделителя `|`. Фильтр
не может быть использован до первого знака `|`. При этом команда, не
являющаяся фильтром, не может быть использована после символа `|`.
Атрибут `filter` может принимать значения `true` или `false`. По умолчанию
используется `filter="false"`. Это означает, что команда не является фильтром.
Если `filter="true"`, то команда объявляется фильтром.
Атрибут `filter` редко используется в конфигах в явном виде. Введен тег `FILTER`,
который объявляет команду, являющуюся фильтром. Кроме указанных ограничений по
использованию фильтров, фильтры ничем не отличаются от обычных команд.
Типичным примером фильтра является стандартная утилита "grep".
#### Атрибуты `min` и `max`
Атрибуты `min` и `max` используются в элементе `PARAM` и определяют сколько
аргументов, введенных оператором, могут быть сопоставлены текущему параметру.
Атрибут `min` определяет минимальное количество аргументов, которые должны
соответствовать параметру, т.е. должны пройти проверку на корректность
относительно типа данных этого параметра. Если `min="0"`, то параметр
становится опциональным. Т.е. если он не введен, то это не является ошибкой.
По умолчанию принимается `min="1"`.
Атрибут `max` определяет максимальное количество однотипных аргументов, которые
могут быть сопоставлены параметру. Если оператор вводит большее количество
аргументов, чем задано в атрибуте `max`, то эти аргументы не будут проверяться
на соответствие текущему параметру, но могут проверяться на соответствие
другим параметрам, определенным после текущего. По умолчанию принимается
`max="1"`.
Атрибут `min="0"` может использоваться в элементе `COMMAND`, чтобы объявить
подкоманду опциональной.
```
```
В примере параметр "param1" объявлен опциональным. Параметру "param2" должно
соответствовать ровно три аргумента, введенных оператором. Параметру "param3"
могут соответствовать от двух до пяти аргументов. Подкоманда "subcommand"
объявлена опциональной.
### VIEW
`VIEW` предназначены для организации команд и других элементов конфигурации в
["области видимости"](#области-видимости). При работе оператора с klish,
существует текущий путь сессии. Элементами пути являются элементы `VIEW`.
Менять текущий путь можно с помощью [команд навигации](#навигация).
Если оператор находится во вложенном `VIEW`, т.е. текущий путь сессии содержит
несколько уровней, подобно вложенным директориям в файловой системе, то оператору
доступны все команды, принадлежащие не только `VIEW`, находящемуся на самом
верхнем уровне пути, но и всех "предыдущих" `VIEW`, составляющих путь.
Можно создавать "теневые" `VIEW`, которые никогда не станут элементами текущего
пути. К этим `VIEW` можно обращаться по [ссылкам](#ссылки-на-элементы) и таким
образом добавлять их содержимое в то место схемы, где создана ссылка.
`VIEW` могут быть определены внутри следующих элементов:
* `KLISH`
* `VIEW`
* `COMMAND`
* `PARAM`
#### Атрибуты элемента `VIEW`
* [`name`](#атрибут-name) - идентификатор элемента.
* [`help`](#атрибут-help) - описание элемента.
* [`ref`](#атрибут-ref) - ссылка на другой `VIEW`.
#### Примеры
```
```
Пример демонстрирует как работают области видимости относительно доступных
оператору команд.
Если текущий путь сессии `/view1`, то оператору доступны команды "cmd1" и "cmd2".
Если текущий путь сессии `/view2`, то оператору доступны команды "cmd1", "cmd2",
"cmd3".
Если текущий путь сессии `/view3`, то оператору доступны команды "cmd2" и "cmd4".
Если текущий путь сессии `/view4`, то оператору доступна команда "cmd5".
Если текущий путь сессии `/view4/view1`, то оператору доступны команды "cmd1",
"cmd2", "cmd5".
Если текущий путь сессии `/view4/{/view1/view1_2}`, то оператору доступны команды
"cmd2", "cmd5". Тут фигурными скобками обозначен тот факт, что элементом пути
может является любой `VIEW` и не имеет значения является ли он вложенным при
определении схемы. Внутри фигурных скобок находится уникальный путь элемента
в схеме. В разделе ["Ссылки не элементы"](#ссылки-на-элементы) объясняется
разница между путем, уникально идентифицирующим элемент внутри схемы, и текущим
путем сессии.
### COMMAND
Тег `COMMAND` объявляет команду. Такая команда может быть выполнена оператором.
Для сопоставления введенного оператором аргумента с командой используется
атрибут [`name`](#атрибут-name). Если требуется, чтобы идентификатор команды
внутри схемы отличался от имени команды, какой она представляется оператору,
то `name` будет содержать внутренний идентификатор, а атрибут
[`value`](#атрибут-value) "пользовательское" название команды. Атрибуты `name` и
`value` могут содержать только одно слово, без пробелов.
Команда мало чем отличается от элемента [`PARAM`](#param). Команда может
содержать подкоманды. Т.е. внутри одной команды может быть объявлена другая
команда, которая на самом деле является параметром первой команды, только этот
параметр идентифицируется фиксированной текстовой строкой.
Типичная команда содержит подсказку [`help`](#атрибут-help) или
[`HELP`](#HELP) и, по необходимости, набор вложенных параметров
[`PARAM`](#param). Так же в команде должны быть указаны действия
[`ACTION`](#action), которые она выполняет.
#### Атрибуты элемента `COMMAND`
* [`name`](#атрибут-name) - идентификатор элемента.
* [`value`](#атрибут-value) - "пользовательское" название команды.
* [`help`](#атрибут-help) - описание элемента.
* [`mode`](#атрибут-mode) - режим обработки вложенных элементов.
* [`min`](#атрибуты-min-и-max) - минимальное количество аргументов командной
строки, сопоставляемых названию команды.
* [`max`](#атрибуты-min-и-max) - максимальное количество аргументов командной
строки, сопоставляемых названию команды.
* [`restore`](#атрибут-restore) - флаг восстановления "родного" для команды
уровня в текущем пути сессии.
* [`ref`](#атрибут-ref) - ссылка на другой `COMMAND`.
#### Примеры
```
echo "Second command"
```
Команда "cmd1" - простейший вариант команды с подсказкой, одним обязательным
параметром типа "ptype1" и выполняемым действием.
Команда "cmd2" - более сложная. Подсказка генерируется динамически. Первый
параметр является опциональной подкомандой с "пользовательским" именем "sub2"
и одним обязательным вложенным параметром. Т.е. оператор, желая использовать
подкоманду должен начать свою командную строку таким образом `cmd2 sub2 ...`.
Если опциональная подкоманда "cmd2_1" использована, то оператор должен указать
значение её обязательного параметра. Вторая подкоманда - это ссылка на другую
команду. Чтобы понять, что это будет значить, достаточно представить, что на
этом месте полностью описана команда на которую указывает ссылка, т.е. "cmd1".
За подкомандами следует обязательный числовой параметр и действие, выполняемое
командой.
### FILTER
Фильтр является командой [`COMMAND`](#command) с тем отличием, что команда
фильтрации не может использоваться самостоятельно. Она лишь обрабатывает вывод
других команд и может быть использована после основной команды и разделяющего
символа `|`. Более подробно использование фильтров описано в разделе
["Фильтры"](#фильтры).
Для тега `FILTER` атрибут [`filter`](#атрибут-filter) принудительно выставлен в
значение `true`. Остальные атрибуты и особенности работы совпадают с элементом
[`COMMAND`](#command).
### PARAM
Элемент `PARAM` описывает параметр команды. Параметр имеет тип, который задается
либо атрибутом `ptype`, либо вложенныи элементом [`PTYPE`](#ptype). При вводе
оператором аргумента, его значение проверяется кодом соответствующего `PTYPE` на
корректность.
В общем случае, значением для параметра может быть либо строка без пробелов,
либо строка с пробелами, заключенная в кавычки.
#### Атрибуты элемента `PARAM`
* [`name`](#атрибут-name) - идентификатор элемента.
* [`value`](#атрибут-value) - произвольное значение, которое может
анализироваться кодом `PTYPE`. Для элемента `COMMAND`, который является
частным случаем параметра, это поле используется как название "пользовательской"
команды.
* [`help`](#атрибут-help) - описание элемента.
* [`mode`](#атрибут-mode) - режим обработки вложенных элементов.
* [`min`](#атрибуты-min-и-max) - минимальное количество аргументов командной
строки, сопоставляемых параметру.
* [`max`](#атрибуты-min-и-max) - максимальное количество аргументов командной
строки, сопоставляемых параметру.
* [`order`](#атрибут-order) - режим обработки опциональных параметров.
* [`ref`](#атрибут-ref) - ссылка на другой `COMMAND`.
* `ptype` - ссылка на тип параметра.
#### Примеры
```
```
Параметры "param1", "param2", "param3" - идентичны. В первом случае тип задается
атрибутом `ptype`. Во втором - вложенным элементом `PTYPE`, который является
ссылкой на тот же тип "ptype1". В третьем - тип параметра определяется
"на месте", т.е. создается новый тип `PTYPE`. Но во всех трех случаях типом
является целое число (см. атрибут `sym`). Задавать тип прямо внутри параметра
может быть удобно, если такой тип больше нигде не нужен.
Параметр "param4" имеет вложенный параметр "param5". После ввода аргумента для
параметра "param4", оператор должен будет ввести аргумент для вложенного
параметра.
### SWITCH
Элемент `SWITCH` является [контейнером](#атрибут-container). Его единственная
задача - это задать режим обработки вложенных элементов. `SWITCH` задает
такой режим обработки вложенных элементов, когда из многих должен быть выбран
только один элемент.
В элементе `SWITCH` атрибут [`mode`](#атрибут-mode) принудительно выставлен в
значение `switch`.
Дополнительно о режимах обработки вложенных элементов можно прочитать в разделе
["Атрибут `mode`"](#атрибут-mode).
#### Атрибуты элемента `SWITCH`
* [`name`](#атрибут-name) - идентификатор элемента.
* [`help`](#атрибут-help) - описание элемента.
Обычно элемент `SWITCH` используется без атрибутов.
#### Примеры
```
```
По умолчанию элемент `COMMAND` имеет атрибут `mode="sequence"`. Если бы в
примере не было элемента `SWITCH`, то оператор должен был последовательно,
одну за другой задать все подкоманды: `cmd1 sub1 sub2 sub3`. Элемент `SWITCH`
изменил режим обработки вложенных элементов и в итоге оператор должен выбрать
только одну подкоманду из трех. Например `cmd1 sub2`.
### SEQ
Элемент `SEQ` является [контейнером](#атрибут-container). Его единственная
задача - это задать режим обработки вложенных элементов. `SEQ` задает
такой режим обработки вложенных элементов, когда все вложенные элементы должны
быть заданы один за другим.
В элементе `SEQ` атрибут [`mode`](#атрибут-mode) принудительно выставлен в
значение `sequence`.
Дополнительно о режимах обработки вложенных элементов можно прочитать в разделе
["Атрибут `mode`"](#атрибут-mode).
#### Атрибуты элемента `SWITCH`
* [`name`](#атрибут-name) - идентификатор элемента.
* [`help`](#атрибут-help) - описание элемента.
Обычно элемент `SEQ` используется без атрибутов.
#### Примеры
```
```
Предположим, что мы создали вспомогательный `VIEW`, содержащий список часто
используемых параметров и назвали его "view1". Все параметры должны
использоваться последовательно, один за другим. А затем в другом `VIEW` объявили
команду, которая должна содержать все эти параметры. Для этого внутри команды
создается ссылка на "view1" и все параметры "попадают" внутрь команды. Однако
элемент `VIEW` по-умолчанию имеет атрибут `mode="switch"` и получается, что
оператор будет вводить не все объявленные параметры, а должен выбрать только
один из них. Чтобы изменить порядок разбора вложенных параметров, используется
элемент `SEQ`. Он меняет порядок разбора параметров на последовательный.
Того же результата можно было добиться добавлением атрибута `mode="sequence"` в
объявление "view1". Использование атрибута - короче, использование элемента
`SEQ` - более наглядно.
### PTYPE
Элемент `PTYPE` описывает тип данных для параметров. Параметры
[`PARAM`](#param) ссылаются на тип данных с помощью атрибута `ptype` или
содержат вложенный элемент `PTYPE`. Задача `PTYPE` проверить переданный
оператором аргумент на корректность и вернуть результат в виде "успех" или
"ошибка". Если сказать точнее, результат проверки можно выразить, как
"подходит" или "не подходит". Внутри элемента `PTYPE` указываются действия
[`ACTION`](#action), которые и выполняют проверку аргумента на соответствие
объявленному типу данных.
#### Атрибуты элемента `PTYPE`
* [`name`](#атрибут-name) - идентификатор элемента.
* [`help`](#атрибут-help) - описание элемента.
* [`ref`](#атрибут-ref) - ссылка на другой `PTYPE`.
#### Примеры
```
```
В примере объявляется тип данных "ptype1". Это целое число и символ `INT` из
стандартного плагина "klish" проверяет, что введенный аргумент действительно
является целым числом.
Другие варианты использования тега `PTYPE` и атрибута `ptype` рассматриваются в
разделе примеров элемента [`PARAM`](#param).
### PROMPT
Элемент `PROMPT` имеет специальное назначение и является вложенным для элемента
`VIEW`. Назначение элемента - формировать приглашение для пользователя, если
родительский `VIEW` является текущим путем сессии. Если текущий путь
многоуровневый, то не найдя элемента `PROMPT` в последнем элементе пути,
механизм поиска поднимется на уровень выше и будет искать `PROMPT` там. И так
вплоть до корневого элемента. Если и там `PROMPT` не будет найден, то
используется стандартное приглашение на усмотрение клиента klish. По умолчанию
клиент klish использует приглашение `$`.
Внутри элемента `PROMPT` указываются действия [`ACTION`](#action), которые
формируют текст приглашения для пользователя.
Элементу `PROMPT` принудительно присвоено значение атрибута `purpose="prompt"`.
Также `PROMPT` является контейнером.
#### Атрибуты элемента `PROMPT`
* [`name`](#атрибут-name) - идентификатор элемента.
* [`help`](#атрибут-help) - описание элемента.
* [`ref`](#атрибут-ref) - ссылка на другой `PROMPT`.
Обычно `PROMPT` используется без атрибутов.
#### Примеры
```
%u@%h>
```
В примере для `VIEW` с именем "main", который является текущим путем по
умолчанию при запуске klish, определяется приглашение пользователя. В `ACTION`
используется символ `prompt` из стандартного плагина "klish", который помогает
в формировании приглашения, заменяя конструкции `%u` или `%h` подстановками.
В частности `%u` заменяется на имя текущего пользователя, а `%h` на имя хоста.
### HELP
Элемент `HELP` имеет специальное назначение и является вложенным для элементов
`COMMAND`, `PARAM`, `PTYPE`. Назначение элемента - формировать текст
"помощи", т.е. подсказку для родительского элемента. Внутри элемента `HELP`
указываются действия [`ACTION`](#action), которые формируют текст подсказки.
Элементу `HELP` принудительно присвоено значение атрибута `purpose="help"`.
Также `HELP` является контейнером.
Клиент klish показывает подсказки по нажатию клавиши `?`.
#### Атрибуты элемента `HELP`
* [`name`](#атрибут-name) - идентификатор элемента.
* [`help`](#атрибут-help) - описание элемента.
* [`ref`](#атрибут-ref) - ссылка на другой `HELP`.
Обычно `HELP` используется без атрибутов.
#### Примеры
```
```
### COMPL
Элемент `COMPL` имеет специальное назначение и является вложенным для элементов
`PARAM`, `PTYPE`. Назначение элемента - формировать варианты автодополнения,
т.е. возможные варианты значений для родительского элемента. Внутри элемента
`COMPL` указываются действия [`ACTION`](#action), которые формируют вывод.
Отдельные варианты в выводе отделяются друг от друга переводом строки.
Элементу `COMPL` принудительно присвоено значение атрибута
`purpose="completion"`. Также `COMPL` является контейнером.
Клиент klish показывает варианты автодополнения по нажатию клавиши `Tab`.
#### Атрибуты элемента `COMPL`
* [`name`](#атрибут-name) - идентификатор элемента.
* [`help`](#атрибут-help) - описание элемента.
* [`ref`](#атрибут-ref) - ссылка на другой `COMPL`.
#### Примеры
```
tcp
udp
icmp
```
Параметр, задающий протокол имеет тип `/STRING` т.е. произвольная строка.
Оператор может ввести произвольный текст, но для удобства параметр имеет
варианты автодополнения.
### COND
> Функциональность элемента `COND` пока не реализована.
Элемент `COND` имеет специальное назначение и является вложенным для элементов
`VIEW`, `COMMAND`, `PARAM`. Назначение элемента - скрыть элемент от оператора в
случае, если указанное в `COND` условие не выполнено. Внутри элемента
`COND` указываются действия [`ACTION`](#action), которые проверяют условие.
Элементу `COND` принудительно присвоено значение атрибута
`purpose="cond"`. Также `COND` является контейнером.
#### Атрибуты элемента `COND`
* [`name`](#атрибут-name) - идентификатор элемента.
* [`help`](#атрибут-help) - описание элемента.
* [`ref`](#атрибут-ref) - ссылка на другой `COND`.
#### Примеры
```
test -e /tmp/cond_file
```
Если файл `/tmp/cond_file` существует, то команда "cmd1" доступна для оператора,
если не существует, то команда скрыта.
## Плагин "klish"
В состав дерева исходных кодов klish входит код стандартного плагина "klish".
Плагин содержит базовые типы данных, команду навигации и другие вспомогательные
символы. В подавляющем большинстве случаев этот плагин нужно использовать.
Однако он не подключается автоматически, т.к. в каких-то редких специфических
случаях может понадобиться возможность работать без него.
Стандартный способ подключить плагин "klish" в конфигурационных файлах:
```
```
Вместе с плагином идет файл `ptypes.xml`, где объявлены базовые типы данных в
виде элементов [`PTYPE`](#ptype). Объявленные типы данных используют символы
плагина для проверки соответствия аргумента типу данных.
### Типы данных
Все символы раздела предназначены для использования в элементах `PTYPE`, если
не указано иное, и проверяют соответствие введенного аргумента типу данных.
#### Символ `COMMAND`
Символ `COMMAND` проверяет, что введенный аргумент совпадает с именем команды.
Т.е. с атрибутами `name` или `value` элементов `COMMAND` или `PARAM`. Если
атрибут `value` задан, то используется его значение в качестве имени команды.
Если не задан, то используется значение атрибута `name`. Регистр символов в
названии команды не учитывается.
#### Символ `completion_COMMAND`
Символ `completion_COMMAND` предназначен для элемента `COMPL`, вложенного в
`PTYPE`. Символ используется, чтобы сформировать строку автодополнения для
команды. Автодополнением для команды является название самой команды. Если
атрибут `value` задан, то используется его значение в качестве имени команды.
Если не задан, то используется значение атрибута `name`.
#### Символ `help_COMMAND`
Символ `help_COMMAND` предназначен для элемента `HELP`, вложенного в
`PTYPE`. Символ используется, чтобы сформировать строку описания (помощи) для
команды. Если атрибут `value` задан, то используется его значение в качестве
имени команды. Если не задан, то используется значение атрибута `name`. В
качестве самой подсказки используется значение атрибута `help` команды.
#### Символ `COMMAND_CASE`
Символ `COMMAND_CASE` полностью аналогичен символу [`COMMAND`](#символ-command),
кроме того, что он учитывает регистр символов в названии команды.
#### Символ `INT`
Символ `INT` проверяет, что введенный аргумент является целым числом.
Разрядность числа соответствует типу `long long int` в языке C.
Внутри элемента [`ACTION`](#action) может быть определен допустимый диапазон для
целого числа. Указывается минимальное и максимальное значения, разделенные
пробелом.
```
-30 80
```
Число может принимать значения в диапазоне от "-30" до "80".
#### Символ `UINT`
Символ `UINT` проверяет, что введенный аргумент является беззнаковым целым
числом. Разрядность числа соответствует типу `unsigned long long int` в языке C.
Внутри элемента [`ACTION`](#action) может быть определен допустимый диапазон для
числа. Указывается минимальное и максимальное значения, разделенные пробелом.
```
30 80
```
Число может принимать значения в диапазоне от "30" до "80".
#### Символ `STRING`
Символ `STRING` проверяет, что введенный аргумент является строкой. Сейчас нет
никаких специфических требований к строкам.
### Навигация
С помощью команд навигации оператор меняет текущий путь сессии.
#### Символ `nav`
Символ `nav` предназначен для навигации. С помощью подкоманд символа `nav` можно
менять текущий путь сессии. Все подкоманды символа `nav` указываются внутри
элемента `ACTION` - каждая команда на отдельной строке.
Подкоманды символа `nav`:
* `push ` - войти в указанный `VIEW`. К текущему пути сессии добавляется
еще один уровень вложенности.
* `pop [num]` - выйти на указанное число уровней вложенности. Т.е. исключить из
текущего пути сессии `num` верхних уровней. Аргумент `num` является
опциональным. По умолчанию `num=1`. Если мы уже находимся в корневом `VIEW`, т.е.
текущий путь содержит только один уровень, то `pop` завершит сессию и выйдет из
klish.
* `top` - перейти в корневой уровень текущего пути сессии. Т.е. выйти из всех
вложенных `VIEW`.
* `replace ` - заменить `VIEW`, находящийся на текущем уровне вложенности
на указанный `VIEW`. Количество уровней вложенности не увеличивается. Меняется
только самый последний компонент пути.
* `exit` - закончить сессию и выйти из klish.
```
pop
push /view_name1
```
Пример показывает как можно повторить подкоманду `replace`, использую другие
подкоманды.
### Вспомогательные функции
#### Символ `nop`
Пустая команда. Символ ничего не делает. Всегда возвращает значение
`0` - "успех".
#### Символ `print`
Печатает текст, указанный в теле элемента `ACTION`. В конце текста перевод
строки не выводится.
```
Text to print
```
#### Символ `printl`
Печатает текст, указанный в теле элемента `ACTION`. В конце текста добавляется
перевод строки.
```
Text to print
```
#### Символ `pwd`
Печатает текущий путь сессии. Нужен в основном для отладки.
#### Символ `prompt`
Символ `prompt` помогает формировать текст приглашения для оператора. В теле
элемента `ACTION` указывается текст приглашения, который может содержать
подстановки вида `%s`, где "s" - это какой либо печатный символ. Вместо такой
конструкции в текст подставляется определенная строка. Список реализованных
подстановок:
* `%%` - сам символ `%`.
* `%h` - имя хоста.
* `%u` - имя текущего пользователя.
```
%u@%h>
```
## Плагин "script"
Плагин "script" содержит всего один символ `script` и служит для выполнения
скриптов. Скрипт содержится в теле элемента `ACTION`. Скрипт может быть написан
на разных скриптовых языках программирования. По умолчанию считается, что скрипт
написан для интерпретатора shell и запускается при помощи `/bin/sh`. Чтобы
выбрать другой интерпретатор, используется "шебанг" (shebang). Шебанг - это
текст вида `#!/path/to/binary`, находящийся в самой первой строке скрипта.
Текст `/path/to/binary` - это путь, по которому находится интерпретатор скрипта.
Плагин `script` входит в состав исходного кода проекта klish, а подключить
плагин можно следующим образом:
```
```
Имя выполняемой команды и значения параметров передаются в скрипт с помощью
переменных окружения. Поддерживаются следующие переменные окружения:
* `KLISH_COMMAND` - имя выполняемой команды.
* `KLISH_PARAM_<имя>` - значение параметра с именем `<имя>`.
* `KLISH_PARAM_<имя>_<индекс>` - один параметр может иметь много значений, если
для него задан атрибут `max` и значение этого атрибута больше единицы. Тогда
значения можно получить по индексу.
Примеры:
```
echo "$KLISH_COMMAND"
ls "$KLISH_PARAM_path"
#!/usr/bin/python
import os
print('ENV', os.getenv("KLISH_COMMAND"))
```
Команда "ls" использует интерпретатор shell и выводит на экран список файлов по
указанному в параметре "path" пути. Обратите внимание, что использование shell
может быть небезопасно, из-за потенциальной возможности оператора внедрить в
скрипт произвольный текст и, соответственно, выполнить произвольную команду.
Использование shell доступно, но не рекомендуется. Очень сложно написать
безопасный скрипт на shell.
Команда "pytest" выполняет скрипт на языке Python. Обратите внимание на то, где
определен шебанг. Первая строка скрипта - это строка, которая находится
непосредственно за элементом `ACTION`. Строка, следующая за строкой, в которой
объявлен `ACTION` считается уже второй и определять шебанг в ней нельзя.
## Плагин "lua"
Плагин "lua" содержит всего один символ `lua` и служит для выполнения
скриптов на языке "Lua". Скрипт содержится в теле элемента `ACTION`. В отличие
от символа `script` из плагина ["script"](#плагин-script), символ `lua` не
вызывает внешнюю программу-интерпретатор для выполнения скриптов, а использует
внутренние механизмы для этого.
Плагин `lua` входит в состав исходного кода проекта klish, а подключить
плагин можно следующим образом:
```
```
Содержимое тега может задавать конфигурацию.
### Параметры конфигурации
Рассмотрим параметры конфигурации плагина.
#### autostart
```
autostart="/usr/share/lua/klish/autostart.lua"
```
Когда плагин инициализируется, создаётся состояние Lua-машины которое (после fork)
и будет использоваться для вызова Lua `ACTION` кода. Если необходимо загрузить
какие-то библиотеки, это можно сделать за счёт задания autostart файла. Параметр
может быть только один.
#### package.path
```
package.path="/usr/lib/lua/clish/?.lua;/usr/lib/lua/?.lua"
```
Задаёт Lua package.path (пути по которым идёт поиск модулей). Параметр может
быть только один.
#### backtrace
```
backtrace=1
```
Показывать ли backtrace при падениях Lua кода. 0 или 1.
### API
При выполнении Lua `ACTION` доступны следующие функции:
#### klish.pars()
Возвращает информацию о параметрах. Возможно два варианта применения этой
функции.
```
local pars = klish.pars()
for k, v in ipairs(pars) do
for i, p in ipairs(pars[v]) do
print(string.format("%s[%d] = %s", v, i, p))
end
end
```
Получение информации о всех параметрах. В таком случае функция вызывается без
аргументов и возвращает таблицу всех параметров. Таблица содержит как список
имён, так и массив значений для каждого имени. В примере выше показан итератор
по всем параметрам с выводом их значений.
Кроме того, klish.pars может быть вызван для взятия значений конкретного
параметра, например:
```
print("int_val = ", klish.pars('int_val')[1])
```
#### klish.ppars()
Работает точно так же как и `klish.ppars()`, но только для родительских
параметров, если они есть в данном контексте.
#### klish.par() и klish.ppar()
Работают так же как и `klish.pars()`, `klish.ppars()` с заданием конкретного
параметра, но возвращают не массив, а значение. Если параметров с таким именем
несколько, то вернётся первый.
#### klish.path()
Возвращает текущий путь в виде массива строк. Например:
```
print(table.concat(klish.path(), "/"))
```
#### klish.context()
Позволяет получить некоторые параметры контекста команды. Принимает на вход
строку -- имя параметра контекста:
- val;
- cmd;
- pcmd;
- uid;
- pid;
- user.
Без параметра возвращает таблицу со всеми доступными параметрами контекста.