|
| 1 | +# PyWorkout — Architecture |
| 2 | + |
| 3 | +## Layout |
| 4 | + |
| 5 | +``` |
| 6 | +PyWorkout/ |
| 7 | +├── main.py the CLI — command loop, workout data, all session state |
| 8 | +├── gui.py a separate Tkinter percentage tracker |
| 9 | +├── __main__.py module entry point (python -m pyworkout) |
| 10 | +├── docker-compose.yml containerised run |
| 11 | +├── Dockerfile published to GHCR |
| 12 | +├── pyproject.toml packaging; published to PyPI |
| 13 | +├── setup.cfg pytest and coverage configuration |
| 14 | +└── tests/ |
| 15 | + ├── test_main.py CLI behaviour |
| 16 | + └── test_gui.py GUI components |
| 17 | +``` |
| 18 | + |
| 19 | +## Two programs, not one |
| 20 | + |
| 21 | +`main.py` and `gui.py` are independent. The CLI does not import the GUI, and the GUI is not |
| 22 | +a front end for the CLI — it is a separate Tkinter window that displays percentage |
| 23 | +completion for a fixed exercise list. |
| 24 | + |
| 25 | +They share the project and the name, and nothing else. Worth knowing before looking for the |
| 26 | +integration point: there isn't one. |
| 27 | + |
| 28 | +## `main.py` is a single function |
| 29 | + |
| 30 | +The entire CLI — muscle-group selection, the command loop, timing, statistics, help output, |
| 31 | +and the exercise data itself — lives inside one `workout()` function of roughly six hundred |
| 32 | +lines. |
| 33 | + |
| 34 | +Consequences that show up in practice: |
| 35 | + |
| 36 | +- **Session state is local variables**, which is why `skip` and `stats` interact badly: |
| 37 | + they manipulate overlapping bookkeeping in the same scope rather than through a shared |
| 38 | + model. See [Roadmap](./roadmap.md). |
| 39 | +- **Help text is printed inline in two places** (around lines 613 and 632) and the two |
| 40 | + copies have drifted — one documents the `skip`/`stats` limitation, the other omits it. |
| 41 | +- **Tests reach the logic through `builtins.input` and `builtins.print`.** Every test in |
| 42 | + `test_main.py` patches those and asserts against captured output, because there is no |
| 43 | + return value to inspect. That is a consequence of the structure, not a testing choice. |
| 44 | + |
| 45 | +Extracting the workout data and the session state into their own modules is the change that |
| 46 | +would unlock most of the rest. |
| 47 | + |
| 48 | +## Data |
| 49 | + |
| 50 | +Exercise definitions — muscle groups, exercises, sets, reps — are literals in `main.py`. |
| 51 | +There is no data file, no database, and nothing persisted between runs. Closing the program |
| 52 | +discards the session. |
| 53 | + |
| 54 | +Video paths are also literals, under a `# Video File Paths` comment, which is why the |
| 55 | +`video` command requires editing source to work. See [Configuration](./configuration.md). |
| 56 | + |
| 57 | +## Timing |
| 58 | + |
| 59 | +Elapsed time is computed from a start timestamp captured by `start` and compared against |
| 60 | +the current time on each `next`, `stats`, and `end`. There is no pause, and no persistence — |
| 61 | +the timer measures wall-clock time from `start`, including any time you spent away from the |
| 62 | +terminal. |
| 63 | + |
| 64 | +The known timer defect tracked for 2.0.0 lives here. |
| 65 | + |
| 66 | +## Distribution |
| 67 | + |
| 68 | +The same code ships four ways: PyPI (`pip install pyworkout`), a GHCR container, a Windows |
| 69 | +executable attached to releases, and the source itself. `pyproject.toml` drives the first, |
| 70 | +`Dockerfile` the second. |
| 71 | + |
| 72 | +Because the PyPI and GHCR pages render the README off-site, its images must be absolute |
| 73 | +URLs — they point at `.github/icons/PyWorkout/`. Relative image paths would break there |
| 74 | +even though they work on github.com. |
| 75 | + |
| 76 | +## Testing |
| 77 | + |
| 78 | +`setup.cfg` configures pytest with coverage, branch coverage, and three report formats. |
| 79 | +Coverage sits around 54%. GUI tests skip in headless environments, since Tkinter needs a |
| 80 | +display — expected in CI rather than a failure. |
0 commit comments