public function execute(PhutilArgumentParser $args)
 {
     $console = PhutilConsole::getConsole();
     $ids = $args->getArg('id');
     if (!$ids) {
         throw new PhutilArgumentUsageException(pht("Use the '%s' flag to specify one or more SMS messages to show.", '--id'));
     }
     $messages = id(new PhabricatorSMS())->loadAllWhere('id IN (%Ld)', $ids);
     if ($ids) {
         $ids = array_fuse($ids);
         $missing = array_diff_key($ids, $messages);
         if ($missing) {
             throw new PhutilArgumentUsageException(pht('Some specified SMS messages do not exist: %s', implode(', ', array_keys($missing))));
         }
     }
     $last_key = last_key($messages);
     foreach ($messages as $message_key => $message) {
         $info = array();
         $info[] = pht('PROPERTIES');
         $info[] = pht('ID: %d', $message->getID());
         $info[] = pht('Status: %s', $message->getSendStatus());
         $info[] = pht('To: %s', $message->getToNumber());
         $info[] = pht('From: %s', $message->getFromNumber());
         $info[] = null;
         $info[] = pht('BODY');
         $info[] = $message->getBody();
         $info[] = null;
         $console->writeOut('%s', implode("\n", $info));
         if ($message_key != $last_key) {
             $console->writeOut("\n%s\n\n", str_repeat('-', 80));
         }
     }
 }
 protected function getPathArgumentForLinterFuture($path)
 {
     $full_path = Filesystem::resolvePath($path);
     $ret = array($full_path);
     // The |path| we get fed needs to be made relative to the project_root,
     // otherwise the |engine| won't recognise it.
     $relative_path = Filesystem::readablePath($full_path, $this->getProjectRoot());
     $changed = $this->getEngine()->getPathChangedLines($relative_path);
     if ($changed !== null) {
         // Convert the ordered set of changed lines to a list of ranges.
         $changed_lines = array_keys(array_filter($changed));
         $ranges = array(array($changed_lines[0], $changed_lines[0]));
         foreach (array_slice($changed_lines, 1) as $line) {
             $range = last($ranges);
             if ($range[1] + 1 === $line) {
                 ++$range[1];
                 $ranges[last_key($ranges)] = $range;
             } else {
                 $ranges[] = array($line, $line);
             }
         }
         foreach ($ranges as $range) {
             $ret[] = sprintf('--lines=%d-%d', $range[0], $range[1]);
         }
     }
     return csprintf('%Ls', $ret);
 }
示例#3
0
 public function __construct(AphrontDatabaseConnection $conn, $pattern)
 {
     $this->conn = $conn;
     $args = func_get_args();
     $args = array_slice($args, 2);
     $this->query = vqsprintf($conn, $pattern, $args);
     self::$futures[] = $this;
     $this->id = last_key(self::$futures);
 }
 public function buildMailSection()
 {
     $inlines = $this->getInlines();
     $comments = mpull($inlines, 'getComment');
     $comments = mpull($comments, null, 'getPHID');
     $parents = $this->loadParents($comments);
     $all_comments = $comments + $parents;
     $this->changesets = $this->loadChangesets($all_comments);
     $this->authors = $this->loadAuthors($all_comments);
     $groups = $this->groupInlines($inlines);
     $hunk_parser = new DifferentialHunkParser();
     $spacer_text = null;
     $spacer_html = phutil_tag('br');
     $section = new PhabricatorMetaMTAMailSection();
     $last_group_key = last_key($groups);
     foreach ($groups as $changeset_id => $group) {
         $changeset = $this->getChangeset($changeset_id);
         if (!$changeset) {
             continue;
         }
         $is_last_group = $changeset_id == $last_group_key;
         $last_inline_key = last_key($group);
         foreach ($group as $inline_key => $inline) {
             $comment = $inline->getComment();
             $parent_phid = $comment->getReplyToCommentPHID();
             $is_last_inline = $inline_key == $last_inline_key;
             $context_text = null;
             $context_html = null;
             if ($parent_phid) {
                 $parent = idx($parents, $parent_phid);
                 if ($parent) {
                     $context_text = $this->renderInline($parent, false, true);
                     $context_html = $this->renderInline($parent, true, true);
                 }
             } else {
                 $patch_text = $this->getPatch($hunk_parser, $comment, false);
                 $context_text = $this->renderPatch($comment, $patch_text, false);
                 $patch_html = $this->getPatch($hunk_parser, $comment, true);
                 $context_html = $this->renderPatch($comment, $patch_html, true);
             }
             $render_text = $this->renderInline($comment, false, false);
             $render_html = $this->renderInline($comment, true, false);
             $section->addPlaintextFragment($context_text);
             $section->addPlaintextFragment($spacer_text);
             $section->addPlaintextFragment($render_text);
             $html_fragment = $this->renderContentBox(array($context_html, $render_html));
             $section->addHTMLFragment($html_fragment);
             if (!$is_last_group || !$is_last_inline) {
                 $section->addPlaintextFragment($spacer_text);
                 $section->addHTMLFragment($spacer_html);
             }
         }
     }
     return $section;
 }
示例#5
0
 public static function popTime($key)
 {
     if ($key !== last_key(self::$stack)) {
         throw new Exception(pht('%s with bad key.', __METHOD__));
     }
     array_pop(self::$stack);
     if (empty(self::$stack)) {
         date_default_timezone_set(self::$originalZone);
     } else {
         $frame = end(self::$stack);
         date_default_timezone_set($frame['timezone']);
     }
 }
示例#6
0
 public static function popTime($key)
 {
     if ($key !== last_key(self::$stack)) {
         throw new Exception('PhabricatorTime::popTime with bad key.');
     }
     array_pop(self::$stack);
     if (empty(self::$stack)) {
         date_default_timezone_set(self::$originalZone);
     } else {
         $frame = end(self::$stack);
         date_default_timezone_set($frame['timezone']);
     }
 }
 public function markupText($text)
 {
     $matches = array();
     $rows = array();
     foreach (explode("\n", $text) as $line) {
         // Ignore ending delimiters.
         $line = rtrim($line, '|');
         preg_match_all('/\\|([^|]*)/', $line, $matches);
         $headings = true;
         $cells = array();
         foreach ($matches[1] as $cell) {
             $cell = trim($cell);
             // Cell isn't empty and doesn't look like heading.
             if (!preg_match('/^(|--+)$/', $cell)) {
                 $headings = false;
             }
             $cells[] = array('type' => 'td', 'content' => $cell);
         }
         if (!$headings) {
             $rows[] = $cells;
         } else {
             if ($rows) {
                 // Mark previous row with headings.
                 foreach ($cells as $i => $cell) {
                     if ($cell['content']) {
                         $rows[last_key($rows)][$i]['type'] = 'th';
                     }
                 }
             }
         }
     }
     if (!$rows) {
         return $this->applyRules($text);
     }
     $out = array();
     $out[] = "<table class=\"remarkup-table\">\n";
     foreach ($rows as $cells) {
         $out[] = '<tr>';
         foreach ($cells as $cell) {
             $out[] = '<' . $cell['type'] . '>';
             $out[] = $this->applyRules($cell['content']);
             $out[] = '</' . $cell['type'] . '>';
         }
         $out[] = "</tr>\n";
     }
     $out[] = "</table>\n";
     return implode($out);
 }
示例#8
0
文件: Tail.php 项目: bundl/lumberjack
 public function execute()
 {
     try {
         $cf = TransactionLog::cf();
         $entries = $cf->getSlice($this->_log, '', '', true, $this->_lines);
         if ($entries) {
             $since = last_key($entries);
             $this->outputEntriesSince($this->_log, $since);
         } else {
             echo "No data found\n";
         }
     } catch (\Exception $e) {
         if ($e->getCode() == 404) {
             echo "No log exists";
         }
         throw $e;
     }
 }
 public function __construct($code)
 {
     // If this is the first time we're building a guard, push the default
     // locale onto the bottom of the stack. We'll never remove it.
     if (empty(self::$stack)) {
         self::$stack[] = PhabricatorEnv::getLocaleCode();
     }
     // If there's no locale, use the server default locale.
     if (!$code) {
         $code = self::$stack[0];
     }
     // Push this new locale onto the stack and set it as the active locale.
     // We keep track of which key this guard owns, in case guards are destroyed
     // out-of-order.
     self::$stack[] = $code;
     $this->key = last_key(self::$stack);
     PhabricatorEnv::setLocaleCode($code);
 }
