Skip to content

Commit 3586b69

Browse files
committed
Initial release
0 parents  commit 3586b69

17 files changed

Lines changed: 1392 additions & 0 deletions

File tree

.codex-plugin/plugin.json

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{
2+
"name": "html-to-markdown",
3+
"version": "0.3.0",
4+
"description": "Convert HTML webpages and files into clean Markdown with easy defaults and advanced extraction.",
5+
"author": {
6+
"name": "Ryan Zhang",
7+
"url": "https://github.com/riainzhang"
8+
},
9+
"skills": "./skills/",
10+
"interface": {
11+
"displayName": "HTML to Markdown",
12+
"shortDescription": "Convert HTML pages to Markdown files.",
13+
"longDescription": "HTML to Markdown gives Codex a local workflow for converting HTML from webpages, files, or stdin into portable Markdown documents with article extraction, selector filtering, batch conversion, table rendering, and plain-text fallback.",
14+
"developerName": "Ryan Zhang",
15+
"websiteURL": "https://github.com/riainzhang",
16+
"category": "Productivity",
17+
"capabilities": [
18+
"HTML conversion",
19+
"Markdown export",
20+
"Article extraction",
21+
"Table rendering",
22+
"Batch conversion",
23+
"Selector filtering"
24+
],
25+
"defaultPrompt": "Convert this HTML webpage to a Markdown file."
26+
}
27+
}

.github/workflows/tests.yml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
name: Tests
2+
3+
on:
4+
push:
5+
pull_request:
6+
7+
jobs:
8+
test:
9+
runs-on: ubuntu-latest
10+
strategy:
11+
matrix:
12+
python-version: ["3.9", "3.11", "3.13"]
13+
steps:
14+
- uses: actions/checkout@v4
15+
- uses: actions/setup-python@v5
16+
with:
17+
python-version: ${{ matrix.python-version }}
18+
- name: Run tests
19+
run: python -m unittest discover -s tests
20+
- name: Run benchmark smoke test
21+
run: python benchmarks/bench_converter.py

