Skip to content

Commit 0cd1d28

Browse files
authored
Merge pull request #182 from wouterj/field-options-multiline
Add support for multi-line field options
2 parents 58ed5c7 + 9a83515 commit 0cd1d28

File tree

10 files changed

+160
-78
lines changed

10 files changed

+160
-78
lines changed

UPGRADE.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@ The library has been updated to use "link target" instead of "link":
1313
* Renamed `LineDataParser::parseLink()` and `LineDataParser::createLink()` to
1414
`LineDataParser::parseLinkTarget()` and `LineDataParser::createLinkTarget()`.
1515

16+
## Refactored Directive Options
17+
18+
* Removed `DirectiveOption` in favor of `FieldOption` (with a different signature)
19+
* Removed `LineDataParser::parseDirectiveOption()` in favor of `LineDataParser::parseFieldOption()`
20+
1621
# Upgrade to 0.5
1722

1823
## Property visibility changed from protected to private

lib/Parser/DirectiveOption.php

Lines changed: 0 additions & 36 deletions
This file was deleted.

lib/Parser/DocumentParser.php

Lines changed: 31 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,9 @@ final class DocumentParser
109109
/** @var string|null */
110110
private $listMarker = null;
111111

112+
/** @var FieldOption|null */
113+
private $fieldOption = null;
114+
112115
/**
113116
* @param Directive[] $directives
114117
*/
@@ -454,20 +457,34 @@ private function parseLine(string $line): bool
454457
break;
455458

