Page MenuHomestyx hydra

No OneTemporary

diff --git a/src/applications/diviner/controller/DivinerAtomController.php b/src/applications/diviner/controller/DivinerAtomController.php
index 028800f28d..54d5f4c5dd 100644
--- a/src/applications/diviner/controller/DivinerAtomController.php
+++ b/src/applications/diviner/controller/DivinerAtomController.php
@@ -1,284 +1,308 @@
<?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)
->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($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());
-
-
+ $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) {
$document->appendChild(
id(new PhabricatorHeaderView())
->setHeader(pht('Methods')));
foreach ($children as $child) {
$document->appendChild(
id(new PhabricatorHeaderView())
->setHeader($child->getName()));
}
}
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 renderAtomTypeName($name) {
return phutil_utf8_ucwords($name);
}
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);
+ }
+
}
diff --git a/src/docs/book/phabricator.book b/src/docs/book/phabricator.book
index 6974b01453..ecac4a6ba0 100644
--- a/src/docs/book/phabricator.book
+++ b/src/docs/book/phabricator.book
@@ -1,17 +1,19 @@
{
"name" : "phabdev",
"title" : "Phabricator Technical Documentation",
"short" : "Phabricator Tech Docs",
"root" : "../../../",
+ "uri.source" :
+ "https://secure.phabricator.com/diffusion/P/browse/master/%f$%l",
"rules" : {
"(\\.php$)" : "DivinerPHPAtomizer"
},
"exclude" : [
"(^externals/)",
"(^scripts/)",
"(^support/)",
"(^resources/)"
],
"groups" : {
}
}
diff --git a/src/docs/book/user.book b/src/docs/book/user.book
index f8f69df87c..35fb7fed15 100644
--- a/src/docs/book/user.book
+++ b/src/docs/book/user.book
@@ -1,43 +1,45 @@
{
"name" : "phabricator",
"title" : "Phabricator User Documentation",
"short" : "Phabricator User Docs",
"root" : "../../../",
+ "uri.source" :
+ "https://secure.phabricator.com/diffusion/P/browse/master/%f$%l",
"rules" : {
"(\\.diviner$)" : "DivinerArticleAtomizer"
},
"exclude" : [
"(^externals/)",
"(^scripts/)",
"(^support/)"
],
"groups" : {
"intro" : {
"name" : "Introduction"
},
"config" : {
"name" : "Configuration"
},
"userguide" : {
"name" : "Application User Guides"
},
"differential" : {
"name" : "Differential (Code Review)"
},
"diffusion" : {
"name" : "Diffusion (Repository Browser)"
},
"maniphest" : {
"name" : "Maniphest (Task Tracking)"
},
"slowvote" : {
"name" : "Slowvote (Polls)"
},
"herald" : {
"name" : "Herald (Notifications)"
},
"phriction" : {
"name" : "Phriction (Wiki)"
}
}
}
diff --git a/src/docs/userguide/diviner.diviner b/src/docs/userguide/diviner.diviner
new file mode 100644
index 0000000000..e392a606d5
--- /dev/null
+++ b/src/docs/userguide/diviner.diviner
@@ -0,0 +1,84 @@
+@title Diviner User Guide
+@group userguide
+
+Using Diviner, a documentation generator.
+
+= Overview =
+
+NOTE: Diviner is new and not yet generally useful.
+
+= Generating Documentation =
+
+To generate documentation, run:
+
+ phabricator/ $ ./bin/diviner generate --book <book>
+
+= .book Files =
+
+Diviner documentation books are configured using JSON `.book` files, which
+look like this:
+
+ name=example.book
+ {
+ "name" : "example",
+ "title" : "Example Documentation",
+ "short" : "Example Docs",
+ "root" : ".",
+ "uri.source" : "http://example.com/diffusion/X/browse/master/%f$%l",
+ "rules" : {
+ "(\\.diviner$)" : "DivinerArticleAtomizer"
+ },
+ "exclude" : [
+ "(^externals/)",
+ "(^scripts/)",
+ "(^support/)"
+ ],
+ "groups" : {
+ "forward" : {
+ "name" : "Doing Stuff"
+ },
+ "reverse" : {
+ "name" : "Undoing Stuff"
+ }
+ }
+ }
+
+The properties in this file are:
+
+ - `name`: Required. Short, unique name to identify the documentation book. This
+ will be used in URIs, so it should not have special characters. Good names
+ are things like `"example"` or `"libcabin"`.
+ - `root`: Required. The root directory (relative to the `.book` file) which
+ documentation should be generated from. Often this will be a value like
+ `"../../"`, to specify the project root (for example, if the `.book` file
+ is in `project/src/docs/example.book`, the value `"../../"` would generate
+ documentation from the `project/` directory.
+ - `title`: Optional. Full human-readable title of the documentation book. This
+ is used when there's plenty of display space and should completely describe
+ the book. Good titles are things like `"Example Documentation"`, or
+ `"libcabin Developer Documentation"`.
+ - `short`: Optional. Shorter version of the title for use when display space
+ is limited (for example, in navigation breadcrumbs). If omitted, the full
+ title is used. Good short titles are things like `"Example Docs"` or
+ `"libcabin Dev Docs"`.
+ - `uri.source`: Optional. Diviner can link from the documentation to a
+ repository browser so that you can quickly jump to the definition of a class
+ or function. To do this, it uses a URI pattern which you specify here.
+ Normally, this URI should point at a repository browser like Diffusion.
+ For example, `"http://repobrowser.yourcompany.com/%f#%l"`. You can use these
+ conversions in the URI, which will be replaced at runtime:
+ - `%f`: Replaced with the name of the file.
+ - `%l`: Replaced with the line number.
+ - `%%`: Replaced with a literal `%` symbol.
+ - `rules`: Optional. A map of regular expressions to Atomizer classes which
+ controls which documentation generator runs on each file. If omitted,
+ Diviner will use its default ruleset. For example, adding the key
+ `"(\\.diviner$)"` to the map with value `"DivinerArticleAtomizer"` tells
+ Diviner to analyze any file with a name ending in `.diviner` using the
+ "article" atomizer.
+ - `exclude`: Optional. A list of regular expressions matching paths which
+ will be excluded from documentation generation for this book. For example,
+ adding a pattern like `"(^externals/)"` or `"(^vendor/)"` will make Diviner
+ ignore those directories.
+ - `groups`: Optional. Describes top level organizational groups which atoms
+ should be placed into.

File Metadata

Mime Type
text/x-diff
Expires
Mon, Mar 16, 11:37 PM (1 d, 8 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
963624
Default Alt Text
(14 KB)

Event Timeline