BASHUI

—

от автора

нажми меня

нажми меня

BASHUI — это BASH + UI, а не то что вы подумали.

Начиная работать над sshto я решил не переизобретать велосипед, вернее не переизобретать велосипед целиком а только некоторые его части и в качестве «рамы с педалями» использовал dialog. Это значительно ускорило разработку, но идея написать свой UI на баше с блекджеком и всем остальным ни на секунду не покидала мой воспалённый мозг. Звёзды сошлись, и я решил воплотить этот проект в жизнь(в bash).

Какой UI без кнопок? С(т)ранный, поэтому я начал с элемента — кнопка. Идея заключается в том что кнопка(и остальные элементы UI) будет представлена функцией. Функцию можно использовать из коробки. Но удобнее сделать «обёртку»(функцию) с какими-то предустановленными параметрами и уже эту функцию использовать по назначению. Для всех элементов UI я подготовил примеры(demo_*) их можно найти в репе. Вот как выглядит пример для кнопки:

#!/bin/bash source bashui  mess="RESULT" name="The Button" title="Push the button, will get a result..."  butt(){     # Новая кнопка на основе button из bashui, параметры:     #1 координата X(колонка)     #2 координата Y(строка)     #3 название кнопки     #4 выполняемая функция     #5 цвет текста     #6 цвет рамки     #7 цвет подложки     local x=$((COLUMNS/2-(${#name}/2+2)))     local y=$((LINES/2))     #       1  2    3       4       5      6       7     button $x $y "$name" "result" "$wht" "$ylw" "$bblk" }  # кнопка выполнит эту функцию result(){     local x=$((COLUMNS/2-${#mess}/2))     local y=$((LINES/2+5))     XY           $x $y  "$mess"     (sleep 1; XY $x $y "${mess//[[:print:]]/ }") & }  # все собрано вместе menu(){     cursor off          # отключаем курсор     default_button butt # это необходимо для активации кнопки     XY $((COLUMNS/2-${#title}/2)) $((LINES/2-2)) "$title"     butt # рисуем кнопку      # в цикле опрос клавиатуры и логика     while true; do         read_input         case $_input_ in               enter ) press_button butt;; # нажат enter, нажимаем кнопку               escape) return;;            # нажат escape, выход         esac     done }  clear menu

«Цветовая палитра» для выбора цвета текста, рамки и подложки задана в bashui вот так:

... #-------------------------+--------------------------------+---------+ #       Text color        |       Background color         |         | #-----------+-------------+--------------+-----------------+         | # Base color|Lighter shade| Base color   | Lighter shade   |         | #-----------+-------------+--------------+-----------------+         | BLK='\e[30m'; blk='\e[90m'; BBLK='\e[40m'; bblk='\e[100m' #| Black   | RED='\e[31m'; red='\e[91m'; BRED='\e[41m'; bred='\e[101m' #| Red     | GRN='\e[32m'; grn='\e[92m'; BGRN='\e[42m'; bgrn='\e[102m' #| Green   | YLW='\e[33m'; ylw='\e[93m'; BYLW='\e[43m'; bylw='\e[103m' #| Yellow  | BLU='\e[34m'; blu='\e[94m'; BBLU='\e[44m'; bblu='\e[104m' #| Blue    | MGN='\e[35m'; mgn='\e[95m'; BMGN='\e[45m'; bmgn='\e[105m' #| Magenta | CYN='\e[36m'; cyn='\e[96m'; BCYN='\e[46m'; bcyn='\e[106m' #| Cyan    | WHT='\e[37m'; wht='\e[97m'; BWHT='\e[47m'; bwht='\e[107m' #| White   | #-------------------------{ Effects }----------------------+---------+ DEF='\e[0m'   #Default color and effects                             | BLD='\e[1m'   #Bold\brighter                                         | DIM='\e[2m'   #Dim\darker                                            | CUR='\e[3m'   #Italic font                                           | ...

Этот код также доступен в отдельной репе bash_color. Попробуем получить результат:

