Page MenuHomePhabricator

Masterwork From Distant Lands

Authored By
alexmv
Sep 14 2017, 10:17 PM
Size
5 KB
Referenced Files
None
Subscribers
None

Masterwork From Distant Lands

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 @@
+<?php
+
+/**
+ * Dropbox addition. This class replicates the behavior of
+ * phutil_console_confirm, but as a Future.
+ *
+ * TODO: It differs in that it only prompts once, even if the answer is neither
+ * 'y' nor 'n' nor ''.
+ */
+final class ConsoleConfirmFuture extends ConsolePromptFuture {
+ protected $defaultNo;
+
+ public function __construct($prompt, $default_no = true) {
+ $this->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 @@
+<?php
+
+/**
+ * Dropbox addition. This class replicates the behavior of
+ * phutil_console_prompt, but as a Future.
+ *
+ * @concrete-extensible
+ */
+class ConsolePromptFuture extends FutureProxy {
+ public function __construct($prompt, $history = '') {
+ $prompt = phutil_console_wrap($prompt.' ', 4);
+
+ try {
+ phutil_console_require_tty();
+ } catch (PhutilConsoleStdinNotInteractiveException $ex) {
+ // Throw after echoing the prompt so the user has some idea what happened.
+ echo $prompt;
+ throw $ex;
+ }
+
+ if (phutil_is_windows()) {
+ throw new Exception('Cannot use prompt as a future on Windows.');
+ } else {
+ // Test if bash is available by seeing if it can run `true`.
+ list($err) = exec_manual('bash -c %s', 'true');
+ if ($err) {
+ throw new Exception('Cannot use prompt as a future without bash.');
+ }
+ }
+
+ // See `terminate`, below, for an explanation of the first `echo`
+ $shell_future = new ExecFuture(
+ 'bash -c %s',
+ csprintf(
+ 'echo $$; '.
+ 'echo -ne "\n\n" >&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();
+ }
+}

File Metadata

Mime Type
text/plain; charset=utf-8
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
780636
Default Alt Text
Masterwork From Distant Lands (5 KB)

Event Timeline