diff --git a/README.md b/README.md index 5b14093..1915809 100644 --- a/README.md +++ b/README.md @@ -108,10 +108,8 @@ Please don't: this is my little human-created thing. Where `segment` is one of: major, minor, patch, stable, alpha, beta, rc, post, dev * commit -* push commits -* use lazygit to create the new version tag - * remember to prefix it with "v" -* push the tags, using lazygit +* When we really really want to release: + * `just release {Release Name can be several words}` ---- diff --git a/__snapshots__/tests/test_initial_layout.raw b/__snapshots__/tests/test_initial_layout.raw index 7c612d4..ab8578c 100644 --- a/__snapshots__/tests/test_initial_layout.raw +++ b/__snapshots__/tests/test_initial_layout.raw @@ -19,208 +19,208 @@ font-weight: 700; } - .terminal-1669918775-matrix { + .terminal-2753228840-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-1669918775-title { + .terminal-2753228840-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-1669918775-r1 { fill: #c5c8c6 } -.terminal-1669918775-r2 { fill: #e0e0e0 } -.terminal-1669918775-r3 { fill: #121212 } -.terminal-1669918775-r4 { fill: #0178d4 } -.terminal-1669918775-r5 { fill: #7ae998 } -.terminal-1669918775-r6 { fill: #2d2d2d } -.terminal-1669918775-r7 { fill: #797979 } -.terminal-1669918775-r8 { fill: #0a180e;font-weight: bold } -.terminal-1669918775-r9 { fill: #e0e0e0;font-weight: bold } -.terminal-1669918775-r10 { fill: #008139 } -.terminal-1669918775-r11 { fill: #0d0d0d } -.terminal-1669918775-r12 { fill: #000000 } -.terminal-1669918775-r13 { fill: #495259 } -.terminal-1669918775-r14 { fill: #ffa62b;font-weight: bold } + .terminal-2753228840-r1 { fill: #c5c8c6 } +.terminal-2753228840-r2 { fill: #e0e0e0 } +.terminal-2753228840-r3 { fill: #121212 } +.terminal-2753228840-r4 { fill: #0178d4 } +.terminal-2753228840-r5 { fill: #7ae998 } +.terminal-2753228840-r6 { fill: #2d2d2d } +.terminal-2753228840-r7 { fill: #797979 } +.terminal-2753228840-r8 { fill: #0a180e;font-weight: bold } +.terminal-2753228840-r9 { fill: #e0e0e0;font-weight: bold } +.terminal-2753228840-r10 { fill: #008139 } +.terminal-2753228840-r11 { fill: #0d0d0d } +.terminal-2753228840-r12 { fill: #000000 } +.terminal-2753228840-r13 { fill: #495259 } +.terminal-2753228840-r14 { fill: #ffa62b;font-weight: bold } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - SammlerApp + SammlerApp - - - - SammlerApp -▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ -Enter Teilchen information: name, description, #tags Add  Search  -▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ - pk                                    Name   Description   Number  Tags         - 019c5d37-d43b-72d0-a4dd-b4816f4c5b3a  name0  description0  9001    tag0 tag0-0  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -▆▆ - -^p palette + + + + SammlerApp +▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ +This is a name. "This is the description" #these #are #tags Add  Search  +▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + pk                                    Name   Description   Number  Tags         + 019c5d37-d43b-72d0-a4dd-b4816f4c5b3a  name0  description0  9001    tag0 tag0-0  + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +▆▆ + +^p palette diff --git a/justfile b/justfile index 4a25b54..0c8c3d7 100644 --- a/justfile +++ b/justfile @@ -9,14 +9,6 @@ default: setup: uv sync -# builds a package -build: - uv build --wheel --clear - -# upload to Package Registry -upload: build - uv run twine upload --repository code.cbo --config-file ~/.config/pypirc dist/*.whl - # console to observe log messages console: uv run textual console @@ -29,16 +21,16 @@ run-console: run: uv run python -m {{ the_app }} -# export dependencies into requirements files -exports-deps: - uv export {{ uv_export_options }} --output-file requirements.txt - uv export {{ uv_export_options }} --output-file requirements.dev.txt --only-dev - # Update dependencies to new versions update-deps: uv lock --upgrade uv sync +# export dependencies into requirements files +exports-deps: + uv export {{ uv_export_options }} --output-file requirements.txt + uv export {{ uv_export_options }} --output-file requirements.dev.txt --only-dev + # Run tests, ARGS are passed-through to pytest test *ARGS: uv run pytest tests.py -m "not final" {{ ARGS }} @@ -50,26 +42,18 @@ alltests *ARGS: coverage: uv run pytest tests.py --cov=src/ --cov-report term --cov-report xml --cov-report html --cov-config pyproject.toml -# run python and markdown +# lint python, markdown, wheel file names, prek config lint: uv run ruff check . markdownlint-cli2 . uv run twine check --strict dist/* + prek validate-config prek.toml -# pretend we are CI -ci: lint - prek run --all-files -# woodpecker-cli exec "whatever" - -[private] -bump segment: - uv version --bump {{ segment }} - -[private] -release segment *release_name: +release *release_name: #!/usr/bin/env fish + set -l release_name {{ release_name }} test -f releasenotes.md; and set -l body "$(cat releasenotes.md)"; or set -l body "" - set -l tag (uv version --short --output-format text --bump {{ segment }}) + set -l tag (uv version --short --output-format text) git add pyproject.toml uv.lock git commit -m "Release version $tag" @@ -78,7 +62,15 @@ release segment *release_name: git push git push origin tag "v$tag" + just build + + fj release create "v$tag: $release_name" --tag "v$tag" --attach dist/*.whl --body "$body" + # just upload + +# builds a package +build: uv build --wheel --clear - fj release create "v$tag: $release_name" --prerelease --tag "v$tag" --attach dist/*.whl --body "$body" - # uv run twine upload --config-file ~/.config/pypirc --repository code.cbo dist/*.whl +# upload to Package Registry +upload: + uv run twine upload --repository code.cbo --config-file ~/.config/pypirc dist/*.whl diff --git a/mise.toml b/mise.toml index ca3c768..cc420a6 100644 --- a/mise.toml +++ b/mise.toml @@ -1,6 +1,7 @@ [env] '_'.python.venv = { path = ".venv", create = true } DATABASE_URL = "sqlite:///database.db" +PREK_COLOR = "never" [tools] difftastic = "latest" diff --git a/prek.toml b/prek.toml index 083a1dc..e22ef62 100644 --- a/prek.toml +++ b/prek.toml @@ -1,5 +1,7 @@ #:tombi schema.strict = false +# see also: https://prek.j178.dev/builtin/ + exclude = { glob = ["__snapshots__/**", "__pycache__", "dist/**"] } [[repos]] @@ -7,19 +9,10 @@ repo = "builtin" hooks = [ { id = "check-case-conflict" }, { id = "check-merge-conflict" }, - { id = "check-json" }, - { id = "check-symlinks" }, { id = "check-toml" }, - { id = "check-yaml", args = [ - # "--allow-multiple-documents", #https://prek.j178.dev/builtin/#check-yaml - ] }, { id = "end-of-file-fixer" }, - { id = "mixed-line-ending", args = [ - "--fix=lf", # https://prek.j178.dev/builtin/#mixed-line-ending - ] }, - { id = "trailing-whitespace", args = [ - "--markdown-linebreak-ext=md", # https://prek.j178.dev/builtin/#trailing-whitespace - ] }, + { id = "mixed-line-ending", args = [ "--fix=lf" ] }, + { id = "trailing-whitespace", args = [ "--markdown-linebreak-ext=md" ] }, ] [[repos]] diff --git a/pyproject.toml b/pyproject.toml index 2fd07df..6356117 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "teilchensammler-cli" -version = "0.4.9" +version = "0.5.0" description = "Build up and maintain an inventory of electronics parts and tools." readme = "README.md" requires-python = ">=3.14,<4.0" diff --git a/src/teilchensammler_cli/main.py b/src/teilchensammler_cli/main.py index c89c61a..42feda2 100644 --- a/src/teilchensammler_cli/main.py +++ b/src/teilchensammler_cli/main.py @@ -4,11 +4,16 @@ This is where the application is implemented. import logging -from textual.app import App +from textual import on +from textual.app import App, ComposeResult +from textual.containers import HorizontalGroup from textual.logging import TextualHandler +from textual.screen import Screen +from textual.widget import Widget +from textual.widgets import Button, DataTable, Footer, Header, Input, Static from .database import create_db_and_tables -from .tui import AddInventoryScreen +from .models import add_to_database, load_initial_data, make_teilchen_input logging.basicConfig( level="NOTSET", @@ -16,6 +21,8 @@ logging.basicConfig( ) logger = logging.getLogger(__name__) +TEILCHEN_DATA_HEADER = "pk Name Description Number Tags".split() + class SammlerApp(App): async def on_mount(self) -> None: @@ -23,6 +30,68 @@ class SammlerApp(App): self.push_screen(AddInventoryScreen()) +class SearchBar(Static): + DEFAULT_CSS = """ + #teilchen-input { + width: 4fr; + } + #button-search, #button-add { + width: 1fr; + } + """ + + def compose(self) -> ComposeResult: + with HorizontalGroup(id="search-bar-widget"): + yield Input( + placeholder='This is a name. "This is the description" #these #are #tags', + tooltip=( + "Enter a name followed by a period, then a description " + 'enclosed in double quotes ("). You should use #hashtags for any meta information. ' + "Hashtags can be placed anywhere." + ), + id="teilchen-input", + type="text", + ) + yield Button("Add", variant="success", classes="search-bar-buttons", id="button-add") + yield Button( + "Search", + variant="default", + classes="search-bar-buttons", + id="button-search", + ) + + @on(Input.Submitted) + async def parse_input(self, event: Input.Submitted) -> None: + + if not (tc := await make_teilchen_input(event.value)): + return + + event.input.value = "" + + teilchen = await add_to_database(tc) + self.screen.query_one(DataTable).add_row( + teilchen.id, teilchen.name, teilchen.description, teilchen.number, teilchen.tags + ) + + +class SearchResults(Widget): + def compose(self) -> ComposeResult: + yield DataTable(id="table-search-result", cursor_type="row", zebra_stripes=True) + + async def on_mount(self) -> None: + table: DataTable = self.query_one(DataTable) + table.add_columns(*TEILCHEN_DATA_HEADER) + table.add_rows(await load_initial_data()) + + +class AddInventoryScreen(Screen): + def compose(self) -> ComposeResult: + yield Header() + yield SearchBar() + yield SearchResults() + yield Footer() + + # so we can import it without running it app = SammlerApp() diff --git a/src/teilchensammler_cli/models.py b/src/teilchensammler_cli/models.py index 77619a8..bca6c42 100644 --- a/src/teilchensammler_cli/models.py +++ b/src/teilchensammler_cli/models.py @@ -3,12 +3,14 @@ import uuid from sqlmodel import ( Field, - SQLModel, - Session, - select, Sequence, + Session, + SQLModel, + select, ) +from .database import engine + logger = logging.getLogger(__name__) @@ -35,6 +37,7 @@ async def make_teilchen_input(text: str) -> TeilchenCreate | None: Returns `None` otherwise. """ import re + from natsort import natsorted text = text.strip() @@ -88,3 +91,13 @@ async def load_initial_data() -> Sequence[Teilchen]: ) # ty:ignore[no-matching-overload] all_teilchen = session.exec(statement).all() return all_teilchen + + +async def add_to_database(tc: TeilchenCreate) -> Teilchen: + with Session(engine) as session: + teilchen = Teilchen.model_validate(tc) + session.add(teilchen) + session.commit() + session.refresh(teilchen) + + return teilchen diff --git a/src/teilchensammler_cli/tui.py b/src/teilchensammler_cli/tui.py deleted file mode 100644 index fd5cd32..0000000 --- a/src/teilchensammler_cli/tui.py +++ /dev/null @@ -1,59 +0,0 @@ -from textual.app import ComposeResult -from textual.containers import HorizontalGroup -from textual.screen import Screen -from textual.widget import Widget -from textual.widgets import Button, DataTable, Footer, Header, Input, Static - -from .models import load_initial_data - - -TEILCHEN_DATA_HEADER = "pk Name Description Number Tags".split() - - -class SearchBar(Static): - DEFAULT_CSS = """ - #teilchen-input { - width: 4fr; - } - #button-search, #button-add { - width: 1fr; - } - """ - - def compose(self) -> ComposeResult: - with HorizontalGroup(id="search-bar-widget"): - yield Input( - placeholder='This is a name. "This is the description" #these #are #tags', - tooltip=( - "Enter a name followed by a period, then a description " - 'enclosed in double quotes ("). You should use #hashtags for any meta information. ' - "Hashtags can be placed anywhere." - ), - id="teilchen-input", - type="text", - ) - yield Button("Add", variant="success", classes="search-bar-buttons", id="button-add") - yield Button( - "Search", - variant="default", - classes="search-bar-buttons", - id="button-search", - ) - - -class SearchResults(Widget): - def compose(self) -> ComposeResult: - yield DataTable(id="table-search-result", cursor_type="row", zebra_stripes=True) - - async def on_mount(self) -> None: - table: DataTable = self.query_one(DataTable) - table.add_columns(*TEILCHEN_DATA_HEADER) - table.add_rows(await load_initial_data()) - - -class AddInventoryScreen(Screen): - def compose(self) -> ComposeResult: - yield Header() - yield SearchBar() - yield SearchResults() - yield Footer() diff --git a/uv.lock b/uv.lock index e6597d8..0c0c8bc 100644 --- a/uv.lock +++ b/uv.lock @@ -1153,7 +1153,7 @@ wheels = [ [[package]] name = "teilchensammler-cli" -version = "0.4.9" +version = "0.5.0" source = { editable = "." } dependencies = [ { name = "ciso8601" },