result

result

Иногда необходимо количество кнопок больше чем одна. И надо как-то равномерно расположить их на экране. Чтобы упростить эту задачу я добавил две вспомогательные функции: x_row и y_row, для горизонтального и вертикального рядов кнопок соответственно, пример:

#!/bin/bash source bashui  defb(){     # Новая кнопка на основе button из bashui, параметры:     #1 координата X(колонка)     #2 координата Y(строка)     #3 название кнопки     #4 выполняемая функция     #5 цвет текста     #6 цвет рамки     #7 цвет подложки     #        1    2      3          4          5      6      7     button "$1" "$2" "butt $3" "fun_${3,,}" "$wht" "$ylw" "$bblk" }  # зачистка вывода clr(){ for i in {5..9}; do XY 15 $i "%$((COLUMNS-15))s"; done; }  but_a(){ defb "$1" "$2" A; } # клонирую but_b(){ defb "$1" "$2" B; } # кнопку but_c(){ defb "$1" "$2" C; } # по but_d(){ defb "$1" "$2" D; } # умолчанию but_e(){ defb "$1" "$2" E; } # шесть but_f(){ defb "$1" "$2" F; } # раз  fun_a(){ clr; XY 15 5 "butt A pressed(${_button_[*]})"; } # создаю fun_b(){ clr; XY 15 5 "butt B pressed(${_button_[*]})"; } # функцию fun_c(){ clr; XY 15 5 "butt C pressed(${_button_[*]})"; } # для fun_d(){ clr; XY 15 5 "butt D pressed(${_button_[*]})"; } # выполнения fun_e(){ clr; XY 15 5 "butt E pressed(${_button_[*]})"; } # каждой fun_f(){ clr; XY 15 5 "butt F pressed(${_button_[*]})"; } # кнопкой  # ряды кнопок это два массива ybuttons=(but_a but_b but_c) # вертикальные кнопки  xbuttons=(but_d but_e but_f) # горизонтальные кнопки  # оба обработчика(все кнопки) собраны в одну функцию для удобства butts(){     y_row    2   "${ybuttons[@]}"     x_row $LINES "${xbuttons[@]}" }  menu(){     cursor off     default_button but_b     XY 1 1 "$red Esc to exit"      while true; do         butts         read_input         case $_input_ in              up     ) select_prev_butt "${ybuttons[@]}";;              down   ) select_next_butt "${ybuttons[@]}";;              left   ) select_prev_butt "${xbuttons[@]}";;              right  ) select_next_butt "${xbuttons[@]}";;              enter  ) press_button      butts          ;;              escape ) return                           ;;         esac     done }  clear menu

Обратите внимание что у кнопок контролируемых через *row функции не установлены явно координаты, значения установлены в "$1" "$2", реальные значения координат передаются в кнопки из *row функций. В качестве аргументов *row функции принимают: позиция на экране(столбец или строка в зависимости от типа функции) и список кнопок. Кнопки(функции) можно просто перечислить так:

x_row 1 but_a but_b but_c

Но тогда придется копипастить список для функций select_(prev|next)_butt что не очень удобно при изменении количества кнопок . Придется редактировать и там тут. Удобней создать массивы для каждого ряда и использовать их в функциях.

Клавиши «вверх», «вниз» выбирают кнопки из вертикального столбика. Клавиши «влево», «вправо» выбирают кнопки из горизонтального ряда. Вместо тысячи слов:

кнопки

кнопки

Строка ввода. Сначала я сделал окно ввода информации ограниченное со всех сторон рамками но выяснилось что read с некоторыми ключами при нажатии backspace ведет себя с(т)ранно…

read fail

read fail

Пришлось отказаться от окна и сделать простую строку во весь экран, пример оформления:

