minchin.md-it.insert 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.
@@ -0,0 +1 @@
1
+ markdown-it-py
@@ -0,0 +1,5 @@
1
+ pip-tools
2
+ minchin.releaser >= 0.9.5
3
+ black
4
+ isort
5
+ pytest
@@ -0,0 +1,46 @@
1
+ Metadata-Version: 2.4
2
+ Name: minchin.md-it.insert
3
+ Version: 1.0.0
4
+ Summary: insert, for Markdown-IT-py
5
+ Author-email: William Minchin <w_minchin@hotmail.com>
6
+ Maintainer-email: William Minchin <w_minchin@hotmail.com>
7
+ License: AGPL-3.0
8
+ Project-URL: Bug Tracker, https://github.com/MinchinWeb/minchin.md-it.insert/issues
9
+ Project-URL: Repository, https://github.com/MinchinWeb/minchin.md-it.insert
10
+ Project-URL: Changelog, https://github.com/MinchinWeb/minchin.md-it.insert/blob/master/CHANGELOG.rst
11
+ Keywords: commonmark,markdown,plugin,insert
12
+ Classifier: Development Status :: 5 - Production/Stable
13
+ Classifier: Environment :: Plugins
14
+ Classifier: Intended Audience :: End Users/Desktop
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Topic :: Text Processing :: Markup :: Markdown
17
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
18
+ Description-Content-Type: text/x-rst
19
+ Requires-Dist: markdown-it-py
20
+ Provides-Extra: dev
21
+ Requires-Dist: pip-tools; extra == "dev"
22
+ Requires-Dist: minchin.releaser>=0.9.5; extra == "dev"
23
+ Requires-Dist: black; extra == "dev"
24
+ Requires-Dist: isort; extra == "dev"
25
+ Requires-Dist: pytest; extra == "dev"
26
+
27
+ Insert for Markdown-IT-Py
28
+ =========================
29
+
30
+ This is a plugin for the Python implementation of
31
+ `Markdown-IT <https://github.com/executablebooks/markdown-it-py>`_ (a CommonMark
32
+ parser) that provides insert (``<ins>``) via double pluses (``++``).
33
+
34
+ Example usage::
35
+
36
+ >>> from markdown_it import MarkdownIt
37
+ >>> from minchin.md_it.insert import insert_plugin
38
+ >>> md = MarkdownIt().use(insert_plugin)
39
+ >>> md.render("I ++added++ this")
40
+ '<p>I <ins>added</ins> this</p>\\n'
41
+ >>> md.render("this++text\\\\ has\\\\ spaces++")
42
+ '<p>this<ins>text\\ has\\ spaces</ins></p>\\n'
43
+
44
+ Tests can be run using ``pytest``.
45
+
46
+ This is ported from hasgeek's Funnel, under the AGPL-3.0 license.
@@ -0,0 +1,20 @@
1
+ Insert for Markdown-IT-Py
2
+ =========================
3
+
4
+ This is a plugin for the Python implementation of
5
+ `Markdown-IT <https://github.com/executablebooks/markdown-it-py>`_ (a CommonMark
6
+ parser) that provides insert (``<ins>``) via double pluses (``++``).
7
+
8
+ Example usage::
9
+
10
+ >>> from markdown_it import MarkdownIt
11
+ >>> from minchin.md_it.insert import insert_plugin
12
+ >>> md = MarkdownIt().use(insert_plugin)
13
+ >>> md.render("I ++added++ this")
14
+ '<p>I <ins>added</ins> this</p>\\n'
15
+ >>> md.render("this++text\\\\ has\\\\ spaces++")
16
+ '<p>this<ins>text\\ has\\ spaces</ins></p>\\n'
17
+
18
+ Tests can be run using ``pytest``.
19
+
20
+ This is ported from hasgeek's Funnel, under the AGPL-3.0 license.
@@ -0,0 +1,14 @@
1
+ """Insert tag plugin, for Markdown-It."""
2
+
3
+ from .funnel import insert_plugin
4
+
5
+ __all__ = ("insert_plugin", )
6
+
7
+ __title__ = "minchin.md-it.insert"
8
+ __tagline__ = "Superscript, for Markdown-IT-Py"
9
+ __version__ = "1.0.0"
10
+ __author__ = "William Minchin"
11
+ __email__ = "w_minchin@hotmail.com"
12
+ __license__ = "AGPL-3.0 License"
13
+ __copyright__ = "Copyright 2026 William Minchin"
14
+ __url__ = "https://github.com/MinchinWeb/minchin.md-it.insert"
@@ -0,0 +1,167 @@
1
+ """
2
+ Markdown-it-py plugin to introduce <ins> markup using ++inserted++.
3
+
4
+ Ported from markdown_it.rules_inline.strikethrough.
5
+ Extracted from ``funnel`` at
6
+ https://github.com/hasgeek/funnel/blob/main/funnel/utils/markdown/mdit_plugins/ins_tag.py
7
+ under the AGPL-3.0 license
8
+
9
+ Copyright (c) 2026 William Minchin
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ from collections.abc import Sequence
15
+
16
+ from markdown_it import MarkdownIt
17
+ from markdown_it.renderer import RendererHTML
18
+ from markdown_it.rules_inline import StateInline
19
+ from markdown_it.rules_inline.state_inline import Delimiter
20
+ from markdown_it.token import Token
21
+ from markdown_it.utils import EnvType, OptionsDict
22
+
23
+ __all__ = ['insert_plugin']
24
+
25
+ PLUS_CHAR = '+'
26
+
27
+
28
+ def tokenize(state: StateInline, silent: bool) -> bool:
29
+ """Insert each marker as a separate text token, and add it to delimiter list."""
30
+ start = state.pos
31
+ ch = state.src[start]
32
+
33
+ if silent:
34
+ return False
35
+
36
+ if ch != PLUS_CHAR:
37
+ return False
38
+
39
+ scanned = state.scanDelims(state.pos, True)
40
+
41
+ length = scanned.length
42
+
43
+ if length < 2:
44
+ return False
45
+
46
+ if length % 2:
47
+ token = state.push('text', '', 0)
48
+ token.content = ch
49
+ length -= 1
50
+
51
+ i = 0
52
+ while i < length:
53
+ token = state.push('text', '', 0)
54
+ token.content = ch + ch
55
+ state.delimiters.append(
56
+ Delimiter(
57
+ marker=ord(ch),
58
+ length=0, # disable "rule of 3" length checks meant for emphasis
59
+ token=len(state.tokens) - 1,
60
+ end=-1,
61
+ open=scanned.can_open,
62
+ close=scanned.can_close,
63
+ )
64
+ )
65
+ i += 2
66
+
67
+ state.pos += scanned.length
68
+ return True
69
+
70
+
71
+ def _post_process(state: StateInline, delimiters: list[Delimiter]) -> None:
72
+ lone_markers = []
73
+ maximum = len(delimiters)
74
+
75
+ for i in range(maximum):
76
+ start_delim = delimiters[i]
77
+ if start_delim.marker != ord(PLUS_CHAR):
78
+ continue
79
+
80
+ if start_delim.end == -1:
81
+ continue
82
+
83
+ end_delim = delimiters[start_delim.end]
84
+
85
+ token = state.tokens[start_delim.token]
86
+ token.type = 'ins_open'
87
+ token.tag = 'ins'
88
+ token.nesting = 1
89
+ token.markup = PLUS_CHAR * 2
90
+ token.content = ''
91
+
92
+ token = state.tokens[end_delim.token]
93
+ token.type = 'ins_close'
94
+ token.tag = 'ins'
95
+ token.nesting = -1
96
+ token.markup = PLUS_CHAR * 2
97
+ token.content = ''
98
+
99
+ end_token = state.tokens[end_delim.token - 1]
100
+
101
+ if end_token.type == 'text' and end_token == PLUS_CHAR:
102
+ lone_markers.append(end_delim.token - 1)
103
+
104
+ # If a marker sequence has an odd number of characters, it's split
105
+ # like this: `+++++` -> `+` + `++` + `++`, leaving one marker at the
106
+ # start of the sequence.
107
+ #
108
+ # So, we have to move all those markers after subsequent ins_close tags.
109
+ #
110
+ while lone_markers:
111
+ i = lone_markers.pop()
112
+ j = i + 1
113
+
114
+ while j < len(state.tokens) and state.tokens[j].type == 'ins_close':
115
+ j += 1
116
+
117
+ j -= 1
118
+
119
+ if i != j:
120
+ token = state.tokens[j]
121
+ state.tokens[j] = state.tokens[i]
122
+ state.tokens[i] = token
123
+
124
+
125
+ def ins_open(
126
+ renderer: RendererHTML, # noqa: ARG001
127
+ tokens: Sequence[Token], # noqa: ARG001
128
+ idx: int, # noqa: ARG001
129
+ options: OptionsDict, # noqa: ARG001
130
+ env: EnvType, # noqa: ARG001
131
+ ) -> str:
132
+ return '<ins>'
133
+
134
+
135
+ def ins_close(
136
+ renderer: RendererHTML, # noqa: ARG001
137
+ tokens: Sequence[Token], # noqa: ARG001
138
+ idx: int, # noqa: ARG001
139
+ options: OptionsDict, # noqa: ARG001
140
+ env: EnvType, # noqa: ARG001
141
+ ) -> str:
142
+ return '</ins>'
143
+
144
+
145
+ def post_process(state: StateInline) -> None:
146
+ """Walk through delimiter list and replace text tokens with tags."""
147
+ tokens_meta = state.tokens_meta
148
+ maximum = len(state.tokens_meta)
149
+ _post_process(state, state.delimiters)
150
+ curr = 0
151
+ while curr < maximum:
152
+ try:
153
+ curr_meta = tokens_meta[curr]
154
+ except IndexError:
155
+ pass
156
+ else:
157
+ if curr_meta and 'delimiters' in curr_meta:
158
+ _post_process(state, curr_meta["delimiters"])
159
+ curr += 1
160
+
161
+
162
+ def insert_plugin(md: MarkdownIt) -> None:
163
+ """Render ``++text++`` markup with a HTML ``<ins>`` tag."""
164
+ md.inline.ruler.before('strikethrough', 'ins', tokenize)
165
+ md.inline.ruler2.before('strikethrough', 'ins', post_process)
166
+ md.add_render_rule('ins_open', ins_open)
167
+ md.add_render_rule('ins_close', ins_close)
@@ -0,0 +1,46 @@
1
+ Metadata-Version: 2.4
2
+ Name: minchin.md-it.insert
3
+ Version: 1.0.0
4
+ Summary: insert, for Markdown-IT-py
5
+ Author-email: William Minchin <w_minchin@hotmail.com>
6
+ Maintainer-email: William Minchin <w_minchin@hotmail.com>
7
+ License: AGPL-3.0
8
+ Project-URL: Bug Tracker, https://github.com/MinchinWeb/minchin.md-it.insert/issues
9
+ Project-URL: Repository, https://github.com/MinchinWeb/minchin.md-it.insert
10
+ Project-URL: Changelog, https://github.com/MinchinWeb/minchin.md-it.insert/blob/master/CHANGELOG.rst
11
+ Keywords: commonmark,markdown,plugin,insert
12
+ Classifier: Development Status :: 5 - Production/Stable
13
+ Classifier: Environment :: Plugins
14
+ Classifier: Intended Audience :: End Users/Desktop
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Topic :: Text Processing :: Markup :: Markdown
17
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
18
+ Description-Content-Type: text/x-rst
19
+ Requires-Dist: markdown-it-py
20
+ Provides-Extra: dev
21
+ Requires-Dist: pip-tools; extra == "dev"
22
+ Requires-Dist: minchin.releaser>=0.9.5; extra == "dev"
23
+ Requires-Dist: black; extra == "dev"
24
+ Requires-Dist: isort; extra == "dev"
25
+ Requires-Dist: pytest; extra == "dev"
26
+
27
+ Insert for Markdown-IT-Py
28
+ =========================
29
+
30
+ This is a plugin for the Python implementation of
31
+ `Markdown-IT <https://github.com/executablebooks/markdown-it-py>`_ (a CommonMark
32
+ parser) that provides insert (``<ins>``) via double pluses (``++``).
33
+
34
+ Example usage::
35
+
36
+ >>> from markdown_it import MarkdownIt
37
+ >>> from minchin.md_it.insert import insert_plugin
38
+ >>> md = MarkdownIt().use(insert_plugin)
39
+ >>> md.render("I ++added++ this")
40
+ '<p>I <ins>added</ins> this</p>\\n'
41
+ >>> md.render("this++text\\\\ has\\\\ spaces++")
42
+ '<p>this<ins>text\\ has\\ spaces</ins></p>\\n'
43
+
44
+ Tests can be run using ``pytest``.
45
+
46
+ This is ported from hasgeek's Funnel, under the AGPL-3.0 license.
@@ -0,0 +1,12 @@
1
+ README.rst
2
+ pyproject.toml
3
+ .requirements/base.in
4
+ .requirements/dev.in
5
+ minchin.md_it.insert.egg-info/PKG-INFO
6
+ minchin.md_it.insert.egg-info/SOURCES.txt
7
+ minchin.md_it.insert.egg-info/dependency_links.txt
8
+ minchin.md_it.insert.egg-info/requires.txt
9
+ minchin.md_it.insert.egg-info/top_level.txt
10
+ minchin/md_it/insert/__init__.py
11
+ minchin/md_it/insert/funnel.py
12
+ tests/test_insert.py
@@ -0,0 +1,8 @@
1
+ markdown-it-py
2
+
3
+ [dev]
4
+ pip-tools
5
+ minchin.releaser>=0.9.5
6
+ black
7
+ isort
8
+ pytest
@@ -0,0 +1,81 @@
1
+ [build-system]
2
+ requires = ["setuptools"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [tool.setuptools.packages.find]
6
+ include = ["minchin*"]
7
+ exclude = ["tests"]
8
+
9
+ [tool.setuptools.dynamic]
10
+ # this has to be the place the version is located via static analysis
11
+ version = {attr = "minchin.md_it.insert.__version__"}
12
+ # description = {attr = "minchin.jrnl.__description__"} # must be pulled from file
13
+ readme = {file = ["README.rst"], content-type = "text/x-rst"}
14
+ dependencies = {file = [".requirements/base.in"]}
15
+
16
+ [tool.setuptools.dynamic.optional-dependencies]
17
+ dev = {file = [".requirements/dev.in"] }
18
+ # release = {file = [".requirements/release.in"] }
19
+ # docs = {file = [".requirements/docs.in"] }
20
+
21
+ [project]
22
+ name = "minchin.md-it.insert"
23
+ description = "insert, for Markdown-IT-py"
24
+ dynamic = [
25
+ "version",
26
+ # "description",
27
+ "readme",
28
+ "dependencies",
29
+ "optional-dependencies",
30
+ ]
31
+ authors = [
32
+ {name = "William Minchin", email="w_minchin@hotmail.com" },
33
+ ]
34
+ maintainers = [
35
+ {name = "William Minchin", email="w_minchin@hotmail.com" },
36
+ ]
37
+ # requires-python = ">=3.6"
38
+ keywords = ["commonmark", "markdown", "plugin", "insert"]
39
+ license = {text = "AGPL-3.0"}
40
+ classifiers = [
41
+ "Development Status :: 5 - Production/Stable",
42
+ "Environment :: Plugins",
43
+ "Intended Audience :: End Users/Desktop",
44
+ "Operating System :: OS Independent",
45
+ "Topic :: Text Processing :: Markup :: Markdown",
46
+ "Topic :: Software Development :: Libraries :: Python Modules",
47
+ ]
48
+
49
+ [project.urls]
50
+ # "Homepage" =
51
+ "Bug Tracker" = "https://github.com/MinchinWeb/minchin.md-it.insert/issues"
52
+ # Documentation =
53
+ Repository = "https://github.com/MinchinWeb/minchin.md-it.insert"
54
+ Changelog = "https://github.com/MinchinWeb/minchin.md-it.insert/blob/master/CHANGELOG.rst"
55
+ # "Release Notes"
56
+
57
+
58
+ [tool.isort]
59
+ # Maintain compatibility with Black
60
+ profile = "black"
61
+ # multi_line_output = 3
62
+
63
+ # Sort imports within their section independent of the import type
64
+ force_sort_within_sections = true
65
+
66
+ known_first_party = ["minchin"]
67
+ sections = "FUTURE,STDLIB,THIRDPARTY,FIRSTPARTY,LOCALFOLDER"
68
+
69
+ skip_gitignore = true
70
+
71
+ [tool.black]
72
+ line-length = 88
73
+
74
+ [tool.flake8]
75
+ # requires flake8-pyproject to read this
76
+ ignore = [
77
+ "E203", # conflicts with `black`
78
+ "W503", # "line break before binary operator", conflicts with `black`
79
+ ]
80
+ max-line-length = 88
81
+ count = true
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,20 @@
1
+ from pathlib import Path
2
+
3
+ from markdown_it import MarkdownIt
4
+ from markdown_it.utils import read_fixture_file
5
+ import pytest
6
+
7
+ from minchin.md_it.insert import insert_plugin
8
+
9
+ FIXTURE_PATH = Path(__file__).parent.joinpath("fixtures")
10
+
11
+
12
+ @pytest.mark.parametrize(
13
+ "line,title,input,expected",
14
+ read_fixture_file(FIXTURE_PATH.joinpath("insert.md")),
15
+ )
16
+ def test_insert_fixtures(line, title, input, expected):
17
+ md = MarkdownIt("commonmark").use(insert_plugin)
18
+ md.options["xhtmlOut"] = False
19
+ text = md.render(input)
20
+ assert text.rstrip() == expected.rstrip()