Skip to content

Commit 68c4f10

Browse files
committed
DOCSP-5504: Only match PAT_EXPLICIT_TILE if needed by role
This PR retools how rst roles are handled, and categorizes them in three ways: * text roles only provide a label field in the AST. * explicit_title roles provide a target field in the AST, as well as optionally a label field. * link roles do not emit a role node at all; instead, they emit a reference with the refuri already set.
1 parent 7fd633c commit 68c4f10

File tree

10 files changed

+240
-50
lines changed

10 files changed

+240
-50
lines changed

snooty/gizaparser/test_steps.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,9 @@ def create_page() -> Tuple[Page, EmbeddedRstParser]:
5353

5454
'<directive name="step"><section><heading><text>Create a </text><literal><text>',
5555
'/etc/apt/sources.list.d/mongodb-org-3.4.list</text></literal><text> file for </text>',
56-
'<role name="guilabel" label="MongoDB" target="MongoDB" raw="MongoDB"></role>',
56+
'<role name="guilabel" label="',
57+
'{\'type\': \'text\', \'value\': \'MongoDB\', \'position\': {\'start\': {\'line\': 1}}}',
58+
'"></role>',
5759
'<text>.</text></heading>',
5860
'<section><heading><text>Optional: action heading</text></heading>'
5961
'<paragraph><text>Create the list file using the command appropriate for ',

snooty/parser.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -139,9 +139,10 @@ def dispatch_visit(self, node: docutils.nodes.Node) -> None:
139139
return
140140
elif node_name == 'role':
141141
doc['name'] = node['name']
142-
doc['label'] = node['label']
143-
doc['target'] = node['target']
144-
doc['raw'] = node['raw']
142+
if 'label' in node:
143+
doc['label'] = node['label']
144+
if 'target' in node:
145+
doc['target'] = node['target']
145146
elif node_name == 'target':
146147
doc['type'] = 'target'
147148
doc['ids'] = node['ids']

snooty/rstparser.py

Lines changed: 73 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -61,22 +61,73 @@ class code(docutils.nodes.General, docutils.nodes.FixedTextElement):
6161

6262

6363
class role(docutils.nodes.Inline, docutils.nodes.Element):
64-
def __init__(self, name: str, rawtext: str, text: str, lineno: int) -> None:
64+
"""Docutils node representing a role."""
65+
def __init__(self, name: str, lineno: int,
66+
label: Optional[str], target: Optional[str]) -> None:
6567
super(role, self).__init__()
6668
self['name'] = name
67-
self['raw'] = text
6869

69-
match = PAT_EXPLICIT_TILE.match(text)
70-
if match:
70+
if label is not None:
7171
self['label'] = {
7272
'type': 'text',
73-
'value': match['label'],
73+
'value': label,
7474
'position': {'start': {'line': lineno}}
7575
}
76-
self['target'] = match['target']
76+
77+
if target is not None:
78+
self['target'] = target
79+
80+
81+
def handle_role_text(typ: str,
82+
rawtext: str,
83+
text: str,
84+
lineno: int,
85+
inliner: docutils.parsers.rst.states.Inliner,
86+
options: Dict[str, object] = {},
87+
content: List[object] = []) -> Tuple[
88+
List[docutils.nodes.Node], List[docutils.nodes.Node]]:
89+
"""Handle roles with plain text content."""
90+
node = role(typ, lineno, text, None)
91+
return [node], []
92+
93+
94+
def handle_role_explicit_title(typ: str, rawtext: str, text: str,
95+
lineno: int, inliner: docutils.parsers.rst.states.Inliner,
96+
options: Dict[str, object] = {},
97+
content: List[object] = []) -> Tuple[
98+
List[docutils.nodes.Node], List[docutils.nodes.Node]]:
99+
"""Handle link-like roles with a target and an optional title."""
100+
match = PAT_EXPLICIT_TILE.match(text)
101+
if match:
102+
node = role(typ, lineno, match['label'], match['target'])
103+
else:
104+
node = role(typ, lineno, None, text)
105+
106+
return [node], []
107+
108+
109+
class LinkRoleHandler:
110+
"""Handle roles which generate a link from a template."""
111+
def __init__(self, url_template: str) -> None:
112+
self.url_template = url_template
113+
114+
def __call__(self, typ: str, rawtext: str, text: str,
115+
lineno: int, inliner: docutils.parsers.rst.states.Inliner,
116+
options: Dict[str, object] = {},
117+
content: List[object] = []) -> Tuple[
118+
List[docutils.nodes.Node], List[docutils.nodes.Node]]:
119+
match = PAT_EXPLICIT_TILE.match(text)
120+
label: Optional[str] = None
121+
if match:
122+
label, target = match['label'], match['target']
77123
else:
78-
self['label'] = text
79-
self['target'] = text
124+
target = text
125+
126+
url = self.url_template % target
127+
if not label:
128+
label = url
129+
node = docutils.nodes.reference(label, label, internal=False, refuri=url)
130+
return [node], []
80131

