md2linkedin 0.2.2__tar.gz → 0.2.3__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.
- {md2linkedin-0.2.2 → md2linkedin-0.2.3}/PKG-INFO +10 -2
- {md2linkedin-0.2.2 → md2linkedin-0.2.3}/README.md +8 -0
- md2linkedin-0.2.3/pyproject.toml +139 -0
- md2linkedin-0.2.2/pyproject.toml → md2linkedin-0.2.3/pyproject.toml.orig +25 -27
- {md2linkedin-0.2.2 → md2linkedin-0.2.3}/src/md2linkedin/_cli.py +10 -2
- {md2linkedin-0.2.2 → md2linkedin-0.2.3}/src/md2linkedin/_converter.py +28 -7
- {md2linkedin-0.2.2 → md2linkedin-0.2.3}/src/md2linkedin/_unicode.py +5 -0
- {md2linkedin-0.2.2 → md2linkedin-0.2.3}/LICENSE.md +0 -0
- {md2linkedin-0.2.2 → md2linkedin-0.2.3}/src/md2linkedin/__init__.py +0 -0
- {md2linkedin-0.2.2 → md2linkedin-0.2.3}/src/md2linkedin/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: md2linkedin
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.3
|
|
4
4
|
Summary: Convert Markdown to LinkedIn-friendly Unicode text
|
|
5
5
|
Keywords: markdown,linkedin,unicode,formatting,conversion
|
|
6
6
|
Author: Indrajeet Patil
|
|
@@ -21,7 +21,7 @@ Classifier: Programming Language :: Python :: 3 :: Only
|
|
|
21
21
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
22
22
|
Classifier: Topic :: Text Processing :: Markup
|
|
23
23
|
Classifier: Topic :: Utilities
|
|
24
|
-
Requires-Dist: click>=8.
|
|
24
|
+
Requires-Dist: click>=8.4.0
|
|
25
25
|
Requires-Python: >=3.10
|
|
26
26
|
Project-URL: Homepage, https://github.com/IndrajeetPatil/md2linkedin
|
|
27
27
|
Project-URL: Documentation, https://www.indrapatil.com/md2linkedin/
|
|
@@ -53,6 +53,9 @@ natively.
|
|
|
53
53
|
| pip | `pip install md2linkedin` |
|
|
54
54
|
| uv | `uv add md2linkedin` |
|
|
55
55
|
|
|
56
|
+
> **Tip:** Run the CLI without installing it with
|
|
57
|
+
> `uvx md2linkedin post.md`.
|
|
58
|
+
|
|
56
59
|
## Usage
|
|
57
60
|
|
|
58
61
|
### Python API
|
|
@@ -85,6 +88,7 @@ print(convert(md))
|
|
|
85
88
|
I'm thrilled to share that 𝘄𝗲 𝗷𝘂𝘀𝘁 𝗹𝗮𝘂𝗻𝗰𝗵𝗲𝗱 a new product!
|
|
86
89
|
|
|
87
90
|
Key highlights:
|
|
91
|
+
|
|
88
92
|
• 𝗣𝗲𝗿𝗳𝗼𝗿𝗺𝗮𝗻𝗰𝗲: 3𝘹 𝘧𝘢𝘴𝘵𝘦𝘳 than the previous version
|
|
89
93
|
• 𝗥𝗲𝗹𝗶𝗮𝗯𝗶𝗹𝗶𝘁𝘆: 𝙯𝙚𝙧𝙤 𝙙𝙤𝙬𝙣𝙩𝙞𝙢𝙚 deployments
|
|
90
94
|
• 𝗗𝗲𝘃𝗲𝗹𝗼𝗽𝗲𝗿 𝗨𝗫: clean, intuitive API
|
|
@@ -158,6 +162,10 @@ notable limitations:
|
|
|
158
162
|
For more examples, check out the package documentation at:
|
|
159
163
|
<https://www.indrapatil.com/md2linkedin/>
|
|
160
164
|
|
|
165
|
+
## See Also
|
|
166
|
+
|
|
167
|
+
- [md2linkedin Web App](https://019d695e-8448-ee38-5db3-7ca4acf2ce2c.share.connect.posit.cloud/) — an interactive web interface for `md2linkedin`, built by [Yann Cohen](https://github.com/iamYannC).
|
|
168
|
+
|
|
161
169
|
## License
|
|
162
170
|
|
|
163
171
|
This project is licensed under the MIT License.
|
|
@@ -21,6 +21,9 @@ natively.
|
|
|
21
21
|
| pip | `pip install md2linkedin` |
|
|
22
22
|
| uv | `uv add md2linkedin` |
|
|
23
23
|
|
|
24
|
+
> **Tip:** Run the CLI without installing it with
|
|
25
|
+
> `uvx md2linkedin post.md`.
|
|
26
|
+
|
|
24
27
|
## Usage
|
|
25
28
|
|
|
26
29
|
### Python API
|
|
@@ -53,6 +56,7 @@ print(convert(md))
|
|
|
53
56
|
I'm thrilled to share that 𝘄𝗲 𝗷𝘂𝘀𝘁 𝗹𝗮𝘂𝗻𝗰𝗵𝗲𝗱 a new product!
|
|
54
57
|
|
|
55
58
|
Key highlights:
|
|
59
|
+
|
|
56
60
|
• 𝗣𝗲𝗿𝗳𝗼𝗿𝗺𝗮𝗻𝗰𝗲: 3𝘹 𝘧𝘢𝘴𝘵𝘦𝘳 than the previous version
|
|
57
61
|
• 𝗥𝗲𝗹𝗶𝗮𝗯𝗶𝗹𝗶𝘁𝘆: 𝙯𝙚𝙧𝙤 𝙙𝙤𝙬𝙣𝙩𝙞𝙢𝙚 deployments
|
|
58
62
|
• 𝗗𝗲𝘃𝗲𝗹𝗼𝗽𝗲𝗿 𝗨𝗫: clean, intuitive API
|
|
@@ -126,6 +130,10 @@ notable limitations:
|
|
|
126
130
|
For more examples, check out the package documentation at:
|
|
127
131
|
<https://www.indrapatil.com/md2linkedin/>
|
|
128
132
|
|
|
133
|
+
## See Also
|
|
134
|
+
|
|
135
|
+
- [md2linkedin Web App](https://019d695e-8448-ee38-5db3-7ca4acf2ce2c.share.connect.posit.cloud/) — an interactive web interface for `md2linkedin`, built by [Yann Cohen](https://github.com/iamYannC).
|
|
136
|
+
|
|
129
137
|
## License
|
|
130
138
|
|
|
131
139
|
This project is licensed under the MIT License.
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "md2linkedin"
|
|
3
|
+
version = "0.2.3"
|
|
4
|
+
description = "Convert Markdown to LinkedIn-friendly Unicode text"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.10"
|
|
7
|
+
license = "MIT"
|
|
8
|
+
license-files = ["LICENSE.md"]
|
|
9
|
+
keywords = [
|
|
10
|
+
"markdown",
|
|
11
|
+
"linkedin",
|
|
12
|
+
"unicode",
|
|
13
|
+
"formatting",
|
|
14
|
+
"conversion",
|
|
15
|
+
]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 3 - Alpha",
|
|
18
|
+
"Intended Audience :: Developers",
|
|
19
|
+
"Intended Audience :: End Users/Desktop",
|
|
20
|
+
"Operating System :: OS Independent",
|
|
21
|
+
"Programming Language :: Python",
|
|
22
|
+
"Programming Language :: Python :: 3.10",
|
|
23
|
+
"Programming Language :: Python :: 3.11",
|
|
24
|
+
"Programming Language :: Python :: 3.12",
|
|
25
|
+
"Programming Language :: Python :: 3.13",
|
|
26
|
+
"Programming Language :: Python :: 3.14",
|
|
27
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
28
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
29
|
+
"Topic :: Text Processing :: Markup",
|
|
30
|
+
"Topic :: Utilities",
|
|
31
|
+
]
|
|
32
|
+
dependencies = ["click>=8.4.0"]
|
|
33
|
+
|
|
34
|
+
[[project.authors]]
|
|
35
|
+
name = "Indrajeet Patil"
|
|
36
|
+
email = "patilindrajeet.science@gmail.com"
|
|
37
|
+
|
|
38
|
+
[project.scripts]
|
|
39
|
+
md2linkedin = "md2linkedin._cli:main"
|
|
40
|
+
|
|
41
|
+
[project.urls]
|
|
42
|
+
Homepage = "https://github.com/IndrajeetPatil/md2linkedin"
|
|
43
|
+
Documentation = "https://www.indrapatil.com/md2linkedin/"
|
|
44
|
+
Repository = "https://github.com/IndrajeetPatil/md2linkedin"
|
|
45
|
+
Issues = "https://github.com/IndrajeetPatil/md2linkedin/issues"
|
|
46
|
+
Changelog = "https://github.com/IndrajeetPatil/md2linkedin/blob/main/CHANGELOG.md"
|
|
47
|
+
|
|
48
|
+
[dependency-groups]
|
|
49
|
+
dev = [
|
|
50
|
+
"coverage>=7.14.0",
|
|
51
|
+
"jupyter>=1.1.1",
|
|
52
|
+
"zensical>=0.0.43",
|
|
53
|
+
"mkdocstrings-python>=2.0.3",
|
|
54
|
+
"ty>=0.0.47",
|
|
55
|
+
"prek>=0.4.1",
|
|
56
|
+
"pytest>=9.0.3",
|
|
57
|
+
"pytest-cov>=7.1.0",
|
|
58
|
+
"pytest-random-order>=1.2.0",
|
|
59
|
+
"ruff>=0.15.13",
|
|
60
|
+
"add-trailing-comma>=4.0.0",
|
|
61
|
+
]
|
|
62
|
+
|
|
63
|
+
[build-system]
|
|
64
|
+
requires = ["uv_build>=0.12.0,<0.13"]
|
|
65
|
+
build-backend = "uv_build"
|
|
66
|
+
|
|
67
|
+
[tool.ruff]
|
|
68
|
+
fix = true
|
|
69
|
+
preview = true
|
|
70
|
+
unsafe-fixes = true
|
|
71
|
+
|
|
72
|
+
[tool.ruff.lint]
|
|
73
|
+
select = ["ALL"]
|
|
74
|
+
ignore = [
|
|
75
|
+
"missing-trailing-comma",
|
|
76
|
+
"incorrect-blank-line-before-class",
|
|
77
|
+
"multi-line-summary-second-line",
|
|
78
|
+
"CPY",
|
|
79
|
+
"ambiguous-unicode-character-docstring",
|
|
80
|
+
"ambiguous-unicode-character-comment",
|
|
81
|
+
]
|
|
82
|
+
|
|
83
|
+
[tool.ruff.lint.per-file-ignores]
|
|
84
|
+
"tests/*" = [
|
|
85
|
+
"D",
|
|
86
|
+
"assert",
|
|
87
|
+
"import-private-name",
|
|
88
|
+
"no-self-use",
|
|
89
|
+
"ambiguous-unicode-character-string",
|
|
90
|
+
"magic-value-comparison",
|
|
91
|
+
"too-many-public-methods",
|
|
92
|
+
]
|
|
93
|
+
|
|
94
|
+
[tool.ruff.format]
|
|
95
|
+
preview = true
|
|
96
|
+
docstring-code-format = true
|
|
97
|
+
|
|
98
|
+
[tool.pytest.ini_options]
|
|
99
|
+
addopts = [
|
|
100
|
+
"--strict-config",
|
|
101
|
+
"--strict-markers",
|
|
102
|
+
"-ra",
|
|
103
|
+
"--verbose",
|
|
104
|
+
"--random-order",
|
|
105
|
+
]
|
|
106
|
+
testpaths = ["tests"]
|
|
107
|
+
filterwarnings = ["error"]
|
|
108
|
+
xfail_strict = true
|
|
109
|
+
python_files = [
|
|
110
|
+
"test_*.py",
|
|
111
|
+
"test-*.py",
|
|
112
|
+
"tests.py",
|
|
113
|
+
"test.py",
|
|
114
|
+
]
|
|
115
|
+
|
|
116
|
+
[tool.coverage.run]
|
|
117
|
+
branch = true
|
|
118
|
+
source = ["md2linkedin"]
|
|
119
|
+
|
|
120
|
+
[tool.coverage.report]
|
|
121
|
+
fail_under = 100
|
|
122
|
+
format = "markdown"
|
|
123
|
+
sort = "-Cover"
|
|
124
|
+
show_missing = true
|
|
125
|
+
skip_empty = true
|
|
126
|
+
|
|
127
|
+
[tool.ty.src]
|
|
128
|
+
exclude = [
|
|
129
|
+
".venv",
|
|
130
|
+
"build",
|
|
131
|
+
"dist",
|
|
132
|
+
"docs",
|
|
133
|
+
]
|
|
134
|
+
|
|
135
|
+
[tool.ty.environment]
|
|
136
|
+
python-version = "3.10"
|
|
137
|
+
|
|
138
|
+
[tool.uv]
|
|
139
|
+
required-version = ">=0.12.0"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "md2linkedin"
|
|
3
|
-
version = "0.2.
|
|
3
|
+
version = "0.2.3"
|
|
4
4
|
description = "Convert Markdown to LinkedIn-friendly Unicode text"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
authors = [
|
|
@@ -26,7 +26,9 @@ classifiers = [
|
|
|
26
26
|
"Topic :: Text Processing :: Markup",
|
|
27
27
|
"Topic :: Utilities",
|
|
28
28
|
]
|
|
29
|
-
dependencies = [
|
|
29
|
+
dependencies = [
|
|
30
|
+
"click>=8.4.0",
|
|
31
|
+
]
|
|
30
32
|
|
|
31
33
|
[project.scripts]
|
|
32
34
|
md2linkedin = "md2linkedin._cli:main"
|
|
@@ -40,21 +42,21 @@ Changelog = "https://github.com/IndrajeetPatil/md2linkedin/blob/main/CHANGELOG.m
|
|
|
40
42
|
|
|
41
43
|
[dependency-groups]
|
|
42
44
|
dev = [
|
|
43
|
-
"coverage>=7.
|
|
45
|
+
"coverage>=7.14.0",
|
|
44
46
|
"jupyter>=1.1.1",
|
|
45
|
-
"zensical>=0.0.
|
|
47
|
+
"zensical>=0.0.43",
|
|
46
48
|
"mkdocstrings-python>=2.0.3",
|
|
47
|
-
"ty>=0.0.
|
|
48
|
-
"prek>=0.
|
|
49
|
+
"ty>=0.0.47",
|
|
50
|
+
"prek>=0.4.1",
|
|
49
51
|
"pytest>=9.0.3",
|
|
50
52
|
"pytest-cov>=7.1.0",
|
|
51
53
|
"pytest-random-order>=1.2.0",
|
|
52
|
-
"ruff>=0.15.
|
|
54
|
+
"ruff>=0.15.13",
|
|
53
55
|
"add-trailing-comma>=4.0.0",
|
|
54
56
|
]
|
|
55
57
|
|
|
56
58
|
[build-system]
|
|
57
|
-
requires = ["uv_build>=0.
|
|
59
|
+
requires = ["uv_build>=0.12.0,<0.13"]
|
|
58
60
|
build-backend = "uv_build"
|
|
59
61
|
|
|
60
62
|
[tool.ruff]
|
|
@@ -65,39 +67,35 @@ unsafe-fixes = true
|
|
|
65
67
|
[tool.ruff.lint]
|
|
66
68
|
select = ["ALL"]
|
|
67
69
|
ignore = [
|
|
68
|
-
|
|
70
|
+
# Required for compatibility with Ruff's formatter.
|
|
71
|
+
"missing-trailing-comma",
|
|
72
|
+
# Mutually exclusive with D211 and D212, respectively.
|
|
73
|
+
"incorrect-blank-line-before-class",
|
|
74
|
+
"multi-line-summary-second-line",
|
|
75
|
+
# This project uses a repository-level LICENSE instead of per-file notices.
|
|
69
76
|
"CPY",
|
|
70
|
-
# Docstring content rules — we intentionally omit Returns/Raises sections
|
|
71
|
-
# in internal helpers, consistent with ignoring D (pydocstyle) above.
|
|
72
|
-
"D",
|
|
73
|
-
"DOC",
|
|
74
77
|
# This package's output IS Unicode Mathematical characters; having them in
|
|
75
78
|
# docstring examples and block-comment separators is intentional.
|
|
76
|
-
"
|
|
77
|
-
"
|
|
79
|
+
"ambiguous-unicode-character-docstring",
|
|
80
|
+
"ambiguous-unicode-character-comment",
|
|
78
81
|
]
|
|
79
82
|
|
|
80
83
|
[tool.ruff.lint.per-file-ignores]
|
|
81
84
|
"tests/*" = [
|
|
82
85
|
# docstring requirements
|
|
83
86
|
"D",
|
|
84
|
-
"DOC",
|
|
85
87
|
# tests can use asserts
|
|
86
|
-
"
|
|
87
|
-
# pytest fixtures (e.g. tmp_path) don't need type annotations
|
|
88
|
-
"ANN001",
|
|
88
|
+
"assert",
|
|
89
89
|
# tests legitimately import private internals to test them directly
|
|
90
|
-
"
|
|
90
|
+
"import-private-name",
|
|
91
91
|
# test methods inside classes don't need @staticmethod — class grouping is intentional
|
|
92
|
-
"
|
|
92
|
+
"no-self-use",
|
|
93
93
|
# test assertions for Unicode output use the literal Unicode chars (intentional)
|
|
94
|
-
"
|
|
95
|
-
# `== ""` vs `not x` — both forms are acceptable in assertions
|
|
96
|
-
"PLC1901",
|
|
94
|
+
"ambiguous-unicode-character-string",
|
|
97
95
|
# magic numbers in assertions are fine (e.g. expected count == 3)
|
|
98
|
-
"
|
|
96
|
+
"magic-value-comparison",
|
|
99
97
|
# large test classes are fine when grouping related cases
|
|
100
|
-
"
|
|
98
|
+
"too-many-public-methods",
|
|
101
99
|
]
|
|
102
100
|
|
|
103
101
|
[tool.ruff.format]
|
|
@@ -140,4 +138,4 @@ exclude = [".venv", "build", "dist", "docs"]
|
|
|
140
138
|
python-version = "3.10"
|
|
141
139
|
|
|
142
140
|
[tool.uv]
|
|
143
|
-
required-version = ">=0.
|
|
141
|
+
required-version = ">=0.12.0"
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Command-line interface for md2linkedin."""
|
|
1
|
+
"""Command-line interface for converting Markdown with md2linkedin."""
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
@@ -54,7 +54,7 @@ def main(
|
|
|
54
54
|
preserve_links: bool,
|
|
55
55
|
no_monospace_code: bool,
|
|
56
56
|
) -> None:
|
|
57
|
-
"""Convert Markdown to LinkedIn-friendly Unicode text.
|
|
57
|
+
r"""Convert Markdown to LinkedIn-friendly Unicode text.
|
|
58
58
|
|
|
59
59
|
Reads from INPUT_FILE (or stdin when INPUT_FILE is omitted) and writes
|
|
60
60
|
LinkedIn-compatible plain text in which bold and italic formatting is
|
|
@@ -80,6 +80,10 @@ def main(
|
|
|
80
80
|
|
|
81
81
|
# Disable monospace code rendering
|
|
82
82
|
md2linkedin README.md --no-monospace-code
|
|
83
|
+
|
|
84
|
+
Raises:
|
|
85
|
+
click.UsageError: If neither an input file nor piped stdin is provided.
|
|
86
|
+
|
|
83
87
|
"""
|
|
84
88
|
monospace_code = not no_monospace_code
|
|
85
89
|
if input_file is not None:
|
|
@@ -112,5 +116,9 @@ def _stdin_is_tty() -> bool:
|
|
|
112
116
|
|
|
113
117
|
Extracted into its own function so tests can mock it cleanly without
|
|
114
118
|
fighting Click's own stdin-swapping inside ``CliRunner.invoke``.
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
Whether stdin is connected to an interactive terminal.
|
|
122
|
+
|
|
115
123
|
"""
|
|
116
124
|
return sys.stdin.isatty()
|
|
@@ -21,7 +21,12 @@ _NESTED_BULLET_MIN_INDENT = 2 # spaces of indentation that triggers a nested bu
|
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
def _normalize_line_endings(text: str) -> str:
|
|
24
|
-
"""Normalize Windows (\\r\\n) and classic Mac (\\r) line endings to \\n.
|
|
24
|
+
r"""Normalize Windows (\\r\\n) and classic Mac (\\r) line endings to \\n.
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
Text containing only Unix-style line endings.
|
|
28
|
+
|
|
29
|
+
"""
|
|
25
30
|
return text.replace("\r\n", "\n").replace("\r", "\n")
|
|
26
31
|
|
|
27
32
|
|
|
@@ -37,6 +42,7 @@ def _protect_code(text: str) -> tuple[str, dict[str, str]]:
|
|
|
37
42
|
Returns:
|
|
38
43
|
A ``(modified_text, placeholder_map)`` tuple where *placeholder_map*
|
|
39
44
|
maps each placeholder back to its original code string.
|
|
45
|
+
|
|
40
46
|
"""
|
|
41
47
|
placeholders: dict[str, str] = {}
|
|
42
48
|
|
|
@@ -76,6 +82,7 @@ def _restore_code(
|
|
|
76
82
|
|
|
77
83
|
Returns:
|
|
78
84
|
Text with all placeholders replaced by their original code content.
|
|
85
|
+
|
|
79
86
|
"""
|
|
80
87
|
for key, original in placeholders.items():
|
|
81
88
|
if original.startswith(("```", "~~~")):
|
|
@@ -112,6 +119,7 @@ def _strip_html_spans(text: str) -> str:
|
|
|
112
119
|
|
|
113
120
|
Returns:
|
|
114
121
|
Text with all span elements removed and their inner content preserved.
|
|
122
|
+
|
|
115
123
|
"""
|
|
116
124
|
prev = None
|
|
117
125
|
while prev != text:
|
|
@@ -121,7 +129,7 @@ def _strip_html_spans(text: str) -> str:
|
|
|
121
129
|
|
|
122
130
|
|
|
123
131
|
def _convert_bold_italic(text: str) -> str:
|
|
124
|
-
"""Replace ``***text***`` (or ``___text___``) with bold-italic Unicode.
|
|
132
|
+
r"""Replace ``***text***`` (or ``___text___``) with bold-italic Unicode.
|
|
125
133
|
|
|
126
134
|
Must run before :func:`_convert_bold` and :func:`_convert_italic` to
|
|
127
135
|
prevent the triple markers from being consumed piecemeal.
|
|
@@ -133,6 +141,7 @@ def _convert_bold_italic(text: str) -> str:
|
|
|
133
141
|
|
|
134
142
|
Returns:
|
|
135
143
|
Text with bold-italic markers replaced.
|
|
144
|
+
|
|
136
145
|
"""
|
|
137
146
|
text = re.sub(
|
|
138
147
|
r"(?<!\\)\*{3}(.+?)(?<!\\)\*{3}",
|
|
@@ -147,7 +156,7 @@ def _convert_bold_italic(text: str) -> str:
|
|
|
147
156
|
|
|
148
157
|
|
|
149
158
|
def _convert_bold(text: str) -> str:
|
|
150
|
-
"""Replace ``**text**`` (or ``__text__``) with bold Unicode.
|
|
159
|
+
r"""Replace ``**text**`` (or ``__text__``) with bold Unicode.
|
|
151
160
|
|
|
152
161
|
Backslash-escaped markers (``\\**``) are not matched.
|
|
153
162
|
|
|
@@ -156,6 +165,7 @@ def _convert_bold(text: str) -> str:
|
|
|
156
165
|
|
|
157
166
|
Returns:
|
|
158
167
|
Text with bold markers replaced.
|
|
168
|
+
|
|
159
169
|
"""
|
|
160
170
|
text = re.sub(
|
|
161
171
|
r"(?<!\\)\*{2}(.+?)(?<!\\)\*{2}",
|
|
@@ -170,7 +180,7 @@ def _convert_bold(text: str) -> str:
|
|
|
170
180
|
|
|
171
181
|
|
|
172
182
|
def _convert_italic(text: str) -> str:
|
|
173
|
-
"""Replace ``*text*`` or ``_text_`` with italic Unicode.
|
|
183
|
+
r"""Replace ``*text*`` or ``_text_`` with italic Unicode.
|
|
174
184
|
|
|
175
185
|
Uses negative look-around to avoid matching asterisks that are part of
|
|
176
186
|
bold (``**``) or bold-italic (``***``) markers already consumed by
|
|
@@ -182,6 +192,7 @@ def _convert_italic(text: str) -> str:
|
|
|
182
192
|
|
|
183
193
|
Returns:
|
|
184
194
|
Text with italic markers replaced.
|
|
195
|
+
|
|
185
196
|
"""
|
|
186
197
|
# *text* — negative look-around prevents matching residual ** markers or \* escapes
|
|
187
198
|
text = re.sub(
|
|
@@ -208,6 +219,7 @@ def _convert_headers(text: str) -> str:
|
|
|
208
219
|
|
|
209
220
|
Returns:
|
|
210
221
|
Text with headers replaced by styled plain text.
|
|
222
|
+
|
|
211
223
|
"""
|
|
212
224
|
separator = "━" * 40
|
|
213
225
|
|
|
@@ -269,6 +281,7 @@ def _strip_links(text: str, *, preserve: bool = False) -> str:
|
|
|
269
281
|
|
|
270
282
|
Returns:
|
|
271
283
|
Text with links handled according to *preserve*.
|
|
284
|
+
|
|
272
285
|
"""
|
|
273
286
|
if preserve:
|
|
274
287
|
return text
|
|
@@ -290,6 +303,7 @@ def _strip_images(text: str) -> str:
|
|
|
290
303
|
|
|
291
304
|
Returns:
|
|
292
305
|
Text with image syntax replaced by alt text.
|
|
306
|
+
|
|
293
307
|
"""
|
|
294
308
|
#  → alt (empty alt → removed)
|
|
295
309
|
return re.sub(
|
|
@@ -311,6 +325,7 @@ def _convert_bullets(text: str) -> str:
|
|
|
311
325
|
|
|
312
326
|
Returns:
|
|
313
327
|
Text with list markers replaced.
|
|
328
|
+
|
|
314
329
|
"""
|
|
315
330
|
|
|
316
331
|
def _bullet(m: re.Match[str]) -> str:
|
|
@@ -328,6 +343,7 @@ def _strip_blockquotes(text: str) -> str:
|
|
|
328
343
|
|
|
329
344
|
Returns:
|
|
330
345
|
Text with blockquote markers stripped from line beginnings.
|
|
346
|
+
|
|
331
347
|
"""
|
|
332
348
|
return re.sub(r"^> ?", "", text, flags=re.MULTILINE)
|
|
333
349
|
|
|
@@ -341,6 +357,7 @@ def _clean_entities(text: str) -> str:
|
|
|
341
357
|
Returns:
|
|
342
358
|
Text with ``>``, ``<``, ``&``, `` ``, ``"``
|
|
343
359
|
replaced by their literal equivalents.
|
|
360
|
+
|
|
344
361
|
"""
|
|
345
362
|
replacements = {
|
|
346
363
|
">": ">",
|
|
@@ -356,13 +373,14 @@ def _clean_entities(text: str) -> str:
|
|
|
356
373
|
|
|
357
374
|
|
|
358
375
|
def _clean_escaped_chars(text: str) -> str:
|
|
359
|
-
"""Remove Markdown backslash escapes (e.g. ``\\*`` → ``*``).
|
|
376
|
+
r"""Remove Markdown backslash escapes (e.g. ``\\*`` → ``*``).
|
|
360
377
|
|
|
361
378
|
Args:
|
|
362
379
|
text: Input text.
|
|
363
380
|
|
|
364
381
|
Returns:
|
|
365
382
|
Text with backslash escapes resolved.
|
|
383
|
+
|
|
366
384
|
"""
|
|
367
385
|
return re.sub(r"\\([\\`*_{}\[\]()#+\-.!])", r"\1", text)
|
|
368
386
|
|
|
@@ -378,6 +396,7 @@ def _normalize_whitespace(text: str) -> str:
|
|
|
378
396
|
|
|
379
397
|
Returns:
|
|
380
398
|
Normalized text with a single trailing newline.
|
|
399
|
+
|
|
381
400
|
"""
|
|
382
401
|
text = re.sub(r"\n{3,}", "\n\n", text)
|
|
383
402
|
return text.strip() + "\n"
|
|
@@ -392,7 +411,7 @@ def convert(
|
|
|
392
411
|
preserve_links: bool = False,
|
|
393
412
|
monospace_code: bool = True,
|
|
394
413
|
) -> str:
|
|
395
|
-
"""Convert Markdown text to LinkedIn-compatible Unicode plain text.
|
|
414
|
+
r"""Convert Markdown text to LinkedIn-compatible Unicode plain text.
|
|
396
415
|
|
|
397
416
|
Bold (``**text**`` / ``__text__``), italic (``*text*`` / ``_text_``), and
|
|
398
417
|
bold-italic (``***text***`` / ``___text___``) markers are replaced with
|
|
@@ -433,6 +452,7 @@ def convert(
|
|
|
433
452
|
|
|
434
453
|
>>> convert("")
|
|
435
454
|
''
|
|
455
|
+
|
|
436
456
|
"""
|
|
437
457
|
if not text or not text.strip():
|
|
438
458
|
return ""
|
|
@@ -461,7 +481,7 @@ def convert_file(
|
|
|
461
481
|
preserve_links: bool = False,
|
|
462
482
|
monospace_code: bool = True,
|
|
463
483
|
) -> Path:
|
|
464
|
-
"""Convert a Markdown file and write the result to a ``.txt`` file.
|
|
484
|
+
r"""Convert a Markdown file and write the result to a ``.txt`` file.
|
|
465
485
|
|
|
466
486
|
Args:
|
|
467
487
|
input_path: Path to the Markdown source file (``.md`` or any text
|
|
@@ -489,6 +509,7 @@ def convert_file(
|
|
|
489
509
|
'𝗯𝗼𝗹𝗱 and 𝘪𝘵𝘢𝘭𝘪𝘤\\n'
|
|
490
510
|
>>> os.unlink(tmp)
|
|
491
511
|
... os.unlink(str(out))
|
|
512
|
+
|
|
492
513
|
"""
|
|
493
514
|
input_path = Path(input_path)
|
|
494
515
|
if not input_path.exists():
|
|
@@ -61,6 +61,7 @@ def to_sans_bold(text: str) -> str:
|
|
|
61
61
|
|
|
62
62
|
>>> to_sans_bold("")
|
|
63
63
|
''
|
|
64
|
+
|
|
64
65
|
"""
|
|
65
66
|
out: list[str] = []
|
|
66
67
|
for c in text:
|
|
@@ -98,6 +99,7 @@ def to_sans_italic(text: str) -> str:
|
|
|
98
99
|
|
|
99
100
|
>>> to_sans_italic("")
|
|
100
101
|
''
|
|
102
|
+
|
|
101
103
|
"""
|
|
102
104
|
out: list[str] = []
|
|
103
105
|
for c in text:
|
|
@@ -130,6 +132,7 @@ def to_sans_bold_italic(text: str) -> str:
|
|
|
130
132
|
|
|
131
133
|
>>> to_sans_bold_italic("")
|
|
132
134
|
''
|
|
135
|
+
|
|
133
136
|
"""
|
|
134
137
|
out: list[str] = []
|
|
135
138
|
for c in text:
|
|
@@ -165,6 +168,7 @@ def to_monospace(text: str) -> str:
|
|
|
165
168
|
|
|
166
169
|
>>> to_monospace("")
|
|
167
170
|
''
|
|
171
|
+
|
|
168
172
|
"""
|
|
169
173
|
out: list[str] = []
|
|
170
174
|
for c in text:
|
|
@@ -205,6 +209,7 @@ def apply_style(text: str, style: Literal["bold", "italic", "bold_italic"]) -> s
|
|
|
205
209
|
|
|
206
210
|
>>> apply_style("hello", "bold_italic")
|
|
207
211
|
'𝙝𝙚𝙡𝙡𝙤'
|
|
212
|
+
|
|
208
213
|
"""
|
|
209
214
|
if style == "bold":
|
|
210
215
|
return to_sans_bold(text)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|