456459
case State::DIRECTIVE:
457-
if (! $this->isDirectiveOption($line)) {
458-
if (! $this->lineChecker->isDirective($line)) {
459-
$directive = $this->getCurrentDirective();
460-
$this->isCode = $directive !== null ? $directive->wantCode() : false;
461-
$this->setState(State::BEGIN);
460+
if ($this->lineChecker->isDirective($line) && $this->directive === null) {
461+
$this->flush();
462+
$this->initDirective($line);
462463

463-
return false;
464+
break;
465+
}
466+
467+
if ($this->fieldOption !== null && $this->lineChecker->isBlockLine($line, $this->fieldOption->getOffset())) {
468+
$this->fieldOption->appendLine($line);
469+
470+
break;
471+
}
472+
473+
if ($this->lineChecker->isFieldOption($line)) {
474+
if ($this->fieldOption !== null) {
475+
$this->directive->setOption($this->fieldOption->getName(), $this->fieldOption->getBody());
464476
}
465477

466-
$this->flush();
467-
$this->initDirective($line);
478+
$this->fieldOption = $this->lineDataParser->parseFieldOption($line);
479+
480+
break;
468481
}
469482

470-
break;
483+
$directive = $this->getCurrentDirective();
484+
$this->isCode = $directive !== null ? $directive->wantCode() : false;
485+
$this->setState(State::BEGIN);
486+
487+
return false;
471488

472489
default:
473490
$this->environment->getErrorManager()->error('Parser ended in an unexcepted state');
@@ -599,6 +616,11 @@ private function flush(): void
599616
if ($this->directive !== null) {
600617
$currentDirective = $this->getCurrentDirective();
601618

619+
if ($this->fieldOption !== null) {
620+
$this->directive->setOption($this->fieldOption->getName(), $this->fieldOption->getBody());
621+
$this->fieldOption = null;
622+
}
623+
602624
if ($currentDirective !== null) {
603625
try {
604626
$currentDirective->process(
@@ -646,23 +668,6 @@ private function getCurrentDirective(): ?Directive
646668
return $this->directives[$name];
647669
}
648670

649-
private function isDirectiveOption(string $line): bool
650-
{
651-
if ($this->directive === null) {
652-
return false;
653-
}
654-
655-
$directiveOption = $this->lineDataParser->parseDirectiveOption($line);
656-
657-
if ($directiveOption === null) {
658-
return false;
659-
}
660-
661-
$this->directive->setOption($directiveOption->getName(), $directiveOption->getValue());
662-
663-
return true;
664-
}
665-
666671
private function initDirective(string $line): bool
667672
{
668673
$parserDirective = $this->lineDataParser->parseDirective($line);

lib/Parser/FieldOption.php

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Doctrine\RST\Parser;
6+
7+
use function ltrim;
8+
use function str_replace;
9+
use function strlen;
10+
use function trim;
11+
12+
final class FieldOption
13+
{
14+
/** @var string */
15+
private $name;
16+
17+
/** @var int */
18+
private $offset;
19+
20+
/** @var string */
21+
private $body;
22+
23+
/** @var int */
24+
private $lineCount = 0;
25+
26+
public function __construct(string $name, int $offset, string $body)
27+
{
28+
$this->name = str_replace('\: ', ': ', $name);
29+
$this->offset = $offset + 1;
30+
$this->body = $body;
31+
}
32+
33+
public function appendLine(string $line): void
34+
{
35+
$trimmedLine = ltrim($line);
36+
if (strlen($trimmedLine) === 0) {
37+
return;
38+
}
39+
40+
if (++$this->lineCount === 1) {
41+
$this->offset = strlen($line) - strlen($trimmedLine);
42+
}
43+
44+
$this->body .= ' ' . $trimmedLine;
45+
}
46+
47+
public function getName(): string
48+
{
49+
return $this->name;
50+
}
51+
52+
public function getOffset(): int
53+
{
54+
return $this->offset;
55+
}
56+
57+
/**
58+
* @return true|string
59+
*/
60+
public function getBody()
61+
{
62+
return trim($this->body) === '' ? true : $this->body;
63+
}
64+
}

lib/Parser/LineChecker.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,11 @@ public function isDirective(string $line): bool
122122
return preg_match('/^\.\. (\|(.+)\| |)([^\s]+)::( (.*)|)$/mUsi', $line) > 0;
123123
}
124124

125+
public function isFieldOption(string $line, int $offset = 0): bool
126+
{
127+
return preg_match('/^\s*:(?:\\\\:\s|[^:]|:\S)+:(?: .+|$)/', $line) > 0;
128+
}
129+
125130
/**
126131
* Check if line is an indented one.
127132
*

lib/Parser/LineDataParser.php

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -80,21 +80,6 @@ private function createLinkTarget(string $name, string $url, string $type): Link
8080
return new Link($name, $url, $type);
8181
}
8282

83-
public function parseDirectiveOption(string $line): ?DirectiveOption
84-
{
85-
if (preg_match('/^(\s+):(.+): (.*)$/mUsi', $line, $match) > 0) {
86-
return new DirectiveOption($match[2], trim($match[3]));
87-
}
88-
89-
if (preg_match('/^(\s+):(.+):(\s*)$/mUsi', $line, $match) > 0) {
90-
$value = trim($match[3]);
91-
92-
return new DirectiveOption($match[2], true);
93-
}
94-
95-
return null;
96-
}
97-
9883
public function parseDirective(string $line): ?Directive
9984
{
10085
if (preg_match('/^\.\. (\|(.+)\| |)([^\s]+)::( (.*)|)$/mUsi', $line, $match) > 0) {
@@ -233,4 +218,13 @@ public function parseDefinitionList(array $lines): DefinitionList
233218

234219
return new DefinitionList($definitionList);
235220
}
221+
222+
public function parseFieldOption(string $line): ?FieldOption
223+
{
224+
if (preg_match('/^(?P<offset>\s*):(?P<name>(?:\\\\:\s|[^:]|:\S)+):(?: +(?P<body>.+)|)$/', $line, $match) > 0) {
225+
return new FieldOption($match['name'], strlen($match['offset']), $match['body'] ?? '');
226+
}
227+
228+
return null;
229+
}
236230
}

tests/Functional/tests/figure/figure.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<figure>
2-
<img src="foo.jpg" width="100" />
2+
<img src="foo.jpg" width="100" alt="Field options might use more than one line" />
33
<figcaption>
44
<p>This is a foo!</p>
55
</figcaption>
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
.. figure:: foo.jpg
22
:width: 100
3+
:alt: Field options might use
4+
more than one line
35

46
This is a foo!

tests/Parser/LineCheckerTest.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,4 +91,16 @@ public function testIsDefinitionListEnded(): void
9191
self::assertFalse($this->lineChecker->isDefinitionListEnded('Term', ' Definition'));
9292
self::assertFalse($this->lineChecker->isDefinitionListEnded('', ' Definition'));
9393
}
94+
95+
public function testIsFieldList(): void
96+
{
97+
self::assertTrue($this->lineChecker->isFieldOption(':glob:'));
98+
self::assertTrue($this->lineChecker->isFieldOption(':Date: 2001-08-16'));
99+
self::assertTrue($this->lineChecker->isFieldOption(' :Date: 2001-08-16'));
100+
self::assertTrue($this->lineChecker->isFieldOption(':Date published: 2001-08-16'));
101+
self::assertTrue($this->lineChecker->isFieldOption(':Date:published: 2001-08-16'));
102+
self::assertTrue($this->lineChecker->isFieldOption(':Date\: published: 2001-08-16'));
103+
self::assertTrue($this->lineChecker->isFieldOption(':Date: published: 2001-08-16'));
104+
self::assertFalse($this->lineChecker->isFieldOption('Date: 2001-08-16'));
105+
}
94106
}

tests/Parser/LineDataParserTest.php

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,37 @@ public function getTestLinks(): array
5252
];
5353
}
5454

55+
/**
56+
* @param true|string $body
57+
*
58+
* @dataProvider getTestFieldOptions
59+
*/
60+
public function testParseFieldOptions(string $line, ?string $name, $body = null): void
61+
{
62+
$actual = $this->lineDataParser->parseFieldOption($line);
63+
if ($name === null) {
64+
self::assertNull($actual);
65+
} else {
66+
self::assertNotNull($actual);
67+
self::assertSame($name, $actual->getName(), 'Incorrect field option name');
68+
self::assertSame($body, $actual->getBody(), 'Incorrect field option body');
69+
}
70+
}
71+
72+
/**
73+
* @return array{string, string|null, true|string}[]
74+
*/
75+
public function getTestFieldOptions(): array
76+
{
77+
return [
78+
[':glob:', 'glob', true],
79+
[':alt: Some text', 'alt', 'Some text'],
80+
[':date:published: 2022-09-20', 'date:published', '2022-09-20'],
81+
[':date\: published: 2022-09-20', 'date: published', '2022-09-20'],
82+
[':date: published: 2022-09-20', 'date', 'published: 2022-09-20'],
83+
];
84+
}
85+
5586
protected function setUp(): void
5687
{
5788
$this->parser = $this->createMock(Parser::class);

0 commit comments

Comments
 (0)