Использование ncurses в PHP

в 13:44, , рубрики: cli, php, интерфейсы, переводы, метки: ,

Как программист и веб-разработчик, вы наверное время от времени пишете программы на php для администрирования сайта из консоли. Средствами языка можно сделать только выводящую/читающую из консоли программу, но с помощью библиотеки ncurses можно создать консольную утилиту с довольно сложным внешним видом, которая будет одинаково выглядеть в разных видах терминалов.

PHP очень гибок и расширяем, с его помощью можно создавать скрипты, которыми можно заменить аналогичные на других языках (Perl/bash-скрипты и прочее). Функциональность ncurses поразительна и действительно выводит возможности работы с терминальным php-приложением на новый уровень.

Предназначение статьи — дать вам «толчок» в сфере написания консольных утилит на php, которые будут использовать возможность этой библиотеки. Я собираюсь показать только самое важное, относящееся к самой библиотеке, и надеюсь дать вам базу для использования ncurses в вашем приложении.

Для кого предназначена статья

Материал предназначен для опытных php-программистов, заинтересованных в создании консольных php-приложений с пользовательским интерфейсом. (прим. переводчика: материал пригодится разработчику любого уровня, здесь нет ничего сложного).

Изучаемые темы

Из данной статьи вы узнаете:

  • Как создавать окна, используя ncurses, и заполнять их данными
  • Как создавать несколько окон и динамически изменять их размеры до размеров терминала
  • Как «ловить» нажатия клавиш
  • Как написать небольшую программу, расширяющую функциональность traceroute, используя все изученные возможности библиотеки ncurses

Определения

  • Ncurses (new curses) — библиотека, свободное ПО, имитирующая curses (System V Release 4.0), и даже больше. Она использует формат Terminfo, поддерживает знакоместа, цвета, множественную подсветку, использование функциональных клавиш и все другие возможности SYSV curses. Если вы использовали linux, то вы скорее всего видели его в действии: midnight commander,
    ncftp, Iptraf, trafshow и многие другие используют ncurser для создания интерфейса.
  • Окно означает лишь секцию терминала, созданную с помощью ncurses.

От автора

Документация по функциями ncurses в php сильно ограничена. Некоторые функции использованные в данной статье отсутствуют на официальном сайте. Приложения с ncurses следуют стилю программирования на C, как если бы они были настоящими программами, а не скриптами.

Если у вас есть опыт программирования в C или C++, возможно вы уже знакомы с ncurses и можете представить, каким полезным инструментом может быть ncurses при использовании с интерпретируемым языком, таким как PHP.

Что нужно знать

Вам нужен PHP, скомпилированный с опцией -with-ncurses на unix-подобной системе. Не все терминалы поддерживают цветной вывод, поэтому я не стал включать использование цветов в статью.

Знайте, что вам может понадобиться «сбрасывать» ваш терминал командной reset. Если возникнет какая-либо ошибка и ncurses_end() не будет вызван, то ваш терминал будет работать неправильно.

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

Начнём с простого приложения прежде чем переходить к сложному.

Простой пример с окнами

Сначала создадим основное окно, которое будет использовать всё доступное пространство. Потом добавим меньшее окно посередине экрана и поместим туда строчку текста. Чтобы получилось вот так:
image

Начать нужно с вызова ncurses_init();, чтобы перейти в режим ncurses. Без этого при вызове функций библиотеки PHP будет падать с ошибкой.

Сценарий для вывода двух окон и строки

<?php
// начинаем с инициализации библиотеки
$ncurse = ncurses_init();
// используем весь экран
$fullscreen = ncurses_newwin ( 0, 0, 0, 0); 
// рисуем рамку вокруг окна
ncurses_border(0,0, 0,0, 0,0, 0,0);
// создаём второе окно
$small = ncurses_newwin(10, 30, 7, 25);
// рамка для него
ncurses_wborder($small,0,0, 0,0, 0,0, 0,0);

ncurses_refresh(); // рисуем окна

// пишем в маленьком окне
ncurses_mvwaddstr($small, 5, 5, "   Test  String   ");

