Skip to content
Merged
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
39 changes: 21 additions & 18 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,25 +13,26 @@ jobs:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v1
uses: actions/setup-python@v5
with:
python-version: 3.7
- uses: pre-commit/action@v2.0.0
python-version: '3.10'
- uses: pre-commit/action@v3.0.1
continue-on-error: true

tests:
runs-on: ${{ matrix.os }}
strategy:
matrix:
python-version: [3.6, 3.7, 3.8, 3.9]
python-version: ['3.10', '3.11', '3.12', '3.13']
os: [ubuntu-latest, windows-latest]

steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}

Expand All @@ -44,26 +45,28 @@ jobs:

- name: Run pytest
run: |
pytest --cov=mdformat_plugin --cov-report=xml --cov-report=term-missing
pytest --cov=mdformat_hallmark --cov-report=xml --cov-report=term-missing

- name: Upload to Codecov
if: matrix.os == 'ubuntu-latest' && matrix.python-version == 3.7
uses: codecov/codecov-action@v1
if: matrix.os == 'ubuntu-latest' && matrix.python-version == 3.10
uses: codecov/codecov-action@v5.4.2
with:
name: pytests-py3.7
flags: pytests
name: codecov-umbrella
flags: unit
env_vars: RUNNER_OS,PYTHON_VERSION
file: ./coverage.xml
fail_ci_if_error: true
token: ${{ secrets.CODECOV_TOKEN }}
fail_ci_if_error: false

pre-commit-hook:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
uses: actions/setup-python@v5
with:
python-version: 3.7
python-version: '3.10'

- name: Installation (deps and package)
run: |
Expand All @@ -82,10 +85,10 @@ jobs:
steps:
- name: Checkout source
uses: actions/checkout@v2
- name: Set up Python 3.7
uses: actions/setup-python@v1
- name: Set up Python 3.10
uses: actions/setup-python@v5
with:
python-version: 3.7
python-version: '3.10'
- name: install flit
run: |
pip install flit~=3.0
Expand Down
18 changes: 9 additions & 9 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,29 +1,29 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v3.4.0
rev: v5.0.0
hooks:
- id: end-of-file-fixer
- id: mixed-line-ending
- id: trailing-whitespace
- id: check-yaml
- id: check-toml
- repo: https://github.com/pre-commit/pygrep-hooks
rev: v1.8.0
rev: v1.10.0
hooks:
- id: python-check-blanket-noqa
- repo: https://github.com/timothycrosley/isort
rev: 5.8.0
rev: 5.13.2
hooks:
- id: isort
- repo: https://github.com/psf/black
rev: 20.8b1
rev: 24.10.0
hooks:
- id: black
- repo: https://gitlab.com/pycqa/flake8
rev: 3.9.0
- repo: https://github.com/pycqa/flake8
rev: 7.3.0
hooks:
- id: flake8
additional_dependencies:
- flake8-bugbear==21.3.2
- flake8-builtins==1.5.3
- flake8-comprehensions==3.4.0
- flake8-bugbear
- flake8-builtins
- flake8-comprehensions
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2020 Executable Books
Copyright (c) 2025 Executable Books

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
33 changes: 10 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,23 +1,10 @@
# mdformat-plugin
# mdformat-hallmark

[![Build Status][ci-badge]][ci-link]
[![codecov.io][cov-badge]][cov-link]
[![PyPI version][pypi-badge]][pypi-link]
[![Build Status](https://github.com/calgray/mdformat-hallmark/actions/workflows/tests.yml/badge.svg?branch=master)](<https://github.com/calgray/mdformat-hallmark/actions?query=workflow%3ATests+branch%3Amaster+event%3Apush>)
[![codecov.io](https://codecov.io/gh/calgray/mdformat-hallmark/branch/main/graph/badge.svg)](https://codecov.io/gh/xarray-contrib/astropy-xarray)
[![PyPI version](https://badge.fury.io/py/mdformat-hallmark.svg)](<https://badge.fury.io/py/mdformat-hallmark>)

An [mdformat](https://github.com/executablebooks/mdformat) plugin for...

## Required changes for a new plugin

This demonstration is setup with a plugin named `plugin`.
There are a number of locations to change.
At a top level for a plugin `foo` at least the following changes are required

- Global find and replace `mdformat_plugin` to `mdformat_foo` including folder names.
- Global find and replace `mdformat-plugin` to `mdformat-foo` including folder names.
- `tests/test_fixtures.py`: `output = mdformat.text(text, extensions={"plugin"})` becomes `output = mdformat.text(text, extensions={"foo"})`
- `pyproject.toml` in addition to the global find and replace: `plugin = "mdformat_plugin"` becomes `foo = "mdformat_foo"`

Do not forget to update authorship / maintainers in `pyproject.toml` as well.
An [mdformat](https://github.com/executablebooks/mdformat) plugin for compatibility with [hallmark](https://github.com/vweevers/hallmark) formatted Markdown and [Common Changelog](https://common-changelog.org/).

## Development

Expand Down Expand Up @@ -75,9 +62,9 @@ or trigger the GitHub Action job, by creating a release with a tag equal to the

Note, this requires generating an API key on PyPi and adding it to the repository `Settings/Secrets`, under the name `PYPI_KEY`.

[ci-badge]: https://github.com/executablebooks/mdformat-plugin/workflows/CI/badge.svg?branch=master
[ci-badge]: https://github.com/executablebooks/mdformat-hallmark/workflows/CI/badge.svg?branch=master
[ci-link]: https://github.com/executablebooks/mdformat/actions?query=workflow%3ACI+branch%3Amaster+event%3Apush
[cov-badge]: https://codecov.io/gh/executablebooks/mdformat-plugin/branch/master/graph/badge.svg
[cov-link]: https://codecov.io/gh/executablebooks/mdformat-plugin
[pypi-badge]: https://img.shields.io/pypi/v/mdformat-plugin.svg
[pypi-link]: https://pypi.org/project/mdformat-plugin
[cov-badge]: https://codecov.io/gh/executablebooks/mdformat-hallmark/branch/master/graph/badge.svg
[cov-link]: https://codecov.io/gh/executablebooks/mdformat-hallmark
[pypi-badge]: https://img.shields.io/pypi/v/mdformat-hallmark.svg
[pypi-link]: https://pypi.org/project/mdformat-hallmark
10 changes: 10 additions & 0 deletions mdformat_hallmark/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
"""
An mdformat plugin for compatibility with hallmark formatted Markdown
and Common Changelog.
"""

__version__ = "0.0.1"

from .hallmark_links_extension import HallmarkLinksExtension

__all__ = ["HallmarkLinksExtension"]
109 changes: 109 additions & 0 deletions mdformat_hallmark/hallmark_links_extension.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
from dataclasses import dataclass
import re

from markdown_it import MarkdownIt
from markdown_it.token import Token
from mdformat.plugins import ParserExtensionInterface
from mdformat.renderer import RenderContext, RenderTreeNode
from packaging.version import InvalidVersion, Version

_REFERENCES_RE = re.compile(
r'^\[([^\]]+)\]:\s*(\S+)(?:\s+"([^"]+)")?$',
re.MULTILINE,
) # regex for `[label]: href "title"`


@dataclass
class SemverReference:
semver: Version
label: str
href: str
title: str | None

def __str__(self):
line = f"[{self.label}]: {self.href}"
if self.title:
line += f' "{self.title}"'
return line


def _extract_semver_references(src: str) -> tuple[list[SemverReference], str]:
refs: list[SemverReference] = []
remove_spans: list[tuple[int, int]] = []

for m in _REFERENCES_RE.finditer(src):
label, href, title = m.groups()
try:
semver = Version(label)
except InvalidVersion:
continue # skip non-semver labels
refs.append(SemverReference(semver, label, href, title))
remove_spans.append(m.span())

# remove references from the source
out_src = src
for start, end in reversed(remove_spans):
out_src = out_src[:start] + out_src[end:]

# Normalize whitespace
out_src = re.sub(r"\n{3,}", "\n\n", out_src).rstrip()

# sort semver references
refs.sort(key=lambda ref: ref.semver, reverse=True)

return refs, out_src


class HallmarkLinksExtension(ParserExtensionInterface):
"""
mdformat plugin extension to format references used by versions and
renders sorted by semantic-version order.
"""

@staticmethod
def update_mdit(mdit: MarkdownIt) -> None:
"""Patch the default parser to render references in semver order."""

original_parse = mdit.parse

def new_parse(src: str, env=None):
if env is None:
env = {}
if not src.lstrip().startswith("# Changelog"):
return original_parse(src, env)

# extract semver references and remove from src
matches, out_src = _extract_semver_references(src)
refs = {
match.label: {"href": match.href, "title": match.title}
for match in matches
}

# call original parse on the cleaned text
tokens = original_parse(out_src, env)

# append a hallmark_refs token at the end
if refs:
token = Token("hallmark_refs", "", 0)
token.meta = {"refs": refs}
tokens.append(token)

return tokens

# patch parser
mdit.parse = new_parse

@staticmethod
def _render_hallmark_refs(node: RenderTreeNode, ctx: RenderContext) -> str:
"""Render collected reference defs in hallmark ordered format."""
refs: dict[str, dict[str, str]] = node.meta["refs"]
out = []
for label, ref in refs.items():
line = f"[{label}]: {ref['href']}"
if ref.get("title"):
line += f' "{ref["title"]}"'
out.append(line)
return "\n\n".join(out)

RENDERERS = {"hallmark_refs": _render_hallmark_refs}
CHANGES_AST = True
5 changes: 0 additions & 5 deletions mdformat_plugin/__init__.py

This file was deleted.

24 changes: 0 additions & 24 deletions mdformat_plugin/plugin.py

This file was deleted.

14 changes: 9 additions & 5 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ requires = ["flit_core >=3.2.0,<4"]
build-backend = "flit_core.buildapi"

[project]
name = "mdformat_plugin"
name = "mdformat-hallmark"
authors = [
{ name = "Chris Sewell", email = "executablebooks@gmail.com" },
{ name = "Callan Gray", email = "cal.j.gray@gmail.com" },
]
readme = "README.md"
classifiers = [
Expand All @@ -18,6 +18,7 @@ keywords = ["mdformat", "markdown", "markdown-it"]
requires-python = ">=3.7"
dependencies = [
"mdformat >=0.7.0,<0.8.0",
"packaging >= 23.0",
]
dynamic = ["version", "description"]

Expand All @@ -30,10 +31,10 @@ test = [
dev = ["pre-commit"]

[project.urls]
Homepage = "https://github.com/executablebooks/mdformat-plugin"
Homepage = "https://github.com/calgray/mdformat-hallmark"

[project.entry-points."mdformat.parser_extension"]
plugin = "mdformat_plugin"
"hallmark" = "mdformat_hallmark:HallmarkLinksExtension"

[tool.flit.sdist]
include = []
Expand All @@ -46,7 +47,10 @@ force_sort_within_sections = true
no_lines_before = ["LOCALFOLDER"]

# Configure isort to work without access to site-packages
known_first_party = ["mdformat_plugin", "tests"]
known_first_party = ["mdformat_hallmark", "tests"]

# Settings for Black compatibility
profile = "black"

[tool.pytest.ini_options]
log_cli = true
Loading
Loading