Index.xyl 81 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <overlay xmlns="http://hoa-project.net/xyl/xylophone">
  3. <yield id="chapter">
  4. <p>Le terminal est une <strong>interface</strong> très
  5. <strong>puissante</strong> qui repose sur de multiples concepts.
  6. <code>Hoa\Console</code> permet d'écrire des <strong>outils</strong> adaptés à
  7. ce type d'environnement.</p>
  8. <h2 id="Table_of_contents">Table des matières</h2>
  9. <tableofcontents id="main-toc" />
  10. <h2 id="Introduction" for="main-toc">Introduction</h2>
  11. <p>De nos jours, nous comptons deux types d'interfaces :
  12. <strong>textuelle</strong> et <strong>graphique</strong>. L'interface
  13. textuelle existe depuis l'origine des ordinateurs, alors appelés
  14. <strong>terminaux</strong>. Cette interface, malgré son aspect « brut », est
  15. fonctionnellement très <strong>puissante</strong> grâce à plusieurs concepts
  16. comme par exemple la ligne de commande ou les <em lang="en">pipes</em>.
  17. Aujourd'hui, elle est encore très utilisée car elle est souvent plus rapide
  18. pour exécuter des tâches <strong>complexes</strong> qu'une interface
  19. graphique. Elle peut être aussi très facilement utilisée à travers des réseaux
  20. ou sur des machines à faibles ressources. Bref, cette interface est toujours
  21. <strong>incontournable</strong>.</p>
  22. <p>Du point de vue de l'utilisateur, il y a trois niveaux à considérer :</p>
  23. <ul>
  24. <li>l'<strong>interface</strong> : afficher et éditer du texte, manipuler la
  25. fenêtre, le curseur etc. ;</li>
  26. <li>le <strong>programme</strong> : interagir avec l'utilisateur avec un
  27. maximum de confort, utiliser la ligne de commande à son plein potentiel,
  28. construire des programmes adaptés à ce type d'interface ;</li>
  29. <li>l'<strong>interaction</strong> avec d'autres programmes : interagir
  30. automatiquement et communiquer avec d'autres programmes.</li>
  31. </ul>
  32. <p>La bibliothèque <code>Hoa\Console</code> propose des outils pour répondre à
  33. ces trois niveaux de problématique. Pour cela, elle se base sur des
  34. <strong>standards</strong>, comme
  35. l'<a href="http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-048.pdf">ECMA-48</a>
  36. qui spécifie la communication avec le système à travers des suites de
  37. caractères ASCII et des codes de contrôle (aussi appelés séquences
  38. d'échappement), ce afin de manipuler la fenêtre, le curseur ou des
  39. périphériques de la machine. D'autres fonctionnalités sont aussi standards
  40. comme la manière de lire des options depuis un programme, très
  41. <strong>inspirée</strong> de systèmes comme
  42. <a href="http://linux.org/">Linux</a>, <a href="http://freebsd.org/">FreeBSD</a> ou
  43. encore <a href="https://en.wikipedia.org/wiki/UNIX_System_V">System V</a>.
  44. D'ailleurs, si vous êtes familier avec plusieurs bibliothèques C, vous ne
  45. serez pas déroutés. Et <em>a contrario</em>, si vous apprenez à utiliser
  46. <code>Hoa\Console</code>, vous ne serez pas perdus en retournant sur des
  47. langages de plus bas niveaux comme le C.</p>
  48. <p>Avant de commmencer, nous aimerions ajouter une petite note
  49. <strong>uniquement</strong> à propos de la gestion de la fenêtre et du
  50. curseur. Aujourd'hui, nous avons le choix entre <strong>plusieurs</strong>
  51. terminaux par système et certains sont plus complets que d'autres. Par
  52. exemple, <a href="https://windows.microsoft.com/">Windows</a> et son terminal
  53. par défaut, le <a href="http://en.wikipedia.org/wiki/MS-DOS">MS-DOS</a>, ne
  54. respecte aucun standard. Dans ce cas, oubliez le standard ECMA-48 et
  55. tournez-vous vers
  56. <a href="http://msdn.microsoft.com/library/ms682087.aspx"
  57. title="Console Reference">la bibliothèque <code>Wincon</code></a>. Il est
  58. souvent recommandé d'utiliser une machine Unix <strong>virtuelle</strong> ou
  59. un <strong>émulateur</strong> de terminal, comme
  60. <a href="http://ttssh2.sourceforge.jp/">TeraTerm</a>, très complet. Même sur
  61. des systèmes proches de la famille BSD, les terminaux distribués par défaut ne
  62. supportent pas tous les standards. C'est le cas de Mac OS X, où nous vous
  63. conseillons d'utiliser <a href="http://iterm2.com">iTerm2</a> au lieu de
  64. Terminal. Enfin, sur d'autres systèmes de la famille Linux ou BSD, nous
  65. conseillons
  66. <a href="http://software.schmorp.de/pkg/rxvt-unicode.html">urxvt</a>. Pour
  67. les autres fonctionnalités, comme la lecture en ligne, la lecture d'options,
  68. les processus etc., <code>Hoa\Console</code> est parfaitement
  69. <strong>compatible</strong> et fonctionnel.</p>
  70. <h2 id="Window" for="main-toc">Fenêtre</h2>
  71. <p>La fenêtre d'un terminal doit être vue comme un <strong>canevas</strong> de
  72. <strong>colonnes</strong> et de <strong>lignes</strong>. La classe
  73. <code>Hoa\Console\Window</code> permet de manipuler la
  74. <strong>fenêtre</strong> du terminal et son <strong>contenu</strong> à travers
  75. des méthodes statiques.</p>
  76. <h3 id="Size_and_position" for="main-toc">Taille et position</h3>
  77. <p>Les premières opérations élémentaires concernent la <strong>taille</strong>
  78. et la <strong>position</strong> de la fenêtre, grâce aux méthode
  79. <code>setSize</code>, <code>getSize</code>, <code>moveTo</code> et
  80. <code>getPosition</code>. La taille se définie avec les unités
  81. <em>colonne</em> × <em>ligne</em> et la position se définie en pixels.
  82. Ainsi :</p>
  83. <pre><code class="language-php">Hoa\Console\Window::setSize(80, 50);
  84. print_r(Hoa\Console\Window::getSize());
  85. print_r(Hoa\Console\Window::getPosition());
  86. /**
  87. * Will output:
  88. * Array
  89. * (
  90. * [x] => 80
  91. * [y] => 50
  92. * )
  93. * Array
  94. * (
  95. * [x] => 104
  96. * [y] => 175
  97. * )
  98. */</code></pre>
  99. <p>Nous remarquerons que la fenêtre se redimensionne <strong>toute
  100. seule</strong>. Ni la taille ni la position de la fenêtre ne sont stockées en
  101. mémoire, elles sont calculées à chaque appel de la méthode
  102. <code>getSize</code> et <code>getPosition</code>. Attention, l'axe <em>y</em>
  103. de la position de la fenêtre se calcule depuis <strong>le bas</strong> de
  104. l'écran et non pas depuis le haut de l'écran comme nous pourrions nous y
  105. attendre !</p>
  106. <p>Il est aussi possible d'écouter l'<strong>événement</strong>
  107. <code>hoa://Event/Console/Window:resize</code> qui est lancé à chaque fois que
  108. la fenêtre est redimensionnée : soit manuellement, soit avec la méthode
  109. <code>setSize</code>. Nous avons besoin de deux choses pour que cet événement
  110. fonctionne :</p>
  111. <ol>
  112. <li><a href="http://php.net/pcntl">l'extension <code>pcntl</code></a> doit
  113. être activée ;</li>
  114. <li>nous devons utiliser
  115. <a href="http://php.net/declare">la structure <code>declare</code></a> pour
  116. que <a href="http://php.net/pcntl_signal">la fonction
  117. <code>pcntl_signal</code></a> fonctionne correctement.</li>
  118. </ol>
  119. <p>Pour mettre le programme en attente passive, nous allons utiliser
  120. <a href="http://php.net/stream_select">la fonction
  121. <code>stream_select</code></a>, c'est un <strong>détail</strong> présent
  122. uniquement pour tester notre code, sinon le programme se terminerait tout de
  123. suite. Ainsi :</p>
  124. <pre><code class="language-php">Consistency\Autoloader::load('Hoa\Console\Window'); // make sure it is loaded.
  125. declare(ticks = 1);
  126. Hoa\Event\Event::getEvent('hoa://Event/Console/Window:resize')
  127. ->attach(function (Hoa\Event\Bucket $bucket) {
  128. $data = $bucket->getData();
  129. $size = $data['size'];
  130. echo 'New size (', $size['x'], ', ', $size['y'], ')', "\n";
  131. });
  132. // Passive loop.
  133. while (true) {
  134. $r = [STDIN];
  135. @stream_select($r, $w, $e, 3600);
  136. }</code></pre>
  137. <p>Lorsque nous modifions la taille de la fenêtre, nous verrons s'afficher par
  138. exemple : <samp>New size (45, 67)</samp>, et ce pour chaque redimensionnement.
  139. Cet événement est intéressant si nous voulons <strong>ré-adapter</strong>
  140. notre présentation.</p>
  141. <p>Enfin, nous pouvons minimiser ou restaurer la fenêtre grâce aux méthodes
  142. statiques <code>Hoa\Console\Window::minimize</code> et
  143. <code>Hoa\Console\Window::restore</code>. Par ailleurs, nous pouvons placer la
  144. fenêtre en arrière-plan (derrière toutes les autres fenêtres) grâce à la
  145. méthode statique <code>Hoa\Console\Window::lower</code>, tout comme nous
  146. pouvons la placer en avant-plan avec <code>Hoa\Console\Window::raise</code>.
  147. Par exemple :</p>
  148. <pre><code class="language-php">Hoa\Console\Window::minimize();
  149. sleep(2);
  150. Hoa\Console\Window::restore();
  151. sleep(2);
  152. Hoa\Console\Window::lower();
  153. sleep(2);
  154. Hoa\Console\Window::raise();
  155. echo 'Back!', "\n";</code></pre>
  156. <h3 id="Title_and_label" for="main-toc">Titre et label</h3>
  157. <p>Le <strong>titre</strong> d'une fenêtre correspond au texte affiché dans sa
  158. <strong>barre</strong> supérieure, dans laquelle sont souvent placés les
  159. contrôles de la fenêtre comme la maximisation, la minimisation etc. Le
  160. <strong>label</strong> correspond au nom associé au <strong>processus</strong>
  161. actuel. Nous trouvons les méthodes <code>setTitle</code>,
  162. <code>getTitle</code> et <code>getLabel</code>, il n'est pas prévu de modifier
  163. le label. Pour définir le titre du processus (ce que nous voyons avec la
  164. commande <code>top</code> ou <code>ps</code> par exemple), il faudra se
  165. référer à <code>Hoa\Console\Processus::setTitle</code> et à
  166. <code>Hoa\Console\Processus::getTitle</code> pour l'obtenir. Ainsi :</p>
  167. <pre><code class="language-php">Hoa\Console\Window::setTitle('Foobar');
  168. var_dump(Hoa\Console\Window::getTitle());
  169. var_dump(Hoa\Console\Window::getLabel());
  170. /**
  171. * Will output:
  172. * string(6) "Foobar"
  173. * string(3) "php"
  174. */</code></pre>
  175. <p>Encore une fois, le titre et le label ne sont pas stockés en mémoire, ils
  176. sont calculés à chaque appel de méthode.</p>
  177. <h3 id="Interact_with_the_content" for="main-toc">Interagir avec le
  178. contenu</h3>
  179. <p><code>Hoa\Console\Window</code> permet aussi de contrôler le
  180. <strong>contenu</strong> de la fenêtre, ou du moins le
  181. <em lang="en">viewport</em>, c'est à dire le contenu <strong>visible</strong>
  182. de la fenêtre. Une seule méthode est actuellement disponible :
  183. <code>scroll</code>, qui permet de <strong>déplacer</strong> le contenu vers
  184. le haut ou vers le bas. Les arguments de cette méthode sont très simples :
  185. <code>up</code> ou <code>↑</code> pour monter d'une ligne, et
  186. <code>down</code> ou <code>↓</code> pour descendre d'une ligne. Nous pouvons
  187. concaténer ces directions par un espace ou alors préciser le nombre de fois où
  188. une direction sera répétée :</p>
  189. <pre><code class="language-php">Hoa\Console\Window::scroll('↑', 10);</code></pre>
  190. <p>En réalité, cette méthode va déplacer le contenu pour qu'il y ait
  191. <em>x</em> lignes respectivement en-dessous ou au-dessus du curseur.
  192. Attention, le curseur <strong>ne change pas</strong> de position !</p>
  193. <p>Même si c'est très souvent inutile, il est possible de
  194. <strong>rafraîchir</strong> la fenêtre, c'est à dire de refaire un rendu
  195. complet. Nous pouvons nous aider de la méthode <code>refresh</code> toujour
  196. sur <code>Hoa\Console\Window</code>.</p>
  197. <p>Enfin, il est possible de placer un texte dans le
  198. <strong>presse-papier</strong> de l'utilisateur à l'aide de la méthode
  199. <code>copy</code> :</p>
  200. <pre><code class="language-php">Hoa\Console\Window::copy('Foobar');</code></pre>
  201. <p>Puis si l'utilisateur colle ce qui est dans son presse-papier, il verra
  202. <samp>Foobar</samp> s'afficher.</p>
  203. <h2 id="Cursor" for="main-toc">Curseur</h2>
  204. <p>À l'intérieur d'une fenêtre, nous avons un curseur qui peut être vu comme
  205. la <strong>pointe</strong> d'un stylo. La classe
  206. <code>Hoa\Console\Cursor</code> permet de manipuler le
  207. <strong>curseur</strong> du terminal à travers des méthodes statiques.</p>
  208. <h3 id="Moving" for="main-toc">Déplacement</h3>
  209. <p>Nous allons commencer par <strong>déplacer</strong> le curseur. Il se
  210. déplace partout dans le <em lang="en">viewport</em>, c'est à dire le contenu
  211. <strong>visible</strong> de la fenêtre du terminal, mais nous allons écrire un
  212. peu de texte et nous déplacer dedans dans un premier temps. La méthode
  213. <code>move</code> sur <code>Hoa\Console\Cursor</code> permet de déplacer le
  214. curseur dans plusieurs <strong>directions</strong>. Tout d'abord de manière
  215. <strong>relative</strong> :</p>
  216. <ul>
  217. <li><code>u[p]</code> ou <code>↑</code>, pour le déplacer à la ligne
  218. supérieure ;</li>
  219. <li><code>r[ight]</code> ou <code>→</code>, pour le déplacer à la colonne
  220. suivante ;</li>
  221. <li><code>d[own]</code> ou <code>↓</code>, pour le déplacer à la ligne
  222. inférieure ;</li>
  223. <li><code>l[eft]</code> ou <code>←</code>, pour le déplacer à la colonne
  224. précédente.</li>
  225. </ul>
  226. <p>Nous trouvons aussi des déplacements <strong>semi-absolus</strong> :</p>
  227. <ul>
  228. <li><code>U[P]</code>, pour le déplacer à la première ligne du
  229. <em lang="en">viewport</em> ;</li>
  230. <li><code>R[IGHT]</code>, pour le déplacer à la dernière colonne du
  231. <em lang="en">viewport</em> ;</li>
  232. <li><code>D[OWN]</code>, pour le déplacer à la dernière ligne du
  233. <em lang="en">viewport</em> ;</li>
  234. <li><code>L[EFT]</code>, pour le déplacer à la première colonne du
  235. <em lang="en">viewport</em>.</li>
  236. </ul>
  237. <p>Ces directions peuvent être concaténées par des espaces, ou alors nous
  238. pouvons préciser le nombre de fois où une direction sera répétée.</p>
  239. <pre><code class="language-php">echo
  240. 'abcdef', "\n",
  241. 'ghijkl', "\n",
  242. 'mnopqr', "\n",
  243. 'stuvwx';
  244. sleep(1);
  245. Hoa\Console\Cursor::move('↑');
  246. sleep(1);
  247. Hoa\Console\Cursor::move('↑ ←');
  248. sleep(1);
  249. Hoa\Console\Cursor::move('←', 3);
  250. sleep(1);
  251. Hoa\Console\Cursor::move('DOWN');
  252. sleep(1);
  253. Hoa\Console\Cursor::move('→', 4);</code></pre>
  254. <p>Lors de l'exécution, nous verrons le curseur se déplacer <strong>tout
  255. seul</strong> de « lettre en lettre » toutes les secondes.</p>
  256. <p>Pour réellement déplacer le curseur de manière <strong>absolue</strong>,
  257. nous utiliserons la méthode <code>moveTo</code> qui prend en argument des
  258. coordonnées en <em>colonne</em> × <em>ligne</em> (la numérotation commence à 1
  259. et non pas à 0). Nous en profitons pour parler de la méthode
  260. <code>getPosition</code> qui permet de connaître la <strong>position</strong>
  261. du curseur. Ainsi, si nous voulons déplacer le curseur à la colonne 12 et à la
  262. ligne 7, puis afficher ces coordonnées, nous écrirons :</p>
  263. <pre><code class="language-php">Hoa\Console\Cursor::moveTo(12, 7);
  264. print_r(Hoa\Console\Cursor::getPosition());
  265. /**
  266. * Will output:
  267. * Array(
  268. * [x] => 12
  269. * [y] => 7
  270. * )
  271. */</code></pre>
  272. <p>Enfin, il arrive très régulièrement que nous voulions déplacer le curseur
  273. <strong>temporairement</strong> pour quelques opérations. Dans ce cas, il est
  274. inutile de récupérer la position actuelle, le déplacer, puis le
  275. repositionner ; nous pouvons profiter des méthodes <code>save</code> et
  276. <code>restore</code>. Comme leur nom l'indique, ces méthodes respectivement
  277. <strong>enregistre</strong> la position du curseur puis
  278. <strong>restaure</strong> le curseur à la position précédemment enregistrée.
  279. Ces fonctions ne manipulent pas de <strong>pile</strong>, il est impossible
  280. d'enregistrer plus d'une seule position à la fois (le nouvel enregistrement
  281. <strong>écrasera</strong> l'ancien). Ainsi, nous allons écrire un texte,
  282. enregistrer la position du curseur, revenir en arrière et réécrire par dessus,
  283. pour enfin revenir à notre position précédente :</p>
  284. <pre><code class="language-php">echo 'hello world';
  285. // Save cursor position.
  286. Hoa\Console\Cursor::save();
  287. sleep(1);
  288. // Go to the begining of the line.
  289. Hoa\Console\Cursor::move('LEFT');
  290. sleep(1);
  291. // Replace “h” by “H”.
  292. echo 'H';
  293. sleep(1);
  294. // Go to “w”.
  295. Hoa\Console\Cursor::move('→', 5);
  296. sleep(1);
  297. // Replace “w” by “W”.
  298. echo 'W';
  299. sleep(1);
  300. // Back to the saved position.
  301. Hoa\Console\Cursor::restore();
  302. sleep(1);
  303. echo '!';</code></pre>
  304. <p>Le résultat final sera <samp>Hello World!</samp>. Nous remarquons qu'à
  305. chaque fois qu'un caractère est écrit, le curseur se
  306. <strong>déplace</strong>.</p>
  307. <h3 id="Content" for="main-toc">Affichage</h3>
  308. <p>Maintenant que le déplacement est acquis, nous allons voir comment
  309. <strong>nettoyer</strong> des lignes et/ou des colonnes. Pour cela, nous nous
  310. appuyons sur la méthode <code>clear</code> qui prend en argument les symboles
  311. suivants (concaténés par un espace) :</p>
  312. <ul>
  313. <li><code>a[ll]</code> ou <code>↕</code>, pour nettoyer tout l'écran et
  314. déplacer le curseur en haut à gauche du <em lang="en">viewport</em> ;</li>
  315. <li><code>u[p]</code> ou <code>↑</code>, pour nettoyer toutes les lignes
  316. au-dessus du curseur ;</li>
  317. <li><code>r[ight]</code> ou <code>→</code>, pour nettoyer le reste de la
  318. ligne à partir du curseur ;</li>
  319. <li><code>d[own]</code> ou <code>↓</code>, pour nettoyer toutes les lignes
  320. en-dessous du curseur ;</li>
  321. <li><code>l[eft]</code> ou <code>←</code>, pour nettoyer du début de la
  322. ligne jusqu'au curseur ;</li>
  323. <li><code>line</code> ou <code>↔</code>, pour nettoyer toute la ligne et
  324. déplacer le curseur en début de ligne.</li>
  325. </ul>
  326. <p>Ainsi, pour nettoyer <strong>toute une ligne</strong> :</p>
  327. <pre><code class="language-php">Hoa\Console\Cursor::clear('↔');</code></pre>
  328. <p>Le curseur peut aussi agir comme un <strong>pinceau</strong> et ainsi
  329. écrire avec différentes <strong>couleurs</strong> ou différents
  330. <strong>styles</strong> grâce à la méthode <code>colorize</code> (nous pouvons
  331. tout mélanger en séparant chaque « commande » par des espaces). Commençons
  332. par énumérer les styles :</p>
  333. <ul>
  334. <li><code>n[ormal]</code>, pour annuler tous les styles appliqués ;</li>
  335. <li><code>b[old]</code>, pour écrire en gras ;</li>
  336. <li><code>u[nderlined]</code>, pour avoir un texte souligné ;</li>
  337. <li><code>bl[ink]</code>, pour avoir un texte qui clignote ;</li>
  338. <li><code>i[nverse]</code>, pour inverser les couleurs d'avant et
  339. d'arrière-plan ;</li>
  340. <li><code>!b[old]</code>, pour annuler le gras ;</li>
  341. <li><code>!u[nderlined]</code>, pour annuler le soulignement ;</li>
  342. <li><code>!bl[ink]</code>, pour annuler le clignotement ;</li>
  343. <li><code>!i[nverse]</code>, pour ne plus inverser les couleurs d'avant et
  344. d'arrière-plan.</li>
  345. </ul>
  346. <p>Ces styles sont très classiques. Passons maintenant aux couleurs. Tout
  347. d'abord, nous devons préciser si nous appliquons une couleur sur
  348. l'<strong>avant-plan</strong> du texte, soit le texte lui-même, ou alors sur
  349. son <strong>arrière-plan</strong>. Pour cela, nous allons nous aider
  350. respectivement de la syntaxe <code>f[ore]g[round](<em>color</em>)</code> et
  351. <code>b[ack]g[round](<em>color</em>)</code>. La valeur de
  352. <code><em>color</em></code> peut être :</p>
  353. <ul>
  354. <li><code>default</code>, pour reprendre la couleur par défaut du
  355. plan ;</li>
  356. <li><code>black</code>, <code>red</code>, <code>green</code>,
  357. <code>yellow</code>, <code>blue</code>, <code>magenta</code>,
  358. <code>cyan</code> ou <code>white</code>, respectivement pour noir, rouge,
  359. vert, jaune, bleu, magenta, cyan ou blanc ;</li>
  360. <li>un numéro entre <code>0</code> et <code>256</code>, correspondant au
  361. numéro de la couleur dans la palette des 256 couleurs ;</li>
  362. <li><code>#<em>rrggbb</em></code> où <code><em>rrggbb</em></code> est un
  363. nombre en hexadécimal correspondant au numéro de la couleur dans la palette
  364. des 2<sup>64</sup> couleurs.</li>
  365. </ul>
  366. <p>Les terminaux manipulent <strong>une</strong> des deux palettes : 8
  367. couleurs ou 256 couleurs. Chaque couleur est <strong>indexée</strong> à partir
  368. de 0. Les noms des couleurs sont <strong>transformés</strong> vers leur index
  369. respectif. Quand une couleur est précisée en hexadécimal, elle est
  370. <strong>rapportée</strong> à la couleur la plus proche dans la palette
  371. comportant 256 couleurs.</p>
  372. <p>Ainsi, si nous voulons écrire <samp>Hello</samp> en jaune sur fond presque
  373. rouge (<code>#932e2e</code>) et en plus souligné, puis <samp> world</samp>
  374. mais non-souligné :</p>
  375. <pre><code class="language-php">Hoa\Console\Cursor::colorize('fg(yellow) bg(#932e2e) underlined');
  376. echo 'Hello';
  377. Hoa\Console\Cursor::colorize('!underlined');
  378. echo ' world';</code></pre>
  379. <p>Enfin, il est possible de modifier les palettes de couleurs grâce à la
  380. méthode <code>changeColor</code>, mais c'est à utiliser avec
  381. <strong>précaution</strong>, cela peut perturber l'utilisateur. Cette méthode
  382. prend en premier argument l'index de la couleur et en second argument sa
  383. valeur en hexadécimal. Par exemple, <code>fg(yellow)</code> correspond à
  384. l'index <code>33</code>, et nous voulons que ce soit maintenant totalement
  385. bleu :</p>
  386. <pre><code class="language-php">Hoa\Console\Cursor::changeColor(33, 0xf00);</code></pre>
  387. <p>Toutefois, la palette de 256 couleurs est suffisamment
  388. <strong>complète</strong> pour ne pas avoir besoin de modifier les
  389. couleurs.</p>
  390. <h3 id="Style" for="main-toc">Style</h3>
  391. <p>Le curseur n'est pas forcément toujours visible. Lors de certaines
  392. opérations, nous pouvons le <strong>cacher</strong>, effectuer nos
  393. déplacements, puis le rendre à nouveau <strong>visible</strong>. Les méthodes
  394. <code>hide</code> et <code>show</code>, toujours sur
  395. <code>Hoa\Console\Cursor</code>, sont là pour ça :</p>
  396. <pre><code class="language-php">echo 'Visible', "\n";
  397. sleep(5);
  398. echo 'Invisible', "\n";
  399. Hoa\Console\Cursor::hide();
  400. sleep(5);
  401. echo 'Visible', "\n";
  402. Hoa\Console\Cursor::show();
  403. sleep(5);</code></pre>
  404. <p>Il existe aussi trois <strong>types</strong> de curseurs, que nous pouvons
  405. choisir avec la méthode <code>setStyle</code> :</p>
  406. <ul>
  407. <li><code>b[lock]</code> ou <code>▋</code>, pour un curseur en forme de
  408. bloc ;</li>
  409. <li><code>u[nderline]</code> ou <code>_</code>, pour un curseur en forme de
  410. trait de soulignement ;</li>
  411. <li><code>v[ertical]</code> ou <code>|</code>, pour un curseur en forme de
  412. barre vertical.</li>
  413. </ul>
  414. <p>Cette méthode prend en second argument un booléen indiquant si le curseur
  415. doit <strong>clignoter</strong> (valeur par défaut) ou pas. Ainsi, nous allons
  416. faire tous les styles :</p>
  417. <pre><code class="language-php">echo 'Block/steady: ';
  418. Hoa\Console\Cursor::setStyle('▋', false);
  419. sleep(3);
  420. echo "\n", 'Vertical/blink: ';
  421. Hoa\Console\Cursor::setStyle('|', true);
  422. sleep(3);
  423. // etc.</code></pre>
  424. <p>Souvent le curseur indique des <strong>zones</strong> ou éléments
  425. d'<strong>interactions</strong> différents, comme le pointeur de la
  426. souris.</p>
  427. <h3 id="Sound" for="main-toc">Son</h3>
  428. <p>Le curseur est aussi capable d'émettre un petit « bip », souvent pour
  429. <strong>attirer</strong> l'attention de l'utilisateur. Nous allons utiliser la
  430. méthode éponyme <code>bip</code> :</p>
  431. <pre><code class="language-php">Hoa\Console\Cursor::bip();</code></pre>
  432. <p>Il n'y a qu'une seule <strong>tonalité</strong> disponible.</p>
  433. <h2 id="Readline" for="main-toc">Lecture en ligne</h2>
  434. <p>Une manière d'<strong>interagir</strong> avec les utilisateurs est de lire
  435. le flux <code>STDIN</code>, à savoir le flux d'entrée. Cette
  436. <strong>lecture</strong> est par défaut très basique : impossible d'effacer,
  437. impossible d'utiliser les flèches, impossible d'utiliser des raccourcis etc.
  438. C'est pourquoi il existe la « lecture en ligne », ou
  439. <em lang="en">readline</em> en anglais, qui reste une lecture sur le flux
  440. <code>STDIN</code>, mais plus <strong>évoluée</strong>. La bibliothèque
  441. <code>Hoa\Console\Readline\Readline</code> propose plusieurs fonctionnalités
  442. que nous allons décrire.</p>
  443. <h3 id="Basic_usage" for="main-toc">Usage basique</h3>
  444. <p>Pour <strong>lire une ligne</strong> (c'est à dire une entrée de
  445. l'utilisateur), nous allons instancier la classe
  446. <code>Hoa\Console\Readline\Readline</code> et appeler dessus la méthode
  447. <code>readLine</code>. Chaque appel de cette méthode va attendre que
  448. l'utilisateur <strong>saisisse</strong> une donnée puis appuye sur
  449. <kbd title="Enter">↵</kbd>. À ce moment là, la méthode retournera la saisie de
  450. l'utilisateur (ou <code>false</code> s'il n'y a plus rien à lire). Cette
  451. méthode prend aussi en argument un <strong>préfixe</strong>, c'est à dire une
  452. donnée à afficher avant la saisie de la ligne. Il arrive que le terme
  453. <em>prompt</em> soit aussi utilisé dans la littérature, les deux notions sont
  454. identiques.</p>
  455. <p>Ainsi, nous allons écrire un programme qui va lire les entrées de
  456. l'utilisateur et faire un écho. Le programme terminera si l'utilisateur saisit
  457. <samp>quit</samp> :</p>
  458. <pre><code class="language-php">$rl = new Hoa\Console\Readline\Readline();
  459. do {
  460. $line = $rl->readLine('> ');
  461. echo '&amp;lt; ', $line, "\n\n";
  462. } while (false !== $line &amp;amp;&amp;amp; 'quit' !== $line);</code></pre>
  463. <p>Maintenant, détaillons les services que nous offre
  464. <code>Hoa\Console\Readline\Readline</code>.</p>
  465. <p>Nous sommes capables de nous <strong>déplacer</strong> (comprendre,
  466. déplacer le curseur) dans la ligne à l'aide des touches <kbd>←</kbd> et
  467. <kbd>→</kbd>. Nous pouvons à tout moment <strong>effacer</strong> un caractère
  468. en arrière avec la touche <kbd title="Backspace">⌫</kbd> ou tous les
  469. caractères jusqu'au début du mot avec <kbd>Ctrl</kbd> + <kbd>W</kbd> (où
  470. <kbd>W</kbd> signifie <em lang="en">word</em>). Nous pouvons également nous
  471. déplacer avec des <strong>raccourcis</strong> claviers communs à beaucoup de
  472. logiciels :</p>
  473. <ul>
  474. <li><kbd>Ctrl</kbd> + <kbd>A</kbd>, pour se déplacer en début de
  475. ligne ;</li>
  476. <li><kbd>Ctrl</kbd> + <kbd>E</kbd>, pour se déplacer en fin de ligne ;</li>
  477. <li><kbd>Ctrl</kbd> + <kbd>B</kbd>, pour se déplacer au début du mot courant
  478. (<kbd>B</kbd> pour <em lang="en">backward</em>) ;</li>
  479. <li><kbd>Ctrl</kbd> + <kbd>F</kbd>, pour se déplacer en fin du mot courant
  480. (<kbd>F</kbd> pour <em lang="en">forward</em>).</li>
  481. </ul>
  482. <p>Nous avons aussi accès à l'<strong>historique</strong> lorsque nous
  483. appuyons sur les touches <kbd>↑</kbd> et <kbd>↓</kbd>, respectivement pour
  484. chercher en arrière et avant dans l'historique. La touche
  485. <kbd title="Tabulation">⇥</kbd> déclenche l'<strong>auto-complétion</strong>
  486. si elle est définie. Et enfin, la touche <kbd title="Enter">↵</kbd> retourne
  487. la saisie.</p>
  488. <p>Il existe aussi la classe <code>Hoa\Console\Readline\Password</code> qui
  489. permet d'avoir un lecteur de lignes avec exactement les mêmes services mais
  490. les caractères <strong>ne s'impriment pas</strong> à l'écran, très utile pour
  491. lire un <strong>mot de passe</strong> :</p>
  492. <pre><code class="language-php">$rl = new Hoa\Console\Readline\Password();
  493. $pwd = $rl->readLine('Password: ');
  494. echo 'Your password is: ', $pwd, "\n";</code></pre>
  495. <h3 id="Shortcuts" for="main-toc">Raccourcis</h3>
  496. <p>Pour comprendre comment créer des raccourcis, il faut un tout petit peu
  497. comprendre le fonctionnement <strong>interne</strong> de
  498. <code>Hoa\Console\Readline\Readline</code>, et il est très simple. À chaque
  499. fois que nous appuyons sur une ou plusieurs touches, une
  500. <strong>chaîne</strong> de caractères représentant cette
  501. <strong>combinaison</strong> est reçue par notre lecteur. Il regarde si une
  502. action est associée à cette chaîne : si oui, il l'exécute, si non, il en
  503. utilise une par défaut qui consiste à afficher la chaîne telle quelle. Chaque
  504. action retourne un <strong>état</strong> pour le lecteur (qui sont des
  505. constantes sur <code>Hoa\Console\Readline\Readline</code>) :</p>
  506. <ul>
  507. <li><code>STATE_CONTINUE</code>, pour continuer la lecture ;</li>
  508. <li><code>STATE_BREAK</code>, pour arrêter la lecture ;</li>
  509. <li><code>STATE_NO_ECHO</code>, pour ne pas afficher la lecture.</li>
  510. </ul>
  511. <p>Ainsi, si une action retourne <code class="language-php">STATE_CONTINUE |
  512. STATE_NO_ECHO</code>, la lecture continuera mais la chaîne qui vient d'être
  513. reçue ne sera pas affichée. Autre exemple, l'action associée à la touche
  514. <kbd title="Enter">↵</kbd> retourne l'état <code>STATE_BREAK</code>.</p>
  515. <p>Pour <strong>ajouter</strong> des actions, nous utilisons la méthode
  516. <code>addMapping</code>. Elle facilite l'ajout grâce à une syntaxe
  517. dédiée :</p>
  518. <ul>
  519. <li><code>\e[<em>…</em></code>, pour les séquences commençant par le
  520. caractère <kbd>Esc</kbd> ;</li>
  521. <li><code>\C-<em>…</em></code>, pour les séquences commençant par le
  522. caractère <kbd>Ctrl</kbd> ;</li>
  523. <li><code><em>x</em></code>, n'importe quel caractère.</li>
  524. </ul>
  525. <p>Par exemple, si nous voulons afficher <code>z</code> à la place de
  526. <code>a</code>, nous écrirons :</p>
  527. <pre><code class="language-php">$rl->addMapping('a', 'z');</code></pre>
  528. <p>Plus compliqué maintenant, nous pouvons utiliser un
  529. <em lang="en">callable</em> en second paramètre de
  530. <code>addMapping</code>. Ce <em lang="en">callable</em> va recevoir l'instance
  531. de <code>Hoa\Console\Readline\Readline</code> en seul argument. Plusieurs
  532. méthodes sont là pour aider à <strong>manipuler</strong> le lecteur (gestion
  533. de l'historique, de la ligne etc.). Par exemple, à chaque fois que nous
  534. appuyerons sur <kbd>Ctrl</kbd> + <kbd>R</kbd>, nous inverserons la casse de la
  535. ligne :</p>
  536. <pre><code class="language-php">$rl = new Hoa\Console\Readline\Readline();
  537. // Add mapping.
  538. $rl->addMapping('\C-R', function (Hoa\Console\Readline\Readline $self) {
  539. // Clear the line.
  540. Hoa\Console\Cursor::clear('↔');
  541. echo $self->getPrefix();
  542. // Get the line text.
  543. $line = $self->getLine();
  544. // New line.
  545. $new = null;
  546. // Loop over all characters.
  547. for ($i = 0, $max = $self->getLineLength(); $i &amp;lt; $max; ++$i) {
  548. $char = mb_substr($line, $i, 1);
  549. if ($char === $lower = mb_strtolower($char)) {
  550. $new .= mb_strtoupper($char);
  551. } else {
  552. $new .= $lower;
  553. }
  554. }
  555. // Set the new line.
  556. $self->setLine($new);
  557. // Set the buffer (and let the readline echoes or not).
  558. $self->setBuffer($new);
  559. // The readline will continue to read.
  560. return $self::STATE_CONTINUE;
  561. });
  562. // Try!
  563. var_dump($rl->readLine('> '));</code></pre>
  564. <p>Il ne faut pas hésiter à regarder comment sont implémentés les raccourcis
  565. précédemment énoncés pour se donner des idées.</p>
  566. <h3 id="Auto-completion" for="main-toc">Auto-complétion</h3>
  567. <p>Un outil également très utile lorsque nous écrivons un lecteur de lignes
  568. est l'<strong>auto-complétion</strong>. Elle se déclenche en appuyant sur la
  569. touche <kbd title="Tabulation">⇥</kbd> si un auto-compléteur a été défini à
  570. l'aide de la méthode <code>setAutocompleter</code>.</p>
  571. <p>Tous les auto-compléteurs doivent implémenter l'interface
  572. <code>Hoa\Console\Readline\Autocompleter\Autocompleter</code>. Quelqu'uns sont
  573. déjà présents pour nous <strong>aider</strong> dans notre développement, comme
  574. <code>Hoa\Console\Readline\Autocompleter\Word</code> qui va auto-compléter la
  575. saisie à partir d'une <strong>liste de mots</strong>. Par exemple :</p>
  576. <pre><code class="language-php">$rl = new Hoa\Console\Readline\Readline();
  577. $rl->setAutocompleter(new Hoa\Console\Readline\Autocompleter\Word([
  578. 'hoa',
  579. 'console',
  580. 'readline',
  581. 'autocompleter',
  582. 'autocompletion',
  583. 'password',
  584. 'awesome'
  585. ]));
  586. var_dump($rl->readLine('> '));</code></pre>
  587. <p>Essayons d'écrire ce que nous voulons, puis où nous le souhaitons, appuyons
  588. sur <kbd title="Tabulation">⇥</kbd>. Si le texte à gauche du curseur commence
  589. par <code>h</code>, alors nous verrons <samp>hoa</samp> s'écrire <strong>d'un
  590. coup</strong> car l'auto-compléteur n'a pas de choix (il retourne une chaîne).
  591. Si l'auto-compléteur ne trouve aucun mot adapté, il ne se passera
  592. <strong>rien</strong> (il retournera <code>null</code>). Et enfin, s'il
  593. trouve <strong>plusieurs mots</strong> (il retournera un tableau), alors un
  594. <strong>menu</strong> s'affichera. Essayons d'auto-compléter simplement
  595. <code>a</code> : le menu proposera <code>autocompleter</code>,
  596. <samp>autocompletion</samp> et <samp>awesome</samp>. Soit nous continuons à
  597. taper et le menu va <strong>disparaître</strong>, soit nous pouvons nous
  598. <strong>déplacer</strong> dans le menu avec les touches
  599. <kbd title="Tabulation">⇥</kbd>, <kbd>↑</kbd>, <kbd>→</kbd>, <kbd>↓</kbd> et
  600. <kbd>←</kbd>, puis <kbd title="Enter">↵</kbd> pour
  601. <strong>sélectionner</strong> un mot. Le comportement est assez
  602. <strong>naturel</strong>.</p>
  603. <p>En plus de l'auto-compléteur sur les mots, nous trouvons un auto-compléteur
  604. sur les <strong>chemins</strong> avec la classe
  605. <code>Hoa\Console\Readline\Autocompleter\Path</code>. À partir d'une racine et
  606. d'un itérateur de fichiers, il est capable d'auto-compléter des chemins. Si la
  607. racine n'est pas précisée, le dossier courant sera utilisé. À chaque
  608. auto-complétion, une nouvelle instance de l'itérateur de fichiers est créée
  609. par une <em lang="en">factory</em>. Elle reçoit en seul argument le chemin à
  610. itérer. La <em lang="en">factory</em> par défaut est définie par la méthode
  611. statique <code>getDefaultIteratorFactory</code> sur
  612. <code>Hoa\Console\Readline\Autocompleter\Path</code>. Elle construit un
  613. itérateur de fichiers de type
  614. <a href="http://php.net/directoryiterator"><code>DirectoryIterator</code></a>.
  615. Chaque valeur calculée par l'itérateur doit être un objet de type
  616. <a href="http://php.net/splfileinfo"><code>SplFileInfo</code></a>. Ainsi, pour
  617. auto-compléter tous les fichiers et dossiers à partir de la racine
  618. <a href="@central_resource:path=Library/Console"><code>hoa://Library/Console</code></a>,
  619. nous écrirons :</p>
  620. <pre><code class="language-php">$rl->setAutocompleter(
  621. new Hoa\Console\Readline\Autocompleter\Path(
  622. resolve('hoa://Library/Console')
  623. )
  624. );</code></pre>
  625. <p>Utiliser une <em lang="en">factory</em> nous offre beaucoup de
  626. <strong>souplesse</strong> et nous permet d'utiliser n'importe quel itérateur
  627. de fichiers, comme par exemple <code>Hoa\File\Finder</code> (voir
  628. <a href="@hack:chapter=File">la bibliothèque <code>Hoa\File</code></a>).
  629. Ainsi, pour n'auto-compléter que les fichiers et dossiers non cachés qui ont
  630. été modifiés les 6 derniers mois triés par leur taille, nous écrirons :</p>
  631. <pre><code class="language-php">$rl->setAutocompleter(
  632. new Hoa\Console\Readline\Autocompleter\Path(
  633. resolve('hoa://Library/Console'),
  634. function ($path) {
  635. $finder = new Hoa\File\Finder();
  636. $finder->in($path)
  637. ->files()
  638. ->directories()
  639. ->maxDepth(1)
  640. ->name('#^(?!\.).#')
  641. ->modified('since 6 months')
  642. ->sortBySize();
  643. return $finder;
  644. }
  645. )
  646. );</code></pre>
  647. <p>Nous pouvons remplacer l'itérateur de fichiers locaux par un itérateur
  648. totalement <strong>différent</strong> : sur des fichiers stockés sur une autre
  649. machine, un service tiers ou même des ressources qui ne sont pas des fichiers
  650. mais ont des URI de la forme d'un chemin.</p>
  651. <p>Enfin, nous pouvons assembler plusieurs auto-compléteurs entre eux grâce à
  652. la classe <code>Hoa\Console\Readline\Autocompleter\Aggregate</code>. L'ordre
  653. de déclaration des auto-compléteurs est important : le premier qui reconnaît
  654. un mot à auto-compléter prendra la main. Ainsi, pour auto-compléter des
  655. chemins et des mots, nous écrirons :</p>
  656. <pre><code class="language-php">$rl->setAutocompleter(
  657. new Hoa\Console\Readline\Autocompleter\Aggregate([
  658. new Hoa\Console\Readline\Autocompleter\Path(),
  659. new Hoa\Console\Readline\Autocompleter\Word($words)
  660. ])
  661. );
  662. </code></pre>
  663. <p>La méthode <code>getAutocompleters</code> de
  664. <code>Hoa\Console\Readline\Autocompleter\Aggregate</code> retourne un objet
  665. <a href="http://php.net/arrayobject"><code>ArrayObject</code></a> pour plus de
  666. souplesse. Nous pouvons ainsi toujours ajouter ou supprimer des
  667. auto-compléteurs après les avoir déclarés dans le constructeur.</p>
  668. <figure>
  669. <img src="https://central.hoa-project.net/Resource/Library/Console/Documentation/Image/Readline_autocompleters.gif?format=raw" />
  670. <figcaption>Exemple d'une agrégation de l'auto-compléteur
  671. <code>Hoa\Console\Readline\Autocompleter\Path</code> avec
  672. <code>Hoa\Console\Readline\Autocompleter\Word</code>.</figcaption>
  673. </figure>
  674. <h2 id="Reading_options" for="main-toc">Lecture d'options</h2>
  675. <p>Une grande force des programmes en ligne de commande est leur
  676. <strong>flexibilité</strong>. Ils sont <strong>dédiés</strong> à une seule
  677. (petite) <strong>tâche</strong> et nous pouvons les paramétrer grâce aux
  678. <strong>options</strong> qu'ils exposent. La <strong>lecture</strong> de ces
  679. options doit être simple et rapide car c'est une tâche répétitive et délicate.
  680. La classe <code>Hoa\Console\Parser</code> et
  681. <code>Hoa\Console\GetOption</code> fonctionnent en <strong>duo</strong> afin
  682. de répondre à cette problématique.</p>
  683. <h3 id="Analyzing_options" for="main-toc">Analyser les options</h3>
  684. <p>Nous allons commencer par utiliser <code>Hoa\Console\Parser</code> qui
  685. permet d'<strong>analyser</strong> les options données à un programme. Peu
  686. importe les options que nous voulons précisément, nous nous contentons de les
  687. analyser pour l'instant. Commençons par utiliser la méthode
  688. <code>parse</code> :</p>
  689. <pre><code class="language-php">$parser = new Hoa\Console\Parser();
  690. $parser->parse('-s --long=value input');
  691. print_r($parser->getSwitches());
  692. print_r($parser->getInputs());
  693. /**
  694. * Will output:
  695. * Array
  696. * (
  697. * [s] => 1
  698. * [long] => value
  699. * )
  700. * Array
  701. * (
  702. * [0] => input
  703. * )
  704. */</code></pre>
  705. <p>Étudions un peu de quoi est constituée une ligne de commande. Nous avons
  706. deux catégories : les <strong>options</strong> (<em lang="en">switches</em>)
  707. et les <strong>entrées</strong> (<em lang="en">inputs</em>). Les entrées sont
  708. tout ce qui n'est pas une option. Une option peut avoir deux formes :
  709. <strong>courte</strong> si elle n'a qu'un seul caractère ou
  710. <strong>longue</strong> si elle en a plusieurs.</p>
  711. <p>Ainsi, <code>-s</code> est une option courte, et <code>--long</code> est
  712. une option longue. Toutefois, il faut aussi considérer le nombre de tirets
  713. devant l'option : avec deux tirets, ce sera toujours une option longue, avec
  714. un seul tiret, ça dépend. Il y a deux écoles qui se différencient avec un seul
  715. <strong>paramètre</strong> : <em lang="en">long only</em>. Prenons un
  716. exemple : <code>-abc</code> est considéré comme <code>-a -b -c</code> si le
  717. paramètre <em lang="en">long only</em> est définie à <code>false</code>, sinon
  718. ce sera équivalent à une option longue, comme <code>--abc</code>.
  719. Majoritairement, ce paramètre est définie à <code>false</code> par défaut et
  720. <code>Hoa\Console\Parser</code> s'est rangé du côté de la majorité. Pour
  721. modifier ce paramètre, il faut utiliser la méthode <code>setLongOnly</code>,
  722. voyons plutôt :</p>
  723. <pre><code class="language-php">// long only is set to false.
  724. $parser->parse('-abc');
  725. print_r($parser->getSwitches());
  726. $parser->setLongOnly(true);
  727. // long only is set to true.
  728. $parser->parse('-abc');
  729. print_r($parser->getSwitches());
  730. /**
  731. * Will output:
  732. * Array
  733. * (
  734. * [a] => 1
  735. * [b] => 1
  736. * [c] => 1
  737. * )
  738. * Array
  739. * (
  740. * [abc] => 1
  741. * )
  742. */</code></pre>
  743. <p>Une option peut être de deux sortes : <strong>booléenne</strong> ou
  744. <strong>valuée</strong>. Si aucune valeur ne lui est associée, elle est
  745. considérée comme booléenne. Ainsi, <code>-s</code> vaut <code>true</code>,
  746. mais <code>-s -s</code> vaut <code>false</code>, et du coup <code>-s -s
  747. -s</code> vaut <code>true</code> et ainsi de suite. Une option booléenne
  748. fonctionne comme un <strong>interrupteur</strong>. Une option valuée a une
  749. valeur associée, soit par un espace, soit par un signe d'égalité (symbole
  750. <code>=</code>). Voici une liste non-exhaustive des possibilités avec la
  751. valeur associée (nous utilisons une option courte mais ça peut être une option
  752. longue) :</p>
  753. <ul>
  754. <li><code>-x=value</code> : <code>value</code> ;</li>
  755. <li><code>-x=va\ lue</code> : <code>va lue</code> ;</li>
  756. <li><code>-x="va lue"</code> : <code>va lue</code> ;</li>
  757. <li><code>-x="va l\"ue"</code> : <code>va l"ue</code> ;</li>
  758. <li><code>-x value</code> : <code>value</code> ;</li>
  759. <li><code>-x va\ lue</code> : <code>va lue</code> ;</li>
  760. <li><code>-x "value"</code> : <code>value</code> ;</li>
  761. <li><code>-x "va lue"</code> : <code>va lue</code> ;</li>
  762. <li><code>-x va\ l"ue</code> : <code>va l"ue</code> ;</li>
  763. <li><code>-x 'va "l"ue'</code> : <code>va "l"ue</code> ;</li>
  764. <li>etc.</li>
  765. </ul>
  766. <p>Les simples (symbole <code>'</code>) et doubles (symbole <code>"</code>)
  767. guillemets sont supportés. Mais attention, il y a des cas particuliers qui ne
  768. sont pas toujours <strong>standards</strong> :</p>
  769. <ul>
  770. <li><code>-x=-value</code> : <code>-value</code> ;</li>
  771. <li><code>-x "-value"</code> : <code>-value</code> ;</li>
  772. <li><code>-x \-value</code> : <code>-value</code> ;</li>
  773. <li><code>-x -value</code> : équivaut à deux options booléennes
  774. <code>-x</code> et <code>-value</code> ;</li>
  775. <li><code>-x=-7</code> : <code>-7</code> ;</li>
  776. <li>etc.</li>
  777. </ul>
  778. <p><em>À l'instar</em> des options booléennes qui fonctionnent comme des
  779. interrupteurs, les options valuées <strong>réécrivent</strong> leurs valeurs
  780. si elles sont déclarées plusieurs fois. Ainsi avec <code>-a=b -a=c</code>,
  781. <code>-a</code> vaudra <code>c</code>.</p>
  782. <p>Enfin, il y a des valeurs qui sont considérées comme
  783. <strong>spéciales</strong>. Nous en distingons deux :</p>
  784. <ul>
  785. <li>les <strong>listes</strong>, à l'aide de la virgule comme séparateur :
  786. <code>-x=a,b,c</code> ;</li>
  787. <li>les <strong>intervalles</strong>, à l'aide du symbole <code>:</code>
  788. (sans espace autour) : <code>-x=1:7</code>.</li>
  789. </ul>
  790. <p>Sans aucune manipulation, ces valeurs ne seront pas considérées comme
  791. spéciales. Il faudra utiliser la méthode
  792. <code>Hoa\Console\Parser::parseSpecialValue</code> comme nous allons le voir
  793. très prochainement.</p>
  794. <h3 id="Read_options_and_inputs" for="main-toc">Lire les options et les
  795. entrées</h3>
  796. <p>Nous savons analyser les options mais ce n'est pas suffisant pour les lire
  797. correctement. Il faut leur donner une petite <strong>sémantique</strong> :
  798. qu'attendent-elles, quelle est leur nature etc. Pour cela, nous allons nous
  799. aider de la classe <code>Hoa\Console\GetOption</code>. Une option est
  800. caractérisée par :</p>
  801. <ul>
  802. <li>un nom <strong>long</strong> ;</li>
  803. <li>un nom <strong>court</strong> ;</li>
  804. <li>un <strong>type</strong>, donné par une des constantes de
  805. <code>Hoa\Console\GetOption</code>, parmi :
  806. <ul>
  807. <li><code>NO_ARGUMENT</code> si l'option est booléenne ;</li>
  808. <li><code>REQUIRED_ARGUMENT</code> si l'option est valuée ;</li>
  809. <li><code>OPTIONAL_ARGUMENT</code> si l'option peut avoir une
  810. valeur.</li>
  811. </ul>
  812. </li>
  813. </ul>
  814. <p>Ces trois informations sont <strong>obligatoires</strong>. Elles doivent
  815. être données au constructeur de <code>Hoa\Console\GetOption</code> en premier
  816. argument. Le second argument est l'analyseur d'options (l'analyse doit être
  817. <strong>préalablement</strong> effectuée). Ainsi nous décrivons deux options :
  818. <code>extract</code> qui est une option booléenne, et <code>directory</code>
  819. qui est une option valuée :</p>
  820. <pre><code class="language-php">$parser = new Hoa\Console\Parser();
  821. $parser->parse('-x --directory=value inputA inputB inputC');
  822. $options = new Hoa\Console\GetOption(
  823. [
  824. // long name type short name
  825. // ↓ ↓ ↓
  826. ['extract', Hoa\Console\GetOption::NO_ARGUMENT, 'x'],
  827. ['directory', Hoa\Console\GetOption::REQUIRED_ARGUMENT, 'd']
  828. ],
  829. $parser
  830. );</code></pre>
  831. <p>Nous pouvons maintenant lire nos options ! Le lecteur d'options fonctionne
  832. comme un itérateur, ou plutôt une <strong>pipette</strong>, à l'aide de la
  833. méthode <code>getOption</code>. Cette méthode retourne le nom court de
  834. l'option lue et assignera la valeur de l'option (un booléen ou une chaîne de
  835. caractères) à son premier argument passé en référence. Quand la pipette est
  836. vide, la méthode <code>getOption</code> retourne <code>false</code>.
  837. Cette structure peut paraître originale mais elle est pourtant très
  838. <strong>répandue</strong>, vous ne serez pas déroutés en la voyant autre part
  839. (exemples
  840. <a href="http://kernel.org/doc/man-pages/online/pages/man3/getopt.3.html#EXAMPLE"
  841. title="getopt(3), Linux Programmer's Manual">dans Linux</a>,
  842. <a href="http://freebsd.org/cgi/man.cgi?query=getopt&amp;sektion=3#EXAMPLES"
  843. title="getopt(3), FreeBSD Library Functions Manual">dans FreeBSD</a> ou
  844. <a href="http://developer.apple.com/library/Mac/#documentation/Darwin/Reference/ManPages/man3/getopt.3.html"
  845. title="getopt(3), BSD Library Functions Manual">dans Mac OS X</a> — même
  846. base de code —). La manière la plus simple pour lire les options est de
  847. définir des valeurs par défaut pour nos options, puis d'utiliser
  848. <code>getOption</code>, ainsi :</p>
  849. <pre><code class="language-php">$extract = false;
  850. $directory = '.';
  851. // short name value
  852. // ↓ ↓
  853. while (false !== $c = $options->getOption($v)) {
  854. switch($c) {
  855. case 'x':
  856. $extract = $v;
  857. break;
  858. case 'd':
  859. $directory = $v;
  860. break;
  861. }
  862. }
  863. var_dump($extract, $directory);
  864. /**
  865. * Will output:
  866. * bool(true)
  867. * string(5) "value"
  868. */</code></pre>
  869. <p>Cela se lit : « tant que nous avons une option à lire, nous récupérons
  870. son nom court dans <code>$c</code> et sa valeur dans <code>$v</code>, puis
  871. nous regardons quoi en faire ».</p>
  872. <p>Pour lire les entrées, nous utiliserons la méthode
  873. <code>Hoa\Console\Parser::listInputs</code> dont tous les arguments (au nombre
  874. de 26) sont passés en <strong>référence</strong>. Ainsi :</p>
  875. <pre><code class="language-php">$parser->listInputs($inputA, $inputB, $inputC);
  876. var_dump($inputA, $inputB, $inputC);
  877. /**
  878. * Will output:
  879. * string(6) "inputA"
  880. * string(6) "inputB"
  881. * string(6) "inputC"
  882. */</code></pre>
  883. <p>Attention, cette façon de procéder implique que les entrées sont
  884. <strong>ordonnées</strong> (comme c'est pratiquement toujours le cas). Mais
  885. aussi, lire les entrées sans avoir préalablement donné l'analyseur à
  886. <code>Hoa\Console\GetOption</code> peut produire des résultats imprévus (car
  887. par défaut, toutes les options sont considérées comme booléennes). Si nous
  888. voulons toutes les entrées et les analyser manuellement si elles ne sont pas
  889. ordonnées, nous pouvons utiliser la méthode
  890. <code>Hoa\Console\Parser::getInputs</code> qui retournera toutes les
  891. entrées.</p>
  892. <h3 id="Special_or_ambiguous_options" for="main-toc">Options spéciales ou
  893. ambiguës</h3>
  894. <p>Revenons sur la méthode <code>Hoa\Console\Parser::parseSpecialValue</code>.
  895. Elle prend deux arguments : une valeur et un tableau de mots-clés. Voyons
  896. plutôt. Nous reprenons notre exemple et modifions le cas pour l'option
  897. <code>d</code> :</p>
  898. <pre data-line="8-11"><code class="language-php">while (false !== $c = $options->getOption($v)) {
  899. switch($c) {
  900. case 'x':
  901. $extract = $v;
  902. break;
  903. case 'd':
  904. $directory = $parser->parseSpecialValue($v, ['HOME' => '/tmp']);
  905. break;
  906. }
  907. }
  908. print_r($directory);</code></pre>
  909. <p>Si nous essayons avec <code>-d=a,b,HOME,c,d</code>, alors <code>-d</code>
  910. aura la valeur suivante :</p>
  911. <pre><code class="language-php">/**
  912. * Array
  913. * (
  914. * [0] => a
  915. * [1] => b
  916. * [2] => /tmp
  917. * [3] => c
  918. * [4] => d
  919. * )
  920. */</code></pre>
  921. <p>Enfin, quand une option lue n'existe pas mais qu'elle est très
  922. <strong>proche</strong> d'une option existante à quelques
  923. <strong>fautes</strong> près (par exemple <code>--dirzctory</code> au lieu de
  924. <code>--directory</code>), nous pouvons utiliser le cas
  925. <code>__ambiguous</code> pour la capturer et la traiter :</p>
  926. <pre data-line="13-16"><code class="language-php">while (false !== $c = $options->getOption($v)) {
  927. switch($c) {
  928. case 'x':
  929. $extract = $v;
  930. break;
  931. case 'd':
  932. $directory = $parser->parseSpecialValue($v, ['HOME' => '/tmp']);
  933. break;
  934. case '__ambiguous':
  935. print_r($v);
  936. break;
  937. }
  938. }</code></pre>
  939. <p>La valeur (dans <code>$v</code>) est un tableau avec trois entrées. Par
  940. exemple avec <code>--dirzctory</code>, nous obtenons :</p>
  941. <pre><code class="language-php">/**
  942. * Array
  943. * (
  944. * [solutions] => Array
  945. * (
  946. * [0] => directory
  947. * )
  948. *
  949. * [value] => y
  950. * [option] => dirzctory
  951. * )
  952. */</code></pre>
  953. <p>La clé <code>solutions</code> propose toutes les options
  954. <strong>similaires</strong>, la clé <code>value</code> donne la valeur de
  955. l'option et <code>option</code> le nom <strong>original</strong> lu. C'est à
  956. l'utilisateur de décider quoi faire à partir de ces informations. Nous pouvons
  957. utiliser la méthode <code>Hoa\Console\GetOption::resolveOptionAmbiguity</code>
  958. en lui donnant ce tableau, et elle choisira la meilleure option si elle existe :</p>
  959. <pre><code class="language-php"> case '__ambiguous':
  960. $options->resolveOptionAmbiguity($v);
  961. break;
  962. </code></pre>
  963. <p>Il est quand même préférable d'<strong>avertir</strong> l'utilisateur qu'il
  964. y a une ambiguïté et de lui demander son avis. Il peut parfois être
  965. <strong>dangereux</strong> de prendre la décision à sa place.</p>
  966. <h3 id="Integrate_a_router_and_a_dispatcher" for="main-toc">Intégrer un
  967. routeur et un dispatcheur</h3>
  968. <p>Jusqu'à maintenant, nous forcions des options et des entrées à l'analyseur.
  969. <code>Hoa\Router\Cli</code> permet d'<strong>extraire</strong> des données
  970. depuis un programme en ligne de commande. Une méthode nous intéresse :
  971. <code>Hoa\Router\Cli::getURI</code>, qui va nous donner toutes les options et
  972. les entrées du programme courant, que nous pourrons alors
  973. <strong>fournir</strong> à notre analyseur. Ainsi :</p>
  974. <pre data-line="2"><code class="language-php">$parser = new Hoa\Console\Parser();
  975. $parser->parse(Hoa\Router\Cli::getURI());
  976. // …</code></pre>
  977. <p>Il est maintenant possible d'interpréter les options que nous donnons à
  978. notre propre programme. Si vous avez écrit les tests dans un fichier nommé
  979. <code>Test.php</code>, alors vous pourrez écrire :</p>
  980. <pre><code class="language-shell">$ php Test.php -x -d=a,b,HOME,c,d inputA inputB
  981. bool(true)
  982. Array
  983. (
  984. [0] => a
  985. [1] => b
  986. [2] => /tmp
  987. [3] => c
  988. [4] => d
  989. )
  990. string(6) "inputA"
  991. string(6) "inputB"
  992. NULL</code></pre>
  993. <p>L'option <code>-x</code> vaut bien <code>true</code>, l'option
  994. <code>-d</code> vaut un tableau (car nous l'avons analysé avec la méthode
  995. <code>Hoa\Console\Parser::parseSpecialValue</code>), et nous avons
  996. <code>inputA</code>, <code>inputB</code> et <code>null</code> en entrée.</p>
  997. <p>C'est un bon début, et nous pourrions nous arrêter là dans la plupart des
  998. cas. Mais il est possible d'aller plus loin en mettant en place un
  999. <strong>dispatcheur</strong> : écrire des commandes dans plusieurs fonctions
  1000. ou classes et les appeler en fonction des options et entrées données à notre
  1001. programme. Nous vous conseillons de regarder le code source de
  1002. <a href="@central_resource:path=Library/Cli/Bin/Hoa.php"><code>hoa://Library/Cli/Bin/Hoa.php</code></a>
  1003. pour vous aider, ainsi que les chapitres de
  1004. <a href="@hack:chapter=Router"><code>Hoa\Router</code></a> et
  1005. <a href="@hack:chapter=Dispatcher"><code>Hoa\Dispatcher</code></a>. Nous
  1006. proposons un exemple rapide sans donner trop de détails sur les bibliothèques
  1007. précédement citées.</p>
  1008. <p>L'idée est la suivante. Grâce à <code>Hoa\Router\Cli</code>, nous allons
  1009. extraire des données de la forme suivante : <code>$ php script.php
  1010. <em>controller</em> <em>tail</em></code>, où <code><em>controller</em></code>
  1011. sera le nom du contrôleur (d'une classe) sur laquelle nous appellerons
  1012. l'action <code>main</code> (soit la méthode <code>main</code> avec les
  1013. paramètres par défaut), et où <code><em>tail</em></code> correspond aux
  1014. options et aux entrées. Le nom du contrôleur est identifié par la variable
  1015. spéciale <code>_call</code> (au niveau de <code>Hoa\Router\Cli</code>) et les
  1016. options ainsi que les entrées par <code>_tail</code> (au niveau de
  1017. <code>Hoa\Dispatcher\Kit</code>). Les options et entrées ne sont pas
  1018. obligatoires. Ensuite, nous allons utiliser <code>Hoa\Dispatcher\Basic</code>
  1019. avec le kit dédié aux terminaux, à savoir
  1020. <code>Hoa\Console\Dispatcher\Kit</code>. Le dispatcheur va chercher à charger
  1021. les classes <code>Application\Controller\<em>controller</em></code> par
  1022. défaut, et l'auto-chargeur va les chercher dans le dossier
  1023. <code>hoa://Application/Controller/<em>controller</em></code>. Nous allons
  1024. donc préciser où se trouve l'application très rapidement. Enfin, le code de
  1025. retour de notre programme sera donné par la valeur de retour de notre
  1026. contrôleur et de notre action. En cas d'erreur, nous l'afficherons et nous
  1027. forcerons un code de retour supérieur à zéro. Ainsi :</p>
  1028. <pre><code class="language-php">try {
  1029. // Prepare the router.
  1030. $router = new Hoa\Router\Cli();
  1031. $router->get(
  1032. 'g',
  1033. '(?&amp;lt;_call>\w+)(?:\s+(?&amp;lt;_tail>.+))?'
  1034. );
  1035. // Prepare the dispatcher.
  1036. $dispatcher = new Hoa\Dispatcher\ClassMethod([
  1037. 'synchronous.call' => 'Application\Controller\(:call:U:)',
  1038. 'synchronous.able' => 'main'
  1039. ]);
  1040. $dispatcher->setKitName('Hoa\Console\Dispatcher\Kit');
  1041. // Dispatch!
  1042. exit($dispatcher->dispatch($router));
  1043. } catch (Hoa\Exception $e) {
  1044. echo $e->raise(true);
  1045. exit($e->getCode() + 1);
  1046. }</code></pre>
  1047. <p>Au même niveau que notre programme, créons le dossier
  1048. <code>Application/Controller/</code> avec le fichier <code>Foo.php</code> à
  1049. l'intérieur, qui contiendra le code suivant :</p>
  1050. <pre><code class="language-php">&amp;lt;?php
  1051. namespace Application\Controller;
  1052. class Foo extends \Hoa\Console\Dispatcher\Kit
  1053. {
  1054. protected $options = [
  1055. ['extract', \Hoa\Console\GetOption::NO_ARGUMENT, 'x'],
  1056. ['directory', \Hoa\Console\GetOption::REQUIRED_ARGUMENT, 'd'],
  1057. ['help', \Hoa\Console\GetOption::NO_ARGUMENT, 'h']
  1058. ];
  1059. public function MainAction()
  1060. {
  1061. $extract = false;
  1062. $directory = '.';
  1063. while (false !== $c = $this->getOption($v)) {
  1064. switch($c) {
  1065. case 'x':
  1066. $extract = $v;
  1067. break;
  1068. case 'd':
  1069. $directory = $this->parser->parseSpecialValue($v, ['HOME' => '/tmp']);
  1070. break;
  1071. case 'h':
  1072. return $this->usage();
  1073. }
  1074. }
  1075. echo 'extract: ';
  1076. var_dump($extract);
  1077. echo 'directory: ';
  1078. print_r($directory);
  1079. return;
  1080. }
  1081. public function usage()
  1082. {
  1083. echo
  1084. 'Usage : foo &amp;lt;options>', "\n",
  1085. 'Options :', "\n",
  1086. $this->makeUsageOptionsList([
  1087. 'x' => 'Whether we need to extract.',
  1088. 'd' => 'Directory to extract.',
  1089. 'h' => 'This help.'
  1090. ]);
  1091. }
  1092. }</code></pre>
  1093. <p>Notre classe étend bien notre kit pour bénéficier des méthodes qu'il
  1094. propose. Entre autre, sa propre méthode <code>getOption</code>, qui va
  1095. exploiter l'attribut <code>$options</code> où sont déclarées les options,
  1096. <code>makeUsageOptionsList</code> pour afficher une aide, sa propre méthode
  1097. <code>resolveOptionAmbiguity</code> qui demande une confirmation à
  1098. l'utilisateur, l'accès au routeur à travers l'attribut <code>$router</code>
  1099. etc. Les kits offrent des <strong>services</strong> à l'application, ils
  1100. <strong>aggrègent</strong> des services offerts par les bibliothèques.
  1101. Maintenant testons :</p>
  1102. <pre><code class="language-shell">$ php Test.php foo -x -d=1:3
  1103. extract: bool(true)
  1104. directory: Array
  1105. (
  1106. [0] => 1
  1107. [1] => 2
  1108. [2] => 3
  1109. )</code></pre>
  1110. <p>Magnifique !</p>
  1111. <p>Précisons que le script <code>hoa</code> est exactement construit de cette
  1112. manière. N'hésitez pas à vous en inspirer.</p>
  1113. <h2 id="Processus" for="main-toc">Processus</h2>
  1114. <p>Dans notre contexte, un <strong>processus</strong> est un programme
  1115. classique qui s'exécute dans un <strong>terminal</strong>. Ce qui est
  1116. intéressant, c'est qu'un tel programme <strong>communique</strong> avec le
  1117. reste de son <strong>environnement</strong> grâce à des
  1118. <strong>tuyaux</strong>, ou <em lang="en">pipes</em> en anglais, numérotés à
  1119. partir de zéro. Certains ont même des noms et sont standards :</p>
  1120. <ul>
  1121. <li><code>STDIN</code> (<code>0</code>) pour lire des
  1122. <strong>entrées</strong> (<em lang="en">standard input</em>) ;</li>
  1123. <li><code>STDOUT</code> (<code>1</code>) pour écrire des
  1124. <strong>sorties</strong> (<em lang="en">standard output</em>) ;</li>
  1125. <li><code>STDERR</code> (<code>2</code>) pour écrire des
  1126. <strong>erreurs</strong> (<em lang="en">standard error</em>).</li>
  1127. </ul>
  1128. <p>Quand un processus s'exécute dans un terminal, <code>STDIN</code> utilise
  1129. le <strong>clavier</strong> comme source de données, et <code>STDOUT</code>
  1130. comme <code>STDERR</code> sont reliés à la <strong>fenêtre</strong> d'un
  1131. terminal. Mais quand un processus est exécuté dans un
  1132. <strong>sous-terminal</strong>, c'est à dire exécuté à partir d'un autre
  1133. processus, <code>STDIN</code> n'est pas relié au clavier, tout comme
  1134. <code>STDOUT</code> et <code>STDERR</code> ne sont pas reliés à l'écran.
  1135. C'est le processus parent qui va écrire et lire sur ces flux pour
  1136. <strong>interagir</strong> avec le « sous »-processus. Ce mécanisme s'appelle
  1137. la <strong>redirection</strong> de flux, nous l'utilisons très souvent quand
  1138. nous écrivons une ligne de commande (voir
  1139. <a href="http://gnu.org/software/bash/manual/bashref.html#Redirections">section
  1140. <em lang="en">Redirections</em> du <em lang="en">Bash Reference
  1141. Manual</em></a>). Ce que nous allons faire utilise une autre syntaxe mais le
  1142. mécanisme est le même.</p>
  1143. <p>Il est très important de savoir que ces flux sont tous
  1144. <strong>asynchrones</strong> les uns par rapport aux autres. Aucun flux n'a
  1145. un impact sur un autre, il n'y a aucun lien entre eux et c'est important pour
  1146. la suite.</p>
  1147. <p>Au niveau de PHP, il est possible d'accéder à ces flux en utilisant
  1148. respectivement les URI suivants : <code>php://stdin</code>,
  1149. <code>php://stdout</code> et <code>php://stderr</code>. Mais nous avons aussi
  1150. les constantes éponymes <code>STDIN</code>, <code>STDOUT</code> et
  1151. <code>STDERR</code>. Elles sont définies comme suit (exemple avec
  1152. <code>STDIN</code>) :</p>
  1153. <pre><code class="language-php">define('STDIN', fopen('php://stdin', 'r'));</code></pre>
  1154. <p>Ces flux ne sont disponibles que si le programme s'exécute en ligne de
  1155. commande. Rappelons-nous également que les <em lang="en">pipes</em> sont
  1156. identifiés par des numéros. Nous pouvons alors utiliser
  1157. <code>php://fd/0</code> pour se référer à <code>STDIN</code>,
  1158. <code>php://fd/1</code> pour <code>STDOUT</code> etc. L'URI
  1159. <code>php://fd/<em>i</em></code> permet d'accéder au fichier ayant le
  1160. <strong>descripteur</strong> <code><em>i</em></code>.</p>
  1161. <h3 id="Very_basic_execution" for="main-toc">Exécution très basique</h3>
  1162. <p>La classe <code>Hoa\Console\Processus</code> propose une manière très
  1163. <strong>rapide</strong> d'exécuter un processus et d'obtenir le résultat de
  1164. <code>STDOUT</code>. C'est le cas le plus commun. Ainsi, nous allons utiliser
  1165. la méthode statique <code>execute</code> :</p>
  1166. <pre><code class="language-php">var_dump(Hoa\Console\Processus::execute('id -u -n'));
  1167. /**
  1168. * Could output:
  1169. * string(3) "hoa"
  1170. */</code></pre>
  1171. <p>Par défaut, la commande sera échappée pour des raisons de sécurité. Si vous
  1172. avez confiance dans la commande, vous pouvez désactiver l'échappement en
  1173. passant <code>false</code> en second argument.</p>
  1174. <p>Nous n'avons aucun contrôle sur les <em lang="en">pipes</em> et même si ça
  1175. convient dans la plupart des cas, ce n'est pas suffisant quand nous souhaitons
  1176. un minimum d'interaction avec le processus.</p>
  1177. <h3 id="Reading_and_writing" for="main-toc">Lecture et écriture</h3>
  1178. <p>Voyons comment <strong>interagir</strong> avec un processus. Nous allons
  1179. considérer le programme <code>LittleProcessus.php</code> suivant :</p>
  1180. <pre><code class="language-php">&amp;lt;?php
  1181. $range = range('a', 'z');
  1182. while (false !== $line = fgets(STDIN)) {
  1183. echo '> ', $range[intval($line)], "\n";
  1184. }</code></pre>
  1185. <p>Pour tester et comprendre son fonctionnement, écrivons la ligne de commande
  1186. suivante et entrons au clavier <code>3</code>, puis <code>4</code> :</p>
  1187. <pre><code class="language-shell">$ php LittleProcessus.php
  1188. 3
  1189. > d
  1190. 4
  1191. > e
  1192. </code></pre>
  1193. <p>Nous pouvons aussi écrire :</p>
  1194. <pre><code class="language-shell">$ seq 0 4 | php LittleProcessus.php
  1195. > a
  1196. > b
  1197. > c
  1198. > d
  1199. > e</code></pre>
  1200. <p>Notre programme va lire chaque ligne sur l'entrée standard, considérer que
  1201. c'est un nombre, et le transformer en caractère qui sera affiché sur la sortie
  1202. standard. Nous aimerions exécuter ce programme en lui donnant nous-même une
  1203. liste de nombres (comme le programme <code>seq</code>) et en observant le
  1204. résultat qu'il produira.</p>
  1205. <p>Une instance de la classe <code>Hoa\Console\Processus</code> représente un
  1206. <strong>processus</strong>. Lors de l'instanciation, nous devons
  1207. préciser :</p>
  1208. <ul>
  1209. <li>le <strong>nom</strong> du processus ;</li>
  1210. <li>ses <strong>options</strong> ;</li>
  1211. <li>la <strong>description</strong> des <em lang="en">pipes</em>.</li>
  1212. </ul>
  1213. <p>Il y a d'autres arguments mais nous les verrons plus tard.</p>
  1214. <p>La description des <em lang="en">pipes</em> a la forme d'un tableau où
  1215. chaque clé représente le numéro du <em lang="en">pipe</em> (plus généralement,
  1216. c'est le <code><em>i</em></code> de <code>php://fd/<em>i</em></code>) et la
  1217. valeur est encore un tableau décrivant la nature du <em lang="en">pipe</em>,
  1218. soit un « vrai » <em lang="en">pipe</em>, soit un fichier, avec leur mode de
  1219. lecture ou d'écriture (parmi <code>r</code>, <code>w</code> ou
  1220. <code>a</code>). Illustrons avec un exemple :</p>
  1221. <pre><code class="language-php">$processus = new Hoa\Console\Processus(
  1222. 'php',
  1223. ['LittleProcessus.php'],
  1224. [
  1225. // STDIN.
  1226. 0 => ['pipe', 'r'],
  1227. // STDOUT.
  1228. 1 => ['file', '/tmp/output', 'a']
  1229. ]
  1230. );</code></pre>
  1231. <p>Dans ce cas, <code>STDIN</code> est un <em lang="en">pipe</em> et
  1232. <code>STDOUT</code> est le fichier <code>/tmp/output</code>. Si nous ne
  1233. précisions pas de descripteur, ce sera équivalent à écrire :</p>
  1234. <pre><code class="language-php">$processus = new Hoa\Console\Processus(
  1235. 'php',
  1236. ['LittleProcessus.php'],
  1237. [
  1238. // STDIN.
  1239. 0 => ['pipe', 'r'],
  1240. // STDOUT.
  1241. 1 => ['pipe', 'w'],
  1242. // STDERR.
  1243. 2 => ['pipe', 'w']
  1244. ]
  1245. );</code></pre>
  1246. <p>Chaque <em lang="en">pipe</em> est reconnu comme un <strong>flux</strong>
  1247. et peut être manipulé comme tel. Quand un <em lang="en">pipe</em> est en
  1248. <strong>lecture</strong> (avec le mode <code>r</code>), cela signifie que le
  1249. processus va <strong>lire</strong> dessus. Donc nous, le processus parent,
  1250. nous allons <strong>écrire</strong> sur ce <em lang="en">pipe</em>. Prenons
  1251. l'exemple de <code>STDIN</code> : le processus lit sur <code>STDIN</code> ce
  1252. que le clavier a écrit dessus. Et inversement, quand un
  1253. <em lang="en">pipe</em> est en <strong>écriture</strong> (avec le mode
  1254. <code>w</code>), cela signifie que nous allons <strong>lire</strong> dessus.
  1255. Prenons l'exemple de <code>STDOUT</code> : l'écran va lire ce que le processus
  1256. lui a écrit.</p>
  1257. <p>La classe <code>Hoa\Console\Processus</code> étend la classe
  1258. <a href="@hack:chapter=Stream"><code>Hoa\Stream</code></a>, et de ce fait,
  1259. nous avons tous les outils nécessaires pour lire et écrire sur les
  1260. <em lang="en">pipes</em> de notre choix. Cette classe propose aussi plusieurs
  1261. <strong>écouteurs</strong> :</p>
  1262. <ul>
  1263. <li><code>start</code>, quand le processus est démarré ;</li>
  1264. <li><code>stop</code>, quand le processus est arrêté ;</li>
  1265. <li><code>input</code>, quand les flux en lecture sont prêts ;</li>
  1266. <li><code>output</code>, quand les flux en écriture sont prêts ;</li>
  1267. <li><code>timeout</code>, quand le processus s'exécute depuis trop
  1268. longtemps.</li>
  1269. </ul>
  1270. <p>Prenons directement un exemple. Nous allons exécuter le processus
  1271. <code>php LittleProcessus.php</code> et attacher des fonctions aux écouteurs
  1272. suivants : <code>input</code> pour écrire une série de chiffres et
  1273. <code>output</code> pour lire le résultat.</p>
  1274. <pre><code class="language-php">$processus = new Hoa\Console\Processus('php LittleProcessus.php');
  1275. $processus->on('input', function ($bucket) {
  1276. $source = $bucket->getSource();
  1277. $data = $bucket->getData();
  1278. echo 'INPUT (', $data['pipe'], ')', "\n";
  1279. $source->writeAll(
  1280. implode("\n", range($i = mt_rand(0, 21), $i + 4)) . "\n"
  1281. );
  1282. return false;
  1283. });
  1284. $processus->on('output', function ($bucket) {
  1285. $data = $bucket->getData();
  1286. echo 'OUTPUT (', $data['pipe'], ') ', $data['line'], "\n";
  1287. return;
  1288. });
  1289. $processus->run();
  1290. /**
  1291. * Could output:
  1292. * INPUT (0)
  1293. * OUTPUT (1) > s
  1294. * OUTPUT (1) > t
  1295. * OUTPUT (1) > u
  1296. * OUTPUT (1) > v
  1297. * OUTPUT (1) > w
  1298. */</code></pre>
  1299. <p>Maintenant, rentrons dans le détail pour bien comprendre les choses.</p>
  1300. <p>Quand un flux en <strong>lecture</strong> est <strong>prêt</strong>, alors
  1301. l'écouteur <code>input</code> se déclenche. Une seule donnée est envoyée :
  1302. <code>pipe</code> qui contient le numéro du <em lang="en">pipe</em> (le
  1303. <code><em>i</em></code> de <code>php://fd/<em>i</em></code>). Quand un flux en
  1304. <strong>écriture</strong> est prêt, alors l'écouteur <code>output</code> se
  1305. déclenche. Deux données sont envoyées : <code>pipe</code> (comme pour
  1306. <code>input</code>) et <code>line</code> qui est la <strong>ligne
  1307. reçue</strong>.</p>
  1308. <p>Nous voyons dans la fonction attachée à l'écouteur <code>input</code> que
  1309. nous écrivons une suite de nombres concaténés par <code>\n</code> (un nombre
  1310. par ligne). Pour cela, nous utilisons la méthode <code>writeAll</code>. Par
  1311. défaut, les méthodes d'écriture écrivent sur le <em lang="en">pipe</em>
  1312. <code>0</code>. Pour changer ce comportement, il faudra donner le numéro de
  1313. <em lang="en">pipe</em> en second argument des méthodes d'écriture. Pareil
  1314. pour les méthodes de lecture mais le <em lang="en">pipe</em> par défaut est
  1315. <code>1</code>.</p>
  1316. <p>Quand un <em lang="en">callable</em> attaché à un écouteur retourne
  1317. <code>false</code>, le <em lang="en">pipe</em> qui a déclenché cet appel sera
  1318. <strong>fermé</strong> juste après. Dans notre cas, la fonction attachée à
  1319. <code>input</code> retourne <code>false</code> juste après avoir écrit, nous
  1320. n'avons plus besoin de ce <em lang="en">pipe</em>. Il est important pour des
  1321. raisons de <strong>performances</strong> de fermer les
  1322. <em lang="en">pipes</em> dès que possible.</p>
  1323. <p>Enfin, pour <strong>exécuter</strong> le processus, nous utilisons la
  1324. méthode <code>Hoa\Console\Processus::run</code> d'arité nulle.</p>
  1325. <p>Dans notre exemple, nous écrivons toutes les données d'un coup mais nous
  1326. pouvons envoyer les données dès qu'elles sont disponibles, ce qui est plus
  1327. performant car le processus n'attend pas un gros paquet de données : il peut
  1328. les traiter au fur et à mesure. Modifions notre exemple pour écrire une donnée
  1329. à chaque fois que <code>STDIN</code> est prêt :</p>
  1330. <pre><code class="language-php">$processus->on('input', function ($bucket) {
  1331. static $i = null;
  1332. static $j = 5;
  1333. if (null === $i) {
  1334. $i = mt_rand(0, 20);
  1335. }
  1336. $data = $bucket->getData();
  1337. echo 'INPUT (', $data['pipe'],')', "\n";
  1338. $source = $bucket->getSource();
  1339. $source->writeLine($i++);
  1340. usleep(50000);
  1341. if (0 >= $j--) {
  1342. return false;
  1343. }
  1344. return;
  1345. });</code></pre>
  1346. <p>Nous initialisons deux variables : <code class="language-php">$i</code> et
  1347. <code class="language-php">$j</code>, qui portent le nombre à envoyer et le
  1348. nombre maximum de données à envoyer. Nous introduisons une latence volontaire
  1349. avec <code class="language-php">usleep(50000)</code> pour laisser le temps à
  1350. <code>STDOUT</code> d'être prêt, ceci afin de mieux illustrer notre exemple.
  1351. Dans ce cas, la sortie serait :</p>
  1352. <pre><code class="language-php">/** Could output:
  1353. * INPUT (0)
  1354. * OUTPUT (1) > h
  1355. * INPUT (0)
  1356. * OUTPUT (1) > i
  1357. * INPUT (0)
  1358. * OUTPUT (1) > j
  1359. * INPUT (0)
  1360. * OUTPUT (1) > k
  1361. * INPUT (0)
  1362. * OUTPUT (1) > l
  1363. * INPUT (0)
  1364. * OUTPUT (1) > m
  1365. */</code></pre>
  1366. <p>Le processus est en attente d'une entrée et lit les données dès qu'elles
  1367. arrivent. Une fois que nous avons envoyé toutes les données, nous fermons le
  1368. <em lang="en">pipe</em>.</p>
  1369. <p>Le processus se <strong>ferme</strong> de lui-même. Nous avons la méthode
  1370. <code>Hoa\Console\Processus::getExitCode</code> pour connaître le
  1371. <strong>code</strong> de retour du processus. Attention, un code
  1372. <code>0</code> représente un <strong>succès</strong>. Comme l'erreur est
  1373. répandue, il existe la méthode
  1374. <code>Hoa\Console\Processus::isSuccessful</code> pour savoir si le processus
  1375. s'est exécuté avec succès ou pas.</p>
  1376. <h3 id="Detect_the_type_of_pipes" for="main-toc">Détecter le type des
  1377. <em lang="en">pipes</em></h3>
  1378. <p>Parfois, il est utile de connaître le <strong>type</strong> des
  1379. <em lang="en">pipes</em>, c'est à dire si c'est une utilisation
  1380. <strong>directe</strong>, un <strong><em lang="en">pipe</em></strong> ou une
  1381. <strong>redirection</strong>. Nous allons nous aider de la classe
  1382. <code>Hoa\Console\Console</code> et de ses méthodes statiques
  1383. <code>isDirect</code>, <code>isPipe</code> et <code>isRedirection</code> pour
  1384. obtenir ces informations.</p>
  1385. <p>Prenons un exemple pour comprendre plus rapidement. Écrivons le fichier
  1386. <code>Type.php</code> qui va étudier le type de <code>STDOUT</code> :</p>
  1387. <pre><code class="language-php">echo 'is direct: ';
  1388. var_dump(Hoa\Console\Console::isDirect(STDOUT));
  1389. echo 'is pipe: ';
  1390. var_dump(Hoa\Console\Console::isPipe(STDOUT));
  1391. echo 'is redirection: ';
  1392. var_dump(Hoa\Console\Console::isRedirection(STDOUT));</code></pre>
  1393. <p>Et maintenant, exécutons ce fichier pour voir le résultat :</p>
  1394. <pre><code class="language-shell">$ php Type.php
  1395. is direct: bool(true)
  1396. is pipe: bool(false)
  1397. is redirection: bool(false)
  1398. $ php Type.php | xargs -I@ echo @
  1399. is direct: bool(false)
  1400. is pipe: bool(true)
  1401. is redirection: bool(false)
  1402. $ php Type.php > /tmp/foo; cat /tmp/foo
  1403. is direct: bool(false)
  1404. is pipe: bool(false)
  1405. is redirection: bool(true)</code></pre>
  1406. <p>Dans le premier cas, <code>STDOUT</code> est bien <strong>direct</strong>
  1407. (pour <code>STDOUT</code>, cela signifie qu'il est <strong>relié</strong> à
  1408. l'écran, pour <code>STDIN</code>, il serait relié au clavier etc.). Dans le
  1409. deuxième cas, <code>STDOUT</code> est un
  1410. <strong><em lang="en">pipe</em></strong>, c'est à dire qu'il est
  1411. <strong>attaché</strong> au <code>STDIN</code> de la commande située après le
  1412. symbole <code>|</code>. Dans le dernier cas, <code>STDOUT</code> est une
  1413. <strong>redirection</strong>, c'est à dire qu'il est <strong>redirigé</strong>
  1414. dans le fichier <code>/tmp/foo</code> (que nous affichons juste après).
  1415. L'opération peut se faire sur <code>STDIN</code>, <code>STDERR</code> ou
  1416. n'importe quelle autre ressource.</p>
  1417. <p>Connaître le type des <em lang="en">pipes</em> peut permettre des
  1418. comportements différents selon le <strong>contexte</strong>. Par exemple,
  1419. <code>Hoa\Console\Readline\Readline</code> lit sur <code>STDIN</code>. Si son
  1420. type est un <em lang="en">pipe</em> ou une redirection, le mode d'édition de
  1421. ligne avancé sera désactivé et il retourne <code>false</code> quand il n'a
  1422. plus rien à lire. Autre exemple, la verbosité des commandes du script
  1423. <code>hoa</code> utilise le type de <code>STDOUT</code> comme valeur par
  1424. défaut : direct pour être verbeux, sinon non-verbeux. Essayez les exemples
  1425. suivants pour voir la différence :</p>
  1426. <pre><code class="language-shell">$ hoa --no-verbose
  1427. $ hoa | xargs -I@ echo @</code></pre>
  1428. <p>Les exemples ne manquent pas mais attention à utiliser cette fonctionnalité
  1429. avec intelligence. Il faut adapter les comportements mais rester
  1430. <strong>cohérent</strong>.</p>
  1431. <h3 id="Execution_conditions" for="main-toc">Condition d'exécution</h3>
  1432. <p>Le processus s'exécute dans un <strong>dossier</strong> particulier et un
  1433. <strong>environnement</strong> particulier. Le dossier est appelé
  1434. <em lang="en">current working directory</em>, souvent abrégé
  1435. <abbr lang="en">cwd</abbr>. Il définit le dossier où sera exécuté le
  1436. processus. Nous pouvons le retrouver en PHP avec
  1437. <a href="http://php.net/getcwd">la fonction <code>getcwd</code></a>.
  1438. L'environnement se définit par un tableau que nous retrouvons par exemple en
  1439. exécutant <code>/usr/bin/env</code>. C'est dans cet environnement qu'est
  1440. présent le <code>PATH</code> par exemple. Ces données sont passées en
  1441. quatrième et cinquième arguments du constructeur de
  1442. <code>Hoa\Console\Processus</code>. Ainsi :</p>
  1443. <pre><code class="language-php">$processus = new Hoa\Console\Processus(
  1444. 'php',
  1445. null, /* no option */
  1446. null, /* use default pipes */
  1447. '/tmp',
  1448. [
  1449. 'FOO' => 'bar',
  1450. 'BAZ' => 'qux',
  1451. 'PATH' => '/usr/bin:/bin'
  1452. ]
  1453. );
  1454. $processus->on('input', function (Hoa\Event\Bucket $bucket) {
  1455. $bucket->getSource()->writeAll(
  1456. '&amp;lt;?php' . "\n" .
  1457. 'var_dump(getcwd());' . "\n" .
  1458. 'print_r($_ENV);'
  1459. );
  1460. return false;
  1461. });
  1462. $processus->on('output', function (Hoa\Event\Bucket $bucket) {
  1463. $data = $bucket->getData();
  1464. echo '> ', $data['line'], "\n";
  1465. return;
  1466. });
  1467. $processus->run();
  1468. /**
  1469. * Will output:
  1470. * > string(12) "/tmp"
  1471. * > Array
  1472. * > (
  1473. * > [FOO] => bar
  1474. * > [PATH] => /usr/bin:/bin
  1475. * > [PWD] => /tmp
  1476. * > [BAZ] => qux
  1477. * > [_] => /usr/bin/php
  1478. * >
  1479. * > )
  1480. */</code></pre>
  1481. <p>Si le <em lang="en">current working directory</em> n'est pas précisé, nous
  1482. utiliserons le même que le programme. Si aucun environnement n'est précisé, le
  1483. processus utilisera celui de son parent.</p>
  1484. <p>Nous pouvons aussi imposer un <strong>temps maximum</strong> de
  1485. <strong>réponse</strong> en seconde au processus (défini à 30 secondes par
  1486. défaut). C'est le dernier argument du constructeur. Nous pouvons utiliser la
  1487. méthode <code>Hoa\Console\Processus::setTimeout</code>. Pour savoir quand ce
  1488. temps est atteint, nous devons utiliser l'écouteur <code>timeout</code>.
  1489. Aucune action ne sera faite automatiquement. Nous pouvons par exemple terminer
  1490. le processus grâce à la méthode <code>Hoa\Console\Processus::terminate</code>.
  1491. Ainsi :</p>
  1492. <pre><code class="language-php">$processus = new Hoa\Console\Processus('php');
  1493. // 3 seconds is enough…
  1494. $processus->setTimeout(3);
  1495. // Sleep 10 seconds.
  1496. $processus->on('input', function (Hoa\Event\Bucket $bucket) {
  1497. $bucket->getSource()->writeAll('&amp;lt;?php sleep(10);');
  1498. return false;
  1499. });
  1500. // Terminate the processus on timeout.
  1501. $processus->on('timeout', function (Hoa\Event\Bucket $bucket) {
  1502. echo 'TIMEOUT, terminate', "\n";
  1503. $bucket->getSource()->terminate();
  1504. return;
  1505. });
  1506. $processus->run();
  1507. /**
  1508. * Will output (after 3 secondes):
  1509. * TIMEOUT, terminate
  1510. */</code></pre>
  1511. <p>Aucun action n'est réalisée automatiquement car elles peuvent être
  1512. nombreuses. Nous pouvons peut-être débloquer le processus, le fermer pour en
  1513. ouvrir un autre, émettre des rapports etc.</p>
  1514. <p>À propos de la méthode <code>terminate</code>, elle peut prendre plusieurs
  1515. valeurs différentes, définies par les constantes de
  1516. <code>Hoa\Console\Processus</code> : <code>SIGHUP</code>, <code>SIGINT</code>,
  1517. <code>SIGQUIT</code>, <code>SIGABRT</code>, <code>SIGKILL</code>,
  1518. <code>SIGALRM</code> et <code>SIGTERM</code> (par défaut). Plusieurs
  1519. <strong>signaux</strong> peuvent être envoyés aux processus pour qu'ils
  1520. s'arrêtent. Pour avoir le détail, voir
  1521. <a href="http://freebsd.org/cgi/man.cgi?query=signal"
  1522. title="signal(3), FreeBSD Library Functions Manual">la page
  1523. <code>signal</code></a>.</p>
  1524. <h3 id="Miscellaneous" for="main-toc">Miscellaneous</h3>
  1525. <p>Les méthodes statiques <code>getTitle</code> et <code>setTitle</code> sur
  1526. la classe <code>Hoa\Console\Processus</code> permettent respectivement
  1527. d'obtenir et de définir le titre du processus. Ainsi :</p>
  1528. <pre><code class="language-php">Hoa\Console\Processus::setTitle('hoa #1');</code></pre>
  1529. <p>Et dans un autre terminal :</p>
  1530. <pre data-line="2"><code class="language-shell">$ ps | grep hoa
  1531. 69578 ttys006 0:00.01 hoa #1
  1532. 70874 ttys008 0:00.00 grep hoa</code></pre>
  1533. <p>Ces méthodes sont très pratiques lorsque nous manipulons beaucoup de
  1534. processus et que nous voulons les identifier efficacement (par exemple avec
  1535. des outils comme <code>top</code> ou <code>ps</code>). Notons qu'elles ne sont
  1536. fonctionnelles que si vous avez PHP5.5 au minimum.</p>
  1537. <p>Une autre méthode statique intéressante est
  1538. <code>Hoa\Console\Processus::locate</code> qui permet de déterminer le chemin
  1539. vers un programme. Par exemple :</p>
  1540. <pre><code class="language-php">var_dump(Hoa\Console\Processus::locate('php'));
  1541. /**
  1542. * Could output:
  1543. * string(12) "/usr/bin/php"
  1544. */</code></pre>
  1545. <p>Dans le cas où le programme n'est pas trouvé, <code>null</code> sera
  1546. retournée. Cette méthode se base sur le <code>PATH</code> de votre
  1547. système.</p>
  1548. <h3 id="Interactive_processus_and_pseudo-terminals" for="main-toc">Processus
  1549. interactifs et pseudo-terminaux</h3>
  1550. <p>Cette section est un peu plus technique mais explique un
  1551. <strong>problème</strong> qui peut arriver avec certains processus dits
  1552. <strong>interactifs</strong>.</p>
  1553. <p>La classe <code>Hoa\Console\Processus</code> permet d'automatiser
  1554. l'interaction avec des processus très facilement. Toutefois, ce n'est pas
  1555. toujours possible de créer cette automatisation, à cause du comportement du
  1556. processus. Nous allons illustrer le problème en écrivant le fichier
  1557. <code>Interactive.php</code> :</p>
  1558. <pre><code class="language-php">&amp;lt;?php
  1559. echo 'Login: ';
  1560. if (false === $login = fgets(STDIN)) {
  1561. fwrite(STDERR, 'Hmm, no login.' . "\n");
  1562. exit(1);
  1563. }
  1564. echo 'Password: ';
  1565. if (false === $password = fgets(STDIN)) {
  1566. fwrite(STDERR, 'Hmm, no password.' . "\n");
  1567. exit(2);
  1568. }
  1569. echo 'Result:', "\n\t", $login, "\t", $password;</code></pre>
  1570. <p>Exécutons ce processus pour voir ce qu'il fait :</p>
  1571. <pre><code class="language-shell">$ php Interactive.php
  1572. Login: myLogin
  1573. Password: myPassword
  1574. Result:
  1575. myLogin
  1576. myPassword</code></pre>
  1577. <p>Et maintenant, automatisons l'exécution de ce processus :</p>
  1578. <pre><code class="language-shell">$ echo 'myLogin\nmyPassword' > data
  1579. $ php Interactive.php &amp;lt; data
  1580. Login: Password: Result:
  1581. myLogin
  1582. myPassword</code></pre>
  1583. <p>Excellent. Nous pourrions avoir le même résultat avec
  1584. <code>Hoa\Console\Processus</code> sans problème. Maintenant, si notre
  1585. processus veut s'assurer que <code>STDIN</code> est vide entre deux entrées,
  1586. il peut ajouter :</p>
  1587. <pre data-line-offset="7" data-line="10"><code class="language-php">}
  1588. fseek(STDIN, 0, SEEK_END);
  1589. echo 'Password: ';</code></pre>
  1590. <p>Et alors dans ce cas, si nous essayons d'automatiser l'exécution :</p>
  1591. <pre><code class="language-shell">$ php Interactive.php &amp;lt; data
  1592. Login: Password: Hmm, no password.</code></pre>
  1593. <p>C'est un comportement tout à fait normal, mais
  1594. <code>Hoa\Console\Processus</code> ne peut rien faire pour remédier à ce
  1595. problème.</p>
  1596. <p>La solution serait d'utiliser un
  1597. <a href="https://en.wikipedia.org/wiki/Pseudo_terminal">pseudo-terminal</a> en
  1598. utilisant les fonctions PTY (voir
  1599. <a href="http://kernel.org/doc/man-pages/online/pages/man7/pty.7.html"
  1600. title="pty(7), Linux Programmer's Manual">dans Linux</a> ou
  1601. <a href="http://freebsd.org/cgi/man.cgi?query=pty"
  1602. title="pty(3), FreeBSD Library Functions Manual" >dans FreeBSD</a>).
  1603. Malheureusement ces fonctions ne sont pas disponibles dans PHP pour des
  1604. raisons techniques. Il n'y a pas de solution possible en PHP pur, mais il
  1605. est toujours envisageable d'utiliser un programme <strong>externe</strong>,
  1606. écrit par exemple en C.</p>
  1607. <h2 id="Conclusion" for="main-toc">Conclusion</h2>
  1608. <p>La bibliothèque <code>Hoa\Console</code> offre des outils
  1609. <strong>complets</strong> pour écrire des programmes adaptés à une interface
  1610. <strong>textuelle</strong>, que ce soit l'interaction avec la fenêtre ou le
  1611. curseur, l'interaction avec l'utilisateur grâce à un lecteur de lignes très
  1612. personnalisable (avec de l'auto-complétion ou des raccourcis), la lecture
  1613. d'options pour les programmes eux-mêmes, la construction de programmes
  1614. élaborés, ou encore l'exécution, l'interaction et la communication avec des
  1615. processus.</p>
  1616. </yield>
  1617. </overlay>