markdown-to-slack-blocks 1.0.0__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.
- markdown_to_slack_blocks-1.0.0/LICENSE +22 -0
- markdown_to_slack_blocks-1.0.0/PKG-INFO +193 -0
- markdown_to_slack_blocks-1.0.0/README.md +169 -0
- markdown_to_slack_blocks-1.0.0/pyproject.toml +43 -0
- markdown_to_slack_blocks-1.0.0/setup.cfg +4 -0
- markdown_to_slack_blocks-1.0.0/src/markdown_to_slack_blocks/__init__.py +45 -0
- markdown_to_slack_blocks-1.0.0/src/markdown_to_slack_blocks/parser.py +784 -0
- markdown_to_slack_blocks-1.0.0/src/markdown_to_slack_blocks/splitter.py +798 -0
- markdown_to_slack_blocks-1.0.0/src/markdown_to_slack_blocks/tags.py +458 -0
- markdown_to_slack_blocks-1.0.0/src/markdown_to_slack_blocks/validator.py +140 -0
- markdown_to_slack_blocks-1.0.0/src/markdown_to_slack_blocks.egg-info/PKG-INFO +193 -0
- markdown_to_slack_blocks-1.0.0/src/markdown_to_slack_blocks.egg-info/SOURCES.txt +15 -0
- markdown_to_slack_blocks-1.0.0/src/markdown_to_slack_blocks.egg-info/dependency_links.txt +1 -0
- markdown_to_slack_blocks-1.0.0/src/markdown_to_slack_blocks.egg-info/requires.txt +4 -0
- markdown_to_slack_blocks-1.0.0/src/markdown_to_slack_blocks.egg-info/top_level.txt +1 -0
- markdown_to_slack_blocks-1.0.0/tests/test_parity.py +491 -0
- markdown_to_slack_blocks-1.0.0/tests/test_tags.py +268 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 https://github.com/udivankin
|
|
4
|
+
Copyright (c) 2026 Nikita Nefedov
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
SOFTWARE.
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: markdown-to-slack-blocks
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Convert Markdown (including GFM tables, mentions, and LLM output) into Slack Block Kit JSON, and back.
|
|
5
|
+
Author: Nikita Nefedov
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/nikita2206/markdown-to-slack-blocks
|
|
8
|
+
Project-URL: Source, https://github.com/nikita2206/markdown-to-slack-blocks
|
|
9
|
+
Keywords: slack,markdown,block-kit,slack-blocks,gfm
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
16
|
+
Classifier: Typing :: Typed
|
|
17
|
+
Requires-Python: >=3.10
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
License-File: LICENSE
|
|
20
|
+
Requires-Dist: markdown-it-py>=3.0.0
|
|
21
|
+
Provides-Extra: dev
|
|
22
|
+
Requires-Dist: pytest>=8; extra == "dev"
|
|
23
|
+
Dynamic: license-file
|
|
24
|
+
|
|
25
|
+
# markdown-to-slack-blocks
|
|
26
|
+
|
|
27
|
+
Convert Markdown into Slack [Block Kit](https://api.slack.com/block-kit) JSON, and render blocks back to Markdown or plain text.
|
|
28
|
+
|
|
29
|
+
This is a Python port of [udivankin/markdown-to-slack-blocks](https://github.com/udivankin/markdown-to-slack-blocks) v1.6.1 (MIT), released here as 1.0.0. It is aimed at the same job: take Markdown from people or from an LLM and post it to Slack without losing headings, lists, code, tables, or mentions.
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pip install markdown-to-slack-blocks
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
```python
|
|
36
|
+
from markdown_to_slack_blocks import markdown_to_blocks
|
|
37
|
+
|
|
38
|
+
blocks = markdown_to_blocks("""
|
|
39
|
+
# Hello World
|
|
40
|
+
This is a **bold** statement.
|
|
41
|
+
""")
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
`markdown_to_blocks` is also available as `markdownToBlocks` if you are moving a call site over from the JavaScript package. The same aliases exist for `splitBlocks`, `splitBlocksWithText`, `blocksToMarkdown`, and `blocksToPlainText`.
|
|
45
|
+
|
|
46
|
+
## What it emits
|
|
47
|
+
|
|
48
|
+
| Markdown | Block |
|
|
49
|
+
| --- | --- |
|
|
50
|
+
| Paragraphs | `section` (`mrkdwn`) by default, or `rich_text` |
|
|
51
|
+
| `#` / `##` | `header` |
|
|
52
|
+
| `###` and below | bold `section`, or a bold `rich_text` section |
|
|
53
|
+
| Lists, quotes, fenced code | `rich_text` (`rich_text_list`, `rich_text_quote`, `rich_text_preformatted`) |
|
|
54
|
+
| `---` | `divider` |
|
|
55
|
+
| A paragraph that is only an image | `image` |
|
|
56
|
+
| GFM tables | `data_table` (or legacy `table`) |
|
|
57
|
+
|
|
58
|
+
Inline styles become Slack mrkdwn (`*bold*`, `_italic_`, `~strike~`, `` `code` ``) inside sections, and `rich_text` style objects otherwise. Links become `<url|label>`.
|
|
59
|
+
|
|
60
|
+
Slack-specific tokens are recognized in the text:
|
|
61
|
+
|
|
62
|
+
- `<@U…>`, `<#C…>`, `<!subteam^S…>`, `<!subteam^T…>`
|
|
63
|
+
- `<!here>`, `<!channel>`, `<!everyone>`
|
|
64
|
+
- `<!date^timestamp^format|fallback>`
|
|
65
|
+
- `:emoji:` shortcodes
|
|
66
|
+
- `#rrggbb` color swatches when color detection is on
|
|
67
|
+
|
|
68
|
+
## Options
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
blocks = markdown_to_blocks(markdown, {
|
|
72
|
+
"mentions": {
|
|
73
|
+
"users": {"username": "U123456"},
|
|
74
|
+
"channels": {"general": "C123456"},
|
|
75
|
+
"user_groups": {"engineers": "S123456"}, # or "userGroups"
|
|
76
|
+
"teams": {"myteam": "T123456"},
|
|
77
|
+
},
|
|
78
|
+
"detect_colors": True, # detectColors
|
|
79
|
+
"prefer_section_blocks": True, # preferSectionBlocks, default True
|
|
80
|
+
"table_block_type": "data_table", # "table" for the legacy block
|
|
81
|
+
"table_caption": "Data table", # "" omits the caption
|
|
82
|
+
})
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Mention IDs are checked before conversion:
|
|
86
|
+
|
|
87
|
+
- users start with `U` or `W`
|
|
88
|
+
- channels start with `C`
|
|
89
|
+
- user groups start with `S`
|
|
90
|
+
- teams start with `T`
|
|
91
|
+
|
|
92
|
+
and the rest of the ID is uppercase alphanumeric.
|
|
93
|
+
|
|
94
|
+
### XML tag handlers
|
|
95
|
+
|
|
96
|
+
Tags the library does not know, such as `<sources>` or `<detailed>`, are not Slack blocks. Pass `xml_tag_handlers` (`xmlTagHandlers`) to turn specific elements into whatever blocks you want. The handler is called with an `XmlTagContext`: the element name, its attributes, and the inner Markdown. `convert` parses that inner Markdown with the same options, so nested elements work too.
|
|
97
|
+
|
|
98
|
+
The tags are parsed with Python's [expat](https://docs.python.org/3/library/pyexpat.html) XML parser, not a regular expression. Names are case-sensitive. Attributes follow XML rules: values are quoted, and entities such as `&` are decoded. The text inside the element is Markdown, so it is not parsed as XML. `a < b` and a raw `&` in the body are kept as written. A start tag that never closes, and a close tag that was never opened, stay as Markdown and do not swallow a later well-formed element. Tags inside fenced code are left alone too.
|
|
99
|
+
|
|
100
|
+
Slack's [`container`](https://docs.slack.dev/reference/block-kit/blocks/container-block/) block is the usual wrapper. `container_block` builds one. `child_blocks` holds at most 10 blocks, and the plain-text title is at most 150 characters.
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
from markdown_to_slack_blocks import container_block, markdown_to_blocks
|
|
104
|
+
|
|
105
|
+
def sources(tag):
|
|
106
|
+
children = tag.convert(tag.body)
|
|
107
|
+
if not children:
|
|
108
|
+
return []
|
|
109
|
+
return container_block(tag.attrs.get("title") or "Sources", children, collapsible=True)
|
|
110
|
+
|
|
111
|
+
def detailed(tag):
|
|
112
|
+
return container_block(
|
|
113
|
+
"Details",
|
|
114
|
+
tag.convert(tag.body),
|
|
115
|
+
collapsible=True,
|
|
116
|
+
default_collapsed=True,
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
blocks = markdown_to_blocks(agent_markdown, {
|
|
120
|
+
"xml_tag_handlers": {"sources": sources, "detailed": detailed},
|
|
121
|
+
})
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
```xml
|
|
125
|
+
Answer text.
|
|
126
|
+
|
|
127
|
+
<sources title="References">
|
|
128
|
+
- [Runbook](https://example.com/runbook)
|
|
129
|
+
</sources>
|
|
130
|
+
|
|
131
|
+
<detailed>
|
|
132
|
+
## Investigation
|
|
133
|
+
The check failed because **disk** was full.
|
|
134
|
+
</detailed>
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Return one block, a list of blocks, or an empty list to drop the element. Return `None` to leave that occurrence as normal Markdown.
|
|
138
|
+
|
|
139
|
+
`register_xml_tag_handler("sources", sources)` installs a process-wide default. An `xml_tag_handlers` entry overrides it, and setting the name to `None` there turns the global handler off for that call. `clear_xml_tag_handlers()` removes the defaults.
|
|
140
|
+
|
|
141
|
+
### Tables
|
|
142
|
+
|
|
143
|
+
Cells are typed from their content:
|
|
144
|
+
|
|
145
|
+
| Cell | Slack cell |
|
|
146
|
+
| --- | --- |
|
|
147
|
+
| Plain text | `raw_text` |
|
|
148
|
+
| A plain number (`10`, `-3.5`) | `raw_number` |
|
|
149
|
+
| Styles, links, mentions, emoji | `rich_text` |
|
|
150
|
+
|
|
151
|
+
### Large messages
|
|
152
|
+
|
|
153
|
+
Slack rejects messages that are too big. `split_blocks` cuts on block boundaries, then inside `rich_text`, then by line inside code blocks. Section and header text is chunked at 3,000 characters first.
|
|
154
|
+
|
|
155
|
+
```python
|
|
156
|
+
from markdown_to_slack_blocks import markdown_to_blocks, split_blocks_with_text
|
|
157
|
+
|
|
158
|
+
for batch in split_blocks_with_text(markdown_to_blocks(very_long_markdown)):
|
|
159
|
+
client.chat_postMessage(channel=channel, text=batch["text"], blocks=batch["blocks"])
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Limits default to 40 blocks and 12,000 JSON characters (`max_blocks` / `maxBlocks`, `max_characters` / `maxCharacters`).
|
|
163
|
+
|
|
164
|
+
### Back to Markdown or plain text
|
|
165
|
+
|
|
166
|
+
```python
|
|
167
|
+
from markdown_to_slack_blocks import blocks_to_markdown, blocks_to_plain_text
|
|
168
|
+
|
|
169
|
+
text = blocks_to_plain_text(blocks) # chat.postMessage fallback
|
|
170
|
+
markdown = blocks_to_markdown(blocks, {
|
|
171
|
+
"mentions": {
|
|
172
|
+
"users": {"U123456": "username"},
|
|
173
|
+
"channels": {"C123456": "general"},
|
|
174
|
+
"user_groups": {"S123456": "engineers"},
|
|
175
|
+
"teams": {"T123456": "myteam"},
|
|
176
|
+
}
|
|
177
|
+
})
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
The Markdown is canonical rather than byte-for-byte identical to the source. Blocks this library produced round-trip cleanly.
|
|
181
|
+
|
|
182
|
+
## Development
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
pip install -e ".[dev]"
|
|
186
|
+
pytest
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
The tests include the upstream fixture corpus (`tests/fixtures`) and check both directions against it.
|
|
190
|
+
|
|
191
|
+
## License
|
|
192
|
+
|
|
193
|
+
MIT. The original library is copyright https://github.com/udivankin. This Python port is copyright Nikita Nefedov. See [LICENSE](LICENSE).
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# markdown-to-slack-blocks
|
|
2
|
+
|
|
3
|
+
Convert Markdown into Slack [Block Kit](https://api.slack.com/block-kit) JSON, and render blocks back to Markdown or plain text.
|
|
4
|
+
|
|
5
|
+
This is a Python port of [udivankin/markdown-to-slack-blocks](https://github.com/udivankin/markdown-to-slack-blocks) v1.6.1 (MIT), released here as 1.0.0. It is aimed at the same job: take Markdown from people or from an LLM and post it to Slack without losing headings, lists, code, tables, or mentions.
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install markdown-to-slack-blocks
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
```python
|
|
12
|
+
from markdown_to_slack_blocks import markdown_to_blocks
|
|
13
|
+
|
|
14
|
+
blocks = markdown_to_blocks("""
|
|
15
|
+
# Hello World
|
|
16
|
+
This is a **bold** statement.
|
|
17
|
+
""")
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
`markdown_to_blocks` is also available as `markdownToBlocks` if you are moving a call site over from the JavaScript package. The same aliases exist for `splitBlocks`, `splitBlocksWithText`, `blocksToMarkdown`, and `blocksToPlainText`.
|
|
21
|
+
|
|
22
|
+
## What it emits
|
|
23
|
+
|
|
24
|
+
| Markdown | Block |
|
|
25
|
+
| --- | --- |
|
|
26
|
+
| Paragraphs | `section` (`mrkdwn`) by default, or `rich_text` |
|
|
27
|
+
| `#` / `##` | `header` |
|
|
28
|
+
| `###` and below | bold `section`, or a bold `rich_text` section |
|
|
29
|
+
| Lists, quotes, fenced code | `rich_text` (`rich_text_list`, `rich_text_quote`, `rich_text_preformatted`) |
|
|
30
|
+
| `---` | `divider` |
|
|
31
|
+
| A paragraph that is only an image | `image` |
|
|
32
|
+
| GFM tables | `data_table` (or legacy `table`) |
|
|
33
|
+
|
|
34
|
+
Inline styles become Slack mrkdwn (`*bold*`, `_italic_`, `~strike~`, `` `code` ``) inside sections, and `rich_text` style objects otherwise. Links become `<url|label>`.
|
|
35
|
+
|
|
36
|
+
Slack-specific tokens are recognized in the text:
|
|
37
|
+
|
|
38
|
+
- `<@U…>`, `<#C…>`, `<!subteam^S…>`, `<!subteam^T…>`
|
|
39
|
+
- `<!here>`, `<!channel>`, `<!everyone>`
|
|
40
|
+
- `<!date^timestamp^format|fallback>`
|
|
41
|
+
- `:emoji:` shortcodes
|
|
42
|
+
- `#rrggbb` color swatches when color detection is on
|
|
43
|
+
|
|
44
|
+
## Options
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
blocks = markdown_to_blocks(markdown, {
|
|
48
|
+
"mentions": {
|
|
49
|
+
"users": {"username": "U123456"},
|
|
50
|
+
"channels": {"general": "C123456"},
|
|
51
|
+
"user_groups": {"engineers": "S123456"}, # or "userGroups"
|
|
52
|
+
"teams": {"myteam": "T123456"},
|
|
53
|
+
},
|
|
54
|
+
"detect_colors": True, # detectColors
|
|
55
|
+
"prefer_section_blocks": True, # preferSectionBlocks, default True
|
|
56
|
+
"table_block_type": "data_table", # "table" for the legacy block
|
|
57
|
+
"table_caption": "Data table", # "" omits the caption
|
|
58
|
+
})
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Mention IDs are checked before conversion:
|
|
62
|
+
|
|
63
|
+
- users start with `U` or `W`
|
|
64
|
+
- channels start with `C`
|
|
65
|
+
- user groups start with `S`
|
|
66
|
+
- teams start with `T`
|
|
67
|
+
|
|
68
|
+
and the rest of the ID is uppercase alphanumeric.
|
|
69
|
+
|
|
70
|
+
### XML tag handlers
|
|
71
|
+
|
|
72
|
+
Tags the library does not know, such as `<sources>` or `<detailed>`, are not Slack blocks. Pass `xml_tag_handlers` (`xmlTagHandlers`) to turn specific elements into whatever blocks you want. The handler is called with an `XmlTagContext`: the element name, its attributes, and the inner Markdown. `convert` parses that inner Markdown with the same options, so nested elements work too.
|
|
73
|
+
|
|
74
|
+
The tags are parsed with Python's [expat](https://docs.python.org/3/library/pyexpat.html) XML parser, not a regular expression. Names are case-sensitive. Attributes follow XML rules: values are quoted, and entities such as `&` are decoded. The text inside the element is Markdown, so it is not parsed as XML. `a < b` and a raw `&` in the body are kept as written. A start tag that never closes, and a close tag that was never opened, stay as Markdown and do not swallow a later well-formed element. Tags inside fenced code are left alone too.
|
|
75
|
+
|
|
76
|
+
Slack's [`container`](https://docs.slack.dev/reference/block-kit/blocks/container-block/) block is the usual wrapper. `container_block` builds one. `child_blocks` holds at most 10 blocks, and the plain-text title is at most 150 characters.
|
|
77
|
+
|
|
78
|
+
```python
|
|
79
|
+
from markdown_to_slack_blocks import container_block, markdown_to_blocks
|
|
80
|
+
|
|
81
|
+
def sources(tag):
|
|
82
|
+
children = tag.convert(tag.body)
|
|
83
|
+
if not children:
|
|
84
|
+
return []
|
|
85
|
+
return container_block(tag.attrs.get("title") or "Sources", children, collapsible=True)
|
|
86
|
+
|
|
87
|
+
def detailed(tag):
|
|
88
|
+
return container_block(
|
|
89
|
+
"Details",
|
|
90
|
+
tag.convert(tag.body),
|
|
91
|
+
collapsible=True,
|
|
92
|
+
default_collapsed=True,
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
blocks = markdown_to_blocks(agent_markdown, {
|
|
96
|
+
"xml_tag_handlers": {"sources": sources, "detailed": detailed},
|
|
97
|
+
})
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
```xml
|
|
101
|
+
Answer text.
|
|
102
|
+
|
|
103
|
+
<sources title="References">
|
|
104
|
+
- [Runbook](https://example.com/runbook)
|
|
105
|
+
</sources>
|
|
106
|
+
|
|
107
|
+
<detailed>
|
|
108
|
+
## Investigation
|
|
109
|
+
The check failed because **disk** was full.
|
|
110
|
+
</detailed>
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Return one block, a list of blocks, or an empty list to drop the element. Return `None` to leave that occurrence as normal Markdown.
|
|
114
|
+
|
|
115
|
+
`register_xml_tag_handler("sources", sources)` installs a process-wide default. An `xml_tag_handlers` entry overrides it, and setting the name to `None` there turns the global handler off for that call. `clear_xml_tag_handlers()` removes the defaults.
|
|
116
|
+
|
|
117
|
+
### Tables
|
|
118
|
+
|
|
119
|
+
Cells are typed from their content:
|
|
120
|
+
|
|
121
|
+
| Cell | Slack cell |
|
|
122
|
+
| --- | --- |
|
|
123
|
+
| Plain text | `raw_text` |
|
|
124
|
+
| A plain number (`10`, `-3.5`) | `raw_number` |
|
|
125
|
+
| Styles, links, mentions, emoji | `rich_text` |
|
|
126
|
+
|
|
127
|
+
### Large messages
|
|
128
|
+
|
|
129
|
+
Slack rejects messages that are too big. `split_blocks` cuts on block boundaries, then inside `rich_text`, then by line inside code blocks. Section and header text is chunked at 3,000 characters first.
|
|
130
|
+
|
|
131
|
+
```python
|
|
132
|
+
from markdown_to_slack_blocks import markdown_to_blocks, split_blocks_with_text
|
|
133
|
+
|
|
134
|
+
for batch in split_blocks_with_text(markdown_to_blocks(very_long_markdown)):
|
|
135
|
+
client.chat_postMessage(channel=channel, text=batch["text"], blocks=batch["blocks"])
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Limits default to 40 blocks and 12,000 JSON characters (`max_blocks` / `maxBlocks`, `max_characters` / `maxCharacters`).
|
|
139
|
+
|
|
140
|
+
### Back to Markdown or plain text
|
|
141
|
+
|
|
142
|
+
```python
|
|
143
|
+
from markdown_to_slack_blocks import blocks_to_markdown, blocks_to_plain_text
|
|
144
|
+
|
|
145
|
+
text = blocks_to_plain_text(blocks) # chat.postMessage fallback
|
|
146
|
+
markdown = blocks_to_markdown(blocks, {
|
|
147
|
+
"mentions": {
|
|
148
|
+
"users": {"U123456": "username"},
|
|
149
|
+
"channels": {"C123456": "general"},
|
|
150
|
+
"user_groups": {"S123456": "engineers"},
|
|
151
|
+
"teams": {"T123456": "myteam"},
|
|
152
|
+
}
|
|
153
|
+
})
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
The Markdown is canonical rather than byte-for-byte identical to the source. Blocks this library produced round-trip cleanly.
|
|
157
|
+
|
|
158
|
+
## Development
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
pip install -e ".[dev]"
|
|
162
|
+
pytest
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
The tests include the upstream fixture corpus (`tests/fixtures`) and check both directions against it.
|
|
166
|
+
|
|
167
|
+
## License
|
|
168
|
+
|
|
169
|
+
MIT. The original library is copyright https://github.com/udivankin. This Python port is copyright Nikita Nefedov. See [LICENSE](LICENSE).
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "markdown-to-slack-blocks"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "Convert Markdown (including GFM tables, mentions, and LLM output) into Slack Block Kit JSON, and back."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
license-files = ["LICENSE"]
|
|
12
|
+
requires-python = ">=3.10"
|
|
13
|
+
authors = [{ name = "Nikita Nefedov" }]
|
|
14
|
+
keywords = [
|
|
15
|
+
"slack",
|
|
16
|
+
"markdown",
|
|
17
|
+
"block-kit",
|
|
18
|
+
"slack-blocks",
|
|
19
|
+
"gfm",
|
|
20
|
+
]
|
|
21
|
+
classifiers = [
|
|
22
|
+
"Programming Language :: Python :: 3",
|
|
23
|
+
"Programming Language :: Python :: 3.10",
|
|
24
|
+
"Programming Language :: Python :: 3.11",
|
|
25
|
+
"Programming Language :: Python :: 3.12",
|
|
26
|
+
"Programming Language :: Python :: 3.13",
|
|
27
|
+
"Programming Language :: Python :: 3.14",
|
|
28
|
+
"Typing :: Typed",
|
|
29
|
+
]
|
|
30
|
+
dependencies = ["markdown-it-py>=3.0.0"]
|
|
31
|
+
|
|
32
|
+
[project.optional-dependencies]
|
|
33
|
+
dev = ["pytest>=8"]
|
|
34
|
+
|
|
35
|
+
[project.urls]
|
|
36
|
+
Homepage = "https://github.com/nikita2206/markdown-to-slack-blocks"
|
|
37
|
+
Source = "https://github.com/nikita2206/markdown-to-slack-blocks"
|
|
38
|
+
|
|
39
|
+
[tool.setuptools.packages.find]
|
|
40
|
+
where = ["src"]
|
|
41
|
+
|
|
42
|
+
[tool.pytest.ini_options]
|
|
43
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""Convert Markdown to Slack Block Kit JSON, and back.
|
|
2
|
+
|
|
3
|
+
Python port of `markdown-to-slack-blocks` (MIT). Behavior tracks the
|
|
4
|
+
JavaScript library at version 1.6.1. This package's own release is 1.0.0.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from .parser import markdown_to_blocks
|
|
8
|
+
from .splitter import (
|
|
9
|
+
blocks_to_markdown,
|
|
10
|
+
blocks_to_plain_text,
|
|
11
|
+
split_blocks,
|
|
12
|
+
split_blocks_with_text,
|
|
13
|
+
)
|
|
14
|
+
from .tags import (
|
|
15
|
+
XmlTagContext,
|
|
16
|
+
clear_xml_tag_handlers,
|
|
17
|
+
container_block,
|
|
18
|
+
register_xml_tag_handler,
|
|
19
|
+
)
|
|
20
|
+
from .validator import validate_blocks_to_markdown_options, validate_options
|
|
21
|
+
|
|
22
|
+
__all__ = [
|
|
23
|
+
"XmlTagContext",
|
|
24
|
+
"blocks_to_markdown",
|
|
25
|
+
"blocks_to_plain_text",
|
|
26
|
+
"clear_xml_tag_handlers",
|
|
27
|
+
"container_block",
|
|
28
|
+
"markdown_to_blocks",
|
|
29
|
+
"register_xml_tag_handler",
|
|
30
|
+
"split_blocks",
|
|
31
|
+
"split_blocks_with_text",
|
|
32
|
+
"validate_blocks_to_markdown_options",
|
|
33
|
+
"validate_options",
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
# Aliases matching the JavaScript export names.
|
|
37
|
+
markdownToBlocks = markdown_to_blocks
|
|
38
|
+
splitBlocks = split_blocks
|
|
39
|
+
splitBlocksWithText = split_blocks_with_text
|
|
40
|
+
blocksToMarkdown = blocks_to_markdown
|
|
41
|
+
blocksToPlainText = blocks_to_plain_text
|
|
42
|
+
validateOptions = validate_options
|
|
43
|
+
validateBlocksToMarkdownOptions = validate_blocks_to_markdown_options
|
|
44
|
+
|
|
45
|
+
__version__ = "1.0.0"
|