pm-markdown 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 @@
1
+ /home/trey/.dotfiles/gitignore
@@ -0,0 +1,9 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2022-present Trey Hunner <trey@treyhunner.com>
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,41 @@
1
+ Metadata-Version: 2.1
2
+ Name: pm-markdown
3
+ Version: 0.0.1
4
+ Project-URL: Documentation, https://github.com/TruthfulTechnology/pm-markdown#readme
5
+ Project-URL: Issues, https://github.com/TruthfulTechnology/pm-markdown/issues
6
+ Project-URL: Source, https://github.com/TruthfulTechnology/pm-markdown
7
+ Author-email: Trey Hunner <trey@pythonmorsels.com>
8
+ License-Expression: MIT
9
+ License-File: LICENSE.txt
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Programming Language :: Python
12
+ Classifier: Programming Language :: Python :: 3.7
13
+ Classifier: Programming Language :: Python :: 3.8
14
+ Classifier: Programming Language :: Python :: 3.9
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: Implementation :: CPython
17
+ Classifier: Programming Language :: Python :: Implementation :: PyPy
18
+ Requires-Python: >=3.7
19
+ Description-Content-Type: text/markdown
20
+
21
+ # Picky Markdown
22
+
23
+ [![PyPI - Version](https://img.shields.io/pypi/v/picky-markdown.svg)](https://pypi.org/project/picky-markdown)
24
+ [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/picky-markdown.svg)](https://pypi.org/project/picky-markdown)
25
+
26
+ -----
27
+
28
+ **Table of Contents**
29
+
30
+ - [Installation](#installation)
31
+ - [License](#license)
32
+
33
+ ## Installation
34
+
35
+ ```console
36
+ pip install picky-markdown
37
+ ```
38
+
39
+ ## License
40
+
41
+ `picky-markdown` is distributed under the terms of the [MIT](https://spdx.org/licenses/MIT.html) license.
@@ -0,0 +1,21 @@
1
+ # Picky Markdown
2
+
3
+ [![PyPI - Version](https://img.shields.io/pypi/v/picky-markdown.svg)](https://pypi.org/project/picky-markdown)
4
+ [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/picky-markdown.svg)](https://pypi.org/project/picky-markdown)
5
+
6
+ -----
7
+
8
+ **Table of Contents**
9
+
10
+ - [Installation](#installation)
11
+ - [License](#license)
12
+
13
+ ## Installation
14
+
15
+ ```console
16
+ pip install picky-markdown
17
+ ```
18
+
19
+ ## License
20
+
21
+ `picky-markdown` is distributed under the terms of the [MIT](https://spdx.org/licenses/MIT.html) license.
@@ -0,0 +1,4 @@
1
+ # SPDX-FileCopyrightText: 2022-present Trey Hunner <trey@treyhunner.com>
2
+ #
3
+ # SPDX-License-Identifier: MIT
4
+ __version__ = '0.0.1'
@@ -0,0 +1,259 @@
1
+ from collections import UserList
2
+ from dataclasses import dataclass
3
+ try:
4
+ from functools import cached_property
5
+ except ImportError:
6
+ from cached_property import cached_property
7
+ import re
8
+
9
+
10
+ BLOCK_LINE_RE = re.compile(r"""
11
+ (?P<code> ```(?P<style> \S* )(?P<flags> .*)?\n )
12
+ |
13
+ (?P<comment> <!--- \s* (?P<comment_text> .*? ) \s* -->\n )
14
+ |
15
+ (?P<text> .*\n )
16
+ """, flags=re.VERBOSE)
17
+
18
+
19
+ @dataclass
20
+ class Link:
21
+ text: str = ""
22
+ anchor: str = ""
23
+ url: str = ""
24
+ hover: str = ""
25
+
26
+ open1_re = re.compile(r"""
27
+ \[ (?P<text> .+? ) \]
28
+ \[ (?P<anchor> [\w\s!.-]+? ) \]
29
+ """, flags=re.VERBOSE)
30
+ open2_re = re.compile(r"""
31
+ \[ (?P<text> (?P<anchor> .[^]\n]+? ) ) \]
32
+ \[\]
33
+ """, flags=re.VERBOSE)
34
+ close_re = re.compile(r"""
35
+ ^
36
+ \[ (?P<anchor> .+? ) \]:
37
+ \s+
38
+ (?P<url> https?://.+ )
39
+ $
40
+ """, flags=re.MULTILINE | re.VERBOSE)
41
+ link_re = re.compile(r"""
42
+ \[ (?P<text> .* ) \]
43
+ \(
44
+ (?P<url> https?://[^\s]+[^)] )
45
+ (?: \s+ " (?P<hover> .* ) " )?
46
+ \)
47
+ """, flags=re.MULTILINE | re.VERBOSE)
48
+
49
+ def __post_init__(self):
50
+ self.anchor = self.anchor.casefold()
51
+
52
+
53
+ class MarkdownBlocks(UserList):
54
+
55
+ @cached_property
56
+ def text_blocks(self):
57
+ return MarkdownBlocks(
58
+ block
59
+ for block in self
60
+ if isinstance(block, Text)
61
+ )
62
+
63
+ @cached_property
64
+ def code_blocks(self):
65
+ return MarkdownBlocks(
66
+ block
67
+ for block in self
68
+ if isinstance(block, Code)
69
+ )
70
+
71
+ @cached_property
72
+ def start_anchors(self):
73
+ return [
74
+ link
75
+ for block in self.text_blocks
76
+ for link in block.start_anchors
77
+ ]
78
+
79
+ @cached_property
80
+ def end_anchors(self):
81
+ return [
82
+ link
83
+ for block in self.text_blocks
84
+ for link in block.end_anchors
85
+ ]
86
+
87
+ @cached_property
88
+ def inline_links(self):
89
+ return [
90
+ link
91
+ for block in self.text_blocks
92
+ for link in block.inline_links
93
+ ]
94
+
95
+ @cached_property
96
+ def links(self):
97
+ links = self.inline_links
98
+ starts = self.start_anchors
99
+ anchors = {
100
+ link.anchor: link
101
+ for link in self.end_anchors
102
+ }
103
+ for link in starts:
104
+ if link.anchor in anchors:
105
+ link.url = anchors[link.anchor].url
106
+ links.append(link)
107
+ return links
108
+
109
+
110
+ @dataclass
111
+ class Text:
112
+ text: str
113
+ line: int
114
+
115
+ @cached_property
116
+ def parts(self):
117
+ if self.text.count("`") % 2 == 1:
118
+ raise ValueError(f"[Line {self.line}] Unclosed backtick in block")
119
+ parts = [""]
120
+ in_code = False
121
+ in_link = False
122
+ escaped = False
123
+ for char in self.text:
124
+ if escaped:
125
+ parts[-1] += char
126
+ elif not in_link and char == "`":
127
+ if in_code:
128
+ parts[-1] += char
129
+ in_code = False
130
+ parts.append("")
131
+ else:
132
+ parts.append(char)
133
+ in_code = True
134
+ elif not in_code and char == "[":
135
+ if in_link:
136
+ parts[-1] += char
137
+ else:
138
+ parts.append(char)
139
+ in_link = True
140
+ elif char == "]":
141
+ parts[-1] += char
142
+ if in_link and parts[-1].count("]") == 2:
143
+ in_link = False
144
+ parts.append("")
145
+ elif char == ":" and in_link and parts[-1].endswith("]"):
146
+ parts[-1] += char
147
+ in_link = False
148
+ elif char == ")":
149
+ parts[-1] += char
150
+ if (in_link
151
+ and parts[-1].count("[") - parts[-1].count("]") == 0
152
+ and parts[-1].count("(") - parts[-1].count(")") == 0):
153
+ in_link = False
154
+ parts.append("")
155
+ elif not in_code and char == "\\":
156
+ parts[-1] += char
157
+ escaped = True
158
+ elif char == "\n" and not in_code and not in_link:
159
+ parts[-1] += char
160
+ parts.append("")
161
+ else:
162
+ parts[-1] += char
163
+ if parts[-1] == "":
164
+ parts.pop()
165
+ return parts
166
+
167
+ @cached_property
168
+ def start_anchors(self):
169
+ links = []
170
+ for part in self.parts:
171
+ match = Link.open1_re.fullmatch(part)
172
+ if match:
173
+ links.append(Link(**match.groupdict()))
174
+ else:
175
+ match = Link.open2_re.fullmatch(part)
176
+ if match:
177
+ links.append(Link(**match.groupdict()))
178
+ return links
179
+
180
+ @cached_property
181
+ def end_anchors(self):
182
+ links = []
183
+ for match in Link.close_re.finditer(self.text):
184
+ links.append(Link(**match.groupdict()))
185
+ return links
186
+
187
+ @cached_property
188
+ def inline_links(self):
189
+ links = []
190
+ for part in self.parts:
191
+ match = Link.link_re.fullmatch(part)
192
+ if match:
193
+ links.append(Link(**match.groupdict()))
194
+ return links
195
+
196
+
197
+ @dataclass
198
+ class Comment:
199
+ text: str
200
+ line: int
201
+
202
+
203
+ @dataclass
204
+ class Code:
205
+ style: str
206
+ flags: str
207
+ text: str
208
+ line: int
209
+
210
+
211
+ def parse(markdown):
212
+ """Return information about each atomic block of code."""
213
+ lines = [0] + [
214
+ m.start() + 1
215
+ for m in re.finditer(r"\n", markdown)
216
+ ]
217
+ def find_line(index):
218
+ for n, line_start in enumerate(lines, start=1):
219
+ if index < line_start:
220
+ return n-1
221
+ raw_blocks = BLOCK_LINE_RE.finditer(markdown)
222
+ blocks = MarkdownBlocks()
223
+ in_code_block = False
224
+ code_block_text = ""
225
+ for n, match in enumerate(raw_blocks):
226
+ kind = match.lastgroup
227
+ line_number = find_line(match.start())
228
+ if in_code_block:
229
+ if kind == "code":
230
+ style = match.group("style")
231
+ flags = match.group("flags")
232
+ if style or flags:
233
+ raise ValueError(
234
+ f"[Line {line_number}] End code block "
235
+ f"with extras: {style} {flags}"
236
+ )
237
+ blocks[-1].text = code_block_text
238
+ in_code_block = False
239
+ else:
240
+ code_block_text += match.group()
241
+ elif kind == "code":
242
+ code_block_text = ""
243
+ blocks.append(Code(
244
+ match.group("style"),
245
+ match.group("flags"),
246
+ "",
247
+ line=line_number,
248
+ ))
249
+ in_code_block = True
250
+ elif kind == "comment":
251
+ blocks.append(
252
+ Comment(match.group("comment_text"), line=line_number)
253
+ )
254
+ else:
255
+ if blocks and isinstance(blocks[-1], Text):
256
+ blocks[-1].text += match.group("text")
257
+ else:
258
+ blocks.append(Text(match.group("text"), line=line_number))
259
+ return blocks
@@ -0,0 +1,63 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "pm-markdown"
7
+ description = ''
8
+ readme = "README.md"
9
+ requires-python = ">=3.7"
10
+ license = "MIT"
11
+ keywords = []
12
+ authors = [
13
+ { name = "Trey Hunner", email = "trey@pythonmorsels.com" },
14
+ ]
15
+ classifiers = [
16
+ "Development Status :: 4 - Beta",
17
+ "Programming Language :: Python",
18
+ "Programming Language :: Python :: 3.7",
19
+ "Programming Language :: Python :: 3.8",
20
+ "Programming Language :: Python :: 3.9",
21
+ "Programming Language :: Python :: 3.10",
22
+ "Programming Language :: Python :: Implementation :: CPython",
23
+ "Programming Language :: Python :: Implementation :: PyPy",
24
+ ]
25
+ dependencies = []
26
+ dynamic = ["version"]
27
+
28
+ [project.urls]
29
+ Documentation = "https://github.com/TruthfulTechnology/pm-markdown#readme"
30
+ Issues = "https://github.com/TruthfulTechnology/pm-markdown/issues"
31
+ Source = "https://github.com/TruthfulTechnology/pm-markdown"
32
+
33
+ [tool.hatch.version]
34
+ path = "pm/markdown/__about__.py"
35
+
36
+ [tool.hatch.build.targets.sdist]
37
+ [tool.hatch.build.targets.wheel]
38
+
39
+ [tool.hatch.envs.default]
40
+ dependencies = [
41
+ "pytest",
42
+ "pytest-cov",
43
+ ]
44
+ [tool.hatch.envs.default.scripts]
45
+ cov = "pytest --cov-report=term-missing --cov-config=pyproject.toml --cov=pm/markdown --cov=tests"
46
+ no-cov = "cov --no-cov"
47
+
48
+ [[tool.hatch.envs.test.matrix]]
49
+ python = ["37", "38", "39", "310"]
50
+
51
+ [tool.coverage.run]
52
+ branch = true
53
+ parallel = true
54
+ omit = [
55
+ "pm/markdown/__about__.py",
56
+ ]
57
+
58
+ [tool.coverage.report]
59
+ exclude_lines = [
60
+ "no cov",
61
+ "if __name__ == .__main__.:",
62
+ "if TYPE_CHECKING:",
63
+ ]
@@ -0,0 +1,3 @@
1
+ # SPDX-FileCopyrightText: 2022-present Trey Hunner <trey@treyhunner.com>
2
+ #
3
+ # SPDX-License-Identifier: MIT