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" },