PageRenderTime 50ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/modules/kodoc/libraries/Kodoc.php

https://github.com/MHordecki/milionkostek
PHP | 862 lines | 588 code | 136 blank | 138 comment | 68 complexity | a4ff52ec41115e69a44c1745eebf6e84 MD5 | raw file
  1. <?php defined('SYSPATH') or die('No direct script access.');
  2. /**
  3. * Kohana - The Swift PHP Framework
  4. *
  5. * License:
  6. * author - Kohana Team
  7. * copyright - (c) 2007 Kohana Team
  8. * license - <http://kohanaphp.com/license.html>
  9. */
  10. /**
  11. * Provides self-generating documentation about Kohana.
  12. */
  13. class Kodoc_Core {
  14. protected static $types = array
  15. (
  16. 'core',
  17. 'config',
  18. 'helpers',
  19. 'libraries',
  20. 'models',
  21. 'views'
  22. );
  23. public static function get_types()
  24. {
  25. return self::$types;
  26. }
  27. public static function get_files()
  28. {
  29. // Extension length
  30. $ext_len = -(strlen(EXT));
  31. $files = array();
  32. foreach (self::$types as $type)
  33. {
  34. $files[$type] = array();
  35. foreach (Kohana::list_files($type, TRUE) as $file)
  36. {
  37. // Not a source file
  38. if (substr($file, $ext_len) !== EXT)
  39. continue;
  40. // Remove the dirs from the filename
  41. $file = preg_replace('!^.+'.$type.'/(.+)'.EXT.'$!', '$1', $file);
  42. // Skip utf8 function files
  43. if ($type === 'core' AND substr($file, 0, 5) === 'utf8/')
  44. continue;
  45. if ($type === 'libraries' AND substr($file, 0, 8) === 'drivers/')
  46. {
  47. // Remove the drivers directory from the file
  48. $file = explode('_', substr($file, 8));
  49. if (count($file) === 1)
  50. {
  51. // Driver interface
  52. $files[$type][current($file)][] = current($file);
  53. }
  54. else
  55. {
  56. // Driver is class suffix
  57. $driver = array_pop($file);
  58. // Library is everything else
  59. $library = implode('_', $file);
  60. // Library driver
  61. $files[$type][$library][] = $driver;
  62. }
  63. }
  64. else
  65. {
  66. $files[$type][$file] = NULL;
  67. }
  68. }
  69. }
  70. return $files;
  71. }
  72. public static function remove_docroot($file)
  73. {
  74. return preg_replace('!^'.preg_quote(DOCROOT, '!').'!', '', $file);
  75. }
  76. public static function humanize_type($types)
  77. {
  78. $types = is_array($types) ? $types : explode('|', $types);
  79. $output = array();
  80. while ($t = array_shift($types))
  81. {
  82. $output[] = '<tt>'.trim($t).'</tt>';
  83. }
  84. return implode(' or ', $output);
  85. }
  86. public static function humanize_value($value)
  87. {
  88. if ($value === NULL)
  89. {
  90. return 'NULL';
  91. }
  92. elseif (is_bool($value))
  93. {
  94. return $value ? 'TRUE' : 'FALSE';
  95. }
  96. elseif (is_string($value))
  97. {
  98. return 'string '.$value;
  99. }
  100. elseif (is_numeric($value))
  101. {
  102. return (is_int($value) ? 'int' : 'float').' '.$value;
  103. }
  104. elseif (is_array($value))
  105. {
  106. return 'array';
  107. }
  108. elseif (is_object($value))
  109. {
  110. return 'object '.get_class($value);
  111. }
  112. }
  113. // All files to be parsed
  114. protected $file = array();
  115. public function __construct($type, $filename)
  116. {
  117. // Parse the file
  118. $this->file = $this->parse($type, $filename);
  119. }
  120. /**
  121. * Fetch documentation for all files parsed.
  122. *
  123. * Returns:
  124. * array: file documentation
  125. */
  126. public function get()
  127. {
  128. return $this->file;
  129. }
  130. /**
  131. * Parse a file for Kodoc commands, classes, and methods.
  132. *
  133. * Parameters:
  134. * string: file type
  135. * string: absolute filename path
  136. */
  137. protected function parse($type, $filename)
  138. {
  139. // File definition
  140. $file = array
  141. (
  142. 'type' => $type,
  143. 'comment' => '',
  144. 'file' => self::remove_docroot($filename),
  145. );
  146. // Read the entire file into an array
  147. $data = file($filename);
  148. foreach ($data as $line)
  149. {
  150. if (strpos($line, 'class') !== FALSE AND preg_match('/(?:class|interface)\s+([a-z0-9_]+).+{$/i', $line, $matches))
  151. {
  152. // Include the file if it has not already been included
  153. class_exists($matches[1], FALSE) or include_once $filename;
  154. // Add class to file info
  155. $file['classes'][] = $this->parse_class($matches[1]);
  156. }
  157. }
  158. if (empty($file['classes']))
  159. {
  160. $block = NULL;
  161. $source = NULL;
  162. foreach ($data as $line)
  163. {
  164. switch (substr(trim($line), 0, 2))
  165. {
  166. case '/*':
  167. $block = '';
  168. continue 2;
  169. break;
  170. case '*/':
  171. $source = TRUE;
  172. continue 2;
  173. break;
  174. }
  175. if ($source === TRUE)
  176. {
  177. if (preg_match('/\$config\[\'(.+?)\'\]\s+=\s+([^;].+)/', $line, $matches))
  178. {
  179. $source = array
  180. (
  181. $matches[1],
  182. $matches[2]
  183. );
  184. }
  185. else
  186. {
  187. $source = array();
  188. }
  189. $file['comments'][] = array_merge($this->parse_comment($block), array('source' => $source));
  190. $block = NULL;
  191. $source = FALSE;
  192. }
  193. elseif (is_string($block))
  194. {
  195. $block .= $line;
  196. }
  197. }
  198. }
  199. return $file;
  200. }
  201. protected function parse_comment($block)
  202. {
  203. if (($block = trim($block)) == '')
  204. return $block;
  205. // Explode the lines into an array and trim them
  206. $block = array_map('trim', explode("\n", $block));
  207. if (current($block) === '/**')
  208. {
  209. // Remove comment opening
  210. array_shift($block);
  211. }
  212. if (end($block) === '*/')
  213. {
  214. // Remove comment closing
  215. array_pop($block);
  216. }
  217. // Start comment
  218. $comment = array();
  219. while ($line = array_shift($block))
  220. {
  221. // Remove * from the line
  222. $line = trim(substr($line, 2));
  223. if (substr($line, 0, 1) === '$' AND substr($line, -1) === '$')
  224. {
  225. // Skip SVN property inserts
  226. continue;
  227. }
  228. if (substr($line, 0, 1) === '@')
  229. {
  230. if (preg_match('/^@(.+?)\s+(.+)$/', $line, $matches))
  231. {
  232. $comment[$matches[1]][] = $matches[2];
  233. }
  234. }
  235. else
  236. {
  237. $comment['about'][] = $line;
  238. }
  239. }
  240. if ( ! empty($comment['about']))
  241. {
  242. $token = '';
  243. $block = '';
  244. $about = '';
  245. foreach ($comment['about'] as $line)
  246. {
  247. if (strpos($line, '`') !== FALSE)
  248. {
  249. $line = preg_replace('/`([^`].+?)`/', '<tt>$1</tt>', $line);
  250. }
  251. if (substr($line, 0, 2) === '- ')
  252. {
  253. if ($token !== 'ul')
  254. {
  255. $about .= $this->comment_block($token, $block);
  256. $block = '';
  257. }
  258. $token = 'ul';
  259. $line = '<li>'.trim(substr($line, 2)).'</li>'."\n";
  260. }
  261. elseif (preg_match('/(.+?)\s+-\s+(.+)/', $line, $matches))
  262. {
  263. if ($token !== 'dl')
  264. {
  265. $about .= $this->comment_block($token, $block);
  266. $block = '';
  267. }
  268. $token = 'dl';
  269. $line = '<dt>'.$matches[1].'</dt>'."\n".'<dd>'.$matches[2].'</dd>'."\n";
  270. }
  271. else
  272. {
  273. $token = 'p';
  274. $line .= ' ';
  275. }
  276. if (trim($line) === '')
  277. {
  278. $about .= $this->comment_block($token, $block);
  279. $block = '';
  280. }
  281. else
  282. {
  283. $block .= $line;
  284. }
  285. }
  286. if ( ! empty($block))
  287. {
  288. $about .= $this->comment_block($token, $block);
  289. }
  290. $comment['about'] = $about;
  291. }
  292. return $comment;
  293. }
  294. protected function comment_block($token, $block)
  295. {
  296. if (empty($token) OR empty($block))
  297. return '';
  298. $block = trim($block);
  299. if (substr($block, 0, 1) === '<')
  300. {
  301. // Insert newlines before and after the block
  302. $block = "\n".$block."\n";
  303. }
  304. return '<'.$token.'>'.$block.'</'.$token.'>'."\n";
  305. }
  306. protected function parse_class($class)
  307. {
  308. // Use reflection to find information
  309. $reflection = new ReflectionClass($class);
  310. // Class definition
  311. $class = array
  312. (
  313. 'name' => $reflection->getName(),
  314. 'comment' => $this->parse_comment($reflection->getDocComment()),
  315. 'final' => $reflection->isFinal(),
  316. 'abstract' => $reflection->isAbstract(),
  317. 'interface' => $reflection->isInterface(),
  318. 'extends' => '',
  319. 'implements' => array(),
  320. 'methods' => array()
  321. );
  322. if ($implements = $reflection->getInterfaces())
  323. {
  324. foreach ($implements as $interface)
  325. {
  326. // Get implemented interfaces
  327. $class['implements'][] = $interface->getName();
  328. }
  329. }
  330. if ($parent = $reflection->getParentClass())
  331. {
  332. // Get parent class
  333. $class['extends'] = $parent->getName();
  334. }
  335. if ($methods = $reflection->getMethods())
  336. {
  337. foreach ($methods as $method)
  338. {
  339. // Don't try to document internal methods
  340. if ($method->isInternal()) continue;
  341. $class['methods'][] = array
  342. (
  343. 'name' => $method->getName(),
  344. 'comment' => $this->parse_comment($method->getDocComment()),
  345. 'class' => $class['name'],
  346. 'final' => $method->isFinal(),
  347. 'static' => $method->isStatic(),
  348. 'abstract' => $method->isAbstract(),
  349. 'visibility' => $this->visibility($method),
  350. 'parameters' => $this->parameters($method)
  351. );
  352. }
  353. }
  354. return $class;
  355. }
  356. /**
  357. * Finds the parameters for a ReflectionMethod.
  358. *
  359. * @param object ReflectionMethod
  360. * @return array
  361. */
  362. protected function parameters(ReflectionMethod $method)
  363. {
  364. $params = array();
  365. if ($parameters = $method->getParameters())
  366. {
  367. foreach ($parameters as $param)
  368. {
  369. // Parameter data
  370. $data = array
  371. (
  372. 'name' => $param->getName()
  373. );
  374. if ($param->isOptional())
  375. {
  376. // Set default value
  377. $data['default'] = $param->getDefaultValue();
  378. }
  379. $params[] = $data;
  380. }
  381. }
  382. return $params;
  383. }
  384. /**
  385. * Finds the visibility of a ReflectionMethod.
  386. *
  387. * @param object ReflectionMethod
  388. * @return string
  389. */
  390. protected function visibility(ReflectionMethod $method)
  391. {
  392. $vis = array_flip(Reflection::getModifierNames($method->getModifiers()));
  393. if (isset($vis['public']))
  394. {
  395. return 'public';
  396. }
  397. if (isset($vis['protected']))
  398. {
  399. return 'protected';
  400. }
  401. if (isset($vis['private']))
  402. {
  403. return 'private';
  404. }
  405. return FALSE;
  406. }
  407. } // End Kodoc
  408. class Kodoc_xCore {
  409. /**
  410. * libraries, helpers, etc
  411. */
  412. protected $files = array
  413. (
  414. 'core' => array(),
  415. 'config' => array(),
  416. 'helpers' => array(),
  417. 'libraries' => array(),
  418. 'models' => array(),
  419. 'views' => array()
  420. );
  421. /**
  422. * $classes[$name] = array $properties;
  423. * $properties = array
  424. * (
  425. * 'drivers' => array $drivers
  426. * 'properties' => array $properties
  427. * 'methods' => array $methods
  428. * )
  429. */
  430. protected $classes = array();
  431. // Holds the current data until parsed
  432. protected $current_class;
  433. // $packages[$name] = array $files;
  434. protected $packages = array();
  435. // PHP's visibility types
  436. protected static $php_visibility = array
  437. (
  438. 'public',
  439. 'protected',
  440. 'private'
  441. );
  442. public function __construct()
  443. {
  444. if (isset(self::$php_visibility[0]))
  445. {
  446. self::$php_visibility = array_flip(self::$php_visibility);
  447. }
  448. foreach ($this->files as $type => $files)
  449. {
  450. foreach (Kohana::list_files($type) as $filepath)
  451. {
  452. // Get the filename with no extension
  453. $file = pathinfo($filepath, PATHINFO_FILENAME);
  454. // Skip indexes and drivers
  455. if ($file === 'index' OR strpos($filepath, 'libraries/drivers') !== FALSE)
  456. continue;
  457. // Add the file
  458. $this->files[$type][$file] = $filepath;
  459. // Parse the file
  460. $this->parse_file($filepath);
  461. }
  462. }
  463. Log::add('debug', 'Kodoc Library initialized');
  464. }
  465. public function get_docs($format = 'html')
  466. {
  467. switch ($format)
  468. {
  469. default:
  470. // Generate HTML via a View
  471. $docs = new View('kodoc_html');
  472. $docs->set('classes', $this->classes)->render();
  473. break;
  474. }
  475. return $docs;
  476. }
  477. protected function parse_file($file)
  478. {
  479. $file = fopen($file, 'r');
  480. $i = 1;
  481. while ($line = fgets($file))
  482. {
  483. if (substr(trim($line), 0, 2) === '/*')
  484. {
  485. // Reset vars
  486. unset($current_doc, $section, $p);
  487. // Prepare for a new doc section
  488. $current_doc = array();
  489. $closing_tag = '*/';
  490. $current_block = 'description';
  491. $p = 0;
  492. // Assign the current doc
  493. $this->current_doc =& $current_doc;
  494. }
  495. elseif (isset($closing_tag))
  496. {
  497. if (substr(trim($line), 0, 1) === '*')
  498. {
  499. // Remove the leading comment
  500. $line = substr(ltrim($line), 2);
  501. if (preg_match('/^([a-z ]+):/i', $line, $matches))
  502. {
  503. $current_block = trim($matches[1]);
  504. }
  505. elseif (isset($current_doc))
  506. {
  507. $line = ltrim($line);
  508. if (preg_match('/^\-\s+(.+)/', $line, $matches))
  509. {
  510. // An unordered list
  511. $current_doc['html'][$current_block]['ul'][] = $matches[1];
  512. }
  513. elseif (preg_match('/^[0-9]+\.\s+(.+)/', $line, $matches))
  514. {
  515. // An ordered list
  516. $current_doc['html'][$current_block]['ol'][] = $matches[1];
  517. }
  518. elseif (preg_match('/^([a-zA-Z ]+)\s+\-\s+(.+)/', $line, $matches))
  519. {
  520. // Definition list
  521. $current_doc['html'][$current_block]['dl'][trim($matches[1])] = trim($matches[2]);
  522. }
  523. else
  524. {
  525. if (trim($line) === '')
  526. {
  527. // Start a new paragraph
  528. $p++;
  529. }
  530. else
  531. {
  532. // Make sure the current paragraph is set
  533. if ( ! isset($current_doc['html'][$current_block]['p'][$p]))
  534. {
  535. $current_doc['html'][$current_block]['p'][$p] = '';
  536. }
  537. // Add to the current paragraph
  538. $current_doc['html'][$current_block]['p'][$p] .= str_replace("\n", ' ', $line);
  539. }
  540. }
  541. }
  542. }
  543. else
  544. {
  545. switch (substr(trim($line), 0, 2))
  546. {
  547. case '//':
  548. case '* ': break;
  549. default:
  550. $line = trim($line);
  551. if ($this->is_function($line) OR $this->is_property($line) OR $this->is_class($line))
  552. {
  553. $clear = NULL;
  554. $this->current_doc =& $clear;
  555. // Restarts searching
  556. unset($closing_tag, $current_doc);
  557. }
  558. break;
  559. }
  560. }
  561. }
  562. $i++;
  563. }
  564. // Close the file
  565. fclose($file);
  566. }
  567. /**
  568. * Method:
  569. * Checks if a line is a class, and parses the data out.
  570. *
  571. * Parameters:
  572. * line - a line from a file
  573. *
  574. * Returns:
  575. * TRUE or FALSE.
  576. */
  577. protected function is_class($line)
  578. {
  579. if (strpos($line, 'class') === FALSE)
  580. {
  581. return FALSE;
  582. }
  583. $line = explode(' ', trim($line));
  584. $class = array
  585. (
  586. 'name' => '',
  587. 'final' => FALSE,
  588. 'extends' => FALSE,
  589. 'drivers' => FALSE
  590. );
  591. if (current($line) === 'final')
  592. {
  593. $class['final'] = (bool) array_shift($line);
  594. }
  595. if (current($line) === 'class')
  596. {
  597. // Remove "class"
  598. array_shift($line);
  599. $name = array_shift($line);
  600. }
  601. if (count($line) > 1)
  602. {
  603. // Remove "extends"
  604. array_shift($line);
  605. $class['extends'] = array_shift($line);
  606. }
  607. if (isset($name))
  608. {
  609. // Add the class into the docs
  610. $this->classes[$name] = array_merge($this->current_doc, $class);
  611. // Set the current class
  612. $this->current_class =& $this->classes[$name];
  613. return TRUE;
  614. }
  615. return FALSE;
  616. }
  617. /**
  618. * Method:
  619. * Checks if a line is a property, and parses the data out.
  620. *
  621. * Parameters:
  622. * line - a line from a file
  623. *
  624. * Returns:
  625. * TRUE or FALSE.
  626. */
  627. protected function is_property($line)
  628. {
  629. static $preg_vis;
  630. if ($preg_vis === NULL)
  631. {
  632. $preg_vis = 'var|'.implode('|', self::$php_visibility);
  633. }
  634. if (strpos($line, '$') === FALSE OR ! preg_match('/^(?:'.$preg_vis.')/', $line))
  635. return FALSE;
  636. $line = explode(' ', $line);
  637. $var = array
  638. (
  639. 'visibility' => FALSE,
  640. 'static' => FALSE,
  641. 'default' => NULL
  642. );
  643. if (current($line) === 'var')
  644. {
  645. // Remove "var"
  646. array_shift($line);
  647. $var['visibility'] = 'public';
  648. }
  649. if (current($line) === 'static')
  650. {
  651. $var['visibility'] = (bool) array_shift($line);
  652. }
  653. // If the visibility is not set, this is not a
  654. if ($var['visibility'] === FALSE)
  655. return FALSE;
  656. if (substr(current($line), 0, 1) === '$')
  657. {
  658. $name = substr(array_shift($line), 1);
  659. $name = rtrim($name, ';');
  660. }
  661. if (count($line) AND current($line) === '=')
  662. {
  663. array_shift($line);
  664. $var['default'] = implode(' ', $line);
  665. }
  666. if (isset($name))
  667. {
  668. // Add property to class
  669. $this->current_class['properties'][$name] = array_merge($this->current_doc, $var);
  670. return TRUE;
  671. }
  672. return FALSE;
  673. }
  674. /**
  675. * Method:
  676. * Checks if a line is a function, and parses the data out.
  677. *
  678. * Parameters:
  679. * line - a line from a file
  680. *
  681. * Returns:
  682. * TRUE or FALSE.
  683. */
  684. protected function is_function($line)
  685. {
  686. if (strpos($line, 'function') === FALSE)
  687. {
  688. return FALSE;
  689. }
  690. $line = explode(' ', trim(strtolower($line)));
  691. $func = array
  692. (
  693. 'final' => FALSE,
  694. 'visibility' => 'public',
  695. 'static' => FALSE,
  696. );
  697. if (current($line) === 'final')
  698. {
  699. $func['final'] = TRUE;
  700. }
  701. if (isset(self::$php_visibility[current($line)]))
  702. {
  703. $func['visibility'] = array_shift($line);
  704. }
  705. if (current($line) === 'static')
  706. {
  707. $func['static'] = (bool) array_shift($line);
  708. }
  709. if (current($line) === 'function')
  710. {
  711. // Remove "function"
  712. array_shift($line);
  713. // Get name
  714. $name = array_shift($line);
  715. // Remove arguments
  716. if (strpos($name, '(') !== FALSE)
  717. {
  718. $name = current(explode('(', $name, 2));
  719. }
  720. // Register the method
  721. $this->current_class['methods'][$name] = array_merge($this->current_doc, $func);
  722. return TRUE;
  723. }
  724. return FALSE;
  725. }
  726. } // End Kodoc