Location: PHPKode > projects > SolarPHP > solar-system-1.1.1/solar/source/solar/Solar/Docs/Phpdoc.php
<?php
/**
 * 
 * Parses a single PHPDoc comment block into summary, narrative, and
 * technical portions.
 * 
 * http://java.sun.com/j2se/javadoc/writingdoccomments/index.html#format
 * 
 * Supported technical tags are ...
 * 
 * For classes ...
 * 
 *     @category name                   # category for the package
 *     @package name                    # class package name
 *     @subpackage name                 # class subpackage name
 *     @copyright info                  # class copyright information
 *     @license uri name text           # licensing information
 *     @version info                    # version information
 * 
 * For properties ...
 * 
 *     @var type [summary]              # class property
 * 
 * For methods ...
 * 
 *     @exception class [summary]       # alias to @throws
 *     @param type [$name] [summary]    # method parameter
 *     @return type [summary]           # method return
 *     @throws class [summary]          # exceptions thrown by the method
 *     @staticvar type $name summary    # use of a static variable within a method
 * 
 * General-purpose ...
 * 
 *     @see name                        # "see also" this element name
 *     @todo summary                    # todo item
 *     @ignore                          # ignore this element
 *     @author name <email> summ        # author name, email, and summary
 *     @deprecated                      # notes the element is deprecated
 *     @deprec                          # alias to @deprecated
 *     @link uri text                   # link to an external URI
 *     @since info                      # element has been available since this time
 *     @example file                    # path to an external example file
 * 
 * Not supported ...
 * 
 *     @global type $globalvar          # description of global variable usage in a function
 *     @name procpagealias              #
 *     @name $globalvaralias            #
 *     @magic                           # phpdoc.de compatibility
 *     @internal                        # private information for advanced developers only
 *     {@code}                          # inline tags
 *     {@docRoot}                       # 
 *     {@inheritDoc}                    # 
 *     {@link}                          # 
 *     {@linkplain}                     # 
 *     {@literal}                       # 
 *     {@value}                         # 
 * 
 * @category Solar
 * 
 * @package Solar_Docs
 * 
 * @author Paul M. Jones <hide@address.com>
 * 
 * @license http://opensource.org/licenses/bsd-license.php BSD
 * 
 * @version $Id: Phpdoc.php 4381 2010-02-14 16:17:22Z pmjones $
 * 
 */
class Solar_Docs_Phpdoc extends Solar_Base
{
    /**
     * 
     * Where the technical information from block tags is stored.
     * 
     * @var array
     * 
     */
    protected $_info = array();
    
    /**
     * 
     * Returns docblock comment parsed into summary, narrative, and
     * technical information portions.
     * 
     * @param string $block The docblock comment text.
     * 
     * @return array An array with keys 'summ', 'narr', and 'tech' 
     * corresponding to the summary, narrative, and technical portions
     * of the docblock.
     * 
     */
    public function parse($block)
    {
        // clear out prior info
        $this->_info = array();
        
        // fix line-endings from windows
        $block = str_replace("\r\n", "\n", $block);
        
        // fix line-endings from mac os 9 and previous
        $block = str_replace("\r", "\n", $block);
        
        // remove the leading comment indicator (slash-star-star)
        $block = preg_replace('/^\s*\/\*\*\s*$/m', '', $block);
        
        // remove the trailing comment indicator (star-slash)
        $block = preg_replace('/^\s*\*\/\s*$/m', '', $block);
        
        // remove the star (and optionally one space) leading each line
        $block = preg_replace('/^\s*\*( )?/m', '', $block);
        
        // wrap with exactly one beginning and ending newline
        $block = "\n" . trim($block) . "\n";
        
        // find narrative and technical portions
        $pos = strpos($block, "\n@");
        if ($pos === false) {
            // apparently no technical section
            $narr = $block;
            $tech = '';
        } else {
            // there appears to be a technical section
            $narr = trim(substr($block, 0, $pos));
            $tech = trim(substr($block, $pos));
        }
        
        // load the formal technical info array
        $this->_loadInfo($tech);
        
        // now take the summary line off the narrative.
        // look for the first sentence-punctuation followed by whitespace,
        // or the first double-newline.
        $punct_ws  = "\n\s*(\n|$)";
        $double_nl = "[\.\?\!](\s|\n|$)";
        preg_match("/^.*(($punct_ws)|($double_nl))/AUms", $narr, $matches);
        if (! empty($matches[0])) {
            $summ = $matches[0];
            $narr = substr($narr, strlen($matches[0]));
        } else {
            $summ = $narr;
            $narr = '';
        }
        
        $summ = str_replace("\n", " ", $summ);
        
        // return the summary, narrative, and technical portions
        return array(
            'summ' => trim($summ),
            'narr' => trim($narr),
            'tech' => $this->_info,
        );
        
    }
    
