diff --git a/README.md b/README.md index a6c95b6..822eff1 100644 --- a/README.md +++ b/README.md @@ -564,3 +564,8 @@ MIT * If you want to learn more about processing streams of data, refer to the documentation of the underlying [react/stream](https://github.com/reactphp/stream) component. +* If you build an interactive CLI tool that reads a command line from STDIN, you + may want to use [clue/arguments](https://github.com/clue/php-arguments) in + order to split this string up into its individual arguments and then use + [clue/commander](https://github.com/clue/php-commander) to route to registered + commands and their required arguments. diff --git a/composer.json b/composer.json index aeb69d3..f4200ef 100644 --- a/composer.json +++ b/composer.json @@ -22,5 +22,9 @@ }, "autoload": { "psr-4": { "Clue\\React\\Stdio\\": "src/" } + }, + "require-dev": { + "clue/commander": "^1.2", + "clue/arguments": "^2.0" } } diff --git a/examples/01-periodic.php b/examples/01-periodic.php new file mode 100644 index 0000000..dee02a6 --- /dev/null +++ b/examples/01-periodic.php @@ -0,0 +1,26 @@ +writeln('Will print periodic messages until you submit anything'); + +// add some periodic noise +$timer = $loop->addPeriodicTimer(0.5, function () use ($stdio) { + $stdio->writeln(date('Y-m-d H:i:s') . ' hello'); +}); + +// react to commands the user entered +$stdio->on('line', function ($line) use ($stdio, $timer) { + $stdio->writeln('you just said: ' . $line . ' (' . strlen($line) . ')'); + + $timer->cancel(); + $stdio->end(); +}); + +$loop->run(); diff --git a/examples/periodic.php b/examples/02-interactive.php similarity index 79% rename from examples/periodic.php rename to examples/02-interactive.php index efbd8ab..1a95b89 100644 --- a/examples/periodic.php +++ b/examples/02-interactive.php @@ -36,20 +36,15 @@ return $offset > 1 ? array() : array('exit', 'quit', 'help', 'echo', 'print', 'printf'); }); -$stdio->writeln('Will print periodic messages until you type "quit" or "exit"'); +$stdio->writeln('Welcome to this interactive demo'); -$stdio->on('line', function ($line) use ($stdio, $loop, &$timer) { +// react to commands the user entered +$stdio->on('line', function ($line) use ($stdio) { $stdio->writeln('you just said: ' . $line . ' (' . strlen($line) . ')'); if (in_array(trim($line), array('quit', 'exit'))) { - $timer->cancel(); $stdio->end(); } }); -// add some periodic noise -$timer = $loop->addPeriodicTimer(2.0, function () use ($stdio) { - $stdio->writeln('hello'); -}); - $loop->run(); diff --git a/examples/03-commander.php b/examples/03-commander.php new file mode 100644 index 0000000..73673dd --- /dev/null +++ b/examples/03-commander.php @@ -0,0 +1,76 @@ +getReadline(); +$readline->setPrompt('> '); + +// limit history to HISTSIZE env +$limit = getenv('HISTSIZE'); +if ($limit === '' || $limit < 0) { + // empty string or negative value means unlimited + $readline->limitHistory(null); +} elseif ($limit !== false) { + // apply any other value if given + $readline->limitHistory($limit); +} + +// register all available commands and their arguments +$router = new Router(); +$router->add('exit | quit', function() use ($stdio) { + $stdio->end(); +}); +$router->add('help', function () use ($stdio) { + $stdio->writeln('Use TAB-completion or use "exit"'); +}); +$router->add('(echo | print) ...', function (array $args) use ($stdio) { + $stdio->writeln(implode(' ', $args['words'])); +}); +$router->add('printf ...', function (array $args) use ($stdio) { + $stdio->writeln(vsprintf($args['format'],$args['args'])); +}); + +// autocomplete the following commands (at offset=0/1 only) +$readline->setAutocomplete(function ($_, $offset) { + return $offset > 1 ? array() : array('exit', 'quit', 'help', 'echo', 'print', 'printf'); +}); + +$stdio->writeln('Welcome to this interactive demo'); + +// react to commands the user entered +$stdio->on('line', function ($line) use ($router, $stdio, $readline) { + // add all lines from input to history + // skip empty line and duplicate of previous line + $all = $readline->listHistory(); + if (trim($line) !== '' && $line !== end($all)) { + $readline->addHistory($line); + } + + try { + $args = Arguments\split($line); + } catch (Arguments\UnclosedQuotesException $e) { + $stdio->writeln('Error: Invalid command syntax (unclosed quotes)'); + return; + } + + // skip empty lines + if (!$args) { + return; + } + + try { + $router->handleArgs($args); + } catch (NoRouteFoundException $e) { + $stdio->writeln('Error: Invalid command usage'); + } +}); + +$loop->run(); diff --git a/examples/login.php b/examples/11-login.php similarity index 100% rename from examples/login.php rename to examples/11-login.php