omni-article-markdown 0.1.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,13 @@
1
+ .DS_STORE
2
+
3
+ # Python-generated files
4
+ __pycache__/
5
+ *.py[oc]
6
+ build/
7
+ dist/
8
+ wheels/
9
+ *.egg-info
10
+
11
+ # Virtual environments
12
+ .venv
13
+ .env
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 caol64
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,244 @@
1
+ Metadata-Version: 2.4
2
+ Name: omni-article-markdown
3
+ Version: 0.1.0
4
+ Summary: Easily convert web articles (blogs, news, documents, etc.) into Markdown format.
5
+ Project-URL: Homepage, https://github.com/caol64/omni-article-markdown
6
+ Author-email: Lei <caol64@gmail.com>
7
+ License-Expression: MIT
8
+ License-File: LICENSE
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Environment :: Console
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Intended Audience :: End Users/Desktop
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Natural Language :: English
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Topic :: Text Processing :: Markup :: Markdown
19
+ Classifier: Topic :: Utilities
20
+ Requires-Python: >=3.13
21
+ Requires-Dist: beautifulsoup4>=4.13.4
22
+ Requires-Dist: click-default-group>=1.2.4
23
+ Requires-Dist: click>=8.2.0
24
+ Requires-Dist: html5lib>=1.1
25
+ Requires-Dist: pip
26
+ Requires-Dist: pluggy>=1.6.0
27
+ Requires-Dist: requests>=2.32.3
28
+ Description-Content-Type: text/markdown
29
+
30
+ # 墨探 (omni-article-markdown)
31
+
32
+ 轻松将网页文章(博客、新闻、文档等)转换为 `Markdown` 格式。
33
+
34
+ ![](data/1.gif)
35
+
36
+ ---
37
+
38
+ ## Recent changes:
39
+
40
+ - 添加插件机制,复杂的页面可以通过插件进行扩展
41
+ - 添加对github gist的支持
42
+ - 示例:https://towardsdatascience.com/hands-on-multi-agent-llm-restaurant-simulation-with-python-and-openai
43
+ - 添加对katex公式的支持
44
+ - 示例:https://quantum.country/qcvc
45
+
46
+ ---
47
+
48
+ ## 1. 功能介绍
49
+ 墨探的开发初衷,是为了解决一个问题:如何将来自互联网上各种不同网站的文章内容,精准且高效地转换成统一的Markdown格式。
50
+
51
+ 众所周知,万维网上的网站设计风格迥异,其HTML结构也呈现出千差万别的特点。这种多样性给自动化内容提取和格式转换带来了巨大的困难。要实现一个能够适应各种复杂HTML结构的通用解决方案,并非易事。
52
+
53
+ 我的想法是:从特定的网站开始适配,以点到面,逐步抽取出通用的解决方案,最后尽可能多的覆盖更多网站。以下是一些我经常访问的的网站,支持的都不错:
54
+
55
+ - 掘金
56
+ - CSDN
57
+ - Medium
58
+ - Freedium
59
+ - 公众号
60
+ - 简书
61
+ - 知乎专栏
62
+ - 今日头条
63
+ - towardsdatascience
64
+ - quantamagazine
65
+
66
+ 只需要一条命令:
67
+
68
+ ```sh
69
+ mdcli https://example.com
70
+ ```
71
+
72
+ 你还可以将网页手动保存为 HTML 文件,再使用本工具解析,也是支持的。
73
+
74
+ ```sh
75
+ mdcli your/path/example.html
76
+ ```
77
+
78
+ ---
79
+
80
+ ## 2. 安装与运行
81
+
82
+ 首先,安装必要依赖项:
83
+ ```sh
84
+ uv pip install .
85
+ ```
86
+
87
+ 然后,可以使用以下两种形式运行:
88
+ ```sh
89
+ uv run mdcli
90
+ ```
91
+
92
+ 或者:
93
+
94
+ ```sh
95
+ python -m omni_article_markdown.cli
96
+ ```
97
+
98
+ ---
99
+
100
+ ## 3. 参数说明
101
+
102
+ 运行命令及参数如下:
103
+
104
+ ```sh
105
+ mdcli <URL_OR_PATH> [-s [SAVE_PATH]]
106
+ ```
107
+
108
+ | 参数 | 说明 |
109
+ |--------------------|------|
110
+ | `URL_OR_PATH` | **必填**,目标网页 URL 或本地 HTML 文件路径。 |
111
+ | `-s, --save` | **可选**,启用保存:<br> - 仅 `-s`:默认保存至 `./`。<br> - `-s <SAVE_PATH>`:保存至指定路径。 |
112
+
113
+ ---
114
+
115
+ ## 4. 使用示例
116
+
117
+ ### 4.1 仅转换
118
+ ```sh
119
+ mdcli https://example.com
120
+ ```
121
+
122
+ 内容会输出值`stdout`。
123
+
124
+ ### 4.2 转换并保存到当前目录
125
+
126
+ ```sh
127
+ mdcli https://example.com -s
128
+ ```
129
+
130
+ 会在当前目录生成一个`<title>.md`的文件。
131
+
132
+ ### 4.3 转换并保存到指定路径
133
+
134
+ ```sh
135
+ mdcli https://example.com -s /home/user/
136
+ ```
137
+
138
+ 会在`/home/user/`目录生成一个`<title>.md`的文件。
139
+
140
+ ```sh
141
+ mdcli https://example.com -s /home/user/data.md
142
+ ```
143
+
144
+ 会生成一个`/home/user/data.md`的文件。
145
+
146
+ ---
147
+
148
+ ## 5. 安装和使用插件
149
+
150
+ 墨探支持通过插件机制来扩展自身的能力。这使得社区可以贡献针对不同网站的定制化解析器,而无需修改核心代码。
151
+
152
+ ### 5.1 什么是插件?
153
+
154
+ 插件是独立的 Python 包,它们实现了墨探定义的特定接口(钩子),从而能够被主程序发现和调用。例如,一个插件可以提供一个专门用于解析知乎专栏文章的 `Reader`。插件往往需要安装额外的依赖,因此不包含在主程序中,有需要的用户可自行安装。
155
+
156
+ ### 5.2 如何安装插件?
157
+
158
+ 你可以使用 `mdcli` 提供的 `install` 命令来安装插件。插件通常会发布到 PyPI(Python Package Index)。
159
+
160
+ **安装命令格式:**
161
+
162
+ ```sh
163
+ mdcli install <PLUGIN_NAME_OR_PACKAGE_NAME> [-U] [-e]
164
+ ```
165
+
166
+ | 参数 | 说明 |
167
+ |----------------------------------|----------------------------------------------------------------------|
168
+ | `<PLUGIN_NAME_OR_PACKAGE_NAME>` | **必填**,插件的名称或其在 PyPI 上的完整包名。 |
169
+ | `-U, --upgrade` | **可选**,如果插件已安装,则尝试升级到最新版本。 |
170
+ | `-e, --editable` | **可选**,基于提供的本地文件路径安装可编辑的包。 |
171
+
172
+ **示例:安装知乎解析插件**
173
+
174
+ ```sh
175
+ mdcli install zhihu
176
+ ```
177
+
178
+ 或者,你可以使用 `-e` 参数安装本地可编辑的包。
179
+
180
+ ```sh
181
+ mdcli install -e "./plugins/omnimd-zhihu-reader"
182
+ ```
183
+
184
+ 安装成功后,当下一次运行 `mdcli` 解析知乎链接时,如果该插件正确实现了接口,它将被自动用于处理该链接。
185
+
186
+ ### 5.3 如何卸载插件?
187
+
188
+ 如果你想移除一个已安装的插件,可以使用 `mdcli` 提供的 `uninstall` 命令。
189
+
190
+ **卸载命令格式:**
191
+
192
+ ```sh
193
+ mdcli uninstall <PLUGIN_NAME_OR_PACKAGE_NAME> [-y]
194
+ ```
195
+
196
+ | 参数 | 说明 |
197
+ |----------------------------------|----------------------------------------------------------|
198
+ | `<PLUGIN_NAME_OR_PACKAGE_NAME>` | **必填**,要卸载的插件的名称或其在 PyPI 上的完整包名。 |
199
+ | `-y, --yes` | **可选**,在卸载前不进行确认提示。 |
200
+
201
+ **示例:卸载知乎解析插件**
202
+
203
+ ```sh
204
+ mdcli uninstall zhihu
205
+ ```
206
+
207
+ 或者,使用插件的全称删除
208
+
209
+ ```sh
210
+ mdcli uninstall omnimd-zhihu-reader
211
+ ```
212
+
213
+ 卸载后,该插件的功能将不再可用。对于之前由该插件处理的 URL,`mdcli` 将会回退到其默认的解析逻辑。
214
+
215
+ ### 5.4 已支持的插件
216
+
217
+ 目前已发布3个插件,你可以按需安装:
218
+
219
+ | 命令 | 说明 |
220
+ |----------------------------------|----------------------------------------------------------|
221
+ | `mdcli install zhihu` | 知乎专栏 |
222
+ | `mdcli install toutiao` | 今日头条 |
223
+ | `mdcli install freedium` | Freedium |
224
+
225
+ ### 5.5 开发自己的插件
226
+
227
+ ---
228
+
229
+ ## 6. 贡献与反馈
230
+ - 发现解析问题?欢迎提交 [Issue](https://github.com/caol64/omni-article-markdown/issues)
231
+ - 改进解析?欢迎贡献 [Pull Request](https://github.com/caol64/omni-article-markdown/pulls)
232
+ - 开发插件?文档正在筹备中
233
+
234
+ ---
235
+
236
+ ## 7. 赞助
237
+
238
+ 如果您觉得不错,可以给我家猫咪买点罐头吃。[喂猫❤️](https://yuzhi.tech/sponsor)
239
+
240
+ ---
241
+
242
+ ## 8. License
243
+
244
+ MIT License
@@ -0,0 +1,215 @@
1
+ # 墨探 (omni-article-markdown)
2
+
3
+ 轻松将网页文章(博客、新闻、文档等)转换为 `Markdown` 格式。
4
+
5
+ ![](data/1.gif)
6
+
7
+ ---
8
+
9
+ ## Recent changes:
10
+
11
+ - 添加插件机制,复杂的页面可以通过插件进行扩展
12
+ - 添加对github gist的支持
13
+ - 示例:https://towardsdatascience.com/hands-on-multi-agent-llm-restaurant-simulation-with-python-and-openai
14
+ - 添加对katex公式的支持
15
+ - 示例:https://quantum.country/qcvc
16
+
17
+ ---
18
+
19
+ ## 1. 功能介绍
20
+ 墨探的开发初衷,是为了解决一个问题:如何将来自互联网上各种不同网站的文章内容,精准且高效地转换成统一的Markdown格式。
21
+
22
+ 众所周知,万维网上的网站设计风格迥异,其HTML结构也呈现出千差万别的特点。这种多样性给自动化内容提取和格式转换带来了巨大的困难。要实现一个能够适应各种复杂HTML结构的通用解决方案,并非易事。
23
+
24
+ 我的想法是:从特定的网站开始适配,以点到面,逐步抽取出通用的解决方案,最后尽可能多的覆盖更多网站。以下是一些我经常访问的的网站,支持的都不错:
25
+
26
+ - 掘金
27
+ - CSDN
28
+ - Medium
29
+ - Freedium
30
+ - 公众号
31
+ - 简书
32
+ - 知乎专栏
33
+ - 今日头条
34
+ - towardsdatascience
35
+ - quantamagazine
36
+
37
+ 只需要一条命令:
38
+
39
+ ```sh
40
+ mdcli https://example.com
41
+ ```
42
+
43
+ 你还可以将网页手动保存为 HTML 文件,再使用本工具解析,也是支持的。
44
+
45
+ ```sh
46
+ mdcli your/path/example.html
47
+ ```
48
+
49
+ ---
50
+
51
+ ## 2. 安装与运行
52
+
53
+ 首先,安装必要依赖项:
54
+ ```sh
55
+ uv pip install .
56
+ ```
57
+
58
+ 然后,可以使用以下两种形式运行:
59
+ ```sh
60
+ uv run mdcli
61
+ ```
62
+
63
+ 或者:
64
+
65
+ ```sh
66
+ python -m omni_article_markdown.cli
67
+ ```
68
+
69
+ ---
70
+
71
+ ## 3. 参数说明
72
+
73
+ 运行命令及参数如下:
74
+
75
+ ```sh
76
+ mdcli <URL_OR_PATH> [-s [SAVE_PATH]]
77
+ ```
78
+
79
+ | 参数 | 说明 |
80
+ |--------------------|------|
81
+ | `URL_OR_PATH` | **必填**,目标网页 URL 或本地 HTML 文件路径。 |
82
+ | `-s, --save` | **可选**,启用保存:<br> - 仅 `-s`:默认保存至 `./`。<br> - `-s <SAVE_PATH>`:保存至指定路径。 |
83
+
84
+ ---
85
+
86
+ ## 4. 使用示例
87
+
88
+ ### 4.1 仅转换
89
+ ```sh
90
+ mdcli https://example.com
91
+ ```
92
+
93
+ 内容会输出值`stdout`。
94
+
95
+ ### 4.2 转换并保存到当前目录
96
+
97
+ ```sh
98
+ mdcli https://example.com -s
99
+ ```
100
+
101
+ 会在当前目录生成一个`<title>.md`的文件。
102
+
103
+ ### 4.3 转换并保存到指定路径
104
+
105
+ ```sh
106
+ mdcli https://example.com -s /home/user/
107
+ ```
108
+
109
+ 会在`/home/user/`目录生成一个`<title>.md`的文件。
110
+
111
+ ```sh
112
+ mdcli https://example.com -s /home/user/data.md
113
+ ```
114
+
115
+ 会生成一个`/home/user/data.md`的文件。
116
+
117
+ ---
118
+
119
+ ## 5. 安装和使用插件
120
+
121
+ 墨探支持通过插件机制来扩展自身的能力。这使得社区可以贡献针对不同网站的定制化解析器,而无需修改核心代码。
122
+
123
+ ### 5.1 什么是插件?
124
+
125
+ 插件是独立的 Python 包,它们实现了墨探定义的特定接口(钩子),从而能够被主程序发现和调用。例如,一个插件可以提供一个专门用于解析知乎专栏文章的 `Reader`。插件往往需要安装额外的依赖,因此不包含在主程序中,有需要的用户可自行安装。
126
+
127
+ ### 5.2 如何安装插件?
128
+
129
+ 你可以使用 `mdcli` 提供的 `install` 命令来安装插件。插件通常会发布到 PyPI(Python Package Index)。
130
+
131
+ **安装命令格式:**
132
+
133
+ ```sh
134
+ mdcli install <PLUGIN_NAME_OR_PACKAGE_NAME> [-U] [-e]
135
+ ```
136
+
137
+ | 参数 | 说明 |
138
+ |----------------------------------|----------------------------------------------------------------------|
139
+ | `<PLUGIN_NAME_OR_PACKAGE_NAME>` | **必填**,插件的名称或其在 PyPI 上的完整包名。 |
140
+ | `-U, --upgrade` | **可选**,如果插件已安装,则尝试升级到最新版本。 |
141
+ | `-e, --editable` | **可选**,基于提供的本地文件路径安装可编辑的包。 |
142
+
143
+ **示例:安装知乎解析插件**
144
+
145
+ ```sh
146
+ mdcli install zhihu
147
+ ```
148
+
149
+ 或者,你可以使用 `-e` 参数安装本地可编辑的包。
150
+
151
+ ```sh
152
+ mdcli install -e "./plugins/omnimd-zhihu-reader"
153
+ ```
154
+
155
+ 安装成功后,当下一次运行 `mdcli` 解析知乎链接时,如果该插件正确实现了接口,它将被自动用于处理该链接。
156
+
157
+ ### 5.3 如何卸载插件?
158
+
159
+ 如果你想移除一个已安装的插件,可以使用 `mdcli` 提供的 `uninstall` 命令。
160
+
161
+ **卸载命令格式:**
162
+
163
+ ```sh
164
+ mdcli uninstall <PLUGIN_NAME_OR_PACKAGE_NAME> [-y]
165
+ ```
166
+
167
+ | 参数 | 说明 |
168
+ |----------------------------------|----------------------------------------------------------|
169
+ | `<PLUGIN_NAME_OR_PACKAGE_NAME>` | **必填**,要卸载的插件的名称或其在 PyPI 上的完整包名。 |
170
+ | `-y, --yes` | **可选**,在卸载前不进行确认提示。 |
171
+
172
+ **示例:卸载知乎解析插件**
173
+
174
+ ```sh
175
+ mdcli uninstall zhihu
176
+ ```
177
+
178
+ 或者,使用插件的全称删除
179
+
180
+ ```sh
181
+ mdcli uninstall omnimd-zhihu-reader
182
+ ```
183
+
184
+ 卸载后,该插件的功能将不再可用。对于之前由该插件处理的 URL,`mdcli` 将会回退到其默认的解析逻辑。
185
+
186
+ ### 5.4 已支持的插件
187
+
188
+ 目前已发布3个插件,你可以按需安装:
189
+
190
+ | 命令 | 说明 |
191
+ |----------------------------------|----------------------------------------------------------|
192
+ | `mdcli install zhihu` | 知乎专栏 |
193
+ | `mdcli install toutiao` | 今日头条 |
194
+ | `mdcli install freedium` | Freedium |
195
+
196
+ ### 5.5 开发自己的插件
197
+
198
+ ---
199
+
200
+ ## 6. 贡献与反馈
201
+ - 发现解析问题?欢迎提交 [Issue](https://github.com/caol64/omni-article-markdown/issues)
202
+ - 改进解析?欢迎贡献 [Pull Request](https://github.com/caol64/omni-article-markdown/pulls)
203
+ - 开发插件?文档正在筹备中
204
+
205
+ ---
206
+
207
+ ## 7. 赞助
208
+
209
+ 如果您觉得不错,可以给我家猫咪买点罐头吃。[喂猫❤️](https://yuzhi.tech/sponsor)
210
+
211
+ ---
212
+
213
+ ## 8. License
214
+
215
+ MIT License
@@ -0,0 +1,9 @@
1
+ from .omni_article_md import OmniArticleMarkdown
2
+
3
+ __all__ = ['OmniArticleMarkdown']
4
+
5
+ DEFAULT_PLUGINS = {
6
+ "zhihu": "omnimd-zhihu-reader",
7
+ "freedium": "omnimd-freedium-reader",
8
+ "toutiao": "omnimd-toutiao-reader",
9
+ }
@@ -0,0 +1,105 @@
1
+ import click
2
+ import sys
3
+ from runpy import run_module
4
+ from click_default_group import DefaultGroup
5
+
6
+ from .omni_article_md import OmniArticleMarkdown
7
+ from . import DEFAULT_PLUGINS
8
+
9
+ @click.group(cls=DefaultGroup, default='parse', default_if_no_args=True)
10
+ def cli():
11
+ """
12
+ A CLI tool to parse articles and save them as Markdown.
13
+ It also supports installing plugins.
14
+ """
15
+ pass
16
+
17
+ @cli.command(name="parse")
18
+ @click.argument("url_or_path")
19
+ @click.option(
20
+ "-s", "--save",
21
+ default=None,
22
+ help="Save result (default: ./). Provide a path to save elsewhere.",
23
+ is_flag=False,
24
+ flag_value="./"
25
+ )
26
+ def parse_article(url_or_path, save):
27
+ """
28
+ Parses an article from a URL or local path and outputs/saves it as Markdown.
29
+ """
30
+ handler = OmniArticleMarkdown(url_or_path)
31
+ article = handler.parse()
32
+
33
+ if save is None:
34
+ click.echo(article)
35
+ else:
36
+ handler.save(save)
37
+ click.echo(f"Article saved to: {handler.save_path}")
38
+
39
+ @cli.command()
40
+ @click.argument("plugin_name")
41
+ @click.option(
42
+ "-U", "--upgrade", is_flag=True, help="Upgrade the plugin if already installed."
43
+ )
44
+ @click.option(
45
+ "-e", "--editable", is_flag=True, help="Install the editable package based on the provided local file path"
46
+ )
47
+ def install(plugin_name, upgrade, editable):
48
+ """
49
+ Installs a plugin for this application.
50
+ For example, to install the 'zhihu' plugin: mdcli install zhihu
51
+ """
52
+ actual_package_name = plugin_name if editable or plugin_name not in DEFAULT_PLUGINS else DEFAULT_PLUGINS[plugin_name]
53
+
54
+ click.echo(f"Attempting to install plugin: {actual_package_name}...")
55
+ args = ["pip", "install"]
56
+ if upgrade:
57
+ args.append("--upgrade")
58
+ args.append(actual_package_name)
59
+
60
+ original_argv = sys.argv
61
+ try:
62
+ sys.argv = args
63
+ run_module("pip", run_name="__main__")
64
+ click.echo(f"Plugin '{actual_package_name}' processed by pip.")
65
+ click.echo("If the plugin provides new functionality, it should now be available.")
66
+ click.echo("You might need to restart the application for changes to take full effect if it involves runtime loading during startup.")
67
+ except Exception as e:
68
+ click.echo(f"Failed to process plugin '{actual_package_name}' with pip: {e}", err=True)
69
+ click.echo("Please ensure pip is installed and the package name is correct.", err=True)
70
+ finally:
71
+ sys.argv = original_argv
72
+
73
+ @cli.command()
74
+ @click.argument("plugin_name")
75
+ @click.option(
76
+ "-y", "--yes", is_flag=True, help="Don't ask for confirmation before uninstalling."
77
+ )
78
+ def uninstall(plugin_name, yes):
79
+ """
80
+ Uninstalls a plugin for this application.
81
+ For example, to uninstall the 'zhihu' plugin: mdcli uninstall zhihu
82
+ """
83
+ actual_package_name = plugin_name if plugin_name not in DEFAULT_PLUGINS else DEFAULT_PLUGINS[plugin_name]
84
+
85
+ click.echo(f"Attempting to uninstall plugin: {actual_package_name}...")
86
+ args = ["pip", "uninstall"]
87
+ if yes:
88
+ args.append("-y")
89
+ args.append(actual_package_name)
90
+
91
+ original_argv = sys.argv
92
+ try:
93
+ sys.argv = args
94
+ run_module("pip", run_name="__main__")
95
+ click.echo(f"Plugin '{actual_package_name}' uninstallation processed by pip.")
96
+ click.echo("The plugin's functionality should no longer be available after the next application start (or if dynamically unloaded).")
97
+ except Exception as e:
98
+ click.echo(f"Failed to process uninstallation of plugin '{actual_package_name}' with pip: {e}", err=True)
99
+ click.echo("Please ensure pip is installed and the package name is correct.", err=True)
100
+ finally:
101
+ sys.argv = original_argv
102
+
103
+
104
+ if __name__ == "__main__":
105
+ cli()
@@ -0,0 +1,22 @@
1
+ from pluggy import HookimplMarker, HookspecMarker
2
+ from typing import Optional, Protocol
3
+
4
+
5
+ hookspec = HookspecMarker("mdcli")
6
+ hookimpl = HookimplMarker("mdcli")
7
+
8
+ class ReaderPlugin(Protocol):
9
+ def can_handle(self, url: str) -> bool:
10
+ ...
11
+
12
+ def read(self, url: str) -> str:
13
+ ...
14
+
15
+
16
+ @hookspec(firstresult=True)
17
+ def get_custom_reader(url: str) -> Optional[ReaderPlugin]:
18
+ """
19
+ Allows plugins to provide a custom reader for a given URL.
20
+ The first plugin that returns a ReaderPlugin instance will be used.
21
+ """
22
+ ...