From 8ae301e0eb94cbb05f4f5064927a8a400ceedaa4 Mon Sep 17 00:00:00 2001 From: "daniel.eades" Date: Fri, 26 Jan 2024 12:57:36 +0000 Subject: [PATCH 1/9] glob 'bugbear' lints and use exception chaining --- myst_parser/mocking.py | 10 +++++----- myst_parser/parsers/docutils_.py | 8 ++++---- pyproject.toml | 2 +- tests/test_renderers/test_fixtures_docutils.py | 2 +- tests/test_renderers/test_fixtures_sphinx.py | 2 +- tests/test_renderers/test_myst_config.py | 2 +- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/myst_parser/mocking.py b/myst_parser/mocking.py index a6f64319..bf781dfa 100644 --- a/myst_parser/mocking.py +++ b/myst_parser/mocking.py @@ -371,14 +371,14 @@ def run(self) -> list[nodes.Element]: # tab_width = self.options.get("tab-width", self.document.settings.tab_width) try: file_content = path.read_text(encoding=encoding, errors=error_handler) - except FileNotFoundError: + except FileNotFoundError as error: raise DirectiveError( 4, f'Directive "{self.name}": file not found: {str(path)!r}' - ) + ) from error except Exception as error: raise DirectiveError( 4, f'Directive "{self.name}": error reading file: {path}\n{error}.' - ) + ) from error # get required section of text startline = self.options.get("start-line", None) @@ -412,10 +412,10 @@ def run(self) -> list[nodes.Element]: if "number-lines" in self.options: try: startline = int(self.options["number-lines"] or 1) - except ValueError: + except ValueError as err: raise DirectiveError( 3, ":number-lines: with non-integer start value" - ) + ) from err endline = startline + len(file_content.splitlines()) if file_content.endswith("\n"): file_content = file_content[:-1] diff --git a/myst_parser/parsers/docutils_.py b/myst_parser/parsers/docutils_.py index d9997b75..89a78b65 100644 --- a/myst_parser/parsers/docutils_.py +++ b/myst_parser/parsers/docutils_.py @@ -97,8 +97,8 @@ def _validate_yaml( """ try: output = yaml.safe_load(value) - except Exception: - raise ValueError("Invalid YAML string") + except Exception as err: + raise ValueError("Invalid YAML string") from err if not isinstance(output, dict): raise ValueError("Expecting a YAML dictionary") return output @@ -115,8 +115,8 @@ def _validate_url_schemes( """ try: output = yaml.safe_load(value) - except Exception: - raise ValueError("Invalid YAML string") + except Exception as err: + raise ValueError("Invalid YAML string") from err if isinstance(output, str): output = {k: None for k in output.split(",")} if not isinstance(output, dict): diff --git a/pyproject.toml b/pyproject.toml index 2993b24f..f0c71a96 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -102,7 +102,7 @@ exclude = [ ] [tool.ruff] -extend-select = ["B0", "C4", "I", "ICN", "ISC", "N", "RUF", "SIM", "UP"] +extend-select = ["B", "C4", "I", "ICN", "ISC", "N", "RUF", "SIM", "UP"] extend-ignore = ["ISC001", "RUF005", "RUF012"] [tool.mypy] diff --git a/tests/test_renderers/test_fixtures_docutils.py b/tests/test_renderers/test_fixtures_docutils.py index 10c9dd87..889d9eed 100644 --- a/tests/test_renderers/test_fixtures_docutils.py +++ b/tests/test_renderers/test_fixtures_docutils.py @@ -131,5 +131,5 @@ def settings_from_cmdline(cmdline: str | None) -> dict[str, Any]: try: pub.process_command_line(shlex.split(cmdline)) except Exception as err: - raise AssertionError(f"Failed to parse commandline: {cmdline}\n{err}") + raise AssertionError(f"Failed to parse commandline: {cmdline}\n{err}") from err return vars(pub.settings) diff --git a/tests/test_renderers/test_fixtures_sphinx.py b/tests/test_renderers/test_fixtures_sphinx.py index 3dfba8f9..443fc9db 100644 --- a/tests/test_renderers/test_fixtures_sphinx.py +++ b/tests/test_renderers/test_fixtures_sphinx.py @@ -46,7 +46,7 @@ def settings_from_json(string: str | None): data = json.loads(string) assert isinstance(data, dict), "settings must be a JSON object" except Exception as err: - raise AssertionError(f"Failed to parse JSON settings: {string}\n{err}") + raise AssertionError(f"Failed to parse JSON settings: {string}\n{err}") from err return data diff --git a/tests/test_renderers/test_myst_config.py b/tests/test_renderers/test_myst_config.py index 4c9c0bc8..e35aee40 100644 --- a/tests/test_renderers/test_myst_config.py +++ b/tests/test_renderers/test_myst_config.py @@ -27,7 +27,7 @@ def test_cmdline(file_params: ParamTestData): except Exception as err: raise AssertionError( f"Failed to parse commandline: {file_params.description}\n{err}" - ) + ) from err settings = vars(pub.settings) report_stream = StringIO() settings["output_encoding"] = "unicode" From 997426f8b156b31813b274dadb535938c29d3984 Mon Sep 17 00:00:00 2001 From: "daniel.eades" Date: Fri, 26 Jan 2024 13:00:01 +0000 Subject: [PATCH 2/9] add 'flake8-commas (COM) lints --- .github/workflows/docutils_setup.py | 3 +- docs/conf.py | 2 +- docs/live_preview.py | 2 +- myst_parser/_docs.py | 13 +- myst_parser/cli.py | 6 +- myst_parser/config/dc_validators.py | 15 +- myst_parser/config/main.py | 30 ++-- myst_parser/inventory.py | 5 +- myst_parser/mdit_to_docutils/base.py | 142 +++++++++++++----- myst_parser/mdit_to_docutils/html_to_nodes.py | 31 ++-- myst_parser/mdit_to_docutils/sphinx_.py | 30 +++- myst_parser/mdit_to_docutils/transforms.py | 24 ++- myst_parser/mocking.py | 35 +++-- myst_parser/parsers/directives.py | 17 ++- myst_parser/parsers/docutils_.py | 58 +++++-- myst_parser/parsers/mdit.py | 8 +- myst_parser/parsers/options.py | 32 ++-- myst_parser/parsers/parse_html.py | 2 +- myst_parser/parsers/sphinx_.py | 6 +- myst_parser/sphinx_ext/directives.py | 12 +- myst_parser/sphinx_ext/main.py | 4 +- myst_parser/sphinx_ext/mathjax.py | 4 +- myst_parser/sphinx_ext/myst_refs.py | 64 ++++++-- myst_parser/warnings_.py | 4 +- pyproject.toml | 4 +- tests/test_commonmark/test_commonmark.py | 8 +- tests/test_docutils.py | 5 +- tests/test_inventory.py | 3 +- tests/test_renderers/test_fixtures_sphinx.py | 20 +-- tests/test_renderers/test_myst_config.py | 2 +- tests/test_renderers/test_myst_refs.py | 3 +- tests/test_renderers/test_parse_directives.py | 36 ++++- tests/test_sphinx/conftest.py | 2 +- tests/test_sphinx/test_sphinx_builds.py | 14 +- 34 files changed, 459 insertions(+), 187 deletions(-) diff --git a/.github/workflows/docutils_setup.py b/.github/workflows/docutils_setup.py index 9d745222..32567c68 100755 --- a/.github/workflows/docutils_setup.py +++ b/.github/workflows/docutils_setup.py @@ -36,7 +36,8 @@ def modify_readme(content: str) -> str: ) content = content.replace("myst-docutils.readthedocs", "myst-parser.readthedocs") content = content.replace( - "readthedocs.org/projects/myst-docutils", "readthedocs.org/projects/myst-parser" + "readthedocs.org/projects/myst-docutils", + "readthedocs.org/projects/myst-parser", ) return content diff --git a/docs/conf.py b/docs/conf.py index 58cceb0e..2a294202 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -67,7 +67,7 @@ { "path": "../myst_parser", "exclude_files": ["_docs.py"], - } + }, ] autodoc2_hidden_objects = ["dunder", "private", "inherited"] autodoc2_replace_annotations = [ diff --git a/docs/live_preview.py b/docs/live_preview.py index 7a3a62b4..7f2fa0dc 100644 --- a/docs/live_preview.py +++ b/docs/live_preview.py @@ -51,7 +51,7 @@ def convert(input_config: str, input_myst: str, writer_name: str) -> dict: "doctitle_xform": False, "sectsubtitle_xform": False, "initial_header_level": 1, - } + }, ) try: output = publish_string( diff --git a/myst_parser/_docs.py b/myst_parser/_docs.py index 964c5aaa..a5032243 100644 --- a/myst_parser/_docs.py +++ b/myst_parser/_docs.py @@ -135,7 +135,7 @@ def run(self): continue if self.options.get("scope") == "local" and field.metadata.get( - "global_only" + "global_only", ): continue @@ -152,7 +152,7 @@ def run(self): f"* - `{name}`", f" - `{ctype}`", f" - {description} (default: `{default}`)", - ] + ], ) count += 1 @@ -202,7 +202,9 @@ def run(self): name = self.arguments[0] # load the directive class klass, _ = directives.directive( - name, self.state.memo.language, self.state.document + name, + self.state.memo.language, + self.state.document, ) if klass is None: LOGGER.warning(f"Directive {name} not found.", line=self.lineno) @@ -402,7 +404,10 @@ class MystLexer(MarkdownLexer): ( r"^(\()([^\n]+)(\)=)(\n)", bygroups( - token.Punctuation, token.Name.Label, token.Punctuation, token.Text + token.Punctuation, + token.Name.Label, + token.Punctuation, + token.Text, ), ), # ::: diff --git a/myst_parser/cli.py b/myst_parser/cli.py index a1f546d9..672d5f8f 100644 --- a/myst_parser/cli.py +++ b/myst_parser/cli.py @@ -27,7 +27,11 @@ def print_anchors(args=None): help="Output file (default stdout)", ) arg_parser.add_argument( - "-l", "--level", type=int, default=2, help="Maximum heading level." + "-l", + "--level", + type=int, + default=2, + help="Maximum heading level.", ) args = arg_parser.parse_args(args) parser = create_md_parser(MdParserConfig(), RendererHTML) diff --git a/myst_parser/config/dc_validators.py b/myst_parser/config/dc_validators.py index 4e13292d..c1161d3c 100644 --- a/myst_parser/config/dc_validators.py +++ b/myst_parser/config/dc_validators.py @@ -36,7 +36,11 @@ def validate_fields(inst: Any) -> None: class ValidatorType(Protocol): def __call__( - self, inst: Any, field: dc.Field, value: Any, suffix: str = "" + self, + inst: Any, + field: dc.Field, + value: Any, + suffix: str = "", ) -> None: ... @@ -63,7 +67,7 @@ def _validator(inst, field, value, suffix=""): if not isinstance(value, type_): raise TypeError( f"'{field.name}{suffix}' must be of type {type_!r} " - f"(got {value!r} that is a {value.__class__!r})." + f"(got {value!r} that is a {value.__class__!r}).", ) return _validator @@ -94,7 +98,7 @@ def is_callable(inst, field, value, suffix=""): if not callable(value): raise TypeError( f"'{field.name}{suffix}' must be callable " - f"(got {value!r} that is a {value.__class__!r})." + f"(got {value!r} that is a {value.__class__!r}).", ) @@ -115,14 +119,15 @@ def _validator(inst, field, value, suffix=""): if not in_options: raise ValueError( - f"'{field.name}{suffix}' must be in {options!r} (got {value!r})" + f"'{field.name}{suffix}' must be in {options!r} (got {value!r})", ) return _validator def deep_iterable( - member_validator: ValidatorType, iterable_validator: ValidatorType | None = None + member_validator: ValidatorType, + iterable_validator: ValidatorType | None = None, ) -> ValidatorType: """ A validator that performs deep validation of an iterable. diff --git a/myst_parser/config/main.py b/myst_parser/config/main.py index ae29dfad..29e3ad33 100644 --- a/myst_parser/config/main.py +++ b/myst_parser/config/main.py @@ -52,7 +52,7 @@ def check_extensions(inst: "MdParserConfig", field: dc.Field, value: Any) -> Non "strikethrough", "substitution", "tasklist", - ] + ], ) if diff: raise ValueError(f"'{field.name}' items not recognised: {diff}") @@ -90,11 +90,11 @@ def check_url_schemes(inst: "MdParserConfig", field: dc.Field, value: Any) -> No raise TypeError(f"'{field.name}[{key}]' keys are not strings: {val!r}") if "url" in val and not isinstance(val["url"], str): raise TypeError( - f"'{field.name}[{key}][url]' is not a string: {val['url']!r}" + f"'{field.name}[{key}][url]' is not a string: {val['url']!r}", ) if "title" in val and not isinstance(val["title"], str): raise TypeError( - f"'{field.name}[{key}][title]' is not a string: {val['title']!r}" + f"'{field.name}[{key}][title]' is not a string: {val['title']!r}", ) if ( "classes" in val @@ -102,12 +102,12 @@ def check_url_schemes(inst: "MdParserConfig", field: dc.Field, value: Any) -> No and not all(isinstance(c, str) for c in val["classes"]) ): raise TypeError( - f"'{field.name}[{key}][classes]' is not a list of str: {val['classes']!r}" + f"'{field.name}[{key}][classes]' is not a list of str: {val['classes']!r}", ) new_dict[key] = val # type: ignore else: raise TypeError( - f"'{field.name}[{key}]' value is not a string or dict: {val!r}" + f"'{field.name}[{key}]' value is not a string or dict: {val!r}", ) setattr(inst, field.name, new_dict) @@ -120,7 +120,7 @@ def check_sub_delimiters(_: "MdParserConfig", field: dc.Field, value: Any) -> No for delim in value: if (not isinstance(delim, str)) or len(delim) != 1: raise TypeError( - f"'{field.name}' does not contain strings of length 1: {value}" + f"'{field.name}' does not contain strings of length 1: {value}", ) @@ -133,7 +133,7 @@ def check_inventories(_: "MdParserConfig", field: dc.Field, value: Any) -> None: raise TypeError(f"'{field.name}' key is not a string: {key!r}") if not isinstance(val, (tuple, list)) or len(val) != 2: raise TypeError( - f"'{field.name}[{key}]' value is not a 2-item list: {val!r}" + f"'{field.name}[{key}]' value is not a 2-item list: {val!r}", ) if not isinstance(val[0], str): raise TypeError(f"'{field.name}[{key}][0]' is not a string: {val[0]}") @@ -142,7 +142,9 @@ def check_inventories(_: "MdParserConfig", field: dc.Field, value: Any) -> None: def check_heading_slug_func( - inst: "MdParserConfig", field: dc.Field, value: Any + inst: "MdParserConfig", + field: dc.Field, + value: Any, ) -> None: """Check that the heading_slug_func is a callable.""" if value is None: @@ -155,7 +157,7 @@ def check_heading_slug_func( value = getattr(mod, function_name) except ImportError as exc: raise TypeError( - f"'{field.name}' could not be loaded from string: {value!r}" + f"'{field.name}' could not be loaded from string: {value!r}", ) from exc setattr(inst, field.name, value) if not callable(value): @@ -169,7 +171,9 @@ def _test_slug_func(text: str) -> str: def check_fence_as_directive( - inst: "MdParserConfig", field: dc.Field, value: Any + inst: "MdParserConfig", + field: dc.Field, + value: Any, ) -> None: """Check that the extensions are a sequence of known strings""" deep_iterable(instance_of(str), instance_of((list, tuple, set)))(inst, field, value) @@ -253,7 +257,7 @@ def __repr__(self) -> str: default=None, metadata={ "validator": optional( - deep_iterable(instance_of(str), instance_of((list, tuple))) + deep_iterable(instance_of(str), instance_of((list, tuple))), ), "help": "Sphinx domain names to search in for link references", "omit": ["docutils"], @@ -311,7 +315,9 @@ def __repr__(self) -> str: default_factory=dict, metadata={ "validator": deep_mapping( - instance_of(str), instance_of(str), instance_of(dict) + instance_of(str), + instance_of(str), + instance_of(dict), ), "merge_topmatter": True, "help": "HTML meta tags", diff --git a/myst_parser/inventory.py b/myst_parser/inventory.py index ad85e777..c85c7e0c 100644 --- a/myst_parser/inventory.py +++ b/myst_parser/inventory.py @@ -401,7 +401,10 @@ def filter_string( def fetch_inventory( - uri: str, *, timeout: None | float = None, base_url: None | str = None + uri: str, + *, + timeout: None | float = None, + base_url: None | str = None, ) -> InventoryType: """Fetch an inventory from a URL or local path.""" if uri.startswith("http://") or uri.startswith("https://"): diff --git a/myst_parser/mdit_to_docutils/base.py b/myst_parser/mdit_to_docutils/base.py index 4a02459d..aae0febc 100644 --- a/myst_parser/mdit_to_docutils/base.py +++ b/myst_parser/mdit_to_docutils/base.py @@ -82,7 +82,7 @@ def make_document(source_path="notset", parser_cls=RSTParser) -> nodes.document: and documents that specify schemes must do so with lowercase letters. """ REGEX_URI_TEMPLATE = re.compile( - r"{{\s*(uri|scheme|netloc|path|params|query|fragment)\s*}}" + r"{{\s*(uri|scheme|netloc|path|params|query|fragment)\s*}}", ) REGEX_DIRECTIVE_START = re.compile(r"^[\s]{0,3}([`]{3,10}|[~]{3,10}|[:]{3,10})\{") @@ -129,14 +129,16 @@ def __getattr__(self, name: str): "_level_to_section", ): raise AttributeError( - f"'{name}' attribute is not available until setup_render() is called" + f"'{name}' attribute is not available until setup_render() is called", ) raise AttributeError( - f"'{type(self).__name__}' object has no attribute '{name}'" + f"'{type(self).__name__}' object has no attribute '{name}'", ) def setup_render( - self, options: dict[str, Any], env: MutableMapping[str, Any] + self, + options: dict[str, Any], + env: MutableMapping[str, Any], ) -> None: """Setup the renderer with per render variables.""" self.md_env = env @@ -148,12 +150,12 @@ def setup_render( # note there are actually two possible language modules: # one from docutils.languages, and one from docutils.parsers.rst.languages self.language_module_rst: ModuleType = get_language_rst( - self.document.settings.language_code + self.document.settings.language_code, ) self._heading_offset: int = 0 # a mapping of heading levels to its currently associated node self._level_to_section: dict[int, nodes.document | nodes.section] = { - 0: self.document + 0: self.document, } # mapping of section slug to (line, id, implicit_text) self._heading_slugs: dict[str, tuple[int | None, str, str]] = {} @@ -228,7 +230,10 @@ def _render_tokens(self, tokens: list[Token]) -> None: ) def render( - self, tokens: Sequence[Token], options, md_env: MutableMapping[str, Any] + self, + tokens: Sequence[Token], + options, + md_env: MutableMapping[str, Any], ) -> nodes.document: """Run the render on a token stream. @@ -251,7 +256,7 @@ def _render_initialise(self) -> None: document=self.document, line=0, reporter=self.reporter, - ) + ), ) def _render_finalise(self) -> None: @@ -318,12 +323,14 @@ def _render_finalise(self) -> None: if value is None: continue substitution_node = nodes.substitution_definition( - str(value), nodes.Text(str(value)) + str(value), + nodes.Text(str(value)), ) substitution_node.source = self.document["source"] substitution_node["names"].append(f"wordcount-{key}") self.document.note_substitution_def( - substitution_node, f"wordcount-{key}" + substitution_node, + f"wordcount-{key}", ) def nested_render_text( @@ -378,7 +385,9 @@ def _restore(): @contextmanager def current_node_context( - self, node: nodes.Element, append: bool = False + self, + node: nodes.Element, + append: bool = False, ) -> Iterator: """Context manager for temporarily setting the current node.""" if append: @@ -408,7 +417,9 @@ def add_line_and_source_path(self, node, token: SyntaxTreeNode) -> None: node.source = self.document["source"] def add_line_and_source_path_r( - self, nodes_: list[nodes.Element], token: SyntaxTreeNode + self, + nodes_: list[nodes.Element], + token: SyntaxTreeNode, ) -> None: """Copy the line number and document source path to the docutils nodes, and recursively to all descendants. @@ -662,7 +673,8 @@ def create_highlighted_code_block( if isinstance(emphasize_lines, str): try: emphasize_lines = self._parse_linenos( - emphasize_lines, len(text.splitlines()) + emphasize_lines, + len(text.splitlines()), ) except ValueError as err: self.create_warning( @@ -677,7 +689,8 @@ def create_highlighted_code_block( node["highlight_args"]["hl_lines"] = emphasize_lines else: node = node_cls( - text, classes=["code"] + ([lexer_name] if lexer_name else []) + text, + classes=["code"] + ([lexer_name] if lexer_name else []), ) try: lex_tokens = Lexer( @@ -698,7 +711,9 @@ def create_highlighted_code_block( if number_lines: lex_tokens = NumberLines( - lex_tokens, lineno_start, lineno_start + len(text.splitlines()) + lex_tokens, + lineno_start, + lineno_start + len(text.splitlines()), ) for classes, value in lex_tokens: @@ -756,14 +771,18 @@ def render_fence(self, token: SyntaxTreeNode) -> None: if "id" in options: options["name"] = options.pop("id") return self.render_directive( - token, name, arguments, additional_options=options + token, + name, + arguments, + additional_options=options, ) if not name and self.sphinx_env is not None: # use the current highlight setting, via the ``highlight`` directive, # or ``highlight_language`` configuration. name = self.sphinx_env.temp_data.get( - "highlight_language", self.sphinx_env.config.highlight_language + "highlight_language", + self.sphinx_env.config.highlight_language, ) lineno_start = 1 @@ -945,7 +964,9 @@ def render_link(self, token: SyntaxTreeNode) -> None: return self.render_link_unknown(token) def render_link_url( - self, token: SyntaxTreeNode, conversion: None | UrlSchemeType = None + self, + token: SyntaxTreeNode, + conversion: None | UrlSchemeType = None, ) -> None: """Render link token (including autolink and linkify), where the link has been identified as an external URL. @@ -953,7 +974,10 @@ def render_link_url( ref_node = nodes.reference() self.add_line_and_source_path(ref_node, token) self.copy_attributes( - token, ref_node, ("class", "id", "reftitle"), aliases={"title": "reftitle"} + token, + ref_node, + ("class", "id", "reftitle"), + aliases={"title": "reftitle"}, ) uri = cast(str, token.attrGet("href") or "") implicit_text: str | None = None @@ -1039,7 +1063,10 @@ def render_link_anchor(self, token: SyntaxTreeNode, target: str) -> None: ref_node["id_link"] = True ref_node["refuri"] = self.md.normalizeLinkText(target) self.copy_attributes( - token, ref_node, ("class", "id", "reftitle"), aliases={"title": "reftitle"} + token, + ref_node, + ("class", "id", "reftitle"), + aliases={"title": "reftitle"}, ) self.current_node.append(ref_node) if token.info != "auto": @@ -1060,7 +1087,10 @@ def render_link_unknown(self, token: SyntaxTreeNode) -> None: ref_node = nodes.reference() self.add_line_and_source_path(ref_node, token) self.copy_attributes( - token, ref_node, ("class", "id", "reftitle"), aliases={"title": "reftitle"} + token, + ref_node, + ("class", "id", "reftitle"), + aliases={"title": "reftitle"}, ) ref_node["refname"] = cast(str, token.attrGet("href") or "") self.document.note_refname(ref_node) @@ -1098,7 +1128,10 @@ def render_link_inventory(self, token: SyntaxTreeNode) -> None: # find the matches matches = self.get_inventory_matches( - target=target, invs=invs, domains=domains, otypes=otypes + target=target, + invs=invs, + domains=domains, + otypes=otypes, ) # warn for 0 or >1 matches @@ -1118,7 +1151,7 @@ def render_link_inventory(self, token: SyntaxTreeNode) -> None: [ inventory.filter_string(m.inv, m.domain, m.otype, m.name) for m in matches[:show_num] - ] + ], ) if len(matches) > show_num: matches_str += ", ..." @@ -1133,11 +1166,17 @@ def render_link_inventory(self, token: SyntaxTreeNode) -> None: match = matches[0] ref_node = nodes.reference("", "", internal=False) ref_node["inv_match"] = inventory.filter_string( - match.inv, match.domain, match.otype, match.name + match.inv, + match.domain, + match.otype, + match.name, ) self.add_line_and_source_path(ref_node, token) self.copy_attributes( - token, ref_node, ("class", "id", "reftitle"), aliases={"title": "reftitle"} + token, + ref_node, + ("class", "id", "reftitle"), + aliases={"title": "reftitle"}, ) ref_node["refuri"] = ( posixpath.join(match.base_url, match.loc) if match.base_url else match.loc @@ -1187,7 +1226,7 @@ def get_inventory_matches( domains=domains, otypes=otypes, targets=target, - ) + ), ) def render_html_inline(self, token: SyntaxTreeNode) -> None: @@ -1203,7 +1242,8 @@ def render_image(self, token: SyntaxTreeNode) -> None: destination = cast(str, token.attrGet("src") or "") if self.md_env.get( - "relative-images", None + "relative-images", + None, ) is not None and not REGEX_SCHEME.match(destination): # make the path relative to an "including" document # this is set when using the `relative-images` option of the MyST `include` directive @@ -1211,7 +1251,7 @@ def render_image(self, token: SyntaxTreeNode) -> None: os.path.join( self.md_env.get("relative-images", ""), os.path.normpath(destination), - ) + ), ) img_node["uri"] = destination @@ -1276,7 +1316,8 @@ def render_front_matter(self, token: SyntaxTreeNode) -> None: } if fields: field_list = self.dict_to_fm_field_list( - fields, language_code=self.document.settings.language_code + fields, + language_code=self.document.settings.language_code, ) self.current_node.append(field_list) @@ -1284,7 +1325,10 @@ def render_front_matter(self, token: SyntaxTreeNode) -> None: 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 + self, + data: dict[str, Any], + language_code: str, + line: int = 0, ) -> nodes.field_list: """Render each key/val pair as a docutils ``field_node``. @@ -1394,7 +1438,7 @@ def render_table_row(self, token: SyntaxTreeNode) -> None: for child in token.children or []: entry = nodes.entry() para = nodes.paragraph( - child.children[0].content if child.children else "" + child.children[0].content if child.children else "", ) style = child.attrGet("style") # i.e. the alignment when using e.g. :-- if style and style in ( @@ -1404,7 +1448,8 @@ def render_table_row(self, token: SyntaxTreeNode) -> None: ): entry["classes"].append(f"text-{cast(str, style).split(':')[1]}") with self.current_node_context( - entry, append=True + entry, + append=True, ), self.current_node_context(para, append=True): self.render_children(child) @@ -1459,7 +1504,10 @@ def render_amsmath(self, token: SyntaxTreeNode) -> None: # note docutils does not currently support the nowrap attribute # or equation numbering, so this is overridden in the sphinx renderer node = nodes.math_block( - token.content, token.content, nowrap=True, classes=["amsmath"] + token.content, + token.content, + nowrap=True, + classes=["amsmath"], ) if token.meta["numbered"] != "*": node["numbered"] = True @@ -1525,7 +1573,10 @@ def render_myst_role(self, token: SyntaxTreeNode) -> None: rawsource = f":{name}:`{token.content}`" lineno = token_line(token) if token.map else 0 role_func, messages = roles.role( - name, self.language_module_rst, lineno, self.reporter + name, + self.language_module_rst, + lineno, + self.reporter, ) if not role_func: self.create_warning( @@ -1580,7 +1631,7 @@ def render_dl(self, token: SyntaxTreeNode) -> None: self.add_line_and_source_path(item, child) with self.current_node_context(item, append=True): term = nodes.term( - child.children[0].content if child.children else "" + child.children[0].content if child.children else "", ) self.add_line_and_source_path(term, child) with self.current_node_context(term): @@ -1725,7 +1776,9 @@ def run_directive( # get directive class output: tuple[Directive | None, list] = directives.directive( - name, self.language_module_rst, self.document + name, + self.language_module_rst, + self.document, ) directive_class, messages = output if not directive_class: @@ -1803,7 +1856,9 @@ def run_directive( result = directive_instance.run() except DirectiveError as error: msg_node = self.reporter.system_message( - error.level, error.msg, line=position + error.level, + error.msg, + line=position, ) msg_node += nodes.literal_block(content, content) result = [msg_node] @@ -1816,11 +1871,13 @@ def run_directive( return [error_msg] assert isinstance( - result, list + result, + list, ), f'Directive "{name}" must return a list of nodes.' for i in range(len(result)): assert isinstance( - result[i], nodes.Node + result[i], + nodes.Node, ), f'Directive "{name}" returned non-Node object (index {i}): {result[i]}' return result @@ -1857,7 +1914,7 @@ def render_substitution(self, token: SyntaxTreeNode, inline: bool) -> None: # try rendering try: rendered = env.from_string(f"{{{{{token.content}}}}}").render( - variable_context + variable_context, ) except Exception as error: self.create_warning( @@ -1902,7 +1959,10 @@ def render_substitution(self, token: SyntaxTreeNode, inline: bool) -> None: def html_meta_to_nodes( - data: dict[str, Any], document: nodes.document, line: int, reporter: Reporter + data: dict[str, Any], + document: nodes.document, + line: int, + reporter: Reporter, ) -> list[nodes.pending | nodes.system_message]: """Replicate the `meta` directive, by converting a dictionary to a list of pending meta nodes diff --git a/myst_parser/mdit_to_docutils/html_to_nodes.py b/myst_parser/mdit_to_docutils/html_to_nodes.py index e327bbe8..38a6bc30 100644 --- a/myst_parser/mdit_to_docutils/html_to_nodes.py +++ b/myst_parser/mdit_to_docutils/html_to_nodes.py @@ -14,7 +14,10 @@ def make_error( - document: nodes.document, error_msg: str, text: str, line_number: int + document: nodes.document, + error_msg: str, + text: str, + line_number: int, ) -> nodes.system_message: return document.reporter.error( error_msg, @@ -43,7 +46,9 @@ def default_html(text: str, source: str, line_number: int) -> list[nodes.Element def html_to_nodes( - text: str, line_number: int, renderer: DocutilsRenderer + text: str, + line_number: int, + renderer: DocutilsRenderer, ) -> list[nodes.Element]: """Convert HTML to docutils nodes.""" if renderer.md_config.gfm_only: @@ -59,10 +64,14 @@ def html_to_nodes( root = tokenize_html(text).strip(inplace=True, recurse=False) except Exception: msg_node = renderer.create_warning( - "HTML could not be parsed", MystWarnings.HTML_PARSE, line=line_number + "HTML could not be parsed", + MystWarnings.HTML_PARSE, + line=line_number, ) return ([msg_node] if msg_node else []) + default_html( - text, renderer.document["source"], line_number + text, + renderer.document["source"], + line_number, ) if len(root) < 1: @@ -86,8 +95,9 @@ def html_to_nodes( if "src" not in child.attrs: return [ renderer.reporter.error( - " missing 'src' attribute", line=line_number - ) + " missing 'src' attribute", + line=line_number, + ), ] content = "\n".join( f":{k}: {v}" @@ -96,8 +106,11 @@ def html_to_nodes( ) nodes_list.extend( renderer.run_directive( - "image", child.attrs["src"], content, line_number - ) + "image", + child.attrs["src"], + content, + line_number, + ), ) else: @@ -132,7 +145,7 @@ def html_to_nodes( ) nodes_list.extend( - renderer.run_directive("admonition", title, content, line_number) + renderer.run_directive("admonition", title, content, line_number), ) return nodes_list diff --git a/myst_parser/mdit_to_docutils/sphinx_.py b/myst_parser/mdit_to_docutils/sphinx_.py index 71942aff..46ce0536 100644 --- a/myst_parser/mdit_to_docutils/sphinx_.py +++ b/myst_parser/mdit_to_docutils/sphinx_.py @@ -65,7 +65,8 @@ def _handle_relative_docs(self, destination: str) -> str: if relative_include is not None and destination.startswith(relative_include[0]): source_dir, include_dir = relative_include[1:] destination = os.path.relpath( - os.path.join(include_dir, os.path.normpath(destination)), source_dir + os.path.join(include_dir, os.path.normpath(destination)), + source_dir, ) return destination @@ -146,17 +147,24 @@ def render_link_unknown(self, token: SyntaxTreeNode) -> None: docname = self.sphinx_env.path2doc(str(potential_path)) if docname: wrap_node = addnodes.pending_xref( - refdomain="doc", reftarget=docname, reftargetid=path_id, **kwargs + refdomain="doc", + reftarget=docname, + reftargetid=path_id, + **kwargs, ) classes = ["xref", "myst"] else: wrap_node = addnodes.download_reference( - refdomain=None, reftarget=path_dest, **kwargs + refdomain=None, + reftarget=path_dest, + **kwargs, ) classes = ["xref", "download", "myst"] else: wrap_node = addnodes.pending_xref( - refdomain=None, reftarget=destination, **kwargs + refdomain=None, + reftarget=destination, + **kwargs, ) classes = ["xref", "myst"] @@ -177,7 +185,7 @@ def get_inventory_matches( domains=domains, otypes=otypes, targets=target, - ) + ), ) def render_math_block_label(self, token: SyntaxTreeNode) -> None: @@ -185,7 +193,11 @@ def render_math_block_label(self, token: SyntaxTreeNode) -> None: label = token.info content = token.content node = nodes.math_block( - content, content, nowrap=False, number=None, label=label + content, + content, + nowrap=False, + number=None, + label=label, ) target = self.add_math_target(node) self.add_line_and_source_path(target, token) @@ -219,7 +231,11 @@ def render_amsmath(self, token: SyntaxTreeNode) -> None: self.current_node.append(target) else: node = nodes.math_block( - content, content, nowrap=True, number=None, classes=["amsmath"] + content, + content, + nowrap=True, + number=None, + classes=["amsmath"], ) self.add_line_and_source_path(node, token) self.current_node.append(node) diff --git a/myst_parser/mdit_to_docutils/transforms.py b/myst_parser/mdit_to_docutils/transforms.py index 3cf03bb8..68b1e233 100644 --- a/myst_parser/mdit_to_docutils/transforms.py +++ b/myst_parser/mdit_to_docutils/transforms.py @@ -22,7 +22,9 @@ def apply(self, **kwargs: t.Any) -> None: # gather the implicit heading slugs # name -> (line, slug, title) slugs: dict[str, tuple[int, str, str]] = getattr( - self.document, "myst_slugs", {} + self.document, + "myst_slugs", + {}, ) # gather explicit references @@ -89,11 +91,15 @@ def apply(self, **kwargs: t.Any) -> None: refnode["refid"] = ref_id if not refnode.children and implicit_title: refnode += nodes.inline( - implicit_title, implicit_title, classes=["std", "std-ref"] + implicit_title, + implicit_title, + classes=["std", "std-ref"], ) elif not refnode.children: refnode += nodes.inline( - "#" + target, "#" + target, classes=["std", "std-ref"] + "#" + target, + "#" + target, + classes=["std", "std-ref"], ) continue @@ -103,7 +109,9 @@ def apply(self, **kwargs: t.Any) -> None: refnode["refid"] = sect_id if not refnode.children and implicit_title: refnode += nodes.inline( - implicit_title, implicit_title, classes=["std", "std-ref"] + implicit_title, + implicit_title, + classes=["std", "std-ref"], ) continue @@ -119,7 +127,9 @@ def apply(self, **kwargs: t.Any) -> None: refexplicit=bool(refnode.children), ) inner_node = nodes.inline( - "", "", classes=["xref", "myst"] + refnode["classes"] + "", + "", + classes=["xref", "myst"] + refnode["classes"], ) for attr in ("ids", "names", "dupnames"): inner_node[attr] = refnode[attr] @@ -141,5 +151,7 @@ def apply(self, **kwargs: t.Any) -> None: refnode["refid"] = normalizeLink(target) if not refnode.children: refnode += nodes.inline( - "#" + target, "#" + target, classes=["std", "std-ref"] + "#" + target, + "#" + target, + classes=["std", "std-ref"], ) diff --git a/myst_parser/mocking.py b/myst_parser/mocking.py index bf781dfa..f9e90327 100644 --- a/myst_parser/mocking.py +++ b/myst_parser/mocking.py @@ -50,7 +50,10 @@ def __init__(self, renderer: DocutilsRenderer): self.rfc_url = "rfc%d.html" def problematic( - self, text: str, rawsource: str, message: nodes.system_message + self, + text: str, + rawsource: str, + message: nodes.system_message, ) -> nodes.problematic: """Record a system message from parsing.""" msgid = self.document.set_id(message, self.parent) @@ -60,7 +63,11 @@ def problematic( return problematic def parse( - self, text: str, lineno: int, memo: Any, parent: nodes.Node + self, + text: str, + lineno: int, + memo: Any, + parent: nodes.Node, ) -> tuple[list[nodes.Node], list[nodes.system_message]]: """Parse the text and return a list of nodes.""" # note the only place this is normally called, @@ -190,7 +197,9 @@ def parse_target(self, block, block_text, lineno: int): return "refuri", unescape(reference) def inline_text( - self, text: str, lineno: int + self, + text: str, + lineno: int, ) -> tuple[list[nodes.Element], list[nodes.Element]]: """Parse text with only inline rules. @@ -373,11 +382,13 @@ def run(self) -> list[nodes.Element]: file_content = path.read_text(encoding=encoding, errors=error_handler) except FileNotFoundError as error: raise DirectiveError( - 4, f'Directive "{self.name}": file not found: {str(path)!r}' + 4, + f'Directive "{self.name}": file not found: {str(path)!r}', ) from error except Exception as error: raise DirectiveError( - 4, f'Directive "{self.name}": error reading file: {path}\n{error}.' + 4, + f'Directive "{self.name}": error reading file: {path}\n{error}.', ) from error # get required section of text @@ -394,7 +405,9 @@ def run(self) -> list[nodes.Element]: raise DirectiveError( 4, 'Directive "{}"; option "{}": text not found "{}".'.format( - self.name, split_on_type, split_on + self.name, + split_on_type, + split_on, ), ) if split_on_type == "start-after": @@ -405,7 +418,9 @@ def run(self) -> list[nodes.Element]: if "literal" in self.options: literal_block = nodes.literal_block( - file_content, source=str(path), classes=self.options.get("class", []) + file_content, + source=str(path), + classes=self.options.get("class", []), ) literal_block.line = 1 # TODO don;t think this should be 1? self.add_name(literal_block) @@ -414,7 +429,8 @@ def run(self) -> list[nodes.Element]: startline = int(self.options["number-lines"] or 1) except ValueError as err: raise DirectiveError( - 3, ":number-lines: with non-integer start value" + 3, + ":number-lines: with non-integer start value", ) from err endline = startline + len(file_content.splitlines()) if file_content.endswith("\n"): @@ -456,7 +472,8 @@ def run(self) -> list[nodes.Element]: self.renderer.reporter.get_source_and_line = lambda li: (str(path), li) if "relative-images" in self.options: self.renderer.md_env["relative-images"] = os.path.relpath( - path.parent, source_dir + path.parent, + source_dir, ) if "relative-docs" in self.options: self.renderer.md_env["relative-docs"] = ( diff --git a/myst_parser/parsers/directives.py b/myst_parser/parsers/directives.py index e49ed429..72a13d01 100644 --- a/myst_parser/parsers/directives.py +++ b/myst_parser/parsers/directives.py @@ -125,7 +125,7 @@ def parse_directive_text( "Cannot split content across first line and body, " "when options block is present (move first line to body)", None, - ) + ), ) body_lines.insert(0, first_line) content_offset = 0 @@ -144,7 +144,11 @@ def parse_directive_text( parse_errors.append(("Has content, but none permitted", None)) return DirectiveParsingResult( - arguments, options, body_lines, content_offset, parse_errors + arguments, + options, + body_lines, + content_offset, + parse_errors, ) @@ -250,7 +254,7 @@ def _parse_directive_options( converted_value = convertor(value) except (ValueError, TypeError) as error: validation_errors.append( - (f"Invalid option value for {name!r}: {value}: {error}", line) + (f"Invalid option value for {name!r}: {value}: {error}", line), ) else: new_options[name] = converted_value @@ -261,14 +265,15 @@ def _parse_directive_options( f"Unknown option keys: {sorted(unknown_options)} " f"(allowed: {sorted(options_spec)})", line, - ) + ), ) return _DirectiveOptions(content, new_options, validation_errors, has_options_block) def parse_directive_arguments( - directive_cls: type[Directive], arg_text: str + directive_cls: type[Directive], + arg_text: str, ) -> list[str]: """Parse (and validate) the directive argument section.""" required = directive_cls.required_arguments @@ -282,6 +287,6 @@ def parse_directive_arguments( else: raise MarkupError( f"maximum {required + optional} argument(s) allowed, " - f"{len(arguments)} supplied" + f"{len(arguments)} supplied", ) return arguments diff --git a/myst_parser/parsers/docutils_.py b/myst_parser/parsers/docutils_.py index 89a78b65..52003c9b 100644 --- a/myst_parser/parsers/docutils_.py +++ b/myst_parser/parsers/docutils_.py @@ -36,18 +36,30 @@ def _validate_int( - setting, value, option_parser, config_parser=None, config_section=None + setting, + value, + option_parser, + config_parser=None, + config_section=None, ) -> int: """Validate an integer setting.""" return int(value) def _validate_comma_separated_set( - setting, value, option_parser, config_parser=None, config_section=None + setting, + value, + option_parser, + config_parser=None, + config_section=None, ) -> Set[str]: """Validate an integer setting.""" value = frontend.validate_comma_separated_list( - setting, value, option_parser, config_parser, config_section + setting, + value, + option_parser, + config_parser, + config_section, ) return set(value) @@ -56,14 +68,22 @@ def _create_validate_tuple(length: int) -> Callable[..., Tuple[str, ...]]: """Create a validator for a tuple of length `length`.""" def _validate( - setting, value, option_parser, config_parser=None, config_section=None + setting, + value, + option_parser, + config_parser=None, + config_section=None, ): string_list = frontend.validate_comma_separated_list( - setting, value, option_parser, config_parser, config_section + setting, + value, + option_parser, + config_parser, + config_section, ) if len(string_list) != length: raise ValueError( - f"Expecting {length} items in {setting}, got {len(string_list)}." + f"Expecting {length} items in {setting}, got {len(string_list)}.", ) return tuple(string_list) @@ -89,7 +109,11 @@ def _create_validate_yaml(field: Field): """Create a deserializer/validator for a json setting.""" def _validate_yaml( - setting, value, option_parser, config_parser=None, config_section=None + setting, + value, + option_parser, + config_parser=None, + config_section=None, ): """Check/normalize a key-value pair setting. @@ -107,7 +131,11 @@ def _validate_yaml( def _validate_url_schemes( - setting, value, option_parser, config_parser=None, config_section=None + setting, + value, + option_parser, + config_parser=None, + config_section=None, ): """Validate a url_schemes setting. @@ -185,12 +213,14 @@ def _attr_to_optparse_option(at: Field, default: Any) -> Tuple[dict, str]: "validator": _create_validate_yaml(at), }, str(default) if default else "" raise AssertionError( - f"Configuration option {at.name} not set up for use in docutils.conf." + f"Configuration option {at.name} not set up for use in docutils.conf.", ) def attr_to_optparse_option( - attribute: Field, default: Any, prefix: str = "myst_" + attribute: Field, + default: Any, + prefix: str = "myst_", ) -> Tuple[str, List[str], Dict[str, Any]]: """Convert an ``MdParserConfig`` attribute into a Docutils setting tuple. @@ -276,7 +306,7 @@ def parse(self, inputstring: str, document: nodes.document) -> None: if len(line) > document.settings.line_length_limit: error = document.reporter.error( f"Line {i+1} exceeds the line-length-limit:" - f" {document.settings.line_length_limit}." + f" {document.settings.line_length_limit}.", ) document.append(error) return @@ -305,7 +335,11 @@ def parse(self, inputstring: str, document: nodes.document) -> None: else: if topmatter: warning = lambda wtype, msg: create_warning( # noqa: E731 - document, msg, wtype, line=1, append_to=document + document, + msg, + wtype, + line=1, + append_to=document, ) config = merge_file_level(config, topmatter, warning) diff --git a/myst_parser/parsers/mdit.py b/myst_parser/parsers/mdit.py index 110baecb..fd1c7e9b 100644 --- a/myst_parser/parsers/mdit.py +++ b/myst_parser/parsers/mdit.py @@ -25,7 +25,8 @@ def create_md_parser( - config: MdParserConfig, renderer: Callable[[MarkdownIt], RendererProtocol] + config: MdParserConfig, + renderer: Callable[[MarkdownIt], RendererProtocol], ) -> MarkdownIt: """Return a Markdown parser with the required MyST configuration.""" @@ -35,7 +36,8 @@ def create_md_parser( if config.commonmark_only: # see https://spec.commonmark.org/ md = MarkdownIt("commonmark", renderer_cls=renderer).use( - wordcount_plugin, per_minute=config.words_per_minute + wordcount_plugin, + per_minute=config.words_per_minute, ) md.options.update({"myst_config": config}) return md @@ -120,7 +122,7 @@ def create_md_parser( "typographer": typographer, "linkify": "linkify" in config.enable_extensions, "myst_config": config, - } + }, ) return md diff --git a/myst_parser/parsers/options.py b/myst_parser/parsers/options.py index 15da7327..4903b841 100644 --- a/myst_parser/parsers/options.py +++ b/myst_parser/parsers/options.py @@ -151,19 +151,21 @@ def __str__(self) -> str: or self.context_mark.column != self.problem_mark.column ): lines.append( - f"at line {self.context_mark.line}, column {self.context_mark.column}" + f"at line {self.context_mark.line}, column {self.context_mark.column}", ) if self.problem is not None: lines.append(self.problem) if self.problem_mark is not None: lines.append( - f"at line {self.problem_mark.line}, column {self.problem_mark.column}" + f"at line {self.problem_mark.line}, column {self.problem_mark.column}", ) return "\n".join(lines) def to_items( - text: str, line_offset: int = 0, column_offset: int = 0 + text: str, + line_offset: int = 0, + column_offset: int = 0, ) -> Iterable[tuple[str, str]]: """Parse a directive option block into (key, value) tuples. @@ -178,7 +180,9 @@ def to_items( def to_tokens( - text: str, line_offset: int = 0, column_offset: int = 0 + text: str, + line_offset: int = 0, + column_offset: int = 0, ) -> Iterable[tuple[KeyToken, ValueToken | None]]: """Parse a directive option, and yield key/value token pairs. @@ -218,7 +222,8 @@ def tokenize(text: str) -> Iterable[Token]: if not stream.column == 0: raise TokenizeError( - "expected key to start at column 0", stream.get_position() + "expected key to start at column 0", + stream.get_position(), ) # find key @@ -273,7 +278,8 @@ def _scan_to_next_token(stream: StreamBuffer) -> None: def _scan_plain_scalar( - stream: StreamBuffer, is_key: bool = False + stream: StreamBuffer, + is_key: bool = False, ) -> KeyToken | ValueToken: chunks = [] start_mark = stream.get_position() @@ -359,7 +365,9 @@ def _scan_line_break(stream: StreamBuffer) -> str: def _scan_flow_scalar( - stream: StreamBuffer, style: Literal["'", '"'], is_key: bool = False + stream: StreamBuffer, + style: Literal["'", '"'], + is_key: bool = False, ) -> KeyToken | ValueToken: double = style == '"' chunks = [] @@ -380,7 +388,9 @@ def _scan_flow_scalar( def _scan_flow_scalar_non_spaces( - stream: StreamBuffer, double: bool, start_mark: Position + stream: StreamBuffer, + double: bool, + start_mark: Position, ) -> list[str]: chunks = [] while True: @@ -530,7 +540,8 @@ def _scan_block_scalar(stream: StreamBuffer, style: Literal["|", ">"]) -> ValueT def _scan_block_scalar_indicators( - stream: StreamBuffer, start_mark: Position + stream: StreamBuffer, + start_mark: Position, ) -> tuple[bool | None, int | None]: chomping = None increment = None @@ -609,7 +620,8 @@ def _scan_block_scalar_indentation( def _scan_block_scalar_breaks( - stream: StreamBuffer, indent: int + stream: StreamBuffer, + indent: int, ) -> tuple[list[str], Position]: chunks = [] end_mark = stream.get_position() diff --git a/myst_parser/parsers/parse_html.py b/myst_parser/parsers/parse_html.py index 5add3582..63c0339b 100644 --- a/myst_parser/parsers/parse_html.py +++ b/myst_parser/parsers/parse_html.py @@ -159,7 +159,7 @@ def strip(self, inplace: bool = False, recurse: bool = False) -> Element: e for e in element.children if not (isinstance(e, Data) and e.data.strip() == "") - ] + ], ) if recurse: for child in element: diff --git a/myst_parser/parsers/sphinx_.py b/myst_parser/parsers/sphinx_.py index e62eba3c..950f9cfe 100644 --- a/myst_parser/parsers/sphinx_.py +++ b/myst_parser/parsers/sphinx_.py @@ -65,7 +65,11 @@ def parse(self, inputstring: str, document: nodes.document) -> None: else: if topmatter: warning = lambda wtype, msg: create_warning( # noqa: E731 - document, msg, wtype, line=1, append_to=document + document, + msg, + wtype, + line=1, + append_to=document, ) config = merge_file_level(config, topmatter, warning) diff --git a/myst_parser/sphinx_ext/directives.py b/myst_parser/sphinx_ext/directives.py index 415c5498..0a040200 100644 --- a/myst_parser/sphinx_ext/directives.py +++ b/myst_parser/sphinx_ext/directives.py @@ -81,8 +81,8 @@ def run(self) -> List[nodes.Node]: return [ self.figure_error( "content should be one image, " - "followed by a single paragraph caption" - ) + "followed by a single paragraph caption", + ), ] image_node, caption_para = node.children @@ -93,16 +93,16 @@ def run(self) -> List[nodes.Node]: return [ self.figure_error( "content should be one image (not found), " - "followed by single paragraph caption" - ) + "followed by single paragraph caption", + ), ] if not isinstance(caption_para, nodes.paragraph): return [ self.figure_error( "content should be one image, " - "followed by single paragraph caption (not found)" - ) + "followed by single paragraph caption (not found)", + ), ] caption_node = nodes.caption(caption_para.rawsource, "", *caption_para.children) diff --git a/myst_parser/sphinx_ext/main.py b/myst_parser/sphinx_ext/main.py index 809056e1..398fa342 100644 --- a/myst_parser/sphinx_ext/main.py +++ b/myst_parser/sphinx_ext/main.py @@ -42,7 +42,9 @@ def setup_sphinx(app: Sphinx, load_parser: bool = False) -> None: # override only the html writer visit methods for rubric, to use the "level" attribute # this allows for nested headers to be correctly rendered app.add_node( - nodes.rubric, override=True, html=(visit_rubric_html, depart_rubric_html) + nodes.rubric, + override=True, + html=(visit_rubric_html, depart_rubric_html), ) # override only the html writer visit methods for container, # to remove the "container" class for divs diff --git a/myst_parser/sphinx_ext/mathjax.py b/myst_parser/sphinx_ext/mathjax.py index 260f0080..8939a0e1 100644 --- a/myst_parser/sphinx_ext/mathjax.py +++ b/myst_parser/sphinx_ext/mathjax.py @@ -31,7 +31,7 @@ def log_override_warning(app: Sphinx, version: int, current: str, new: str) -> N logger.warning( f"`{config_name}` is being overridden by myst-parser: '{current}' -> '{new}'. " "Set `suppress_warnings=['myst.mathjax']` to ignore this warning, or " - "`myst_update_mathjax=False` if this is undesirable." + "`myst_update_mathjax=False` if this is undesirable.", ) @@ -101,7 +101,7 @@ def html_visit_displaymath(self: HTMLTranslator, node: nodes.math_block) -> None """ if "amsmath" in node.get("classes", []): self.body.append( - self.starttag(node, "div", CLASS="math notranslate nohighlight amsmath") + self.starttag(node, "div", CLASS="math notranslate nohighlight amsmath"), ) if node["number"]: number = get_node_equation_number(self, node) diff --git a/myst_parser/sphinx_ext/myst_refs.py b/myst_parser/sphinx_ext/myst_refs.py index 74edcb37..1f605953 100644 --- a/myst_parser/sphinx_ext/myst_refs.py +++ b/myst_parser/sphinx_ext/myst_refs.py @@ -36,7 +36,11 @@ class MystReferenceResolver(ReferencesResolver): default_priority = 9 # higher priority than ReferencesResolver (10) def log_warning( - self, target: None | str, msg: str, subtype: MystWarnings, **kwargs: Any + self, + target: None | str, + msg: str, + subtype: MystWarnings, + **kwargs: Any, ): """Log a warning, with a myst type and specific subtype.""" @@ -93,7 +97,10 @@ def run(self, **kwargs: Any) -> None: # multiple domains resolved the reference try: newnode = self.resolve_myst_ref_any( - refdoc, node, contnode, search_domains + refdoc, + node, + contnode, + search_domains, ) except NoUri: newnode = contnode @@ -101,7 +108,10 @@ def run(self, **kwargs: Any) -> None: # If no local domain could resolve the reference, try to # resolve it as an inter-sphinx reference newnode = self._resolve_myst_ref_intersphinx( - node, contnode, target, search_domains + node, + contnode, + target, + search_domains, ) if newnode is None: # if still not resolved, log a warning, @@ -174,13 +184,19 @@ def resolve_myst_ref_doc(self, node: pending_xref): innernode.extend(node[0].children) else: innernode = nodes.inline( - implicit_text, implicit_text, classes=inner_classes + implicit_text, + implicit_text, + classes=inner_classes, ) assert self.app.builder try: ref_node = make_refnode( - self.app.builder, from_docname, ref_docname, targetid, innernode + self.app.builder, + from_docname, + ref_docname, + targetid, + innernode, ) except NoUri: ref_node = innernode @@ -229,7 +245,11 @@ def resolve_myst_ref_any( docname, labelid = stddomain.objects[key] domain_role = "std:" + stddomain.role_for_objtype(objtype) ref_node = make_refnode( - self.app.builder, refdoc, docname, labelid, contnode + self.app.builder, + refdoc, + docname, + labelid, + contnode, ) results.append((domain_role, ref_node)) @@ -242,8 +262,13 @@ def resolve_myst_ref_any( try: results.extend( domain.resolve_any_xref( - self.env, refdoc, self.app.builder, target, node, contnode - ) + self.env, + refdoc, + self.app.builder, + target, + node, + contnode, + ), ) except NotImplementedError: # the domain doesn't yet support the new interface @@ -258,7 +283,13 @@ def resolve_myst_ref_any( ) for role in domain.roles: res = domain.resolve_xref( - self.env, refdoc, self.app.builder, role, target, node, contnode + self.env, + refdoc, + self.app.builder, + role, + target, + node, + contnode, ) if res and len(res) and isinstance(res[0], nodes.Element): results.append((f"{domain.name}:{role}", res)) @@ -294,7 +325,10 @@ def stringify(name, node): return newnode def _resolve_ref_nested( - self, node: pending_xref, fromdocname: str, target=None + self, + node: pending_xref, + fromdocname: str, + target=None, ) -> Element | None: """This is the same as ``sphinx.domains.std._resolve_ref_xref``, but allows for nested syntax, rather than converting the inner node to raw text. @@ -322,7 +356,9 @@ def _resolve_ref_nested( return make_refnode(self.app.builder, fromdocname, docname, labelid, innernode) def _resolve_doc_nested( - self, node: pending_xref, fromdocname: str + self, + node: pending_xref, + fromdocname: str, ) -> Element | None: """This is the same as ``sphinx.domains.std._resolve_doc_xref``, but allows for nested syntax, rather than converting the inner node to raw text. @@ -370,7 +406,7 @@ def _resolve_myst_ref_intersphinx( [ inventory.filter_string(m.inv, m.domain, m.otype, m.name) for m in matches[:show_num] - ] + ], ) if len(matches) > show_num: matches_str += ", ..." @@ -391,11 +427,11 @@ def _resolve_myst_ref_intersphinx( newnode.append(contnode) elif match.text: newnode.append( - contnode.__class__(match.text, match.text, classes=["iref", "myst"]) + contnode.__class__(match.text, match.text, classes=["iref", "myst"]), ) else: newnode.append( - nodes.literal(match.name, match.name, classes=["iref", "myst"]) + nodes.literal(match.name, match.name, classes=["iref", "myst"]), ) return newnode diff --git a/myst_parser/warnings_.py b/myst_parser/warnings_.py index 7493d89d..1cbd898b 100644 --- a/myst_parser/warnings_.py +++ b/myst_parser/warnings_.py @@ -64,7 +64,9 @@ class MystWarnings(Enum): def _is_suppressed_warning( - type: str, subtype: str, suppress_warnings: Sequence[str] + type: str, + subtype: str, + suppress_warnings: Sequence[str], ) -> bool: """Check whether the warning is suppressed or not. diff --git a/pyproject.toml b/pyproject.toml index f0c71a96..b9fef549 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -102,8 +102,8 @@ exclude = [ ] [tool.ruff] -extend-select = ["B", "C4", "I", "ICN", "ISC", "N", "RUF", "SIM", "UP"] -extend-ignore = ["ISC001", "RUF005", "RUF012"] +extend-select = ["B", "C4", "COM", "I", "ICN", "ISC", "N", "RUF", "SIM", "UP"] +extend-ignore = ["ISC001", "RUF005", "RUF012", "COM812"] [tool.mypy] show_error_codes = true diff --git a/tests/test_commonmark/test_commonmark.py b/tests/test_commonmark/test_commonmark.py index 3cee1eb8..52d1a54f 100644 --- a/tests/test_commonmark/test_commonmark.py +++ b/tests/test_commonmark/test_commonmark.py @@ -11,7 +11,8 @@ from myst_parser.parsers.mdit import create_md_parser with open( - os.path.join(os.path.dirname(__file__), "commonmark.json"), encoding="utf8" + os.path.join(os.path.dirname(__file__), "commonmark.json"), + encoding="utf8", ) as fin: tests = json.load(fin) @@ -26,7 +27,7 @@ def test_commonmark(entry): # but not strictly CommonMark, # see: https://talk.commonmark.org/t/metadata-in-documents/721/86 pytest.skip( - "Thematic breaks on the first line conflict with front matter syntax" + "Thematic breaks on the first line conflict with front matter syntax", ) test_case = entry["markdown"] md = create_md_parser(MdParserConfig(), RendererHTML) @@ -38,7 +39,8 @@ def test_commonmark(entry): if entry["example"] in [187, 209, 210]: # this doesn't have any bearing on the output output = output.replace( - "
", "
\n
" + "
", + "
\n
", ) assert output == entry["html"] diff --git a/tests/test_docutils.py b/tests/test_docutils.py index 74ff04b5..74faf528 100644 --- a/tests/test_docutils.py +++ b/tests/test_docutils.py @@ -115,7 +115,8 @@ def test_include_from_rst(tmp_path): parser = RSTParser() document = make_document(parser_cls=RSTParser) parser.parse( - f".. include:: {include_path}\n :parser: myst_parser.docutils_", document + f".. include:: {include_path}\n :parser: myst_parser.docutils_", + document, ) assert ( document.pformat().strip() @@ -125,6 +126,6 @@ def test_include_from_rst(tmp_path):
Title - """ + """, ).strip() ) diff --git a/tests/test_inventory.py b/tests/test_inventory.py index 825bcccb..4b064a55 100644 --- a/tests/test_inventory.py +++ b/tests/test_inventory.py @@ -51,7 +51,8 @@ def test_inv_filter_wildcard(data_regression): @pytest.mark.parametrize( - "options", [(), ("-d", "std"), ("-o", "doc"), ("-n", "ref"), ("-l", "index.html*")] + "options", + [(), ("-d", "std"), ("-o", "doc"), ("-n", "ref"), ("-l", "index.html*")], ) def test_inv_cli_v2(options, capsys, file_regression): inventory_cli([str(STATIC / "objects_v2.inv"), "-f", "yaml", *options]) diff --git a/tests/test_renderers/test_fixtures_sphinx.py b/tests/test_renderers/test_fixtures_sphinx.py index 443fc9db..1b9725db 100644 --- a/tests/test_renderers/test_fixtures_sphinx.py +++ b/tests/test_renderers/test_fixtures_sphinx.py @@ -27,7 +27,7 @@ def test_syntax_elements(file_params, sphinx_doctree_no_tr: CreateDoctree): @pytest.mark.param_file(FIXTURE_PATH / "sphinx_link_resolution.md") def test_link_resolution(file_params, sphinx_doctree: CreateDoctree): sphinx_doctree.set_conf( - {"extensions": ["myst_parser"], **settings_from_json(file_params.description)} + {"extensions": ["myst_parser"], **settings_from_json(file_params.description)}, ) sphinx_doctree.srcdir.joinpath("test.txt").touch() sphinx_doctree.srcdir.joinpath("other.rst").write_text(":orphan:\n\nTest\n====") @@ -69,7 +69,7 @@ def test_sphinx_directives(file_params, sphinx_doctree_no_tr: CreateDoctree): # TODO fix skipped directives # TODO test domain directives if file_params.title.startswith("SKIP") or file_params.title.startswith( - "SPHINX4-SKIP" + "SPHINX4-SKIP", ): pytest.skip(file_params.title) @@ -82,10 +82,12 @@ def test_sphinx_directives(file_params, sphinx_doctree_no_tr: CreateDoctree): pformat = pformat.replace('"2147483647"', '"9223372036854775807"') # changed in sphinx 7.1 (but fixed in 7.2) pformat = pformat.replace( - 'classes="sig sig-object sig sig-object"', 'classes="sig sig-object"' + 'classes="sig sig-object sig sig-object"', + 'classes="sig sig-object"', ) pformat = pformat.replace( - 'classes="sig-name descname sig-name descname"', 'classes="sig-name descname"' + 'classes="sig-name descname sig-name descname"', + 'classes="sig-name descname"', ) pformat = pformat.replace( 'classes="sig-prename descclassname sig-prename descclassname"', @@ -124,7 +126,7 @@ def test_sphinx_roles(file_params, sphinx_doctree_no_tr: CreateDoctree): @pytest.mark.param_file(FIXTURE_PATH / "dollarmath.md") def test_dollarmath(file_params, sphinx_doctree_no_tr: CreateDoctree): sphinx_doctree_no_tr.set_conf( - {"extensions": ["myst_parser"], "myst_enable_extensions": ["dollarmath"]} + {"extensions": ["myst_parser"], "myst_enable_extensions": ["dollarmath"]}, ) result = sphinx_doctree_no_tr(file_params.content, "index.md") file_params.assert_expected(result.pformat("index"), rstrip_lines=True) @@ -134,7 +136,7 @@ def test_dollarmath(file_params, sphinx_doctree_no_tr: CreateDoctree): def test_amsmath(file_params, sphinx_doctree_no_tr: CreateDoctree, monkeypatch): monkeypatch.setattr(SphinxRenderer, "_random_label", lambda self: "mock-uuid") sphinx_doctree_no_tr.set_conf( - {"extensions": ["myst_parser"], "myst_enable_extensions": ["amsmath"]} + {"extensions": ["myst_parser"], "myst_enable_extensions": ["amsmath"]}, ) result = sphinx_doctree_no_tr(file_params.content, "index.md") file_params.assert_expected(result.pformat("index"), rstrip_lines=True) @@ -144,7 +146,7 @@ def test_amsmath(file_params, sphinx_doctree_no_tr: CreateDoctree, monkeypatch): def test_containers(file_params, sphinx_doctree_no_tr: CreateDoctree, monkeypatch): monkeypatch.setattr(SphinxRenderer, "_random_label", lambda self: "mock-uuid") sphinx_doctree_no_tr.set_conf( - {"extensions": ["myst_parser"], "myst_enable_extensions": ["colon_fence"]} + {"extensions": ["myst_parser"], "myst_enable_extensions": ["colon_fence"]}, ) result = sphinx_doctree_no_tr(file_params.content, "index.md") file_params.assert_expected(result.pformat("index"), rstrip_lines=True) @@ -160,7 +162,7 @@ def test_evalrst_elements(file_params, sphinx_doctree_no_tr: CreateDoctree): @pytest.mark.param_file(FIXTURE_PATH / "definition_lists.md") def test_definition_lists(file_params, sphinx_doctree_no_tr: CreateDoctree): sphinx_doctree_no_tr.set_conf( - {"extensions": ["myst_parser"], "myst_enable_extensions": ["deflist"]} + {"extensions": ["myst_parser"], "myst_enable_extensions": ["deflist"]}, ) result = sphinx_doctree_no_tr(file_params.content, "index.md") file_params.assert_expected(result.pformat("index"), rstrip_lines=True) @@ -172,7 +174,7 @@ def test_attributes(file_params, sphinx_doctree_no_tr: CreateDoctree): { "extensions": ["myst_parser"], "myst_enable_extensions": ["attrs_inline", "attrs_block"], - } + }, ) result = sphinx_doctree_no_tr(file_params.content, "index.md") file_params.assert_expected(result.pformat("index"), rstrip_lines=True) diff --git a/tests/test_renderers/test_myst_config.py b/tests/test_renderers/test_myst_config.py index e35aee40..36d6f421 100644 --- a/tests/test_renderers/test_myst_config.py +++ b/tests/test_renderers/test_myst_config.py @@ -26,7 +26,7 @@ def test_cmdline(file_params: ParamTestData): pub.process_command_line(shlex.split(file_params.description)) except Exception as err: raise AssertionError( - f"Failed to parse commandline: {file_params.description}\n{err}" + f"Failed to parse commandline: {file_params.description}\n{err}", ) from err settings = vars(pub.settings) report_stream = StringIO() diff --git a/tests/test_renderers/test_myst_refs.py b/tests/test_renderers/test_myst_refs.py index 6412eadc..4c896636 100644 --- a/tests/test_renderers/test_myst_refs.py +++ b/tests/test_renderers/test_myst_refs.py @@ -39,6 +39,7 @@ def test_parse( outcome = doctree.pformat() if result.warnings.strip(): outcome += "\n\n" + result.warnings.strip().replace("", "").replace( - "", "" + "", + "", ) file_regression.check(outcome, basename=test_name, extension=".xml") diff --git a/tests/test_renderers/test_parse_directives.py b/tests/test_renderers/test_parse_directives.py index a8c8fa74..64ec61a6 100644 --- a/tests/test_renderers/test_parse_directives.py +++ b/tests/test_renderers/test_parse_directives.py @@ -19,7 +19,8 @@ def test_option_parsing(file_params): """Test parsing of directive options.""" result = list(options_to_items(file_params.content)) file_params.assert_expected( - json.dumps(result, ensure_ascii=False, indent=2), rstrip_lines=True + json.dumps(result, ensure_ascii=False, indent=2), + rstrip_lines=True, ) @@ -49,7 +50,10 @@ def test_parsing(file_params): raise AssertionError(f"Unknown directive: {name}") try: result = parse_directive_text( - klass, first_line[0] if first_line else "", tokens[0].content, line=0 + klass, + first_line[0] if first_line else "", + tokens[0].content, + line=0, ) except MarkupError as err: outcome = f"error: {err}" @@ -68,7 +72,8 @@ def test_parsing(file_params): @pytest.mark.parametrize( - "descript,klass,arguments,content", [("no content", Rubric, "", "a")] + "descript,klass,arguments,content", + [("no content", Rubric, "", "a")], ) def test_parsing_errors(descript, klass, arguments, content): with pytest.raises(MarkupError): @@ -77,7 +82,10 @@ def test_parsing_errors(descript, klass, arguments, content): def test_parsing_full_yaml(): result = parse_directive_text( - Note, "", "---\na: [1]\n---\ncontent", validate_options=False + Note, + "", + "---\na: [1]\n---\ncontent", + validate_options=False, ) assert not result.warnings assert result.options == {"a": [1]} @@ -88,28 +96,40 @@ def test_additional_options(): """Allow additional options to be passed to a directive.""" # this should be fine result = parse_directive_text( - Note, "", "content", additional_options={"class": "bar"} + Note, + "", + "content", + additional_options={"class": "bar"}, ) assert not result.warnings assert result.options == {"class": ["bar"]} assert result.body == ["content"] # body on first line should also be fine result = parse_directive_text( - Note, "content", "other", additional_options={"class": "bar"} + Note, + "content", + "other", + additional_options={"class": "bar"}, ) assert not result.warnings assert result.options == {"class": ["bar"]} assert result.body == ["content", "other"] # additional option should not take precedence result = parse_directive_text( - Note, "content", ":class: foo", additional_options={"class": "bar"} + Note, + "content", + ":class: foo", + additional_options={"class": "bar"}, ) assert not result.warnings assert result.options == {"class": ["foo"]} assert result.body == ["content"] # this should warn about the unknown option result = parse_directive_text( - Note, "", "content", additional_options={"foo": "bar"} + Note, + "", + "content", + additional_options={"foo": "bar"}, ) assert len(result.warnings) == 1 assert "Unknown option" in result.warnings[0][0] diff --git a/tests/test_sphinx/conftest.py b/tests/test_sphinx/conftest.py index 4a244872..b785679d 100644 --- a/tests/test_sphinx/conftest.py +++ b/tests/test_sphinx/conftest.py @@ -115,7 +115,7 @@ def read( # convert absolute filenames for node in findall(doctree)( - lambda n: "source" in n and not isinstance(n, str) + lambda n: "source" in n and not isinstance(n, str), ): node["source"] = pathlib.Path(node["source"]).name diff --git a/tests/test_sphinx/test_sphinx_builds.py b/tests/test_sphinx/test_sphinx_builds.py index 57039d8f..1966b7fc 100644 --- a/tests/test_sphinx/test_sphinx_builds.py +++ b/tests/test_sphinx/test_sphinx_builds.py @@ -238,7 +238,9 @@ def test_extended_syntaxes( @pytest.mark.sphinx( - buildername="html", srcdir=os.path.join(SOURCE_DIR, "includes"), freshenv=True + buildername="html", + srcdir=os.path.join(SOURCE_DIR, "includes"), + freshenv=True, ) def test_includes( app, @@ -314,7 +316,9 @@ def test_include_from_rst( reason="Footnote HTML changed in docutils 0.19", ) @pytest.mark.sphinx( - buildername="html", srcdir=os.path.join(SOURCE_DIR, "footnotes"), freshenv=True + buildername="html", + srcdir=os.path.join(SOURCE_DIR, "footnotes"), + freshenv=True, ) def test_footnotes( app, @@ -403,7 +407,9 @@ def test_substitutions( @pytest.mark.sphinx( - buildername="gettext", srcdir=os.path.join(SOURCE_DIR, "gettext"), freshenv=True + buildername="gettext", + srcdir=os.path.join(SOURCE_DIR, "gettext"), + freshenv=True, ) def test_gettext( app, @@ -464,7 +470,7 @@ def test_gettext_html( regress_ext=".html", replace={ # upstream bug https://github.com/sphinx-doc/sphinx/issues/11689 - '"Permalink to this heading"': '"Lien permanent vers cette rubrique"' + '"Permalink to this heading"': '"Lien permanent vers cette rubrique"', }, ) From caae7d38cc7784dba6e104b8cfbb876e5e0d4a2e Mon Sep 17 00:00:00 2001 From: "daniel.eades" <daniel.eades@seebyte.com> Date: Fri, 26 Jan 2024 13:02:40 +0000 Subject: [PATCH 3/9] add 'flake8-future-annotations (FA)' lints --- myst_parser/config/main.py | 56 +++++++++++++--------------- myst_parser/parsers/docutils_.py | 29 +++++++------- myst_parser/sphinx_ext/directives.py | 8 ++-- pyproject.toml | 2 +- 4 files changed, 46 insertions(+), 49 deletions(-) diff --git a/myst_parser/config/main.py b/myst_parser/config/main.py index 29e3ad33..f292002c 100644 --- a/myst_parser/config/main.py +++ b/myst_parser/config/main.py @@ -1,19 +1,15 @@ """The configuration for the myst parser.""" +from __future__ import annotations + import dataclasses as dc from importlib import import_module from typing import ( Any, Callable, - Dict, Iterable, Iterator, - List, - Optional, Sequence, - Set, - Tuple, TypedDict, - Union, ) from myst_parser.warnings_ import MystWarnings @@ -30,7 +26,7 @@ ) -def check_extensions(inst: "MdParserConfig", field: dc.Field, value: Any) -> None: +def check_extensions(inst: MdParserConfig, field: dc.Field, value: Any) -> None: """Check that the extensions are a list of known strings""" if not isinstance(value, Iterable): raise TypeError(f"'{field.name}' not iterable: {value}") @@ -64,10 +60,10 @@ class UrlSchemeType(TypedDict, total=False): url: str title: str - classes: List[str] + classes: list[str] -def check_url_schemes(inst: "MdParserConfig", field: dc.Field, value: Any) -> None: +def check_url_schemes(inst: MdParserConfig, field: dc.Field, value: Any) -> None: """Check that the external schemes are of the right format.""" if isinstance(value, (list, tuple)): if not all(isinstance(v, str) for v in value): @@ -77,7 +73,7 @@ def check_url_schemes(inst: "MdParserConfig", field: dc.Field, value: Any) -> No if not isinstance(value, dict): raise TypeError(f"'{field.name}' is not a dictionary: {value!r}") - new_dict: Dict[str, Optional[UrlSchemeType]] = {} + new_dict: dict[str, UrlSchemeType | None] = {} for key, val in value.items(): if not isinstance(key, str): raise TypeError(f"'{field.name}' key is not a string: {key!r}") @@ -113,7 +109,7 @@ def check_url_schemes(inst: "MdParserConfig", field: dc.Field, value: Any) -> No setattr(inst, field.name, new_dict) -def check_sub_delimiters(_: "MdParserConfig", field: dc.Field, value: Any) -> None: +def check_sub_delimiters(_: MdParserConfig, field: dc.Field, value: Any) -> None: """Check that the sub_delimiters are a tuple of length 2 of strings of length 1""" if (not isinstance(value, (tuple, list))) or len(value) != 2: raise TypeError(f"'{field.name}' is not a tuple of length 2: {value}") @@ -124,7 +120,7 @@ def check_sub_delimiters(_: "MdParserConfig", field: dc.Field, value: Any) -> No ) -def check_inventories(_: "MdParserConfig", field: dc.Field, value: Any) -> None: +def check_inventories(_: MdParserConfig, field: dc.Field, value: Any) -> None: """Check that the inventories are a dict of {str: (str, Optional[str])}""" if not isinstance(value, dict): raise TypeError(f"'{field.name}' is not a dictionary: {value!r}") @@ -142,7 +138,7 @@ def check_inventories(_: "MdParserConfig", field: dc.Field, value: Any) -> None: def check_heading_slug_func( - inst: "MdParserConfig", + inst: MdParserConfig, field: dc.Field, value: Any, ) -> None: @@ -171,7 +167,7 @@ def _test_slug_func(text: str) -> str: def check_fence_as_directive( - inst: "MdParserConfig", + inst: MdParserConfig, field: dc.Field, value: Any, ) -> None: @@ -191,7 +187,7 @@ def __repr__(self) -> str: """Return a string representation of the config.""" # this replicates the auto-generated __repr__, # but also allows for a repr function to be defined on the field - attributes: List[str] = [] + attributes: list[str] = [] for name, val, f in self.as_triple(): if not f.repr: continue @@ -216,7 +212,7 @@ def __repr__(self) -> str: }, ) - enable_extensions: Set[str] = dc.field( + enable_extensions: set[str] = dc.field( default_factory=set, metadata={"validator": check_extensions, "help": "Enable syntax extensions"}, ) @@ -237,7 +233,7 @@ def __repr__(self) -> str: }, ) - url_schemes: Dict[str, Optional[UrlSchemeType]] = dc.field( + url_schemes: dict[str, UrlSchemeType | None] = dc.field( default_factory=lambda: { "http": None, "https": None, @@ -253,7 +249,7 @@ def __repr__(self) -> str: }, ) - ref_domains: Optional[Iterable[str]] = dc.field( + ref_domains: Iterable[str] | None = dc.field( default=None, metadata={ "validator": optional( @@ -264,7 +260,7 @@ def __repr__(self) -> str: }, ) - fence_as_directive: Set[str] = dc.field( + fence_as_directive: set[str] = dc.field( default_factory=set, metadata={ "validator": check_fence_as_directive, @@ -298,7 +294,7 @@ def __repr__(self) -> str: }, ) - heading_slug_func: Optional[Callable[[str], str]] = dc.field( + heading_slug_func: Callable[[str], str] | None = dc.field( default=None, metadata={ "validator": check_heading_slug_func, @@ -311,7 +307,7 @@ def __repr__(self) -> str: }, ) - html_meta: Dict[str, str] = dc.field( + html_meta: dict[str, str] = dc.field( default_factory=dict, metadata={ "validator": deep_mapping( @@ -343,7 +339,7 @@ def __repr__(self) -> str: # Extension specific - substitutions: Dict[str, Any] = dc.field( + substitutions: dict[str, Any] = dc.field( default_factory=dict, metadata={ "validator": deep_mapping(instance_of(str), any_, instance_of(dict)), @@ -354,7 +350,7 @@ def __repr__(self) -> str: }, ) - sub_delimiters: Tuple[str, str] = dc.field( + sub_delimiters: tuple[str, str] = dc.field( default=("{", "}"), repr=False, metadata={ @@ -459,7 +455,7 @@ def __repr__(self) -> str: }, ) - inventories: Dict[str, Tuple[str, Optional[str]]] = dc.field( + inventories: dict[str, tuple[str, str | None]] = dc.field( default_factory=dict, repr=False, metadata={ @@ -473,7 +469,7 @@ def __repr__(self) -> str: def __post_init__(self): validate_fields(self) - def copy(self, **kwargs: Any) -> "MdParserConfig": + def copy(self, **kwargs: Any) -> MdParserConfig: """Return a new object replacing specified fields with new values. Note: initiating the copy will also validate the new fields. @@ -481,7 +477,7 @@ def copy(self, **kwargs: Any) -> "MdParserConfig": return dc.replace(self, **kwargs) @classmethod - def get_fields(cls) -> Tuple[dc.Field, ...]: + def get_fields(cls) -> tuple[dc.Field, ...]: """Return all attribute fields in this class.""" return dc.fields(cls) @@ -489,7 +485,7 @@ def as_dict(self, dict_factory=dict) -> dict: """Return a dictionary of field name -> value.""" return dc.asdict(self, dict_factory=dict_factory) - def as_triple(self) -> Iterable[Tuple[str, Any, dc.Field]]: + def as_triple(self) -> Iterable[tuple[str, Any, dc.Field]]: """Yield triples of (name, value, field).""" fields = {f.name: f for f in dc.fields(self.__class__)} for name, value in dc.asdict(self).items(): @@ -498,7 +494,7 @@ def as_triple(self) -> Iterable[Tuple[str, Any, dc.Field]]: def merge_file_level( config: MdParserConfig, - topmatter: Dict[str, Any], + topmatter: dict[str, Any], warning: Callable[[MystWarnings, str], None], ) -> MdParserConfig: """Merge the file-level topmatter with the global config. @@ -509,7 +505,7 @@ def merge_file_level( :returns: A new config object """ # get updates - updates: Dict[str, Any] = {} + updates: dict[str, Any] = {} myst = topmatter.get("myst", {}) if not isinstance(myst, dict): warning(MystWarnings.MD_TOPMATTER, f"'myst' key not a dict: {type(myst)}") @@ -561,7 +557,7 @@ class TopmatterReadError(Exception): """Topmatter parsing error.""" -def read_topmatter(text: Union[str, Iterator[str]]) -> Optional[Dict[str, Any]]: +def read_topmatter(text: str | Iterator[str]) -> dict[str, Any] | None: """Read the (optional) YAML topmatter from a source string. This is identified by the first line starting with `---`, diff --git a/myst_parser/parsers/docutils_.py b/myst_parser/parsers/docutils_.py index 52003c9b..1572774e 100644 --- a/myst_parser/parsers/docutils_.py +++ b/myst_parser/parsers/docutils_.py @@ -1,13 +1,12 @@ """MyST Markdown parser for docutils.""" +from __future__ import annotations + from dataclasses import Field from typing import ( Any, Callable, - Dict, Iterable, - List, Literal, - Optional, Sequence, Set, Tuple, @@ -52,7 +51,7 @@ def _validate_comma_separated_set( option_parser, config_parser=None, config_section=None, -) -> Set[str]: +) -> set[str]: """Validate an integer setting.""" value = frontend.validate_comma_separated_list( setting, @@ -64,7 +63,7 @@ def _validate_comma_separated_set( return set(value) -def _create_validate_tuple(length: int) -> Callable[..., Tuple[str, ...]]: +def _create_validate_tuple(length: int) -> Callable[..., tuple[str, ...]]: """Create a validator for a tuple of length `length`.""" def _validate( @@ -152,7 +151,7 @@ def _validate_url_schemes( return output -def _attr_to_optparse_option(at: Field, default: Any) -> Tuple[dict, str]: +def _attr_to_optparse_option(at: Field, default: Any) -> tuple[dict, str]: """Convert a field into a Docutils optparse options dict. :returns: (option_dict, default) @@ -221,7 +220,7 @@ def attr_to_optparse_option( attribute: Field, default: Any, prefix: str = "myst_", -) -> Tuple[str, List[str], Dict[str, Any]]: +) -> tuple[str, list[str], dict[str, Any]]: """Convert an ``MdParserConfig`` attribute into a Docutils setting tuple. :returns: A tuple of ``(help string, option flags, optparse kwargs)``. @@ -267,7 +266,7 @@ def create_myst_config( class Parser(RstParser): """Docutils parser for Markedly Structured Text (MyST).""" - supported: Tuple[str, ...] = ("md", "markdown", "myst") + supported: tuple[str, ...] = ("md", "markdown", "myst") """Aliases this parser supports.""" settings_spec = ( @@ -379,7 +378,7 @@ def __init__(self): self.translator_class = SimpleTranslator -def _run_cli(writer_name: str, writer_description: str, argv: Optional[List[str]]): +def _run_cli(writer_name: str, writer_description: str, argv: list[str] | None): """Run the command line interface for a particular writer.""" publish_cmdline( parser=Parser(), @@ -391,17 +390,17 @@ def _run_cli(writer_name: str, writer_description: str, argv: Optional[List[str] ) -def cli_html(argv: Optional[List[str]] = None) -> None: +def cli_html(argv: list[str] | None = None) -> None: """Cmdline entrypoint for converting MyST to HTML.""" _run_cli("html", "(X)HTML documents", argv) -def cli_html5(argv: Optional[List[str]] = None): +def cli_html5(argv: list[str] | None = None): """Cmdline entrypoint for converting MyST to HTML5.""" _run_cli("html5", "HTML5 documents", argv) -def cli_html5_demo(argv: Optional[List[str]] = None): +def cli_html5_demo(argv: list[str] | None = None): """Cmdline entrypoint for converting MyST to simple HTML5 demonstrations. This is a special case of the HTML5 writer, @@ -439,17 +438,17 @@ def to_html5_demo(inputstring: str, **kwargs) -> str: ) -def cli_latex(argv: Optional[List[str]] = None): +def cli_latex(argv: list[str] | None = None): """Cmdline entrypoint for converting MyST to LaTeX.""" _run_cli("latex", "LaTeX documents", argv) -def cli_xml(argv: Optional[List[str]] = None): +def cli_xml(argv: list[str] | None = None): """Cmdline entrypoint for converting MyST to XML.""" _run_cli("xml", "Docutils-native XML", argv) -def cli_pseudoxml(argv: Optional[List[str]] = None): +def cli_pseudoxml(argv: list[str] | None = None): """Cmdline entrypoint for converting MyST to pseudo-XML.""" _run_cli("pseudoxml", "pseudo-XML", argv) diff --git a/myst_parser/sphinx_ext/directives.py b/myst_parser/sphinx_ext/directives.py index 0a040200..8f3b1356 100644 --- a/myst_parser/sphinx_ext/directives.py +++ b/myst_parser/sphinx_ext/directives.py @@ -1,6 +1,8 @@ """MyST specific directives""" +from __future__ import annotations + from copy import copy -from typing import List, Tuple, cast +from typing import cast from docutils import nodes from docutils.parsers.rst import directives @@ -27,7 +29,7 @@ class SubstitutionReferenceRole(SphinxRole): Note, in ``docutils/parsers/rst/roles.py`` this is left unimplemented. """ - def run(self) -> Tuple[List[nodes.Node], List[nodes.system_message]]: + def run(self) -> tuple[list[nodes.Node], list[nodes.system_message]]: subref_node = nodes.substitution_reference(self.rawtext, self.text) self.set_source_info(subref_node, self.lineno) subref_node["refname"] = nodes.fully_normalize_name(self.text) @@ -59,7 +61,7 @@ class FigureMarkdown(SphinxDirective): "name": directives.unchanged, } - def run(self) -> List[nodes.Node]: + def run(self) -> list[nodes.Node]: figwidth = self.options.pop("width", None) figclasses = self.options.pop("class", None) align = self.options.pop("align", None) diff --git a/pyproject.toml b/pyproject.toml index b9fef549..9ff75949 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -102,7 +102,7 @@ exclude = [ ] [tool.ruff] -extend-select = ["B", "C4", "COM", "I", "ICN", "ISC", "N", "RUF", "SIM", "UP"] +extend-select = ["B", "C4", "COM", "FA", "I", "ICN", "ISC", "N", "RUF", "SIM", "UP"] extend-ignore = ["ISC001", "RUF005", "RUF012", "COM812"] [tool.mypy] From b7f7a1d99cc5ab596254adc4d74b38a4bd1804dd Mon Sep 17 00:00:00 2001 From: "daniel.eades" <daniel.eades@seebyte.com> Date: Fri, 26 Jan 2024 13:05:31 +0000 Subject: [PATCH 4/9] add 'flake8-pie (PIE)' lints --- .github/workflows/docutils_setup.py | 2 +- myst_parser/config/main.py | 2 +- myst_parser/inventory.py | 2 +- myst_parser/parsers/parse_html.py | 2 +- myst_parser/warnings_.py | 2 +- pyproject.toml | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/docutils_setup.py b/.github/workflows/docutils_setup.py index 32567c68..122ea2c5 100755 --- a/.github/workflows/docutils_setup.py +++ b/.github/workflows/docutils_setup.py @@ -16,7 +16,7 @@ def modify_toml(content: str) -> str: dependencies = [] sphinx_extra = [] for dep in doc["project"]["dependencies"]: - if dep.startswith("docutils") or dep.startswith("sphinx"): + if dep.startswith(("docutils", "sphinx")): sphinx_extra.append(dep) else: dependencies.append(dep) diff --git a/myst_parser/config/main.py b/myst_parser/config/main.py index f292002c..70b6eec2 100644 --- a/myst_parser/config/main.py +++ b/myst_parser/config/main.py @@ -579,7 +579,7 @@ def read_topmatter(text: str | Iterator[str]) -> dict[str, Any] | None: return None top_matter = [] for line in text: - if line.startswith("---") or line.startswith("..."): + if line.startswith(("---", "...")): break top_matter.append(line.rstrip() + "\n") try: diff --git a/myst_parser/inventory.py b/myst_parser/inventory.py index c85c7e0c..183f4b44 100644 --- a/myst_parser/inventory.py +++ b/myst_parser/inventory.py @@ -407,7 +407,7 @@ def fetch_inventory( base_url: None | str = None, ) -> InventoryType: """Fetch an inventory from a URL or local path.""" - if uri.startswith("http://") or uri.startswith("https://"): + if uri.startswith(("http://", "https://")): with urlopen(uri, timeout=timeout) as stream: return load(stream, base_url=base_url) with open(uri, "rb") as stream: diff --git a/myst_parser/parsers/parse_html.py b/myst_parser/parsers/parse_html.py index 63c0339b..d7bfadd4 100644 --- a/myst_parser/parsers/parse_html.py +++ b/myst_parser/parsers/parse_html.py @@ -362,7 +362,7 @@ def enclose(self, name: str): count = 0 # It pops all the items which do not match with the closing tag. - for _ in range(0, count): + for _ in range(count): self.stack.pop() diff --git a/myst_parser/warnings_.py b/myst_parser/warnings_.py index 1cbd898b..551e77bc 100644 --- a/myst_parser/warnings_.py +++ b/myst_parser/warnings_.py @@ -24,7 +24,7 @@ class MystWarnings(Enum): """Duplicate Markdown reference definition.""" MD_FOOTNOTE_DUPE = "footnote" """Duplicate Markdown footnote definition.""" - MD_FOOTNOTE_MISSING = "footnote" + MD_FOOTNOTE_MISSING = "footnote" # noqa: PIE796 """Missing Markdown footnote definition.""" MD_HEADING_NON_CONSECUTIVE = "header" """Non-consecutive heading levels.""" diff --git a/pyproject.toml b/pyproject.toml index 9ff75949..5e6f5780 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -102,7 +102,7 @@ exclude = [ ] [tool.ruff] -extend-select = ["B", "C4", "COM", "FA", "I", "ICN", "ISC", "N", "RUF", "SIM", "UP"] +extend-select = ["B", "C4", "COM", "FA", "I", "ICN", "ISC", "N", "PIE", "RUF", "SIM", "UP"] extend-ignore = ["ISC001", "RUF005", "RUF012", "COM812"] [tool.mypy] From e0b5f08936cad58fe7e7509a39950583fd1a820f Mon Sep 17 00:00:00 2001 From: "daniel.eades" <daniel.eades@seebyte.com> Date: Fri, 26 Jan 2024 13:10:42 +0000 Subject: [PATCH 5/9] add 'pygrep-hooks (PGH)' lints --- myst_parser/_docs.py | 2 +- myst_parser/config/main.py | 2 +- myst_parser/mdit_to_docutils/base.py | 4 ++-- myst_parser/sphinx_ext/mathjax.py | 6 +++--- pyproject.toml | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/myst_parser/_docs.py b/myst_parser/_docs.py index a5032243..aaf84e1f 100644 --- a/myst_parser/_docs.py +++ b/myst_parser/_docs.py @@ -96,7 +96,7 @@ def field_type(field): get_args(field.type) if get_origin(field.type) is Union else [field.type] ) ctype = " | ".join( - str("None" if ftype == type(None) else ftype) # type: ignore + str("None" if ftype == type(None) else ftype) # type: ignore[comparison-overlap] for ftype in ftypes ) ctype = " ".join(ctype.splitlines()) diff --git a/myst_parser/config/main.py b/myst_parser/config/main.py index 70b6eec2..009d9985 100644 --- a/myst_parser/config/main.py +++ b/myst_parser/config/main.py @@ -100,7 +100,7 @@ def check_url_schemes(inst: MdParserConfig, field: dc.Field, value: Any) -> None raise TypeError( f"'{field.name}[{key}][classes]' is not a list of str: {val['classes']!r}", ) - new_dict[key] = val # type: ignore + new_dict[key] = val # type: ignore[assignment] else: raise TypeError( f"'{field.name}[{key}]' value is not a string or dict: {val!r}", diff --git a/myst_parser/mdit_to_docutils/base.py b/myst_parser/mdit_to_docutils/base.py index aae0febc..9cf55b1f 100644 --- a/myst_parser/mdit_to_docutils/base.py +++ b/myst_parser/mdit_to_docutils/base.py @@ -1640,9 +1640,9 @@ def render_dl(self, token: SyntaxTreeNode) -> None: from sphinx.domains.std import make_glossary_term term = make_glossary_term( - self.sphinx_env, # type: ignore + self.sphinx_env, # type: ignore[arg-type] term.children, - None, # type: ignore + None, # type: ignore[arg-type] term.source, term.line, node_id=None, diff --git a/myst_parser/sphinx_ext/mathjax.py b/myst_parser/sphinx_ext/mathjax.py index 8939a0e1..d569087d 100644 --- a/myst_parser/sphinx_ext/mathjax.py +++ b/myst_parser/sphinx_ext/mathjax.py @@ -53,14 +53,14 @@ def override_mathjax(app: Sphinx): if "dollarmath" not in app.config["myst_enable_extensions"]: return - if not app.env.myst_config.update_mathjax: # type: ignore + if not app.env.myst_config.update_mathjax: # type: ignore[attr-defined] return - mjax_classes = app.env.myst_config.mathjax_classes # type: ignore + mjax_classes = app.env.myst_config.mathjax_classes # type: ignore[attr-defined] if "mathjax3_config" in app.config: # sphinx 4 + mathjax 3 - app.config.mathjax3_config = app.config.mathjax3_config or {} # type: ignore + app.config.mathjax3_config = app.config.mathjax3_config or {} # type: ignore[attr-defined] app.config.mathjax3_config.setdefault("options", {}) if ( "processHtmlClass" in app.config.mathjax3_config["options"] diff --git a/pyproject.toml b/pyproject.toml index 5e6f5780..53c3dba2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -102,7 +102,7 @@ exclude = [ ] [tool.ruff] -extend-select = ["B", "C4", "COM", "FA", "I", "ICN", "ISC", "N", "PIE", "RUF", "SIM", "UP"] +extend-select = ["B", "C4", "COM", "FA", "I", "ICN", "ISC", "N", "PGH", "PIE", "RUF", "SIM", "UP"] extend-ignore = ["ISC001", "RUF005", "RUF012", "COM812"] [tool.mypy] From e647521f75590b2c5d97d5f785bd2465dcc73ce0 Mon Sep 17 00:00:00 2001 From: "daniel.eades" <daniel.eades@seebyte.com> Date: Fri, 26 Jan 2024 13:12:19 +0000 Subject: [PATCH 6/9] add 'Perflint (PERF)' lints --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 53c3dba2..6360aba4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -102,7 +102,7 @@ exclude = [ ] [tool.ruff] -extend-select = ["B", "C4", "COM", "FA", "I", "ICN", "ISC", "N", "PGH", "PIE", "RUF", "SIM", "UP"] +extend-select = ["B", "C4", "COM", "FA", "I", "ICN", "ISC", "N", "PERF", "PGH", "PIE", "RUF", "SIM", "UP"] extend-ignore = ["ISC001", "RUF005", "RUF012", "COM812"] [tool.mypy] From b26df6b919c69923abea19106ebe9ef0b39858bf Mon Sep 17 00:00:00 2001 From: "daniel.eades" <daniel.eades@seebyte.com> Date: Fri, 26 Jan 2024 13:13:02 +0000 Subject: [PATCH 7/9] add 'refurb (FURB)' lints --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 6360aba4..6e9541a8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -102,7 +102,7 @@ exclude = [ ] [tool.ruff] -extend-select = ["B", "C4", "COM", "FA", "I", "ICN", "ISC", "N", "PERF", "PGH", "PIE", "RUF", "SIM", "UP"] +extend-select = ["B", "C4", "COM", "FA", "FURB", "I", "ICN", "ISC", "N", "PERF", "PGH", "PIE", "RUF", "SIM", "UP"] extend-ignore = ["ISC001", "RUF005", "RUF012", "COM812"] [tool.mypy] From 698a6965b5a4e411b46f07ffe090d9e2730d815f Mon Sep 17 00:00:00 2001 From: Chris Sewell <chrisj_sewell@hotmail.com> Date: Tue, 26 Mar 2024 03:58:50 +0100 Subject: [PATCH 8/9] Revert some future annotations --- myst_parser/config/main.py | 82 ++++++++++++++++---------------- myst_parser/parsers/docutils_.py | 29 +++++------ pyproject.toml | 4 ++ 3 files changed, 59 insertions(+), 56 deletions(-) diff --git a/myst_parser/config/main.py b/myst_parser/config/main.py index 009d9985..32587693 100644 --- a/myst_parser/config/main.py +++ b/myst_parser/config/main.py @@ -1,15 +1,19 @@ """The configuration for the myst parser.""" -from __future__ import annotations - import dataclasses as dc from importlib import import_module from typing import ( Any, Callable, + Dict, Iterable, Iterator, + List, + Optional, Sequence, + Set, + Tuple, TypedDict, + Union, ) from myst_parser.warnings_ import MystWarnings @@ -26,7 +30,7 @@ ) -def check_extensions(inst: MdParserConfig, field: dc.Field, value: Any) -> None: +def check_extensions(inst: "MdParserConfig", field: dc.Field, value: Any) -> None: """Check that the extensions are a list of known strings""" if not isinstance(value, Iterable): raise TypeError(f"'{field.name}' not iterable: {value}") @@ -48,7 +52,7 @@ def check_extensions(inst: MdParserConfig, field: dc.Field, value: Any) -> None: "strikethrough", "substitution", "tasklist", - ], + ] ) if diff: raise ValueError(f"'{field.name}' items not recognised: {diff}") @@ -60,10 +64,10 @@ class UrlSchemeType(TypedDict, total=False): url: str title: str - classes: list[str] + classes: List[str] -def check_url_schemes(inst: MdParserConfig, field: dc.Field, value: Any) -> None: +def check_url_schemes(inst: "MdParserConfig", field: dc.Field, value: Any) -> None: """Check that the external schemes are of the right format.""" if isinstance(value, (list, tuple)): if not all(isinstance(v, str) for v in value): @@ -73,7 +77,7 @@ def check_url_schemes(inst: MdParserConfig, field: dc.Field, value: Any) -> None if not isinstance(value, dict): raise TypeError(f"'{field.name}' is not a dictionary: {value!r}") - new_dict: dict[str, UrlSchemeType | None] = {} + new_dict: Dict[str, Optional[UrlSchemeType]] = {} for key, val in value.items(): if not isinstance(key, str): raise TypeError(f"'{field.name}' key is not a string: {key!r}") @@ -86,11 +90,11 @@ def check_url_schemes(inst: MdParserConfig, field: dc.Field, value: Any) -> None raise TypeError(f"'{field.name}[{key}]' keys are not strings: {val!r}") if "url" in val and not isinstance(val["url"], str): raise TypeError( - f"'{field.name}[{key}][url]' is not a string: {val['url']!r}", + f"'{field.name}[{key}][url]' is not a string: {val['url']!r}" ) if "title" in val and not isinstance(val["title"], str): raise TypeError( - f"'{field.name}[{key}][title]' is not a string: {val['title']!r}", + f"'{field.name}[{key}][title]' is not a string: {val['title']!r}" ) if ( "classes" in val @@ -98,29 +102,29 @@ def check_url_schemes(inst: MdParserConfig, field: dc.Field, value: Any) -> None and not all(isinstance(c, str) for c in val["classes"]) ): raise TypeError( - f"'{field.name}[{key}][classes]' is not a list of str: {val['classes']!r}", + f"'{field.name}[{key}][classes]' is not a list of str: {val['classes']!r}" ) new_dict[key] = val # type: ignore[assignment] else: raise TypeError( - f"'{field.name}[{key}]' value is not a string or dict: {val!r}", + f"'{field.name}[{key}]' value is not a string or dict: {val!r}" ) setattr(inst, field.name, new_dict) -def check_sub_delimiters(_: MdParserConfig, field: dc.Field, value: Any) -> None: +def check_sub_delimiters(_: "MdParserConfig", field: dc.Field, value: Any) -> None: """Check that the sub_delimiters are a tuple of length 2 of strings of length 1""" if (not isinstance(value, (tuple, list))) or len(value) != 2: raise TypeError(f"'{field.name}' is not a tuple of length 2: {value}") for delim in value: if (not isinstance(delim, str)) or len(delim) != 1: raise TypeError( - f"'{field.name}' does not contain strings of length 1: {value}", + f"'{field.name}' does not contain strings of length 1: {value}" ) -def check_inventories(_: MdParserConfig, field: dc.Field, value: Any) -> None: +def check_inventories(_: "MdParserConfig", field: dc.Field, value: Any) -> None: """Check that the inventories are a dict of {str: (str, Optional[str])}""" if not isinstance(value, dict): raise TypeError(f"'{field.name}' is not a dictionary: {value!r}") @@ -129,7 +133,7 @@ def check_inventories(_: MdParserConfig, field: dc.Field, value: Any) -> None: raise TypeError(f"'{field.name}' key is not a string: {key!r}") if not isinstance(val, (tuple, list)) or len(val) != 2: raise TypeError( - f"'{field.name}[{key}]' value is not a 2-item list: {val!r}", + f"'{field.name}[{key}]' value is not a 2-item list: {val!r}" ) if not isinstance(val[0], str): raise TypeError(f"'{field.name}[{key}][0]' is not a string: {val[0]}") @@ -138,9 +142,7 @@ def check_inventories(_: MdParserConfig, field: dc.Field, value: Any) -> None: def check_heading_slug_func( - inst: MdParserConfig, - field: dc.Field, - value: Any, + inst: "MdParserConfig", field: dc.Field, value: Any ) -> None: """Check that the heading_slug_func is a callable.""" if value is None: @@ -153,7 +155,7 @@ def check_heading_slug_func( value = getattr(mod, function_name) except ImportError as exc: raise TypeError( - f"'{field.name}' could not be loaded from string: {value!r}", + f"'{field.name}' could not be loaded from string: {value!r}" ) from exc setattr(inst, field.name, value) if not callable(value): @@ -167,9 +169,7 @@ def _test_slug_func(text: str) -> str: def check_fence_as_directive( - inst: MdParserConfig, - field: dc.Field, - value: Any, + inst: "MdParserConfig", field: dc.Field, value: Any ) -> None: """Check that the extensions are a sequence of known strings""" deep_iterable(instance_of(str), instance_of((list, tuple, set)))(inst, field, value) @@ -187,7 +187,7 @@ def __repr__(self) -> str: """Return a string representation of the config.""" # this replicates the auto-generated __repr__, # but also allows for a repr function to be defined on the field - attributes: list[str] = [] + attributes: List[str] = [] for name, val, f in self.as_triple(): if not f.repr: continue @@ -212,7 +212,7 @@ def __repr__(self) -> str: }, ) - enable_extensions: set[str] = dc.field( + enable_extensions: Set[str] = dc.field( default_factory=set, metadata={"validator": check_extensions, "help": "Enable syntax extensions"}, ) @@ -233,7 +233,7 @@ def __repr__(self) -> str: }, ) - url_schemes: dict[str, UrlSchemeType | None] = dc.field( + url_schemes: Dict[str, Optional[UrlSchemeType]] = dc.field( default_factory=lambda: { "http": None, "https": None, @@ -249,18 +249,18 @@ def __repr__(self) -> str: }, ) - ref_domains: Iterable[str] | None = dc.field( + ref_domains: Optional[Iterable[str]] = dc.field( default=None, metadata={ "validator": optional( - deep_iterable(instance_of(str), instance_of((list, tuple))), + deep_iterable(instance_of(str), instance_of((list, tuple))) ), "help": "Sphinx domain names to search in for link references", "omit": ["docutils"], }, ) - fence_as_directive: set[str] = dc.field( + fence_as_directive: Set[str] = dc.field( default_factory=set, metadata={ "validator": check_fence_as_directive, @@ -294,7 +294,7 @@ def __repr__(self) -> str: }, ) - heading_slug_func: Callable[[str], str] | None = dc.field( + heading_slug_func: Optional[Callable[[str], str]] = dc.field( default=None, metadata={ "validator": check_heading_slug_func, @@ -307,13 +307,11 @@ def __repr__(self) -> str: }, ) - html_meta: dict[str, str] = dc.field( + html_meta: Dict[str, str] = dc.field( default_factory=dict, metadata={ "validator": deep_mapping( - instance_of(str), - instance_of(str), - instance_of(dict), + instance_of(str), instance_of(str), instance_of(dict) ), "merge_topmatter": True, "help": "HTML meta tags", @@ -339,7 +337,7 @@ def __repr__(self) -> str: # Extension specific - substitutions: dict[str, Any] = dc.field( + substitutions: Dict[str, Any] = dc.field( default_factory=dict, metadata={ "validator": deep_mapping(instance_of(str), any_, instance_of(dict)), @@ -350,7 +348,7 @@ def __repr__(self) -> str: }, ) - sub_delimiters: tuple[str, str] = dc.field( + sub_delimiters: Tuple[str, str] = dc.field( default=("{", "}"), repr=False, metadata={ @@ -455,7 +453,7 @@ def __repr__(self) -> str: }, ) - inventories: dict[str, tuple[str, str | None]] = dc.field( + inventories: Dict[str, Tuple[str, Optional[str]]] = dc.field( default_factory=dict, repr=False, metadata={ @@ -469,7 +467,7 @@ def __repr__(self) -> str: def __post_init__(self): validate_fields(self) - def copy(self, **kwargs: Any) -> MdParserConfig: + def copy(self, **kwargs: Any) -> "MdParserConfig": """Return a new object replacing specified fields with new values. Note: initiating the copy will also validate the new fields. @@ -477,7 +475,7 @@ def copy(self, **kwargs: Any) -> MdParserConfig: return dc.replace(self, **kwargs) @classmethod - def get_fields(cls) -> tuple[dc.Field, ...]: + def get_fields(cls) -> Tuple[dc.Field, ...]: """Return all attribute fields in this class.""" return dc.fields(cls) @@ -485,7 +483,7 @@ def as_dict(self, dict_factory=dict) -> dict: """Return a dictionary of field name -> value.""" return dc.asdict(self, dict_factory=dict_factory) - def as_triple(self) -> Iterable[tuple[str, Any, dc.Field]]: + def as_triple(self) -> Iterable[Tuple[str, Any, dc.Field]]: """Yield triples of (name, value, field).""" fields = {f.name: f for f in dc.fields(self.__class__)} for name, value in dc.asdict(self).items(): @@ -494,7 +492,7 @@ def as_triple(self) -> Iterable[tuple[str, Any, dc.Field]]: def merge_file_level( config: MdParserConfig, - topmatter: dict[str, Any], + topmatter: Dict[str, Any], warning: Callable[[MystWarnings, str], None], ) -> MdParserConfig: """Merge the file-level topmatter with the global config. @@ -505,7 +503,7 @@ def merge_file_level( :returns: A new config object """ # get updates - updates: dict[str, Any] = {} + updates: Dict[str, Any] = {} myst = topmatter.get("myst", {}) if not isinstance(myst, dict): warning(MystWarnings.MD_TOPMATTER, f"'myst' key not a dict: {type(myst)}") @@ -557,7 +555,7 @@ class TopmatterReadError(Exception): """Topmatter parsing error.""" -def read_topmatter(text: str | Iterator[str]) -> dict[str, Any] | None: +def read_topmatter(text: Union[str, Iterator[str]]) -> Optional[Dict[str, Any]]: """Read the (optional) YAML topmatter from a source string. This is identified by the first line starting with `---`, diff --git a/myst_parser/parsers/docutils_.py b/myst_parser/parsers/docutils_.py index 1572774e..52003c9b 100644 --- a/myst_parser/parsers/docutils_.py +++ b/myst_parser/parsers/docutils_.py @@ -1,12 +1,13 @@ """MyST Markdown parser for docutils.""" -from __future__ import annotations - from dataclasses import Field from typing import ( Any, Callable, + Dict, Iterable, + List, Literal, + Optional, Sequence, Set, Tuple, @@ -51,7 +52,7 @@ def _validate_comma_separated_set( option_parser, config_parser=None, config_section=None, -) -> set[str]: +) -> Set[str]: """Validate an integer setting.""" value = frontend.validate_comma_separated_list( setting, @@ -63,7 +64,7 @@ def _validate_comma_separated_set( return set(value) -def _create_validate_tuple(length: int) -> Callable[..., tuple[str, ...]]: +def _create_validate_tuple(length: int) -> Callable[..., Tuple[str, ...]]: """Create a validator for a tuple of length `length`.""" def _validate( @@ -151,7 +152,7 @@ def _validate_url_schemes( return output -def _attr_to_optparse_option(at: Field, default: Any) -> tuple[dict, str]: +def _attr_to_optparse_option(at: Field, default: Any) -> Tuple[dict, str]: """Convert a field into a Docutils optparse options dict. :returns: (option_dict, default) @@ -220,7 +221,7 @@ def attr_to_optparse_option( attribute: Field, default: Any, prefix: str = "myst_", -) -> tuple[str, list[str], dict[str, Any]]: +) -> Tuple[str, List[str], Dict[str, Any]]: """Convert an ``MdParserConfig`` attribute into a Docutils setting tuple. :returns: A tuple of ``(help string, option flags, optparse kwargs)``. @@ -266,7 +267,7 @@ def create_myst_config( class Parser(RstParser): """Docutils parser for Markedly Structured Text (MyST).""" - supported: tuple[str, ...] = ("md", "markdown", "myst") + supported: Tuple[str, ...] = ("md", "markdown", "myst") """Aliases this parser supports.""" settings_spec = ( @@ -378,7 +379,7 @@ def __init__(self): self.translator_class = SimpleTranslator -def _run_cli(writer_name: str, writer_description: str, argv: list[str] | None): +def _run_cli(writer_name: str, writer_description: str, argv: Optional[List[str]]): """Run the command line interface for a particular writer.""" publish_cmdline( parser=Parser(), @@ -390,17 +391,17 @@ def _run_cli(writer_name: str, writer_description: str, argv: list[str] | None): ) -def cli_html(argv: list[str] | None = None) -> None: +def cli_html(argv: Optional[List[str]] = None) -> None: """Cmdline entrypoint for converting MyST to HTML.""" _run_cli("html", "(X)HTML documents", argv) -def cli_html5(argv: list[str] | None = None): +def cli_html5(argv: Optional[List[str]] = None): """Cmdline entrypoint for converting MyST to HTML5.""" _run_cli("html5", "HTML5 documents", argv) -def cli_html5_demo(argv: list[str] | None = None): +def cli_html5_demo(argv: Optional[List[str]] = None): """Cmdline entrypoint for converting MyST to simple HTML5 demonstrations. This is a special case of the HTML5 writer, @@ -438,17 +439,17 @@ def to_html5_demo(inputstring: str, **kwargs) -> str: ) -def cli_latex(argv: list[str] | None = None): +def cli_latex(argv: Optional[List[str]] = None): """Cmdline entrypoint for converting MyST to LaTeX.""" _run_cli("latex", "LaTeX documents", argv) -def cli_xml(argv: list[str] | None = None): +def cli_xml(argv: Optional[List[str]] = None): """Cmdline entrypoint for converting MyST to XML.""" _run_cli("xml", "Docutils-native XML", argv) -def cli_pseudoxml(argv: list[str] | None = None): +def cli_pseudoxml(argv: Optional[List[str]] = None): """Cmdline entrypoint for converting MyST to pseudo-XML.""" _run_cli("pseudoxml", "pseudo-XML", argv) diff --git a/pyproject.toml b/pyproject.toml index 27dcca29..bbdd086b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -105,6 +105,10 @@ exclude = [ extend-select = ["B", "C4", "COM", "FA", "FURB", "I", "ICN", "ISC", "N", "PERF", "PGH", "PIE", "RUF", "SIM", "UP"] extend-ignore = ["ISC001", "RUF005", "RUF012", "COM812"] +[tool.ruff.per-file-ignores] +"myst_parser/parsers/docutils_.py" = ["FA"] +"myst_parser/config/main.py" = ["FA"] + [tool.mypy] show_error_codes = true check_untyped_defs = true From 8d99f4e396201d0c35d355b49ae0127c3970935b Mon Sep 17 00:00:00 2001 From: Chris Sewell <chrisj_sewell@hotmail.com> Date: Tue, 26 Mar 2024 04:12:20 +0100 Subject: [PATCH 9/9] revert add 'flake8-commas (COM) lints --- .github/workflows/docutils_setup.py | 3 +- docs/conf.py | 2 +- docs/live_preview.py | 2 +- myst_parser/_docs.py | 13 +- myst_parser/cli.py | 6 +- myst_parser/config/dc_validators.py | 15 +- myst_parser/inventory.py | 5 +- myst_parser/mdit_to_docutils/base.py | 142 +++++------------- myst_parser/mdit_to_docutils/html_to_nodes.py | 31 ++-- myst_parser/mdit_to_docutils/sphinx_.py | 30 +--- myst_parser/mdit_to_docutils/transforms.py | 24 +-- myst_parser/mocking.py | 35 ++--- myst_parser/parsers/directives.py | 5 +- myst_parser/parsers/docutils_.py | 58 ++----- myst_parser/parsers/mdit.py | 8 +- myst_parser/parsers/options.py | 32 ++-- myst_parser/parsers/parse_html.py | 2 +- myst_parser/parsers/sphinx_.py | 6 +- myst_parser/sphinx_ext/directives.py | 12 +- myst_parser/sphinx_ext/main.py | 4 +- myst_parser/sphinx_ext/mathjax.py | 4 +- myst_parser/sphinx_ext/myst_refs.py | 64 ++------ myst_parser/warnings_.py | 4 +- pyproject.toml | 4 +- tests/test_commonmark/test_commonmark.py | 8 +- tests/test_docutils.py | 5 +- tests/test_inventory.py | 3 +- tests/test_renderers/test_fixtures_sphinx.py | 20 ++- tests/test_renderers/test_myst_config.py | 2 +- tests/test_renderers/test_myst_refs.py | 3 +- tests/test_renderers/test_parse_directives.py | 36 +---- tests/test_sphinx/conftest.py | 2 +- tests/test_sphinx/test_sphinx_builds.py | 14 +- 33 files changed, 171 insertions(+), 433 deletions(-) diff --git a/.github/workflows/docutils_setup.py b/.github/workflows/docutils_setup.py index 122ea2c5..5e371531 100755 --- a/.github/workflows/docutils_setup.py +++ b/.github/workflows/docutils_setup.py @@ -36,8 +36,7 @@ def modify_readme(content: str) -> str: ) content = content.replace("myst-docutils.readthedocs", "myst-parser.readthedocs") content = content.replace( - "readthedocs.org/projects/myst-docutils", - "readthedocs.org/projects/myst-parser", + "readthedocs.org/projects/myst-docutils", "readthedocs.org/projects/myst-parser" ) return content diff --git a/docs/conf.py b/docs/conf.py index 2a294202..58cceb0e 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -67,7 +67,7 @@ { "path": "../myst_parser", "exclude_files": ["_docs.py"], - }, + } ] autodoc2_hidden_objects = ["dunder", "private", "inherited"] autodoc2_replace_annotations = [ diff --git a/docs/live_preview.py b/docs/live_preview.py index 7f2fa0dc..7a3a62b4 100644 --- a/docs/live_preview.py +++ b/docs/live_preview.py @@ -51,7 +51,7 @@ def convert(input_config: str, input_myst: str, writer_name: str) -> dict: "doctitle_xform": False, "sectsubtitle_xform": False, "initial_header_level": 1, - }, + } ) try: output = publish_string( diff --git a/myst_parser/_docs.py b/myst_parser/_docs.py index aaf84e1f..d9c58e6d 100644 --- a/myst_parser/_docs.py +++ b/myst_parser/_docs.py @@ -135,7 +135,7 @@ def run(self): continue if self.options.get("scope") == "local" and field.metadata.get( - "global_only", + "global_only" ): continue @@ -152,7 +152,7 @@ def run(self): f"* - `{name}`", f" - `{ctype}`", f" - {description} (default: `{default}`)", - ], + ] ) count += 1 @@ -202,9 +202,7 @@ def run(self): name = self.arguments[0] # load the directive class klass, _ = directives.directive( - name, - self.state.memo.language, - self.state.document, + name, self.state.memo.language, self.state.document ) if klass is None: LOGGER.warning(f"Directive {name} not found.", line=self.lineno) @@ -404,10 +402,7 @@ class MystLexer(MarkdownLexer): ( r"^(\()([^\n]+)(\)=)(\n)", bygroups( - token.Punctuation, - token.Name.Label, - token.Punctuation, - token.Text, + token.Punctuation, token.Name.Label, token.Punctuation, token.Text ), ), # ::: diff --git a/myst_parser/cli.py b/myst_parser/cli.py index 672d5f8f..a1f546d9 100644 --- a/myst_parser/cli.py +++ b/myst_parser/cli.py @@ -27,11 +27,7 @@ def print_anchors(args=None): help="Output file (default stdout)", ) arg_parser.add_argument( - "-l", - "--level", - type=int, - default=2, - help="Maximum heading level.", + "-l", "--level", type=int, default=2, help="Maximum heading level." ) args = arg_parser.parse_args(args) parser = create_md_parser(MdParserConfig(), RendererHTML) diff --git a/myst_parser/config/dc_validators.py b/myst_parser/config/dc_validators.py index c1161d3c..4e13292d 100644 --- a/myst_parser/config/dc_validators.py +++ b/myst_parser/config/dc_validators.py @@ -36,11 +36,7 @@ def validate_fields(inst: Any) -> None: class ValidatorType(Protocol): def __call__( - self, - inst: Any, - field: dc.Field, - value: Any, - suffix: str = "", + self, inst: Any, field: dc.Field, value: Any, suffix: str = "" ) -> None: ... @@ -67,7 +63,7 @@ def _validator(inst, field, value, suffix=""): if not isinstance(value, type_): raise TypeError( f"'{field.name}{suffix}' must be of type {type_!r} " - f"(got {value!r} that is a {value.__class__!r}).", + f"(got {value!r} that is a {value.__class__!r})." ) return _validator @@ -98,7 +94,7 @@ def is_callable(inst, field, value, suffix=""): if not callable(value): raise TypeError( f"'{field.name}{suffix}' must be callable " - f"(got {value!r} that is a {value.__class__!r}).", + f"(got {value!r} that is a {value.__class__!r})." ) @@ -119,15 +115,14 @@ def _validator(inst, field, value, suffix=""): if not in_options: raise ValueError( - f"'{field.name}{suffix}' must be in {options!r} (got {value!r})", + f"'{field.name}{suffix}' must be in {options!r} (got {value!r})" ) return _validator def deep_iterable( - member_validator: ValidatorType, - iterable_validator: ValidatorType | None = None, + member_validator: ValidatorType, iterable_validator: ValidatorType | None = None ) -> ValidatorType: """ A validator that performs deep validation of an iterable. diff --git a/myst_parser/inventory.py b/myst_parser/inventory.py index 183f4b44..bdcd8bb0 100644 --- a/myst_parser/inventory.py +++ b/myst_parser/inventory.py @@ -401,10 +401,7 @@ def filter_string( def fetch_inventory( - uri: str, - *, - timeout: None | float = None, - base_url: None | str = None, + uri: str, *, timeout: None | float = None, base_url: None | str = None ) -> InventoryType: """Fetch an inventory from a URL or local path.""" if uri.startswith(("http://", "https://")): diff --git a/myst_parser/mdit_to_docutils/base.py b/myst_parser/mdit_to_docutils/base.py index 665fbcd4..0817f5ca 100644 --- a/myst_parser/mdit_to_docutils/base.py +++ b/myst_parser/mdit_to_docutils/base.py @@ -82,7 +82,7 @@ def make_document(source_path="notset", parser_cls=RSTParser) -> nodes.document: and documents that specify schemes must do so with lowercase letters. """ REGEX_URI_TEMPLATE = re.compile( - r"{{\s*(uri|scheme|netloc|path|params|query|fragment)\s*}}", + r"{{\s*(uri|scheme|netloc|path|params|query|fragment)\s*}}" ) REGEX_DIRECTIVE_START = re.compile(r"^[\s]{0,3}([`]{3,10}|[~]{3,10}|[:]{3,10})\{") @@ -129,16 +129,14 @@ def __getattr__(self, name: str): "_level_to_section", ): raise AttributeError( - f"'{name}' attribute is not available until setup_render() is called", + f"'{name}' attribute is not available until setup_render() is called" ) raise AttributeError( - f"'{type(self).__name__}' object has no attribute '{name}'", + f"'{type(self).__name__}' object has no attribute '{name}'" ) def setup_render( - self, - options: dict[str, Any], - env: MutableMapping[str, Any], + self, options: dict[str, Any], env: MutableMapping[str, Any] ) -> None: """Setup the renderer with per render variables.""" self.md_env = env @@ -150,12 +148,12 @@ def setup_render( # note there are actually two possible language modules: # one from docutils.languages, and one from docutils.parsers.rst.languages self.language_module_rst: ModuleType = get_language_rst( - self.document.settings.language_code, + self.document.settings.language_code ) self._heading_offset: int = 0 # a mapping of heading levels to its currently associated node self._level_to_section: dict[int, nodes.document | nodes.section] = { - 0: self.document, + 0: self.document } # mapping of section slug to (line, id, implicit_text) self._heading_slugs: dict[str, tuple[int | None, str, str]] = {} @@ -230,10 +228,7 @@ def _render_tokens(self, tokens: list[Token]) -> None: ) def render( - self, - tokens: Sequence[Token], - options, - md_env: MutableMapping[str, Any], + self, tokens: Sequence[Token], options, md_env: MutableMapping[str, Any] ) -> nodes.document: """Run the render on a token stream. @@ -256,7 +251,7 @@ def _render_initialise(self) -> None: document=self.document, line=0, reporter=self.reporter, - ), + ) ) def _render_finalise(self) -> None: @@ -323,14 +318,12 @@ def _render_finalise(self) -> None: if value is None: continue substitution_node = nodes.substitution_definition( - str(value), - nodes.Text(str(value)), + str(value), nodes.Text(str(value)) ) substitution_node.source = self.document["source"] substitution_node["names"].append(f"wordcount-{key}") self.document.note_substitution_def( - substitution_node, - f"wordcount-{key}", + substitution_node, f"wordcount-{key}" ) def nested_render_text( @@ -385,9 +378,7 @@ def _restore(): @contextmanager def current_node_context( - self, - node: nodes.Element, - append: bool = False, + self, node: nodes.Element, append: bool = False ) -> Iterator: """Context manager for temporarily setting the current node.""" if append: @@ -417,9 +408,7 @@ def add_line_and_source_path(self, node, token: SyntaxTreeNode) -> None: node.source = self.document["source"] def add_line_and_source_path_r( - self, - nodes_: list[nodes.Element], - token: SyntaxTreeNode, + self, nodes_: list[nodes.Element], token: SyntaxTreeNode ) -> None: """Copy the line number and document source path to the docutils nodes, and recursively to all descendants. @@ -673,8 +662,7 @@ def create_highlighted_code_block( if isinstance(emphasize_lines, str): try: emphasize_lines = self._parse_linenos( - emphasize_lines, - len(text.splitlines()), + emphasize_lines, len(text.splitlines()) ) except ValueError as err: self.create_warning( @@ -689,8 +677,7 @@ def create_highlighted_code_block( node["highlight_args"]["hl_lines"] = emphasize_lines else: node = node_cls( - text, - classes=["code"] + ([lexer_name] if lexer_name else []), + text, classes=["code"] + ([lexer_name] if lexer_name else []) ) try: lex_tokens = Lexer( @@ -711,9 +698,7 @@ def create_highlighted_code_block( if number_lines: lex_tokens = NumberLines( - lex_tokens, - lineno_start, - lineno_start + len(text.splitlines()), + lex_tokens, lineno_start, lineno_start + len(text.splitlines()) ) for classes, value in lex_tokens: @@ -771,18 +756,14 @@ def render_fence(self, token: SyntaxTreeNode) -> None: if "id" in options: options["name"] = options.pop("id") return self.render_directive( - token, - name, - arguments, - additional_options=options, + token, name, arguments, additional_options=options ) if not name and self.sphinx_env is not None: # use the current highlight setting, via the ``highlight`` directive, # or ``highlight_language`` configuration. name = self.sphinx_env.temp_data.get( - "highlight_language", - self.sphinx_env.config.highlight_language, + "highlight_language", self.sphinx_env.config.highlight_language ) lineno_start = 1 @@ -964,9 +945,7 @@ def render_link(self, token: SyntaxTreeNode) -> None: return self.render_link_unknown(token) def render_link_url( - self, - token: SyntaxTreeNode, - conversion: None | UrlSchemeType = None, + self, token: SyntaxTreeNode, conversion: None | UrlSchemeType = None ) -> None: """Render link token (including autolink and linkify), where the link has been identified as an external URL. @@ -974,10 +953,7 @@ def render_link_url( ref_node = nodes.reference() self.add_line_and_source_path(ref_node, token) self.copy_attributes( - token, - ref_node, - ("class", "id", "reftitle"), - aliases={"title": "reftitle"}, + token, ref_node, ("class", "id", "reftitle"), aliases={"title": "reftitle"} ) uri = cast(str, token.attrGet("href") or "") implicit_text: str | None = None @@ -1063,10 +1039,7 @@ def render_link_anchor(self, token: SyntaxTreeNode, target: str) -> None: ref_node["id_link"] = True ref_node["refuri"] = self.md.normalizeLinkText(target) self.copy_attributes( - token, - ref_node, - ("class", "id", "reftitle"), - aliases={"title": "reftitle"}, + token, ref_node, ("class", "id", "reftitle"), aliases={"title": "reftitle"} ) self.current_node.append(ref_node) if token.info != "auto": @@ -1087,10 +1060,7 @@ def render_link_unknown(self, token: SyntaxTreeNode) -> None: ref_node = nodes.reference() self.add_line_and_source_path(ref_node, token) self.copy_attributes( - token, - ref_node, - ("class", "id", "reftitle"), - aliases={"title": "reftitle"}, + token, ref_node, ("class", "id", "reftitle"), aliases={"title": "reftitle"} ) ref_node["refname"] = cast(str, token.attrGet("href") or "") self.document.note_refname(ref_node) @@ -1128,10 +1098,7 @@ def render_link_inventory(self, token: SyntaxTreeNode) -> None: # find the matches matches = self.get_inventory_matches( - target=target, - invs=invs, - domains=domains, - otypes=otypes, + target=target, invs=invs, domains=domains, otypes=otypes ) # warn for 0 or >1 matches @@ -1151,7 +1118,7 @@ def render_link_inventory(self, token: SyntaxTreeNode) -> None: [ inventory.filter_string(m.inv, m.domain, m.otype, m.name) for m in matches[:show_num] - ], + ] ) if len(matches) > show_num: matches_str += ", ..." @@ -1166,17 +1133,11 @@ def render_link_inventory(self, token: SyntaxTreeNode) -> None: match = matches[0] ref_node = nodes.reference("", "", internal=False) ref_node["inv_match"] = inventory.filter_string( - match.inv, - match.domain, - match.otype, - match.name, + match.inv, match.domain, match.otype, match.name ) self.add_line_and_source_path(ref_node, token) self.copy_attributes( - token, - ref_node, - ("class", "id", "reftitle"), - aliases={"title": "reftitle"}, + token, ref_node, ("class", "id", "reftitle"), aliases={"title": "reftitle"} ) ref_node["refuri"] = ( posixpath.join(match.base_url, match.loc) if match.base_url else match.loc @@ -1226,7 +1187,7 @@ def get_inventory_matches( domains=domains, otypes=otypes, targets=target, - ), + ) ) def render_html_inline(self, token: SyntaxTreeNode) -> None: @@ -1242,8 +1203,7 @@ def render_image(self, token: SyntaxTreeNode) -> None: destination = cast(str, token.attrGet("src") or "") if self.md_env.get( - "relative-images", - None, + "relative-images", None ) is not None and not REGEX_SCHEME.match(destination): # make the path relative to an "including" document # this is set when using the `relative-images` option of the MyST `include` directive @@ -1251,7 +1211,7 @@ def render_image(self, token: SyntaxTreeNode) -> None: os.path.join( self.md_env.get("relative-images", ""), os.path.normpath(destination), - ), + ) ) img_node["uri"] = destination @@ -1316,8 +1276,7 @@ def render_front_matter(self, token: SyntaxTreeNode) -> None: } if fields: field_list = self.dict_to_fm_field_list( - fields, - language_code=self.document.settings.language_code, + fields, language_code=self.document.settings.language_code ) self.current_node.append(field_list) @@ -1325,10 +1284,7 @@ def render_front_matter(self, token: SyntaxTreeNode) -> None: 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, + self, data: dict[str, Any], language_code: str, line: int = 0 ) -> nodes.field_list: """Render each key/val pair as a docutils ``field_node``. @@ -1438,7 +1394,7 @@ def render_table_row(self, token: SyntaxTreeNode) -> None: for child in token.children or []: entry = nodes.entry() para = nodes.paragraph( - child.children[0].content if child.children else "", + child.children[0].content if child.children else "" ) style = child.attrGet("style") # i.e. the alignment when using e.g. :-- if style and style in ( @@ -1448,8 +1404,7 @@ def render_table_row(self, token: SyntaxTreeNode) -> None: ): entry["classes"].append(f"text-{cast(str, style).split(':')[1]}") with self.current_node_context( - entry, - append=True, + entry, append=True ), self.current_node_context(para, append=True): self.render_children(child) @@ -1504,10 +1459,7 @@ def render_amsmath(self, token: SyntaxTreeNode) -> None: # note docutils does not currently support the nowrap attribute # or equation numbering, so this is overridden in the sphinx renderer node = nodes.math_block( - token.content, - token.content, - nowrap=True, - classes=["amsmath"], + token.content, token.content, nowrap=True, classes=["amsmath"] ) if token.meta["numbered"] != "*": node["numbered"] = True @@ -1573,10 +1525,7 @@ def render_myst_role(self, token: SyntaxTreeNode) -> None: rawsource = f":{name}:`{token.content}`" lineno = token_line(token) if token.map else 0 role_func, messages = roles.role( - name, - self.language_module_rst, - lineno, - self.reporter, + name, self.language_module_rst, lineno, self.reporter ) if not role_func: self.create_warning( @@ -1631,7 +1580,7 @@ def render_dl(self, token: SyntaxTreeNode) -> None: self.add_line_and_source_path(item, child) with self.current_node_context(item, append=True): term = nodes.term( - child.children[0].content if child.children else "", + child.children[0].content if child.children else "" ) self.add_line_and_source_path(term, child) with self.current_node_context(term): @@ -1776,9 +1725,7 @@ def run_directive( # get directive class output: tuple[Directive | None, list] = directives.directive( - name, - self.language_module_rst, - self.document, + name, self.language_module_rst, self.document ) directive_class, messages = output if not directive_class: @@ -1856,9 +1803,7 @@ def run_directive( result = directive_instance.run() except DirectiveError as error: msg_node = self.reporter.system_message( - error.level, - error.msg, - line=position, + error.level, error.msg, line=position ) msg_node += nodes.literal_block(content, content) result = [msg_node] @@ -1871,13 +1816,11 @@ def run_directive( return [error_msg] assert isinstance( - result, - list, + result, list ), f'Directive "{name}" must return a list of nodes.' for i in range(len(result)): assert isinstance( - result[i], - nodes.Node, + result[i], nodes.Node ), f'Directive "{name}" returned non-Node object (index {i}): {result[i]}' return result @@ -1914,7 +1857,7 @@ def render_substitution(self, token: SyntaxTreeNode, inline: bool) -> None: # try rendering try: rendered = env.from_string(f"{{{{{token.content}}}}}").render( - variable_context, + variable_context ) except Exception as error: self.create_warning( @@ -1959,10 +1902,7 @@ def render_substitution(self, token: SyntaxTreeNode, inline: bool) -> None: def html_meta_to_nodes( - data: dict[str, Any], - document: nodes.document, - line: int, - reporter: Reporter, + data: dict[str, Any], document: nodes.document, line: int, reporter: Reporter ) -> list[nodes.pending | nodes.system_message]: """Replicate the `meta` directive, by converting a dictionary to a list of pending meta nodes diff --git a/myst_parser/mdit_to_docutils/html_to_nodes.py b/myst_parser/mdit_to_docutils/html_to_nodes.py index 38a6bc30..e327bbe8 100644 --- a/myst_parser/mdit_to_docutils/html_to_nodes.py +++ b/myst_parser/mdit_to_docutils/html_to_nodes.py @@ -14,10 +14,7 @@ def make_error( - document: nodes.document, - error_msg: str, - text: str, - line_number: int, + document: nodes.document, error_msg: str, text: str, line_number: int ) -> nodes.system_message: return document.reporter.error( error_msg, @@ -46,9 +43,7 @@ def default_html(text: str, source: str, line_number: int) -> list[nodes.Element def html_to_nodes( - text: str, - line_number: int, - renderer: DocutilsRenderer, + text: str, line_number: int, renderer: DocutilsRenderer ) -> list[nodes.Element]: """Convert HTML to docutils nodes.""" if renderer.md_config.gfm_only: @@ -64,14 +59,10 @@ def html_to_nodes( root = tokenize_html(text).strip(inplace=True, recurse=False) except Exception: msg_node = renderer.create_warning( - "HTML could not be parsed", - MystWarnings.HTML_PARSE, - line=line_number, + "HTML could not be parsed", MystWarnings.HTML_PARSE, line=line_number ) return ([msg_node] if msg_node else []) + default_html( - text, - renderer.document["source"], - line_number, + text, renderer.document["source"], line_number ) if len(root) < 1: @@ -95,9 +86,8 @@ def html_to_nodes( if "src" not in child.attrs: return [ renderer.reporter.error( - "<img> missing 'src' attribute", - line=line_number, - ), + "<img> missing 'src' attribute", line=line_number + ) ] content = "\n".join( f":{k}: {v}" @@ -106,11 +96,8 @@ def html_to_nodes( ) nodes_list.extend( renderer.run_directive( - "image", - child.attrs["src"], - content, - line_number, - ), + "image", child.attrs["src"], content, line_number + ) ) else: @@ -145,7 +132,7 @@ def html_to_nodes( ) nodes_list.extend( - renderer.run_directive("admonition", title, content, line_number), + renderer.run_directive("admonition", title, content, line_number) ) return nodes_list diff --git a/myst_parser/mdit_to_docutils/sphinx_.py b/myst_parser/mdit_to_docutils/sphinx_.py index 46ce0536..71942aff 100644 --- a/myst_parser/mdit_to_docutils/sphinx_.py +++ b/myst_parser/mdit_to_docutils/sphinx_.py @@ -65,8 +65,7 @@ def _handle_relative_docs(self, destination: str) -> str: if relative_include is not None and destination.startswith(relative_include[0]): source_dir, include_dir = relative_include[1:] destination = os.path.relpath( - os.path.join(include_dir, os.path.normpath(destination)), - source_dir, + os.path.join(include_dir, os.path.normpath(destination)), source_dir ) return destination @@ -147,24 +146,17 @@ def render_link_unknown(self, token: SyntaxTreeNode) -> None: docname = self.sphinx_env.path2doc(str(potential_path)) if docname: wrap_node = addnodes.pending_xref( - refdomain="doc", - reftarget=docname, - reftargetid=path_id, - **kwargs, + refdomain="doc", reftarget=docname, reftargetid=path_id, **kwargs ) classes = ["xref", "myst"] else: wrap_node = addnodes.download_reference( - refdomain=None, - reftarget=path_dest, - **kwargs, + refdomain=None, reftarget=path_dest, **kwargs ) classes = ["xref", "download", "myst"] else: wrap_node = addnodes.pending_xref( - refdomain=None, - reftarget=destination, - **kwargs, + refdomain=None, reftarget=destination, **kwargs ) classes = ["xref", "myst"] @@ -185,7 +177,7 @@ def get_inventory_matches( domains=domains, otypes=otypes, targets=target, - ), + ) ) def render_math_block_label(self, token: SyntaxTreeNode) -> None: @@ -193,11 +185,7 @@ def render_math_block_label(self, token: SyntaxTreeNode) -> None: label = token.info content = token.content node = nodes.math_block( - content, - content, - nowrap=False, - number=None, - label=label, + content, content, nowrap=False, number=None, label=label ) target = self.add_math_target(node) self.add_line_and_source_path(target, token) @@ -231,11 +219,7 @@ def render_amsmath(self, token: SyntaxTreeNode) -> None: self.current_node.append(target) else: node = nodes.math_block( - content, - content, - nowrap=True, - number=None, - classes=["amsmath"], + content, content, nowrap=True, number=None, classes=["amsmath"] ) self.add_line_and_source_path(node, token) self.current_node.append(node) diff --git a/myst_parser/mdit_to_docutils/transforms.py b/myst_parser/mdit_to_docutils/transforms.py index 68b1e233..3cf03bb8 100644 --- a/myst_parser/mdit_to_docutils/transforms.py +++ b/myst_parser/mdit_to_docutils/transforms.py @@ -22,9 +22,7 @@ def apply(self, **kwargs: t.Any) -> None: # gather the implicit heading slugs # name -> (line, slug, title) slugs: dict[str, tuple[int, str, str]] = getattr( - self.document, - "myst_slugs", - {}, + self.document, "myst_slugs", {} ) # gather explicit references @@ -91,15 +89,11 @@ def apply(self, **kwargs: t.Any) -> None: refnode["refid"] = ref_id if not refnode.children and implicit_title: refnode += nodes.inline( - implicit_title, - implicit_title, - classes=["std", "std-ref"], + implicit_title, implicit_title, classes=["std", "std-ref"] ) elif not refnode.children: refnode += nodes.inline( - "#" + target, - "#" + target, - classes=["std", "std-ref"], + "#" + target, "#" + target, classes=["std", "std-ref"] ) continue @@ -109,9 +103,7 @@ def apply(self, **kwargs: t.Any) -> None: refnode["refid"] = sect_id if not refnode.children and implicit_title: refnode += nodes.inline( - implicit_title, - implicit_title, - classes=["std", "std-ref"], + implicit_title, implicit_title, classes=["std", "std-ref"] ) continue @@ -127,9 +119,7 @@ def apply(self, **kwargs: t.Any) -> None: refexplicit=bool(refnode.children), ) inner_node = nodes.inline( - "", - "", - classes=["xref", "myst"] + refnode["classes"], + "", "", classes=["xref", "myst"] + refnode["classes"] ) for attr in ("ids", "names", "dupnames"): inner_node[attr] = refnode[attr] @@ -151,7 +141,5 @@ def apply(self, **kwargs: t.Any) -> None: refnode["refid"] = normalizeLink(target) if not refnode.children: refnode += nodes.inline( - "#" + target, - "#" + target, - classes=["std", "std-ref"], + "#" + target, "#" + target, classes=["std", "std-ref"] ) diff --git a/myst_parser/mocking.py b/myst_parser/mocking.py index 52822588..3fedbaed 100644 --- a/myst_parser/mocking.py +++ b/myst_parser/mocking.py @@ -50,10 +50,7 @@ def __init__(self, renderer: DocutilsRenderer): self.rfc_url = "rfc%d.html" def problematic( - self, - text: str, - rawsource: str, - message: nodes.system_message, + self, text: str, rawsource: str, message: nodes.system_message ) -> nodes.problematic: """Record a system message from parsing.""" msgid = self.document.set_id(message, self.parent) @@ -63,11 +60,7 @@ def problematic( return problematic def parse( - self, - text: str, - lineno: int, - memo: Any, - parent: nodes.Node, + self, text: str, lineno: int, memo: Any, parent: nodes.Node ) -> tuple[list[nodes.Node], list[nodes.system_message]]: """Parse the text and return a list of nodes.""" # note the only place this is normally called, @@ -197,9 +190,7 @@ def parse_target(self, block, block_text, lineno: int): return "refuri", unescape(reference) def inline_text( - self, - text: str, - lineno: int, + self, text: str, lineno: int ) -> tuple[list[nodes.Element], list[nodes.Element]]: """Parse text with only inline rules. @@ -382,13 +373,11 @@ def run(self) -> list[nodes.Element]: file_content = path.read_text(encoding=encoding, errors=error_handler) except FileNotFoundError as error: raise DirectiveError( - 4, - f'Directive "{self.name}": file not found: {str(path)!r}', + 4, f'Directive "{self.name}": file not found: {str(path)!r}' ) from error except Exception as error: raise DirectiveError( - 4, - f'Directive "{self.name}": error reading file: {path}\n{error}.', + 4, f'Directive "{self.name}": error reading file: {path}\n{error}.' ) from error # get required section of text @@ -405,9 +394,7 @@ def run(self) -> list[nodes.Element]: raise DirectiveError( 4, 'Directive "{}"; option "{}": text not found "{}".'.format( - self.name, - split_on_type, - split_on, + self.name, split_on_type, split_on ), ) if split_on_type == "start-after": @@ -418,9 +405,7 @@ def run(self) -> list[nodes.Element]: if "literal" in self.options: literal_block = nodes.literal_block( - file_content, - source=str(path), - classes=self.options.get("class", []), + file_content, source=str(path), classes=self.options.get("class", []) ) literal_block.line = 1 # TODO don;t think this should be 1? self.add_name(literal_block) @@ -429,8 +414,7 @@ def run(self) -> list[nodes.Element]: startline = int(self.options["number-lines"] or 1) except ValueError as err: raise DirectiveError( - 3, - ":number-lines: with non-integer start value", + 3, ":number-lines: with non-integer start value" ) from err endline = startline + len(file_content.splitlines()) if file_content.endswith("\n"): @@ -472,8 +456,7 @@ def run(self) -> list[nodes.Element]: self.renderer.reporter.get_source_and_line = lambda li: (str(path), li) if "relative-images" in self.options: self.renderer.md_env["relative-images"] = os.path.relpath( - path.parent, - source_dir, + path.parent, source_dir ) if "relative-docs" in self.options: self.renderer.md_env["relative-docs"] = ( diff --git a/myst_parser/parsers/directives.py b/myst_parser/parsers/directives.py index c2fda4b9..e4cfee8f 100644 --- a/myst_parser/parsers/directives.py +++ b/myst_parser/parsers/directives.py @@ -299,8 +299,7 @@ def _parse_directive_options( def parse_directive_arguments( - directive_cls: type[Directive], - arg_text: str, + directive_cls: type[Directive], arg_text: str ) -> list[str]: """Parse (and validate) the directive argument section.""" required = directive_cls.required_arguments @@ -314,6 +313,6 @@ def parse_directive_arguments( else: raise MarkupError( f"maximum {required + optional} argument(s) allowed, " - f"{len(arguments)} supplied", + f"{len(arguments)} supplied" ) return arguments diff --git a/myst_parser/parsers/docutils_.py b/myst_parser/parsers/docutils_.py index 52003c9b..89a78b65 100644 --- a/myst_parser/parsers/docutils_.py +++ b/myst_parser/parsers/docutils_.py @@ -36,30 +36,18 @@ def _validate_int( - setting, - value, - option_parser, - config_parser=None, - config_section=None, + setting, value, option_parser, config_parser=None, config_section=None ) -> int: """Validate an integer setting.""" return int(value) def _validate_comma_separated_set( - setting, - value, - option_parser, - config_parser=None, - config_section=None, + setting, value, option_parser, config_parser=None, config_section=None ) -> Set[str]: """Validate an integer setting.""" value = frontend.validate_comma_separated_list( - setting, - value, - option_parser, - config_parser, - config_section, + setting, value, option_parser, config_parser, config_section ) return set(value) @@ -68,22 +56,14 @@ def _create_validate_tuple(length: int) -> Callable[..., Tuple[str, ...]]: """Create a validator for a tuple of length `length`.""" def _validate( - setting, - value, - option_parser, - config_parser=None, - config_section=None, + setting, value, option_parser, config_parser=None, config_section=None ): string_list = frontend.validate_comma_separated_list( - setting, - value, - option_parser, - config_parser, - config_section, + setting, value, option_parser, config_parser, config_section ) if len(string_list) != length: raise ValueError( - f"Expecting {length} items in {setting}, got {len(string_list)}.", + f"Expecting {length} items in {setting}, got {len(string_list)}." ) return tuple(string_list) @@ -109,11 +89,7 @@ def _create_validate_yaml(field: Field): """Create a deserializer/validator for a json setting.""" def _validate_yaml( - setting, - value, - option_parser, - config_parser=None, - config_section=None, + setting, value, option_parser, config_parser=None, config_section=None ): """Check/normalize a key-value pair setting. @@ -131,11 +107,7 @@ def _validate_yaml( def _validate_url_schemes( - setting, - value, - option_parser, - config_parser=None, - config_section=None, + setting, value, option_parser, config_parser=None, config_section=None ): """Validate a url_schemes setting. @@ -213,14 +185,12 @@ def _attr_to_optparse_option(at: Field, default: Any) -> Tuple[dict, str]: "validator": _create_validate_yaml(at), }, str(default) if default else "" raise AssertionError( - f"Configuration option {at.name} not set up for use in docutils.conf.", + f"Configuration option {at.name} not set up for use in docutils.conf." ) def attr_to_optparse_option( - attribute: Field, - default: Any, - prefix: str = "myst_", + attribute: Field, default: Any, prefix: str = "myst_" ) -> Tuple[str, List[str], Dict[str, Any]]: """Convert an ``MdParserConfig`` attribute into a Docutils setting tuple. @@ -306,7 +276,7 @@ def parse(self, inputstring: str, document: nodes.document) -> None: if len(line) > document.settings.line_length_limit: error = document.reporter.error( f"Line {i+1} exceeds the line-length-limit:" - f" {document.settings.line_length_limit}.", + f" {document.settings.line_length_limit}." ) document.append(error) return @@ -335,11 +305,7 @@ def parse(self, inputstring: str, document: nodes.document) -> None: else: if topmatter: warning = lambda wtype, msg: create_warning( # noqa: E731 - document, - msg, - wtype, - line=1, - append_to=document, + document, msg, wtype, line=1, append_to=document ) config = merge_file_level(config, topmatter, warning) diff --git a/myst_parser/parsers/mdit.py b/myst_parser/parsers/mdit.py index fd1c7e9b..110baecb 100644 --- a/myst_parser/parsers/mdit.py +++ b/myst_parser/parsers/mdit.py @@ -25,8 +25,7 @@ def create_md_parser( - config: MdParserConfig, - renderer: Callable[[MarkdownIt], RendererProtocol], + config: MdParserConfig, renderer: Callable[[MarkdownIt], RendererProtocol] ) -> MarkdownIt: """Return a Markdown parser with the required MyST configuration.""" @@ -36,8 +35,7 @@ def create_md_parser( if config.commonmark_only: # see https://spec.commonmark.org/ md = MarkdownIt("commonmark", renderer_cls=renderer).use( - wordcount_plugin, - per_minute=config.words_per_minute, + wordcount_plugin, per_minute=config.words_per_minute ) md.options.update({"myst_config": config}) return md @@ -122,7 +120,7 @@ def create_md_parser( "typographer": typographer, "linkify": "linkify" in config.enable_extensions, "myst_config": config, - }, + } ) return md diff --git a/myst_parser/parsers/options.py b/myst_parser/parsers/options.py index 4903b841..15da7327 100644 --- a/myst_parser/parsers/options.py +++ b/myst_parser/parsers/options.py @@ -151,21 +151,19 @@ def __str__(self) -> str: or self.context_mark.column != self.problem_mark.column ): lines.append( - f"at line {self.context_mark.line}, column {self.context_mark.column}", + f"at line {self.context_mark.line}, column {self.context_mark.column}" ) if self.problem is not None: lines.append(self.problem) if self.problem_mark is not None: lines.append( - f"at line {self.problem_mark.line}, column {self.problem_mark.column}", + f"at line {self.problem_mark.line}, column {self.problem_mark.column}" ) return "\n".join(lines) def to_items( - text: str, - line_offset: int = 0, - column_offset: int = 0, + text: str, line_offset: int = 0, column_offset: int = 0 ) -> Iterable[tuple[str, str]]: """Parse a directive option block into (key, value) tuples. @@ -180,9 +178,7 @@ def to_items( def to_tokens( - text: str, - line_offset: int = 0, - column_offset: int = 0, + text: str, line_offset: int = 0, column_offset: int = 0 ) -> Iterable[tuple[KeyToken, ValueToken | None]]: """Parse a directive option, and yield key/value token pairs. @@ -222,8 +218,7 @@ def tokenize(text: str) -> Iterable[Token]: if not stream.column == 0: raise TokenizeError( - "expected key to start at column 0", - stream.get_position(), + "expected key to start at column 0", stream.get_position() ) # find key @@ -278,8 +273,7 @@ def _scan_to_next_token(stream: StreamBuffer) -> None: def _scan_plain_scalar( - stream: StreamBuffer, - is_key: bool = False, + stream: StreamBuffer, is_key: bool = False ) -> KeyToken | ValueToken: chunks = [] start_mark = stream.get_position() @@ -365,9 +359,7 @@ def _scan_line_break(stream: StreamBuffer) -> str: def _scan_flow_scalar( - stream: StreamBuffer, - style: Literal["'", '"'], - is_key: bool = False, + stream: StreamBuffer, style: Literal["'", '"'], is_key: bool = False ) -> KeyToken | ValueToken: double = style == '"' chunks = [] @@ -388,9 +380,7 @@ def _scan_flow_scalar( def _scan_flow_scalar_non_spaces( - stream: StreamBuffer, - double: bool, - start_mark: Position, + stream: StreamBuffer, double: bool, start_mark: Position ) -> list[str]: chunks = [] while True: @@ -540,8 +530,7 @@ def _scan_block_scalar(stream: StreamBuffer, style: Literal["|", ">"]) -> ValueT def _scan_block_scalar_indicators( - stream: StreamBuffer, - start_mark: Position, + stream: StreamBuffer, start_mark: Position ) -> tuple[bool | None, int | None]: chomping = None increment = None @@ -620,8 +609,7 @@ def _scan_block_scalar_indentation( def _scan_block_scalar_breaks( - stream: StreamBuffer, - indent: int, + stream: StreamBuffer, indent: int ) -> tuple[list[str], Position]: chunks = [] end_mark = stream.get_position() diff --git a/myst_parser/parsers/parse_html.py b/myst_parser/parsers/parse_html.py index d7bfadd4..971c3a28 100644 --- a/myst_parser/parsers/parse_html.py +++ b/myst_parser/parsers/parse_html.py @@ -159,7 +159,7 @@ def strip(self, inplace: bool = False, recurse: bool = False) -> Element: e for e in element.children if not (isinstance(e, Data) and e.data.strip() == "") - ], + ] ) if recurse: for child in element: diff --git a/myst_parser/parsers/sphinx_.py b/myst_parser/parsers/sphinx_.py index 950f9cfe..e62eba3c 100644 --- a/myst_parser/parsers/sphinx_.py +++ b/myst_parser/parsers/sphinx_.py @@ -65,11 +65,7 @@ def parse(self, inputstring: str, document: nodes.document) -> None: else: if topmatter: warning = lambda wtype, msg: create_warning( # noqa: E731 - document, - msg, - wtype, - line=1, - append_to=document, + document, msg, wtype, line=1, append_to=document ) config = merge_file_level(config, topmatter, warning) diff --git a/myst_parser/sphinx_ext/directives.py b/myst_parser/sphinx_ext/directives.py index 8f3b1356..ebd88295 100644 --- a/myst_parser/sphinx_ext/directives.py +++ b/myst_parser/sphinx_ext/directives.py @@ -83,8 +83,8 @@ def run(self) -> list[nodes.Node]: return [ self.figure_error( "content should be one image, " - "followed by a single paragraph caption", - ), + "followed by a single paragraph caption" + ) ] image_node, caption_para = node.children @@ -95,16 +95,16 @@ def run(self) -> list[nodes.Node]: return [ self.figure_error( "content should be one image (not found), " - "followed by single paragraph caption", - ), + "followed by single paragraph caption" + ) ] if not isinstance(caption_para, nodes.paragraph): return [ self.figure_error( "content should be one image, " - "followed by single paragraph caption (not found)", - ), + "followed by single paragraph caption (not found)" + ) ] caption_node = nodes.caption(caption_para.rawsource, "", *caption_para.children) diff --git a/myst_parser/sphinx_ext/main.py b/myst_parser/sphinx_ext/main.py index 398fa342..809056e1 100644 --- a/myst_parser/sphinx_ext/main.py +++ b/myst_parser/sphinx_ext/main.py @@ -42,9 +42,7 @@ def setup_sphinx(app: Sphinx, load_parser: bool = False) -> None: # override only the html writer visit methods for rubric, to use the "level" attribute # this allows for nested headers to be correctly rendered app.add_node( - nodes.rubric, - override=True, - html=(visit_rubric_html, depart_rubric_html), + nodes.rubric, override=True, html=(visit_rubric_html, depart_rubric_html) ) # override only the html writer visit methods for container, # to remove the "container" class for divs diff --git a/myst_parser/sphinx_ext/mathjax.py b/myst_parser/sphinx_ext/mathjax.py index d569087d..e430c3ba 100644 --- a/myst_parser/sphinx_ext/mathjax.py +++ b/myst_parser/sphinx_ext/mathjax.py @@ -31,7 +31,7 @@ def log_override_warning(app: Sphinx, version: int, current: str, new: str) -> N logger.warning( f"`{config_name}` is being overridden by myst-parser: '{current}' -> '{new}'. " "Set `suppress_warnings=['myst.mathjax']` to ignore this warning, or " - "`myst_update_mathjax=False` if this is undesirable.", + "`myst_update_mathjax=False` if this is undesirable." ) @@ -101,7 +101,7 @@ def html_visit_displaymath(self: HTMLTranslator, node: nodes.math_block) -> None """ if "amsmath" in node.get("classes", []): self.body.append( - self.starttag(node, "div", CLASS="math notranslate nohighlight amsmath"), + self.starttag(node, "div", CLASS="math notranslate nohighlight amsmath") ) if node["number"]: number = get_node_equation_number(self, node) diff --git a/myst_parser/sphinx_ext/myst_refs.py b/myst_parser/sphinx_ext/myst_refs.py index 1f605953..74edcb37 100644 --- a/myst_parser/sphinx_ext/myst_refs.py +++ b/myst_parser/sphinx_ext/myst_refs.py @@ -36,11 +36,7 @@ class MystReferenceResolver(ReferencesResolver): default_priority = 9 # higher priority than ReferencesResolver (10) def log_warning( - self, - target: None | str, - msg: str, - subtype: MystWarnings, - **kwargs: Any, + self, target: None | str, msg: str, subtype: MystWarnings, **kwargs: Any ): """Log a warning, with a myst type and specific subtype.""" @@ -97,10 +93,7 @@ def run(self, **kwargs: Any) -> None: # multiple domains resolved the reference try: newnode = self.resolve_myst_ref_any( - refdoc, - node, - contnode, - search_domains, + refdoc, node, contnode, search_domains ) except NoUri: newnode = contnode @@ -108,10 +101,7 @@ def run(self, **kwargs: Any) -> None: # If no local domain could resolve the reference, try to # resolve it as an inter-sphinx reference newnode = self._resolve_myst_ref_intersphinx( - node, - contnode, - target, - search_domains, + node, contnode, target, search_domains ) if newnode is None: # if still not resolved, log a warning, @@ -184,19 +174,13 @@ def resolve_myst_ref_doc(self, node: pending_xref): innernode.extend(node[0].children) else: innernode = nodes.inline( - implicit_text, - implicit_text, - classes=inner_classes, + implicit_text, implicit_text, classes=inner_classes ) assert self.app.builder try: ref_node = make_refnode( - self.app.builder, - from_docname, - ref_docname, - targetid, - innernode, + self.app.builder, from_docname, ref_docname, targetid, innernode ) except NoUri: ref_node = innernode @@ -245,11 +229,7 @@ def resolve_myst_ref_any( docname, labelid = stddomain.objects[key] domain_role = "std:" + stddomain.role_for_objtype(objtype) ref_node = make_refnode( - self.app.builder, - refdoc, - docname, - labelid, - contnode, + self.app.builder, refdoc, docname, labelid, contnode ) results.append((domain_role, ref_node)) @@ -262,13 +242,8 @@ def resolve_myst_ref_any( try: results.extend( domain.resolve_any_xref( - self.env, - refdoc, - self.app.builder, - target, - node, - contnode, - ), + self.env, refdoc, self.app.builder, target, node, contnode + ) ) except NotImplementedError: # the domain doesn't yet support the new interface @@ -283,13 +258,7 @@ def resolve_myst_ref_any( ) for role in domain.roles: res = domain.resolve_xref( - self.env, - refdoc, - self.app.builder, - role, - target, - node, - contnode, + self.env, refdoc, self.app.builder, role, target, node, contnode ) if res and len(res) and isinstance(res[0], nodes.Element): results.append((f"{domain.name}:{role}", res)) @@ -325,10 +294,7 @@ def stringify(name, node): return newnode def _resolve_ref_nested( - self, - node: pending_xref, - fromdocname: str, - target=None, + self, node: pending_xref, fromdocname: str, target=None ) -> Element | None: """This is the same as ``sphinx.domains.std._resolve_ref_xref``, but allows for nested syntax, rather than converting the inner node to raw text. @@ -356,9 +322,7 @@ def _resolve_ref_nested( return make_refnode(self.app.builder, fromdocname, docname, labelid, innernode) def _resolve_doc_nested( - self, - node: pending_xref, - fromdocname: str, + self, node: pending_xref, fromdocname: str ) -> Element | None: """This is the same as ``sphinx.domains.std._resolve_doc_xref``, but allows for nested syntax, rather than converting the inner node to raw text. @@ -406,7 +370,7 @@ def _resolve_myst_ref_intersphinx( [ inventory.filter_string(m.inv, m.domain, m.otype, m.name) for m in matches[:show_num] - ], + ] ) if len(matches) > show_num: matches_str += ", ..." @@ -427,11 +391,11 @@ def _resolve_myst_ref_intersphinx( newnode.append(contnode) elif match.text: newnode.append( - contnode.__class__(match.text, match.text, classes=["iref", "myst"]), + contnode.__class__(match.text, match.text, classes=["iref", "myst"]) ) else: newnode.append( - nodes.literal(match.name, match.name, classes=["iref", "myst"]), + nodes.literal(match.name, match.name, classes=["iref", "myst"]) ) return newnode diff --git a/myst_parser/warnings_.py b/myst_parser/warnings_.py index 40d8ac4e..30a8fa8d 100644 --- a/myst_parser/warnings_.py +++ b/myst_parser/warnings_.py @@ -68,9 +68,7 @@ class MystWarnings(Enum): def _is_suppressed_warning( - type: str, - subtype: str, - suppress_warnings: Sequence[str], + type: str, subtype: str, suppress_warnings: Sequence[str] ) -> bool: """Check whether the warning is suppressed or not. diff --git a/pyproject.toml b/pyproject.toml index bbdd086b..836473b6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -102,8 +102,8 @@ exclude = [ ] [tool.ruff] -extend-select = ["B", "C4", "COM", "FA", "FURB", "I", "ICN", "ISC", "N", "PERF", "PGH", "PIE", "RUF", "SIM", "UP"] -extend-ignore = ["ISC001", "RUF005", "RUF012", "COM812"] +extend-select = ["B", "C4", "FA", "FURB", "I", "ICN", "ISC", "N", "PERF", "PGH", "PIE", "RUF", "SIM", "UP"] +extend-ignore = ["ISC001", "RUF005", "RUF012"] [tool.ruff.per-file-ignores] "myst_parser/parsers/docutils_.py" = ["FA"] diff --git a/tests/test_commonmark/test_commonmark.py b/tests/test_commonmark/test_commonmark.py index 52d1a54f..3cee1eb8 100644 --- a/tests/test_commonmark/test_commonmark.py +++ b/tests/test_commonmark/test_commonmark.py @@ -11,8 +11,7 @@ from myst_parser.parsers.mdit import create_md_parser with open( - os.path.join(os.path.dirname(__file__), "commonmark.json"), - encoding="utf8", + os.path.join(os.path.dirname(__file__), "commonmark.json"), encoding="utf8" ) as fin: tests = json.load(fin) @@ -27,7 +26,7 @@ def test_commonmark(entry): # but not strictly CommonMark, # see: https://talk.commonmark.org/t/metadata-in-documents/721/86 pytest.skip( - "Thematic breaks on the first line conflict with front matter syntax", + "Thematic breaks on the first line conflict with front matter syntax" ) test_case = entry["markdown"] md = create_md_parser(MdParserConfig(), RendererHTML) @@ -39,8 +38,7 @@ def test_commonmark(entry): if entry["example"] in [187, 209, 210]: # this doesn't have any bearing on the output output = output.replace( - "<blockquote></blockquote>", - "<blockquote>\n</blockquote>", + "<blockquote></blockquote>", "<blockquote>\n</blockquote>" ) assert output == entry["html"] diff --git a/tests/test_docutils.py b/tests/test_docutils.py index 74faf528..74ff04b5 100644 --- a/tests/test_docutils.py +++ b/tests/test_docutils.py @@ -115,8 +115,7 @@ def test_include_from_rst(tmp_path): parser = RSTParser() document = make_document(parser_cls=RSTParser) parser.parse( - f".. include:: {include_path}\n :parser: myst_parser.docutils_", - document, + f".. include:: {include_path}\n :parser: myst_parser.docutils_", document ) assert ( document.pformat().strip() @@ -126,6 +125,6 @@ def test_include_from_rst(tmp_path): <section ids="title" names="title"> <title> Title - """, + """ ).strip() ) diff --git a/tests/test_inventory.py b/tests/test_inventory.py index 4b064a55..825bcccb 100644 --- a/tests/test_inventory.py +++ b/tests/test_inventory.py @@ -51,8 +51,7 @@ def test_inv_filter_wildcard(data_regression): @pytest.mark.parametrize( - "options", - [(), ("-d", "std"), ("-o", "doc"), ("-n", "ref"), ("-l", "index.html*")], + "options", [(), ("-d", "std"), ("-o", "doc"), ("-n", "ref"), ("-l", "index.html*")] ) def test_inv_cli_v2(options, capsys, file_regression): inventory_cli([str(STATIC / "objects_v2.inv"), "-f", "yaml", *options]) diff --git a/tests/test_renderers/test_fixtures_sphinx.py b/tests/test_renderers/test_fixtures_sphinx.py index 1b9725db..443fc9db 100644 --- a/tests/test_renderers/test_fixtures_sphinx.py +++ b/tests/test_renderers/test_fixtures_sphinx.py @@ -27,7 +27,7 @@ def test_syntax_elements(file_params, sphinx_doctree_no_tr: CreateDoctree): @pytest.mark.param_file(FIXTURE_PATH / "sphinx_link_resolution.md") def test_link_resolution(file_params, sphinx_doctree: CreateDoctree): sphinx_doctree.set_conf( - {"extensions": ["myst_parser"], **settings_from_json(file_params.description)}, + {"extensions": ["myst_parser"], **settings_from_json(file_params.description)} ) sphinx_doctree.srcdir.joinpath("test.txt").touch() sphinx_doctree.srcdir.joinpath("other.rst").write_text(":orphan:\n\nTest\n====") @@ -69,7 +69,7 @@ def test_sphinx_directives(file_params, sphinx_doctree_no_tr: CreateDoctree): # TODO fix skipped directives # TODO test domain directives if file_params.title.startswith("SKIP") or file_params.title.startswith( - "SPHINX4-SKIP", + "SPHINX4-SKIP" ): pytest.skip(file_params.title) @@ -82,12 +82,10 @@ def test_sphinx_directives(file_params, sphinx_doctree_no_tr: CreateDoctree): pformat = pformat.replace('"2147483647"', '"9223372036854775807"') # changed in sphinx 7.1 (but fixed in 7.2) pformat = pformat.replace( - 'classes="sig sig-object sig sig-object"', - 'classes="sig sig-object"', + 'classes="sig sig-object sig sig-object"', 'classes="sig sig-object"' ) pformat = pformat.replace( - 'classes="sig-name descname sig-name descname"', - 'classes="sig-name descname"', + 'classes="sig-name descname sig-name descname"', 'classes="sig-name descname"' ) pformat = pformat.replace( 'classes="sig-prename descclassname sig-prename descclassname"', @@ -126,7 +124,7 @@ def test_sphinx_roles(file_params, sphinx_doctree_no_tr: CreateDoctree): @pytest.mark.param_file(FIXTURE_PATH / "dollarmath.md") def test_dollarmath(file_params, sphinx_doctree_no_tr: CreateDoctree): sphinx_doctree_no_tr.set_conf( - {"extensions": ["myst_parser"], "myst_enable_extensions": ["dollarmath"]}, + {"extensions": ["myst_parser"], "myst_enable_extensions": ["dollarmath"]} ) result = sphinx_doctree_no_tr(file_params.content, "index.md") file_params.assert_expected(result.pformat("index"), rstrip_lines=True) @@ -136,7 +134,7 @@ def test_dollarmath(file_params, sphinx_doctree_no_tr: CreateDoctree): def test_amsmath(file_params, sphinx_doctree_no_tr: CreateDoctree, monkeypatch): monkeypatch.setattr(SphinxRenderer, "_random_label", lambda self: "mock-uuid") sphinx_doctree_no_tr.set_conf( - {"extensions": ["myst_parser"], "myst_enable_extensions": ["amsmath"]}, + {"extensions": ["myst_parser"], "myst_enable_extensions": ["amsmath"]} ) result = sphinx_doctree_no_tr(file_params.content, "index.md") file_params.assert_expected(result.pformat("index"), rstrip_lines=True) @@ -146,7 +144,7 @@ def test_amsmath(file_params, sphinx_doctree_no_tr: CreateDoctree, monkeypatch): def test_containers(file_params, sphinx_doctree_no_tr: CreateDoctree, monkeypatch): monkeypatch.setattr(SphinxRenderer, "_random_label", lambda self: "mock-uuid") sphinx_doctree_no_tr.set_conf( - {"extensions": ["myst_parser"], "myst_enable_extensions": ["colon_fence"]}, + {"extensions": ["myst_parser"], "myst_enable_extensions": ["colon_fence"]} ) result = sphinx_doctree_no_tr(file_params.content, "index.md") file_params.assert_expected(result.pformat("index"), rstrip_lines=True) @@ -162,7 +160,7 @@ def test_evalrst_elements(file_params, sphinx_doctree_no_tr: CreateDoctree): @pytest.mark.param_file(FIXTURE_PATH / "definition_lists.md") def test_definition_lists(file_params, sphinx_doctree_no_tr: CreateDoctree): sphinx_doctree_no_tr.set_conf( - {"extensions": ["myst_parser"], "myst_enable_extensions": ["deflist"]}, + {"extensions": ["myst_parser"], "myst_enable_extensions": ["deflist"]} ) result = sphinx_doctree_no_tr(file_params.content, "index.md") file_params.assert_expected(result.pformat("index"), rstrip_lines=True) @@ -174,7 +172,7 @@ def test_attributes(file_params, sphinx_doctree_no_tr: CreateDoctree): { "extensions": ["myst_parser"], "myst_enable_extensions": ["attrs_inline", "attrs_block"], - }, + } ) result = sphinx_doctree_no_tr(file_params.content, "index.md") file_params.assert_expected(result.pformat("index"), rstrip_lines=True) diff --git a/tests/test_renderers/test_myst_config.py b/tests/test_renderers/test_myst_config.py index 36d6f421..e35aee40 100644 --- a/tests/test_renderers/test_myst_config.py +++ b/tests/test_renderers/test_myst_config.py @@ -26,7 +26,7 @@ def test_cmdline(file_params: ParamTestData): pub.process_command_line(shlex.split(file_params.description)) except Exception as err: raise AssertionError( - f"Failed to parse commandline: {file_params.description}\n{err}", + f"Failed to parse commandline: {file_params.description}\n{err}" ) from err settings = vars(pub.settings) report_stream = StringIO() diff --git a/tests/test_renderers/test_myst_refs.py b/tests/test_renderers/test_myst_refs.py index 4c896636..6412eadc 100644 --- a/tests/test_renderers/test_myst_refs.py +++ b/tests/test_renderers/test_myst_refs.py @@ -39,7 +39,6 @@ def test_parse( outcome = doctree.pformat() if result.warnings.strip(): outcome += "\n\n" + result.warnings.strip().replace("", "").replace( - "", - "", + "", "" ) file_regression.check(outcome, basename=test_name, extension=".xml") diff --git a/tests/test_renderers/test_parse_directives.py b/tests/test_renderers/test_parse_directives.py index 7ae3979d..0cb6292a 100644 --- a/tests/test_renderers/test_parse_directives.py +++ b/tests/test_renderers/test_parse_directives.py @@ -19,8 +19,7 @@ def test_option_parsing(file_params): """Test parsing of directive options.""" result = list(options_to_items(file_params.content)) file_params.assert_expected( - json.dumps(result, ensure_ascii=False, indent=2), - rstrip_lines=True, + json.dumps(result, ensure_ascii=False, indent=2), rstrip_lines=True ) @@ -50,10 +49,7 @@ def test_parsing(file_params): raise AssertionError(f"Unknown directive: {name}") try: result = parse_directive_text( - klass, - first_line[0] if first_line else "", - tokens[0].content, - line=0, + klass, first_line[0] if first_line else "", tokens[0].content, line=0 ) except MarkupError as err: outcome = f"error: {err}" @@ -72,8 +68,7 @@ def test_parsing(file_params): @pytest.mark.parametrize( - "descript,klass,arguments,content", - [("no content", Rubric, "", "a")], + "descript,klass,arguments,content", [("no content", Rubric, "", "a")] ) def test_parsing_errors(descript, klass, arguments, content): with pytest.raises(MarkupError): @@ -82,10 +77,7 @@ def test_parsing_errors(descript, klass, arguments, content): def test_parsing_full_yaml(): result = parse_directive_text( - Note, - "", - "---\na: [1]\n---\ncontent", - validate_options=False, + Note, "", "---\na: [1]\n---\ncontent", validate_options=False ) assert not result.warnings assert result.options == {"a": [1]} @@ -96,40 +88,28 @@ def test_additional_options(): """Allow additional options to be passed to a directive.""" # this should be fine result = parse_directive_text( - Note, - "", - "content", - additional_options={"class": "bar"}, + Note, "", "content", additional_options={"class": "bar"} ) assert not result.warnings assert result.options == {"class": ["bar"]} assert result.body == ["content"] # body on first line should also be fine result = parse_directive_text( - Note, - "content", - "other", - additional_options={"class": "bar"}, + Note, "content", "other", additional_options={"class": "bar"} ) assert not result.warnings assert result.options == {"class": ["bar"]} assert result.body == ["content", "other"] # additional option should not take precedence result = parse_directive_text( - Note, - "content", - ":class: foo", - additional_options={"class": "bar"}, + Note, "content", ":class: foo", additional_options={"class": "bar"} ) assert not result.warnings assert result.options == {"class": ["foo"]} assert result.body == ["content"] # this should warn about the unknown option result = parse_directive_text( - Note, - "", - "content", - additional_options={"foo": "bar"}, + Note, "", "content", additional_options={"foo": "bar"} ) assert len(result.warnings) == 1 assert "Unknown option" in result.warnings[0].msg diff --git a/tests/test_sphinx/conftest.py b/tests/test_sphinx/conftest.py index b785679d..4a244872 100644 --- a/tests/test_sphinx/conftest.py +++ b/tests/test_sphinx/conftest.py @@ -115,7 +115,7 @@ def read( # convert absolute filenames for node in findall(doctree)( - lambda n: "source" in n and not isinstance(n, str), + lambda n: "source" in n and not isinstance(n, str) ): node["source"] = pathlib.Path(node["source"]).name diff --git a/tests/test_sphinx/test_sphinx_builds.py b/tests/test_sphinx/test_sphinx_builds.py index 1966b7fc..57039d8f 100644 --- a/tests/test_sphinx/test_sphinx_builds.py +++ b/tests/test_sphinx/test_sphinx_builds.py @@ -238,9 +238,7 @@ def test_extended_syntaxes( @pytest.mark.sphinx( - buildername="html", - srcdir=os.path.join(SOURCE_DIR, "includes"), - freshenv=True, + buildername="html", srcdir=os.path.join(SOURCE_DIR, "includes"), freshenv=True ) def test_includes( app, @@ -316,9 +314,7 @@ def test_include_from_rst( reason="Footnote HTML changed in docutils 0.19", ) @pytest.mark.sphinx( - buildername="html", - srcdir=os.path.join(SOURCE_DIR, "footnotes"), - freshenv=True, + buildername="html", srcdir=os.path.join(SOURCE_DIR, "footnotes"), freshenv=True ) def test_footnotes( app, @@ -407,9 +403,7 @@ def test_substitutions( @pytest.mark.sphinx( - buildername="gettext", - srcdir=os.path.join(SOURCE_DIR, "gettext"), - freshenv=True, + buildername="gettext", srcdir=os.path.join(SOURCE_DIR, "gettext"), freshenv=True ) def test_gettext( app, @@ -470,7 +464,7 @@ def test_gettext_html( regress_ext=".html", replace={ # upstream bug https://github.com/sphinx-doc/sphinx/issues/11689 - '"Permalink to this heading"': '"Lien permanent vers cette rubrique"', + '"Permalink to this heading"': '"Lien permanent vers cette rubrique"' }, )