Page MenuHomePhabricator

D21331.id50761.diff
No OneTemporary

D21331.id50761.diff

diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -396,6 +396,8 @@
'ArcanistPlusOperatorOnStringsXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistPlusOperatorOnStringsXHPASTLinterRuleTestCase.php',
'ArcanistProjectConfigurationSource' => 'config/source/ArcanistProjectConfigurationSource.php',
'ArcanistPrompt' => 'toolset/ArcanistPrompt.php',
+ 'ArcanistPromptResponse' => 'toolset/ArcanistPromptResponse.php',
+ 'ArcanistPromptsConfigOption' => 'config/option/ArcanistPromptsConfigOption.php',
'ArcanistPromptsWorkflow' => 'toolset/workflow/ArcanistPromptsWorkflow.php',
'ArcanistPublicPropertyXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistPublicPropertyXHPASTLinterRule.php',
'ArcanistPublicPropertyXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistPublicPropertyXHPASTLinterRuleTestCase.php',
@@ -1420,6 +1422,8 @@
'ArcanistPlusOperatorOnStringsXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
'ArcanistProjectConfigurationSource' => 'ArcanistWorkingCopyConfigurationSource',
'ArcanistPrompt' => 'Phobject',
+ 'ArcanistPromptResponse' => 'Phobject',
+ 'ArcanistPromptsConfigOption' => 'ArcanistMultiSourceConfigOption',
'ArcanistPromptsWorkflow' => 'ArcanistWorkflow',
'ArcanistPublicPropertyXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
'ArcanistPublicPropertyXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
diff --git a/src/config/arc/ArcanistArcConfigurationEngineExtension.php b/src/config/arc/ArcanistArcConfigurationEngineExtension.php
--- a/src/config/arc/ArcanistArcConfigurationEngineExtension.php
+++ b/src/config/arc/ArcanistArcConfigurationEngineExtension.php
@@ -6,6 +6,7 @@
const EXTENSIONKEY = 'arc';
const KEY_ALIASES = 'aliases';
+ const KEY_PROMPTS = 'prompts';
public function newConfigurationOptions() {
// TOOLSETS: Restore "load", and maybe this other stuff.
@@ -113,6 +114,14 @@
pht(
'Configured command aliases. Use the "alias" workflow to define '.
'aliases.')),
+ id(new ArcanistPromptsConfigOption())
+ ->setKey(self::KEY_PROMPTS)
+ ->setDefaultValue(array())
+ ->setSummary(pht('List of prompt responses.'))
+ ->setHelp(
+ pht(
+ 'Configured prompt aliases. Use the "prompts" workflow to '.
+ 'show prompts and responses.')),
id(new ArcanistStringListConfigOption())
->setKey('arc.land.onto')
->setDefaultValue(array())
diff --git a/src/config/option/ArcanistPromptsConfigOption.php b/src/config/option/ArcanistPromptsConfigOption.php
new file mode 100644
--- /dev/null
+++ b/src/config/option/ArcanistPromptsConfigOption.php
@@ -0,0 +1,51 @@
+<?php
+
+final class ArcanistPromptsConfigOption
+ extends ArcanistMultiSourceConfigOption {
+
+ public function getType() {
+ return 'map<string, prompt>';
+ }
+
+ public function getValueFromStorageValue($value) {
+ if (!is_array($value)) {
+ throw new Exception(pht('Expected a list!'));
+ }
+
+ if (!phutil_is_natural_list($value)) {
+ throw new Exception(pht('Expected a natural list!'));
+ }
+
+ $responses = array();
+ foreach ($value as $spec) {
+ $responses[] = ArcanistPromptResponse::newFromConfig($spec);
+ }
+
+ return $responses;
+ }
+
+ protected function didReadStorageValueList(array $list) {
+ assert_instances_of($list, 'ArcanistConfigurationSourceValue');
+
+ $results = array();
+ foreach ($list as $spec) {
+ $source = $spec->getConfigurationSource();
+ $value = $spec->getValue();
+
+ $value->setConfigurationSource($source);
+
+ $results[] = $value;
+ }
+
+ return $results;
+ }
+
+ public function getDisplayValueFromValue($value) {
+ return pht('Use the "prompts" workflow to review prompt responses.');
+ }
+
+ public function getStorageValueFromValue($value) {
+ return mpull($value, 'getStorageDictionary');
+ }
+
+}
diff --git a/src/config/source/ArcanistConfigurationSource.php b/src/config/source/ArcanistConfigurationSource.php
--- a/src/config/source/ArcanistConfigurationSource.php
+++ b/src/config/source/ArcanistConfigurationSource.php
@@ -4,6 +4,7 @@
extends Phobject {
const SCOPE_USER = 'user';
+ const SCOPE_WORKING_COPY = 'working-copy';
abstract public function getSourceDisplayName();
abstract public function getAllKeys();
diff --git a/src/config/source/ArcanistLocalConfigurationSource.php b/src/config/source/ArcanistLocalConfigurationSource.php
--- a/src/config/source/ArcanistLocalConfigurationSource.php
+++ b/src/config/source/ArcanistLocalConfigurationSource.php
@@ -7,4 +7,12 @@
return pht('Local Config File');
}
+ public function isWritableConfigurationSource() {
+ return true;
+ }
+
+ public function getConfigurationSourceScope() {
+ return ArcanistConfigurationSource::SCOPE_WORKING_COPY;
+ }
+
}
diff --git a/src/runtime/ArcanistRuntime.php b/src/runtime/ArcanistRuntime.php
--- a/src/runtime/ArcanistRuntime.php
+++ b/src/runtime/ArcanistRuntime.php
@@ -884,7 +884,6 @@
$legacy[] = new ArcanistGetConfigWorkflow();
$legacy[] = new ArcanistSetConfigWorkflow();
$legacy[] = new ArcanistInstallCertificateWorkflow();
- $legacy[] = new ArcanistLandWorkflow();
$legacy[] = new ArcanistLintersWorkflow();
$legacy[] = new ArcanistLintWorkflow();
$legacy[] = new ArcanistListWorkflow();
diff --git a/src/toolset/ArcanistPrompt.php b/src/toolset/ArcanistPrompt.php
--- a/src/toolset/ArcanistPrompt.php
+++ b/src/toolset/ArcanistPrompt.php
@@ -70,8 +70,10 @@
$this->getKey()));
}
- $options = '[y/N]';
- $default = 'N';
+ $options = '[y/N/?]';
+ $default = 'n';
+
+ $saved_response = $this->getSavedResponse();
try {
phutil_console_require_tty();
@@ -101,54 +103,78 @@
echo "\n";
$result = null;
+ $is_saved = false;
while (true) {
- echo tsprintf(
- '**<bg:cyan> %s </bg>** %s %s ',
- '>>>',
- $query,
- $options);
-
- 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.
- }
+ if ($saved_response !== null) {
+ $is_saved = true;
+
+ $response = $saved_response;
+ $saved_response = null;
+ } else {
+ echo tsprintf(
+ '**<bg:cyan> %s </bg>** %s %s ',
+ '>>>',
+ $query,
+ $options);
- $response = '';
while (true) {
- $bytes = fread($stdin, 8192);
- if ($bytes === false) {
- throw new Exception(
- pht('fread() from stdin failed with an error.'));
+ $is_saved = false;
+
+ $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.
}
- if (!strlen($bytes)) {
- break;
+ $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;
}
- $response .= $bytes;
+ break;
}
+ $response = trim($response);
if (!strlen($response)) {
- continue;
+ $response = $default;
}
-
- 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') {
@@ -160,12 +186,127 @@
$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);
}
}
diff --git a/src/toolset/ArcanistPromptResponse.php b/src/toolset/ArcanistPromptResponse.php
new file mode 100644
--- /dev/null
+++ b/src/toolset/ArcanistPromptResponse.php
@@ -0,0 +1,59 @@
+<?php
+
+final class ArcanistPromptResponse
+ extends Phobject {
+
+ private $prompt;
+ private $response;
+ private $configurationSource;
+
+ public static function newFromConfig($map) {
+
+ PhutilTypeSpec::checkMap(
+ $map,
+ array(
+ 'prompt' => 'string',
+ 'response' => 'string',
+ ));
+
+ return id(new self())
+ ->setPrompt($map['prompt'])
+ ->setResponse($map['response']);
+ }
+
+ public function getStorageDictionary() {
+ return array(
+ 'prompt' => $this->getPrompt(),
+ 'response' => $this->getResponse(),
+ );
+ }
+
+ public function setPrompt($prompt) {
+ $this->prompt = $prompt;
+ return $this;
+ }
+
+ public function getPrompt() {
+ return $this->prompt;
+ }
+
+ public function setResponse($response) {
+ $this->response = $response;
+ return $this;
+ }
+
+ public function getResponse() {
+ return $this->response;
+ }
+
+ public function setConfigurationSource(
+ ArcanistConfigurationSource $configuration_source) {
+ $this->configurationSource = $configuration_source;
+ return $this;
+ }
+
+ public function getConfigurationSource() {
+ return $this->configurationSource;
+ }
+
+}
diff --git a/src/toolset/workflow/ArcanistPromptsWorkflow.php b/src/toolset/workflow/ArcanistPromptsWorkflow.php
--- a/src/toolset/workflow/ArcanistPromptsWorkflow.php
+++ b/src/toolset/workflow/ArcanistPromptsWorkflow.php
@@ -1,6 +1,7 @@
<?php
-final class ArcanistPromptsWorkflow extends ArcanistWorkflow {
+final class ArcanistPromptsWorkflow
+ extends ArcanistWorkflow {
public function supportsToolset(ArcanistToolset $toolset) {
return true;
@@ -12,14 +13,34 @@
public function getWorkflowInformation() {
$help = pht(<<<EOTEXT
-Show information about prompts a workflow may execute and configure default
+Show information about prompts a workflow may execute, and review saved
responses.
**Show Prompts**
To show possible prompts a workflow may execute, run:
- $ arc prompts <workflow>
+ $ arc prompts __workflow__
+
+**Saving Responses**
+
+If you always want to answer a particular prompt in a certain way, you can
+save your response to the prompt. When you encounter the prompt again, your
+saved response will be used automatically.
+
+To save a response, add "*" or "!" to the end of the response you want to save
+when you answer the prompt:
+
+ - Using "*" will save the response in user configuration. In the future,
+ the saved answer will be used any time you encounter the prompt (in any
+ project).
+ - Using "!" will save the response in working copy configuration. In the
+ future, the saved answer will be used when you encounter the prompt in
+ the current working copy.
+
+For example, if you would like to always answer "y" to a particular prompt,
+respond with "y*" or "y!" to save your response.
+
EOTEXT
);
@@ -65,16 +86,51 @@
return 0;
}
+ $prompts = msort($prompts, 'getKey');
+
+ $blocks = array();
foreach ($prompts as $prompt) {
- echo tsprintf(
- "**%s**\n",
+ $block = array();
+ $block[] = tsprintf(
+ "<bg:green>** %s **</bg> **%s**\n\n",
+ pht('PROMPT'),
$prompt->getKey());
- echo tsprintf(
- "%s\n",
+ $block[] = tsprintf(
+ "%W\n",
$prompt->getDescription());
+
+ $responses = $this->getSavedResponses($prompt->getKey());
+ if ($responses) {
+ $block[] = tsprintf("\n");
+ foreach ($responses as $response) {
+ $block[] = tsprintf(
+ " <bg:cyan>** > **</bg> %s\n",
+ pht(
+ 'You have saved the response "%s" to this prompt.',
+ $response->getResponse()));
+ }
+ }
+
+ $blocks[] = $block;
}
+ echo tsprintf('%B', phutil_glue($blocks, tsprintf("\n")));
+
return 0;
}
+ private function getSavedResponses($prompt_key) {
+ $config_key = ArcanistArcConfigurationEngineExtension::KEY_PROMPTS;
+ $config = $this->getConfig($config_key);
+
+ $responses = array();
+ foreach ($config as $response) {
+ if ($response->getPrompt() === $prompt_key) {
+ $responses[] = $response;
+ }
+ }
+
+ return $responses;
+ }
+
}
diff --git a/src/workflow/ArcanistLandWorkflow.php b/src/workflow/ArcanistLandWorkflow.php
--- a/src/workflow/ArcanistLandWorkflow.php
+++ b/src/workflow/ArcanistLandWorkflow.php
@@ -237,12 +237,13 @@
$this->newPrompt('arc.land.confirm')
->setDescription(
pht(
- 'Confirms that the correct changes have been selected.')),
+ 'Confirms that the correct changes have been selected to '.
+ 'land.')),
$this->newPrompt('arc.land.implicit')
->setDescription(
pht(
'Confirms that local commits which are not associated with '.
- 'a revision should land.')),
+ 'a revision have been associated correctly and should land.')),
$this->newPrompt('arc.land.unauthored')
->setDescription(
pht(
@@ -267,11 +268,11 @@
$this->newPrompt('arc.land.failed-builds')
->setDescription(
pht(
- 'Confirms that revisions with failed builds.')),
+ 'Confirms that revisions with failed builds should land.')),
$this->newPrompt('arc.land.ongoing-builds')
->setDescription(
pht(
- 'Confirms that revisions with ongoing builds.')),
+ 'Confirms that revisions with ongoing builds should land.')),
);
}

File Metadata

Mime Type
text/plain
Expires
Fri, Mar 21, 4:59 PM (6 d, 13 h ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
7326128
Default Alt Text
D21331.id50761.diff (17 KB)

Event Timeline