// обновляем маленькое окно для вывода строки
ncurses_wrefresh($small);

$pressed = ncurses_getch(); // ждём нажатия клавиши

ncurses_end(); // выходим из режима ncurses, чистим экран

Попробуйте изменить размеры окна и запустить скрипт ещё раз. Основное окно опять будет занимать ровно всё пространство.

Теперь вы может захотите увидеть какое-нибудь действо при нажатий на клавишу, или вы хотите сделать возможность выхода из приложения при нажатии на какую-либо кнопку. Я предпочитаю использовать для этого esc (27 ascii-код).
Чтобы добавить возможность выхода по нажатию на ESC, добавьте данный код вместо $pressed = ncurses_getch();

while (true) {
	$pressed = ncurses_getch(); // ждём нажатия клавиши
	if ($pressed == 27) {
		break;
	} else {
	ncurses_mvwaddstr($small, 5, 5, $pressed);
	ncurses_wrefresh($small);
	}
}

Теперь если вы нажмёте esc, программа завершится, в ином случае ascii-код нажатой клавиши будет отображён в маленьком окне.

Можно добавить заголовок, поместив код перед вызовом ncurses_refresh();:

ncurses_attron(NCURSES_A_REVERSE);
ncurses_mvaddstr(0,1,"My first ncurses application");
ncurses_attroff(NCURSES_A_REVERSE);

Вы не ограничены режимом REVERSE (цвет и фон меняются местами), также есть DIM, UNDERLINE и другие.

Можно повысить комфорт, добавив интерактивный выбор из меню.

Создаём меню

Многие программы имеют меню и возможность выбора из него. В прошлом на «чистом» PHP мы могли лишь выводить список вариантов, и давать возможность ввести номер нужного. Такой неудобный выбор одного варианта можно заменить более интуитивно понятным.
image

Сценарий вывода меню с возможностью выбора

<?php
define('ESCAPE_KEY', 27);
$ncurse = ncurses_init();
$fullscreen = ncurses_newwin ( 0, 0, 0, 0); 
ncurses_border(0,0, 0,0, 0,0, 0,0);
$small = ncurses_newwin(10, 30, 7, 25);
ncurses_wborder($small,0,0, 0,0, 0,0, 0,0);
ncurses_attron(NCURSES_A_REVERSE);
ncurses_mvaddstr(0,1,"My first ncurses application");
ncurses_attroff(NCURSES_A_REVERSE);
ncurses_refresh();

$currently_selected = 0;
$menu = array('one', 'two', 'three', 'four');

while (true) {
	for($i=0; $i<count($menu); $i++){
		$out = $menu[$i];
		if($currently_selected == intval($i)){ 
			ncurses_wattron($small,NCURSES_A_REVERSE);
			ncurses_mvwaddstr($small, 1+$i, 1, $out);
			ncurses_wattroff($small,NCURSES_A_REVERSE);
		} else {
			ncurses_mvwaddstr($small, 1+$i, 1, $out);
		}
	}

	ncurses_wrefresh($small);

	$pressed = ncurses_getch();

	if ($pressed == NCURSES_KEY_UP) {
		$currently_selected--; 
		if ($currently_selected < 0)
			$currently_selected = 0;
	} elseif ($pressed == NCURSES_KEY_DOWN) {
		$currently_selected++;
		if ($currently_selected >= count($menu))
			$currently_selected = count($menu)-1;
	} elseif($pressed == ESCAPE_KEY) {
		break;
	} else {
	ncurses_mvwaddstr($small, 5, 5, $pressed);
	}
}

ncurses_end();

По получившемуся меню можно перемещаться кнопками со стрелками и выходить с помощью esc.

Сейчас вы возможно думаете о консольных приложениях, которые вы можете создать, зная только несколько выше использованных функций. Далее по тексту мы создадим полезное приложение, которое будет стартовой точкой дальнейшей разработки.

Всё вместе

На сайте php.net перечислены 119 функций библиотеки ncurses. В нижеприведённой таблице есть список использованных функций и ссылки на документацию. Замечу, что четыре из перечисленных функций отсутствуют в официальной документации. (прим. переводчика: в настоящий момент все функции содержаться в документации на php.net, правда без подробного описания).

