From db837972ba31fa624025ae0323d7c4503f849c06 Mon Sep 17 00:00:00 2001 From: Dafenxz0 Date: Sun, 7 Jun 2026 11:57:08 +0200 Subject: [PATCH 1/2] fix: package sound assets from correct path --- README.md | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b2dece1..201c9f4 100644 --- a/README.md +++ b/README.md @@ -210,7 +210,7 @@ Tests: pytest tests/ Version: kept in sync between trushell/__init__.py and pyproject.toml To add a custom sound for jokes, put an .mp3 or .wav file into -trushell/chronoterm/sounds/ – it will appear in the ‘settings’ menu. +trushell/sounds/ – it will appear in the ‘settings’ menu. License diff --git a/pyproject.toml b/pyproject.toml index baf3a30..ff35ef9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -55,7 +55,7 @@ build-backend = "hatchling.build" [tool.hatch.build] packages = ["trushell"] -include = ["README.md", "LICENSE", "trushell/chronoterm/sounds/*"] +include = ["README.md", "LICENSE", "trushell/sounds/*"] [tool.ruff] line-length = 88 From d2b3da11c6698111de3fc96e02a40531848e78ac Mon Sep 17 00:00:00 2001 From: Dafenxz0 Date: Sun, 7 Jun 2026 13:26:49 +0200 Subject: [PATCH 2/2] address maintainer review feedback --- CHANGELOG.md | 7 ++++++ tests/test_database.py | 42 ++++++++++++++++++++++++---------- trushell/cli.py | 4 +--- trushell/core/database.py | 48 ++++++++++++++++++++++----------------- 4 files changed, 65 insertions(+), 36 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 34168ef..68bdcb8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. +## Unreleased + +- Fixed wheel packaging so joke sound assets are included from `trushell/sounds/`. +- Updated README custom sound instructions to use the current `trushell/sounds/` path. +- Replaced a one-off Rich console print in `cli.py` with `typer.echo()`. +- Added double-checked database initialization to avoid taking the initialization lock after startup. + ## 0.1.0 - Initial release - Reorganized the repository into a proper Python package. diff --git a/tests/test_database.py b/tests/test_database.py index 1482c32..7c2aaae 100644 --- a/tests/test_database.py +++ b/tests/test_database.py @@ -1,11 +1,16 @@ -from trushell.core.database import _create_table, get_all_todos, get_db_connection, insert_todo +from trushell.core.database import _ensure_initialized, get_all_todos, get_db_connection, insert_todo from trushell.core.models import Todo -def test_get_db_connection_returns_fresh_connection(monkeypatch, tmp_path) -> None: +def _use_temp_database(monkeypatch, tmp_path): db_path = tmp_path / "todos.db" - monkeypatch.setattr("trushell.core.database.DB_PATH", db_path) + monkeypatch.setattr("trushell.core.database._DB_PATH", db_path) + monkeypatch.setattr("trushell.core.database._INITIALIZED", False) + return db_path + +def test_get_db_connection_returns_fresh_connection(monkeypatch, tmp_path) -> None: + _use_temp_database(monkeypatch, tmp_path) conn_one = get_db_connection() conn_two = get_db_connection() @@ -16,10 +21,9 @@ def test_get_db_connection_returns_fresh_connection(monkeypatch, tmp_path) -> No def test_insert_todo_assigns_sequential_positions(monkeypatch, tmp_path) -> None: - db_path = tmp_path / "todos.db" - monkeypatch.setattr("trushell.core.database.DB_PATH", db_path) + _use_temp_database(monkeypatch, tmp_path) - _create_table() + _ensure_initialized() insert_todo(Todo(task="first", category="work")) insert_todo(Todo(task="second", category="work")) @@ -30,20 +34,18 @@ def test_insert_todo_assigns_sequential_positions(monkeypatch, tmp_path) -> None def test_get_all_todos_works_with_local_connections(monkeypatch, tmp_path) -> None: - db_path = tmp_path / "todos.db" - monkeypatch.setattr("trushell.core.database.DB_PATH", db_path) + _use_temp_database(monkeypatch, tmp_path) - _create_table() + _ensure_initialized() insert_todo(Todo(task="alpha", category="study")) assert len(get_all_todos()) == 1 def test_get_all_todos_returns_rows_ordered_by_position(monkeypatch, tmp_path) -> None: - db_path = tmp_path / "todos.db" - monkeypatch.setattr("trushell.core.database.DB_PATH", db_path) + _use_temp_database(monkeypatch, tmp_path) - _create_table() + _ensure_initialized() with get_db_connection() as conn: conn.execute( "INSERT INTO todos VALUES (?, ?, ?, ?, ?, ?)", @@ -58,3 +60,19 @@ def test_get_all_todos_returns_rows_ordered_by_position(monkeypatch, tmp_path) - assert [task.task for task in tasks] == ["first", "second"] assert [task.position for task in tasks] == [0, 1] + + +def test_ensure_initialized_skips_lock_when_already_initialized(monkeypatch, tmp_path) -> None: + _use_temp_database(monkeypatch, tmp_path) + _ensure_initialized() + + class FailingLock: + def __enter__(self): + raise AssertionError("lock should not be acquired after initialization") + + def __exit__(self, exc_type, exc, tb): + return False + + monkeypatch.setattr("trushell.core.database._INITIALIZE_LOCK", FailingLock()) + + _ensure_initialized() diff --git a/trushell/cli.py b/trushell/cli.py index ae6f9bc..8b6ab09 100644 --- a/trushell/cli.py +++ b/trushell/cli.py @@ -8,7 +8,6 @@ import time import typer from pathlib import Path -from rich.console import Console from textual.app import App, ComposeResult from textual.binding import Binding from textual.widgets import Footer, Header, TextArea @@ -23,7 +22,6 @@ from .core.trukernel import EXIT_SENTINEL, get_kernel app = typer.Typer(name="trushell", help="TruShell manifest-driven launcher.") -console = Console() def app_with_lower() -> None: @@ -290,7 +288,7 @@ def _handle_cd_command(raw_command: str) -> bool: try: os.chdir(target) - console.print(f"[green]→ {os.getcwd()}[/green]") + typer.echo(f"→ {os.getcwd()}") except (FileNotFoundError, NotADirectoryError, PermissionError) as error: typer.secho(f"❌ Cannot navigate: {error}", fg=typer.colors.RED) except OSError as error: diff --git a/trushell/core/database.py b/trushell/core/database.py index 4fe9ed6..0df2696 100644 --- a/trushell/core/database.py +++ b/trushell/core/database.py @@ -1,6 +1,7 @@ from __future__ import annotations import sqlite3 +import threading from pathlib import Path from typing import List, Optional @@ -10,6 +11,7 @@ # Global state to track initialization _INITIALIZED = False +_INITIALIZE_LOCK = threading.Lock() _DB_PATH: Optional[Path] = None @@ -34,27 +36,31 @@ def _ensure_initialized() -> None: if _INITIALIZED: return - db_path = _get_db_path() - - # Open a direct connection to initialize. - # We do NOT use get_db_connection() here to avoid recursion. - conn = sqlite3.connect(str(db_path), check_same_thread=False) - try: - cursor = conn.cursor() - cursor.execute( - """CREATE TABLE IF NOT EXISTS todos ( - task TEXT, - category TEXT, - date_added TEXT, - date_completed TEXT, - status INTEGER, - position INTEGER - )""" - ) - conn.commit() - _INITIALIZED = True - finally: - conn.close() + with _INITIALIZE_LOCK: + if _INITIALIZED: + return + + db_path = _get_db_path() + + # Open a direct connection to initialize. + # We do NOT use get_db_connection() here to avoid recursion. + conn = sqlite3.connect(str(db_path), check_same_thread=False) + try: + cursor = conn.cursor() + cursor.execute( + """CREATE TABLE IF NOT EXISTS todos ( + task TEXT, + category TEXT, + date_added TEXT, + date_completed TEXT, + status INTEGER, + position INTEGER + )""" + ) + conn.commit() + _INITIALIZED = True + finally: + conn.close() def get_db_connection() -> sqlite3.Connection: