Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F2893754
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
33 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 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
Details
Attached
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)
Attached To
Mode
rP Phorge
Attached
Detach File
Event Timeline
Log In to Comment