#!/bin/bash source bashui  my_reader(){     # Новое окно ввода на основе reader из bashui, параметры:     #1 строка на которой появится окно ввода     #2 максимальное кол-во символов     #3 имя окна ввода     #4 имя переменной в которую будет записан текст     #5 текст по умолчанию     #5 цвет текста     #6 цвет рамки     #7 цвет подложки     #      1  2          3         4    5      6      7      8     reader 10 20 'test data input' td "$td" "$wht" "$ylw" "$bblk" }  td='initial text' clear my_reader clear bye "$td"
ввод информации

ввод информации

Ну и самое интересное — список. Это окно с произвольным количеством колонок в котором построчно отображаются какие-то данные. Количество колонок задается в диапазоне от 1 до N, где N = сколько_влезет_в_окно_терминала и определяется методом научного тыка. Выбор элементов осуществляется при помощи клавиш курсора, pgUp/Down и горячих клавиш. Каждому элементу первого столбца в таблице присваивается горячая клавиша соответствующая первому символу. Вот пример создания меню из списка и нескольких кнопок:

#!/bin/bash source bashui  defb(){     # Новая кнопка на основе button из bashui, параметры:     #1 координата X(колонка)     #2 координата Y(строка)     #3 название кнопки     #4 выполняемая функция     #5 цвет текста     #6 цвет рамки     #7 цвет подложки     #        1    2      3          4          5      6      7     button "$1" "$2" "butt $3" "fun_${3,,}" "$wht" "$ylw" "$bblk" }  clr(){ for i in {1..4}; do XY 1 $i "%${COLUMNS}s"; done; } # clear output area but_d(){ defb "$1" "$2" D; } but_e(){ defb "$1" "$2" E; } but_f(){ defb "$1" "$2" F; }  fun_d(){ clr; XY 1 1 "butt D; target: $_target_"; } fun_e(){ clr; XY 1 1 "butt E; description: ${_target_[1]}"; } fun_f(){ clr; XY 1 1 "butt F; all items of target:"               printf -v text -- '%s\n' "${_target_[@]}"               XY 1 2  "$text"; } clear w=$((COLUMNS-10)) my_items(){     # Новый список на основе items, параметры:     #1 координата X(колонка)     #2 координата Y(строка)     #3 ширина окна     #4 высота окна, мин. 5     #5 кол-во колонок, N или в % от ширины     #6 название списка     #7 цвет текста     #8 цвет рамки     #9 цвет подложки     local mess="Columns via number(${bred}Esc to continue$bblk)"     #     1  2  3  4      5        6      7       8      9    data     items 10 5 $w 15      3     "$mess" "$wht" "$ylw" "$bblk" "$@" } my_items2(){     local mess="Columns via percent of Width(${bred}Esc to exit$bblk)"     #     1  2  3  4      5        6      7       8      9    data     items 10 5 $w 15 '30 55 15' "$mess" "$wht" "$ylw" "$bblk" "$@" }  data=(               # массив с тестовыми данными     #-------------{ first line - column descriptions }-------------------- $red'Item name'        $blu'Item description'                 $grn'Status'     #-----------------------{ the data }----------------------------------     'first'        $BLD$ylw'Long description text'                'true'     'second'               'Description 2'                        'O_o'     'third'                'description 3'                        'false'     'fourth'         "${red}Long ${grn}description ${blu}text"    'true'     ''                     ''                                     '' $ylw'fifth'                'Description 2'                        'O_o'     'sixth'                'description 3'                        'false'     'midle'            $grn'Long description text'                'true'     'long name row2'       'Description 2'                        'O_o'     ''                     ''                                      ''     'row 3'                'description 3'                        'false'     'row1'             $blu'Long description text'                'true'     'row1'                 'Long description text'                'true'     'last'                 'Description 2'                        'O_o' )  xbuttons=(but_d but_e but_f) butts (){ x_row $LINES "${xbuttons[@]}"; } ilist (){ my_items     "${data[@]}"    ; } ilist2(){ my_items2    "${data[@]}"    ; } menu  (){     cursor off     default_button but_d     while true; do         $1; butts         read_input         case $_input_ in              up     ) select_prev_item "${data[@]}"    ;;              down   ) select_next_item "${data[@]}"    ;;              left   ) select_prev_butt "${xbuttons[@]}";;              right  ) select_next_butt "${xbuttons[@]}";;              enter  ) press_button         butts       ;;              escape ) return;;              pgup   ) select_prev_item  --fast   "${data[@]}";;              pgdown ) select_next_item  --fast   "${data[@]}";;              *      ) select_by_hot_key $_input_ "${data[@]}";;         esac     done }  menu ilist  # Columns via number menu ilist2 # Columns via percent

В примере создается список из трех стобцов, поэтому данные в массиве data оформлены в виде таблицы с тремя столбцами, чтобы визуально сразу представить как данные будут отображаться в списке. Первые 3(по количеству столбцов) элемента из массива данных, будут использованы в качестве названий стобцов и не отображаются в списке. Элементы можно «раскрашивать» разными цветами, добавляя соответствующие цветовые коды в начало элемента данных. Предусмотрено два способа установки ширины столбца:

  • автоматический, вы указываете только количество столбцов, ширина столбцов в этом случае определяется как ширина_окна/количество_столбцов и одинакова для всех столбцов

    менюшка, равномерное распределение

    менюшка, равномерное распределение
  • % от ширины окна, в этом случае в 5-й аргумент необходимо передать не цифру количества столбцов а строку, содержащую несколько(по количеству столбцов) значений в %, сумма которых должна составить 100%, например '30 55 15'

    менюшка, распределение в % от ширины

    менюшка, распределение в % от ширины

Основным является первый столбец, но можно получить информацию из всех столбцов и строить рабочий процесс соответствующим образом. Полазим по менюшке:

меню

меню

В гифке продемонстрированы все типы перемещения: курсором, pgUp/Down и через горячие кнопки. А вот как реализован опрос клавиатуры:

read_input(){     read -rsN1 _input_     case ${_input_,,} in             # One symbol keys support                 ' '*) _input_=space;;               $'\t'*) _input_=tab  ;;               $'\n'*) _input_=enter;;             # escape sequences additional check             $'\u1b'*) read -rsN4 -t 0.001 _input_                       case ${_input_,,} in                                    # arrows, pgUp/Down and escape support                                    *a*) _input_=up    ;;                                    *b*) _input_=down  ;;                                    *d*) _input_=left  ;;                                    *c*) _input_=right ;;                                    *5*) _input_=pgup  ;;                                    *6*) _input_=pgdown;;                                     '') _input_=escape;;                       esac;;                    # the rest, return 1 symbol in lowercase                    *) _input_=${_input_,,}                       _input_=${_input_:0:1};;     esac }

Я опробовал этот bashui’вый интерфейс на одном из своих проектов sshto, посмотрите что из этого получилось demo_sshto

bashuisshto список хостов

bashuisshto список хостов
bashuisshto список команд

bashuisshto список команд

Проект находится на ранней стадии развития, абсолютно всё может поменяться как в лучшую так и в худшую сторону неоднократно, смело тащите в прод!)

Потрогать мой bashui можно тут.

кстати, что касается …hui(18+)

Разное касается, лично я предпочитаю класическую схему. Но иногда выдаю вот такое вот, для своей bash игры piu-piu я добавил режим penis’а О_о

Активируется он так:

penis=big ./piu-piu

В этом режиме вертолет заменяется на гигантский фалоимитатор с пропеллером. Вместо бонусов таблетки виагры от которых он увеличивается в размерах и стреляет эм, известно чем. Выглядит всё это непотребство как-то так:

shake'em

shake’em

Режим кооператива и дуэль тоже оху… там все еще хуже О_о

пссс…

Часто хочется писнуть что-нибудь но для полноценной статьи требуется куча времени которого нет( Решил попробовать формат телеграм канала, заходите!)

Творите, выдумывайте, пробуйте!)

Лайки, пальцы.


ссылка на оригинал статьи https://habr.com/ru/articles/773942/