Compare commits
No commits in common. "cf192c7d021972fac45035494f697707e6574cc2" and "444839b8e651eb47b925a92ea8fe0980be0fdcd3" have entirely different histories.
cf192c7d02
...
444839b8e6
8 changed files with 405 additions and 578 deletions
|
|
@ -1,7 +0,0 @@
|
||||||
[env]
|
|
||||||
'_'.python.venv = { path = ".venv", create = true }
|
|
||||||
|
|
||||||
[tools]
|
|
||||||
python = "3.14"
|
|
||||||
ruff = "latest"
|
|
||||||
uv = "latest"
|
|
||||||
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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]
|
||||||
else:
|
if not description:
|
||||||
description = ""
|
logger.warning("Could not extract 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"),
|
|
||||||
]
|
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
4
tests.py
4
tests.py
|
|
@ -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))
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue