Changeset View
Changeset View
Standalone View
Standalone View
scripts/symbols/generate_ctags_symbols.php
| #!/usr/bin/env php | #!/usr/bin/env php | ||||
| <?php | <?php | ||||
| $root = dirname(dirname(dirname(__FILE__))); | $root = dirname(dirname(dirname(__FILE__))); | ||||
| require_once $root.'/scripts/__init_script__.php'; | require_once $root.'/scripts/__init_script__.php'; | ||||
| $args = new PhutilArgumentParser($argv); | |||||
| $args->setSynopsis(<<<EOSYNOPSIS | |||||
| **generate_ctags_symbols.php** [__options__] | |||||
| Generate repository symbols using Exuberant Ctags. Paths are read from stdin. | |||||
| EOSYNOPSIS | |||||
| ); | |||||
| $args->parseStandardArguments(); | |||||
| if (ctags_check_executable() == false) { | if (ctags_check_executable() == false) { | ||||
| echo phutil_console_format( | echo phutil_console_format( | ||||
| "Could not find Exuberant ctags. Make sure it is installed and\n". | "%s\n\n%s\n", | ||||
| "available in executable path.\n\n". | pht( | ||||
| "Exuberant ctags project page: http://ctags.sourceforge.net/\n"); | 'Could not find Exuberant Ctags. Make sure it is installed and '. | ||||
| 'available in executable path.'), | |||||
| pht( | |||||
| 'Exuberant Ctags project page: %s', | |||||
| 'http://ctags.sourceforge.net/')); | |||||
| exit(1); | exit(1); | ||||
| } | } | ||||
| if ($argc !== 1 || posix_isatty(STDIN)) { | if (posix_isatty(STDIN)) { | ||||
| echo phutil_console_format( | echo phutil_console_format( | ||||
| "usage: find . -type f -name '*.py' | ./generate_ctags_symbols.php\n"); | "%s\n", | ||||
| pht( | |||||
| 'Usage: %s', | |||||
| "find . -type f -name '*.py' | ./generate_ctags_symbols.php")); | |||||
| exit(1); | exit(1); | ||||
| } | } | ||||
| $input = file_get_contents('php://stdin'); | $input = file_get_contents('php://stdin'); | ||||
| $input = trim($input); | |||||
| $input = explode("\n", $input); | |||||
| $data = array(); | $data = array(); | ||||
| $futures = array(); | $futures = array(); | ||||
| foreach ($input as $file) { | foreach (explode("\n", trim($input)) as $file) { | ||||
| $file = Filesystem::readablePath($file); | $file = Filesystem::readablePath($file); | ||||
| $futures[$file] = ctags_get_parser_future($file); | $futures[$file] = ctags_get_parser_future($file); | ||||
| } | } | ||||
| $futures = id(new FutureIterator($futures)) | $futures = new FutureIterator($futures); | ||||
| ->limit(8); | foreach ($futures->limit(8) as $file => $future) { | ||||
| foreach ($futures as $file => $future) { | |||||
| $tags = $future->resolve(); | $tags = $future->resolve(); | ||||
| $tags = explode("\n", $tags[1]); | $tags = explode("\n", $tags[1]); | ||||
| foreach ($tags as $tag) { | foreach ($tags as $tag) { | ||||
| $parts = explode(';', $tag); | $parts = explode(';', $tag); | ||||
| // skip lines that we can not parse | |||||
| // Skip lines that we can not parse. | |||||
| if (count($parts) < 2) { | if (count($parts) < 2) { | ||||
| continue; | continue; | ||||
| } | } | ||||
| // split ctags information | // Split ctags information. | ||||
| $tag_info = explode("\t", $parts[0]); | $tag_info = explode("\t", $parts[0]); | ||||
| // split exuberant ctags "extension fields" (additional information) | |||||
| // Split exuberant ctags "extension fields" (additional information). | |||||
| $parts[1] = trim($parts[1], "\t \""); | $parts[1] = trim($parts[1], "\t \""); | ||||
| $extension_fields = explode("\t", $parts[1]); | $extension_fields = explode("\t", $parts[1]); | ||||
| // skip lines that we can not parse | // Skip lines that we can not parse. | ||||
| if (count($tag_info) < 3 || count($extension_fields) < 2) { | if (count($tag_info) < 3 || count($extension_fields) < 2) { | ||||
| continue; | continue; | ||||
| } | } | ||||
| // default $context to empty | // Default context to empty. | ||||
| $extension_fields[] = ''; | $extension_fields[] = ''; | ||||
| list($token, $file_path, $line_num) = $tag_info; | list($token, $file_path, $line_num) = $tag_info; | ||||
| list($type, $language, $context) = $extension_fields; | list($type, $language, $context) = $extension_fields; | ||||
| // skip lines with tokens containing a space | // Skip lines with tokens containing a space. | ||||
| if (strpos($token, ' ') !== false) { | if (strpos($token, ' ') !== false) { | ||||
| continue; | continue; | ||||
| } | } | ||||
| // strip "language:" | // Strip "language:" | ||||
| $language = substr($language, 9); | $language = substr($language, 9); | ||||
| // To keep consistent with "Separate with commas, for example: php, py" | // To keep consistent with "Separate with commas, for example: php, py" | ||||
| // in Arcanist Project edit form. | // in Arcanist Project edit form. | ||||
| $language = str_ireplace('python', 'py', $language); | $language = str_ireplace('python', 'py', $language); | ||||
| // also, "normalize" c++ and c# | // Also, "normalize" C++ and C#. | ||||
| $language = str_ireplace('c++', 'cpp', $language); | $language = str_ireplace('c++', 'cpp', $language); | ||||
| $language = str_ireplace('c#', 'cs', $language); | $language = str_ireplace('c#', 'cs', $language); | ||||
| // Ruby has "singleton method", for example | // Ruby has "singleton method", for example. | ||||
| $type = substr(str_replace(' ', '_', $type), 0, 12); | $type = substr(str_replace(' ', '_', $type), 0, 12); | ||||
| // class:foo, struct:foo, union:foo, enum:foo, ... | // class:foo, struct:foo, union:foo, enum:foo, ... | ||||
| $context = last(explode(':', $context, 2)); | $context = last(explode(':', $context, 2)); | ||||
| $ignore = array( | $ignore = array( | ||||
| 'variable' => true, | 'variable' => true, | ||||
| ); | ); | ||||
| if (empty($ignore[$type])) { | if (empty($ignore[$type])) { | ||||
| print_symbol($file_path, $line_num, $type, $token, $context, $language); | print_symbol($file_path, $line_num, $type, $token, $context, $language); | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| function ctags_get_parser_future($file_path) { | function ctags_get_parser_future($path) { | ||||
| $future = new ExecFuture('ctags -n --fields=Kls -o - %s', | $future = new ExecFuture('ctags -n --fields=Kls -o - %s', $path); | ||||
| $file_path); | |||||
| return $future; | return $future; | ||||
| } | } | ||||
| function ctags_check_executable() { | function ctags_check_executable() { | ||||
| $future = new ExecFuture('ctags --version'); | $result = exec_manual('ctags --version'); | ||||
| $result = $future->resolve(); | return !empty($result[1]); | ||||
| if (empty($result[1])) { | |||||
| return false; | |||||
| } | |||||
| return true; | |||||
| } | } | ||||
| function print_symbol($file, $line_num, $type, $token, $context, $language) { | function print_symbol($file, $line_num, $type, $token, $context, $language) { | ||||
| // get rid of relative path | // Get rid of relative path. | ||||
| $file = explode('/', $file); | $file = explode('/', $file); | ||||
| if ($file[0] == '.' || $file[0] == '..') { | if ($file[0] == '.' || $file[0] == '..') { | ||||
| array_shift($file); | array_shift($file); | ||||
| } | } | ||||
| $file = '/'.implode('/', $file); | $file = '/'.implode('/', $file); | ||||
| $parts = array( | $parts = array( | ||||
| $context, | $context, | ||||
| $token, | $token, | ||||
| $type, | $type, | ||||
| strtolower($language), | strtolower($language), | ||||
| $line_num, | $line_num, | ||||
| $file, | $file, | ||||
| ); | ); | ||||
| echo implode(' ', $parts)."\n"; | echo implode(' ', $parts)."\n"; | ||||
| } | } | ||||