DocblockFormatter.php 4.48 KB
Newer Older
Rinto committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168
<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2015 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\Formatter;

use Psy\Util\Docblock;
use Symfony\Component\Console\Formatter\OutputFormatter;

/**
 * A pretty-printer for docblocks.
 */
class DocblockFormatter implements Formatter
{
    private static $vectorParamTemplates = array(
        'type' => 'info',
        'var'  => 'strong',
    );

    /**
     * Format a docblock.
     *
     * @param \Reflector $reflector
     *
     * @return string Formatted docblock
     */
    public static function format(\Reflector $reflector)
    {
        $docblock = new Docblock($reflector);
        $chunks   = array();

        if (!empty($docblock->desc)) {
            $chunks[] = '<comment>Description:</comment>';
            $chunks[] = self::indent(OutputFormatter::escape($docblock->desc), '  ');
            $chunks[] = '';
        }

        if (!empty($docblock->tags)) {
            foreach ($docblock::$vectors as $name => $vector) {
                if (isset($docblock->tags[$name])) {
                    $chunks[] = sprintf('<comment>%s:</comment>', self::inflect($name));
                    $chunks[] = self::formatVector($vector, $docblock->tags[$name]);
                    $chunks[] = '';
                }
            }

            $tags = self::formatTags(array_keys($docblock::$vectors), $docblock->tags);
            if (!empty($tags)) {
                $chunks[] = $tags;
                $chunks[] = '';
            }
        }

        return rtrim(implode("\n", $chunks));
    }

    /**
     * Format a docblock vector, for example, `@throws`, `@param`, or `@return`.
     *
     * @see DocBlock::$vectors
     *
     * @param array $vector
     * @param array $lines
     *
     * @return string
     */
    private static function formatVector(array $vector, array $lines)
    {
        $template = array(' ');
        foreach ($vector as $type) {
            $max = 0;
            foreach ($lines as $line) {
                $chunk = $line[$type];
                $cur = empty($chunk) ? 0 : strlen($chunk) + 1;
                if ($cur > $max) {
                    $max = $cur;
                }
            }

            $template[] = self::getVectorParamTemplate($type, $max);
        }
        $template = implode(' ', $template);

        return implode("\n", array_map(function ($line) use ($template) {
            $escaped = array_map(array('Symfony\Component\Console\Formatter\OutputFormatter', 'escape'), $line);

            return rtrim(vsprintf($template, $escaped));
        }, $lines));
    }

    /**
     * Format docblock tags.
     *
     * @param array $skip Tags to exclude
     * @param array $tags Tags to format
     *
     * @return string formatted tags
     */
    private static function formatTags(array $skip, array $tags)
    {
        $chunks = array();

        foreach ($tags as $name => $values) {
            if (in_array($name, $skip)) {
                continue;
            }

            foreach ($values as $value) {
                $chunks[] = sprintf('<comment>%s%s</comment> %s', self::inflect($name), empty($value) ? '' : ':', OutputFormatter::escape($value));
            }

            $chunks[] = '';
        }

        return implode("\n", $chunks);
    }

    /**
     * Get a docblock vector template.
     *
     * @param string $type Vector type
     * @param int    $max  Pad width
     *
     * @return string
     */
    private static function getVectorParamTemplate($type, $max)
    {
        if (!isset(self::$vectorParamTemplates[$type])) {
            return sprintf('%%-%ds', $max);
        }

        return sprintf('<%s>%%-%ds</%s>', self::$vectorParamTemplates[$type], $max, self::$vectorParamTemplates[$type]);
    }

    /**
     * Indent a string.
     *
     * @param string $text   String to indent
     * @param string $indent (default: '  ')
     *
     * @return string
     */
    private static function indent($text, $indent = '  ')
    {
        return $indent . str_replace("\n", "\n" . $indent, $text);
    }

    /**
     * Convert underscored or whitespace separated words into sentence case.
     *
     * @param string $text
     *
     * @return string
     */
    private static function inflect($text)
    {
        $words = trim(preg_replace('/[\s_-]+/', ' ', preg_replace('/([a-z])([A-Z])/', '$1 $2', $text)));

        return implode(' ', array_map('ucfirst', explode(' ', $words)));
    }
}