    /**
     * 
     * Gets the technical information from a docblock comment.
     * 
     * @param string $tech The technical portion of a docblock.
     * 
     * @return array An array of technical information.
     * 
     */
    protected function _loadInfo($tech)
    {
        $tech = "\n" . trim($tech) . "\n";
        
        // split into elements at each "\n@"
        $split = preg_split(
            '/\n\@/m',
            $tech,
            -1,
            PREG_SPLIT_NO_EMPTY
        );
        
        // process each element
        foreach ($split as $line) {
            $line = trim($line);
            $found = preg_match('/(\w+)(?:\s+)?(.*)/ms', $line, $matches);
            if (! $found) {
                continue;
            }
            
            $func = "parse" . ucfirst($matches[1]);
            $line = str_replace("\n", ' ', $matches[2]);
            if (is_callable(array($this, $func))) {
                $this->$func($line);
            }
        }
    }
    
    /**
     * 
     * Parses one or more @param lines into $this->_info.
     * 
     * @param string $line The block line.
     * 
     * @return void
     * 
     */
    public function parseParam($line)
    {
        // string|array $varname Summary or description.
        // string|array $varname
        // string|array Summary or description.
        preg_match('/(\S+)?((\s+\&?\$)(\S+))?((\s+)(.*))?/', $line, $matches);
        
        if (! $matches) {
            return;
        }
        
        // do we have a params array?
        if (empty($this->_info['param'])) {
            $this->_info['param'] = array();
        }
        
        // variable type
        $type = $matches[1];
        
        // if no variable name, name for the param count
        if (empty($matches[4])) {
            $name = count($this->_info['param']);
        } else {
            $name = $matches[4];
        }
        
        // always need a summary element
        if (empty($matches[7])) {
            $summ = '';
        } else {
            $summ = $matches[7];
        }
        
        // save the param
        $this->_info['param'][$name] = array(
            'type' => $type,
            'summ' => $summ,
        );
    }
    
    /**
     * 
     * Parses one @return line into $this->_info.
     * 
     * @param string $line The block line.
     * 
     * @return void
     * 
     */
    public function parseReturn($line)
    {
        $parts = $this->_2part($line);
        if ($parts) {
            $this->_info['return'] = array(
                'type' => $parts[0],
                'summ' => $parts[1],
            );
        }
    }
    
    /**
     * 
     * Parses one or more @todo lines into $this->_info.
     * 
     * @param string $line The block line.
     * 
     * @return void
     * 
     */
    public function parseTodo($line)
    {
        // no parsing needed
        $line = trim($line);
        if ($line) {
            $this->_info['todo'][] = $line;
        }
    }
    
    /**
     * 
     * Parses one or more @see lines into $this->_info.
     * 
     * @param string $line The block line.
     * 
     * @return void
     * 
     */
    public function parseSee($line)
    {
        $line = trim($line);
        if ($line) {
            $this->_info['see'][] = $line;
        }
    }
    
    /**
     * 
     * Parses one @var line into $this->_info.
     * 
     * @param string $line The block line.
     * 
     * @return void
     * 
     */
    public function parseVar($line)
    {
        // @var (single)
        // string|array summary
        $parts = $this->_2part($line);
        if ($parts) {
            $this->_info['var'] = array(
                'type' => $parts[0],
                'summ' => $parts[1],
            );
        }
    }
    
    /**
     * 
     * Parses one or more @throws lines into $this->_info.
     * 
     * @param string $line The block line.
     * 
     * @return void
     * 
     */
    public function parseThrows($line)
    {
        $parts = $this->_2part($line);
        if ($parts) {
            $this->_info['throws'][] = array(
                'type' => $parts[0],
                'summ' => $parts[1],
            );
        }
    }
    
    /**
     * 
     * Parses one or more @exception lines into $this->_info; alias for @throws.
     * 
     * @param string $line The block line.
     * 
     * @return void
     * 
     */
    public function parseException($line)
    {
        return $this->parseThrows($line);
    }
    
    /**
     * 
     * Parses one @category line into $this->_info.
     * 
     * @param string $line The block line.
     * 
     * @return void
     * 
     */
    public function parseCategory($line)
    {
        $this->_info['category'] = $this->_1part($line);
    }
    
    /**
     * 
     * Parses one @package line into $this->_info.
     * 
     * @param string $line The block line.
     * 
     * @return void
     * 
     */
    public function parsePackage($line)
    {
        $parts = $this->_2part($line);
        if ($parts) {
            $this->_info['package'] = array(
                'name' => $parts[0],
                'summ' => $parts[1],
            );
        }
    }
    
    /**
     * 
     * Parses one @subpackage line into $this->_info.
     * 
     * @param string $line The block line.
     * 
     * @return void
     * 
     */
    public function parseSubpackage($line)
    {
        $this->_info['subpackage'] = $this->_1part($line);
    }
    
    /**
     * 
     * Parses one @ignore line into $this->_info.
     * 
     * @param string $line The block line.
     * 
     * @return void
     * 
     */
    public function parseIgnore($line)
    {
        $this->_info['ignore'] = true;
    }
    
