CLI Tools and Extensions


When you're working in a browser-based environment, there are many things that you do not have to be concerned with. For instance, you don't have to worry about the details of how a text field works. You use the appropriate <INPUT> tag and the browser does the rest for you. Unfortunately, in a terminal-based environment, you may quickly discover how much we take our Web browsers for granted. The bottom line is that developing effective user interfaces from a terminal can be a very difficult task without the proper tools. In this section you'll learn which extensions PHP provides to assist in this task, and you'll learn about wonderful third-party software that will help you along the way.

It is important to note that nearly all the extensions and tools provided in this section are restricted to Unix-based systems. Unless otherwise noted, these extensions and tools are not available in Windows environments.

The Readline Extension

The first extension we'll look at is the most basicthe Readline extension. The purpose of this extension is to provide an easy-to-use means of accepting the simplest of input from the user. To understand how this extension is useful, let's look at the everyday command prompt.

In a Unix-terminal window, you are greeted with a prompt (usually along the lines of [user@foo.com mydirectory]#). From this command prompt you can type in commands, reload previously executed commands, use the arrow keys to scroll back and insert characters in commands, and so on. All these simple pieces of functionality would require an enormous amount of code to implement if you had to do it yourself. Thankfully, however, this task is exactly what the Readline extension is for. In all, the Readline extension consists of eight functions designed to provide all this functionality to your terminal PHP scripts.

NOTE

Although eight functions are in the Readline extension, only seven will be discussed. The readline_info() function has been omitted because it has no real bearing in PHP scripts. See http://www.php.net/readline_info for more information.


To use Readline, it must first be enabled in your CLI version of PHP. To do so, either download a package with it already installed or configure PHP to compile it into the CLI using the --with-readline when you configure PHP using ./configure.

To use the Readline extension in your scripts, the primary function is the appropriately named readline() function with the following syntax:

 readline([$prompt]) 

$prompt is an optional string that will immediately precede the input to serve as a prompt. Calling this function will immediately request a single line of input from the user and will return that string (minus the newline character) as its return value.

In most cases, the readline() function is the only function you'll need because it provides all the aforementioned functionality of a standard terminal prompt (including history, inserting, and so on). For those users who demand more from their input prompts, let's take a look at the other six functions and how they are used within PHP.

Of the six remaining functions of any use within PHP, five of them are related to the history saved by the Readline extension. The first three of these functions are used to retrieve, add to, and erase the current input history of the Readline extension, whereas the last two relate to saving and retrieving that history information from a file. We'll discuss the latter first.

Although usually not a necessity, at times it might be useful to manipulate the contents of the current input history of the Readline extension. The first of these functions is the readline_add_history() function, which, as its name implies, enables you to manually add a string to the input history as if the user had provided it. The syntax for this function is as follows:

 readline_add_history($new_string) 

$new_string is the string to add to the history (no return value). Conversely, although sometimes it might be useful to add a string to the input history, it is probably more useful to erase the history. There is no means to erase a single entry from the history, but the entire history can be erased using the readline_clear_history() function. This function accepts no parameters and always returns a Boolean true.

The third history-access function is readline_list_history(). This function is used to retrieve an integer-indexed array of values representing all the items in the input history. This function also accepts no parameters and, as expected, returns an array of all the items in the history.

Now that you know how to manipulate the current history of the Readline extension, let's take a look at the functions that enable you to preserve that history. These functions are useful for times when you are using the Readline extension for multiple input fields and would like each to retain its own independent history. This is accomplished by reading and/or writing the history to a file and retrieving it later through the readline_read_history() and readline_write_history() functions. The syntax for these functions follows:

 readline_read_history($filename); readline_write_history($filename); 

$filename represents the file to read or write for each respective function. Both functions return a Boolean indicating whether the file was written or read successfully.

To demonstrate the use of these Readline support functions, let's write a small, yet useful, script. This script (or more appropriately, class) is a self-contained solution allowing you to write scripts that read input with multiple histories quickly and easily. The class works by assigning a unique identifier to each of your prompts and recalling the histories associated with those identifiers on request. Of course, this script also takes care of adding the last submitted command into the appropriate history file. Listing 17.2 shows this class (called reader) in full.

Listing 17.2. A Class for Multiple Readline Histories
 <?php class reader {     public $path = "/tmp/";     private $current_handle;     private $handles = array();     public function clear() {         @unlink($this->path.$this->handles[$this->current_handle]);         unset($this->handles[$this->current_handle]);         readline_clear_history();     }     public function read($prompt) {         $str = readline($prompt);         readline_add_history($str);         return $str;     }     public function set_history($handle) {         if(!isset($this->handles[$handle])) {             $uniqfile = uniqid("rh_");             $this->handles[$handle] = $uniqfile;         }         if((count(readline_list_history()) == 0)) {             if(file_exists($this->path.$this->handles[$handle])) {                 if(!readline_read_history($this->path .                 $this->handles[$handle])) {                     trigger_error("Could not load history " .                     "file for ID '$handle'", E_USER_WARNING);                     return false;                 }             }         } else {             if(!readline_write_history($this->path .             $this->handles[$this->current_handle]))             {                 trigger_error("Could not write history file for ID '".                 $this->current_handle."'", E_USER_WARNING);                 return false;             }             readline_clear_history();             if(isset($this->handles[$handle]) &&             file_exists($this->path.$this->handles[$handle])) {                 if(!readline_read_history($this->path .                 $this->handles[$handle])) {                     trigger_error("Could not load history file for ID '" .                     $handle."'", E_USER_WARNING);                     return false;                 }             }         }         $this->current_handle = $handle;         return true;     } } ?> 

As you can see, the reader class makes use of three member variables: $path, $current_handle, and $handles. The reader class also provides three member functions: read(), clear(), and set_history(). The real "magic" in this class is accomplished through the set_history(), which, as you can see, takes a single parameter. This parameter is the handle (or identifier) for the desired Readline history. No real restriction exists on what this handle can be (other than a scalar value), and it must be used anytime you want to access that particular history. The second function, read(), is a very simple function that accepts a single parameter (the prompt) and displays a Readline input field to the console. The only difference between the read() function and a normal call to readline() is that an additional call to readline_add_history() is made when using read() to add the last command to the history in question. The third and final function, clear(), is also a trivial function that erases the current history information.

To use this class, you'll need to make sure that the $path member variable is set to a directory where PHP can store the relevant history data and create an instance of reader. After it is created, you can start using it by calling the set_history() function to create a new history and then using the read() function to read input from the user. Note that even when you are using the reader class, you can still use all of the Readline functions as you would normally. The reader class works in such a way that a call to readline_list_history(), for example, will return the current history set by the last set_history() call. Listing 17.3 is an example of the reader class in action:

Listing 17.3. An Example of Using the reader Class
 <?php     $r = new reader;     $r->set_history('foo');     $answer = $r->read("Foo: ");     $answer = $r->read("Foo: ");     $answer = $r->set_history('bar');     $answer = $r->read("Bar: ");     $answer = $r->read("Bar: ");     $r->clear();     $answer = $r->read("Bar (no history available): "); ?> 

The reader script accomplishes this flexibility by assigning the history data a unique identifier called a handle. Each time your script switches handles, the current history data is saved to a file in the directory specified by $path, and the new history, if any, is loaded. Because this is all accomplished using readline_write_history() and readline_read_history(), it enables you to switch histories transparently without losing any of the functionality provided by the Readline extension.

Creating User Interfaces

When working with the CLI version of PHP, one of the things I miss the most is the capability to rapidly develop interfaces for my scripts. With HTML no longer at your disposal, you have already seen how difficult it can be to implement simple text fields into your CLI scripts. Unix environments provide something called ncurses, a library designed to allow you to have complete control over the terminal and draw your own custom interface. Unfortunately, although PHP does provide the ncurses extension to enable you to access these capabilities, at the time of this writing the extension was still unreliable and experimental. However, an alternative to using the ncurses library does exist! Along with ncurses, most versions of Unix provide an amazingly useful application called dialog (or cdialog). This program provides a means to develop quick user interfaces for shell scripts that take advantage of the ncurses library, and it works beautifully with PHP.

The dialog command provides a wide range of interface functionality, including automatic calendars, checklists, file selection windows, progress meters, information boxes, input boxes, menus, message boxes, password boxes, option button lists, question boxes, and much more. Perhaps the best part when you're working the dialog command is that it is truly a "black box" solutionyou tell it what you want it to display, it does so, and returns any information submitted from the user (if it was an input box). It is a very professional way of making CLI applications. Before we get into how to use dialog from within your PHP applications, it is better to start off with using dialog directly to get a feel for it. To start, you'll need to make sure that the dialog application is available (which it seems to be on most Unix systems). You can test this theory by opening a console and typing dialog at the prompt:

 [john@coggeshall.org -]# dialog cdialog (ComeOn Dialog!) version 0.9a-20010527 * Display dialog boxes from shell scripts * Usage: dialog <options> { --and-widget <options> } where options are "common" options, followed by "box" options ... (remainder excluded) ... 

If you didn't get an option list something like what was shown previously, you do not have dialog installed. To install dialog, you'll need the source, which is available from http://invisible-island.net/dialog/. After you have downloaded the latest version (at the time of writing this, it was 0.9b), extract it to a directory and compile it as shown:

 [root@coggeshall.org -]# mv cdialog-0.9b.tar.gz /usr/local/src [root@coggeshall.org -]# cd /usr/local/src [root@coggeshall.org src]# gunzip cdialog-0.9b.tar.gz [root@coggeshall.org src]# tar -xf cdialog-0.9b.tar [root@coggeshall.org src]# cd cdialog-0.9b [root@coggeshall.org cdialog-0.9b] ./configure ...output of configure omitted... [root@coggeshall.org cdialog-0.9b]# make ...output of make omitted... [root@coggeshall.org cdialog-0.9b]# make install ...output of make omitted... 

To test your installation, a number of sample dialogs are available in the cdialog-0.9a/samples/ directory:

 [root@coggeshall.org cdialog-0.9a]# cd samples [root@coggeshall.org samples]# ./inputbox 

When you execute one, you should see something like the following:

Figure 17.1. If you see something similar to this, you've successfully installed the dialog application and you're ready to move on to using it!


NOTE

If you are accessing the terminal remotely via Telnet or ssh, note that many terminal emulation applications tend to mess up dialogs (any time ncurses is used). Sometimes the background or non-dynamic portions of the display will not render or otherwise look strange. If you can run the dialog command and receive an option screen, you were successful in your installation of the application. For best results, any Unix-based terminal application or Windows application completely supporting vt100 or vt220 is recommended (such as Putty, available at http://www.chiark.greenend.org.uk/~sgtatham/putty/).


Now that you have confirmed that you indeed do have dialog installed, let's examine how it works. With dialog, everything is done by passing certain command-line arguments to the application to define what type of window you would like displayed and then capturing the return value and/or output from the application to determine what happened.

The basic (practical) usage for the dialog command is as follows:

 [john@coggeshall.org -]# dialog [common_options] [command [parameters] 2>output] 

common_options is one of the options common to all commands, and command is the type of dialog to display to the user using the parameters provided by parameters. The dialog command will exit with an exit code and store any response (input from an input box, for example) in the file specified by output. To illustrate the use of dialog, consider the results of the following command:

 [john@coggeshall.org -]# dialog --inputbox "What is your Name" 0 0                               2>/tmp/output_temp 

When executing, the dialog application creates a simple text input field that is 20 rows by 30 columns in size and contains the text, "What is your Name" along with the input field.

Depending on what the user decides to do when presented with this input box, a few different things happen. First, the dialog application itself will return one of three values as an exit code, as shown in Table 17.2:

Table 17.2. Return Values of the dialog Command

0

User pressed Yes/OK

1

User pressed No/Cancel

255

An error has occurred, or the user canceled the dialog by pressing Esc.


Along with the exit code, the file /tmp/output_temp contains any text that was received from the dialog application. Because of the way this file was written, every successive call to dialog will overwrite the current contents (meaning that you'll only have to read the input from the user from a single file).

In this case, we have used the --inputbox parameter to create an input field. Other possible widgets that could have been created and a likely use for them are shown in Table 17.3:

Table 17.3. Widgets Available Using the dialog Command

Yes/No Box

--yesno <text> <height> <width>

Description: Displays a Message Box with the choices Yes or No and the text <text>.

Message Box

--msgbox <text> <height> <width>

Description: Displays a Message Box with an OK button and the text <text>.

Info Box

--infobox <text> <height> <width>

Description: Displays a box containing <text> with an Exit button.

Input Box

--inputbox <text> <height> <width> [<init>]

Description: Displays a box containing <text>, an input field, and an OK button. An optional <init> value can be used to set the default value.

Text Box

--textbox <file> <height> <width>

Description: Displays a scrollable window containing the text in <file>.

Menu

--menu <text> <height> <width> <mheight> <tag1> <item1>

Description: Displays a Menu with text <text> with an embedded menu of height <mheight> containing any number of items specified by <tag>/<item> pairs (where <item> is the description of the menu item and <tag> is the value it returns when selected).

Checklist

--checklist <text> <height> <width> <lheight> <tag1> <item1> <status1>

Description: Displays a list of check boxes with text <text> in a scrollable segment of height <lheight>. Each check box item consists of the value to return if selected <tag>, the description of the item <item>, and a flag indicating whether it is "checked" <status> (either On or Off).

Radiolist

--radiolist <text> <height> <width><lheight> <tag1> <item1> <status1>

Description: Displays a list of radio buttons with text <text> in a scrollable segment of height <lheight>. Each radio item consists of the value to return if selected <tag>, the description of the item <item>, and a flag indicating whether it is the selected item <status> (either On or Off). Note that in any given Radiolist, oneand only oneof the provided items should have a <status> of On.

Gauge

--gauge <text> <height> <width> <percent>

Description: Displays a progress meter with text <text> with a initial completed percentage of <percent>. This command takes integers from standard input (stdin) indicating the current percentage of the progress bar. You may also change the value of <text> while the progress bar is running by sending the string "XXX" to standard input, sending your text, and ending with another "XXX" string.

TailBox

--tailbox <text> <height> <width>

Description: Displays a window containing <text> and an Exit button. This widget functions much like the Unix command tail with the -f option. Useful for displaying a message at the end of the program's execution.

BG Tailbox

--tailboxbg <text> <height> <width>

Description: Displays the same thing as Tailbox, except it is run in the background. Similar to using tail -f &.

Calendar

--calendar <text> <height> <width> <day> <month> <year>

Description: Displays a calendar from which the user can select a particular day. Resulting output is the day in DD/MM/YYYY format.

Password Box

--password <text> <height> <width> [<init>]

Description: Displays the same thing as an input box, except that for security it hides the characters typed. An optional <init> value can be used to set the default value.

Time Box

--timebox <text> <height> <width> <hour> <minute> <second>

Description: Displays a window containing the time specified and allows the user to change that time. Resulting output is the set time in HH:MM::SS format.


NOTE

Although all these widgets are available as part of the dialog application, some of them may not be available, depending on your system (for example, if it was available when it was compiled). If a particular option does not work at all and the dialog command-line help is displayed, the option is unavailable.


When passing the parameters for a particular widget, it is useful to note that dialog is capable of automatically guessing the width and height for your windows. This is done one of two ways depending on how you would like your windows to look. The first method is to set <height> and <width> both to zero, in which case dialog will use the default size. The second method is to set both <height> and <width> to 1, in which case the maximum size for the window will be used.

Along with all these widgets, an incredible number of options exist that apply to these widgets or to the look and feel of the interface in general. Because there are so many of these command-line options, I will cover only the ones with particular meaning for working with PHP scripts. If you would like a description of each possible command-line option, a manual page is available by executing man dialog from your console. Some of the more interesting arguments are shown in Table 17.4:

Table 17.4. Useful dialog Command-Line Arguments

--cr-wrap

Interpret newline characters in text as newlines on the screen instead of allowing dialog to automatically wrap the text.

--print-maxsize

Returns a string to the output file of the format "Maxsize: Y, X" indicating the maximum size allowed for a dialog widget.

--separate-output

Normally, the output from a check box list is in the format "foo" "bar". That is, all checked items are returned on a single line in double quotes. This option changes that behavior to display each without quotes on its own line. This works only with the CheckList widget.

--tab-correct

Convert tab characters '\t' to normal spaces. Required if displaying text containing tab characters.

--tab-len

The number of spaces each tab represents.


Now that you know how to use the dialog command from the command prompt, let's take a look at how it works from a PHP perspective. For your CLI scripts to take advantage of the dialog function, first you have to know how to execute the application from within PHP. Perhaps the first thought that comes to mind is to use the system() function to execute it as a system call; however, that is the wrong approach. Although system() may work for certain combinations of dialog options, widgets such as the gauge (--gauge) widget cannot work under this system because they require input from stdin. For this reason, the popen() function has been chosen. When using the popen() function, you can open a unidirectional pipe between the dialog application and your PHP scripts. The syntax for the popen() function is similar to fopen(), as you can see next:

 popen($command, $mode); 

$command is the complete path and command (including arguments) to execute, and $mode is the mode under which to open the process. Possible values for the $mode parameter are identical to those found for the fopen() function for reading and writing (appending is not applicable for the popen() function). Upon execution, this function will return a file reference identical to that returned when using fopen(); hence, any function that works with a file reference can be used (such as fgets(), fputs(), and so on) with it.

One oddity of the popen() function when compared to fopen() is that popen() will always return a valid file reference, regardless of whether it was actually successful in executing the desired command. This is provided to allow your PHP access to the error message generated by the attempted execution of the command.

NOTE

Chances are that if an error occurred when popen() TRied to execute a command, the error message will be displayed to stderr (standard error), not stdout (standard out). To capture error messages written to stderr from PHP, you'll need to redirect that output to stdout by appending 2>&1 to the end of your command, as shown:

 $fr = popen("badcommand 2>&1", "w+"); 


After you have finished any work being done through the popen() function, you'll need to close the file reference using the counterpart to the fclose() function, pclose(). Like fclose(), pclose() accepts a single parameter (the reference to close):

 pclose($reference); 

When concerned with exit codes, as we are when working with the dialog command, the pclose() function is quite important. Upon closing the process, the pclose() function returns the exit code received from the process that was being run.

To provide a working example in line with our discussion, Listing 17.4 uses PHP and dialog to create the same input box created earlier in the chapter from the command line:

Listing 17.4. Using popen() and pclose()
 <?php      $command = "/usr/bin/dialog --inputbox " .                 "'What is your Name' 0 0 2>/tmp/php_temp";      $pr = popen($command, 'w');      $exit_code = pclose($pr);          switch($exit_code) {           case 0:                // User pressed 'Yes' or 'Ok' get the input                $input = implode("", file("/tmp/php_temp"));                echo "\nYou typed: $input\n";                break;           case 1:                echo "\nWhy did you cancel?\n";                break;      } ?> 



PHP 5 Unleashed
PHP 5 Unleashed
ISBN: 067232511X
EAN: 2147483647
Year: 2004
Pages: 257

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net