81132

82133
def parse_linenos(term: str, max_val: int) -> List[Tuple[int, int]]:
@@ -267,14 +318,6 @@ def run(self) -> List[docutils.nodes.Node]:
267318
return [node]
268319

269320

270-
def handle_role(typ: str, rawtext: str, text: str,
271-
lineno: int, inliner: object,
272-
options: Dict[str, object] = {},
273-
content: List[object] = []) -> Tuple[List[object], List[object]]:
274-
node = role(typ, rawtext, text, lineno)
275-
return [node], []
276-
277-
278321
class NoTransformRstParser(docutils.parsers.rst.Parser):
279322
def get_transforms(self) -> List[object]:
280323
return []
@@ -303,6 +346,7 @@ def register_spec_with_docutils(spec: specparser.Spec) -> None:
303346
directives = list(spec.directive.items())
304347
roles = list(spec.role.items())
305348

349+
# Define rstobjects
306350
for name, rst_object in spec.rstobject.items():
307351
directive = rst_object.create_directive()
308352
role = rst_object.create_role()
@@ -340,8 +384,20 @@ class DocutilsDirective(BaseDocutilsDirective):
340384
docutils.parsers.rst.directives.register_directive('guide', LegacyGuideDirective)
341385
docutils.parsers.rst.directives.register_directive('guide-index', LegacyGuideIndexDirective)
342386

387+
# Define roles
343388
for name, role_spec in roles:
344-
docutils.parsers.rst.roles.register_local_role(name, handle_role)
389+
handler = None
390+
if not role_spec.type or role_spec.type == specparser.PrimitiveRoleType.text:
391+
handler = handle_role_text
392+
elif isinstance(role_spec.type, specparser.LinkRoleType):
393+
handler = LinkRoleHandler(role_spec.type.link)
394+
elif role_spec.type == specparser.PrimitiveRoleType.explicit_title:
395+
handler = handle_role_explicit_title
396+
397+
if not handler:
398+
raise ValueError('Unknown role type "{}"'.format(role_spec.type))
399+
400+
docutils.parsers.rst.roles.register_local_role(name, handler)
345401

346402

347403
class Parser(Generic[_V]):

snooty/rstspec.toml

Lines changed: 41 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -267,142 +267,158 @@ inherit = "_guides-base"
267267
###### Roles
268268
[role.guilabel]
269269
help = """Used to specify a label or button in a GUI."""
270+
type = "text"
270271

271272
[role.abbr]
272273
help = """Abbreviation with hover text."""
274+
type = "text"
273275

274276
[role.file]
275277
help = """Show a file path."""
278+
type = "text"
276279

277280
[role.icon-fa5]
278281
help = """Show a FontAwesome 5 Solid icon."""
282+
type = "explicit_title"
279283

280284
[role.icon]
281285
inherit = "icon-fa5"
286+
type = "explicit_title"
282287

283288
[role.icon-fa5-brands]
284289
help = """Show a FontAwesome 5 Brand icon."""
290+
type = "explicit_title"
285291

286292
[role.iconb]
287293
inherit = "icon-fa5-brands"
288294

289295
[role.icon-mms]
290296
help = """Show an MMS icon."""
297+
type = "explicit_title"
291298

292299
[role.icon-mms-org]
293300
help = """Show an MMS-org icon."""
301+
type = "explicit_title"
294302

295303
[role.icon-charts]
296304
help = """Show a MongoDB Charts icon."""
305+
type = "explicit_title"
297306

298307
[role.icon-fa4]
299308
help = """Show a FontAwesome 4 icon."""
309+
type = "explicit_title"
300310

