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); }
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; }
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']); } }
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); }
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); }
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'); }
/** * 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); }
/** * 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); }
/** * 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)); }
/** * @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; }
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')); } }
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([])); }
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')); } }
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; }