diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 98139c72..11cff90a 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -50,6 +50,7 @@ phutil_register_library_map(array( 'ArcanistEventType' => 'events/constant/ArcanistEventType.php', 'ArcanistExportWorkflow' => 'workflow/ArcanistExportWorkflow.php', 'ArcanistFilenameLinter' => 'lint/linter/ArcanistFilenameLinter.php', + 'ArcanistFlagWorkflow' => 'workflow/ArcanistFlagWorkflow.php', 'ArcanistGeneratedLinter' => 'lint/linter/ArcanistGeneratedLinter.php', 'ArcanistGetConfigWorkflow' => 'workflow/ArcanistGetConfigWorkflow.php', 'ArcanistGitAPI' => 'repository/api/ArcanistGitAPI.php', @@ -168,6 +169,7 @@ phutil_register_library_map(array( 'ArcanistEventType' => 'PhutilEventType', 'ArcanistExportWorkflow' => 'ArcanistBaseWorkflow', 'ArcanistFilenameLinter' => 'ArcanistLinter', + 'ArcanistFlagWorkflow' => 'ArcanistBaseWorkflow', 'ArcanistGeneratedLinter' => 'ArcanistLinter', 'ArcanistGetConfigWorkflow' => 'ArcanistBaseWorkflow', 'ArcanistGitAPI' => 'ArcanistRepositoryAPI', diff --git a/src/workflow/ArcanistFlagWorkflow.php b/src/workflow/ArcanistFlagWorkflow.php new file mode 100644 index 00000000..6e172ee8 --- /dev/null +++ b/src/workflow/ArcanistFlagWorkflow.php @@ -0,0 +1,231 @@ + 'red', // Red + 1 => 'yellow', // Orange + 2 => 'yellow', // Yellow + 3 => 'green', // Green + 4 => 'blue', // Blue + 5 => 'magenta', // Pink + 6 => 'magenta', // Purple + 7 => 'default', // Checkered + ); + + private static $colorSpec = array( + 'red' => 0, 'r' => 0, 0 => 0, + 'orange' => 1, 'o' => 1, 1 => 1, + 'yellow' => 2, 'y' => 2, 2 => 2, + 'green' => 3, 'g' => 3, 3 => 3, + 'blue' => 4, 'b' => 4, 4 => 4, + 'pink' => 5, 'p' => 5, 5 => 5, + 'purple' => 6, 'v' => 6, 6 => 6, + 'checkered' => 7, 'c' => 7, 7 => 7, + ); + + public function getCommandSynopses() { + return phutil_console_format(<< 'objects', + 'clear' => array( + 'help' => 'Delete the flag on an object.' + ), + 'edit' => array( + 'help' => 'Edit the flag on an object.' + ), + 'color' => array( + 'param' => 'color', + 'help' => 'Set the color of a flag.' + ), + 'note' => array( + 'param' => 'note', + 'help' => 'Set the note on a flag.' + ), + ); + } + + public function requiresConduit() { + return true; + } + + public function requiresAuthentication() { + return true; + } + + private static function flagWasEdited($flag, $verb) { + $color = idx(self::$colorMap, $flag['color'], 'cyan'); + $note = $flag['note']; + if ($note) { + // Make sure notes that are long or have line breaks in them or + // whatever don't mess up the formatting. + $note = implode(' ', preg_split('/\s+/', $note)); + $note = ' ('.phutil_utf8_shorten($note, 40, '...').')'; + } + echo phutil_console_format( + "%s flag%s $verb!\n", + $flag['colorName'], + $note); + } + + public function run() { + $conduit = $this->getConduit(); + $objects = $this->getArgument('objects', array()); + $phids = array(); + + $clear = $this->getArgument('clear'); + $edit = $this->getArgument('edit'); + // I don't trust PHP to distinguish 0 (red) from null. + $color = $this->getArgument('color', -1); + $note = $this->getArgument('note'); + $editing = $edit || ($color != -1) || $note; + + if ($editing && $clear) { + throw new ArcanistUsageException("You can't both edit and clear a flag."); + } + if (($editing || $clear) && count($objects) != 1) { + throw new ArcanistUsageException("Specify exactly one object."); + } + + if (!empty($objects)) { + // First off, convert the passed objects to PHIDs. + $handles = $conduit->callMethodSynchronous( + 'phid.lookup', + array( + 'names' => $objects, + )); + foreach ($objects as $object) { + if (isset($handles[$object])) { + $phids[$object] = $handles[$object]['phid']; + } else { + echo phutil_console_format("**%s** doesn't exist.\n", $object); + } + } + if (empty($phids)) { + // flag.query treats an empty objectPHIDs parameter as "don't use this + // constraint". However, if the user gives a list of objects but none + // of them exist and have flags, we shouldn't dump the full list on + // them after telling them that. Conveniently, we already told them, + // so we can go quit now. + return 0; + } + } + + if ($clear) { + // All right, we're going to clear a flag. First clear it. Then tell the + // user we cleared it. Step four: profit! + $flag = $conduit->callMethodSynchronous( + 'flag.delete', + array( + 'objectPHID' => head($phids), + )); + if (!$flag) { + echo phutil_console_format("**%s** has no flag to clear.\n", $object); + } else { + self::flagWasEdited($flag, 'deleted'); + } + } elseif ($editing) { + // Let's set some flags. Just like Minesweeper, but less distracting. + $flag_params = array( + 'objectPHID' => head($phids), + ); + if (isset(self::$colorSpec[$color])) { + $flag_params['color'] = self::$colorSpec[strtolower($color)]; + } + if ($note) { + $flag_params['note'] = $note; + } + $flag = $conduit->callMethodSynchronous( + 'flag.edit', + $flag_params + ); + self::flagWasEdited($flag, $flag['new'] ? 'created' : 'edited'); + } else { + // Okay, list mode. Let's find the flags, which we didn't need to do + // otherwise because Conduit does it for us. + $flags = ipull( + $this->getConduit()->callMethodSynchronous( + 'flag.query', + array( + 'ownerPHIDs' => array($this->getUserPHID()), + 'objectPHIDs' => array_values($phids), + )), + null, + 'objectPHID'); + foreach ($phids as $object => $phid) { + if (!isset($flags[$phid])) { + echo phutil_console_format("**%s** has no flag.\n", $object); + } + } + + if (empty($flags)) { + // If the user passed no object names, then we should print the full + // list, but it's empty, so tell the user they have no flags. + // If the user passed object names, we already told them all their + // objects are nonexistent or unflagged. + if (empty($objects)) { + echo "You have no flagged objects.\n"; + } + } else { + // Print ALL the flags. With fancy formatting. Because fancy formatting + // is _cool_. + $name_len = 1 + max(array_map('strlen', ipull($flags, 'colorName'))); + foreach ($flags as $flag) { + $color = idx(self::$colorMap, $flag['color'], 'cyan'); + echo phutil_console_format( + "[%s] %s\n", + str_pad($flag['colorName'], $name_len), + $flag['handle']['fullname']); + if ($flag['note']) { + $note = phutil_console_wrap($flag['note'], $name_len + 3); + echo rtrim($note)."\n"; + } + } + } + } + } + +}