gfm-math-lint 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- gfm_math_lint-0.1.0/.gitignore +33 -0
- gfm_math_lint-0.1.0/CHANGELOG.md +12 -0
- gfm_math_lint-0.1.0/LICENSE +21 -0
- gfm_math_lint-0.1.0/PKG-INFO +363 -0
- gfm_math_lint-0.1.0/README.md +333 -0
- gfm_math_lint-0.1.0/pyproject.toml +161 -0
- gfm_math_lint-0.1.0/src/gfm_math_lint/__init__.py +46 -0
- gfm_math_lint-0.1.0/src/gfm_math_lint/__main__.py +10 -0
- gfm_math_lint-0.1.0/src/gfm_math_lint/cli.py +425 -0
- gfm_math_lint-0.1.0/src/gfm_math_lint/comment.py +68 -0
- gfm_math_lint-0.1.0/src/gfm_math_lint/config.py +126 -0
- gfm_math_lint-0.1.0/src/gfm_math_lint/fixer.py +91 -0
- gfm_math_lint-0.1.0/src/gfm_math_lint/linter.py +279 -0
- gfm_math_lint-0.1.0/src/gfm_math_lint/py.typed +0 -0
- gfm_math_lint-0.1.0/src/gfm_math_lint/rules/__init__.py +113 -0
- gfm_math_lint-0.1.0/src/gfm_math_lint/rules/base.py +35 -0
- gfm_math_lint-0.1.0/src/gfm_math_lint/rules/code_rules.py +174 -0
- gfm_math_lint-0.1.0/src/gfm_math_lint/rules/diagram_rules.py +99 -0
- gfm_math_lint-0.1.0/src/gfm_math_lint/rules/image_rules.py +74 -0
- gfm_math_lint-0.1.0/src/gfm_math_lint/rules/math_rules.py +1123 -0
- gfm_math_lint-0.1.0/src/gfm_math_lint/rules/shell_rules.py +69 -0
- gfm_math_lint-0.1.0/src/gfm_math_lint/rules/table_rules.py +92 -0
- gfm_math_lint-0.1.0/src/gfm_math_lint/tokenizer.py +553 -0
- gfm_math_lint-0.1.0/src/gfm_math_lint/verifier.py +386 -0
- gfm_math_lint-0.1.0/tests/__init__.py +31 -0
- gfm_math_lint-0.1.0/tests/conftest.py +25 -0
- gfm_math_lint-0.1.0/tests/test_audit_regressions.py +261 -0
- gfm_math_lint-0.1.0/tests/test_cli.py +204 -0
- gfm_math_lint-0.1.0/tests/test_code_rules.py +46 -0
- gfm_math_lint-0.1.0/tests/test_comment.py +47 -0
- gfm_math_lint-0.1.0/tests/test_config.py +58 -0
- gfm_math_lint-0.1.0/tests/test_diagram_rules.py +34 -0
- gfm_math_lint-0.1.0/tests/test_fixer.py +49 -0
- gfm_math_lint-0.1.0/tests/test_github_api_oracle.py +100 -0
- gfm_math_lint-0.1.0/tests/test_image_rules.py +49 -0
- gfm_math_lint-0.1.0/tests/test_math_rules.py +296 -0
- gfm_math_lint-0.1.0/tests/test_shell_rules.py +31 -0
- gfm_math_lint-0.1.0/tests/test_suppression.py +68 -0
- gfm_math_lint-0.1.0/tests/test_table_rules.py +45 -0
- gfm_math_lint-0.1.0/tests/test_tokenizer.py +152 -0
- gfm_math_lint-0.1.0/tests/test_verifier.py +169 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
*.egg-info/
|
|
6
|
+
dist/
|
|
7
|
+
build/
|
|
8
|
+
|
|
9
|
+
# Environments
|
|
10
|
+
.venv/
|
|
11
|
+
.env
|
|
12
|
+
|
|
13
|
+
# Testing & Coverage
|
|
14
|
+
.coverage*
|
|
15
|
+
htmlcov/
|
|
16
|
+
.pytest_cache/
|
|
17
|
+
.ruff_cache/
|
|
18
|
+
.mypy_cache/
|
|
19
|
+
|
|
20
|
+
# IDE & OS
|
|
21
|
+
.idea/
|
|
22
|
+
.vscode/
|
|
23
|
+
.zed/
|
|
24
|
+
.DS_Store
|
|
25
|
+
|
|
26
|
+
# Temporary & Scratch
|
|
27
|
+
temp/
|
|
28
|
+
*.html
|
|
29
|
+
preview.html
|
|
30
|
+
.direnv/
|
|
31
|
+
*.pem
|
|
32
|
+
*.key
|
|
33
|
+
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [0.1.0] - 2026-09-30
|
|
9
|
+
|
|
10
|
+
- Initial release.
|
|
11
|
+
|
|
12
|
+
[0.1.0]: https://github.com/jacobhammond/gfm-math-lint/releases/tag/v0.1.0
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jacob Hammond
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: gfm-math-lint
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Linter and auto-fixer for GitHub-Flavored Markdown math (MathJax), tables, code fences, and Mermaid
|
|
5
|
+
Project-URL: Homepage, https://github.com/jacobhammond/gfm-math-lint
|
|
6
|
+
Project-URL: Documentation, https://github.com/jacobhammond/gfm-math-lint#readme
|
|
7
|
+
Project-URL: Repository, https://github.com/jacobhammond/gfm-math-lint
|
|
8
|
+
Project-URL: Issues, https://github.com/jacobhammond/gfm-math-lint/issues
|
|
9
|
+
Project-URL: Changelog, https://github.com/jacobhammond/gfm-math-lint/blob/main/CHANGELOG.md
|
|
10
|
+
Author-email: Jacob Hammond <114956625+jacobhammond@users.noreply.github.com>
|
|
11
|
+
License-Expression: MIT
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Keywords: gfm,katex,linter,markdown,mathjax,pre-commit
|
|
14
|
+
Classifier: Development Status :: 4 - Beta
|
|
15
|
+
Classifier: Environment :: Console
|
|
16
|
+
Classifier: Intended Audience :: Developers
|
|
17
|
+
Classifier: Programming Language :: Python
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
24
|
+
Classifier: Topic :: Documentation
|
|
25
|
+
Classifier: Topic :: Software Development
|
|
26
|
+
Classifier: Topic :: Utilities
|
|
27
|
+
Classifier: Typing :: Typed
|
|
28
|
+
Requires-Python: >=3.11
|
|
29
|
+
Description-Content-Type: text/markdown
|
|
30
|
+
|
|
31
|
+
# gfm-math-lint
|
|
32
|
+
|
|
33
|
+
[](https://github.com/jacobhammond/gfm-math-lint/actions/workflows/ci.yml)
|
|
34
|
+
[](https://pypi.org/project/gfm-math-lint/)
|
|
35
|
+
[](https://github.com/jacobhammond/gfm-math-lint/blob/main/LICENSE)
|
|
36
|
+
[](https://www.python.org/)
|
|
37
|
+
|
|
38
|
+
Zero-dependency, high-performance GitHub Flavored Markdown (GFM) linter and auto-fixer specializing in LaTeX/MathJax math syntax, CommonMark table escaping, fenced code nesting, Mermaid diagram boundaries, shell script expansion guards, and modern AVIF asset recommendations.
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Key Features
|
|
43
|
+
|
|
44
|
+
- **Pure Standard Library**: Zero third-party runtime dependencies. Runs instantly with Python 3.11+.
|
|
45
|
+
- **8-Zone Lexical State Machine**: High-fidelity tokenizer classifying document lines into `FRONTMATTER`, `FENCED_CODE`, `INLINE_CODE`, `DISPLAY_MATH`, `INLINE_MATH`, `TABLE`, `COMMENT`, and `PROSE`. Prevents false positives by ensuring rules only inspect their designated semantic zones.
|
|
46
|
+
- **Reverse-Offset Edit Algebra**: Deterministic bottom-up, right-to-left fix application guaranteeing idempotency: `fix(fix(c)) ≡ fix(c)`.
|
|
47
|
+
- **Unified Diff & In-Place Fixing**: Inspect exact changes in advance with `--diff` or auto-fix in-place with `fix` / `--fix`.
|
|
48
|
+
- **GitHub Rendering Oracle (`verify`)**: Directly audits documents against GitHub's live REST API (`POST /markdown` or `gh api /markdown`) to detect unrendered math in prose, HTML tag collisions, and broken table grids.
|
|
49
|
+
- **KaTeX Browser Preview with Zero Red Boxes**: Generates standalone HTML (`--html`) or opens a live browser preview (`--browser`) with automated HTML entity normalization (`&gt;` → `>`) and duplicate MathML clipboard suppression (`output: "html"`), delivering clean math preview across Firefox, Chrome, and Safari.
|
|
50
|
+
- **Safe GitHub Commenting**: Post markdown-rendered comments to PRs and issues via `gh` CLI with stdin streaming (`--body-file -`), preventing shell expansion corruption of LaTeX equations, with live `--preview` rendering.
|
|
51
|
+
- **Pre-commit Native**: Exit code contract (`0` clean, `1` violations/fixes, `2` error) designed specifically for CI/CD and git hook workflows.
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## Installation
|
|
57
|
+
|
|
58
|
+
### With `uv` (Recommended)
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
uv tool install gfm-math-lint
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### With `pip`
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
pip install gfm-math-lint
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## Usage
|
|
73
|
+
|
|
74
|
+
### 1. Checking & Fixing Markdown Files
|
|
75
|
+
|
|
76
|
+
Check all markdown files in a directory or individually, preview diffs, and automatically fix violations:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
# Check files for violations
|
|
80
|
+
gfm-math-lint check docs/ README.md
|
|
81
|
+
|
|
82
|
+
# Output violations in GitHub Actions workflow command format (::warning / ::error)
|
|
83
|
+
gfm-math-lint check --format github docs/
|
|
84
|
+
|
|
85
|
+
# Specify an explicit configuration file
|
|
86
|
+
gfm-math-lint check --config pyproject.toml docs/
|
|
87
|
+
|
|
88
|
+
# Preview automated fixes as a unified diff without modifying files
|
|
89
|
+
gfm-math-lint check --diff docs/
|
|
90
|
+
|
|
91
|
+
# Automatically fix violations in-place using the 'fix' shortcut
|
|
92
|
+
gfm-math-lint fix docs/ README.md
|
|
93
|
+
|
|
94
|
+
# Or equivalently via the check subcommand:
|
|
95
|
+
gfm-math-lint check --fix docs/ README.md
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### 2. Scanning Shell Scripts & Workflows
|
|
99
|
+
|
|
100
|
+
Scan bash/sh scripts and GitHub Actions workflow files for unsafe double-quoted body arguments (`-b "..."` or `--body "..."`) in `gh pr comment` / `gh issue comment` commands that expose LaTeX `$x$` expressions to shell expansion:
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
gfm-math-lint check-shell .github/workflows/ scripts/
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### 3. Safe GitHub Comment Posting
|
|
107
|
+
|
|
108
|
+
Post comments safely without shell expansion or argument size limits by piping content via stdin directly to `gh`:
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
# Preview rendered markdown via GitHub REST API (POST /markdown)
|
|
112
|
+
gfm-math-lint comment --preview --file report.md
|
|
113
|
+
|
|
114
|
+
# Post safely to a pull request
|
|
115
|
+
gfm-math-lint comment --pr 42 --file report.md
|
|
116
|
+
|
|
117
|
+
# Post safely to an issue
|
|
118
|
+
gfm-math-lint comment --issue 108 --file notes.md
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### 4. Semantic Verification & Browser Preview (GitHub API Oracle)
|
|
122
|
+
|
|
123
|
+
Verify your documents directly against GitHub's live REST API markdown rendering oracle (`POST /markdown` or `gh api /markdown`) to catch unrendered math, HTML tag collisions, or table fracturing:
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
# Verify all markdown files against GitHub API oracle
|
|
127
|
+
gfm-math-lint verify docs/ README.md
|
|
128
|
+
|
|
129
|
+
# Authenticate with an explicit GitHub token (or uses $GITHUB_TOKEN / $GH_TOKEN)
|
|
130
|
+
gfm-math-lint verify --token "$GITHUB_TOKEN" docs/
|
|
131
|
+
|
|
132
|
+
# Verify and export full rendered HTML preview
|
|
133
|
+
gfm-math-lint verify --html preview.html docs/specification.md
|
|
134
|
+
|
|
135
|
+
# Verify and immediately launch KaTeX-rendered preview in local web browser
|
|
136
|
+
# (Features automated entity normalization & MathML duplicate clipboard suppression)
|
|
137
|
+
gfm-math-lint verify --browser docs/specification.md
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## Rule Catalog
|
|
143
|
+
|
|
144
|
+
### Math & LaTeX Rules (`GFM-M**`)
|
|
145
|
+
|
|
146
|
+
| Rule ID | Name | Severity | Auto-Fix | Description |
|
|
147
|
+
| :--- | :--- | :---: | :---: | :--- |
|
|
148
|
+
| `GFM-M01` | `math-code-fence-to-display` | Error | Yes | Convert `` ```math `` code fences to standalone `$$` display math blocks. |
|
|
149
|
+
| `GFM-M02` | `display-math-isolation` | Error | Yes | Enforce display math `$$` opening/closing delimiters sit on isolated lines with blank line padding and no internal blanks. |
|
|
150
|
+
| `GFM-M03` | `text-mode-underscore-escape` | Error | Yes | Enforce double-escaped underscores (`\\_`) inside text-mode LaTeX macros (`\text{...}`, `\mathrm{...}`). |
|
|
151
|
+
| `GFM-M04` | `inline-math-whitespace` | Error | Yes | Strip leading and trailing inner whitespace from inline math delimiters (`$ x $` → `$x$`). |
|
|
152
|
+
| `GFM-M05` | `curly-brace-escape` | Error | Yes | Convert `\{` and `\}` delimiters in math expressions to `\lbrace` and `\rbrace` (e.g. `\left\{` → `\left\lbrace`). |
|
|
153
|
+
| `GFM-M06` | `relational-operator-collision` | Error | Yes | Encode relational operator (`<` to `\lt`) before letters in math to avoid HTML parser entity consumption. |
|
|
154
|
+
| `GFM-M07` | `asterisk-emphasis-collision` | Error | Yes | Convert raw asterisks (`*`) in superscripts, subscripts, and expressions to `\ast` to prevent GFM emphasis stripping. |
|
|
155
|
+
| `GFM-M08` | `disallowed-macro-conversion` | Error | Yes | Replace `\operatorname{...}` with `\mathop{\mathrm{...}}` and `\operatorname*{...}` with `\mathop{\mathrm{...}}\limits` to preserve operator limits. |
|
|
156
|
+
| `GFM-M09` | `align-environment` | Error | Yes | Replace unsupported `align`/`align*` environments with KaTeX `aligned` environment. |
|
|
157
|
+
| `GFM-M10` | `currency-dollar-sign` | Warning | Yes | Escape currency dollar signs on prose lines containing multiple dollars or coexisting with math (`$10 to $20` → `\$10 to \$20`). |
|
|
158
|
+
| `GFM-M11` | `latex-delimiters-conversion` | Error | Yes | Convert standard LaTeX display delimiters (`\[ ... \]`) to `$$` and inline delimiters (`\( ... \)`) to `$`. |
|
|
159
|
+
| `GFM-M12` | `inline-math-underscore-collision` | Error | Yes | Escape unescaped underscores (`\_`) in inline and single-line display dollar math spans to prevent CommonMark emphasis collision (`<em>...</em>`). |
|
|
160
|
+
| `GFM-M13` | `math-delimiter-boundaries` | Error | Yes | Enforce GitHub-compliant math delimiter boundaries: combine hyphenated math pairs (`$A$–$B$` → `$A\text{–}B$`), separate glued units (`$15\ ^\circ$C` → `$15\ ^\circ$ C`), and prevent bracket collisions (`$G(L)$)` → `$G(L)$ )`). |
|
|
161
|
+
|
|
162
|
+
### Table Rules (`GFM-T**`)
|
|
163
|
+
|
|
164
|
+
| Rule ID | Name | Severity | Auto-Fix | Description |
|
|
165
|
+
| :--- | :--- | :---: | :---: | :--- |
|
|
166
|
+
| `GFM-T01` | `table-pipe-collision` | Error | Yes | Escape raw pipes in table math spans (`|`) and code spans (`\|`) to prevent column fracturing. |
|
|
167
|
+
|
|
168
|
+
### Code & Fence Nesting Rules (`GFM-C**`)
|
|
169
|
+
|
|
170
|
+
| Rule ID | Name | Severity | Auto-Fix | Description |
|
|
171
|
+
| :--- | :--- | :---: | :---: | :--- |
|
|
172
|
+
| `GFM-C01` | `nested-fence-length` | Error | Yes | Ensure outer fenced code blocks enclosing inner backtick fences use strictly longer backtick runs per CommonMark §4.5. |
|
|
173
|
+
| `GFM-C02` | `inline-code-backticks` | Error | Yes | Ensure inline code spans containing backticks use multi-backtick delimiters (`` `...` ``) per CommonMark §6.3. |
|
|
174
|
+
|
|
175
|
+
### Diagram Rules (`GFM-D**`)
|
|
176
|
+
|
|
177
|
+
| Rule ID | Name | Severity | Auto-Fix | Description |
|
|
178
|
+
| :--- | :--- | :---: | :---: | :--- |
|
|
179
|
+
| `GFM-D01` | `mermaid-identifier` | Warning | No | Enforce alphanumeric IDs for Mermaid subgraphs and multi-word node labels with explicit quoted titles. |
|
|
180
|
+
| `GFM-D02` | `code-fence-symmetry` | Error | Yes | Require identical opening and closing fence lengths and characters for code blocks. |
|
|
181
|
+
|
|
182
|
+
### Shell Scanner Rules (`GFM-S**`)
|
|
183
|
+
|
|
184
|
+
| Rule ID | Name | Severity | Auto-Fix | Description |
|
|
185
|
+
| :--- | :--- | :---: | :---: | :--- |
|
|
186
|
+
| `GFM-S01` | `unsafe-shell-expansion` | Error | No | Scan shell scripts and workflows for unsafe double-quoted body arguments in `gh` CLI invocations. |
|
|
187
|
+
|
|
188
|
+
### Asset & Image Rules (`GFM-I**`)
|
|
189
|
+
|
|
190
|
+
| Rule ID | Name | Severity | Auto-Fix | Description |
|
|
191
|
+
| :--- | :--- | :---: | :---: | :--- |
|
|
192
|
+
| `GFM-I01` | `image-avif-format` | Warning | No | Enforce modern `.avif` format for markdown images and figures. |
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
## Architectural Rationale & Standards Alignment
|
|
197
|
+
|
|
198
|
+
`gfm-math-lint` is engineered around the multi-stage parsing pipeline implemented by GitHub's rendering engine (`cmark-gfm` followed by client-side MathJax). Understanding this compilation model clarifies why these rules exist, why specific conversions are preferred, and how they align with authoritative web standards.
|
|
199
|
+
|
|
200
|
+
### 1. The Multi-Stage GFM Parsing Pipeline
|
|
201
|
+
|
|
202
|
+
When GitHub processes a Markdown document containing mathematical formulas, it executes stages in strict sequence:
|
|
203
|
+
1. **CommonMark / GFM Inline Parser (`cmark-gfm`)**: Parses block structure, escapes, links, code spans, HTML tags, and emphasis.
|
|
204
|
+
2. **HTML Sanitization**: Filters and scrubs disallowed HTML elements and attributes.
|
|
205
|
+
3. **Client-Side Math Engine (MathJax)**: Identifies delimited math zones (`$...$` and `$$...$$`) and compiles LaTeX syntax into HTML/MathML DOM nodes.
|
|
206
|
+
|
|
207
|
+
Because **Markdown parsing executes before LaTeX parsing**, standard TeX escaping conventions can be intercepted and destroyed by CommonMark.
|
|
208
|
+
|
|
209
|
+
### 2. CommonMark §2.4 Backslash Escapes
|
|
210
|
+
|
|
211
|
+
Per [CommonMark Spec 0.30 §2.4 (Backslash escapes)](https://spec.commonmark.org/0.30/#backslash-escapes):
|
|
212
|
+
> Any ASCII punctuation character may be backslash-escaped:
|
|
213
|
+
> `! " # $ % & ' ( ) * + , - . / : ; < = > ? @ [ \ ] ^ _ ` { | } ~`
|
|
214
|
+
> Backslashes before other characters are treated as literal backslashes.
|
|
215
|
+
|
|
216
|
+
This rule directly dictates the design of several core rules:
|
|
217
|
+
- **`GFM-M03` (`text-mode-underscore-escape`)**: In LaTeX, an underscore inside text mode is escaped as `\_` (e.g. `\text{ave\_shift}`). However, because `_` is one of CommonMark's 32 ASCII punctuation characters, `cmark-gfm` strips the backslash during stage 1. KaTeX receives `\text{ave_shift}`, triggering a fatal syntax error. `GFM-M03` enforces double-escaped backslashes (`\\_`), ensuring one backslash survives stage 1 to reach KaTeX as `\_`.
|
|
218
|
+
- **`GFM-M05` (`curly-brace-escape`)**: In TeX, dynamic curly braces are written `\left\{ ... \right\}`. Because `{` and `}` are CommonMark ASCII punctuation characters, the backslashes are consumed during stage 1. KaTeX receives `\left{`, throwing `Missing or unrecognized delimiter for \left`. `GFM-M05` converts `\{` and `\}` to `\lbrace` and `\rbrace` (e.g. `\left\lbrace ... \right\rbrace`), which do not rely on fragile punctuation escapes.
|
|
219
|
+
- **CommonMark §6.1 / §6.3 Immunity of Code Spans**: Per [CommonMark Spec 0.30 §6.1](https://spec.commonmark.org/0.30/#code-spans), *"Backslash escapes do not work in code spans."* Inside backticks, backslashes are preserved literally. That is why backtick math (`` $`\text{ave_shift}`$ ``) is natively immune to backslash stripping, whereas standard dollar math requires double escaping.
|
|
220
|
+
|
|
221
|
+
### 3. Delimiter Portability & Standards Alignment
|
|
222
|
+
|
|
223
|
+
#### `GFM-M01`: Why Convert `` ```math `` to `$$`?
|
|
224
|
+
[GitHub's official guide on writing mathematical expressions](https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/writing-mathematical-expressions) documents ```` ```math ```` code fences as an alternative display block syntax.
|
|
225
|
+
However, `gfm-math-lint` converts ```` ```math ```` to canonical `$$\n...\n$$` blocks for cross-platform portability:
|
|
226
|
+
- Outside GitHub's proprietary web view, tools such as **VS Code Markdown Preview, PyCharm, Obsidian, Jupyter Notebooks, Pandoc, MkDocs (Material), and Sphinx** do *not* recognize ```` ```math ```` as math blocks, rendering them as unrendered code fences.
|
|
227
|
+
- Converting to standalone `$$` ensures 100% universal rendering across all major Markdown and LaTeX tooling while rendering flawlessly on GitHub.
|
|
228
|
+
|
|
229
|
+
#### `GFM-M11`: Why Convert `\[ ... \]` and `\( ... \)`?
|
|
230
|
+
Standard LaTeX documents written for paper publishing or ArXiv use `\[ ... \]` for display math and `\( ... \)` for inline math.
|
|
231
|
+
- GitHub's Markdown parser does **not** recognize `\[ ... \]` or `\( ... \)` as math delimiters.
|
|
232
|
+
- Furthermore, per CommonMark §2.4, `[` and `(` are ASCII punctuation characters. When `cmark-gfm` runs, it strips the backslashes, leaving raw unrendered text (e.g., `[ x = 1 ]` or `( x + y )`).
|
|
233
|
+
- `GFM-M11` converts standard LaTeX delimiters to GitHub-compatible delimiters (`$$` and `$`).
|
|
234
|
+
|
|
235
|
+
#### `GFM-M02`: Display Math Blank Line Isolation
|
|
236
|
+
GitHub Docs notes that display blocks can be preceded by a backslash newline. However, `gfm-math-lint` enforces full blank line padding above and below `$$` display blocks:
|
|
237
|
+
- CommonMark §4.5 (Lazy Paragraph Continuation) allows lines following a paragraph to be grouped into the same block unless separated by a blank line.
|
|
238
|
+
- Enforcing blank lines guarantees clear AST separation and satisfies common Markdown linters such as `markdownlint` (rule MD031).
|
|
239
|
+
|
|
240
|
+
#### `GFM-M10`: Currency Dollar Signs (`\$` vs `<span>$</span>`)
|
|
241
|
+
GitHub Docs mentions using `<span>$</span>` to prevent literal dollar signs from triggering math rendering.
|
|
242
|
+
`gfm-math-lint` deliberately enforces backslash escaping (`\$` per CommonMark §2.4) instead:
|
|
243
|
+
- `\$` maintains pure, readable Markdown without polluting documents with raw HTML tags.
|
|
244
|
+
- Both GitHub and CommonMark officially specify `\$` as an escaped literal character.
|
|
245
|
+
|
|
246
|
+
#### `GFM-M13`: GitHub Delimiter Boundary & Punctuation Constraints
|
|
247
|
+
GitHub's inline math parser enforces strict boundary conditions on opening and closing `$` delimiters:
|
|
248
|
+
- **Opening `$` Delimiter Boundary:** An opening `$` is only parsed as math if preceded by whitespace, start-of-line, or an opening parenthesis `(`. Preceding punctuation characters such as hyphens (`-`), en-dashes (`–`), em-dashes (`—`), slashes (`/`), or brackets (`[`) cause GitHub to reject the `$` as math. Expressions like `$A$–$B$` leave `–$B$` as plain text. `GFM-M13` combines compound expressions into `$A\text{–}B$`.
|
|
249
|
+
- **Closing `$` Delimiter Boundary:** A closing `$` is rejected if immediately followed by an ASCII letter or digit (`[a-zA-Z0-9]`). Units glued to math expressions (`$15\ ^\circ$C`, `$0.759$dB`) fail to close and render as literal text. `GFM-M13` automatically separates trailing alphanumeric units with a space (`$15\ ^\circ$ C`, `$0.759$ dB`).
|
|
250
|
+
- **Parenthesis Collisions:** In nested parenthetical prose like `($A = B$ instead of $G(L)$)`, the closing `$` directly preceding `)` collides with bracket matching in `cmark-gfm`. `GFM-M13` inserts a space (`$G(L)$ )`) ensuring flawless delimiter recognition.
|
|
251
|
+
|
|
252
|
+
---
|
|
253
|
+
|
|
254
|
+
## Configuration
|
|
255
|
+
|
|
256
|
+
Configure `gfm-math-lint` in your project's `pyproject.toml` under `[tool.gfm-math-lint]`:
|
|
257
|
+
|
|
258
|
+
```toml
|
|
259
|
+
[tool.gfm-math-lint]
|
|
260
|
+
# Files or glob patterns to exclude from linting
|
|
261
|
+
exclude = [
|
|
262
|
+
"docs/archive/*",
|
|
263
|
+
"vendor/**",
|
|
264
|
+
]
|
|
265
|
+
|
|
266
|
+
# Globally ignored rule IDs
|
|
267
|
+
ignore = [
|
|
268
|
+
"GFM-I01", # Allow PNG/JPEG images without AVIF conversion warning
|
|
269
|
+
]
|
|
270
|
+
|
|
271
|
+
# Per-file rule ignores
|
|
272
|
+
[tool.gfm-math-lint.per-file-ignores]
|
|
273
|
+
"docs/legacy/*.md" = ["GFM-M08", "GFM-M10"]
|
|
274
|
+
"docs/math_guide.md" = ["GFM-M05"]
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
---
|
|
278
|
+
|
|
279
|
+
## Inline Rule Suppression
|
|
280
|
+
|
|
281
|
+
Suppress rules directly in Markdown files using HTML comments:
|
|
282
|
+
|
|
283
|
+
### Multi-line Block Suppression
|
|
284
|
+
|
|
285
|
+
```markdown
|
|
286
|
+
<!-- gfm-math-lint-disable GFM-M02, GFM-M04 -->
|
|
287
|
+
$$
|
|
288
|
+
E = mc^2 \\
|
|
289
|
+
$$
|
|
290
|
+
<!-- gfm-math-lint-enable GFM-M02, GFM-M04 -->
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
### Next-Line Suppression
|
|
294
|
+
|
|
295
|
+
```markdown
|
|
296
|
+
<!-- gfm-math-lint-disable-next-line GFM-M10 -->
|
|
297
|
+
The price varied between $10 and $20 per unit.
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
Omit the rule IDs to disable or enable all rules for the target scope:
|
|
301
|
+
|
|
302
|
+
```markdown
|
|
303
|
+
<!-- gfm-math-lint-disable-next-line -->
|
|
304
|
+
| Metric | $||x|| < 1e-9$ |
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
---
|
|
308
|
+
|
|
309
|
+
## Pre-Commit Hook Integration
|
|
310
|
+
|
|
311
|
+
### Using `gfm-math-lint` in Your Repository
|
|
312
|
+
|
|
313
|
+
Add `gfm-math-lint` to your `.pre-commit-config.yaml` to validate equations and guard shell scripts:
|
|
314
|
+
|
|
315
|
+
```yaml
|
|
316
|
+
repos:
|
|
317
|
+
- repo: https://github.com/jacobhammond/gfm-math-lint
|
|
318
|
+
rev: v0.1.0
|
|
319
|
+
hooks:
|
|
320
|
+
# Validates math, table, code fence, and boundary issues
|
|
321
|
+
- id: gfm-math-lint
|
|
322
|
+
# Guards against unsafe shell expansions in CI workflows and scripts
|
|
323
|
+
- id: gfm-shell-lint
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
---
|
|
327
|
+
|
|
328
|
+
## Contributing & Development
|
|
329
|
+
|
|
330
|
+
```bash
|
|
331
|
+
# Clone the repository
|
|
332
|
+
git clone https://github.com/jacobhammond/gfm-math-lint.git
|
|
333
|
+
cd gfm-math-lint
|
|
334
|
+
|
|
335
|
+
# Install development virtualenv and tools with uv
|
|
336
|
+
uv sync
|
|
337
|
+
|
|
338
|
+
# Install local pre-commit git hooks
|
|
339
|
+
uv run pre-commit install
|
|
340
|
+
|
|
341
|
+
# Run all pre-commit hooks across the repository
|
|
342
|
+
uv run --no-sync pre-commit run --all-files
|
|
343
|
+
|
|
344
|
+
# Run the test suite with coverage
|
|
345
|
+
uv run pytest
|
|
346
|
+
|
|
347
|
+
# Run linting and formatting checks
|
|
348
|
+
uv run ruff check .
|
|
349
|
+
uv run ruff format --check .
|
|
350
|
+
|
|
351
|
+
# Run strict type checking
|
|
352
|
+
uv run mypy src tests --strict
|
|
353
|
+
|
|
354
|
+
# Run self-checks on repository documents and scripts
|
|
355
|
+
uv run gfm-math-lint check README.md CHANGELOG.md CONTRIBUTING.md docs/ .github/
|
|
356
|
+
uv run gfm-math-lint check-shell .github/
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
---
|
|
360
|
+
|
|
361
|
+
## License
|
|
362
|
+
|
|
363
|
+
MIT License. See [LICENSE](https://github.com/jacobhammond/gfm-math-lint/blob/main/LICENSE) for details.
|