diff --git a/.gitignore b/.gitignore index 3319ad0..7066c52 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +.plans/ + # Python __pycache__/ *.py[cod] diff --git a/CLAUDE.md b/CLAUDE.md index 5bbae2a..d337d38 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -3,10 +3,9 @@ Plain text training log parser and analyzer. Uses a custom tree-sitter grammar to parse `.ox` log files into structured data for querying and analysis. - ## Usage Notes - `SPEC.md` is your guide for the goals, non-goals, and roadmap for this repo. -- if you edit `tree-sitter-ox/grammar.js` run `cd tree-sitter-ox && tree-sitter generate && cd ..` then reinstall the package with `uv cache clean tree-sitter-ox && uv sync` before doing any additional work. The cache clean is required because uv caches built wheels by version number and won't rebuild the C extension otherwise. +- If you edit `tree-sitter-ox/grammar.js` run `cd tree-sitter-ox && tree-sitter generate && cd ..` then reinstall with `uv cache clean tree-sitter-ox && uv sync`. The cache clean is required because uv caches built wheels by version number and won't rebuild the C extension otherwise. ## Commands @@ -22,24 +21,29 @@ uv run ruff format src/ tests/ # format ``` src/ox/ parse.py - Tree-sitter node → data structures (the core parser) - data.py - Dataclasses: TrainingSet, Movement, TrainingSession, TrainingLog + data.py - Dataclasses: TrainingSet, Movement, TrainingSession, TrainingLog, Note, WeighIn, StoredQuery, Diagnostic db.py - In-memory SQLite layer: create_db(log) → Connection reports.py - Reports: volume, matrix; get_all_reports() merges builtin + plugin reports plugins.py - Plugin discovery and registry (report + generator types) units.py - Pint unit registry (shared instance) - cli.py - Click CLI with interactive REPL (stats, history, report, generate, query, tables, reload) - lsp.py - LSP server for .ox files (ox-lsp command) + cli.py - Click CLI with interactive REPL (stats, history, report, generate, query, tables, lint, reload) + lsp.py - LSP server: diagnostics, movement completion, comment folding + lint.py - Parse error collection for CLI lint command and LSP builtins/ - e1rm.py - Builtin estimated 1RM report plugin (Brzycki/Epley) - wendler531.py - Builtin Wendler 5/3/1 generator plugin + e1rm.py - Estimated 1RM report (Brzycki/Epley) + weighin.py - Weigh-in stats/plot report (rolling average, trend, multi-scale) + wendler531.py - Wendler 5/3/1 cycle generator tests/ - conftest.py - Shared fixtures (simple_log_content, simple_log_file, weight_edge_cases, simple_db, example_db) - test_parse.py - Unit tests for weight/rep parsing - test_data.py - Unit tests for data structures - test_db.py - Tests for SQLite schema, loading, views, queries - test_reports.py - Tests for reports, arg parsing, registry - test_plugins.py - Tests for plugin registration, loading, builtins - test_integration.py - End-to-end parsing tests + conftest.py - Shared fixtures (simple_log_*, weight_edge_cases, log_with_query_*, log_with_weigh_ins_*, weigh_in_multi_scale_*, simple_db, example_db) + test_parse.py - Weight/rep parsing + test_data.py - Data structures + test_db.py - SQLite schema, loading, views, queries + test_reports.py - Reports, arg parsing, registry + test_plugins.py - Plugin registration, loading, builtins + test_integration.py - End-to-end parsing + test_weighin.py - Weigh-in report (rolling avg, trend, table/plot/stats) + test_notes.py - Note parsing, session notes, DB population + test_lint.py - Diagnostic collection tree-sitter-ox/ grammar.js - Tree-sitter grammar definition for .ox format editors/ @@ -59,16 +63,31 @@ example/ # Single-line entry: date flag exercise: weight reps "note" 2025-01-10 * pullups: BW 5x10 -# Session block: multiple exercises in one session +# Session block @session 2025-01-11 * Upper Day bench-press: 135lb 5x5 kb-oh-press: 24kg 5/5/5 @end +# Weigh-in: date W weight [time] [scale] +2025-01-10 W 185lb T06:30 "home" + +# Note: date note "text" +2025-01-10 note "deload week" + +# Stored query: date query "name" "SQL" +2025-01-10 query "recent" "SELECT * FROM training LIMIT 10" + +# Include another file +@include "other.ox" + # Flags: * = completed, ! = planned, W = weigh-in -# Weight formats: 24kg, 135lb, BW (bodyweight), 24kg+32kg (combined), 24kg/32kg/48kg (progressive), BW/25lb/50lb (mixed BW/progressive) +# Weight units: kg, lb, g, oz, stone, grain, and more (any pint-compatible mass unit) +# Weight formats: 24kg, BW, 24kg+32kg (combined), 24kg/32kg/48kg (progressive) # Rep formats: 5x5 (sets x reps), 5/5/5 (per-set reps) +# Duration: ISO 8601 (PT30M, PT1H30M15S) +# Distance: numeric + unit (m, km, ft, mi, etc.) ``` ## Conventions @@ -81,4 +100,4 @@ kb-oh-press: 24kg 5/5/5 - Tree-sitter nodes are processed in `parse.py`; data structures live in `data.py` — keep this separation ## Known Issues -- Progressive weights for the same movement require explicit units, this is known bug and applicable tests are skipped. +- Progressive weights for the same movement require explicit units, this is a known bug and applicable tests are skipped. diff --git a/README.md b/README.md index fac53cd..95ea03e 100644 --- a/README.md +++ b/README.md @@ -1,306 +1,100 @@ # Ox -`ox` is a plain text format for tracking training. Write your training sessions in a text file, parse them into structured data, and analyze progress over time. +Plain text training log format and toolchain. Write workouts in `.ox` files, parse into structured data, analyze progress over time. -Named after [Milo of Croton](https://en.wikipedia.org/wiki/Milo_of_Croton), the ancient Greek Olympic wrestler who, in the off-season, carried a newborn calf daily so that by the time the Olympics came around he was carrying a 4-year-old ox. - -Inspired by plain-text accounting systems like [Beancount](https://github.com/beancount/beancount). - -## Overview - -Physical training is most effective when starting small, working consitently, and practicing over decades. -Over that time, both the individual and available software will change. -The old method (paper), provides infinite flexibility, but analyse and progress tracking is hard. The new methods (a plethera of apps) are less flexible and do not give user cfull ontrol of their data. -Plaintext provides the data longevity and access, so I just need a syntax that provides the flexibility and ease of input as paper along with the analytics of software. Here is my implementation. - -Currently, you can: - -- Define excercises (this essentially just adds metadata right now, in the future I'd like to incorporate it into the analysis). -- Record both completed and planned training sessions (though planned training sessions are all but ignored in the parsing) -- Training sessions consist of a movement (squat) with details that can include weights, reps, time, distance, and notes. - - Time and distance would be for things like running, but flexbility is key, so use it however you like! -- Perform some simple analysis via a CLI that shows progression for a given movement over time, or broad stats on all your recorded movements. -- record a bodyweight measurement - -In the furture, I would like to: - -- Make it easier to write plugins to perform specific analyses. - - Right now you can do this through the python API, so you can write a script for yourself. - - It'd also be nice to calculate RM values given a weight / rep set and track it over time (useful for programs like [Wendler's 5/3/1](https://www.jimwendler.com/collections/books-programs)) -- Make the `tree-sitter` parser more robust by ensuring it can handle any valid units the user wants to use. - - `tree-sitter` provides the tree, then the python library processes the data. `pint` is used for units, so I'd like `tree-sitter` to accept any valid `pint` units for a given dimension. For example, right now you can only use `lbs` and `kg` for weights (ie mass, if you want to get technical), it should accept all iterations of those that `pint` accepts, as well as `ton` (if you're ambitiious). -- Build out the vscode extension that not only includes syntax highlights but has a full LSP (with suggestions) and snippets. -- Provide the abililty to plan and track different cycles (micro/meso/macro) for a given movement or session. - -## Documentation - -Full documentation available at [https://konnerhorton.github.io/ox](https://konnerhorton.github.io/ox) - -- **[Getting Started](getting-started.md)** - Your first training log -- **[CLI Reference](cli-reference.md)** - Command-line interface guide -- **[API Reference](api-reference.md)** - Python library usage -- **[Editor Support](editor-support.md)** - Support for editors - -## Installation - -```bash -pip install ox -``` - -Or install from source: - -```bash -git clone https://github.com/konnerhorton/ox.git -cd ox -pip install -e . -``` +Inspired by [Beancount](https://github.com/beancount/beancount) (plain text accounting, but for training). Named after [Milo of Croton](https://en.wikipedia.org/wiki/Milo_of_Croton). ## Quick Start -Create a training log file (e.g., `training.ox`): +Create `training.ox`: ``` -2025-11-14 * pullups: 24kg 5/5/5 +2025-01-14 * pullups: 24kg 5/5/5 @session -2024-01-23 * Upper Volume -bench-press: 135lbs 5x10 -overhead-press: 85lbs 4x10 +2025-01-15 * Upper Volume +bench-press: 135lb 5x10 +overhead-press: 85lb 4x10 pullup: BW 5x8 @end -2024-01-23 W bodyweight: 155lbs "morning" +2025-01-15 W 185lb T06:30 "home" ``` -Parse and analyze your log: - -```python -from ox import parse +Run the CLI: -# Parse your training log -log = parse("training.ox") - -# Analyze your progress -for date, movement in log.movements("pullups"): - print(f"{date}: {movement.total_reps} reps @ {movement.top_set_weight}") +```bash +ox training.ox ``` -## Features - -- **Simple syntax** - Plain text format that's easy to write and read -- **Flexible logging** - Single-line entries or multi-exercise sessions -- **Progress tracking** - Parse logs into structured data for analysis -- **Unit support** - Mix kg, lbs, bodyweight seamlessly -- **Notes and comments** - Track how you felt, form cues, and more - -## Syntax Overview - -**Entry**: A record in your log, either single-line or multiline. - -**Item**: Data within the Entry, can be an excercise, note, or measurement. -Items must have associated details. - -**Details**: Specific details about the Item like reps, sets, notes, weights, or times. - -### Single-line entries - -Useful for single Items (like a single-excercise session or a weigh-in) or when you don't have a reason to group Items. +## Documentation -``` -2025-11-14 * pullups: 24kg 5/5/5 -2025-11-14 * run: 5km 25min -2025-11-14 W bodyweight: 155lbs -``` +Full docs at [konnerhorton.github.io/ox](https://konnerhorton.github.io/ox): -**Format:** +- [Getting Started](https://konnerhorton.github.io/ox/getting-started/) — first training log +- [CLI Reference](https://konnerhorton.github.io/ox/cli-reference/) — commands and usage +- [Reports & Plugins](https://konnerhorton.github.io/ox/plugins/) — built-in reports, plugin system +- [API Reference](https://konnerhorton.github.io/ox/api-reference/) — Python library +- [Editor Support](https://konnerhorton.github.io/ox/editor-support/) — VSCode, Neovim, Helix -```ebnf -single_line_entry = date, " ", flag, " ", item, ": ", details ; +## Syntax Overview -date = digit, digit, digit, digit, "-", digit, digit, "-", digit, digit ; -flag = "*" | "!" | "W" ; -item = identifier ; -details = detail, { " ", detail } ; ``` +# Single-line entry +2025-01-14 * squat: 135lb 5x5 "felt good" -### Multi-line entries (sessions) - -Use tagged blocks for workouts with multiple exercises: - -``` +# Session block @session -2025-11-14 * Upper Day -pullups: 24kg 5/5/5 -kb-oh-press: 32kg 4x4 -kb-row: 32kg 4x4 -note: felt strong today +2025-01-15 * Lower Body +squat: 135lb 5x5 +deadlift: 185lb 3x5 +note: easy day @end -``` -**Format:** +# Weigh-in +2025-01-15 W 185lb T06:30 "home" -```ebnf -multiline_entry = "@session", newline, - date, " ", flag, " ", name, newline, - { item, ": ", details, newline }, - "@end" ; +# Note +2025-01-15 note "deload week" -name = text_until_newline ; +# Include another file +@include "other.ox" ``` -The session name (`Upper Day`) can be arbitrary or refer to a predefined template (future feature). - -### Exercise definitions - -It can be useful to define excercises for reference, the syntax below allows this. -All fields shown are options, and you can add arbitaray ones as needed. - -``` -@exercise kb-oh-press -equipment: kettlebell -pattern: press -url: https://example.com/kb-press-tutorial -note: keep elbow tight, don't flare -@end -``` +**Flags:** `*` completed, `!` planned, `W` weigh-in -**Fields:** +**Weights:** `24kg`, `135lb`, `BW`, `24kg+32kg` (combined), `24kg/32kg/48kg` (progressive) -- `equipment`: Type of equipment (kettlebell, barbell, bodyweight, etc.) -- `pattern`: Movement pattern (press, squat, hinge, pull, etc.) -- `url`: Link to tutorial or form reference -- `note`: Form cues or other notes +**Reps:** `5x5` (sets x reps), `5/3/1` (per-set) -### Flags +**Duration:** ISO 8601 (`PT30M`, `PT1H30M15S`) -- `*` - Completed -- `!` - Planned -- `W` - Weigh-in +**Distance:** `5km`, `3mi`, `400m` -### Details +**Exercise names:** no spaces, hyphenated lowercase (`kb-oh-press`, `bb-back-squat`) -**Weights:** - -``` -24kg single weight -24kg+32kg combined weights (two kettlebells) -24kg/32kg progressive weights (different sets) -155lbs pounds -BW bodyweight -``` - -**Reps:** - -``` -5/3/1 sets with different reps -3x5 3 sets of 5 reps -``` - -**Time:** - -``` -25min time -``` - -**Distance:** - -``` -5km distance -``` - -**Notes:** - -Notes can either be part of an Item's details (with quotes): - -``` -2025-11-14 * pullups: 24kg 5/5/5 "felt strong" -``` - -Or it's own line (Item) and does not require quotes: - -``` -note: felt really strong today, hit a PR -``` - -### Item Naming Conventions - -The only rule is that they use no spaces, but we also recommend making them descriptive: - -`{weight-type}-{descriptor}-{movement}` - -`kb-oh-press` == Kettlebell Overhead Press -`bb-back-squat` == Barbell Back Squat - -### Comments - -Use `#` for standalone comments (ignored by parser): - -``` -# Week 1 - Deload -2025-11-14 * pullups: 20kg 5/5/5 - -# This is a note for myself -2025-11-15 * run: 5km -``` - -Comments are not stored as data. Use `note:` items if you want to preserve notes for analysis. - -## Examples - -See [example/example.ox](example/example.ox) for a complete training log example spanning multiple weeks with various exercises and rep schemes. - -## Editor Support - -A VSCode extension with syntax highlighting is available in `editors/vscode`. To install, symlink it to your VSCode extensions folder: +## Installation ```bash -ln -s /path/to/ox/editors/vscode ~/.vscode/extensions/ox -``` - -Then reload VSCode. See the [editor documentation](https://konnerhorton.github.io/ox/editor-support) for more details. - -## Data Structures - -The parser converts entries into Python dataclasses for analysis: - -```python -from ox import parse - -log = parse("training.ox") - -# Get all pullup sessions -for date, movement in log.movements("pullups"): - print(f"{date}: {movement.total_reps} reps @ {movement.top_set_weight}") +pip install ox ``` -See the [API Reference](api-reference.md) for complete details. - -## Development +From source: ```bash -# Clone the repository git clone https://github.com/konnerhorton/ox.git cd ox - -# Install in development mode pip install -e . - -# Run tests -pytest ``` -## License - -MIT License - see [LICENSE](LICENSE) file for details. - -## Contributing - -Contributions welcome! Please feel free to submit a Pull Request. +## Development -## Inspiration +```bash +uv sync +uv run pytest +uv run ruff check src/ tests/ +``` -This project draws inspiration from: +## License -- [Beancount](https://github.com/beancount/beancount) - Plain text accounting -- [Ledger](https://www.ledger-cli.org/) - Command line accounting tool -- Plain text workflows and future-proof data formats +MIT — see [LICENSE](LICENSE). diff --git a/SPEC.md b/SPEC.md index f5209d7..bc62dc8 100644 --- a/SPEC.md +++ b/SPEC.md @@ -5,8 +5,7 @@ A plain text training log format and toolchain. Write workouts in a `.ox` text file, parse them into structured data, analyze progress over time. Inspired by [Beancount](https://github.com/beancount/beancount) — plain text accounting, but for training. -A big guiding principle is that one should plan their training over the course of years and decades, not weeks and months. -So, you should be able to track progress over that time frame. +Designed for tracking progress over years and decades, not just weeks and months. ## Who is it for? @@ -15,41 +14,41 @@ Developers and power users comfortable with text files and CLIs. ## Core Principles -- **Plain text is the source of truth.** The `.ox` file is the canonical record. Everything else is derived from it. +- **Plain text is the source of truth.** The `.ox` file is the canonical record. Everything else is derived. - **No cloud, no accounts, no lock-in.** Your data is a text file on your filesystem. -- **Paper-like flexibility with software analytics.** The format should be as easy to write as scribbling in a notebook, but parseable for real analysis. +- **Paper-like flexibility with software analytics.** As easy to write as a notebook, but parseable for real analysis. - **Long-lived data.** A log written today should be readable in 20 years. Text outlasts apps. ## Current State ### What works today -- **Tree-sitter grammar** (`tree-sitter-ox/grammar.js`) parses `.ox` files into syntax trees -- **Python parser** (`src/ox/parse.py`) converts tree-sitter nodes into dataclasses -- **Data model** (`src/ox/data.py`) — `TrainingSet`, `Movement`, `TrainingSession`, `TrainingLog` -- **SQLite query layer** (`src/ox/db.py`) — in-memory DB with `sessions`, `movements`, `sets` tables and `training` view; arbitrary SQL via CLI +- **Tree-sitter grammar** (`tree-sitter-ox/grammar.js`) — parses `.ox` files into syntax trees; supports many mass units (kg, lb, oz, stone, grain, etc.), ISO 8601 durations, and distance units +- **Python parser** (`src/ox/parse.py`) — tree-sitter nodes → dataclasses +- **Data model** (`src/ox/data.py`) — `TrainingSet`, `Movement`, `TrainingSession`, `TrainingLog`, `Note`, `WeighIn`, `StoredQuery`, `Diagnostic` +- **SQLite query layer** (`src/ox/db.py`) — in-memory DB with `sessions`, `movements`, `sets`, `notes`, `session_notes`, `weigh_ins`, `queries` tables and `training` view - **Plugin system** (`src/ox/plugins.py`) — discovery from `~/.ox/plugins/`, entry points, and builtins; report and generator types -- **Reports** (`src/ox/reports.py`) — `volume` (volume over time) and `matrix` (session count per movement) with parameterized args; `get_all_reports()` merges builtin and plugin reports -- **Builtin e1rm plugin** — estimated 1RM via Brzycki/Epley formulas, filters on `^rm` note convention -- **Builtin wendler531 plugin** — Wendler 5/3/1 cycle generator -- **CLI** (`src/ox/cli.py`) — interactive REPL with `stats`, `history`, `report`, `generate`, `query`, `tables`, `reload` commands and tab completion -- **LSP** (`src/ox/lsp.py`) — basic diagnostics (syntax errors) for editor integration +- **Built-in reports** (`src/ox/reports.py`) — `volume` (volume over time) and `matrix` (session count per movement) +- **Built-in plugins** — `e1rm` (estimated 1RM via Brzycki/Epley), `weighin` (weight tracking with stats/plot/rolling average), `wendler531` (5/3/1 cycle generator) +- **CLI** (`src/ox/cli.py`) — interactive REPL with `stats`, `history`, `report`, `generate`, `query`, `tables`, `lint`, `reload` commands and tab completion +- **LSP** (`src/ox/lsp.py`) — diagnostics (syntax errors + include validation), movement name completion, comment folding ranges +- **Weigh-in tracking** — full pipeline: parse → `WeighIn` dataclass → DB → builtin report with table/plot/stats output +- **Notes** — standalone and session-level notes, parse → `Note` dataclass → DB, `to_ox()` round-trip +- **Stored queries** — named SQL queries embedded in `.ox` files, accessible via CLI `query` command +- **Include directives** — `@include "path.ox"` with recursive resolution and cycle detection +- **Lint** (`src/ox/lint.py`) — parse error collection for CLI and LSP - **VSCode extension** — syntax highlighting - **MkDocs documentation site** (`docs/`) - **Round-trip serialization** — `to_ox()` methods write data back to `.ox` format -- **Unit handling** — weights tracked as `pint.Quantity` (kg, lbs) +- **Unit handling** — weights tracked as `pint.Quantity` ### What's incomplete -- Weigh-in processing (`W` flag) — parsed by tree-sitter but not processed into data structures -- Planned sessions (`!` flag) — parsed but mostly ignored in analysis -- Exercise definitions (`@exercise` blocks) — parsed by tree-sitter but not used in analysis +- Planned sessions (`!` flag) — parsed but ignored in analysis +- Exercise definitions (`@exercise` blocks) — parsed but not used in analysis - Template blocks (`@template`) — grammar exists, no processing - Progressive implied weights (e.g. `160/185/210lbs`) — known parsing bug -- LSP only does diagnostics — no completions, hover, or go-to-definition -- Tree-sitter grammar only accepts `kg` and `lbs` — should accept any valid pint mass unit -- `pint.Quantity` should be used for time as well -- CLI exercise autocompletion (tab-complete exercise names from the log, not just commands) +- CLI exercise autocompletion (tab-complete exercise names, not just commands) ## Direction @@ -57,22 +56,17 @@ Developers and power users comfortable with text files and CLIs. - Cycle tracking — micro/meso/macro periodization - Exercise definitions feeding into analysis (e.g. grouping by movement pattern) -- Generator plugins (e.g. Wendler 5/3/1 cycle generation from a training max) - -### CLI - -- Exercise autocompletion (tab-complete exercise names from the log, not just commands) +- `pint.Quantity` for time/distance — enables derived units like pace and speed ### Better editor experience -- LSP completions (exercise names, session templates) -- Hover info (exercise definitions, recent history for a movement) +- LSP hover info (exercise definitions, recent history for a movement) +- LSP completions for session templates - Snippets for common entry patterns -- Broader unit support in the grammar (any valid pint mass unit) ## Non-Goals -- **Not a workout planner or coach.** Ox records and analyzes what you did. It doesn't tell you what to do. -- **Not a web app or mobile app.** It's a CLI tool and text format. Edit your `.ox` file however you want. +- **Not a workout planner or coach.** Ox records and analyzes. It doesn't prescribe. +- **Not a web app or mobile app.** CLI tool and text format. - **Not a social platform.** No sharing, leaderboards, or community features. -- **No proprietary format extensions.** The `.ox` format should stay simple enough to read with no tooling at all. +- **No proprietary format extensions.** The `.ox` format stays simple enough to read with no tooling. diff --git a/docs/LICENSE.md b/docs/LICENSE.md new file mode 100644 index 0000000..0e8c6fc --- /dev/null +++ b/docs/LICENSE.md @@ -0,0 +1,3 @@ +# License + +--8<-- "LICENSE" diff --git a/docs/api-reference.md b/docs/api-reference.md index 47c51be..a928711 100644 --- a/docs/api-reference.md +++ b/docs/api-reference.md @@ -4,18 +4,14 @@ icon: material/api # API Reference -Use ox as a Python library to parse and analyze your training logs. - ## Basic Usage ```python from pathlib import Path from ox.cli import parse_file -# Parse a training log file log = parse_file(Path("training.ox")) -# Access all sessions for session in log.sessions: print(f"{session.date}: {session.name}") for movement in session.movements: @@ -24,335 +20,127 @@ for session in log.sessions: ## Data Structures -### TrainingLog - -A collection of training sessions with query methods. - -**Attributes:** -- `sessions`: `tuple[TrainingSession, ...]` - All sessions in the log -- `notes`: `tuple[Note, ...]` - Top-level notes not attached to a session -- `diagnostics`: `tuple[Diagnostic, ...]` - Parse errors and warnings +All are frozen dataclasses with `slots=True`. -**Properties:** -- `completed_sessions`: sessions with flag `"*"` -- `planned_sessions`: sessions with flag `"!"` +### TrainingLog -**Methods:** -- `movements(name=None)` - Iterate over `(date, Movement)` pairs, optionally filtered by name -- `movement_history(name)` - Sorted list of `(date, Movement)` for a given exercise -- `most_recent_session(name)` - Most recent `(date, Movement)` for a given exercise +| Attribute | Type | +|---|---| +| `sessions` | `tuple[TrainingSession, ...]` | +| `notes` | `tuple[Note, ...]` | +| `diagnostics` | `tuple[Diagnostic, ...]` | -**Example:** -```python -print(len(log.completed_sessions)) # 45 -print(len(log.planned_sessions)) # 2 -print(len(log.notes)) # top-level notes +**Properties:** `completed_sessions`, `planned_sessions` -for date, movement in log.movements("squat"): - print(f"{date}: {movement.total_reps} reps") -``` +**Methods:** `movements(name=None)`, `movement_history(name)`, `most_recent_session(name)` ### TrainingSession -Represents a single workout session. - -**Attributes:** -- `date`: `datetime.date` - The date of the session -- `name`: `str | None` - Session name (e.g., "Upper Body"), `None` for single-line entries -- `flag`: `str` - Session flag (`"*"`, `"!"`, or `"W"`) -- `movements`: `tuple[Movement, ...]` - Exercises in this session -- `notes`: `tuple[Note, ...]` - Notes attached to this session - -**Example:** -```python -session = log.sessions[0] -print(session.date) # 2024-01-15 -print(session.name) # "Upper Body" -print(session.flag) # "*" -print(len(session.movements)) # 3 -print(session.notes) # () -``` +| Attribute | Type | +|---|---| +| `date` | `datetime.date` | +| `name` | `str \| None` | +| `flag` | `str` (`"*"`, `"!"`, `"W"`) | +| `movements` | `tuple[Movement, ...]` | +| `notes` | `tuple[Note, ...]` | ### Movement -Represents a single exercise/movement within a session. - -**Attributes:** -- `name`: `str` - Exercise name (e.g., `"squat"`, `"bench-press"`) -- `sets`: `list[TrainingSet]` - List of sets performed -- `note`: `str | None` - Movement-specific note - -**Properties:** -- `total_reps`: `int` - Total reps across all sets -- `top_set_weight`: `Quantity | None` - Heaviest weight used across all sets +| Attribute | Type | +|---|---| +| `name` | `str` | +| `sets` | `list[TrainingSet]` | +| `note` | `str \| None` | -**Methods:** -- `total_volume()` - Total volume (`reps × weight`) across all sets, or `None` for bodyweight movements -- `to_ox(compact_reps=False)` - Serialize back to `.ox` format +**Properties:** `total_reps`, `top_set_weight` -**Example:** -```python -movement = session.movements[0] -print(movement.name) # "squat" -print(movement.total_reps) # 25 -print(movement.top_set_weight) # 185 pound -print(movement.total_volume()) # 4625 pound -print(movement.note) # "felt heavy" -``` +**Methods:** `total_volume()`, `to_ox(compact_reps=False)` ### TrainingSet -Represents a single set of an exercise. +| Attribute | Type | +|---|---| +| `reps` | `int` | +| `weight` | `Quantity \| None` | -**Attributes:** -- `reps`: `int` - Number of repetitions -- `weight`: `Quantity | None` - Weight used (`None` means bodyweight) +**Properties:** `volume` (`reps × weight`, or `None` for BW) -**Properties:** -- `volume`: `Quantity | None` - `reps × weight`, or `None` for bodyweight sets +### WeighIn -**Example:** -```python -training_set = movement.sets[0] -print(training_set.reps) # 5 -print(training_set.weight) # 185 pound -print(training_set.weight.to('kg')) # 83.91 kilogram -print(training_set.volume) # 925 pound -``` +| Attribute | Type | +|---|---| +| `date` | `datetime.date` | +| `weight` | `Quantity` | +| `time_of_day` | `str \| None` | +| `scale` | `str \| None` | ### Note -Represents a note entry — either top-level (standalone) or attached to a session. +| Attribute | Type | +|---|---| +| `text` | `str` | +| `date` | `datetime.date \| None` | -**Attributes:** -- `text`: `str` - The note text -- `date`: `datetime.date | None` - Set for standalone `note_entry` lines; `None` for in-session notes +### StoredQuery -**Example:** -```python -# Top-level notes from the log -for note in log.notes: - print(f"{note.date}: {note.text}") - -# Session-level notes -for note in session.notes: - print(note.text) -``` +| Attribute | Type | +|---|---| +| `name` | `str` | +| `sql` | `str` | +| `date` | `datetime.date` | ### Diagnostic -Represents a parse error or warning found in the log file. - -**Attributes:** -- `line`: `int` - 1-based line number -- `col`: `int` - 0-based column -- `end_line`: `int` - 1-based end line -- `end_col`: `int` - 0-based end column -- `message`: `str` - Description of the error -- `severity`: `str` - `"error"` or `"warning"` - -**Example:** -```python -for diag in log.diagnostics: - print(f"Line {diag.line}, col {diag.col}: {diag.message}") -``` - -## Common Tasks - -### Filter by Exercise - -Get all instances of a specific exercise: - -```python -from pathlib import Path -from ox.cli import parse_file - -log = parse_file(Path("training.ox")) - -for date, movement in log.movements("squat"): - print(f"{date}: {len(movement.sets)} sets, {movement.total_reps} reps") -``` - -### Track Progress Over Time - -```python -from pathlib import Path -from ox.cli import parse_file - -log = parse_file(Path("training.ox")) - -squat_history = log.movement_history("squat") - -for date, movement in squat_history: - top = movement.top_set_weight - print(f"{date}: top set {top}") -``` - -### Calculate Volume - -```python -from pathlib import Path -from ox.cli import parse_file - -log = parse_file(Path("training.ox")) - -for session in log.completed_sessions: - volumes = [ - m.total_volume() - for m in session.movements - if m.total_volume() is not None - ] - if volumes: - total = sum(v.to('kg').magnitude for v in volumes) - print(f"{session.date}: {total:.1f} kg total volume") -``` +| Attribute | Type | +|---|---| +| `line` | `int` | +| `col` | `int` | +| `end_line` | `int` | +| `end_col` | `int` | +| `message` | `str` | +| `severity` | `str` (`"error"` or `"warning"`) | -### Working with Units +## Working with Units -ox uses [pint](https://pint.readthedocs.io/) for unit handling: +ox uses [pint](https://pint.readthedocs.io/) for weights: ```python -movement = log.sessions[0].movements[0] weight = movement.sets[0].weight -# Convert units -print(weight.to('kg')) # Convert to kilograms -print(weight.to('pound')) # Convert to pounds +weight.to('kg') # convert +weight.magnitude # numeric value +weight.units # unit object -# Get numeric value -print(weight.magnitude) # Just the number -print(weight.units) # Just the unit - -# Compare weights from ox.units import ureg if weight > 100 * ureg.pound: print("Heavy!") ``` -### Filter by Date Range - -```python -from datetime import date -from pathlib import Path -from ox.cli import parse_file - -log = parse_file(Path("training.ox")) - -start_date = date(2024, 1, 1) -end_date = date(2024, 1, 31) - -january_sessions = [ - s for s in log.sessions - if start_date <= s.date <= end_date -] - -print(f"Sessions in January: {len(january_sessions)}") -``` - -### Check for Parse Errors - -```python -from pathlib import Path -from ox.cli import parse_file - -log = parse_file(Path("training.ox")) - -if log.diagnostics: - for d in log.diagnostics: - print(f"Line {d.line}: {d.message} ({d.severity})") -else: - print("No parse errors.") -``` - -### Serialize Back to .ox Format - -Data structures support round-trip serialization: - -```python -from pathlib import Path -from ox.cli import parse_file - -log = parse_file(Path("training.ox")) - -# Serialize a session back to .ox format -session = log.sessions[0] -print(session.to_ox()) - -# Serialize a movement -movement = session.movements[0] -print(movement.to_ox()) -``` - -### Export to Dictionary - -```python -from pathlib import Path -from ox.cli import parse_file -import json - -log = parse_file(Path("training.ox")) - -data = [] -for session in log.sessions: - session_data = { - "date": session.date.isoformat(), - "name": session.name, - "flag": session.flag, - "movements": [ - { - "name": m.name, - "note": m.note, - "sets": [ - {"reps": s.reps, "weight": str(s.weight)} - for s in m.sets - ], - } - for m in session.movements - ], - } - data.append(session_data) - -with open("training_data.json", "w") as f: - json.dump(data, f, indent=2) -``` - -## Using the Database Layer +## Database Layer -For more complex queries, load your log into an in-memory SQLite database: +Load into SQLite for complex queries: ```python -from pathlib import Path -from ox.cli import parse_file from ox.db import create_db -log = parse_file(Path("training.ox")) conn = create_db(log) - -# Query total volume per week for a movement rows = conn.execute(""" - SELECT - date(date, '-' || strftime('%w', date) || ' days') AS week, - SUM(reps * weight_magnitude) AS volume + SELECT movement_name, SUM(reps * weight_magnitude) AS volume FROM training WHERE movement_name = 'squat' - GROUP BY week - ORDER BY week + GROUP BY movement_name """).fetchall() - -for week, volume in rows: - print(f"{week}: {volume:.1f}") - conn.close() ``` -See [Reports & Plugins](plugins.md) for built-in reports that use this layer. +**Tables:** `sessions`, `movements`, `sets`, `notes`, `session_notes`, `weigh_ins`, `queries` -## API Documentation +**Views:** `training` (denormalized join of sessions/movements/sets) -For complete source-level documentation, see: +## Round-trip Serialization -- `src/ox/data.py` - Core data structures (`TrainingLog`, `TrainingSession`, `Movement`, `TrainingSet`, `Note`, `Diagnostic`) -- `src/ox/parse.py` - Tree-sitter node processing -- `src/ox/cli.py` - `parse_file()` entry point -- `src/ox/db.py` - SQLite schema and `create_db()` -- `src/ox/units.py` - Pint unit registry (`ureg`) +```python +session.to_ox() # serialize session to .ox format +movement.to_ox() # serialize movement +note.to_ox() # serialize note +``` diff --git a/docs/cli-reference.md b/docs/cli-reference.md index 27e532c..59e6d57 100644 --- a/docs/cli-reference.md +++ b/docs/cli-reference.md @@ -4,391 +4,107 @@ icon: material/console # CLI Reference -Ox provides an interactive command-line interface for analyzing your training logs. - -## Installation - -The CLI is included when you install ox: - -```bash -pip install ox -``` - -## Basic Usage - -Start the interactive analyzer by providing your training log file: +## Usage ```bash ox training.ox ``` -You'll see a prompt like this: - -``` -Loading training.ox... -✓ Loaded 45 completed, 2 planned sessions - -Type 'help' for commands, 'exit' to quit - -ox> -``` - -If the file has parse errors, a warning is shown: - -``` -Warning: 2 parse error(s). Run 'lint' for details. -``` +Opens an interactive REPL. Parse errors are summarized on load — run `lint` for details. ## Commands ### `stats` -Show summary statistics for all exercises in your log. +Summary table of all exercises: session count, total reps, last session date. -**Usage:** ``` ox> stats ``` -**Output:** -``` -Training Statistics -Exercise Sessions Total Reps Last Session -squat 12 180 2024-01-20 -bench-press 10 150 2024-01-19 -kb-swing 8 600 2024-01-18 -pullup 15 300 2024-01-21 - -Completed sessions: 45 -Planned sessions: 2 -Unique exercises: 12 -``` - -### `history` +### `history EXERCISE` -Show detailed training history for a specific exercise. +Per-exercise history: date, sets/reps, top weight, volume. -**Usage:** -``` -ox> history EXERCISE -``` - -**Example:** ``` ox> history squat ``` -**Output:** -``` -History: squat -Date Sets × Reps Top Weight Volume -2024-01-15 5 + 5 + 5 + 5 + 5 135 lbs 3375 lb -2024-01-17 5 + 5 + 5 + 5 + 5 140 lbs 3500 lb -2024-01-19 5 + 5 + 5 + 5 + 5 145 lbs 3625 lb -``` - -**Notes:** -- Exercise names must match exactly (case-sensitive) -- Use the name as it appears in your log (e.g., `kb-swing`, not `kettlebell swing`) - -### `report` - -List available reports or run one. Reports query the SQLite database and return tabular results. - -**Usage:** -``` -ox> report # list available reports -ox> report REPORT_NAME [OPTIONS] # run a report -``` - -**Example — list reports:** -``` -ox> report - -Available Reports: - volume - Volume over time for a movement - Usage: report volume -m/--movement [-b/--bin ] - matrix - Session count per movement per time period - Usage: report matrix [-b/--bin ] - e1rm - Estimated 1RM progression for a movement - Usage: report e1rm -m/--movement [-f/--formula ] -``` +### `report [NAME [OPTIONS]]` -**Example — run `volume` report:** -``` -ox> report volume -m squat -ox> report volume --movement deadlift --bin monthly -``` +List or run reports. Reports query the SQLite database and return tables. -**Example — run `matrix` report:** -``` -ox> report matrix -ox> report matrix --bin monthly -``` - -**Example — run `e1rm` report:** ``` +ox> report # list available reports +ox> report volume -m squat --bin monthly ox> report e1rm -m deadlift -ox> report e1rm --movement squat --formula epley ``` -For details on each report, see [Reports & Plugins](plugins.md). +See [Reports & Plugins](plugins.md) for details. -### `generate` +### `generate [NAME [OPTIONS]]` -List available generators or run one. Generators produce `.ox` formatted text for planning sessions. +List or run generators. Generators produce `.ox` text for planning. -**Usage:** ``` -ox> generate # list available generators -ox> generate GENERATOR_NAME [OPTIONS] # run a generator +ox> generate # list available generators +ox> generate wendler531 -m squat:315,bench:225 ``` -Generator output can be pasted directly into your training log. - -For details on generators and how to install them, see [Reports & Plugins](plugins.md). - -### `query` +### `query SQL` -Run a raw SQL query against your training data. +Run raw SQL against the training database. Use named queries defined in your `.ox` file or write inline SQL. -**Usage:** -``` -ox> query SELECT ... -``` - -**Examples:** ``` ox> query SELECT * FROM sessions LIMIT 10 -ox> query SELECT movement_name, COUNT(*) as sessions FROM movements GROUP BY movement_name ORDER BY sessions DESC -ox> query SELECT date, weight_magnitude, reps FROM training WHERE movement_name = 'squat' ORDER BY date +ox> query SELECT movement_name, COUNT(*) FROM movements GROUP BY movement_name ``` -Use `tables` to see what tables and views are available. - -### `tables` +### `tables [-h]` -Show available tables and views in the SQLite database. +List database tables and views. Use `-h` to show column details. -**Usage:** ``` ox> tables -``` - -**Output:** -``` - movements (table) - sessions (table) - training (table) -``` - -### `reload` - -Reload the log file from disk without restarting. Use this after editing your training log. - -**Usage:** -``` -ox> reload -``` - -**Output:** -``` -Reloading training.ox... -✓ Loaded 46 completed, 2 planned sessions +ox> tables -h ``` ### `lint` Show parse errors in the log file. -**Usage:** ``` ox> lint ``` -**Output (no errors):** -``` -No parse errors found. -``` - -**Output (with errors):** -``` -Line 42, col 0: Syntax error -Line 87, col 12: Missing weight -``` - -Lint errors are also summarized on startup and after `reload`. - -### `help` +### `reload` -Display available commands. +Re-parse the log file from disk. -**Usage:** ``` -ox> help +ox> reload ``` -### `exit` or `quit` +### `help` -Exit the program. +Show available commands. -**Usage:** -``` -ox> exit -``` - -or +### `exit` / `quit` -``` -ox> quit -``` +Exit the REPL. `Ctrl+D` also works. ## Keyboard Shortcuts -- **Tab** - Auto-complete commands -- **Ctrl+C** - Cancel current input (doesn't exit) -- **Ctrl+D** - Exit the program -- **Up/Down arrows** - Navigate command history +| Key | Action | +|---|---| +| Tab | Auto-complete commands | +| Ctrl+C | Cancel current input | +| Ctrl+D | Exit | +| Up/Down | Command history | -## Command-Line Options - -### Version - -Show the installed version: - -```bash -ox --version -``` - -### Help - -Show command-line help: +## CLI Options ```bash -ox --help +ox --version # show version +ox --help # show help ``` - -**Output:** -``` -Usage: ox [OPTIONS] FILE - - Interactive training log analyzer. - - FILE: Path to training log file - -Options: - --version Show the version and exit. - --help Show this message and exit. -``` - -## Examples - -### Analyze Your Training Log - -```bash -$ ox ~/training/2024.ox -Loading /home/user/training/2024.ox... -✓ Loaded 156 completed, 4 planned sessions - -Type 'help' for commands, 'exit' to quit - -ox> stats -``` - -### Check Exercise Progression - -```bash -ox> history deadlift -``` - -### Query the Database Directly - -```bash -ox> query SELECT strftime('%Y-%m', date) as month, SUM(reps * weight_magnitude) as volume FROM training WHERE movement_name = 'squat' GROUP BY month -``` - -### Multiple Logs - -Want to analyze different time periods? Run ox with different files: - -```bash -# Analyze Q1 -ox training-q1-2024.ox - -# Analyze Q2 -ox training-q2-2024.ox -``` - -## Tips - -### Use Consistent Naming - -The CLI is case-sensitive and matches exercise names exactly: - -``` -squat ✓ matches -Squat ✗ different -squat-barbell ✗ different -``` - -Use consistent naming in your log for best results. - -### Tab Completion - -Press Tab to complete commands: - -``` -ox> sta[Tab] -ox> stats -``` - -### Quick Analysis Workflow - -1. Open your training log in your text editor -2. Add today's workout -3. Save the file -4. Run `ox training.ox` in a terminal -5. Use `reload` if you want to keep the session open and re-parse -6. Use `history` to check progress -7. Keep training! - -## Troubleshooting - -### File Not Found - -``` -Error: Invalid value for 'FILE': Path 'training.ox' does not exist. -``` - -**Solution:** Make sure the file path is correct: -```bash -# Use absolute path -ox /home/user/training.ox - -# Or relative path -cd ~/training -ox training.ox -``` - -### Parse Errors - -If the CLI warns about parse errors on load, run `lint` to see details: - -``` -ox> lint -Line 42, col 0: Syntax error -``` - -- Dates must be `YYYY-MM-DD` format -- Exercise names can't have spaces -- Sessions must have `@session` and `@end` tags - -See the [syntax documentation](index.md) for details. - -### No History Found - -``` -No history found for 'sqat' -``` - -**Solution:** Check your spelling. Exercise names must match exactly. Use `stats` to see all exercise names in your log. diff --git a/docs/editor-support.md b/docs/editor-support.md index fae82d0..d0dcdc5 100644 --- a/docs/editor-support.md +++ b/docs/editor-support.md @@ -4,118 +4,39 @@ icon: material/puzzle # Editor Support -## VSCode +## VSCode Extension -A VSCode extension providing syntax highlighting for `.ox` files is included in the repository at `editors/vscode`. +Syntax highlighting for `.ox` files. Located in `editors/vscode`. -### Installation - -VSCode extensions must be installed globally. Symlink the extension to your VSCode extensions folder: +### Install ```bash ln -s /path/to/ox/editors/vscode ~/.vscode/extensions/ox ``` -Then reload VSCode (`Ctrl+Shift+P` → "Developer: Reload Window"). +Reload VSCode (`Ctrl+Shift+P` → "Developer: Reload Window"). ### Features -- Syntax highlighting for all Ox constructs: - - Dates, flags, and session names - - Exercise names and metadata keys - - Weights, rep schemes, time, and distance values - - Quoted strings and comments -- Language configuration with: - - Comment toggling (`#`) - - Auto-closing quotes - - Code folding for `@session` and `@exercise` blocks - -### Development Mode - -If you're developing the extension, you can run it in debug mode instead of symlinking. Create `editors/vscode/.vscode/launch.json`: - -```json -{ - "version": "0.2.0", - "configurations": [ - { - "name": "Run Extension", - "type": "extensionHost", - "request": "launch", - "args": ["--extensionDevelopmentPath=${workspaceFolder}"] - } - ] -} -``` - -Open `editors/vscode` as a workspace and press F5 to launch a new VSCode window with the extension loaded. +- Syntax highlighting (dates, flags, weights, reps, comments, strings) +- Comment toggling (`#`) +- Comment section folding +- Auto-closing quotes ## Language Server (LSP) -Ox ships with a Language Server Protocol implementation (`ox-lsp`) that provides real-time diagnostics in any LSP-compatible editor. - -### What it provides - -- **Diagnostics** — syntax errors are underlined as you type, on save, and on open - -### Starting the server - -The `ox-lsp` command is installed alongside `ox`: - -```bash -ox-lsp -``` - -The server communicates over stdio and is typically launched automatically by your editor's LSP client. - -### VSCode +`ox-lsp` provides real-time editor integration via stdio. -Add the following to your VSCode `settings.json` to enable the language server alongside the syntax-highlighting extension: - -```json -{ - "languageServerExample.trace.server": "verbose" -} -``` - -Or configure it via a generic LSP client extension (e.g., `vscode-languageclient`) pointing to `ox-lsp`. - -### Neovim (with nvim-lspconfig) - -```lua -local lspconfig = require('lspconfig') -local configs = require('lspconfig.configs') - -if not configs.ox_lsp then - configs.ox_lsp = { - default_config = { - cmd = { 'ox-lsp' }, - filetypes = { 'ox' }, - root_dir = function(fname) - return lspconfig.util.find_git_ancestor(fname) or vim.fn.getcwd() - end, - settings = {}, - }, - } -end - -lspconfig.ox_lsp.setup {} -``` - -### Helix +### Features -Add to your `languages.toml`: +- **Diagnostics** — syntax errors and invalid `@include` paths +- **Completions** — movement name autocomplete +- **Folding** — collapse comment sections -```toml -[[language]] -name = "ox" -file-types = ["ox"] -language-servers = ["ox-lsp"] +### Editor Configuration -[language-server.ox-lsp] -command = "ox-lsp" -``` +**VSCode** — use a generic LSP client extension pointing to `ox-lsp`. -## Other Editors +## Tree-sitter Grammar -A tree-sitter grammar is available in `tree-sitter-ox` which can be used to add Ox support to any editor with tree-sitter integration (Neovim, Helix, Emacs, etc.). +Available in `tree-sitter-ox/` for editors with tree-sitter support. diff --git a/docs/getting-started.md b/docs/getting-started.md index 0a9a3cc..3877d3e 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -4,137 +4,122 @@ icon: material/rocket-launch # Getting Started -This guide will help you start tracking your training with ox. - ## Installation -Install via pip: - ```bash pip install ox ``` -## Your First Training Log +## Your First Log -Create a file called `training.ox` and add your first workout: +Create `training.ox`: ``` -2024-01-15 * squat: 135lbs 5x5 +2024-01-15 * squat: 135lb 5x5 ``` -That's it! You've logged your first exercise. +Run it: -## Adding More Detail +```bash +ox training.ox +ox> stats +ox> history squat +``` -### Single-line entries +## Syntax -Log quick workouts or single exercises: +### Single-line entries ``` -2024-01-15 * squat: 135lbs 5x5 -2024-01-15 * bench-press: 135lbs 5x5 -2024-01-15 * run: 5km 25min +2024-01-15 * squat: 135lb 5x5 +2024-01-15 * run: 5km PT25M +2024-01-15 W 185lb T06:30 "home" +2024-01-15 note "deload week" ``` -### Multi-exercise sessions - -Group related exercises together: +### Session blocks ``` @session 2024-01-16 * Upper Body -bench-press: 135lbs 5x5 -overhead-press: 95lbs 3x8 +bench-press: 135lb 5x5 +overhead-press: 95lb 3x8 pullup: BW 4x10 -note: felt strong today +note: "felt strong today" @end ``` -## Understanding the Syntax +### Entry types + +- `*` — completed +- `!` — planned +- `W` — weigh-in +- `note` — freeform note +- `query` — stored SQL query + +### Weights -### Date Format -Always use `YYYY-MM-DD` format: ``` -2024-01-15 ✓ correct -01/15/2024 ✗ wrong -15-01-2024 ✗ wrong +135lb pounds +24kg kilograms +BW bodyweight +24kg+32kg combined (two bells) +135lb/155lb/175lb progressive (per-set) ``` -### Flags -- `*` = Completed (what you actually did) -- `!` = Planned (what you intend to do) -- `W` = Weigh-in (body weight measurement) +### Reps -### Exercise Names -Use descriptive names with no spaces: ``` -squat ✓ -bench-press ✓ -kb-swing ✓ (kettlebell swing) -bb-deadlift ✓ (barbell deadlift) -bench press ✗ (has space) +5x5 5 sets of 5 reps +5/3/1 3 sets with different reps +10/8/6/4/2 pyramid ``` -### Weight Formats +### Exercise names + +No spaces — hyphens are common but any non-space format works: + ``` -135lbs pounds -24kg kilograms -BW bodyweight -24kg+32kg combined weights (two bells) -135lbs/155lbs/175lbs progressive weights +squat kb-swing bb-deadlift +bench-press kb-oh-press bb-back-squat ``` -### Rep Schemes +### Includes + +Split logs across files: + ``` -5x5 5 sets of 5 reps -5/5/5 3 sets of 5 reps -10/8/6/4/2 descending reps (pyramid) +@include "upper.ox" +@include "lower.ox" ``` -## Complete Example - -Here's a week of training: +## Example ``` -# Week 1 - Starting Strength +# Week 1 @session 2024-01-15 * Lower Body -squat: 135lbs 5x5 -deadlift: 185lbs 3x5 -box-jump: BW 3x5 +squat: 135lb 5x5 +deadlift: 185lb 3x5 @end -2024-01-16 * run: 5km 28min "felt good" +2024-01-16 * run: 5km PT28M "felt good" @session 2024-01-17 * Upper Body -bench-press: 135lbs 5x5 -overhead-press: 95lbs 3x8 +bench-press: 135lb 5x5 +overhead-press: 95lb 3x8 pullup: BW 4x10 @end -@session -2024-01-18 * KB Workout -kb-swing: 32kg 5x15 -kb-snatch: 24kg 5x5 "each arm" -kb-clean-and-press: 24kg 5x3 "each arm" -@end - -@session -2024-01-19 * Lower Body -squat: 140lbs 5x5 "felt heavier than Monday" -deadlift: 190lbs 3x5 -box-jump: BW 3x5 -@end - -2024-01-20 * rest-day: "active recovery walk" +2024-01-17 W 185lb T06:30 "home" ``` ## Next Steps -- Check out the [full syntax documentation](index.md) for all features -- See [example.ox](https://github.com/konnerhorton/ox/blob/master/example/example.ox) for a complete training log -- Explore [CLI commands](cli-reference.md) for working with logs -- Learn about [built-in reports and the plugin system](plugins.md) -- Use the [Python API](api-reference.md) for custom analysis +- [CLI Reference](cli-reference.md) — commands and usage +- [Reports & Plugins](plugins.md) — built-in analysis and extending ox +- [API Reference](api-reference.md) — Python library +- [Editor Support](editor-support.md) — syntax highlighting and LSP +- [example.ox](https://github.com/konnerhorton/ox/blob/main/example/example.ox) — full reference log diff --git a/docs/markdown.md b/docs/markdown.md deleted file mode 100644 index 7ea6d1e..0000000 --- a/docs/markdown.md +++ /dev/null @@ -1,98 +0,0 @@ ---- -icon: simple/markdown ---- - -# Markdown in 5min - -## Headers -``` -# H1 Header -## H2 Header -### H3 Header -#### H4 Header -##### H5 Header -###### H6 Header -``` - -## Text formatting -``` -**bold text** -*italic text* -***bold and italic*** -~~strikethrough~~ -`inline code` -``` - -## Links and images -``` -[Link text](https://example.com) -[Link with title](https://example.com "Hover title") -![Alt text](image.jpg) -![Image with title](image.jpg "Image title") -``` - -## Lists -``` -Unordered: -- Item 1 -- Item 2 - - Nested item - -Ordered: -1. First item -2. Second item -3. Third item -``` - -## Blockquotes -``` -> This is a blockquote -> Multiple lines ->> Nested quote -``` - -## Code blocks -```` -```javascript -function hello() { - console.log("Hello, world!"); -} -``` -```` - -## Tables -``` -| Header 1 | Header 2 | Header 3 | -|----------|----------|----------| -| Row 1 | Data | Data | -| Row 2 | Data | Data | -``` - -## Horizontal rule -``` ---- -or -*** -or -___ -``` - -## Task lists -``` -- [x] Completed task -- [ ] Incomplete task -- [ ] Another task -``` - -## Escaping characters -``` -Use backslash to escape: \* \_ \# \` -``` - -## Line breaks -``` -End a line with two spaces -to create a line break. - -Or use a blank line for a new paragraph. -``` \ No newline at end of file diff --git a/docs/plugins.md b/docs/plugins.md index 25f210b..0e63550 100644 --- a/docs/plugins.md +++ b/docs/plugins.md @@ -4,157 +4,114 @@ icon: material/puzzle-edit # Reports & Plugins -Ox has a plugin system for extending analysis and training plan generation. Plugins come in two types: - -- **Reports** — query the SQLite database and return tabular results -- **Generators** — accept parameters and return `.ox` formatted text for planning +Two plugin types: **reports** (query DB, return tables) and **generators** (produce `.ox` text). ## Built-in Reports -These reports are included with ox and available immediately. - ### `volume` -Volume over time for a single movement. - -**CLI usage:** -``` -ox> report volume -m MOVEMENT [-b BIN] -``` - -**Parameters:** -- `-m` / `--movement` *(required)* — exercise name to filter by -- `-b` / `--bin` *(default: `weekly`)* — time bin: `daily`, `weekly`, `weekly-num`, or `monthly` +Volume over time for a movement. -**Examples:** ``` ox> report volume -m squat -ox> report volume --movement deadlift --bin monthly +ox> report volume -m deadlift --bin monthly --unit kg ``` -**Output columns:** `period`, `total_volume`, `total_reps`, `avg_weight_per_rep` +| Param | Default | Options | +|---|---|---| +| `-m/--movement` | *required* | exercise name | +| `-b/--bin` | `weekly` | `daily`, `weekly`, `weekly-num`, `monthly` | +| `-u/--unit` | `lb` | any mass unit | ### `matrix` -Session count per movement per time period. Rows are time periods; columns are movement names sorted by frequency. +Session count per movement per time period. -**CLI usage:** -``` -ox> report matrix [-b BIN] -``` - -**Parameters:** -- `-b` / `--bin` *(default: `weekly`)* — time bin: `daily`, `weekly`, `weekly-num`, or `monthly` - -**Examples:** ``` ox> report matrix ox> report matrix --bin monthly ``` -**Output columns:** `period`, then one column per movement name +| Param | Default | Options | +|---|---|---| +| `-b/--bin` | `weekly` | `daily`, `weekly`, `weekly-num`, `monthly` | ### `e1rm` -Estimated one-rep max (1RM) progression for a movement over time. - -Only considers sets where the movement note contains `^rm` — this is the convention for marking max-effort sets. +Estimated 1RM progression. Only uses sets with `^rm` in the note. -**CLI usage:** -``` -ox> report e1rm -m MOVEMENT [-f FORMULA] -``` - -**Parameters:** -- `-m` / `--movement` *(required)* — exercise name -- `-f` / `--formula` *(default: `brzycki`)* — formula to use: `brzycki` or `epley` - -**Examples:** ``` ox> report e1rm -m deadlift -ox> report e1rm --movement squat --formula epley +ox> report e1rm -m squat --formula epley --output plot ``` -**Output columns:** `date`, `estimated_1rm`, `weight`, `reps`, `unit` - -**Marking max-effort sets in your log:** +| Param | Default | Options | +|---|---|---| +| `-m/--movement` | *required* | exercise name | +| `-f/--formula` | `brzycki` | `brzycki`, `epley` | +| `-u/--unit` | `lb` | any mass unit | +| `-o/--output` | `table` | `table`, `plot` | -Add `^rm` anywhere in the movement note: +Mark max-effort sets in your log with `^rm`: ``` deadlift: 315lb 1x3 "^rm top set" -squat: 225lb 5x1 "^rm — new training max" ``` -## Example Generator +### `weighin` -An example generator plugin for Wendler 5/3/1 cycles is included in `examples/plugins/wendler531.py`. It is not installed by default — see [Installing Plugins](#installing-plugins) below. +Body weight tracking with statistics and trend analysis. -### `wendler531` - -Generates a 4-week Wendler 5/3/1 cycle as `.ox` planned sessions. - -**CLI usage (after installing):** ``` -ox> generate wendler531 -m MOVEMENT -t TRAINING_MAX [-u UNIT] [-d START_DATE] +ox> report weighin +ox> report weighin --output plot --window 14 +ox> report weighin --output stats ``` -**Parameters:** -- `-m` / `--movement` *(required)* — movement name (e.g., `squat`) -- `-t` / `--training_max` *(required)* — training max weight (numeric) -- `-u` / `--unit` *(default: `lbs`)* — weight unit: `lbs` or `kg` -- `-d` / `--start_date` *(default: today)* — start date as `YYYY-MM-DD` +| Param | Default | Options | +|---|---|---| +| `-u/--unit` | `lb` | any mass unit | +| `-o/--output` | `table` | `table`, `plot`, `stats` | +| `-w/--window` | `7` | rolling average window (days) | -**Example:** -``` -ox> generate wendler531 -m squat -t 315 -ox> generate wendler531 -m bench-press -t 200 -u lbs -d 2026-03-01 -``` +Supports multiple scales — `stats` output shows per-scale breakdowns. + +## Built-in Generator + +### `wendler531` -**Output:** Valid `.ox` text with `!` (planned) sessions that you can paste into your log: +Generates a 4-week Wendler 5/3/1 cycle as planned sessions. ``` -# Wendler 5/3/1 — squat (TM: 315lbs) - -@session -2026-03-01 ! 5s Week -squat: 205lbs 1x5 -squat: 235lbs 1x5 -squat: 270lbs 1x5 -@end - -@session -2026-03-08 ! 3s Week -... +ox> generate wendler531 -m squat:315,bench:225 +ox> generate wendler531 -m deadlift:405 --unit kg --start-date 2026-03-01 ``` -## Installing Plugins - -### Personal scripts (`~/.ox/plugins/`) +| Param | Default | Options | +|---|---|---| +| `-m/--movements` | *required* | `name:max` pairs, comma-separated | +| `-u/--unit` | `lb` | `lb`, `kg` | +| `-d/--start-date` | today | `YYYY-MM-DD` | +| `-r/--rm` | `true` | `true`, `false` — tag sets with `^rm` | -Place any `.py` file in `~/.ox/plugins/`. It will be loaded automatically on startup. +## Installing Plugins -To install the bundled Wendler 5/3/1 example: +### Personal scripts -```bash -mkdir -p ~/.ox/plugins -cp /path/to/ox/examples/plugins/wendler531.py ~/.ox/plugins/ -``` +Place `.py` files in `~/.ox/plugins/` — loaded automatically. -### Installable packages (entry points) +### Entry points -Plugins can also be distributed as Python packages that register via the `ox.plugins` entry point group. In your `pyproject.toml`: +Distribute as a Python package with an `ox.plugins` entry point: ```toml [project.entry-points."ox.plugins"] my_plugin = "my_package.my_module" ``` -The referenced module must export a `register()` function (see below). - ## Writing a Plugin -A plugin is a Python module that exports a `register()` function returning a list of plugin descriptors. +Export a `register()` function returning a list of descriptors. ### Report plugin @@ -168,19 +125,16 @@ def my_report(conn: sqlite3.Connection, movement: str) -> tuple[list[str], list[ ).fetchall() return ["date", "total_reps"], rows - def register(): - return [ - { - "type": "report", - "name": "my-report", - "fn": my_report, - "description": "Total reps per day for a movement", - "params": [ - {"name": "movement", "type": str, "required": True, "short": "m"}, - ], - } - ] + return [{ + "type": "report", + "name": "my-report", + "fn": my_report, + "description": "Total reps per day", + "params": [ + {"name": "movement", "type": str, "required": True, "short": "m"}, + ], + }] ``` ### Generator plugin @@ -190,44 +144,42 @@ def my_generator(movement: str, sets: int = 5) -> str: from datetime import date today = date.today().strftime("%Y-%m-%d") lines = ["@session", f"{today} ! Generated Session"] - for _ in range(sets): - lines.append(f"{movement}: BW 1x10") + lines += [f"{movement}: BW 1x10" for _ in range(sets)] lines.append("@end") return "\n".join(lines) - def register(): - return [ - { - "type": "generator", - "name": "my-generator", - "fn": my_generator, - "description": "Generate a bodyweight session", - "params": [ - {"name": "movement", "type": str, "required": True, "short": "m"}, - {"name": "sets", "type": int, "required": False, "default": 5, "short": "s"}, - ], - } - ] + return [{ + "type": "generator", + "name": "my-generator", + "fn": my_generator, + "description": "Bodyweight session generator", + "params": [ + {"name": "movement", "type": str, "required": True, "short": "m"}, + {"name": "sets", "type": int, "required": False, "default": 5, "short": "s"}, + ], + }] ``` -### Plugin descriptor fields +### Descriptor fields + +**Plugin:** | Field | Required | Description | |---|---|---| | `type` | yes | `"report"` or `"generator"` | -| `name` | yes | Name used in the CLI (`report NAME` or `generate NAME`) | -| `fn` | yes | Callable that implements the plugin | -| `description` | yes | Short description shown in listings | -| `params` | yes | List of parameter descriptors (see below) | +| `name` | yes | CLI name | +| `fn` | yes | Callable | +| `description` | yes | Short description | +| `params` | yes | Parameter descriptors | | `needs_db` | no | If `True`, generator receives `conn` as first arg | -### Parameter descriptor fields +**Parameter:** | Field | Required | Description | |---|---|---| -| `name` | yes | Parameter name (used as `--name` flag) | -| `type` | yes | Python type to cast the value to (e.g., `str`, `int`) | -| `required` | yes | Whether the parameter is required | -| `default` | no | Default value if not provided | -| `short` | no | Single-character short flag (e.g., `"m"` → `-m`) | +| `name` | yes | `--name` flag | +| `type` | yes | Python type (`str`, `int`, etc.) | +| `required` | yes | Whether required | +| `default` | no | Default value | +| `short` | no | Single-char short flag | diff --git a/mkdocs.yml b/mkdocs.yml index 9f07a8d..820b2a4 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -2,6 +2,12 @@ site_name: ox site_url: https://konnerhorton.github.io/ox theme: name: material + palette: + - scheme: default + primary: deep purple + accent: amber + icon: + logo: material/dumbbell nav: - Home: index.md