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
15 changes: 15 additions & 0 deletions .markdownlint.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Default state for all rules
default: true

# MD013/line-length - Line length
MD013:
line_length: 99999
code_block_line_length: 99
tables: false

# MD024/no-duplicate-heading - Multiple headers with same content
MD024: false

# MD035/hr-style - Horizontal rule style
MD035:
style: "---"
23 changes: 23 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,26 @@ repos:
- flake8-bugbear
- flake8-builtins
- flake8-comprehensions

# tests with hallmark
- repo: local
hooks:
- id: mdformat
name: mdformat
entry: mdformat
language: python
types: [markdown]
files: pre-commit-test.md|CHANGELOG.md
stages: [pre-commit]
- repo: local
hooks:
- id: hallmark
name: hallmark
entry: hallmark
args: [fix, --verbose]
language: node
types: [markdown]
files: pre-commit-test.md|CHANGELOG.md
stages: [pre-commit]
additional_dependencies:
- hallmark@5.0.1
6 changes: 3 additions & 3 deletions .pre-commit-hook.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
- id: mdformat-hallmark
name: mdformat-hallmark
description: "CommonMark compliant Markdown formatter"
- id: mdformat
name: mdformat
description: "CommonMark and Hallmark compliant Markdown formatter"
entry: mdformat
language: python
types: [markdown]
Expand Down
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Changelog

All notable changes to this project are documented in this file.

The format is based on [Common Changelog](https://common-changelog.org/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.1.0] - 2025-08-24

### Added

- Add corrected table styling and information around [MarkdownStyleGuide](https://cirosantilli.com/markdown-style-guide/) used by hallmark ([#3](https://github.com/calgray/mdformat-hallmark/pull/3); Callan Gray)
- Add `.pre-commit-hook.yaml` and corrected publishing information ([#2](https://github.com/calgray/mdformat-hallmark/pull/2); Callan Gray)
- Add `hallmark` extension ([#1](https://github.com/calgray/mdformat-hallmark/pull/1); Callan Gray)

[0.1.0]: https://github.com/calgray/mdformat-hallmark/releases/tag/v0.1.0
44 changes: 32 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
# mdformat-hallmark

[![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>)
[![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/master/graph/badge.svg)](https://codecov.io/gh/calgray/mdformat-hallmark)
[![PyPI version](https://badge.fury.io/py/mdformat-hallmark.svg)](<https://badge.fury.io/py/mdformat-hallmark>)
[![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 compatibility with [hallmark](https://github.com/vweevers/hallmark) formatted Markdown and [Common Changelog](https://common-changelog.org/) that allows both formatters and linters to simultaneously pass for quality assurance.
An [mdformat](https://github.com/executablebooks/mdformat) plugin for compatibility with [hallmark](https://github.com/vweevers/hallmark), [MarkdownStyleGuide](https://cirosantilli.com/markdown-style-guide) and [Common Changelog](https://common-changelog.org/) that allows both formatters and linters to simultaneously pass for quality assurance.

## Features

- `hallmark` style reference formatting for changelogs with blank lines and sort by semantic version.
- `remark-preset-lint-markdown-style-guide` style compatibility.
- `hallmark` style formatting of definitions at end of the document with:
- blank line seperators
- keep label casing
- sort first by semantic version labels
- sort second by alphanumeric labels

## Install

Expand All @@ -27,7 +32,29 @@ After installing the plugin, run `mdformat` for Markdown files including Common
mdformat README.md CHANGELOG.md

# with extension explicitly required
mdformat --extensions hallmark README.md CHANGELOG.md
mdformat --extensions hallmark --extensions tables README.md CHANGELOG.md
```

## Pre-Commit Usage

```yaml
repos:
- repo: https://github.com/executablebooks/mdformat
rev: 0.7.19
hooks:
- id: mdformat
additional_dependencies:
- mdformat-hallmark
```

for latest developement version:

```yaml
repos:
- repo: https://github.com/calgray/mdformat-hallmark
rev: master
hooks:
- id: mdformat
```

## Development
Expand Down Expand Up @@ -85,10 +112,3 @@ flit publish
or trigger the GitHub Action job, by creating a release with a tag equal to the version, e.g. `v0.0.1`.

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-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-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
4 changes: 2 additions & 2 deletions mdformat_hallmark/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@

__version__ = "0.0.1"

from .hallmark_links_extension import HallmarkLinksExtension
from .hallmark_definitions_extension import HallmarkDefinitionsExtension

__all__ = ["HallmarkLinksExtension"]
__all__ = ["HallmarkDefinitionsExtension"]
129 changes: 129 additions & 0 deletions mdformat_hallmark/hallmark_definitions_extension.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
from contextlib import suppress
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

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


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


@dataclass
class Definition:
label: str
href: str
title: str | None


def _extract_references(src: str, is_changelog: bool) -> tuple[list[Definition], str]:
semver_defs: list[SemverDefinition] = []
remark_defs: list[Definition] = []
remove_spans: list[tuple[int, int]] = []

for m in _DEFINITION_RE.finditer(src):
label, href, title = m.groups()
semver = None
if is_changelog:
with suppress(InvalidVersion):
semver = Version(label)
semver_defs.append(SemverDefinition(semver, label, href, title))
if not semver:
# remove definition has no references
reference_re = rf"\[{re.escape(label)}\](?!:)"
if re.search(reference_re, src, flags=re.MULTILINE):
remark_defs.append(Definition(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
semver_defs.sort(key=lambda ref: ref.semver, reverse=True)

# sort remark references
remark_defs.sort(key=lambda ref: ref.label)

out_refs = [Definition(ref.label, ref.href, title) for ref in semver_defs]
out_refs.extend(remark_defs)
return out_refs, out_src


class HallmarkDefinitionsExtension(ParserExtensionInterface):
"""mdformat plugin extension to format definitions in remark and hallmark style.

* Hallmark detected Changelog versions used as definition labels are
rendered first by descending semantic-version order.
* Remark detected definitions are rendered after by alphanumeric order.
* All definition labels keep the original casing.
"""

@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 = {}

is_changelog = src.lstrip().startswith("# Changelog")

# extract definitions and remove from src
remark_defs, out_src = _extract_references(src, is_changelog)
defs = {
match.label: {"href": match.href, "title": match.title}
for match in remark_defs
}

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

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

return tokens

# patch parser
mdit.parse = new_parse

@staticmethod
def _render_remark_defs(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)

@staticmethod
def _render_hr(node: RenderTreeNode, context: RenderContext) -> str:
return "---"

RENDERERS = {"remark_defs": _render_remark_defs, "hr": _render_hr}
CHANGES_AST = True
103 changes: 0 additions & 103 deletions mdformat_hallmark/hallmark_links_extension.py

This file was deleted.

3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ keywords = ["mdformat", "markdown", "markdown-it"]
requires-python = ">=3.7"
dependencies = [
"mdformat >=0.7.0,<0.8.0",
"mdformat-tables >= 1.0.0",
"packaging >= 23.0",
]
dynamic = ["version", "description"]
Expand All @@ -34,7 +35,7 @@ dev = ["pre-commit"]
Homepage = "https://github.com/calgray/mdformat-hallmark"

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

[tool.flit.sdist]
include = []
Expand Down
Loading
Loading