Skip to content

Commit

Permalink
Add CHANGES_AST switch to plugin API so that AST breaking plugins c…
Browse files Browse the repository at this point in the history
…an disable the equality check (#49)

* Add `CHANGES_AST` switch to plugin API so that AST breaking plugins can disable the equality check

* Remove bad test docstring
  • Loading branch information
hukkinj1 authored Oct 3, 2020
1 parent 2730892 commit 58731b4
Show file tree
Hide file tree
Showing 3 changed files with 72 additions and 19 deletions.
5 changes: 4 additions & 1 deletion mdformat/_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,12 @@ def run(cli_args: Sequence[str]) -> int: # noqa: C901
action="store_true",
help="Apply consecutive numbering to ordered lists",
)
changes_ast = False
for plugin in mdformat.plugins.PARSER_EXTENSIONS.values():
if hasattr(plugin, "add_cli_options"):
plugin.add_cli_options(parser)
if getattr(plugin, "CHANGES_AST", False):
changes_ast = True

args = parser.parse_args(cli_args)

Expand Down Expand Up @@ -64,7 +67,7 @@ def run(cli_args: Sequence[str]) -> int: # noqa: C901
format_errors_found = True
sys.stderr.write(f'Error: File "{path_str}" is not formatted.\n')
else:
if not is_md_equal(
if not changes_ast and not is_md_equal(
original_str,
formatted_str,
options,
Expand Down
12 changes: 9 additions & 3 deletions mdformat/plugins.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,21 @@ def _load_codeformatters() -> Dict[str, Callable[[str, str], str]]:
class ParserExtensionInterface(Protocol):
"""A interface for parser extension plugins."""

def add_cli_options(self, parser: argparse.ArgumentParser) -> None:
# Does the plugin's formatting change Markdown AST or not?
# (optional, default: False)
CHANGES_AST: bool = False

@staticmethod
def add_cli_options(parser: argparse.ArgumentParser) -> None:
"""Add options to the mdformat CLI, to be stored in
mdit.options["mdformat"] (optional)"""

def update_mdit(self, mdit: MarkdownIt) -> None:
@staticmethod
def update_mdit(mdit: MarkdownIt) -> None:
"""Update the parser, e.g. by adding a plugin: `mdit.use(myplugin)`"""

@staticmethod
def render_token(
self,
renderer: MDRenderer,
tokens: Sequence[Token],
index: int,
Expand Down
74 changes: 59 additions & 15 deletions tests/test_plugins.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import argparse
from textwrap import dedent
from typing import List, Optional, Tuple
from typing import Any, Mapping, Optional, Sequence, Tuple
from unittest.mock import call, patch

from markdown_it import MarkdownIt
Expand All @@ -16,19 +16,20 @@


class ExampleFrontMatterPlugin:
"""A class for extending the base parser."""
"""A plugin that adds front_matter extension to the parser."""

@staticmethod
def update_mdit(mdit: MarkdownIt):
"""Update the parser, e.g. by adding a plugin: `mdit.use(myplugin)`"""
mdit.use(front_matter.front_matter_plugin)

@staticmethod
def render_token(
renderer: MDRenderer, tokens: List[Token], index: int, options: dict, env: dict
renderer: MDRenderer,
tokens: Sequence[Token],
index: int,
options: Mapping[str, Any],
env: dict,
) -> Optional[Tuple[str, int]]:
"""Convert a token to a string, or return None if no render method
available."""
token = tokens[index]
if token.type == "front_matter":
text = yaml.dump(yaml.safe_load(token.content))
Expand Down Expand Up @@ -62,19 +63,20 @@ def test_front_matter(monkeypatch):


class ExampleTablePlugin:
"""A class for extending the base parser."""
"""A plugin that adds table extension to the parser."""

@staticmethod
def update_mdit(mdit: MarkdownIt):
"""Update the parser, e.g. by adding a plugin: `mdit.use(myplugin)`"""
mdit.enable("table")

@staticmethod
def render_token(
renderer: MDRenderer, tokens: List[Token], index: int, options: dict, env: dict
renderer: MDRenderer,
tokens: Sequence[Token],
index: int,
options: Mapping[str, Any],
env: dict,
) -> Optional[Tuple[str, int]]:
"""Convert a token to a string, or return None if no render method
available."""
token = tokens[index]
if token.type == "table_open":
# search for the table close, and return a dummy output
Expand Down Expand Up @@ -111,17 +113,14 @@ def test_table(monkeypatch):


class ExamplePluginWithCli:
"""A class for extending the base parser."""
"""A plugin that adds CLI options."""

@staticmethod
def update_mdit(mdit: MarkdownIt):
"""Update the parser, e.g. by adding a plugin: `mdit.use(myplugin)`"""
mdit.enable("table")

@staticmethod
def add_cli_options(parser: argparse.ArgumentParser) -> None:
"""Add options to the mdformat CLI, to be stored in
mdit.options["mdformat"]"""
parser.add_argument("--o1", type=str)
parser.add_argument("--o2", type=str, default="a")
parser.add_argument("--o3", dest="arg_name", type=int)
Expand Down Expand Up @@ -161,3 +160,48 @@ def test_cli_options(monkeypatch, tmp_path):
},
}
assert calls[0] == call([], expected, {}), calls[0]


class ExampleASTChangingPlugin:
"""A plugin that makes AST breaking formatting changes."""

CHANGES_AST = True

TEXT_REPLACEMENT = "Content replaced completely. AST is now broken!"

@staticmethod
def update_mdit(mdit: MarkdownIt):
pass

@staticmethod
def render_token(
renderer: MDRenderer,
tokens: Sequence[Token],
index: int,
options: Mapping[str, Any],
env: dict,
) -> Optional[Tuple[str, int]]:
token = tokens[index]
if token.type == "text":
return ExampleASTChangingPlugin.TEXT_REPLACEMENT, index
return None


def test_ast_changing_plugin(monkeypatch, tmp_path):
plugin = ExampleASTChangingPlugin()
monkeypatch.setitem(PARSER_EXTENSIONS, "ast_changer", plugin)
file_path = tmp_path / "test_markdown.md"

# Test that the AST changing formatting is applied successfully
# under normal operation.
file_path.write_text("Some markdown here\n")
assert run((str(file_path),)) == 0
assert file_path.read_text() == plugin.TEXT_REPLACEMENT + "\n"

# Set the plugin's `CHANGES_AST` flag to False and test that the
# equality check triggers, notices the AST breaking changes and a
# non-zero error code is returned.
plugin.CHANGES_AST = False
file_path.write_text("Some markdown here\n")
assert run((str(file_path),)) == 1
assert file_path.read_text() == "Some markdown here\n"

0 comments on commit 58731b4

Please sign in to comment.