├── .github └── workflows │ ├── ci.yml │ └── release.yml ├── .gitignore ├── .gitpod.yml ├── CHANGES.rst ├── LICENSE ├── MANIFEST.in ├── README.rst ├── pyproject.toml ├── readme_renderer ├── __init__.py ├── __main__.py ├── clean.py ├── markdown.py ├── py.typed ├── rst.py └── txt.py ├── setup.cfg ├── tests ├── __init__.py ├── fixtures │ ├── test_CommonMark_001.html │ ├── test_CommonMark_001.md │ ├── test_CommonMark_002.html │ ├── test_CommonMark_002.md │ ├── test_CommonMark_003.html │ ├── test_CommonMark_003.md │ ├── test_CommonMark_004.html │ ├── test_CommonMark_004.md │ ├── test_CommonMark_005.html │ ├── test_CommonMark_005.md │ ├── test_CommonMark_006.html │ ├── test_CommonMark_006.md │ ├── test_CommonMark_007.html │ ├── test_CommonMark_007.md │ ├── test_CommonMark_008.html │ ├── test_CommonMark_008.md │ ├── test_CommonMark_009.html │ ├── test_CommonMark_009.md │ ├── test_CommonMark_smart_strong.html │ ├── test_CommonMark_smart_strong.md │ ├── test_CommonMark_strong.html │ ├── test_CommonMark_strong.md │ ├── test_CommonMark_style.html │ ├── test_CommonMark_style.md │ ├── test_GFM_001.html │ ├── test_GFM_001.md │ ├── test_GFM_002.html │ ├── test_GFM_002.md │ ├── test_GFM_003.html │ ├── test_GFM_003.md │ ├── test_GFM_004.html │ ├── test_GFM_004.md │ ├── test_GFM_005.html │ ├── test_GFM_005.md │ ├── test_GFM_006.html │ ├── test_GFM_006.md │ ├── test_GFM_007.html │ ├── test_GFM_007.md │ ├── test_GFM_008.html │ ├── test_GFM_008.md │ ├── test_GFM_009.html │ ├── test_GFM_009.md │ ├── test_GFM_010.html │ ├── test_GFM_010.md │ ├── test_GFM_011.html │ ├── test_GFM_011.md │ ├── test_GFM_012.html │ ├── test_GFM_012.md │ ├── test_GFM_013.html │ ├── test_GFM_013.md │ ├── test_GFM_014.html │ ├── test_GFM_014.md │ ├── test_GFM_015.html │ ├── test_GFM_015.md │ ├── test_GFM_016.html │ ├── test_GFM_016.md │ ├── test_GFM_017.html │ ├── test_GFM_017.md │ ├── test_GFM_018.html │ ├── test_GFM_018.md │ ├── test_GFM_019.html │ ├── test_GFM_019.md │ ├── test_GFM_020.html │ ├── test_GFM_020.md │ ├── test_GFM_021.html │ ├── test_GFM_021.md │ ├── test_GFM_022.html │ ├── test_GFM_022.md │ ├── test_GFM_023.html │ ├── test_GFM_023.md │ ├── test_GFM_024.html │ ├── test_GFM_024.md │ ├── test_GFM_025.html │ ├── test_GFM_025.md │ ├── test_GFM_doublequotes.html │ ├── test_GFM_doublequotes.md │ ├── test_GFM_highlight.html │ ├── test_GFM_highlight.md │ ├── test_GFM_highlight_default_py.html │ ├── test_GFM_highlight_default_py.md │ ├── test_GFM_img.html │ ├── test_GFM_img.md │ ├── test_GFM_malicious_pre.html │ ├── test_GFM_malicious_pre.md │ ├── test_GFM_picture.html │ ├── test_GFM_picture.md │ ├── test_GFM_style.html │ ├── test_GFM_style.md │ ├── test_rst_003.html │ ├── test_rst_003.rst │ ├── test_rst_004.html │ ├── test_rst_004.rst │ ├── test_rst_005.html │ ├── test_rst_005.rst │ ├── test_rst_006.html │ ├── test_rst_006.rst │ ├── test_rst_007.html │ ├── test_rst_007.rst │ ├── test_rst_008.html │ ├── test_rst_008.rst │ ├── test_rst_admonitions.html │ ├── test_rst_admonitions.rst │ ├── test_rst_bibtex.html │ ├── test_rst_bibtex.rst │ ├── test_rst_caption.html │ ├── test_rst_caption.rst │ ├── test_rst_citations.html │ ├── test_rst_citations.rst │ ├── test_rst_contents.html │ ├── test_rst_contents.rst │ ├── test_rst_docinfo.html │ ├── test_rst_docinfo.rst │ ├── test_rst_figure.html │ ├── test_rst_figure.rst │ ├── test_rst_footnotes.html │ ├── test_rst_footnotes.rst │ ├── test_rst_linkify.html │ ├── test_rst_linkify.rst │ ├── test_rst_math.html │ ├── test_rst_math.rst │ ├── test_rst_png.html │ ├── test_rst_png.rst │ ├── test_rst_png_attrs.html │ ├── test_rst_png_attrs.rst │ ├── test_rst_svg.html │ ├── test_rst_svg.rst │ ├── test_rst_svg_attrs.html │ ├── test_rst_svg_attrs.rst │ ├── test_rst_tables.html │ ├── test_rst_tables.rst │ ├── test_txt_001.html │ └── test_txt_001.txt ├── test_clean.py ├── test_cli.py ├── test_markdown.py ├── test_noextra.py ├── test_rst.py └── test_txt.py └── tox.ini /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: [push, pull_request] 3 | jobs: 4 | test: 5 | name: Test - ${{ matrix.python-version }} 6 | runs-on: ubuntu-latest 7 | strategy: 8 | matrix: 9 | python-version: 10 | - "pypy-3.9" 11 | - "pypy-3.10" 12 | - "3.9" 13 | - "3.10" 14 | - "3.11" 15 | - "3.12" 16 | - "3.13" 17 | steps: 18 | - name: Check out repository 19 | uses: actions/checkout@v4 20 | - name: Install Python 21 | uses: actions/setup-python@v5 22 | with: 23 | python-version: ${{ matrix.python-version }} 24 | check-latest: true 25 | - name: Install dependencies 26 | run: python -m pip install tox 27 | - name: Run tests 28 | run: | 29 | python -m tox -e py 30 | python -m tox -e noextra 31 | lint: 32 | name: Lint 33 | runs-on: ubuntu-latest 34 | steps: 35 | - name: Check out repository 36 | uses: actions/checkout@v4 37 | - name: Install Python 38 | uses: actions/setup-python@v5 39 | with: 40 | python-version: "3.x" 41 | check-latest: true 42 | - name: Install dependencies 43 | run: python -m pip install tox 44 | - name: Run linting 45 | run: python -m tox -e pep8 46 | - name: Run mypy 47 | run: python -m tox -e mypy 48 | packaging: 49 | name: Packaging 50 | runs-on: ubuntu-latest 51 | steps: 52 | - name: Check out repository 53 | uses: actions/checkout@v4 54 | - name: Install Python 55 | uses: actions/setup-python@v5 56 | with: 57 | python-version: "3.x" 58 | check-latest: true 59 | - name: Install dependencies 60 | run: python -m pip install tox 61 | - name: Test packaging 62 | run: python -m tox -e packaging 63 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | release: 4 | types: [published] 5 | jobs: 6 | build-and-publish: 7 | runs-on: ubuntu-latest 8 | permissions: 9 | # IMPORTANT: this permission is mandatory for trusted publishing 10 | id-token: write 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v4 14 | with: 15 | ref: ${{ github.event.release.tag_name }} 16 | - name: Set up Python 17 | uses: actions/setup-python@v5 18 | with: 19 | python-version: "3.x" 20 | check-latest: true 21 | - name: Install build dependencies 22 | run: pip install -U setuptools wheel build 23 | - name: Build 24 | run: python -m build . 25 | - name: Publish 26 | uses: pypa/gh-action-pypi-publish@release/v1 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | _build/ 3 | build/ 4 | dist/ 5 | htmlcov/ 6 | 7 | .tox/ 8 | .cache/ 9 | .coverage 10 | *.egg-info/ 11 | *.egg 12 | *.eggs 13 | *.py[co] 14 | .pytest_cache/ 15 | .python-version 16 | 17 | .idea/ 18 | .vscode/ 19 | -------------------------------------------------------------------------------- /.gitpod.yml: -------------------------------------------------------------------------------- 1 | # See https://www.gitpod.io/docs/references/gitpod-yml for full reference 2 | 3 | tasks: 4 | - name: Setup Development and run Tests 5 | 6 | init: | 7 | # Upgrade pyenv itself 8 | pyenv update 9 | 10 | export PY_VERSIONS="3.9 3.10 3.11 3.12 3.13" 11 | 12 | # Install all supported Python versions 13 | for py in $PY_VERSIONS; 14 | do pyenv install "$py":latest --skip-existing ; 15 | done 16 | 17 | # Make versions available via $PATH, exclude GitPod default 18 | pyenv global $(pyenv versions --bare | grep -v 3.8.13) 19 | 20 | # Install `tox` test orchestrator 21 | pip install tox 22 | 23 | command: tox 24 | 25 | vscode: 26 | extensions: 27 | - ms-python.python 28 | - EditorConfig.EditorConfig 29 | - tamasfe.even-better-toml 30 | -------------------------------------------------------------------------------- /CHANGES.rst: -------------------------------------------------------------------------------- 1 | Changes 2 | ======= 3 | 4 | 44.0 (2024-07-08) 5 | ----------------- 6 | 7 | * Drop support for Python 3.8 (#315) 8 | * Require docutils 0.21.2 and higher (#315) 9 | * Remove HTML5 ```` tag from the list of allowed HTML tags (#315) 10 | * Test all supported CPython and PyPy versions in CI (#315) 11 | * Resolve Node 16 deprecation warnings in CI (#309) 12 | * Lint specific directories (#312) 13 | * Build a wheel once for all tox test environments (#308) 14 | * Lazy open output files, and always close them (#314) 15 | * Gitpod: Migrate to the Even Better TOML extension (#306) 16 | * check-manifest: Remove a now-default ``.gitpod.yml`` exclusion (#307) 17 | 18 | 43.0 (2024-02-26) 19 | ----------------- 20 | 21 | * Allow HTML5 ``picture`` tag through cleaner (#299) 22 | * Test against Python 3.12 (#300) 23 | 24 | 42.0 (2023-09-07) 25 | ----------------- 26 | 27 | * Migrate from ``bleach`` to ``nh3`` (#295) 28 | * Migrate from ``setup.py`` to ``pyproject.toml`` 29 | 30 | 41.0 (2023-08-18) 31 | ----------------- 32 | 33 | * Allow HTML5 ``figcaption`` tag through cleaner (#291) 34 | * Test ``README.rst`` from this project (#288) 35 | 36 | 40.0 (2023-06-16) 37 | ----------------- 38 | 39 | * Add CLI option to render package README. (#271) 40 | * Adapt tests to pygments 2.14.0 (#272) 41 | * Update release process to use Trusted Publishing (#276) 42 | * Replace usage of deprecated ``pkg_resources`` with ``importlib.metadata`` (#281) 43 | * Drop support for Python 3.7 (#282), Test against Python 3.11 (#280) 44 | 45 | 37.3 (2022-10-31) 46 | ----------------- 47 | 48 | * Allow HTML5 ``figure`` tag through cleaner (#265) 49 | 50 | 37.2 (2022-09-24) 51 | ----------------- 52 | 53 | * Allow HTML5 ``s`` tag through cleaner (#261) 54 | 55 | 37.1 (2022-09-03) 56 | ----------------- 57 | 58 | * Allow HTML5 ``nav`` tag through cleaner (#259) 59 | 60 | 37.0 (2022-08-21) 61 | ----------------- 62 | 63 | * Remove command line example from docs (#197) 64 | * Multiple pyproject.toml fixes (#251) 65 | * Confirm handling multiple inline strong (#252) 66 | * Convert RST output to HTML5 (#253) 67 | * Add Typing to classifiers (#254) 68 | * Development tweaks - coverage reporting, actions updates (#255) 69 | * Add test confirming behavior with unknown lexers (#256) 70 | 71 | 36.0 (2022-08-06) 72 | ----------------- 73 | 74 | * Enable gitpod development (#238) 75 | * Allow rst admonitions to render (#242) 76 | * Add badges to README (#243) 77 | * Update codebase for modern Python (#244) 78 | * Fix table cell spans (#245) 79 | * Allow ``math`` directive in rst (#246) 80 | * Preserve ``lang`` attribute in ``pre`` (#247) 81 | 82 | 35.0 (2022-04-19) 83 | ----------------- 84 | 85 | * Add py.typed to the built wheel (#228) 86 | * Use isolated build for tox (#229) 87 | * Fix renderer ignore (#230) 88 | * Remove legacy check command and distutils (#233) 89 | * Emit a warning when no content is rendered (#231) 90 | * Drop support for Python 3.6 (#236) 91 | * Update html attribute order in tests (#235) 92 | 93 | 34.0 (2022-03-11) 94 | ----------------- 95 | 96 | * Add static types (#225) 97 | 98 | 33.0 (2022-03-05) 99 | ----------------- 100 | 101 | * Support cmarkgfm>=0.8.0 (#224) 102 | 103 | 33.0 (2022-02-05) 104 | ----------------- 105 | 106 | * Support cmarkgfm>=0.8.0 (#224) 107 | * Support Python 3.10 108 | 109 | 32.0 (2021-12-13) 110 | ----------------- 111 | 112 | * Allow start attribute in ordered lists (#216) 113 | * No limit rendering RST one column field names (#219) 114 | 115 | 31.0 (2021-12-09) 116 | ----------------- 117 | 118 | * Render disabled checkboxes from Markdown (#217) 119 | 120 | 30.0 (2021-09-30) 121 | ----------------- 122 | 123 | * support cmarkgfm>=0.6.0 (#209) 124 | 125 | 29.0 (2021-02-22) 126 | ----------------- 127 | 128 | * Support cmarkgfm>=0.5.0 (#180) 129 | * Drop support for Python 2 and 3.5 (#188) 130 | 131 | 28.0 (2020-10-20) 132 | ----------------- 133 | 134 | * Support Python 3.9 135 | 136 | 27.0 (2020-10-09) 137 | ----------------- 138 | 139 | * Add support for align attribute rendering Markdown headers (#173) 140 | 141 | 26.0 (2020-04-22) 142 | ----------------- 143 | 144 | * Fix regression with image width/height attributes (#164) 145 | 146 | 147 | 25.0 (2020-03-14) 148 | ----------------- 149 | 150 | * Support Python 3.7 and 3.8 151 | * Drop support for Python 3.4 152 | * Require Pygments>=2.5.1 153 | 154 | 155 | 24.0 (2018-10-27) 156 | ----------------- 157 | 158 | * Remove dependency on ``future``. (#134) 159 | 160 | 161 | 23.0 (2018-10-22) 162 | ----------------- 163 | 164 | * Breaking change: Move the cmarkgfm dependency into an extra (#130). Users 165 | that want to render Markdown will need to install readme_render[md] instead. 166 | 167 | 168 | 22.0 (2018-09-17) 169 | ----------------- 170 | 171 | * Unify handling of SVG and other images in RST. (#113) 172 | * Support width and alignment of images in RST (#114) 173 | 174 | 175 | 21.0 (2018-05-28) 176 | ----------------- 177 | 178 | * Allow . (#105) 179 | * Add specific tests for the raw directive. (#103) 180 | * Include docinfo when rendering rst. (#99) 181 | * Make Python 3 the default lexer for highlighting Python code in Markdown (#97) 182 | * Allow align attribute on

