ReadlineClient.php 2.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109
  1. <?php
  2. /* vim: set shiftwidth=2 expandtab softtabstop=2: */
  3. namespace Boris;
  4. /**
  5. * The Readline client is what the user spends their time entering text into.
  6. *
  7. * Input is collected and sent to {@link \Boris\EvalWorker} for processing.
  8. */
  9. class ReadlineClient {
  10. private $_socket;
  11. private $_prompt;
  12. private $_historyFile;
  13. private $_clear = false;
  14. /**
  15. * Create a new ReadlineClient using $socket for communication.
  16. *
  17. * @param resource $socket
  18. */
  19. public function __construct($socket) {
  20. $this->_socket = $socket;
  21. }
  22. /**
  23. * Start the client with an prompt and readline history path.
  24. *
  25. * This method never returns.
  26. *
  27. * @param string $prompt
  28. * @param string $historyFile
  29. */
  30. public function start($prompt, $historyFile) {
  31. readline_read_history($historyFile);
  32. declare(ticks = 1);
  33. pcntl_signal(SIGCHLD, SIG_IGN);
  34. pcntl_signal(SIGINT, array($this, 'clear'), true);
  35. // wait for the worker to finish executing hooks
  36. if (fread($this->_socket, 1) != EvalWorker::READY) {
  37. throw new \RuntimeException('EvalWorker failed to start');
  38. }
  39. $parser = new ShallowParser();
  40. $buf = '';
  41. $lineno = 1;
  42. for (;;) {
  43. $this->_clear = false;
  44. $line = readline(
  45. sprintf(
  46. '[%d] %s',
  47. $lineno,
  48. ($buf == ''
  49. ? $prompt
  50. : str_pad('*> ', strlen($prompt), ' ', STR_PAD_LEFT))
  51. )
  52. );
  53. if ($this->_clear) {
  54. $buf = '';
  55. continue;
  56. }
  57. if (false === $line) {
  58. $buf = 'exit(0);'; // ctrl-d acts like exit
  59. }
  60. if (strlen($line) > 0) {
  61. readline_add_history($line);
  62. }
  63. $buf .= sprintf("%s\n", $line);
  64. if ($statements = $parser->statements($buf)) {
  65. ++$lineno;
  66. $buf = '';
  67. foreach ($statements as $stmt) {
  68. if (false === $written = fwrite($this->_socket, $stmt)) {
  69. throw new \RuntimeException('Socket error: failed to write data');
  70. }
  71. if ($written > 0) {
  72. $status = fread($this->_socket, 1);
  73. if ($status == EvalWorker::EXITED) {
  74. readline_write_history($historyFile);
  75. echo "\n";
  76. exit(0);
  77. } elseif ($status == EvalWorker::FAILED) {
  78. break;
  79. }
  80. }
  81. }
  82. }
  83. }
  84. }
  85. /**
  86. * Clear the input buffer.
  87. */
  88. public function clear() {
  89. // FIXME: I'd love to have this send \r to readline so it puts the user on a blank line
  90. $this->_clear = true;
  91. }
  92. }