markdown2jira 0.0.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,33 @@
1
+ Metadata-Version: 2.4
2
+ Name: markdown2jira
3
+ Version: 0.0.1
4
+ Summary: Convert Markdown to Jira wiki markup
5
+ Keywords: markdown,jira,wiki markup,confluence
6
+ Author: Abraham Murciano
7
+ Author-email: Abraham Murciano <abrahammurciano@gmail.com>
8
+ License-Expression: MIT
9
+ Requires-Dist: wenmode>=0.15.1
10
+ Requires-Python: >=3.14
11
+ Project-URL: repository, https://github.com/abrahammurciano/python-markdown2jira
12
+ Project-URL: documentation, https://abrahammurciano.github.io/python-markdown2jira/markdown2jira
13
+ Description-Content-Type: text/markdown
14
+
15
+ # markdown2jira
16
+ Convert Markdown to Jira wiki markup
17
+
18
+ ## Installation
19
+
20
+ You can install this package with pip.
21
+ ```sh
22
+ $ pip install markdown2jira
23
+ ```
24
+
25
+ ## Links
26
+
27
+ [![Documentation](https://img.shields.io/badge/Documentation-C61C3E?style=for-the-badge&logo=Read+the+Docs&logoColor=%23FFFFFF)](https://abrahammurciano.github.io/python-markdown2jira)
28
+
29
+ [![Source Code - GitHub](https://img.shields.io/badge/Source_Code-GitHub-181717?style=for-the-badge&logo=GitHub&logoColor=%23FFFFFF)](https://github.com/abrahammurciano/python-markdown2jira.git)
30
+
31
+ [![PyPI - markdown2jira](https://img.shields.io/badge/PyPI-markdown2jira-006DAD?style=for-the-badge&logo=PyPI&logoColor=%23FFD242)](https://pypi.org/project/markdown2jira/)
32
+
33
+ ## Usage
@@ -0,0 +1,19 @@
1
+ # markdown2jira
2
+ Convert Markdown to Jira wiki markup
3
+
4
+ ## Installation
5
+
6
+ You can install this package with pip.
7
+ ```sh
8
+ $ pip install markdown2jira
9
+ ```
10
+
11
+ ## Links
12
+
13
+ [![Documentation](https://img.shields.io/badge/Documentation-C61C3E?style=for-the-badge&logo=Read+the+Docs&logoColor=%23FFFFFF)](https://abrahammurciano.github.io/python-markdown2jira)
14
+
15
+ [![Source Code - GitHub](https://img.shields.io/badge/Source_Code-GitHub-181717?style=for-the-badge&logo=GitHub&logoColor=%23FFFFFF)](https://github.com/abrahammurciano/python-markdown2jira.git)
16
+
17
+ [![PyPI - markdown2jira](https://img.shields.io/badge/PyPI-markdown2jira-006DAD?style=for-the-badge&logo=PyPI&logoColor=%23FFD242)](https://pypi.org/project/markdown2jira/)
18
+
19
+ ## Usage
@@ -0,0 +1,122 @@
1
+ [project]
2
+ name = "markdown2jira"
3
+ version = "0.0.1"
4
+ description = "Convert Markdown to Jira wiki markup"
5
+ license = "MIT"
6
+ readme = "README.md"
7
+ requires-python = ">=3.14"
8
+ keywords = [
9
+ "markdown",
10
+ "jira",
11
+ "wiki markup",
12
+ "confluence",
13
+ ]
14
+ dependencies = ["wenmode>=0.15.1"]
15
+
16
+ [[project.authors]]
17
+ name = "Abraham Murciano"
18
+ email = "abrahammurciano@gmail.com"
19
+
20
+ [project.urls]
21
+ repository = "https://github.com/abrahammurciano/python-markdown2jira"
22
+ documentation = "https://abrahammurciano.github.io/python-markdown2jira/markdown2jira"
23
+
24
+ [dependency-groups]
25
+ dev = [
26
+ "basedpyright",
27
+ "mypy",
28
+ "pdoc3",
29
+ "pytest",
30
+ "ruff",
31
+ ]
32
+
33
+ [build-system]
34
+ requires = ["uv_build>=0.12,<0.13"]
35
+ build-backend = "uv_build"
36
+
37
+ [tool.uv.build-backend]
38
+ module-name = ["markdown2jira"]
39
+
40
+ [tool.mypy]
41
+ exclude = [
42
+ ".venv",
43
+ "venv",
44
+ ]
45
+
46
+ [tool.basedpyright]
47
+ reportUnusedCallResult = "none"
48
+ reportAny = "none"
49
+ reportIncompatibleUnannotatedOverride = "warning"
50
+ reportUnannotatedClassAttribute = "none"
51
+ reportUnusedParameter = "none"
52
+ reportCallInDefaultInitializer = "none"
53
+ reportExplicitAny = "none"
54
+ reportInvalidCast = "none"
55
+ reportUnknownLambdaType = "none"
56
+ reportImportCycles = "hint"
57
+
58
+ [tool.ruff.lint]
59
+ select = ["ALL"]
60
+ ignore = [
61
+ "A001",
62
+ "A002",
63
+ "ANN401",
64
+ "C901",
65
+ "COM812",
66
+ "CPY001",
67
+ "D100",
68
+ "D101",
69
+ "D102",
70
+ "D103",
71
+ "D104",
72
+ "D105",
73
+ "D106",
74
+ "D107",
75
+ "D203",
76
+ "D213",
77
+ "D413",
78
+ "E501",
79
+ "EM101",
80
+ "EM102",
81
+ "G004",
82
+ "PLC0415",
83
+ "PLR0911",
84
+ "PLR0912",
85
+ "PLR0913",
86
+ "PLR2004",
87
+ "PT013",
88
+ "PYI025",
89
+ "S101",
90
+ "S104",
91
+ "S105",
92
+ "S311",
93
+ "S603",
94
+ "S608",
95
+ "TC001",
96
+ "TC002",
97
+ "TC003",
98
+ "TC006",
99
+ "TD002",
100
+ "TD003",
101
+ "TRY003",
102
+ "TRY400",
103
+ ]
104
+
105
+ [tool.ruff.lint.flake8-annotations]
106
+ allow-star-arg-any = true
107
+
108
+ [tool.ruff.lint.flake8-pytest-style]
109
+ parametrize-values-type = "tuple"
110
+ parametrize-values-row-type = "tuple"
111
+
112
+ [tool.ruff.lint.pylint]
113
+ max-args = 10
114
+
115
+ [tool.ruff.lint.per-file-ignores]
116
+ "tests/**.py" = ["ARG001"]
117
+
118
+ [tool.ruff.lint.isort]
119
+ split-on-trailing-comma = false
120
+
121
+ [tool.ruff.format]
122
+ skip-magic-trailing-comma = true
@@ -0,0 +1,106 @@
1
+ [project]
2
+ name = "markdown2jira"
3
+ # Version is overwritten at build time by CI based on git tag
4
+ version = "0.0.1"
5
+ description = "Convert Markdown to Jira wiki markup"
6
+ authors = [{ name = "Abraham Murciano", email = "abrahammurciano@gmail.com" }]
7
+ license = "MIT"
8
+ readme = "README.md"
9
+ requires-python = ">=3.14"
10
+ keywords = ["markdown", "jira", "wiki markup", "confluence"]
11
+ dependencies = ["wenmode>=0.15.1"]
12
+
13
+ [project.urls]
14
+ repository = "https://github.com/abrahammurciano/python-markdown2jira"
15
+ documentation = "https://abrahammurciano.github.io/python-markdown2jira/markdown2jira"
16
+
17
+ [dependency-groups]
18
+ dev = ["basedpyright", "mypy", "pdoc3", "pytest", "ruff"]
19
+
20
+ [build-system]
21
+ requires = ["uv_build>=0.12,<0.13"]
22
+ build-backend = "uv_build"
23
+
24
+ [tool.uv.build-backend]
25
+ module-name = ["markdown2jira"]
26
+
27
+ [tool.mypy]
28
+ exclude = [".venv", "venv"]
29
+
30
+ [tool.basedpyright]
31
+ reportUnusedCallResult = "none"
32
+ reportAny = "none"
33
+ reportIncompatibleUnannotatedOverride = "warning"
34
+ reportUnannotatedClassAttribute = "none"
35
+ reportUnusedParameter = "none"
36
+ reportCallInDefaultInitializer = "none"
37
+ reportExplicitAny = "none"
38
+ reportInvalidCast = "none"
39
+ reportUnknownLambdaType = "none"
40
+ reportImportCycles = "hint"
41
+
42
+ [tool.ruff.lint]
43
+ select = ["ALL"]
44
+ ignore = [
45
+ "A001",
46
+ "A002",
47
+ "ANN401",
48
+ "C901",
49
+ "COM812",
50
+ "CPY001",
51
+ "D100",
52
+ "D101",
53
+ "D102",
54
+ "D103",
55
+ "D104",
56
+ "D105",
57
+ "D106",
58
+ "D107",
59
+ "D203",
60
+ "D213",
61
+ "D413",
62
+ "E501",
63
+ "EM101",
64
+ "EM102",
65
+ "G004",
66
+ "PLC0415",
67
+ "PLR0911",
68
+ "PLR0912",
69
+ "PLR0913",
70
+ "PLR2004",
71
+ "PT013",
72
+ "PYI025",
73
+ "S101",
74
+ "S104",
75
+ "S105",
76
+ "S311",
77
+ "S603",
78
+ "S608",
79
+ "TC001",
80
+ "TC002",
81
+ "TC003",
82
+ "TC006",
83
+ "TD002",
84
+ "TD003",
85
+ "TRY003",
86
+ "TRY400",
87
+ ]
88
+
89
+ [tool.ruff.lint.flake8-annotations]
90
+ allow-star-arg-any = true
91
+
92
+ [tool.ruff.lint.flake8-pytest-style]
93
+ parametrize-values-type = "tuple"
94
+ parametrize-values-row-type = "tuple"
95
+
96
+ [tool.ruff.lint.pylint]
97
+ max-args = 10
98
+
99
+ [tool.ruff.lint.per-file-ignores]
100
+ "tests/**.py" = ["ARG001"]
101
+
102
+ [tool.ruff.lint.isort]
103
+ split-on-trailing-comma = false
104
+
105
+ [tool.ruff.format]
106
+ skip-magic-trailing-comma = true
@@ -0,0 +1,12 @@
1
+ """Convert Markdown to Jira wiki markup.
2
+
3
+ .. include:: ../../README.md
4
+ """
5
+
6
+ from importlib import metadata
7
+
8
+ from ._convert import Markdown2Jira, convert
9
+ from ._renderer import JiraRenderContext, JiraRenderer
10
+
11
+ __version__ = metadata.version(__package__ or __name__)
12
+ __all__ = ["JiraRenderContext", "JiraRenderer", "Markdown2Jira", "convert"]
@@ -0,0 +1,55 @@
1
+ from collections.abc import Iterable
2
+ from types import ModuleType
3
+
4
+ from wenmode import Wenmode
5
+ from wenmode.plugins import PluginModule
6
+ from wenmode.presets import PresetFactory, github
7
+ from wenmode.renderers import BaseRenderer
8
+ from wenmode.rules import Rule
9
+
10
+ from ._renderer import JiraRenderer
11
+
12
+
13
+ class Markdown2Jira:
14
+ """Converts Markdown to Jira wiki markup, with configurable parsing rules and rendering.
15
+
16
+ Args:
17
+ rules: The Markdown parsing rules to use. Defaults to the GitHub preset (tables, task lists, strikethrough, auto-links, footnotes).
18
+ renderer: The renderer used to turn the parsed Markdown into Jira wiki markup. Defaults to `JiraRenderer`. Subclass `JiraRenderer` to support custom node types.
19
+ plugins: Wenmode plugins to extend parsing with, e.g. for custom syntax.
20
+ positions: Whether to track source positions on parsed nodes.
21
+ """
22
+
23
+ def __init__(
24
+ self,
25
+ rules: Iterable[type[Rule] | Rule] | PresetFactory | None = None,
26
+ *,
27
+ renderer: BaseRenderer | None = None,
28
+ plugins: Iterable[PluginModule | ModuleType] = (),
29
+ positions: bool = False,
30
+ ) -> None:
31
+ self._wenmode = Wenmode(
32
+ rules if rules is not None else github(),
33
+ renderer=renderer or JiraRenderer(),
34
+ plugins=plugins,
35
+ positions=positions,
36
+ )
37
+
38
+ def convert(self, content: str) -> str:
39
+ """Convert a Markdown string to Jira wiki markup."""
40
+ return self._wenmode.render(content).strip("\n")
41
+
42
+
43
+ _default = Markdown2Jira()
44
+
45
+
46
+ def convert(content: str) -> str:
47
+ """Convert a Markdown string to Jira wiki markup, using the default rules and renderer.
48
+
49
+ Args:
50
+ content: The Markdown source to convert.
51
+
52
+ Returns:
53
+ The equivalent Jira wiki markup.
54
+ """
55
+ return _default.convert(content)
@@ -0,0 +1,208 @@
1
+ from dataclasses import dataclass, field
2
+ from typing import override
3
+
4
+ from wenmode.nodes import (
5
+ Blockquote,
6
+ Break,
7
+ Code,
8
+ Delete,
9
+ Emphasis,
10
+ Heading,
11
+ Html,
12
+ Image,
13
+ InlineCode,
14
+ Link,
15
+ List,
16
+ ListItem,
17
+ Node,
18
+ Paragraph,
19
+ Strong,
20
+ Table,
21
+ TableCell,
22
+ TableRow,
23
+ Text,
24
+ ThematicBreak,
25
+ )
26
+ from wenmode.renderers import BaseRenderer, RenderContext
27
+
28
+
29
+ @dataclass
30
+ class JiraRenderContext(RenderContext):
31
+ list_markers: list[str] = field(default_factory=list)
32
+
33
+
34
+ class JiraRenderer(BaseRenderer):
35
+ name = "jira"
36
+
37
+ @override
38
+ def create_context(self, node: Node | None = None) -> JiraRenderContext:
39
+ return JiraRenderContext()
40
+
41
+
42
+ @JiraRenderer.register("paragraph")
43
+ def render_paragraph(
44
+ renderer: JiraRenderer, node: Paragraph, context: JiraRenderContext
45
+ ) -> str:
46
+ return renderer.render_children(node.children, context) + "\n\n"
47
+
48
+
49
+ @JiraRenderer.register("heading")
50
+ def render_heading(
51
+ renderer: JiraRenderer, node: Heading, context: JiraRenderContext
52
+ ) -> str:
53
+ depth = min(max(node.depth, 1), 6)
54
+ title = renderer.render_children(node.children, context)
55
+ return f"h{depth}. {title}\n\n"
56
+
57
+
58
+ @JiraRenderer.register("blockquote")
59
+ def render_blockquote(
60
+ renderer: JiraRenderer, node: Blockquote, context: JiraRenderContext
61
+ ) -> str:
62
+ body = renderer.render_children(node.children, context).rstrip("\n")
63
+ return f"{{quote}}\n{body}\n{{quote}}\n\n"
64
+
65
+
66
+ @JiraRenderer.register("thematicBreak")
67
+ def render_thematic_break(
68
+ _renderer: JiraRenderer, _node: ThematicBreak, _context: JiraRenderContext
69
+ ) -> str:
70
+ return "----\n\n"
71
+
72
+
73
+ @JiraRenderer.register("html")
74
+ def render_html(
75
+ _renderer: JiraRenderer, node: Html, _context: JiraRenderContext
76
+ ) -> str:
77
+ value = node.value.rstrip("\n")
78
+ return f"{{noformat}}\n{value}\n{{noformat}}\n\n"
79
+
80
+
81
+ @JiraRenderer.register("text")
82
+ def render_text(
83
+ _renderer: JiraRenderer, node: Text, _context: JiraRenderContext
84
+ ) -> str:
85
+ return node.value
86
+
87
+
88
+ @JiraRenderer.register("inlineCode")
89
+ def render_inline_code(
90
+ _renderer: JiraRenderer, node: InlineCode, _context: JiraRenderContext
91
+ ) -> str:
92
+ return "{{" + node.value + "}}"
93
+
94
+
95
+ @JiraRenderer.register("strong")
96
+ def render_strong(
97
+ renderer: JiraRenderer, node: Strong, context: JiraRenderContext
98
+ ) -> str:
99
+ return "*" + renderer.render_children(node.children, context) + "*"
100
+
101
+
102
+ @JiraRenderer.register("emphasis")
103
+ def render_emphasis(
104
+ renderer: JiraRenderer, node: Emphasis, context: JiraRenderContext
105
+ ) -> str:
106
+ return "_" + renderer.render_children(node.children, context) + "_"
107
+
108
+
109
+ @JiraRenderer.register("delete")
110
+ def render_delete(
111
+ renderer: JiraRenderer, node: Delete, context: JiraRenderContext
112
+ ) -> str:
113
+ return "-" + renderer.render_children(node.children, context) + "-"
114
+
115
+
116
+ @JiraRenderer.register("link")
117
+ def render_link(renderer: JiraRenderer, node: Link, context: JiraRenderContext) -> str:
118
+ text = renderer.render_children(node.children, context)
119
+ if node.title:
120
+ return f"[{text}|{node.url}|{node.title}]"
121
+ return f"[{text}|{node.url}]"
122
+
123
+
124
+ @JiraRenderer.register("image")
125
+ def render_image(
126
+ _renderer: JiraRenderer, node: Image, _context: JiraRenderContext
127
+ ) -> str:
128
+ if node.alt:
129
+ return f"!{node.url}|alt={node.alt}!"
130
+ return f"!{node.url}!"
131
+
132
+
133
+ @JiraRenderer.register("break")
134
+ def render_break(
135
+ _renderer: JiraRenderer, _node: Break, _context: JiraRenderContext
136
+ ) -> str:
137
+ return "\\\\\n"
138
+
139
+
140
+ @JiraRenderer.register("code")
141
+ def render_code(
142
+ _renderer: JiraRenderer, node: Code, _context: JiraRenderContext
143
+ ) -> str:
144
+ header = f"{{code:{node.lang}}}" if node.lang else "{code}"
145
+ code = node.value if node.value.endswith("\n") else node.value + "\n"
146
+ return f"{header}\n{code}{{code}}\n\n"
147
+
148
+
149
+ @JiraRenderer.register("list")
150
+ def render_list(renderer: JiraRenderer, node: List, context: JiraRenderContext) -> str:
151
+ context.list_markers.append("#" if node.ordered else "*")
152
+ text = renderer.render_children(node.children, context)
153
+ context.list_markers.pop()
154
+ return text if context.list_markers else text + "\n"
155
+
156
+
157
+ @JiraRenderer.register("listItem")
158
+ def render_list_item(
159
+ renderer: JiraRenderer, node: ListItem, context: JiraRenderContext
160
+ ) -> str:
161
+ marker = "".join(context.list_markers)
162
+ if node.checked is not None:
163
+ marker += " [x]" if node.checked else " [ ]"
164
+ parts: list[str] = []
165
+ for child in node.children:
166
+ if isinstance(child, List):
167
+ parts.append(renderer.render_node(child, context))
168
+ else:
169
+ text = renderer.render_node(child, context).rstrip("\n")
170
+ parts.append(f"{marker} {text}\n")
171
+ return "".join(parts)
172
+
173
+
174
+ @JiraRenderer.register("table")
175
+ def render_table(
176
+ renderer: JiraRenderer, node: Table, context: JiraRenderContext
177
+ ) -> str:
178
+ rows = [child for child in node.children if isinstance(child, TableRow)]
179
+ if not rows:
180
+ return ""
181
+ head, *body = rows
182
+ lines = [render_table_head(renderer, head, context)]
183
+ lines.extend(render_table_row(renderer, row, context) for row in body)
184
+ return "".join(lines) + "\n"
185
+
186
+
187
+ def table_row_cells(row: TableRow) -> list[TableCell]:
188
+ return [cell for cell in row.children if isinstance(cell, TableCell)]
189
+
190
+
191
+ def render_table_head(
192
+ renderer: JiraRenderer, row: TableRow, context: JiraRenderContext
193
+ ) -> str:
194
+ cells = "".join(
195
+ "||" + renderer.render_children(cell.children, context)
196
+ for cell in table_row_cells(row)
197
+ )
198
+ return cells + "||\n"
199
+
200
+
201
+ def render_table_row(
202
+ renderer: JiraRenderer, row: TableRow, context: JiraRenderContext
203
+ ) -> str:
204
+ cells = "".join(
205
+ "|" + renderer.render_children(cell.children, context)
206
+ for cell in table_row_cells(row)
207
+ )
208
+ return cells + "|\n"