Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F15417415
D21331.id50761.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
17 KB
Referenced Files
None
Subscribers
None
D21331.id50761.diff
View Options
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
Details
Attached
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)
Attached To
Mode
D21331: Allow users to save prompt responses in "arc" workflows
Attached
Detach File
Event Timeline
Log In to Comment