.gitignore

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
__pycache__/
2+
*.pyc
3+
*.pyo
4+
.DS_Store
5+
Thumbs.db
6+
*.egg-info/
7+
build/
8+
dist/
9+
.venv/
10+
venv/
11+
12+
# Ignore generated root-level conversion outputs while keeping project docs.
13+
/*.md
14+
!/README.md
15+
!/CHANGELOG.md

CHANGELOG.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Changelog
2+
3+
## 0.3.0
4+
5+
- Added friendly root entrypoints: `html2md.py`, `html2md.cmd`, and `html2md`.
6+
- Added selector filtering with `--include-selector` and `--exclude-selector`.
7+
- Added batch conversion with `--input` and `--output-dir`.
8+
- Added GitHub Actions test workflow.
9+
- Added local benchmark and expanded tests.
10+
11+
## 0.2.0
12+
13+
- Replaced the initial stream renderer with an original tree-based renderer.
14+
- Added article extraction, table rendering, and plain-text fallback.
15+
16+
## 0.1.0
17+
18+
- Initial Codex plugin scaffold and Python converter.

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2026 Ryan Zhang
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.

README.md

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
# HTML to Markdown
2+
3+
A simple, original HTML-to-Markdown converter for webpages, local `.html` files, and pasted HTML.
4+
Run it with no arguments, paste a URL, and it writes a clean `.md` file for you.
5+
6+
## What it does
7+
8+
- Converts headings, paragraphs, links, images, lists, blockquotes, inline code, code blocks, and tables.
9+
- Uses `--content-mode auto` by default to keep likely article content and skip obvious site chrome.
10+
- Supports `--content-mode full` when you want the whole page body.
11+
- Supports `--plain-text` when maximum text retention matters more than Markdown formatting.
12+
- Supports simple selectors such as `--include-selector article` and `--exclude-selector nav,footer,.ad`.
13+
- Supports batch conversion with glob input and an output directory.
14+
- Accepts a URL, a local file path, or stdin.
15+
- Writes Markdown to stdout or a `.md` file.
16+
- Uses only the Python standard library.
17+
18+
## Quick Start
19+
20+
From the plugin root:
21+
22+
```bash
23+
python html2md.py
24+
```
25+
26+
Then paste the webpage URL or local HTML file path. The Markdown file will be generated in the
27+
current directory.
28+
29+
On Windows, if `python` points to Python 2, use:
30+
31+
```powershell
32+
html2md.cmd
33+
```
34+
35+
You can also run the script directly:
36+
37+
```bash
38+
python scripts/html_to_markdown.py "https://example.com" --output example.md
39+
```
40+
41+
Or install it locally and use the `html2md` command:
42+
43+
```bash
44+
python -m pip install -e .
45+
html2md
46+
```
47+
48+
## Useful Commands
49+
50+
Convert a local file:
51+
52+
```bash
53+
python html2md.py examples/sample.html --output sample.md
54+
```
55+
56+
Keep the full page body instead of automatic article extraction:
57+
58+
```bash
59+
python html2md.py "https://en.wikipedia.org/wiki/GitHub" --output github-full.md --content-mode full
60+
```
61+
62+
Keep more visible text with simpler formatting:
63+
64+
```bash
65+
python html2md.py "https://en.wikipedia.org/wiki/GitHub" --output github.txt.md --plain-text
66+
```
67+
68+
Convert only a selected part of the page:
69+
70+
```bash
71+
python html2md.py page.html --output article.md --include-selector article --exclude-selector nav,footer,.ad
72+
```
73+
74+
Batch convert local HTML files:
75+
76+
```bash
77+
python html2md.py --input "pages/*.html" --output-dir md
78+
```
79+
80+
If a remote webpage times out, increase the timeout and retry count:
81+
82+
```bash
83+
python html2md.py "https://en.wikipedia.org/wiki/GitHub" --output github.md --timeout 180 --retries 5
84+
```
85+
86+
Convert pasted HTML:
87+
88+
```bash
89+
Get-Content page.html | py -3 scripts/html_to_markdown.py - --output page.md
90+
```
91+
92+
## Quality And Speed
93+
94+
Run tests:
95+
96+
```bash
97+
py -3 -m unittest discover -s tests
98+
```
99+
100+
Run the local benchmark:
101+
102+
```bash
103+
py -3 benchmarks/bench_converter.py
104+
```
105+
106+
## Project Structure
107+
108+
```text
109+
html-2-markdown/
110+
html2md.py # Friendly Python entrypoint
111+
html2md.cmd # Windows launcher
112+
html2md # Unix-like launcher
113+
scripts/html_to_markdown.py # Core converter
114+
tests/ # Unit tests
115+
benchmarks/ # Local benchmark
116+
examples/ # Sample input and output
117+
.github/workflows/ # GitHub Actions tests
118+
.codex-plugin/ # Optional Codex plugin metadata
119+
```
120+
121+
## Installing as a Codex plugin
122+
123+
This repository is already shaped as a Codex plugin. After cloning it, install or load it through your Codex plugin workflow.
124+
125+
## Author
126+
127+
Created and maintained by [Ryan Zhang](https://github.com/riainzhang).
128+
129+
## Open access and legal notes
130+
131+
Open-sourcing this plugin is okay. For the safest public release:
132+
133+
- Use an open-source license such as MIT.
134+
- Do not include copied website content in the repository unless you have permission.
135+
- Respect website terms, robots policies, copyright, paywalls, and login requirements when converting remote pages.
136+
- Preserve attribution when sharing converted content.
137+
138+
## License
139+
140+
MIT

benchmarks/bench_converter.py

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
#!/usr/bin/env python3
2+
"""Run a small local benchmark for the converter."""
3+
4+
import tempfile
5+
import time
6+
from pathlib import Path
7+
import sys
8+
9+
10+
ROOT = Path(__file__).resolve().parents[1]
11+
sys.path.insert(0, str(ROOT / "scripts"))
12+
13+
import html_to_markdown as converter
14+
15+
16+
SAMPLE_ARTICLE = """
17+
<!doctype html>
18+
<html>
19+
<head><title>Benchmark Article</title></head>
20+
<body>
21+
<nav>Home Products Login</nav>
22+
<main id="content">
23+
<h1>Benchmark Article</h1>
24+
<p>This benchmark page contains links, tables, nested lists, blockquotes, and code.</p>
25+
<p>Visit <a href="/docs">the documentation</a> for more details.</p>
26+
<table>
27+
<tr><th>Name</th><th>Value</th></tr>
28+
<tr><td>Alpha</td><td>One</td></tr>
29+
<tr><td>Beta</td><td>Two</td></tr>
30+
</table>
31+
<ul>
32+
<li>Fast defaults<ul><li>No dependencies</li><li>Interactive mode</li></ul></li>
33+
<li>Batch mode</li>
34+
</ul>
35+
<blockquote><p>Readable Markdown is the goal.</p></blockquote>
36+
<pre><code>print("hello")</code></pre>
37+
</main>
38+
<footer>Contact Subscribe Legal</footer>
39+
</body>
40+
</html>
41+
"""
42+
43+
44+
def main() -> None:
45+
with tempfile.NamedTemporaryFile("w", suffix=".html", delete=False, encoding="utf-8") as handle:
46+
handle.write(SAMPLE_ARTICLE)
47+
source = handle.name
48+
49+
iterations = 200
50+
started = time.perf_counter()
51+
markdown = ""
52+
try:
53+
for _ in range(iterations):
54+
markdown = converter.convert(source, content_mode="auto")
55+
finally:
56+
Path(source).unlink(missing_ok=True)
57+
elapsed = time.perf_counter() - started
58+
59+
checks = {
60+
"article text": "Benchmark Article" in markdown,
61+
"table": "| Name | Value |" in markdown,
62+
"nested list": " - No dependencies" in markdown,
63+
"site chrome removed": "Home Products Login" not in markdown and "Contact Subscribe Legal" not in markdown,
64+
}
65+
passed = sum(1 for value in checks.values() if value)
66+
pages_per_second = iterations / elapsed if elapsed else 0
67+
68+
print("HTML to Markdown benchmark")
69+
print("Iterations: {}".format(iterations))
70+
print("Elapsed: {:.3f}s".format(elapsed))
71+
print("Throughput: {:.1f} pages/sec".format(pages_per_second))
72+
print("Output size: {} chars".format(len(markdown)))
73+
print("Quality checks: {}/{} passed".format(passed, len(checks)))
74+
for name, ok in checks.items():
75+
print("- {}: {}".format(name, "ok" if ok else "failed"))
76+
77+
78+
if __name__ == "__main__":
79+
main()

examples/sample.html

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8">
5+
<title>Sample HTML Page</title>
6+
</head>
7+
<body>
8+
<main>
9+
<h1>Sample HTML Page</h1>
10+
<p>This page shows <strong>basic conversion</strong> to Markdown.</p>
11+
<h2>Links and Lists</h2>
12+
<p>Visit <a href="https://example.com">Example</a>.</p>
13+
<ul>
14+
<li>Headings</li>
15+
<li>Paragraphs</li>
16+
<li>Links</li>
17+
</ul>
18+
<pre><code>console.log("hello markdown");</code></pre>
19+
</main>
20+
</body>
21+
</html>

examples/sample.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Sample HTML Page
2+
3+
This page shows **basic conversion** to Markdown.
4+
5+
## Links and Lists
6+
7+
Visit [Example](https://example.com).
8+
9+
- Headings
10+
- Paragraphs
11+
- Links
12+
13+
```text
14+
console.log("hello markdown");
15+
```

html2md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
#!/usr/bin/env sh
2+
python3 "$(dirname "$0")/html2md.py" "$@"

0 commit comments

Comments
 (0)