313 lines
8.7 KiB
PHP
Executable file
313 lines
8.7 KiB
PHP
Executable file
#!/usr/bin/env php
|
|
<?php
|
|
|
|
/*
|
|
* This file is part of Psy Shell.
|
|
*
|
|
* (c) 2012-2017 Justin Hileman
|
|
*
|
|
* For the full copyright and license information, please view the LICENSE
|
|
* file that was distributed with this source code.
|
|
*/
|
|
|
|
define('WRAP_WIDTH', 100);
|
|
|
|
$count = 0;
|
|
|
|
if (count($argv) !== 3 || !is_dir($argv[1])) {
|
|
echo "usage: build_manual path/to/manual output_filename.db\n";
|
|
exit(1);
|
|
}
|
|
|
|
function htmlwrap($text, $width = null)
|
|
{
|
|
if ($width === null) {
|
|
$width = WRAP_WIDTH;
|
|
}
|
|
|
|
$len = strlen($text);
|
|
|
|
$return = array();
|
|
$lastSpace = null;
|
|
$inTag = false;
|
|
$i = $tagWidth = 0;
|
|
do {
|
|
switch (substr($text, $i, 1)) {
|
|
case "\n":
|
|
$return[] = trim(substr($text, 0, $i));
|
|
$text = substr($text, $i);
|
|
$len = strlen($text);
|
|
|
|
$i = $lastSpace = 0;
|
|
continue;
|
|
|
|
case ' ':
|
|
if (!$inTag) {
|
|
$lastSpace = $i;
|
|
}
|
|
break;
|
|
|
|
case '<':
|
|
$inTag = true;
|
|
break;
|
|
|
|
case '>':
|
|
$inTag = false;
|
|
break;
|
|
}
|
|
|
|
if ($inTag) {
|
|
$tagWidth++;
|
|
}
|
|
|
|
$i++;
|
|
|
|
if (!$inTag && ($i - $tagWidth > $width)) {
|
|
$lastSpace = $lastSpace ?: $width;
|
|
|
|
$return[] = trim(substr($text, 0, $lastSpace));
|
|
$text = substr($text, $lastSpace);
|
|
$len = strlen($text);
|
|
|
|
$i = $tagWidth = 0;
|
|
}
|
|
} while ($i < $len);
|
|
|
|
$return[] = trim($text);
|
|
|
|
return implode("\n", $return);
|
|
}
|
|
|
|
function extract_paragraphs($element)
|
|
{
|
|
$paragraphs = array();
|
|
foreach ($element->getElementsByTagName('para') as $p) {
|
|
$text = '';
|
|
foreach ($p->childNodes as $child) {
|
|
// @todo figure out if there's something we can do with tables.
|
|
if ($child instanceof DOMElement && $child->tagName === 'table') {
|
|
continue;
|
|
}
|
|
|
|
// skip references, because ugh.
|
|
if (preg_match('{^\s*&[a-z][a-z\.]+;\s*$}', $child->textContent)) {
|
|
continue;
|
|
}
|
|
|
|
$text .= $child->ownerDocument->saveXML($child);
|
|
}
|
|
|
|
if ($text = trim(preg_replace('{\n[ \t]+}', ' ', $text))) {
|
|
$paragraphs[] = $text;
|
|
}
|
|
}
|
|
|
|
return implode("\n\n", $paragraphs);
|
|
}
|
|
|
|
function format_doc($doc)
|
|
{
|
|
$chunks = array();
|
|
|
|
if (!empty($doc['description'])) {
|
|
$chunks[] = '<comment>Description:</comment>';
|
|
$chunks[] = indent_text(htmlwrap(thunk_tags($doc['description']), WRAP_WIDTH - 2));
|
|
$chunks[] = '';
|
|
}
|
|
|
|
if (!empty($doc['params'])) {
|
|
$chunks[] = '<comment>Param:</comment>';
|
|
|
|
$typeMax = max(array_map(function ($param) {
|
|
return strlen($param['type']);
|
|
}, $doc['params']));
|
|
|
|
$max = max(array_map(function ($param) {
|
|
return strlen($param['name']);
|
|
}, $doc['params']));
|
|
|
|
$template = ' <info>%-' . $typeMax . 's</info> <strong>%-' . $max . 's</strong> %s';
|
|
$indent = str_repeat(' ', $typeMax + $max + 6);
|
|
$wrapWidth = WRAP_WIDTH - strlen($indent);
|
|
|
|
foreach ($doc['params'] as $param) {
|
|
$desc = indent_text(htmlwrap(thunk_tags($param['description']), $wrapWidth), $indent, false);
|
|
$chunks[] = sprintf($template, $param['type'], $param['name'], $desc);
|
|
}
|
|
$chunks[] = '';
|
|
}
|
|
|
|
if (isset($doc['return']) || isset($doc['return_type'])) {
|
|
$chunks[] = '<comment>Return:</comment>';
|
|
|
|
$type = isset($doc['return_type']) ? $doc['return_type'] : 'unknown';
|
|
$desc = isset($doc['return']) ? $doc['return'] : '';
|
|
|
|
$indent = str_repeat(' ', strlen($type) + 4);
|
|
$wrapWidth = WRAP_WIDTH - strlen($indent);
|
|
|
|
if (!empty($desc)) {
|
|
$desc = indent_text(htmlwrap(thunk_tags($doc['return']), $wrapWidth), $indent, false);
|
|
}
|
|
|
|
$chunks[] = sprintf(' <info>%s</info> %s', $type, $desc);
|
|
$chunks[] = '';
|
|
}
|
|
|
|
array_pop($chunks); // get rid of the trailing newline
|
|
|
|
return implode("\n", $chunks);
|
|
}
|
|
|
|
function thunk_tags($text)
|
|
{
|
|
$tagMap = array(
|
|
'parameter>' => 'strong>',
|
|
'function>' => 'strong>',
|
|
'literal>' => 'return>',
|
|
'type>' => 'info>',
|
|
'constant>' => 'info>',
|
|
);
|
|
|
|
$andBack = array(
|
|
'&' => '&',
|
|
'&true;' => '<return>true</return>',
|
|
'&false;' => '<return>false</return>',
|
|
'&null;' => '<return>null</return>',
|
|
);
|
|
|
|
return strtr(strip_tags(strtr($text, $tagMap), '<strong><return><info>'), $andBack);
|
|
}
|
|
|
|
function indent_text($text, $indent = ' ', $leading = true)
|
|
{
|
|
return ($leading ? $indent : '') . str_replace("\n", "\n" . $indent, $text);
|
|
}
|
|
|
|
function find_type($xml, $paramName)
|
|
{
|
|
foreach ($xml->getElementsByTagName('methodparam') as $param) {
|
|
if ($type = $param->getElementsByTagName('type')->item(0)) {
|
|
if ($parameter = $param->getElementsByTagName('parameter')->item(0)) {
|
|
if ($paramName === $parameter->textContent) {
|
|
return $type->textContent;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function format_function_doc($xml)
|
|
{
|
|
$doc = array();
|
|
$refsect1s = $xml->getElementsByTagName('refsect1');
|
|
foreach ($refsect1s as $refsect1) {
|
|
$role = $refsect1->getAttribute('role');
|
|
switch ($role) {
|
|
case 'description':
|
|
$doc['description'] = extract_paragraphs($refsect1);
|
|
|
|
if ($synopsis = $refsect1->getElementsByTagName('methodsynopsis')->item(0)) {
|
|
foreach ($synopsis->childNodes as $node) {
|
|
if ($node instanceof DOMElement && $node->tagName === 'type') {
|
|
$doc['return_type'] = $node->textContent;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 'returnvalues':
|
|
// do nothing.
|
|
$doc['return'] = extract_paragraphs($refsect1);
|
|
break;
|
|
|
|
case 'parameters':
|
|
$params = array();
|
|
$vars = $refsect1->getElementsByTagName('varlistentry');
|
|
foreach ($vars as $var) {
|
|
if ($name = $var->getElementsByTagName('parameter')->item(0)) {
|
|
$params[] = array(
|
|
'name' => '$' . $name->textContent,
|
|
'type' => find_type($xml, $name->textContent),
|
|
'description' => extract_paragraphs($var),
|
|
);
|
|
}
|
|
}
|
|
|
|
$doc['params'] = $params;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// and the purpose
|
|
if ($purpose = $xml->getElementsByTagName('refpurpose')->item(0)) {
|
|
$desc = htmlwrap($purpose->textContent);
|
|
if (isset($doc['description'])) {
|
|
$desc .= "\n\n" . $doc['description'];
|
|
}
|
|
|
|
$doc['description'] = trim($desc);
|
|
}
|
|
|
|
$ids = array();
|
|
foreach ($xml->getElementsByTagName('refname') as $ref) {
|
|
$ids[] = $ref->textContent;
|
|
}
|
|
|
|
return array($ids, format_doc($doc));
|
|
}
|
|
|
|
function format_class_doc($xml)
|
|
{
|
|
// @todo implement this
|
|
return array(array(), null);
|
|
}
|
|
|
|
$dir = new RecursiveDirectoryIterator($argv[1]);
|
|
$filter = new RecursiveCallbackFilterIterator($dir, function ($current, $key, $iterator) {
|
|
return $current->getFilename()[0] !== '.' &&
|
|
($current->isDir() || $current->getExtension() === 'xml') &&
|
|
strpos($current->getFilename(), 'entities.') !== 0 &&
|
|
$current->getFilename() !== 'pdo_4d'; // Temporarily blacklist this one, the docs are weird.
|
|
});
|
|
$iterator = new RecursiveIteratorIterator($filter);
|
|
|
|
$docs = array();
|
|
foreach ($iterator as $file) {
|
|
$xmlstr = str_replace('&', '&', file_get_contents($file));
|
|
|
|
$xml = new DOMDocument();
|
|
$xml->preserveWhiteSpace = false;
|
|
|
|
if (!@$xml->loadXml($xmlstr)) {
|
|
echo "XML Parse Error: $file\n";
|
|
continue;
|
|
}
|
|
|
|
if ($xml->getElementsByTagName('refentry')->length !== 0) {
|
|
list($ids, $doc) = format_function_doc($xml);
|
|
} elseif ($xml->getElementsByTagName('classref')->length !== 0) {
|
|
list($ids, $doc) = format_class_doc($xml);
|
|
} else {
|
|
$ids = array();
|
|
$doc = null;
|
|
}
|
|
|
|
foreach ($ids as $id) {
|
|
$docs[$id] = $doc;
|
|
}
|
|
}
|
|
|
|
if (is_file($argv[2])) {
|
|
unlink($argv[2]);
|
|
}
|
|
|
|
$db = new PDO('sqlite:' . $argv[2]);
|
|
|
|
$db->query('CREATE TABLE php_manual (id char(256) PRIMARY KEY, doc TEXT)');
|
|
$cmd = $db->prepare('INSERT INTO php_manual (id, doc) VALUES (?, ?)');
|
|
foreach ($docs as $id => $doc) {
|
|
$cmd->execute(array($id, $doc));
|
|
}
|