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"; | ||||
} | } |