Initial commit

This commit is contained in:
Connor Johnstone committed 2025-12-30 15:50:49 -05:00
commit 99d4903d81
13 files changed
+328

No files matched your search

+19
View File
@@ -0,0 +1,19 @@
.idea
build
**/runinfo
**/__pycache__
**/htmlcov
.venv
test/report.txt
test/coverage.txt
**/dist
**/*.egg-info
**/*.eggs
.coverage
coverage.xml
CLAUDE.md
public/
*.log
*.hd5
*.h5
+1
View File
@@ -0,0 +1 @@
3.12
+23
View File
@@ -0,0 +1,23 @@
.PHONY: install install-dev test test-cov clean
install:
pip install -e .
install-dev:
pip install -e ".[dev]"
test:
pytest
test-cov:
pytest --cov=stack --cov-report=term-missing
clean:
rm -rf build/
rm -rf dist/
rm -rf *.egg-info/
rm -rf src/*.egg-info/
rm -rf .pytest_cache/
rm -rf .coverage
rm -rf htmlcov/
find . -type d -name __pycache__ -exec rm -rf {} +
+52
View File
@@ -0,0 +1,52 @@
[build-system]
requires = ["setuptools>=61.0", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = "stack"
version = "0.1.0"
description = "A command line application"
readme = "README.md"
license = {text = "MIT"}
requires-python = ">=3.12"
classifiers = [
"Development Status :: 3 - Alpha",
"Environment :: Console",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
]
dependencies = [
"PyYAML>=6.0",
"pydantic>=2.0",
]
[project.optional-dependencies]
dev = [
"pytest>=8.0",
"pytest-cov>=4.0",
]
[project.scripts]
stack-builder = "builder.cli:main"
stack-parser = "parser.cli:main"
[tool.setuptools.packages.find]
where = ["src"]
[tool.pytest.ini_options]
testpaths = ["tests"]
pythonpath = ["src"]
addopts = "-v --tb=short"
[tool.coverage.run]
source = ["src/builder", "src/parser", "src/language"]
branch = true
[tool.coverage.report]
exclude_lines = [
"pragma: no cover",
"if __name__ == .__main__.:",
]
+3
View File
@@ -0,0 +1,3 @@
""" The builder application for the stack workout "language" """
__version__ = "0.1.0"
+6
View File
@@ -0,0 +1,6 @@
"""Entry point for running the package as a module: python -m builder."""
from builder.cli import main
if __name__ == "__main__":
main()
+33
View File
@@ -0,0 +1,33 @@
"""Command line interface for stack."""
import argparse
import sys
from builder import __version__
def create_parser() -> argparse.ArgumentParser:
"""Create and configure the argument parser."""
parser = argparse.ArgumentParser(
prog="stack",
description="A command line application",
)
parser.add_argument(
"-v", "--version",
action="version",
version=f"%(prog)s {__version__}",
)
return parser
def main(argv: list[str] | None = None) -> int:
"""Main entry point for the CLI."""
parser = create_parser()
args = parser.parse_args(argv)
print("Hello from stack!")
return 0
if __name__ == "__main__":
sys.exit(main())
+110
View File
@@ -0,0 +1,110 @@
"""Language module for workout definition configuration."""
__version__ = "0.1.0"
import yaml
from enum import Enum
from typing import Collection
from pydantic import BaseModel, computed_field
class Sport(Enum):
"""You need to know the sport to know how to plan for different goals. For now we'll just do powerlifting."""
POWERLIFTING = 1
class LiftGoal(Enum):
"""Different set/rep schemes for different goals."""
STRENGTH = 1
VOLUME = 2
class Lift(Enum):
"""Not an exhaustive list yet"""
SQUAT = 1
HIGH_BAR_SQUAT = 2
PAUSE_SQUAT = 3
BENCH = 4
CLOSE_GRIP_BENCH = 5
LARSEN_PRESS = 6
DEADLIFT = 7
PAUSE_DEADLIFT = 8
HALT_DEADLIFT = 9
def intensity_from_rpe_reps(rpe: float, reps: int) -> float:
"""Calculate suggested intensity percentage from planned RPE and planned reps"""
rpe_dimension = [5.0, 5.5, 6.0, 6.5, 7.0, 7.5, 8.0, 8.5, 9.0, 9.5, 10.0]
reps_dimension = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
grid = [
[100, 95.5, 92.2, 89.2, 86.3, 83.7, 81.1, 78.6, 76.2, 73.9],
[97.8, 93.9, 90.7, 87.8, 85.0, 82.4, 79.9, 77.4, 75.1, 72.3],
[95.5, 92.2, 89.2, 86.3, 83.7, 81.1, 78.6, 76.2, 73.9, 70.7],
[93.9, 90.7, 87.8, 85.0, 82.4, 79.9, 77.4, 75.1, 72.3, 69.4],
[92.2, 89.2, 86.3, 83.7, 81.1, 78.6, 76.2, 73.9, 70.7, 68.0],
[90.7, 87.8, 85.0, 82.4, 79.9, 77.4, 75.1, 72.3, 69.4, 66.7],
[89.2, 86.3, 83.7, 81.1, 78.6, 76.2, 73.9, 70.7, 68.0, 65.3],
[87.8, 85.0, 82.4, 79.9, 77.4, 75.1, 72.3, 69.4, 66.7, 64.0],
[86.3, 83.7, 81.1, 78.6, 76.2, 73.9, 70.7, 68.0, 65.3, 62.6],
[85.0, 82.4, 79.9, 77.4, 75.1, 72.3, 69.4, 66.7, 64.0, 61.3],
[83.7, 81.1, 78.6, 76.2, 73.9, 70.7, 68.0, 65.3, 62.6, 60.0],
]
rpe_index = rpe_dimension.index(rpe)
reps_index = reps_dimension.index(reps)
return grid[reps_index][rpe_index]
class Plan(BaseModel):
"""Class representing a workout plan."""
# Fields
squat_plan: list[LiftGoal]
bench_plan: list[LiftGoal]
deadlift_plan: list[LiftGoal]
volume_day_amount: int
strength_day_amount: int
squat_1rm: int
bench_1rm: int
deadlift_1rm: int
# Fields with defaults
sport: Sport = Sport.POWERLIFTING
num_weeks: int = 4
num_workouts: int = 4
# Internal constants
_RPE_TARGETS = {
3: [7.0, 8.0, 9.0],
4: [7.0, 8.0, 8.5, 9.0],
5: [7.0, 7.5, 8.0, 8.5, 9.0],
}
@computed_field
def rpe_targets(self) -> list[float]:
"""Get RPE targets based on number of sets"""
return self._RPE_TARGETS[self.volume_day_amount]
def generate_week(self) -> list[list[tuple[Lift, LiftGoal]]]:
"""
Generate the list of workouts for the plan
So we have a specified number of each lift to get in per week (including their "goal", so whether it's a volume or strength day). On top of this we have a specified number of total workouts per week. We need to distribute the lifts across the workouts as evenly as possible for a given week.
"""
workouts: list[list[tuple[Lift, LiftGoal]]] = [[] for _ in range(self.num_workouts)]
lift_plans = {
Lift.SQUAT: self.squat_plan,
Lift.BENCH: self.bench_plan,
Lift.DEADLIFT: self.deadlift_plan,
}
workout_index = 0
for lift, goals in lift_plans.items():
for goal in goals:
workouts[workout_index].append((lift, goal))
workout_index = (workout_index + 1) % self.num_workouts
return workouts
+3
View File
@@ -0,0 +1,3 @@
""" The parser application for the stack workout "language" """
__version__ = "0.1.0"
+6
View File
@@ -0,0 +1,6 @@
""" Entry point for parsing a stack file: python -m parser """
from parser.cli import main
if __name__ == "__main__":
main()
+33
View File
@@ -0,0 +1,33 @@
"""Command line interface for stack."""
import argparse
import sys
from parser import __version__
def create_parser() -> argparse.ArgumentParser:
"""Create and configure the argument parser."""
parser = argparse.ArgumentParser(
prog="stack",
description="A command line application",
)
parser.add_argument(
"-v", "--version",
action="version",
version=f"%(prog)s {__version__}",
)
return parser
def main(argv: list[str] | None = None) -> int:
"""Main entry point for the CLI."""
parser = create_parser()
args = parser.parse_args(argv)
print("Hello from stack!")
return 0
if __name__ == "__main__":
sys.exit(main())
+1
View File
@@ -0,0 +1 @@
"""Tests for stack."""
+38
View File
@@ -0,0 +1,38 @@
"""Tests for the CLI module."""
from builder import cli as bcli
from parser import cli as pcli
def test_builder_version_flag(capsys):
"""Test that --version prints version info."""
parser = bcli.create_parser()
try:
parser.parse_args(["--version"])
except SystemExit:
pass
captured = capsys.readouterr()
assert "stack" in captured.out
def test_builder_main_returns_zero():
"""Test that main returns 0 on success."""
result = bcli.main([])
assert result == 0
def test_parser_version_flag(capsys):
"""Test that --version prints version info."""
parser = pcli.create_parser()
try:
parser.parse_args(["--version"])
except SystemExit:
pass
captured = capsys.readouterr()
assert "stack" in captured.out
def test_parser_main_returns_zero():
"""Test that main returns 0 on success."""
result = pcli.main([])
assert result == 0