PHP auto-completion interactive console

    In this small article, I will show how to use the console with auto-completion in Tab-script in my PHP script. Of the similar articles on the hub 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, of the difficulties is 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 possible, how) to create your own console with commands and tips 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 as follows:
    - we are preparing 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 commands for additions
    - we will organize an endless program cycle, exit - by the command 'exit'

    It seems that we do not need anything else.

    To make it a little more interesting, we will make the console understand that it is now necessary to substitute. We will make two “levels” of substitution: when entering the first word in the console, we will offer actions, and when entering the second word - nouns. If the console has more words, then pressing Tab does not change the line.

    For our example, we need functions:
    - readline_completion_function - Registers our own input line processing function
    -readline - Read the line
    - readline_info - with its help we find out detailed information about the line in the console, by pressing Tab

    . Actually, there is very little work, so right to the point. Here is the code for a small class responsible for vocabulary and command processing:

    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 takes the last word from the console as the first parameter, and to get the full line, you will need to use readline_info (). Well, then - check what order the word is being entered now, and return one of the dictionaries for auto-substitution.

    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 extremely 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


    That's it.

    In the extension, methods for working with the history of teams are still available, so you can make a helicopter at all.

    How you will use it is up to you.
    As an experiment, after I figured out the console, I made a sketch of a text game with a couple of rooms and objects in them, so that the player would go around and pick up objects 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 displayed.

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

    Sources, if suddenly someone is curious, here .

    PS if you are planning to do something more serious like that, look at the Console component for Symfony2. There everything is already done as it should and you won’t have to exhaust your bike.

    Also popular now: