📜 ⬆️ ⬇️

PHP autocomplete console

In this small article I will show you how to use the console with autocompletion in your PHP script by pressing Tab. From similar articles on Habré, I found only an article from CKOPOBAPKuH , and it has a slightly different direction, although the essence is the same.

In fact, there is no magic here, because of the difficulties - to formulate for yourself how your console should work. Therefore, a minimum of words, a minimum of code, only the most necessary.

There is a question: is it possible (and if so, how) to make your own console with commands and hints in PHP.
There is an answer: it is possible, but the corresponding extension (readline) for PHP is available only on Linux, alas.
')


So let's get started.

The action plan is:
- prepare a method that will process incoming data by pressing Tab and return a list of commands for auto-completion.
- we are preparing a list of these same teams to add
- organize an endless program loop, exit - at the 'exit' command

It seems we will not need anything else.

To make it a little more interesting, let's make it so that the console understands what needs to be substituted now. We make two “levels” of substitution: when we enter the first word in the console, we will suggest actions, and when entering the second word, nouns. If there are more words in the console, then pressing Tab does not change the line.

For our example, we need the following functions:
- readline_completion_function - registers our own incoming line processing function
- readline - Read line
- readline_info - with its help we will learn detailed information about the line in the console, by pressing Tab

In fact, quite a bit of work, so immediately to the point. Here is the code for a small class responsible for the dictionary and command handling:

Dictionary.php
class Dictionary { const EXIT_COMMAND = 'exit'; protected $mainDictionary = [ 'list', 'load', 'get', 'go', 'put', 'parse', 'paint', 'delete', 'download', self::EXIT_COMMAND ]; protected $subDictionary = [ 'level', 'library', 'document', 'dragon', 'daemon', 'data', 'port', 'password', 'paragraph' ]; private $promptLine = '> '; public function initCommandCompletion() { // if readline lib accessible - use it for command completions if (function_exists('readline_completion_function')) { readline_completion_function( function ($currWord, $stringPosition, $cursorInLine) { $fullLine = readline_info()['line_buffer']; if (count( explode(' ', $fullLine) ) > 2 ) { return []; } // if not first word - return list allowed commands if (strrpos($fullLine, ' ') !== false && ( strrpos($fullLine, $currWord) === false || strrpos($fullLine, ' ') < strrpos($fullLine, $currWord)) ) { return $this->subDictionary; } return $this->mainDictionary; } ); } } public function readCommand() { if (function_exists('readline')) { $command = readline($this->promptLine); } else { fputs(STDOUT, $this->promptLine); $command = fgets(STDIN); } return $command; } public function executeCommand($command) { $param = ''; if (strpos($command, ' ') !== false) { list ($command, $param) = explode(' ', $command, 2); } // NEED TO CHECK EXISTS COMMAND if (!$this->isCommandExists($command)) { fputs(STDOUT, "Hey! I don't know what are you talking about!\n"); return false; } // AND NOW CHECK FOR COMMAND AND RUN IT $message = "You try to run command '{$command}'"; if (!empty($param)) { $message .= " and with param '{$param}'."; } fputs(STDOUT, $message . "\n"); return true; } private function isCommandExists($command) { return in_array($command, array_merge($this->mainDictionary, $this->subDictionary)); } } 



For our purposes, all the most necessary and interesting is in the initCommandCompletion () method. And more ... And nothing more interesting and no. The anonymous function that we use when calling calls the last word from the console as the first parameter, and to get the full line, you need to use readline_info (). Well, after that, we check which word is currently being entered in order, and return one of the dictionaries for autosubstitution.

And to get the effect - use this class. Create index.php with the following contents:

index.php
 require_once __DIR__ . '/Dictionary.php'; $app = new Dictionary(); $app->initCommandCompletion(); // START LOOP. 'exit' command will stop execution while (true) { $command = $app->readCommand(); $command = trim($command); if ($command == Dictionary::EXIT_COMMAND) { break; } $app->executeCommand($command); } exit; 



No magic, everything is very simple:

For the first word - one dictionary is used:
 $ php index.php > l[Tab] list load >l 


For the second word - another:
 $ php index.php > li[Tab] > list l[Tab] level library >list l 


Well that's all.

In the extension methods are still available to work with the history of the teams, so you can make quite a helicopter.

How you will use it is your business.
I am in the form of an experiment, after I figured out the console, made a sketch of a text game with a couple of rooms and objects in them, so that the player would go and pick up items or throw them out of inventory. Accordingly - a set of commands, and for the second word in the command the name of the items in the room and in the inventory is shown.

Cutting was interesting. On the first wave of enthusiasm, so to speak. :)

Sources, if anyone is curious, here .

PS If you are going to do something more serious like this, look at the Console component for Symfony2. There everything is already done as it should and will not have to grind out your bike.

Source: https://habr.com/ru/post/234563/


All Articles