├── .markdownlint.jsonc ├── .github └── workflows │ ├── deploy.yml │ └── python-package.yml ├── .gitignore ├── LICENSE ├── pyproject.toml ├── mdx_gh_links.py ├── README.md └── test_gh_links.py /.markdownlint.jsonc: -------------------------------------------------------------------------------- 1 | // Markdownlint rules 2 | { 3 | 4 | "default": true, 5 | 6 | // Disable line length check for tables and code blocks 7 | "MD013": { 8 | "code_blocks": false, 9 | "tables": false 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: deploy 2 | 3 | on: 4 | push: 5 | tags: 6 | - "*" 7 | 8 | jobs: 9 | pypi: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v4 13 | - name: Setup Python 14 | uses: actions/setup-python@v5 15 | with: 16 | python-version: 3.11 17 | - name: Install dependencies 18 | run: | 19 | python -m pip install --upgrade pip setuptools wheel build 20 | - name: Build 21 | run: | 22 | python -m build 23 | - name: Publish 24 | if: success() 25 | uses: pypa/gh-action-pypi-publish@v1.13.0 26 | with: 27 | user: __token__ 28 | password: ${{ secrets.PYPI_TOKEN }} 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | .hypothesis/ 47 | 48 | # Translations 49 | *.mo 50 | *.pot 51 | 52 | # Django stuff: 53 | *.log 54 | local_settings.py 55 | 56 | # Flask stuff: 57 | instance/ 58 | .webassets-cache 59 | 60 | # Scrapy stuff: 61 | .scrapy 62 | 63 | # Sphinx documentation 64 | docs/_build/ 65 | 66 | # PyBuilder 67 | target/ 68 | 69 | # IPython Notebook 70 | .ipynb_checkpoints 71 | 72 | # pyenv 73 | .python-version 74 | 75 | # celery beat schedule file 76 | celerybeat-schedule 77 | 78 | # dotenv 79 | .env 80 | 81 | # virtualenv 82 | venv/ 83 | ENV/ 84 | 85 | # Spyder project settings 86 | .spyderproject 87 | 88 | # Rope project settings 89 | .ropeproject 90 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Python-Markdown Github-Links Extension is Copyright (c) 2017-2018 by Waylan 2 | Limberg. All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | * Neither the name of HTMLTree nor the names of its contributors may be 13 | used to endorse or promote products derived from this software without 14 | specific prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY WAYLAN LIMBERG ''AS IS'' AND ANY 17 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL ANY CONTRIBUTORS TO Github-Links Extension 20 | BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 22 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 23 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 24 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 25 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 26 | POSSIBILITY OF SUCH DAMAGE. 27 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | # Minimum requirements for the build system to execute. 3 | requires = ["setuptools>=61.2"] 4 | build-backend = "setuptools.build_meta" 5 | 6 | [project] 7 | name = 'mdx_gh_links' 8 | version = '0.4' 9 | description = "An extension to Python-Markdown which adds support for shorthand links to GitHub users, repositories, issues and commits." 10 | readme = {file = 'README.md', content-type='text/markdown'} 11 | authors = [ 12 | {name = 'Waylan limberg', email = 'waylan.limberg@icloud.com'} 13 | ] 14 | license = {file = 'LICENSE'} 15 | dependencies = [ 16 | "markdown>=3.0.0" 17 | ] 18 | keywords = ['markdown', 'python-markdown', 'github'] 19 | classifiers = [ 20 | 'Development Status :: 4 - Beta', 21 | 'License :: OSI Approved :: BSD License', 22 | 'Operating System :: OS Independent', 23 | 'Programming Language :: Python', 24 | 'Programming Language :: Python :: 3', 25 | 'Programming Language :: Python :: 3.8', 26 | 'Programming Language :: Python :: 3.9', 27 | 'Programming Language :: Python :: 3.10', 28 | 'Programming Language :: Python :: 3.11', 29 | 'Programming Language :: Python :: Implementation :: CPython', 30 | 'Programming Language :: Python :: Implementation :: PyPy', 31 | 'Topic :: Documentation', 32 | 'Topic :: Text Processing', 33 | 'Topic :: Text Processing :: Markup', 34 | 'Topic :: Text Processing :: Markup :: HTML' 35 | ] 36 | 37 | [project.urls] 38 | 'Homepage' = 'https://pypi.org/project/mdx-gh-links/' 39 | 'Repository' = 'https://github.com/Python-Markdown/github-links' 40 | 'Issue Tracker' = 'https://github.com/Python-Markdown/github-links/issues' 41 | 42 | [tool.setuptools] 43 | py-modules = ['mdx_gh_links'] 44 | -------------------------------------------------------------------------------- /.github/workflows/python-package.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - "**" 7 | 8 | jobs: 9 | test: 10 | strategy: 11 | fail-fast: false 12 | matrix: 13 | os: [ubuntu-latest, windows-latest, macos-latest] 14 | python-version: 15 | ["3.8", "3.9", "3.10", "3.11", "pypy-3.8", "pypy-3.9", "pypy-3.10"] 16 | runs-on: ${{ matrix.os }} 17 | steps: 18 | - uses: actions/checkout@v4 19 | - name: Setup Python ${{ matrix.python-version }} 20 | uses: actions/setup-python@v5 21 | with: 22 | python-version: ${{ matrix.python-version }} 23 | - name: Install dependencies 24 | run: | 25 | python -m pip install --upgrade pip coverage markdown 26 | - name: Run tests 27 | run: | 28 | coverage run test_gh_links.py 29 | - name: Generate coverage report 30 | if: success() 31 | run: | 32 | coverage xml 33 | coverage report --show-missing --include=mdx_gh_links.py 34 | - name: Upload Results 35 | if: success() 36 | uses: codecov/codecov-action@v3 37 | with: 38 | files: ./coverage.xml 39 | flags: unittests 40 | name: ${{ matrix.os }}/Python ${{ matrix.python-version }} 41 | fail_ci_if_error: false 42 | 43 | flake8: 44 | runs-on: ubuntu-latest 45 | steps: 46 | - uses: actions/checkout@v4 47 | - uses: TrueBrain/actions-flake8@master 48 | with: 49 | max_line_length: 118 50 | 51 | mdlint: 52 | runs-on: ubuntu-latest 53 | steps: 54 | - uses: actions/checkout@v4 55 | - uses: DavidAnson/markdownlint-cli2-action@v14 56 | with: 57 | config: ".markdownlint.jsonc" 58 | globs: "**/*.md" 59 | -------------------------------------------------------------------------------- /mdx_gh_links.py: -------------------------------------------------------------------------------- 1 | """ 2 | Github Links - A Python-Markdown Extension. 3 | 4 | BSD License 5 | 6 | Copyright (c) 2017-2018 by Waylan Limberg. All rights reserved. 7 | 8 | Redistribution and use in source and binary forms, with or without 9 | modification, are permitted provided that the following conditions are met: 10 | 11 | * Redistributions of source code must retain the above copyright 12 | notice, this list of conditions and the following disclaimer. 13 | * Redistributions in binary form must reproduce the above copyright 14 | notice, this list of conditions and the following disclaimer in the 15 | documentation and/or other materials provided with the distribution. 16 | * Neither the name of HTMLTree nor the names of its contributors may be 17 | used to endorse or promote products derived from this software without 18 | specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY WAYLAN LIMBERG ''AS IS'' AND ANY 21 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 22 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL ANY CONTRIBUTORS TO Github-Links Extension 24 | BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 25 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 26 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 27 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 28 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 29 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 30 | POSSIBILITY OF SUCH DAMAGE. 31 | """ 32 | 33 | from markdown.extensions import Extension 34 | from markdown.inlinepatterns import Pattern 35 | from xml.etree import ElementTree 36 | 37 | 38 | RE_PARTS = dict( 39 | USER=r'[-_\w]{1,39}\b', 40 | REPO=r'[-_.\w]+\b' 41 | ) 42 | 43 | 44 | def _build_link(label, title, href, classes): 45 | el = ElementTree.Element('a') 46 | el.text = label 47 | el.set('title', title) 48 | el.set('href', href) 49 | el.set('class', classes) 50 | return el 51 | 52 | 53 | class MentionPattern(Pattern): 54 | ANCESTOR_EXCLUDES = ('a',) 55 | 56 | def __init__(self, config, md): 57 | MENTION_RE = r'(@({USER})(?:\/({REPO}))?)'.format(**RE_PARTS) 58 | super(MentionPattern, self).__init__(MENTION_RE, md) 59 | self.config = config 60 | 61 | def handleMatch(self, m): 62 | label = m.group(2) 63 | user = m.group(3) 64 | repo = m.group(4) 65 | if repo: 66 | title = 'GitHub Repository: @{0}/{1}'.format(user, repo) 67 | href = '{0}/{1}/{2}'.format(self.config['domain'], user, repo) 68 | else: 69 | title = 'GitHub User: @{0}'.format(user) 70 | href = '{0}/{1}'.format(self.config['domain'], user) 71 | return _build_link(label, title, href, 'gh-link gh-mention') 72 | 73 | 74 | class IssuePattern(Pattern): 75 | ANCESTOR_EXCLUDES = ('a',) 76 | 77 | def __init__(self, config, md): 78 | ISSUE_RE = r'((?:({USER})\/({REPO}))?#([0-9]+))'.format(**RE_PARTS) 79 | super(IssuePattern, self).__init__(ISSUE_RE, md) 80 | self.config = config 81 | 82 | def handleMatch(self, m): 83 | label = m.group(2) 84 | user = m.group(3) or self.config['user'] 85 | repo = m.group(4) or self.config['repo'] 86 | num = m.group(5).lstrip('0') 87 | title = 'GitHub Issue {0}/{1} #{2}'.format(user, repo, num) 88 | href = '{0}/{1}/{2}/issues/{3}'.format(self.config['domain'], user, repo, num) 89 | return _build_link(label, title, href, 'gh-link gh-issue') 90 | 91 | 92 | class CommitPattern(Pattern): 93 | ANCESTOR_EXCLUDES = ('a',) 94 | 95 | def __init__(self, config, md): 96 | COMMIT_RE = r'((?:({USER})(?:\/({REPO}))?@|\b)([a-f0-9]{{40}})\b)'.format(**RE_PARTS) 97 | super(CommitPattern, self).__init__(COMMIT_RE, md) 98 | self.config = config 99 | 100 | def handleMatch(self, m): 101 | user = m.group(3) 102 | repo = m.group(4) or self.config['repo'] 103 | commit = m.group(5) 104 | short = commit[:7] 105 | if user: 106 | label = '{0}@{1}'.format(m.group(2).split('@')[0], short) 107 | else: 108 | label = short 109 | user = self.config['user'] 110 | title = 'GitHub Commit: {0}/{1}@{2}'.format(user, repo, commit) 111 | href = '{0}/{1}/{2}/commit/{3}'.format(self.config['domain'], user, repo, commit) 112 | return _build_link(label, title, href, 'gh-link gh-commit') 113 | 114 | 115 | class GithubLinks(Extension): 116 | def __init__(self, *args, **kwargs): 117 | self.config = { 118 | 'domain': ['https://github.com', 'GitHub domain or Enterprise Server domain'], 119 | 'user': ['', 'GitHub user or organization.'], 120 | 'repo': ['', 'Repository name.'] 121 | } 122 | super(GithubLinks, self).__init__(*args, **kwargs) 123 | 124 | def extendMarkdown(self, md): 125 | md.ESCAPED_CHARS.append('@') 126 | md.inlinePatterns.register(IssuePattern(self.getConfigs(), md), 'issue', 20) 127 | md.inlinePatterns.register(MentionPattern(self.getConfigs(), md), 'mention', 20) 128 | md.inlinePatterns.register(CommitPattern(self.getConfigs(), md), 'commit', 20) 129 | 130 | 131 | def makeExtension(*args, **kwargs): # pragma: no cover 132 | return GithubLinks(*args, **kwargs) 133 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Python-Markdown Github-Links Extension 2 | 3 | An extension to Python-Markdown which adds support for shorthand links to GitHub 4 | users, repositories, issues and commits. 5 | 6 | ## Installation 7 | 8 | To install the extension run the following command: 9 | 10 | ```sh 11 | pip install mdx-gh-links 12 | ``` 13 | 14 | ## Usage 15 | 16 | To use the extension simply include its name in the list of extensions passed to 17 | Python-Markdown. 18 | 19 | ```python 20 | import markdown 21 | markdown.markdown(src, extensions=['mdx_gh_links']) 22 | ``` 23 | 24 | ### Configuration Options 25 | 26 | To set configuration options, you may pass them to Markdown's `exension_configs` 27 | keyword... 28 | 29 | ```python 30 | markdown.markdown( 31 | src, 32 | extensions=['mdx_gh_links'], 33 | extension_configs={ 34 | 'mdx_gh_links': {'user': 'foo', 'repo': 'bar'} 35 | } 36 | ) 37 | ``` 38 | 39 | ... or you may import and pass the configs directly to an instance of the 40 | `mdx_gh_links.GithubLinks` class... 41 | 42 | ```python 43 | from mdx_gh_links import GithubLinks 44 | markdown.markdown(src, extensions=[GithubLinks(user='foo', repo='bar')]) 45 | ``` 46 | 47 | The following configuration options are available: 48 | 49 | #### user 50 | 51 | A GitHub user name or organization. If no user or organization is specified in 52 | a GitHub link, then the value of this option will be used. 53 | 54 | #### repo 55 | 56 | A GitHub repository. If no repository is specified in a GitHub link, then the 57 | value of this option will be used. 58 | 59 | ### domain 60 | 61 | The domain of the host server for the repository. Defaults to 62 | `https://github.com`, but may be set to the root of a GitHub Enterprise Server. 63 | 64 | ## Syntax 65 | 66 | This extension implements shorthand to specify links to GitHub in various ways. 67 | 68 | All links in the generated HTML are assigned a `gh-link` class as well as a class 69 | unique to that type of link. See each type for the specific class assigned. 70 | 71 | ### Mentions 72 | 73 | Link directly to a GitHub user, organization or repository. Note that no 74 | verification is made that an actual user, organization or repository exists. As 75 | the syntax does not differentiate between users and organizations, all 76 | organizations are assumed to be users. However, this assumption is only 77 | reflected in the title of a link. 78 | 79 | Mentions use the format `@{user}` to link to a user or organization and 80 | `@{user}/{repo}` to link to a repository. The defaults defined in the 81 | configuration options are ignored by mentions. A mention may be escaped by 82 | adding a backslash immediately before the at sign (`@`). 83 | 84 | All mentions are assigned the `gh-mention` class. 85 | 86 | The following table provides some examples: 87 | 88 | | shorthand | href | rendered result | 89 | | ----------- | ---------------------------- | -------------------------------------------------------------------- | 90 | | `@foo` | `https://github.com/foo` | [@foo](https://github.com/foo "GitHub User: @foo") | 91 | | `@foo/bar` | `https://github.com/foo/bar` | [@foo/bar](https://github.com/foo/bar "GitHub Repository: @foo/bar") | 92 | | `\@foo` | | @foo | 93 | | `\@foo/bar` | | @foo/bar | 94 | 95 | ### Issues 96 | 97 | Link directly to a GitHub issue or pull request (PR). Note that no verification 98 | is made that an actual issue or PR exists. As the syntax does not differentiate 99 | between Issues and PRs, all URLs point to "issues". Fortunately, GitHub will 100 | properly redirect an issue URL to a PR URL if appropriate. 101 | 102 | Issue links use the format `#{num}` or `{user}/{repo}#{num}`. `{num}` is the 103 | number assigned to the issue or PR. `{user}` and `{repo}` will use the 104 | defaults defined in the configuration options if not provided. An issue link may 105 | be escaped by adding a backslash immediately before the hash mark (`#`). 106 | 107 | All issue links are assigned the `gh-issue` class. 108 | 109 | The following table provides various examples (with the defaults set as 110 | `user='user', repo='repo'`): 111 | 112 | | shorthand | href | rendered result | 113 | | -------------- | -------------------------------------------- | ----------------------------------------------------------------------------------- | 114 | | `#123` | `https://github.com/user/repo/issues/123` | [#123](https://github.com/user/repo/issues/123 "GitHub Issue user/repo #123") | 115 | | `foo/bar#123` | `https://github.com/foo/bar/issues/123` | [foo/bar#123](https://github.com/foo/bar/issues/123 "GitHub Issue foo/bar #123") | 116 | | `\#123` | | #123 | 117 | | `foo/bar\#123` | | foo/bar#123 | 118 | 119 | ### Commits 120 | 121 | Link directly to a GitHub Commit. Note that no verification is made that an 122 | actual commit exists. 123 | 124 | Commit links consist of a complete 40 character SHA hash and may optionally be 125 | prefaced by `{user}@` or `{user/repo}@`. `{user}` and `{repo}` will use the 126 | defaults defined in the configuration options if not provided. To avoid a 40 127 | character hash from being linked, wrap it in a code span. 128 | 129 | All commit links are assigned the `gh-commit` class. 130 | 131 | The following table provides various examples (with the defaults set as 132 | `user='user', repo='repo'`): 133 | 134 | | shorthand | href | rendered result | 135 | | -------------------------------------------------- | --------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------| 136 | | `72df691791fb36f00cf5363fefe757c8d3042656` | `https://github.com/user/repo/commit/72df691791fb36f00cf5363fefe757c8d3042656` | [72df691](https://github.com/user/repo/commit/72df691791fb36f00cf5363fefe757c8d3042656 "GitHub Commit: user/repo@72df691791fb36f00cf5363fefe757c8d3042656") | 137 | | `foo@72df691791fb36f00cf5363fefe757c8d3042656` | `https://github.com/foo/repo/commit/72df691791fb36f00cf5363fefe757c8d3042656` | [foo@72df691](https://github.com/foo/repo/commit/72df691791fb36f00cf5363fefe757c8d3042656 "GitHub Commit: foo/repo@72df691791fb36f00cf5363fefe757c8d3042656") | 138 | | `foo/bar@72df691791fb36f00cf5363fefe757c8d3042656` | `https://github.com/foo/bar/commit/72df691791fb36f00cf5363fefe757c8d3042656` | [foo/bar@72df691](https://github.com/foo/bar/commit/72df691791fb36f00cf5363fefe757c8d3042656 "GitHub Commit: foo/bar@72df691791fb36f00cf5363fefe757c8d3042656") | 139 | | `` `72df691791fb36f00cf5363fefe757c8d3042656` `` | | `72df691791fb36f00cf5363fefe757c8d3042656` | 140 | 141 | ## License 142 | 143 | The Python-Markdown Github-Links Extension is licensed under the [BSD License] as 144 | defined in `LICENSE`. 145 | 146 | [BSD License]: http://opensource.org/licenses/BSD-3-Clause 147 | 148 | ## Change Log 149 | 150 | ### Version 0.4 (2023/12/22) 151 | 152 | Add `domain` configuration option in order to support GitHub Enterprise Servers. 153 | 154 | ### Version 0.3.1 (2023/07/28) 155 | 156 | Include README in release. 157 | 158 | ### Version 0.3 (2022/07/18) 159 | 160 | Update dependencies. 161 | 162 | ### Version 0.2 (2018/03/09) 163 | 164 | Ignore mentions/issues/commits within Markdown links. 165 | 166 | ### Version 0.1 (2017/11/10) 167 | 168 | The initial release. 169 | -------------------------------------------------------------------------------- /test_gh_links.py: -------------------------------------------------------------------------------- 1 | """ 2 | Github Links - A Python-Markdown Extension. 3 | 4 | BSD License 5 | 6 | Copyright (c) 2017-2018 by Waylan Limberg. All rights reserved. 7 | 8 | Redistribution and use in source and binary forms, with or without 9 | modification, are permitted provided that the following conditions are met: 10 | 11 | * Redistributions of source code must retain the above copyright 12 | notice, this list of conditions and the following disclaimer. 13 | * Redistributions in binary form must reproduce the above copyright 14 | notice, this list of conditions and the following disclaimer in the 15 | documentation and/or other materials provided with the distribution. 16 | * Neither the name of HTMLTree nor the names of its contributors may be 17 | used to endorse or promote products derived from this software without 18 | specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY WAYLAN LIMBERG ''AS IS'' AND ANY 21 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 22 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL ANY CONTRIBUTORS TO Github-Links Extension 24 | BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 25 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 26 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 27 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 28 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 29 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 30 | POSSIBILITY OF SUCH DAMAGE. 31 | """ 32 | 33 | import unittest 34 | from markdown import markdown 35 | from mdx_gh_links import GithubLinks 36 | 37 | 38 | class TestGithubLinks(unittest.TestCase): 39 | maxDiff = None 40 | 41 | def assertMarkdownRenders(self, source, expected, **kwargs): 42 | 'Test that source Markdown text renders to expected output.' 43 | configs = {'user': 'Python-Markdown', 'repo': 'github-links'} 44 | configs.update(kwargs) 45 | output = markdown(source, extensions=[GithubLinks(**configs)]) 46 | self.assertMultiLineEqual(output, expected) 47 | 48 | # Issue Tests 49 | def test_issue(self): 50 | self.assertMarkdownRenders( 51 | 'Issue #123.', 52 | '

