Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 21 additions & 2 deletions mdformat_footnote/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ def _keep_orphans(options: ContextOptions) -> bool:
return bool(get_conf(options, "keep_orphans")) or False


def _keep_position(options: ContextOptions) -> bool:
"""Check if footnote definitions should stay at their source position."""
return bool(get_conf(options, "keep_position")) or False


def add_cli_argument_group(group: argparse._ArgumentGroup) -> None:
"""Add options to the mdformat CLI.

Expand All @@ -34,16 +39,29 @@ def add_cli_argument_group(group: argparse._ArgumentGroup) -> None:
"(default: remove them)"
),
)
group.add_argument(
"--keep-footnote-position",
action="store_const",
const=True,
dest="keep_position",
help=(
"Keep footnote definitions at their source position "
"(default: move them to the end of the document)"
),
)


def update_mdit(mdit: MarkdownIt) -> None:
"""Update the parser, adding the footnote plugin."""
mdit.use(footnote_plugin)
keep_position = _keep_position(mdit.options)
mdit.use(footnote_plugin, move_to_end=not keep_position)
# Disable inline footnotes for now, since we don't have rendering
# support for them yet.
mdit.disable("footnote_inline")
if keep_position:
return
# Reorder footnotes by reference order, fix IDs, and handle orphans.
# Must run before footnote_tail.
# Must run before footnote_tail, which only exists when move_to_end is set.
keep_orphans = _keep_orphans(mdit.options)
reorder_fn = partial(reorder_footnotes_by_definition, keep_orphans=keep_orphans)
mdit.core.ruler.before("footnote_tail", "reorder_footnotes", reorder_fn)
Expand Down Expand Up @@ -89,6 +107,7 @@ def _render_children(node: RenderTreeNode, context: RenderContext) -> str:

RENDERERS: Mapping[str, Render] = {
"footnote": _footnote_renderer,
"footnote_reference": _footnote_renderer,
"footnote_ref": _footnote_ref_renderer,
"footnote_block": _render_children,
}
15 changes: 15 additions & 0 deletions tests/fixtures/cli_integration.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,18 @@ Referenced [^used]

[^orphan]: This is never referenced
.

CLI keep position flag test
.
Para one.[^a]

[^a]: Definition A.

Para two.
.
Para one.[^a]

[^a]: Definition A.

Para two.
.
59 changes: 59 additions & 0 deletions tests/fixtures/keep_position.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
Single footnote stays at its source position
.
Para one.[^a]

[^a]: Definition A.

Para two.
.
Para one.[^a]

[^a]: Definition A.

Para two.
.

Multiple footnotes each stay near their own reference
.
Para one.[^a]

[^a]: Definition A.

Para two.[^b]

[^b]: Definition B.
.
Para one.[^a]

[^a]: Definition A.

Para two.[^b]

[^b]: Definition B.
.

Definition before its reference stays in place
.
[^early]: Defined early.

Para references it here.[^early]
.
[^early]: Defined early.

Para references it here.[^early]
.

Nested footnote body stays at its own source position
.
Body text.[^a]

[^a]: First, references another.[^b]

[^b]: Second.
.
Body text.[^a]

[^a]: First, references another.[^b]

[^b]: Second.
.
35 changes: 34 additions & 1 deletion tests/test_cli_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,45 @@ def test_cli_keep_orphans_flag():
assert output_keep.strip() == expected_keep.strip()


def test_cli_keep_position_flag():
"""Test --keep-footnote-position flag from command line."""
text, expected_keep = get_fixture(
"cli_integration.md", "CLI keep position flag test"
)

with tempfile.TemporaryDirectory() as tmpdir:
input_file = Path(tmpdir) / "test.md"
input_file.write_text(text)

# Default behavior: move definitions to the end of the document
result = subprocess.run(
["python", "-m", "mdformat", str(input_file)],
capture_output=True,
text=True,
)
assert result.returncode == 0
output_default = input_file.read_text()
assert output_default.strip() != expected_keep.strip()

# With --keep-footnote-position: preserve source position
input_file.write_text(text) # Reset file
result = subprocess.run(
["python", "-m", "mdformat", "--keep-footnote-position", str(input_file)],
capture_output=True,
text=True,
)
assert result.returncode == 0
output_keep = input_file.read_text()
assert output_keep.strip() == expected_keep.strip()


def test_cli_help_shows_option():
"""Test that --keep-footnote-orphans appears in help."""
"""Test that --keep-footnote-orphans and --keep-footnote-position appear in help."""
result = subprocess.run(
["python", "-m", "mdformat", "--help"],
capture_output=True,
text=True,
)
assert result.returncode == 0
assert "--keep-footnote-orphans" in result.stdout
assert "--keep-footnote-position" in result.stdout
2 changes: 2 additions & 0 deletions tests/test_fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ def _get_options(filename: str, title: str) -> dict:
return {"wrap": 40}
if "keep orphans" in title.lower():
return {"keep_orphans": True}
if "keep position" in title.lower() or filename == "keep_position.md":
return {"keep_position": True}
return {}


Expand Down
Loading