tags (#98) 183 | 184 | 185 | 20.0 (2018-04-13) 186 | ----------------- 187 | 188 | * Reformat docutils messages into more human-friendly output. (#92) 189 | * Fix incorrect escaping of html entities in pre tags when using markdown. (#89) 190 | * Allow width, height, alt, and align attributes on img tags. (#91) 191 | * Fix check to not report success when there is invalid RST. (#90) 192 | 193 | 194 | 19.0 (2018-04-10) 195 | ----------------- 196 | 197 | * Allow details and summary tags. (#77) 198 | * Add .pytest_cache to .gitignore. (#85) 199 | * Warn about Markdown content type when checking RST. (#83) 200 | * Update pep8 tox env to use py3.6. (#84) 201 | * Add Pygments-based syntax highlighting for Markdown. (#82) 202 | * Update docs about check to talk about Markdown. (#80) 203 | 204 | 205 | 18.1 (2018-04-01) 206 | ----------------- 207 | 208 | * Change Github-flavored Markdown identifier from ``gfm`` to ``GFM``. 209 | 210 | 211 | 18.0 (2018-03-30) 212 | ----------------- 213 | 214 | * Add support for GitHub-flavored Markdown. (#67) 215 | * Switch to cmarkgfm completely for rendering Markdown. (#68) 216 | * Warn about missing long description. (#69) 217 | * Make Github-Flavored Markdown the default variant (#73) 218 | 219 | 220 | 17.4 (2018-03-16) 221 | ----------------- 222 | 223 | * All renderers now accept arbitrary ``kwargs`` for future-proofing. 224 | 225 | 226 | 17.3 (2018-03-08) 227 | ----------------- 228 | 229 | * Gracefully handle new exceptions from bleach. 230 | 231 | 232 | 17.2 (2017-03-11) 233 | ----------------- 234 | 235 | * Fix an issue cleaning plaintext values. 236 | 237 | 238 | 17.1 (2017-03-09) 239 | ----------------- 240 | 241 | * Fix an issue attempting to clean data that had ```` tags without a href. 242 | 243 | 244 | 17.0 (2017-03-08) 245 | ----------------- 246 | 247 | * Fix issue with bleach >= 2.0. 248 | 249 | 250 | 16.0 (2016-12-09) 251 | ----------------- 252 | 253 | * Fix issue with docutils >= 0.13.1. 254 | 255 | 256 | 0.7.0 (2016-01-04) 257 | ------------------ 258 | 259 | * Renamed to ``readme_renderer``: https://github.com/pypa/readme_renderer 260 | to work around an name overlap with ``README`` files shipped in Python's 261 | default site-packages directory on certain case-insensitive file systems. 262 | 263 | * Added `PyPA Code of Conduct`_. 264 | 265 | * Allow and tags when cleaning rST HTML output. 266 | 267 | * Dropped support for Python 2.6. 268 | 269 | .. _PyPA Code of Conduct: https://www.pypa.io/en/latest/code-of-conduct/ 270 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE README.rst CHANGES.rst 2 | include tox.ini 3 | include readme_renderer/py.typed 4 | 5 | recursive-include tests *.html 6 | recursive-include tests *.py 7 | recursive-include tests *.rst 8 | recursive-include tests *.txt 9 | recursive-include tests *.md 10 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Readme Renderer 2 | =============== 3 | 4 | .. image:: https://badge.fury.io/py/readme-renderer.svg 5 | :target: https://badge.fury.io/py/readme-renderer 6 | 7 | .. image:: https://github.com/pypa/readme_renderer/actions/workflows/ci.yml/badge.svg 8 | :target: https://github.com/pypa/readme_renderer/actions/workflows/ci.yml 9 | 10 | Readme Renderer is a library that will safely render arbitrary 11 | ``README`` files into HTML. It is designed to be used in Warehouse_ to 12 | render the ``long_description`` for packages. It can handle Markdown, 13 | reStructuredText (``.rst``), and plain text. 14 | 15 | .. _Warehouse: https://github.com/pypa/warehouse 16 | 17 | 18 | Check Description Locally 19 | ------------------------- 20 | 21 | To locally check whether your long descriptions will render on PyPI, first 22 | build your distributions, and then use the |twine check|_ command. 23 | 24 | 25 | Code of Conduct 26 | --------------- 27 | 28 | Everyone interacting in the readme_renderer project's codebases, issue trackers, 29 | chat rooms, and mailing lists is expected to follow the `PSF Code of Conduct`_. 30 | 31 | 32 | .. |twine check| replace:: ``twine check`` 33 | .. _twine check: https://packaging.python.org/guides/making-a-pypi-friendly-readme#validating-restructuredtext-markup 34 | .. _PSF Code of Conduct: https://github.com/pypa/.github/blob/main/CODE_OF_CONDUCT.md 35 | 36 | Copyright © 2014, [The Python Packaging Authority]. 37 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "readme_renderer" 3 | version = "44.0" 4 | description = "readme_renderer is a library for rendering readme descriptions for Warehouse" 5 | authors = [ 6 | {name = "The Python Packaging Authority", email = "admin@mail.pypi.org"} 7 | ] 8 | readme = "README.rst" 9 | license = {text = "Apache License, Version 2.0"} 10 | dependencies = [ 11 | "nh3>=0.2.14", 12 | "docutils>=0.21.2", 13 | "Pygments>=2.5.1", 14 | ] 15 | classifiers = [ 16 | "Intended Audience :: Developers", 17 | "License :: OSI Approved :: Apache Software License", 18 | "Natural Language :: English", 19 | "Operating System :: MacOS :: MacOS X", 20 | "Operating System :: POSIX", 21 | "Operating System :: POSIX :: BSD", 22 | "Operating System :: POSIX :: Linux", 23 | "Operating System :: Microsoft :: Windows", 24 | "Programming Language :: Python", 25 | "Programming Language :: Python :: 3", 26 | "Programming Language :: Python :: 3 :: Only", 27 | "Programming Language :: Python :: 3.9", 28 | "Programming Language :: Python :: 3.10", 29 | "Programming Language :: Python :: 3.11", 30 | "Programming Language :: Python :: 3.12", 31 | "Programming Language :: Python :: 3.13", 32 | "Programming Language :: Python :: Implementation :: CPython", 33 | "Programming Language :: Python :: Implementation :: PyPy", 34 | "Typing :: Typed" 35 | ] 36 | requires-python = ">=3.9" 37 | 38 | [project.optional-dependencies] 39 | md = ["cmarkgfm>=0.8.0"] 40 | 41 | [tool.setuptools] 42 | include-package-data = true 43 | 44 | [tool.setuptools.packages.find] 45 | exclude = ["tests", "tests.*"] 46 | 47 | [project.urls] 48 | Home-page = "https://github.com/pypa/readme_renderer" 49 | 50 | [build-system] 51 | requires = ["setuptools>=40.8.0"] 52 | build-backend = "setuptools.build_meta" 53 | 54 | 55 | [tool.coverage.run] 56 | branch = true 57 | 58 | [tool.mypy] 59 | strict = true 60 | warn_unused_configs = true 61 | show_error_codes = true 62 | enable_error_code = [ 63 | "ignore-without-code" 64 | ] 65 | 66 | [[tool.mypy.overrides]] 67 | # These modules do not yet have types available. 68 | module = [ 69 | "cmarkgfm.*" 70 | ] 71 | ignore_missing_imports = true 72 | -------------------------------------------------------------------------------- /readme_renderer/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014 Donald Stufft 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | -------------------------------------------------------------------------------- /readme_renderer/__main__.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | from readme_renderer.markdown import render as render_md 3 | from readme_renderer.rst import render as render_rst 4 | from readme_renderer.txt import render as render_txt 5 | import pathlib 6 | from importlib.metadata import metadata 7 | import sys 8 | from typing import Optional, List 9 | 10 | 11 | def main(cli_args: Optional[List[str]] = None) -> None: 12 | parser = argparse.ArgumentParser( 13 | description="Renders a .md, .rst, or .txt README to HTML", 14 | ) 15 | parser.add_argument("-p", "--package", help="Get README from package metadata", 16 | action="store_true") 17 | parser.add_argument("-f", "--format", choices=["md", "rst", "txt"], 18 | help="README format (inferred from input file name or package)") 19 | parser.add_argument('input', help="Input README file or package name") 20 | parser.add_argument('-o', '--output', help="Output file (default: stdout)", 21 | default='-') 22 | args = parser.parse_args(cli_args) 23 | 24 | content_format = args.format 25 | if args.package: 26 | message = metadata(args.input) 27 | source = message.get_payload() # type: ignore[attr-defined] # noqa: E501 https://peps.python.org/pep-0566/ 28 | 29 | # Infer the format of the description from package metadata. 30 | if not content_format: 31 | content_type = message.get("Description-Content-Type", "text/x-rst") 32 | if content_type == "text/x-rst": 33 | content_format = "rst" 34 | elif content_type == "text/markdown": 35 | content_format = "md" 36 | elif content_type == "text/plain": 37 | content_format = "txt" 38 | else: 39 | raise ValueError(f"invalid content type {content_type} for package " 40 | "`long_description`") 41 | else: 42 | filename = pathlib.Path(args.input) 43 | content_format = content_format or filename.suffix.lstrip(".") 44 | with filename.open() as fp: 45 | source = fp.read() 46 | 47 | if content_format == "md": 48 | rendered = render_md(source, stream=sys.stderr) 49 | elif content_format == "rst": 50 | rendered = render_rst(source, stream=sys.stderr) 51 | elif content_format == "txt": 52 | rendered = render_txt(source, stream=sys.stderr) 53 | else: 54 | raise ValueError(f"invalid README format: {content_format} (expected `md`, " 55 | "`rst`, or `txt`)") 56 | if rendered is None: 57 | sys.exit(1) 58 | if args.output == "-": 59 | print(rendered, file=sys.stdout) 60 | else: 61 | with open(args.output, "w") as fp: 62 | print(rendered, file=fp) 63 | 64 | 65 | if __name__ == '__main__': 66 | main() 67 | -------------------------------------------------------------------------------- /readme_renderer/clean.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014 Donald Stufft 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from typing import Dict, Optional, Set 16 | 17 | import nh3 18 | 19 | 20 | ALLOWED_TAGS = { 21 | # Bleach Defaults 22 | "a", "abbr", "acronym", "b", "blockquote", "code", "em", "i", "li", "ol", 23 | "strong", "ul", 24 | 25 | # Custom Additions 26 | "br", "caption", "cite", "col", "colgroup", "dd", "del", "details", "div", 27 | "dl", "dt", "h1", "h2", "h3", "h4", "h5", "h6", "hr", "img", "p", "pre", 28 | "span", "sub", "summary", "sup", "table", "tbody", "td", "th", "thead", 29 | "tr", "tt", "kbd", "var", "input", "section", "aside", "nav", "figure", 30 | "figcaption", "picture", 31 | } 32 | 33 | ALLOWED_ATTRIBUTES = { 34 | # Bleach Defaults 35 | "a": {"href", "title"}, 36 | "abbr": {"title"}, 37 | "acronym": {"title"}, 38 | 39 | # Custom Additions 40 | "*": {"id"}, 41 | "hr": {"class"}, 42 | "img": {"src", "width", "height", "alt", "align", "class"}, 43 | "span": {"class"}, 44 | "th": {"align", "class"}, 45 | "td": {"align", "colspan", "rowspan"}, 46 | "div": {"align", "class"}, 47 | "h1": {"align"}, 48 | "h2": {"align"}, 49 | "h3": {"align"}, 50 | "h4": {"align"}, 51 | "h5": {"align"}, 52 | "h6": {"align"}, 53 | "code": {"class"}, 54 | "p": {"align", "class"}, 55 | "pre": {"lang"}, 56 | "ol": {"start"}, 57 | "input": {"type", "checked", "disabled"}, 58 | "aside": {"class"}, 59 | "dd": {"class"}, 60 | "dl": {"class"}, 61 | "dt": {"class"}, 62 | "ul": {"class"}, 63 | "nav": {"class"}, 64 | "figure": {"class"}, 65 | } 66 | 67 | 68 | def clean( 69 | html: str, 70 | tags: Optional[Set[str]] = None, 71 | attributes: Optional[Dict[str, Set[str]]] = None 72 | ) -> Optional[str]: 73 | if tags is None: 74 | tags = ALLOWED_TAGS 75 | if attributes is None: 76 | attributes = ALLOWED_ATTRIBUTES 77 | 78 | try: 79 | cleaned = nh3.clean( 80 | html, 81 | tags=ALLOWED_TAGS, 82 | attributes=ALLOWED_ATTRIBUTES, 83 | link_rel="nofollow", 84 | url_schemes={"http", "https", "mailto"}, 85 | ) 86 | 87 | return cleaned 88 | except ValueError: 89 | return None 90 | -------------------------------------------------------------------------------- /readme_renderer/markdown.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014 Donald Stufft 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import re 16 | import warnings 17 | from typing import cast, Any, Dict, Callable, Match, Optional 18 | 19 | from html import unescape 20 | 21 | import pygments 22 | import pygments.lexers 23 | import pygments.formatters 24 | 25 | from .clean import clean 26 | 27 | _EXTRA_WARNING = ( 28 | "Markdown renderers are not available. " 29 | "Install 'readme_renderer[md]' to enable Markdown rendering." 30 | ) 31 | 32 | try: 33 | import cmarkgfm 34 | from cmarkgfm.cmark import Options as cmarkgfmOptions 35 | variants: Dict[str, Callable[[str], str]] = { 36 | "GFM": lambda raw: cast(str, cmarkgfm.github_flavored_markdown_to_html( 37 | raw, options=cmarkgfmOptions.CMARK_OPT_UNSAFE 38 | )), 39 | "CommonMark": lambda raw: cast(str, cmarkgfm.markdown_to_html( 40 | raw, options=cmarkgfmOptions.CMARK_OPT_UNSAFE 41 | )), 42 | } 43 | except ImportError: 44 | warnings.warn(_EXTRA_WARNING) 45 | variants = {} 46 | 47 | # Make code fences with `python` as the language default to highlighting as 48 | # Python 3. 49 | _LANG_ALIASES = { 50 | 'python': 'python3', 51 | } 52 | 53 | 54 | def render( 55 | raw: str, 56 | variant: str = "GFM", 57 | **kwargs: Any 58 | ) -> Optional[str]: 59 | if not variants: 60 | warnings.warn(_EXTRA_WARNING) 61 | return None 62 | 63 | renderer = variants.get(variant) 64 | 65 | if not renderer: 66 | return None 67 | 68 | rendered = renderer(raw) 69 | 70 | if not rendered: 71 | return None 72 | 73 | highlighted = _highlight(rendered) 74 | cleaned = clean(highlighted) 75 | return cleaned 76 | 77 | 78 | def _highlight(html: str) -> str: 79 | """Syntax-highlights HTML-rendered Markdown. 80 | 81 | Plucks sections to highlight that conform the the GitHub fenced code info 82 | string as defined at https://github.github.com/gfm/#info-string. 83 | 84 | Args: 85 | html (str): The rendered HTML. 86 | 87 | Returns: 88 | str: The HTML with Pygments syntax highlighting applied to all code 89 | blocks. 90 | """ 91 | 92 | formatter = pygments.formatters.HtmlFormatter(nowrap=True) 93 | 94 | code_expr = re.compile( 95 | # cmarkgfm<0.6.0:

print('hello')
96 | # cmarkgfm>=0.6.0:
print('hello')
97 | r'(
(?P[^"]+?)">'
 98 |         '(?(in_code)|)(?P.+?)'
 99 |         r'
', re.DOTALL) 100 | 101 | def replacer(match: Match[Any]) -> str: 102 | try: 103 | lang = match.group('lang') 104 | lang = _LANG_ALIASES.get(lang, lang) 105 | lexer = pygments.lexers.get_lexer_by_name(lang) 106 | except ValueError: 107 | lexer = pygments.lexers.TextLexer() 108 | 109 | code = match.group('code') 110 | 111 | # Decode html entities in the code. cmark tries to be helpful and 112 | # translate '"' to '"', but it confuses pygments. Pygments will 113 | # escape any html entities when re-writing the code, and we run 114 | # everything through bleach after. 115 | code = unescape(code) 116 | 117 | highlighted = pygments.highlight(code, lexer, formatter) 118 | 119 | return f'
{highlighted}
' 120 | 121 | result = code_expr.sub(replacer, html) 122 | 123 | return result 124 | -------------------------------------------------------------------------------- /readme_renderer/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pypa/readme_renderer/04d5cfe76850192364eff344be7fe27730af8484/readme_renderer/py.typed -------------------------------------------------------------------------------- /readme_renderer/rst.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014 Donald Stufft 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import io 16 | from typing import Any, Dict, IO, Optional, Union 17 | 18 | from docutils.core import publish_parts 19 | from docutils.nodes import colspec, image 20 | from docutils.writers.html5_polyglot import HTMLTranslator, Writer 21 | from docutils.utils import SystemMessage 22 | 23 | from .clean import clean 24 | 25 | 26 | class ReadMeHTMLTranslator(HTMLTranslator): # type: ignore[misc] # docutils is incomplete, returns `Any` python/typeshed#7256 # noqa E501 27 | 28 | # Overrides base class not to output `` tag for SVG images. 29 | object_image_types: Dict[str, str] = {} 30 | 31 | def emptytag( 32 | self, 33 | node: Union[colspec, image], 34 | tagname: str, 35 | suffix: str = "\n", 36 | **attributes: Any 37 | ) -> Any: 38 | """Override this to add back the width/height attributes.""" 39 | if tagname == "img": 40 | if "width" in node: 41 | attributes["width"] = node["width"] 42 | if "height" in node: 43 | attributes["height"] = node["height"] 44 | 45 | return super().emptytag( 46 | node, tagname, suffix, **attributes 47 | ) 48 | 49 | 50 | SETTINGS = { 51 | # Cloaking email addresses provides a small amount of additional 52 | # privacy protection for email addresses inside of a chunk of ReST. 53 | "cloak_email_addresses": True, 54 | 55 | # Prevent a lone top level heading from being promoted to document 56 | # title, and thus second level headings from being promoted to top 57 | # level. 58 | "doctitle_xform": True, 59 | 60 | # Prevent a lone subsection heading from being promoted to section 61 | # title, and thus second level headings from being promoted to top 62 | # level. 63 | "sectsubtitle_xform": True, 64 | 65 | # Set our initial header level 66 | "initial_header_level": 2, 67 | 68 | # Prevent local files from being included into the rendered output. 69 | # This is a security concern because people can insert files 70 | # that are part of the system, such as /etc/passwd. 71 | "file_insertion_enabled": False, 72 | 73 | # Halt rendering and throw an exception if there was any errors or 74 | # warnings from docutils. 75 | "halt_level": 2, 76 | 77 | # Output math blocks as LaTeX that can be interpreted by MathJax for 78 | # a prettier display of Math formulas. 79 | # Pass a dummy path to supress docutils warning and emit HTML. 80 | "math_output": "MathJax /dummy.js", 81 | 82 | # Disable raw html as enabling it is a security risk, we do not want 83 | # people to be able to include any old HTML in the final output. 84 | "raw_enabled": False, 85 | 86 | # Disable all system messages from being reported. 87 | "report_level": 5, 88 | 89 | # Use typographic quotes, and transform --, ---, and ... into their 90 | # typographic counterparts. 91 | "smart_quotes": True, 92 | 93 | # Strip all comments from the rendered output. 94 | "strip_comments": True, 95 | 96 | # Use the short form of syntax highlighting so that the generated 97 | # Pygments CSS can be used to style the output. 98 | "syntax_highlight": "short", 99 | 100 | # Maximum width (in characters) for one-column field names. 101 | # 0 means "no limit" 102 | "field_name_limit": 0, 103 | } 104 | 105 | 106 | def render( 107 | raw: str, 108 | stream: Optional[IO[str]] = None, 109 | **kwargs: Any 110 | ) -> Optional[str]: 111 | if stream is None: 112 | # Use a io.StringIO as the warning stream to prevent warnings from 113 | # being printed to sys.stderr. 114 | stream = io.StringIO() 115 | 116 | settings = SETTINGS.copy() 117 | settings["warning_stream"] = stream 118 | 119 | writer = Writer() 120 | writer.translator_class = ReadMeHTMLTranslator 121 | 122 | try: 123 | parts = publish_parts(raw, writer=writer, settings_overrides=settings) 124 | except SystemMessage: 125 | rendered = None 126 | else: 127 | rendered = parts.get("docinfo", "") + parts.get("fragment", "") 128 | 129 | if rendered: 130 | return clean(rendered) 131 | else: 132 | # If the warnings stream is empty, docutils had none, so add ours. 133 | if not stream.tell(): 134 | stream.write("No content rendered from RST source.") 135 | return None 136 | -------------------------------------------------------------------------------- /readme_renderer/txt.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015 Donald Stufft 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from typing import Any, Optional 16 | 17 | from .clean import clean 18 | 19 | from html import escape as html_escape 20 | 21 | 22 | def render(raw: str, **kwargs: Any) -> Optional[str]: 23 | rendered = html_escape(raw).replace("\n", "
") 24 | return clean(rendered, tags={"br"}) 25 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | license_files = LICENSE 3 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pypa/readme_renderer/04d5cfe76850192364eff344be7fe27730af8484/tests/__init__.py -------------------------------------------------------------------------------- /tests/fixtures/test_CommonMark_001.html: -------------------------------------------------------------------------------- 1 |

Hello

2 | -------------------------------------------------------------------------------- /tests/fixtures/test_CommonMark_001.md: -------------------------------------------------------------------------------- 1 | Hello 2 | -------------------------------------------------------------------------------- /tests/fixtures/test_CommonMark_002.html: -------------------------------------------------------------------------------- 1 |

http://mymalicioussite.com/

2 | -------------------------------------------------------------------------------- /tests/fixtures/test_CommonMark_002.md: -------------------------------------------------------------------------------- 1 | http://mymalicioussite.com/ 2 | -------------------------------------------------------------------------------- /tests/fixtures/test_CommonMark_003.html: -------------------------------------------------------------------------------- 1 |

Required packages

2 |

To run the PyPI software, you need Python 2.5+ and PostgreSQL

3 |

Quick development setup

4 |

Make sure you are sitting

5 | -------------------------------------------------------------------------------- /tests/fixtures/test_CommonMark_003.md: -------------------------------------------------------------------------------- 1 | ## Required packages 2 | 3 | To run the PyPI software, you need Python 2.5+ and PostgreSQL 4 | 5 | ## Quick development setup 6 | Make sure you are sitting 7 | -------------------------------------------------------------------------------- /tests/fixtures/test_CommonMark_004.html: -------------------------------------------------------------------------------- 1 |

Required packages

2 |

To run the PyPI software, you need Python 2.5+ and PostgreSQL

3 |

Quick development setup

4 |

Make sure you are sitting

5 | -------------------------------------------------------------------------------- /tests/fixtures/test_CommonMark_004.md: -------------------------------------------------------------------------------- 1 |

Required packages

2 |

To run the PyPI software, you need Python 2.5+ and PostgreSQL

3 |

Quick development setup

4 |

Make sure you are sitting

-------------------------------------------------------------------------------- /tests/fixtures/test_CommonMark_005.html: -------------------------------------------------------------------------------- 1 |

Click here

2 | -------------------------------------------------------------------------------- /tests/fixtures/test_CommonMark_005.md: -------------------------------------------------------------------------------- 1 | Click here 2 | -------------------------------------------------------------------------------- /tests/fixtures/test_CommonMark_006.html: -------------------------------------------------------------------------------- 1 | Click here 2 | -------------------------------------------------------------------------------- /tests/fixtures/test_CommonMark_006.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/test_CommonMark_007.html: -------------------------------------------------------------------------------- 1 |

Something naughty this way comes

2 | 3 | -------------------------------------------------------------------------------- /tests/fixtures/test_CommonMark_007.md: -------------------------------------------------------------------------------- 1 | Something naughty this way comes 2 | 5 | -------------------------------------------------------------------------------- /tests/fixtures/test_CommonMark_008.html: -------------------------------------------------------------------------------- 1 |

Here is some Python code for a Dog:

2 |
class Dog(Animal):
 3 |     def __init__(self, name):
 4 |         self.name = name
 5 | 
 6 |     def make_sound(self):
 7 |         print('Ruff!')
 8 | 
 9 | dog = Dog('Fido')
10 | 
11 |

and then here is some bash:

12 |
if [ "$1" = "--help" ]; then
13 |     echo "OK"
14 | fi
15 | 
16 |

or click SurveyMonkey

17 | -------------------------------------------------------------------------------- /tests/fixtures/test_CommonMark_008.md: -------------------------------------------------------------------------------- 1 | Here is some Python code for a `Dog`: 2 | 3 | ```python 4 | class Dog(Animal): 5 | def __init__(self, name): 6 | self.name = name 7 | 8 | def make_sound(self): 9 | print('Ruff!') 10 | 11 | dog = Dog('Fido') 12 | ``` 13 | 14 | and then here is some bash: 15 | 16 | ```bash 17 | if [ "$1" = "--help" ]; then 18 | echo "OK" 19 | fi 20 | ``` 21 | 22 | or click [SurveyMonkey](http://www.surveymonkey.com) 23 | -------------------------------------------------------------------------------- /tests/fixtures/test_CommonMark_009.html: -------------------------------------------------------------------------------- 1 |

Normal text

2 |
3 | Why am I getting an error during installation/when launching rtv? 4 |
5 | -------------------------------------------------------------------------------- /tests/fixtures/test_CommonMark_009.md: -------------------------------------------------------------------------------- 1 | Normal text 2 | 3 |
4 | Why am I getting an error during installation/when launching rtv? 5 |
6 | -------------------------------------------------------------------------------- /tests/fixtures/test_CommonMark_smart_strong.html: -------------------------------------------------------------------------------- 1 |

Regular double underscore words.

2 |

Text with double__underscore__words.

3 | -------------------------------------------------------------------------------- /tests/fixtures/test_CommonMark_smart_strong.md: -------------------------------------------------------------------------------- 1 | Regular __double underscore__ words. 2 | 3 | Text with double__underscore__words. 4 | -------------------------------------------------------------------------------- /tests/fixtures/test_CommonMark_strong.html: -------------------------------------------------------------------------------- 1 |

sphinx: a password Store that Perfectly Hides from Itself (No Xaggeration)

2 | -------------------------------------------------------------------------------- /tests/fixtures/test_CommonMark_strong.md: -------------------------------------------------------------------------------- 1 | sphinx: a password **S**tore that **P**erfectly **H**ides from **I**tself (**N**o **X**aggeration) 2 | -------------------------------------------------------------------------------- /tests/fixtures/test_CommonMark_style.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tests/fixtures/test_CommonMark_style.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tests/fixtures/test_GFM_001.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
foobar
bazbim
15 | -------------------------------------------------------------------------------- /tests/fixtures/test_GFM_001.md: -------------------------------------------------------------------------------- 1 | | foo | bar | 2 | | --- | --- | 3 | | baz | bim | 4 | -------------------------------------------------------------------------------- /tests/fixtures/test_GFM_002.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
abcdefghi
barbaz
15 | -------------------------------------------------------------------------------- /tests/fixtures/test_GFM_002.md: -------------------------------------------------------------------------------- 1 | | abc | defghi | 2 | :-: | -----------: 3 | bar | baz 4 | -------------------------------------------------------------------------------- /tests/fixtures/test_GFM_003.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
f|oo
b | az
b | im
16 | -------------------------------------------------------------------------------- /tests/fixtures/test_GFM_003.md: -------------------------------------------------------------------------------- 1 | | f\|oo | 2 | | ------ | 3 | | b `\|` az | 4 | | b **\|** im | 5 | -------------------------------------------------------------------------------- /tests/fixtures/test_GFM_004.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
abcdef
barbaz
15 |
16 |

bar

17 |
18 | -------------------------------------------------------------------------------- /tests/fixtures/test_GFM_004.md: -------------------------------------------------------------------------------- 1 | | abc | def | 2 | | --- | --- | 3 | | bar | baz | 4 | > bar 5 | -------------------------------------------------------------------------------- /tests/fixtures/test_GFM_005.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
abcdef
barbaz
bar
19 |

bar

20 | -------------------------------------------------------------------------------- /tests/fixtures/test_GFM_005.md: -------------------------------------------------------------------------------- 1 | | abc | def | 2 | | --- | --- | 3 | | bar | baz | 4 | bar 5 | 6 | bar 7 | -------------------------------------------------------------------------------- /tests/fixtures/test_GFM_006.html: -------------------------------------------------------------------------------- 1 |

| abc | def | 2 | | --- | 3 | | bar |

4 | -------------------------------------------------------------------------------- /tests/fixtures/test_GFM_006.md: -------------------------------------------------------------------------------- 1 | | abc | def | 2 | | --- | 3 | | bar | 4 | -------------------------------------------------------------------------------- /tests/fixtures/test_GFM_007.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
abcdef
bar
barbaz
19 | -------------------------------------------------------------------------------- /tests/fixtures/test_GFM_007.md: -------------------------------------------------------------------------------- 1 | | abc | def | 2 | | --- | --- | 3 | | bar | 4 | | bar | baz | boo | 5 | -------------------------------------------------------------------------------- /tests/fixtures/test_GFM_008.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
abcdef
9 | -------------------------------------------------------------------------------- /tests/fixtures/test_GFM_008.md: -------------------------------------------------------------------------------- 1 | | abc | def | 2 | | --- | --- | 3 | -------------------------------------------------------------------------------- /tests/fixtures/test_GFM_009.html: -------------------------------------------------------------------------------- 1 |

Hi Hello, world!

2 | -------------------------------------------------------------------------------- /tests/fixtures/test_GFM_009.md: -------------------------------------------------------------------------------- 1 | ~Hi~ Hello, world! 2 | -------------------------------------------------------------------------------- /tests/fixtures/test_GFM_010.html: -------------------------------------------------------------------------------- 1 |

This text~~~~ is ~~~~curious.

2 | -------------------------------------------------------------------------------- /tests/fixtures/test_GFM_010.md: -------------------------------------------------------------------------------- 1 | This ~text~~~~ is ~~~~curious~. 2 | -------------------------------------------------------------------------------- /tests/fixtures/test_GFM_011.html: -------------------------------------------------------------------------------- 1 |

This ~~has a

2 |

new paragraph~~.

3 | -------------------------------------------------------------------------------- /tests/fixtures/test_GFM_011.md: -------------------------------------------------------------------------------- 1 | This ~~has a 2 | 3 | new paragraph~~. 4 | -------------------------------------------------------------------------------- /tests/fixtures/test_GFM_012.html: -------------------------------------------------------------------------------- 1 |

www.commonmark.org

2 | -------------------------------------------------------------------------------- /tests/fixtures/test_GFM_012.md: -------------------------------------------------------------------------------- 1 | www.commonmark.org 2 | -------------------------------------------------------------------------------- /tests/fixtures/test_GFM_013.html: -------------------------------------------------------------------------------- 1 |

Visit www.commonmark.org/help for more information.

2 | -------------------------------------------------------------------------------- /tests/fixtures/test_GFM_013.md: -------------------------------------------------------------------------------- 1 | Visit www.commonmark.org/help for more information. 2 | -------------------------------------------------------------------------------- /tests/fixtures/test_GFM_014.html: -------------------------------------------------------------------------------- 1 |

Visit www.commonmark.org.

2 |

Visit www.commonmark.org/a.b.

3 | -------------------------------------------------------------------------------- /tests/fixtures/test_GFM_014.md: -------------------------------------------------------------------------------- 1 | Visit www.commonmark.org. 2 | 3 | Visit www.commonmark.org/a.b. 4 | -------------------------------------------------------------------------------- /tests/fixtures/test_GFM_015.html: -------------------------------------------------------------------------------- 1 |

www.google.com/search?q=Markup+(business)

2 |

(www.google.com/search?q=Markup+(business))

3 | -------------------------------------------------------------------------------- /tests/fixtures/test_GFM_015.md: -------------------------------------------------------------------------------- 1 | www.google.com/search?q=Markup+(business) 2 | 3 | (www.google.com/search?q=Markup+(business)) 4 | -------------------------------------------------------------------------------- /tests/fixtures/test_GFM_016.html: -------------------------------------------------------------------------------- 1 |

www.google.com/search?q=(business))+ok

2 | -------------------------------------------------------------------------------- /tests/fixtures/test_GFM_016.md: -------------------------------------------------------------------------------- 1 | www.google.com/search?q=(business))+ok 2 | -------------------------------------------------------------------------------- /tests/fixtures/test_GFM_017.html: -------------------------------------------------------------------------------- 1 |

www.google.com/search?q=commonmark&hl=en

2 |

www.google.com/search?q=commonmark&hl;

3 | -------------------------------------------------------------------------------- /tests/fixtures/test_GFM_017.md: -------------------------------------------------------------------------------- 1 | www.google.com/search?q=commonmark&hl=en 2 | 3 | www.google.com/search?q=commonmark&hl; 4 | -------------------------------------------------------------------------------- /tests/fixtures/test_GFM_018.html: -------------------------------------------------------------------------------- 1 |

www.commonmark.org/he<lp

2 | -------------------------------------------------------------------------------- /tests/fixtures/test_GFM_018.md: -------------------------------------------------------------------------------- 1 | www.commonmark.org/hehttp://commonmark.org

2 |

(Visit https://encrypted.google.com/search?q=Markup+(business))

3 |

Anonymous FTP is available at ftp://foo.bar.baz.

4 | -------------------------------------------------------------------------------- /tests/fixtures/test_GFM_019.md: -------------------------------------------------------------------------------- 1 | http://commonmark.org 2 | 3 | (Visit https://encrypted.google.com/search?q=Markup+(business)) 4 | 5 | Anonymous FTP is available at ftp://foo.bar.baz. 6 | -------------------------------------------------------------------------------- /tests/fixtures/test_GFM_020.html: -------------------------------------------------------------------------------- 1 |

foo@bar.baz

2 | -------------------------------------------------------------------------------- /tests/fixtures/test_GFM_020.md: -------------------------------------------------------------------------------- 1 | foo@bar.baz 2 | -------------------------------------------------------------------------------- /tests/fixtures/test_GFM_021.html: -------------------------------------------------------------------------------- 1 |

hello@mail+xyz.example isn't valid, but hello+xyz@mail.example is.

2 | -------------------------------------------------------------------------------- /tests/fixtures/test_GFM_021.md: -------------------------------------------------------------------------------- 1 | hello@mail+xyz.example isn't valid, but hello+xyz@mail.example is. 2 | -------------------------------------------------------------------------------- /tests/fixtures/test_GFM_022.html: -------------------------------------------------------------------------------- 1 |

a.b-c_d@a.b

2 |

a.b-c_d@a.b.

3 |

a.b-c_d@a.b-

4 |

a.b-c_d@a.b_

5 | -------------------------------------------------------------------------------- /tests/fixtures/test_GFM_022.md: -------------------------------------------------------------------------------- 1 | a.b-c_d@a.b 2 | 3 | a.b-c_d@a.b. 4 | 5 | a.b-c_d@a.b- 6 | 7 | a.b-c_d@a.b_ 8 | -------------------------------------------------------------------------------- /tests/fixtures/test_GFM_023.html: -------------------------------------------------------------------------------- 1 |

<title> <style>

2 |
3 | <xmp> is disallowed. <XMP> is also disallowed. 4 |
5 |
-------------------------------------------------------------------------------- /tests/fixtures/test_GFM_023.md: -------------------------------------------------------------------------------- 1 | <style> <em> 2 | 3 | <blockquote> 4 | <xmp> is disallowed. <XMP> is also disallowed. 5 | </blockquote> 6 | -------------------------------------------------------------------------------- /tests/fixtures/test_GFM_024.html: -------------------------------------------------------------------------------- 1 | <ul> 2 | <li><input type="checkbox" disabled=""> Valid unchecked checkbox</li> 3 | <li><input type="checkbox" checked="" disabled=""> Valid checked checkbox</li> 4 | <li><input type="checkbox"> Invalid enabled checkbox</li> 5 | <li> 6 | <input> 7 | </li> 8 | <li> 9 | <input type="submit"> 10 | </li> 11 | <li> 12 | <input> 13 | </li> 14 | <li> 15 | <input type="checkbox" checked=""> 16 | </li> 17 | </ul> 18 | -------------------------------------------------------------------------------- /tests/fixtures/test_GFM_024.md: -------------------------------------------------------------------------------- 1 | - [ ] Valid unchecked checkbox 2 | - [x] Valid checked checkbox 3 | - <input type="checkbox"> Invalid enabled checkbox 4 | - <input onlick="alert('XSS')" /> 5 | - <input type="submit" /> 6 | - <input method="PUT" /> 7 | - <input type="checkbox" checked /> 8 | -------------------------------------------------------------------------------- /tests/fixtures/test_GFM_025.html: -------------------------------------------------------------------------------- 1 | <ol start="3"> 2 | <li>Foo</li> 3 | </ol> 4 | <p>Some text</p> 5 | <ol start="2"> 6 | <li> 7 | <p>Bar</p> 8 | </li> 9 | <li> 10 | <p>Baz</p> 11 | </li> 12 | </ol> 13 | -------------------------------------------------------------------------------- /tests/fixtures/test_GFM_025.md: -------------------------------------------------------------------------------- 1 | 3. Foo 2 | 3 | Some text 4 | 5 | 2. Bar 6 | 7 | 1. Baz 8 | -------------------------------------------------------------------------------- /tests/fixtures/test_GFM_doublequotes.html: -------------------------------------------------------------------------------- 1 | <p>This is normal text.</p> 2 | <pre><code>This is code text. 3 | </code></pre> 4 | <pre lang="python3"><span class="k">def</span><span class="w"> </span><span class="nf">this_is_python</span><span class="p">():</span> 5 | <span class="w"> </span><span class="sd">"""This is a docstring."""</span> 6 | <span class="k">pass</span> 7 | </pre> 8 | <pre lang="go"><span class="kd">func</span><span class="w"> </span><span class="nx">ThisIsGo</span><span class="p">(){</span> 9 | <span class="w"> </span><span class="k">return</span> 10 | <span class="p">}</span> 11 | </pre> 12 | -------------------------------------------------------------------------------- /tests/fixtures/test_GFM_doublequotes.md: -------------------------------------------------------------------------------- 1 | This is normal text. 2 | 3 | ``` 4 | This is code text. 5 | ``` 6 | 7 | ```python 8 | def this_is_python(): 9 | """This is a docstring.""" 10 | pass 11 | ``` 12 | 13 | ```go 14 | func ThisIsGo(){ 15 | return 16 | } 17 | ``` 18 | -------------------------------------------------------------------------------- /tests/fixtures/test_GFM_highlight.html: -------------------------------------------------------------------------------- 1 | <p>This is normal text.</p> 2 | <pre><code>This is code text. 3 | </code></pre> 4 | <pre lang="python3"><span class="k">def</span><span class="w"> </span><span class="nf">this_is_python</span><span class="p">():</span> 5 | <span class="k">pass</span> 6 | </pre> 7 | <pre lang="go"><span class="kd">func</span><span class="w"> </span><span class="nx">ThisIsGo</span><span class="p">(){</span> 8 | <span class="w"> </span><span class="k">return</span> 9 | <span class="p">}</span> 10 | </pre> 11 | <pre lang="abc">An unknown code fence block 12 | </pre> 13 | -------------------------------------------------------------------------------- /tests/fixtures/test_GFM_highlight.md: -------------------------------------------------------------------------------- 1 | This is normal text. 2 | 3 | ``` 4 | This is code text. 5 | ``` 6 | 7 | ```python 8 | def this_is_python(): 9 | pass 10 | ``` 11 | 12 | ```go 13 | func ThisIsGo(){ 14 | return 15 | } 16 | ``` 17 | 18 | ```abc 19 | An unknown code fence block 20 | ``` 21 | -------------------------------------------------------------------------------- /tests/fixtures/test_GFM_highlight_default_py.html: -------------------------------------------------------------------------------- 1 | <pre lang="python3"><span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">this_is_python</span><span class="p">():</span> 2 | <span class="k">pass</span> 3 | 4 | <span class="nb">print</span><span class="p">(</span><span class="k">await</span> <span class="n">this_is_python</span><span class="p">())</span> 5 | </pre> 6 | -------------------------------------------------------------------------------- /tests/fixtures/test_GFM_highlight_default_py.md: -------------------------------------------------------------------------------- 1 | ```python 2 | async def this_is_python(): 3 | pass 4 | 5 | print(await this_is_python()) 6 | ``` 7 | -------------------------------------------------------------------------------- /tests/fixtures/test_GFM_img.html: -------------------------------------------------------------------------------- 1 | <p><img src="https://octodex.github.com/images/yaktocat.png" alt="Image of Yaktocat"></p> 2 | <p align="center"> 3 | <img src="https://octodex.github.com/images/yaktocat.png" width="20%" height="100px" alt="Image of Yaktocat"> 4 | </p> 5 | -------------------------------------------------------------------------------- /tests/fixtures/test_GFM_img.md: -------------------------------------------------------------------------------- 1 | ![Image of Yaktocat](https://octodex.github.com/images/yaktocat.png) 2 | 3 | <p align="center"> 4 | <img src="https://octodex.github.com/images/yaktocat.png" width="20%" height="100px" alt="Image of Yaktocat"> 5 | </p> 6 | -------------------------------------------------------------------------------- /tests/fixtures/test_GFM_malicious_pre.html: -------------------------------------------------------------------------------- 1 | <p>This is normal text.</p> 2 | <pre lang="python3"><span class="k">def</span><span class="w"> </span><span class="nf">this_is_python</span><span class="p">():</span> 3 | <span class="w"> </span><span class="sd">"""This is a docstring."""</span> 4 | <span class="k">pass</span> 5 | <span class="o"><</span><span class="n">script</span> <span class="nb">type</span><span class="o">=</span><span class="s2">"text/javascript"</span><span class="o">></span><span class="n">alert</span><span class="p">(</span><span class="s1">'I am evil.'</span><span class="p">);</span><span class="o"></</span><span class="n">script</span><span class="o">></span> 6 | </pre> 7 | -------------------------------------------------------------------------------- /tests/fixtures/test_GFM_malicious_pre.md: -------------------------------------------------------------------------------- 1 | This is normal text. 2 | 3 | ```python 4 | def this_is_python(): 5 | """This is a docstring.""" 6 | pass 7 | <script type="text/javascript">alert('I am evil.');</script> 8 | ``` 9 | -------------------------------------------------------------------------------- /tests/fixtures/test_GFM_picture.html: -------------------------------------------------------------------------------- 1 | <picture> 2 | <img src="/media/cc0-images/painted-hand-298-332.jpg" alt=""> 3 | </picture> 4 | -------------------------------------------------------------------------------- /tests/fixtures/test_GFM_picture.md: -------------------------------------------------------------------------------- 1 | <picture> 2 | <img src="/media/cc0-images/painted-hand-298-332.jpg" alt="" /> 3 | </picture> 4 | -------------------------------------------------------------------------------- /tests/fixtures/test_GFM_style.html: -------------------------------------------------------------------------------- 1 | <img src="https://example.com/badge.png"> 2 | -------------------------------------------------------------------------------- /tests/fixtures/test_GFM_style.md: -------------------------------------------------------------------------------- 1 | <img src="https://example.com/badge.png"> 2 | -------------------------------------------------------------------------------- /tests/fixtures/test_rst_003.html: -------------------------------------------------------------------------------- 1 | <section id="required-packages"> 2 | <h2>Required packages</h2> 3 | <p>To run the PyPI software, you need Python 2.5+ and PostgreSQL</p> 4 | </section> 5 | <section id="quick-development-setup"> 6 | <h2>Quick development setup</h2> 7 | <p>Make sure you are sitting</p> 8 | </section> 9 | -------------------------------------------------------------------------------- /tests/fixtures/test_rst_003.rst: -------------------------------------------------------------------------------- 1 | Required packages 2 | ----------------- 3 | 4 | To run the PyPI software, you need Python 2.5+ and PostgreSQL 5 | 6 | 7 | Quick development setup 8 | ----------------------- 9 | 10 | Make sure you are sitting 11 | -------------------------------------------------------------------------------- /tests/fixtures/test_rst_004.html: -------------------------------------------------------------------------------- 1 | <p><div id=”required-packages”> 2 | <h2>Required packages</h2> 3 | <p>To run the PyPI software, you need Python 2.5+ and PostgreSQL</p> 4 | </div> 5 | <div id=”quick-development-setup”> 6 | <h2>Quick development setup</h2> 7 | <p>Make sure you are sitting</p> 8 | </div></p> 9 | -------------------------------------------------------------------------------- /tests/fixtures/test_rst_004.rst: -------------------------------------------------------------------------------- 1 | <div id="required-packages"> 2 | <h2>Required packages</h2> 3 | <p>To run the PyPI software, you need Python 2.5+ and PostgreSQL</p> 4 | </div> 5 | <div id="quick-development-setup"> 6 | <h2>Quick development setup</h2> 7 | <p>Make sure you are sitting</p> 8 | </div> 9 | -------------------------------------------------------------------------------- /tests/fixtures/test_rst_005.html: -------------------------------------------------------------------------------- 1 | <p><a href=”<a href="http://mymalicioussite.com/" rel="nofollow">http://mymalicioussite.com/</a>”>Click here</a></p> 2 | -------------------------------------------------------------------------------- /tests/fixtures/test_rst_005.rst: -------------------------------------------------------------------------------- 1 | <a href="http://mymalicioussite.com/">Click here</a> -------------------------------------------------------------------------------- /tests/fixtures/test_rst_006.html: -------------------------------------------------------------------------------- 1 | <p><iframe src=”<a href="http://mymalicioussite.com/" rel="nofollow">http://mymalicioussite.com/</a>”>Click here</iframe></p> 2 | -------------------------------------------------------------------------------- /tests/fixtures/test_rst_006.rst: -------------------------------------------------------------------------------- 1 | <iframe src="http://mymalicioussite.com/">Click here</iframe> -------------------------------------------------------------------------------- /tests/fixtures/test_rst_007.html: -------------------------------------------------------------------------------- 1 | Something naughty this way comes 2 | <script> 3 | alert("Hello"); 4 | </script> 5 | -------------------------------------------------------------------------------- /tests/fixtures/test_rst_007.rst: -------------------------------------------------------------------------------- 1 | Something naughty this way comes 2 | <script> 3 | alert("Hello"); 4 | </script> 5 | -------------------------------------------------------------------------------- /tests/fixtures/test_rst_008.html: -------------------------------------------------------------------------------- 1 | <p>Here is some Python code for a <span class="docutils literal">Dog</span>:</p> 2 | <pre><code><span class="k">class</span><span class="w"> </span><span class="nc">Dog</span><span class="p">(</span><span class="n">Animal</span><span class="p">):</span><span class="w"> 3 | </span> <span class="k">def</span><span class="w"> </span><span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</span><span class="p">):</span><span class="w"> 4 | </span> <span class="bp">self</span><span class="o">.</span><span class="n">name</span> <span class="o">=</span> <span class="n">name</span><span class="w"> 5 | 6 | </span> <span class="k">def</span><span class="w"> </span><span class="nf">make_sound</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span><span class="w"> 7 | </span> <span class="nb">print</span><span class="p">(</span><span class="s1">'Ruff!'</span><span class="p">)</span><span class="w"> 8 | 9 | </span><span class="n">dog</span> <span class="o">=</span> <span class="n">Dog</span><span class="p">(</span><span class="s1">'Fido'</span><span class="p">)</span></code></pre> 10 | <p>and then here is some bash:</p> 11 | <pre><code><span class="k">if</span><span class="w"> </span><span class="o">[</span><span class="w"> </span><span class="s2">"</span><span class="nv">$1</span><span class="s2">"</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"--help"</span><span class="w"> </span><span class="o">]</span><span class="p">;</span><span class="w"> </span><span class="k">then</span><span class="w"> 12 | </span><span class="nb">echo</span><span class="w"> </span><span class="s2">"OK"</span><span class="w"> 13 | </span><span class="k">fi</span></code></pre> 14 | <p>or click <a href="http://www.surveymonkey.com" rel="nofollow">SurveyMonkey</a></p> 15 | <pre><code>An unknown code fence block</code></pre> 16 | -------------------------------------------------------------------------------- /tests/fixtures/test_rst_008.rst: -------------------------------------------------------------------------------- 1 | Here is some Python code for a ``Dog``: 2 | 3 | .. code-block:: python 4 | 5 | class Dog(Animal): 6 | def __init__(self, name): 7 | self.name = name 8 | 9 | def make_sound(self): 10 | print('Ruff!') 11 | 12 | dog = Dog('Fido') 13 | 14 | and then here is some bash: 15 | 16 | .. code-block:: bash 17 | 18 | if [ "$1" = "--help" ]; then 19 | echo "OK" 20 | fi 21 | 22 | or click `SurveyMonkey <http://www.surveymonkey.com>`_ 23 | 24 | 25 | .. code-block:: abc 26 | 27 | An unknown code fence block 28 | -------------------------------------------------------------------------------- /tests/fixtures/test_rst_admonitions.html: -------------------------------------------------------------------------------- 1 | <aside class="admonition danger"> 2 | <p class="admonition-title">!DANGER!</p> 3 | <p>Will Robinson</p> 4 | </aside> 5 | <aside class="admonition note"> 6 | <p class="admonition-title">Note</p> 7 | <p>F Sharp is a note, right?</p> 8 | </aside> 9 | <aside class="admonition admonition-see-also"> 10 | <p class="admonition-title">See also</p> 11 | <p>A customized admonition. 12 | Read more at <a href="https://docutils.sourceforge.io/docs/ref/rst/directives.html#admonitions" rel="nofollow">docutils</a></p> 13 | </aside> 14 | -------------------------------------------------------------------------------- /tests/fixtures/test_rst_admonitions.rst: -------------------------------------------------------------------------------- 1 | .. danger:: Will Robinson 2 | 3 | .. note:: 4 | 5 | F Sharp is a note, right? 6 | 7 | 8 | .. admonition:: See also 9 | 10 | A customized admonition. 11 | Read more at `docutils <https://docutils.sourceforge.io/docs/ref/rst/directives.html#admonitions>`_ 12 | -------------------------------------------------------------------------------- /tests/fixtures/test_rst_bibtex.html: -------------------------------------------------------------------------------- 1 | <pre><code><span class="nc">@article</span><span class="p">{</span><span class="nl">the_impact_of_pygments_docutils_config_and_html5</span><span class="p">,</span><span class="w"> 2 | </span><span class="na">year</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="s">{2022}</span><span class="p">,</span></code></pre> 3 | -------------------------------------------------------------------------------- /tests/fixtures/test_rst_bibtex.rst: -------------------------------------------------------------------------------- 1 | .. code:: bibtex 2 | 3 | @article{the_impact_of_pygments_docutils_config_and_html5, 4 | year = {2022}, 5 | -------------------------------------------------------------------------------- /tests/fixtures/test_rst_caption.html: -------------------------------------------------------------------------------- 1 | <table> 2 | <caption>Multiplication</caption> 3 | <thead> 4 | <tr><th class="head"><p>1</p></th> 5 | <th class="head"><p>2</p></th> 6 | <th class="head"><p>3</p></th> 7 | <th class="head"><p>4</p></th> 8 | <th class="head"><p>5</p></th> 9 | </tr> 10 | </thead> 11 | <tbody> 12 | <tr><td><p>1</p></td> 13 | <td><p>2</p></td> 14 | <td><p>3</p></td> 15 | <td><p>4</p></td> 16 | <td><p>5</p></td> 17 | </tr> 18 | <tr><td><p>2</p></td> 19 | <td><p>4</p></td> 20 | <td><p>6</p></td> 21 | <td><p>8</p></td> 22 | <td><p>10</p></td> 23 | </tr> 24 | <tr><td><p>3</p></td> 25 | <td><p>6</p></td> 26 | <td><p>9</p></td> 27 | <td><p>12</p></td> 28 | <td><p>15</p></td> 29 | </tr> 30 | <tr><td><p>4</p></td> 31 | <td><p>8</p></td> 32 | <td><p>12</p></td> 33 | <td><p>16</p></td> 34 | <td><p>20</p></td> 35 | </tr> 36 | <tr><td><p>5</p></td> 37 | <td><p>10</p></td> 38 | <td><p>15</p></td> 39 | <td><p>20</p></td> 40 | <td><p>25</p></td> 41 | </tr> 42 | </tbody> 43 | </table> 44 | -------------------------------------------------------------------------------- /tests/fixtures/test_rst_caption.rst: -------------------------------------------------------------------------------- 1 | .. The following test case is based on https://github.com/tiran/defusedxml/blob/f2c7c35b25f80c08923be49ac1f81e9cf95bd2ae/README.txt#L180 2 | 3 | .. csv-table:: Multiplication 4 | :header: "1", "2", "3", "4", "5" 5 | 6 | 1,2,3,4,5 7 | 2,4,6,8,10 8 | 3,6,9,12,15 9 | 4,8,12,16,20 10 | 5,10,15,20,25 11 | -------------------------------------------------------------------------------- /tests/fixtures/test_rst_citations.html: -------------------------------------------------------------------------------- 1 | <p>Citation references, like <a href="#cit2002" id="citation-reference-1" rel="nofollow">[CIT2002]</a>. 2 | Note that citations may get 3 | rearranged, e.g., to the bottom of 4 | the “page”.</p> 5 | <div class="citation-list"> 6 | <div class="citation" id="cit2002"> 7 | <span class="label"><span class="fn-bracket">[</span><a href="#citation-reference-1" rel="nofollow">CIT2002</a><span class="fn-bracket">]</span></span> 8 | <p>A citation 9 | (as often used in journals).</p> 10 | </div> 11 | </div> 12 | <p>Citation labels contain alphanumerics, 13 | underlines, hyphens and fullstops. 14 | Case is not significant.</p> 15 | <p>Given a citation like <a href="#this" id="citation-reference-2" rel="nofollow">[this]</a>, one 16 | can also refer to it like <a href="#this" rel="nofollow">this</a>.</p> 17 | <div class="citation-list"> 18 | <div class="citation" id="this"> 19 | <span class="label"><span class="fn-bracket">[</span><a href="#citation-reference-2" rel="nofollow">this</a><span class="fn-bracket">]</span></span> 20 | <p>here.</p> 21 | </div> 22 | </div> 23 | -------------------------------------------------------------------------------- /tests/fixtures/test_rst_citations.rst: -------------------------------------------------------------------------------- 1 | .. Citations https://docutils.sourceforge.io/docs/user/rst/quickref.html#citations 2 | 3 | Citation references, like [CIT2002]_. 4 | Note that citations may get 5 | rearranged, e.g., to the bottom of 6 | the "page". 7 | 8 | .. [CIT2002] A citation 9 | (as often used in journals). 10 | 11 | Citation labels contain alphanumerics, 12 | underlines, hyphens and fullstops. 13 | Case is not significant. 14 | 15 | Given a citation like [this]_, one 16 | can also refer to it like this_. 17 | 18 | .. [this] here. 19 | -------------------------------------------------------------------------------- /tests/fixtures/test_rst_contents.html: -------------------------------------------------------------------------------- 1 | <nav class="contents" id="contents"> 2 | <p class="topic-title"><a href="#top" rel="nofollow">Contents</a></p> 3 | <ul class="simple"> 4 | <li><p><a href="#features" id="toc-entry-1" rel="nofollow">Features</a></p></li> 5 | <li><p><a href="#installation" id="toc-entry-2" rel="nofollow">Installation</a></p></li> 6 | </ul> 7 | </nav> 8 | <section id="features"> 9 | <h2><a href="#toc-entry-1" rel="nofollow">Features</a></h2> 10 | <ul class="simple"> 11 | <li><p>Eats cheese</p></li> 12 | </ul> 13 | </section> 14 | <section id="installation"> 15 | <h2><a href="#toc-entry-2" rel="nofollow">Installation</a></h2> 16 | <p class="section-subtitle" id="requirements">Requirements</p> 17 | <ul class="simple"> 18 | <li><p>Teeth</p></li> 19 | <li><p>Good taste</p></li> 20 | </ul> 21 | <p>Let’s eat some cheese together!</p> 22 | </section> 23 | -------------------------------------------------------------------------------- /tests/fixtures/test_rst_contents.rst: -------------------------------------------------------------------------------- 1 | .. Tests that using a Table of Contents directive renders correctly 2 | 3 | .. contents:: 4 | 5 | Features 6 | ======== 7 | 8 | * Eats cheese 9 | 10 | Installation 11 | ============ 12 | 13 | Requirements 14 | ------------ 15 | 16 | * Teeth 17 | * Good taste 18 | 19 | Let's eat some cheese together! 20 | -------------------------------------------------------------------------------- /tests/fixtures/test_rst_docinfo.html: -------------------------------------------------------------------------------- 1 | <dl class="docinfo simple"> 2 | <dt class="project">Project<span class="colon">:</span></dt> 3 | <dd class="project"><p>pg_query – Pythonic wrapper around libpg_query</p> 4 | </dd> 5 | <dt class="created">Created<span class="colon">:</span></dt> 6 | <dd class="created"><p>mer 02 ago 2017 14:49:24 CEST</p> 7 | </dd> 8 | <dt class="author">Author<span class="colon">:</span></dt> 9 | <dd class="author"><p>Lele Gaifax <<a href="mailto:lele%40metapensiero.it" rel="nofollow">lele<span>@</span>metapensiero<span>.</span>it</a>></p></dd> 10 | <dt class="license">License<span class="colon">:</span></dt> 11 | <dd class="license"><p>GNU General Public License version 3 or later</p> 12 | </dd> 13 | <dt class="copyright">Copyright<span class="colon">:</span></dt> 14 | <dd class="copyright">© 2017, 2018 Lele Gaifax</dd> 15 | </dl> 16 | <section id="pg-query"> 17 | <h2>pg_query</h2> 18 | </section> 19 | -------------------------------------------------------------------------------- /tests/fixtures/test_rst_docinfo.rst: -------------------------------------------------------------------------------- 1 | :Project: pg_query -- Pythonic wrapper around libpg_query 2 | :Created: mer 02 ago 2017 14:49:24 CEST 3 | :Author: Lele Gaifax <lele@metapensiero.it> 4 | :License: GNU General Public License version 3 or later 5 | :Copyright: © 2017, 2018 Lele Gaifax 6 | 7 | ========== 8 | pg_query 9 | ========== 10 | -------------------------------------------------------------------------------- /tests/fixtures/test_rst_figure.html: -------------------------------------------------------------------------------- 1 | <figure class="align-center"> 2 | <img alt="https://raw.githubusercontent.com/schlatterbeck/plot-antenna/master/test/12-el-azimuth.png" src="https://raw.githubusercontent.com/schlatterbeck/plot-antenna/master/test/12-el-azimuth.png"> 3 | <figcaption> 4 | <p>This is the caption for the figure</p> 5 | </figcaption> 6 | </figure> 7 | -------------------------------------------------------------------------------- /tests/fixtures/test_rst_figure.rst: -------------------------------------------------------------------------------- 1 | .. figure:: https://raw.githubusercontent.com/schlatterbeck/plot-antenna/master/test/12-el-azimuth.png 2 | :align: center 3 | 4 | This is the caption for the figure -------------------------------------------------------------------------------- /tests/fixtures/test_rst_footnotes.html: -------------------------------------------------------------------------------- 1 | <p>Footnote references, like <a href="#footnote-1" id="footnote-reference-1" rel="nofollow"><span class="fn-bracket">[</span>5<span class="fn-bracket">]</span></a>. 2 | Note that footnotes may get 3 | rearranged, e.g., to the bottom of 4 | the “page”.</p> 5 | <aside class="footnote-list brackets"> 6 | <aside class="footnote brackets" id="footnote-1"> 7 | <span class="label"><span class="fn-bracket">[</span><a href="#footnote-reference-1" rel="nofollow">5</a><span class="fn-bracket">]</span></span> 8 | <p>A numerical footnote. Note 9 | there’s no colon after the <span class="docutils literal">]</span>.</p> 10 | </aside> 11 | </aside> 12 | <p>Autonumbered footnotes are 13 | possible, like using <a href="#footnote-2" id="footnote-reference-2" rel="nofollow"><span class="fn-bracket">[</span>1<span class="fn-bracket">]</span></a> and <a href="#footnote-3" id="footnote-reference-3" rel="nofollow"><span class="fn-bracket">[</span>2<span class="fn-bracket">]</span></a>.</p> 14 | <aside class="footnote-list brackets"> 15 | <aside class="footnote brackets" id="footnote-2"> 16 | <span class="label"><span class="fn-bracket">[</span><a href="#footnote-reference-2" rel="nofollow">1</a><span class="fn-bracket">]</span></span> 17 | <p>This is the first one.</p> 18 | </aside> 19 | <aside class="footnote brackets" id="footnote-3"> 20 | <span class="label"><span class="fn-bracket">[</span><a href="#footnote-reference-3" rel="nofollow">2</a><span class="fn-bracket">]</span></span> 21 | <p>This is the second one.</p> 22 | </aside> 23 | </aside> 24 | <p>They may be assigned ‘autonumber 25 | labels’ - for instance, 26 | <a href="#fourth" id="footnote-reference-4" rel="nofollow"><span class="fn-bracket">[</span>4<span class="fn-bracket">]</span></a> and <a href="#third" id="footnote-reference-5" rel="nofollow"><span class="fn-bracket">[</span>3<span class="fn-bracket">]</span></a>.</p> 27 | <aside class="footnote-list brackets"> 28 | <aside class="footnote brackets" id="third"> 29 | <span class="label"><span class="fn-bracket">[</span><a href="#footnote-reference-5" rel="nofollow">3</a><span class="fn-bracket">]</span></span> 30 | <p>a.k.a. <a href="#third" rel="nofollow">third</a></p> 31 | </aside> 32 | <aside class="footnote brackets" id="fourth"> 33 | <span class="label"><span class="fn-bracket">[</span><a href="#footnote-reference-4" rel="nofollow">4</a><span class="fn-bracket">]</span></span> 34 | <p>a.k.a. <a href="#fourth" rel="nofollow">fourth</a></p> 35 | </aside> 36 | </aside> 37 | <p>Auto-symbol footnotes are also 38 | possible, like this: <a href="#footnote-4" id="footnote-reference-6" rel="nofollow"><span class="fn-bracket">[</span>*<span class="fn-bracket">]</span></a> and <a href="#footnote-5" id="footnote-reference-7" rel="nofollow"><span class="fn-bracket">[</span>†<span class="fn-bracket">]</span></a>.</p> 39 | <aside class="footnote-list brackets"> 40 | <aside class="footnote brackets" id="footnote-4"> 41 | <span class="label"><span class="fn-bracket">[</span><a href="#footnote-reference-6" rel="nofollow">*</a><span class="fn-bracket">]</span></span> 42 | <p>This is the first one.</p> 43 | </aside> 44 | <aside class="footnote brackets" id="footnote-5"> 45 | <span class="label"><span class="fn-bracket">[</span><a href="#footnote-reference-7" rel="nofollow">†</a><span class="fn-bracket">]</span></span> 46 | <p>This is the second one.</p> 47 | </aside> 48 | </aside> 49 | -------------------------------------------------------------------------------- /tests/fixtures/test_rst_footnotes.rst: -------------------------------------------------------------------------------- 1 | .. Footnotes https://docutils.sourceforge.io/docs/user/rst/quickref.html#footnotes 2 | 3 | Footnote references, like [5]_. 4 | Note that footnotes may get 5 | rearranged, e.g., to the bottom of 6 | the "page". 7 | 8 | .. [5] A numerical footnote. Note 9 | there's no colon after the ``]``. 10 | 11 | Autonumbered footnotes are 12 | possible, like using [#]_ and [#]_. 13 | 14 | .. [#] This is the first one. 15 | .. [#] This is the second one. 16 | 17 | They may be assigned 'autonumber 18 | labels' - for instance, 19 | [#fourth]_ and [#third]_. 20 | 21 | .. [#third] a.k.a. third_ 22 | 23 | .. [#fourth] a.k.a. fourth_ 24 | 25 | Auto-symbol footnotes are also 26 | possible, like this: [*]_ and [*]_. 27 | 28 | .. [*] This is the first one. 29 | .. [*] This is the second one. 30 | -------------------------------------------------------------------------------- /tests/fixtures/test_rst_linkify.html: -------------------------------------------------------------------------------- 1 | <a href="https://travis-ci.org/tulsawebdevs/django-multi-gtfs" rel="nofollow"> 2 | <img alt="https://travis-ci.org/tulsawebdevs/django-multi-gtfs.svg?branch=master" src="https://travis-ci.org/tulsawebdevs/django-multi-gtfs.svg?branch=master"> 3 | </a> 4 | <a href="https://coveralls.io/github/tulsawebdevs/django-multi-gtfs" rel="nofollow"> 5 | <img alt="https://coveralls.io/repos/github/tulsawebdevs/django-multi-gtfs/badge.svg?branch=django19" src="https://coveralls.io/repos/github/tulsawebdevs/django-multi-gtfs/badge.svg?branch=django19"> 6 | </a> 7 | <p><strong>multigtfs</strong> is an <a href="http://choosealicense.com/licenses/apache/" rel="nofollow">Apache 2.0</a>-licensed Django app that supports importing 8 | and exporting of GTFS feeds. All features of the <a href="https://developers.google.com/transit/gtfs/reference" rel="nofollow">June 20, 2012 reference</a> 9 | are supported, including <a href="https://developers.google.com/transit/gtfs/changes#RevisionHistory" rel="nofollow">all changes</a> up to February 17, 2014. 10 | It allows multiple feeds to be stored in the database at once.</p> 11 | <p>It requires a spatial databases compatible with <a href="https://docs.djangoproject.com/en/dev/ref/contrib/gis/" rel="nofollow">GeoDjango</a>. <a href="http://www.postgresql.org" rel="nofollow">PostgreSQL</a> 9.x 12 | and <a href="http://postgis.refractions.net" rel="nofollow">PostGIS</a> 2.x are recommended for development and production, since these 13 | support all the GeoDjango features.</p> 14 | <section id="status"> 15 | <h2>Status</h2> 16 | <p>multigtfs is ready for your GTFS project.</p> 17 | <p>Point releases (0.4.1 to 0.4.2) should be safe, only adding features or fixing 18 | bugs. Minor updates (0.3.3 to 0.4.0) may include significant changes that will 19 | break relying code. In the worst case scenario, you may need to export your 20 | GTFS feeds in the original version, update multigtfs and your code, and 21 | re-import.</p> 22 | <p>multigtfs works with Django 1.5 through 1.9. In the next version, support 23 | will be limited to Django’s supported releases, so if you are using an old 24 | version you will want to update to at least 1.8, the long-term support (LTS) 25 | release.</p> 26 | <p>All valid GTFS feeds are supported for import and export. This includes 27 | feeds with extra columns not yet included in the GTFS spec, and feeds that 28 | omit <span class="docutils literal">calendar.txt</span> in favor of <span class="docutils literal">calendar_dates.txt</span> (such as the TriMet 29 | archive feeds). If you find a feed that doesn’t work, <a href="https://github.com/tulsawebdevs/django-multi-gtfs/issues" rel="nofollow">file a bug</a>!</p> 30 | <p>See the <a href="https://github.com/tulsawebdevs/django-multi-gtfs/issues?state=open" rel="nofollow">issues list</a> for more details on bugs and feature requests.</p> 31 | </section> 32 | <section id="example-project"> 33 | <h2>Example project</h2> 34 | <p>Check out the <a href="examples/explore/README.md" rel="nofollow">example project</a>.</p> 35 | </section> 36 | <section id="development"> 37 | <h2>Development</h2> 38 | <dl class="field-list simple"> 39 | <dt>Code<span class="colon">:</span></dt> 40 | <dd><p><a href="https://github.com/tulsawebdevs/django-multi-gtfs" rel="nofollow">https://github.com/tulsawebdevs/django-multi-gtfs</a></p> 41 | </dd> 42 | <dt>Issues<span class="colon">:</span></dt> 43 | <dd><p><a href="https://github.com/tulsawebdevs/django-multi-gtfs/issues" rel="nofollow">https://github.com/tulsawebdevs/django-multi-gtfs/issues</a></p> 44 | </dd> 45 | <dt>Dev Docs<span class="colon">:</span></dt> 46 | <dd><p><a href="http://multigtfs.readthedocs.org/" rel="nofollow">http://multigtfs.readthedocs.org/</a></p> 47 | </dd> 48 | <dt>IRC<span class="colon">:</span></dt> 49 | <dd><p><a rel="nofollow">irc://irc.freenode.net/tulsawebdevs</a></p> 50 | </dd> 51 | </dl> 52 | </section> 53 | -------------------------------------------------------------------------------- /tests/fixtures/test_rst_linkify.rst: -------------------------------------------------------------------------------- 1 | multigtfs: GTFS as a Django app 2 | =============================== 3 | 4 | .. image:: https://travis-ci.org/tulsawebdevs/django-multi-gtfs.svg?branch=master 5 | :target: https://travis-ci.org/tulsawebdevs/django-multi-gtfs 6 | 7 | .. image:: https://coveralls.io/repos/github/tulsawebdevs/django-multi-gtfs/badge.svg?branch=django19 8 | :target: https://coveralls.io/github/tulsawebdevs/django-multi-gtfs 9 | 10 | .. Omit badges from docs 11 | 12 | **multigtfs** is an `Apache 2.0`_-licensed Django app that supports importing 13 | and exporting of GTFS feeds. All features of the `June 20, 2012 reference`_ 14 | are supported, including `all changes`_ up to February 17, 2014. 15 | It allows multiple feeds to be stored in the database at once. 16 | 17 | It requires a spatial databases compatible with GeoDjango_. PostgreSQL_ 9.x 18 | and PostGIS_ 2.x are recommended for development and production, since these 19 | support all the GeoDjango features. 20 | 21 | Status 22 | ------ 23 | multigtfs is ready for your GTFS project. 24 | 25 | Point releases (0.4.1 to 0.4.2) should be safe, only adding features or fixing 26 | bugs. Minor updates (0.3.3 to 0.4.0) may include significant changes that will 27 | break relying code. In the worst case scenario, you may need to export your 28 | GTFS feeds in the original version, update multigtfs and your code, and 29 | re-import. 30 | 31 | multigtfs works with Django 1.5 through 1.9. In the next version, support 32 | will be limited to Django's supported releases, so if you are using an old 33 | version you will want to update to at least 1.8, the long-term support (LTS) 34 | release. 35 | 36 | All valid GTFS feeds are supported for import and export. This includes 37 | feeds with extra columns not yet included in the GTFS spec, and feeds that 38 | omit ``calendar.txt`` in favor of ``calendar_dates.txt`` (such as the TriMet 39 | archive feeds). If you find a feed that doesn't work, `file a bug`_! 40 | 41 | See the `issues list`_ for more details on bugs and feature requests. 42 | 43 | Example project 44 | --------------- 45 | Check out the `example project <examples/explore/README.md>`_. 46 | 47 | Development 48 | ----------- 49 | 50 | :Code: https://github.com/tulsawebdevs/django-multi-gtfs 51 | :Issues: https://github.com/tulsawebdevs/django-multi-gtfs/issues 52 | :Dev Docs: http://multigtfs.readthedocs.org/ 53 | :IRC: irc://irc.freenode.net/tulsawebdevs 54 | 55 | 56 | .. _`Apache 2.0`: http://choosealicense.com/licenses/apache/ 57 | .. _`June 20, 2012 reference`: https://developers.google.com/transit/gtfs/reference 58 | .. _`all changes`: https://developers.google.com/transit/gtfs/changes#RevisionHistory 59 | .. _PostgreSQL: http://www.postgresql.org 60 | .. _PostGIS: http://postgis.refractions.net 61 | .. _GeoDjango: https://docs.djangoproject.com/en/dev/ref/contrib/gis/ 62 | .. _`file a bug`: https://github.com/tulsawebdevs/django-multi-gtfs/issues 63 | .. _`issues list`: https://github.com/tulsawebdevs/django-multi-gtfs/issues?state=open 64 | -------------------------------------------------------------------------------- /tests/fixtures/test_rst_math.html: -------------------------------------------------------------------------------- 1 | <p>A <span class="docutils literal">math</span> directive:</p> 2 | <div class="math"> 3 | \begin{equation*} 4 | \alpha _t(i) = P(O_1, O_2, \ldots O_t, q_t = S_i \lambda ) 5 | \end{equation*} 6 | </div> 7 | <p>A <span class="docutils literal">:math:</span> role:</p> 8 | <p>The area of a circle is <span class="math">\(A_\text{c} = (\pi/4) d^2\)</span>.</p> 9 | -------------------------------------------------------------------------------- /tests/fixtures/test_rst_math.rst: -------------------------------------------------------------------------------- 1 | .. Sample from https://docutils.sourceforge.io/docs/ref/rst/directives.html#math 2 | 3 | A ``math`` directive: 4 | 5 | .. math:: 6 | 7 | α_t(i) = P(O_1, O_2, … O_t, q_t = S_i λ) 8 | 9 | 10 | .. Sample from https://docutils.sourceforge.io/docs/ref/rst/roles.html#math 11 | 12 | A ``:math:`` role: 13 | 14 | The area of a circle is :math:`A_\text{c} = (\pi/4) d^2`. 15 | -------------------------------------------------------------------------------- /tests/fixtures/test_rst_png.html: -------------------------------------------------------------------------------- 1 | <img alt="https://example.com/badge.png" src="https://example.com/badge.png"> 2 | -------------------------------------------------------------------------------- /tests/fixtures/test_rst_png.rst: -------------------------------------------------------------------------------- 1 | .. image:: https://example.com/badge.png 2 | -------------------------------------------------------------------------------- /tests/fixtures/test_rst_png_attrs.html: -------------------------------------------------------------------------------- 1 | <img alt="alternate text" class="align-right" height="100px" src="https://example.com/badge.png" width="25.0%"> 2 | -------------------------------------------------------------------------------- /tests/fixtures/test_rst_png_attrs.rst: -------------------------------------------------------------------------------- 1 | .. image:: https://example.com/badge.png 2 | :height: 100px 3 | :width: 25.0% 4 | :alt: alternate text 5 | :align: right 6 | -------------------------------------------------------------------------------- /tests/fixtures/test_rst_svg.html: -------------------------------------------------------------------------------- 1 | <img alt="https://example.com/badge.svg" src="https://example.com/badge.svg"> 2 | -------------------------------------------------------------------------------- /tests/fixtures/test_rst_svg.rst: -------------------------------------------------------------------------------- 1 | .. image:: https://example.com/badge.svg 2 | -------------------------------------------------------------------------------- /tests/fixtures/test_rst_svg_attrs.html: -------------------------------------------------------------------------------- 1 | <img alt="alternate text" class="align-right" height="100px" src="https://example.com/badge.svg" width="25.0%"> 2 | -------------------------------------------------------------------------------- /tests/fixtures/test_rst_svg_attrs.rst: -------------------------------------------------------------------------------- 1 | .. image:: https://example.com/badge.svg 2 | :height: 100px 3 | :width: 25.0% 4 | :alt: alternate text 5 | :align: right 6 | -------------------------------------------------------------------------------- /tests/fixtures/test_rst_tables.html: -------------------------------------------------------------------------------- 1 | <table> 2 | <thead> 3 | <tr><th class="head"><p>Header row, column 1 4 | (header rows optional)</p></th> 5 | <th class="head"><p>Header 2</p></th> 6 | <th class="head"><p>Header 3</p></th> 7 | <th class="head"><p>Header 4</p></th> 8 | </tr> 9 | </thead> 10 | <tbody> 11 | <tr><td><p>body row 1, column 1</p></td> 12 | <td><p>column 2</p></td> 13 | <td><p>column 3</p></td> 14 | <td><p>column 4</p></td> 15 | </tr> 16 | <tr><td><p>body row 2</p></td> 17 | <td colspan="3"><p>Cells may span columns.</p></td> 18 | </tr> 19 | <tr><td><p>body row 3</p></td> 20 | <td rowspan="2"><p>Cells may 21 | span rows.</p></td> 22 | <td colspan="2" rowspan="2"><ul class="simple"> 23 | <li><p>Table cells</p></li> 24 | <li><p>contain</p></li> 25 | <li><p>body elements.</p></li> 26 | </ul> 27 | </td> 28 | </tr> 29 | <tr><td><p>body row 4</p></td> 30 | </tr> 31 | </tbody> 32 | </table> 33 | <table> 34 | <thead> 35 | <tr><th class="head"><p>title1</p></th> 36 | <th class="head"><p>title2</p></th> 37 | </tr> 38 | </thead> 39 | <tbody> 40 | <tr><td><p>col1</p></td> 41 | <td><p>col2</p></td> 42 | </tr> 43 | <tr><td rowspan="2"><p>mutirow</p></td> 44 | <td><p>cell1</p></td> 45 | </tr> 46 | <tr><td><p>cell2</p></td> 47 | </tr> 48 | <tr><td><p>singlerow</p></td> 49 | <td><p>cell3</p></td> 50 | </tr> 51 | </tbody> 52 | </table> 53 | -------------------------------------------------------------------------------- /tests/fixtures/test_rst_tables.rst: -------------------------------------------------------------------------------- 1 | .. Example from https://docutils.sourceforge.io/docs/ref/rst/restructuredtext.html#tables 2 | 3 | +------------------------+------------+----------+----------+ 4 | | Header row, column 1 | Header 2 | Header 3 | Header 4 | 5 | | (header rows optional) | | | | 6 | +========================+============+==========+==========+ 7 | | body row 1, column 1 | column 2 | column 3 | column 4 | 8 | +------------------------+------------+----------+----------+ 9 | | body row 2 | Cells may span columns. | 10 | +------------------------+------------+---------------------+ 11 | | body row 3 | Cells may | - Table cells | 12 | +------------------------+ span rows. | - contain | 13 | | body row 4 | | - body elements. | 14 | +------------------------+------------+---------------------+ 15 | 16 | .. Example from #182 17 | 18 | +---------------+---------------+ 19 | | title1 | title2 | 20 | +===============+===============+ 21 | | col1 | col2 | 22 | +---------------+---------------+ 23 | | mutirow | cell1 | 24 | | +---------------+ 25 | | | cell2 | 26 | +---------------+---------------+ 27 | | singlerow | cell3 | 28 | +---------------+---------------+ 29 | -------------------------------------------------------------------------------- /tests/fixtures/test_txt_001.html: -------------------------------------------------------------------------------- 1 | # Algen<br><br>Algen generates opionated ORM classes for sqlalchemy given a simple schema<br>either as a commandline string or as a yaml file.<br>It is designed to have minimal dependencies and is trivially extensible.<br>A command line tool is bundled along to help generate the models.<br>For DB specific types, only postgres is currently supported.<br>The tool currently assumes that sqlalchemy's declarative base object<br>is to be imported like ```from .alchemy_base import Base```<br><br><br>### CLI<br>```bash<br>$ algen --help<br>Usage: algen [OPTIONS]<br><br>Options:<br> -n, --name TEXT Name of model<br> -c, --columns TEXT Column definition. e.g. col_name:col_type Can be<br> used multiple times hence named columns. e.g. -c<br> foo:Int -c bar:Unicode(20)<br> -d, --destination PATH Destination directory. Default will assume 'Models'<br> directory inside the current working directory<br> -y, --yaml PATH Yaml file describing the Model. This supersedes the<br> column definition provided through --columns option.<br> --help Show this message and exit.<br>```<br><br>Given a file as follows:<br>```yaml<br>Person:<br> columns:<br> - name: id<br> type: BigInteger<br> primary_key: True<br> auto_increment: True<br> - name: name<br> type: Unicode(255)<br> - name: is_vip<br> type: Boolean<br> - name: created_at<br> type: DateTime(timezone=True)<br><br>Address:<br> columns:<br> - name: id<br> type: BigInteger<br> primary_key: True<br> auto_increment: True<br> - name: line1<br> type: Unicode()<br> - name: line2<br> type: Unicode()<br> - name: line3<br> type: Unicode()<br> - name: postcode<br> type: Unicode(10)<br> index: True<br>```<br><br>The cli tool will create two the following two files ```Person.py``` and ```Address.py```.<br><br>```python<br>from __future__ import unicode_literals, absolute_import, print_function<br><br>from collections import namedtuple<br><br>from sqlalchemy import Column, DateTime, Boolean, Unicode, BigInteger<br><br><br>from .alchemy_base import Base<br><br>__author__ = 'danishabdullah'<br><br><br>class Person(Base):<br> __tablename__ = 'persons'<br><br> id = Column(BigInteger, primary_key=True, auto_increment=True)<br> name = Column(Unicode(255), )<br> is_vip = Column(Boolean, )<br> created_at = Column(DateTime(timezone=True), )<br><br><br> def __init__(self, id=None, name=None, is_vip=None, created_at=None):<br> self.id = id<br> self.name = name<br> self.is_vip = is_vip<br> self.created_at = created_at<br><br> def add(self, session):<br> session.add(self)<br><br> def update(self, name=None, is_vip=None, created_at=None):<br> # This function only updates a value if it is not None.<br> # Falsy values go through in the normal way.<br> # To set things to None use the usual syntax:<br> # Person.column_name = None<br><br> if name is not None:<br> self.name = name<br><br> if is_vip is not None:<br> self.is_vip = is_vip<br><br> if created_at is not None:<br> self.created_at = created_at<br><br> def delete(self, session):<br> session.delete(self)<br><br> def to_dict(self):<br> return {x: y for x, y in self.__dict__.items() if not x.startswith("_sa")}<br><br> def get_proxy_cls(self):<br> # PersonProxy is useful when you want to persist data<br> # independent of the sqlalchemy session. It's just a namedtuple<br> # that has very low memory/cpu footprint compared the regular<br> # orm class instances.<br> keys = self.to_dict().keys()<br> name = "PersonProxy"<br> return namedtuple(name, keys)<br><br> def to_proxy(self):<br> # Proxy-ing is useful when you want to persist data<br> # independent of the sqlalchemy session. It's just a namedtuple<br> # that has very low memory/cpu footprint compared the regular<br> # orm class instances.<br> cls = self._get_proxy_cls()<br> return cls(**self.to_dict())<br><br> @classmethod<br> def from_proxy(cls, proxy):<br> return cls(**proxy._asdict())<br><br> def __hash__(self):<br> return hash(str(self.id))<br><br> def __eq__(self, other):<br> return (self.id == other.id)<br><br> def __neq__(self, other):<br> return not (self.id == other.id)<br><br> def __str__(self):<br> return "<Person: {id}>".format(id=self.id)<br><br> def __unicode__(self):<br> return "<Person: {id}>".format(id=self.id)<br><br> def __repr__(self):<br> return "<Person: {id}>".format(id=self.id)<br>```<br><br>```python<br>from __future__ import unicode_literals, absolute_import, print_function<br><br>from collections import namedtuple<br><br>from sqlalchemy import Column, Unicode, BigInteger<br><br><br>from .alchemy_base import Base<br><br>__author__ = 'danishabdullah'<br><br><br>class Address(Base):<br> __tablename__ = 'addresses'<br><br> id = Column(BigInteger, primary_key=True, auto_increment=True)<br> line1 = Column(Unicode(), )<br> line2 = Column(Unicode(), )<br> line3 = Column(Unicode(), )<br> postcode = Column(Unicode(10), index=True)<br><br><br> def __init__(self, id=None, line1=None, line2=None, line3=None, postcode=None):<br> self.id = id<br> self.line1 = line1<br> self.line2 = line2<br> self.line3 = line3<br> self.postcode = postcode<br><br> def add(self, session):<br> session.add(self)<br><br> def update(self, line1=None, line2=None, line3=None, postcode=None):<br> # This function only updates a value if it is not None.<br> # Falsy values go through in the normal way.<br> # To set things to None use the usual syntax:<br> # Address.column_name = None<br><br> if line1 is not None:<br> self.line1 = line1<br><br> if line2 is not None:<br> self.line2 = line2<br><br> if line3 is not None:<br> self.line3 = line3<br><br> if postcode is not None:<br> self.postcode = postcode<br><br> def delete(self, session):<br> session.delete(self)<br><br> def to_dict(self):<br> return {x: y for x, y in self.__dict__.items() if not x.startswith("_sa")}<br><br> def get_proxy_cls(self):<br> # AddressProxy is useful when you want to persist data<br> # independent of the sqlalchemy session. It's just a namedtuple<br> # that has very low memory/cpu footprint compared the regular<br> # orm class instances.<br> keys = self.to_dict().keys()<br> name = "AddressProxy"<br> return namedtuple(name, keys)<br><br> def to_proxy(self):<br> # Proxy-ing is useful when you want to persist data<br> # independent of the sqlalchemy session. It's just a namedtuple<br> # that has very low memory/cpu footprint compared the regular<br> # orm class instances.<br> cls = self._get_proxy_cls()<br> return cls(**self.to_dict())<br><br> @classmethod<br> def from_proxy(cls, proxy):<br> return cls(**proxy._asdict())<br><br> def __hash__(self):<br> return hash(str(self.id))<br><br> def __eq__(self, other):<br> return (self.id == other.id)<br><br> def __neq__(self, other):<br> return not (self.id == other.id)<br><br> def __str__(self):<br> return "<Address: {id}>".format(id=self.id)<br><br> def __unicode__(self):<br> return "<Address: {id}>".format(id=self.id)<br><br> def __repr__(self):<br> return "<Address: {id}>".format(id=self.id)<br><br>```<br> -------------------------------------------------------------------------------- /tests/fixtures/test_txt_001.txt: -------------------------------------------------------------------------------- 1 | # Algen 2 | 3 | Algen generates opionated ORM classes for sqlalchemy given a simple schema 4 | either as a commandline string or as a yaml file. 5 | It is designed to have minimal dependencies and is trivially extensible. 6 | A command line tool is bundled along to help generate the models. 7 | For DB specific types, only postgres is currently supported. 8 | The tool currently assumes that sqlalchemy's declarative base object 9 | is to be imported like ```from .alchemy_base import Base``` 10 | 11 | 12 | ### CLI 13 | ```bash 14 | $ algen --help 15 | Usage: algen [OPTIONS] 16 | 17 | Options: 18 | -n, --name TEXT Name of model 19 | -c, --columns TEXT Column definition. e.g. col_name:col_type Can be 20 | used multiple times hence named columns. e.g. -c 21 | foo:Int -c bar:Unicode(20) 22 | -d, --destination PATH Destination directory. Default will assume 'Models' 23 | directory inside the current working directory 24 | -y, --yaml PATH Yaml file describing the Model. This supersedes the 25 | column definition provided through --columns option. 26 | --help Show this message and exit. 27 | ``` 28 | 29 | Given a file as follows: 30 | ```yaml 31 | Person: 32 | columns: 33 | - name: id 34 | type: BigInteger 35 | primary_key: True 36 | auto_increment: True 37 | - name: name 38 | type: Unicode(255) 39 | - name: is_vip 40 | type: Boolean 41 | - name: created_at 42 | type: DateTime(timezone=True) 43 | 44 | Address: 45 | columns: 46 | - name: id 47 | type: BigInteger 48 | primary_key: True 49 | auto_increment: True 50 | - name: line1 51 | type: Unicode() 52 | - name: line2 53 | type: Unicode() 54 | - name: line3 55 | type: Unicode() 56 | - name: postcode 57 | type: Unicode(10) 58 | index: True 59 | ``` 60 | 61 | The cli tool will create two the following two files ```Person.py``` and ```Address.py```. 62 | 63 | ```python 64 | from __future__ import unicode_literals, absolute_import, print_function 65 | 66 | from collections import namedtuple 67 | 68 | from sqlalchemy import Column, DateTime, Boolean, Unicode, BigInteger 69 | 70 | 71 | from .alchemy_base import Base 72 | 73 | __author__ = 'danishabdullah' 74 | 75 | 76 | class Person(Base): 77 | __tablename__ = 'persons' 78 | 79 | id = Column(BigInteger, primary_key=True, auto_increment=True) 80 | name = Column(Unicode(255), ) 81 | is_vip = Column(Boolean, ) 82 | created_at = Column(DateTime(timezone=True), ) 83 | 84 | 85 | def __init__(self, id=None, name=None, is_vip=None, created_at=None): 86 | self.id = id 87 | self.name = name 88 | self.is_vip = is_vip 89 | self.created_at = created_at 90 | 91 | def add(self, session): 92 | session.add(self) 93 | 94 | def update(self, name=None, is_vip=None, created_at=None): 95 | # This function only updates a value if it is not None. 96 | # Falsy values go through in the normal way. 97 | # To set things to None use the usual syntax: 98 | # Person.column_name = None 99 | 100 | if name is not None: 101 | self.name = name 102 | 103 | if is_vip is not None: 104 | self.is_vip = is_vip 105 | 106 | if created_at is not None: 107 | self.created_at = created_at 108 | 109 | def delete(self, session): 110 | session.delete(self) 111 | 112 | def to_dict(self): 113 | return {x: y for x, y in self.__dict__.items() if not x.startswith("_sa")} 114 | 115 | def get_proxy_cls(self): 116 | # PersonProxy is useful when you want to persist data 117 | # independent of the sqlalchemy session. It's just a namedtuple 118 | # that has very low memory/cpu footprint compared the regular 119 | # orm class instances. 120 | keys = self.to_dict().keys() 121 | name = "PersonProxy" 122 | return namedtuple(name, keys) 123 | 124 | def to_proxy(self): 125 | # Proxy-ing is useful when you want to persist data 126 | # independent of the sqlalchemy session. It's just a namedtuple 127 | # that has very low memory/cpu footprint compared the regular 128 | # orm class instances. 129 | cls = self._get_proxy_cls() 130 | return cls(**self.to_dict()) 131 | 132 | @classmethod 133 | def from_proxy(cls, proxy): 134 | return cls(**proxy._asdict()) 135 | 136 | def __hash__(self): 137 | return hash(str(self.id)) 138 | 139 | def __eq__(self, other): 140 | return (self.id == other.id) 141 | 142 | def __neq__(self, other): 143 | return not (self.id == other.id) 144 | 145 | def __str__(self): 146 | return "<Person: {id}>".format(id=self.id) 147 | 148 | def __unicode__(self): 149 | return "<Person: {id}>".format(id=self.id) 150 | 151 | def __repr__(self): 152 | return "<Person: {id}>".format(id=self.id) 153 | ``` 154 | 155 | ```python 156 | from __future__ import unicode_literals, absolute_import, print_function 157 | 158 | from collections import namedtuple 159 | 160 | from sqlalchemy import Column, Unicode, BigInteger 161 | 162 | 163 | from .alchemy_base import Base 164 | 165 | __author__ = 'danishabdullah' 166 | 167 | 168 | class Address(Base): 169 | __tablename__ = 'addresses' 170 | 171 | id = Column(BigInteger, primary_key=True, auto_increment=True) 172 | line1 = Column(Unicode(), ) 173 | line2 = Column(Unicode(), ) 174 | line3 = Column(Unicode(), ) 175 | postcode = Column(Unicode(10), index=True) 176 | 177 | 178 | def __init__(self, id=None, line1=None, line2=None, line3=None, postcode=None): 179 | self.id = id 180 | self.line1 = line1 181 | self.line2 = line2 182 | self.line3 = line3 183 | self.postcode = postcode 184 | 185 | def add(self, session): 186 | session.add(self) 187 | 188 | def update(self, line1=None, line2=None, line3=None, postcode=None): 189 | # This function only updates a value if it is not None. 190 | # Falsy values go through in the normal way. 191 | # To set things to None use the usual syntax: 192 | # Address.column_name = None 193 | 194 | if line1 is not None: 195 | self.line1 = line1 196 | 197 | if line2 is not None: 198 | self.line2 = line2 199 | 200 | if line3 is not None: 201 | self.line3 = line3 202 | 203 | if postcode is not None: 204 | self.postcode = postcode 205 | 206 | def delete(self, session): 207 | session.delete(self) 208 | 209 | def to_dict(self): 210 | return {x: y for x, y in self.__dict__.items() if not x.startswith("_sa")} 211 | 212 | def get_proxy_cls(self): 213 | # AddressProxy is useful when you want to persist data 214 | # independent of the sqlalchemy session. It's just a namedtuple 215 | # that has very low memory/cpu footprint compared the regular 216 | # orm class instances. 217 | keys = self.to_dict().keys() 218 | name = "AddressProxy" 219 | return namedtuple(name, keys) 220 | 221 | def to_proxy(self): 222 | # Proxy-ing is useful when you want to persist data 223 | # independent of the sqlalchemy session. It's just a namedtuple 224 | # that has very low memory/cpu footprint compared the regular 225 | # orm class instances. 226 | cls = self._get_proxy_cls() 227 | return cls(**self.to_dict()) 228 | 229 | @classmethod 230 | def from_proxy(cls, proxy): 231 | return cls(**proxy._asdict()) 232 | 233 | def __hash__(self): 234 | return hash(str(self.id)) 235 | 236 | def __eq__(self, other): 237 | return (self.id == other.id) 238 | 239 | def __neq__(self, other): 240 | return not (self.id == other.id) 241 | 242 | def __str__(self): 243 | return "<Address: {id}>".format(id=self.id) 244 | 245 | def __unicode__(self): 246 | return "<Address: {id}>".format(id=self.id) 247 | 248 | def __repr__(self): 249 | return "<Address: {id}>".format(id=self.id) 250 | 251 | ``` 252 | -------------------------------------------------------------------------------- /tests/test_clean.py: -------------------------------------------------------------------------------- 1 | from readme_renderer.clean import clean 2 | 3 | 4 | def test_invalid_link(): 5 | assert clean( 6 | '<a href="http://exam](ple.com">foo</a>' 7 | ) == '<a rel="nofollow">foo</a>' 8 | -------------------------------------------------------------------------------- /tests/test_cli.py: -------------------------------------------------------------------------------- 1 | import pathlib 2 | import pytest 3 | from readme_renderer.__main__ import main 4 | import tempfile 5 | from unittest import mock 6 | 7 | 8 | @pytest.fixture(params=["test_CommonMark_001.md", "test_rst_003.rst", 9 | "test_GFM_001.md", "test_txt_001.txt"]) 10 | def input_file(request): 11 | path = pathlib.Path("tests/fixtures", request.param) 12 | # Skip markdown tests if the cmarkgfm optional dependency is not installed. 13 | if path.suffix == ".md": 14 | pytest.importorskip("cmarkgfm") 15 | return path 16 | 17 | 18 | @pytest.mark.parametrize("output_file", [False, True]) 19 | def test_cli_input_file(input_file, output_file): 20 | with mock.patch("builtins.print") as print_: 21 | if output_file: 22 | with tempfile.TemporaryDirectory() as tmpdir: 23 | output = pathlib.Path(tmpdir) / "output.html" 24 | main(["-o", str(output), str(input_file)]) 25 | with output.open() as fp: 26 | result = fp.read() 27 | else: 28 | main([str(input_file)]) 29 | 30 | print_.assert_called_once() 31 | (result,), kwargs = print_.call_args 32 | 33 | with input_file.with_suffix(".html").open() as fp: 34 | expected = fp.read() 35 | assert result.strip() == expected.strip() 36 | 37 | if output_file: 38 | assert kwargs["file"].name == str(output) 39 | 40 | 41 | def test_cli_invalid_format(): 42 | with mock.patch("pathlib.Path.open"), \ 43 | pytest.raises(ValueError, match="invalid README format: invalid"): 44 | main(["no-file.invalid"]) 45 | 46 | 47 | def test_cli_explicit_format(input_file): 48 | fmt = input_file.suffix.lstrip(".") 49 | with input_file.open() as fp, \ 50 | mock.patch("pathlib.Path.open", return_value=fp), \ 51 | mock.patch("builtins.print") as print_: 52 | main(["-f", fmt, "no-file.invalid"]) 53 | print_.assert_called_once() 54 | (result,), _ = print_.call_args 55 | 56 | with input_file.with_suffix(".html").open() as fp: 57 | assert result.strip() == fp.read().strip() 58 | 59 | 60 | @pytest.mark.parametrize("package, contains", [ 61 | ("readme_renderer", "Readme Renderer is a library that will safely render"), 62 | ("docutils", "Docutils is a modular system for processing documentation"), 63 | ]) 64 | def test_cli_package(package, contains): 65 | with mock.patch("builtins.print") as print_: 66 | main(["-p", package]) 67 | print_.assert_called_once() 68 | (result,), _ = print_.call_args 69 | assert contains in result 70 | -------------------------------------------------------------------------------- /tests/test_markdown.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | import pytest 4 | 5 | from readme_renderer.markdown import render, variants 6 | 7 | 8 | @pytest.mark.parametrize( 9 | ("md_filename", "html_filename", "variant"), 10 | [ 11 | (pytest.param(fn, fn.with_suffix(".html"), variant, id=fn.name)) 12 | for variant in variants 13 | for fn in Path(__file__).parent.glob(f"fixtures/test_{variant}*.md") 14 | ], 15 | ) 16 | def test_md_fixtures(md_filename, html_filename, variant): 17 | # Get our Markup 18 | with open(md_filename, encoding='utf-8') as f: 19 | md_markup = f.read() 20 | 21 | # Get our expected 22 | with open(html_filename, encoding="utf-8") as f: 23 | expected = f.read() 24 | 25 | assert render(md_markup, variant=variant) == expected 26 | 27 | 28 | def test_missing_variant(): 29 | assert render('Hello', variant="InvalidVariant") is None 30 | -------------------------------------------------------------------------------- /tests/test_noextra.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from readme_renderer.markdown import render, variants 4 | 5 | 6 | @pytest.mark.skipif(variants, reason="Extra is installed") 7 | @pytest.mark.parametrize("variant", ('GFM', 'CommonMark')) 8 | def test_no_extra(variant): 9 | with pytest.warns(UserWarning) as warnings: 10 | assert render('Hello', variant=variant) is None 11 | assert len(warnings) == 1 12 | assert warnings[0].message.args[0] == ( 13 | "Markdown renderers are not available. " 14 | "Install 'readme_renderer[md]' to enable Markdown rendering." 15 | ) 16 | -------------------------------------------------------------------------------- /tests/test_rst.py: -------------------------------------------------------------------------------- 1 | import io 2 | from pathlib import Path 3 | 4 | import pytest 5 | 6 | from readme_renderer.rst import render 7 | 8 | 9 | @pytest.mark.parametrize( 10 | ("rst_filename", "html_filename"), 11 | [ 12 | (pytest.param(fn, fn.with_suffix(".html"), id=fn.name)) 13 | for fn in Path(__file__).parent.glob("fixtures/test_*.rst") 14 | ], 15 | ) 16 | def test_rst_fixtures(rst_filename, html_filename): 17 | # Get our Markup 18 | with open(rst_filename, encoding='utf-8') as f: 19 | rst_markup = f.read() 20 | 21 | # Get our expected 22 | with open(html_filename, encoding="utf-8") as f: 23 | expected = f.read() 24 | 25 | out = render(rst_markup) 26 | 27 | if "<" in expected: 28 | assert out == expected 29 | else: 30 | assert out is None 31 | 32 | 33 | def test_rst_001(): 34 | assert render('Hello') == '<p>Hello</p>\n' 35 | 36 | 37 | def test_rst_002(): 38 | assert render('http://mymalicioussite.com/') == ( 39 | '<p><a href="http://mymalicioussite.com/" rel="nofollow">' 40 | 'http://mymalicioussite.com/</a></p>\n' 41 | ) 42 | 43 | 44 | def test_rst_raw(): 45 | warnings = io.StringIO() 46 | assert render(""" 47 | .. raw:: html 48 | <script>I am evil</script> 49 | 50 | """, stream=warnings) is None 51 | 52 | assert '"raw" directive disabled' in warnings.getvalue() 53 | 54 | 55 | def test_rst_empty_file(): 56 | warnings = io.StringIO() 57 | assert render("", stream=warnings) is None 58 | 59 | assert "No content rendered from RST source." in warnings.getvalue() 60 | 61 | 62 | def test_rst_header_only(): 63 | warnings = io.StringIO() 64 | assert render(""" 65 | Header 66 | ====== 67 | """, stream=warnings) is None 68 | 69 | assert "No content rendered from RST source." in warnings.getvalue() 70 | 71 | 72 | def test_header_and_malformed_emits_docutils_warning_only(): 73 | warnings = io.StringIO() 74 | assert render(""" 75 | Header 76 | ====== 77 | 78 | ====== 79 | """, stream=warnings) is None 80 | 81 | assert len(warnings.getvalue().splitlines()) == 1 82 | assert "No content rendered from RST source." not in warnings.getvalue() 83 | 84 | 85 | def test_own_readme(): 86 | """Render the project's README.rst from root.""" 87 | readme = Path(__file__).parent.parent / "README.rst" 88 | rendered = render(readme.read_text(encoding="utf-8")) 89 | assert rendered is not None 90 | -------------------------------------------------------------------------------- /tests/test_txt.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | import pytest 4 | 5 | from readme_renderer.txt import render 6 | 7 | 8 | @pytest.mark.parametrize( 9 | ("txt_filename", "html_filename"), 10 | [ 11 | (pytest.param(fn, fn.with_suffix(".html"), id=fn.name)) 12 | for fn in Path(__file__).parent.glob("fixtures/test_*.txt") 13 | ], 14 | ) 15 | def test_txt_fixtures(txt_filename, html_filename): 16 | # Get our Markup 17 | with open(txt_filename, encoding='utf-8') as f: 18 | txt_markup = f.read() 19 | 20 | # Get our expected 21 | with open(html_filename, encoding="utf-8") as f: 22 | expected = f.read() 23 | 24 | out = render(txt_markup) 25 | 26 | assert out.strip() == expected.strip() 27 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = 3 | py{39, 310, 311, 312, 313} 4 | pypy{39, 310} 5 | pep8 6 | packaging 7 | noextra 8 | mypy 9 | isolated_build = True 10 | 11 | [testenv] 12 | package = wheel 13 | wheel_build_env = build_wheel 14 | deps = 15 | pytest 16 | pytest-cov 17 | pytest-icdiff 18 | setenv = 19 | # Display up to 20 frames in backtraces when showing ResourceWarnings. 20 | PYTHONTRACEMALLOC=20 21 | commands = 22 | pytest -Wall --strict-markers --cov {posargs} 23 | extras = md 24 | 25 | [testenv:mypy] 26 | basepython = python3 27 | deps = 28 | mypy 29 | types-bleach 30 | types-docutils 31 | types-Pygments 32 | commands = mypy readme_renderer 33 | 34 | [testenv:pep8] 35 | basepython = python3 36 | skip_install = true 37 | deps = 38 | flake8 39 | pep8-naming 40 | commands = flake8 readme_renderer tests 41 | 42 | [testenv:packaging] 43 | deps = 44 | check-manifest 45 | build 46 | twine 47 | commands = 48 | check-manifest 49 | python -m build 50 | python -m twine check dist/* 51 | 52 | [testenv:noextra] 53 | basepython = python3 54 | extras = 55 | 56 | [flake8] 57 | select = E,W,F,N 58 | max-line-length = 88 59 | --------------------------------------------------------------------------------