Completed
Pull Request — master (#43)
by Jitendra
02:05 queued 13s
created

DocsCommand::execute()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 21
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 11
nc 3
nop 0
dl 0
loc 21
rs 9.9
c 0
b 0
f 0
1
<?php
2
3
/*
4
 * This file is part of the PHINT package.
5
 *
6
 * (c) Jitendra Adhikari <[email protected]>
7
 *     <https://github.com/adhocore>
8
 *
9
 * Licensed under MIT license.
10
 */
11
12
namespace Ahc\Phint\Console;
13
14
use Ahc\Phint\Generator\TwigGenerator;
15
use CrazyFactory\DocBlocks\DocBlock;
16
17
class DocsCommand extends BaseCommand
18
{
19
    /** @var string Command name */
20
    protected $_name = 'docs';
21
22
    /** @var string Command description */
23
    protected $_desc = 'Generate basic readme docs from docblocks';
24
25
    /** @var string Current working dir */
26
    protected $_workDir;
27
28
    /**
29
     * Configure the command options/arguments.
30
     *
31
     * @return void
32
     */
33
    protected function onConstruct()
34
    {
35
        $this->_workDir  = \realpath(\getcwd());
36
37
        $this
38
            ->option('-o --output', 'Output file (default README.md). For old project you should use something else'
39
                . "\n(OR mark region with <!-- DOCS START --> and <!-- DOCS END --> to inject docs)",
40
                null, 'README.md'
41
            )
42
            ->option('-a --with-abstract', 'Create stub for abstract/interface class')
43
            ->usage(
44
                '<bold>  phint docs</end>               Appends to readme.md<eol/>' .
45
                '<bold>  phint d</end> <comment>-o docs/api.md</end>   Writes to docs/api.md<eol/>'
46
            );
47
    }
48
49
    /**
50
     * Generate test stubs.
51
     *
52
     * @return void
53
     */
54
    public function execute()
55
    {
56
        $io = $this->app()->io();
57
58
        $io->comment('Preparing metadata ...', true);
59
        $docsMetadata = $this->prepare();
60
61
        if (empty($docsMetadata)) {
62
            $io->bgGreen('Looks like nothing to do here', true);
63
64
            return;
65
        }
66
67
        $io->comment('Generating docs ...', true);
68
        $generated = $this->generate($docsMetadata, $this->values());
69
70
        if ($generated) {
71
            $io->cyan("$generated doc(s) generated", true);
72
        }
73
74
        $io->ok('Done', true);
75
    }
76
77
    protected function prepare(): array
78
    {
79
        // Sorry psr-0!
80
        $namespaces = $this->_composer->config('autoload.psr-4');
81
82
        $srcPaths = [];
83
        foreach ($namespaces as $ns => $path) {
84
            if (\preg_match('!^(source|src|lib|class)/?!', $path)) {
85
                $srcPaths[] = $this->_pathUtil->join($this->_workDir, $path);
86
            } else {
87
                unset($namespaces[$ns]);
88
            }
89
        }
90
91
        $classes = $this->_pathUtil->loadClasses($srcPaths, \array_keys($namespaces));
92
93
        return $this->getClassesMetadata($classes);
94
    }
95
96
    protected function getClassesMetadata(array $classes): array
97
    {
98
        $metadata = [];
99
100
        foreach ($classes as $classFqcn) {
101
            if ([] === $meta = $this->getClassMetadata($classFqcn)) {
102
                continue;
103
            }
104
105
            $metadata[] = $meta;
106
        }
107
108
        return $metadata;
109
    }
110
111
    protected function getClassMetadata(string $classFqcn): array
112
    {
113
        $reflex = new \ReflectionClass($classFqcn);
114
115
        if (!$this->shouldGenerateDocs($reflex)) {
116
            return [];
117
        }
118
119
        $methods = [];
120
        $isTrait = $reflex->isTrait();
121
        $name    = $reflex->getShortName();
122
        $exclude = ['__construct', '__destruct'];
123
124
        foreach ($reflex->getMethods(\ReflectionMethod::IS_PUBLIC) as $m) {
125
            if ($m->class !== $classFqcn && \in_array($m->name, $exclude)) {
126
                continue;
127
            }
128
129
            $methods[$m->name] = $this->getMethodMetadata($m);
130
        }
131
132
        if (empty($methods)) {
133
            return [];
134
        }
135
136
        $texts = (new DocBlock($reflex))->texts();
137
        $title = \array_shift($texts);
138
139
        return \compact('classFqcn', 'name', 'isTrait', 'title', 'texts', 'methods');
140
    }
141
142
    protected function shouldGenerateDocs(\ReflectionClass $reflex): bool
143
    {
144
        if ($this->abstract) {
0 ignored issues
show
Bug Best Practice introduced by
The property abstract does not exist on Ahc\Phint\Console\DocsCommand. Since you implemented __get, consider adding a @property annotation.
Loading history...
145
            return true;
146
        }
147
148
        return !$reflex->isInterface() && !$reflex->isAbstract();
149
    }
150
151
    protected function getMethodMetadata(\ReflectionMethod $method): array
152
    {
153
        $params = [];
154
        $parser = new DocBlock($method);
155
156
        foreach ($parser->find('param') as $param) {
157
            $params[] = \preg_replace(['/(.*\$\w+)(.*)/', '/ +/'], ['$1', ' '], $param->getValue());
158
        }
159
160
        if (null !== $return = $parser->first('return')) {
161
            $return = \preg_replace('/ .*?$/', '', $return->getValue());
162
        }
163
164
        $texts = $parser->texts();
165
        $title = \array_shift($texts);
166
167
        return ['static' => $method->isStatic()] + \compact('title', 'texts', 'params', 'return');
168
    }
169
170
    protected function generate(array $docsMetadata, array $parameters): int
171
    {
172
        $templatePath = __DIR__ . '/../../resources';
173
        $generator    = new TwigGenerator($templatePath, $this->getCachePath());
174
175
        if (!$this->_pathUtil->isAbsolute($parameters['output'])) {
176
            $parameters['output'] = $this->_pathUtil->join($this->_workDir, $parameters['output']);
177
        }
178
179
        return $generator->generateDocs($docsMetadata, $parameters);
180
    }
181
}
182