diff --git a/scripts/utils/utf8.php b/scripts/utils/utf8.php index 95ba740..57bf629 100755 --- a/scripts/utils/utf8.php +++ b/scripts/utils/utf8.php @@ -1,189 +1,189 @@ #!/usr/bin/env php setTagline('utf8 charset test script'); $args->setSynopsis(<<parseStandardArguments(); $args->parse(array( array( 'name' => 'context', 'short' => 'C', 'param' => 'lines', 'default' => 3, 'help' => 'Show __lines__ lines of context instead of the default 3.', 'conflicts' => array( 'test' => 'with --test, context is not shown.', ), ), array( 'name' => 'test', 'short' => 't', 'help' => 'Print file names containing invalid UTF-8 to stdout.', ), array( 'name' => 'help', 'short' => 'h', 'help' => 'Show this help.', ), array( 'name' => 'files', 'wildcard' => true, ), )); $is_test = $args->getArg('test'); $context = $args->getArg('context'); $files = $args->getArg('files'); if (empty($files)) { $args->printHelpAndExit(); } if ($is_test) { $err = test($files); } else { $err = show($files, $context); } exit($err); function read($file) { if ($file == '-') { return file_get_contents('php://stdin'); } else { return Filesystem::readFile($file); } } function name($file) { if ($file == '-') { return 'stdin'; } else { return $file; } } function test(array $files) { foreach ($files as $file) { $data = read($file); if (!phutil_is_utf8($data)) { echo name($file)."\n"; } } return 0; } function show(array $files, $context) { foreach ($files as $file) { $data = read($file); $ok = phutil_is_utf8($data); if ($ok) { echo "OKAY"; } else { echo "FAIL"; } echo " ".name($file)."\n"; if (!$ok) { $lines = explode("\n", $data); $len = count($lines); $map = array(); $bad = array(); foreach ($lines as $n => $line) { if (phutil_is_utf8($line)) { continue; } $bad[$n] = true; for ($jj = max(0, $n - $context); $jj < min($len, $n + 1 + $context); $jj++) { $map[$jj] = true; } } $width = strlen(max(array_keys($map))); // Set $last such that we print a newline on the first iteration thorugh // the loop. $last = -2; foreach ($map as $idx => $ignored) { if ($idx != $last + 1) { printf("\n"); } $last = $idx; $line = $lines[$idx]; if (!empty($bad[$idx])) { $line = show_problems($line); } printf(" % {$width}d %s\n", $idx + 1, $line); } echo "\n"; } } } function show_problems($line) { $regex = "/^(". "[\x01-\x7F]+". "|([\xC2-\xDF][\x80-\xBF])". "|([\xE0-\xEF][\x80-\xBF][\x80-\xBF])". "|([\xF0-\xF4][\x80-\xBF][\x80-\xBF][\x80-\xBF]))/"; $out = ''; while (strlen($line)) { $match = null; if (preg_match($regex, $line, $match)) { $out .= $match[1]; $line = substr($line, strlen($match[1])); } else { $chr = sprintf("<0x%0X>", ord($line[0])); $chr = phutil_console_format('##%s##', $chr); $out .= $chr; $line = substr($line, 1); } } return $out; } diff --git a/src/parser/argument/parser/PhutilArgumentParser.php b/src/parser/argument/parser/PhutilArgumentParser.php index fe926af..6d0ea60 100644 --- a/src/parser/argument/parser/PhutilArgumentParser.php +++ b/src/parser/argument/parser/PhutilArgumentParser.php @@ -1,397 +1,424 @@ bin = $argv[0]; $this->argv = array_slice($argv, 1); } public function parsePartial(array $specs) { foreach ($specs as $key => $spec) { if (is_array($spec)) { $specs[$key] = PhutilArgumentSpecification::newQuickSpec( $spec); } } $this->mergeSpecs($specs); $specs_by_name = mpull($specs, null, 'getName'); $specs_by_short = mpull($specs, null, 'getShortAlias'); unset($specs_by_short[null]); $argv = $this->argv; $len = count($argv); for ($ii = 0; $ii < $len; $ii++) { $arg = $argv[$ii]; $map = null; if ($arg == '--') { // This indicates "end of flags". break; } else if ($arg == '-') { // This is a normal argument (e.g., stdin). continue; } else if (!strncmp('--', $arg, 2)) { $pre = '--'; $arg = substr($arg, 2); $map = $specs_by_name; } else if (!strncmp('-', $arg, 1) && strlen($arg) > 1) { $pre = '-'; $arg = substr($arg, 1); $map = $specs_by_short; } if ($map) { $val = null; $parts = explode('=', $arg, 2); if (count($parts) == 2) { list($arg, $val) = $parts; } if (isset($map[$arg])) { $spec = $map[$arg]; unset($argv[$ii]); $param_name = $spec->getParamName(); if ($val !== null) { if ($param_name === null) { throw new PhutilArgumentUsageException( "Argument '{$pre}{$arg}' does not take a parameter."); } } else { if ($param_name !== null) { if ($ii + 1 < $len) { $val = $argv[$ii + 1]; unset($argv[$ii + 1]); $ii++; } else { throw new PhutilArgumentUsageException( "Argument '{$pre}{$arg}' requires a parameter."); } } else { $val = true; } } if (!$spec->getRepeatable()) { if (array_key_exists($spec->getName(), $this->results)) { throw new PhutilArgumentUsageException( "Argument '{$pre}{$arg}' was provided twice."); } } $conflicts = $spec->getConflicts(); foreach ($conflicts as $conflict => $reason) { if (array_key_exists($conflict, $this->results)) { if (!is_string($reason) || !strlen($reason)) { $reason = '.'; } else { $reason = ': '.$reason.'.'; } throw new PhutilArgumentUsageException( "Argument '{$pre}{$arg}' conflicts with argument ". "'--{$conflict}'{$reason}"); } } if ($spec->getRepeatable()) { if ($spec->getParamName() === null) { if (empty($this->results[$spec->getName()])) { $this->results[$spec->getName()] = 0; } $this->results[$spec->getName()]++; } else { $this->results[$spec->getName()][] = $val; } } else { $this->results[$spec->getName()] = $val; } } } } foreach ($specs as $spec) { if ($spec->getWildcard()) { $this->results[$spec->getName()] = $this->filterWildcardArgv($argv); $argv = array(); break; } } $this->argv = array_values($argv); return $this; } public function parseFull(array $specs) { $this->parsePartial($specs); if (count($this->argv)) { $arg = head($this->argv); throw new PhutilArgumentUsageException( "Unrecognized argument '{$arg}'."); } return $this; } public function parse(array $specs) { try { $this->parseFull($specs); } catch (PhutilArgumentUsageException $ex) { $this->printUsageException($ex); exit(77); } } public function getArg($name) { if (empty($this->specs[$name])) { throw new PhutilArgumentSpecificationException( "No specification exists for argument '{$name}'!"); } if (idx($this->results, $name) !== null) { return $this->results[$name]; } return $this->specs[$name]->getDefault(); } public function getUnconsumedArgumentVector() { return $this->argv; } private function filterWildcardArgv(array $argv) { foreach ($argv as $key => $value) { if ($value == '--') { unset($argv[$key]); break; } else if (!strncmp($value, '-', 1) && strlen($value) > 1) { throw new PhutilArgumentUsageException( "Argument '{$value}' is unrecognized. Use '--' to indicate the ". "end of flags."); } } return array_values($argv); } private function mergeSpecs(array $specs) { $short_map = mpull($this->specs, null, 'getShortAlias'); unset($short_map[null]); $wildcard = null; foreach ($this->specs as $spec) { if ($spec->getWildcard()) { $wildcard = $spec; break; } } foreach ($specs as $spec) { $spec->validate(); $name = $spec->getName(); if (isset($this->specs[$name])) { throw new PhutilArgumentSpecificationException( "Two argument specifications have the same name ('{$name}')."); } $short = $spec->getShortAlias(); if ($short) { if (isset($short_map[$short])) { throw new PhutilArgumentSpecificationException( "Two argument specifications have the same short alias ". "('{$short}')."); } $short_map[$short] = $spec; } if ($spec->getWildcard()) { if ($wildcard) { throw new PhutilArgumentSpecificationException( "Two argument specifications are marked as wildcard arguments. ". "You can have a maximum of one wildcard argument."); } else { $wildcard = $spec; } } $this->specs[$name] = $spec; } foreach ($this->specs as $name => $spec) { foreach ($spec->getConflicts() as $conflict => $reason) { if (empty($this->specs[$conflict])) { throw new PhutilArgumentSpecificationException( "Argument '{$name}' conflicts with unspecified argument ". "'{$conflict}'."); } if ($conflict == $name) { throw new PhutilArgumentSpecificationException( "Argument '{$name}' conflicts with itself!"); } } } } public function setSynopsis($synopsis) { $this->synopsis = $synopsis; return $this; } public function setTagline($tagline) { $this->tagline = $tagline; return $this; } public function renderHelp() { $out = array(); if ($this->bin) { $out[] = $this->format('**NAME**'); $name = $this->indent(6, '**%s**', basename($this->bin)); if ($this->tagline) { $name .= $this->format(' - '.$this->tagline); } $out[] = $name; $out[] = null; } if ($this->synopsis) { $out[] = $this->format('**SYNOPSIS**'); $out[] = $this->indent(6, $this->synopsis); $out[] = null; } $specs = $this->specs; foreach ($specs as $key => $spec) { if ($spec->getWildcard()) { unset($specs[$key]); } } if ($specs) { $out[] = $this->format('**OPTION REFERENCE**'); $out[] = null; $specs = msort($specs, 'getName'); foreach ($specs as $spec) { $name = $this->indent(6, '__--%s__', $spec->getName()); $short = null; if ($spec->getShortAlias()) { $short = $this->format(', __-%s__', $spec->getShortAlias()); } if ($spec->getParamName()) { $param = $this->format(' __%s__', $spec->getParamName()); $name .= $param; if ($short) { $short .= $param; } } $out[] = $name.$short; $out[] = $this->indent(10, $spec->getHelp()); $out[] = null; } } $out[] = null; return implode("\n", $out); } public function printHelpAndExit() { echo $this->renderHelp(); exit(77); } private function format($str /*, ... */) { $args = func_get_args(); return call_user_func_array( 'phutil_console_format', $args); } private function indent($level, $str /*, ... */) { $args = func_get_args(); $args = array_slice($args, 1); $text = call_user_func_array(array($this, 'format'), $args); return str_repeat(' ', $level).phutil_console_wrap($text, $level); } /** * Parse "standard" arguments and apply their effects: * - * --trace Enable service call tracing. - * --no-ansi Disable ANSI color/style sequences. + * --trace Enable service call tracing. + * --no-ansi Disable ANSI color/style sequences. + * --xprofile Write out an XHProf profile. * * @return this + * + * @phutil-external-symbol function xhprof_enable */ public function parseStandardArguments() { try { $this->parsePartial( array( array( 'name' => 'trace', 'help' => 'Trace command execution and show service calls.', ), array( 'name' => 'no-ansi', 'help' => 'Disable ANSI terminal codes, printing plain text with '. 'no color or style.', ), + array( + 'name' => 'xprofile', + 'param' => 'profile', + 'help' => 'Profile script execution and write results to a file.', + ), )); } catch (PhutilArgumentUsageException $ex) { $this->printUsageException($ex); exit(77); } if ($this->getArg('trace')) { PhutilServiceProfiler::installEchoListener(); } if ($this->getArg('no-ansi')) { PhutilConsoleFormatter::disableANSI(true); } if (function_exists('posix_isatty') && !posix_isatty(STDOUT)) { PhutilConsoleFormatter::disableANSI(true); } + $xprofile = $this->getArg('xprofile'); + if ($xprofile) { + if (!function_exists('xhprof_enable')) { + throw new Exception("To use '--xprofile', you must install XHProf."); + } + + xhprof_enable(0); + register_shutdown_function(array($this, 'shutdownProfiler')); + } + return $this; } + /** + * @phutil-external-symbol function xhprof_disable + */ + public function shutdownProfiler() { + $data = xhprof_disable(); + $data = serialize($data); + Filesystem::writeFile($this->getArg('xprofile'), $data); + } + public function printUsageException(PhutilArgumentUsageException $ex) { file_put_contents( 'php://stderr', $this->format('**Usage Exception:** '.$ex->getMessage()."\n")); } } diff --git a/src/parser/argument/parser/__init__.php b/src/parser/argument/parser/__init__.php index 5d39fe2..d507458 100644 --- a/src/parser/argument/parser/__init__.php +++ b/src/parser/argument/parser/__init__.php @@ -1,17 +1,18 @@