301311
[role.xml]
302312
help = """Use XML to create reStructuredText nodes."""
313+
type = "text"
303314

304315
[role.rfc]
305316
help = """Reference an IETF RFC."""
317+
type = "explicit_title"
306318

307319
[role.hardlink]
308320
help = """Link to a URL in the current project."""
321+
type = "explicit_title"
309322

310323
[role.doc]
311324
help = """Link to a page in the current project."""
325+
type = "explicit_title"
312326

313327
[role.manual]
314328
help = """Link to a page in the latest stable version of the MongoDB manual."""
315-
type = "link http://docs.mongodb.com/manual%s"
329+
type = {link = "https://docs.mongodb.com/manual%s"}
316330

317331
[role.ref]
318332
help = """Link to a named target."""
333+
type = "explicit_title"
319334

320335
[role.term]
321336
help = """Link to a term in the glossary."""
337+
type = "explicit_title"
322338

323339
[role.issue]
324340
help = """Link to a JIRA ticket."""
325-
type = "link https://jira.mongodb.org/browse/%s"
341+
type = {link = "https://jira.mongodb.org/browse/%s"}
326342

327343
[role.perl-api]
328344
help = "Link to a page in the Perl driver's CPAN API reference."
329-
type = "link https://metacpan.org/pod/MongoDB::%s"
345+
type = {link = "https://metacpan.org/pod/MongoDB::%s"}
330346

331347
[role.node-docs]
332348
help = """Link to a page in the Node.js driver's documentation."""
333-
type = "link http://mongodb.github.io/node-mongodb-native/3.0/%s"
349+
type = {link = "http://mongodb.github.io/node-mongodb-native/3.0/%s"}
334350

335351
[role.node-api]
336352
help = """Link to a page in the Node.js driver's API reference."""
337-
type = "link http://mongodb.github.io/node-mongodb-native/3.0/api/%s"
353+
type = {link = "http://mongodb.github.io/node-mongodb-native/3.0/api/%s"}
338354

339355
[role.ruby-api]
340356
help = """Link to a page in the Ruby driver's API reference."""
341-
type = "link http://api.mongodb.com/ruby/current/Mongo/%s"
357+
type = {link = "http://api.mongodb.com/ruby/current/Mongo/%s"}
342358

343359
[role.scala-api]
344360
help = """Link to a page in the Scala driver's API reference."""
345-
type = "link http://mongodb.github.io/mongo-scala-driver/2.2/scaladoc/org/mongodb/scala/MongoCollection.html#%s"
361+
type = {link = "http://mongodb.github.io/mongo-scala-driver/2.2/scaladoc/org/mongodb/scala/MongoCollection.html#%s"}
346362

347363
[role.csharp-docs]
348364
help = """Link to a page in the C# driver's documentation."""
349-
type = "link https://mongodb.github.io/mongo-csharp-driver/2.5/reference/%s"
365+
type = {link = "https://mongodb.github.io/mongo-csharp-driver/2.5/reference/%s"}
350366

351367
[role.csharp-api]
352368
help = """Link to a page in the C# driver's API reference."""
353-
type = "link https://mongodb.github.io/mongo-csharp-driver/2.5/apidocs/html/%s.htm"
369+
type = {link = "https://mongodb.github.io/mongo-csharp-driver/2.5/apidocs/html/%s.htm"}
354370

355371
[role.java-async-docs]
356372
help = """Link to the async Java driver's documentation."""
357-
type = "link http://mongodb.github.io/mongo-java-driver/3.7/%s"
373+
type = {link = "http://mongodb.github.io/mongo-java-driver/3.7/%s"}
358374

359375
[role.java-async-api]
360376
help = """Link to the async Java driver's API reference."""
361-
type = "link http://mongodb.github.io/mongo-java-driver/3.7/javadoc/%s"
377+
type = {link = "http://mongodb.github.io/mongo-java-driver/3.7/javadoc/%s"}
362378

363379
[role.java-sync-api]
364380
help = """Link to the synchronous Java driver's API reference."""
365-
type = "link http://mongodb.github.io/mongo-java-driver/3.7/javadoc/%s"
381+
type = {link = "http://mongodb.github.io/mongo-java-driver/3.7/javadoc/%s"}
366382

