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

dg / texy / 39601969498

05 Oct 2026 06:36AM UTC coverage: 95.456% (-0.9%) from 96.335%
39601969498

push

github

dg
examples: Figure as Definition List lives in one directory

The demo sat in "Figure as Definition List" while its sample and image
were in "figure as definition list", and the readme linked the latter;
on a case-sensitive filesystem the demo did not find its sample.

3508 of 3675 relevant lines covered (95.46%)

0.95 hits per line

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

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

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

8
namespace Texy\Modules;
9

10
use Texy;
11
use Texy\{Helpers, Range, Syntax};
12
use Texy\Nodes\{BlockNode, CodeBlockNode, CommentNode, ContentNode, SectionNode};
13
use Texy\Parsing\{OffsetMap, ParseContext};
14
use function strlen;
15

16

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

36

37
        public function beforeParse(): void
38
        {
39
                $this->texy->addBlockSyntax(
1 ✔
40
                        Syntax::Blocks,
1 ✔
41
                        '~^
42
                                /--++ [ \t]*+                 # opening tag /--
43
                                (.*)                          # content type (1)
44
                                ' . Texy\Patterns::ModifierHAlign . '? # modifier (2)
1 ✔
45
                                $
46
                                ((?:                         # content (3)
47
                                        \n (?0) |                # recursive nested blocks
48
                                        \n.*+                    # or any content
49
                                )*)
50
                                (?:
51
                                        \n \\\--.* $ |           # closing tag
52
                                        \z                       # or end of input
53
                                )
54
                        ~mUix',
55
                        $this->parse(...),
1 ✔
56
                );
57

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

75

76
        /**
77
         * Parses ```language fenced code blocks; content is verbatim.
78
         * @param  array{string, string, string, string}  $matches
79
         * @param  array{int, int, int, int}  $offsets
80
         */
81
        public function parseFenced(ParseContext $context, array $matches, array $offsets): CodeBlockNode
82
        {
83
                [, , $mInfo, $mContent] = $matches;
1 ✔
84

85
                // a Texy modifier may trail the info string: ```php .{file: Foo.php}
86
                $mod = null;
1 ✔
87
                if ($m = Texy\Regexp::match($mInfo, '~' . Texy\Patterns::Modifier . '$~x', captureOffset: true)) {
1 ✔
88
                        $mod = Texy\Modifier::parse($m[1][0], $offsets[2] + $m[1][1]);
×
89
                        $mInfo = substr($mInfo, 0, $m[0][1]);
×
90
                }
91

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

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

106

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

116
                $mod = Texy\Modifier::parse($mMod, $offsets[2] ?? null);
1 ✔
117
                $parts = Texy\Regexp::split($mParam, '~\s+~', limit: 2);
1 ✔
118
                // the opening pattern matches case-insensitively, so /--PRE must mean the same
119
                // as /--pre; only the type is lowercased, a language name keeps its spelling
120
                $blocktype = empty($parts[0]) ? Syntax::BlockDefault : 'block/' . Helpers::toLower($parts[0]);
1 ✔
121
                $param = empty($parts[1]) ? null : $parts[1];
1 ✔
122

123
                [$content, $map] = OffsetMap::outdentLines(OffsetMap::linesOf($mContent, $offsets[3]));
1 ✔
124
                $range = new Range($offsets[0], strlen($matches[0]));
1 ✔
125

126
                if ($blocktype === Syntax::BlockCode) {
1 ✔
127
                        return new CodeBlockNode('code', $content, $param, $mod, $range, codeMap: $map);
1 ✔
128

129
                } elseif ($blocktype === Syntax::BlockDefault) {
1 ✔
130
                        return new CodeBlockNode('default', $content, $param, $mod, $range, codeMap: $map);
1 ✔
131

132
                } elseif ($blocktype === Syntax::BlockPre) {
1 ✔
133
                        return new CodeBlockNode('pre', $content, $param, $mod, $range, $this->parseHtml($context, $content, $map), $map);
1 ✔
134

135
                } elseif ($blocktype === Syntax::BlockComment) {
1 ✔
136
                        return new CommentNode($content, $range);
1 ✔
137

138
                } elseif ($blocktype === Syntax::BlockHtml || $blocktype === Syntax::BlockText) {
1 ✔
139
                        // html/text blocks don't use outdent - preserve original indentation
140
                        $content = trim($mContent, "\n");
1 ✔
141
                        $map = OffsetMap::fromLines(OffsetMap::linesOf($content, $offsets[3] + strspn($mContent, "\n")));
1 ✔
142
                        return $blocktype === Syntax::BlockHtml
1 ✔
143
                                ? new CodeBlockNode('html', $content, null, $mod, $range, $this->parseHtml($context, $content, $map), $map)
1 ✔
144
                                : new CodeBlockNode('text', $content, null, $mod, $range, codeMap: $map);
1 ✔
145

146
                } elseif ($blocktype === Syntax::BlockDiv) {
1 ✔
147
                        [$content, $map] = OffsetMap::outdentLines(OffsetMap::linesOf($mContent, $offsets[3]), firstLine: true);
1 ✔
148
                        if ($content === '') {
1 ✔
149
                                return null;
×
150
                        }
151
                        $parsed = $context->parseBlock($content);
1 ✔
152
                        $map->applyTo($parsed);
1 ✔
153
                        return new SectionNode($parsed, 'div', $mod, $range);
1 ✔
154

155
                } elseif ($blocktype === Syntax::BlockTexySource) {
1 ✔
156
                        // parsed as Texy, its HTML is displayed as source code
157
                        if ($content === '') {
1 ✔
158
                                return null;
×
159
                        }
160
                        $parsed = $context->parseBlock($content);
1 ✔
161
                        $map->applyTo($parsed);
1 ✔
162
                        return new CodeBlockNode('texysource', $content, null, $mod, $range, $parsed, $map);
1 ✔
163
                }
164

165
                // /--php and friends: a code block in that language. Anything that does not look
166
                // like a language name stays refused and degrades to visible text.
167
                return Texy\Regexp::match($parts[0], '~^[a-z][a-z0-9_+#-]*$~i')
1 ✔
168
                        ? new CodeBlockNode('code', $content, $parts[0], $mod, $range, codeMap: $map)
1 ✔
169
                        : null;
1 ✔
170
        }
171

172

173
        /**
174
         * Parses the markup of html and pre blocks, which knows HTML tags, comments and entities only.
175
         */
176
        private function parseHtml(ParseContext $context, string $text, OffsetMap $map): ContentNode
177
        {
178
                $parsed = $context->getInlineParser()
1 ✔
179
                        ->withPatterns([Syntax::HtmlTag, Syntax::HtmlComment, Syntax::HtmlEntity])
1 ✔
180
                        ->parse($context, $text);
1 ✔
181
                $map->applyTo($parsed);
1 ✔
182
                return $parsed;
1 ✔
183
        }
184
}
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