Функция Описание Документация
ncurses_init Инициализирует ncurses www.php.net/manual/en/function.ncurses-init.php
ncurses_newwin Создаёт новое окно www.php.net/manual/en/function.ncurses-newwin.php
ncurses_getmaxyx(resource window, int
return Y, int return X);
Записывает в переменные X и Y максимальных размеров терминала
ncurses_border Рисует рамку вокруг основного окна www.php.net/manual/en/function.ncurses-border.php
ncurses_refresh Обновляет основное окно. Для перерисовки второстепенных окон используйте ncurses_wrefresh www.php.net/manual/en/function.ncurses-refresh.php
ncurses_attron Применяет атрибут к выводимому тексту www.php.net/manual/en/function.ncurses-attron.php
ncurses_attroff Отключает применение атрибута www.php.net/manual/en/function.ncurses-attroff.php
ncurses_mvaddstr Выводит строку www.php.net/manual/en/function.ncurses-mvaddstr.php
ncurses_wborder (resource window, int
left, int right, int top, int bottom, int tl_corner, int tr_corner, int
bl_corner, int br_corner);
Рисует рамку для второстепенного окна.
ncurses_wattron(resource window, int
attribute)
Идентично ncurses_attron, только применяется для окна window
ncurses_mvwaddstr Помещает строку во второстепенное окно www.php.net/manual/en/function.ncurses-mvwaddstr.php
ncurses_wattroff (resource window, int
attribute)
Идентично ncurses_wattroff, только применяется для окна window
ncurses_wrefresh Перерисовывает второстепенное окно. www.php.net/manual/en/function.ncurses-wrefresh.php
ncurses_getch Ждёт ввода с клавиатуры или мыши. www.php.net/manual/en/function.ncurses-getch.php

Улучшенный traceroute

Сейчас создадим действительно полезную программу: traceroute с выводом whois-информации о каждом прыжке.
image

Этот скрипт запускает traceroute до zend.com (макс 10 прыжков) и показывает результаты в одном окне. По элементам можно переходить с помощью стрелок. При нажатии на enter информация о данном ip будет показана в нижнем окне. По нажатию на esc произойдёт завершение работы программы.

Содержимое скрипта

<?php

// константы для кнопок
define("ESCAPE_KEY", 27);
define("ENTER_KEY", 13);

// начальные данные
$tr_return = traceroute("www.zend.com");
array_shift($tr_return);
$ncurses_session = ncurses_init();
$main = ncurses_newwin(0, 0, 0, 0); // основное окно
ncurses_getmaxyx($main, $lines, $columns);
ncurses_border(0, 0, 0, 0, 0, 0, 0, 0);

// рамка для окна
ncurses_attron(NCURSES_A_REVERSE);
ncurses_mvaddstr(0,1, "Traceroute example");
ncurses_attroff(NCURSES_A_REVERSE);

// окно поменьше, которое подстраивается под размеры терминала ...
$lower_frame_window = ncurses_newwin ($lines-14, $columns-3, 13, 1);
ncurses_wborder($lower_frame_window, 0,0, 0,0, 0,0, 0,0); // обрамим
$lower_main_window = ncurses_newwin ($lines - 16, $columns-5, 15, 2);
$main_list_window = ncurses_newwin (12, $columns-3, 1, 1);
ncurses_wborder($main_list_window, 0,0, 0,0, 0,0, 0,0); // обрамим
ncurses_refresh();