Issue #123.

', 55 | ) 56 | 57 | def test_issue_leading_zero(self): 58 | self.assertMarkdownRenders( 59 | 'Issue #012.', 60 | '

Issue #012.

', 63 | ) 64 | 65 | def test_non_issue(self): 66 | self.assertMarkdownRenders( 67 | 'Issue #notanissue.', 68 | '

Issue #notanissue.

', 69 | ) 70 | 71 | def test_issue_with_repo(self): 72 | self.assertMarkdownRenders( 73 | 'Issue Organization/Repo#123.', 74 | '

Issue Organization/Repo#123.

', 77 | ) 78 | 79 | def test_issue_leading_zero_with_repo(self): 80 | self.assertMarkdownRenders( 81 | 'Issue Organization/Repo#012.', 82 | '

Issue Organization/Repo#012.

', 85 | ) 86 | 87 | def test_non_issue_with_repo(self): 88 | self.assertMarkdownRenders( 89 | 'Issue Organization/Repo#notanissue.', 90 | '

Issue Organization/Repo#notanissue.

', 91 | ) 92 | 93 | def test_escaped_issue(self): 94 | self.assertMarkdownRenders( 95 | 'Issue \\#123.', 96 | '

Issue #123.

', 97 | ) 98 | 99 | def test_escaped_issue_with_repo(self): 100 | self.assertMarkdownRenders( 101 | 'Issue Organization/Repo\\#123.', 102 | '