    /**
     * 
     * Parses one or more @author lines into $this->_info.
     * 
     * @param string $line The block line.
     * 
     * @return void
     * 
     * @todo Do specialized parsing, looking for <hide@address.com> in the
     * middle of the line.
     * 
     */
    public function parseAuthor($line)
    {
        // no parsing needed
        $line = trim($line);
        if ($line) {
            $this->_info['author'][] = $line;
        }
    }
    
    /**
     * 
     * Parses one @copyright line into $this->_info.
     * 
     * @param string $line The block line.
     * 
     * @return void
     * 
     */
    public function parseCopyright($line)
    {
        // no parsing needed
        $line = trim($line);
        if ($line) {
            $this->_info['copyright'][] = $line;
        }
    }
    
    /**
     * 
     * Parses one @deprecated line into $this->_info.
     * 
     * @param string $line The block line.
     * 
     * @return void
     * 
     */
    public function parseDeprecated($line)
    {
        $this->_info['deprecated'] = $this->_1part($line);
    }
    
    /**
     * 
     * Parses one @deprec line into $this->_info; alias for @deprecated.
     * 
     * @param string $line The block line.
     * 
     * @return void
     * 
     */
    public function parseDeprec($line)
    {
        return $this->parseDeprecated($line);
    }
    
    /**
     * 
     * Parses one @license line into $this->_info.
     * 
     * @param string $line The block line.
     * 
     * @return void
     * 
     */
    public function parseLicense($line)
    {
        $parts = $this->_3part($line);
        if ($parts) {
            $this->_info['license'] = array(
                'uri'  => $parts[0],
                'name' => $parts[1],
                'text' => $parts[2],
            );
        }
    }
    
    /**
     * 
     * Parses one or more @link lines into $this->_info.
     * 
     * @param string $line The block line.
     * 
     * @return void
     * 
     */
    public function parseLink($line)
    {
        $parts = $this->_2part($line);
        if ($parts) {
            $this->_info['link'][] = array(
                'uri'  => $parts[0],
                'text' => $parts[1],
            );
        }
    }
    
    /**
     * 
     * Parses one @since line into $this->_info.
     * 
     * @param string $line The block line.
     * 
     * @return void
     * 
     */
    public function parseSince($line)
    {
        $this->_info['since'] = $this->_1part($line);
    }
    
    /**
     * 
     * Parses one @version line into $this->_info.
     * 
     * @param string $line The block line.
     * 
     * @return void
     * 
     */
    public function parseVersion($line)
    {
        $this->_info['version'] = $this->_1part($line);
    }
    
    /**
     * 
     * Parses one @example line into $this->_info.
     * 
     * @param string $line The block line.
     * 
     * @return void
     * 
     */
    public function parseExample($line)
    {
        $this->_info['example'] = $this->_1part($line);
    }
    
    /**
     * 
     * Parses one or more @staticvar lines into $this->_info.
     * 
     * @param string $line The block line.
     * 
     * @return void
     * 
     * @todo Use @param parsing algorithm.
     * 
     */
    public function parseStaticvar($line)
    {
        $parts = $this->_3part($line);
        if ($parts) {
            $this->_info['staticvar'][] = array(
                'type' => $parts[0],
                'name' => $parts[1],
                'summ' => $parts[2],
            );
        }
    }
    
    /**
     * 
     * Parses one or more @config lines into $this->_info.
     * 
     * @param string $line The block line.
     * 
     * @return void
     * 
     * @todo Use @param parsing algorithm.
     * 
     */
    public function parseConfig($line)
    {
        $parts = $this->_3part($line);
        if ($parts) {
            $name = $parts[1];
            $this->_info['key'][$name] = array(
                'type' => $parts[0],
                'name' => $parts[1],
                'summ' => $parts[2],
            );
        }
    }
    
    /**
     * 
     * Parses a one-part block line; strips everything after the first space.
     * 
     * @param string $line The block line.
     * 
     * @return string
     * 
     */
    protected function _1part($line)
    {
        return preg_replace('/^(\S+)(\s.*)/', '$1', trim($line));
    }
    
    /**
     * 
     * Parses a two-part block line; part 2 is optional.
     * 
     * @param string $line The block line.
     * 
     * @return array An array of the parts.
     * 
     */
    protected function _2part($line)
    {
        preg_match('/(\S+)((\s+)(.*))?/', $line, $matches);
        if (empty($matches)) {
            return array();
        }
        if (empty($matches[4])) {
            $matches[4] = '';
        }
        return array(
            $matches[1],
            $matches[4],
        );
    }
    
    /**
     * 
     * Parses a three-part block line; parts 2 and 3 are optional.
     * 
     * @param string $line The block line.
     * 
     * @return array An array of the parts.
     * 
     */
    protected function _3part($line)
    {
        preg_match('/([\S]+)((\s+)(\S+))?((\s+)(.*))?/', $line, $matches);
        if (empty($matches)) {
            return array();
        }
        if (empty($matches[4])) {
            $matches[4] = '';
        }
        if (empty($matches[7])) {
            $matches[7] = '';
        }
        return array(
            $matches[1],
            $matches[4],
            $matches[7],
        );
    }
}
Return current item: SolarPHP