$currently_selected = 0;
while(true) {
		for($a=0; $a < count($tr_return); $a++){
			$out = $tr_return[$a];
			if($currently_selected == intval($a)) {
				ncurses_wattron($main_list_window,NCURSES_A_REVERSE);
				ncurses_mvwaddstr ($main_list_window, 1+$a, 1, $out);
				ncurses_wattroff($main_list_window,NCURSES_A_REVERSE);
			} else {
				ncurses_mvwaddstr ($main_list_window, 1+$a, 1, $out);
			}

		}


	ncurses_move(-1,1); // убираем курсор из поля зрения
	ncurses_wrefresh($lower_frame_window);
	ncurses_wrefresh($lower_main_window); // отображаем окна
	ncurses_wrefresh($main_list_window);
	// ждём нажатия клавиши
	$y = ncurses_getch($lower_main_window);

	if ($y == ENTER_KEY) {
		$newout = explode(" ", trim($tr_return[$currently_selected]));
		$rwhois_return = rwhois($newout[2]);

		foreach ($rwhois_return as $n => $l) {
			ncurses_mvwaddstr($lower_main_window, $n - 1, 1, $l);
		}
	} elseif($y == ESCAPE_KEY) {
		ncurses_end();
		exit;
	} elseif ($y == NCURSES_KEY_UP) {
		$currently_selected--;
		if ($currently_selected < 0)
			$currently_selected = 0;
	} elseif($y == NCURSES_KEY_DOWN) {
		$currently_selected++;
		if ($currently_selected >= count($tr_return))
			$currently_selected = count($tr_return)-1;
	}

}

// следующие две функции для получения данных

function traceroute($address) {
	exec("traceroute -n -m 10 $address", $trreturn);
	return $trreturn;
}

// reverse whois
function rwhois($query) {
	$fp = fsockopen ("rwhois.arin.net", 4321, $errno, $errstr, 30);
	if (!$fp) {
			$ret[] = "$errstr ($errno)n";
	} else {
			fputs($fp, "$queryrn");
			while (!feof($fp)) {
				$back = trim(fgets ($fp, 256));
				if (empty($back) || stripos($back, ':') === false || substr($back, 0, 1) == '#')
					continue;
				$ret[] = $back;
			}//wend
		  fclose ($fp);
	}
	return $ret;
}

В заключение

Выше изложенный код довольно прост и может послужить базой для вашего приложения с использованием ncurses.

Мы не рассматривали использование мыши, цветов и многих других функций вывода.

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

/** Creates an ncurses window that is write-safe on the left-hand side of the screen
	* @param integer $size is how wide it will be
	* @return window handle of inner window.
	*/
function left_window($size=15){
	global $fullscreen;
	ncurses_getmaxyx($fullscreen, $MAX_Y, $MAX_X);
	$c = ncurses_newwin ($MAX_Y-2 ,$size, 1, 1);
	ncurses_wborder($c,0,0, 0,0, 0,0, 0,0); // border it
	// now create window overtop the other just
	// slightly smaller so that we won't write over
	// the border.
	$d = ncurses_newwin ($MAX_Y-4 ,$size-2, 1+1, 2);
	ncurses_wrefresh($c); // show it
	ncurses_wrefresh($d); 
	return $d;
}

#
# creates an upper-right window
#
function upperr_window($size=15){
	global $fullscreen;
	ncurses_getmaxyx($fullscreen, $MAX_Y, $MAX_X);
	$c = ncurses_newwin ($size ,$size, 1, $MAX_X-($size+1));
	ncurses_wborder($c,0,0, 0,0, 0,0, 0,0); // border it
	ncurses_wrefresh($c); // show it
	return $c;
}

Удачного программирования!

Полезные ссылки

  • www.opengroup.org/onlinepubs/007908799/xcurses/curses.h.html — это документация по заголовочному файлу ncurses, которая будет отличным справочником по функциям библиотеки.
  • dickey.his.com/ncurses/ncurses-intro.html — полезный материал по использованию ncurses в языке C. Много чего из него можно использовать с PHP.
  • И всегда используйте man ncurses. К тому же каждая функция имеет свою страницу справки (напр. man wborder
От переводчика

Установка расширения ncurses

  1. Установить pecl. Пакет php5-dev в ubuntu, php-pear в arch
  2. Установить само расширение: pecl install ncurses (права рут, поэтому с sudo)
  3. Подключить расширение. Добавить строчку «extension=ncurses.so» в свой php.ini

Оригинальная статья: devzone.zend.com/173/using-ncurses-in-php/
Прошу сообщать об опечатках в лс

Автор: wapmorgan

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js