5 * This file is part of Psy Shell.
7 * (c) 2012-2017 Justin Hileman
9 * For the full copyright and license information, please view the LICENSE
10 * file that was distributed with this source code.
13 define('WRAP_WIDTH', 100);
17 if (count($argv) !== 3 || !is_dir($argv[1])) {
18 echo "usage: build_manual path/to/manual output_filename.db\n";
22 function htmlwrap($text, $width = null)
24 if ($width === null) {
35 switch (substr($text, $i, 1)) {
37 $return[] = trim(substr($text, 0, $i));
38 $text = substr($text, $i);
65 if (!$inTag && ($i - $tagWidth > $width)) {
66 $lastSpace = $lastSpace ?: $width;
68 $return[] = trim(substr($text, 0, $lastSpace));
69 $text = substr($text, $lastSpace);
76 $return[] = trim($text);
78 return implode("\n", $return);
81 function extract_paragraphs($element)
84 foreach ($element->getElementsByTagName('para') as $p) {
86 foreach ($p->childNodes as $child) {
87 // @todo figure out if there's something we can do with tables.
88 if ($child instanceof DOMElement && $child->tagName === 'table') {
92 // skip references, because ugh.
93 if (preg_match('{^\s*&[a-z][a-z\.]+;\s*$}', $child->textContent)) {
97 $text .= $child->ownerDocument->saveXML($child);
100 if ($text = trim(preg_replace('{\n[ \t]+}', ' ', $text))) {
101 $paragraphs[] = $text;
105 return implode("\n\n", $paragraphs);
108 function format_doc($doc)
112 if (!empty($doc['description'])) {
113 $chunks[] = '<comment>Description:</comment>';
114 $chunks[] = indent_text(htmlwrap(thunk_tags($doc['description']), WRAP_WIDTH - 2));
118 if (!empty($doc['params'])) {
119 $chunks[] = '<comment>Param:</comment>';
121 $typeMax = max(array_map(function ($param) {
122 return strlen($param['type']);
125 $max = max(array_map(function ($param) {
126 return strlen($param['name']);
129 $template = ' <info>%-' . $typeMax . 's</info> <strong>%-' . $max . 's</strong> %s';
130 $indent = str_repeat(' ', $typeMax + $max + 6);
131 $wrapWidth = WRAP_WIDTH - strlen($indent);
133 foreach ($doc['params'] as $param) {
134 $desc = indent_text(htmlwrap(thunk_tags($param['description']), $wrapWidth), $indent, false);
135 $chunks[] = sprintf($template, $param['type'], $param['name'], $desc);
140 if (isset($doc['return']) || isset($doc['return_type'])) {
141 $chunks[] = '<comment>Return:</comment>';
143 $type = isset($doc['return_type']) ? $doc['return_type'] : 'unknown';
144 $desc = isset($doc['return']) ? $doc['return'] : '';
146 $indent = str_repeat(' ', strlen($type) + 4);
147 $wrapWidth = WRAP_WIDTH - strlen($indent);
150 $desc = indent_text(htmlwrap(thunk_tags($doc['return']), $wrapWidth), $indent, false);
153 $chunks[] = sprintf(' <info>%s</info> %s', $type, $desc);
157 array_pop($chunks); // get rid of the trailing newline
159 return implode("\n", $chunks);
162 function thunk_tags($text)
165 'parameter>' => 'strong>',
166 'function>' => 'strong>',
167 'literal>' => 'return>',
169 'constant>' => 'info>',
174 '&true;' => '<return>true</return>',
175 '&false;' => '<return>false</return>',
176 '&null;' => '<return>null</return>',
179 return strtr(strip_tags(strtr($text, $tagMap), '<strong><return><info>'), $andBack);
182 function indent_text($text, $indent = ' ', $leading = true)
184 return ($leading ? $indent : '') . str_replace("\n", "\n" . $indent, $text);
187 function find_type($xml, $paramName)
189 foreach ($xml->getElementsByTagName('methodparam') as $param) {
190 if ($type = $param->getElementsByTagName('type')->item(0)) {
191 if ($parameter = $param->getElementsByTagName('parameter')->item(0)) {
192 if ($paramName === $parameter->textContent) {
193 return $type->textContent;
200 function format_function_doc($xml)
203 $refsect1s = $xml->getElementsByTagName('refsect1');
204 foreach ($refsect1s as $refsect1) {
205 $role = $refsect1->getAttribute('role');
208 $doc['description'] = extract_paragraphs($refsect1);
210 if ($synopsis = $refsect1->getElementsByTagName('methodsynopsis')->item(0)) {
211 foreach ($synopsis->childNodes as $node) {
212 if ($node instanceof DOMElement && $node->tagName === 'type') {
213 $doc['return_type'] = $node->textContent;
222 $doc['return'] = extract_paragraphs($refsect1);
227 $vars = $refsect1->getElementsByTagName('varlistentry');
228 foreach ($vars as $var) {
229 if ($name = $var->getElementsByTagName('parameter')->item(0)) {
231 'name' => '$' . $name->textContent,
232 'type' => find_type($xml, $name->textContent),
233 'description' => extract_paragraphs($var),
238 $doc['params'] = $params;
244 if ($purpose = $xml->getElementsByTagName('refpurpose')->item(0)) {
245 $desc = htmlwrap($purpose->textContent);
246 if (isset($doc['description'])) {
247 $desc .= "\n\n" . $doc['description'];
250 $doc['description'] = trim($desc);
254 foreach ($xml->getElementsByTagName('refname') as $ref) {
255 $ids[] = $ref->textContent;
258 return [$ids, format_doc($doc)];
261 function format_class_doc($xml)
263 // @todo implement this
267 $dir = new RecursiveDirectoryIterator($argv[1]);
268 $filter = new RecursiveCallbackFilterIterator($dir, function ($current, $key, $iterator) {
269 return $current->getFilename()[0] !== '.' &&
270 ($current->isDir() || $current->getExtension() === 'xml') &&
271 strpos($current->getFilename(), 'entities.') !== 0 &&
272 $current->getFilename() !== 'pdo_4d'; // Temporarily blacklist this one, the docs are weird.
274 $iterator = new RecursiveIteratorIterator($filter);
277 foreach ($iterator as $file) {
278 $xmlstr = str_replace('&', '&', file_get_contents($file));
280 $xml = new DOMDocument();
281 $xml->preserveWhiteSpace = false;
283 if (!@$xml->loadXml($xmlstr)) {
284 echo "XML Parse Error: $file\n";
288 if ($xml->getElementsByTagName('refentry')->length !== 0) {
289 list($ids, $doc) = format_function_doc($xml);
290 } elseif ($xml->getElementsByTagName('classref')->length !== 0) {
291 list($ids, $doc) = format_class_doc($xml);
297 foreach ($ids as $id) {
302 if (is_file($argv[2])) {
306 $db = new PDO('sqlite:' . $argv[2]);
308 $db->query('CREATE TABLE php_manual (id char(256) PRIMARY KEY, doc TEXT)');
309 $cmd = $db->prepare('INSERT INTO php_manual (id, doc) VALUES (?, ?)');
310 foreach ($docs as $id => $doc) {
311 $cmd->execute([$id, $doc]);