Chris@0
|
1 #!/usr/bin/env php
|
Chris@0
|
2 <?php
|
Chris@0
|
3
|
Chris@0
|
4 /*
|
Chris@0
|
5 * This file is part of Psy Shell.
|
Chris@0
|
6 *
|
Chris@0
|
7 * (c) 2012-2017 Justin Hileman
|
Chris@0
|
8 *
|
Chris@0
|
9 * For the full copyright and license information, please view the LICENSE
|
Chris@0
|
10 * file that was distributed with this source code.
|
Chris@0
|
11 */
|
Chris@0
|
12
|
Chris@0
|
13 define('WRAP_WIDTH', 100);
|
Chris@0
|
14
|
Chris@0
|
15 $count = 0;
|
Chris@0
|
16
|
Chris@0
|
17 if (count($argv) !== 3 || !is_dir($argv[1])) {
|
Chris@0
|
18 echo "usage: build_manual path/to/manual output_filename.db\n";
|
Chris@0
|
19 exit(1);
|
Chris@0
|
20 }
|
Chris@0
|
21
|
Chris@0
|
22 function htmlwrap($text, $width = null)
|
Chris@0
|
23 {
|
Chris@0
|
24 if ($width === null) {
|
Chris@0
|
25 $width = WRAP_WIDTH;
|
Chris@0
|
26 }
|
Chris@0
|
27
|
Chris@0
|
28 $len = strlen($text);
|
Chris@0
|
29
|
Chris@0
|
30 $return = array();
|
Chris@0
|
31 $lastSpace = null;
|
Chris@0
|
32 $inTag = false;
|
Chris@0
|
33 $i = $tagWidth = 0;
|
Chris@0
|
34 do {
|
Chris@0
|
35 switch (substr($text, $i, 1)) {
|
Chris@0
|
36 case "\n":
|
Chris@0
|
37 $return[] = trim(substr($text, 0, $i));
|
Chris@0
|
38 $text = substr($text, $i);
|
Chris@0
|
39 $len = strlen($text);
|
Chris@0
|
40
|
Chris@0
|
41 $i = $lastSpace = 0;
|
Chris@0
|
42 continue;
|
Chris@0
|
43
|
Chris@0
|
44 case ' ':
|
Chris@0
|
45 if (!$inTag) {
|
Chris@0
|
46 $lastSpace = $i;
|
Chris@0
|
47 }
|
Chris@0
|
48 break;
|
Chris@0
|
49
|
Chris@0
|
50 case '<':
|
Chris@0
|
51 $inTag = true;
|
Chris@0
|
52 break;
|
Chris@0
|
53
|
Chris@0
|
54 case '>':
|
Chris@0
|
55 $inTag = false;
|
Chris@0
|
56
|
Chris@0
|
57 default:
|
Chris@0
|
58 }
|
Chris@0
|
59
|
Chris@0
|
60 if ($inTag) {
|
Chris@0
|
61 $tagWidth++;
|
Chris@0
|
62 }
|
Chris@0
|
63
|
Chris@0
|
64 $i++;
|
Chris@0
|
65
|
Chris@0
|
66 if (!$inTag && ($i - $tagWidth > $width)) {
|
Chris@0
|
67 $lastSpace = $lastSpace ?: $width;
|
Chris@0
|
68
|
Chris@0
|
69 $return[] = trim(substr($text, 0, $lastSpace));
|
Chris@0
|
70 $text = substr($text, $lastSpace);
|
Chris@0
|
71 $len = strlen($text);
|
Chris@0
|
72
|
Chris@0
|
73 $i = $tagWidth = 0;
|
Chris@0
|
74 }
|
Chris@0
|
75 } while ($i < $len);
|
Chris@0
|
76
|
Chris@0
|
77 $return[] = trim($text);
|
Chris@0
|
78
|
Chris@0
|
79 return implode("\n", $return);
|
Chris@0
|
80 }
|
Chris@0
|
81
|
Chris@0
|
82 function extract_paragraphs($element)
|
Chris@0
|
83 {
|
Chris@0
|
84 $paragraphs = array();
|
Chris@0
|
85 foreach ($element->getElementsByTagName('para') as $p) {
|
Chris@0
|
86 $text = '';
|
Chris@0
|
87 foreach ($p->childNodes as $child) {
|
Chris@0
|
88 // @todo figure out if there's something we can do with tables.
|
Chris@0
|
89 if ($child instanceof DOMElement && $child->tagName === 'table') {
|
Chris@0
|
90 continue;
|
Chris@0
|
91 }
|
Chris@0
|
92
|
Chris@0
|
93 // skip references, because ugh.
|
Chris@0
|
94 if (preg_match('{^\s*&[a-z][a-z\.]+;\s*$}', $child->textContent)) {
|
Chris@0
|
95 continue;
|
Chris@0
|
96 }
|
Chris@0
|
97
|
Chris@0
|
98 $text .= $child->ownerDocument->saveXML($child);
|
Chris@0
|
99 }
|
Chris@0
|
100
|
Chris@0
|
101 if ($text = trim(preg_replace('{\n[ \t]+}', ' ', $text))) {
|
Chris@0
|
102 $paragraphs[] = $text;
|
Chris@0
|
103 }
|
Chris@0
|
104 }
|
Chris@0
|
105
|
Chris@0
|
106 return implode("\n\n", $paragraphs);
|
Chris@0
|
107 }
|
Chris@0
|
108
|
Chris@0
|
109 function format_doc($doc)
|
Chris@0
|
110 {
|
Chris@0
|
111 $chunks = array();
|
Chris@0
|
112
|
Chris@0
|
113 if (!empty($doc['description'])) {
|
Chris@0
|
114 $chunks[] = '<comment>Description:</comment>';
|
Chris@0
|
115 $chunks[] = indent_text(htmlwrap(thunk_tags($doc['description']), WRAP_WIDTH - 2));
|
Chris@0
|
116 $chunks[] = '';
|
Chris@0
|
117 }
|
Chris@0
|
118
|
Chris@0
|
119 if (!empty($doc['params'])) {
|
Chris@0
|
120 $chunks[] = '<comment>Param:</comment>';
|
Chris@0
|
121
|
Chris@0
|
122 $typeMax = max(array_map(function ($param) {
|
Chris@0
|
123 return strlen($param['type']);
|
Chris@0
|
124 }, $doc['params']));
|
Chris@0
|
125
|
Chris@0
|
126 $max = max(array_map(function ($param) {
|
Chris@0
|
127 return strlen($param['name']);
|
Chris@0
|
128 }, $doc['params']));
|
Chris@0
|
129
|
Chris@0
|
130 $template = ' <info>%-' . $typeMax . 's</info> <strong>%-' . $max . 's</strong> %s';
|
Chris@0
|
131 $indent = str_repeat(' ', $typeMax + $max + 6);
|
Chris@0
|
132 $wrapWidth = WRAP_WIDTH - strlen($indent);
|
Chris@0
|
133
|
Chris@0
|
134 foreach ($doc['params'] as $param) {
|
Chris@0
|
135 $desc = indent_text(htmlwrap(thunk_tags($param['description']), $wrapWidth), $indent, false);
|
Chris@0
|
136 $chunks[] = sprintf($template, $param['type'], $param['name'], $desc);
|
Chris@0
|
137 }
|
Chris@0
|
138 $chunks[] = '';
|
Chris@0
|
139 }
|
Chris@0
|
140
|
Chris@0
|
141 if (isset($doc['return']) || isset($doc['return_type'])) {
|
Chris@0
|
142 $chunks[] = '<comment>Return:</comment>';
|
Chris@0
|
143
|
Chris@0
|
144 $type = isset($doc['return_type']) ? $doc['return_type'] : 'unknown';
|
Chris@0
|
145 $desc = isset($doc['return']) ? $doc['return'] : '';
|
Chris@0
|
146
|
Chris@0
|
147 $indent = str_repeat(' ', strlen($type) + 4);
|
Chris@0
|
148 $wrapWidth = WRAP_WIDTH - strlen($indent);
|
Chris@0
|
149
|
Chris@0
|
150 if (!empty($desc)) {
|
Chris@0
|
151 $desc = indent_text(htmlwrap(thunk_tags($doc['return']), $wrapWidth), $indent, false);
|
Chris@0
|
152 }
|
Chris@0
|
153
|
Chris@0
|
154 $chunks[] = sprintf(' <info>%s</info> %s', $type, $desc);
|
Chris@0
|
155 $chunks[] = '';
|
Chris@0
|
156 }
|
Chris@0
|
157
|
Chris@0
|
158 array_pop($chunks); // get rid of the trailing newline
|
Chris@0
|
159
|
Chris@0
|
160 return implode("\n", $chunks);
|
Chris@0
|
161 }
|
Chris@0
|
162
|
Chris@0
|
163 function thunk_tags($text)
|
Chris@0
|
164 {
|
Chris@0
|
165 $tagMap = array(
|
Chris@0
|
166 'parameter>' => 'strong>',
|
Chris@0
|
167 'function>' => 'strong>',
|
Chris@0
|
168 'literal>' => 'return>',
|
Chris@0
|
169 'type>' => 'info>',
|
Chris@0
|
170 'constant>' => 'info>',
|
Chris@0
|
171 );
|
Chris@0
|
172
|
Chris@0
|
173 $andBack = array(
|
Chris@0
|
174 '&' => '&',
|
Chris@0
|
175 '&true;' => '<return>true</return>',
|
Chris@0
|
176 '&false;' => '<return>false</return>',
|
Chris@0
|
177 '&null;' => '<return>null</return>',
|
Chris@0
|
178 );
|
Chris@0
|
179
|
Chris@0
|
180 return strtr(strip_tags(strtr($text, $tagMap), '<strong><return><info>'), $andBack);
|
Chris@0
|
181 }
|
Chris@0
|
182
|
Chris@0
|
183 function indent_text($text, $indent = ' ', $leading = true)
|
Chris@0
|
184 {
|
Chris@0
|
185 return ($leading ? $indent : '') . str_replace("\n", "\n" . $indent, $text);
|
Chris@0
|
186 }
|
Chris@0
|
187
|
Chris@0
|
188 function find_type($xml, $paramName)
|
Chris@0
|
189 {
|
Chris@0
|
190 foreach ($xml->getElementsByTagName('methodparam') as $param) {
|
Chris@0
|
191 if ($type = $param->getElementsByTagName('type')->item(0)) {
|
Chris@0
|
192 if ($parameter = $param->getElementsByTagName('parameter')->item(0)) {
|
Chris@0
|
193 if ($paramName === $parameter->textContent) {
|
Chris@0
|
194 return $type->textContent;
|
Chris@0
|
195 }
|
Chris@0
|
196 }
|
Chris@0
|
197 }
|
Chris@0
|
198 }
|
Chris@0
|
199 }
|
Chris@0
|
200
|
Chris@0
|
201 function format_function_doc($xml)
|
Chris@0
|
202 {
|
Chris@0
|
203 $doc = array();
|
Chris@0
|
204 $refsect1s = $xml->getElementsByTagName('refsect1');
|
Chris@0
|
205 foreach ($refsect1s as $refsect1) {
|
Chris@0
|
206 $role = $refsect1->getAttribute('role');
|
Chris@0
|
207 switch ($role) {
|
Chris@0
|
208 case 'description':
|
Chris@0
|
209 $doc['description'] = extract_paragraphs($refsect1);
|
Chris@0
|
210
|
Chris@0
|
211 if ($synopsis = $refsect1->getElementsByTagName('methodsynopsis')->item(0)) {
|
Chris@0
|
212 foreach ($synopsis->childNodes as $node) {
|
Chris@0
|
213 if ($node instanceof DOMElement && $node->tagName === 'type') {
|
Chris@0
|
214 $doc['return_type'] = $node->textContent;
|
Chris@0
|
215 break;
|
Chris@0
|
216 }
|
Chris@0
|
217 }
|
Chris@0
|
218 }
|
Chris@0
|
219 break;
|
Chris@0
|
220
|
Chris@0
|
221 case 'returnvalues':
|
Chris@0
|
222 // do nothing.
|
Chris@0
|
223 $doc['return'] = extract_paragraphs($refsect1);
|
Chris@0
|
224 break;
|
Chris@0
|
225
|
Chris@0
|
226 case 'parameters':
|
Chris@0
|
227 $params = array();
|
Chris@0
|
228 $vars = $refsect1->getElementsByTagName('varlistentry');
|
Chris@0
|
229 foreach ($vars as $var) {
|
Chris@0
|
230 if ($name = $var->getElementsByTagName('parameter')->item(0)) {
|
Chris@0
|
231 $params[] = array(
|
Chris@0
|
232 'name' => '$' . $name->textContent,
|
Chris@0
|
233 'type' => find_type($xml, $name->textContent),
|
Chris@0
|
234 'description' => extract_paragraphs($var),
|
Chris@0
|
235 );
|
Chris@0
|
236 }
|
Chris@0
|
237 }
|
Chris@0
|
238
|
Chris@0
|
239 $doc['params'] = $params;
|
Chris@0
|
240 break;
|
Chris@0
|
241 }
|
Chris@0
|
242 }
|
Chris@0
|
243
|
Chris@0
|
244 // and the purpose
|
Chris@0
|
245 if ($purpose = $xml->getElementsByTagName('refpurpose')->item(0)) {
|
Chris@0
|
246 $desc = htmlwrap($purpose->textContent);
|
Chris@0
|
247 if (isset($doc['description'])) {
|
Chris@0
|
248 $desc .= "\n\n" . $doc['description'];
|
Chris@0
|
249 }
|
Chris@0
|
250
|
Chris@0
|
251 $doc['description'] = trim($desc);
|
Chris@0
|
252 }
|
Chris@0
|
253
|
Chris@0
|
254 $ids = array();
|
Chris@0
|
255 foreach ($xml->getElementsByTagName('refname') as $ref) {
|
Chris@0
|
256 $ids[] = $ref->textContent;
|
Chris@0
|
257 }
|
Chris@0
|
258
|
Chris@0
|
259 return array($ids, format_doc($doc));
|
Chris@0
|
260 }
|
Chris@0
|
261
|
Chris@0
|
262 function format_class_doc($xml)
|
Chris@0
|
263 {
|
Chris@0
|
264 // @todo implement this
|
Chris@0
|
265 return array(array(), null);
|
Chris@0
|
266 }
|
Chris@0
|
267
|
Chris@0
|
268 $dir = new RecursiveDirectoryIterator($argv[1]);
|
Chris@0
|
269 $filter = new RecursiveCallbackFilterIterator($dir, function ($current, $key, $iterator) {
|
Chris@0
|
270 return $current->getFilename()[0] !== '.' &&
|
Chris@0
|
271 ($current->isDir() || $current->getExtension() === 'xml') &&
|
Chris@0
|
272 strpos($current->getFilename(), 'entities.') !== 0 &&
|
Chris@0
|
273 $current->getFilename() !== 'pdo_4d'; // Temporarily blacklist this one, the docs are weird.
|
Chris@0
|
274 });
|
Chris@0
|
275 $iterator = new RecursiveIteratorIterator($filter);
|
Chris@0
|
276
|
Chris@0
|
277 $docs = array();
|
Chris@0
|
278 foreach ($iterator as $file) {
|
Chris@0
|
279 $xmlstr = str_replace('&', '&', file_get_contents($file));
|
Chris@0
|
280
|
Chris@0
|
281 $xml = new DOMDocument();
|
Chris@0
|
282 $xml->preserveWhiteSpace = false;
|
Chris@0
|
283
|
Chris@0
|
284 if (!@$xml->loadXml($xmlstr)) {
|
Chris@0
|
285 echo "XML Parse Error: $file\n";
|
Chris@0
|
286 continue;
|
Chris@0
|
287 }
|
Chris@0
|
288
|
Chris@0
|
289 if ($xml->getElementsByTagName('refentry')->length !== 0) {
|
Chris@0
|
290 list($ids, $doc) = format_function_doc($xml);
|
Chris@0
|
291 } elseif ($xml->getElementsByTagName('classref')->length !== 0) {
|
Chris@0
|
292 list($ids, $doc) = format_class_doc($xml);
|
Chris@0
|
293 } else {
|
Chris@0
|
294 $ids = array();
|
Chris@0
|
295 $doc = null;
|
Chris@0
|
296 }
|
Chris@0
|
297
|
Chris@0
|
298 foreach ($ids as $id) {
|
Chris@0
|
299 $docs[$id] = $doc;
|
Chris@0
|
300 }
|
Chris@0
|
301 }
|
Chris@0
|
302
|
Chris@0
|
303 if (is_file($argv[2])) {
|
Chris@0
|
304 unlink($argv[2]);
|
Chris@0
|
305 }
|
Chris@0
|
306
|
Chris@0
|
307 $db = new PDO('sqlite:' . $argv[2]);
|
Chris@0
|
308
|
Chris@0
|
309 $db->query('CREATE TABLE php_manual (id char(256) PRIMARY KEY, doc TEXT)');
|
Chris@0
|
310 $cmd = $db->prepare('INSERT INTO php_manual (id, doc) VALUES (?, ?)');
|
Chris@0
|
311 foreach ($docs as $id => $doc) {
|
Chris@0
|
312 $cmd->execute(array($id, $doc));
|
Chris@0
|
313 }
|