icingaweb2/modules/doc/library/Doc/DocParser.php

172 lines
5.6 KiB
PHP
Raw Normal View History

2013-10-16 14:45:23 +02:00
<?php
// {{{ICINGA_LICENSE_HEADER}}}
// {{{ICINGA_LICENSE_HEADER}}}
2013-10-16 14:45:23 +02:00
namespace Icinga\Module\Doc;
use SplDoublyLinkedList;
use Icinga\Exception\NotReadableError;
use Icinga\Module\Doc\Exception\DocEmptyException;
use Icinga\Module\Doc\Exception\DocException;
2013-10-16 14:45:23 +02:00
/**
* Parser for documentation written in Markdown
2013-10-16 14:45:23 +02:00
*/
class DocParser
2013-10-16 14:45:23 +02:00
{
/**
* Path to the documentation
*
* @var string
*/
protected $path;
/**
* Iterator over documentation files
*
* @var DocIterator
*/
protected $docIterator;
2013-10-16 14:45:23 +02:00
/**
* Create a new documentation parser for the given path
*
* @param string $path Path to the documentation
2013-10-16 14:45:23 +02:00
*
* @throws DocException If the documentation directory does not exist
* @throws NotReadableError If the documentation directory is not readable
* @throws DocEmptyException If the documentation directory is empty
*/
public function __construct($path)
{
if (! is_dir($path)) {
throw new DocException(
mt('doc', 'Documentation directory') . ' \'' . $path . '\' ' . mt('doc', 'does not exist')
);
}
if (! is_readable($path)) {
throw new DocException(
mt('doc', 'Documentation directory') . ' \'' . $path . '\' ' . mt('doc', 'is not readable')
);
}
$docIterator = new DocIterator($path);
if ($docIterator->count() === 0) {
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;
}
/**
* Extract atx- or setext-style headers from the given lines
*
* @param string $line
* @param string $lastLine
*
* @return array|null An array containing the header and the header level or null if there's nothing to extract
*/
protected function extractHeader($line, $lastLine)
{
if (! $line) {
return null;
}
$header = null;
if ($line
&& $line[0] === '#'
&& preg_match('/^#+/', $line, $match) === 1
) {
// Atx
$level = strlen($match[0]);
$header = trim(substr($line, $level));
if (! $header) {
return null;
}
} elseif (
$line
&& ($line[0] === '=' || $line[0] === '-')
&& preg_match('/^[=-]+\s*$/', $line, $match) === 1
) {
// Setext
$header = trim($lastLine);
if (! $header) {
return null;
}
if ($match[0][0] === '=') {
$level = 1;
} else {
$level = 2;
}
}
if ($header === null) {
return null;
}
if ($header[0] === '<'
&& preg_match('#(?:<(?P<tag>a|span) (?:id|name)="(?P<id>.+)"></(?P=tag)>)\s*#u', $header, $match)
) {
$header = str_replace($match[0], '', $header);
$id = $match['id'];
} else {
$id = null;
}
return array($header, $id, $level);
}
/**
* Get the documentation tree
*
* @return DocTree
*/
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()) {
$chapterTitle = $header;
$section = new Section($id, $header, $level, $nofollow, $chapterTitle);
$tree->addRoot($section);
} else {
$chapterTitle = $stack->bottom()->getTitle();
$section = new Section($id, $header, $level, $nofollow, $chapterTitle);
$tree->addChild($section, $stack->top());
}
$stack->push($section);
} else {
$stack->top()->appendContent($line);
}
// Save last line for setext-style headers
$lastLine = $line;
}
}
return $tree;
2013-10-16 14:45:23 +02:00
}
}