2013-06-01 00:14:39 +02:00
|
|
|
<?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();
|
|
|
|
}
|
|
|
|
|
2013-08-27 12:14:00 +02:00
|
|
|
// 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.
|
|
|
|
|
2013-06-06 17:36:51 +02:00
|
|
|
$symbol = id(new DivinerAtomQuery())
|
2013-06-01 00:14:39 +02:00
|
|
|
->setViewer($viewer)
|
|
|
|
->withBookPHIDs(array($book->getPHID()))
|
|
|
|
->withTypes(array($this->atomType))
|
|
|
|
->withNames(array($this->atomName))
|
|
|
|
->withContexts(array($this->atomContext))
|
|
|
|
->withIndexes(array($this->atomIndex))
|
|
|
|
->needAtoms(true)
|
2013-08-28 18:54:39 +02:00
|
|
|
->needExtends(true)
|
2013-06-01 00:14:39 +02:00
|
|
|
->executeOne();
|
|
|
|
|
2013-06-06 17:36:51 +02:00
|
|
|
if (!$symbol) {
|
2013-06-01 00:14:39 +02:00
|
|
|
return new Aphront404Response();
|
|
|
|
}
|
|
|
|
|
2013-06-06 17:36:51 +02:00
|
|
|
$atom = $symbol->getAtom();
|
|
|
|
|
2013-08-28 18:54:39 +02:00
|
|
|
$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();
|
|
|
|
}
|
|
|
|
|
2013-06-01 00:14:39 +02:00
|
|
|
$crumbs = $this->buildApplicationCrumbs();
|
|
|
|
|
|
|
|
$crumbs->addCrumb(
|
|
|
|
id(new PhabricatorCrumbView())
|
2013-06-06 17:36:51 +02:00
|
|
|
->setName($book->getShortTitle())
|
2013-06-01 00:14:39 +02:00
|
|
|
->setHref('/book/'.$book->getName().'/'));
|
|
|
|
|
2013-06-06 17:36:51 +02:00
|
|
|
$atom_short_title = $atom->getDocblockMetaValue(
|
|
|
|
'short',
|
|
|
|
$symbol->getTitle());
|
|
|
|
|
2013-06-01 00:14:39 +02:00
|
|
|
$crumbs->addCrumb(
|
|
|
|
id(new PhabricatorCrumbView())
|
2013-06-06 17:36:51 +02:00
|
|
|
->setName($atom_short_title));
|
|
|
|
|
|
|
|
$header = id(new PhabricatorHeaderView())
|
|
|
|
->setHeader($symbol->getTitle())
|
|
|
|
->addTag(
|
|
|
|
id(new PhabricatorTagView())
|
|
|
|
->setType(PhabricatorTagView::TYPE_STATE)
|
|
|
|
->setBackgroundColor(PhabricatorTagView::COLOR_BLUE)
|
|
|
|
->setName($this->renderAtomTypeName($atom->getType())));
|
|
|
|
|
|
|
|
$properties = id(new PhabricatorPropertyListView());
|
|
|
|
|
|
|
|
$group = $atom->getDocblockMetaValue('group');
|
|
|
|
if ($group) {
|
|
|
|
$group_name = $book->getGroupName($group);
|
|
|
|
} else {
|
|
|
|
$group_name = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
$properties->addProperty(
|
|
|
|
pht('Defined'),
|
|
|
|
$atom->getFile().':'.$atom->getLine());
|
|
|
|
|
2013-08-28 18:54:39 +02:00
|
|
|
|
|
|
|
$this->buildExtendsAndImplements($properties, $symbol);
|
|
|
|
|
2013-08-27 12:14:00 +02:00
|
|
|
$warnings = $atom->getWarnings();
|
|
|
|
if ($warnings) {
|
|
|
|
$warnings = id(new AphrontErrorView())
|
|
|
|
->setErrors($warnings)
|
|
|
|
->setTitle(pht('Documentation Warnings'))
|
|
|
|
->setSeverity(AphrontErrorView::SEVERITY_WARNING);
|
|
|
|
}
|
|
|
|
|
2013-06-06 17:36:51 +02:00
|
|
|
$field = 'default';
|
|
|
|
$engine = id(new PhabricatorMarkupEngine())
|
|
|
|
->setViewer($viewer)
|
|
|
|
->addObject($symbol, $field)
|
|
|
|
->process();
|
2013-06-01 00:14:39 +02:00
|
|
|
|
2013-06-06 17:36:51 +02:00
|
|
|
$content = $engine->getOutput($symbol, $field);
|
|
|
|
|
2013-08-27 12:14:00 +02:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2013-06-06 17:36:51 +02:00
|
|
|
$toc = $engine->getEngineMetadata(
|
|
|
|
$symbol,
|
|
|
|
$field,
|
|
|
|
PhutilRemarkupEngineRemarkupHeaderBlockRule::KEY_HEADER_TOC,
|
|
|
|
array());
|
2013-06-01 00:14:39 +02:00
|
|
|
|
|
|
|
$document = id(new PHUIDocumentView())
|
2013-06-06 17:36:51 +02:00
|
|
|
->setBook($book->getTitle(), $group_name)
|
|
|
|
->setHeader($header)
|
|
|
|
->appendChild($properties)
|
2013-08-27 12:14:00 +02:00
|
|
|
->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));
|
|
|
|
}
|
2013-06-06 17:36:51 +02:00
|
|
|
|
2013-08-28 18:54:39 +02:00
|
|
|
if ($children) {
|
|
|
|
$document->appendChild(
|
|
|
|
id(new PhabricatorHeaderView())
|
|
|
|
->setHeader(pht('Methods')));
|
|
|
|
foreach ($children as $child) {
|
|
|
|
$document->appendChild(
|
|
|
|
id(new PhabricatorHeaderView())
|
|
|
|
->setHeader($child->getName()));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-06-06 17:36:51 +02:00
|
|
|
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));
|
|
|
|
}
|
|
|
|
|
2013-06-06 21:47:40 +02:00
|
|
|
$document->setSideNav($side, PHUIDocumentView::NAV_TOP);
|
2013-06-06 17:36:51 +02:00
|
|
|
}
|
2013-06-01 00:14:39 +02:00
|
|
|
|
|
|
|
return $this->buildApplicationPage(
|
|
|
|
array(
|
|
|
|
$crumbs,
|
|
|
|
$document,
|
|
|
|
),
|
|
|
|
array(
|
2013-06-06 17:36:51 +02:00
|
|
|
'title' => $symbol->getTitle(),
|
2013-06-01 00:14:39 +02:00
|
|
|
'device' => true,
|
|
|
|
));
|
|
|
|
}
|
|
|
|
|
2013-06-06 17:36:51 +02:00
|
|
|
private function renderAtomTypeName($name) {
|
|
|
|
return phutil_utf8_ucwords($name);
|
|
|
|
}
|
|
|
|
|
2013-08-28 18:54:39 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2013-06-01 00:14:39 +02:00
|
|
|
}
|