Function bodies 373 total
_render_day_list method · python · L598-L613 (16 LOC)garmin_extract/gui/screens/pull_progress.py
def _render_day_list(self) -> str:
lines: list[str] = []
for ds in self._day_states:
if ds.status == "done":
tally = min(ds.done + ds.failed, ds.total) if ds.total else ds.done + ds.failed
count = f"({tally}/{ds.total})" if ds.total else ""
failed = f" {ds.failed} failed" if ds.failed else ""
lines.append(f"\u2713 {ds.date_str} {count}{failed}")
elif ds.status == "active":
count = f"{ds.done + ds.failed}/{ds.total}" if ds.total else "\u2026"
lines.append(f"\u25cf {ds.date_str} ({count})")
elif ds.status == "skipped":
lines.append(f"\u21b7 {ds.date_str} (skipped)")
else:
lines.append(f"\u25cb {ds.date_str}")
return "\n".join(lines)_render_metric_list method · python · L615-L622 (8 LOC)garmin_extract/gui/screens/pull_progress.py
def _render_metric_list(self) -> str:
if not self._live_metrics:
return "Waiting for first metric..."
lines: list[str] = []
for name, state in self._live_metrics:
icon = "\u2713" if state == "done" else "\u2717"
lines.append(f"{icon} {name}")
return "\n".join(lines)_cancel method · python · L626-L632 (7 LOC)garmin_extract/gui/screens/pull_progress.py
def _cancel(self) -> None:
if self._proc is not None:
try:
self._proc.terminate()
except Exception:
pass
self.reject()closeEvent method · python · L634-L640 (7 LOC)garmin_extract/gui/screens/pull_progress.py
def closeEvent(self, event: object) -> None: # noqa: N802
if self._proc is not None:
try:
self._proc.terminate()
except Exception:
pass
super().closeEvent(event)_check_python function · python · L32-L34 (3 LOC)garmin_extract/gui/screens/setup.py
def _check_python() -> tuple[bool, str]:
v = sys.version_info
return v >= (3, 12), f"Python {v.major}.{v.minor}.{v.micro}"_check_chrome function · python · L37-L41 (5 LOC)garmin_extract/gui/screens/setup.py
def _check_chrome() -> tuple[bool, str]:
from garmin_extract.menu import _find_chrome
found, version = _find_chrome()
return found, version or "Not found"_check_packages function · python · L44-L50 (7 LOC)garmin_extract/gui/screens/setup.py
def _check_packages() -> tuple[bool, str]:
from garmin_extract.menu import _missing_packages
missing = _missing_packages()
if not missing:
return True, "All installed"
return False, f"Missing: {', '.join(missing)}"Open data scored by Repobility · https://repobility.com
_check_credentials function · python · L53-L56 (4 LOC)garmin_extract/gui/screens/setup.py
def _check_credentials() -> tuple[bool, str]:
from garmin_extract._credentials import check_credentials
return check_credentials()_check_gmail function · python · L59-L64 (6 LOC)garmin_extract/gui/screens/setup.py
def _check_gmail() -> tuple[bool, str]:
if not GMAIL_CREDS_FILE.exists():
return False, "google_credentials.json missing"
if not GMAIL_TOKEN_FILE.exists():
return False, "Credentials found — not yet authorized"
return True, "Authorized"_StatusSignals class · python · L70-L76 (7 LOC)garmin_extract/gui/screens/setup.py
class _StatusSignals(QObject):
"""Signals emitted from background threads to update the UI."""
prereq_done = Signal(bool, str) # (all_ok, summary)
creds_done = Signal(bool, str) # (ok, detail)
gmail_done = Signal(bool, str) # (ok, detail)
all_done = Signal()_StatusCard class · python · L82-L149 (68 LOC)garmin_extract/gui/screens/setup.py
class _StatusCard(QFrame):
"""A clickable card showing a section name, status icon, and detail text."""
def __init__(self, title: str, subtitle: str, parent: QWidget | None = None) -> None:
super().__init__(parent)
self.setObjectName("status-card")
self.setCursor(Qt.CursorShape.PointingHandCursor)
self.setStyleSheet("""
QFrame#status-card {
background-color: #313244;
border: 1px solid #45475a;
border-radius: 8px;
padding: 16px;
}
QFrame#status-card:hover {
border-color: #89b4fa;
}
""")
layout = QHBoxLayout(self)
layout.setContentsMargins(16, 12, 16, 12)
# Left: icon + text
left = QVBoxLayout()
left.setSpacing(4)
self._title = QLabel(title)
self._title.setStyleSheet("font-size: 16px; font-weight: bold; background: transparent;")
left.addWidget__init__ method · python · L85-L129 (45 LOC)garmin_extract/gui/screens/setup.py
def __init__(self, title: str, subtitle: str, parent: QWidget | None = None) -> None:
super().__init__(parent)
self.setObjectName("status-card")
self.setCursor(Qt.CursorShape.PointingHandCursor)
self.setStyleSheet("""
QFrame#status-card {
background-color: #313244;
border: 1px solid #45475a;
border-radius: 8px;
padding: 16px;
}
QFrame#status-card:hover {
border-color: #89b4fa;
}
""")
layout = QHBoxLayout(self)
layout.setContentsMargins(16, 12, 16, 12)
# Left: icon + text
left = QVBoxLayout()
left.setSpacing(4)
self._title = QLabel(title)
self._title.setStyleSheet("font-size: 16px; font-weight: bold; background: transparent;")
left.addWidget(self._title)
self._subtitle = QLabel(subtitle)
self._subtitle.setStyleSheet("font-size: 13pset_status method · python · L131-L142 (12 LOC)garmin_extract/gui/screens/setup.py
def set_status(self, ok: bool, detail: str) -> None:
if ok:
self._icon.setText("✓")
self._icon.setStyleSheet("font-size: 22px; color: #a6e3a1; background: transparent;")
else:
self._icon.setText("✗")
self._icon.setStyleSheet("font-size: 22px; color: #f38ba8; background: transparent;")
# Strip Rich markup for GUI display
import re
clean = re.sub(r"\[/?[^\]]*\]", "", detail)
self._detail.setText(clean)mousePressEvent method · python · L147-L149 (3 LOC)garmin_extract/gui/screens/setup.py
def mousePressEvent(self, event: object) -> None: # noqa: N802
if self._callback and callable(self._callback):
self._callback()PrereqDialog class · python · L155-L225 (71 LOC)garmin_extract/gui/screens/setup.py
class PrereqDialog(QDialog):
"""Modal dialog that runs prerequisite checks with live output."""
def __init__(self, parent: QWidget | None = None) -> None:
super().__init__(parent)
self.setWindowTitle("Prerequisites")
self.setMinimumSize(600, 400)
self.resize(700, 500)
layout = QVBoxLayout(self)
self._log = QTextEdit()
self._log.setReadOnly(True)
self._log.setStyleSheet(
"background-color: #1e1e2e; color: #cdd6f4; font-family: monospace;"
" font-size: 13px; border: 1px solid #45475a; border-radius: 4px;"
)
layout.addWidget(self._log)
self._progress = QProgressBar()
self._progress.setMaximum(3)
self._progress.setValue(0)
layout.addWidget(self._progress)
self._status = QLabel("Running checks…")
self._status.setStyleSheet("color: #6c7086;")
self._status.setAlignment(Qt.AlignmentFlag.AlignCenter)
layout.addWidgetRepobility (the analyzer behind this table) · https://repobility.com
__init__ method · python · L158-L197 (40 LOC)garmin_extract/gui/screens/setup.py
def __init__(self, parent: QWidget | None = None) -> None:
super().__init__(parent)
self.setWindowTitle("Prerequisites")
self.setMinimumSize(600, 400)
self.resize(700, 500)
layout = QVBoxLayout(self)
self._log = QTextEdit()
self._log.setReadOnly(True)
self._log.setStyleSheet(
"background-color: #1e1e2e; color: #cdd6f4; font-family: monospace;"
" font-size: 13px; border: 1px solid #45475a; border-radius: 4px;"
)
layout.addWidget(self._log)
self._progress = QProgressBar()
self._progress.setMaximum(3)
self._progress.setValue(0)
layout.addWidget(self._progress)
self._status = QLabel("Running checks…")
self._status.setStyleSheet("color: #6c7086;")
self._status.setAlignment(Qt.AlignmentFlag.AlignCenter)
layout.addWidget(self._status)
btn_layout = QHBoxLayout()
btn_layout.addStretch()
self._clos_run_checks method · python · L199-L217 (19 LOC)garmin_extract/gui/screens/setup.py
def _run_checks(self) -> None:
checks = [
("Python 3.12+", _check_python),
("Google Chrome", _check_chrome),
("Python packages", _check_packages),
]
failed: list[str] = []
for i, (name, fn) in enumerate(checks):
ok, detail = fn()
icon = "✓" if ok else "✗"
self._signals.log_line.emit(f" {icon} {name}: {detail}")
if not ok:
failed.append(name)
self._signals.progress.emit(i + 1)
if failed:
self._signals.finished.emit(f"Issues found: {', '.join(failed)}")
else:
self._signals.finished.emit("All checks passed ✓")_on_finished method · python · L222-L225 (4 LOC)garmin_extract/gui/screens/setup.py
def _on_finished(self, summary: str) -> None:
self._status.setText(summary)
self._close_btn.setEnabled(True)
self._progress.hide()_CheckSignals class · python · L228-L231 (4 LOC)garmin_extract/gui/screens/setup.py
class _CheckSignals(QObject):
log_line = Signal(str)
progress = Signal(int)
finished = Signal(str)CredentialsDialog class · python · L237-L388 (152 LOC)garmin_extract/gui/screens/setup.py
class CredentialsDialog(QDialog):
"""Modal dialog for entering Garmin Connect credentials."""
def __init__(self, parent: QWidget | None = None) -> None:
super().__init__(parent)
self.setWindowTitle("Garmin Connect Credentials")
self.setMinimumWidth(480)
self.setModal(True)
layout = QVBoxLayout(self)
layout.setSpacing(12)
# Keyring status
self._mode_label = QLabel("Detecting keyring…")
self._mode_label.setStyleSheet("color: #6c7086; font-size: 13px;")
layout.addWidget(self._mode_label)
# Warning panel (hidden by default)
self._warning = QLabel()
self._warning.setWordWrap(True)
self._warning.setStyleSheet(
"background-color: #45475a; color: #f38ba8; border: 1px solid #f38ba8;"
" border-radius: 6px; padding: 12px; font-size: 13px;"
)
self._warning.hide()
layout.addWidget(self._warning)
# Email
layout.ad__init__ method · python · L240-L301 (62 LOC)garmin_extract/gui/screens/setup.py
def __init__(self, parent: QWidget | None = None) -> None:
super().__init__(parent)
self.setWindowTitle("Garmin Connect Credentials")
self.setMinimumWidth(480)
self.setModal(True)
layout = QVBoxLayout(self)
layout.setSpacing(12)
# Keyring status
self._mode_label = QLabel("Detecting keyring…")
self._mode_label.setStyleSheet("color: #6c7086; font-size: 13px;")
layout.addWidget(self._mode_label)
# Warning panel (hidden by default)
self._warning = QLabel()
self._warning.setWordWrap(True)
self._warning.setStyleSheet(
"background-color: #45475a; color: #f38ba8; border: 1px solid #f38ba8;"
" border-radius: 6px; padding: 12px; font-size: 13px;"
)
self._warning.hide()
layout.addWidget(self._warning)
# Email
layout.addWidget(QLabel("Email"))
self._email = QLineEdit()
self._email.setPlaceholderText("_load_existing method · python · L303-L314 (12 LOC)garmin_extract/gui/screens/setup.py
def _load_existing(self) -> None:
try:
from garmin_extract._credentials import load_credentials
email, _ = load_credentials()
if email:
self._email.setText(email)
self._password.setFocus()
else:
self._email.setFocus()
except Exception:
self._email.setFocus()_detect_keyring method · python · L316-L323 (8 LOC)garmin_extract/gui/screens/setup.py
def _detect_keyring(self) -> None:
from garmin_extract._credentials import detect_keyring
ok, detail = detect_keyring()
# Use QMetaObject.invokeMethod for thread safety — but simpler
# to use a signal. For brevity, just set and update in main thread.
self._keyring_available = ok
self._keyring_detail = detailRepobility's GitHub App fixes findings like these · https://github.com/apps/repobility-bot
showEvent method · python · L325-L349 (25 LOC)garmin_extract/gui/screens/setup.py
def showEvent(self, event: object) -> None: # noqa: N802
super().showEvent(event)
# Poll for keyring detection result (runs nearly instantly)
from PySide6.QtCore import QTimer
def _apply() -> None:
if self._keyring_available is None:
QTimer.singleShot(50, _apply)
return
if self._keyring_available:
self._mode_label.setText(f"● Keyring: {self._keyring_detail}")
self._mode_label.setStyleSheet("color: #a6e3a1; font-size: 13px;")
else:
self._mode_label.setText(
"⚠ No secure keyring — credentials will be saved to .env (plaintext)"
)
self._mode_label.setStyleSheet("color: #f9e2af; font-size: 13px;")
self._warning.setText(
"⚠ PLAINTEXT WARNING\n\n"
"Saving will write your password to a plain-text file on disk.\n"
"An_save method · python · L351-L388 (38 LOC)garmin_extract/gui/screens/setup.py
def _save(self) -> None:
from garmin_extract._credentials import save_to_env, save_to_keyring
email = self._email.text().strip()
password = self._password.text().strip()
self._error.hide()
self._success.hide()
if not email:
self._error.setText("Email is required.")
self._error.show()
self._email.setFocus()
return
if not password:
self._error.setText("Password is required.")
self._error.show()
self._password.setFocus()
return
if self._keyring_available:
ok, detail = save_to_keyring(email, password)
if ok:
self._success.setText(f"✓ Saved to keyring — {email}")
self._success.show()
else:
self._error.setText(f"Keyring save failed: {detail}")
self._error.show()
return
else:
save_to_env(email,GmailOAuthDialog class · python · L394-L524 (131 LOC)garmin_extract/gui/screens/setup.py
class GmailOAuthDialog(QDialog):
"""Modal dialog for Gmail OAuth authorization flow."""
def __init__(self, parent: QWidget | None = None) -> None:
super().__init__(parent)
self.setWindowTitle("Gmail OAuth Setup")
self.setMinimumSize(650, 450)
self.setModal(True)
layout = QVBoxLayout(self)
self._log = QTextEdit()
self._log.setReadOnly(True)
self._log.setStyleSheet(
"background-color: #1e1e2e; color: #cdd6f4; font-family: monospace;"
" font-size: 13px; border: 1px solid #45475a; border-radius: 4px;"
)
layout.addWidget(self._log)
# Code input area (hidden until needed)
self._code_frame = QFrame()
self._code_frame.hide()
code_layout = QVBoxLayout(self._code_frame)
code_layout.setContentsMargins(0, 8, 0, 0)
code_layout.addWidget(QLabel("Paste the authorization code below:"))
self._code_input = QLineEdit()
self._co__init__ method · python · L397-L443 (47 LOC)garmin_extract/gui/screens/setup.py
def __init__(self, parent: QWidget | None = None) -> None:
super().__init__(parent)
self.setWindowTitle("Gmail OAuth Setup")
self.setMinimumSize(650, 450)
self.setModal(True)
layout = QVBoxLayout(self)
self._log = QTextEdit()
self._log.setReadOnly(True)
self._log.setStyleSheet(
"background-color: #1e1e2e; color: #cdd6f4; font-family: monospace;"
" font-size: 13px; border: 1px solid #45475a; border-radius: 4px;"
)
layout.addWidget(self._log)
# Code input area (hidden until needed)
self._code_frame = QFrame()
self._code_frame.hide()
code_layout = QVBoxLayout(self._code_frame)
code_layout.setContentsMargins(0, 8, 0, 0)
code_layout.addWidget(QLabel("Paste the authorization code below:"))
self._code_input = QLineEdit()
self._code_input.setPlaceholderText("Paste code here")
self._code_input.returnPressed.connect_start_flow method · python · L445-L471 (27 LOC)garmin_extract/gui/screens/setup.py
def _start_flow(self) -> None:
if not GMAIL_CREDS_FILE.exists():
self._log.append(
"google_credentials.json not found.\n\n"
"To create it:\n"
" 1. Go to https://console.cloud.google.com\n"
" 2. Create a project\n"
" 3. Enable the Gmail API\n"
" 4. Credentials → Create → OAuth 2.0 Client ID → Desktop app\n"
" 5. Download JSON → save as google_credentials.json\n"
" in the project root directory\n\n"
"Then come back here to complete authorization."
)
self._status.setText("google_credentials.json required")
return
if GMAIL_TOKEN_FILE.exists():
self._log.append(
"✓ Gmail MFA is already authorized.\n\n"
"Token file found: .google_token.json\n\n"
"To re-authorize, delete .google_token.json and run this again."
_do_auth method · python · L473-L504 (32 LOC)garmin_extract/gui/screens/setup.py
def _do_auth(self) -> None:
import subprocess
try:
proc = subprocess.Popen(
[sys.executable, "-u", str(ROOT / "scripts" / "setup_gmail_auth.py")],
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
stdin=subprocess.DEVNULL,
text=True,
cwd=str(ROOT),
)
assert proc.stdout is not None
code_shown = False
for raw_line in iter(proc.stdout.readline, ""):
line = raw_line.rstrip("\n")
self._signals.log_line.emit(line)
if line.startswith("https://") and not code_shown:
self._auth_url = line.strip()
if "Waiting up to" in line and not code_shown:
code_shown = True
self._signals.show_code_input.emit(self._auth_url)
proc.wait()
if proc.returncode == 0:
self._sign_show_code_input method · python · L509-L512 (4 LOC)garmin_extract/gui/screens/setup.py
def _show_code_input(self, auth_url: str) -> None:
self._status.setText("Open the URL above in any browser, then paste the code below")
self._code_frame.show()
self._code_input.setFocus()_submit_code method · python · L514-L520 (7 LOC)garmin_extract/gui/screens/setup.py
def _submit_code(self) -> None:
code = self._code_input.text().strip()
if not code:
return
GMAIL_AUTH_CODE_FILE.write_text(code + "\n")
self._code_frame.hide()
self._status.setText("Code submitted — waiting for confirmation…")Generated by Repobility's multi-pass static-analysis pipeline (https://repobility.com)
_on_finished method · python · L522-L524 (3 LOC)garmin_extract/gui/screens/setup.py
def _on_finished(self, summary: str) -> None:
self._status.setText(summary)
self._code_frame.hide()_GmailSignals class · python · L527-L530 (4 LOC)garmin_extract/gui/screens/setup.py
class _GmailSignals(QObject):
log_line = Signal(str)
show_code_input = Signal(str)
finished = Signal(str)SetupPage class · python · L536-L617 (82 LOC)garmin_extract/gui/screens/setup.py
class SetupPage(QWidget):
"""The Initial Setup page shown in the main window's stacked widget."""
def __init__(self, parent: QWidget | None = None) -> None:
super().__init__(parent)
layout = QVBoxLayout(self)
layout.setContentsMargins(40, 32, 40, 32)
layout.setSpacing(8)
heading = QLabel("Initial Setup")
heading.setObjectName("heading")
layout.addWidget(heading)
sub = QLabel("Configure credentials and prerequisites")
sub.setObjectName("subheading")
layout.addWidget(sub)
layout.addSpacing(16)
# ── Prerequisite card ─────────────────────────
self._prereq_card = _StatusCard("Prerequisites", "Python, Chrome, packages")
self._prereq_card.on_click(self._open_prereqs)
layout.addWidget(self._prereq_card)
layout.addSpacing(4)
# ── Credentials card ──────────────────────────
self._creds_card = _StatusCard("Garmin Credentials", "Email and __init__ method · python · L539-L582 (44 LOC)garmin_extract/gui/screens/setup.py
def __init__(self, parent: QWidget | None = None) -> None:
super().__init__(parent)
layout = QVBoxLayout(self)
layout.setContentsMargins(40, 32, 40, 32)
layout.setSpacing(8)
heading = QLabel("Initial Setup")
heading.setObjectName("heading")
layout.addWidget(heading)
sub = QLabel("Configure credentials and prerequisites")
sub.setObjectName("subheading")
layout.addWidget(sub)
layout.addSpacing(16)
# ── Prerequisite card ─────────────────────────
self._prereq_card = _StatusCard("Prerequisites", "Python, Chrome, packages")
self._prereq_card.on_click(self._open_prereqs)
layout.addWidget(self._prereq_card)
layout.addSpacing(4)
# ── Credentials card ──────────────────────────
self._creds_card = _StatusCard("Garmin Credentials", "Email and password")
self._creds_card.on_click(self._open_credentials)
layout.addWidget(self._cre_check_all method · python · L587-L602 (16 LOC)garmin_extract/gui/screens/setup.py
def _check_all(self) -> None:
py_ok, _ = _check_python()
chrome_ok, _ = _check_chrome()
pkg_ok, _ = _check_packages()
all_ok = py_ok and chrome_ok and pkg_ok
parts = []
for ok, name in [(py_ok, "Python"), (chrome_ok, "Chrome"), (pkg_ok, "Packages")]:
parts.append(f"{'✓' if ok else '✗'} {name}")
self._signals.prereq_done.emit(all_ok, " ".join(parts))
creds_ok, creds_str = _check_credentials()
self._signals.creds_done.emit(creds_ok, creds_str)
gmail_ok, gmail_str = _check_gmail()
self._signals.gmail_done.emit(gmail_ok, gmail_str)_open_prereqs method · python · L604-L607 (4 LOC)garmin_extract/gui/screens/setup.py
def _open_prereqs(self) -> None:
dlg = PrereqDialog(self)
dlg.exec()
self.refresh_status()_open_credentials method · python · L609-L612 (4 LOC)garmin_extract/gui/screens/setup.py
def _open_credentials(self) -> None:
dlg = CredentialsDialog(self)
dlg.exec()
self.refresh_status()_open_gmail method · python · L614-L617 (4 LOC)garmin_extract/gui/screens/setup.py
def _open_gmail(self) -> None:
dlg = GmailOAuthDialog(self)
dlg.exec()
self.refresh_status()Open data scored by Repobility · https://repobility.com
_run_script_if_frozen function · python · L20-L57 (38 LOC)garmin_extract/__main__.py
def _run_script_if_frozen() -> None:
"""If invoked as `garmin-extract.exe -u <script> [args...]`, run the script."""
if not getattr(sys, "frozen", False):
return
if len(sys.argv) < 3 or sys.argv[1] != "-u":
return
script = sys.argv[2]
sys.argv = [script] + sys.argv[3:]
# Add the script's directory to sys.path so sibling imports work
# (e.g. pullers/garmin.py does `from _gmail_mfa import ...`)
from pathlib import Path
script_dir = str(Path(script).resolve().parent)
if script_dir not in sys.path:
sys.path.insert(0, script_dir)
# Unbuffered UTF-8 output so the GUI sees progress in real time
# (Windows default pipe encoding is cp1252 which can't encode ✓/✗ etc.)
for stream in (sys.stdout, sys.stderr):
try:
stream.reconfigure(encoding="utf-8", line_buffering=True, write_through=True)
except Exception:
pass
import runpy
try:
runpy.run_path(script, run_nameprompt_with_navigation function · python · L51-L61 (11 LOC)garmin_extract/menu.py
def prompt_with_navigation(prompt_text: str) -> str:
"""Wrap any input prompt; raise navigation signals on b / x / q."""
response = input(prompt_text).strip()
lower = response.lower()
if lower == "b":
raise BackSignal
if lower == "x":
raise ExitToMainSignal
if lower == "q":
raise QuitSignal
return response_continue function · python · L64-L66 (3 LOC)garmin_extract/menu.py
def _continue(prompt: str = "\n Press Enter to continue...") -> None:
"""Pause with a continue prompt. Navigation signals propagate normally."""
prompt_with_navigation(prompt)header function · python · L78-L86 (9 LOC)garmin_extract/menu.py
def header(title: str) -> None:
content = f" {title} "
width = max(50, len(content))
padding = width - len(content)
print()
console.print(f"╔{'═' * width}╗")
console.print(f"║[bold]{content}{' ' * padding}[/]║")
console.print(f"╚{'═' * width}╝")
print()load_env function · python · L93-L101 (9 LOC)garmin_extract/menu.py
def load_env() -> dict[str, str]:
vals: dict[str, str] = {}
if ENV.exists():
for line in ENV.read_text().splitlines():
line = line.strip()
if line and not line.startswith("#") and "=" in line:
k, _, v = line.partition("=")
vals[k.strip()] = v.strip()
return valssave_env function · python · L104-L110 (7 LOC)garmin_extract/menu.py
def save_env(vals: dict[str, str]) -> None:
lines = [
"# Garmin Connect credentials",
f"GARMIN_EMAIL={vals.get('GARMIN_EMAIL', '')}",
f"GARMIN_PASSWORD={vals.get('GARMIN_PASSWORD', '')}",
]
ENV.write_text("\n".join(lines) + "\n")_find_chrome function · python · L118-L163 (46 LOC)garmin_extract/menu.py
def _find_chrome() -> tuple[bool, str | None]:
system = platform.system()
# Windows: chrome.exe --version launches Chrome instead of printing version.
# Check standard install paths and read VersionInfo via PowerShell.
if system == "Windows":
paths = [
r"C:\Program Files\Google\Chrome\Application\chrome.exe",
r"C:\Program Files (x86)\Google\Chrome\Application\chrome.exe",
os.path.expandvars(r"%LOCALAPPDATA%\Google\Chrome\Application\chrome.exe"),
]
for path in paths:
if not Path(path).is_file():
continue
try:
ps = f"(Get-Item '{path}').VersionInfo.ProductVersion"
r = subprocess.run(
["powershell", "-NoProfile", "-Command", ps],
capture_output=True,
text=True,
timeout=5,
)
version = r.stdout.strip() if r.returncode == 0 else ""
_find_xvfb function · python · L166-L171 (6 LOC)garmin_extract/menu.py
def _find_xvfb() -> bool:
try:
r = subprocess.run(["which", "Xvfb"], capture_output=True, timeout=5)
return r.returncode == 0
except (FileNotFoundError, subprocess.TimeoutExpired, OSError):
return FalseRepobility (the analyzer behind this table) · https://repobility.com
_missing_packages function · python · L174-L188 (15 LOC)garmin_extract/menu.py
def _missing_packages() -> list[str]:
checks = [
("seleniumbase", "seleniumbase"),
("dotenv", "python-dotenv"),
("google.oauth2", "google-auth"),
("googleapiclient", "google-api-python-client"),
("requests_oauthlib", "requests-oauthlib"),
]
missing = []
for mod, pkg in checks:
try:
__import__(mod)
except ImportError:
missing.append(pkg)
return missingcheck_prerequisites function · python · L196-L485 (290 LOC)garmin_extract/menu.py
def check_prerequisites() -> None: # noqa: C901
header("Setup Wizard")
system = platform.system()
issues: list[str] = []
# ── Step 1: Python ───────────────────────────────────────────────────────
print("Step 1 of 5 — Python version")
print()
v = sys.version_info
ok = v >= (3, 12)
if ok:
console.print(f" [green]✓[/] Python {v.major}.{v.minor}.{v.micro}")
else:
console.print(
f" [red]✗[/] Python {v.major}.{v.minor}.{v.micro}"
" — version 3.12 or newer is required."
)
print()
print(" Python is the programming language this tool is written in.")
print(" Version 3.12 added features this code depends on.")
print()
if system == "Windows":
print(" Download the latest Python from: https://www.python.org/downloads/")
print()
print(" During installation, make sure to check:")
print(' ☑ "Add Python to PATH"')
configure_credentials function · python · L493-L524 (32 LOC)garmin_extract/menu.py
def configure_credentials() -> None:
header("Configure Garmin Credentials")
print(" Stored in .env (gitignored — never committed).\n")
env = load_env()
cur_email = env.get("GARMIN_EMAIL", "")
cur_pass = env.get("GARMIN_PASSWORD", "")
if cur_email:
print(f" Current email: {cur_email}")
new_email = prompt_with_navigation(" New email (Enter to keep): ")
email = new_email or cur_email
else:
email = prompt_with_navigation(" Garmin email: ")
if cur_pass:
print(f" Current password: {'*' * min(len(cur_pass), 12)}")
new_pass = getpass.getpass(" New password (Enter to keep): ")
password = new_pass or cur_pass
else:
password = getpass.getpass(" Garmin password: ")
if not email or not password:
print("\n No changes made.")
_continue()
return
env["GARMIN_EMAIL"] = email
env["GARMIN_PASSWORD"] = password
save_env(env)
console.pri