示例#10
0
 private final function makeContent($include)
 {
     $results = array();
     $lines = explode("\n", $this->changes);
     // NOTE: To determine whether the recomposed file should have a trailing
     // newline, we look for a "\ No newline at end of file" line which appears
     // after a line which we don't exclude. For example, if we're constructing
     // the "new" side of a diff (excluding "-"), we want to ignore this one:
     //
     //    - x
     //    \ No newline at end of file
     //    + x
     //
     // ...since it's talking about the "old" side of the diff, but interpret
     // this as meaning we should omit the newline:
     //
     //    - x
     //    + x
     //    \ No newline at end of file
     $n = strpos($include, '+') !== false ? $this->newOffset : $this->oldOffset;
     $use_next_newline = false;
     foreach ($lines as $line) {
         if (!isset($line[0])) {
             continue;
         }
         if ($line[0] == '\\') {
             if ($use_next_newline) {
                 $results[last_key($results)] = rtrim(end($results), "\n");
             }
         } else {
             if (strpos($include, $line[0]) === false) {
                 $use_next_newline = false;
             } else {
                 $use_next_newline = true;
                 $results[$n] = substr($line, 1) . "\n";
             }
         }
         if ($line[0] == ' ' || strpos($include, $line[0]) !== false) {
             $n++;
         }
     }
     return $results;
 }
 public function render()
 {
     require_celerity_resource('phabricator-source-code-view-css');
     require_celerity_resource('syntax-highlighting-css');
     Javelin::initBehavior('phabricator-oncopy', array());
     if ($this->canClickHighlight) {
         Javelin::initBehavior('phabricator-line-linker');
     }
     $line_number = 1;
     $rows = array();
     $lines = $this->lines;
     if ($this->truncatedFirstLines) {
         $lines[] = phutil_tag('span', array('class' => 'c'), pht('...'));
     } else {
         if ($this->truncatedFirstBytes) {
             $last_key = last_key($lines);
             $lines[$last_key] = hsprintf('%s%s', $lines[$last_key], phutil_tag('span', array('class' => 'c'), pht('...')));
         }
     }
     foreach ($lines as $line) {
         // NOTE: See phabricator-oncopy behavior.
         $content_line = hsprintf("​%s", $line);
         $row_attributes = array();
         if (isset($this->highlights[$line_number])) {
             $row_attributes['class'] = 'phabricator-source-highlight';
         }
         if ($this->canClickHighlight) {
             $line_uri = $this->uri . '$' . $line_number;
             $line_href = (string) new PhutilURI($line_uri);
             $tag_number = javelin_tag('a', array('href' => $line_href), $line_number);
         } else {
             $tag_number = javelin_tag('span', array(), $line_number);
         }
         $rows[] = phutil_tag('tr', $row_attributes, array(javelin_tag('th', array('class' => 'phabricator-source-line', 'sigil' => 'phabricator-source-line'), $tag_number), phutil_tag('td', array('class' => 'phabricator-source-code'), $content_line)));
         $line_number++;
     }
     $classes = array();
     $classes[] = 'phabricator-source-code-view';
     $classes[] = 'remarkup-code';
     $classes[] = 'PhabricatorMonospaced';
     return phutil_tag_div('phabricator-source-code-container', javelin_tag('table', array('class' => implode(' ', $classes), 'sigil' => 'phabricator-source'), phutil_implode_html('', $rows)));
 }
 private function buildResponse($title, $body)
 {
     $nav = $this->buildSideNavView();
     $nav->selectFilter('database/');
     if (!$title) {
         $title = pht('Database Status');
     }
     $ref = $this->ref;
     $database = $this->database;
     $table = $this->table;
     $column = $this->column;
     $key = $this->key;
     $links = array();
     $links[] = array(pht('Database Status'), 'database/');
     if ($database) {
         $links[] = array($database, "database/{$ref}/{$database}/");
     }
     if ($table) {
         $links[] = array($table, "database/{$ref}/{$database}/{$table}/");
     }
     if ($column) {
         $links[] = array($column, "database/{$ref}/{$database}/{$table}/col/{$column}/");
     }
     if ($key) {
         $links[] = array($key, "database/{$ref}/{$database}/{$table}/key/{$key}/");
     }
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs->setBorder(true);
     $last_key = last_key($links);
     foreach ($links as $link_key => $link) {
         list($name, $href) = $link;
         if ($link_key == $last_key) {
             $crumbs->addTextCrumb($name);
         } else {
             $crumbs->addTextCrumb($name, $this->getApplicationURI($href));
         }
     }
     $doc_link = PhabricatorEnv::getDoclink('Managing Storage Adjustments');
     $header = id(new PHUIHeaderView())->setHeader($title)->setProfileHeader(true)->addActionLink(id(new PHUIButtonView())->setTag('a')->setIcon('fa-book')->setHref($doc_link)->setText(pht('Learn More')));
     $content = id(new PhabricatorConfigPageView())->setHeader($header)->setContent($body);
     return $this->newPage()->setTitle($title)->setCrumbs($crumbs)->setNavigation($nav)->appendChild($content)->addClass('white-background');
 }
示例#13
0
 /**
  * Add another future to the set of futures. This is useful if you have a
  * set of futures to run mostly in parallel, but some futures depend on
  * others.
  *
  * @param Future  @{class:Future} to add to iterator
  * @task basics
  */
 public function addFuture(Future $future, $key = null)
 {
     if ($key === null) {
         $this->futures[] = $future;
         $this->wait[] = last_key($this->futures);
     } else {
         if (!isset($this->futures[$key])) {
             $this->futures[$key] = $future;
             $this->wait[] = $key;
         } else {
             throw new Exception("Invalid key {$key}");
         }
     }
     // Start running the future if we don't have $this->limit futures running
     // already. updateWorkingSet() won't start running the future if there's no
     // limit, so we'll manually poke it here in that case.
     $this->updateWorkingSet();
     if (!$this->limit) {
         $future->isReady();
     }
     return $this;
 }
 public function markupText($text, $children)
 {
     $lines = explode("\n", $text);
     $first_key = head_key($lines);
     $last_key = last_key($lines);
     while (trim($lines[$last_key]) === '') {
         unset($lines[$last_key]);
         $last_key = last_key($lines);
     }
     $matches = null;
     preg_match(self::START_BLOCK_PATTERN, head($lines), $matches);
     $argv = array();
     if (isset($matches[2])) {
         $argv = id(new PhutilSimpleOptions())->parse($matches[2]);
     }
     $interpreters = id(new PhutilSymbolLoader())->setAncestorClass('PhutilRemarkupBlockInterpreter')->loadObjects();
     foreach ($interpreters as $interpreter) {
         $interpreter->setEngine($this->getEngine());
     }
     $lines[$first_key] = preg_replace(self::START_BLOCK_PATTERN, '', $lines[$first_key]);
     $lines[$last_key] = preg_replace(self::END_BLOCK_PATTERN, '', $lines[$last_key]);
     if (trim($lines[$first_key]) === '') {
         unset($lines[$first_key]);
     }
     if (trim($lines[$last_key]) === '') {
         unset($lines[$last_key]);
     }
     $content = implode("\n", $lines);
     $interpreters = mpull($interpreters, null, 'getInterpreterName');
     if (isset($interpreters[$matches[1]])) {
         return $interpreters[$matches[1]]->markupContent($content, $argv);
     }
     $message = pht('No interpreter found: %s', $matches[1]);
     if ($this->getEngine()->isTextMode()) {
         return '(' . $message . ')';
     }
     return phutil_tag('div', array('class' => 'remarkup-interpreter-error'), $message);
 }
 public function markupText($text, $children)
 {
     $matches = array();
     $rows = array();
     foreach (explode("\n", $text) as $line) {
         // Ignore ending delimiters.
         $line = rtrim($line, '|');
         // NOTE: The complexity in this regular expression allows us to match
         // a table like "| a | [[ href | b ]] | c |".
         preg_match_all('/\\|' . '(' . '(?:' . '(?:\\[\\[.*?\\]\\])' . '|' . '(?:[^|[]+)' . '|' . '(?:\\[[^\\|[])' . ')*' . ')/', $line, $matches);
         $headings = true;
         $cells = array();
         foreach ($matches[1] as $cell) {
             $cell = trim($cell);
             // Cell isn't empty and doesn't look like heading.
             if (!preg_match('/^(|--+)$/', $cell)) {
                 $headings = false;
             }
             $cells[] = array('type' => 'td', 'content' => $this->applyRules($cell));
         }
         if (!$headings) {
             $rows[] = array('type' => 'tr', 'content' => $cells);
         } else {
             if ($rows) {
                 // Mark previous row with headings.
                 foreach ($cells as $i => $cell) {
                     if ($cell['content']) {
                         $rows[last_key($rows)]['content'][$i]['type'] = 'th';
                     }
                 }
             }
         }
     }
     if (!$rows) {
         return $this->applyRules($text);
     }
     return $this->renderRemarkupTable($rows);
 }
