Commit: f94bc8ae4cce0be008104fdd7589637fac93aa85

Author: Nate Abele | Date: 2010-07-11 23:05:00 -0400
Moving `\analysis\DocExtractor` to `\docs\Extractor`. Tweaking layout and animation of hiding/showing source code.
diff --git a/controllers/BrowserController.php b/controllers/BrowserController.php index 92b66af..3eb0418 100644 --- a/controllers/BrowserController.php +++ b/controllers/BrowserController.php @@ -8,10 +8,10 @@ namespace li3_docs\controllers; -use \Exception; -use \DirectoryIterator; -use \lithium\core\Libraries; -use \lithium\analysis\Inspector; +use Exception; +use DirectoryIterator; +use lithium\core\Libraries; +use lithium\analysis\Inspector; /** * This is the Lithium API browser controller. This class introspects your application's libraries, @@ -20,7 +20,7 @@ use \lithium\analysis\Inspector; class BrowserController extends \lithium\action\Controller { /** - * The `DocExtractor` class dependency, which can be replaced with a proxy file to read from + * The `Extractor` class dependency, which can be replaced with a proxy file to read from * a cache or database. * * @var array @@ -28,7 +28,7 @@ class BrowserController extends \lithium\action\Controller { protected $_classes = array( 'media' => 'lithium\net\http\Media', 'response' => 'lithium\action\Response', - 'docExtractor' => 'li3_docs\extensions\analysis\DocExtractor' + 'extractor' => 'li3_docs\extensions\docs\Extractor' ); /** @@ -65,15 +65,15 @@ class BrowserController extends \lithium\action\Controller { * current entity. */ public function view() { - $docExtractor = $this->_classes['docExtractor']; + $extractor = $this->_classes['extractor']; - if (!$library = $docExtractor::library($this->request->lib)) { + if (!$library = $extractor::library($this->request->lib)) { return $this->render('../errors/not_found'); } $name = $library['prefix'] . join('\\', func_get_args()); $options = array('namespaceDoc' => $this->docFile); - $object = $docExtractor::get($this->request->lib, $name, $options); + $object = $extractor::get($this->request->lib, $name, $options); $crumbs = $this->_crumbs($object); return compact('name', 'library', 'object', 'crumbs'); } diff --git a/extensions/analysis/DocExtractor.php b/extensions/analysis/DocExtractor.php deleted file mode 100644 index 490066f..0000000 --- a/extensions/analysis/DocExtractor.php +++ /dev/null @@ -1,164 +0,0 @@ -<?php -/** - * Lithium: the most rad php framework - * - * @copyright Copyright 2009, Union of RAD (http://union-of-rad.org) - * @license http://opensource.org/licenses/bsd-license.php The BSD License - */ - -namespace li3_docs\extensions\analysis; - -use \Exception; -use \lithium\core\Libraries; -use \lithium\util\Inflector; -use \lithium\analysis\Inspector; - -class DocExtractor extends \lithium\core\StaticObject { - - public static function get($library, $identifier, array $options = array()) { - $defaults = array('namespaceDoc' => null); - $options += $defaults; - $data = Inspector::info($identifier); - - $proto = compact('identifier', 'library') + array( - 'name' => null, - 'type' => Inspector::type($identifier), - 'info' => array(), - 'classes' => null, - 'methods' => null, - 'properties' => null, - 'parent' => null, - 'children' => null, - 'source' => null, - 'subClasses' => array(), - 'description' => isset($data['description']) ? $data['description'] : null, - 'text' => isset($data['text']) ? $data['text'] : null, - ); - $format = "_{$proto['type']}"; - $data = static::$format($proto, (array) $data, $options); - - foreach (array('text', 'description') as $key) { - $data[$key] = static::_embedCode($data[$key]); - } - return $data; - } - - public static function library($name, array $options = array()) { - $defaults = array('docs' => 'config/docs.json'); - $options += $defaults; - - if (!$config = Libraries::get($name)) { - return array(); - } - - if (file_exists($file = "{$config['path']}/{$options['docs']}")) { - $config += (array) json_decode(file_get_contents($file)); - } - return $config + array('title' => Inflector::humanize($name)); - } - - protected static function _method(array $object, array $data, array $options = array()) { - if (!$data) { - return array(); - } - $lines = Inspector::lines($data['file'], range($data['start'], $data['end'])); - $object = array('source' => join("\n", $lines)) + $object; - $object += array('tags' => isset($data['tags']) ? $data['tags'] : array()); - - if (isset($object['tags']['return'])) { - list($type, $text) = explode(' ', $object['tags']['return'], 2) + array('', ''); - $object['return'] = compact('type', 'text'); - } - return $object; - } - - protected static function _namespace(array $object, array $data, array $options = array()) { - $library = $object['library']; - $identifier = $object['identifier']; - $config = Libraries::get($library); - - $path = preg_replace('/^' . preg_quote($config['prefix'], '/') . '/', '', $identifier); - $path = '/' . str_replace('\\', '/', $path); - $object['children'] = array(); - - foreach (Libraries::find($library, array('namespaces' => true) + compact('path')) as $c) { - $libPath = Libraries::path($c, array('dirs' => true)); - $type = is_dir($libPath) ? 'namespace' : 'class'; - $object['children'][$c] = $type; - } - - $path = $config['path'] . rtrim($path, '/'); - $doc = "{$path}/{$options['namespaceDoc']}"; - $object['text'] = file_exists($doc) ? file_get_contents($doc) : null; - return $object; - } - - protected static function _class(array $object, array $data, array $options = array()) { - $identifier = $object['identifier']; - $proto = array( - 'parent' => get_parent_class($identifier), - 'methods' => Inspector::methods($identifier, null, array('public' => false)), - 'properties' => get_class_vars($identifier) - ); - - if ($proto['parent']) { - $parentProps = get_class_vars($proto['parent']); - $proto['properties'] = array_diff_key($proto['properties'], $parentProps); - } - $classes = Libraries::find($object['library'], array('recursive' => true)); - - $proto['subClasses'] = array_filter($classes, function($class) use ($identifier) { - if (preg_match('/\\\(libraries)\\\/', $class)) { - return false; - } - try { - return get_parent_class($class) == $identifier; - } catch (Exception $e) { - return false; - } - }); - sort($proto['subClasses']); - return $proto + $object + array('tags' => isset($data['tags']) ? $data['tags'] : array()); - } - - protected static function _property(array $object, array $data, array $options = array()) { - return $object + $data; - } - - /** - * Replaces class and method references with code snippets pulled from the class. - * - * @param string $text - * @param array $options - * @return string - */ - protected static function _embedCode($text, array $options = array()) { - $defaults = array('pad' => "\t"); - $options += $defaults; - $regex = '(?P<class>[A-Za-z0-9_\\\]+)::(?P<method>[A-Za-z0-9_]+)\((?P<lines>[0-9-]+)'; - - if (!preg_match_all("/\{\{\{\s*(embed:{$regex}\))\s*\}\}\}/", $text, $matches)) { - return $text; - } - - foreach ($matches['class'] as $i => $class) { - $methods = array($matches['method'][$i]); - $markers = Inspector::methods($class, 'extents', compact('methods')); - $methodStart = $markers[current($methods)][0]; - $replace = $matches[0][$i]; - - list($start, $end) = explode('-', $matches['lines'][$i]); - $lines = range(intval($start) + $methodStart, intval($end) + $methodStart); - $lines = Inspector::lines($class, $lines); - - $pad = substr_count(current($lines), $options['pad'], 0, 4); - $lines = array_map('substr', $lines, array_fill(0, count($lines), 2)); - - $code = '{{{' . join("\n", $lines) . '}}}'; - $text = str_replace($replace, $code, $text); - } - return $text; - } -} - -?> \ No newline at end of file diff --git a/extensions/docs/Extractor.php b/extensions/docs/Extractor.php new file mode 100644 index 0000000..992b56f --- /dev/null +++ b/extensions/docs/Extractor.php @@ -0,0 +1,248 @@ +<?php +/** + * Lithium: the most rad php framework + * + * @copyright Copyright 2009, Union of RAD (http://union-of-rad.org) + * @license http://opensource.org/licenses/bsd-license.php The BSD License + */ + +namespace li3_docs\extensions\docs; + +use Exception; +use lithium\core\Libraries; +use lithium\util\Inflector; +use lithium\analysis\Docblock; +use lithium\analysis\Inspector; + +class Extractor extends \lithium\core\StaticObject { + + public static function get($library, $identifier, array $options = array()) { + $defaults = array('namespaceDoc' => null); + $options += $defaults; + $path = Libraries::path($identifier); + + if (file_exists($path) && !static::_isClassFile($path)) { + return static::_file(compact('library', 'path', 'identifier'), $options); + } + $data = Inspector::info($identifier); + + $proto = compact('identifier', 'library') + array( + 'name' => null, + 'type' => Inspector::type($identifier), + 'info' => array(), + 'classes' => null, + 'methods' => null, + 'properties' => null, + 'parent' => null, + 'children' => null, + 'source' => null, + 'subClasses' => array(), + 'description' => isset($data['description']) ? $data['description'] : null, + 'text' => isset($data['text']) ? $data['text'] : null, + ); + $format = "_{$proto['type']}"; + $data = static::$format($proto, (array) $data, $options); + + foreach (array('text', 'description') as $key) { + $data[$key] = static::_embedCode($data[$key]); + } + return $data; + } + + public static function library($name, array $options = array()) { + $defaults = array('docs' => 'config/docs.json'); + $options += $defaults; + + if (!$config = Libraries::get($name)) { + return array(); + } + + if (file_exists($file = "{$config['path']}/{$options['docs']}")) { + $config += (array) json_decode(file_get_contents($file)); + } + return $config + array('title' => Inflector::humanize($name)); + } + + protected static function _method(array $object, array $data, array $options = array()) { + if (!$data) { + return array(); + } + $lines = Inspector::lines($data['file'], range($data['start'], $data['end'])); + $object = array('source' => join("\n", $lines)) + $object; + $object += array('tags' => isset($data['tags']) ? $data['tags'] : array()); + + if (isset($object['tags']['return'])) { + list($type, $text) = explode(' ', $object['tags']['return'], 2) + array('', ''); + $object['return'] = compact('type', 'text'); + } + return $object; + } + + protected static function _namespace(array $object, array $data, array $options = array()) { + $library = $object['library']; + $identifier = $object['identifier']; + $config = Libraries::get($library); + + $path = preg_replace('/^' . preg_quote($config['prefix'], '/') . '/', '', $identifier); + $path = '/' . str_replace('\\', '/', $path); + $object['children'] = array(); + + foreach (Libraries::find($library, array('namespaces' => true) + compact('path')) as $c) { + $libPath = Libraries::path($c, array('dirs' => true)); + $type = is_dir($libPath) ? 'namespace' : 'class'; + $object['children'][$c] = $type; + } + + $path = $config['path'] . rtrim($path, '/'); + $doc = "{$path}/{$options['namespaceDoc']}"; + $object['text'] = file_exists($doc) ? file_get_contents($doc) : null; + return $object; + } + + protected static function _class(array $object, array $data, array $options = array()) { + $identifier = $object['identifier']; + $proto = array( + 'parent' => get_parent_class($identifier), + 'methods' => Inspector::methods($identifier, null, array('public' => false)), + 'properties' => get_class_vars($identifier) + ); + + if ($proto['parent']) { + $parentProps = get_class_vars($proto['parent']); + $proto['properties'] = array_diff_key($proto['properties'], $parentProps); + } + $classes = Libraries::find($object['library'], array('recursive' => true)); + + $proto['subClasses'] = array_filter($classes, function($class) use ($identifier) { + if (preg_match('/\\\(libraries)\\\/', $class)) { + return false; + } + try { + return get_parent_class($class) == $identifier; + } catch (Exception $e) { + return false; + } + }); + sort($proto['subClasses']); + return $proto + $object + array('tags' => isset($data['tags']) ? $data['tags'] : array()); + } + + protected static function _property(array $object, array $data, array $options = array()) { + return $object + $data; + } + + protected static function _file(array $object, array $options = array()) { + $identifier = $object['identifier']; + $config = Libraries::get($object['library']); + $ds = DIRECTORY_SEPARATOR; + + $data = compact('identifier') + array( + 'name' => '', + 'type' => 'file', + 'info' => static::_codeToDoc(file_get_contents($object['path'])), + 'children' => array(), + 'subClasses' => array() + ); + $subPath = dirname($object['path']) . $ds . basename($object['path'], '.php'); + + if (is_dir($subPath)) { + $path = preg_replace('/^' . preg_quote($config['prefix'], '/') . '/', '', $identifier); + $path = '/' . str_replace('\\', '/', $path); + $searchOpts = array('recursive' => true, 'namespaces' => true, 'filter' => false); + + foreach (Libraries::find($object['library'], compact('path') + $searchOpts) as $file) { + $libPath = Libraries::path($file, array('dirs' => true)); + $type = is_dir($libPath) ? 'namespace' : 'class'; + $data['children'][$file] = $type; + } + } + return $data; + } + + protected static function _codeToDoc($code) { + $tokens = token_get_all($code); + $display = array(); + $current = ''; + + foreach ($tokens as $i => $token) { + if ($i == 0 || ($token[0] == T_CLOSE_TAG && ($i + 1) == count($tokens))) { + continue; + } + if ($token[0] == T_DOC_COMMENT) { + if (preg_match('/@copyright/', $token[1])) { + continue; + } + if (!trim($current)) { + $current = ''; + } + if ($current) { + $display[] = "{{{\n{$current}}}}"; + $current = ''; + } + $doc = Docblock::comment($token[1]); + + foreach (array('text', 'description') as $key) { + $doc[$key] = static::_embedCode($doc[$key]); + } + $display[] = $doc; + continue; + } + $current .= (is_array($token) ? $token[1] : $token); + } + if ($current) { + $display[] = "{{{\n{$current}}}}"; + } + return $display; + } + + /** + * Replaces class and method references with code snippets pulled from the class. + * + * @param string $text + * @param array $options + * @return string + */ + protected static function _embedCode($text, array $options = array()) { + $defaults = array('pad' => "\t"); + $options += $defaults; + $regex = '(?P<class>[A-Za-z0-9_\\\]+)::(?P<method>[A-Za-z0-9_]+)\((?P<lines>[0-9-]+)'; + + if (!preg_match_all("/\{\{\{\s*(embed:{$regex}\))\s*\}\}\}/", $text, $matches)) { + return $text; + } + + foreach ($matches['class'] as $i => $class) { + $methods = array($matches['method'][$i]); + $markers = Inspector::methods($class, 'extents', compact('methods')); + $methodStart = $markers[current($methods)][0]; + $replace = $matches[0][$i]; + + list($start, $end) = explode('-', $matches['lines'][$i]); + $lines = range(intval($start) + $methodStart, intval($end) + $methodStart); + $lines = Inspector::lines($class, $lines); + + $pad = substr_count(current($lines), $options['pad'], 0, 4); + $lines = array_map('substr', $lines, array_fill(0, count($lines), 2)); + + $code = '{{{' . join("\n", $lines) . '}}}'; + $text = str_replace($replace, $code, $text); + } + return $text; + } + + protected static function _isClassFile($path) { + $tokens = token_get_all(file_get_contents($path)); + + for ($i = 2; $i < count($tokens); $i++) { + if (!($tokens[$i - 2][0] == T_CLASS && $tokens[$i - 1][0] == T_WHITESPACE)) { + continue; + } + if ($tokens[$i][0] == T_STRING) { + return true; + } + } + return false; + } +} + +?> \ No newline at end of file diff --git a/views/browser/view.html.php b/views/browser/view.html.php index a6eb314..e7a2cf4 100644 --- a/views/browser/view.html.php +++ b/views/browser/view.html.php @@ -13,14 +13,9 @@ $this->title($namespace); ); ?> <?php // Related items ?> -<?php if (isset($object['tags']['see'])) { ?> - <h4><?=$t('Related', array('scope' => 'li3_docs')); ?></h4> - <ul class="related"> - <?php foreach ((array) $object['tags']['see'] as $name) { ?> - <li><?=$this->html->link($name, $this->docs->identifierUrl($name)); ?></li> - <?php } ?> - </ul> -<?php } ?> +<?=$this->view()->render( + array('element' => 'related'), compact('object', 'scope'), array('library' => 'li3_docs') +); ?> <?=$this->view()->render( array('element' => 'links'), @@ -41,10 +36,14 @@ $this->title($namespace); <?php // Method source ?> <?php if (isset($object['source'])) { ?> - <div class="source-wrapper"> - <pre class="source-code"> - <code class="php"><?php echo $h($object['source']); ?></code> - </pre> + <div class="source-display"> + <div class="source-wrapper"> + <pre class="source-code"> + <code class="php"><?=$object['source']; ?></code> + </pre> + </div> + <button class="source-toggle"> + <?=$t('Show source', array('scope' => 'li3_docs')); ?> + </button> </div> - <button class="source-toggle"><?=$t('Show source', array('scope' => 'li3_docs')); ?></button> <?php } ?> \ No newline at end of file diff --git a/views/elements/file.html.php b/views/elements/file.html.php new file mode 100644 index 0000000..d229a19 --- /dev/null +++ b/views/elements/file.html.php @@ -0,0 +1,42 @@ +<?php if ($object['children']) { ?> + <div class="contents"> + <h4><?=$t('Package contents', array('scope' => 'li3_docs')); ?></h4> + <ul class="children"> + <?php foreach ($object['children'] as $class => $type) { ?> + <?php + $parts = explode('\\', $class); + $url = $this->docs->identifierUrl($class); + ?> + <li class="<?=$type; ?>"> + <?=$this->html->link(basename(end($parts)), $url); ?> + </li> + <?php } ?> + </ul> + </div> +<?php } ?> + + +<?php foreach ($object['info'] as $info) { ?> + <?php if (is_string($info)) { ?> + <p class="markdown"> + <?=$info; ?> + </p> + <?php } else { ?> + <p class="markdown"><?=$info['description']; ?></p> + <p class="markdown"><?=$info['text']; ?></p> + <?php if (isset($info['tags']['see'])) { ?> + <?=$this->view()->render( + array('element' => 'related'), + compact('scope') + array('object' => $info), + array('library' => 'li3_docs') + ); ?> + <?php } ?> + <?php if (isset($info['tags']['link'])) { ?> + <?=$this->view()->render( + array('element' => 'links'), + compact('scope') + array('object' => $info), + array('library' => 'li3_docs') + ); ?> + <?php } ?> + <?php } ?> +<?php } ?> diff --git a/views/elements/related.html.php b/views/elements/related.html.php new file mode 100644 index 0000000..fbd79b5 --- /dev/null +++ b/views/elements/related.html.php @@ -0,0 +1,8 @@ +<?php if (isset($object['tags']['see'])) { ?> + <h4><?=$t('Related', array('scope' => 'li3_docs')); ?></h4> + <ul class="related"> + <?php foreach ((array) $object['tags']['see'] as $name) { ?> + <li><?=$this->html->link($name, $this->docs->identifierUrl($name)); ?></li> + <?php } ?> + </ul> +<?php } ?> diff --git a/views/layouts/default.html.php b/views/layouts/default.html.php index 1ec31d0..e7cc3e3 100644 --- a/views/layouts/default.html.php +++ b/views/layouts/default.html.php @@ -6,8 +6,8 @@ * @license http://opensource.org/licenses/bsd-license.php The BSD License */ -use \lithium\core\Environment; -use \lithium\g11n\Locale; +use lithium\g11n\Locale; +use lithium\core\Environment; ?> <!doctype html> @@ -27,7 +27,7 @@ use \lithium\g11n\Locale; <?php //$this->_view->render(array('element' => 'locale_navigation')); ?> <div id="container"> <div id="header"> - <h1><?php echo $this->html->link($t('Lithium API', array('scope' => 'li3_docs')), array( + <h1><?=$this->html->link($t('Lithium API', array('scope' => 'li3_docs')), array( 'library' => 'li3_docs', 'controller' => 'browser', 'action' => 'index' )); ?></h1> <ul class="crumbs"> @@ -46,7 +46,7 @@ use \lithium\g11n\Locale; </div> <div id="content"> - <?php echo $this->content; ?> + <?=$this->content; ?> </div> </div> <div id="footer-spacer"></div> @@ -54,7 +54,7 @@ use \lithium\g11n\Locale; <div id="footer"> <p class="copyright"> - <?=$t('Pretty much everything is © {:year} and beyond, the Union of Rad', array( + <?=$t('Pretty much everything is © {:year} and beyond, the Union of RAD', array( 'year' => date('Y'), 'scope' => 'li3_docs' )); ?> @@ -83,16 +83,13 @@ use \lithium\g11n\Locale; }); $('.source-toggle').bind('click', function() { - visible = $(codeSelector).is(':visible'); - - if (visible) { + if ($(codeSelector).is(':visible')) { text = '<?=$t('Show source', array('scope' => 'li3_docs')); ?>'; } else { text = '<?=$t('Hide source', array('scope' => 'li3_docs')); ?>'; } - $(this).text(text); - - visible ? $(codeSelector).slideUp() : $(codeSelector).slideDown(); + $button = $(this); + $(codeSelector).slideToggle(400, function() { $button.text(text); }); }); hljs.initHighlighting(); }); diff --git a/webroot/css/li3_docs.css b/webroot/css/li3_docs.css index 6c67bd0..f0c9bb7 100644 --- a/webroot/css/li3_docs.css +++ b/webroot/css/li3_docs.css @@ -147,6 +147,9 @@ span.parent { span.type { color: #454545; } +.source-display { + margin-top: 20px; +} #footer p { color: #fff; } \ No newline at end of file