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();
|
|
|
|
|
2013-09-08 18:16:55 +02:00
|
|
|
require_celerity_resource('diviner-shared-css');
|
|
|
|
|
2013-06-01 00:14:39 +02:00
|
|
|
$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-08-28 18:57:20 +02:00
|
|
|
->needChildren(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-06-01 00:14:39 +02:00
|
|
|
$crumbs = $this->buildApplicationCrumbs();
|
|
|
|
|
2013-12-19 02:47:34 +01:00
|
|
|
$crumbs->addTextCrumb(
|
|
|
|
$book->getShortTitle(),
|
|
|
|
'/book/'.$book->getName().'/');
|
2013-06-01 00:14:39 +02:00
|
|
|
|
2013-06-06 17:36:51 +02:00
|
|
|
$atom_short_title = $atom->getDocblockMetaValue(
|
|
|
|
'short',
|
|
|
|
$symbol->getTitle());
|
|
|
|
|
2013-12-19 02:47:34 +01:00
|
|
|
$crumbs->addTextCrumb($atom_short_title);
|
2013-06-06 17:36:51 +02:00
|
|
|
|
2013-09-17 18:12:37 +02:00
|
|
|
$header = id(new PHUIHeaderView())
|
2013-09-08 18:11:59 +02:00
|
|
|
->setHeader($this->renderFullSignature($symbol))
|
2013-06-06 17:36:51 +02:00
|
|
|
->addTag(
|
2014-01-14 23:09:52 +01:00
|
|
|
id(new PHUITagView())
|
|
|
|
->setType(PHUITagView::TYPE_STATE)
|
|
|
|
->setBackgroundColor(PHUITagView::COLOR_BLUE)
|
2013-09-08 18:11:59 +02:00
|
|
|
->setName(DivinerAtom::getAtomTypeNameString($atom->getType())));
|
2013-06-06 17:36:51 +02:00
|
|
|
|
2013-10-11 16:53:56 +02:00
|
|
|
$properties = id(new PHUIPropertyListView());
|
2013-06-06 17:36:51 +02:00
|
|
|
|
2013-09-02 20:33:02 +02:00
|
|
|
$group = $atom->getProperty('group');
|
2013-06-06 17:36:51 +02:00
|
|
|
if ($group) {
|
|
|
|
$group_name = $book->getGroupName($group);
|
|
|
|
} else {
|
|
|
|
$group_name = null;
|
|
|
|
}
|
|
|
|
|
2013-08-28 18:54:54 +02:00
|
|
|
$this->buildDefined($properties, $symbol);
|
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-08-30 18:17:27 +02:00
|
|
|
$methods = $this->composeMethods($symbol);
|
|
|
|
|
2013-06-06 17:36:51 +02:00
|
|
|
$field = 'default';
|
|
|
|
$engine = id(new PhabricatorMarkupEngine())
|
|
|
|
->setViewer($viewer)
|
2013-08-30 18:17:27 +02:00
|
|
|
->addObject($symbol, $field);
|
|
|
|
foreach ($methods as $method) {
|
|
|
|
foreach ($method['atoms'] as $matom) {
|
|
|
|
$engine->addObject($matom, $field);
|
|
|
|
}
|
2013-08-27 12:14:00 +02:00
|
|
|
}
|
2013-08-30 18:17:27 +02:00
|
|
|
$engine->process();
|
2013-08-27 12:14:00 +02:00
|
|
|
|
2013-08-30 18:17:27 +02:00
|
|
|
$content = $this->renderDocumentationText($symbol, $engine);
|
2013-08-27 12:14:00 +02:00
|
|
|
|
2013-06-06 17:36:51 +02:00
|
|
|
$toc = $engine->getEngineMetadata(
|
|
|
|
$symbol,
|
|
|
|
$field,
|
2014-08-04 16:55:43 +02:00
|
|
|
PhutilRemarkupHeaderBlockRule::KEY_HEADER_TOC,
|
2013-06-06 17:36:51 +02:00
|
|
|
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)
|
2014-07-28 19:36:16 +02:00
|
|
|
->addClass('diviner-view')
|
2014-03-06 20:28:24 +01:00
|
|
|
->setFontKit(PHUIDocumentView::FONT_SOURCE_SANS)
|
2013-06-06 17:36:51 +02:00
|
|
|
->appendChild($properties)
|
2013-08-27 12:14:00 +02:00
|
|
|
->appendChild($warnings)
|
|
|
|
->appendChild($content);
|
|
|
|
|
2013-08-30 18:17:27 +02:00
|
|
|
$document->appendChild($this->buildParametersAndReturn(array($symbol)));
|
2013-08-27 12:14:00 +02:00
|
|
|
|
2013-08-28 18:57:20 +02:00
|
|
|
if ($methods) {
|
|
|
|
$tasks = $this->composeTasks($symbol);
|
|
|
|
|
|
|
|
if ($tasks) {
|
|
|
|
$methods_by_task = igroup($methods, 'task');
|
|
|
|
|
2013-09-08 18:15:22 +02:00
|
|
|
// 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,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-09-05 21:29:07 +02:00
|
|
|
$section = id(new DivinerSectionView())
|
|
|
|
->setHeader(pht('Tasks'));
|
2013-08-28 18:57:20 +02:00
|
|
|
|
|
|
|
foreach ($tasks as $spec) {
|
2013-09-05 21:29:07 +02:00
|
|
|
$section->addContent(
|
2013-09-17 18:12:37 +02:00
|
|
|
id(new PHUIHeaderView())
|
2013-09-05 21:29:07 +02:00
|
|
|
->setNoBackground(true)
|
2013-08-28 18:57:20 +02:00
|
|
|
->setHeader($spec['title']));
|
|
|
|
|
|
|
|
$task_methods = idx($methods_by_task, $spec['name'], array());
|
2013-09-08 18:12:33 +02:00
|
|
|
$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();
|
2013-08-28 18:57:20 +02:00
|
|
|
foreach ($task_methods as $task_method) {
|
|
|
|
$atom = last($task_method['atoms']);
|
2013-09-08 18:12:33 +02:00
|
|
|
|
|
|
|
$item = $this->renderFullSignature($atom, true);
|
|
|
|
|
|
|
|
if (strlen($atom->getSummary())) {
|
|
|
|
$item = array(
|
|
|
|
$item,
|
|
|
|
" \xE2\x80\x94 ",
|
2014-10-07 15:01:04 +02:00
|
|
|
$atom->getSummary(),
|
|
|
|
);
|
2013-09-08 18:12:33 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
$list_items[] = phutil_tag('li', array(), $item);
|
2013-08-28 18:57:20 +02:00
|
|
|
}
|
2013-09-08 18:12:33 +02:00
|
|
|
|
|
|
|
$box_content[] = phutil_tag(
|
|
|
|
'ul',
|
|
|
|
array(
|
|
|
|
'class' => 'diviner-list',
|
|
|
|
),
|
|
|
|
$list_items);
|
2013-08-28 18:57:20 +02:00
|
|
|
} else {
|
2013-09-05 21:29:07 +02:00
|
|
|
$no_methods = pht('No methods for this task.');
|
2013-09-08 18:12:33 +02:00
|
|
|
$box_content = phutil_tag('em', array(), $no_methods);
|
2013-08-28 18:57:20 +02:00
|
|
|
}
|
2013-09-08 18:12:33 +02:00
|
|
|
|
|
|
|
$inner_box->appendChild($box_content);
|
2013-09-05 21:29:07 +02:00
|
|
|
$section->addContent($inner_box);
|
2013-08-28 18:57:20 +02:00
|
|
|
}
|
2013-09-05 21:29:07 +02:00
|
|
|
$document->appendChild($section);
|
2013-08-28 18:57:20 +02:00
|
|
|
}
|
|
|
|
|
2013-09-05 21:29:07 +02:00
|
|
|
$section = id(new DivinerSectionView())
|
|
|
|
->setHeader(pht('Methods'));
|
|
|
|
|
2013-08-28 18:57:20 +02:00
|
|
|
foreach ($methods as $spec) {
|
2013-08-30 18:17:27 +02:00
|
|
|
$matom = last($spec['atoms']);
|
2013-09-17 18:12:37 +02:00
|
|
|
$method_header = id(new PHUIHeaderView())
|
2013-09-08 18:11:59 +02:00
|
|
|
->setNoBackground(true);
|
2013-08-28 18:57:20 +02:00
|
|
|
|
|
|
|
$inherited = $spec['inherited'];
|
|
|
|
if ($inherited) {
|
|
|
|
$method_header->addTag(
|
2014-01-14 23:09:52 +01:00
|
|
|
id(new PHUITagView())
|
|
|
|
->setType(PHUITagView::TYPE_STATE)
|
|
|
|
->setBackgroundColor(PHUITagView::COLOR_GREY)
|
2013-08-28 18:57:20 +02:00
|
|
|
->setName(pht('Inherited')));
|
|
|
|
}
|
|
|
|
|
2013-09-08 18:11:59 +02:00
|
|
|
$method_header->setHeader($this->renderFullSignature($matom));
|
2013-08-30 18:17:27 +02:00
|
|
|
|
2013-09-05 21:29:07 +02:00
|
|
|
$section->addContent(
|
2013-08-30 18:17:27 +02:00
|
|
|
array(
|
|
|
|
$method_header,
|
|
|
|
$this->renderMethodDocumentationText($symbol, $spec, $engine),
|
|
|
|
$this->buildParametersAndReturn($spec['atoms']),
|
|
|
|
));
|
2013-08-28 18:54:39 +02:00
|
|
|
}
|
2013-09-05 21:29:07 +02:00
|
|
|
$document->appendChild($section);
|
2013-08-28 18:54:39 +02:00
|
|
|
}
|
|
|
|
|
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
|
|
|
));
|
|
|
|
}
|
|
|
|
|
2013-08-28 18:54:39 +02:00
|
|
|
private function buildExtendsAndImplements(
|
2013-10-11 16:53:56 +02:00
|
|
|
PHUIPropertyListView $view,
|
2013-08-28 18:54:39 +02:00
|
|
|
DivinerLiveSymbol $symbol) {
|
|
|
|
|
|
|
|
$lineage = $this->getExtendsLineage($symbol);
|
|
|
|
if ($lineage) {
|
2013-09-08 18:13:46 +02:00
|
|
|
$tags = array();
|
|
|
|
foreach ($lineage as $item) {
|
2013-09-08 18:16:55 +02:00
|
|
|
$tags[] = $this->renderAtomTag($item);
|
2013-09-08 18:13:46 +02:00
|
|
|
}
|
|
|
|
|
2013-09-13 01:08:47 +02:00
|
|
|
$caret = phutil_tag('span', array('class' => 'caret-right msl msr'));
|
|
|
|
$tags = phutil_implode_html($caret, $tags);
|
2013-09-08 18:13:46 +02:00
|
|
|
$view->addProperty(pht('Extends'), $tags);
|
2013-08-28 18:54:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
$implements = $this->getImplementsLineage($symbol);
|
|
|
|
if ($implements) {
|
|
|
|
$items = array();
|
|
|
|
foreach ($implements as $spec) {
|
|
|
|
$via = $spec['via'];
|
|
|
|
$iface = $spec['interface'];
|
|
|
|
if ($via == $symbol) {
|
2013-09-08 18:16:55 +02:00
|
|
|
$items[] = $this->renderAtomTag($iface);
|
2013-08-28 18:54:39 +02:00
|
|
|
} else {
|
2013-09-08 18:16:55 +02:00
|
|
|
$items[] = array(
|
|
|
|
$this->renderAtomTag($iface),
|
|
|
|
" \xE2\x97\x80 ",
|
2014-10-07 15:01:04 +02:00
|
|
|
$this->renderAtomTag($via),
|
|
|
|
);
|
2013-08-28 18:54:39 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$view->addProperty(
|
|
|
|
pht('Implements'),
|
|
|
|
phutil_implode_html(phutil_tag('br'), $items));
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2013-09-08 18:16:55 +02:00
|
|
|
private function renderAtomTag(DivinerLiveSymbol $symbol) {
|
2014-01-14 23:09:52 +01:00
|
|
|
return id(new PHUITagView())
|
|
|
|
->setType(PHUITagView::TYPE_OBJECT)
|
2013-09-08 18:16:55 +02:00
|
|
|
->setName($symbol->getName())
|
|
|
|
->setHref($symbol->getURI());
|
|
|
|
}
|
|
|
|
|
2013-08-28 18:54:39 +02:00
|
|
|
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-08-28 18:54:54 +02:00
|
|
|
private function buildDefined(
|
2013-10-11 16:53:56 +02:00
|
|
|
PHUIPropertyListView $view,
|
2013-08-28 18:54:54 +02:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2013-08-28 18:57:20 +02:00
|
|
|
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,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-09-08 18:15:22 +02:00
|
|
|
$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;
|
2013-08-28 18:57:20 +02:00
|
|
|
}
|
|
|
|
|
2013-09-08 18:12:33 +02:00
|
|
|
private function renderFullSignature(
|
|
|
|
DivinerLiveSymbol $symbol,
|
|
|
|
$is_link = false) {
|
2013-08-30 18:17:27 +02:00
|
|
|
switch ($symbol->getType()) {
|
|
|
|
case DivinerAtom::TYPE_CLASS:
|
|
|
|
case DivinerAtom::TYPE_INTERFACE:
|
|
|
|
case DivinerAtom::TYPE_METHOD:
|
|
|
|
case DivinerAtom::TYPE_FUNCTION:
|
|
|
|
break;
|
|
|
|
default:
|
2014-03-06 20:28:24 +01:00
|
|
|
return $symbol->getTitle();
|
2013-08-30 18:17:27 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
$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;
|
|
|
|
}
|
|
|
|
|
2013-09-08 18:11:59 +02:00
|
|
|
$anchor = null;
|
|
|
|
switch ($symbol->getType()) {
|
|
|
|
case DivinerAtom::TYPE_METHOD:
|
|
|
|
$anchor = $symbol->getType().'/'.$symbol->getName();
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
2013-08-30 18:17:27 +02:00
|
|
|
|
2013-09-08 18:11:59 +02:00
|
|
|
$out[] = phutil_tag(
|
|
|
|
$anchor ? 'a' : 'span',
|
|
|
|
array(
|
|
|
|
'class' => 'diviner-atom-signature-name',
|
|
|
|
'href' => $anchor ? '#'.$anchor : null,
|
2013-09-08 18:12:33 +02:00
|
|
|
'name' => $is_link ? null : $anchor,
|
2013-09-08 18:11:59 +02:00
|
|
|
),
|
|
|
|
$symbol->getName());
|
|
|
|
|
|
|
|
$out = phutil_implode_html(' ', $out);
|
2013-08-30 18:17:27 +02:00
|
|
|
|
|
|
|
$parameters = $atom->getProperty('parameters');
|
|
|
|
if ($parameters !== null) {
|
|
|
|
$pout = array();
|
|
|
|
foreach ($parameters as $parameter) {
|
2013-09-08 18:16:55 +02:00
|
|
|
$pout[] = idx($parameter, 'name', '...');
|
2013-08-30 18:17:27 +02:00
|
|
|
}
|
2013-09-08 18:11:59 +02:00
|
|
|
$out = array($out, '('.implode(', ', $pout).')');
|
2013-08-30 18:17:27 +02:00
|
|
|
}
|
|
|
|
|
2013-09-08 18:11:59 +02:00
|
|
|
return phutil_tag(
|
|
|
|
'span',
|
|
|
|
array(
|
|
|
|
'class' => 'diviner-atom-signature',
|
|
|
|
),
|
|
|
|
$out);
|
2013-08-30 18:17:27 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-09-04 20:50:57 +02:00
|
|
|
if (nonempty($parameters)) {
|
2013-08-30 18:17:27 +02:00
|
|
|
$out[] = id(new DivinerParameterTableView())
|
2013-09-04 20:50:57 +02:00
|
|
|
->setHeader(pht('Parameters'))
|
2013-08-30 18:17:27 +02:00
|
|
|
->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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-09-04 20:50:57 +02:00
|
|
|
if (nonempty($return)) {
|
2013-08-30 18:17:27 +02:00
|
|
|
$out[] = id(new DivinerReturnTableView())
|
2013-09-04 20:50:57 +02:00
|
|
|
->setHeader(pht('Return'))
|
2013-08-30 18:17:27 +02:00
|
|
|
->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();
|
2013-09-08 18:13:46 +02:00
|
|
|
$content = phutil_tag(
|
|
|
|
'div',
|
|
|
|
array(
|
|
|
|
'class' => 'diviner-message-not-documented',
|
|
|
|
),
|
|
|
|
DivinerAtom::getThisAtomIsNotDocumentedString($atom->getType()));
|
2013-08-30 18:17:27 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
2013-09-08 18:15:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
$doc = $this->renderDocumentationText($symbol, $engine);
|
|
|
|
|
|
|
|
if (($impl !== $parent) || $out) {
|
|
|
|
$where = id(new PHUIBoxView())
|
|
|
|
->addPadding(PHUI::PADDING_MEDIUM_LEFT)
|
|
|
|
->addPadding(PHUI::PADDING_MEDIUM_RIGHT)
|
2013-09-05 21:29:07 +02:00
|
|
|
->addClass('diviner-method-implementation-header')
|
2013-09-08 18:15:22 +02:00
|
|
|
->appendChild($impl->getName());
|
|
|
|
$doc = array($where, $doc);
|
|
|
|
|
|
|
|
if ($impl !== $parent) {
|
|
|
|
$doc = phutil_tag(
|
|
|
|
'div',
|
|
|
|
array(
|
|
|
|
'class' => 'diviner-method-implementation-inherited',
|
|
|
|
),
|
|
|
|
$doc);
|
|
|
|
}
|
2013-08-30 18:17:27 +02:00
|
|
|
}
|
2013-09-08 18:15:22 +02:00
|
|
|
|
|
|
|
$out[] = $doc;
|
2013-08-30 18:17:27 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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;
|
|
|
|
}
|
|
|
|
|
2013-06-01 00:14:39 +02:00
|
|
|
}
|