From e55664f0d50239d38f6f6aa2187aceb35957c36a Mon Sep 17 00:00:00 2001 From: Ryan Chou <88779759+ryanchou1994@users.noreply.github.com> Date: Wed, 9 Sep 2026 05:35:57 +0800 Subject: [PATCH] Fix file-variable CLI flag and header ID setup --- README.md | 9 ++++++ lib/markdown2.py | 20 ++++++++----- test/test_cli.py | 66 ++++++++++++++++++++++++++++++++++++++++++ test/test_markdown2.py | 10 +++++++ 4 files changed, 97 insertions(+), 8 deletions(-) create mode 100644 test/test_cli.py diff --git a/README.md b/README.md index f1e89c9f..c8d84430 100644 --- a/README.md +++ b/README.md @@ -71,6 +71,15 @@ I think pip-based installation will enable this as well: ```shell $ markdown2 foo.md > foo.html ``` + +Use `--use-file-vars` to enable extras declared in Emacs-style file variables, +such as `` at the top of a file. +This flag takes no value; file variables are ignored by default. + +```shell +$ markdown2 --use-file-vars foo.md > foo.html +``` + See the [project wiki](https://github.com/trentm/python-markdown2/wiki), [lib/markdown2.py](https://github.com/trentm/python-markdown2/blob/master/lib/markdown2.py) docstrings and/or `python markdown2.py --help` for more details. diff --git a/lib/markdown2.py b/lib/markdown2.py index bf7df1ef..56db29a7 100755 --- a/lib/markdown2.py +++ b/lib/markdown2.py @@ -360,13 +360,7 @@ def __init__( else: self._toc_depth = self.extras["toc"].get("depth", 6) - if 'header-ids' in self.extras: - if not isinstance(self.extras['header-ids'], dict): - self.extras['header-ids'] = { - 'mixed': False, - 'prefix': self.extras['header-ids'], - 'reset-count': True - } + self._normalize_header_ids() if 'break-on-newline' in self.extras: # `break-on-newline` is an alias for the breaks extra's `on_newline` @@ -412,7 +406,17 @@ def reset(self): self._setup_extras() self._toc = [] + def _normalize_header_ids(self): + if 'header-ids' in self.extras: + if not isinstance(self.extras['header-ids'], dict): + self.extras['header-ids'] = { + 'mixed': False, + 'prefix': self.extras['header-ids'], + 'reset-count': True + } + def _setup_extras(self): + self._normalize_header_ids() if "footnotes" in self.extras: # order of insertion matters for footnotes. Use ordered dict for Python < 3.7 # https://docs.python.org/3/whatsnew/3.7.html#summary-release-highlights @@ -4871,7 +4875,7 @@ def main(argv=None): parser.add_argument("-x", "--extras", action="append", help="Turn on specific extra features (not part of " "the core Markdown spec). See above.") - parser.add_argument("--use-file-vars", + parser.add_argument("--use-file-vars", action="store_true", help="Look for and use Emacs-style 'markdown-extras' " "file var to turn on extras. See " "") diff --git a/test/test_cli.py b/test/test_cli.py new file mode 100644 index 00000000..3ffdbc06 --- /dev/null +++ b/test/test_cli.py @@ -0,0 +1,66 @@ +"""Regression tests for the markdown2 command line interface.""" + +import subprocess +import sys +import tempfile +import unittest +from pathlib import Path + + +class FileVarsTestCase(unittest.TestCase): + source = "\n# Heading\n" + script = Path(__file__).resolve().parent.parent / "lib" / "markdown2.py" + + def run_cli(self, *args, text=""): + result = subprocess.run( + [sys.executable, str(self.script), *map(str, args)], + input=text, + text=True, + capture_output=True, + timeout=10, + ) + self.assertEqual(result.returncode, 0, result.stderr) + return result.stdout + + def test_file_vars_from_stdin(self): + html = self.run_cli("--use-file-vars", text=self.source) + self.assertIn('

Heading

', html) + + def test_file_vars_before_path(self): + with tempfile.TemporaryDirectory() as directory: + path = Path(directory) / "input.md" + path.write_text(self.source, encoding="utf-8") + html = self.run_cli("--use-file-vars", path) + self.assertIn('

Heading

', html) + + def test_file_vars_after_path(self): + with tempfile.TemporaryDirectory() as directory: + path = Path(directory) / "input.md" + path.write_text(self.source, encoding="utf-8") + html = self.run_cli(path, "--use-file-vars") + self.assertIn('

Heading

', html) + + def test_file_vars_with_multiple_paths(self): + with tempfile.TemporaryDirectory() as directory: + paths = [ + Path(directory) / name for name in ("first.md", "second.md") + ] + for path in paths: + path.write_text(self.source, encoding="utf-8") + html = self.run_cli("--use-file-vars", *paths) + self.assertEqual(html.count('

Heading

'), 2) + + def test_file_vars_with_output_option(self): + with tempfile.TemporaryDirectory() as directory: + output = Path(directory) / "output.html" + stdout = self.run_cli( + "--use-file-vars", "--output", output, text=self.source + ) + self.assertEqual(stdout, "") + html = output.read_text(encoding="utf-8") + self.assertIn('

Heading

', html) + + def test_file_vars_are_disabled_by_default(self): + html = self.run_cli(text=self.source) + self.assertIn("

Heading

", html) + self.assertNotIn('id="heading"', html) diff --git a/test/test_markdown2.py b/test/test_markdown2.py index 0dd22ad9..64ed8075 100755 --- a/test/test_markdown2.py +++ b/test/test_markdown2.py @@ -220,6 +220,16 @@ class DirectTestCase(_MarkdownTestCase): Python-markdown (markdown.py). """ + def test_header_ids_from_file_vars(self): + md = markdown2.Markdown(use_file_vars=True) + modeline = "\n" + for option, heading_id in ( + ("", "heading"), ("=chapter", "chapter-heading") + ): + html = md.convert(modeline.format(option) + "# Heading\n") + self.assertIn('

Heading

'.format(heading_id), html) + self.assertEqual(md.convert("# Heading\n"), "

Heading

\n") + def test_slow_hr(self): import time text = """\