diff --git a/docs/sphinx/reference.md b/docs/sphinx/reference.md index 5d3235c8..74b0c89f 100644 --- a/docs/sphinx/reference.md +++ b/docs/sphinx/reference.md @@ -32,6 +32,9 @@ To do so, use the keywords beginning `myst_`. * - `myst_linkify_fuzzy_links` - `True` - If `False`, only links that contain a scheme (such as `http`) will be recognised as external links. +* - `myst_title_to_header` + - `False` + - If `True`, the `title` key of a document front-matter is converted to a header at the top of the document. * - `myst_heading_anchors` - `None` - Enable auto-generated heading anchors, up to a maximum level, [see here](syntax/header-anchors) for details. diff --git a/docs/syntax/syntax.md b/docs/syntax/syntax.md index df27ea8c..05f5efe5 100644 --- a/docs/syntax/syntax.md +++ b/docs/syntax/syntax.md @@ -398,9 +398,27 @@ This is a YAML block at the start of the document, as used for example in :::{seealso} Top-matter is also used for the [substitution syntax extension](syntax/substitutions), -and can be used to store information for blog posting (see [ablog's myst-parser support](https://ablog.readthedocs.io/manual/markdown/)). +and can be used to store information for blog posting (see [ablog's myst-parser support](https://ablog.readthedocs.io/en/latest/manual/markdown/)). ::: +### Setting a title + +If `myst_title_to_header` is set to `True`, and a `title` key is present in the front matter, +then the title will be used as the document's header (parsed as Markdown. +For example: + +```md +--- +title: My Title with *emphasis* +--- +``` + +would be equivalent to: + +```md +# My Title with *emphasis* +``` + (syntax/html_meta)= ### Setting HTML Metadata diff --git a/myst_parser/docutils_renderer.py b/myst_parser/docutils_renderer.py index aac3655e..eba7718b 100644 --- a/myst_parser/docutils_renderer.py +++ b/myst_parser/docutils_renderer.py @@ -831,6 +831,9 @@ def render_front_matter(self, token: SyntaxTreeNode) -> None: ) ) + if data.get("title") and self.md_config.title_to_header: + self.nested_render_text(f"# {data['title']}", 0) + def dict_to_fm_field_list( self, data: Dict[str, Any], language_code: str, line: int = 0 ) -> nodes.field_list: diff --git a/myst_parser/main.py b/myst_parser/main.py index 5994855d..d094b239 100644 --- a/myst_parser/main.py +++ b/myst_parser/main.py @@ -1,3 +1,6 @@ +"""This module holds the global configuration for the parser ``MdParserConfig``, +and the ``create_md_parser`` function, which creates a parser from the config. +""" from typing import Any, Callable, Dict, Iterable, Optional, Sequence, Tuple, Union, cast import attr @@ -146,6 +149,12 @@ def check_extensions(self, attribute, value): metadata={"help": "Add line numbers to code blocks with these languages"}, ) + title_to_header: bool = attr.ib( + default=False, + validator=instance_of(bool), + metadata={"help": "Convert a `title` field in the top-matter to a H1 header"}, + ) + heading_anchors: Optional[int] = attr.ib( default=None, validator=optional(in_([1, 2, 3, 4, 5, 6, 7])), diff --git a/tests/test_renderers/fixtures/myst-config.txt b/tests/test_renderers/fixtures/myst-config.txt new file mode 100644 index 00000000..ef4a81f6 --- /dev/null +++ b/tests/test_renderers/fixtures/myst-config.txt @@ -0,0 +1,26 @@ +[title-to-header] --myst-title-to-header="yes" +. +--- +title: "The title *nested syntax*" +--- + +# Other header +. + + + + + title + + + + The title *nested syntax* +
+ + The title + <emphasis> + nested syntax + <section ids="other-header" names="other\ header"> + <title> + Other header +. diff --git a/tests/test_renderers/test_error_reporting.py b/tests/test_renderers/test_error_reporting.py index daa677e8..e6cfa6f0 100644 --- a/tests/test_renderers/test_error_reporting.py +++ b/tests/test_renderers/test_error_reporting.py @@ -1,3 +1,4 @@ +"""Tests of the warning reporting for different MyST Markdown inputs.""" from io import StringIO from pathlib import Path diff --git a/tests/test_renderers/test_myst_config.py b/tests/test_renderers/test_myst_config.py new file mode 100644 index 00000000..24af9afc --- /dev/null +++ b/tests/test_renderers/test_myst_config.py @@ -0,0 +1,34 @@ +"""Test (docutils) parsing with different ``MdParserConfig`` options set.""" +import shlex +from io import StringIO +from pathlib import Path + +from docutils.core import Publisher, publish_doctree +from pytest_param_files import with_parameters + +from myst_parser.docutils_ import Parser + +FIXTURE_PATH = Path(__file__).parent.joinpath("fixtures") + + +@with_parameters(FIXTURE_PATH / "myst-config.txt") +def test_cmdline(file_params): + """The description is parsed as a docutils commandline""" + pub = Publisher(parser=Parser()) + option_parser = pub.setup_option_parser() + try: + settings = option_parser.parse_args( + shlex.split(file_params.description) + ).__dict__ + except Exception as err: + raise AssertionError( + f"Failed to parse commandline: {file_params.description}\n{err}" + ) + report_stream = StringIO() + settings["warning_stream"] = report_stream + doctree = publish_doctree( + file_params.content, + parser=Parser(), + settings_overrides=settings, + ) + file_params.assert_expected(doctree.pformat(), rstrip_lines=True)