123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469 |
- <?php
- /**
- * Documentation generator.
- *
- * @package Kohana/Userguide
- * @category Base
- * @author Kohana Team
- * @copyright (c) Kohana Team
- * @license https://koseven.ga/LICENSE.md
- */
- class Kohana_Kodoc {
- /**
- * @var string PCRE fragment for matching 'Class', 'Class::method', 'Class::method()' or 'Class::$property'
- */
- public static $regex_class_member = '((\w++)(?:::(\$?\w++))?(?:\(\))?)';
- /**
- * Make a class#member API link using an array of matches from [Kodoc::$regex_class_member]
- *
- * @param array $matches array( 1 => link text, 2 => class name, [3 => member name] )
- * @return string
- */
- public static function link_class_member($matches)
- {
- $link = $matches[1];
- $class = $matches[2];
- $member = NULL;
- if (isset($matches[3]))
- {
- // If the first char is a $ it is a property, e.g. Kohana::$base_url
- if ($matches[3][0] === '$')
- {
- $member = '#property:'.substr($matches[3], 1);
- }
- elseif (preg_match('/^[A-Z_\x7f-\xff][A-Z0-9_\x7f-\xff]*$/', $matches[3]))
- {
- $member = '#constant:'.substr($matches[3],0);
- }
- else
- {
- $member = '#'.$matches[3];
- }
- }
- return HTML::anchor(Route::get('docs/api')->uri(['class' => $class]).$member, $link, NULL, NULL, TRUE);
- }
- public static function factory($class)
- {
- return new Kodoc_Class($class);
- }
- /**
- * Creates an html list of all classes sorted by category (or package if no category)
- *
- * @return string the html for the menu
- */
- public static function menu()
- {
- $classes = Kodoc::classes();
- ksort($classes);
- $menu = [];
- $route = Route::get('docs/api');
- foreach ($classes as $class)
- {
- if (Kodoc::is_transparent($class, $classes))
- continue;
- $class = Kodoc_Class::factory($class);
- // Test if we should show this class
- if ( ! Kodoc::show_class($class))
- continue;
- $link = HTML::anchor($route->uri(['class' => $class->class->name]), $class->class->name);
- if (isset($class->tags['package']))
- {
- foreach ($class->tags['package'] as $package)
- {
- if (isset($class->tags['category']))
- {
- foreach ($class->tags['category'] as $category)
- {
- $menu[$package][$category][] = $link;
- }
- }
- else
- {
- $menu[$package]['Base'][] = $link;
- }
- }
- }
- else
- {
- $menu['[Unknown]']['Base'][] = $link;
- }
- }
- // Sort the packages
- ksort($menu);
- return View::factory('userguide/api/menu')
- ->bind('menu', $menu);
- }
- /**
- * Returns an array of all the classes available, built by listing all files in the classes folder.
- *
- * @param array array of files, obtained using Kohana::list_files
- * @return array an array of all the class names
- */
- public static function classes(array $list = NULL)
- {
- if ($list === NULL)
- {
- $list = Kohana::list_files('classes');
- }
- $classes = [];
- // This will be used a lot!
- $ext_length = strlen(EXT);
- foreach ($list as $name => $path)
- {
- if (is_array($path))
- {
- $classes += Kodoc::classes($path);
- }
- elseif (substr($name, -$ext_length) === EXT)
- {
- // Remove "classes/" and the extension
- $class = substr($name, 8, -$ext_length);
- // Convert slashes to underscores
- $class = str_replace(DIRECTORY_SEPARATOR, '_', $class);
- $classes[$class] = $class;
- }
- }
- return $classes;
- }
- /**
- * Get all classes and methods of files in a list.
- *
- * > I personally don't like this as it was used on the index page. Way too much stuff on one page. It has potential for a package index page though.
- * > For example: class_methods( Kohana::list_files('classes/sprig') ) could make a nice index page for the sprig package in the api browser
- * > ~bluehawk
- *
- */
- public static function class_methods(array $list = NULL)
- {
- $list = Kodoc::classes($list);
- $classes = [];
- foreach ($list as $class)
- {
- // Skip transparent extension classes
- if (Kodoc::is_transparent($class))
- continue;
- $_class = new ReflectionClass($class);
- $methods = [];
- foreach ($_class->getMethods() as $_method)
- {
- $declares = $_method->getDeclaringClass()->name;
- // Remove the transparent prefix from declaring classes
- if ($child = Kodoc::is_transparent($declares))
- {
- $declares = $child;
- }
- if ($declares === $_class->name OR $declares === "Core")
- {
- $methods[] = $_method->name;
- }
- }
- sort($methods);
- $classes[$_class->name] = $methods;
- }
- return $classes;
- }
- /**
- * Generate HTML for the content of a tag.
- *
- * @param string $tag Name of the tag without @
- * @param string $text Content of the tag
- * @return string HTML
- */
- public static function format_tag($tag, $text)
- {
- if ($tag === 'license')
- {
- if (strpos($text, '://') !== FALSE)
- return HTML::anchor($text);
- }
- elseif ($tag === 'link')
- {
- $split = preg_split('/\s+/', $text, 2);
- return HTML::anchor(
- $split[0],
- isset($split[1]) ? $split[1] : $split[0]
- );
- }
- elseif ($tag === 'copyright')
- {
- // Convert the copyright symbol
- return str_replace('(c)', '©', $text);
- }
- elseif ($tag === 'throws')
- {
- $route = Route::get('docs/api');
- if (preg_match('/^(\w+)\W(.*)$/D', $text, $matches))
- {
- return HTML::anchor(
- $route->uri(['class' => $matches[1]]),
- $matches[1]
- ).' '.$matches[2];
- }
- return HTML::anchor(
- $route->uri(['class' => $text]),
- $text
- );
- }
- elseif ($tag === 'see' OR $tag === 'uses')
- {
- if (preg_match('/^'.Kodoc::$regex_class_member.'/', $text, $matches))
- return Kodoc::link_class_member($matches);
- }
- return $text;
- }
- /**
- * Parse a comment to extract the description and the tags
- *
- * [!!] Converting the output to HTML in this method is deprecated in 3.3
- *
- * @param string $comment The DocBlock to parse
- * @param boolean $html Whether or not to convert the return values
- * to HTML (deprecated)
- * @return array array(string $description, array $tags)
- */
- public static function parse($comment, $html = TRUE)
- {
- // Normalize all new lines to \n
- $comment = str_replace(["\r\n", "\n"], "\n", $comment);
- // Split into lines while capturing without leading whitespace
- preg_match_all('/^\s*\* ?(.*)\n/m', $comment, $lines);
- // Tag content
- $tags = [];
- /**
- * Process a tag and add it to $tags
- *
- * @param string $tag Name of the tag without @
- * @param string $text Content of the tag
- * @return void
- */
- $add_tag = function ($tag, $text) use ($html, & $tags)
- {
- // Don't show @access lines, they are shown elsewhere
- if ($tag !== 'access')
- {
- if ($html)
- {
- $text = Kodoc::format_tag($tag, $text);
- }
- // Add the tag
- $tags[$tag][] = $text;
- }
- };
- $comment = $tag = NULL;
- $end = count($lines[1]) - 1;
- foreach ($lines[1] as $i => $line)
- {
- // Search this line for a tag
- if (preg_match('/^@(\S+)\s*(.+)?$/', $line, $matches))
- {
- if ($tag)
- {
- // Previous tag is finished
- $add_tag($tag, $text);
- }
- $tag = $matches[1];
- $text = isset($matches[2]) ? $matches[2] : '';
- if ($i === $end)
- {
- // No more lines
- $add_tag($tag, $text);
- }
- }
- elseif ($tag)
- {
- // This is the continuation of the previous tag
- $text .= "\n".$line;
- if ($i === $end)
- {
- // No more lines
- $add_tag($tag, $text);
- }
- }
- else
- {
- $comment .= "\n".$line;
- }
- }
- $comment = trim($comment, "\n");
- if ($comment AND $html)
- {
- // Parse the comment with Markdown
- $comment = Kodoc_Markdown::markdown($comment);
- }
- return [$comment, $tags];
- }
- /**
- * Get the source of a function
- *
- * @param string the filename
- * @param int start line?
- * @param int end line?
- */
- public static function source($file, $start, $end)
- {
- if ( ! $file) return FALSE;
- $file = file($file, FILE_IGNORE_NEW_LINES);
- $file = array_slice($file, $start - 1, $end - $start + 1);
- if (preg_match('/^(\s+)/', $file[0], $matches))
- {
- $padding = strlen($matches[1]);
- foreach ($file as & $line)
- {
- $line = substr($line, $padding);
- }
- }
- return implode("\n", $file);
- }
- /**
- * Test whether a class should be shown, based on the api_packages config option
- *
- * @param Kodoc_Class the class to test
- * @return bool whether this class should be shown
- */
- public static function show_class(Kodoc_Class $class)
- {
- $api_packages = Kohana::$config->load('userguide.api_packages');
- // If api_packages is true, all packages should be shown
- if ($api_packages === TRUE)
- return TRUE;
- // Get the package tags for this class (as an array)
- $packages = Arr::get($class->tags, 'package', ['None']);
- $show_this = FALSE;
- // Loop through each package tag
- foreach ($packages as $package)
- {
- // If this package is in the allowed packages, set show this to true
- if (in_array($package, explode(',', $api_packages)))
- $show_this = TRUE;
- }
- return $show_this;
- }
- /**
- * Checks whether a class is a transparent extension class or not.
- *
- * This method takes an optional $classes parameter, a list of all defined
- * class names. If provided, the method will return false unless the extension
- * class exists. If not, the method will only check known transparent class
- * prefixes.
- *
- * Transparent prefixes are defined in the userguide.php config file:
- *
- * 'transparent_prefixes' => array(
- * 'Kohana' => TRUE,
- * );
- *
- * Module developers can therefore add their own transparent extension
- * namespaces and exclude them from the userguide.
- *
- * @param string $class The name of the class to check for transparency
- * @param array $classes An optional list of all defined classes
- * @return false If this is not a transparent extension class
- * @return string The name of the class that extends this (in the case provided)
- * @throws InvalidArgumentException If the $classes array is provided and the $class variable is not lowercase
- */
- public static function is_transparent($class, $classes = NULL)
- {
- static $transparent_prefixes = NULL;
- if ( ! $transparent_prefixes)
- {
- $transparent_prefixes = Kohana::$config->load('userguide.transparent_prefixes');
- }
- // Split the class name at the first underscore
- $segments = explode('_',$class,2);
- if ((count($segments) == 2) AND (isset($transparent_prefixes[$segments[0]])))
- {
- if ($segments[1] === 'Core')
- {
- // Cater for Module extends Module_Core naming
- $child_class = $segments[0];
- }
- else
- {
- // Cater for Foo extends Module_Foo naming
- $child_class = $segments[1];
- }
- // It is only a transparent class if the unprefixed class also exists
- if ($classes AND ! isset($classes[$child_class]))
- return FALSE;
- // Return the name of the child class
- return $child_class;
- }
- else
- {
- // Not a transparent class
- return FALSE;
- }
- }
- } // End Kodoc
|