Page MenuHomePhorge

No OneTemporary

diff --git a/src/applications/diviner/controller/DivinerAtomController.php b/src/applications/diviner/controller/DivinerAtomController.php
index d381a53d03..d923d3d641 100644
--- a/src/applications/diviner/controller/DivinerAtomController.php
+++ b/src/applications/diviner/controller/DivinerAtomController.php
@@ -1,304 +1,424 @@
<?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();
$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))
->needAtoms(true)
->needExtends(true)
+ ->needChildren(true)
->executeOne();
if (!$symbol) {
return new Aphront404Response();
}
$atom = $symbol->getAtom();
-
- $extends = $atom->getExtends();
-
- $child_hashes = $atom->getChildHashes();
- if ($child_hashes) {
- $children = id(new DivinerAtomQuery())
- ->setViewer($viewer)
- ->withIncludeUndocumentable(true)
- ->withNodeHashes($child_hashes)
- ->execute();
- } else {
- $children = array();
- }
-
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName($book->getShortTitle())
->setHref('/book/'.$book->getName().'/'));
$atom_short_title = $atom->getDocblockMetaValue(
'short',
$symbol->getTitle());
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName($atom_short_title));
$header = id(new PhabricatorHeaderView())
->setHeader($symbol->getTitle())
->addTag(
id(new PhabricatorTagView())
->setType(PhabricatorTagView::TYPE_STATE)
->setBackgroundColor(PhabricatorTagView::COLOR_BLUE)
->setName(DivinerAtom::getAtomTypeNameString($atom->getType())));
$properties = id(new PhabricatorPropertyListView());
$group = $atom->getDocblockMetaValue('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 AphrontErrorView())
->setErrors($warnings)
->setTitle(pht('Documentation Warnings'))
->setSeverity(AphrontErrorView::SEVERITY_WARNING);
}
$field = 'default';
$engine = id(new PhabricatorMarkupEngine())
->setViewer($viewer)
->addObject($symbol, $field)
->process();
$content = $engine->getOutput($symbol, $field);
if (strlen(trim($symbol->getMarkupText($field)))) {
$content = phutil_tag(
'div',
array(
'class' => 'phabricator-remarkup',
),
array(
$content,
));
} else {
$undoc = DivinerAtom::getThisAtomIsNotDocumentedString($atom->getType());
$content = id(new AphrontErrorView())
->appendChild($undoc)
->setSeverity(AphrontErrorView::SEVERITY_NODATA);
}
$toc = $engine->getEngineMetadata(
$symbol,
$field,
PhutilRemarkupEngineRemarkupHeaderBlockRule::KEY_HEADER_TOC,
array());
$document = id(new PHUIDocumentView())
->setBook($book->getTitle(), $group_name)
->setHeader($header)
->appendChild($properties)
->appendChild($warnings)
->appendChild($content);
$parameters = $atom->getProperty('parameters');
if ($parameters !== null) {
$document->appendChild(
id(new PhabricatorHeaderView())
->setHeader(pht('Parameters')));
$document->appendChild(
id(new DivinerParameterTableView())
->setParameters($parameters));
}
$return = $atom->getProperty('return');
if ($return !== null) {
$document->appendChild(
id(new PhabricatorHeaderView())
->setHeader(pht('Return')));
$document->appendChild(
id(new DivinerReturnTableView())
->setReturn($return));
}
- if ($children) {
+ $methods = $this->composeMethods($symbol);
+ if ($methods) {
+
+ $tasks = $this->composeTasks($symbol);
+
+ if ($tasks) {
+ $methods_by_task = igroup($methods, 'task');
+
+ $document->appendChild(
+ id(new PhabricatorHeaderView())
+ ->setHeader(pht('Tasks')));
+
+ if (isset($methods_by_task[''])) {
+ $tasks[''] = array(
+ 'name' => '',
+ 'title' => pht('Other Methods'),
+ 'defined' => $symbol,
+ );
+ }
+
+ foreach ($tasks as $spec) {
+ $document->appendChild(
+ id(new PhabricatorHeaderView())
+ ->setHeader($spec['title']));
+
+ $task_methods = idx($methods_by_task, $spec['name'], array());
+ if ($task_methods) {
+ $document->appendChild(hsprintf('<ul>'));
+ foreach ($task_methods as $task_method) {
+ $atom = last($task_method['atoms']);
+ $document->appendChild(
+ hsprintf('<li>%s()</li>', $atom->getName()));
+ }
+ $document->appendChild(hsprintf('</ul>'));
+ } else {
+ $document->appendChild("No methods for this task.");
+ }
+ }
+ }
+
$document->appendChild(
id(new PhabricatorHeaderView())
->setHeader(pht('Methods')));
- foreach ($children as $child) {
- $document->appendChild(
- id(new PhabricatorHeaderView())
- ->setHeader($child->getName()));
+ foreach ($methods as $spec) {
+ $method_header = id(new PhabricatorHeaderView())
+ ->setHeader(last($spec['atoms'])->getName());
+
+ $inherited = $spec['inherited'];
+ if ($inherited) {
+ $method_header->addTag(
+ id(new PhabricatorTagView())
+ ->setType(PhabricatorTagView::TYPE_STATE)
+ ->setBackgroundColor(PhabricatorTagView::COLOR_GREY)
+ ->setName(pht('Inherited')));
+ }
+
+ $document->appendChild($method_header);
}
}
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(),
'device' => true,
));
}
private function buildExtendsAndImplements(
PhabricatorPropertyListView $view,
DivinerLiveSymbol $symbol) {
$lineage = $this->getExtendsLineage($symbol);
if ($lineage) {
$lineage = mpull($lineage, 'getName');
$lineage = implode(' > ', $lineage);
$view->addProperty(pht('Extends'), $lineage);
}
$implements = $this->getImplementsLineage($symbol);
if ($implements) {
$items = array();
foreach ($implements as $spec) {
$via = $spec['via'];
$iface = $spec['interface'];
if ($via == $symbol) {
$items[] = $iface->getName();
} else {
$items[] = $iface->getName().' (via '.$via->getName().')';
}
}
$view->addProperty(
pht('Implements'),
phutil_implode_html(phutil_tag('br'), $items));
}
}
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(
PhabricatorPropertyListView $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,
+ );
+ }
+ }
+
+ return $task_specs + $extends_task_specs;
+ }
+
}
diff --git a/src/applications/diviner/publisher/DivinerLivePublisher.php b/src/applications/diviner/publisher/DivinerLivePublisher.php
index 164aa6f548..62004da063 100644
--- a/src/applications/diviner/publisher/DivinerLivePublisher.php
+++ b/src/applications/diviner/publisher/DivinerLivePublisher.php
@@ -1,144 +1,151 @@
<?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)
->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 ($is_documentable) {
$renderer = $this->getRenderer();
$summary = $renderer->renderAtomSummary($atom);
$summary = (string)phutil_safe_html($summary);
$symbol->setSummary($summary);
}
$symbol->save();
- if ($is_documentable) {
- $storage = $this->loadAtomStorageForSymbol($symbol)
- ->setAtomData($atom->toDictionary())
- ->setContent(null)
- ->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?
+
+ $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 06e6fda1e3..662bc834d8 100644
--- a/src/applications/diviner/query/DivinerAtomQuery.php
+++ b/src/applications/diviner/query/DivinerAtomQuery.php
@@ -1,325 +1,408 @@
<?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 $nodeHashes;
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 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).
*
* 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.
* @return this
*/
public function withIncludeGhosts($include) {
$this->includeGhosts = $include;
return $this;
}
public function needExtends($need) {
$this->needExtends = $need;
return $this;
}
public function withIncludeUndocumentable($include) {
$this->includeUndocumentable = $include;
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);
}
- $need_atoms = $this->needAtoms;
-
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;
}
private 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->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) {
$where[] = qsprintf(
$conn_r,
'isDocumentable = 1');
}
if (!$this->includeGhosts) {
$where[] = qsprintf(
$conn_r,
'graphHash IS NOT NULL');
}
if ($this->nodeHashes) {
$where[] = qsprintf(
$conn_r,
'nodeHash IN (%Ls)',
$this->nodeHashes);
}
$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 childeren 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);
+ }
+ }
+ }
+
}
diff --git a/src/applications/diviner/storage/DivinerLiveSymbol.php b/src/applications/diviner/storage/DivinerLiveSymbol.php
index 8a8a4bb79f..51bad9b94e 100644
--- a/src/applications/diviner/storage/DivinerLiveSymbol.php
+++ b/src/applications/diviner/storage/DivinerLiveSymbol.php
@@ -1,172 +1,183 @@
<?php
final class DivinerLiveSymbol extends DivinerDAO
implements PhabricatorPolicyInterface, PhabricatorMarkupInterface {
protected $phid;
protected $bookPHID;
protected $context;
protected $type;
protected $name;
protected $atomIndex;
protected $graphHash;
protected $identityHash;
protected $nodeHash;
protected $title;
protected $groupName;
protected $summary;
protected $isDocumentable = 0;
private $book = self::ATTACHABLE;
private $atom = self::ATTACHABLE;
private $extends = self::ATTACHABLE;
+ private $children = self::ATTACHABLE;
public function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
self::CONFIG_TIMESTAMPS => false,
) + parent::getConfiguration();
}
public function generatePHID() {
return PhabricatorPHID::generateNewPHID(
DivinerPHIDTypeAtom::TYPECONST);
}
public function getBook() {
return $this->assertAttached($this->book);
}
public function attachBook(DivinerLiveBook $book) {
$this->book = $book;
return $this;
}
public function getAtom() {
return $this->assertAttached($this->atom);
}
public function attachAtom(DivinerLiveAtom $atom) {
$this->atom = DivinerAtom::newFromDictionary($atom->getAtomData());
return $this;
}
public function getURI() {
$parts = array(
'book',
$this->getBook()->getName(),
$this->getType(),
);
if ($this->getContext()) {
$parts[] = $this->getContext();
}
$parts[] = $this->getName();
if ($this->getAtomIndex()) {
$parts[] = $this->getAtomIndex();
}
return '/'.implode('/', $parts).'/';
}
public function getSortKey() {
return $this->getTitle();
}
public function save() {
// NOTE: The identity hash is just a sanity check because the unique tuple
// on this table is way way too long to fit into a normal UNIQUE KEY. We
// don't use it directly, but its existence prevents duplicate records.
if (!$this->identityHash) {
$this->identityHash = PhabricatorHash::digestForIndex(
serialize(
array(
'bookPHID' => $this->getBookPHID(),
'context' => $this->getContext(),
'type' => $this->getType(),
'name' => $this->getName(),
'index' => $this->getAtomIndex(),
)));
}
return parent::save();
}
public function getTitle() {
$title = parent::getTitle();
if (!strlen($title)) {
$title = $this->getName();
}
return $title;
}
public function attachExtends(array $extends) {
assert_instances_of($extends, 'DivinerLiveSymbol');
$this->extends = $extends;
return $this;
}
public function getExtends() {
return $this->assertAttached($this->extends);
}
+ public function attachChildren(array $children) {
+ assert_instances_of($children, 'DivinerLiveSymbol');
+ $this->children = $children;
+ return $this;
+ }
+
+ public function getChildren() {
+ return $this->assertAttached($this->children);
+ }
+
/* -( PhabricatorPolicyInterface )----------------------------------------- */
public function getCapabilities() {
return $this->getBook()->getCapabilities();
}
public function getPolicy($capability) {
return $this->getBook()->getPolicy($capability);
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
return $this->getBook()->hasAutomaticCapability($capability, $viewer);
}
/* -( Markup Interface )--------------------------------------------------- */
public function getMarkupFieldKey($field) {
return $this->getPHID().':'.$field.':'.$this->getGraphHash();
}
public function newMarkupEngine($field) {
$engine = PhabricatorMarkupEngine::newMarkupEngine(array());
$engine->setConfig('preserve-linebreaks', false);
// $engine->setConfig('diviner.renderer', new DivinerDefaultRenderer());
$engine->setConfig('header.generate-toc', true);
return $engine;
}
public function getMarkupText($field) {
return $this->getAtom()->getDocblockText();
}
public function didMarkupText(
$field,
$output,
PhutilMarkupEngine $engine) {
return $output;
}
public function shouldUseMarkupCache($field) {
return false;
}
}

File Metadata

Mime Type
text/x-diff
Expires
Sun, Jan 19, 19:00 (1 w, 3 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1127641
Default Alt Text
(33 KB)

Event Timeline