NamespaceIndexBuilder::shouldRenderIndex()   A
last analyzed

Complexity

Conditions 4
Paths 5

Size

Total Lines 12
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 2 Features 0
Metric Value
cc 4
eloc 6
c 2
b 2
f 0
nc 5
nop 2
dl 0
loc 12
rs 10
1
<?php
2
/**
3
 * @copyright Copyright (c) 2017 Julius Härtl <[email protected]>
4
 * @author    Julius Härtl <[email protected]>
5
 * @license   GNU AGPL version 3 or any later version
6
 *
7
 *  This program is free software: you can redistribute it and/or modify
8
 *  it under the terms of the GNU Affero General Public License as
9
 *  published by the Free Software Foundation, either version 3 of the
10
 *  License, or (at your option) any later version.
11
 *
12
 *  This program is distributed in the hope that it will be useful,
13
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
 *  GNU Affero General Public License for more details.
16
 *
17
 *  You should have received a copy of the GNU Affero General Public License
18
 *  along with this program. If not, see <http://www.gnu.org/licenses/>.
19
 */
20
21
namespace JuliusHaertl\PHPDocToRst\Builder;
22
23
use phpDocumentor\Reflection\DocBlock\Tags\Param;
24
use phpDocumentor\Reflection\Fqsen;
25
use phpDocumentor\Reflection\Php\Argument;
26
use phpDocumentor\Reflection\Php\Constant;
27
use phpDocumentor\Reflection\Php\Function_;
28
use phpDocumentor\Reflection\Php\Namespace_;
29
30
/**
31
 * This class will build an index for each namespace.
32
 * It contains a toc for child namespaces, classes, traits, interfaces and functions.
33
 */
