Skip to content

Commit

Permalink
Add colon syntax for directive options. (#50)
Browse files Browse the repository at this point in the history
Also add error reporting for YAML block parsing.
  • Loading branch information
chrisjsewell committed Feb 19, 2020
1 parent 4150051 commit ef7c18a
Show file tree
Hide file tree
Showing 6 changed files with 214 additions and 58 deletions.
13 changes: 13 additions & 0 deletions docs/using/syntax.md
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,19 @@ print('my 1st line')
print(f'my {a}nd line')
```

As a short-hand alternative, more closely resembling the reStructuredText syntax, options may also be denoted by an initial block, whereby all lines start with '`:`', for example:

````
```{code-block} python
:lineno-start: 10
:emphasize-lines: 1, 3
a = 2
print('my 1st line')
print(f'my {a}nd line')
```
````

### Nesting directives

You can nest directives by ensuring that the ticklines corresponding to the
Expand Down
79 changes: 71 additions & 8 deletions myst_parser/docutils_renderer.py
Original file line number Diff line number Diff line change
Expand Up @@ -421,25 +421,88 @@ def render_role(self, token):
self.current_node += problematic

def render_directive(self, token):
"""parse fenced code blocks as directives.
Such a fenced code block starts with `{directive_name}`,
followed by arguments on the same line.
Directive options are read from a YAML block,
if the first content line starts with `---`, e.g.
::
```{directive_name} arguments
---
option1: name
option2: |
Longer text block
---
content...
```
Or the option block will be parsed if the first content line starts with `:`,
as a YAML block consisting of every line that starts with a `:`, e.g.
::
```{directive_name} arguments
:option1: name
:option2: other
content...
```
If the first line of a directive's content is blank, this will be stripped
from the content.
This is to allow for separation between the option block and content.
"""
name = token.language[1:-1]
content = token.children[0].content
options = {}
# get YAML options
if content.startswith("---"):
content = "\n".join(content.splitlines()[1:])
# get YAML options
match = re.search(r"^-{3,}", content, re.MULTILINE)
if match:
yaml_block = content[: match.start()]
content = content[match.end() :] # TODO advance line number
content = content[match.end() + 1 :] # TODO advance line number
else:
yaml_block = content
content = ""
try:
options = yaml.safe_load(yaml_block) or {}
except yaml.parser.ParserError:
# TODO handle/report yaml parse error
pass
# TODO check options are an un-nested dict?
except (yaml.parser.ParserError, yaml.scanner.ScannerError) as error:
msg_node = self.document.reporter.system_message(
3, "Directive options:\n" + str(error), line=token.range[0]
) # 3 is ERROR level
msg_node += nodes.literal_block(yaml_block, yaml_block)
self.current_node += [msg_node]
return
# TODO check options are an un-nested dict / json serialize ?
elif content.startswith(":"):
content_lines = content.splitlines() # type: list
yaml_lines = []
while content_lines:
if not content_lines[0].startswith(":"):
break
yaml_lines.append(content_lines.pop(0)[1:])
yaml_block = "\n".join(yaml_lines)
content = "\n".join(content_lines)
try:
options = yaml.safe_load(yaml_block) or {}
except (yaml.parser.ParserError, yaml.scanner.ScannerError) as error:
msg_node = self.document.reporter.system_message(
3, "Directive options:\n" + str(error), line=token.range[0]
) # 3 is ERROR level
msg_node += nodes.literal_block(yaml_block, yaml_block)
self.current_node += [msg_node]
return

# remove first line if blank
content_lines = content.splitlines()
if content_lines and not content_lines[0].strip():
content_lines = content_lines[1:]

# TODO directive name white/black lists
directive_class, messages = directives.directive(
Expand All @@ -466,13 +529,13 @@ def render_directive(self, token):
# TODO option parsing
options=options,
# the directive content line by line
content=content.splitlines(),
content=content_lines,
# the absolute line number of the first line of the directive
lineno=token.range[0],
# the line offset of the first line of the content
content_offset=0,
# a string containing the entire directive
block_text=content,
block_text="\n".join(content_lines),
state=MockState(self, state_machine, token.range[0]),
state_machine=state_machine,
)
Expand Down
4 changes: 3 additions & 1 deletion myst_parser/sphinx_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,4 +169,6 @@ def parse(self, inputstring, document):
pass
renderer = SphinxRenderer(document=document)
with renderer:
renderer.render(Document(inputstring))
# TODO capture parsing errors and report via docutils/sphinx
tokens = Document(inputstring)
renderer.render(tokens)
4 changes: 4 additions & 0 deletions pytest.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[pytest]
addopts = --ignore=setup.py
markers =
sphinx: set parameters for the sphinx `app` fixture (see ipypublish/sphinx/tests/conftest.py)

0 comments on commit ef7c18a

Please sign in to comment.