367383
[role.atlas]
368384
help = """Link to the synchronous Atlas documentation."""
369-
type = "link https://docs.atlas.mongodb.com/%s"
385+
type = {link = "https://docs.atlas.mongodb.com/%s"}
370386

371387
[role."v2.2"]
372-
type = "link https://docs.mongodb.com/v2.2%s"
388+
type = {link = "https://docs.mongodb.com/v2.2%s"}
373389

374390
[role."v2.4"]
375-
type = "link https://docs.mongodb.com/v2.4%s"
391+
type = {link = "https://docs.mongodb.com/v2.4%s"}
376392

377393
[role."v2.6"]
378-
type = "link https://docs.mongodb.com/v2.6%s"
394+
type = {link = "https://docs.mongodb.com/v2.6%s"}
379395

380396
[role."v3.0"]
381-
type = "link https://docs.mongodb.com/v3.0%s"
397+
type = {link = "https://docs.mongodb.com/v3.0%s"}
382398

383399
[role."v3.2"]
384-
type = "link https://docs.mongodb.com/v3.2%s"
400+
type = {link = "https://docs.mongodb.com/v3.2%s"}
385401

386402
[role."v3.4"]
387-
type = "link https://docs.mongodb.com/v3.4%s"
403+
type = {link = "https://docs.mongodb.com/v3.4%s"}
388404

389405
[role."v3.6"]
390-
type = "link https://docs.mongodb.com/v3.6%s"
406+
type = {link = "https://docs.mongodb.com/v3.6%s"}
391407

392408
[role."v4.0"]
393-
type = "link https://docs.mongodb.com/v4.0%s"
409+
type = {link = "https://docs.mongodb.com/v4.0%s"}
394410

395411
[role.bic]
396-
type = "link https://docs.mongodb.com/bi-connector/current%s"
412+
type = {link = "https://docs.mongodb.com/bi-connector/current%s"}
397413

398414
[role.k8s]
399-
type = "link https://docs.mongodb.com/kubernetes-operator/stable%s"
415+
type = {link = "https://docs.mongodb.com/kubernetes-operator/stable%s"}
400416

401417
[role.product]
402-
type = "link http://www.mongodb.com/products/%s?jmp=docs"
418+
type = {link = "http://www.mongodb.com/products/%s?jmp=docs"}
403419

404420
[role.dl]
405-
type = "link http://www.mongodb.com/download-center/%s?jmp=docs"
421+
type = {link = "http://www.mongodb.com/download-center/%s?jmp=docs"}
406422

407423
### Types of objects (directive & role pairs)
408424
[rstobject."py:class"]

snooty/specparser.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,13 @@ class _Inheritable(Protocol):
1717
inherit: Optional[str]
1818

1919

20+
@checked
21+
@dataclass
22+
class LinkRoleType:
23+
"""Configuration for a role which links to a specific URL template."""
24+
link: str
25+
26+
2027
_T = TypeVar('_T', bound=_Inheritable)
2128
SPEC_VERSION = 0
2229
StringOrStringlist = Union[List[str], str, None]
@@ -31,6 +38,14 @@ class _Inheritable(Protocol):
3138
'flag',
3239
'linenos'
3340
))
41+
PrimitiveRoleType = Enum('PrimitiveRoleType', (
42+
'text',
43+
'explicit_title'
44+
))
45+
46+
#: Spec definition of a role: this can be either a PrimitiveRoleType, or
47+
#: an object requiring additional configuration.
48+
RoleType = Union[PrimitiveRoleType, LinkRoleType]
3449

3550
#: docutils option validation function for each of the above primitive types
3651
VALIDATORS: Dict[PrimitiveType, Callable[[Any], Any]] = {
@@ -83,7 +98,7 @@ class Role:
8398
inherit: Optional[str]
8499
help: Optional[str]
85100
example: Optional[str]
86-
type: Optional[ArgumentType]
101+
type: Optional[RoleType]
87102
deprecated: bool = field(default=False)
88103

89104

@@ -112,7 +127,7 @@ def create_role(self) -> Role:
112127
inherit=None,
113128
help=self.help,
114129
example=None,
115-
type=None,
130+
type=PrimitiveRoleType.explicit_title,
116131
deprecated=self.deprecated)
117132

118133

0 commit comments

Comments
 (0)