34
class NamespaceIndexBuilder extends PhpDomainBuilder
35
{
36
    const RENDER_INDEX_NAMESPACE = 0;
37
    const RENDER_INDEX_CLASSES = 1;
38
    const RENDER_INDEX_TRAITS = 2;
39
    const RENDER_INDEX_INTERFACES = 3;
40
    const RENDER_INDEX_FUNCTIONS = 4;
41
    const RENDER_INDEX_CONSTANTS = 5;
42
43
    /** @var Namespace_ */
44
    private $currentNamespace;
45
46
    /** @var Namespace_[] */
47
    private $namespaces;
48
49
    /** @var Namespace_[] */
50
    private $childNamespaces = [];
51
52
    /** @var Function_[] */
53
    private $functions;
54
55
    /** @var Constant[] */
56
    private $constants;
57
58
    public function __construct($extensions, $namespaces, Namespace_ $current, $functions, $constants)
59
    {
60
        parent::__construct($extensions);
61
        $this->namespaces = $namespaces;
62
        $this->currentNamespace = $current;
63
        $this->functions = $functions;
64
        $this->constants = $constants;
65
        $this->findChildNamespaces();
66
    }
67
68
    /**
69
     * Find child namespaces for current namespace.
70
     */
71
    private function findChildNamespaces()
72
    {
73
        $currentNamespaceFqsen = (string) $this->currentNamespace->getFqsen();
74
        /** @var Namespace_ $namespace */
75
        foreach ($this->namespaces as $namespace) {
76
            // check if not root and doesn't start with current namespace
77
            if ($currentNamespaceFqsen !== '\\' && strpos(
78
                (string) $namespace->getFqsen(),
79
                $currentNamespaceFqsen.'\\'
80
            ) !== 0) {
81
                continue;
82
            }
83
            if ((string) $namespace->getFqsen() !== $currentNamespaceFqsen && strpos(
84
                (string) $namespace->getFqsen(),
85
                $currentNamespaceFqsen
86
            ) === 0) {
87
                // only keep first level children
88
                $childrenPath = substr(
89
                    (string) $namespace->getFqsen(),
90
                    strlen((string) $this->currentNamespace->getFqsen()) + 1
91
                );
92
                if (strpos($childrenPath, '\\') === false) {
93
                    $this->childNamespaces[] = $namespace;
94
                }
95
            }
96
        }
97
    }
98
99
    public function render()
100
    {
101
        $currentNamespaceFqsen = (string) $this->currentNamespace->getFqsen();
102
        if ($currentNamespaceFqsen !== '\\') {
103
            $label = str_replace('\\', '-', $currentNamespaceFqsen);
104
            $this->addLine('.. _namespace'.$label.':')->addLine();
105
            $this->addH1(RstBuilder::escape($this->currentNamespace->getName()));
106
            $this->addLine(self::escape($currentNamespaceFqsen))->addLine();
107
        } else {
108
            $label = 'root-namespace';
109
            $this->addLine('.. _namespace-'.$label.':')->addLine();
110
            $this->addH1(RstBuilder::escape('\\'));
111
        }
112
        $this->addLine();
113
114
        $this->addIndex(self::RENDER_INDEX_NAMESPACE);
115
        $this->addIndex(self::RENDER_INDEX_INTERFACES);
116
        $this->addIndex(self::RENDER_INDEX_CLASSES);
117
        $this->addIndex(self::RENDER_INDEX_TRAITS);
118
119
        if ($this->shouldRenderIndex(self::RENDER_INDEX_CONSTANTS)) {
120
            $this->addConstants($this->constants);
121
        }
122
        $this->addFunctions();
123
    }
124
125
    protected function addIndex($type)
126
    {
127
        if ($this->shouldRenderIndex($type)) {
128
            $this->addH2($this->getHeaderForType($type));
129
            $this->addLine('.. toctree::');
130
            $this->indent();
131
            $this->addLine(':maxdepth: 1')->addLine();
132
            /** @var Fqsen $entry */
133
            foreach ($this->getElementList($type) as $entry) {
134
                if (!$this->shouldRenderIndex($type, $entry)) {
135
                    continue;
136
                }
137
                if ($type === self::RENDER_INDEX_NAMESPACE) {
138
                    $this->addLine($entry->getName().' <'.$entry->getName().'/index>');
139
                } else {
140
                    $this->addElementTocEntry($entry);
141
                }
142
            }
143
            $this->unindent();
144
            $this->addLine();
145
        }
146
    }
147
148
    private function shouldRenderIndex($type, $element = null)
149
    {
150
        foreach ($this->extensions as $extension) {
151
            if (!$extension->shouldRenderIndex($type, $element)) {
152
                return false;
153
            }
154
        }
155
        if ($element === null) {
156
            return count($this->getElementList($type)) > 0;
157
        }
158
159
        return true;
160
    }
161
162
    /**
163
     * @param int $type
164
     *
165
     * @return array
166
     */
167
    private function getElementList($type)
168
    {
169
        $elements = [];
170
        switch ($type) {
171
            case self::RENDER_INDEX_NAMESPACE:
172
                $elements = $this->childNamespaces;
173
                break;
174
            case self::RENDER_INDEX_CLASSES:
175
                $elements = $this->currentNamespace->getClasses();
176
                break;
177
            case self::RENDER_INDEX_INTERFACES:
178
                $elements = $this->currentNamespace->getInterfaces();
179
                break;
180
            case self::RENDER_INDEX_TRAITS:
181
                $elements = $this->currentNamespace->getTraits();
182
                break;
183
            case self::RENDER_INDEX_FUNCTIONS:
184
                $elements = $this->functions;
185
                break;
186
            case self::RENDER_INDEX_CONSTANTS:
187
                $elements = $this->constants;
188
                break;
189
        }
190
191
        return $elements;
192
    }
193
194
    private function getHeaderForType($type)
195
    {
196
        $headers = [self::RENDER_INDEX_NAMESPACE  => 'Namespaces',
197
            self::RENDER_INDEX_INTERFACES         => 'Interfaces',
198
            self::RENDER_INDEX_CLASSES            => 'Classes',
199
            self::RENDER_INDEX_TRAITS             => 'Traits',
200
            self::RENDER_INDEX_FUNCTIONS          => 'Functions',
201
            self::RENDER_INDEX_CONSTANTS          => 'Constants',
202
        ];
203
204
        return $headers[$type];
205
    }
206
207
    private function addElementTocEntry(Fqsen $entry)
208
    {
209
        $currentNamespaceFqsen = (string) $this->currentNamespace->getFqsen();
210
        $subPath = $entry;
211
        if ($currentNamespaceFqsen !== '\\' && substr(
212
            $entry,
213
            0,
214
            strlen($currentNamespaceFqsen)
215
        ) === $currentNamespaceFqsen) {
216
            $subPath = substr($entry, strlen($currentNamespaceFqsen));
217
        }
218
        $path = substr(str_replace('\\', '/', $subPath), 1);
219
        $this->addLine($entry->getName().' <'.$path.'>');
220
    }
221
222
    private function addFunctions()
223
    {
224
        if (!$this->shouldRenderIndex(self::RENDER_INDEX_FUNCTIONS)) {
225
            return;
226
        }
227
        $this->addH2('Functions');
228
        /** @var Function_ $function */
229
        foreach ($this->functions as $function) {
230
            if (!$this->shouldRenderIndex(self::RENDER_INDEX_FUNCTIONS, $function)) {
231
                continue;
232
            }
233
            $docBlock = $function->getDocBlock();
234
            $params = [];
235
            if ($docBlock !== null) {
236
                /** @var Param $param */
237
                foreach ($docBlock->getTagsByName('param') as $param) {
238
                    $params[$param->getVariableName()] = $param;
239
                }
240
            }
241
            $args = '';
242
            /** @var Argument $argument */
243
            foreach ($function->getArguments() as $argument) {
244
                // TODO: defaults, types
245
                $args .= '$'.$argument->getName().', ';
246
            }
247
            $args = substr($args, 0, -2);
248
            $this->beginPhpDomain('function', $function->getName().'('.$args.')');
249
            $this->addDocBlockDescription($function);
250
            if (!empty($params)) {
251
                foreach ($function->getArguments() as $argument) {
252
                    if (array_key_exists($argument->getName(), $params)) {
253
                        /** @var Param $param */
254
                        $param = $params[$argument->getName()];
255
                        if ($param !== null) {
256
                            $this->addMultiline(
257
                                ':param '.self::escape($param->getType()).' $'.$argument->getName().': '.$param->getDescription(),
258
                                true
259
                            );
260
                        }
261
                    }
262
                }
263
            }
264
            $this->endPhpDomain('function');
265
        }
266
    }
267
}
268