Compare commits

..

No commits in common. "cf192c7d021972fac45035494f697707e6574cc2" and "444839b8e651eb47b925a92ea8fe0980be0fdcd3" have entirely different histories.

8 changed files with 405 additions and 578 deletions

View file

@ -1,7 +0,0 @@
[env]
'_'.python.venv = { path = ".venv", create = true }
[tools]
python = "3.14"
ruff = "latest"
uv = "latest"

View file

@ -49,7 +49,6 @@ teilchensammler-cli = "teilchensammler_cli.main:main"
[dependency-groups] [dependency-groups]
dev = [ dev = [
"ipython>=9.10.0",
"pytest>=9.0.1", "pytest>=9.0.1",
"pytest-asyncio>=1.3.0", "pytest-asyncio>=1.3.0",
"pytest-cov>=7.0.0", "pytest-cov>=7.0.0",

View file

@ -1,6 +1,6 @@
from sqlmodel import SQLModel, create_engine from sqlmodel import SQLModel, create_engine
sqlite_url = "sqlite:///:memory:" # TODO: get url from environment sqlite_url = "sqlite:///:memory:"
engine = create_engine(sqlite_url, echo=True) engine = create_engine(sqlite_url, echo=True)

View file

@ -19,6 +19,13 @@ logging.basicConfig(
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class SammlerApp(App[None]):
async def on_mount(self) -> None:
create_db_and_tables()
try_this()
_ = self.push_screen(AddInventoryScreen())
FAKE_INPUT = """ FAKE_INPUT = """
Ein Teilchen. #Tag03 #tag12 "Dieses Teilchen ist nur zum testen" #tag1 #tag2 Ein Teilchen. #Tag03 #tag12 "Dieses Teilchen ist nur zum testen" #tag1 #tag2
""".strip(" \n") """.strip(" \n")
@ -38,17 +45,11 @@ def try_this():
logger.info(f"{db_teilchen=}") logger.info(f"{db_teilchen=}")
class SammlerApp(App[None]):
async def on_mount(self) -> None:
create_db_and_tables()
try_this()
_ = self.push_screen(AddInventoryScreen())
app = SammlerApp() app = SammlerApp()
def main() -> None: def main() -> None:
# app = SammlerApp()
app.run() app.run()

View file

@ -1,6 +1,8 @@
import logging import logging
import re
import uuid import uuid
from natsort import natsorted
from sqlmodel import ( from sqlmodel import (
Field, Field,
SQLModel, SQLModel,
@ -9,25 +11,24 @@ from sqlmodel import (
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
TeilchenDatum = tuple[int, str, str, str, str] class TeilchenBase(SQLModel):
text: str
class TeilchenCreate(SQLModel): class TeilchenCreate(TeilchenBase):
description: str | None description: str | None
name: str = Field(index=True) name: str = Field(index=True)
number: int = Field(default=1) number: int = Field(default=1)
text: str
tags: str | None tags: str | None
text: str # The original input as entered by the user
class Teilchen(TeilchenCreate, table=True): class Teilchen(TeilchenCreate, table=True):
id: uuid.UUID = Field(default_factory=uuid.uuid7, primary_key=True) # ty:ignore[unresolved-attribute] id: uuid.UUID = Field(default_factory=uuid.uuid7, primary_key=True)
async def make_teilchen_input(text: str) -> TeilchenCreate | None: def make_teilchen_input(text: str) -> TeilchenCreate | None:
import re
from natsort import natsorted
if not text: if not text:
logger.error("Empty text.") logger.error("Empty text.")
return None return None
@ -40,14 +41,13 @@ async def make_teilchen_input(text: str) -> TeilchenCreate | None:
description_start = text.find('"', name_end + 1) description_start = text.find('"', name_end + 1)
description_end = text.find('"', description_start + 1) description_end = text.find('"', description_start + 1)
if description_end > description_start: description = text[description_start:description_end]
description = text[description_start:description_end] if not description:
else: logger.warning("Could not extract description.")
description = ""
tags = re.findall(r"#\w+", text.lower()) tags = re.findall(r"#\w+", text.lower())
if not tags: if not tags:
logger.info("No tags found in text.") logger.warning("No tags found in text")
return TeilchenCreate( return TeilchenCreate(
name=name, name=name,
@ -55,14 +55,3 @@ async def make_teilchen_input(text: str) -> TeilchenCreate | None:
tags=" ".join(natsorted(tags)), tags=" ".join(natsorted(tags)),
text=text, text=text,
) )
async def load_initial_data() -> list[TeilchenDatum]:
return [
(0, "Name0", "Description0", "9000", "#tag0 #tag00 #tag000"),
(1, "Name1", "Description1", "9001", "#tag1 #tag11 #tag111"),
(2, "Name2", "Description2", "9002", "#tag2 #tag22 #tag222"),
(3, "Name3", "Description3", "9003", "#tag3 #tag33 #tag333"),
(4, "Name4", "Description4", "9004", "#tag4 #tag44 #tag444"),
(5, "Name5", "Description5", "9005", "#tag5 #tag55 #tag555"),
]

View file

@ -1,15 +1,13 @@
from typing import Literal, final, override
from textual.app import ComposeResult from textual.app import ComposeResult
from textual.containers import HorizontalGroup from textual.containers import HorizontalGroup
from textual.screen import Screen from textual.screen import Screen
from textual.widget import Widget from textual.widget import Widget
from textual.widgets import Button, DataTable, Footer, Header, Input, Static from textual.widgets import DataTable, Footer, Header, Static, Input, Button
from .models import load_initial_data
FAKE_DATA_HEADER = "pk Name Description Number Tags"
@final
class SearchBar(Static): class SearchBar(Static):
DEFAULT_CSS = """ DEFAULT_CSS = """
#teilchen-input { #teilchen-input {
@ -20,6 +18,7 @@ class SearchBar(Static):
} }
""" """
@override
def compose(self) -> ComposeResult: def compose(self) -> ComposeResult:
with HorizontalGroup(id="search-bar-widget"): with HorizontalGroup(id="search-bar-widget"):
yield Input( yield Input(
@ -41,17 +40,41 @@ class SearchBar(Static):
) )
TeilchenDatum = tuple[int, str, str, str, str]
TeilchenHeader = tuple[
Literal["pk"],
Literal["Name"],
Literal["Description"],
Literal["Number"],
Literal["Tags"],
]
FAKE_DATA: list[TeilchenHeader | TeilchenDatum] = [
("pk", "Name", "Description", "Number", "Tags"),
(0, "Name0", "Description0", "9000", "#tag0 #tag00 #tag000"),
(1, "Name1", "Description1", "9001", "#tag1 #tag11 #tag111"),
(2, "Name2", "Description2", "9002", "#tag2 #tag22 #tag222"),
(3, "Name3", "Description3", "9003", "#tag3 #tag33 #tag333"),
(4, "Name4", "Description4", "9004", "#tag4 #tag44 #tag444"),
(5, "Name5", "Description5", "9005", "#tag5 #tag55 #tag555"),
]
@final
class SearchResults(Widget): class SearchResults(Widget):
@override
def compose(self) -> ComposeResult: def compose(self) -> ComposeResult:
yield DataTable(id="table-search-result", cursor_type="row", zebra_stripes=True) yield DataTable(id="table-search-result", cursor_type="row", zebra_stripes=True)
async def on_mount(self) -> None: async def on_mount(self) -> None:
table: DataTable[None] = self.query_one(DataTable[None]) table: DataTable[None] = self.query_one(DataTable[None])
_ = table.add_columns(FAKE_DATA_HEADER) _ = table.add_columns(*FAKE_DATA[0]) # pyright: ignore[reportArgumentType]
_ = table.add_rows(await load_initial_data()) # ty:ignore[invalid-argument-type] _ = table.add_rows(FAKE_DATA[1:]) # pyright: ignore[reportArgumentType]
class AddInventoryScreen(Screen[None]): class AddInventoryScreen(Screen[None]):
@override
def compose(self) -> ComposeResult: def compose(self) -> ComposeResult:
yield Header() yield Header()
yield SearchBar() yield SearchBar()

View file

@ -1,4 +1,2 @@
def test_initial_layout(snap_compare): # pyright: ignore[reportMissingParameterType, reportUnknownParameterType] def test_initial_layout(snap_compare): # pyright: ignore[reportMissingParameterType, reportUnknownParameterType]
from teilchensammler_cli.main import app assert snap_compare("src/teilchensammler_cli/main.py", terminal_size=(130, 40))
assert snap_compare(app, terminal_size=(130, 40))

878
uv.lock generated

File diff suppressed because it is too large Load diff