示例#16
0
 /**
  * Get diff parts, but replace large blocks of unchanged text with "."
  * parts representing missing context.
  */
 public function getSummaryParts()
 {
     $parts = $this->getParts();
     $head_key = head_key($parts);
     $last_key = last_key($parts);
     $results = array();
     foreach ($parts as $key => $part) {
         $is_head = $key == $head_key;
         $is_last = $key == $last_key;
         switch ($part['type']) {
             case '=':
                 $pieces = $this->splitTextForSummary($part['text']);
                 if ($is_head || $is_last) {
                     $need = 2;
                 } else {
                     $need = 3;
                 }
                 // We don't have enough pieces to omit anything, so just continue.
                 if (count($pieces) < $need) {
                     $results[] = $part;
                     break;
                 }
                 if (!$is_head) {
                     $results[] = array('type' => '=', 'text' => head($pieces));
                 }
                 $results[] = array('type' => '.', 'text' => null);
                 if (!$is_last) {
                     $results[] = array('type' => '=', 'text' => last($pieces));
                 }
                 break;
             default:
                 $results[] = $part;
                 break;
         }
     }
     return $results;
 }
 public function renderBurn()
 {
     $request = $this->getRequest();
     $user = $request->getUser();
     $handle = null;
     $project_phid = $request->getStr('project');
     if ($project_phid) {
         $phids = array($project_phid);
         $handles = $this->loadViewerHandles($phids);
         $handle = $handles[$project_phid];
     }
     $table = new ManiphestTransaction();
     $conn = $table->establishConnection('r');
     $joins = '';
     if ($project_phid) {
         $joins = qsprintf($conn, 'JOIN %T t ON x.taskID = t.id
       JOIN %T p ON p.taskPHID = t.phid AND p.projectPHID = %s', id(new ManiphestTask())->getTableName(), id(new ManiphestTaskProject())->getTableName(), $project_phid);
     }
     $data = queryfx_all($conn, 'SELECT x.oldValue, x.newValue, x.dateCreated FROM %T x %Q
     WHERE transactionType = %s
     ORDER BY x.dateCreated ASC', $table->getTableName(), $joins, ManiphestTransactionType::TYPE_STATUS);
     $stats = array();
     $day_buckets = array();
     $open_tasks = array();
     foreach ($data as $key => $row) {
         // NOTE: Hack to avoid json_decode().
         $oldv = trim($row['oldValue'], '"');
         $newv = trim($row['newValue'], '"');
         $old_is_open = $oldv === (string) ManiphestTaskStatus::STATUS_OPEN;
         $new_is_open = $newv === (string) ManiphestTaskStatus::STATUS_OPEN;
         $is_open = $new_is_open && !$old_is_open;
         $is_close = $old_is_open && !$new_is_open;
         $data[$key]['_is_open'] = $is_open;
         $data[$key]['_is_close'] = $is_close;
         if (!$is_open && !$is_close) {
             // This is either some kind of bogus event, or a resolution change
             // (e.g., resolved -> invalid). Just skip it.
             continue;
         }
         $day_bucket = phabricator_format_local_time($row['dateCreated'], $user, 'Yz');
         $day_buckets[$day_bucket] = $row['dateCreated'];
         if (empty($stats[$day_bucket])) {
             $stats[$day_bucket] = array('open' => 0, 'close' => 0);
         }
         $stats[$day_bucket][$is_close ? 'close' : 'open']++;
     }
     $template = array('open' => 0, 'close' => 0);
     $rows = array();
     $rowc = array();
     $last_month = null;
     $last_month_epoch = null;
     $last_week = null;
     $last_week_epoch = null;
     $week = null;
     $month = null;
     $last = last_key($stats) - 1;
     $period = $template;
     foreach ($stats as $bucket => $info) {
         $epoch = $day_buckets[$bucket];
         $week_bucket = phabricator_format_local_time($epoch, $user, 'YW');
         if ($week_bucket != $last_week) {
             if ($week) {
                 $rows[] = $this->formatBurnRow('Week of ' . phabricator_date($last_week_epoch, $user), $week);
                 $rowc[] = 'week';
             }
             $week = $template;
             $last_week = $week_bucket;
             $last_week_epoch = $epoch;
         }
         $month_bucket = phabricator_format_local_time($epoch, $user, 'Ym');
         if ($month_bucket != $last_month) {
             if ($month) {
                 $rows[] = $this->formatBurnRow(phabricator_format_local_time($last_month_epoch, $user, 'F, Y'), $month);
                 $rowc[] = 'month';
             }
             $month = $template;
             $last_month = $month_bucket;
             $last_month_epoch = $epoch;
         }
         $rows[] = $this->formatBurnRow(phabricator_date($epoch, $user), $info);
         $rowc[] = null;
         $week['open'] += $info['open'];
         $week['close'] += $info['close'];
         $month['open'] += $info['open'];
         $month['close'] += $info['close'];
         $period['open'] += $info['open'];
         $period['close'] += $info['close'];
     }
     if ($week) {
         $rows[] = $this->formatBurnRow('Week To Date', $week);
         $rowc[] = 'week';
     }
     if ($month) {
         $rows[] = $this->formatBurnRow('Month To Date', $month);
         $rowc[] = 'month';
     }
     $rows[] = $this->formatBurnRow('All Time', $period);
     $rowc[] = 'aggregate';
     $rows = array_reverse($rows);
     $rowc = array_reverse($rowc);
     $table = new AphrontTableView($rows);
     $table->setRowClasses($rowc);
     $table->setHeaders(array('Period', 'Opened', 'Closed', 'Change'));
     $table->setColumnClasses(array('right wide', 'n', 'n', 'n'));
     if ($handle) {
         $header = "Task Burn Rate for Project " . $handle->renderLink();
         $caption = "<p>NOTE: This table reflects tasks <em>currently</em> in " . "the project. If a task was opened in the past but added to " . "the project recently, it is counted on the day it was " . "opened, not the day it was categorized. If a task was part " . "of this project in the past but no longer is, it is not " . "counted at all.</p>";
     } else {
         $header = "Task Burn Rate for All Tasks";
         $caption = null;
     }
     $panel = new AphrontPanelView();
     $panel->setHeader($header);
     $panel->setCaption($caption);
     $panel->appendChild($table);
     $tokens = array();
     if ($handle) {
         $tokens = array($handle->getPHID() => $handle->getFullName());
     }
     $filter = $this->renderReportFilters($tokens, $has_window = false);
     $id = celerity_generate_unique_node_id();
     $chart = phutil_render_tag('div', array('id' => $id, 'style' => 'border: 1px solid #6f6f6f; ' . 'margin: 1em 2em; ' . 'height: 400px; '), '');
     list($burn_x, $burn_y) = $this->buildSeries($data);
     require_celerity_resource('raphael-core');
     require_celerity_resource('raphael-g');
     require_celerity_resource('raphael-g-line');
     Javelin::initBehavior('line-chart', array('hardpoint' => $id, 'x' => array($burn_x), 'y' => array($burn_y), 'xformat' => 'epoch'));
     return array($filter, $chart, $panel);
 }
 public function run()
 {
     $console = PhutilConsole::getConsole();
     $working_copy = $this->getWorkingCopy();
     $configuration_manager = $this->getConfigurationManager();
     $engine = $this->newLintEngine($this->getArgument('engine'));
     $rev = $this->getArgument('rev');
     $paths = $this->getArgument('paths');
     $use_cache = $this->getArgument('cache', null);
     $everything = $this->getArgument('everything');
     if ($everything && $paths) {
         throw new ArcanistUsageException(pht('You can not specify paths with %s. The %s flag lints every file.', '--everything', '--everything'));
     }
     if ($use_cache === null) {
         $use_cache = (bool) $configuration_manager->getConfigFromAnySource('arc.lint.cache', false);
     }
     if ($rev && $paths) {
         throw new ArcanistUsageException(pht('Specify either %s or paths.', '--rev'));
     }
     // NOTE: When the user specifies paths, we imply --lintall and show all
     // warnings for the paths in question. This is easier to deal with for
     // us and less confusing for users.
     $this->shouldLintAll = $paths ? true : false;
     if ($this->getArgument('lintall')) {
         $this->shouldLintAll = true;
     } else {
         if ($this->getArgument('only-changed')) {
             $this->shouldLintAll = false;
         }
     }
     if ($everything) {
         $paths = iterator_to_array($this->getRepositoryApi()->getAllFiles());
         $this->shouldLintAll = true;
     } else {
         $paths = $this->selectPathsForWorkflow($paths, $rev);
     }
     $this->engine = $engine;
     $engine->setMinimumSeverity($this->getArgument('severity', self::DEFAULT_SEVERITY));
     $file_hashes = array();
     if ($use_cache) {
         $engine->setRepositoryVersion($this->getRepositoryVersion());
         $cache = $this->readScratchJSONFile('lint-cache.json');
         $cache = idx($cache, $this->getCacheKey(), array());
         $cached = array();
         foreach ($paths as $path) {
             $abs_path = $engine->getFilePathOnDisk($path);
             if (!Filesystem::pathExists($abs_path)) {
                 continue;
             }
             $file_hashes[$abs_path] = md5_file($abs_path);
             if (!isset($cache[$path])) {
                 continue;
             }
             $messages = idx($cache[$path], $file_hashes[$abs_path]);
             if ($messages !== null) {
                 $cached[$path] = $messages;
             }
         }
         if ($cached) {
             $console->writeErr("%s\n", pht("Using lint cache, use '%s' to disable it.", '--cache 0'));
         }
         $engine->setCachedResults($cached);
     }
     // Propagate information about which lines changed to the lint engine.
     // This is used so that the lint engine can drop warning messages
     // concerning lines that weren't in the change.
     $engine->setPaths($paths);
     if (!$this->shouldLintAll) {
         foreach ($paths as $path) {
             // Note that getChangedLines() returns null to indicate that a file
             // is binary or a directory (i.e., changed lines are not relevant).
             $engine->setPathChangedLines($path, $this->getChangedLines($path, 'new'));
         }
     }
     // Enable possible async linting only for 'arc diff' not 'arc lint'
     if ($this->getParentWorkflow()) {
         $engine->setEnableAsyncLint(true);
     } else {
         $engine->setEnableAsyncLint(false);
     }
     if ($this->getArgument('only-new')) {
         $conduit = $this->getConduit();
         $api = $this->getRepositoryAPI();
         if ($rev) {
             $api->setBaseCommit($rev);
         }
         $svn_root = id(new PhutilURI($api->getSourceControlPath()))->getPath();
         $all_paths = array();
         foreach ($paths as $path) {
             $path = str_replace(DIRECTORY_SEPARATOR, '/', $path);
             $full_paths = array($path);
             $change = $this->getChange($path);
             $type = $change->getType();
             if (ArcanistDiffChangeType::isOldLocationChangeType($type)) {
                 $full_paths = $change->getAwayPaths();
             } else {
                 if (ArcanistDiffChangeType::isNewLocationChangeType($type)) {
                     continue;
                 } else {
                     if (ArcanistDiffChangeType::isDeleteChangeType($type)) {
                         continue;
                     }
                 }
             }
             foreach ($full_paths as $full_path) {
                 $all_paths[$svn_root . '/' . $full_path] = $path;
             }
         }
         $lint_future = $conduit->callMethod('diffusion.getlintmessages', array('repositoryPHID' => idx($this->loadProjectRepository(), 'phid'), 'branch' => '', 'commit' => $api->getBaseCommit(), 'files' => array_keys($all_paths)));
     }
     $failed = null;
     try {
         $engine->run();
     } catch (Exception $ex) {
         $failed = $ex;
     }
     $results = $engine->getResults();
     if ($this->getArgument('only-new')) {
         $total = 0;
         foreach ($results as $result) {
             $total += count($result->getMessages());
         }
         // Don't wait for response with default value of --only-new.
         $timeout = null;
         if ($this->getArgument('only-new') === null || !$total) {
             $timeout = 0;
         }
         $raw_messages = $this->resolveCall($lint_future, $timeout);
         if ($raw_messages && $total) {
             $old_messages = array();
             $line_maps = array();
             foreach ($raw_messages as $message) {
                 $path = $all_paths[$message['path']];
                 $line = $message['line'];
                 $code = $message['code'];
                 if (!isset($line_maps[$path])) {
                     $line_maps[$path] = $this->getChange($path)->buildLineMap();
                 }
                 $new_lines = idx($line_maps[$path], $line);
                 if (!$new_lines) {
                     // Unmodified lines after last hunk.
                     $last_old = $line_maps[$path] ? last_key($line_maps[$path]) : 0;
                     $news = array_filter($line_maps[$path]);
                     $last_new = $news ? last(end($news)) : 0;
                     $new_lines = array($line + $last_new - $last_old);
                 }
                 $error = array($code => array(true));
                 foreach ($new_lines as $new) {
                     if (isset($old_messages[$path][$new])) {
                         $old_messages[$path][$new][$code][] = true;
                         break;
                     }
                     $old_messages[$path][$new] =& $error;
                 }
                 unset($error);
             }
             foreach ($results as $result) {
                 foreach ($result->getMessages() as $message) {
                     $path = str_replace(DIRECTORY_SEPARATOR, '/', $message->getPath());
                     $line = $message->getLine();
                     $code = $message->getCode();
                     if (!empty($old_messages[$path][$line][$code])) {
                         $message->setObsolete(true);
                         array_pop($old_messages[$path][$line][$code]);
                     }
                 }
                 $result->sortAndFilterMessages();
             }
         }
     }
     if ($this->getArgument('never-apply-patches')) {
         $apply_patches = false;
     } else {
         $apply_patches = true;
     }
     if ($this->getArgument('apply-patches')) {
         $prompt_patches = false;
     } else {
         $prompt_patches = true;
     }
     if ($this->getArgument('amend-all')) {
         $this->shouldAmendChanges = true;
         $this->shouldAmendWithoutPrompt = true;
     }
     if ($this->getArgument('amend-autofixes')) {
         $prompt_autofix_patches = false;
         $this->shouldAmendChanges = true;
         $this->shouldAmendAutofixesWithoutPrompt = true;
     } else {
         $prompt_autofix_patches = true;
     }
     $repository_api = $this->getRepositoryAPI();
     if ($this->shouldAmendChanges) {
         $this->shouldAmendChanges = $repository_api->supportsAmend() && !$this->isHistoryImmutable();
     }
     $wrote_to_disk = false;
     switch ($this->getArgument('output')) {
         case 'json':
             $renderer = new ArcanistJSONLintRenderer();
             $prompt_patches = false;
             $apply_patches = $this->getArgument('apply-patches');
             break;
         case 'summary':
             $renderer = new ArcanistSummaryLintRenderer();
             break;
         case 'none':
             $prompt_patches = false;
             $apply_patches = $this->getArgument('apply-patches');
             $renderer = new ArcanistNoneLintRenderer();
             break;
         case 'compiler':
             $renderer = new ArcanistCompilerLintRenderer();
             $prompt_patches = false;
             $apply_patches = $this->getArgument('apply-patches');
             break;
         case 'xml':
             $renderer = new ArcanistCheckstyleXMLLintRenderer();
             $prompt_patches = false;
             $apply_patches = $this->getArgument('apply-patches');
             break;
         default:
             $renderer = new ArcanistConsoleLintRenderer();
             $renderer->setShowAutofixPatches($prompt_autofix_patches);
             break;
     }
     $all_autofix = true;
     $tmp = null;
     if ($this->getArgument('outfile') !== null) {
         $tmp = id(new TempFile())->setPreserveFile(true);
     }
     $preamble = $renderer->renderPreamble();
     if ($tmp) {
         Filesystem::appendFile($tmp, $preamble);
     } else {
         $console->writeOut('%s', $preamble);
     }
     foreach ($results as $result) {
         $result_all_autofix = $result->isAllAutofix();
         if (!$result->getMessages() && !$result_all_autofix) {
             continue;
         }
         if (!$result_all_autofix) {
             $all_autofix = false;
         }
         $lint_result = $renderer->renderLintResult($result);
         if ($lint_result) {
             if ($tmp) {
                 Filesystem::appendFile($tmp, $lint_result);
             } else {
                 $console->writeOut('%s', $lint_result);
             }
         }
         if ($apply_patches && $result->isPatchable()) {
             $patcher = ArcanistLintPatcher::newFromArcanistLintResult($result);
             $old_file = $result->getFilePathOnDisk();
             if ($prompt_patches && !($result_all_autofix && !$prompt_autofix_patches)) {
                 if (!Filesystem::pathExists($old_file)) {
                     $old_file = '/dev/null';
                 }
                 $new_file = new TempFile();
                 $new = $patcher->getModifiedFileContent();
                 Filesystem::writeFile($new_file, $new);
                 // TODO: Improve the behavior here, make it more like
                 // difference_render().
                 list(, $stdout, $stderr) = exec_manual('diff -u %s %s', $old_file, $new_file);
                 $console->writeOut('%s', $stdout);
                 $console->writeErr('%s', $stderr);
                 $prompt = pht('Apply this patch to %s?', phutil_console_format('__%s__', $result->getPath()));
                 if (!$console->confirm($prompt, $default = true)) {
                     continue;
                 }
             }
             $patcher->writePatchToDisk();
             $wrote_to_disk = true;
             $file_hashes[$old_file] = md5_file($old_file);
         }
     }
     $postamble = $renderer->renderPostamble();
     if ($tmp) {
         Filesystem::appendFile($tmp, $postamble);
         Filesystem::rename($tmp, $this->getArgument('outfile'));
     } else {
         $console->writeOut('%s', $postamble);
     }
     if ($wrote_to_disk && $this->shouldAmendChanges) {
         if ($this->shouldAmendWithoutPrompt || $this->shouldAmendAutofixesWithoutPrompt && $all_autofix) {
             $console->writeOut("<bg:yellow>** %s **</bg> %s\n", pht('LINT NOTICE'), pht('Automatically amending HEAD with lint patches.'));
             $amend = true;
         } else {
             $amend = $console->confirm(pht('Amend HEAD with lint patches?'));
         }
         if ($amend) {
             if ($repository_api instanceof ArcanistGitAPI) {
                 // Add the changes to the index before amending
                 $repository_api->execxLocal('add -u');
             }
             $repository_api->amendCommit();
         } else {
             throw new ArcanistUsageException(pht('Sort out the lint changes that were applied to the working ' . 'copy and relint.'));
         }
     }
     if ($this->getArgument('output') == 'json') {
         // NOTE: Required by save_lint.php in Phabricator.
         return 0;
     }
     if ($failed) {
         if ($failed instanceof ArcanistNoEffectException) {
             if ($renderer instanceof ArcanistNoneLintRenderer) {
                 return 0;
             }
         }
         throw $failed;
     }
     $unresolved = array();
     $has_warnings = false;
     $has_errors = false;
     foreach ($results as $result) {
         foreach ($result->getMessages() as $message) {
             if (!$message->isPatchApplied()) {
                 if ($message->isError()) {
                     $has_errors = true;
                 } else {
                     if ($message->isWarning()) {
                         $has_warnings = true;
                     }
                 }
                 $unresolved[] = $message;
             }
         }
     }
     $this->unresolvedMessages = $unresolved;
     $cache = $this->readScratchJSONFile('lint-cache.json');
     $cached = idx($cache, $this->getCacheKey(), array());
     if ($cached || $use_cache) {
         $stopped = $engine->getStoppedPaths();
         foreach ($results as $result) {
             $path = $result->getPath();
             if (!$use_cache) {
                 unset($cached[$path]);
                 continue;
             }
             $abs_path = $engine->getFilePathOnDisk($path);
             if (!Filesystem::pathExists($abs_path)) {
                 continue;
             }
             $version = $result->getCacheVersion();
             $cached_path = array();
             if (isset($stopped[$path])) {
                 $cached_path['stopped'] = $stopped[$path];
             }
             $cached_path['repository_version'] = $this->getRepositoryVersion();
             foreach ($result->getMessages() as $message) {
                 $granularity = $message->getGranularity();
                 if ($granularity == ArcanistLinter::GRANULARITY_GLOBAL) {
                     continue;
                 }
                 if (!$message->isPatchApplied()) {
                     $cached_path[] = $message->toDictionary();
                 }
             }
             $hash = idx($file_hashes, $abs_path);
             if (!$hash) {
                 $hash = md5_file($abs_path);
             }
             $cached[$path] = array($hash => array($version => $cached_path));
         }
         $cache[$this->getCacheKey()] = $cached;
         // TODO: Garbage collection.
         $this->writeScratchJSONFile('lint-cache.json', $cache);
     }
     // Take the most severe lint message severity and use that
     // as the result code.
     if ($has_errors) {
         $result_code = self::RESULT_ERRORS;
     } else {
         if ($has_warnings) {
             $result_code = self::RESULT_WARNINGS;
         } else {
             $result_code = self::RESULT_OKAY;
         }
     }
     if (!$this->getParentWorkflow()) {
         if ($result_code == self::RESULT_OKAY) {
             $console->writeOut('%s', $renderer->renderOkayResult());
         }
     }
     return $result_code;
 }
 private function splitTextIntoBlocks($text, $depth = 0)
 {
     // Apply basic block and paragraph normalization to the text. NOTE: We don't
     // strip trailing whitespace because it is semantic in some contexts,
     // notably inlined diffs that the author intends to show as a code block.
     $text = phutil_split_lines($text, true);
     $block_rules = $this->blockRules;
     $blocks = array();
     $cursor = 0;
     $prev_block = array();
     while (isset($text[$cursor])) {
         $starting_cursor = $cursor;
         foreach ($block_rules as $block_rule) {
             $num_lines = $block_rule->getMatchingLineCount($text, $cursor);
             if ($num_lines) {
                 if ($blocks) {
                     $prev_block = last($blocks);
                 }
                 $curr_block = array('start' => $cursor, 'num_lines' => $num_lines, 'rule' => $block_rule, 'is_empty' => self::isEmptyBlock($text, $cursor, $num_lines), 'children' => array());
                 if ($prev_block && self::shouldMergeBlocks($text, $prev_block, $curr_block)) {
                     $blocks[last_key($blocks)]["num_lines"] += $curr_block["num_lines"];
                     $blocks[last_key($blocks)]["is_empty"] = $blocks[last_key($blocks)]["is_empty"] && $curr_block["is_empty"];
                 } else {
                     $blocks[] = $curr_block;
                 }
                 $cursor += $num_lines;
                 break;
             }
         }
         if ($starting_cursor === $cursor) {
             throw new Exception("Block in text did not match any block rule.");
         }
     }
     foreach ($blocks as $key => $block) {
         $lines = array_slice($text, $block['start'], $block['num_lines']);
         $blocks[$key]['text'] = implode('', $lines);
     }
     // Stop splitting child blocks apart if we get too deep. This arrests
     // any blocks which have looping child rules, and stops the stack from
     // exploding if someone writes a hilarious comment with 5,000 levels of
     // quoted text.
     if ($depth < self::MAX_CHILD_DEPTH) {
         foreach ($blocks as $key => $block) {
             $rule = $block['rule'];
             if (!$rule->supportsChildBlocks()) {
                 continue;
             }
             list($parent_text, $child_text) = $rule->extractChildText($block['text']);
             $blocks[$key]['text'] = $parent_text;
             $blocks[$key]['children'] = $this->splitTextIntoBlocks($child_text, $depth + 1);
         }
     }
     return $blocks;
 }
 public function renderBurn()
 {
     $request = $this->getRequest();
     $viewer = $request->getUser();
     $handle = null;
     $project_phid = $request->getStr('project');
     if ($project_phid) {
         $phids = array($project_phid);
         $handles = $this->loadViewerHandles($phids);
         $handle = $handles[$project_phid];
     }
     $table = new ManiphestTransaction();
     $conn = $table->establishConnection('r');
     $joins = '';
     if ($project_phid) {
         $joins = qsprintf($conn, 'JOIN %T t ON x.objectPHID = t.phid
       JOIN %T p ON p.src = t.phid AND p.type = %d AND p.dst = %s', id(new ManiphestTask())->getTableName(), PhabricatorEdgeConfig::TABLE_NAME_EDGE, PhabricatorProjectObjectHasProjectEdgeType::EDGECONST, $project_phid);
     }
     $data = queryfx_all($conn, 'SELECT x.oldValue, x.newValue, x.dateCreated FROM %T x %Q
     WHERE transactionType = %s
     ORDER BY x.dateCreated ASC', $table->getTableName(), $joins, ManiphestTransaction::TYPE_STATUS);
     $stats = array();
     $day_buckets = array();
     $open_tasks = array();
     foreach ($data as $key => $row) {
         // NOTE: Hack to avoid json_decode().
         $oldv = trim($row['oldValue'], '"');
         $newv = trim($row['newValue'], '"');
         if ($oldv == 'null') {
             $old_is_open = false;
         } else {
             $old_is_open = ManiphestTaskStatus::isOpenStatus($oldv);
         }
         $new_is_open = ManiphestTaskStatus::isOpenStatus($newv);
         $is_open = $new_is_open && !$old_is_open;
         $is_close = $old_is_open && !$new_is_open;
         $data[$key]['_is_open'] = $is_open;
         $data[$key]['_is_close'] = $is_close;
         if (!$is_open && !$is_close) {
             // This is either some kind of bogus event, or a resolution change
             // (e.g., resolved -> invalid). Just skip it.
             continue;
         }
         $day_bucket = phabricator_format_local_time($row['dateCreated'], $viewer, 'Yz');
         $day_buckets[$day_bucket] = $row['dateCreated'];
         if (empty($stats[$day_bucket])) {
             $stats[$day_bucket] = array('open' => 0, 'close' => 0);
         }
         $stats[$day_bucket][$is_close ? 'close' : 'open']++;
     }
     $template = array('open' => 0, 'close' => 0);
     $rows = array();
     $rowc = array();
     $last_month = null;
     $last_month_epoch = null;
     $last_week = null;
     $last_week_epoch = null;
     $week = null;
     $month = null;
     $last = last_key($stats) - 1;
     $period = $template;
     foreach ($stats as $bucket => $info) {
         $epoch = $day_buckets[$bucket];
         $week_bucket = phabricator_format_local_time($epoch, $viewer, 'YW');
         if ($week_bucket != $last_week) {
             if ($week) {
                 $rows[] = $this->formatBurnRow(pht('Week of %s', phabricator_date($last_week_epoch, $viewer)), $week);
                 $rowc[] = 'week';
             }
             $week = $template;
             $last_week = $week_bucket;
             $last_week_epoch = $epoch;
         }
         $month_bucket = phabricator_format_local_time($epoch, $viewer, 'Ym');
         if ($month_bucket != $last_month) {
             if ($month) {
                 $rows[] = $this->formatBurnRow(phabricator_format_local_time($last_month_epoch, $viewer, 'F, Y'), $month);
                 $rowc[] = 'month';
             }
             $month = $template;
             $last_month = $month_bucket;
             $last_month_epoch = $epoch;
         }
         $rows[] = $this->formatBurnRow(phabricator_date($epoch, $viewer), $info);
         $rowc[] = null;
         $week['open'] += $info['open'];
         $week['close'] += $info['close'];
         $month['open'] += $info['open'];
         $month['close'] += $info['close'];
         $period['open'] += $info['open'];
         $period['close'] += $info['close'];
     }
     if ($week) {
         $rows[] = $this->formatBurnRow(pht('Week To Date'), $week);
         $rowc[] = 'week';
     }
     if ($month) {
         $rows[] = $this->formatBurnRow(pht('Month To Date'), $month);
         $rowc[] = 'month';
     }
     $rows[] = $this->formatBurnRow(pht('All Time'), $period);
     $rowc[] = 'aggregate';
     $rows = array_reverse($rows);
     $rowc = array_reverse($rowc);
     $table = new AphrontTableView($rows);
     $table->setRowClasses($rowc);
     $table->setHeaders(array(pht('Period'), pht('Opened'), pht('Closed'), pht('Change')));
     $table->setColumnClasses(array('right wide', 'n', 'n', 'n'));
     if ($handle) {
         $inst = pht('NOTE: This table reflects tasks currently in ' . 'the project. If a task was opened in the past but added to ' . 'the project recently, it is counted on the day it was ' . 'opened, not the day it was categorized. If a task was part ' . 'of this project in the past but no longer is, it is not ' . 'counted at all.');
         $header = pht('Task Burn Rate for Project %s', $handle->renderLink());
         $caption = phutil_tag('p', array(), $inst);
     } else {
         $header = pht('Task Burn Rate for All Tasks');
         $caption = null;
     }
     if ($caption) {
         $caption = id(new PHUIInfoView())->appendChild($caption)->setSeverity(PHUIInfoView::SEVERITY_NOTICE);
     }
     $panel = new PHUIObjectBoxView();
     $panel->setHeaderText($header);
     if ($caption) {
         $panel->setInfoView($caption);
     }
     $panel->setTable($table);
     $tokens = array();
     if ($handle) {
         $tokens = array($handle);
     }
     $filter = $this->renderReportFilters($tokens, $has_window = false);
     $id = celerity_generate_unique_node_id();
     $chart = phutil_tag('div', array('id' => $id, 'style' => 'border: 1px solid #BFCFDA; ' . 'background-color: #fff; ' . 'margin: 8px 16px; ' . 'height: 400px; '), '');
     list($burn_x, $burn_y) = $this->buildSeries($data);
     require_celerity_resource('d3');
     require_celerity_resource('phui-chart-css');
     Javelin::initBehavior('line-chart', array('hardpoint' => $id, 'x' => array($burn_x), 'y' => array($burn_y), 'xformat' => 'epoch', 'yformat' => 'int'));
     $box = id(new PHUIObjectBoxView())->setHeaderText(pht('Burnup Rate'))->appendChild($chart);
     return array($filter, $box, $panel);
 }
示例#21
0
 /**
  * Start the server. This method returns after the client limit or idle
  * limit are exceeded. If neither limit is configured, this method does not
  * exit.
  *
  * @return null
  *
  * @task server
  */
 public function start()
 {
     // Create the unix domain socket in the working copy to listen for clients.
     $socket = $this->startWorkingCopySocket();
     $this->socket = $socket;
     if (!$this->doNotDaemonize) {
         $this->daemonize();
     }
     // Start the Mercurial process which we'll forward client requests to.
     $hg = $this->startMercurialProcess();
     $clients = array();
     $this->log(null, pht('Listening'));
     $this->idleSince = time();
     while (true) {
         // Wait for activity on any active clients, the Mercurial process, or
         // the listening socket where new clients connect.
         PhutilChannel::waitForAny(array_merge($clients, array($hg)), array('read' => $socket ? array($socket) : array(), 'except' => $socket ? array($socket) : array()));
         if (!$hg->update()) {
             throw new Exception(pht('Server exited unexpectedly!'));
         }
         // Accept any new clients.
         while ($socket && ($client = $this->acceptNewClient($socket))) {
             $clients[] = $client;
             $key = last_key($clients);
             $client->setName($key);
             $this->log($client, pht('Connected'));
             $this->idleSince = time();
             // Check if we've hit the client limit. If there's a configured
             // client limit and we've hit it, stop accepting new connections
             // and close the socket.
             $this->lifetimeClientCount++;
             if ($this->clientLimit) {
                 if ($this->lifetimeClientCount >= $this->clientLimit) {
                     $this->closeSocket();
                     $socket = null;
                 }
             }
         }
         // Update all the active clients.
         foreach ($clients as $key => $client) {
             if ($this->updateClient($client, $hg)) {
                 // In this case, the client is still connected so just move on to
                 // the next one. Otherwise we continue below and handle the
                 // disconnect.
                 continue;
             }
             $this->log($client, pht('Disconnected'));
             unset($clients[$key]);
             // If we have a client limit and we've served that many clients, exit.
             if ($this->clientLimit) {
                 if ($this->lifetimeClientCount >= $this->clientLimit) {
                     if (!$clients) {
                         $this->log(null, pht('Exiting (Client Limit)'));
                         return;
                     }
                 }
             }
         }
         // If we have an idle limit and haven't had any activity in at least
         // that long, exit.
         if ($this->idleLimit) {
             $remaining = $this->idleLimit - (time() - $this->idleSince);
             if ($remaining <= 0) {
                 $this->log(null, pht('Exiting (Idle Limit)'));
                 return;
             }
             if ($remaining <= 5) {
                 $this->log(null, pht('Exiting in %d seconds', $remaining));
             }
         }
     }
 }
 private function addState(array $set)
 {
     $seen = array();
     foreach ($set as $item) {
         $seen[$item[0]][$item[1]][$item[2]][$item[3]] = true;
     }
     $end = $this->getEndSymbol();
     $epsilon = $this->getEpsilonSymbol();
     for ($ii = 0; $ii < count($set); $ii++) {
         $item = $set[$ii];
         $production = $this->rules[$item[0]][$item[1]];
         $next = $production[$item[2]];
         if ($this->isTerminal($next)) {
             continue;
         } else {
             if ($next === $epsilon) {
                 continue;
             } else {
                 if ($next === $end) {
                     continue;
                 }
             }
         }
         $v = array_slice($production, $item[2] + 1, -1);
         $v[] = $item[3];
         $v[] = $end;
         $firsts = $this->getFirstForProduction($v);
         foreach ($firsts as $nfirst => $ignored) {
             if (!$this->isTerminal($nfirst)) {
                 unset($firsts[$nfirst]);
             }
         }
         foreach ($this->rules[$next] as $pkey => $nproduction) {
             foreach ($firsts as $nfirst => $ignored) {
                 if (isset($seen[$next][$pkey][0][$nfirst])) {
                     continue;
                 }
                 $set[] = array($next, $pkey, 0, $nfirst);
                 $seen[$next][$pkey][0][$nfirst] = true;
             }
         }
     }
     $hash = $this->hashSet($set);
     if (isset($this->setHashes[$hash])) {
         return array(false, $this->setHashes[$hash]);
     }
     $this->states[] = $set;
     $state = last_key($this->states);
     $this->setHashes[$hash] = $state;
     return array(true, $state);
 }
 public function markupText($text, $children)
 {
     if (preg_match('/^\\s*```/', $text)) {
         // If this is a ```-style block, trim off the backticks and any leading
         // blank line.
         $text = preg_replace('/^\\s*```(\\s*\\n)?/', '', $text);
         $text = preg_replace('/```\\s*$/', '', $text);
     }
     $lines = explode("\n", $text);
     while ($lines && !strlen(last($lines))) {
         unset($lines[last_key($lines)]);
     }
     $options = array('counterexample' => false, 'lang' => null, 'name' => null, 'lines' => null);
     $parser = new PhutilSimpleOptions();
     $custom = $parser->parse(head($lines));
     if ($custom) {
         $valid = true;
         foreach ($custom as $key => $value) {
             if (!array_key_exists($key, $options)) {
                 $valid = false;
                 break;
             }
         }
         if ($valid) {
             array_shift($lines);
             $options = $custom + $options;
         }
     }
     // Normalize the text back to a 0-level indent.
     $min_indent = 80;
     foreach ($lines as $line) {
         for ($ii = 0; $ii < strlen($line); $ii++) {
             if ($line[$ii] != ' ') {
                 $min_indent = min($ii, $min_indent);
                 break;
             }
         }
     }
     $text = implode("\n", $lines);
     if ($min_indent) {
         $indent_string = str_repeat(' ', $min_indent);
         $text = preg_replace('/^' . $indent_string . '/m', '', $text);
     }
     if ($this->getEngine()->isTextMode()) {
         $out = array();
         $header = array();
         if ($options['counterexample']) {
             $header[] = 'counterexample';
         }
         if ($options['name'] != '') {
             $header[] = 'name=' . $options['name'];
         }
         if ($header) {
             $out[] = implode(', ', $header);
         }
         $text = preg_replace('/^/m', '  ', $text);
         $out[] = $text;
         return implode("\n", $out);
     }
     if (empty($options['lang'])) {
         // If the user hasn't specified "lang=..." explicitly, try to guess the
         // language. If we fail, fall back to configured defaults.
         $lang = PhutilLanguageGuesser::guessLanguage($text);
         if (!$lang) {
             $lang = nonempty($this->getEngine()->getConfig('phutil.codeblock.language-default'), 'text');
         }
         $options['lang'] = $lang;
     }
     $code_body = $this->highlightSource($text, $options);
     $name_header = null;
     if ($this->getEngine()->isHTMLMailMode()) {
         $header_attributes = array('style' => 'padding: 6px 8px;
       background: #fdf5d4;
       color: rgba(0,0,0,.75);
       font-weight: bold;
       display: inline-block;
       border-top: 1px solid #f1c40f;
       border-left: 1px solid #f1c40f;
       border-right: 1px solid #f1c40f;
       margin-bottom: -1px;');
     } else {
         $header_attributes = array('class' => 'remarkup-code-header');
     }
     if ($options['name']) {
         $name_header = phutil_tag('div', $header_attributes, $options['name']);
     }
     $class = 'remarkup-code-block';
     if ($options['counterexample']) {
         $class = 'remarkup-code-block code-block-counterexample';
     }
     $attributes = array('class' => $class, 'data-code-lang' => $options['lang'], 'data-sigil' => 'remarkup-code-block');
     return phutil_tag('div', $attributes, array($name_header, $code_body));
 }
示例#24
0
 /**
  * @task test
  */
 public static function popEnvironment($key)
 {
     $stack_key = last_key(self::$stack);
     array_pop(self::$stack);
     if ($stack_key !== $key) {
         throw new Exception("Scoped environments were destroyed in a diffent order than they " . "were initialized.");
     }
 }
 public function buildView()
 {
     $stories = $this->stories;
     $stories = mpull($stories, null, 'getChronologicalKey');
     // Aggregate notifications. Generally, we can aggregate notifications only
     // by object, e.g. "a updated T123" and "b updated T123" can become
     // "a and b updated T123", but we can't combine "a updated T123" and
     // "a updated T234" into "a updated T123 and T234" because there would be
     // nowhere sensible for the notification to link to, and no reasonable way
     // to unambiguously clear it.
     // Each notification emits keys it can aggregate on. For instance, if this
     // story is "a updated T123", it might emit a key like this:
     //
     //   task:phid123:unread => PhabricatorFeedStoryManiphestAggregate
     //
     // All the unread notifications about the task with PHID "phid123" will
     // emit the same key, telling us we can aggregate them into a single
     // story of type "PhabricatorFeedStoryManiphestAggregate", which could
     // read like "a and b updated T123".
     //
     // A story might be able to aggregate in multiple ways. Although this is
     // unlikely for stories in a notification context, stories in a feed context
     // can also aggregate by actor:
     //
     //   task:phid123   => PhabricatorFeedStoryManiphestAggregate
     //   actor:user123  => PhabricatorFeedStoryActorAggregate
     //
     // This means the story can either become "a and b updated T123" or
     // "a updated T123 and T456". When faced with multiple possibilities, it's
     // our job to choose the best aggregation.
     //
     // For now, we use a simple greedy algorithm and repeatedly select the
     // aggregate story which consumes the largest number of individual stories
     // until no aggregate story exists that consumes more than one story.
     // Build up a map of all the possible aggregations.
     $chronokey_map = array();
     $aggregation_map = array();
     $agg_types = array();
     foreach ($stories as $chronokey => $story) {
         $chronokey_map[$chronokey] = $story->getNotificationAggregations();
         foreach ($chronokey_map[$chronokey] as $key => $type) {
             $agg_types[$key] = $type;
             $aggregation_map[$key]['keys'][$chronokey] = true;
         }
     }
     // Repeatedly select the largest available aggregation until none remain.
     $aggregated_stories = array();
     while ($aggregation_map) {
         // Count the size of each aggregation, removing any which will consume
         // fewer than 2 stories.
         foreach ($aggregation_map as $key => $dict) {
             $size = count($dict['keys']);
             if ($size > 1) {
                 $aggregation_map[$key]['size'] = $size;
             } else {
                 unset($aggregation_map[$key]);
             }
         }
         // If we're out of aggregations, break out.
         if (!$aggregation_map) {
             break;
         }
         // Select the aggregation we're going to make, and remove it from the
         // map.
         $aggregation_map = isort($aggregation_map, 'size');
         $agg_info = idx(last($aggregation_map), 'keys');
         $agg_key = last_key($aggregation_map);
         unset($aggregation_map[$agg_key]);
         // Select all the stories it aggregates, and remove them from the master
         // list of stories and from all other possible aggregations.
         $sub_stories = array();
         foreach ($agg_info as $chronokey => $ignored) {
             $sub_stories[$chronokey] = $stories[$chronokey];
             unset($stories[$chronokey]);
             foreach ($chronokey_map[$chronokey] as $key => $type) {
                 unset($aggregation_map[$key]['keys'][$chronokey]);
             }
             unset($chronokey_map[$chronokey]);
         }
         // Build the aggregate story.
         krsort($sub_stories);
         $story_class = $agg_types[$agg_key];
         $conv = array(head($sub_stories)->getStoryData());
         $new_story = newv($story_class, $conv);
         $new_story->setAggregateStories($sub_stories);
         $aggregated_stories[] = $new_story;
     }
     // Combine the aggregate stories back into the list of stories.
     $stories = array_merge($stories, $aggregated_stories);
     $stories = mpull($stories, null, 'getChronologicalKey');
     krsort($stories);
     $null_view = new AphrontNullView();
     foreach ($stories as $story) {
         try {
             $view = $story->renderView();
         } catch (Exception $ex) {
             // TODO: Render a nice debuggable notice instead?
             continue;
         }
         $null_view->appendChild($view->renderNotification($this->user));
     }
     return $null_view;
 }
示例#26
0
 public function run()
 {
     $console = PhutilConsole::getConsole();
     $linters = id(new PhutilClassMapQuery())->setAncestorClass('ArcanistLinter')->execute();
     try {
         $built = $this->newLintEngine()->buildLinters();
     } catch (ArcanistNoEngineException $ex) {
         $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("<bg:" . $color . ">** %s **</bg> **%s** (%s)\n", $text, nonempty($linter['name'], '-'), $linter['short']);
         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'));
     }
 }
示例#27
0
 public function testHeadKeyLastKey()
 {
     $this->assertEquals('a', head_key(['a' => 0, 'b' => 1]));
     $this->assertEquals('b', last_key(['a' => 0, 'b' => 1]));
     $this->assertEquals(null, head_key([]));
     $this->assertEquals(null, last_key([]));
 }
示例#28
0
 public function run()
 {
     $console = PhutilConsole::getConsole();
     $linters = id(new PhutilSymbolLoader())->setAncestorClass('ArcanistLinter')->loadObjects();
     try {
         $built = $this->newLintEngine()->buildLinters();
     } catch (ArcanistNoEngineException $ex) {
         $built = array();
     }
     // 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.
     $groups = array();
     foreach ($built as $linter) {
         $groups[get_class($linter)][] = $linter;
     }
     $linter_info = array();
     foreach ($linters as $key => $linter) {
         $installed = idx($groups, $key, array());
         $exception = null;
         if ($installed) {
             $status = 'configured';
             try {
                 $version = head($installed)->getVersion();
             } catch (Exception $ex) {
                 $status = 'error';
                 $exception = $ex;
             }
         } else {
             $status = 'available';
             $version = null;
         }
         $linter_info[$key] = array('short' => $linter->getLinterConfigurationName(), 'class' => get_class($linter), 'status' => $status, 'version' => $version, 'name' => $linter->getInfoName(), 'uri' => $linter->getInfoURI(), 'description' => $linter->getInfoDescription(), 'exception' => $exception, 'options' => $linter->getLinterConfigurationOptions());
     }
     $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("<bg:" . $color . ">** %s **</bg> **%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'));
     }
 }
示例#29
0
 public function testHeadKeyLastKey()
 {
     $this->assertEqual('a', head_key(array('a' => 0, 'b' => 1)));
     $this->assertEqual('b', last_key(array('a' => 0, 'b' => 1)));
     $this->assertEqual(null, head_key(array()));
     $this->assertEqual(null, last_key(array()));
 }
 private function generate()
 {
     if ($this->generated) {
         return;
     }
     $multi_row = true;
     $multi_col = true;
     $margin_w = 1;
     $margin_h = 1;
     $type = $this->type;
     switch ($type) {
         case self::TYPE_STANDARD:
             break;
         case self::TYPE_REPEAT_X:
             $multi_col = false;
             $margin_w = 0;
             $width = null;
             foreach ($this->sprites as $sprite) {
                 if ($width === null) {
                     $width = $sprite->getSourceW();
                 } else {
                     if ($width !== $sprite->getSourceW()) {
                         throw new Exception(pht("All sprites in a '%s' sheet must have the same width.", 'repeat-x'));
                     }
                 }
             }
             break;
         case self::TYPE_REPEAT_Y:
             $multi_row = false;
             $margin_h = 0;
             $height = null;
             foreach ($this->sprites as $sprite) {
                 if ($height === null) {
                     $height = $sprite->getSourceH();
                 } else {
                     if ($height !== $sprite->getSourceH()) {
                         throw new Exception(pht("All sprites in a '%s' sheet must have the same height.", 'repeat-y'));
                     }
                 }
             }
             break;
         default:
             throw new Exception(pht("Unknown sprite sheet type '%s'!", $type));
     }
     $css = array();
     if ($this->cssHeader) {
         $css[] = $this->cssHeader;
     }
     $out_w = 0;
     $out_h = 0;
     // Lay out the sprite sheet. We attempt to build a roughly square sheet
     // so it's easier to manage, since 2000x20 is more cumbersome for humans
     // to deal with than 200x200.
     //
     // To do this, we use a simple greedy algorithm, adding sprites one at a
     // time. For each sprite, if the sheet is at least as wide as it is tall
     // we create a new row. Otherwise, we try to add it to an existing row.
     //
     // This isn't optimal, but does a reasonable job in most cases and isn't
     // too messy.
     // Group the sprites by their sizes. We lay them out in the sheet as
     // boxes, but then put them into the boxes in the order they were added
     // so similar sprites end up nearby on the final sheet.
     $boxes = array();
     foreach (array_reverse($this->sprites) as $sprite) {
         $s_w = $sprite->getSourceW() + $margin_w;
         $s_h = $sprite->getSourceH() + $margin_h;
         $boxes[$s_w][$s_h][] = $sprite;
     }
     $rows = array();
     foreach ($this->sprites as $sprite) {
         $s_w = $sprite->getSourceW() + $margin_w;
         $s_h = $sprite->getSourceH() + $margin_h;
         // Choose a row for this sprite.
         $maybe = array();
         foreach ($rows as $key => $row) {
             if ($row['h'] < $s_h) {
                 // We can only add it to a row if the row is at least as tall as the
                 // sprite.
                 continue;
             }
             // We prefer rows which have the same height as the sprite, and then
             // rows which aren't yet very wide.
             $wasted_v = $row['h'] - $s_h;
             $wasted_h = $row['w'] / $out_w;
             $maybe[$key] = $wasted_v + $wasted_h;
         }
         $row_key = null;
         if ($maybe && $multi_col) {
             // If there were any candidate rows, pick the best one.
             asort($maybe);
             $row_key = head_key($maybe);
         }
         if ($row_key !== null && $multi_row) {
             // If there's a candidate row, but adding the sprite to it would make
             // the sprite wider than it is tall, create a new row instead. This
             // generally keeps the sprite square-ish.
             if ($rows[$row_key]['w'] + $s_w > $out_h) {
                 $row_key = null;
             }
         }
         if ($row_key === null) {
             // Add a new row.
             $rows[] = array('w' => 0, 'h' => $s_h, 'boxes' => array());
             $row_key = last_key($rows);
             $out_h += $s_h;
         }
         // Add the sprite box to the row.
         $row = $rows[$row_key];
         $row['w'] += $s_w;
         $row['boxes'][] = array($s_w, $s_h);
         $rows[$row_key] = $row;
         $out_w = max($row['w'], $out_w);
     }
     $images = array();
     foreach ($this->scales as $scale) {
         $img = imagecreatetruecolor($out_w * $scale, $out_h * $scale);
         imagesavealpha($img, true);
         imagefill($img, 0, 0, imagecolorallocatealpha($img, 0, 0, 0, 127));
         $images[$scale] = $img;
     }
     // Put the shorter rows first. At the same height, put the wider rows first.
     // This makes the resulting sheet more human-readable.
     foreach ($rows as $key => $row) {
         $rows[$key]['sort'] = $row['h'] + (1 - $row['w'] / $out_w);
     }
     $rows = isort($rows, 'sort');
     $pos_x = 0;
     $pos_y = 0;
     $rules = array();
     foreach ($rows as $row) {
         $max_h = 0;
         foreach ($row['boxes'] as $box) {
             $sprite = array_pop($boxes[$box[0]][$box[1]]);
             foreach ($images as $scale => $img) {
                 $src = $this->loadSource($sprite, $scale);
                 imagecopy($img, $src, $scale * $pos_x, $scale * $pos_y, $scale * $sprite->getSourceX(), $scale * $sprite->getSourceY(), $scale * $sprite->getSourceW(), $scale * $sprite->getSourceH());
             }
             $rule = $sprite->getTargetCSS();
             $cssx = -$pos_x . 'px';
             $cssy = -$pos_y . 'px';
             $rules[$sprite->getName()] = "{$rule} {\n" . "  background-position: {$cssx} {$cssy};\n}";
             $pos_x += $sprite->getSourceW() + $margin_w;
             $max_h = max($max_h, $sprite->getSourceH());
         }
         $pos_x = 0;
         $pos_y += $max_h + $margin_h;
     }
     // Generate CSS rules in input order.
     foreach ($this->sprites as $sprite) {
         $css[] = $rules[$sprite->getName()];
     }
     $this->images = $images;
     $this->css = implode("\n\n", $css) . "\n";
     $this->generated = true;
 }