3
final class ArcanistFlagWorkflow extends ArcanistWorkflow {
5
private static $colorMap = array(
7
1 => 'yellow', // Orange
8
2 => 'yellow', // Yellow
11
5 => 'magenta', // Pink
12
6 => 'magenta', // Purple
13
7 => 'default', // Checkered
16
private static $colorSpec = array(
17
'red' => 0, 'r' => 0, 0 => 0,
18
'orange' => 1, 'o' => 1, 1 => 1,
19
'yellow' => 2, 'y' => 2, 2 => 2,
20
'green' => 3, 'g' => 3, 3 => 3,
21
'blue' => 4, 'b' => 4, 4 => 4,
22
'pink' => 5, 'p' => 5, 5 => 5,
23
'purple' => 6, 'v' => 6, 6 => 6,
24
'checkered' => 7, 'c' => 7, 7 => 7,
27
public function getWorkflowName() {
31
public function getCommandSynopses() {
32
return phutil_console_format(<<<EOTEXT
33
**flag** [__object__ ...]
34
**flag** __object__ --clear
35
**flag** __object__ [--edit] [--color __color__] [--note __note__]
40
public function getCommandHelp() {
41
return phutil_console_format(<<<EOTEXT
42
In the first form, list objects you've flagged. You can provide the
43
names of one or more objects (Maniphest tasks T#\##, Differential
44
revisions D###, Diffusion references rXXX???, or PHIDs PHID-XXX-???)
45
to print only flags for those objects.
47
In the second form, clear an existing flag on one object.
49
In the third form, create or update a flag on one object. Color
50
defaults to blue and note to empty, but if you omit both you must
56
public function getArguments() {
60
'help' => 'Delete the flag on an object.',
63
'help' => 'Edit the flag on an object.',
67
'help' => 'Set the color of a flag.',
71
'help' => 'Set the note on a flag.',
76
public function requiresConduit() {
80
public function requiresAuthentication() {
84
private static function flagWasEdited($flag, $verb) {
85
$color = idx(self::$colorMap, $flag['color'], 'cyan');
86
$note = $flag['note'];
88
// Make sure notes that are long or have line breaks in them or
89
// whatever don't mess up the formatting.
90
$note = implode(' ', preg_split('/\s+/', $note));
92
id(new PhutilUTF8StringTruncator())
93
->setMaximumGlyphs(40)
94
->setTerminator('...')
95
->truncateString($note).
98
echo phutil_console_format(
99
"<fg:{$color}>%s</fg> flag%s $verb!\n",
104
public function run() {
105
$conduit = $this->getConduit();
106
$objects = $this->getArgument('objects', array());
109
$clear = $this->getArgument('clear');
110
$edit = $this->getArgument('edit');
111
// I don't trust PHP to distinguish 0 (red) from null.
112
$color = $this->getArgument('color', -1);
113
$note = $this->getArgument('note');
114
$editing = $edit || ($color != -1) || $note;
116
if ($editing && $clear) {
117
throw new ArcanistUsageException("You can't both edit and clear a flag.");
119
if (($editing || $clear) && count($objects) != 1) {
120
throw new ArcanistUsageException('Specify exactly one object.');
123
if (!empty($objects)) {
124
// First off, convert the passed objects to PHIDs.
125
$handles = $conduit->callMethodSynchronous(
130
foreach ($objects as $object) {
131
if (isset($handles[$object])) {
132
$phids[$object] = $handles[$object]['phid'];
134
echo phutil_console_format("**%s** doesn't exist.\n", $object);
138
// flag.query treats an empty objectPHIDs parameter as "don't use this
139
// constraint". However, if the user gives a list of objects but none
140
// of them exist and have flags, we shouldn't dump the full list on
141
// them after telling them that. Conveniently, we already told them,
142
// so we can go quit now.
148
// All right, we're going to clear a flag. First clear it. Then tell the
149
// user we cleared it. Step four: profit!
150
$flag = $conduit->callMethodSynchronous(
153
'objectPHID' => head($phids),
156
echo phutil_console_format("**%s** has no flag to clear.\n", $object);
158
self::flagWasEdited($flag, 'deleted');
160
} else if ($editing) {
161
// Let's set some flags. Just like Minesweeper, but less distracting.
162
$flag_params = array(
163
'objectPHID' => head($phids),
165
if (isset(self::$colorSpec[$color])) {
166
$flag_params['color'] = self::$colorSpec[strtolower($color)];
169
$flag_params['note'] = $note;
171
$flag = $conduit->callMethodSynchronous(
174
self::flagWasEdited($flag, $flag['new'] ? 'created' : 'edited');
176
// Okay, list mode. Let's find the flags, which we didn't need to do
177
// otherwise because Conduit does it for us.
179
$this->getConduit()->callMethodSynchronous(
182
'ownerPHIDs' => array($this->getUserPHID()),
183
'objectPHIDs' => array_values($phids),
187
foreach ($phids as $object => $phid) {
188
if (!isset($flags[$phid])) {
189
echo phutil_console_format("**%s** has no flag.\n", $object);
194
// If the user passed no object names, then we should print the full
195
// list, but it's empty, so tell the user they have no flags.
196
// If the user passed object names, we already told them all their
197
// objects are nonexistent or unflagged.
198
if (empty($objects)) {
199
echo "You have no flagged objects.\n";
202
// Print ALL the flags. With fancy formatting. Because fancy formatting
204
$name_len = 1 + max(array_map('strlen', ipull($flags, 'colorName')));
205
foreach ($flags as $flag) {
206
$color = idx(self::$colorMap, $flag['color'], 'cyan');
207
echo phutil_console_format(
208
"[<fg:{$color}>%s</fg>] %s\n",
209
str_pad($flag['colorName'], $name_len),
210
$flag['handle']['fullname']);
212
$note = phutil_console_wrap($flag['note'], $name_len + 3);
213
echo rtrim($note)."\n";