Page MenuHomePhorge

ArcanistPrompt.php
No OneTemporary

ArcanistPrompt.php

<?php
final class ArcanistPrompt
extends Phobject {
private $key;
private $workflow;
private $description;
private $query;
public function setKey($key) {
$this->key = $key;
return $this;
}
public function getKey() {
return $this->key;
}
public function setWorkflow(ArcanistWorkflow $workflow) {
$this->workflow = $workflow;
return $this;
}
public function getWorkflow() {
return $this->workflow;
}
public function setDescription($description) {
$this->description = $description;
return $this;
}
public function getDescription() {
return $this->description;
}
public function setQuery($query) {
$this->query = $query;
return $this;
}
public function getQuery() {
return $this->query;
}
public function execute() {
$workflow = $this->getWorkflow();
if ($workflow) {
$workflow_ok = $workflow->hasPrompt($this->getKey());
} else {
$workflow_ok = false;
}
if (!$workflow_ok) {
throw new Exception(
pht(
'Prompt ("%s") is executing, but it is not properly bound to the '.
'invoking workflow. You may have called "newPrompt()" to execute a '.
'prompt instead of "getPrompt()". Use "newPrompt()" when defining '.
'prompts and "getPrompt()" when executing them.',
$this->getKey()));
}
$query = $this->getQuery();
if (!strlen($query)) {
throw new Exception(
pht(
'Prompt ("%s") has no query text!',
$this->getKey()));
}
$options = '[y/N/?]';
$default = 'n';
$saved_response = $this->getSavedResponse();
try {
phutil_console_require_tty();
} catch (PhutilConsoleStdinNotInteractiveException $ex) {
// TOOLSETS: Clean this up to provide more details to the user about how
// they can configure prompts to be answered.
// Throw after echoing the prompt so the user has some idea what happened.
echo $query."\n";
throw $ex;
}
$stdin = fopen('php://stdin', 'r');
if (!$stdin) {
throw new Exception(pht('Failed to open stdin for reading.'));
}
// NOTE: We're making stdin nonblocking so that we can respond to signals
// immediately. If we don't, and you ^C during a prompt, the program does
// not handle the signal until fgets() returns.
// On Windows, we skip this because stdin can not be made nonblocking.
if (!phutil_is_windows()) {
$ok = stream_set_blocking($stdin, false);
if (!$ok) {
throw new Exception(pht('Unable to set stdin nonblocking.'));
}
}
echo "\n";
$result = null;
$is_saved = false;
while (true) {
if ($saved_response !== null) {
$is_saved = true;
$response = $saved_response;
$saved_response = null;
} else {
echo tsprintf(
'**<bg:cyan> %s </bg>** %s %s ',
'>>>',
$query,
$options);
$is_saved = false;
if (phutil_is_windows()) {
$response = fgets($stdin);
} else {
while (true) {
$read = array($stdin);
$write = array();
$except = array();
$ok = @stream_select($read, $write, $except, 1);
if ($ok === false) {
// NOTE: We may be interrupted by a system call, particularly if
// the window is resized while a prompt is shown and the terminal
// sends SIGWINCH.
// If we are, just continue below and try to read from stdin. If
// we were interrupted, we should read nothing and continue
// normally. If the pipe is broken, the read should fail.
}
$response = '';
while (true) {
$bytes = fread($stdin, 8192);
if ($bytes === false) {
throw new Exception(
pht('fread() from stdin failed with an error.'));
}
if (!strlen($bytes)) {
break;
}
$response .= $bytes;
}
if (!strlen($response)) {
continue;
}
break;
}
}
$response = trim($response);
if (!strlen($response)) {
$response = $default;
}
}
$save_scope = null;
if (!$is_saved) {
$matches = null;
if (preg_match('(^(.*)([!*])\z)', $response, $matches)) {
$response = $matches[1];
if ($matches[2] === '*') {
$save_scope = ArcanistConfigurationSource::SCOPE_USER;
} else {
$save_scope = ArcanistConfigurationSource::SCOPE_WORKING_COPY;
}
}
}
if (phutil_utf8_strtolower($response) == 'y') {
$result = true;
break;
}
if (phutil_utf8_strtolower($response) == 'n') {
$result = false;
break;
}
if (phutil_utf8_strtolower($response) == '?') {
echo tsprintf(
"\n<bg:green>** %s **</bg> **%s**\n\n",
pht('PROMPT'),
$this->getKey());
echo tsprintf(
"%s\n",
$this->getDescription());
echo tsprintf("\n");
echo tsprintf(
"%s\n",
pht(
'The default response to this prompt is "%s".',
$default));
echo tsprintf("\n");
echo tsprintf(
"%?\n",
pht(
'Use "*" after a response to save it in user configuration.'));
echo tsprintf(
"%?\n",
pht(
'Use "!" after a response to save it in working copy '.
'configuration.'));
echo tsprintf(
"%?\n",
pht(
'Run "arc help prompts" for detailed help on configuring '.
'responses.'));
echo tsprintf("\n");
continue;
}
}
if ($save_scope !== null) {
$this->saveResponse($save_scope, $response);
}
if ($is_saved) {
echo tsprintf(
"<bg:cyan>** %s **</bg> %s **<%s>**\n".
"<bg:cyan>** %s **</bg> (%s)\n\n",
'>>>',
$query,
$response,
'>>>',
pht(
'Using saved response to prompt "%s".',
$this->getKey()));
}
if (!$result) {
throw new ArcanistUserAbortException();
}
}
private function getSavedResponse() {
$config_key = ArcanistArcConfigurationEngineExtension::KEY_PROMPTS;
$workflow = $this->getWorkflow();
$config = $workflow->getConfig($config_key);
$prompt_key = $this->getKey();
$prompt_response = null;
foreach ($config as $response) {
if ($response->getPrompt() === $prompt_key) {
$prompt_response = $response;
}
}
if ($prompt_response === null) {
return null;
}
return $prompt_response->getResponse();
}
private function saveResponse($scope, $response_value) {
$config_key = ArcanistArcConfigurationEngineExtension::KEY_PROMPTS;
$workflow = $this->getWorkflow();
echo tsprintf(
"<bg:green>** %s **</bg> %s\n",
pht('SAVE PROMPT'),
pht(
'Saving response "%s" to prompt "%s".',
$response_value,
$this->getKey()));
$source_list = $workflow->getConfigurationSourceList();
$source = $source_list->getWritableSourceFromScope($scope);
$response_list = $source_list->getConfigFromScopes(
$config_key,
array($scope));
foreach ($response_list as $key => $response) {
if ($response->getPrompt() === $this->getKey()) {
unset($response_list[$key]);
}
}
if ($response_value !== null) {
$response_list[] = id(new ArcanistPromptResponse())
->setPrompt($this->getKey())
->setResponse($response_value);
}
$option = $source_list->getConfigOption($config_key);
$option->writeValue($source, $response_list);
}
}

File Metadata

Mime Type
text/x-php
Expires
Thu, Mar 27, 01:46 (1 w, 5 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1241787
Default Alt Text
ArcanistPrompt.php (7 KB)

Event Timeline