Development
How to set up the project, run the checks, and find your way around the code.
Setup
git clone https://github.com/bradmontgomery/word2html.git
cd word2html
uv sync
Commands
| Command | What it does |
|---|---|
uv run pytest | Run the tests |
uv run pytest --cov | Run the tests, and require at least 90% coverage |
uv run ruff check | Lint |
uv run ruff format | Format the code |
uv run mypy | Check types in strict mode |
uv build | Build the wheel and source distribution |
uv run word2html file.docx | Run your working copy |
How a conversion works
- Preflight (
docx.py). Validates the file and its archive, parses every XML part as a stream with limits, and looks for hidden text and images that'll be lost. It also notes whether the document has any media. - Convert (
pandoc.py). Finds pandoc, checks its version, and runs it with memory and time limits.--embed-resourcesis only used if there's media, because it needs about six times the memory. - Scan (
html_scan.py). Parses the final HTML to find unsafe links and active content. - Decide (
main.py). Refuses active content always, and refuses unsafe links and hidden text with--strict. - Write (
main.py). Atomically replaces the output file.
Project layout
| Path | Contents |
|---|---|
word2html/main.py | convert_to_html(), atomic writes, and the command line |
word2html/docx.py | Preflight validation and hidden-text detection |
word2html/pandoc.py | Finding and running pandoc, and the minimum version |
word2html/html_scan.py | Unsafe link and active content scanning |
word2html/common.py | Limits, Finding, and terminal output sanitizing |
word2html/errors.py | Exceptions |
tests/ | The test suite |
docs/ | This documentation, as static HTML |
.github/ | The CI workflow and Dependabot configuration |
Tests
Tests use real pandoc wherever behaviour depends on it, and the suite fails rather than skips if pandoc is missing. A small stub stands in for pandoc only to simulate states that are hard to produce for real, such as a hanging or too-old pandoc.
Test documents are built in code by helpers in tests/conftest.py, so no binary files are committed:
def test_hidden_text_warns(make_docx):
path = make_docx(body=para(run("secret", "<w:vanish/>")))
result = convert_to_html(path)
assert result.warnings[0].kind is FindingKind.HIDDEN_TEXT
| File | Covers |
|---|---|
test_convert.py | Normal conversions, and what's kept or dropped |
test_paths.py | Unusual filenames, including the wrong-file regressions |
test_links.py | Unsafe links and --strict |
test_active_content.py | Active content and allowed media |
test_hidden_text.py | Hidden text detection |
test_malformed.py | Corrupt and hostile documents |
test_limits.py | Resource limits |
test_output.py | Atomic writes, symlinks, and permissions |
test_cli.py | Exit codes, options, and pandoc failures |
test_docs.py | That these docs have no broken links and still match the code |
When you add a guard, make sure a test fails without it: temporarily disable the guard, and confirm the suite notices.
Continuous integration
GitHub Actions runs on every pull request and push to main:
| Job | What it checks |
|---|---|
lint | The lockfile, ruff, formatting, and mypy |
test | The tests with coverage, on Linux and macOS, with Python 3.12 to 3.15 and pandoc 3.13 |
pandoc-minimum | The tests on the oldest supported pandoc |
package | Installs the built wheel in a clean environment and converts a document |
Actions are pinned to full commit hashes, and Dependabot proposes weekly updates for actions and Python packages. The pandoc versions in ci.yml are updated by hand.
Changing the minimum pandoc version
Update these together. test_docs.py catches a mismatch in the code and these docs:
MIN_PANDOC_VERSIONinword2html/pandoc.pyPANDOC_MINIMUMin.github/workflows/ci.ymlREADME.mdand the pages indocs/
Working on these docs
The site is plain HTML with one stylesheet and no build step. To preview it:
python3 -m http.server --directory docs 8000
Then open http://localhost:8000. When you add a page, add it to the sidebar on every page. Keep links relative, because the site is served from /word2html/.
Publishing with GitHub Pages
- Merge the docs to
main. - In the repository, open Settings › Pages.
- Under Build and deployment, set Source to Deploy from a branch.
- Choose the
mainbranch and the/docsfolder, then save.
The site is published to bradmontgomery.github.io/word2html. The empty docs/.nojekyll file tells GitHub to serve the files as they are.
Releasing
- Update
versioninpyproject.toml, then runuv lock. - Update the version in the docs.
test_docs.pychecks this. - Merge to
main, after CI passes. - Run
uv build, and thenuv publish.