Completed
Push — master ( 52e853...8255d7 )
by Colin
01:03
created

TableOfContentsBuilder::onDocumentParsed()   B

Complexity

Conditions 9
Paths 16

Size

Total Lines 52

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 26
CRAP Score 9.0294

Importance

Changes 0
Metric Value
dl 0
loc 52
c 0
b 0
f 0
ccs 26
cts 28
cp 0.9286
rs 7.4917
cc 9
nc 16
nop 1
crap 9.0294

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/*
4
 * This file is part of the league/commonmark package.
5
 *
6
 * (c) Colin O'Dell <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace League\CommonMark\Extension\TableOfContents;
13
14
use League\CommonMark\Block\Element\Document;
15
use League\CommonMark\Block\Element\Heading;
16
use League\CommonMark\Block\Element\ListBlock;
17
use League\CommonMark\Block\Element\ListData;
18
use League\CommonMark\Block\Element\ListItem;
19
use League\CommonMark\Block\Element\Paragraph;
20
use League\CommonMark\Event\DocumentParsedEvent;
21
use League\CommonMark\Exception\InvalidOptionException;
22
use League\CommonMark\Extension\HeadingPermalink\HeadingPermalink;
23
use League\CommonMark\Extension\TableOfContents\Normalizer\AsIsNormalizerStrategy;
24
use League\CommonMark\Extension\TableOfContents\Normalizer\FlatNormalizerStrategy;
25
use League\CommonMark\Extension\TableOfContents\Normalizer\RelativeNormalizerStrategy;
26
use League\CommonMark\Inline\Element\Link;
27
use League\CommonMark\Util\ConfigurationAwareInterface;
28
use League\CommonMark\Util\ConfigurationInterface;
29
30
final class TableOfContentsBuilder implements ConfigurationAwareInterface
31
{
32
    public const STYLE_BULLET = ListBlock::TYPE_BULLET;
33
    public const STYLE_ORDERED = ListBlock::TYPE_ORDERED;
34
35
    public const NORMALIZE_DISABLED = 'as-is';
36
    public const NORMALIZE_RELATIVE = 'relative';
37
    public const NORMALIZE_FLAT = 'flat';
38
39
    public const POSITION_TOP = 'top';
40
    public const POSITION_BEFORE_HEADINGS = 'before-headings';
41
42
    /** @var ConfigurationInterface */
43
    private $config;
44
45 33
    public function onDocumentParsed(DocumentParsedEvent $event)
46
    {
47 33
        $document = $event->getDocument();
48 33
        $toc = $this->createToc();
49
50 33
        $normalizer = $this->getNormalizer($toc);
51 33
        [$min, $max] = $this->getMinAndMaxHeadingLevels();
0 ignored issues
show
Bug introduced by
The variable $min does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $max does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
52
53 33
        $firstHeading = null;
54
55 33
        foreach ($this->getHeadingLinks($document) as $headingLink) {
56 30
            $heading = $headingLink->parent();
57
            // Make sure this is actually tied to a heading
58 30
            if (!$heading instanceof Heading) {
59
                continue;
60
            }
61
62
            // Skip any headings outside the configured min/max levels
63 30
            if ($heading->getLevel() < $min || $heading->getLevel() > $max) {
64 3
                continue;
65
            }
66
67
            // Keep track of the first heading we see - we might need this later
68 30
            $firstHeading = $firstHeading ?? $heading;
69
70
            // Create the new link
71 30
            $link = new Link('#' . $headingLink->getSlug(), $heading->getStringContent());
72 30
            $paragraph = new Paragraph();
73 30
            $paragraph->appendChild($link);
74
75 30
            $listItem = new ListItem($toc->getListData());
76 30
            $listItem->appendChild($paragraph);
77
78
            // Add it to the correct place
79 30
            $normalizer->addItem($heading->getLevel(), $listItem);
80
        }
81
82
        // Don't add the TOC if no headings were present
83 33
        if (!$toc->hasChildren() || $firstHeading === null) {
84 3
            return;
85
        }
86
87
        // Add the TOC to the Document
88 30
        $position = $this->config->get('table_of_contents/position', self::POSITION_TOP);
89 30
        if ($position === self::POSITION_TOP) {
90 27
            $document->prependChild($toc);
91 3
        } elseif ($position === self::POSITION_BEFORE_HEADINGS) {
92 3
            $firstHeading->insertBefore($toc);
93
        } else {
94
            throw new InvalidOptionException(\sprintf('Invalid config option "%s" for "table_of_contents/position"', $position));
95
        }
96 30
    }
97
98 33
    private function createToc(): TableOfContents
99
    {
100 33
        $listData = new ListData();
101
102 33
        $style = $this->config->get('table_of_contents/style', self::STYLE_BULLET);
103 33
        if ($style === self::STYLE_BULLET) {
104 30
            $listData->type = ListBlock::TYPE_BULLET;
105 3
        } elseif ($style === self::STYLE_ORDERED) {
106 3
            $listData->type = ListBlock::TYPE_ORDERED;
107
        } else {
108
            throw new InvalidOptionException(\sprintf('Invalid config option "%s" for "table_of_contents/style"', $style));
109
        }
110
111 33
        $toc = new TableOfContents($listData);
112
113 33
        $class = $this->config->get('table_of_contents/html_class', 'table-of-contents');
114 33
        if (!empty($class)) {
115 33
            $toc->data['attributes']['class'] = $class;
116
        }
117
118 33
        return $toc;
119
    }
120
121
    /**
122
     * @return array<int>
123
     */
124 33
    private function getMinAndMaxHeadingLevels(): array
125
    {
126
        return [
127 33
            (int) $this->config->get('table_of_contents/min_heading_level', 1),
128 33
            (int) $this->config->get('table_of_contents/max_heading_level', 6),
129
        ];
130
    }
131
132
    /**
133
     * @param Document $document
134
     *
135
     * @return HeadingPermalink[]
136
     */
137 33
    private function getHeadingLinks(Document $document)
138
    {
139 33
        $walker = $document->walker();
140 33
        while ($event = $walker->next()) {
141 33
            if ($event->isEntering() && ($node = $event->getNode()) instanceof HeadingPermalink) {
142 30
                yield $node;
143
            }
144
        }
145 33
    }
146
147 33
    private function getNormalizer(TableOfContents $toc)
148
    {
149 33
        $strategy = $this->config->get('table_of_contents/normalize', self::NORMALIZE_RELATIVE);
150 33
        if ($strategy === self::NORMALIZE_DISABLED) {
151 3
            return new AsIsNormalizerStrategy($toc);
152 30
        } elseif ($strategy === self::NORMALIZE_RELATIVE) {
153 27
            return new RelativeNormalizerStrategy($toc);
154 3
        } elseif ($strategy === self::NORMALIZE_FLAT) {
155 3
            return new FlatNormalizerStrategy($toc);
156
        }
157
158
        throw new InvalidOptionException(\sprintf('Invalid config option "%s" for "table_of_contents/normalize"', $strategy));
159
    }
160
161
    /**
162
     * {@inheritdoc}
163
     */
164 33
    public function setConfiguration(ConfigurationInterface $config)
165
    {
166 33
        $this->config = $config;
167 33
    }
168
}
169