diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 569a906..64490e7 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -48,6 +48,8 @@ phutil_register_library_map(array( 'ConduitClientException' => 'conduit/ConduitClientException.php', 'ConduitClientTestCase' => 'conduit/__tests__/ConduitClientTestCase.php', 'ConduitFuture' => 'conduit/ConduitFuture.php', + 'ConsoleConfirmFuture' => 'future/ConsoleConfirmFuture.php', + 'ConsolePromptFuture' => 'future/ConsolePromptFuture.php', 'ExecFuture' => 'future/exec/ExecFuture.php', 'ExecFutureTestCase' => 'future/exec/__tests__/ExecFutureTestCase.php', 'ExecPassthruTestCase' => 'future/exec/__tests__/ExecPassthruTestCase.php', @@ -647,6 +649,8 @@ phutil_register_library_map(array( 'ConduitClientException' => 'Exception', 'ConduitClientTestCase' => 'PhutilTestCase', 'ConduitFuture' => 'FutureProxy', + 'ConsoleConfirmFuture' => 'ConsolePromptFuture', + 'ConsolePromptFuture' => 'FutureProxy', 'ExecFuture' => 'PhutilExecutableFuture', 'ExecFutureTestCase' => 'PhutilTestCase', 'ExecPassthruTestCase' => 'PhutilTestCase', diff --git a/src/future/ConsoleConfirmFuture.php b/src/future/ConsoleConfirmFuture.php new file mode 100644 index 0000000..efb3909 --- /dev/null +++ b/src/future/ConsoleConfirmFuture.php @@ -0,0 +1,27 @@ +defaultNo = $default_no; + $prompt_options = $default_no ? '[y/N]' : '[Y/n]'; + $prompt .= " $prompt_options"; + parent::__construct($prompt); + } + + protected function didReceiveResult($result) { + if ($this->defaultNo) { + return ($result == 'y'); + } else { + return ($result != 'n'); + } + } +} diff --git a/src/future/ConsolePromptFuture.php b/src/future/ConsolePromptFuture.php new file mode 100644 index 0000000..ecbb749 --- /dev/null +++ b/src/future/ConsolePromptFuture.php @@ -0,0 +1,83 @@ +&2; '. + 'history -r %s 2>/dev/null; '. + 'read -e -p %s; '. + 'echo "$REPLY" 2>/dev/null; '. + 'history -s "$REPLY" 2>/dev/null; '. + 'history -w %s 2>/dev/null', + $history, + $prompt, + $history)); + + // `read` writes its prompt to STDERR, and reads from STDIN; hook those up + // to what we believe them to be for us, while consuming STDOUT so we can + // determine the output. + $shell_future->setDescriptors(array( + 0 => array('file', 'php://stdin', 'r'), + 1 => array('pipe', 'w'), + 2 => array('file', 'php://stderr', 'w'), + )); + + $this->setProxiedFuture($shell_future); + } + + protected function didReceiveResult($result) { + list($err, $stdout) = $result; + list($pid, $stdout) = explode("\n", $stdout); + return $stdout; + } + + final public function terminate() { + // The first output of the command is the PID of the bash with the readline, + // which is called from a `/bin/sh` set up by PHP. Terminating the outer + // /bin/sh leaves the inner `bash` still running, and still attached to + // `/dev/tty` -- unless we kill that inner process it thus consumes the + // first character after PHP exits. + list($stdout) = $this->getProxiedFuture()->read(); + list($pid) = explode("\n", $stdout); + $this->setProxiedFuture(new ImmediateFuture(null)); + // SIGHUP, but PCNTL isn't always available so we can't use that constant; + // hardcode the signal number. + posix_kill($pid, 1); + // Make sure we reset terminal echo, etc. + exec('stty sane'); + } + + public function __destruct() { + $this->terminate(); + } +}