diff options
Diffstat (limited to 'egometrics.py')
| -rw-r--r-- | egometrics.py | 578 |
1 files changed, 8 insertions, 570 deletions
diff --git a/egometrics.py b/egometrics.py index d882a59..f45fa6d 100644 --- a/egometrics.py +++ b/egometrics.py @@ -1,11 +1,17 @@ import sqlite3 from db import get_connection, init_db -import models -import ui +from screens import ( + log_workout, + manage_workout_exercises, + manage_workout_templates, + view_workout_sessions, +) def main_menu(conn: sqlite3.Connection) -> None: + import ui + while True: ui.clear_screen() print("=== EgoMetrics ===\n") @@ -27,574 +33,6 @@ def main_menu(conn: sqlite3.Connection) -> None: break -# --- Workout Exercises --- - - -def manage_workout_exercises(conn: sqlite3.Connection) -> None: - while True: - ui.clear_screen() - ui.print_header("Manage Workout Exercises") - print("1. List Exercises") - print("2. Add Exercise") - print("3. Edit Exercise") - print("4. Delete Exercise") - print("5. Back") - choice = input("\n> ").strip() - if choice == "1": - list_workout_exercises(conn) - elif choice == "2": - add_workout_exercise(conn) - elif choice == "3": - edit_workout_exercise(conn) - elif choice == "4": - delete_workout_exercise(conn) - elif choice == "5": - break - - -def list_workout_exercises(conn: sqlite3.Connection, pause: bool = True) -> None: - exercises = models.list_workout_exercises(conn) - ui.print_header("All Workout Exercises") - ui.print_table( - ["ID", "Name", "BW?", "Note"], - [ - [ - str(e["id"]), - e["name"], - "Yes" if e["bw_relative"] else "No", - e["note"] or "", - ] - for e in exercises - ], - ) - if pause: - ui.pause() - - -def add_workout_exercise(conn: sqlite3.Connection) -> None: - name = ui.prompt_str("Name: ") - assert name is not None - bw_relative = ui.confirm("Is weight relative to body weight?") - note = ui.prompt_str("Note (optional): ", required=False) - try: - models.add_workout_exercise(conn, name, bw_relative, note) - print(f'Exercise "{name}" added.') - except sqlite3.IntegrityError: - print(f'Exercise "{name}" already exists.') - ui.pause() - - -def edit_workout_exercise(conn: sqlite3.Connection) -> None: - list_workout_exercises(conn, pause=False) - eid = ui.prompt_int("\nExercise ID to edit: ") - assert eid is not None - ex = models.get_workout_exercise(conn, eid) - if not ex: - print("Exercise not found.") - return - print(f'Editing "{ex["name"]}" (leave blank to keep current value)') - name = ui.prompt_str(f"Name [{ex['name']}]: ", required=False) - bw_cur = "Yes" if ex["bw_relative"] else "No" - print(f"BW-relative [{bw_cur}]: ", end="") - bw_input = input().strip().lower() - if bw_input in ("y", "yes"): - bw_relative: bool | None = True - elif bw_input in ("n", "no"): - bw_relative = False - else: - bw_relative = None # keep current - note = ui.prompt_str(f"Note [{ex['note'] or ''}]: ", required=False) - try: - models.update_workout_exercise(conn, eid, name, bw_relative, note) - print("Exercise updated.") - except sqlite3.IntegrityError: - print(f'An exercise named "{name}" already exists.') - ui.pause() - - -def delete_workout_exercise(conn: sqlite3.Connection) -> None: - list_workout_exercises(conn, pause=False) - eid = ui.prompt_int("\nExercise ID to delete: ") - assert eid is not None - ex = models.get_workout_exercise(conn, eid) - if not ex: - print("Exercise not found.") - return - if not ui.confirm(f'Delete "{ex["name"]}"?'): - return - try: - models.delete_workout_exercise(conn, eid) - print("Exercise deleted.") - except sqlite3.IntegrityError: - print( - f'Cannot delete "{ex["name"]}" — it is used in existing sessions or templates.' - ) - ui.pause() - - -# --- Workout Sessions --- - - -def log_workout(conn: sqlite3.Connection) -> None: - exercises = models.list_workout_exercises(conn) - if not exercises: - print("No exercises defined. Add some first via Manage Workout Exercises.") - return - - # Session metadata - ui.clear_screen() - ui.print_header("Log Workout") - date_time = ui.prompt_datetime("Date/Time") - session_note = ui.prompt_str("Session note (optional): ", required=False) - - # Check if user wants to start from a template - entries: list[dict] = [] - templates = models.list_workout_templates(conn) - if templates: - print("\nStart from a template?") - for i, t in enumerate(templates, 1): - print(f" {i}. {t['name']}") - print(" 0. No template (blank session)") - choice = input("\n> ").strip() - try: - idx = int(choice) - if 1 <= idx <= len(templates): - # Load template exercises as defaults - template_id = templates[idx - 1]["id"] - _, tmpl_entries = models.get_workout_template_detail(conn, template_id) - print(f'\nUsing template "{templates[idx - 1]["name"]}"') - print("Press Enter to accept defaults, or type new values.\n") - for te in tmpl_entries: - print(f" -- {te['exercise_name']} --") - sets = ui.prompt_int( - f" Sets [{te['sets'] or ''}]: ", - min_val=1, - default=te["sets"], - ) - assert sets is not None - reps, weight, rest_time = ui.prompt_sets_detail( - sets, - default_reps=te["reps"], - default_rest=te["rest_time"], - bw_relative=bool(te["bw_relative"]), - ) - lsrpe = ui.prompt_int(" Last Set RPE: ", min_val=1, max_val=10) - note = ui.prompt_str( - f" Note [{te['note'] or ''}]: ", - required=False, - default=te["note"], - ) - entries.append( - { - "exercise_id": te["exercise_id"], - "exercise_name": te["exercise_name"], - "bw_relative": bool(te["bw_relative"]), - "sets": sets, - "reps": reps, - "weight": weight, - "rest_time": rest_time, - "lsrpe": lsrpe, - "note": note, - } - ) - print(f' "{te["exercise_name"]}" added to session.') - except (ValueError, IndexError): - pass # Invalid input or 0 — proceed without template - - # Build exercise list — collect everything in memory before saving - while True: - print("\nAvailable exercises:") - for e in exercises: - print(f" {e['id']}. {e['name']}") - print() - choice = input("Select exercise ID ('d' = done): ").strip() - - if choice.lower() == "d": - if not entries: - print("No exercises added. Aborting.") - return - break - - # Select existing exercise and prompt for set details - try: - eid = int(choice) - except ValueError: - print("Invalid input.") - continue - ex = models.get_workout_exercise(conn, eid) - if not ex: - print("Exercise not found.") - continue - - print(f"\n -- {ex['name']} --") - sets = ui.prompt_int(" Sets: ", min_val=1) - assert sets is not None - reps, weight, rest_time = ui.prompt_sets_detail( - sets, bw_relative=bool(ex["bw_relative"]) - ) - lsrpe = ui.prompt_int(" Last Set RPE: ", min_val=1, max_val=10) - note = ui.prompt_str(" Note (optional): ", required=False) - - entries.append( - { - "exercise_id": eid, - "exercise_name": ex["name"], - "bw_relative": bool(ex["bw_relative"]), - "sets": sets, - "reps": reps, - "weight": weight, - "rest_time": rest_time, - "lsrpe": lsrpe, - "note": note, - } - ) - print(f' "{ex["name"]}" added to session.') - - # Show summary and confirm before writing to DB - ui.clear_screen() - ui.print_header("Session Summary") - print(f"Date: {date_time}") - if session_note: - print(f"Note: {session_note}") - ui.print_table( - ["#", "Exercise", "Sets", "Reps", "Weight", "Rest", "LSRPE", "Note"], - [ - [ - str(i), - e["exercise_name"], - str(e["sets"]), - str(e["reps"]).replace(",", "/"), - ui.format_weight(e["weight"], e["bw_relative"]), - ui.format_rest_time(e["rest_time"]), - str(e["lsrpe"]), - e["note"] or "", - ] - for i, e in enumerate(entries, 1) - ], - ) - - if ui.confirm("\nSave this session?"): - models.save_workout_session(conn, date_time, session_note, entries) - print("Session saved!") - else: - print("Session discarded.") - ui.pause() - - -def view_workout_sessions(conn: sqlite3.Connection) -> None: - while True: - ui.clear_screen() - # List all sessions with exercise name preview - sessions = models.list_workout_sessions(conn) - if not sessions: - print("\nNo sessions recorded yet.") - return - ui.print_header("Past Workout Sessions") - ui.print_table( - ["#", "Date", "Exercises", "Note"], - [ - [str(i), s["date_time"], s["exercises"] or "", s["note"] or ""] - for i, s in enumerate(sessions, 1) - ], - ) - - # Select a session to view details - choice = input("\nSelect # for details ('b' = back): ").strip() - if choice.lower() == "b": - break - try: - idx = int(choice) - 1 - if idx < 0 or idx >= len(sessions): - print("Invalid selection.") - continue - except ValueError: - print("Invalid input.") - continue - - # Show full session detail with all exercise entries - ui.clear_screen() - session_id = sessions[idx]["id"] - session, entries = models.get_workout_session_detail(conn, session_id) - assert session is not None - ui.print_header(f"Session: {session['date_time']}") - if session["note"]: - print(f"Note: {session['note']}\n") - ui.print_table( - ["#", "Exercise", "Sets", "Reps", "Weight", "Rest", "LSRPE", "Note"], - [ - [ - str(e["position"]), - e["exercise_name"], - str(e["sets"]), - str(e["reps"]).replace(",", "/"), - ui.format_weight(str(e["weight"]), bool(e["bw_relative"])), - ui.format_rest_time(e["rest_time"]), - str(e["lsrpe"]), - e["note"] or "", - ] - for e in entries - ], - ) - action = input("\nActions: (b)ack, (d)elete session\n> ").strip().lower() - if action == "d": - if ui.confirm("Delete this session?"): - models.delete_workout_session(conn, session_id) - print("Session deleted.") - ui.pause() - - -# --- Workout Templates --- - - -def manage_workout_templates(conn: sqlite3.Connection) -> None: - while True: - ui.clear_screen() - ui.print_header("Manage Workout Templates") - print("1. List Templates") - print("2. Create Template") - print("3. View/Edit Template") - print("4. Delete Template") - print("5. Back") - choice = input("\n> ").strip() - if choice == "1": - list_workout_templates(conn) - elif choice == "2": - create_workout_template(conn) - elif choice == "3": - edit_workout_template(conn) - elif choice == "4": - delete_workout_template(conn) - elif choice == "5": - break - - -def list_workout_templates(conn: sqlite3.Connection, pause: bool = True) -> None: - templates = models.list_workout_templates(conn) - ui.print_header("All Workout Templates") - ui.print_table( - ["ID", "Name", "Exercises"], - [[str(t["id"]), t["name"], t["exercises"] or ""] for t in templates], - ) - if pause: - ui.pause() - - -def create_workout_template(conn: sqlite3.Connection) -> None: - name = ui.prompt_str("Template name: ") - assert name is not None - try: - template_id = models.create_workout_template(conn, name) - print(f'Template "{name}" created.') - except sqlite3.IntegrityError: - print(f'Template "{name}" already exists.') - ui.pause() - return - # Immediately enter exercise editor - print("Now add exercises to the template.") - ui.pause() - edit_template_exercises(conn, template_id) - - -def edit_workout_template(conn: sqlite3.Connection) -> None: - templates = models.list_workout_templates(conn) - if not templates: - print("\nNo templates defined.") - return - list_workout_templates(conn, pause=False) - tid = ui.prompt_int("\nTemplate ID to edit: ") - assert tid is not None - template = models.get_workout_template(conn, tid) - if not template: - print("Template not found.") - return - edit_template_exercises(conn, tid) - - -def edit_template_exercises(conn: sqlite3.Connection, template_id: int) -> None: - """Interactive editor for a template's exercise list.""" - while True: - ui.clear_screen() - template, entries = models.get_workout_template_detail(conn, template_id) - assert template is not None - ui.print_header(f'Template: {template["name"]}') - - # Build in-memory list from current DB state - exercises_data: list[dict] = [ - { - "exercise_id": e["exercise_id"], - "exercise_name": e["exercise_name"], - "bw_relative": bool(e["bw_relative"]), - "sets": e["sets"], - "reps": e["reps"], - "lsrpe": e["lsrpe"], - "rest_time": e["rest_time"], - "note": e["note"], - } - for e in entries - ] - - if exercises_data: - ui.print_table( - ["#", "Exercise", "Sets", "Reps", "LSRPE", "Rest", "Note"], - [ - [ - str(i), - e["exercise_name"], - str(e["sets"]), - str(e["reps"]), - str(e["lsrpe"]), - f"{e['rest_time']}s", - e["note"] or "", - ] - for i, e in enumerate(exercises_data, 1) - ], - ) - else: - print(" (no exercises)") - - print( - "\nActions: (a)dd exercise, (r)emove, (m)ove, (e)dit defaults, (n)ame, (b)ack" - ) - action = input("> ").strip().lower() - - if action == "b": - break - - elif action == "n": - new_name = ui.prompt_str("New template name: ") - assert new_name is not None - try: - models.update_workout_template_name(conn, template_id, new_name) - print(f'Template renamed to "{new_name}".') - except sqlite3.IntegrityError: - print(f'Template "{new_name}" already exists.') - ui.pause() - - elif action == "a": - available = models.list_workout_exercises(conn) - if not available: - print("No exercises defined. Create some first.") - continue - print("\nAvailable exercises:") - for e in available: - print(f" {e['id']}. {e['name']}") - eid = ui.prompt_int("\nExercise ID to add: ") - assert eid is not None - ex = models.get_workout_exercise(conn, eid) - if not ex: - print("Exercise not found.") - continue - print(f"\n -- {ex['name']} (defaults) --") - sets = ui.prompt_int(" Sets: ", min_val=1) - reps = ui.prompt_int(" Reps: ", min_val=1) - lsrpe = ui.prompt_int(" LSRPE: ", min_val=1, max_val=10) - rest_time = ui.prompt_int(" Rest time in seconds: ", min_val=0) - note = ui.prompt_str(" Note (optional): ", required=False) - exercises_data.append( - { - "exercise_id": eid, - "sets": sets, - "reps": reps, - "lsrpe": lsrpe, - "rest_time": rest_time, - "note": note, - } - ) - models.save_workout_template_exercises(conn, template_id, exercises_data) - print(f'"{ex["name"]}" added to template.') - ui.pause() - - elif action == "r": - if not exercises_data: - print("No exercises to remove.") - continue - pos = ui.prompt_int("Position # to remove: ", min_val=1) - assert pos is not None - if pos > len(exercises_data): - print("Invalid position.") - continue - removed = exercises_data.pop(pos - 1) - models.save_workout_template_exercises(conn, template_id, exercises_data) - print(f'Removed "{removed["exercise_name"]}".') - ui.pause() - - elif action == "m": - if len(exercises_data) < 2: - print("Need at least 2 exercises to move.") - continue - from_pos = ui.prompt_int( - "Move from position #: ", min_val=1, max_val=len(exercises_data) - ) - to_pos = ui.prompt_int( - "Move to position #: ", min_val=1, max_val=len(exercises_data) - ) - assert from_pos is not None and to_pos is not None - item = exercises_data.pop(from_pos - 1) - exercises_data.insert(to_pos - 1, item) - models.save_workout_template_exercises(conn, template_id, exercises_data) - print("Exercise moved.") - ui.pause() - - elif action == "e": - if not exercises_data: - print("No exercises to edit.") - continue - pos = ui.prompt_int("Position # to edit: ", min_val=1) - assert pos is not None - if pos > len(exercises_data): - print("Invalid position.") - continue - entry = exercises_data[pos - 1] - print(f'\n -- {entry["exercise_name"]} (edit defaults) --') - entry["sets"] = ui.prompt_int( - f" Sets [{entry['sets'] or ''}]: ", min_val=1, default=entry["sets"] - ) - entry["reps"] = ui.prompt_int( - f" Reps [{entry['reps'] or ''}]: ", min_val=1, default=entry["reps"] - ) - entry["lsrpe"] = ui.prompt_int( - f" LSRPE [{entry['lsrpe']}]: ", - min_val=1, - max_val=10, - default=entry["lsrpe"], - ) - entry["rest_time"] = ui.prompt_int( - f" Rest time in seconds [{entry['rest_time']}]: ", - min_val=0, - default=entry["rest_time"], - ) - entry["note"] = ui.prompt_str( - f" Note [{entry['note'] or ''}]: ", - required=False, - default=entry["note"], - ) - models.save_workout_template_exercises(conn, template_id, exercises_data) - print("Defaults updated.") - ui.pause() - - -def delete_workout_template(conn: sqlite3.Connection) -> None: - templates = models.list_workout_templates(conn) - if not templates: - print("\nNo templates defined.") - return - list_workout_templates(conn, pause=False) - tid = ui.prompt_int("\nTemplate ID to delete: ") - assert tid is not None - template = models.get_workout_template(conn, tid) - if not template: - print("Template not found.") - return - if not ui.confirm(f'Delete template "{template["name"]}"?'): - return - models.delete_workout_template(conn, tid) - print("Template deleted.") - ui.pause() - - -# --- Entry Point --- - - def main() -> None: init_db() conn = get_connection() |
