• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

dg / texy / 29698730446

19 Jul 2026 06:14PM UTC coverage: 93.477% (+0.4%) from 93.08%
29698730446

push

github

dg
PHPStan ignore paths follow the output layer split

3296 of 3526 relevant lines covered (93.48%)

0.93 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.83
/src/Texy/Modules/BlockModule.php
1
<?php declare(strict_types=1);
2

3
/**
4
 * This file is part of the Texy! (https://texy.nette.org)
5
 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
6
 */
7

8
namespace Texy\Modules;
9

10
use Texy;
11
use Texy\Helpers;
12
use Texy\Nodes\BlockNode;
13
use Texy\Nodes\CodeBlockNode;
14
use Texy\Nodes\CommentNode;
15
use Texy\Nodes\SectionNode;
16
use Texy\ParseContext;
17
use Texy\Range;
18
use Texy\Syntax;
19
use function strlen, trim;
20

21

22
/**
23
 * Processes special blocks (/-- code, html, text, div, etc.).
24
 */
25
final class BlockModule extends Texy\Module
26
{
27
        public function __construct(
1✔
28
                private Texy\Texy $texy,
29
        ) {
30
                $texy->allowed[Syntax::BlockDefault] = true;
1✔
31
                $texy->allowed[Syntax::BlockPre] = true;
1✔
32
                $texy->allowed[Syntax::BlockCode] = true;
1✔
33
                $texy->allowed[Syntax::BlockHtml] = true;
1✔
34
                $texy->allowed[Syntax::BlockText] = true;
1✔
35
                $texy->allowed[Syntax::BlockTexySource] = true;
1✔
36
                $texy->allowed[Syntax::BlockComment] = true;
1✔
37
                $texy->allowed[Syntax::BlockDiv] = true;
1✔
38
                $texy->allowed[Syntax::CodeFenced] = true;
1✔
39
        }
1✔
40

41

42
        public function beforeParse(string &$text): void
1✔
43
        {
44
                $this->texy->registerBlockPattern(
1✔
45
                        $this->parse(...),
1✔
46
                        '~^
47
                                /--++ \ *+                    # opening tag /--
48
                                (.*)                          # content type (1)
49
                                ' . Texy\Patterns::ModifierHAlign . '? # modifier (2)
1✔
50
                                $
51
                                ((?:                         # content (3)
52
                                        \n (?0) |                # recursive nested blocks
53
                                        \n.*+                    # or any content
54
                                )*)
55
                                (?:
56
                                        \n \\\--.* $ |           # closing tag
57
                                        \z                       # or end of input
58
                                )
59
                        ~mUix',
60
                        Syntax::Blocks,
1✔
61
                );
62

63
                // ```language
64
                $this->texy->registerBlockPattern(
1✔
65
                        $this->parseFenced(...),
1✔
66
                        '~^
1✔
67
                                (`{3,}) \ *+                        # opening fence (1)
68
                                ([^\n`]*)                           # info string (2)
69
                                $
70
                                ((?: \n (?! \1 `*+ \ *+ $ ) .*+ )*) # content (3): lines that are not a valid closing fence
71
                                (?:
72
                                        \n \1 `*+ \ *+ $ |              # closing fence (same length or longer)
73
                                        \z                              # or end of input
74
                                )
75
                        ~mx',
76
                        Syntax::CodeFenced,
1✔
77
                );
78
        }
1✔
79

80

81
        /**
82
         * Parses ```language fenced code blocks; content is verbatim.
83
         * @param  array<?string>  $matches
84
         * @param  array<?int>  $offsets
85
         */
86
        public function parseFenced(ParseContext $context, array $matches, array $offsets): CodeBlockNode
1✔
87
        {
88
                /** @var array{string, string, string, string} $matches */
89
                [, , $mInfo, $mContent] = $matches;
1✔
90

91
                // first word of the info string is the language (GFM)
92
                $language = Texy\Regexp::split(trim($mInfo), '~\s+~', limit: 2)[0];
1✔
93

94
                return new CodeBlockNode(
1✔
95
                        'code',
1✔
96
                        substr($mContent, 1), // drop the separator "\n"; intentional blank lines stay
1✔
97
                        $language === '' ? null : $language,
1✔
98
                        null,
1✔
99
                        new Range($offsets[0], strlen($matches[0])),
1✔
100
                );
101
        }
102

103

104
        /**
105
         * Parses blocks /--foo
106
         * @param  array<?string>  $matches
107
         * @param  array<?int>  $offsets
108
         */
109
        public function parse(ParseContext $context, array $matches, array $offsets): ?BlockNode
1✔
110
        {
111
                /** @var array{string, string, ?string, string} $matches */
112
                [, $mParam, $mMod, $mContent] = $matches;
1✔
113

114
                $mod = Texy\Modifier::parse($mMod, $offsets[2] ?? null);
1✔
115
                $parts = Texy\Regexp::split($mParam, '~\s+~', limit: 2);
1✔
116
                $blocktype = empty($parts[0]) ? Syntax::BlockDefault : 'block/' . $parts[0];
1✔
117
                $param = empty($parts[1]) ? null : $parts[1];
1✔
118

119
                $content = Helpers::outdent($mContent);
1✔
120
                $range = new Range($offsets[0], strlen($matches[0]));
1✔
121

122
                if ($blocktype === Syntax::BlockCode) {
1✔
123
                        return new CodeBlockNode('code', $content, $param, $mod, $range);
1✔
124

125
                } elseif ($blocktype === Syntax::BlockDefault || $blocktype === Syntax::BlockPre) {
1✔
126
                        return new CodeBlockNode($blocktype === Syntax::BlockPre ? 'pre' : 'default', $content, $param, $mod, $range);
1✔
127

128
                } elseif ($blocktype === Syntax::BlockComment) {
1✔
129
                        return new CommentNode($content, $range);
1✔
130

131
                } elseif ($blocktype === Syntax::BlockHtml) {
1✔
132
                        // html/text blocks don't use outdent - preserve original indentation
133
                        return new CodeBlockNode('html', trim($mContent, "\n"), null, $mod, $range);
1✔
134

135
                } elseif ($blocktype === Syntax::BlockText) {
1✔
136
                        // html/text blocks don't use outdent - preserve original indentation
137
                        return new CodeBlockNode('text', trim($mContent, "\n"), null, $mod, $range);
1✔
138

139
                } elseif ($blocktype === Syntax::BlockDiv) {
1✔
140
                        [$content, $map] = Texy\OffsetMap::outdentLines(
1✔
141
                                Texy\OffsetMap::linesOf($mContent, $offsets[3] ?? 0),
1✔
142
                                firstLine: true,
1✔
143
                        );
144
                        if ($content === '') {
1✔
145
                                return null;
×
146
                        }
147
                        $parsed = $context->parseBlock($content);
1✔
148
                        $map->applyTo($parsed);
1✔
149
                        return new SectionNode($parsed, 'div', $mod, $range);
1✔
150

151
                } elseif ($blocktype === Syntax::BlockTexySource) {
1✔
152
                        // Store raw texy content, will be parsed and displayed as HTML source in handler
153
                        $content = Helpers::outdent($mContent);
1✔
154
                        if ($content === '') {
1✔
155
                                return null;
×
156
                        }
157
                        return new CodeBlockNode('texysource', $content, null, $mod, $range);
1✔
158
                }
159

160
                return null;
1✔
161
        }
162
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc