Skip to content

Commit 60d29ff

Browse files
wouterjjaapio
authored andcommitted
Update doc and ref text role to new link resolving
1 parent 522f540 commit 60d29ff

File tree

19 files changed

+197
-449
lines changed

19 files changed

+197
-449
lines changed
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace phpDocumentor\Guides\RestructuredText\TextRoles;
6+
7+
use phpDocumentor\Guides\Nodes\Inline\AbstractLinkInlineNode;
8+
use phpDocumentor\Guides\ParserContext;
9+
use phpDocumentor\Guides\RestructuredText\Parser\InlineLexer;
10+
use Psr\Log\LoggerInterface;
11+
12+
use function sprintf;
13+
use function trim;
14+
15+
/** @see https://docutils.sourceforge.io/docs/ref/rst/restructuredtext.html#embedded-uris-and-aliases */
16+
abstract class AbstractReferenceTextRole implements TextRole
17+
{
18+
private InlineLexer $lexer;
19+
20+
public function __construct(
21+
private readonly LoggerInterface $logger,
22+
) {
23+
// Do not inject the $lexer. It contains a state.
24+
$this->lexer = new InlineLexer();
25+
}
26+
27+
public function processNode(
28+
ParserContext $parserContext,
29+
string $role,
30+
string $content,
31+
string $rawContent,
32+
): AbstractLinkInlineNode {
33+
$referenceTarget = null;
34+
$value = null;
35+
36+
$part = '';
37+
$this->lexer->setInput($content);
38+
$this->lexer->moveNext();
39+
$this->lexer->moveNext();
40+
while ($this->lexer->token !== null) {
41+
$token = $this->lexer->token;
42+
switch ($token->type) {
43+
case InlineLexer::EMBEDED_URL_START:
44+
$value = trim($part);
45+
$part = '';
46+
47+
break;
48+
case InlineLexer::EMBEDED_URL_END:
49+
if ($value === null) {
50+
// not inside the embedded URL
51+
$part .= $token->value;
52+
break;
53+
}
54+
55+
if ($this->lexer->peek() !== null) {
56+
$this->logger->warning(
57+
sprintf(
58+
'Reference contains unexpected content after closing `>`: "%s"',
59+
$content,
60+
),
61+
$parserContext->getLoggerInformation(),
62+
);
63+
}
64+
65+
$referenceTarget = $part;
66+
$part = '';
67+
68+
break 2;
69+
default:
70+
$part .= $token->value;
71+
}
72+
73+
$this->lexer->moveNext();
74+
}
75+
76+
$value .= trim($part);
77+
78+
if ($referenceTarget === null) {
79+
$referenceTarget = $value;
80+
$value = null;
81+
}
82+
83+
return $this->createNode($referenceTarget, $value);
84+
}
85+
86+
abstract protected function createNode(string $referenceName, string $referenceTarget): AbstractLinkInlineNode;
87+
}

packages/guides-restructured-text/src/RestructuredText/TextRoles/DocReferenceTextRole.php

Lines changed: 4 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -4,26 +4,12 @@
44

55
namespace phpDocumentor\Guides\RestructuredText\TextRoles;
66

7+
use phpDocumentor\Guides\Nodes\Inline\AbstractLinkInlineNode;
78
use phpDocumentor\Guides\Nodes\Inline\DocReferenceNode;
8-
use phpDocumentor\Guides\Nodes\Inline\InlineNode;
9-
use phpDocumentor\Guides\ParserContext;
10-
use phpDocumentor\Guides\RestructuredText\Parser\InlineLexer;
11-
use Psr\Log\LoggerInterface;
129

13-
use function sprintf;
14-
use function trim;
15-
16-
class DocReferenceTextRole implements TextRole
10+
class DocReferenceTextRole extends AbstractReferenceTextRole
1711
{
1812
final public const NAME = 'doc';
19-
private InlineLexer $lexer;
20-
21-
public function __construct(
22-
private readonly LoggerInterface $logger,
23-
) {
24-
// Do not inject the $lexer. It contains a state.
25-
$this->lexer = new InlineLexer();
26-
}
2713

2814
public function getName(): string
2915
{
@@ -37,83 +23,8 @@ public function getAliases(): array
3723
}
3824

3925
/** @return DocReferenceNode */
40-
public function processNode(
41-
ParserContext $parserContext,
42-
string $role,
43-
string $content,
44-
string $rawContent,
45-
): InlineNode {
46-
$anchor = null;
47-
$text = null;
48-
$domain = null;
49-
$part = '';
50-
$this->lexer->setInput($content);
51-
$this->lexer->moveNext();
52-
$this->lexer->moveNext();
53-
while ($this->lexer->token !== null) {
54-
$token = $this->lexer->token;
55-
switch ($token->type) {
56-
case InlineLexer::EMBEDED_URL_START:
57-
$text = trim(($domain ? $domain . ':' : '') . $part);
58-
$domain = null;
59-
$part = '';
60-
break;
61-
case InlineLexer::EMBEDED_URL_END:
62-
if ($this->lexer->peek() !== null) {
63-
$this->logger->warning(
64-
sprintf(
65-
'Reference contains unexpected content after closing `>`: "%s"',
66-
$content,
67-
),
68-
$parserContext->getLoggerInformation(),
69-
);
70-
}
71-
72-
break 2;
73-
case InlineLexer::COLON:
74-
$domain = $part;
75-
$part = '';
76-
break;
77-
case InlineLexer::OCTOTHORPE:
78-
$anchor = $this->parseAnchor();
79-
break;
80-
default:
81-
$part .= $token->value;
82-
}
83-
84-
$this->lexer->moveNext();
85-
}
86-
87-
return new DocReferenceNode(
88-
documentLink: trim($part),
89-
anchor: $anchor,
90-
domain: $domain,
91-
text: $text,
92-
);
93-
}
94-
95-
private function parseAnchor(): string
26+
protected function createNode(string $referenceTarget, string|null $referenceName): AbstractLinkInlineNode
9627
{
97-
$anchor = '';
98-
$this->lexer->moveNext();
99-
while ($this->lexer->token !== null) {
100-
$token = $this->lexer->token;
101-
102-
switch ($token->type) {
103-
case InlineLexer::BACKTICK:
104-
case InlineLexer::EMBEDED_URL_END:
105-
$this->lexer->resetPosition($token->position);
106-
107-
return $anchor;
108-
109-
default:
110-
$anchor .= $token->value;
111-
break;
112-
}
113-
114-
$this->lexer->moveNext();
115-
}
116-
117-
return $anchor;
28+
return new DocReferenceNode($referenceTarget, $referenceName ?? '');
11829
}
11930
}

packages/guides-restructured-text/src/RestructuredText/TextRoles/ReferenceTextRole.php

Lines changed: 5 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -4,26 +4,12 @@
44

55
namespace phpDocumentor\Guides\RestructuredText\TextRoles;
66

7-
use phpDocumentor\Guides\Nodes\Inline\InlineNode;
7+
use phpDocumentor\Guides\Nodes\Inline\AbstractLinkInlineNode;
88
use phpDocumentor\Guides\Nodes\Inline\ReferenceNode;
9-
use phpDocumentor\Guides\ParserContext;
10-
use phpDocumentor\Guides\RestructuredText\Parser\InlineLexer;
11-
use Psr\Log\LoggerInterface;
129

13-
use function sprintf;
14-
use function trim;
15-
16-
class ReferenceTextRole implements TextRole
10+
class ReferenceTextRole extends AbstractReferenceTextRole
1711
{
1812
final public const NAME = 'ref';
19-
private InlineLexer $lexer;
20-
21-
public function __construct(
22-
private readonly LoggerInterface $logger,
23-
) {
24-
// Do not inject the $lexer. It contains a state.
25-
$this->lexer = new InlineLexer();
26-
}
2713

2814
public function getName(): string
2915
{
@@ -37,52 +23,8 @@ public function getAliases(): array
3723
}
3824

3925
/** @return ReferenceNode */
40-
public function processNode(
41-
ParserContext $parserContext,
42-
string $role,
43-
string $content,
44-
string $rawContent,
45-
): InlineNode {
46-
$domain = null;
47-
$text = null;
48-
$part = '';
49-
$this->lexer->setInput($content);
50-
$this->lexer->moveNext();
51-
$this->lexer->moveNext();
52-
while ($this->lexer->token !== null) {
53-
$token = $this->lexer->token;
54-
switch ($token->type) {
55-
case InlineLexer::EMBEDED_URL_START:
56-
$text = trim(($domain ? $domain . ':' : '') . $part);
57-
$part = '';
58-
break;
59-
case InlineLexer::EMBEDED_URL_END:
60-
if ($this->lexer->peek() !== null) {
61-
$this->logger->warning(
62-
sprintf(
63-
'Reference contains unexpected content after closing `>`: "%s"',
64-
$content,
65-
),
66-
$parserContext->getLoggerInformation(),
67-
);
68-
}
69-
70-
break 2;
71-
case InlineLexer::COLON:
72-
$domain = $part;
73-
$part = '';
74-
break;
75-
default:
76-
$part .= $token->value;
77-
}
78-
79-
$this->lexer->moveNext();
80-
}
81-
82-
return new ReferenceNode(
83-
referenceName: trim($part),
84-
domain: $domain,
85-
text: $text,
86-
);
26+
protected function createNode(string $referenceTarget, string|null $referenceName): AbstractLinkInlineNode
27+
{
28+
return new ReferenceNode($referenceTarget, $referenceName ?? '');
8729
}
8830
}

packages/guides-restructured-text/tests/unit/TextRoles/DocReferenceTextRoleTest.php

Lines changed: 8 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -28,17 +28,13 @@ public function setUp(): void
2828
public function testDocReferenceIsParsedIntoDocReferenceNode(
2929
string $span,
3030
string $url,
31-
string|null $domain = null,
32-
string|null $anchor = null,
3331
string|null $text = null,
3432
): void {
3533
$result = $this->docReferenceTextRole->processNode($this->parserContext, 'doc', $span, $span);
3634

3735
self::assertInstanceOf(DocReferenceNode::class, $result);
38-
self::assertEquals($url, $result->getDocumentLink(), 'DocumentLinks are different');
39-
self::assertEquals($domain, $result->getDomain(), 'Domains are different');
40-
self::assertEquals($anchor, $result->getAnchor(), 'Anchors are different');
41-
self::assertEquals($text ?? $url, $result->getText());
36+
self::assertEquals($url, $result->getTargetReference(), 'DocumentLinks are different');
37+
self::assertEquals($text ?? '', $result->toString());
4238
}
4339

4440
/** @return array<string, array<string, string|null>> */
@@ -59,34 +55,17 @@ public static function docReferenceProvider(): array
5955
],
6056
'doc with domain' => [
6157
'span' => 'mydomain:path/to/document',
62-
'url' => 'path/to/document',
63-
'domain' => 'mydomain',
64-
],
65-
'doc with anchor' => [
66-
'span' => 'foo/subdoc#anchor',
67-
'url' => 'foo/subdoc',
68-
'domain' => null,
69-
'anchor' => 'anchor',
70-
],
71-
'doc with domain, role and anchor' => [
72-
'span' => 'mydomain:foo/subdoc#anchor',
73-
'url' => 'foo/subdoc',
74-
'domain' => 'mydomain',
75-
'anchor' => 'anchor',
58+
'url' => 'mydomain:path/to/document',
7659
],
7760
'doc role, anchor and custom text' => [
7861
'span' => 'link <mydomain:foo/subdoc#anchor>',
79-
'url' => 'foo/subdoc',
80-
'domain' => 'mydomain',
81-
'anchor' => 'anchor',
62+
'url' => 'mydomain:foo/subdoc#anchor',
8263
'text' => 'link',
8364
],
84-
'doc role, with double point in text' => [
85-
'span' => 'text: sometext <mydomain:foo/subdoc#anchor>',
86-
'url' => 'foo/subdoc',
87-
'domain' => 'mydomain',
88-
'anchor' => 'anchor',
89-
'text' => 'text: sometext',
65+
'doc role, with greater-than character in text' => [
66+
'span' => 'text->sometext <subdoc>',
67+
'url' => 'subdoc',
68+
'text' => 'text->sometext',
9069
],
9170
];
9271
}

packages/guides-restructured-text/tests/unit/TextRoles/ReferenceTextRoleTest.php

Lines changed: 5 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -25,18 +25,16 @@ public function setUp(): void
2525
}
2626

2727
#[DataProvider('referenceProvider')]
28-
public function testReferenceIsParsedIntoDocReferenceNode(
28+
public function testReferenceIsParsedIntoRefReferenceNode(
2929
string $span,
3030
string $url,
31-
string|null $domain = null,
3231
string|null $text = null,
3332
): void {
3433
$result = $this->referenceTextRole->processNode($this->parserContext, 'doc', $span, $span);
3534

3635
self::assertInstanceOf(ReferenceNode::class, $result);
37-
self::assertEquals($url, $result->getReferenceName(), 'ReferenceNames are different');
38-
self::assertEquals($domain, $result->getDomain(), 'Domains are different');
39-
self::assertEquals($text ?? $url, $result->getText());
36+
self::assertEquals($url, $result->getTargetReference(), 'ReferenceNames are different');
37+
self::assertEquals($text ?? '', $result->toString());
4038
}
4139

4240
/** @return array<string, array<string, string|null>> */
@@ -51,23 +49,11 @@ public static function referenceProvider(): array
5149
'span' => 'title ref',
5250
'referenceName' => 'title ref',
5351
],
54-
'ref role with domain' => [
55-
'span' => 'mydomain:title ref',
56-
'referenceName' => 'title ref',
57-
'domain' => 'mydomain',
58-
],
59-
'ref role with domain and custom text' => [
60-
'span' => 'link <mydomain:something>',
52+
'ref role withcustom text' => [
53+
'span' => 'link <something>',
6154
'referenceName' => 'something',
62-
'domain' => 'mydomain',
6355
'text' => 'link',
6456
],
65-
'ref role colon in text' => [
66-
'span' => 'Text: with colon <mydomain:something>',
67-
'referenceName' => 'something',
68-
'domain' => 'mydomain',
69-
'text' => 'Text: with colon',
70-
],
7157
];
7258
}
7359
}

0 commit comments

Comments
 (0)