From 054385984d709d78375daeee5ad073ffd4145d5f Mon Sep 17 00:00:00 2001 From: Chris Halbersma Date: Fri, 16 Jun 2023 09:23:33 -0700 Subject: [PATCH] First version of mistune Jira --- .github/workflows/python-publish-stag.yml | 38 ++++++ .github/workflows/python-publish.yml | 36 +++++ .gitignore | 1 + README.md | 34 +++++ mistune_jira/__init__.py | 2 + mistune_jira/jiraRenderer.py | 153 ++++++++++++++++++++++ pyproject.toml | 27 ++++ requirements.txt | 1 + 8 files changed, 292 insertions(+) create mode 100644 .github/workflows/python-publish-stag.yml create mode 100644 .github/workflows/python-publish.yml create mode 100644 mistune_jira/__init__.py create mode 100644 mistune_jira/jiraRenderer.py create mode 100644 pyproject.toml create mode 100644 requirements.txt diff --git a/.github/workflows/python-publish-stag.yml b/.github/workflows/python-publish-stag.yml new file mode 100644 index 0000000..565b972 --- /dev/null +++ b/.github/workflows/python-publish-stag.yml @@ -0,0 +1,38 @@ +# This workflow will upload a Python Package using Twine when a release is created +# For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries + +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. + +name: Upload Python Package [Staging] + +on: + push: + branches: + - main + +jobs: + deploy: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.11' + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install build + - name: Build package + run: python -m build + - name: Publish package + uses: pypa/gh-action-pypi-publish@release/v1 + with: + user: __token__ + password: ${{ secrets.STAG_PYPI_TOKEN }} + repository_url: https://test.pypi.org/legacy/ diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml new file mode 100644 index 0000000..da91178 --- /dev/null +++ b/.github/workflows/python-publish.yml @@ -0,0 +1,36 @@ +# This workflow will upload a Python Package using Twine when a release is created +# For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries + +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. + +name: Upload Python Package + +on: + release: + types: [published] + +jobs: + deploy: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.11' + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install build + - name: Build package + run: python -m build + - name: Publish package + uses: pypa/gh-action-pypi-publish@release/v1 + with: + user: __token__ + password: ${{ secrets.PROD_PYPI_TOKEN }} diff --git a/.gitignore b/.gitignore index 68bc17f..b33498a 100644 --- a/.gitignore +++ b/.gitignore @@ -158,3 +158,4 @@ cython_debug/ # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. #.idea/ +/.idea diff --git a/README.md b/README.md index 7d233d9..193ec08 100644 --- a/README.md +++ b/README.md @@ -11,3 +11,37 @@ The goal is to be able to have data in markdown and then use that markdown to cr ticket in Jira. Hopefully Jira will someday fully support markdown and make this project irrelevant. + +## Usage + +```python +import mistune +import mistune_jira + +text = ''' +# Lorem Ipsum + +This is some sample markdown. [Say hi to google](https://www.google.com) as an example link +to be converted. + +HR line +--- + +* a list of cool things +* doggies + * Little Doggies (Won't yet render correctly) + * Shaggy Doggies + * Grumpy Doggies +* spaceships +* $2 bills + +''' + +rend = mistune_jira.JiraRenderer() + +markdown_parser = mistune.Markdown(renderer=rend) + +jira_compat = markdown_parser(text) + +print(jira_compat) +``` \ No newline at end of file diff --git a/mistune_jira/__init__.py b/mistune_jira/__init__.py new file mode 100644 index 0000000..b11c849 --- /dev/null +++ b/mistune_jira/__init__.py @@ -0,0 +1,2 @@ + +from .jiraRenderer import JiraRenderer \ No newline at end of file diff --git a/mistune_jira/jiraRenderer.py b/mistune_jira/jiraRenderer.py new file mode 100644 index 0000000..545e6ae --- /dev/null +++ b/mistune_jira/jiraRenderer.py @@ -0,0 +1,153 @@ +#!/usr/bin/env python3 + +import re + +from mistune import BaseRenderer, BlockState +from mistune.util import strip_end +from mistune.renderers._list import render_list + + +class JiraRenderer(BaseRenderer): + + ''' + MistuneJiraRenderer + + Based off the RSTRenderer and some earlier work from 2018 of mine + ''' + + NAME = "jira" + + HEADING_MARKERS = [ + "h1. ", + "h2. ", + "h3. ", + "h4. ", + "h5. ", + "h6. " + ] + + INLINE_IMAGE_PREFIX = "img-" + + def iter_tokens(self, tokens, state): + prev = None + for tok in tokens: + # ignore blank line + if tok['type'] == 'blank_line': + continue + tok['prev'] = prev + prev = tok + yield self.render_token(tok, state) + + def __call__(self, tokens, state: BlockState): + state.env['inline_images'] = [] + out = self.render_tokens(tokens, state) + return strip_end(out) + + def render_referrences(self, state: BlockState): + images = state.env['inline_images'] + for index, token in enumerate(images): + attrs = token['attrs'] + alt = self.render_children(token, state) + ident = self.INLINE_IMAGE_PREFIX + str(index) + yield '.. |' + ident + '| image:: ' + attrs['url'] + '\n :alt: ' + alt + + def render_children(self, token, state: BlockState): + children = token['children'] + return self.render_tokens(children, state) + + def text(self, token, state): + + text = token["raw"] + + return text + + def emphasis(self, token, state): + + return "_{}_".format(self.render_children(token, state)) + + def strong(self, token, state): + + return "*{}*".format(self.render_children(token, state)) + + def linebreak(self, token, state): + + return "\\" + + def softbreak(self, token, state): + + return " " + + def inline_html(self, token, BlockState): + return token['raw'] + + + def link(self, token, state): + # This might need more logic to handle different types of links + + attrs = token['attrs'] + text = self.render_children(token, state) + + return "[{}|{}]".format(text, attrs["url"]) + + def codespan(self, token, state): + + return "{{{{{}}}}}".format(token["raw"]) + + def paragraph(self, token, state): + + return "{} \n\n".format(self.render_children(token, state)) + + def heading(self, token, state): + + level = token["attrs"]["level"] + + return "\nh{}. {}\n".format(level, self.render_children(token, state)) + + def thematic_break(self, token, state): + + return "\n----\n" + + def block_text(self, token, state): + + return "{}\n".format(self.render_children(token, state)) + + def block_code(self, token, state): + attrs = token.get('attrs', {}) + info = attrs.get('info') + try: + lang = info.split()[0] + except IndexError: + langstr = "" + else: + if lang not in ("actionscript", "ada", "applescript", "bash", "c", "c#", "c++", "css", "erlang", "go", "groovy", + "haskell", "html", "javascript", "json", "lua", "nyan", "objc", "perl", "php", "python", "r", + "ruby", "scala", "sql", "swift", "visualbasic", "xml", "yaml"): + langstr = "" + else: + langstr = ":{}".format(lang) + + codestr = token['raw'] + + return '''\n{{code{langstr}}} +{codestr} +{{code}}\n'''.format(langstr=langstr, codestr=codestr) + + def block_quote(self, token, state): + + return '''\n{{quote}} +{quote} +{{quote}}\n'''.format(quote=self.render_children(token, state)) + + def block_html(self, token, state): + + return '''\n{{noformat}} +{html} +{{noformat}}'''.format(html=token["raw"]) + + def block_error(self, token, state): + return '''\n{{noformat}} +{error} +{{noformat}}'''.format(error=token["raw"]) + + def list(self, token, state): + return render_list(self, token, state) diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..a4425c8 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,27 @@ +[build-system] +requires = [ + "twine", + "setuptools" +] +build-backend = "setuptools.build_meta" + +[project] +name = "mistune_jira" +version = "2023.6.16.0" +authors = [ + {name = "Chris Halbersma", email = "chris@halbersma.us"}, +] +description = "A mostly functional plugin for mistune to render Jira's markup language" +readme = "README.md" +requires-python = ">=3.10" +keywords = ["mistune", "jira", "atlassian"] +license = {text = "BSD-3-Clause"} +classifiers = [ + "Programming Language :: Python :: 3", +] +dependencies = [ + "mistune", +] + +[options] +packages = ["mistune_jira"] diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..ff70ea1 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +mistune>=3.0.1 \ No newline at end of file