diff --git a/src/workflow/ArcanistLintersWorkflow.php b/src/workflow/ArcanistLintersWorkflow.php index 6d70b4d6..a521a586 100644 --- a/src/workflow/ArcanistLintersWorkflow.php +++ b/src/workflow/ArcanistLintersWorkflow.php @@ -11,7 +11,7 @@ final class ArcanistLintersWorkflow extends ArcanistWorkflow { public function getCommandSynopses() { return phutil_console_format(<< array( 'help' => pht('Show detailed information, including options.'), ), + 'search' => array( + 'param' => 'search', + 'repeat' => true, + 'help' => pht( + 'Search for linters. Search is case-insensitive, and is performed'. + 'against name and description of each linter.'), + ), + '*' => 'exact', ); } @@ -46,6 +56,164 @@ EOTEXT $built = array(); } + $linter_info = $this->getLintersInfo($linters, $built); + + $status_map = $this->getStatusMap(); + $pad = ' '; + + $color_map = array( + 'configured' => 'green', + 'available' => 'yellow', + 'error' => 'red', + ); + + $is_verbose = $this->getArgument('verbose'); + $exact = $this->getArgument('exact'); + $search_terms = $this->getArgument('search'); + + if ($exact && $search_terms) { + throw new ArcanistUsageException( + 'Specify either search expression or exact name'); + } + + if ($exact) { + $linter_info = $this->findExactNames($linter_info, $exact); + if (!$linter_info) { + $console->writeOut( + "%s\n", + pht( + 'No match found. Try `%s %s` to search for a linter.', + 'arc linters --search', + $exact[0])); + return; + } + $is_verbose = true; + } + + if ($search_terms) { + $linter_info = $this->filterByNames($linter_info, $search_terms); + } + + + foreach ($linter_info as $key => $linter) { + $status = $linter['status']; + $color = $color_map[$status]; + $text = $status_map[$status]; + $print_tail = false; + + $console->writeOut( + "** %s ** **%s** (%s)\n", + $text, + nonempty($linter['short'], '-'), + $linter['name']); + + if ($linter['exception']) { + $console->writeOut( + "\n%s**%s**\n%s\n", + $pad, + get_class($linter['exception']), + phutil_console_wrap( + $linter['exception']->getMessage(), + strlen($pad))); + $print_tail = true; + } + + if ($is_verbose) { + $version = $linter['version']; + $uri = $linter['uri']; + if ($version || $uri) { + $console->writeOut("\n"); + $print_tail = true; + } + + if ($version) { + $console->writeOut("%s%s **%s**\n", $pad, pht('Version'), $version); + } + + if ($uri) { + $console->writeOut("%s__%s__\n", $pad, $linter['uri']); + } + + $description = $linter['description']; + if ($description) { + $console->writeOut( + "\n%s\n", + phutil_console_wrap($linter['description'], strlen($pad))); + $print_tail = true; + } + + $options = $linter['options']; + if ($options) { + $console->writeOut( + "\n%s**%s**\n\n", + $pad, + pht('Configuration Options')); + + $last_option = last_key($options); + foreach ($options as $option => $option_spec) { + $console->writeOut( + "%s__%s__ (%s)\n", + $pad, + $option, + $option_spec['type']); + + $console->writeOut( + "%s\n", + phutil_console_wrap( + $option_spec['help'], + strlen($pad) + 2)); + + if ($option != $last_option) { + $console->writeOut("\n"); + } + } + $print_tail = true; + } + + if ($print_tail) { + $console->writeOut("\n"); + } + } + } + + if (!$is_verbose) { + $console->writeOut( + "%s\n", + pht('(Run `%s` for more details.)', 'arc linters --verbose')); + } + } + + + /** + * Get human-readable linter statuses, padded to fixed width. + * + * @return map Human-readable linter status names. + */ + private function getStatusMap() { + $text_map = array( + 'configured' => pht('CONFIGURED'), + 'available' => pht('AVAILABLE'), + 'error' => pht('ERROR'), + ); + + $sizes = array(); + foreach ($text_map as $key => $string) { + $sizes[$key] = phutil_utf8_console_strlen($string); + } + + $longest = max($sizes); + foreach ($text_map as $key => $string) { + if ($sizes[$key] < $longest) { + $text_map[$key] .= str_repeat(' ', $longest - $sizes[$key]); + } + } + + $text_map['padding'] = str_repeat(' ', $longest); + + return $text_map; + } + + private function getLintersInfo(array $linters, array $built) { // Note that an engine can emit multiple linters of the same class to run // different rulesets on different groups of files, so these linters do not // necessarily have unique classes or types. @@ -85,131 +253,40 @@ EOTEXT ); } - $linter_info = isort($linter_info, 'short'); - - $status_map = $this->getStatusMap(); - $pad = ' '; - - $color_map = array( - 'configured' => 'green', - 'available' => 'yellow', - 'error' => 'red', - ); - - foreach ($linter_info as $key => $linter) { - $status = $linter['status']; - $color = $color_map[$status]; - $text = $status_map[$status]; - $print_tail = false; - - $console->writeOut( - "** %s ** **%s** (%s)\n", - $text, - nonempty($linter['short'], '-'), - $linter['name']); - - if ($linter['exception']) { - $console->writeOut( - "\n%s**%s**\n%s\n", - $pad, - get_class($linter['exception']), - phutil_console_wrap( - $linter['exception']->getMessage(), - strlen($pad))); - $print_tail = true; - } - - $version = $linter['version']; - $uri = $linter['uri']; - if ($version || $uri) { - $console->writeOut("\n"); - $print_tail = true; - } - - if ($version) { - $console->writeOut("%s%s **%s**\n", $pad, pht('Version'), $version); - } - - if ($uri) { - $console->writeOut("%s__%s__\n", $pad, $linter['uri']); - } - - $description = $linter['description']; - if ($description) { - $console->writeOut( - "\n%s\n", - phutil_console_wrap($linter['description'], strlen($pad))); - $print_tail = true; - } - - $options = $linter['options']; - if ($options && $this->getArgument('verbose')) { - $console->writeOut( - "\n%s**%s**\n\n", - $pad, - pht('Configuration Options')); - - $last_option = last_key($options); - foreach ($options as $option => $option_spec) { - $console->writeOut( - "%s__%s__ (%s)\n", - $pad, - $option, - $option_spec['type']); - - $console->writeOut( - "%s\n", - phutil_console_wrap( - $option_spec['help'], - strlen($pad) + 2)); - - if ($option != $last_option) { - $console->writeOut("\n"); - } - } - $print_tail = true; - } - - if ($print_tail) { - $console->writeOut("\n"); - } - } - - if (!$this->getArgument('verbose')) { - $console->writeOut( - "%s\n", - pht('(Run `%s` for more details.)', 'arc linters --verbose')); - } + return isort($linter_info, 'short'); } + private function filterByNames(array $linters, array $search_terms) { + $filtered = array(); - /** - * Get human-readable linter statuses, padded to fixed width. - * - * @return map Human-readable linter status names. - */ - private function getStatusMap() { - $text_map = array( - 'configured' => pht('CONFIGURED'), - 'available' => pht('AVAILABLE'), - 'error' => pht('ERROR'), - ); - - $sizes = array(); - foreach ($text_map as $key => $string) { - $sizes[$key] = phutil_utf8_console_strlen($string); - } - - $longest = max($sizes); - foreach ($text_map as $key => $string) { - if ($sizes[$key] < $longest) { - $text_map[$key] .= str_repeat(' ', $longest - $sizes[$key]); + foreach ($linters as $key => $linter) { + $name = $linter['name']; + $short = $linter['short']; + $description = $linter['description']; + foreach ($search_terms as $term) { + if (stripos($name, $term) !== false || + stripos($short, $term) !== false || + stripos($description, $term) !== false) { + $filtered[$key] = $linter; + } } } + return $filtered; + } - $text_map['padding'] = str_repeat(' ', $longest); + private function findExactNames(array $linters, array $names) { + $filtered = array(); - return $text_map; + foreach ($linters as $key => $linter) { + $name = $linter['name']; + + foreach ($names as $term) { + if (strcasecmp($name, $term) == 0) { + $filtered[$key] = $linter; + } + } + } + return $filtered; } }