Architecture
How Cowser is organized and how rendering works.
Table of Contents
Module layout
cowser/
├── __init__.py # re-exports the stable surface + __version__
├── __main__.py # python -m cowser entry point
├── api.py # cowser.api: backwards-compatible public names
├── cli.py # argparse CLI, config defaults, REPL
├── config.py # reads ~/.config/neostore/cowser/config.toml
├── core.py # render(), render_pair(), bubble & wrapping logic
├── cows.py # Cow dataclass + built-in COWS registry (35 animals)
├── modes.py # mode -> (eyes, tongue) presets
├── ansi.py # colors, rainbow, bold, legacy-console sanitizing
├── fortunes.py # built-in fortunes for --fortune
├── plugins.py # art packs (.cow/.txt/.art loaders)
└── py.typed # PEP 561 marker
Supporting files at the repo root: main.py (thin wrapper for
python main.py ...), cowser.cmd / cowser.ps1 (Windows wrappers),
scripts/build_binary.py (PyInstaller), and config.example.toml.
Data flow
flowchart LR
A[cli.py] -->|name / message / stdin / fortune| B[core.py]
B --> C[cows.py registry]
B --> D[modes.py presets]
B --> E[core build_speech_bubble]
E --> F[ansi.py styling]
F --> G[cli.py prints]
H[plugins.py] -->|register art pack cows| C
I[config.py] -->|defaults| A
The Cow registry
cowser/cows.py defines a frozen Cow dataclass with name, art,
category, and tags, and a module-level COWS list. resolve_cow() in
core.py picks a cow by:
None— a random cow,- a
Cowinstance, - a zero-based index into
COWS, - an exact name, or
- a unique name prefix.
search_cows() filters the registry by name, category, or tag; register_cow()
appends new cows at runtime (used by the art-pack loader).
Token substitution
Art can contain two placeholders:
{eyes}— replaced by--eyes(defaulto o).{tongue|default}— replaced by--tongueor a mode's preset tongue; with no tongue given, the token's built-in default is used so normal rendering is unaffected.
--mode maps to an (eyes, tongue) pair in modes.py and only fills in
eyes/tongue that were not otherwise provided:
"dead": ("xx", "U "),
"borg": ("==", None), # None leaves the tongue alone
Rendering pipeline
core.render() (and render_pair() for two animals):
- Resolve the cow(s).
- Optionally word-wrap the text (
wrap_text, preserving paragraphs). - Apply eyes/tongue/mode substitutions (
_substituted_art). - Build the speech or thought bubble (
build_speech_bubble). - Apply styling (
_style: rainbow, color, bold). - Return the finished string — no I/O;
render()is a pure function.
The CLI adds two steps around it: it loads art packs first, and it sanitizes
output for legacy consoles (ansi.needs_fallback / ansi.sanitize_legacy)
before printing. On codepages that cannot encode the output, smart quotes,
dashes, accents, and other symbols are mapped to plain ASCII.
Design notes
- Pure core, thin CLI. All rendering logic is side-effect free so it can
be embedded in other tools; only
cli.pytouches stdout, stdin, and files. - Stable surface.
cowser.api.__all__is the backwards-compatible public API; internal helpers are private (leading underscore) or undocumented. - Zero-dependency default. On Python 3.11+ there are no runtime
dependencies;
tomliis only required on 3.8–3.10.