mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-10 08:52:39 +01:00
Improve Diviner linking
Summary: Do this somewhat reasonably: - For links to the same documentation book (the common case), go look up that the thing you're linking to actualy exists. If it doesn't, render a <span> which we can make have a red background and warn about later. - For links to some other book, just generate a link and hope it hits something. We can improve and augment this later. - For non-documentation links (links in comments, e.g.) just generate a query link into the Diviner app. We'll do a query and figure out where to send the user after they click the link. We could pre-resolve these later. Test Plan: Generated documentation, saw it build mostly-correct links when objects were referenced correctly. Used preview to generate various `@{x:y|z}` things and made sure they ended up reasonable-looking. Reviewers: chad Reviewed By: chad CC: aran Maniphest Tasks: T988 Differential Revision: https://secure.phabricator.com/D5001
This commit is contained in:
parent
16accb591c
commit
cd41b834f7
7 changed files with 276 additions and 24 deletions
|
@ -285,8 +285,10 @@ final class DivinerAtom {
|
||||||
|
|
||||||
public function getRef() {
|
public function getRef() {
|
||||||
$group = null;
|
$group = null;
|
||||||
|
$title = null;
|
||||||
if ($this->docblockMeta) {
|
if ($this->docblockMeta) {
|
||||||
$group = $this->getDocblockMetaValue('group');
|
$group = $this->getDocblockMetaValue('group');
|
||||||
|
$title = $this->getDocblockMetaValue('title');
|
||||||
}
|
}
|
||||||
|
|
||||||
return id(new DivinerAtomRef())
|
return id(new DivinerAtomRef())
|
||||||
|
@ -294,6 +296,7 @@ final class DivinerAtom {
|
||||||
->setContext($this->getContext())
|
->setContext($this->getContext())
|
||||||
->setType($this->getType())
|
->setType($this->getType())
|
||||||
->setName($this->getName())
|
->setName($this->getName())
|
||||||
|
->setTitle($title)
|
||||||
->setGroup($group);
|
->setGroup($group);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ final class DivinerAtomRef {
|
||||||
private $group;
|
private $group;
|
||||||
private $summary;
|
private $summary;
|
||||||
private $index;
|
private $index;
|
||||||
|
private $title;
|
||||||
|
|
||||||
public function getSortKey() {
|
public function getSortKey() {
|
||||||
return implode(
|
return implode(
|
||||||
|
@ -47,7 +48,7 @@ final class DivinerAtomRef {
|
||||||
"Atom names must not be in the form '/@\d+/'. This pattern is ".
|
"Atom names must not be in the form '/@\d+/'. This pattern is ".
|
||||||
"reserved for disambiguating atoms with similar names.");
|
"reserved for disambiguating atoms with similar names.");
|
||||||
}
|
}
|
||||||
$this->name = $name;
|
$this->name = $normal_name;
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,7 +79,11 @@ final class DivinerAtomRef {
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setBook($book) {
|
public function setBook($book) {
|
||||||
$this->book = self::normalizeString($book);
|
if ($book === null) {
|
||||||
|
$this->book = $book;
|
||||||
|
} else {
|
||||||
|
$this->book = self::normalizeString($book);
|
||||||
|
}
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,6 +100,15 @@ final class DivinerAtomRef {
|
||||||
return $this->group;
|
return $this->group;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function setTitle($title) {
|
||||||
|
$this->title = $title;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTitle() {
|
||||||
|
return $this->title;
|
||||||
|
}
|
||||||
|
|
||||||
public function toDictionary() {
|
public function toDictionary() {
|
||||||
return array(
|
return array(
|
||||||
'book' => $this->getBook(),
|
'book' => $this->getBook(),
|
||||||
|
@ -104,6 +118,7 @@ final class DivinerAtomRef {
|
||||||
'group' => $this->getGroup(),
|
'group' => $this->getGroup(),
|
||||||
'index' => $this->getIndex(),
|
'index' => $this->getIndex(),
|
||||||
'summary' => $this->getSummary(),
|
'summary' => $this->getSummary(),
|
||||||
|
'title' => $this->getTitle(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -113,6 +128,7 @@ final class DivinerAtomRef {
|
||||||
unset($dict['group']);
|
unset($dict['group']);
|
||||||
unset($dict['index']);
|
unset($dict['index']);
|
||||||
unset($dict['summary']);
|
unset($dict['summary']);
|
||||||
|
unset($dict['title']);
|
||||||
|
|
||||||
ksort($dict);
|
ksort($dict);
|
||||||
return md5(serialize($dict)).'S';
|
return md5(serialize($dict)).'S';
|
||||||
|
@ -120,13 +136,14 @@ final class DivinerAtomRef {
|
||||||
|
|
||||||
public static function newFromDictionary(array $dict) {
|
public static function newFromDictionary(array $dict) {
|
||||||
$obj = new DivinerAtomRef();
|
$obj = new DivinerAtomRef();
|
||||||
$obj->book = idx($dict, 'book');
|
$obj->setBook(idx($dict, 'book'));
|
||||||
$obj->context = idx($dict, 'context');
|
$obj->setContext(idx($dict, 'context'));
|
||||||
$obj->type = idx($dict, 'type');
|
$obj->setType(idx($dict, 'type'));
|
||||||
$obj->name = idx($dict, 'name');
|
$obj->setName(idx($dict, 'name'));
|
||||||
$obj->group = idx($dict, 'group');
|
$obj->group = idx($dict, 'group');
|
||||||
$obj->index = idx($dict, 'index');
|
$obj->index = idx($dict, 'index');
|
||||||
$obj->summary = idx($dict, 'summary');
|
$obj->summary = idx($dict, 'summary');
|
||||||
|
$obj->title = idx($dict, 'title');
|
||||||
return $obj;
|
return $obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -163,8 +180,8 @@ final class DivinerAtomRef {
|
||||||
// Replace all spaces with underscores.
|
// Replace all spaces with underscores.
|
||||||
$str = preg_replace('/ +/', '_', $str);
|
$str = preg_replace('/ +/', '_', $str);
|
||||||
|
|
||||||
// Replace control characters with "@".
|
// Replace control characters with "X".
|
||||||
$str = preg_replace('/[\x00-\x19]/', '@', $str);
|
$str = preg_replace('/[\x00-\x19]/', 'X', $str);
|
||||||
|
|
||||||
// Replace specific problematic names with alternative names.
|
// Replace specific problematic names with alternative names.
|
||||||
$alternates = array(
|
$alternates = array(
|
||||||
|
|
|
@ -2,35 +2,130 @@
|
||||||
|
|
||||||
final class DivinerRemarkupRuleSymbol extends PhutilRemarkupRule {
|
final class DivinerRemarkupRuleSymbol extends PhutilRemarkupRule {
|
||||||
|
|
||||||
|
const KEY_RULE_ATOM_REF = 'rule.diviner.atomref';
|
||||||
|
|
||||||
public function apply($text) {
|
public function apply($text) {
|
||||||
|
// Grammar here is:
|
||||||
|
//
|
||||||
|
// rule = '@{' maybe_type name maybe_title '}'
|
||||||
|
// maybe_type = null | type ':' | type '@' book ':'
|
||||||
|
// name = name | name '@' context
|
||||||
|
// maybe_title = null | '|' title
|
||||||
|
//
|
||||||
|
// So these are all valid:
|
||||||
|
//
|
||||||
|
// @{name}
|
||||||
|
// @{type : name}
|
||||||
|
// @{name | title}
|
||||||
|
// @{type @ book : name @ context | title}
|
||||||
|
|
||||||
return $this->replaceHTML(
|
return $this->replaceHTML(
|
||||||
'/(?:^|\B)@{(?:(?P<type>[^:]+?):)?(?P<name>[^}]+?)}/',
|
'/(?:^|\B)@{'.
|
||||||
|
'(?:(?P<type>[^:]+?):)?'.
|
||||||
|
'(?P<name>[^}|]+?)'.
|
||||||
|
'(?:[|](?P<title>[^}]+))?'.
|
||||||
|
'}/',
|
||||||
array($this, 'markupSymbol'),
|
array($this, 'markupSymbol'),
|
||||||
$text);
|
$text);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function markupSymbol($matches) {
|
public function markupSymbol($matches) {
|
||||||
$type = $matches['type'];
|
$type = (string)idx($matches, 'type');
|
||||||
$name = $matches['name'];
|
$name = (string)$matches['name'];
|
||||||
|
$title = (string)idx($matches, 'title');
|
||||||
|
|
||||||
// Collapse sequences of whitespace into a single space.
|
// Collapse sequences of whitespace into a single space.
|
||||||
$name = preg_replace('/\s+/', ' ', $name);
|
$type = preg_replace('/\s+/', ' ', trim($type));
|
||||||
|
$name = preg_replace('/\s+/', ' ', trim($name));
|
||||||
|
$title = preg_replace('/\s+/', ' ', trim($title));
|
||||||
|
|
||||||
|
$ref = array();
|
||||||
|
|
||||||
$book = null;
|
|
||||||
if (strpos($type, '@') !== false) {
|
if (strpos($type, '@') !== false) {
|
||||||
list($type, $book) = explode('@', $type, 2);
|
list($type, $book) = explode('@', $type, 2);
|
||||||
|
$ref['type'] = trim($type);
|
||||||
|
$ref['book'] = trim($book);
|
||||||
|
} else {
|
||||||
|
$ref['type'] = $type;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: This doesn't actually do anything useful yet.
|
if (strpos($name, '@') !== false) {
|
||||||
|
list($name, $context) = explode('@', $name, 2);
|
||||||
|
$ref['name'] = trim($name);
|
||||||
|
$ref['context'] = trim($context);
|
||||||
|
} else {
|
||||||
|
$ref['name'] = $name;
|
||||||
|
}
|
||||||
|
|
||||||
$link = phutil_tag(
|
$ref['title'] = $title;
|
||||||
'a',
|
|
||||||
array(
|
|
||||||
'href' => '#',
|
|
||||||
),
|
|
||||||
$name);
|
|
||||||
|
|
||||||
return $this->getEngine()->storeText($link);
|
foreach ($ref as $key => $value) {
|
||||||
|
if ($value === '') {
|
||||||
|
unset($ref[$key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$engine = $this->getEngine();
|
||||||
|
$token = $engine->storeText('');
|
||||||
|
|
||||||
|
$key = self::KEY_RULE_ATOM_REF;
|
||||||
|
$data = $engine->getTextMetadata($key, array());
|
||||||
|
$data[$token] = $ref;
|
||||||
|
$engine->setTextMetadata($key, $data);
|
||||||
|
|
||||||
|
return $token;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function didMarkupText() {
|
||||||
|
$engine = $this->getEngine();
|
||||||
|
|
||||||
|
$key = self::KEY_RULE_ATOM_REF;
|
||||||
|
$data = $engine->getTextMetadata($key, array());
|
||||||
|
|
||||||
|
$renderer = $engine->getConfig('diviner.renderer');
|
||||||
|
|
||||||
|
foreach ($data as $token => $ref_dict) {
|
||||||
|
$ref = DivinerAtomRef::newFromDictionary($ref_dict);
|
||||||
|
$title = nonempty($ref->getTitle(), $ref->getName());
|
||||||
|
|
||||||
|
$href = null;
|
||||||
|
if ($renderer) {
|
||||||
|
// Here, we're generating documentation. If possible, we want to find
|
||||||
|
// the real atom ref so we can render the correct default title and
|
||||||
|
// render invalid links in an alternate style.
|
||||||
|
|
||||||
|
$ref = $renderer->normalizeAtomRef($ref);
|
||||||
|
if ($ref) {
|
||||||
|
$title = nonempty($ref->getTitle(), $ref->getName());
|
||||||
|
$href = $renderer->getHrefForAtomRef($ref);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Here, we're generating commment text or something like that. Just
|
||||||
|
// link to Diviner and let it sort things out.
|
||||||
|
|
||||||
|
$href = id(new PhutilURI('/diviner/find/'))
|
||||||
|
->setQueryParams($ref_dict + array('jump' => true));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($href) {
|
||||||
|
$link = phutil_tag(
|
||||||
|
'a',
|
||||||
|
array(
|
||||||
|
'class' => 'atom-ref',
|
||||||
|
'href' => $href,
|
||||||
|
),
|
||||||
|
$title);
|
||||||
|
} else {
|
||||||
|
$link = phutil_tag(
|
||||||
|
'span',
|
||||||
|
array(
|
||||||
|
'class' => 'atom-ref-invalid',
|
||||||
|
),
|
||||||
|
$title);
|
||||||
|
}
|
||||||
|
|
||||||
|
$engine->overwriteStoredText($token, $link);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ abstract class DivinerPublisher {
|
||||||
private $symbolReverseMap;
|
private $symbolReverseMap;
|
||||||
|
|
||||||
public function setRenderer(DivinerRenderer $renderer) {
|
public function setRenderer(DivinerRenderer $renderer) {
|
||||||
|
$renderer->setPublisher($this);
|
||||||
$this->renderer = $renderer;
|
$this->renderer = $renderer;
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
@ -103,6 +104,7 @@ abstract class DivinerPublisher {
|
||||||
abstract protected function loadAllPublishedHashes();
|
abstract protected function loadAllPublishedHashes();
|
||||||
abstract protected function deleteDocumentsByHash(array $hashes);
|
abstract protected function deleteDocumentsByHash(array $hashes);
|
||||||
abstract protected function createDocumentsByHash(array $hashes);
|
abstract protected function createDocumentsByHash(array $hashes);
|
||||||
|
abstract public function findAtomByRef(DivinerAtomRef $ref);
|
||||||
|
|
||||||
final public function publishAtoms(array $hashes) {
|
final public function publishAtoms(array $hashes) {
|
||||||
$existing = $this->loadAllPublishedHashes();
|
$existing = $this->loadAllPublishedHashes();
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
final class DivinerStaticPublisher extends DivinerPublisher {
|
final class DivinerStaticPublisher extends DivinerPublisher {
|
||||||
|
|
||||||
private $publishCache;
|
private $publishCache;
|
||||||
|
private $atomNameMap;
|
||||||
|
|
||||||
private function getPublishCache() {
|
private function getPublishCache() {
|
||||||
if (!$this->publishCache) {
|
if (!$this->publishCache) {
|
||||||
|
@ -115,6 +116,48 @@ final class DivinerStaticPublisher extends DivinerPublisher {
|
||||||
Filesystem::writeFile($path, $content);
|
Filesystem::writeFile($path, $content);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function findAtomByRef(DivinerAtomRef $ref) {
|
||||||
|
if ($ref->getBook() != $this->getConfig('name')) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->atomNameMap === null) {
|
||||||
|
$name_map = array();
|
||||||
|
foreach ($this->getPublishCache()->getIndex() as $hash => $dict) {
|
||||||
|
$name_map[$dict['name']][$hash] = $dict;
|
||||||
|
}
|
||||||
|
$this->atomNameMap = $name_map;
|
||||||
|
}
|
||||||
|
|
||||||
|
$name = $ref->getName();
|
||||||
|
if (empty($this->atomNameMap[$name])) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$candidates = $this->atomNameMap[$name];
|
||||||
|
foreach ($candidates as $key => $dict) {
|
||||||
|
$candidates[$key] = DivinerAtomRef::newFromDict($dict);
|
||||||
|
if ($ref->getType()) {
|
||||||
|
if ($candidates[$key]->getType() != $ref->getType()) {
|
||||||
|
unset($candidates[$key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($ref->getContext()) {
|
||||||
|
if ($candidates[$key]->getContext() != $ref->getContext()) {
|
||||||
|
unset($candidates[$key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have exactly one uniquely identifiable atom, return it.
|
||||||
|
if (count($candidates) == 1) {
|
||||||
|
return $this->getAtomFromNodeHash(last_key($candidates));
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
private function addAtomToIndex($hash, DivinerAtom $atom) {
|
private function addAtomToIndex($hash, DivinerAtom $atom) {
|
||||||
$ref = $atom->getRef();
|
$ref = $atom->getRef();
|
||||||
$ref->setIndex($this->getAtomSimilarIndex($atom));
|
$ref->setIndex($this->getAtomSimilarIndex($atom));
|
||||||
|
|
|
@ -91,12 +91,17 @@ final class DivinerDefaultRenderer extends DivinerRenderer {
|
||||||
protected function renderAtomDescription(DivinerAtom $atom) {
|
protected function renderAtomDescription(DivinerAtom $atom) {
|
||||||
$text = $this->getAtomDescription($atom);
|
$text = $this->getAtomDescription($atom);
|
||||||
$engine = $this->getBlockMarkupEngine();
|
$engine = $this->getBlockMarkupEngine();
|
||||||
|
|
||||||
|
$this->pushAtomStack($atom);
|
||||||
|
$description = $engine->markupText($text);
|
||||||
|
$this->popAtomStack($atom);
|
||||||
|
|
||||||
return phutil_tag(
|
return phutil_tag(
|
||||||
'div',
|
'div',
|
||||||
array(
|
array(
|
||||||
'class' => 'atom-description',
|
'class' => 'atom-description',
|
||||||
),
|
),
|
||||||
$engine->markupText($text));
|
$description);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getAtomDescription(DivinerAtom $atom) {
|
protected function getAtomDescription(DivinerAtom $atom) {
|
||||||
|
@ -106,12 +111,17 @@ final class DivinerDefaultRenderer extends DivinerRenderer {
|
||||||
public function renderAtomSummary(DivinerAtom $atom) {
|
public function renderAtomSummary(DivinerAtom $atom) {
|
||||||
$text = $this->getAtomSummary($atom);
|
$text = $this->getAtomSummary($atom);
|
||||||
$engine = $this->getInlineMarkupEngine();
|
$engine = $this->getInlineMarkupEngine();
|
||||||
|
|
||||||
|
$this->pushAtomStack($atom);
|
||||||
|
$summary = $engine->markupText($text);
|
||||||
|
$this->popAtomStack();
|
||||||
|
|
||||||
return phutil_tag(
|
return phutil_tag(
|
||||||
'span',
|
'span',
|
||||||
array(
|
array(
|
||||||
'class' => 'atom-summary',
|
'class' => 'atom-summary',
|
||||||
),
|
),
|
||||||
$engine->markupText($text));
|
$summary);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getAtomSummary(DivinerAtom $atom) {
|
protected function getAtomSummary(DivinerAtom $atom) {
|
||||||
|
@ -172,15 +182,66 @@ final class DivinerDefaultRenderer extends DivinerRenderer {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getBlockMarkupEngine() {
|
protected function getBlockMarkupEngine() {
|
||||||
return PhabricatorMarkupEngine::newMarkupEngine(
|
$engine = PhabricatorMarkupEngine::newMarkupEngine(
|
||||||
array(
|
array(
|
||||||
'preserve-linebreaks' => false,
|
'preserve-linebreaks' => false,
|
||||||
));
|
));
|
||||||
|
$engine->setConfig('diviner.renderer', $this);
|
||||||
|
return $engine;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getInlineMarkupEngine() {
|
protected function getInlineMarkupEngine() {
|
||||||
return $this->getBlockMarkupEngine();
|
return $this->getBlockMarkupEngine();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function normalizeAtomRef(DivinerAtomRef $ref) {
|
||||||
|
if (!strlen($ref->getBook())) {
|
||||||
|
$ref->setBook($this->getConfig('name'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($ref->getBook() != $this->getConfig('name')) {
|
||||||
|
// If the ref is from a different book, we can't normalize it. Just return
|
||||||
|
// it as-is if it has enough information to resolve.
|
||||||
|
if ($ref->getName() && $ref->getType()) {
|
||||||
|
return $ref;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$atom = $this->getPublisher()->findAtomByRef($ref);
|
||||||
|
if ($atom) {
|
||||||
|
return $atom->getRef();
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getAtomHrefDepth(DivinerAtom $atom) {
|
||||||
|
if ($atom->getContext()) {
|
||||||
|
return 4;
|
||||||
|
} else {
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getHrefForAtomRef(DivinerAtomRef $ref) {
|
||||||
|
$atom = $this->peekAtomStack();
|
||||||
|
$depth = $this->getAtomHrefDepth($atom);
|
||||||
|
$href = str_repeat('../', $depth);
|
||||||
|
|
||||||
|
$book = $ref->getBook();
|
||||||
|
$type = $ref->getType();
|
||||||
|
$name = $ref->getName();
|
||||||
|
$context = $ref->getContext();
|
||||||
|
|
||||||
|
$href .= $book.'/'.$type.'/';
|
||||||
|
if ($context !== null) {
|
||||||
|
$href .= $context.'/';
|
||||||
|
}
|
||||||
|
$href .= $name.'/';
|
||||||
|
|
||||||
|
return $href;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,8 +2,39 @@
|
||||||
|
|
||||||
abstract class DivinerRenderer {
|
abstract class DivinerRenderer {
|
||||||
|
|
||||||
|
private $publisher;
|
||||||
|
private $atomStack;
|
||||||
|
|
||||||
|
public function setPublisher($publisher) {
|
||||||
|
$this->publisher = $publisher;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPublisher() {
|
||||||
|
return $this->publisher;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getConfig($key, $default = null) {
|
||||||
|
return $this->getPublisher()->getConfig($key, $default);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function pushAtomStack(DivinerAtom $atom) {
|
||||||
|
$this->atomStack[] = $atom;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function peekAtomStack() {
|
||||||
|
return end($this->atomStack);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function popAtomStack() {
|
||||||
|
array_pop($this->atomStack);
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
abstract public function renderAtom(DivinerAtom $atom);
|
abstract public function renderAtom(DivinerAtom $atom);
|
||||||
abstract public function renderAtomSummary(DivinerAtom $atom);
|
abstract public function renderAtomSummary(DivinerAtom $atom);
|
||||||
abstract public function renderAtomIndex(array $refs);
|
abstract public function renderAtomIndex(array $refs);
|
||||||
|
abstract public function getHrefForAtomRef(DivinerAtomRef $ref);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue