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;
|
namespace Icinga\Module\Doc;
|
||||||
|
|
||||||
require_once 'IcingaVendor/Parsedown/Parsedown.php';
|
use SplDoublyLinkedList;
|
||||||
|
|
||||||
use Parsedown;
|
|
||||||
use Icinga\Data\Tree\Node;
|
|
||||||
use Icinga\Exception\NotReadableError;
|
use Icinga\Exception\NotReadableError;
|
||||||
use Icinga\Module\Doc\Exception\ChapterNotFoundException;
|
|
||||||
use Icinga\Module\Doc\Exception\DocEmptyException;
|
use Icinga\Module\Doc\Exception\DocEmptyException;
|
||||||
use Icinga\Module\Doc\Exception\DocException;
|
use Icinga\Module\Doc\Exception\DocException;
|
||||||
|
|
||||||
|
@ -44,152 +40,26 @@ class DocParser
|
||||||
public function __construct($path)
|
public function __construct($path)
|
||||||
{
|
{
|
||||||
if (! is_dir($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)) {
|
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);
|
$docIterator = new DocIterator($path);
|
||||||
if ($docIterator->count() === 0) {
|
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->path = $path;
|
||||||
$this->docIterator = $docIterator;
|
$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
|
* Extract atx- or setext-style headers from the given lines
|
||||||
*
|
*
|
||||||
|
@ -208,7 +78,7 @@ class DocParser
|
||||||
&& $line[0] === '#'
|
&& $line[0] === '#'
|
||||||
&& preg_match('/^#+/', $line, $match) === 1
|
&& preg_match('/^#+/', $line, $match) === 1
|
||||||
) {
|
) {
|
||||||
// Atx-style
|
// Atx
|
||||||
$level = strlen($match[0]);
|
$level = strlen($match[0]);
|
||||||
$header = trim(substr($line, $level));
|
$header = trim(substr($line, $level));
|
||||||
if (! $header) {
|
if (! $header) {
|
||||||
|
@ -233,36 +103,69 @@ class DocParser
|
||||||
if ($header === null) {
|
if ($header === null) {
|
||||||
return 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] === '<'
|
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);
|
$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
|
* @return DocTree
|
||||||
* @param int $level
|
|
||||||
*/
|
*/
|
||||||
protected function reduceToc(array &$tocStack, $level) {
|
public function getDocTree()
|
||||||
while (end($tocStack)->level >= $level) {
|
{
|
||||||
array_pop($tocStack);
|
$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