|
Apache/2.4.41 (Ubuntu) Linux vmi616275.contaboserver.net 5.4.0-84-generic #94-Ubuntu SMP Thu Aug 26 20:27:37 UTC 2021 x86_64 uid=33(www-data) gid=33(www-data) groups=33(www-data) server ip : 62.171.164.128 | your ip : 127.0.0.1 safemode OFF > / home / dev2.destoffenstraat.com / vendor / hoa / console / Documentation / En / |
Filename | /home/dev2.destoffenstraat.com/vendor/hoa/console/Documentation/En/Index.xyl |
Size | 74.32 kb |
Permission | rw-r--r-- |
Owner | root : root |
Create time | 17-Aug-2025 10:26 |
Last modified | 02-May-2017 14:26 |
Last accessed | 23-Aug-2025 03:56 |
Actions | edit | rename | delete | download (gzip) |
View | text | code | image |
<?xml version="1.0" encoding="utf-8"?>
<overlay xmlns="http://hoa-project.net/xyl/xylophone">
<yield id="chapter">
<p>The terminal is a very <strong>powerful interface</strong> that is based on
multiple concepts. <code>Hoa\Console</code> allows to write
<strong>tools</strong> that are adapted to this kind of environment.</p>
<h2 id="Table_of_contents">Table of contents</h2>
<tableofcontents id="main-toc" />
<h2 id="Introduction" for="main-toc">Introduction</h2>
<p>Nowadays, we have two kinds of interface: <strong>textual</strong> and
<strong>graphical</strong>. The textual interface exist since the origin of
computers, then called terminal. This interface, despite its “raw” aspect, is
functionally very <strong>powerful</strong> thanks to several concepts such as
the readline or pipes. Today, it is even more used because it is often faster
than a graphical interface when executing <strong>complex</strong> tasks. It
can also easily be used through networks or on a machine with low
performances. In short, this interface is still
<strong>indispensable</strong>.</p>
<p>From the user point of view, there is three levels to consider:</p>
<ul>
<li>the <strong>interface</strong>: print and edit text, manipulate the
window, the cursor etc.,</li>
<li>the <strong>program</strong>: interact with the user with a maximum of
comfort, use the readline as much better as possible, build programs adapted
to this kind of interface,</li>
<li>the <strong>interaction</strong> with other programs: automatically
interact and communicate with other programs.</li>
</ul>
<p>The <code>Hoa\Console</code> library provides tools to answer to these
three levels. Thus, it is based on standards, such as
<a href="http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-048.pdf">ECMA-48</a>
that specifies the communication with the system through sequences of ASCII
characters and control codes (also called escaping sequences), all of that, in
order to manipulate the window, the cursor or other devices of the machine.
Other features are also standard such as the way we read options from a
program, very <strong>inspired</strong> of systems like
<a href="http://linux.org/">Linux</a>, <a href="http://freebsd.org/">FreeBSD</a>
or <a href="https://en.wikipedia.org/wiki/UNIX_System_V">System V</a>. By the
way, if you are familiar with several C libraries, you will not be lost. And
<em>a contrario</em>, if you learn to use <code>Hoa\Console</code>, you will
not be lost when using low-level languages such as C.</p>
<p>Before starting, we would like to add a little note
<strong>only</strong> about the window and cursor support. Today, we have the
choice among <strong>many</strong> terminals per system and some of them are
more comprehensive than others. For example,
<a href="https://windows.microsoft.com/">Windows</a> and its default terminal,
<a href="http://en.wikipedia.org/wiki/MS-DOS">MS-DOS</a>, does not respect any
standard. In this case, forget the ECMA-48 standard and take a look at
<a href="http://msdn.microsoft.com/library/ms682087.aspx"
title="Console Reference">the <code>Wincon</code> library</a>. It is often
recommended to use a <strong>virtual</strong> Unix machine or a terminal
<strong>emulator</strong>, such as
<a href="http://ttssh2.sourceforge.jp/">TeraTerm</a>, very comprehensive. Even
on systems that are closed to the BSD family, the embeded terminals do not
support all the standards. This is the case of Mac OS X where we recommend to
use <a href="http://iterm2.com">iTerm2</a> instead of Terminal. Finally, on
other systems of the Linux or BSD family, we recommend
<a href="http://software.schmorp.de/pkg/rxvt-unicode.html">urxvt</a>. For
other features, such as the readline, the options reading, the processus etc.,
<code>Hoa\Console</code> is perfectly <strong>compatible</strong> and
suitable.</p>
<h2 id="Window" for="main-toc">Window</h2>
<p>The window of a terminal must be understood as a <strong>canvas</strong> of
<strong>columns</strong> and <strong>lines</strong>. The
<code>Hoa\Console\Window</code> allows to manipulate the
<strong>window</strong> of a terminal and its <strong>content</strong> through
static methods.</p>
<h3 id="Size_and_position" for="main-toc">Size and position</h3>
<p>The first elementary operations are about the <strong>size</strong> and the
<strong>position</strong> of the window, thanks to the <code>setSize</code>,
<code>getSize</code>, <code>moveTo</code> and <code>getPosition</code>
methods. The size is defined with the <em>column</em> × <em>line</em> unit and
the position is defined in pixels. Thus:</p>
<pre><code class="language-php">Hoa\Console\Window::setSize(80, 50);
print_r(Hoa\Console\Window::getSize());
print_r(Hoa\Console\Window::getPosition());
/**
* Will output:
* Array
* (
* [x] => 80
* [y] => 50
* )
* Array
* (
* [x] => 104
* [y] => 175
* )
*/</code></pre>
<p>We will notice that the window is resized <strong>itself</strong>. Nor the
size or the position of the window is stored in memory, they are computed at
each call of the <code>getSize</code> and <code>getPosition</code> method.
Attention, the <em>y</em> axe of the window position is computed from the
<strong>bottom</strong> of the screen and not from the top of the screen as we
might expect!</p>
<p>It is also possible to listen the
<code>hoa://Event/Console/Window:resize</code> <strong>event</strong> that is
fired each time the window is resized: either manually or with the
<code>setSize</code> method. We need two things in order to see this event
working:</p>
<ol>
<li><a href="http://php.net/pcntl">the <code>pcntl</code> extension</a> must
be activated,</li>
<li>we have to use <a href="http://php.net/declare">the <code>declare</code>
structure</a> to enable the <a href="http://php.net/pcntl_signal">the
<code>pcntl_signal</code> function</a>.</li>
</ol>
<p>To put the program in a passive waiting state, we will use
<a href="http://php.net/stream_select">the <code>stream_select</code>
function</a>, this is a <strong>detail</strong>, only present to test our
code, else the program will terminate directly. Thus:</p>
<pre><code class="language-php">Consistency\Autoloader::load('Hoa\Console\Window'); // make sure it is loaded.
declare(ticks = 1);
Hoa\Event\Event::getEvent('hoa://Event/Console/Window:resize')
->attach(function (Hoa\Event\Bucket $bucket) {
$data = $bucket->getData();
$size = $data['size'];
echo 'New size (', $size['x'], ', ', $size['y'], ')', "\n";
});
// Passive loop.
while (true) {
$r = [STDIN];
@stream_select($r, $w, $e, 3600);
}</code></pre>
<p>When we modify the size of the window, we will see for example:
<code>New size (45, 67)</code>, and this, for each resize. This event is
interesting if we would like to <strong>re-layout</strong> our view.</p>
<p>Finally, we can minimize or restore the window thanks to the
<code>Hoa\Console\Window::minimize</code> and
<code>Hoa\Console\Window::restore</code> static methods. Moreover, we can put
the window in the background (behind all other windows) thanks to the
<code>Hoa\Console\Window::lower</code> static method, just like we can place
it in the foreground with <code>Hoa\Console\Window::raise</code>. For
example:</p>
<pre><code class="language-php">Hoa\Console\Window::minimize();
sleep(2);
Hoa\Console\Window::restore();
sleep(2);
Hoa\Console\Window::lower();
sleep(2);
Hoa\Console\Window::raise();
echo 'Back!', "\n";</code></pre>
<h3 id="Title_and_label" for="main-toc">Title and label</h3>
<p>The <strong>title</strong> of a window is the text displayed in the top
<strong>bar</strong> in which the controls of the window are often placed,
such as the maximization, the minimization etc. The <strong>label</strong> is
the name associated to the current <strong>processus</strong>. We have the
<code>setTitle</code>, <code>getTitle</code> and <code>getLabel</code>
methods, it is not possible to modify the label. To define the title of the
processus (what we see with the <code>top</code> or <code>ps</code> command
for example), we must take a look at
<code>Hoa\Console\Processus::setTitle</code> and also at
<code>Hoa\Console\Processus::getTitle</code> to get it. Thus:</p>
<pre><code class="language-php">Hoa\Console\Window::setTitle('Foobar');
var_dump(Hoa\Console\Window::getTitle());
var_dump(Hoa\Console\Window::getLabel());
/**
* Will output:
* string(6) "Foobar"
* string(3) "php"
*/</code></pre>
<p>Once again, the title and the label are not stored in memory, they are
computed at each call of the methods.</p>
<h3 id="Interact_with_the_content" for="main-toc">Interact with the
content</h3>
<p><code>Hoa\Console\Window</code> also allows to control the
<strong>content</strong> of the window, or at least the viewport, it means the
<strong>visible</strong> content of the window. Only one method is available:
<code>scroll</code>, that allows to <strong>move</strong> the content to the
top or the bottom. The arguments of this method are very simple:
<code>up</code> or <code>↑</code> to move up of one line and <code>down</code>
or <code>↓</code> to move down of one line. We are able to concatenate those
directions by a space or to repeat one direction:</p>
<pre><code class="language-php">Hoa\Console\Window::scroll('↑', 10);</code></pre>
<p>In reality, this method will move the content in order to get <em>x</em>
new lines respectively bellow or above the cursor. Attention, the cursor
<strong>does not move</strong>!</p>
<p>Even if it is most of the time useless, it is possible to
<strong>refresh</strong> the window, it means to redo a full render. We can
use the <code>refresh</code> method, still on
<code>Hoa\Console\Window</code>.</p>
<p>Finally, it is possible to put a text inside the <strong>clipboard</strong>
of the user thanks to the <code>copy</code> method:</p>
<pre><code class="language-php">Hoa\Console\Window::copy('Foobar');</code></pre>
<p>Then, if the user pastes what is in the clipboard, she will see
<code>Foobar</code>.</p>
<h2 id="Cursor" for="main-toc">Cursor</h2>
<p>Inside a window, we have a cursor that can be seen as a tip of pen. The
<code>Hoa\Console\Cursor</code> class allows to manipulate the
<strong>cursor</strong> of the terminal through static methods.</p>
<h3 id="Moving" for="main-toc">Moving</h3>
<p>We will start to <strong>move</strong> the cursor. It can move everywhere
inside the viewport, it means the <strong>visible</strong> content of the
terminal window, but we will write a piece of text and move around at first.
The <code>move</code> method on <code>Hoa\Console\Cursor</code> allows to move
the cursor in several <strong>directions</strong>. First of all,
relatively:</p>
<ul>
<li><code>u[p]</code> or <code>↑</code>, to move the cursor one line
above,</li>
<li><code>r[ight]</code> or <code>→</code>, to move the cursor on the next
column,</li>
<li><code>d[own]</code> or <code>↓</code>, to move the cursor one line
bellow,</li>
<li><code>l[eft]</code> or <code>←</code>, to move the cursor on the
previous column.</li>
</ul>
<p>We also have <strong>semi-absolute</strong> moving:</p>
<ul>
<li><code>U[P]</code>, to move the cursor on the first line of the
viewport,</li>
<li><code>R[IGHT]</code>, to move the cursor on the last column of the
viewport,</li>
<li><code>D[OWN]</code>, to move the cursor on the last line of the
viewport,</li>
<li><code>L[EFT]</code>, to move the cursor on the first column of the
viewport.</li>
</ul>
<p>We are able to concatenate those directions by a space or to repeat one
direction.</p>
<pre><code class="language-php">echo
'abcdef', "\n",
'ghijkl', "\n",
'mnopqr', "\n",
'stuvwx';
sleep(1);
Hoa\Console\Cursor::move('↑');
sleep(1);
Hoa\Console\Cursor::move('↑ ←');
sleep(1);
Hoa\Console\Cursor::move('←', 3);
sleep(1);
Hoa\Console\Cursor::move('DOWN');
sleep(1);
Hoa\Console\Cursor::move('→', 4);</code></pre>
<p>During the execution, we will see the cursor moving <strong>by
itself</strong> from a letter to another one each second.</p>
<p>To actually move the cursor in an <strong>absolute</strong> way, we will
use the <code>moveTo</code> method that takes <em>column</em> × <em>line</em>
coordinates (starting from 1 and not 0) as argument. Similarly, there is the
<code>getPosition</code> method that returns the <strong>position</strong> of
the cursor. Thus, if we would like to move the cursor from the column 12 and
line 7, and then print its coordinates, we will write:</p>
<pre><code class="language-php">Hoa\Console\Cursor::moveTo(12, 7);
print_r(Hoa\Console\Cursor::getPosition());
/**
* Will output:
* Array(
* [x] => 12
* [y] => 7
* )
*/</code></pre>
<p>Finally, we often would like to move the cursor <strong>temporary</strong>
for some operations. In this case, it is useless to get its current position,
to move it and then re-position it; we can benefit from the <code>save</code>
and <code>restore</code> methods. As their names suggest, they respectively
<strong>save</strong> the position of the cursor and then
<strong>restore</strong> the cursor as the previously saved position. These
functions do not manipulate a <strong>stack</strong>, it is impossible to save
more than one position at a time (the new record will <strong>erase</strong>
the previous one). Thus, we will write a text, save the position of the
cursor, go back to rewrite, and then go back to our previous position:</p>
<pre><code class="language-php">echo 'hello world';
// Save cursor position.
Hoa\Console\Cursor::save();
sleep(1);
// Go to the begining of the line.
Hoa\Console\Cursor::move('LEFT');
sleep(1);
// Replace “h” by “H”.
echo 'H';
sleep(1);
// Go to “w”.
Hoa\Console\Cursor::move('→', 5);
sleep(1);
// Replace “w” by “W”.
echo 'W';
sleep(1);
// Back to the saved position.
Hoa\Console\Cursor::restore();
sleep(1);
echo '!';</code></pre>
<p>The final result will be <code>Hello World!</code>. We can notice that each
time a character is written, the cursor <strong>moves</strong>.</p>
<h3 id="Content" for="main-toc">Content</h3>
<p>Now we know how to move, we will see how to <strong>clean</strong> some
lines and/or columns. We will use the <code>clean</code> method that takes the
following symbols (concatenated by a space) as argument:</p>
<ul>
<li><code>a[ll]</code> or <code>↕</code>, to clean all the screen and move
the cursor in the top-left corner of the viewport,</li>
<li><code>u[p]</code> or <code>↑</code>, to clean all the lines above the
cursor,</li>
<li><code>r[ight]</code> or <code>→</code>, to clean the rest of the line
from the cursor,</li>
<li><code>d[own]</code> or <code>↓</code>, to clean all the lines bellow the
cursor,</li>
<li><code>l[eft]</code> or <code>←</code>, to clean the line from its
beginning to the cursor,</li>
<li><code>line</code> or <code>↔</code>, to clean all the line and move the
cursor to its beginning.</li>
</ul>
<p>Thus, to clean <strong>a line</strong>:</p>
<pre><code class="language-php">Hoa\Console\Cursor::clear('↔');</code></pre>
<p>The cursor can act as a brush and thus write in different
<strong>colors</strong> or different <strong>styles</strong> thanks to the
<code>colorize</code> method (we can mix everything by separating each
“command” by spaces). We start by enumerating all the styles:</p>
<ul>
<li><code>n[ormal]</code>, to cancel all applied styles,</li>
<li><code>b[old]</code>, to write in bold,</li>
<li><code>u[nderlined]</code>, to get an underlined text,</li>
<li><code>bl[ink]</code>, to get a blinking text,</li>
<li><code>i[nverse]</code>, to reverse colors of the foreground and the
background,</li>
<li><code>!b[old]</code>, to cancel the bold,</li>
<li><code>!u[nderlined]</code>, to cancel the underline,</li>
<li><code>!bl[ink]</code>, to cancel the blink,</li>
<li><code>!i[nverse]</code>, to no longer reverse colors of the foreground
and the background.</li>
</ul>
<p>Those styles are very classical. Now, let's see the colors. Before all, we
have to say if we apply a color on the <strong>foreground</strong> of the
text (the text itself) or on the <strong>background</strong>. Then, we will
respectively use the <code>f[ore]g[round](<em>color</em>)</code> or
<code>b[ack]g[round](<em>color</em>)</code> syntax. The value of
<code><em>color</em></code> can be:</p>
<ul>
<li><code>default</code>, to get the default color of the ground,</li>
<li><code>black</code>, <code>red</code>, <code>green</code>,
<code>yellow</code>, <code>blue</code>, <code>magenta</code>,
<code>cyan</code> or <code>white</code>,</li>
<li>a number between <code>0</code> and <code>256</code>, representing the
number of the color in the 256 colors palette,</li>
<li><code>#<em>rrggbb</em></code> where <code><em>rrggbb</em></code> is the
hexadecimal number representing the number of the color in the
2<sup>64</sup> colors palette.</li>
</ul>
<p>Terminals manipulate <strong>one</strong> of two palettes: 8 colors or 256
colors. Each color is <strong>indexed</strong> starting from 0. The names of
colors are <strong>transformed</strong> into their respective index. When a
color is given in hexadecimal, the <strong>closest</strong> color in the 256
colors palette is used.</p>
<p>Thus, if we would like to write <code>Hello</code> in yellow on a red-like
background (<code>#932e2e</code>) and underlined, then <code> world</code> but
not underlined:</p>
<pre><code class="language-php">Hoa\Console\Cursor::colorize('fg(yellow) bg(#932e2e) underlined');
echo 'Hello';
Hoa\Console\Cursor::colorize('!underlined');
echo ' world';</code></pre>
<p>Finally, it is possible to modify the palette of colors thanks to the
<code>changeColor</code> method, but use it with <strong>caution</strong>,
this can disturb the user. This method takes as first argument the index of
the color and as second argument its hexadecimal value. For example,
<code>fg(yellow)</code> matches the <code>33</code> index, and we would like
to see it blue:</p>
<pre><code class="language-php">Hoa\Console\Cursor::changeColor(33, 0xf00);</code></pre>
<p>However, the 256 colors palette is <strong>wide</strong> enough to not need
to redefine colors.</p>
<h3 id="Style" for="main-toc">Style</h3>
<p>The cursor is not necessary visible. During some operations, we can
<strong>hide</strong> it, make some moves and then make it
<strong>visible</strong> again. The <code>hide</code> and <code>show</code>
methods, still on <code>Hoa\Console\Cursor</code>, are here for that:</p>
<pre><code class="language-php">echo 'Visible', "\n";
sleep(5);
echo 'Invisible', "\n";
Hoa\Console\Cursor::hide();
sleep(5);
echo 'Visible', "\n";
Hoa\Console\Cursor::show();
sleep(5);</code></pre>
<p>There is three <strong>types</strong> of cursors, that we will choose with
the <code>setStyle</code> method:</p>
<ul>
<li><code>b[lock]</code> or <code>▋</code>, to get a cursor represented by a
block,</li>
<li><code>u[nderline]</code> or <code>_</code>, to get a cursor represented
by an underscore,</li>
<li><code>v[ertical]</code> or <code>|</code>, to get a cursor represented
by a pipe.</li>
</ul>
<p>This method takes as a second argument a boolean which indicates if the
cursor must <strong>blink</strong> (default value) or not. Thus, we will try
all the styles:</p>
<pre><code class="language-php">echo 'Block/steady: ';
Hoa\Console\Cursor::setStyle('▋', false);
sleep(3);
echo "\n", 'Vertical/blink: ';
Hoa\Console\Cursor::setStyle('|', true);
sleep(3);
// etc.</code></pre>
<p>The cursor often indicates <strong>area</strong> or
<strong>interactive</strong> elements, just like the pointer of a mouse.</p>
<h3 id="Sound" for="main-toc">Sound</h3>
<p>The cursor is also able to emit a little “bip”, most of the time to
<strong>bring</strong> the attention of the user. We will use the eponym
<code>bip</code> method:</p>
<pre><code class="language-php">Hoa\Console\Cursor::bip();</code></pre>
<p>There is only one available <strong>tonality</strong>.</p>
<h2 id="Readline" for="main-toc">Readline</h2>
<p>A way to <strong>interact</strong> with users is to read the
<code>STDIN</code> stream, namely the input stream. This
<strong>reading</strong> is by default very basic: unable to erase, unable to
use arrows, unable to use shortcuts etc. That's why there is the readline,
that is still a reading on the <code>STDIN</code> stream, but more
<strong>sophisticated</strong>. The <code>Hoa\Console\Readline\Readline</code>
library provides several features that we will describe.</p>
<h3 id="Basic_usage" for="main-toc">Basic usage</h3>
<p>To <strong>read a line</strong> (it means an input from the user), we will
instanciate the <code>Hoa\Console\Readline\Readline</code> class and call the
<code>readLine</code> method. Each call of this method will wait that the user
<strong>inputs</strong> a data and then hits <kbd title="Enter">↵</kbd>. At
this moment, the method will returns the input from the user (or
<code>false</code> if there is nothing to read). This method also takes a
<strong>prefix</strong> as argument, it means a data to print before the line.
Sometimes, the term <em>prompt</em> could be used in the literature, both
notions are identical.</p>
<p>Thus, we will write a program that will read the inputs from the user and
make an echo. The program will finish if the user inputs
<code>quit</code>:</p>
<pre><code class="language-php">$rl = new Hoa\Console\Readline\Readline();
do {
$line = $rl->readLine('> ');
echo '&lt; ', $line, "\n\n";
} while (false !== $line &amp;&amp; 'quit' !== $line);</code></pre>
<p>Now, let's detail the features offered by
<code>Hoa\Console\Readline\Readline</code>.</p>
<p>We are able to <strong>move</strong> (understand, move the cursor) in the
line with the help of the <kbd>←</kbd> and <kbd>→</kbd> keys. We can
<strong>erase</strong> a character back at any moment with the
<kbd title="Backspace">⌫</kbd> key or all the characters until the beginning
of the word with <kbd>Ctrl</kbd> + <kbd>W</kbd> (where <kbd>W</kbd> stands for
word). We are also able to move the cursor by using <strong>shortcuts</strong>
that a shared by many softwares:</p>
<ul>
<li><kbd>Ctrl</kbd> + <kbd>A</kbd>, to move at the beginning of the
line,</li>
<li><kbd>Ctrl</kbd> + <kbd>E</kbd>, to move at the end of the line,</li>
<li><kbd>Ctrl</kbd> + <kbd>B</kbd>, to move at the beginning of the current
word (<kbd>B</kbd> stands for backward),</li>
<li><kbd>Ctrl</kbd> + <kbd>F</kbd>, to move at the end of the current word
(<kbd>F</kbd> stands for forward).</li>
</ul>
<p>We also have access to the <strong>history</strong> when we hit the
<kbd>↑</kbd> and <kbd>↓</kbd> keys, respectively to search back and forth in
the history. The <kbd title="Tabulation">⇥</kbd> key fires the
<strong>auto-completion</strong> if set. And finally, the
<kbd title="Enter">↵</kbd> key returns the input.</p>
<p>There is also the <code>Hoa\Console\Readline\Password</code> class that
allows to have a readline with the same features but the characters
<strong>are not printed</strong> to the screen, very useful to read a
<strong>password</strong>:</p>
<pre><code class="language-php">$rl = new Hoa\Console\Readline\Password();
$pwd = $rl->readLine('Password: ');
echo 'Your password is: ', $pwd, "\n";</code></pre>
<h3 id="Shortcuts" for="main-toc">Shortcuts</h3>
<p>To understand how to create a shortcut, we have to understand a little bit
how the <code>Hoa\Console\Readline\Readline</code> class works
<strong>internally</strong>, and this is very simple. Each time we hit one or
many keys, a <strong>string</strong> representing this
<strong>combination</strong> is received by our readline. It looks for an
associated action to this string: if one exists, then this one will be
executed, else the readline will use a default action consisting to print the
string verbatim. Each action returns the <strong>state</strong> of the
readline (which are constants on
<code>Hoa\Console\Readline\Readline</code>):</p>
<ul>
<li><code>STATE_CONTINUE</code>, to continue the reading,</li>
<li><code>STATE_BREAK</code>, to stop the reading,</li>
<li><code>STATE_NO_ECHO</code>, to not print the reading.</li>
</ul>
<p>Thus, if an action returns <code class="language-php">STATE_CONTINUE |
STATE_NO_ECHO</code>, the reading will continue but the string that has been
received will not be printed. Another example, the action associated to the
<kbd title="Enter">↵</kbd> key returns the <code>STATE_BREAK</code> state.</p>
<p>To <strong>add</strong> actions, we use the <code>addMapping</code> method.
This latter eases the add thanks to a dedicated syntax:</p>
<ul>
<li><code>\e[<em>…</em></code>, for sequences starting by the <kbd>Esc</kbd>
character,</li>
<li><code>\C-<em>…</em></code>, for sequences starting by the
<kbd>Ctrl</kbd> character,</li>
<li><code><em>x</em></code>, any character.</li>
</ul>
<p>For example, if we would like to print <code>z</code> instead of
<code>a</code>, we will write:</p>
<pre><code class="language-php">$rl->addMapping('a', 'z');</code></pre>
<p>More sophisticated this time, we can use a callable as the second argument
of the <code>addMapping</code> method. This callable will receive the instance
of <code>Hoa\Console\Readline\Readline</code> as the only argument. Several
methods will help to <strong>manipulate</strong> the reading (to manage the
history, the line etc.). For example, each time we will hit
<kbd>Ctrl</kbd> + <kbd>R</kbd>, we will reverse the case of the line:</p>
<pre><code class="language-php">$rl = new Hoa\Console\Readline\Readline();
// Add mapping.
$rl->addMapping('\C-R', function (Hoa\Console\Readline\Readline $self) {
// Clear the line.
Hoa\Console\Cursor::clear('↔');
echo $self->getPrefix();
// Get the line text.
$line = $self->getLine();
// New line.
$new = null;
// Loop over all characters.
for ($i = 0, $max = $self->getLineLength(); $i &lt; $max; ++$i) {
$char = mb_substr($line, $i, 1);
if ($char === $lower = mb_strtolower($char)) {
$new .= mb_strtoupper($char);
} else {
$new .= $lower;
}
}
// Set the new line.
$self->setLine($new);
// Set the buffer (and let the readline echoes or not).
$self->setBuffer($new);
// The readline will continue to read.
return $self::STATE_CONTINUE;
});
// Try!
var_dump($rl->readLine('> '));</code></pre>
<p>Do not hesitate to take a look at how previous listed shortcuts have been
implemented to get ideas.</p>
<h3 id="Auto-completion" for="main-toc">Auto-completion</h3>
<p>Another very useful tool when we are writing a readline is the
<strong>auto-completion</strong>. It is fired when hiting the
<kbd title="Tabulation">⇥</kbd> key if the auto-completer has been defined
with the <code>setAutocompleter</code> method.</p>
<p>All the auto-completers must implement the
<code>Hoa\Console\Readline\Autocompleter\Autocompleter</code> interface. Some
of them are already distributed to <strong>help</strong> us in our
development, such as <code>Hoa\Console\Readline\Autocompleter\Word</code> that
will auto-complete the input based on a <strong>list of words</strong>. For
example:</p>
<pre><code class="language-php">$rl = new Hoa\Console\Readline\Readline();
$rl->setAutocompleter(new Hoa\Console\Readline\Autocompleter\Word([
'hoa',
'console',
'readline',
'autocompleter',
'autocompletion',
'password',
'awesome'
]));
var_dump($rl->readLine('> '));</code></pre>
<p>Let's try to write something, then where we would like, we hit
<kbd title="Tabulation">⇥</kbd>. If the text at the left of the cursor starts
by <code>h</code>, then we will see <code>hoa</code> <strong>printed</strong>
in the line because the auto-completer has no choice (it returns a string). If
the auto-completer does not find any appropriated word,
<strong>nothing</strong> will happen (it will return <code>null</code>). And
finally, if it finds <strong>several words</strong> (it will return an array),
then a <strong>menu</strong> will appear. Let's try to simply auto-complete
<code>a</code>: the menu will propose <code>autocompleter</code>,
<code>autocompletion</code> and <code>awesome</code>. Either we continue to
hit and the menu will <strong>disappear</strong>, or we can
<strong>move</strong> the cursor inside the menu with the
<kbd title="Tabulation">⇥</kbd>, <kbd>↑</kbd>, <kbd>→</kbd>, <kbd>↓</kbd> and
<kbd>←</kbd> keys, then <kbd title="Enter">↵</kbd> to <strong>select</strong>
a word. This behavior is quite <strong>natural</strong>.</p>
<p>In addition to the auto-completer on words, we find the auto-completer on
<strong>paths</strong> with the
<code>Hoa\Console\Readline\Autocompleter\Path</code> class. Based on a root
and a file iterator, this auto-completer is able to auto-complete paths.
If the root is undefined, it will be set to the current working directory. At
each auto-completion, a new instance of the file iterator is created by a
factory. This latter receives the path to iterate as a unique argument. By
default, the factory is defined by the <code>getDefaultIteratorFactory</code>
static method on <code>Hoa\Console\Readline\Autocompleter\Path</code>. The
factory builds a file iterator of type
<a href="http://php.net/directoryiterator"><code>DirectoryIterator</code></a>.
Each value computed by the iterator must be an object of type
<a href="http://php.net/splfileinfo"><code>SplFileInfo</code></a>. Thus, to
auto-complete all the files and directories from the
<a href="@central_resource:path=Library/Console"><code>hoa://Library/Console</code></a>
root, we will write:</p>
<pre><code class="language-php">$rl->setAutocompleter(
new Hoa\Console\Readline\Autocompleter\Path(
resolve('hoa://Library/Console')
)
);</code></pre>
<p>Using a factory offers a lot of <strong>flexibility</strong> and allows us
to use any file iterator, such as <code>Hoa\File\Finder</code> (please, see
<a href="@hack:chapter=File">the <code>Hoa\File</code> library</a>). Thus, to
only auto-complete not hidden files and directories that have been modified
the last 6 months and sorted by their size, we will write:</p>
<pre><code class="language-php">$rl->setAutocompleter(
new Hoa\Console\Readline\Autocompleter\Path(
resolve('hoa://Library/Console'),
function ($path) {
$finder = new Hoa\File\Finder();
$finder->in($path)
->files()
->directories()
->maxDepth(1)
->name('#^(?!\.).#')
->modified('since 6 months')
->sortBySize();
return $finder;
}
)
);</code></pre>
<p>We can replace the local file iterator by another totally
<strong>different</strong> iterator: on files that are stored in another
machine, a third party service or even resources that are not files but have
path-like URI.</p>
<p>Finally, we can aggregate several auto-completers together thanks to the
<code>Hoa\Console\Readline\Autocompleter\Aggregate</code> class. The
declaration order of auto-completers is important: the first one that matches
a word to auto-complete will break the loop over all the auto-completers.
Thus, to auto-complete paths and words, we will write:</p>
<pre><code class="language-php">$rl->setAutocompleter(
new Hoa\Console\Readline\Autocompleter\Aggregate([
new Hoa\Console\Readline\Autocompleter\Path(),
new Hoa\Console\Readline\Autocompleter\Word($words)
])
);
</code></pre>
<p>The <code>getAutocompleters</code> method from
<code>Hoa\Console\Readline\Autocompleter\Aggregate</code> returns an
<a href="http://php.net/arrayobject"><code>ArrayObject</code></a> object for
more flexibility. We can consequently add or delete auto-completers after
having declared them in the constructor.</p>
<figure>
<img src="https://central.hoa-project.net/Resource/Library/Console/Documentation/Image/Readline_autocompleters.gif?format=raw" />
<figcaption>Example of an aggregation of the
<code>Hoa\Console\Readline\Autocompleter\Path</code> auto-completer with
<code>Hoa\Console\Readline\Autocompleter\Word</code>.</figcaption>
</figure>
<h2 id="Reading_options" for="main-toc">Reading options</h2>
<p>A big benefit of programs with a command line interface is their
<strong>flexibility</strong>. They are <strong>dedicated</strong> to one
(little) <strong>task</strong> and we are able to parameterize them thanks to
<strong>options</strong> that they exposed. The <strong>reading</strong> of
those options must be very simple and straightforward because this is a
repetitive and subtle task. The <code>Hoa\Console\Parser</code> and
<code>Hoa\Console\GetOption</code> classes act as a <strong>duo</strong> to
solve this problem.</p>
<h3 id="Analyzing_options" for="main-toc">Analyzing options</h3>
<p>We start with <code>Hoa\Console\Parser</code> that
<strong>analyzes</strong> options given to a program. Whatever the options we
would like to have, we just analyze them for now. Let's start by using the
<code>parse</code> method:</p>
<pre><code class="language-php">$parser = new Hoa\Console\Parser();
$parser->parse('-s --long=value input');
print_r($parser->getSwitches());
print_r($parser->getInputs());
/**
* Will output:
* Array
* (
* [s] => 1
* [long] => value
* )
* Array
* (
* [0] => input
* )
*/</code></pre>
<p>Let's see of what a command line is composed. We have two categories: the
<strong>options</strong> (switches) and the <strong>inputs</strong>. The
inputs are everything that is not an option. An option can have two forms:
<strong>short</strong> if it has only one character or <strong>long</strong>
if it has many.</p>
<p>Thus, <code>-s</code> is a short option and <code>--long</code> is a
long option. However, we have to consider the number of hyphen in front of the
option: with two hyphens, it will always be a long option, with one hyphen, it
depends. There is two schools that differenciate themselves with one
<strong>parameter</strong>: <em>long only</em>. Let's take an example:
<code>-abc</code> is considered as <code>-a -b -c</code> if the <em>long
only</em> parameter is set to <code>false</code>, else it will be equivalent
to a long option, such as <code>--abc</code>. Most of the time, this parameter
is set to <code>false</code> by default and <code>Hoa\Console\Parser</code>
has adopted the position of the majority. To modify this parameter, we have to
use the <code>setLongOnly</code> method, let's see though:</p>
<pre><code class="language-php">// long only is set to false.
$parser->parse('-abc');
print_r($parser->getSwitches());
$parser->setLongOnly(true);
// long only is set to true.
$parser->parse('-abc');
print_r($parser->getSwitches());
/**
* Will output:
* Array
* (
* [a] => 1
* [b] => 1
* [c] => 1
* )
* Array
* (
* [abc] => 1
* )
*/</code></pre>
<p>An option can be of two kinds: <strong>boolean</strong> or
<strong>valued</strong>. If no value is associated, it is considered as a
boolean. Thus, <code>-s</code> represents <code>true</code>, but <code>-s
-s</code> represents <code>false</code>, <code>-s -s -s</code> represents
<code>true</code> and so on. A boolean option behaves like a
<strong>switch</strong>. A valued option has an associated value by using
either a space or an equal sign (symbol <code>=</code>). Here is a
non-exhaustive list of potential syntaxes (we use a short option but it could
be a long one):</p>
<ul>
<li><code>-x=value</code>: <code>value</code>,</li>
<li><code>-x=va\ lue</code>: <code>va lue</code>,</li>
<li><code>-x="va lue"</code>: <code>va lue</code>,</li>
<li><code>-x="va l\"ue"</code>: <code>va l"ue</code>,</li>
<li><code>-x value</code>: <code>value</code>,</li>
<li><code>-x va\ lue</code>: <code>va lue</code>,</li>
<li><code>-x "value"</code>: <code>value</code>,</li>
<li><code>-x "va lue"</code>: <code>va lue</code>,</li>
<li><code>-x va\ l"ue</code>: <code>va l"ue</code>,</li>
<li><code>-x 'va "l"ue'</code>: <code>va "l"ue</code>,</li>
<li>etc.</li>
</ul>
<p>The simple (symbol <code>'</code>) and double (symbol <code>"</code>)
quotes are supported. But be aware that there is particular syntaxes which are
still not <strong>standards</strong>:</p>
<ul>
<li><code>-x=-value</code>: <code>-value</code>,</li>
<li><code>-x "-value"</code>: <code>-value</code>,</li>
<li><code>-x \-value</code>: <code>-value</code>,</li>
<li><code>-x -value</code>: is the equivalent of two booleans,
<code>-x</code> and <code>-value,</code></li>
<li><code>-x=-7</code>: <code>-7</code>,</li>
<li>etc.</li>
</ul>
<p><em>A l'instar</em> of boolean options that behave like switches, the
valued options <strong>rewrite</strong> their values if they are declared more
than once. Thus, <code>-a=b -a=c</code> represents <code>c</code>.</p>
<p>Finally, there is values that are considered as <strong>special</strong>.
We distinguish two of them:</p>
<ul>
<li><strong>lists</strong>, with the help of a comma as a separator:
<code>-x=a,b,c</code>,</li>
<li><strong>intervalles</strong>, with the help of the <code>:</code> symbol
(without spaces around it): <code>-x=1:7</code>.</li>
</ul>
<p>Without any specific manipulation, those values will not be considered as
special. We will have to use the
<code>Hoa\Console\Parser::parseSpecialValue</code> method as we will see
bellow.</p>
<h3 id="Read_options_and_inputs" for="main-toc">Read options and inputs</h3>
<p>We know how to analyze options but this is not enough to read them
correctly. We have to define a little <strong>semantics</strong>: what do
their expect, what is their nature etc. We will use the
<code>Hoa\Console\GetOption</code> class. An option is defined by:</p>
<ul>
<li>a <strong>long</strong> name,</li>
<li>a <strong>short</strong> name,</li>
<li>a <strong>type</strong>, given by one of the
<code>Hoa\Console\GetOption</code> constants among:
<ul>
<li><code>NO_ARGUMENT</code> if the option is a boolean one,</li>
<li><code>REQUIRED_ARGUMENT</code> if the option is a valued one,</li>
<li><code>OPTIONAL_ARGUMENT</code> if the option can receive a
value.</li>
</ul>
</li>
</ul>
<p>These three informations <strong>must</strong> be specified. They must be
given to the constructor of <code>Hoa\Console\GetOption</code> as first
argument. The second one is the option analyzer (the analysis must be
<strong>already</strong> done). Thus, we describe two options:
<code>extract</code> that is a boolean option and <code>directory</code> that
is a valued option:</p>
<pre><code class="language-php">$parser = new Hoa\Console\Parser();
$parser->parse('-x --directory=value inputA inputB inputC');
$options = new Hoa\Console\GetOption(
[
// long name type short name
// ↓ ↓ ↓
['extract', Hoa\Console\GetOption::NO_ARGUMENT, 'x'],
['directory', Hoa\Console\GetOption::REQUIRED_ARGUMENT, 'd']
],
$parser
);</code></pre>
<p>We are now ready to read our options! The options reader behaves like an
iterator, or like a <strong>pipette</strong> though, thanks to the
<code>getOption</code> method. This method returns the short name of the
option currently read and will assign the value of the option (a boolean or a
string) to its first argument passed by reference. When the pipette is empty,
the <code>getOption</code> method will return <code>false</code>. This
structure might seem original but it is widely <strong>spread</strong>, you
will not be lost when seeing it in other progams (examples
<a href="http://kernel.org/doc/man-pages/online/pages/man3/getopt.3.html#EXAMPLE"
title="getopt(3), Linux Programmer's Manual">in Linux</a>,
<a href="http://freebsd.org/cgi/man.cgi?query=getopt&sektion=3#EXAMPLES"
title="getopt(3), FreeBSD Library Functions Manual">in FreeBSD</a> or
<a href="http://developer.apple.com/library/Mac/#documentation/Darwin/Reference/ManPages/man3/getopt.3.html"
title="getopt(3), BSD Library Functions Manual">in Mac OS X</a> —same code
base—). The simplest way to read options are to define them default values
and then to use <code>getOption</code>, thus:</p>
<pre><code class="language-php">$extract = false;
$directory = '.';
// short name value
// ↓ ↓
while (false !== $c = $options->getOption($v)) {
switch($c) {
case 'x':
$extract = $v;
break;
case 'd':
$directory = $v;
break;
}
}
var_dump($extract, $directory);
/**
* Will output:
* bool(true)
* string(5) "value"
*/</code></pre>
<p>We read it like this: “while we have an option to read, we get the short
name in <code>$c</code> and its value in <code>$v</code>, then we see what to
do”.</p>
<p>To read inputs, we will use the <code>Hoa\Console\Parser::listInputs</code>
whose all arguments (a total of 26) are passed by <strong>reference</strong>.
Thus:</p>
<pre><code class="language-php">$parser->listInputs($inputA, $inputB, $inputC);
var_dump($inputA, $inputB, $inputC);
/**
* Will output:
* string(6) "inputA"
* string(6) "inputB"
* string(6) "inputC"
*/</code></pre>
<p>Attention, this approach implies that the inputs are
<strong>ordered</strong> (as usualy most of the time). But also, reading the
inputs without having formely given the analyzer to
<code>Hoa\Console\GetOption</code> can lead to unexpected results (because by
default, all options are considered as booleans). If we would like all the
inputs and analyze them manually if they are not ordered, we can use the
<code>Hoa\Console\Parser::getInputs</code> method which will return all the
inputs.</p>
<h3 id="Special_or_ambiguous_options" for="main-toc">Special or ambiguous
options</h3>
<p>Back to our <code>Hoa\Console\Parser::parseSpecialValue</code> method. It
takes two arguments: a value and an array of keywords. We re-use our example
and modify the case of the <code>d</code> option:</p>
<pre data-line="8-11"><code class="language-php">while (false !== $c = $options->getOption($v)) {
switch($c) {
case 'x':
$extract = $v;
break;
case 'd':
$directory = $parser->parseSpecialValue($v, ['HOME' => '/tmp']);
break;
}
}
print_r($directory);</code></pre>
<p>If we try with <code>-d=a,b,HOME,c,d</code> then <code>-d</code> will have
the following value:</p>
<pre><code class="language-php">/**
* Array
* (
* [0] => a
* [1] => b
* [2] => /tmp
* [3] => c
* [4] => d
* )
*/</code></pre>
<p>Finally, when a read option does not exist but it is
<strong>close</strong> to an existing option modulo some
<strong>typos</strong> (for example <code>--dirzctory</code> instead of
<code>--directory</code>), we can use the <code>__ambiguous</code> case to
capture and compute it:</p>
<pre data-line="13-16"><code class="language-php">while (false !== $c = $options->getOption($v)) {
switch($c) {
case 'x':
$extract = $v;
break;
case 'd':
$directory = $parser->parseSpecialValue($v, ['HOME' => '/tmp']);
break;
case '__ambiguous':
print_r($v);
break;
}
}</code></pre>
<p>The value (in <code>$v</code>) is an array with three entries. For example
with <code>--dirzctory</code>, we have:</p>
<pre><code class="language-php">/**
* Array
* (
* [solutions] => Array
* (
* [0] => directory
* )
*
* [value] => y
* [option] => dirzctory
* )
*/</code></pre>
<p>The <code>solutions</code> key provides all the <strong>similar</strong>
options, the <code>value</code> key gives the value of the option and
<code>option</code> is the <strong>original</strong> read name. It is part to
the user to decide what to do based on these informations. We can use the
<code>Hoa\Console\GetOption::resolveOptionAmbiguity</code> method by giving it
the array, and it will choose the best option if it exists:</p>
<pre><code class="language-php"> case '__ambiguous':
$options->resolveOptionAmbiguity($v);
break;
</code></pre>
<p>It is preferable to <strong>advise</strong> the user that an ambiguity
happened and ask her a decision. Sometimes, it can be
<strong>dangerous</strong> to make decision in her place.</p>
<h3 id="Integrate_a_router_and_a_dispatcher" for="main-toc">Integrate a router
and a dispatcher</h3>
<p>So far, we coerced the options and the inputs to the analyzer.
<code>Hoa\Router\Cli</code> allows to <strong>extract</strong> data from a
command line program. One method interests us:
<code>Hoa\Router\Cli::getURI</code> will give us all the options and inputs of
the running program, that we will <strong>provide</strong> to our analyzer.
Thus:</p>
<pre data-line="2"><code class="language-php">$parser = new Hoa\Console\Parser();
$parser->parse(Hoa\Router\Cli::getURI());
// …</code></pre>
<p>Now, it is possible to interprete the options that we will give to our
program. If you have written the tests in a file named <code>Test.php</code>,
then you will be able to write:</p>
<pre><code class="language-shell">$ php Test.php -x -d=a,b,HOME,c,d inputA inputB
bool(true)
Array
(
[0] => a
[1] => b
[2] => /tmp
[3] => c
[4] => d
)
string(6) "inputA"
string(6) "inputB"
NULL</code></pre>
<p>The <code>-x</code> option is set to <code>true</code>, the <code>-d</code>
option is an array (because we have analyzed it with the
<code>Hoa\Console\Parser::parseSpecialValue</code> method), and we have
<code>inputA</code>, <code>inputB</code> and <code>null</code> as inputs.</p>
<p>This is a good start and we can stop here most of the time. But it is
possible to go further by using a <strong>dispatcher</strong>: writing
commands in several functions or classes and call them regarding the given
options and inputs to our program. We recommend to read the source code of
<a href="@central_resource:path=Library/Cli/Bin/Hoa.php"><code>hoa://Library/Cli/Bin/Hoa.php</code></a>
to help yourself, along with the
<a href="@hack:chapter=Router"><code>Hoa\Router</code></a> and
<a href="@hack:chapter=Dispatcher"><code>Hoa\Dispatcher</code></a> chapters.
We propose a quick example without giving too much details about those
libraries.</p>
<p>The idea is as follows. Thanks to <code>Hoa\Router\Cli</code> we are going
to extract data of the following form: <code>$ php script.php
<em>controller</em> <em>tail</em></code>, where
<code><em>controller</em></code> will be the name of the controller (of a
class) on which we will call the <code>main</code> action (it means the
<code>main</code> method with its default parameters) and where
<code><em>tail</em></code> represents the options and the inputs. The name of
the controller is identified by the special <code>_call</code> variable (from
<code>Hoa\Router\Cli</code>) and the options along with the inputs are
identified by <code>_tail</code> (from <code>Hoa\Dispatcher\Kit</code>). The
options and the inputs are not mandatory. Then, we will use
<code>Hoa\Dispatcher\Basic</code> with the dedicated kit of terminals, namely
<code>Hoa\Console\Dispatcher\Kit</code>. The dispatcher will try to load the
<code>Application\Controller\<em>controller</em></code> class by default, and
the autoloader will load them from the
<code>hoa://Application/Controller/<em>controller</em></code> directory. We
will then set quickly where the application is located. Finally, the exit code
of our program will be given by the value returned by our controller and
action. If an error occurred, we will print it and force an exit code greater
than zero. Thus:</p>
<pre><code class="language-php">try {
// Prepare the router.
$router = new Hoa\Router\Cli();
$router->get(
'g',
'(?&lt;_call>\w+)(?:\s+(?&lt;_tail>.+))?'
);
// Prepare the dispatcher.
$dispatcher = new Hoa\Dispatcher\ClassMethod([
'synchronous.call' => 'Application\Controller\(:call:U:)',
'synchronous.able' => 'main'
]);
$dispatcher->setKitName('Hoa\Console\Dispatcher\Kit');
// Dispatch!
exit($dispatcher->dispatch($router));
} catch (Hoa\Exception $e) {
echo $e->raise(true);
exit($e->getCode() + 1);
}</code></pre>
<p>At the same level of our program, let's create the
<code>Application/Controller/</code> directory with the <code>Foo.php</code>
file inside, which will contain the following code:</p>
<pre><code class="language-php">&lt;?php
namespace Application\Controller;
class Foo extends \Hoa\Console\Dispatcher\Kit
{
protected $options = [
['extract', \Hoa\Console\GetOption::NO_ARGUMENT, 'x'],
['directory', \Hoa\Console\GetOption::REQUIRED_ARGUMENT, 'd'],
['help', \Hoa\Console\GetOption::NO_ARGUMENT, 'h']
];
public function MainAction()
{
$extract = false;
$directory = '.';
while (false !== $c = $this->getOption($v)) {
switch($c) {
case 'x':
$extract = $v;
break;
case 'd':
$directory = $this->parser->parseSpecialValue($v, ['HOME' => '/tmp']);
break;
case 'h':
return $this->usage();
}
}
echo 'extract: ';
var_dump($extract);
echo 'directory: ';
print_r($directory);
return;
}
public function usage()
{
echo
'Usage : foo &lt;options>', "\n",
'Options :', "\n",
$this->makeUsageOptionsList([
'x' => 'Whether we need to extract.',
'd' => 'Directory to extract.',
'h' => 'This help.'
]);
}
}</code></pre>
<p>Our class extends our kit to benefit from the provided methods. Among other
things, its own <code>getOption</code> method that will exploit the
<code>$options</code> attribute where the options are declared,
<code>makeUsageOptionsList</code> to print a usage, its own
<code>resolveOptionAmbiguity</code> method that asks a confirmation from the
user, the router access through the <code>$router</code> attribute etc. Kits
offer <strong>services</strong> to the application, they
<strong>aggregate</strong> services offered by the libraries. Now, let's
test:</p>
<pre><code class="language-shell">$ php Test.php foo -x -d=1:3
extract: bool(true)
directory: Array
(
[0] => 1
[1] => 2
[2] => 3
)</code></pre>
<p>Awesome!</p>
<p>Let's note that the <code>hoa</code> script is exactly build like this. Do
not hesitate to find inspiration from it.</p>
<h2 id="Processus" for="main-toc">Processus</h2>
<p>In our context, a <strong>processus</strong> is a classical program that
is executed in a <strong>terminal</strong>. The interesting part is that such
a program <strong>communicates</strong> with the rest of its
<strong>environment</strong> thanks to <strong>pipes</strong>, numbered from
zero. Some of them have even names:</p>
<ul>
<li><code>STDIN</code> (<code>0</code>) to read <strong>inputs</strong>
(<em>standard input</em>),</li>
<li><code>STDOUT</code> (<code>1</code>) to write <strong>outputs</strong>
(<em>standard output</em>),</li>
<li><code>STDERR</code> (<code>2</code>) to write <strong>errors</strong>
(<em>standard error</em>).</li>
</ul>
<p>When a processus is executed in a terminal, <code>STDIN</code> will use the
<strong>keyboard</strong> as the source of data, and <code>STDOUT</code> with
<code>STDERR</code> are linked to the <strong>window</strong> of the terminal.
But when a processus is executed in a <strong>sub-terminal</strong>, it means
executed from another processus, <code>STDIN</code> is no longer linked to the
keyboard, like <code>STDOUT</code> and <code>STDERR</code> are not linked to
the screen. This is the parent processus that will read and write on these
streams to <strong>interact</strong> with the “sub”-processus. This mechanism
is called a <strong>redirection</strong> of stream, we use it very often when
we write a command line (please, see the
<a href="http://gnu.org/software/bash/manual/bashref.html#Redirections">section
Redirections from the Bash Reference Manual</a>). What we are going to do use
another syntax but the mechanism is exactly the same.</p>
<p>It is very important to know that these streams are all
<strong>asynchronous</strong> from each other. Not one of these streams will
have an impact on another one, there is no link between them and that is very
important for the rest of this section.</p>
<p>From the PHP level, it is possible to access to these streams by using
respectively the following URI: <code>php://stdin</code>,
<code>php://stdout</code> and <code>php://stderr</code>. However, we also have
the eponymous <code>STDIN</code>, <code>STDOUT</code> and <code>STDERR</code>
constants. They are defined as follows (example with <code>STDIN</code>):</p>
<pre><code class="language-php">define('STDIN', fopen('php://stdin', 'r'));</code></pre>
<p>These streams are available if and only if the program is executed from a
command line. Remind that pipes are identified by numbers. We are then able to
use <code>php://fd/0</code> to represent <code>STDIN</code>,
<code>php://fd/1</code> to represent <code>STDOUT</code> etc. The
<code>php://fd/<em>i</em></code> URI allows to access the file with the
<code><em>i</em></code> <strong>descriptor</strong>.</p>
<h3 id="Very_basic_execution" for="main-toc">Very basic execution</h3>
<p>The <code>Hoa\Console\Processus</code> class provides a very
<strong>quick</strong> way to execute a processus and get the result of
<code>STDOUT</code>. This is the most common case. Thus, we will use the
<code>execute</code> static method:</p>
<pre><code class="language-php">var_dump(Hoa\Console\Processus::execute('id -u -n'));
/**
* Could output:
* string(3) "hoa"
*/</code></pre>
<p>By default, the command will be escaped for security reasons. If you are
confident in the command, you can desactivate the escaping by setting the
second argument to <code>false</code>.</p>
<p>We have no control on the pipes and even if it is suitable for the majority
of cases, this is not enough when we would like a minimum of interaction with
the processus.</p>
<h3 id="Reading_and_writing" for="main-toc">Reading and writing</h3>
<p>Let's see how to <strong>interact</strong> with a processus. We will
consider the following <code>LittleProcessus.php</code> program:</p>
<pre><code class="language-php">&lt;?php
$range = range('a', 'z');
while (false !== $line = fgets(STDIN)) {
echo '> ', $range[intval($line)], "\n";
}</code></pre>
<p>To test and understand its behavior, let's write the following command line
and hit <code>3</code> and <code>4</code> on the keyboard:</p>
<pre><code class="language-shell">$ php LittleProcessus.php
3
> d
4
> e
</code></pre>
<p>We can also write:</p>
<pre><code class="language-shell">$ seq 0 4 | php LittleProcessus.php
> a
> b
> c
> d
> e</code></pre>
<p>Our program will read each line on the standard input, considering it is a
number and transforming it into a character that will be printed on the
standard output. We would like to execute this program by giving it our own
custom list of numbers (like the <code>seq</code> program does) and observe
the produced result.</p>
<p>An instance of the <code>Hoa\Console\Processus</code> class represents a
<strong>processus</strong>. During the instanciation, we can set:</p>
<ul>
<li>the <strong>name</strong> of the processus,</li>
<li>its <strong>options</strong>,</li>
<li>the <strong>description</strong> of pipes.</li>
</ul>
<p>There is other arguments but we will see them further.</p>
<p>The description of pipes has the form of an array where each key represents
the number of the pipe (more generally, this is the <code><em>i</em></code>
from <code>php://fd/<em>i</em></code>) and its value is also an array
describing the nature of the pipe: either a “real” pipe, or a file with their
reading or writing mode (among <code>r</code>, <code>w</code> or
<code>a</code>). Let's illustrate with an example:</p>
<pre><code class="language-php">$processus = new Hoa\Console\Processus(
'php',
['LittleProcessus.php'],
[
// STDIN.
0 => ['pipe', 'r'],
// STDOUT.
1 => ['file', '/tmp/output', 'a']
]
);</code></pre>
<p>In this case, <code>STDIN</code> is a pipe and <code>STDOUT</code> is the
<code>/tmp/output</code> file. If we do not set a descriptor, it will be
equivalent to write:</p>
<pre><code class="language-php">$processus = new Hoa\Console\Processus(
'php',
['LittleProcessus.php'],
[
// STDIN.
0 => ['pipe', 'r'],
// STDOUT.
1 => ['pipe', 'w'],
// STDERR.
2 => ['pipe', 'w']
]
);</code></pre>
<p>Each pipe is identified as a stream and can be manipulated as such. When a
pipe is in <strong>reading</strong> (with the <code>r</code> mode), it means
that the processus will <strong>read</strong> from it. So we, the parent
processus, will <strong>write</strong> on this pipe. Let's take the example of
<code>STDIN</code>: the processus reads from <code>STDIN</code> what the
keyboard has written on it. And conversely, when a pipe is in
<strong>writing</strong> (with the <code>w</code> mode), it means that we will
<strong>read</strong> from it. Let's take the example of <code>STDOUT</code>:
the screen will read what the processus has written to it.</p>
<p>The <code>Hoa\Console\Processus</code> class extends the
<a href="@hack:chapter=Stream"><code>Hoa\Stream</code></a> class, and
consequently, we have all the necessary tools to read and write on the pipe of
our choice. This class also provides several <strong>listeners</strong>:</p>
<ul>
<li><code>start</code>, when the processus is started,</li>
<li><code>stop</code>, when the processus is stopped,</li>
<li><code>input</code>, when the reading streams are ready,</li>
<li><code>output</code>, when the writing streams are ready,</li>
<li><code>timeout</code>, when the processus is executing too much
time.</li>
</ul>
<p>Let's take directly an example. We will execute the <code>php
LittleProcessus.php</code> processus and attach functions to the following
listeners: <code>input</code> to write a sequence of numbers and
<code>output</code> to read the result.</p>
<pre><code class="language-php">$processus = new Hoa\Console\Processus('php LittleProcessus.php');
$processus->on('input', function ($bucket) {
$source = $bucket->getSource();
$data = $bucket->getData();
echo 'INPUT (', $data['pipe'], ')', "\n";
$source->writeAll(
implode("\n", range($i = mt_rand(0, 21), $i + 4)) . "\n"
);
return false;
});
$processus->on('output', function ($bucket) {
$data = $bucket->getData();
echo 'OUTPUT (', $data['pipe'], ') ', $data['line'], "\n";
return;
});
$processus->run();
/**
* Could output:
* INPUT (0)
* OUTPUT (1) > s
* OUTPUT (1) > t
* OUTPUT (1) > u
* OUTPUT (1) > v
* OUTPUT (1) > w
*/</code></pre>
<p>Now, let's see the details to understand well.</p>
<p>When a <strong>reading</strong> stream is <strong>ready</strong>, the
<code>input</code> listener will be fired. Only one data is sent:
<code>pipe</code>, that contains the number of the pipe (the
<code><em>i</em></code> of <code>php://fd/<em>i</em></code>). When a
<strong>writing</strong> stream is ready, the <code>output</code> listener is
fired. Two data are sent: <code>pipe</code> (like <code>input</code>) and
<code>line</code> that contains the <strong>received line</strong>.</p>
<p>We see in the function attached to the <code>input</code> listener that we
write a sequence of numbers concatenated by <code>\n</code> (one number per
line). In order to achieve this, we use the <code>writeAll</code> method. By
default, the writing methods write on the <code>0</code> pipe. To change this
behavior, we will have to set the number of the pipe as the second argument of
the writing methods. Same for the reading methods but the default pipe is
<code>1</code>.</p>
<p>When the callable attached to a listener returns <code>false</code>, the
pipe that has fired this call will be <strong>closed</strong> just after. In
our case, the function attached to <code>input</code> returns
<code>false</code> just after having written the data, we no longer need this
pipe. It is important for <strong>performance</strong> reasons to close pipes
as soon as possible.</p>
<p>Finally, to <strong>execute</strong> the processus, we use the
<code>Hoa\Console\Processus::run</code> method with a null arity.</p>
<p>In our example, we write all data at once but we can send them as soon as
they are ready, which is more efficient because the processus does no wait a
big bucket of data and can compute them gradually. Let's modify our example to
write a data each time <code>STDIN</code> is ready:</p>
<pre><code class="language-php">$processus->on('input', function ($bucket) {
static $i = null;
static $j = 5;
if (null === $i) {
$i = mt_rand(0, 20);
}
$data = $bucket->getData();
echo 'INPUT (', $data['pipe'],')', "\n";
$source = $bucket->getSource();
$source->writeLine($i++);
usleep(50000);
if (0 >= $j--) {
return false;
}
return;
});</code></pre>
<p>We initialize two variables: <code class="language-php">$i</code> and
<code class="language-php">$j</code>, that hold the number to send and the
maximum number of data to send. We introduce a voluntary latency with
<code class="language-php">usleep(50000)</code> in order that
<code>STDOUT</code> to be ready, only to illustrate our example. In this case,
the output would be:</p>
<pre><code class="language-php">/** Could output:
* INPUT (0)
* OUTPUT (1) > h
* INPUT (0)
* OUTPUT (1) > i
* INPUT (0)
* OUTPUT (1) > j
* INPUT (0)
* OUTPUT (1) > k
* INPUT (0)
* OUTPUT (1) > l
* INPUT (0)
* OUTPUT (1) > m
*/</code></pre>
<p>The processus is waiting an input and read data when they are received.
Once we have sent all the data, we close the pipe.</p>
<p>The processus will close itself. We have the
<code>Hoa\Console\Processus::getExitCode</code> method to know the exit
<strong>code</strong> of the processus. Attention, a <code>0</code> code
represents a success. Because it is a common error, there is the
<code>Hoa\Console\Processus::isSuccessful</code> method to know if the
processus has been executed with success or not.</p>
<h3 id="Detect_the_type_of_pipes" for="main-toc">Detect the type of pipes</h3>
<p>Sometimes, it is useful to know the <strong>type</strong> of pipes, it
means if it is a <strong>direct</strong> (regular) use, a
<strong>pipe</strong> or a <strong>redirection</strong>. We will make use of
the <code>Hoa\Console\Console</code> class and its <code>isDirect</code>,
<code>isPipe</code> and <code>isRedirection</code> static methods to get these
informations.</p>
<p>Let's take an example to understand. Let's write the <code>Type.php</code>
file that will study the type of <code>STDOUT</code>:</p>
<pre><code class="language-php">echo 'is direct: ';
var_dump(Hoa\Console\Console::isDirect(STDOUT));
echo 'is pipe: ';
var_dump(Hoa\Console\Console::isPipe(STDOUT));
echo 'is redirection: ';
var_dump(Hoa\Console\Console::isRedirection(STDOUT));</code></pre>
<p>And now, let's execute this file to see the result:</p>
<pre><code class="language-shell">$ php Type.php
is direct: bool(true)
is pipe: bool(false)
is redirection: bool(false)
$ php Type.php | xargs -I@ echo @
is direct: bool(false)
is pipe: bool(true)
is redirection: bool(false)
$ php Type.php > /tmp/foo; cat /tmp/foo
is direct: bool(false)
is pipe: bool(false)
is redirection: bool(true)</code></pre>
<p>In the first case, <code>STDOUT</code> is <strong>direct</strong> (for
<code>STDOUT</code>, it means that it is <strong>linked</strong> to the
screen, for <code>STDIN</code>, it will be linked to the keyboard etc.). In
the second case, <code>STDOUT</code> is a <strong>pipe</strong>, it means that
it is <strong>attached</strong> to the <code>STDIN</code> of the command
written after the <code>|</code> symbol. In the last case, <code>STDOUT</code>
is a <strong>redirection</strong>, it means that it is
<strong>redirected</strong> in the <code>/tmp/foo</code> file (that we print
just after). The operation can be done on <code>STDIN</code>,
<code>STDERR</code> or any other resource.</p>
<p>Knowing the type of pipes can allow different behaviors according to the
<strong>context</strong>. For example,
<code>Hoa\Console\Readline\Readline</code> reads on <code>STDIN</code>. If its
type is a pipe or a redirection, the advanced edition mode of the line will be
disabled and it will return <code>false</code> when there will be no more
things to read. Another example, the commands verbosity of the
<code>hoa</code> script uses the type of <code>STDOUT</code> as the default
value: direct to be verbose, else not verbose. Try the following examples to
see the difference:</p>
<pre><code class="language-shell">$ hoa --no-verbose
$ hoa | xargs -I@ echo @</code></pre>
<p>Examples are numerous but be careful to use this feature with precautions.
We have to adapt the behaviors but keep them <strong>consistent</strong>.</p>
<h3 id="Execution_conditions" for="main-toc">Execution conditions</h3>
<p>The processus is executed in a particular <strong>directory</strong> and a
particular <strong>environment</strong>. This directory is called the current
working directory, often abbreviated <abbr lang="en">cwd</abbr>. It defines
the directory where the processus will be executed. We can find it in PHP with
<a href="http://php.net/getcwd">the <code>getcwd</code> function</a>. The
environment is defined by an array, which can be retrieved for example by
executing <code>/usr/bin/env</code>. For example, the <code>PATH</code> is
defined in this environment. These data are given as fourth and fifth
arguments of the <code>Hoa\Console\Processus</code> constructor. Thus:</p>
<pre><code class="language-php">$processus = new Hoa\Console\Processus(
'php',
null, /* no option */
null, /* use default pipes */
'/tmp',
[
'FOO' => 'bar',
'BAZ' => 'qux',
'PATH' => '/usr/bin:/bin'
]
);
$processus->on('input', function (Hoa\Event\Bucket $bucket) {
$bucket->getSource()->writeAll(
'&lt;?php' . "\n" .
'var_dump(getcwd());' . "\n" .
'print_r($_ENV);'
);
return false;
});
$processus->on('output', function (Hoa\Event\Bucket $bucket) {
$data = $bucket->getData();
echo '> ', $data['line'], "\n";
return;
});
$processus->run();
/**
* Will output:
* > string(12) "/tmp"
* > Array
* > (
* > [FOO] => bar
* > [PATH] => /usr/bin:/bin
* > [PWD] => /tmp
* > [BAZ] => qux
* > [_] => /usr/bin/php
* >
* > )
*/</code></pre>
<p>If the current working directory is not defined, we will use the one of the
program. If the environment is not defined, the processus will use the one of
its parent.</p>
<p>We are also able to set a <strong>maximum time</strong> in seconds in order
to get a <strong>response</strong> from the processus (defined to 30 seconds
by default). This is the last argument of the constructor. We can use the
<code>Hoa\Console\Processus::setTimeout</code> method. To know when this time
is reached, we must use the <code>timeout</code> listener. No action will be
done automatically. We can for example terminate the processus thanks to the
<code>Hoa\Console\Processus::terminate</code> method. Thus:</p>
<pre><code class="language-php">$processus = new Hoa\Console\Processus('php');
// 3 seconds is enough…
$processus->setTimeout(3);
// Sleep 10 seconds.
$processus->on('input', function (Hoa\Event\Bucket $bucket) {
$bucket->getSource()->writeAll('&lt;?php sleep(10);');
return false;
});
// Terminate the processus on timeout.
$processus->on('timeout', function (Hoa\Event\Bucket $bucket) {
echo 'TIMEOUT, terminate', "\n";
$bucket->getSource()->terminate();
return;
});
$processus->run();
/**
* Will output (after 3 secondes):
* TIMEOUT, terminate
*/</code></pre>
<p>No action is done automatically because they can be numerous. Maybe we can
unblock the processus, or close it to open other ones, emit some logs etc.</p>
<p>About the <code>terminate</code> method, it can take several different
values, defined by the constants of <code>Hoa\Console\Processus</code>:
<code>SIGHUP</code>, <code>SIGINT</code>, <code>SIGQUIT</code>,
<code>SIGABRT</code>, <code>SIGKILL</code>, <code>SIGALRM</code> and
<code>SIGTERM</code> (by default). Several <strong>signals</strong> can be
send to the processus in order to stop them. To get the detail, please, see
<a href="http://freebsd.org/cgi/man.cgi?query=signal"
title="signal(3), FreeBSD Library Functions Manual">the
<code>signal</code></a> page.</p>
<h3 id="Miscellaneous" for="main-toc">Miscellaneous</h3>
<p>The <code>getTitle</code> and <code>setTitle</code> static methods on the
<code>Hoa\Console\Processus</code> class respectively allow to get and set the
title of the processus. Thus:</p>
<pre><code class="language-php">Hoa\Console\Processus::setTitle('hoa #1');</code></pre>
<p>And in another terminal:</p>
<pre data-line="2"><code class="language-shell">$ ps | grep hoa
69578 ttys006 0:00.01 hoa #1
70874 ttys008 0:00.00 grep hoa</code></pre>
<p>These methods are really useful when we manipulate a lot of processus and
we would like to identify them efficiently (for example with tools like
<code>top</code> or <code>ps</code>). Note that they are working only if you
have at least PHP5.5.</p>
<p>Another interesting static method is
<code>Hoa\Console\Processus::locate</code> that allows to determine the path
to a given program. For example:</p>
<pre><code class="language-php">var_dump(Hoa\Console\Processus::locate('php'));
/**
* Could output:
* string(12) "/usr/bin/php"
*/</code></pre>
<p>In the case where the program is not found, <code>null</code> will be
returned. This method is based on the <code>PATH</code> of your system.</p>
<h3 id="Interactive_processus_and_pseudo-terminals" for="main-toc">Interactive
processus and pseudo-terminals</h3>
<p>This section is a little bit technical but it explains a
<strong>problem</strong> that can be met with some processus called
<strong>interactive</strong>.</p>
<p>The <code>Hoa\Console\Processus</code> class allows to automate the
interaction with processus very easily. However, it is not always possible to
create such an automation because of the behavior of the processus. We will
illustrate the problem by writing the <code>Interactive.php</code> file:</p>
<pre><code class="language-php">&lt;?php
echo 'Login: ';
if (false === $login = fgets(STDIN)) {
fwrite(STDERR, 'Hmm, no login.' . "\n");
exit(1);
}
echo 'Password: ';
if (false === $password = fgets(STDIN)) {
fwrite(STDERR, 'Hmm, no password.' . "\n");
exit(2);
}
echo 'Result:', "\n\t", $login, "\t", $password;</code></pre>
<p>Let's execute this processus to see what it does:</p>
<pre><code class="language-shell">$ php Interactive.php
Login: myLogin
Password: myPassword
Result:
myLogin
myPassword</code></pre>
<p>And now, let's automate the execution of this processus:</p>
<pre><code class="language-shell">$ echo 'myLogin\nmyPassword' > data
$ php Interactive.php &lt; data
Login: Password: Result:
myLogin
myPassword</code></pre>
<p>Excellent. We could easily get the same result with
<code>Hoa\Console\Processus</code>. Now, if our processus want to ensure that
<code>STDIN</code> is empty between two inputs, it can add:</p>
<pre data-line-offset="7" data-line="10"><code class="language-php">}
fseek(STDIN, 0, SEEK_END);
echo 'Password: ';</code></pre>
<p>And then in this case, if we try to automate the execution:</p>
<pre><code class="language-shell">$ php Interactive.php &lt; data
Login: Password: Hmm, no password.</code></pre>
<p>This is absolutely a normal behavior, but
<code>Hoa\Console\Processus</code> can do nothing to solve this issue.</p>
<p>The solution would be to use a
<a href="https://en.wikipedia.org/wiki/Pseudo_terminal">pseudo-terminal</a> by
using the PTY functions (please, see
<a href="http://kernel.org/doc/man-pages/online/pages/man7/pty.7.html"
title="pty(7), Linux Programmer's Manual">in Linux</a> or
<a href="http://freebsd.org/cgi/man.cgi?query=pty"
title="pty(3), FreeBSD Library Functions Manual" >in FreeBSD</a>).
Unfortunately these functions are not available in PHP for technical reasons.
There is no possible solutions in pure PHP, but it is still conceivable to use
an <strong>external</strong> program, written in C for example.</p>
<h2 id="Conclusion" for="main-toc">Conclusion</h2>
<p>The <code>Hoa\Console</code> library provides
<strong>comprehensive</strong> tools to write programs tailored to a
<strong>textual</strong> interface, whether it be for interaction with the
window or the cursor, the interaction with the user thanks to a very
customizable readline (with autocompletion and shortcuts), the options reading
for programs themselves, the construction of elaborated programs, or even the
execution, interaction and communication with processus.</p>
</yield>
</overlay>
<overlay xmlns="http://hoa-project.net/xyl/xylophone">
<yield id="chapter">
<p>The terminal is a very <strong>powerful interface</strong> that is based on
multiple concepts. <code>Hoa\Console</code> allows to write
<strong>tools</strong> that are adapted to this kind of environment.</p>
<h2 id="Table_of_contents">Table of contents</h2>
<tableofcontents id="main-toc" />
<h2 id="Introduction" for="main-toc">Introduction</h2>
<p>Nowadays, we have two kinds of interface: <strong>textual</strong> and
<strong>graphical</strong>. The textual interface exist since the origin of
computers, then called terminal. This interface, despite its “raw” aspect, is
functionally very <strong>powerful</strong> thanks to several concepts such as
the readline or pipes. Today, it is even more used because it is often faster
than a graphical interface when executing <strong>complex</strong> tasks. It
can also easily be used through networks or on a machine with low
performances. In short, this interface is still
<strong>indispensable</strong>.</p>
<p>From the user point of view, there is three levels to consider:</p>
<ul>
<li>the <strong>interface</strong>: print and edit text, manipulate the
window, the cursor etc.,</li>
<li>the <strong>program</strong>: interact with the user with a maximum of
comfort, use the readline as much better as possible, build programs adapted
to this kind of interface,</li>
<li>the <strong>interaction</strong> with other programs: automatically
interact and communicate with other programs.</li>
</ul>
<p>The <code>Hoa\Console</code> library provides tools to answer to these
three levels. Thus, it is based on standards, such as
<a href="http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-048.pdf">ECMA-48</a>
that specifies the communication with the system through sequences of ASCII
characters and control codes (also called escaping sequences), all of that, in
order to manipulate the window, the cursor or other devices of the machine.
Other features are also standard such as the way we read options from a
program, very <strong>inspired</strong> of systems like
<a href="http://linux.org/">Linux</a>, <a href="http://freebsd.org/">FreeBSD</a>
or <a href="https://en.wikipedia.org/wiki/UNIX_System_V">System V</a>. By the
way, if you are familiar with several C libraries, you will not be lost. And
<em>a contrario</em>, if you learn to use <code>Hoa\Console</code>, you will
not be lost when using low-level languages such as C.</p>
<p>Before starting, we would like to add a little note
<strong>only</strong> about the window and cursor support. Today, we have the
choice among <strong>many</strong> terminals per system and some of them are
more comprehensive than others. For example,
<a href="https://windows.microsoft.com/">Windows</a> and its default terminal,
<a href="http://en.wikipedia.org/wiki/MS-DOS">MS-DOS</a>, does not respect any
standard. In this case, forget the ECMA-48 standard and take a look at
<a href="http://msdn.microsoft.com/library/ms682087.aspx"
title="Console Reference">the <code>Wincon</code> library</a>. It is often
recommended to use a <strong>virtual</strong> Unix machine or a terminal
<strong>emulator</strong>, such as
<a href="http://ttssh2.sourceforge.jp/">TeraTerm</a>, very comprehensive. Even
on systems that are closed to the BSD family, the embeded terminals do not
support all the standards. This is the case of Mac OS X where we recommend to
use <a href="http://iterm2.com">iTerm2</a> instead of Terminal. Finally, on
other systems of the Linux or BSD family, we recommend
<a href="http://software.schmorp.de/pkg/rxvt-unicode.html">urxvt</a>. For
other features, such as the readline, the options reading, the processus etc.,
<code>Hoa\Console</code> is perfectly <strong>compatible</strong> and
suitable.</p>
<h2 id="Window" for="main-toc">Window</h2>
<p>The window of a terminal must be understood as a <strong>canvas</strong> of
<strong>columns</strong> and <strong>lines</strong>. The
<code>Hoa\Console\Window</code> allows to manipulate the
<strong>window</strong> of a terminal and its <strong>content</strong> through
static methods.</p>
<h3 id="Size_and_position" for="main-toc">Size and position</h3>
<p>The first elementary operations are about the <strong>size</strong> and the
<strong>position</strong> of the window, thanks to the <code>setSize</code>,
<code>getSize</code>, <code>moveTo</code> and <code>getPosition</code>
methods. The size is defined with the <em>column</em> × <em>line</em> unit and
the position is defined in pixels. Thus:</p>
<pre><code class="language-php">Hoa\Console\Window::setSize(80, 50);
print_r(Hoa\Console\Window::getSize());
print_r(Hoa\Console\Window::getPosition());
/**
* Will output:
* Array
* (
* [x] => 80
* [y] => 50
* )
* Array
* (
* [x] => 104
* [y] => 175
* )
*/</code></pre>
<p>We will notice that the window is resized <strong>itself</strong>. Nor the
size or the position of the window is stored in memory, they are computed at
each call of the <code>getSize</code> and <code>getPosition</code> method.
Attention, the <em>y</em> axe of the window position is computed from the
<strong>bottom</strong> of the screen and not from the top of the screen as we
might expect!</p>
<p>It is also possible to listen the
<code>hoa://Event/Console/Window:resize</code> <strong>event</strong> that is
fired each time the window is resized: either manually or with the
<code>setSize</code> method. We need two things in order to see this event
working:</p>
<ol>
<li><a href="http://php.net/pcntl">the <code>pcntl</code> extension</a> must
be activated,</li>
<li>we have to use <a href="http://php.net/declare">the <code>declare</code>
structure</a> to enable the <a href="http://php.net/pcntl_signal">the
<code>pcntl_signal</code> function</a>.</li>
</ol>
<p>To put the program in a passive waiting state, we will use
<a href="http://php.net/stream_select">the <code>stream_select</code>
function</a>, this is a <strong>detail</strong>, only present to test our
code, else the program will terminate directly. Thus:</p>
<pre><code class="language-php">Consistency\Autoloader::load('Hoa\Console\Window'); // make sure it is loaded.
declare(ticks = 1);
Hoa\Event\Event::getEvent('hoa://Event/Console/Window:resize')
->attach(function (Hoa\Event\Bucket $bucket) {
$data = $bucket->getData();
$size = $data['size'];
echo 'New size (', $size['x'], ', ', $size['y'], ')', "\n";
});
// Passive loop.
while (true) {
$r = [STDIN];
@stream_select($r, $w, $e, 3600);
}</code></pre>
<p>When we modify the size of the window, we will see for example:
<code>New size (45, 67)</code>, and this, for each resize. This event is
interesting if we would like to <strong>re-layout</strong> our view.</p>
<p>Finally, we can minimize or restore the window thanks to the
<code>Hoa\Console\Window::minimize</code> and
<code>Hoa\Console\Window::restore</code> static methods. Moreover, we can put
the window in the background (behind all other windows) thanks to the
<code>Hoa\Console\Window::lower</code> static method, just like we can place
it in the foreground with <code>Hoa\Console\Window::raise</code>. For
example:</p>
<pre><code class="language-php">Hoa\Console\Window::minimize();
sleep(2);
Hoa\Console\Window::restore();
sleep(2);
Hoa\Console\Window::lower();
sleep(2);
Hoa\Console\Window::raise();
echo 'Back!', "\n";</code></pre>
<h3 id="Title_and_label" for="main-toc">Title and label</h3>
<p>The <strong>title</strong> of a window is the text displayed in the top
<strong>bar</strong> in which the controls of the window are often placed,
such as the maximization, the minimization etc. The <strong>label</strong> is
the name associated to the current <strong>processus</strong>. We have the
<code>setTitle</code>, <code>getTitle</code> and <code>getLabel</code>
methods, it is not possible to modify the label. To define the title of the
processus (what we see with the <code>top</code> or <code>ps</code> command
for example), we must take a look at
<code>Hoa\Console\Processus::setTitle</code> and also at
<code>Hoa\Console\Processus::getTitle</code> to get it. Thus:</p>
<pre><code class="language-php">Hoa\Console\Window::setTitle('Foobar');
var_dump(Hoa\Console\Window::getTitle());
var_dump(Hoa\Console\Window::getLabel());
/**
* Will output:
* string(6) "Foobar"
* string(3) "php"
*/</code></pre>
<p>Once again, the title and the label are not stored in memory, they are
computed at each call of the methods.</p>
<h3 id="Interact_with_the_content" for="main-toc">Interact with the
content</h3>
<p><code>Hoa\Console\Window</code> also allows to control the
<strong>content</strong> of the window, or at least the viewport, it means the
<strong>visible</strong> content of the window. Only one method is available:
<code>scroll</code>, that allows to <strong>move</strong> the content to the
top or the bottom. The arguments of this method are very simple:
<code>up</code> or <code>↑</code> to move up of one line and <code>down</code>
or <code>↓</code> to move down of one line. We are able to concatenate those
directions by a space or to repeat one direction:</p>
<pre><code class="language-php">Hoa\Console\Window::scroll('↑', 10);</code></pre>
<p>In reality, this method will move the content in order to get <em>x</em>
new lines respectively bellow or above the cursor. Attention, the cursor
<strong>does not move</strong>!</p>
<p>Even if it is most of the time useless, it is possible to
<strong>refresh</strong> the window, it means to redo a full render. We can
use the <code>refresh</code> method, still on
<code>Hoa\Console\Window</code>.</p>
<p>Finally, it is possible to put a text inside the <strong>clipboard</strong>
of the user thanks to the <code>copy</code> method:</p>
<pre><code class="language-php">Hoa\Console\Window::copy('Foobar');</code></pre>
<p>Then, if the user pastes what is in the clipboard, she will see
<code>Foobar</code>.</p>
<h2 id="Cursor" for="main-toc">Cursor</h2>
<p>Inside a window, we have a cursor that can be seen as a tip of pen. The
<code>Hoa\Console\Cursor</code> class allows to manipulate the
<strong>cursor</strong> of the terminal through static methods.</p>
<h3 id="Moving" for="main-toc">Moving</h3>
<p>We will start to <strong>move</strong> the cursor. It can move everywhere
inside the viewport, it means the <strong>visible</strong> content of the
terminal window, but we will write a piece of text and move around at first.
The <code>move</code> method on <code>Hoa\Console\Cursor</code> allows to move
the cursor in several <strong>directions</strong>. First of all,
relatively:</p>
<ul>
<li><code>u[p]</code> or <code>↑</code>, to move the cursor one line
above,</li>
<li><code>r[ight]</code> or <code>→</code>, to move the cursor on the next
column,</li>
<li><code>d[own]</code> or <code>↓</code>, to move the cursor one line
bellow,</li>
<li><code>l[eft]</code> or <code>←</code>, to move the cursor on the
previous column.</li>
</ul>
<p>We also have <strong>semi-absolute</strong> moving:</p>
<ul>
<li><code>U[P]</code>, to move the cursor on the first line of the
viewport,</li>
<li><code>R[IGHT]</code>, to move the cursor on the last column of the
viewport,</li>
<li><code>D[OWN]</code>, to move the cursor on the last line of the
viewport,</li>
<li><code>L[EFT]</code>, to move the cursor on the first column of the
viewport.</li>
</ul>
<p>We are able to concatenate those directions by a space or to repeat one
direction.</p>
<pre><code class="language-php">echo
'abcdef', "\n",
'ghijkl', "\n",
'mnopqr', "\n",
'stuvwx';
sleep(1);
Hoa\Console\Cursor::move('↑');
sleep(1);
Hoa\Console\Cursor::move('↑ ←');
sleep(1);
Hoa\Console\Cursor::move('←', 3);
sleep(1);
Hoa\Console\Cursor::move('DOWN');
sleep(1);
Hoa\Console\Cursor::move('→', 4);</code></pre>
<p>During the execution, we will see the cursor moving <strong>by
itself</strong> from a letter to another one each second.</p>
<p>To actually move the cursor in an <strong>absolute</strong> way, we will
use the <code>moveTo</code> method that takes <em>column</em> × <em>line</em>
coordinates (starting from 1 and not 0) as argument. Similarly, there is the
<code>getPosition</code> method that returns the <strong>position</strong> of
the cursor. Thus, if we would like to move the cursor from the column 12 and
line 7, and then print its coordinates, we will write:</p>
<pre><code class="language-php">Hoa\Console\Cursor::moveTo(12, 7);
print_r(Hoa\Console\Cursor::getPosition());
/**
* Will output:
* Array(
* [x] => 12
* [y] => 7
* )
*/</code></pre>
<p>Finally, we often would like to move the cursor <strong>temporary</strong>
for some operations. In this case, it is useless to get its current position,
to move it and then re-position it; we can benefit from the <code>save</code>
and <code>restore</code> methods. As their names suggest, they respectively
<strong>save</strong> the position of the cursor and then
<strong>restore</strong> the cursor as the previously saved position. These
functions do not manipulate a <strong>stack</strong>, it is impossible to save
more than one position at a time (the new record will <strong>erase</strong>
the previous one). Thus, we will write a text, save the position of the
cursor, go back to rewrite, and then go back to our previous position:</p>
<pre><code class="language-php">echo 'hello world';
// Save cursor position.
Hoa\Console\Cursor::save();
sleep(1);
// Go to the begining of the line.
Hoa\Console\Cursor::move('LEFT');
sleep(1);
// Replace “h” by “H”.
echo 'H';
sleep(1);
// Go to “w”.
Hoa\Console\Cursor::move('→', 5);
sleep(1);
// Replace “w” by “W”.
echo 'W';
sleep(1);
// Back to the saved position.
Hoa\Console\Cursor::restore();
sleep(1);
echo '!';</code></pre>
<p>The final result will be <code>Hello World!</code>. We can notice that each
time a character is written, the cursor <strong>moves</strong>.</p>
<h3 id="Content" for="main-toc">Content</h3>
<p>Now we know how to move, we will see how to <strong>clean</strong> some
lines and/or columns. We will use the <code>clean</code> method that takes the
following symbols (concatenated by a space) as argument:</p>
<ul>
<li><code>a[ll]</code> or <code>↕</code>, to clean all the screen and move
the cursor in the top-left corner of the viewport,</li>
<li><code>u[p]</code> or <code>↑</code>, to clean all the lines above the
cursor,</li>
<li><code>r[ight]</code> or <code>→</code>, to clean the rest of the line
from the cursor,</li>
<li><code>d[own]</code> or <code>↓</code>, to clean all the lines bellow the
cursor,</li>
<li><code>l[eft]</code> or <code>←</code>, to clean the line from its
beginning to the cursor,</li>
<li><code>line</code> or <code>↔</code>, to clean all the line and move the
cursor to its beginning.</li>
</ul>
<p>Thus, to clean <strong>a line</strong>:</p>
<pre><code class="language-php">Hoa\Console\Cursor::clear('↔');</code></pre>
<p>The cursor can act as a brush and thus write in different
<strong>colors</strong> or different <strong>styles</strong> thanks to the
<code>colorize</code> method (we can mix everything by separating each
“command” by spaces). We start by enumerating all the styles:</p>
<ul>
<li><code>n[ormal]</code>, to cancel all applied styles,</li>
<li><code>b[old]</code>, to write in bold,</li>
<li><code>u[nderlined]</code>, to get an underlined text,</li>
<li><code>bl[ink]</code>, to get a blinking text,</li>
<li><code>i[nverse]</code>, to reverse colors of the foreground and the
background,</li>
<li><code>!b[old]</code>, to cancel the bold,</li>
<li><code>!u[nderlined]</code>, to cancel the underline,</li>
<li><code>!bl[ink]</code>, to cancel the blink,</li>
<li><code>!i[nverse]</code>, to no longer reverse colors of the foreground
and the background.</li>
</ul>
<p>Those styles are very classical. Now, let's see the colors. Before all, we
have to say if we apply a color on the <strong>foreground</strong> of the
text (the text itself) or on the <strong>background</strong>. Then, we will
respectively use the <code>f[ore]g[round](<em>color</em>)</code> or
<code>b[ack]g[round](<em>color</em>)</code> syntax. The value of
<code><em>color</em></code> can be:</p>
<ul>
<li><code>default</code>, to get the default color of the ground,</li>
<li><code>black</code>, <code>red</code>, <code>green</code>,
<code>yellow</code>, <code>blue</code>, <code>magenta</code>,
<code>cyan</code> or <code>white</code>,</li>
<li>a number between <code>0</code> and <code>256</code>, representing the
number of the color in the 256 colors palette,</li>
<li><code>#<em>rrggbb</em></code> where <code><em>rrggbb</em></code> is the
hexadecimal number representing the number of the color in the
2<sup>64</sup> colors palette.</li>
</ul>
<p>Terminals manipulate <strong>one</strong> of two palettes: 8 colors or 256
colors. Each color is <strong>indexed</strong> starting from 0. The names of
colors are <strong>transformed</strong> into their respective index. When a
color is given in hexadecimal, the <strong>closest</strong> color in the 256
colors palette is used.</p>
<p>Thus, if we would like to write <code>Hello</code> in yellow on a red-like
background (<code>#932e2e</code>) and underlined, then <code> world</code> but
not underlined:</p>
<pre><code class="language-php">Hoa\Console\Cursor::colorize('fg(yellow) bg(#932e2e) underlined');
echo 'Hello';
Hoa\Console\Cursor::colorize('!underlined');
echo ' world';</code></pre>
<p>Finally, it is possible to modify the palette of colors thanks to the
<code>changeColor</code> method, but use it with <strong>caution</strong>,
this can disturb the user. This method takes as first argument the index of
the color and as second argument its hexadecimal value. For example,
<code>fg(yellow)</code> matches the <code>33</code> index, and we would like
to see it blue:</p>
<pre><code class="language-php">Hoa\Console\Cursor::changeColor(33, 0xf00);</code></pre>
<p>However, the 256 colors palette is <strong>wide</strong> enough to not need
to redefine colors.</p>
<h3 id="Style" for="main-toc">Style</h3>
<p>The cursor is not necessary visible. During some operations, we can
<strong>hide</strong> it, make some moves and then make it
<strong>visible</strong> again. The <code>hide</code> and <code>show</code>
methods, still on <code>Hoa\Console\Cursor</code>, are here for that:</p>
<pre><code class="language-php">echo 'Visible', "\n";
sleep(5);
echo 'Invisible', "\n";
Hoa\Console\Cursor::hide();
sleep(5);
echo 'Visible', "\n";
Hoa\Console\Cursor::show();
sleep(5);</code></pre>
<p>There is three <strong>types</strong> of cursors, that we will choose with
the <code>setStyle</code> method:</p>
<ul>
<li><code>b[lock]</code> or <code>▋</code>, to get a cursor represented by a
block,</li>
<li><code>u[nderline]</code> or <code>_</code>, to get a cursor represented
by an underscore,</li>
<li><code>v[ertical]</code> or <code>|</code>, to get a cursor represented
by a pipe.</li>
</ul>
<p>This method takes as a second argument a boolean which indicates if the
cursor must <strong>blink</strong> (default value) or not. Thus, we will try
all the styles:</p>
<pre><code class="language-php">echo 'Block/steady: ';
Hoa\Console\Cursor::setStyle('▋', false);
sleep(3);
echo "\n", 'Vertical/blink: ';
Hoa\Console\Cursor::setStyle('|', true);
sleep(3);
// etc.</code></pre>
<p>The cursor often indicates <strong>area</strong> or
<strong>interactive</strong> elements, just like the pointer of a mouse.</p>
<h3 id="Sound" for="main-toc">Sound</h3>
<p>The cursor is also able to emit a little “bip”, most of the time to
<strong>bring</strong> the attention of the user. We will use the eponym
<code>bip</code> method:</p>
<pre><code class="language-php">Hoa\Console\Cursor::bip();</code></pre>
<p>There is only one available <strong>tonality</strong>.</p>
<h2 id="Readline" for="main-toc">Readline</h2>
<p>A way to <strong>interact</strong> with users is to read the
<code>STDIN</code> stream, namely the input stream. This
<strong>reading</strong> is by default very basic: unable to erase, unable to
use arrows, unable to use shortcuts etc. That's why there is the readline,
that is still a reading on the <code>STDIN</code> stream, but more
<strong>sophisticated</strong>. The <code>Hoa\Console\Readline\Readline</code>
library provides several features that we will describe.</p>
<h3 id="Basic_usage" for="main-toc">Basic usage</h3>
<p>To <strong>read a line</strong> (it means an input from the user), we will
instanciate the <code>Hoa\Console\Readline\Readline</code> class and call the
<code>readLine</code> method. Each call of this method will wait that the user
<strong>inputs</strong> a data and then hits <kbd title="Enter">↵</kbd>. At
this moment, the method will returns the input from the user (or
<code>false</code> if there is nothing to read). This method also takes a
<strong>prefix</strong> as argument, it means a data to print before the line.
Sometimes, the term <em>prompt</em> could be used in the literature, both
notions are identical.</p>
<p>Thus, we will write a program that will read the inputs from the user and
make an echo. The program will finish if the user inputs
<code>quit</code>:</p>
<pre><code class="language-php">$rl = new Hoa\Console\Readline\Readline();
do {
$line = $rl->readLine('> ');
echo '&lt; ', $line, "\n\n";
} while (false !== $line &amp;&amp; 'quit' !== $line);</code></pre>
<p>Now, let's detail the features offered by
<code>Hoa\Console\Readline\Readline</code>.</p>
<p>We are able to <strong>move</strong> (understand, move the cursor) in the
line with the help of the <kbd>←</kbd> and <kbd>→</kbd> keys. We can
<strong>erase</strong> a character back at any moment with the
<kbd title="Backspace">⌫</kbd> key or all the characters until the beginning
of the word with <kbd>Ctrl</kbd> + <kbd>W</kbd> (where <kbd>W</kbd> stands for
word). We are also able to move the cursor by using <strong>shortcuts</strong>
that a shared by many softwares:</p>
<ul>
<li><kbd>Ctrl</kbd> + <kbd>A</kbd>, to move at the beginning of the
line,</li>
<li><kbd>Ctrl</kbd> + <kbd>E</kbd>, to move at the end of the line,</li>
<li><kbd>Ctrl</kbd> + <kbd>B</kbd>, to move at the beginning of the current
word (<kbd>B</kbd> stands for backward),</li>
<li><kbd>Ctrl</kbd> + <kbd>F</kbd>, to move at the end of the current word
(<kbd>F</kbd> stands for forward).</li>
</ul>
<p>We also have access to the <strong>history</strong> when we hit the
<kbd>↑</kbd> and <kbd>↓</kbd> keys, respectively to search back and forth in
the history. The <kbd title="Tabulation">⇥</kbd> key fires the
<strong>auto-completion</strong> if set. And finally, the
<kbd title="Enter">↵</kbd> key returns the input.</p>
<p>There is also the <code>Hoa\Console\Readline\Password</code> class that
allows to have a readline with the same features but the characters
<strong>are not printed</strong> to the screen, very useful to read a
<strong>password</strong>:</p>
<pre><code class="language-php">$rl = new Hoa\Console\Readline\Password();
$pwd = $rl->readLine('Password: ');
echo 'Your password is: ', $pwd, "\n";</code></pre>
<h3 id="Shortcuts" for="main-toc">Shortcuts</h3>
<p>To understand how to create a shortcut, we have to understand a little bit
how the <code>Hoa\Console\Readline\Readline</code> class works
<strong>internally</strong>, and this is very simple. Each time we hit one or
many keys, a <strong>string</strong> representing this
<strong>combination</strong> is received by our readline. It looks for an
associated action to this string: if one exists, then this one will be
executed, else the readline will use a default action consisting to print the
string verbatim. Each action returns the <strong>state</strong> of the
readline (which are constants on
<code>Hoa\Console\Readline\Readline</code>):</p>
<ul>
<li><code>STATE_CONTINUE</code>, to continue the reading,</li>
<li><code>STATE_BREAK</code>, to stop the reading,</li>
<li><code>STATE_NO_ECHO</code>, to not print the reading.</li>
</ul>
<p>Thus, if an action returns <code class="language-php">STATE_CONTINUE |
STATE_NO_ECHO</code>, the reading will continue but the string that has been
received will not be printed. Another example, the action associated to the
<kbd title="Enter">↵</kbd> key returns the <code>STATE_BREAK</code> state.</p>
<p>To <strong>add</strong> actions, we use the <code>addMapping</code> method.
This latter eases the add thanks to a dedicated syntax:</p>
<ul>
<li><code>\e[<em>…</em></code>, for sequences starting by the <kbd>Esc</kbd>
character,</li>
<li><code>\C-<em>…</em></code>, for sequences starting by the
<kbd>Ctrl</kbd> character,</li>
<li><code><em>x</em></code>, any character.</li>
</ul>
<p>For example, if we would like to print <code>z</code> instead of
<code>a</code>, we will write:</p>
<pre><code class="language-php">$rl->addMapping('a', 'z');</code></pre>
<p>More sophisticated this time, we can use a callable as the second argument
of the <code>addMapping</code> method. This callable will receive the instance
of <code>Hoa\Console\Readline\Readline</code> as the only argument. Several
methods will help to <strong>manipulate</strong> the reading (to manage the
history, the line etc.). For example, each time we will hit
<kbd>Ctrl</kbd> + <kbd>R</kbd>, we will reverse the case of the line:</p>
<pre><code class="language-php">$rl = new Hoa\Console\Readline\Readline();
// Add mapping.
$rl->addMapping('\C-R', function (Hoa\Console\Readline\Readline $self) {
// Clear the line.
Hoa\Console\Cursor::clear('↔');
echo $self->getPrefix();
// Get the line text.
$line = $self->getLine();
// New line.
$new = null;
// Loop over all characters.
for ($i = 0, $max = $self->getLineLength(); $i &lt; $max; ++$i) {
$char = mb_substr($line, $i, 1);
if ($char === $lower = mb_strtolower($char)) {
$new .= mb_strtoupper($char);
} else {
$new .= $lower;
}
}
// Set the new line.
$self->setLine($new);
// Set the buffer (and let the readline echoes or not).
$self->setBuffer($new);
// The readline will continue to read.
return $self::STATE_CONTINUE;
});
// Try!
var_dump($rl->readLine('> '));</code></pre>
<p>Do not hesitate to take a look at how previous listed shortcuts have been
implemented to get ideas.</p>
<h3 id="Auto-completion" for="main-toc">Auto-completion</h3>
<p>Another very useful tool when we are writing a readline is the
<strong>auto-completion</strong>. It is fired when hiting the
<kbd title="Tabulation">⇥</kbd> key if the auto-completer has been defined
with the <code>setAutocompleter</code> method.</p>
<p>All the auto-completers must implement the
<code>Hoa\Console\Readline\Autocompleter\Autocompleter</code> interface. Some
of them are already distributed to <strong>help</strong> us in our
development, such as <code>Hoa\Console\Readline\Autocompleter\Word</code> that
will auto-complete the input based on a <strong>list of words</strong>. For
example:</p>
<pre><code class="language-php">$rl = new Hoa\Console\Readline\Readline();
$rl->setAutocompleter(new Hoa\Console\Readline\Autocompleter\Word([
'hoa',
'console',
'readline',
'autocompleter',
'autocompletion',
'password',
'awesome'
]));
var_dump($rl->readLine('> '));</code></pre>
<p>Let's try to write something, then where we would like, we hit
<kbd title="Tabulation">⇥</kbd>. If the text at the left of the cursor starts
by <code>h</code>, then we will see <code>hoa</code> <strong>printed</strong>
in the line because the auto-completer has no choice (it returns a string). If
the auto-completer does not find any appropriated word,
<strong>nothing</strong> will happen (it will return <code>null</code>). And
finally, if it finds <strong>several words</strong> (it will return an array),
then a <strong>menu</strong> will appear. Let's try to simply auto-complete
<code>a</code>: the menu will propose <code>autocompleter</code>,
<code>autocompletion</code> and <code>awesome</code>. Either we continue to
hit and the menu will <strong>disappear</strong>, or we can
<strong>move</strong> the cursor inside the menu with the
<kbd title="Tabulation">⇥</kbd>, <kbd>↑</kbd>, <kbd>→</kbd>, <kbd>↓</kbd> and
<kbd>←</kbd> keys, then <kbd title="Enter">↵</kbd> to <strong>select</strong>
a word. This behavior is quite <strong>natural</strong>.</p>
<p>In addition to the auto-completer on words, we find the auto-completer on
<strong>paths</strong> with the
<code>Hoa\Console\Readline\Autocompleter\Path</code> class. Based on a root
and a file iterator, this auto-completer is able to auto-complete paths.
If the root is undefined, it will be set to the current working directory. At
each auto-completion, a new instance of the file iterator is created by a
factory. This latter receives the path to iterate as a unique argument. By
default, the factory is defined by the <code>getDefaultIteratorFactory</code>
static method on <code>Hoa\Console\Readline\Autocompleter\Path</code>. The
factory builds a file iterator of type
<a href="http://php.net/directoryiterator"><code>DirectoryIterator</code></a>.
Each value computed by the iterator must be an object of type
<a href="http://php.net/splfileinfo"><code>SplFileInfo</code></a>. Thus, to
auto-complete all the files and directories from the
<a href="@central_resource:path=Library/Console"><code>hoa://Library/Console</code></a>
root, we will write:</p>
<pre><code class="language-php">$rl->setAutocompleter(
new Hoa\Console\Readline\Autocompleter\Path(
resolve('hoa://Library/Console')
)
);</code></pre>
<p>Using a factory offers a lot of <strong>flexibility</strong> and allows us
to use any file iterator, such as <code>Hoa\File\Finder</code> (please, see
<a href="@hack:chapter=File">the <code>Hoa\File</code> library</a>). Thus, to
only auto-complete not hidden files and directories that have been modified
the last 6 months and sorted by their size, we will write:</p>
<pre><code class="language-php">$rl->setAutocompleter(
new Hoa\Console\Readline\Autocompleter\Path(
resolve('hoa://Library/Console'),
function ($path) {
$finder = new Hoa\File\Finder();
$finder->in($path)
->files()
->directories()
->maxDepth(1)
->name('#^(?!\.).#')
->modified('since 6 months')
->sortBySize();
return $finder;
}
)
);</code></pre>
<p>We can replace the local file iterator by another totally
<strong>different</strong> iterator: on files that are stored in another
machine, a third party service or even resources that are not files but have
path-like URI.</p>
<p>Finally, we can aggregate several auto-completers together thanks to the
<code>Hoa\Console\Readline\Autocompleter\Aggregate</code> class. The
declaration order of auto-completers is important: the first one that matches
a word to auto-complete will break the loop over all the auto-completers.
Thus, to auto-complete paths and words, we will write:</p>
<pre><code class="language-php">$rl->setAutocompleter(
new Hoa\Console\Readline\Autocompleter\Aggregate([
new Hoa\Console\Readline\Autocompleter\Path(),
new Hoa\Console\Readline\Autocompleter\Word($words)
])
);
</code></pre>
<p>The <code>getAutocompleters</code> method from
<code>Hoa\Console\Readline\Autocompleter\Aggregate</code> returns an
<a href="http://php.net/arrayobject"><code>ArrayObject</code></a> object for
more flexibility. We can consequently add or delete auto-completers after
having declared them in the constructor.</p>
<figure>
<img src="https://central.hoa-project.net/Resource/Library/Console/Documentation/Image/Readline_autocompleters.gif?format=raw" />
<figcaption>Example of an aggregation of the
<code>Hoa\Console\Readline\Autocompleter\Path</code> auto-completer with
<code>Hoa\Console\Readline\Autocompleter\Word</code>.</figcaption>
</figure>
<h2 id="Reading_options" for="main-toc">Reading options</h2>
<p>A big benefit of programs with a command line interface is their
<strong>flexibility</strong>. They are <strong>dedicated</strong> to one
(little) <strong>task</strong> and we are able to parameterize them thanks to
<strong>options</strong> that they exposed. The <strong>reading</strong> of
those options must be very simple and straightforward because this is a
repetitive and subtle task. The <code>Hoa\Console\Parser</code> and
<code>Hoa\Console\GetOption</code> classes act as a <strong>duo</strong> to
solve this problem.</p>
<h3 id="Analyzing_options" for="main-toc">Analyzing options</h3>
<p>We start with <code>Hoa\Console\Parser</code> that
<strong>analyzes</strong> options given to a program. Whatever the options we
would like to have, we just analyze them for now. Let's start by using the
<code>parse</code> method:</p>
<pre><code class="language-php">$parser = new Hoa\Console\Parser();
$parser->parse('-s --long=value input');
print_r($parser->getSwitches());
print_r($parser->getInputs());
/**
* Will output:
* Array
* (
* [s] => 1
* [long] => value
* )
* Array
* (
* [0] => input
* )
*/</code></pre>
<p>Let's see of what a command line is composed. We have two categories: the
<strong>options</strong> (switches) and the <strong>inputs</strong>. The
inputs are everything that is not an option. An option can have two forms:
<strong>short</strong> if it has only one character or <strong>long</strong>
if it has many.</p>
<p>Thus, <code>-s</code> is a short option and <code>--long</code> is a
long option. However, we have to consider the number of hyphen in front of the
option: with two hyphens, it will always be a long option, with one hyphen, it
depends. There is two schools that differenciate themselves with one
<strong>parameter</strong>: <em>long only</em>. Let's take an example:
<code>-abc</code> is considered as <code>-a -b -c</code> if the <em>long
only</em> parameter is set to <code>false</code>, else it will be equivalent
to a long option, such as <code>--abc</code>. Most of the time, this parameter
is set to <code>false</code> by default and <code>Hoa\Console\Parser</code>
has adopted the position of the majority. To modify this parameter, we have to
use the <code>setLongOnly</code> method, let's see though:</p>
<pre><code class="language-php">// long only is set to false.
$parser->parse('-abc');
print_r($parser->getSwitches());
$parser->setLongOnly(true);
// long only is set to true.
$parser->parse('-abc');
print_r($parser->getSwitches());
/**
* Will output:
* Array
* (
* [a] => 1
* [b] => 1
* [c] => 1
* )
* Array
* (
* [abc] => 1
* )
*/</code></pre>
<p>An option can be of two kinds: <strong>boolean</strong> or
<strong>valued</strong>. If no value is associated, it is considered as a
boolean. Thus, <code>-s</code> represents <code>true</code>, but <code>-s
-s</code> represents <code>false</code>, <code>-s -s -s</code> represents
<code>true</code> and so on. A boolean option behaves like a
<strong>switch</strong>. A valued option has an associated value by using
either a space or an equal sign (symbol <code>=</code>). Here is a
non-exhaustive list of potential syntaxes (we use a short option but it could
be a long one):</p>
<ul>
<li><code>-x=value</code>: <code>value</code>,</li>
<li><code>-x=va\ lue</code>: <code>va lue</code>,</li>
<li><code>-x="va lue"</code>: <code>va lue</code>,</li>
<li><code>-x="va l\"ue"</code>: <code>va l"ue</code>,</li>
<li><code>-x value</code>: <code>value</code>,</li>
<li><code>-x va\ lue</code>: <code>va lue</code>,</li>
<li><code>-x "value"</code>: <code>value</code>,</li>
<li><code>-x "va lue"</code>: <code>va lue</code>,</li>
<li><code>-x va\ l"ue</code>: <code>va l"ue</code>,</li>
<li><code>-x 'va "l"ue'</code>: <code>va "l"ue</code>,</li>
<li>etc.</li>
</ul>
<p>The simple (symbol <code>'</code>) and double (symbol <code>"</code>)
quotes are supported. But be aware that there is particular syntaxes which are
still not <strong>standards</strong>:</p>
<ul>
<li><code>-x=-value</code>: <code>-value</code>,</li>
<li><code>-x "-value"</code>: <code>-value</code>,</li>
<li><code>-x \-value</code>: <code>-value</code>,</li>
<li><code>-x -value</code>: is the equivalent of two booleans,
<code>-x</code> and <code>-value,</code></li>
<li><code>-x=-7</code>: <code>-7</code>,</li>
<li>etc.</li>
</ul>
<p><em>A l'instar</em> of boolean options that behave like switches, the
valued options <strong>rewrite</strong> their values if they are declared more
than once. Thus, <code>-a=b -a=c</code> represents <code>c</code>.</p>
<p>Finally, there is values that are considered as <strong>special</strong>.
We distinguish two of them:</p>
<ul>
<li><strong>lists</strong>, with the help of a comma as a separator:
<code>-x=a,b,c</code>,</li>
<li><strong>intervalles</strong>, with the help of the <code>:</code> symbol
(without spaces around it): <code>-x=1:7</code>.</li>
</ul>
<p>Without any specific manipulation, those values will not be considered as
special. We will have to use the
<code>Hoa\Console\Parser::parseSpecialValue</code> method as we will see
bellow.</p>
<h3 id="Read_options_and_inputs" for="main-toc">Read options and inputs</h3>
<p>We know how to analyze options but this is not enough to read them
correctly. We have to define a little <strong>semantics</strong>: what do
their expect, what is their nature etc. We will use the
<code>Hoa\Console\GetOption</code> class. An option is defined by:</p>
<ul>
<li>a <strong>long</strong> name,</li>
<li>a <strong>short</strong> name,</li>
<li>a <strong>type</strong>, given by one of the
<code>Hoa\Console\GetOption</code> constants among:
<ul>
<li><code>NO_ARGUMENT</code> if the option is a boolean one,</li>
<li><code>REQUIRED_ARGUMENT</code> if the option is a valued one,</li>
<li><code>OPTIONAL_ARGUMENT</code> if the option can receive a
value.</li>
</ul>
</li>
</ul>
<p>These three informations <strong>must</strong> be specified. They must be
given to the constructor of <code>Hoa\Console\GetOption</code> as first
argument. The second one is the option analyzer (the analysis must be
<strong>already</strong> done). Thus, we describe two options:
<code>extract</code> that is a boolean option and <code>directory</code> that
is a valued option:</p>
<pre><code class="language-php">$parser = new Hoa\Console\Parser();
$parser->parse('-x --directory=value inputA inputB inputC');
$options = new Hoa\Console\GetOption(
[
// long name type short name
// ↓ ↓ ↓
['extract', Hoa\Console\GetOption::NO_ARGUMENT, 'x'],
['directory', Hoa\Console\GetOption::REQUIRED_ARGUMENT, 'd']
],
$parser
);</code></pre>
<p>We are now ready to read our options! The options reader behaves like an
iterator, or like a <strong>pipette</strong> though, thanks to the
<code>getOption</code> method. This method returns the short name of the
option currently read and will assign the value of the option (a boolean or a
string) to its first argument passed by reference. When the pipette is empty,
the <code>getOption</code> method will return <code>false</code>. This
structure might seem original but it is widely <strong>spread</strong>, you
will not be lost when seeing it in other progams (examples
<a href="http://kernel.org/doc/man-pages/online/pages/man3/getopt.3.html#EXAMPLE"
title="getopt(3), Linux Programmer's Manual">in Linux</a>,
<a href="http://freebsd.org/cgi/man.cgi?query=getopt&sektion=3#EXAMPLES"
title="getopt(3), FreeBSD Library Functions Manual">in FreeBSD</a> or
<a href="http://developer.apple.com/library/Mac/#documentation/Darwin/Reference/ManPages/man3/getopt.3.html"
title="getopt(3), BSD Library Functions Manual">in Mac OS X</a> —same code
base—). The simplest way to read options are to define them default values
and then to use <code>getOption</code>, thus:</p>
<pre><code class="language-php">$extract = false;
$directory = '.';
// short name value
// ↓ ↓
while (false !== $c = $options->getOption($v)) {
switch($c) {
case 'x':
$extract = $v;
break;
case 'd':
$directory = $v;
break;
}
}
var_dump($extract, $directory);
/**
* Will output:
* bool(true)
* string(5) "value"
*/</code></pre>
<p>We read it like this: “while we have an option to read, we get the short
name in <code>$c</code> and its value in <code>$v</code>, then we see what to
do”.</p>
<p>To read inputs, we will use the <code>Hoa\Console\Parser::listInputs</code>
whose all arguments (a total of 26) are passed by <strong>reference</strong>.
Thus:</p>
<pre><code class="language-php">$parser->listInputs($inputA, $inputB, $inputC);
var_dump($inputA, $inputB, $inputC);
/**
* Will output:
* string(6) "inputA"
* string(6) "inputB"
* string(6) "inputC"
*/</code></pre>
<p>Attention, this approach implies that the inputs are
<strong>ordered</strong> (as usualy most of the time). But also, reading the
inputs without having formely given the analyzer to
<code>Hoa\Console\GetOption</code> can lead to unexpected results (because by
default, all options are considered as booleans). If we would like all the
inputs and analyze them manually if they are not ordered, we can use the
<code>Hoa\Console\Parser::getInputs</code> method which will return all the
inputs.</p>
<h3 id="Special_or_ambiguous_options" for="main-toc">Special or ambiguous
options</h3>
<p>Back to our <code>Hoa\Console\Parser::parseSpecialValue</code> method. It
takes two arguments: a value and an array of keywords. We re-use our example
and modify the case of the <code>d</code> option:</p>
<pre data-line="8-11"><code class="language-php">while (false !== $c = $options->getOption($v)) {
switch($c) {
case 'x':
$extract = $v;
break;
case 'd':
$directory = $parser->parseSpecialValue($v, ['HOME' => '/tmp']);
break;
}
}
print_r($directory);</code></pre>
<p>If we try with <code>-d=a,b,HOME,c,d</code> then <code>-d</code> will have
the following value:</p>
<pre><code class="language-php">/**
* Array
* (
* [0] => a
* [1] => b
* [2] => /tmp
* [3] => c
* [4] => d
* )
*/</code></pre>
<p>Finally, when a read option does not exist but it is
<strong>close</strong> to an existing option modulo some
<strong>typos</strong> (for example <code>--dirzctory</code> instead of
<code>--directory</code>), we can use the <code>__ambiguous</code> case to
capture and compute it:</p>
<pre data-line="13-16"><code class="language-php">while (false !== $c = $options->getOption($v)) {
switch($c) {
case 'x':
$extract = $v;
break;
case 'd':
$directory = $parser->parseSpecialValue($v, ['HOME' => '/tmp']);
break;
case '__ambiguous':
print_r($v);
break;
}
}</code></pre>
<p>The value (in <code>$v</code>) is an array with three entries. For example
with <code>--dirzctory</code>, we have:</p>
<pre><code class="language-php">/**
* Array
* (
* [solutions] => Array
* (
* [0] => directory
* )
*
* [value] => y
* [option] => dirzctory
* )
*/</code></pre>
<p>The <code>solutions</code> key provides all the <strong>similar</strong>
options, the <code>value</code> key gives the value of the option and
<code>option</code> is the <strong>original</strong> read name. It is part to
the user to decide what to do based on these informations. We can use the
<code>Hoa\Console\GetOption::resolveOptionAmbiguity</code> method by giving it
the array, and it will choose the best option if it exists:</p>
<pre><code class="language-php"> case '__ambiguous':
$options->resolveOptionAmbiguity($v);
break;
</code></pre>
<p>It is preferable to <strong>advise</strong> the user that an ambiguity
happened and ask her a decision. Sometimes, it can be
<strong>dangerous</strong> to make decision in her place.</p>
<h3 id="Integrate_a_router_and_a_dispatcher" for="main-toc">Integrate a router
and a dispatcher</h3>
<p>So far, we coerced the options and the inputs to the analyzer.
<code>Hoa\Router\Cli</code> allows to <strong>extract</strong> data from a
command line program. One method interests us:
<code>Hoa\Router\Cli::getURI</code> will give us all the options and inputs of
the running program, that we will <strong>provide</strong> to our analyzer.
Thus:</p>
<pre data-line="2"><code class="language-php">$parser = new Hoa\Console\Parser();
$parser->parse(Hoa\Router\Cli::getURI());
// …</code></pre>
<p>Now, it is possible to interprete the options that we will give to our
program. If you have written the tests in a file named <code>Test.php</code>,
then you will be able to write:</p>
<pre><code class="language-shell">$ php Test.php -x -d=a,b,HOME,c,d inputA inputB
bool(true)
Array
(
[0] => a
[1] => b
[2] => /tmp
[3] => c
[4] => d
)
string(6) "inputA"
string(6) "inputB"
NULL</code></pre>
<p>The <code>-x</code> option is set to <code>true</code>, the <code>-d</code>
option is an array (because we have analyzed it with the
<code>Hoa\Console\Parser::parseSpecialValue</code> method), and we have
<code>inputA</code>, <code>inputB</code> and <code>null</code> as inputs.</p>
<p>This is a good start and we can stop here most of the time. But it is
possible to go further by using a <strong>dispatcher</strong>: writing
commands in several functions or classes and call them regarding the given
options and inputs to our program. We recommend to read the source code of
<a href="@central_resource:path=Library/Cli/Bin/Hoa.php"><code>hoa://Library/Cli/Bin/Hoa.php</code></a>
to help yourself, along with the
<a href="@hack:chapter=Router"><code>Hoa\Router</code></a> and
<a href="@hack:chapter=Dispatcher"><code>Hoa\Dispatcher</code></a> chapters.
We propose a quick example without giving too much details about those
libraries.</p>
<p>The idea is as follows. Thanks to <code>Hoa\Router\Cli</code> we are going
to extract data of the following form: <code>$ php script.php
<em>controller</em> <em>tail</em></code>, where
<code><em>controller</em></code> will be the name of the controller (of a
class) on which we will call the <code>main</code> action (it means the
<code>main</code> method with its default parameters) and where
<code><em>tail</em></code> represents the options and the inputs. The name of
the controller is identified by the special <code>_call</code> variable (from
<code>Hoa\Router\Cli</code>) and the options along with the inputs are
identified by <code>_tail</code> (from <code>Hoa\Dispatcher\Kit</code>). The
options and the inputs are not mandatory. Then, we will use
<code>Hoa\Dispatcher\Basic</code> with the dedicated kit of terminals, namely
<code>Hoa\Console\Dispatcher\Kit</code>. The dispatcher will try to load the
<code>Application\Controller\<em>controller</em></code> class by default, and
the autoloader will load them from the
<code>hoa://Application/Controller/<em>controller</em></code> directory. We
will then set quickly where the application is located. Finally, the exit code
of our program will be given by the value returned by our controller and
action. If an error occurred, we will print it and force an exit code greater
than zero. Thus:</p>
<pre><code class="language-php">try {
// Prepare the router.
$router = new Hoa\Router\Cli();
$router->get(
'g',
'(?&lt;_call>\w+)(?:\s+(?&lt;_tail>.+))?'
);
// Prepare the dispatcher.
$dispatcher = new Hoa\Dispatcher\ClassMethod([
'synchronous.call' => 'Application\Controller\(:call:U:)',
'synchronous.able' => 'main'
]);
$dispatcher->setKitName('Hoa\Console\Dispatcher\Kit');
// Dispatch!
exit($dispatcher->dispatch($router));
} catch (Hoa\Exception $e) {
echo $e->raise(true);
exit($e->getCode() + 1);
}</code></pre>
<p>At the same level of our program, let's create the
<code>Application/Controller/</code> directory with the <code>Foo.php</code>
file inside, which will contain the following code:</p>
<pre><code class="language-php">&lt;?php
namespace Application\Controller;
class Foo extends \Hoa\Console\Dispatcher\Kit
{
protected $options = [
['extract', \Hoa\Console\GetOption::NO_ARGUMENT, 'x'],
['directory', \Hoa\Console\GetOption::REQUIRED_ARGUMENT, 'd'],
['help', \Hoa\Console\GetOption::NO_ARGUMENT, 'h']
];
public function MainAction()
{
$extract = false;
$directory = '.';
while (false !== $c = $this->getOption($v)) {
switch($c) {
case 'x':
$extract = $v;
break;
case 'd':
$directory = $this->parser->parseSpecialValue($v, ['HOME' => '/tmp']);
break;
case 'h':
return $this->usage();
}
}
echo 'extract: ';
var_dump($extract);
echo 'directory: ';
print_r($directory);
return;
}
public function usage()
{
echo
'Usage : foo &lt;options>', "\n",
'Options :', "\n",
$this->makeUsageOptionsList([
'x' => 'Whether we need to extract.',
'd' => 'Directory to extract.',
'h' => 'This help.'
]);
}
}</code></pre>
<p>Our class extends our kit to benefit from the provided methods. Among other
things, its own <code>getOption</code> method that will exploit the
<code>$options</code> attribute where the options are declared,
<code>makeUsageOptionsList</code> to print a usage, its own
<code>resolveOptionAmbiguity</code> method that asks a confirmation from the
user, the router access through the <code>$router</code> attribute etc. Kits
offer <strong>services</strong> to the application, they
<strong>aggregate</strong> services offered by the libraries. Now, let's
test:</p>
<pre><code class="language-shell">$ php Test.php foo -x -d=1:3
extract: bool(true)
directory: Array
(
[0] => 1
[1] => 2
[2] => 3
)</code></pre>
<p>Awesome!</p>
<p>Let's note that the <code>hoa</code> script is exactly build like this. Do
not hesitate to find inspiration from it.</p>
<h2 id="Processus" for="main-toc">Processus</h2>
<p>In our context, a <strong>processus</strong> is a classical program that
is executed in a <strong>terminal</strong>. The interesting part is that such
a program <strong>communicates</strong> with the rest of its
<strong>environment</strong> thanks to <strong>pipes</strong>, numbered from
zero. Some of them have even names:</p>
<ul>
<li><code>STDIN</code> (<code>0</code>) to read <strong>inputs</strong>
(<em>standard input</em>),</li>
<li><code>STDOUT</code> (<code>1</code>) to write <strong>outputs</strong>
(<em>standard output</em>),</li>
<li><code>STDERR</code> (<code>2</code>) to write <strong>errors</strong>
(<em>standard error</em>).</li>
</ul>
<p>When a processus is executed in a terminal, <code>STDIN</code> will use the
<strong>keyboard</strong> as the source of data, and <code>STDOUT</code> with
<code>STDERR</code> are linked to the <strong>window</strong> of the terminal.
But when a processus is executed in a <strong>sub-terminal</strong>, it means
executed from another processus, <code>STDIN</code> is no longer linked to the
keyboard, like <code>STDOUT</code> and <code>STDERR</code> are not linked to
the screen. This is the parent processus that will read and write on these
streams to <strong>interact</strong> with the “sub”-processus. This mechanism
is called a <strong>redirection</strong> of stream, we use it very often when
we write a command line (please, see the
<a href="http://gnu.org/software/bash/manual/bashref.html#Redirections">section
Redirections from the Bash Reference Manual</a>). What we are going to do use
another syntax but the mechanism is exactly the same.</p>
<p>It is very important to know that these streams are all
<strong>asynchronous</strong> from each other. Not one of these streams will
have an impact on another one, there is no link between them and that is very
important for the rest of this section.</p>
<p>From the PHP level, it is possible to access to these streams by using
respectively the following URI: <code>php://stdin</code>,
<code>php://stdout</code> and <code>php://stderr</code>. However, we also have
the eponymous <code>STDIN</code>, <code>STDOUT</code> and <code>STDERR</code>
constants. They are defined as follows (example with <code>STDIN</code>):</p>
<pre><code class="language-php">define('STDIN', fopen('php://stdin', 'r'));</code></pre>
<p>These streams are available if and only if the program is executed from a
command line. Remind that pipes are identified by numbers. We are then able to
use <code>php://fd/0</code> to represent <code>STDIN</code>,
<code>php://fd/1</code> to represent <code>STDOUT</code> etc. The
<code>php://fd/<em>i</em></code> URI allows to access the file with the
<code><em>i</em></code> <strong>descriptor</strong>.</p>
<h3 id="Very_basic_execution" for="main-toc">Very basic execution</h3>
<p>The <code>Hoa\Console\Processus</code> class provides a very
<strong>quick</strong> way to execute a processus and get the result of
<code>STDOUT</code>. This is the most common case. Thus, we will use the
<code>execute</code> static method:</p>
<pre><code class="language-php">var_dump(Hoa\Console\Processus::execute('id -u -n'));
/**
* Could output:
* string(3) "hoa"
*/</code></pre>
<p>By default, the command will be escaped for security reasons. If you are
confident in the command, you can desactivate the escaping by setting the
second argument to <code>false</code>.</p>
<p>We have no control on the pipes and even if it is suitable for the majority
of cases, this is not enough when we would like a minimum of interaction with
the processus.</p>
<h3 id="Reading_and_writing" for="main-toc">Reading and writing</h3>
<p>Let's see how to <strong>interact</strong> with a processus. We will
consider the following <code>LittleProcessus.php</code> program:</p>
<pre><code class="language-php">&lt;?php
$range = range('a', 'z');
while (false !== $line = fgets(STDIN)) {
echo '> ', $range[intval($line)], "\n";
}</code></pre>
<p>To test and understand its behavior, let's write the following command line
and hit <code>3</code> and <code>4</code> on the keyboard:</p>
<pre><code class="language-shell">$ php LittleProcessus.php
3
> d
4
> e
</code></pre>
<p>We can also write:</p>
<pre><code class="language-shell">$ seq 0 4 | php LittleProcessus.php
> a
> b
> c
> d
> e</code></pre>
<p>Our program will read each line on the standard input, considering it is a
number and transforming it into a character that will be printed on the
standard output. We would like to execute this program by giving it our own
custom list of numbers (like the <code>seq</code> program does) and observe
the produced result.</p>
<p>An instance of the <code>Hoa\Console\Processus</code> class represents a
<strong>processus</strong>. During the instanciation, we can set:</p>
<ul>
<li>the <strong>name</strong> of the processus,</li>
<li>its <strong>options</strong>,</li>
<li>the <strong>description</strong> of pipes.</li>
</ul>
<p>There is other arguments but we will see them further.</p>
<p>The description of pipes has the form of an array where each key represents
the number of the pipe (more generally, this is the <code><em>i</em></code>
from <code>php://fd/<em>i</em></code>) and its value is also an array
describing the nature of the pipe: either a “real” pipe, or a file with their
reading or writing mode (among <code>r</code>, <code>w</code> or
<code>a</code>). Let's illustrate with an example:</p>
<pre><code class="language-php">$processus = new Hoa\Console\Processus(
'php',
['LittleProcessus.php'],
[
// STDIN.
0 => ['pipe', 'r'],
// STDOUT.
1 => ['file', '/tmp/output', 'a']
]
);</code></pre>
<p>In this case, <code>STDIN</code> is a pipe and <code>STDOUT</code> is the
<code>/tmp/output</code> file. If we do not set a descriptor, it will be
equivalent to write:</p>
<pre><code class="language-php">$processus = new Hoa\Console\Processus(
'php',
['LittleProcessus.php'],
[
// STDIN.
0 => ['pipe', 'r'],
// STDOUT.
1 => ['pipe', 'w'],
// STDERR.
2 => ['pipe', 'w']
]
);</code></pre>
<p>Each pipe is identified as a stream and can be manipulated as such. When a
pipe is in <strong>reading</strong> (with the <code>r</code> mode), it means
that the processus will <strong>read</strong> from it. So we, the parent
processus, will <strong>write</strong> on this pipe. Let's take the example of
<code>STDIN</code>: the processus reads from <code>STDIN</code> what the
keyboard has written on it. And conversely, when a pipe is in
<strong>writing</strong> (with the <code>w</code> mode), it means that we will
<strong>read</strong> from it. Let's take the example of <code>STDOUT</code>:
the screen will read what the processus has written to it.</p>
<p>The <code>Hoa\Console\Processus</code> class extends the
<a href="@hack:chapter=Stream"><code>Hoa\Stream</code></a> class, and
consequently, we have all the necessary tools to read and write on the pipe of
our choice. This class also provides several <strong>listeners</strong>:</p>
<ul>
<li><code>start</code>, when the processus is started,</li>
<li><code>stop</code>, when the processus is stopped,</li>
<li><code>input</code>, when the reading streams are ready,</li>
<li><code>output</code>, when the writing streams are ready,</li>
<li><code>timeout</code>, when the processus is executing too much
time.</li>
</ul>
<p>Let's take directly an example. We will execute the <code>php
LittleProcessus.php</code> processus and attach functions to the following
listeners: <code>input</code> to write a sequence of numbers and
<code>output</code> to read the result.</p>
<pre><code class="language-php">$processus = new Hoa\Console\Processus('php LittleProcessus.php');
$processus->on('input', function ($bucket) {
$source = $bucket->getSource();
$data = $bucket->getData();
echo 'INPUT (', $data['pipe'], ')', "\n";
$source->writeAll(
implode("\n", range($i = mt_rand(0, 21), $i + 4)) . "\n"
);
return false;
});
$processus->on('output', function ($bucket) {
$data = $bucket->getData();
echo 'OUTPUT (', $data['pipe'], ') ', $data['line'], "\n";
return;
});
$processus->run();
/**
* Could output:
* INPUT (0)
* OUTPUT (1) > s
* OUTPUT (1) > t
* OUTPUT (1) > u
* OUTPUT (1) > v
* OUTPUT (1) > w
*/</code></pre>
<p>Now, let's see the details to understand well.</p>
<p>When a <strong>reading</strong> stream is <strong>ready</strong>, the
<code>input</code> listener will be fired. Only one data is sent:
<code>pipe</code>, that contains the number of the pipe (the
<code><em>i</em></code> of <code>php://fd/<em>i</em></code>). When a
<strong>writing</strong> stream is ready, the <code>output</code> listener is
fired. Two data are sent: <code>pipe</code> (like <code>input</code>) and
<code>line</code> that contains the <strong>received line</strong>.</p>
<p>We see in the function attached to the <code>input</code> listener that we
write a sequence of numbers concatenated by <code>\n</code> (one number per
line). In order to achieve this, we use the <code>writeAll</code> method. By
default, the writing methods write on the <code>0</code> pipe. To change this
behavior, we will have to set the number of the pipe as the second argument of
the writing methods. Same for the reading methods but the default pipe is
<code>1</code>.</p>
<p>When the callable attached to a listener returns <code>false</code>, the
pipe that has fired this call will be <strong>closed</strong> just after. In
our case, the function attached to <code>input</code> returns
<code>false</code> just after having written the data, we no longer need this
pipe. It is important for <strong>performance</strong> reasons to close pipes
as soon as possible.</p>
<p>Finally, to <strong>execute</strong> the processus, we use the
<code>Hoa\Console\Processus::run</code> method with a null arity.</p>
<p>In our example, we write all data at once but we can send them as soon as
they are ready, which is more efficient because the processus does no wait a
big bucket of data and can compute them gradually. Let's modify our example to
write a data each time <code>STDIN</code> is ready:</p>
<pre><code class="language-php">$processus->on('input', function ($bucket) {
static $i = null;
static $j = 5;
if (null === $i) {
$i = mt_rand(0, 20);
}
$data = $bucket->getData();
echo 'INPUT (', $data['pipe'],')', "\n";
$source = $bucket->getSource();
$source->writeLine($i++);
usleep(50000);
if (0 >= $j--) {
return false;
}
return;
});</code></pre>
<p>We initialize two variables: <code class="language-php">$i</code> and
<code class="language-php">$j</code>, that hold the number to send and the
maximum number of data to send. We introduce a voluntary latency with
<code class="language-php">usleep(50000)</code> in order that
<code>STDOUT</code> to be ready, only to illustrate our example. In this case,
the output would be:</p>
<pre><code class="language-php">/** Could output:
* INPUT (0)
* OUTPUT (1) > h
* INPUT (0)
* OUTPUT (1) > i
* INPUT (0)
* OUTPUT (1) > j
* INPUT (0)
* OUTPUT (1) > k
* INPUT (0)
* OUTPUT (1) > l
* INPUT (0)
* OUTPUT (1) > m
*/</code></pre>
<p>The processus is waiting an input and read data when they are received.
Once we have sent all the data, we close the pipe.</p>
<p>The processus will close itself. We have the
<code>Hoa\Console\Processus::getExitCode</code> method to know the exit
<strong>code</strong> of the processus. Attention, a <code>0</code> code
represents a success. Because it is a common error, there is the
<code>Hoa\Console\Processus::isSuccessful</code> method to know if the
processus has been executed with success or not.</p>
<h3 id="Detect_the_type_of_pipes" for="main-toc">Detect the type of pipes</h3>
<p>Sometimes, it is useful to know the <strong>type</strong> of pipes, it
means if it is a <strong>direct</strong> (regular) use, a
<strong>pipe</strong> or a <strong>redirection</strong>. We will make use of
the <code>Hoa\Console\Console</code> class and its <code>isDirect</code>,
<code>isPipe</code> and <code>isRedirection</code> static methods to get these
informations.</p>
<p>Let's take an example to understand. Let's write the <code>Type.php</code>
file that will study the type of <code>STDOUT</code>:</p>
<pre><code class="language-php">echo 'is direct: ';
var_dump(Hoa\Console\Console::isDirect(STDOUT));
echo 'is pipe: ';
var_dump(Hoa\Console\Console::isPipe(STDOUT));
echo 'is redirection: ';
var_dump(Hoa\Console\Console::isRedirection(STDOUT));</code></pre>
<p>And now, let's execute this file to see the result:</p>
<pre><code class="language-shell">$ php Type.php
is direct: bool(true)
is pipe: bool(false)
is redirection: bool(false)
$ php Type.php | xargs -I@ echo @
is direct: bool(false)
is pipe: bool(true)
is redirection: bool(false)
$ php Type.php > /tmp/foo; cat /tmp/foo
is direct: bool(false)
is pipe: bool(false)
is redirection: bool(true)</code></pre>
<p>In the first case, <code>STDOUT</code> is <strong>direct</strong> (for
<code>STDOUT</code>, it means that it is <strong>linked</strong> to the
screen, for <code>STDIN</code>, it will be linked to the keyboard etc.). In
the second case, <code>STDOUT</code> is a <strong>pipe</strong>, it means that
it is <strong>attached</strong> to the <code>STDIN</code> of the command
written after the <code>|</code> symbol. In the last case, <code>STDOUT</code>
is a <strong>redirection</strong>, it means that it is
<strong>redirected</strong> in the <code>/tmp/foo</code> file (that we print
just after). The operation can be done on <code>STDIN</code>,
<code>STDERR</code> or any other resource.</p>
<p>Knowing the type of pipes can allow different behaviors according to the
<strong>context</strong>. For example,
<code>Hoa\Console\Readline\Readline</code> reads on <code>STDIN</code>. If its
type is a pipe or a redirection, the advanced edition mode of the line will be
disabled and it will return <code>false</code> when there will be no more
things to read. Another example, the commands verbosity of the
<code>hoa</code> script uses the type of <code>STDOUT</code> as the default
value: direct to be verbose, else not verbose. Try the following examples to
see the difference:</p>
<pre><code class="language-shell">$ hoa --no-verbose
$ hoa | xargs -I@ echo @</code></pre>
<p>Examples are numerous but be careful to use this feature with precautions.
We have to adapt the behaviors but keep them <strong>consistent</strong>.</p>
<h3 id="Execution_conditions" for="main-toc">Execution conditions</h3>
<p>The processus is executed in a particular <strong>directory</strong> and a
particular <strong>environment</strong>. This directory is called the current
working directory, often abbreviated <abbr lang="en">cwd</abbr>. It defines
the directory where the processus will be executed. We can find it in PHP with
<a href="http://php.net/getcwd">the <code>getcwd</code> function</a>. The
environment is defined by an array, which can be retrieved for example by
executing <code>/usr/bin/env</code>. For example, the <code>PATH</code> is
defined in this environment. These data are given as fourth and fifth
arguments of the <code>Hoa\Console\Processus</code> constructor. Thus:</p>
<pre><code class="language-php">$processus = new Hoa\Console\Processus(
'php',
null, /* no option */
null, /* use default pipes */
'/tmp',
[
'FOO' => 'bar',
'BAZ' => 'qux',
'PATH' => '/usr/bin:/bin'
]
);
$processus->on('input', function (Hoa\Event\Bucket $bucket) {
$bucket->getSource()->writeAll(
'&lt;?php' . "\n" .
'var_dump(getcwd());' . "\n" .
'print_r($_ENV);'
);
return false;
});
$processus->on('output', function (Hoa\Event\Bucket $bucket) {
$data = $bucket->getData();
echo '> ', $data['line'], "\n";
return;
});
$processus->run();
/**
* Will output:
* > string(12) "/tmp"
* > Array
* > (
* > [FOO] => bar
* > [PATH] => /usr/bin:/bin
* > [PWD] => /tmp
* > [BAZ] => qux
* > [_] => /usr/bin/php
* >
* > )
*/</code></pre>
<p>If the current working directory is not defined, we will use the one of the
program. If the environment is not defined, the processus will use the one of
its parent.</p>
<p>We are also able to set a <strong>maximum time</strong> in seconds in order
to get a <strong>response</strong> from the processus (defined to 30 seconds
by default). This is the last argument of the constructor. We can use the
<code>Hoa\Console\Processus::setTimeout</code> method. To know when this time
is reached, we must use the <code>timeout</code> listener. No action will be
done automatically. We can for example terminate the processus thanks to the
<code>Hoa\Console\Processus::terminate</code> method. Thus:</p>
<pre><code class="language-php">$processus = new Hoa\Console\Processus('php');
// 3 seconds is enough…
$processus->setTimeout(3);
// Sleep 10 seconds.
$processus->on('input', function (Hoa\Event\Bucket $bucket) {
$bucket->getSource()->writeAll('&lt;?php sleep(10);');
return false;
});
// Terminate the processus on timeout.
$processus->on('timeout', function (Hoa\Event\Bucket $bucket) {
echo 'TIMEOUT, terminate', "\n";
$bucket->getSource()->terminate();
return;
});
$processus->run();
/**
* Will output (after 3 secondes):
* TIMEOUT, terminate
*/</code></pre>
<p>No action is done automatically because they can be numerous. Maybe we can
unblock the processus, or close it to open other ones, emit some logs etc.</p>
<p>About the <code>terminate</code> method, it can take several different
values, defined by the constants of <code>Hoa\Console\Processus</code>:
<code>SIGHUP</code>, <code>SIGINT</code>, <code>SIGQUIT</code>,
<code>SIGABRT</code>, <code>SIGKILL</code>, <code>SIGALRM</code> and
<code>SIGTERM</code> (by default). Several <strong>signals</strong> can be
send to the processus in order to stop them. To get the detail, please, see
<a href="http://freebsd.org/cgi/man.cgi?query=signal"
title="signal(3), FreeBSD Library Functions Manual">the
<code>signal</code></a> page.</p>
<h3 id="Miscellaneous" for="main-toc">Miscellaneous</h3>
<p>The <code>getTitle</code> and <code>setTitle</code> static methods on the
<code>Hoa\Console\Processus</code> class respectively allow to get and set the
title of the processus. Thus:</p>
<pre><code class="language-php">Hoa\Console\Processus::setTitle('hoa #1');</code></pre>
<p>And in another terminal:</p>
<pre data-line="2"><code class="language-shell">$ ps | grep hoa
69578 ttys006 0:00.01 hoa #1
70874 ttys008 0:00.00 grep hoa</code></pre>
<p>These methods are really useful when we manipulate a lot of processus and
we would like to identify them efficiently (for example with tools like
<code>top</code> or <code>ps</code>). Note that they are working only if you
have at least PHP5.5.</p>
<p>Another interesting static method is
<code>Hoa\Console\Processus::locate</code> that allows to determine the path
to a given program. For example:</p>
<pre><code class="language-php">var_dump(Hoa\Console\Processus::locate('php'));
/**
* Could output:
* string(12) "/usr/bin/php"
*/</code></pre>
<p>In the case where the program is not found, <code>null</code> will be
returned. This method is based on the <code>PATH</code> of your system.</p>
<h3 id="Interactive_processus_and_pseudo-terminals" for="main-toc">Interactive
processus and pseudo-terminals</h3>
<p>This section is a little bit technical but it explains a
<strong>problem</strong> that can be met with some processus called
<strong>interactive</strong>.</p>
<p>The <code>Hoa\Console\Processus</code> class allows to automate the
interaction with processus very easily. However, it is not always possible to
create such an automation because of the behavior of the processus. We will
illustrate the problem by writing the <code>Interactive.php</code> file:</p>
<pre><code class="language-php">&lt;?php
echo 'Login: ';
if (false === $login = fgets(STDIN)) {
fwrite(STDERR, 'Hmm, no login.' . "\n");
exit(1);
}
echo 'Password: ';
if (false === $password = fgets(STDIN)) {
fwrite(STDERR, 'Hmm, no password.' . "\n");
exit(2);
}
echo 'Result:', "\n\t", $login, "\t", $password;</code></pre>
<p>Let's execute this processus to see what it does:</p>
<pre><code class="language-shell">$ php Interactive.php
Login: myLogin
Password: myPassword
Result:
myLogin
myPassword</code></pre>
<p>And now, let's automate the execution of this processus:</p>
<pre><code class="language-shell">$ echo 'myLogin\nmyPassword' > data
$ php Interactive.php &lt; data
Login: Password: Result:
myLogin
myPassword</code></pre>
<p>Excellent. We could easily get the same result with
<code>Hoa\Console\Processus</code>. Now, if our processus want to ensure that
<code>STDIN</code> is empty between two inputs, it can add:</p>
<pre data-line-offset="7" data-line="10"><code class="language-php">}
fseek(STDIN, 0, SEEK_END);
echo 'Password: ';</code></pre>
<p>And then in this case, if we try to automate the execution:</p>
<pre><code class="language-shell">$ php Interactive.php &lt; data
Login: Password: Hmm, no password.</code></pre>
<p>This is absolutely a normal behavior, but
<code>Hoa\Console\Processus</code> can do nothing to solve this issue.</p>
<p>The solution would be to use a
<a href="https://en.wikipedia.org/wiki/Pseudo_terminal">pseudo-terminal</a> by
using the PTY functions (please, see
<a href="http://kernel.org/doc/man-pages/online/pages/man7/pty.7.html"
title="pty(7), Linux Programmer's Manual">in Linux</a> or
<a href="http://freebsd.org/cgi/man.cgi?query=pty"
title="pty(3), FreeBSD Library Functions Manual" >in FreeBSD</a>).
Unfortunately these functions are not available in PHP for technical reasons.
There is no possible solutions in pure PHP, but it is still conceivable to use
an <strong>external</strong> program, written in C for example.</p>
<h2 id="Conclusion" for="main-toc">Conclusion</h2>
<p>The <code>Hoa\Console</code> library provides
<strong>comprehensive</strong> tools to write programs tailored to a
<strong>textual</strong> interface, whether it be for interaction with the
window or the cursor, the interaction with the user thanks to a very
customizable readline (with autocompletion and shortcuts), the options reading
for programs themselves, the construction of elaborated programs, or even the
execution, interaction and communication with processus.</p>
</yield>
</overlay>