Issue Organization/Repo#123.

', 103 | ) 104 | 105 | def test_issue_in_link(self): 106 | self.assertMarkdownRenders( 107 | '[Issue #123](#).', 108 | '

Issue #123.

', 109 | ) 110 | 111 | # Mention Tests 112 | def test_mention_user(self): 113 | self.assertMarkdownRenders( 114 | 'User @foo.', 115 | '

User @foo.

', 118 | ) 119 | 120 | def test_mention_complex_user(self): 121 | self.assertMarkdownRenders( 122 | 'User @Foo_Bar-42.', 123 | '

User @Foo_Bar-42.

', 126 | ) 127 | 128 | def test_escape_mention_user(self): 129 | self.assertMarkdownRenders( 130 | 'User \\@foo.', 131 | '

User @foo.

', 132 | ) 133 | 134 | def test_mention_repo(self): 135 | self.assertMarkdownRenders( 136 | 'User @foo/bar.', 137 | '

User @foo/bar.

', 140 | ) 141 | 142 | def test_mention_repo_complex(self): 143 | self.assertMarkdownRenders( 144 | 'User @foo/bar_baz-42.0.', 145 | '

User @foo/bar_baz-42.0.

', 148 | ) 149 | 150 | def test_escape_mention_repo(self): 151 | self.assertMarkdownRenders( 152 | 'User \\@foo/bar.', 153 | '

User @foo/bar.

', 154 | ) 155 | 156 | def test_mention_in_link(self): 157 | self.assertMarkdownRenders( 158 | 'User [@foo](#).', 159 | '

User @foo.

', 160 | ) 161 | 162 | # Commit Tests 163 | def test_commit(self): 164 | self.assertMarkdownRenders( 165 | 'Commit 83fb46b3b7ab8ad4316681fc4637c521da265f1d.', 166 | '

Commit 83fb46b.

', 170 | ) 171 | 172 | def test_commit_user(self): 173 | self.assertMarkdownRenders( 174 | 'Commit foo@15abb8b3b02df0e380e9b4c71f3bd206c9751a93.', 175 | '

Commit foo@15abb8b.

', 178 | ) 179 | 180 | def test_commit_user_repo(self): 181 | self.assertMarkdownRenders( 182 | 'Commit foo/bar@a75944f869d728ca9bc5472daf3f249b6c341308.', 183 | '

Commit foo/bar@a75944f.

', 186 | ) 187 | 188 | def test_escape_commit(self): 189 | self.assertMarkdownRenders( 190 | 'Commit `83fb46b3b7ab8ad4316681fc4637c521da265f1d`.', 191 | '

Commit 83fb46b3b7ab8ad4316681fc4637c521da265f1d.

', 192 | ) 193 | 194 | def test_commit_in_link(self): 195 | self.assertMarkdownRenders( 196 | 'Commit [83fb46b3b7ab8ad4316681fc4637c521da265f1d](#).', 197 | '

Commit 83fb46b3b7ab8ad4316681fc4637c521da265f1d.

', 198 | ) 199 | 200 | 201 | if __name__ == '__main__': 202 | unittest.main() 203 | --------------------------------------------------------------------------------