├── .flake8 ├── .github ├── dependabot.yml ├── issue_template.md ├── pull_request_template.md └── workflows │ └── build.yml ├── .gitignore ├── .mailmap ├── .readthedocs.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── docs ├── _static │ ├── custom.css │ └── omnilib.png ├── _templates │ ├── badges.html │ └── omnilib.html ├── changelog.rst ├── conf.py ├── example.md ├── included.md └── index.md ├── makefile ├── pyproject.toml └── sphinx_mdinclude ├── __init__.py ├── __version__.py ├── parse.py ├── render.py ├── sphinx.py └── tests ├── __init__.py ├── __main__.py ├── test.md ├── test.rst ├── test_renderer.py └── test_smoke.py /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | ignore = 3 | # mccabe complexity 4 | C901 5 | 6 | # covered by black/usort 7 | E1 8 | E2 9 | E3 10 | E4 11 | E501 12 | W503 13 | max-line-length = 88 14 | per-file-ignores = 15 | __init__.py: F401 16 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "monthly" 7 | - package-ecosystem: "pip" 8 | directory: "/" 9 | schedule: 10 | interval: "monthly" 11 | -------------------------------------------------------------------------------- /.github/issue_template.md: -------------------------------------------------------------------------------- 1 | ### Description 2 | 3 | 4 | 5 | ### Details 6 | 7 | * OS: 8 | * Python version: 9 | * Project version: 10 | * Can you repro on main? 11 | * Can you repro in a clean virtualenv? 12 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ### Description 2 | 3 | 4 | 5 | Fixes: # 6 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | on: 3 | push: 4 | branches: 5 | - main 6 | tags: 7 | - v* 8 | pull_request: 9 | 10 | jobs: 11 | sphinx-mdinclude: 12 | runs-on: ${{ matrix.os }} 13 | strategy: 14 | fail-fast: false 15 | matrix: 16 | python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] 17 | os: [macOS-latest, ubuntu-latest, windows-latest] 18 | 19 | steps: 20 | - name: Checkout 21 | uses: actions/checkout@v4 22 | - name: Set Up Python ${{ matrix.python-version }} 23 | uses: actions/setup-python@v5 24 | with: 25 | python-version: ${{ matrix.python-version }} 26 | - name: Install 27 | run: make install 28 | - name: Test 29 | run: make test 30 | - name: Lint 31 | run: make lint 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | html/ 2 | 3 | # Byte-compiled / optimized / DLL files 4 | __pycache__/ 5 | *.py[cod] 6 | *$py.class 7 | 8 | # C extensions 9 | *.so 10 | 11 | # Distribution / packaging 12 | .Python 13 | env/ 14 | build/ 15 | develop-eggs/ 16 | dist/ 17 | downloads/ 18 | eggs/ 19 | .eggs/ 20 | lib/ 21 | lib64/ 22 | parts/ 23 | sdist/ 24 | var/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | 29 | # Virtualenv 30 | bin/ 31 | lib/ 32 | lib64 33 | pyvenv.cfg 34 | .venv 35 | 36 | # PyInstaller 37 | # Usually these files are written by a python script from a template 38 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 39 | *.manifest 40 | *.spec 41 | 42 | # Installer logs 43 | pip-log.txt 44 | pip-delete-this-directory.txt 45 | 46 | # Unit test / coverage reports 47 | htmlcov/ 48 | .tox/ 49 | .coverage 50 | .coverage.* 51 | .cache 52 | .mypy_cache 53 | nosetests.xml 54 | coverage.xml 55 | *,cover 56 | .hypothesis/ 57 | 58 | # Translations 59 | *.mo 60 | *.pot 61 | 62 | # Django stuff: 63 | *.log 64 | 65 | # Sphinx documentation 66 | docs/_build/ 67 | 68 | # PyBuilder 69 | target/ 70 | 71 | # pyenv python configuration file 72 | .python-version 73 | 74 | ###### direnv ###### 75 | .direnv 76 | .envrc 77 | 78 | ###### zsh-autoenv ###### 79 | .autoenv.zsh 80 | .autoenv_leave.zsh 81 | 82 | ### project specific ignore files 83 | wiki 84 | tmp 85 | pytest.xml 86 | chromedriver 87 | 88 | # node files 89 | node_modules 90 | 91 | # phantomjs 92 | ghostdriver.log 93 | 94 | # vim 95 | **.swp 96 | 97 | # nox 98 | .nox/* 99 | -------------------------------------------------------------------------------- /.mailmap: -------------------------------------------------------------------------------- 1 | Amethyst Reese 2 | -------------------------------------------------------------------------------- /.readthedocs.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | sphinx: 3 | configuration: docs/conf.py 4 | build: 5 | os: ubuntu-22.04 6 | tools: 7 | python: "3.10" 8 | python: 9 | install: 10 | - method: pip 11 | path: . 12 | extra_requirements: 13 | - dev 14 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | sphinx-mdinclude 2 | ================ 3 | 4 | [![Generated by attribution][attribution-badge]][attribution-url] 5 | 6 | 7 | v0.6.2 8 | ------ 9 | 10 | Maintenance release 11 | 12 | - Fix minimum docutils dependency requirement (#80) 13 | 14 | ```text 15 | $ git shortlog -s v0.6.1...v0.6.2 16 | 2 Amethyst Reese 17 | 1 Emil van der Westhuizen 18 | ``` 19 | 20 | 21 | v0.6.1 22 | ------ 23 | 24 | Bugfix release 25 | 26 | - Fix rendering of codespans within link text (#68) 27 | - Added explicit dependency on Sphinx (#70) 28 | 29 | ```text 30 | $ git shortlog -s v0.6.0...v0.6.1 31 | 2 Amethyst Reese 32 | 1 Peter Sobot 33 | 5 dependabot[bot] 34 | ``` 35 | 36 | 37 | v0.6.0 38 | ------ 39 | 40 | Feature release 41 | 42 | - Added support for mistune v3, dropped support for mistune v2 (#22, #46) 43 | - Added strict type annotations and type checking (#62) 44 | - Cleaned up legacy implementation from m2r (#58) 45 | 46 | Many thanks to Julian Gilbey for the help upgrading to mistune v3! 47 | 48 | ```text 49 | $ git shortlog -s v0.5.4...v0.6.0 50 | 3 Amethyst Reese 51 | 1 Julian Gilbey 52 | 3 dependabot[bot] 53 | ``` 54 | 55 | 56 | v0.5.4 57 | ------ 58 | 59 | Maintenance release 60 | 61 | - Better testing of sphinx extension and docutils directive (#48) 62 | - Support fixes for docutils v0.21 (#55) 63 | - Add support for Python 3.11 and 3.12 (#25, #39) 64 | - Drop support for Python 3.7 (#25) 65 | 66 | ```text 67 | $ git shortlog -s v0.5.3...v0.5.4 68 | 8 Amethyst Reese 69 | 16 dependabot[bot] 70 | ``` 71 | 72 | 73 | v0.5.3 74 | ------ 75 | 76 | Bugfix release 77 | 78 | - Fix deprecation warnings from docutils (#11) 79 | - Fix dependency ranges and validate compatibility 80 | - Pin to mistune `2.x` in preparation for breaking changes in `3.0` (#15) 81 | - Package migrated to the Omnilib Project 82 | 83 | ```text 84 | $ git shortlog -s v0.5.2...v0.5.3 85 | 5 Amethyst Reese 86 | 2 Patrick Hoefler 87 | 3 Samuel Giffard 88 | ``` 89 | 90 | 91 | v0.5.2 92 | ------ 93 | 94 | Maintenance release 95 | 96 | - Add support for docutils `0.19` (#8) 97 | - Enable building docs without installing package (#6) 98 | 99 | ```text 100 | $ git shortlog -s v0.5.1...v0.5.2 101 | 6 Amethyst Reese 102 | ``` 103 | 104 | 105 | v0.5.1 106 | ------ 107 | 108 | Bugfix release 109 | 110 | - Fix autolinks (#3, #2) 111 | 112 | ```text 113 | $ git shortlog -s v0.5.0...v0.5.1 114 | 3 Amethyst Reese 115 | 2 Tim Hatch 116 | ``` 117 | 118 | 119 | v0.5.0 120 | ------ 121 | 122 | Feature release 123 | 124 | - Support for mistune 2.0 with major refactoring of codebase 125 | - Removal of sphinx configuration options with better defaults 126 | 127 | ```text 128 | $ git shortlog -s v0.5.0b1...v0.5.0 129 | 3 Amethyst Reese 130 | ``` 131 | 132 | 133 | v0.5.0b1 134 | -------- 135 | 136 | Feature release 137 | 138 | - Support for mistune 2.0 with major refactoring of codebase 139 | - Removal of sphinx configuration options with better defaults 140 | 141 | ```text 142 | $ git shortlog -s v0.4.0...v0.5.0b1 143 | 7 Amethyst Reese 144 | ``` 145 | 146 | 147 | v0.4.0 148 | ------ 149 | 150 | Feature release 151 | 152 | - Forked project from m2r2 153 | - Renamed package to `sphinx-mdinclude` 154 | - Removes CLI to focus on Sphinx extension 155 | - Remove mermaid extension support 156 | - Overhauled documentation with less boilerplate 157 | - Move tests into `sphinx_mdinclude` namespace 158 | - Pin dependencies to mistune<1.0 and docutils<0.18 159 | - Build using flit instead of setuptools 160 | - Formatted with latest black and µsort 161 | 162 | ```text 163 | $ git shortlog -s v0.4.0b1...v0.4.0 164 | 4 Amethyst Reese 165 | 1 dependabot[bot] 166 | ``` 167 | 168 | 169 | v0.4.0b1 170 | -------- 171 | 172 | Beta release 173 | 174 | * Forked project from m2r2 175 | * Renamed package to `sphinx-mdinclude` 176 | * Removes CLI to focus on Sphinx extension 177 | * Overhauled documentation with less boilerplate 178 | * Move tests into `sphinx_mdinclude` namespace 179 | * Pin dependencies to mistune<1.0 and docutils<0.18 180 | * Build using flit instead of setuptools 181 | * Formatted with latest black and µsort 182 | 183 | ```text 184 | $ git shortlog -s v0.2.7...v0.4.0b1 185 | 18 Amethyst Reese 186 | 10 CrossNox 187 | 1 Ezequiel Rosas 188 | 1 illes 189 | 1 kalvdans 190 | ``` 191 | 192 | 193 | v0.3.1 194 | ------ 195 | 196 | * Fix argparse for python3.10 197 | 198 | ```text 199 | $ git shortlog -s v0.3.0...v0.3.1 200 | 1 Bas Nijholt 201 | 13 CrossNox 202 | 1 Daniel Caballero 203 | 1 illes 204 | 1 kalvdans 205 | ``` 206 | 207 | 208 | v0.3.0 209 | ------ 210 | 211 | * Add support for mermaid code 212 | * Change bump for bump2version 213 | 214 | ```text 215 | $ git shortlog -s v0.2.8...v0.3.0 216 | 1 CrossNox 217 | ``` 218 | 219 | 220 | v0.2.8 221 | ------ 222 | 223 | * Fix bug that made multiple inline mathematical expressions fail to render 224 | 225 | ```text 226 | $ git shortlog -s v0.2.5...v0.2.8 227 | 1 CrossNox 228 | ``` 229 | 230 | 231 | v0.2.7 232 | ------ 233 | 234 | * Add official python3.9 support 235 | * Fix classifiers 236 | 237 | ```text 238 | $ git shortlog -s v0.2.6...v0.2.7 239 | 2 CrossNox 240 | ``` 241 | 242 | 243 | v0.2.6 244 | ------ 245 | 246 | * Fix error for Sphinx3.3.0 247 | 248 | ```text 249 | $ git shortlog -s v0.2.5...v0.2.6 250 | 1 Bas Nijholt 251 | 1 CrossNox 252 | 1 Daniel Caballero 253 | ``` 254 | 255 | 256 | v0.2.5 257 | ------ 258 | 259 | * Update repo name in every reference 260 | 261 | ```text 262 | $ git shortlog -s v0.2.4...v0.2.5 263 | 1 CrossNox 264 | ``` 265 | 266 | 267 | v0.2.4 268 | ------ 269 | 270 | * Central versioning 271 | * Add bump 272 | 273 | ```text 274 | $ git shortlog -s v0.2.3...v0.2.4 275 | 4 CrossNox 276 | ``` 277 | 278 | 279 | v0.2.3 280 | ------ 281 | 282 | * Fix https://github.com/miyakogi/m2r/issues/51 283 | * Change `tox` for `nox` 284 | * Add `pre-commit` hooks and fix styling 285 | 286 | ```text 287 | $ git shortlog -s v0.2.1...v0.2.3 288 | 9 CrossNox 289 | ``` 290 | 291 | 292 | v0.2.1 293 | ------ 294 | 295 | * Add `--disable-inline-math` and `m2r_disable_inline_math` sphinx option 296 | 297 | ```text 298 | $ git shortlog -s v0.2.0...v0.2.1 299 | 3 Morgan Willcock 300 | 1 kyluca 301 | 9 miyakogi 302 | ``` 303 | 304 | 305 | v0.2.0 306 | ------ 307 | 308 | * Add `start-line` and `end-line` option to `mdinclude` directive 309 | * Add `anonymous_references` option ([#26](https://github.com/miyakogi/m2r/pull/26)) 310 | 311 | ```text 312 | $ git shortlog -s v0.1.15...v0.2.0 313 | 3 Jake VanderPlas 314 | 11 miyakogi 315 | ``` 316 | 317 | 318 | v0.1.15 319 | ------- 320 | 321 | * Support Sphinx's doc/ref directives for relative links ([#16](https://github.com/miyakogi/m2r/pull/16)) 322 | 323 | ```text 324 | $ git shortlog -s v0.1.14...v0.1.15 325 | 9 Ryan Lane 326 | 7 miyakogi 327 | ``` 328 | 329 | 330 | v0.1.14 331 | ------- 332 | 333 | * Implement markdown link with title 334 | 335 | ```text 336 | $ git shortlog -s v0.1.13...v0.1.14 337 | 11 miyakogi 338 | ``` 339 | 340 | 341 | v0.1.13 342 | ------- 343 | 344 | * Catch up sphinx updates 345 | 346 | ```text 347 | $ git shortlog -s v0.1.12...v0.1.13 348 | 8 miyakogi 349 | ``` 350 | 351 | 352 | v0.1.12 353 | ------- 354 | 355 | * Support multi byte characters for heading 356 | 357 | ```text 358 | $ git shortlog -s v0.1.11...v0.1.12 359 | 6 miyakogi 360 | ``` 361 | 362 | 363 | v0.1.11 364 | ------- 365 | 366 | * Add metadata for sphinx 367 | * Add `convert(src)` function, which is shortcut of `m2r.M2R()(src)` 368 | 369 | ```text 370 | $ git shortlog -s v0.1.10...v0.1.11 371 | 13 miyakogi 372 | ``` 373 | 374 | 375 | v0.1.10 376 | ------- 377 | 378 | * Include CHANGES and test files in source distribution 379 | 380 | ```text 381 | $ git shortlog -s v0.1.9...v0.1.10 382 | 4 miyakogi 383 | ``` 384 | 385 | 386 | v0.1.9 387 | ------ 388 | 389 | * Print help when input_file is not specified on command-line 390 | 391 | ```text 392 | $ git shortlog -s v0.1.8...v0.1.9 393 | 5 miyakogi 394 | ``` 395 | 396 | 397 | v0.1.8 398 | ------ 399 | 400 | * Update metadata on setup.py 401 | 402 | ```text 403 | $ git shortlog -s v0.1.7...v0.1.8 404 | 6 miyakogi 405 | ``` 406 | 407 | 408 | v0.1.7 409 | ------ 410 | 411 | * Fix undefined name error (PR #5). 412 | 413 | ```text 414 | $ git shortlog -s v0.1.6...v0.1.7 415 | 1 Kai Fricke 416 | 1 cclauss 417 | 9 miyakogi 418 | ``` 419 | 420 | 421 | v0.1.6 422 | ------ 423 | 424 | * Drop python 3.3 support 425 | * Improve image_link regex (PR #3) 426 | 427 | ```text 428 | $ git shortlog -s v0.1.5...v0.1.6 429 | 1 John W. O'Brien 430 | 1 Nikola Forró 431 | 6 miyakogi 432 | ``` 433 | 434 | 435 | v0.1.5 436 | ------ 437 | 438 | * Support multiple backticks in inline code, like: ```backticks (``) in code``` 439 | 440 | ```text 441 | $ git shortlog -s v0.1.4...v0.1.5 442 | 6 miyakogi 443 | ``` 444 | 445 | 446 | v0.1.4 447 | ------ 448 | 449 | * Support indented directives/reST-comments 450 | * Support role-name after backticks (`` `text`:role: style``) 451 | 452 | ```text 453 | $ git shortlog -s v0.1.3...v0.1.4 454 | 6 miyakogi 455 | ``` 456 | 457 | 458 | v0.1.3 459 | ------ 460 | 461 | * Remove extra escaped-spaces ('\ ') 462 | * before and after normal spaces 463 | * at the beginning of lines 464 | * before dots 465 | 466 | ```text 467 | $ git shortlog -s v0.1.2...v0.1.3 468 | 4 miyakogi 469 | ``` 470 | 471 | 472 | v0.1.2 473 | ------ 474 | 475 | * Add reST's `::` marker support 476 | * Add options to disable emphasis by underscore (`_` or `__`) 477 | 478 | ```text 479 | $ git shortlog -s v0.1.1...v0.1.2 480 | 9 miyakogi 481 | ``` 482 | 483 | 484 | v0.1.1 485 | ------ 486 | 487 | * Fix Bug: when code or link is placed at the end of line, spaces to the next word is disappeared 488 | 489 | ```text 490 | $ git shortlog -s v0.1...v0.1.1 491 | 6 miyakogi 492 | ``` 493 | 494 | 495 | v0.1 496 | ---- 497 | 498 | First public release. 499 | 500 | ```text 501 | $ git shortlog -s v0.1 502 | 40 miyakogi 503 | ``` 504 | 505 | [attribution-badge]: 506 | https://img.shields.io/badge/generated%20by-attribution-informational 507 | [attribution-url]: https://attribution.omnilib.dev 508 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Hiroyuki Takagi 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. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | sphinx-mdinclude 2 | ================ 3 | 4 | Sphinx extension for including or writing pages in Markdown format. 5 | 6 | [![version](https://img.shields.io/pypi/v/sphinx-mdinclude.svg)](https://pypi.python.org/pypi/sphinx-mdinclude) 7 | [![documentation](https://img.shields.io/badge/docs-latest-success)](https://sphinx-mdinclude.readthedocs.io) 8 | [![changelog](https://img.shields.io/badge/change-log-blue)](https://sphinx-mdinclude.omnilidb.dev/en/latest/changelog.html) 9 | [![license](https://img.shields.io/pypi/l/sphinx-mdinclude.svg)](https://github.com/omnilib/sphinx-mdinclude/blob/main/LICENSE) 10 | 11 | 12 | sphinx-mdinclude is a simple Sphinx extension that enables including Markdown documents 13 | from within reStructuredText. It provides the `.. mdinclude::` directive, and 14 | automatically converts the content of Markdown documents to reStructuredText format. 15 | 16 | sphinx-mdinclude is a fork of [m2r](https://github.com/miyakogi/m2r) and 17 | [m2r2](https://github.com/crossnox/m2r2), focused only on providing a Sphinx extension. 18 | 19 | ## Features 20 | 21 | * Basic markdown and some extensions (see below) 22 | * inline/block-level raw html 23 | * fenced-code block 24 | * tables 25 | * footnotes (``[^1]``) 26 | * Inline- and Block-level rst markups 27 | * single- and multi-line directives (`.. directive::`) 28 | * inline-roles (``:code:`print(1)` ...``) 29 | * ref-link (``see `ref`_``) 30 | * footnotes (``[#fn]_``) 31 | * math extension inspired by [recommonmark](https://recommonmark.readthedocs.io/en/latest/index.html) 32 | * Sphinx extension 33 | * add markdown support for sphinx 34 | * ``mdinclude`` directive to include markdown from md or rst files 35 | * option to parse relative links into ref and doc directives (``md_parse_relative_links``) 36 | 37 | ## Restrictions 38 | 39 | * In the rst's directives, markdown is not available. Please write in rst. 40 | * Column alignment of tables is not supported. (rst does not support this feature) 41 | * Heading with overline-and-underline is not supported. 42 | * Heading with underline is OK 43 | * Rst heading marks are currently hard-coded and unchangeable. 44 | * H1: `=`, H2: `-`, H3: `^`, H4: `~`, H5: `"`, H6: `#` 45 | 46 | ## Installation 47 | 48 | Python 3.6 or newer is required. 49 | 50 | ``` 51 | pip install sphinx-mdinclude 52 | ``` 53 | 54 | ## Usage 55 | 56 | In your Sphinx `conf.py`, add the following lines: 57 | 58 | ```python 59 | extensions = [ 60 | ..., 61 | 'sphinx_mdinclude', 62 | ] 63 | ``` 64 | 65 | Markdown files with the `.md` extension will be loaded and used by Sphinx, similar to 66 | any other `.rst` files. 67 | 68 | To include Markdown files within other files, use the `.. mdinclude:: ` 69 | directive. This applies the conversion from Markdown to reStructuredText format. 70 | 71 | ## License 72 | 73 | `sphinx-mdinclude` is copyright Hiroyuki Takagi, CrossNox, and [Amethyst Reese][], 74 | and licensed under the MIT license. I am providing code in this repository to you 75 | under an open source license. This is my personal repository; the license you receive 76 | to my code is from me and not from my employer. See the [LICENSE][] file for details. 77 | 78 | [Amethyst Reese]: https://noswap.com 79 | [LICENSE]: https://github.com/omnilib/sphinx-mdinclude/blob/main/LICENSE 80 | 81 | -------------------------------------------------------------------------------- /docs/_static/custom.css: -------------------------------------------------------------------------------- 1 | div.omnilib { 2 | margin-top: 24px; 3 | } 4 | 5 | div.omnilib-badges { 6 | margin-top: 12px; 7 | margin-bottom: 10px; 8 | } 9 | 10 | div.omnilib-badges a { 11 | color: #bbb; 12 | text-decoration: none; 13 | font-size: 24px; 14 | border: none; 15 | } 16 | 17 | div.omnilib-badges a:hover { 18 | color: #97F; 19 | border: none; 20 | } 21 | 22 | div.sphinxsidebarwrapper h1.logo a { 23 | font-family: ff-tisa-web-pro, Georgia, serif; 24 | } 25 | 26 | dl.function, dl.attribute, dl.class, dl.method, dl.field-list, dl.data { 27 | margin-bottom: 15px; 28 | } 29 | 30 | div.deprecated { 31 | margin: 20px 0px; 32 | padding: 10px 30px; 33 | background-color: #EEE; 34 | border: 1px solid #CCC; 35 | } 36 | -------------------------------------------------------------------------------- /docs/_static/omnilib.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omnilib/sphinx-mdinclude/b25b3cf8653c275453dd2fd9c5494e48c80e40cd/docs/_static/omnilib.png -------------------------------------------------------------------------------- /docs/_templates/badges.html: -------------------------------------------------------------------------------- 1 |
2 | Star 4 |
5 | 6 | -------------------------------------------------------------------------------- /docs/_templates/omnilib.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |

The Omnilib Project

4 | 5 | Omnilib is a group of MIT licensed software libraries developed under a 6 | common, inclusive Code of Conduct. 7 | We are committed to providing a welcoming and open space for all contributors who adhere to these rules. 8 | 9 |
10 |
11 | 12 | 13 | 14 |   15 | 16 | 17 | 18 |   19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | -------------------------------------------------------------------------------- /docs/changelog.rst: -------------------------------------------------------------------------------- 1 | Changelog 2 | ========= 3 | 4 | .. mdinclude:: ../CHANGELOG.md 5 | :start-line: 2 6 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # This file only contains a selection of the most common options. For a full 4 | # list see the documentation: 5 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 6 | 7 | # -- Project information ----------------------------------------------------- 8 | 9 | import datetime 10 | import os 11 | import pathlib 12 | import sys 13 | 14 | project = "sphinx-mdinclude" 15 | copyright = f"{datetime.date.today().year}, Hiroyuki Takagi, CrossNox, Amethyst Reese" 16 | author = "Amethyst Reese" 17 | 18 | root = pathlib.Path(__file__).parent.parent 19 | print(f"root = {repr(root)}") 20 | sys.path.insert(0, root.as_posix()) 21 | 22 | # -- General configuration --------------------------------------------------- 23 | 24 | # Add any Sphinx extension module names here, as strings. They can be 25 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 26 | # ones. 27 | extensions = [ 28 | "sphinx.ext.autodoc", 29 | 'sphinx.ext.mathjax', 30 | 'sphinx.ext.viewcode', 31 | 'sphinx.ext.githubpages', 32 | "sphinx.ext.intersphinx", 33 | "sphinx_mdinclude", 34 | ] 35 | 36 | # Add any paths that contain templates here, relative to this directory. 37 | templates_path = ["_templates"] 38 | 39 | # List of patterns, relative to source directory, that match files and 40 | # directories to ignore when looking for source files. 41 | # This pattern also affects html_static_path and html_extra_path. 42 | exclude_patterns = [] 43 | 44 | autodoc_default_options = { 45 | "show-inheritance": True, 46 | "members": True, 47 | "undoc-members": True, 48 | } 49 | autodoc_member_order = "groupwise" 50 | autodoc_typehints = "description" 51 | 52 | highlight_language = "python3" 53 | intersphinx_mapping = {"python": ("https://docs.python.org/3", None)} 54 | master_doc = "index" 55 | 56 | # Set canonical URL from the Read the Docs Domain 57 | html_baseurl = os.environ.get("READTHEDOCS_CANONICAL_URL", "") 58 | 59 | # -- Options for HTML output ------------------------------------------------- 60 | 61 | # The theme to use for HTML and HTML Help pages. See the documentation for 62 | # a list of builtin themes. 63 | # 64 | html_theme = "alabaster" 65 | html_theme_options = { 66 | "description": "Markdown extension for Sphinx", 67 | "fixed_sidebar": True, 68 | "badge_branch": "main", 69 | "github_button": False, 70 | "github_user": "omnilib", 71 | "github_repo": "sphinx-mdinclude", 72 | "show_powered_by": False, 73 | "sidebar_collapse": False, 74 | "extra_nav_links": { 75 | "Report Issues": "https://github.com/omnilib/sphinx-mdinclude/issues", 76 | }, 77 | } 78 | 79 | html_sidebars = { 80 | "**": [ 81 | "about.html", 82 | "badges.html", 83 | "navigation.html", 84 | "relations.html", 85 | "searchbox.html", 86 | "omnilib.html", 87 | ], 88 | } 89 | 90 | # Add any paths that contain custom static files (such as style sheets) here, 91 | # relative to this directory. They are copied after the builtin static files, 92 | # so a file named "default.css" will overwrite the builtin "default.css". 93 | html_static_path = ["_static"] 94 | -------------------------------------------------------------------------------- /docs/example.md: -------------------------------------------------------------------------------- 1 | # Example 2 | 3 | This page is written in mixed markdown and reST. 4 | Source code is [here](https://github.com/omnilib/sphinx-mdinclude/raw/main/docs/example.md). 5 | 6 | ## Basic Markups (inline) 7 | 8 | A **strong**, *emphasis*, ~~deleted~~, `code with single-backtick`, 9 | ``code with two-backticks``, ```code can include multiple (``) backticks```, 10 | :code:`reST's code role`, and inline html delete. 11 | 12 | ### Link 13 | 14 | Auto link to . 15 | 16 | Link to [example.com](http://example.com/) in markdown. 17 | 18 | Link to [anchor](#testlabel) in markdown. 19 | 20 | Link to `example.com `_ in reST. 21 | 22 | Link to `example`_ in reST_ref. 23 | 24 | Link to [example.com](http://example.com/ "example") with title in markdown. 25 | 26 | .. _example: http://example.com 27 | 28 | 29 | .. _testlabel: 30 | 31 | ## Basic Markups (block) 32 | 33 | This is a simple sentence. 34 | 35 | | sentence with 36 | | newlines 37 | | (reST) 38 | 39 | Sentence with 40 | hard-wrap (markdown, trailing two spaces) 41 | 42 | > block quote 43 | > second line 44 | > > nested quote 45 | 46 | --- 47 | 48 |
This is a red, raw-html block.
49 | 50 | > Block quote after raw-html directive 51 | 52 | ### List 53 | 54 | #### Unordered list 55 | 56 | * unordered list 57 | new line 58 | * next item 59 | * nested list 60 | with new line 61 | * nested list item 2 62 | * original depth 63 | 1. ordered list item 64 | 2. second 65 | with new line 66 | * original depth again 67 | 68 | #### Ordered list 69 | 70 | 1. ordered list 71 | in new line 72 | 2. second item 73 | * nested unordered list 74 | * second item 75 | with new line 76 | 3. original depth 77 | 1. nested ordered list 78 | with new line 79 | 2. again 80 | 4. original depth again 81 | 82 | ### Code Block 83 | 84 | Simple, indented code block 85 | 86 | pip install sphinx 87 | 88 | Code block with triple backticks and language. 89 | 90 | ```python 91 | def a(n: int) -> None: 92 | for i in range(n): 93 | print(i) 94 | ``` 95 | 96 | Triple-tildes (`~~~`) are also available. 97 | 98 | ~~~ 99 | def a(n: int) -> None: 100 | for i in range(n): 101 | print(i) 102 | ~~~ 103 | 104 | Here is reST style code block. 105 | 106 | .. code-block:: python 107 | 108 | if True: 109 | print('\n') 110 | 111 | ## Extensions 112 | 113 | ### Table (Markdown-Style) 114 | 115 | (cell-alignment is not supported currently) 116 | 117 | | Table Header 1 | Table Header 2 | Table Header 3 | 118 | |----------------|----------------|----------------| 119 | | normal | *italic* | **bold** | 120 | | `code` | ~~deleted~~ | inline-html | 121 | 122 | ### Math 123 | 124 | This is `$E = mc^2$` inline math. 125 | 126 | The below is math-block (markdown-style). 127 | 128 | ```math 129 | E = mc^2 130 | ``` 131 | 132 | The below is reST-style math-block. 133 | 134 | .. math:: 135 | 136 | E = mc^2 137 | 138 | 139 | ### Include Markdown file 140 | 141 | To include markdown file: 142 | 143 | ```rest 144 | .. mdinclude:: path-to-file.md 145 | ``` 146 | 147 | To include markdown file with specific lines: 148 | 149 | ```rest 150 | .. mdinclude:: included.md 151 | :start-line: 2 152 | :end-line: -2 153 | ``` 154 | 155 | Original ``included.md`` file is: 156 | 157 | .. include:: included.md 158 | :code: md 159 | 160 | This file included as: 161 | 162 | ```md 163 | #### Include this line 164 | ``` 165 | 166 | and results in HTML as below: 167 | 168 | .. mdinclude:: included.md 169 | :start-line: 2 170 | :end-line: -2 171 | 172 | ### Footnote 173 | 174 | Footnote[^1] and footnote[^key] with markdown. 175 | 176 | Footnote with reST\ [#a]_. 177 | 178 | 179 | [^1]: footnote 1 180 | [^key]: footnote key 181 | .. [#a] reST footnote 182 | -------------------------------------------------------------------------------- /docs/included.md: -------------------------------------------------------------------------------- 1 | NOT-INCLUDED 2 | 3 | #### Include this line 4 | 5 | NOT-INCLUDED 6 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | .. toctree:: 2 | :maxdepth: 1 3 | :hidden: 4 | 5 | example 6 | changelog 7 | 8 | .. mdinclude:: ../README.md 9 | -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | SRCS:=sphinx_mdinclude 2 | 3 | .venv: 4 | python -m venv .venv 5 | source .venv/bin/activate && make install 6 | echo 'run `source .venv/bin/activate` to use virtualenv' 7 | 8 | venv: .venv 9 | 10 | install: 11 | python -m pip install -U pip 12 | python -m pip install -Ue .[dev] 13 | 14 | release: lint test clean 15 | flit publish 16 | 17 | format: 18 | python -m ufmt format $(SRCS) 19 | 20 | lint: 21 | python -m flake8 $(SRCS) 22 | python -m ufmt check $(SRCS) 23 | 24 | test: 25 | python -m coverage run -m $(SRCS).tests 26 | python -m coverage report 27 | python -m mypy --install-types --non-interactive -p $(SRCS) 28 | 29 | deps: 30 | python -m pessimist --requirements= -c 'python -m sphinx_mdinclude.tests' . 31 | 32 | .PHONY: html 33 | html: .venv README.md docs/*.md docs/conf.py 34 | source .venv/bin/activate && sphinx-build -ab html docs html 35 | 36 | clean: 37 | rm -rf build dist html *.egg-info .mypy_cache 38 | 39 | distclean: clean 40 | rm -rf .venv 41 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["flit_core >=3.8,<4"] 3 | build-backend = "flit_core.buildapi" 4 | 5 | [project] 6 | name = "sphinx_mdinclude" 7 | authors = [ 8 | {name = "Hiroyuki Takagi", email = "miyako.dev@gmail.com"}, 9 | {name = "CrossNox", email = "ijmermet+m2r2@gmail.com"}, 10 | {name = "Amethyst Reese", email = "amy@noswap.com"}, 11 | ] 12 | maintainers = [ 13 | {name = "Amethyst Reese", email = "amy@noswap.com"}, 14 | ] 15 | readme = "README.md" 16 | classifiers = [ 17 | "Development Status :: 4 - Beta", 18 | "Framework :: Sphinx :: Extension", 19 | "Intended Audience :: Developers", 20 | "License :: OSI Approved :: MIT License", 21 | "Natural Language :: English", 22 | "Topic :: Text Processing", 23 | ] 24 | keywords = ["Markdown", "reStructuredText", "sphinx-extension"] 25 | dynamic = ["version", "description"] 26 | requires-python = ">=3.8" 27 | dependencies = [ 28 | "mistune >=3.0,<4.0", 29 | "docutils >=0.19,<1.0", 30 | "pygments >= 2.8", 31 | "sphinx >= 6", 32 | ] 33 | 34 | [project.optional-dependencies] 35 | dev = [ 36 | "docutils==0.20.1; python_version < '3.9'", 37 | "docutils==0.21.2; python_version >= '3.9'", 38 | "mistune==3.0.2", 39 | 40 | "attribution==1.8.0", 41 | "black==24.4.2", 42 | "coverage==7.5.1", 43 | "flake8==7.0.0", 44 | "flit==3.9.0", 45 | "mypy==1.10.0", 46 | "sphinx==7.1.2; python_version < '3.9'", 47 | "sphinx==7.3.7; python_version >= '3.9'", 48 | "ufmt==2.5.1", 49 | "usort==1.0.8.post1", 50 | ] 51 | 52 | [project.urls] 53 | Github = "https://github.com/omnilib/sphinx-mdinclude" 54 | 55 | 56 | [tool.attribution] 57 | name = "sphinx-mdinclude" 58 | package = "sphinx_mdinclude" 59 | signed_tags = true 60 | version_file = true 61 | ignored_authors = ["dependabot"] 62 | 63 | [tool.coverage.run] 64 | branch = true 65 | include = ["sphinx_mdinclude/*"] 66 | omit = ["sphinx_mdinclude/tests/*"] 67 | 68 | [tool.coverage.report] 69 | fail_under = 50 70 | precision = 1 71 | show_missing = true 72 | skip_covered = true 73 | 74 | [tool.mypy] 75 | python_version = "3.8" 76 | strict = true 77 | ignore_missing_imports = true 78 | disallow_untyped_calls = false 79 | -------------------------------------------------------------------------------- /sphinx_mdinclude/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | Markdown extension for Sphinx 6 | """ 7 | 8 | __author__ = "Hiroyuki Takagi " 9 | from .__version__ import __version__ 10 | 11 | from .render import convert, RestMarkdown 12 | from .sphinx import setup 13 | 14 | __all__ = [ 15 | "convert", 16 | "RestMarkdown", 17 | "setup", 18 | ] 19 | -------------------------------------------------------------------------------- /sphinx_mdinclude/__version__.py: -------------------------------------------------------------------------------- 1 | """ 2 | This file is automatically generated by attribution. 3 | 4 | Do not edit manually. Get more info at https://attribution.omnilib.dev 5 | """ 6 | 7 | __version__ = "0.6.2" 8 | -------------------------------------------------------------------------------- /sphinx_mdinclude/parse.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Dict, Match, Tuple 2 | 3 | from mistune import BlockParser, InlineParser 4 | from mistune.core import BlockState, InlineState 5 | from mistune.helpers import HTML_ATTRIBUTES, HTML_TAGNAME 6 | 7 | 8 | State = Dict[str, Any] 9 | Token = Dict[str, Any] 10 | Element = Tuple[str, ...] 11 | 12 | 13 | class RestBlockParser(BlockParser): 14 | SPECIFICATION = BlockParser.SPECIFICATION.copy() 15 | SPECIFICATION.update( 16 | { 17 | "directive": r"(?ms:^(?P *\.\..*?)\n(?=\S))", 18 | "oneline_directive": r"(?ms:^(?P *\.\..*?)$)", 19 | "rest_code_block": r"(?m:^::\s*$)", 20 | } 21 | ) 22 | 23 | DEFAULT_RULES = BlockParser.DEFAULT_RULES + ( # type: ignore[has-type] 24 | "directive", 25 | "oneline_directive", 26 | "rest_code_block", 27 | ) 28 | 29 | def parse_directive(self, m: Match[str], state: BlockState) -> int: 30 | state.append_token({"type": "directive", "raw": m.group("directive_1")}) 31 | return m.end() 32 | 33 | def parse_oneline_directive(self, m: Match[str], state: BlockState) -> int: 34 | # reuse directive output 35 | state.append_token({"type": "directive", "raw": m.group("directive_2")}) 36 | # $ does not count '\n' 37 | return m.end() + 1 38 | 39 | def parse_rest_code_block(self, m: Match[str], state: BlockState) -> int: 40 | state.append_token({"type": "rest_code_block", "text": ""}) 41 | # $ does not count '\n' 42 | return m.end() + 1 43 | 44 | 45 | class RestInlineParser(InlineParser): 46 | # make inline_html span open/contents/close instead of just a single tag 47 | INLINE_HTML = ( 48 | r"(?" # open tag 49 | r"(.*)" 50 | r"(?|" # close tag 51 | r"(?|" # open/close tag 52 | r"(?|" 53 | r"(?|" # doctype 54 | r"(?" # cdata 55 | ) 56 | 57 | SPECIFICATION = InlineParser.SPECIFICATION.copy() 58 | SPECIFICATION.update( 59 | { 60 | "inline_html": INLINE_HTML, 61 | "inline_math": r"`\$(?P.*?)\$`", 62 | "rest_role": r":.*?:`.*?`|`[^`]+`:.*?:", 63 | "rest_link": r"`[^`]*?`_", 64 | "eol_literal_marker": r"(?P\s+)?::\s*$", 65 | } 66 | ) 67 | 68 | # Order is important: need these rules to be checked before the 69 | # default rules 70 | DEFAULT_RULES = ( 71 | "inline_math", 72 | "rest_role", 73 | "rest_link", 74 | "eol_literal_marker", 75 | ) + InlineParser.DEFAULT_RULES # type: ignore[has-type] 76 | 77 | def parse_rest_role(self, m: Match[str], state: InlineState) -> int: 78 | """Pass through rest role.""" 79 | state.append_token({"type": "rest_role", "raw": m.group(0)}) 80 | return m.end() 81 | 82 | def parse_rest_link(self, m: Match[str], state: InlineState) -> int: 83 | """Pass through rest link.""" 84 | state.append_token({"type": "rest_link", "raw": m.group(0)}) 85 | return m.end() 86 | 87 | def parse_inline_math(self, m: Match[str], state: InlineState) -> int: 88 | """Pass through inline math.""" 89 | state.append_token({"type": "inline_math", "raw": m.group("math_1")}) 90 | return m.end() 91 | 92 | def parse_eol_literal_marker(self, m: Match[str], state: InlineState) -> int: 93 | """Pass through rest link.""" 94 | marker = ":" if m.group("eol_space") is None else "" 95 | state.append_token({"type": "eol_literal_marker", "raw": marker}) 96 | # $ does not count '\n' 97 | return m.end() + 1 98 | -------------------------------------------------------------------------------- /sphinx_mdinclude/render.py: -------------------------------------------------------------------------------- 1 | import re 2 | import textwrap 3 | from functools import partial 4 | from importlib import import_module 5 | from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple 6 | 7 | from docutils.utils import column_width 8 | from mistune import Markdown 9 | from mistune.core import BaseRenderer, BlockState 10 | from mistune.plugins import _plugins 11 | 12 | from .parse import RestBlockParser, RestInlineParser 13 | 14 | CACHED_MODULES: Dict[str, Any] = {} 15 | DEFAULT_PLUGINS = ["strikethrough", "footnotes", "table"] 16 | 17 | PROLOG = """\ 18 | .. role:: raw-html-md(raw) 19 | :format: html 20 | 21 | """ 22 | 23 | 24 | class RestRenderer(BaseRenderer): 25 | _include_raw_html = False 26 | indent = " " * 3 27 | list_marker = "{#__rest_list_mark__#}" 28 | hmarks = { 29 | 1: "=", 30 | 2: "-", 31 | 3: "^", 32 | 4: "~", 33 | 5: '"', 34 | 6: "#", 35 | } 36 | 37 | def __init__(self, *args: Any, **kwargs: Any) -> None: 38 | self._indent_block = partial(textwrap.indent, prefix=self.indent) 39 | super().__init__(*args, **kwargs) 40 | 41 | def render_token(self, token: Dict[str, Any], state: BlockState) -> str: 42 | # based on mistune 3.0.2, mistune/renderers/html.py 43 | func: Callable[..., str] = self._get_method(token["type"]) 44 | attrs = token.get("attrs") 45 | style = token.get("style") 46 | 47 | if "raw" in token: 48 | text = token["raw"] 49 | elif "children" in token: 50 | text = self.render_tokens(token["children"], state) 51 | else: 52 | if attrs: 53 | return func(**attrs) 54 | else: 55 | return func() 56 | 57 | # We have to special-case block_code, as it needs to know the 58 | # style as well to determine whether to add a blank line at the 59 | # end (so as to retain the original behaviour) 60 | if token["type"] == "block_code": 61 | if attrs: 62 | return func(text, style=style, **attrs) 63 | else: 64 | return func(text, style=style) 65 | 66 | if attrs: 67 | return func(text, **attrs) 68 | else: 69 | return func(text) 70 | 71 | def finalize(self, data: Iterable[str]) -> str: 72 | return "".join(data) 73 | 74 | def _raw_html(self, html: str) -> str: 75 | self._include_raw_html = True 76 | return r":raw-html-md:`{}`".format(html) 77 | 78 | def block_code(self, code: str, style: str, info: Optional[str] = None) -> str: 79 | if info == "math": 80 | first_line = "\n.. math::\n\n" 81 | elif info: 82 | first_line = "\n.. code-block:: {}\n\n".format(info) 83 | else: 84 | # first_line = "\n::\n\n" 85 | first_line = "\n.. code-block::\n\n" 86 | newline = "\n" if style == "indent" else "" 87 | return first_line + self._indent_block(code + newline) 88 | 89 | def block_quote(self, text: str) -> str: 90 | # text includes some empty line 91 | return "\n..\n\n{}\n\n".format(self._indent_block(text.strip("\n"))) 92 | 93 | def block_text(self, text: str) -> str: 94 | return text 95 | 96 | def block_html(self, html: str) -> str: 97 | """Rendering block level pure html content. 98 | 99 | :param html: text content of the html snippet. 100 | """ 101 | return "\n\n.. raw:: html\n\n" + self._indent_block(html) + "\n" 102 | 103 | def heading(self, text: str, level: int, **attrs: Any) -> str: 104 | """Rendering header/heading tags like ``

`` ``

``. 105 | 106 | :param text: rendered text content for the header. 107 | :param level: a number for the header level, for example: 1. 108 | :param attrs: other attributes of the header. 109 | """ 110 | return "\n{0}\n{1}\n".format(text, self.hmarks[level] * column_width(text)) 111 | 112 | def thematic_break(self) -> str: 113 | """Rendering method for ``
`` tag.""" 114 | return "\n----\n" 115 | 116 | def list(self, text: str, ordered: bool, **attrs: Any) -> str: 117 | """Rendering list tags like ``