Skip to main content

ctrl-kd

Convert WordStar-era files to modern formats. ^KD: save and done.

ctrl-kd reads WordStar 4 documents, WordStar 5–7 documents, and WordStar print-to-disk files (the printer byte stream, captured to a file — a distinct format most converters mangle), and writes plain text, Markdown, HTML, RTF, or PDF (typewriter-set on the built-in Courier fonts — no dependencies, the page as it would have printed).

$ ctrl-kd ESSAY.WS                      # -> ESSAY.md
$ ctrl-kd ESSAY.WS -t html -t rtf       # multiple formats
$ ctrl-kd ESSAY.WS -t pdf --mode printed # a facsimile of the 1990 printout
$ ctrl-kd --mode printed LETTER.WS      # line-for-line, as it printed in 1990
$ ctrl-kd --diagnose MYSTERY.FIL        # what IS this file?

Why another converter?

Existing tools each lose something. Fed a WordStar 4 file, converters written for WS7 delete the last letter of every word (WS4 set bit 7 on it). Most delete soft returns outright — Jon Michaels + March 6, 1992 becomes Jon MichaelsMarch 6, 1992 — which also destroys every poem, because poem lines end in soft returns too. And print-to-disk files aren't WordStar documents at all, so feeding them to a WordStar converter produces stray superscripts and garbage.

ctrl-kd was built by converting a real 1987–1992 corpus (high-school and college papers, poems, stories — WordStar 4 on DOS, dot-matrix printer) and verifying against surviving period printouts of the same documents. Its rules are empirical:

  • Detection by content, never by extension. WS4 vs WS5+ vs print stream vs plain text vs binary, with the evidence shown in --diagnose.
  • The wrap test. WordStar wrapped only when the next word didn't fit. So a soft return where the next word would have fit (strictly — WordStar wrapped even on an exact-margin fit) is a deliberate break: a poem line, a heading. Everything else is word wrap and joins with a space. The margin is estimated from the 90th percentile of soft-wrapped line lengths (floor 65, the default).
  • Break runs. Soft/hard return runs containing a hard return and a blank line are paragraph breaks; a lone hard return is the author's deliberate line break. Double-spaced documents (blank soft lines between every line) collapse automatically.
  • Ruler lines mean columns. A .rr----!---- dot line defines tab stops; the document's alignment is space-built and only survives fixed-width. Such documents render printed in every mode.
  • Print streams render verbatim — they ARE the printed page — with printer style codes decoded (superscript/underline/italic/bold pairs; table in core.PRINT_CODES, derived from a late-80s dot-matrix driver and overridable).
  • WS5+ symmetric blocks (0x1D: real footnotes/endnotes, headings, page breaks — machinery added in WS5) are parsed with their nested structure, verified against the 86 WordStar 7 documents in Robert J. Sawyer's public WordStar archive: footnotes extract with in-text references ([^n] in Markdown), paragraph styles become headings, and 82/86 convert with zero mojibake. More WS5–7 corpora still welcome.

Modes

  • --mode modern (default): reflowed paragraphs, semantic markup, deliberate line breaks kept.
  • --mode printed: every line as laid out, fixed-width, .pa/form-feed page breaks honored — how it came off the printer.

Install

Straight from GitHub (not yet on PyPI):

$ pipx install git+https://github.com/jonmichaels/ctrl-kd

or pip install git+https://github.com/jonmichaels/ctrl-kd into an environment of your choice. Python ≥ 3.9, no dependencies. Library API: ctrlkd.convert(data, to='html').

Adding an output format

An output format is one function over the parsed document — register it with the @ctrlkd.emitter decorator, or ship it as a pip-installable plugin via the ctrlkd.emitters entry-point group and it appears in the CLI automatically. EXTENDING.md has the IR contract, a complete worked example (BBCode in ~40 lines), and a checklist.

Lineage

Standing on the shoulders of the tools and documentation that kept WordStar readable: Yohanes Nugroho's WS-CON, Michael Petrie's English port, the wsconvert project, Robert J. Sawyer's WordStar archive, and the WordStar format documentation community. Behaviors were studied and reimplemented; no code was copied. The development corpus is personal and is not distributed — tests use synthetic fixtures that encode the same behaviors.

Credits

Written by Jon Michaels — whose 1987–1992 WordStar files, and the need to read them again, are the reason this exists — with Athena (Claude, Anthropic) as co-author: the byte archaeology, the wrap test, and the implementation grew out of a joint effort to recover those disks. Every commit carries the co-author trailer.

License

MIT © Jon Michaels

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

ctrl_kd-1.1.2.tar.gz (20.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

ctrl_kd-1.1.2-py3-none-any.whl (19.3 kB view details)

Uploaded Python 3

File details

Details for the file ctrl_kd-1.1.2.tar.gz.

File metadata

  • Download URL: ctrl_kd-1.1.2.tar.gz
  • Upload date:
  • Size: 20.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for ctrl_kd-1.1.2.tar.gz
Algorithm Hash digest
SHA256 9a52d3e6445f8d0f83aa1d970385fd285c65892c9e55d2d6a3c4a235ff262482
MD5 cb1c1adb11477dd9e178d5e8d6104164
BLAKE2b-256 0c0435a51fc8b58df6573d5d5dc52607b38260b367a16ddde1b68b14ef0187e1

See more details on using hashes here.

Provenance

The following attestation bundles were made for ctrl_kd-1.1.2.tar.gz:

Publisher: publish.yml on jonmichaels/ctrl-kd

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file ctrl_kd-1.1.2-py3-none-any.whl.

File metadata

  • Download URL: ctrl_kd-1.1.2-py3-none-any.whl
  • Upload date:
  • Size: 19.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for ctrl_kd-1.1.2-py3-none-any.whl
Algorithm Hash digest
SHA256 f665ae11d3aaff7a0fa4394b7f90cff6b622e25338d2629389e5e9a704e9b39a
MD5 0ffd16f83f550c53b026fa6e4b1d2074
BLAKE2b-256 65360c31f8cbc40ccb60121cbc21cd642e3ae6aaeb3382ae37ca8db897fe727f

See more details on using hashes here.

Provenance

The following attestation bundles were made for ctrl_kd-1.1.2-py3-none-any.whl:

Publisher: publish.yml on jonmichaels/ctrl-kd

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page