Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F2894261
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Advanced/Developer...
View Handle
View Hovercard
Size
45 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/src/applications/diviner/controller/DivinerAtomController.php b/src/applications/diviner/controller/DivinerAtomController.php
index 798c524367..10ebcccb3e 100644
--- a/src/applications/diviner/controller/DivinerAtomController.php
+++ b/src/applications/diviner/controller/DivinerAtomController.php
@@ -1,677 +1,679 @@
<?php
final class DivinerAtomController extends DivinerController {
private $bookName;
private $atomType;
private $atomName;
private $atomContext;
private $atomIndex;
public function shouldAllowPublic() {
return true;
}
public function willProcessRequest(array $data) {
$this->bookName = $data['book'];
$this->atomType = $data['type'];
$this->atomName = $data['name'];
$this->atomContext = nonempty(idx($data, 'context'), null);
$this->atomIndex = nonempty(idx($data, 'index'), null);
}
public function processRequest() {
$request = $this->getRequest();
$viewer = $request->getUser();
require_celerity_resource('diviner-shared-css');
$book = id(new DivinerBookQuery())
->setViewer($viewer)
->withNames(array($this->bookName))
->executeOne();
if (!$book) {
return new Aphront404Response();
}
// TODO: This query won't load ghosts, because they'll fail `needAtoms()`.
// Instead, we might want to load ghosts and render a message like
// "this thing existed in an older version, but no longer does", especially
// if we add content like comments.
$symbol = id(new DivinerAtomQuery())
->setViewer($viewer)
->withBookPHIDs(array($book->getPHID()))
->withTypes(array($this->atomType))
->withNames(array($this->atomName))
->withContexts(array($this->atomContext))
->withIndexes(array($this->atomIndex))
+ ->withGhosts(false)
+ ->withIsDocumentable(true)
->needAtoms(true)
->needExtends(true)
->needChildren(true)
->executeOne();
if (!$symbol) {
return new Aphront404Response();
}
$atom = $symbol->getAtom();
$crumbs = $this->buildApplicationCrumbs();
$crumbs->setBorder(true);
$crumbs->addTextCrumb(
$book->getShortTitle(),
'/book/'.$book->getName().'/');
$atom_short_title = $atom->getDocblockMetaValue(
'short',
$symbol->getTitle());
$crumbs->addTextCrumb($atom_short_title);
$header = id(new PHUIHeaderView())
->setHeader($this->renderFullSignature($symbol))
->addTag(
id(new PHUITagView())
->setType(PHUITagView::TYPE_STATE)
->setBackgroundColor(PHUITagView::COLOR_BLUE)
->setName(DivinerAtom::getAtomTypeNameString($atom->getType())));
$properties = id(new PHUIPropertyListView());
$group = $atom->getProperty('group');
if ($group) {
$group_name = $book->getGroupName($group);
} else {
$group_name = null;
}
$this->buildDefined($properties, $symbol);
$this->buildExtendsAndImplements($properties, $symbol);
$warnings = $atom->getWarnings();
if ($warnings) {
$warnings = id(new PHUIInfoView())
->setErrors($warnings)
->setTitle(pht('Documentation Warnings'))
->setSeverity(PHUIInfoView::SEVERITY_WARNING);
}
$methods = $this->composeMethods($symbol);
$field = 'default';
$engine = id(new PhabricatorMarkupEngine())
->setViewer($viewer)
->addObject($symbol, $field);
foreach ($methods as $method) {
foreach ($method['atoms'] as $matom) {
$engine->addObject($matom, $field);
}
}
$engine->process();
$content = $this->renderDocumentationText($symbol, $engine);
$toc = $engine->getEngineMetadata(
$symbol,
$field,
PhutilRemarkupHeaderBlockRule::KEY_HEADER_TOC,
array());
$document = id(new PHUIDocumentView())
->setBook($book->getTitle(), $group_name)
->setHeader($header)
->addClass('diviner-view')
->setFontKit(PHUIDocumentView::FONT_SOURCE_SANS)
->appendChild($properties)
->appendChild($warnings)
->appendChild($content);
$document->appendChild($this->buildParametersAndReturn(array($symbol)));
if ($methods) {
$tasks = $this->composeTasks($symbol);
if ($tasks) {
$methods_by_task = igroup($methods, 'task');
// Add phantom tasks for methods which have a "@task" name that isn't
// documented anywhere, or methods that have no "@task" name.
foreach ($methods_by_task as $task => $ignored) {
if (empty($tasks[$task])) {
$tasks[$task] = array(
'name' => $task,
'title' => $task ? $task : pht('Other Methods'),
'defined' => $symbol,
);
}
}
$section = id(new DivinerSectionView())
->setHeader(pht('Tasks'));
foreach ($tasks as $spec) {
$section->addContent(
id(new PHUIHeaderView())
->setNoBackground(true)
->setHeader($spec['title']));
$task_methods = idx($methods_by_task, $spec['name'], array());
$inner_box = id(new PHUIBoxView())
->addPadding(PHUI::PADDING_LARGE_LEFT)
->addPadding(PHUI::PADDING_LARGE_RIGHT)
->addPadding(PHUI::PADDING_LARGE_BOTTOM);
$box_content = array();
if ($task_methods) {
$list_items = array();
foreach ($task_methods as $task_method) {
$atom = last($task_method['atoms']);
$item = $this->renderFullSignature($atom, true);
if (strlen($atom->getSummary())) {
$item = array(
$item,
" \xE2\x80\x94 ",
$atom->getSummary(),
);
}
$list_items[] = phutil_tag('li', array(), $item);
}
$box_content[] = phutil_tag(
'ul',
array(
'class' => 'diviner-list',
),
$list_items);
} else {
$no_methods = pht('No methods for this task.');
$box_content = phutil_tag('em', array(), $no_methods);
}
$inner_box->appendChild($box_content);
$section->addContent($inner_box);
}
$document->appendChild($section);
}
$section = id(new DivinerSectionView())
->setHeader(pht('Methods'));
foreach ($methods as $spec) {
$matom = last($spec['atoms']);
$method_header = id(new PHUIHeaderView())
->setNoBackground(true);
$inherited = $spec['inherited'];
if ($inherited) {
$method_header->addTag(
id(new PHUITagView())
->setType(PHUITagView::TYPE_STATE)
->setBackgroundColor(PHUITagView::COLOR_GREY)
->setName(pht('Inherited')));
}
$method_header->setHeader($this->renderFullSignature($matom));
$section->addContent(
array(
$method_header,
$this->renderMethodDocumentationText($symbol, $spec, $engine),
$this->buildParametersAndReturn($spec['atoms']),
));
}
$document->appendChild($section);
}
if ($toc) {
$side = new PHUIListView();
$side->addMenuItem(
id(new PHUIListItemView())
->setName(pht('Contents'))
->setType(PHUIListItemView::TYPE_LABEL));
foreach ($toc as $key => $entry) {
$side->addMenuItem(
id(new PHUIListItemView())
->setName($entry[1])
->setHref('#'.$key));
}
$document->setSideNav($side, PHUIDocumentView::NAV_TOP);
}
return $this->buildApplicationPage(
array(
$crumbs,
$document,
),
array(
'title' => $symbol->getTitle(),
));
}
private function buildExtendsAndImplements(
PHUIPropertyListView $view,
DivinerLiveSymbol $symbol) {
$lineage = $this->getExtendsLineage($symbol);
if ($lineage) {
$tags = array();
foreach ($lineage as $item) {
$tags[] = $this->renderAtomTag($item);
}
$caret = phutil_tag('span', array('class' => 'caret-right msl msr'));
$tags = phutil_implode_html($caret, $tags);
$view->addProperty(pht('Extends'), $tags);
}
$implements = $this->getImplementsLineage($symbol);
if ($implements) {
$items = array();
foreach ($implements as $spec) {
$via = $spec['via'];
$iface = $spec['interface'];
if ($via == $symbol) {
$items[] = $this->renderAtomTag($iface);
} else {
$items[] = array(
$this->renderAtomTag($iface),
" \xE2\x97\x80 ",
$this->renderAtomTag($via),
);
}
}
$view->addProperty(
pht('Implements'),
phutil_implode_html(phutil_tag('br'), $items));
}
}
private function renderAtomTag(DivinerLiveSymbol $symbol) {
return id(new PHUITagView())
->setType(PHUITagView::TYPE_OBJECT)
->setName($symbol->getName())
->setHref($symbol->getURI());
}
private function getExtendsLineage(DivinerLiveSymbol $symbol) {
foreach ($symbol->getExtends() as $extends) {
if ($extends->getType() == 'class') {
$lineage = $this->getExtendsLineage($extends);
$lineage[] = $extends;
return $lineage;
}
}
return array();
}
private function getImplementsLineage(DivinerLiveSymbol $symbol) {
$implements = array();
// Do these first so we get interfaces ordered from most to least specific.
foreach ($symbol->getExtends() as $extends) {
if ($extends->getType() == 'interface') {
$implements[$extends->getName()] = array(
'interface' => $extends,
'via' => $symbol,
);
}
}
// Now do parent interfaces.
foreach ($symbol->getExtends() as $extends) {
if ($extends->getType() == 'class') {
$implements += $this->getImplementsLineage($extends);
}
}
return $implements;
}
private function buildDefined(
PHUIPropertyListView $view,
DivinerLiveSymbol $symbol) {
$atom = $symbol->getAtom();
$defined = $atom->getFile().':'.$atom->getLine();
$link = $symbol->getBook()->getConfig('uri.source');
if ($link) {
$link = strtr(
$link,
array(
'%%' => '%',
'%f' => phutil_escape_uri($atom->getFile()),
'%l' => phutil_escape_uri($atom->getLine()),
));
$defined = phutil_tag(
'a',
array(
'href' => $link,
'target' => '_blank',
),
$defined);
}
$view->addProperty(pht('Defined'), $defined);
}
private function composeMethods(DivinerLiveSymbol $symbol) {
$methods = $this->findMethods($symbol);
if (!$methods) {
return $methods;
}
foreach ($methods as $name => $method) {
// Check for "@task" on each parent, to find the most recently declared
// "@task".
$task = null;
foreach ($method['atoms'] as $key => $method_symbol) {
$atom = $method_symbol->getAtom();
if ($atom->getDocblockMetaValue('task')) {
$task = $atom->getDocblockMetaValue('task');
}
}
$methods[$name]['task'] = $task;
// Set 'inherited' if this atom has no implementation of the method.
if (last($method['implementations']) !== $symbol) {
$methods[$name]['inherited'] = true;
} else {
$methods[$name]['inherited'] = false;
}
}
return $methods;
}
private function findMethods(DivinerLiveSymbol $symbol) {
$child_specs = array();
foreach ($symbol->getExtends() as $extends) {
if ($extends->getType() == DivinerAtom::TYPE_CLASS) {
$child_specs = $this->findMethods($extends);
}
}
foreach ($symbol->getChildren() as $child) {
if ($child->getType() == DivinerAtom::TYPE_METHOD) {
$name = $child->getName();
if (isset($child_specs[$name])) {
$child_specs[$name]['atoms'][] = $child;
$child_specs[$name]['implementations'][] = $symbol;
} else {
$child_specs[$name] = array(
'atoms' => array($child),
'defined' => $symbol,
'implementations' => array($symbol),
);
}
}
}
return $child_specs;
}
private function composeTasks(DivinerLiveSymbol $symbol) {
$extends_task_specs = array();
foreach ($symbol->getExtends() as $extends) {
$extends_task_specs += $this->composeTasks($extends);
}
$task_specs = array();
$tasks = $symbol->getAtom()->getDocblockMetaValue('task');
if (strlen($tasks)) {
$tasks = phutil_split_lines($tasks, $retain_endings = false);
foreach ($tasks as $task) {
list($name, $title) = explode(' ', $task, 2);
$name = trim($name);
$title = trim($title);
$task_specs[$name] = array(
'name' => $name,
'title' => $title,
'defined' => $symbol,
);
}
}
$specs = $task_specs + $extends_task_specs;
// Reorder "@tasks" in original declaration order. Basically, we want to
// use the documentation of the closest subclass, but put tasks which
// were declared by parents first.
$keys = array_keys($extends_task_specs);
$specs = array_select_keys($specs, $keys) + $specs;
return $specs;
}
private function renderFullSignature(
DivinerLiveSymbol $symbol,
$is_link = false) {
switch ($symbol->getType()) {
case DivinerAtom::TYPE_CLASS:
case DivinerAtom::TYPE_INTERFACE:
case DivinerAtom::TYPE_METHOD:
case DivinerAtom::TYPE_FUNCTION:
break;
default:
return $symbol->getTitle();
}
$atom = $symbol->getAtom();
$out = array();
if ($atom->getProperty('final')) {
$out[] = 'final';
}
if ($atom->getProperty('abstract')) {
$out[] = 'abstract';
}
if ($atom->getProperty('access')) {
$out[] = $atom->getProperty('access');
}
if ($atom->getProperty('static')) {
$out[] = 'static';
}
switch ($symbol->getType()) {
case DivinerAtom::TYPE_CLASS:
case DivinerAtom::TYPE_INTERFACE:
$out[] = $symbol->getType();
break;
case DivinerAtom::TYPE_FUNCTION:
switch ($atom->getLanguage()) {
case 'php':
$out[] = $symbol->getType();
break;
}
break;
case DivinerAtom::TYPE_METHOD:
switch ($atom->getLanguage()) {
case 'php':
$out[] = DivinerAtom::TYPE_FUNCTION;
break;
}
break;
}
$anchor = null;
switch ($symbol->getType()) {
case DivinerAtom::TYPE_METHOD:
$anchor = $symbol->getType().'/'.$symbol->getName();
break;
default:
break;
}
$out[] = phutil_tag(
$anchor ? 'a' : 'span',
array(
'class' => 'diviner-atom-signature-name',
'href' => $anchor ? '#'.$anchor : null,
'name' => $is_link ? null : $anchor,
),
$symbol->getName());
$out = phutil_implode_html(' ', $out);
$parameters = $atom->getProperty('parameters');
if ($parameters !== null) {
$pout = array();
foreach ($parameters as $parameter) {
$pout[] = idx($parameter, 'name', '...');
}
$out = array($out, '('.implode(', ', $pout).')');
}
return phutil_tag(
'span',
array(
'class' => 'diviner-atom-signature',
),
$out);
}
private function buildParametersAndReturn(array $symbols) {
assert_instances_of($symbols, 'DivinerLiveSymbol');
$symbols = array_reverse($symbols);
$out = array();
$collected_parameters = null;
foreach ($symbols as $symbol) {
$parameters = $symbol->getAtom()->getProperty('parameters');
if ($parameters !== null) {
if ($collected_parameters === null) {
$collected_parameters = array();
}
foreach ($parameters as $key => $parameter) {
if (isset($collected_parameters[$key])) {
$collected_parameters[$key] += $parameter;
} else {
$collected_parameters[$key] = $parameter;
}
}
}
}
if (nonempty($parameters)) {
$out[] = id(new DivinerParameterTableView())
->setHeader(pht('Parameters'))
->setParameters($parameters);
}
$collected_return = null;
foreach ($symbols as $symbol) {
$return = $symbol->getAtom()->getProperty('return');
if ($return) {
if ($collected_return) {
$collected_return += $return;
} else {
$collected_return = $return;
}
}
}
if (nonempty($return)) {
$out[] = id(new DivinerReturnTableView())
->setHeader(pht('Return'))
->setReturn($collected_return);
}
return $out;
}
private function renderDocumentationText(
DivinerLiveSymbol $symbol,
PhabricatorMarkupEngine $engine) {
$field = 'default';
$content = $engine->getOutput($symbol, $field);
if (strlen(trim($symbol->getMarkupText($field)))) {
$content = phutil_tag(
'div',
array(
'class' => 'phabricator-remarkup',
),
$content);
} else {
$atom = $symbol->getAtom();
$content = phutil_tag(
'div',
array(
'class' => 'diviner-message-not-documented',
),
DivinerAtom::getThisAtomIsNotDocumentedString($atom->getType()));
}
return $content;
}
private function renderMethodDocumentationText(
DivinerLiveSymbol $parent,
array $spec,
PhabricatorMarkupEngine $engine) {
$symbols = array_values($spec['atoms']);
$implementations = array_values($spec['implementations']);
$field = 'default';
$out = array();
foreach ($symbols as $key => $symbol) {
$impl = $implementations[$key];
if ($impl !== $parent) {
if (!strlen(trim($symbol->getMarkupText($field)))) {
continue;
}
}
$doc = $this->renderDocumentationText($symbol, $engine);
if (($impl !== $parent) || $out) {
$where = id(new PHUIBoxView())
->addPadding(PHUI::PADDING_MEDIUM_LEFT)
->addPadding(PHUI::PADDING_MEDIUM_RIGHT)
->addClass('diviner-method-implementation-header')
->appendChild($impl->getName());
$doc = array($where, $doc);
if ($impl !== $parent) {
$doc = phutil_tag(
'div',
array(
'class' => 'diviner-method-implementation-inherited',
),
$doc);
}
}
$out[] = $doc;
}
// If we only have inherited implementations but none have documentation,
// render the last one here so we get the "this thing has no documentation"
// element.
if (!$out) {
$out[] = $this->renderDocumentationText($symbol, $engine);
}
return $out;
}
}
diff --git a/src/applications/diviner/controller/DivinerBookController.php b/src/applications/diviner/controller/DivinerBookController.php
index fcc9aa822b..125edbb6ab 100644
--- a/src/applications/diviner/controller/DivinerBookController.php
+++ b/src/applications/diviner/controller/DivinerBookController.php
@@ -1,101 +1,103 @@
<?php
final class DivinerBookController extends DivinerController {
private $bookName;
public function shouldAllowPublic() {
return true;
}
public function willProcessRequest(array $data) {
$this->bookName = $data['book'];
}
public function processRequest() {
$request = $this->getRequest();
$viewer = $request->getUser();
$book = id(new DivinerBookQuery())
->setViewer($viewer)
->withNames(array($this->bookName))
->executeOne();
if (!$book) {
return new Aphront404Response();
}
$crumbs = $this->buildApplicationCrumbs();
$crumbs->setBorder(true);
$crumbs->addTextCrumb(
$book->getShortTitle(),
'/book/'.$book->getName().'/');
$header = id(new PHUIHeaderView())
->setHeader($book->getTitle())
->setUser($viewer)
->setPolicyObject($book)
->setEpoch($book->getDateModified());
$document = new PHUIDocumentView();
$document->setHeader($header);
$document->addClass('diviner-view');
$document->setFontKit(PHUIDocumentView::FONT_SOURCE_SANS);
$atoms = id(new DivinerAtomQuery())
->setViewer($viewer)
->withBookPHIDs(array($book->getPHID()))
+ ->withGhosts(false)
+ ->withIsDocumentable(true)
->execute();
$atoms = msort($atoms, 'getSortKey');
$group_spec = $book->getConfig('groups');
if (!is_array($group_spec)) {
$group_spec = array();
}
$groups = mgroup($atoms, 'getGroupName');
$groups = array_select_keys($groups, array_keys($group_spec)) + $groups;
if (isset($groups[''])) {
$no_group = $groups[''];
unset($groups['']);
$groups[''] = $no_group;
}
$out = array();
foreach ($groups as $group => $atoms) {
$group_name = $book->getGroupName($group);
if (!strlen($group_name)) {
$group_name = pht('Free Radicals');
}
$section = id(new DivinerSectionView())
->setHeader($group_name);
$section->addContent($this->renderAtomList($atoms));
$out[] = $section;
}
$preface = $book->getPreface();
$preface_view = null;
if (strlen($preface)) {
$preface_view =
PhabricatorMarkupEngine::renderOneObject(
id(new PhabricatorMarkupOneOff())->setContent($preface),
'default',
$viewer);
}
$document->appendChild($preface_view);
$document->appendChild($out);
return $this->buildApplicationPage(
array(
$crumbs,
$document,
),
array(
'title' => $book->getTitle(),
));
}
}
diff --git a/src/applications/diviner/controller/DivinerFindController.php b/src/applications/diviner/controller/DivinerFindController.php
index 0f73164da6..061e357b9f 100644
--- a/src/applications/diviner/controller/DivinerFindController.php
+++ b/src/applications/diviner/controller/DivinerFindController.php
@@ -1,91 +1,94 @@
<?php
final class DivinerFindController extends DivinerController {
public function shouldAllowPublic() {
return true;
}
public function processRequest() {
$request = $this->getRequest();
$viewer = $request->getUser();
$book_name = $request->getStr('book');
$query_text = $request->getStr('name');
$book = null;
if ($book_name) {
$book = id(new DivinerBookQuery())
->setViewer($viewer)
->withNames(array($book_name))
->executeOne();
if (!$book) {
return new Aphront404Response();
}
}
$query = id(new DivinerAtomQuery())
->setViewer($viewer);
if ($book) {
$query->withBookPHIDs(array($book->getPHID()));
}
$context = $request->getStr('context');
if (strlen($context)) {
$query->withContexts(array($context));
}
$type = $request->getStr('type');
if (strlen($type)) {
$query->withTypes(array($type));
}
+ $query->withGhosts(false);
+ $query->withIsDocumentable(true);
+
$name_query = clone $query;
$name_query->withNames(
array(
$query_text,
// TODO: This could probably be more smartly normalized in the DB,
// but just fake it for now.
phutil_utf8_strtolower($query_text),
));
$atoms = $name_query->execute();
if (!$atoms) {
$title_query = clone $query;
$title_query->withTitles(array($query_text));
$atoms = $title_query->execute();
}
$not_found_uri = $this->getApplicationURI();
if (!$atoms) {
$dialog = id(new AphrontDialogView())
->setUser($viewer)
->setTitle(pht('Documentation Not Found'))
->appendChild(
pht(
'Unable to find the specified documentation. You may have '.
'followed a bad or outdated link.'))
->addCancelButton($not_found_uri, pht('Read More Documentation'));
return id(new AphrontDialogResponse())->setDialog($dialog);
}
if (count($atoms) == 1 && $request->getBool('jump')) {
$atom_uri = head($atoms)->getURI();
return id(new AphrontRedirectResponse())->setURI($atom_uri);
}
$list = $this->renderAtomList($atoms);
return $this->buildApplicationPage(
$list,
array(
'title' => array(pht('Find'), pht('"%s"', $query_text)),
));
}
}
diff --git a/src/applications/diviner/publisher/DivinerLivePublisher.php b/src/applications/diviner/publisher/DivinerLivePublisher.php
index 9e3ea7d1ef..71002ccd2a 100644
--- a/src/applications/diviner/publisher/DivinerLivePublisher.php
+++ b/src/applications/diviner/publisher/DivinerLivePublisher.php
@@ -1,152 +1,152 @@
<?php
final class DivinerLivePublisher extends DivinerPublisher {
private $book;
private function loadBook() {
if (!$this->book) {
$book_name = $this->getConfig('name');
$book = id(new DivinerLiveBook())->loadOneWhere('name = %s', $book_name);
if (!$book) {
$book = id(new DivinerLiveBook())
->setName($book_name)
->setViewPolicy(PhabricatorPolicies::POLICY_USER)
->save();
}
$book->setConfigurationData($this->getConfigurationData())->save();
$this->book = $book;
}
return $this->book;
}
private function loadSymbolForAtom(DivinerAtom $atom) {
$symbol = id(new DivinerAtomQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withBookPHIDs(array($this->loadBook()->getPHID()))
->withTypes(array($atom->getType()))
->withNames(array($atom->getName()))
->withContexts(array($atom->getContext()))
->withIndexes(array($this->getAtomSimilarIndex($atom)))
->withIncludeUndocumentable(true)
->withIncludeGhosts(true)
->executeOne();
if ($symbol) {
return $symbol;
}
return id(new DivinerLiveSymbol())
->setBookPHID($this->loadBook()->getPHID())
->setType($atom->getType())
->setName($atom->getName())
->setContext($atom->getContext())
->setAtomIndex($this->getAtomSimilarIndex($atom));
}
private function loadAtomStorageForSymbol(DivinerLiveSymbol $symbol) {
$storage = id(new DivinerLiveAtom())->loadOneWhere(
'symbolPHID = %s',
$symbol->getPHID());
if ($storage) {
return $storage;
}
return id(new DivinerLiveAtom())
->setSymbolPHID($symbol->getPHID());
}
protected function loadAllPublishedHashes() {
$symbols = id(new DivinerAtomQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withBookPHIDs(array($this->loadBook()->getPHID()))
- ->withIncludeUndocumentable(true)
+ ->withGhosts(false)
->execute();
return mpull($symbols, 'getGraphHash');
}
protected function deleteDocumentsByHash(array $hashes) {
$atom_table = new DivinerLiveAtom();
$symbol_table = new DivinerLiveSymbol();
$conn_w = $symbol_table->establishConnection('w');
$strings = array();
foreach ($hashes as $hash) {
$strings[] = qsprintf($conn_w, '%s', $hash);
}
foreach (PhabricatorLiskDAO::chunkSQL($strings, ', ') as $chunk) {
queryfx(
$conn_w,
'UPDATE %T SET graphHash = NULL, nodeHash = NULL
WHERE graphHash IN (%Q)',
$symbol_table->getTableName(),
$chunk);
}
queryfx(
$conn_w,
'DELETE a FROM %T a LEFT JOIN %T s
ON a.symbolPHID = s.phid
WHERE s.graphHash IS NULL',
$atom_table->getTableName(),
$symbol_table->getTableName());
}
protected function createDocumentsByHash(array $hashes) {
foreach ($hashes as $hash) {
$atom = $this->getAtomFromGraphHash($hash);
$ref = $atom->getRef();
$symbol = $this->loadSymbolForAtom($atom);
$is_documentable = $this->shouldGenerateDocumentForAtom($atom);
$symbol
->setGraphHash($hash)
->setIsDocumentable((int)$is_documentable)
->setTitle($ref->getTitle())
->setGroupName($ref->getGroup())
->setNodeHash($atom->getHash());
if ($atom->getType() !== DivinerAtom::TYPE_FILE) {
$renderer = $this->getRenderer();
$summary = $renderer->getAtomSummary($atom);
$symbol->setSummary($summary);
} else {
$symbol->setSummary('');
}
$symbol->save();
// TODO: We probably need a finer-grained sense of what "documentable"
// atoms are. Neither files nor methods are currently considered
// documentable, but for different reasons: files appear nowhere, while
// methods just don't appear at the top level. These are probably
// separate concepts. Since we need atoms in order to build method
// documentation, we insert them here. This also means we insert files,
// which are unnecessary and unused. Make sure this makes sense, but then
// probably introduce separate "isTopLevel" and "isDocumentable" flags?
// TODO: Yeah do that soon ^^^
if ($atom->getType() !== DivinerAtom::TYPE_FILE) {
$storage = $this->loadAtomStorageForSymbol($symbol)
->setAtomData($atom->toDictionary())
->setContent(null)
->save();
}
}
}
public function findAtomByRef(DivinerAtomRef $ref) {
// TODO: Actually implement this.
return null;
}
}
diff --git a/src/applications/diviner/query/DivinerAtomQuery.php b/src/applications/diviner/query/DivinerAtomQuery.php
index 70759a2cc1..12600927ff 100644
--- a/src/applications/diviner/query/DivinerAtomQuery.php
+++ b/src/applications/diviner/query/DivinerAtomQuery.php
@@ -1,447 +1,447 @@
<?php
final class DivinerAtomQuery extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $phids;
private $bookPHIDs;
private $names;
private $types;
private $contexts;
private $indexes;
- private $includeUndocumentable;
- private $includeGhosts;
+ private $isDocumentable;
+ private $isGhost;
private $nodeHashes;
private $titles;
private $nameContains;
private $needAtoms;
private $needExtends;
private $needChildren;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withBookPHIDs(array $phids) {
$this->bookPHIDs = $phids;
return $this;
}
public function withTypes(array $types) {
$this->types = $types;
return $this;
}
public function withNames(array $names) {
$this->names = $names;
return $this;
}
public function withContexts(array $contexts) {
$this->contexts = $contexts;
return $this;
}
public function withIndexes(array $indexes) {
$this->indexes = $indexes;
return $this;
}
public function withNodeHashes(array $hashes) {
$this->nodeHashes = $hashes;
return $this;
}
public function withTitles($titles) {
$this->titles = $titles;
return $this;
}
public function withNameContains($text) {
$this->nameContains = $text;
return $this;
}
public function needAtoms($need) {
$this->needAtoms = $need;
return $this;
}
public function needChildren($need) {
$this->needChildren = $need;
return $this;
}
/**
- * Include "ghosts", which are symbols which used to exist but do not exist
- * currently (for example, a function which existed in an older version of
- * the codebase but was deleted).
+ * Include or exclude "ghosts", which are symbols which used to exist but do
+ * not exist currently (for example, a function which existed in an older
+ * version of the codebase but was deleted).
*
* These symbols had PHIDs assigned to them, and may have other sorts of
* metadata that we don't want to lose (like comments or flags), so we don't
* delete them outright. They might also come back in the future: the change
* which deleted the symbol might be reverted, or the documentation might
* have been generated incorrectly by accident. In these cases, we can
* restore the original data.
*
- * However, most callers are not interested in these symbols, so they are
- * excluded by default. You can use this method to include them in results.
- *
- * @param bool True to include ghosts.
+ * @param bool
* @return this
*/
- public function withIncludeGhosts($include) {
- $this->includeGhosts = $include;
+ public function withGhosts($ghosts) {
+ $this->isGhost = $ghosts;
return $this;
}
public function needExtends($need) {
$this->needExtends = $need;
return $this;
}
- public function withIncludeUndocumentable($include) {
- $this->includeUndocumentable = $include;
+ public function withIsDocumentable($documentable) {
+ $this->isDocumentable = $documentable;
return $this;
}
protected function loadPage() {
$table = new DivinerLiveSymbol();
$conn_r = $table->establishConnection('r');
$data = queryfx_all(
$conn_r,
'SELECT * FROM %T %Q %Q %Q',
$table->getTableName(),
$this->buildWhereClause($conn_r),
$this->buildOrderClause($conn_r),
$this->buildLimitClause($conn_r));
return $table->loadAllFromArray($data);
}
protected function willFilterPage(array $atoms) {
$books = array_unique(mpull($atoms, 'getBookPHID'));
$books = id(new DivinerBookQuery())
->setViewer($this->getViewer())
->withPHIDs($books)
->execute();
$books = mpull($books, null, 'getPHID');
foreach ($atoms as $key => $atom) {
$book = idx($books, $atom->getBookPHID());
if (!$book) {
unset($atoms[$key]);
continue;
}
$atom->attachBook($book);
}
if ($this->needAtoms) {
$atom_data = id(new DivinerLiveAtom())->loadAllWhere(
'symbolPHID IN (%Ls)',
mpull($atoms, 'getPHID'));
$atom_data = mpull($atom_data, null, 'getSymbolPHID');
foreach ($atoms as $key => $atom) {
$data = idx($atom_data, $atom->getPHID());
if (!$data) {
unset($atoms[$key]);
continue;
}
$atom->attachAtom($data);
}
}
// Load all of the symbols this symbol extends, recursively. Commonly,
// this means all the ancestor classes and interfaces it extends and
// implements.
if ($this->needExtends) {
// First, load all the matching symbols by name. This does 99% of the
// work in most cases, assuming things are named at all reasonably.
$names = array();
foreach ($atoms as $atom) {
foreach ($atom->getAtom()->getExtends() as $xref) {
$names[] = $xref->getName();
}
}
if ($names) {
$xatoms = id(new DivinerAtomQuery())
->setViewer($this->getViewer())
->withNames($names)
->needExtends(true)
->needAtoms(true)
->needChildren($this->needChildren)
->execute();
$xatoms = mgroup($xatoms, 'getName', 'getType', 'getBookPHID');
} else {
$xatoms = array();
}
foreach ($atoms as $atom) {
$alang = $atom->getAtom()->getLanguage();
$extends = array();
foreach ($atom->getAtom()->getExtends() as $xref) {
// If there are no symbols of the matching name and type, we can't
// resolve this.
if (empty($xatoms[$xref->getName()][$xref->getType()])) {
continue;
}
// If we found matches in the same documentation book, prefer them
// over other matches. Otherwise, look at all the the matches.
$matches = $xatoms[$xref->getName()][$xref->getType()];
if (isset($matches[$atom->getBookPHID()])) {
$maybe = $matches[$atom->getBookPHID()];
} else {
$maybe = array_mergev($matches);
}
if (!$maybe) {
continue;
}
// Filter out matches in a different language, since, e.g., PHP
// classes can not implement JS classes.
$same_lang = array();
foreach ($maybe as $xatom) {
if ($xatom->getAtom()->getLanguage() == $alang) {
$same_lang[] = $xatom;
}
}
if (!$same_lang) {
continue;
}
// If we have duplicates remaining, just pick the first one. There's
// nothing more we can do to figure out which is the real one.
$extends[] = head($same_lang);
}
$atom->attachExtends($extends);
}
}
if ($this->needChildren) {
$child_hashes = $this->getAllChildHashes($atoms, $this->needExtends);
if ($child_hashes) {
$children = id(new DivinerAtomQuery())
->setViewer($this->getViewer())
->withIncludeUndocumentable(true)
->withNodeHashes($child_hashes)
->needAtoms($this->needAtoms)
->execute();
$children = mpull($children, null, 'getNodeHash');
} else {
$children = array();
}
$this->attachAllChildren($atoms, $children, $this->needExtends);
}
return $atoms;
}
protected function buildWhereClause(AphrontDatabaseConnection $conn_r) {
$where = array();
if ($this->ids) {
$where[] = qsprintf(
$conn_r,
'id IN (%Ld)',
$this->ids);
}
if ($this->phids) {
$where[] = qsprintf(
$conn_r,
'phid IN (%Ls)',
$this->phids);
}
if ($this->bookPHIDs) {
$where[] = qsprintf(
$conn_r,
'bookPHID IN (%Ls)',
$this->bookPHIDs);
}
if ($this->types) {
$where[] = qsprintf(
$conn_r,
'type IN (%Ls)',
$this->types);
}
if ($this->names) {
$where[] = qsprintf(
$conn_r,
'name IN (%Ls)',
$this->names);
}
if ($this->titles) {
$hashes = array();
foreach ($this->titles as $title) {
$slug = DivinerAtomRef::normalizeTitleString($title);
$hash = PhabricatorHash::digestForIndex($slug);
$hashes[] = $hash;
}
$where[] = qsprintf(
$conn_r,
'titleSlugHash in (%Ls)',
$hashes);
}
if ($this->contexts) {
$with_null = false;
$contexts = $this->contexts;
foreach ($contexts as $key => $value) {
if ($value === null) {
unset($contexts[$key]);
$with_null = true;
continue;
}
}
if ($contexts && $with_null) {
$where[] = qsprintf(
$conn_r,
'context IN (%Ls) OR context IS NULL',
$contexts);
} else if ($contexts) {
$where[] = qsprintf(
$conn_r,
'context IN (%Ls)',
$contexts);
} else if ($with_null) {
$where[] = qsprintf(
$conn_r,
'context IS NULL');
}
}
if ($this->indexes) {
$where[] = qsprintf(
$conn_r,
'atomIndex IN (%Ld)',
$this->indexes);
}
- if (!$this->includeUndocumentable) {
+ if ($this->isDocumentable !== null) {
$where[] = qsprintf(
$conn_r,
- 'isDocumentable = 1');
+ 'isDocumentable = %d',
+ (int)$this->isDocumentable);
}
- if (!$this->includeGhosts) {
- $where[] = qsprintf(
- $conn_r,
- 'graphHash IS NOT NULL');
+ if ($this->isGhost !== null) {
+ if ($this->isGhost) {
+ $where[] = qsprintf($conn_r, 'graphHash IS NULL');
+ } else {
+ $where[] = qsprintf($conn_r, 'graphHash IS NOT NULL');
+ }
}
if ($this->nodeHashes) {
$where[] = qsprintf(
$conn_r,
'nodeHash IN (%Ls)',
$this->nodeHashes);
}
if ($this->nameContains) {
// NOTE: This CONVERT() call makes queries case-insensitive, since the
// column has binary collation. Eventually, this should move into
// fulltext.
$where[] = qsprintf(
$conn_r,
'CONVERT(name USING utf8) LIKE %~',
$this->nameContains);
}
$where[] = $this->buildPagingClause($conn_r);
return $this->formatWhereClause($where);
}
/**
* Walk a list of atoms and collect all the node hashes of the atoms'
* children. When recursing, also walk up the tree and collect children of
* atoms they extend.
*
* @param list<DivinerLiveSymbol> List of symbols to collect child hashes of.
* @param bool True to collect children of extended atoms,
* as well.
* @return map<string, string> Hashes of atoms' children.
*/
private function getAllChildHashes(array $symbols, $recurse_up) {
assert_instances_of($symbols, 'DivinerLiveSymbol');
$hashes = array();
foreach ($symbols as $symbol) {
foreach ($symbol->getAtom()->getChildHashes() as $hash) {
$hashes[$hash] = $hash;
}
if ($recurse_up) {
$hashes += $this->getAllChildHashes($symbol->getExtends(), true);
}
}
return $hashes;
}
/**
* Attach child atoms to existing atoms. In recursive mode, also attach child
* atoms to atoms that these atoms extend.
*
* @param list<DivinerLiveSymbol> List of symbols to attach children to.
* @param map<string, DivinerLiveSymbol> Map of symbols, keyed by node hash.
* @param bool True to attach children to extended atoms, as well.
* @return void
*/
private function attachAllChildren(
array $symbols,
array $children,
$recurse_up) {
assert_instances_of($symbols, 'DivinerLiveSymbol');
assert_instances_of($children, 'DivinerLiveSymbol');
foreach ($symbols as $symbol) {
$symbol_children = array();
foreach ($symbol->getAtom()->getChildHashes() as $hash) {
if (isset($children[$hash])) {
$symbol_children[] = $children[$hash];
}
}
$symbol->attachChildren($symbol_children);
if ($recurse_up) {
$this->attachAllChildren($symbol->getExtends(), $children, true);
}
}
}
public function getQueryApplicationClass() {
return 'PhabricatorDivinerApplication';
}
}
diff --git a/src/applications/diviner/storage/DivinerLiveBook.php b/src/applications/diviner/storage/DivinerLiveBook.php
index ef7a9bb277..41e6ab5b21 100644
--- a/src/applications/diviner/storage/DivinerLiveBook.php
+++ b/src/applications/diviner/storage/DivinerLiveBook.php
@@ -1,108 +1,106 @@
<?php
final class DivinerLiveBook extends DivinerDAO
implements
PhabricatorPolicyInterface,
PhabricatorDestructibleInterface {
protected $name;
protected $viewPolicy;
protected $configurationData = array();
protected function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
self::CONFIG_SERIALIZATION => array(
'configurationData' => self::SERIALIZATION_JSON,
),
self::CONFIG_COLUMN_SCHEMA => array(
'name' => 'text64',
),
self::CONFIG_KEY_SCHEMA => array(
'key_phid' => null,
'phid' => array(
'columns' => array('phid'),
'unique' => true,
),
'name' => array(
'columns' => array('name'),
'unique' => true,
),
),
) + parent::getConfiguration();
}
public function getConfig($key, $default = null) {
return idx($this->configurationData, $key, $default);
}
public function setConfig($key, $value) {
$this->configurationData[$key] = $value;
return $this;
}
public function generatePHID() {
return PhabricatorPHID::generateNewPHID(
DivinerBookPHIDType::TYPECONST);
}
public function getTitle() {
return $this->getConfig('title', $this->getName());
}
public function getShortTitle() {
return $this->getConfig('short', $this->getTitle());
}
public function getPreface() {
return $this->getConfig('preface');
}
public function getGroupName($group) {
$groups = $this->getConfig('groups', array());
$spec = idx($groups, $group, array());
return idx($spec, 'name', $group);
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
);
}
public function getPolicy($capability) {
return PhabricatorPolicies::getMostOpenPolicy();
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
return false;
}
public function describeAutomaticCapability($capability) {
return null;
}
/* -( PhabricatorDestructibleInterface )----------------------------------- */
public function destroyObjectPermanently(
PhabricatorDestructionEngine $engine) {
$this->openTransaction();
$atoms = id(new DivinerAtomQuery())
->setViewer($engine->getViewer())
->withBookPHIDs(array($this->getPHID()))
- ->withIncludeGhosts(true)
- ->withIncludeUndocumentable(true)
->execute();
foreach ($atoms as $atom) {
$engine->destroyObject($atom);
}
$this->delete();
$this->saveTransaction();
}
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sun, Jan 19, 19:39 (1 d, 9 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1128056
Default Alt Text
(45 KB)
Attached To
Mode
rP Phorge
Attached
Detach File
Event Timeline
Log In to Comment