doc/DocParser: Replace `getDoc()' and `getToc()' with `getDocTree()'
refs #4820
This commit is contained in:
parent
e26d360561
commit
134db3fc66
|
@ -4,12 +4,8 @@
|
|||
|
||||
namespace Icinga\Module\Doc;
|
||||
|
||||
require_once 'IcingaVendor/Parsedown/Parsedown.php';
|
||||
|
||||
use Parsedown;
|
||||
use Icinga\Data\Tree\Node;
|
||||
use SplDoublyLinkedList;
|
||||
use Icinga\Exception\NotReadableError;
|
||||
use Icinga\Module\Doc\Exception\ChapterNotFoundException;
|
||||
use Icinga\Module\Doc\Exception\DocEmptyException;
|
||||
use Icinga\Module\Doc\Exception\DocException;
|
||||
|
||||
|
@ -44,152 +40,26 @@ class DocParser
|
|||
public function __construct($path)
|
||||
{
|
||||
if (! is_dir($path)) {
|
||||
throw new DocException('Doc directory `' . $path . '\' does not exist');
|
||||
throw new DocException(
|
||||
mt('doc', 'Documentation directory') . ' \'' . $path . '\' ' . mt('doc', 'does not exist')
|
||||
);
|
||||
}
|
||||
if (! is_readable($path)) {
|
||||
throw new NotReadableError('Doc directory `' . $path . '\' is not readable');
|
||||
throw new DocException(
|
||||
mt('doc', 'Documentation directory') . ' \'' . $path . '\' ' . mt('doc', 'is not readable')
|
||||
);
|
||||
}
|
||||
$docIterator = new DocIterator($path);
|
||||
if ($docIterator->count() === 0) {
|
||||
throw new DocEmptyException('Doc directory `' . $path . '\' is empty');
|
||||
throw new DocEmptyException(
|
||||
mt('doc', 'Documentation directory') . ' \'' . $path . '\' '
|
||||
. mt('doc', 'does not contain any non-empty Markdown file (\'.md\' suffix')
|
||||
);
|
||||
}
|
||||
$this->path = $path;
|
||||
$this->docIterator = $docIterator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the table of contents
|
||||
*
|
||||
* @return Node
|
||||
*/
|
||||
public function getToc()
|
||||
{
|
||||
$tocStack = array((object) array(
|
||||
'level' => 0,
|
||||
'node' => new Node()
|
||||
));
|
||||
foreach ($this->docIterator as $fileObject) {
|
||||
$line = null;
|
||||
$currentChapterName = null;
|
||||
while (! $fileObject->eof()) {
|
||||
// Save last line for setext-style headers
|
||||
$lastLine = $line;
|
||||
$line = $fileObject->fgets();
|
||||
$header = $this->extractHeader($line, $lastLine);
|
||||
if ($header !== null) {
|
||||
list($header, $level) = $header;
|
||||
$id = $this->extractHeaderId($header);
|
||||
$nofollow = false;
|
||||
$this->reduceToc($tocStack, $level);
|
||||
if ($id === null) {
|
||||
$path = array();
|
||||
foreach (array_slice($tocStack, 1) as $entity) {
|
||||
$path[] = $entity->node->getValue()->title;
|
||||
}
|
||||
$path[] = $header;
|
||||
$id = implode('-', $path);
|
||||
$nofollow = true;
|
||||
}
|
||||
$id = urlencode(str_replace('.', '.', strip_tags($id)));
|
||||
if ($currentChapterName === null) {
|
||||
// The first header is the chapter's name
|
||||
$currentChapterName = $id;
|
||||
$id = null;
|
||||
}
|
||||
$node = end($tocStack)->node->appendChild(
|
||||
(object) array(
|
||||
'id' => $id,
|
||||
'title' => $header,
|
||||
'nofollow' => $nofollow,
|
||||
'chapterName' => $currentChapterName
|
||||
)
|
||||
);
|
||||
$tocStack[] = (object) array(
|
||||
'level' => $level,
|
||||
'node' => $node
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $tocStack[0]->node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a chapter
|
||||
*
|
||||
* @param string $chapterName
|
||||
*
|
||||
* @return string
|
||||
* @throws ChapterNotFoundException If the chapter was not found
|
||||
*/
|
||||
public function getChapter($chapterName)
|
||||
{
|
||||
$tocStack = array((object) array(
|
||||
'level' => 0,
|
||||
'node' => new Node()
|
||||
));
|
||||
foreach ($this->docIterator as $fileObject) {
|
||||
$line = null;
|
||||
$currentChapterName = null;
|
||||
$chapter = array();
|
||||
while (! $fileObject->eof()) {
|
||||
// Save last line for setext-style headers
|
||||
$lastLine = $line;
|
||||
$line = $fileObject->fgets();
|
||||
$header = $this->extractHeader($line, $lastLine);
|
||||
if ($header !== null) {
|
||||
list($header, $level) = $header;
|
||||
$id = $this->extractHeaderId($header);
|
||||
$this->reduceToc($tocStack, $level);
|
||||
if ($id === null) {
|
||||
$path = array();
|
||||
foreach (array_slice($tocStack, 1) as $entity) {
|
||||
$path[] = $entity->node->getValue()->title;
|
||||
}
|
||||
$path[] = $header;
|
||||
$id = implode('-', $path);
|
||||
}
|
||||
$id = urlencode(str_replace('.', '.', strip_tags($id)));
|
||||
if ($currentChapterName === null) {
|
||||
$currentChapterName = $id;
|
||||
$id = null;
|
||||
}
|
||||
$node = end($tocStack)->node->appendChild(
|
||||
(object) array(
|
||||
'title' => $header
|
||||
)
|
||||
);
|
||||
$tocStack[] = (object) array(
|
||||
'level' => $level,
|
||||
'node' => $node
|
||||
);
|
||||
$line = '<a name="' . $id . '"></a>' . PHP_EOL . $line;
|
||||
}
|
||||
$chapter[] = $line;
|
||||
}
|
||||
if ($currentChapterName === $chapterName) {
|
||||
return preg_replace_callback(
|
||||
'#<pre><code class="language-php">(.*?)\</code></pre>#s',
|
||||
array($this, 'highlight'),
|
||||
Parsedown::instance()->text(implode('', $chapter))
|
||||
);
|
||||
}
|
||||
}
|
||||
throw new ChapterNotFoundException('Chapter \'' . $chapterName . '\' not found');
|
||||
}
|
||||
|
||||
/**
|
||||
* Syntax highlighting for PHP code
|
||||
*
|
||||
* @param $match
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function highlight($match)
|
||||
{
|
||||
return highlight_string(htmlspecialchars_decode($match[1]), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract atx- or setext-style headers from the given lines
|
||||
*
|
||||
|
@ -208,7 +78,7 @@ class DocParser
|
|||
&& $line[0] === '#'
|
||||
&& preg_match('/^#+/', $line, $match) === 1
|
||||
) {
|
||||
// Atx-style
|
||||
// Atx
|
||||
$level = strlen($match[0]);
|
||||
$header = trim(substr($line, $level));
|
||||
if (! $header) {
|
||||
|
@ -233,36 +103,69 @@ class DocParser
|
|||
if ($header === null) {
|
||||
return null;
|
||||
}
|
||||
return array($header, $level);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract header id in an a or a span tag
|
||||
*
|
||||
* @param string &$header
|
||||
*
|
||||
* @return id|null
|
||||
*/
|
||||
protected function extractHeaderId(&$header)
|
||||
{
|
||||
if ($header[0] === '<'
|
||||
&& preg_match('#(?:<(?P<tag>a|span) id="(?P<id>.+)"></(?P=tag)>)#u', $header, $match)
|
||||
&& preg_match('#(?:<(?P<tag>a|span) (?:id|name)="(?P<id>.+)"></(?P=tag)>)\s*#u', $header, $match)
|
||||
) {
|
||||
$header = str_replace($match[0], '', $header);
|
||||
return $match['id'];
|
||||
$id = $match['id'];
|
||||
} else {
|
||||
$id = null;
|
||||
}
|
||||
return null;
|
||||
return array($header, $id, $level);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reduce the toc stack to the given level
|
||||
* Get the documentation tree
|
||||
*
|
||||
* @param array &$tocStack
|
||||
* @param int $level
|
||||
* @return DocTree
|
||||
*/
|
||||
protected function reduceToc(array &$tocStack, $level) {
|
||||
while (end($tocStack)->level >= $level) {
|
||||
array_pop($tocStack);
|
||||
public function getDocTree()
|
||||
{
|
||||
$tree = new DocTree();
|
||||
$stack = new SplDoublyLinkedList();
|
||||
foreach ($this->docIterator as $fileInfo) {
|
||||
/* @var $file \SplFileInfo */
|
||||
$file = $fileInfo->openFile();
|
||||
/* @var $file \SplFileObject */
|
||||
$lastLine = null;
|
||||
foreach ($file as $line) {
|
||||
$header = $this->extractHeader($line, $lastLine);
|
||||
if ($header !== null) {
|
||||
list($header, $id, $level) = $header; // When overwriting the variable to extract, it has to be
|
||||
// list()'s first parameter since list() assigns the values
|
||||
// starting with the right-most parameter
|
||||
while (! $stack->isEmpty() && $stack->top()->getLevel() >= $level) {
|
||||
$stack->pop();
|
||||
}
|
||||
if ($id === null) {
|
||||
$path = array();
|
||||
foreach ($stack as $section) {
|
||||
/* @var $section Section */
|
||||
$path[] = $section->getTitle();
|
||||
}
|
||||
$path[] = $header;
|
||||
$id = implode('-', $path);
|
||||
$nofollow = true;
|
||||
} else {
|
||||
$nofollow = false;
|
||||
}
|
||||
if ($stack->isEmpty()) {
|
||||
$chapterName = $header;
|
||||
$section = new Section($id, $header, $level, $nofollow, $chapterName);
|
||||
$tree->addRoot($section);
|
||||
} else {
|
||||
$chapterName = $stack->bottom()->getTitle();
|
||||
$section = new Section($id, $header, $level, $nofollow, $chapterName);
|
||||
$tree->addChild($section, $stack->top());
|
||||
}
|
||||
$stack->push($section);
|
||||
} else {
|
||||
$stack->top()->appendContent($line);
|
||||
}
|
||||
// Save last line for setext-style headers
|
||||
$lastLine = $line;
|
||||
}
|
||||
}
|
||||
return $tree;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue