Compare commits
11 commits
dd79123d7b
...
98ebb69fa4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
98ebb69fa4 | ||
|
|
f4f2e98c94 | ||
|
|
5d10f25c39 | ||
|
|
db47501cd0 | ||
|
|
1b565e5a4e | ||
|
|
dd42e6119c | ||
|
|
84f488f06a | ||
|
|
3e898d1771 | ||
|
|
13239d6f73 | ||
|
|
90388ed48c | ||
|
|
387a6f4e34 |
10 changed files with 216 additions and 209 deletions
|
|
@ -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,
|
Where `segment` is one of: major, minor, patch, stable, alpha, beta, rc,
|
||||||
post, dev
|
post, dev
|
||||||
* commit
|
* commit
|
||||||
* push commits
|
* When we really really want to release:
|
||||||
* use lazygit to create the new version tag
|
* `just release {Release Name can be several words}`
|
||||||
* remember to prefix it with "v"
|
|
||||||
* push the tags, using lazygit
|
|
||||||
|
|
||||||
----
|
----
|
||||||
|
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 29 KiB |
48
justfile
48
justfile
|
|
@ -9,14 +9,6 @@ default:
|
||||||
setup:
|
setup:
|
||||||
uv sync
|
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 to observe log messages
|
||||||
console:
|
console:
|
||||||
uv run textual console
|
uv run textual console
|
||||||
|
|
@ -29,16 +21,16 @@ run-console:
|
||||||
run:
|
run:
|
||||||
uv run python -m {{ the_app }}
|
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 dependencies to new versions
|
||||||
update-deps:
|
update-deps:
|
||||||
uv lock --upgrade
|
uv lock --upgrade
|
||||||
uv sync
|
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
|
# Run tests, ARGS are passed-through to pytest
|
||||||
test *ARGS:
|
test *ARGS:
|
||||||
uv run pytest tests.py -m "not final" {{ ARGS }}
|
uv run pytest tests.py -m "not final" {{ ARGS }}
|
||||||
|
|
@ -50,26 +42,18 @@ alltests *ARGS:
|
||||||
coverage:
|
coverage:
|
||||||
uv run pytest tests.py --cov=src/ --cov-report term --cov-report xml --cov-report html --cov-config pyproject.toml
|
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:
|
lint:
|
||||||
uv run ruff check .
|
uv run ruff check .
|
||||||
markdownlint-cli2 .
|
markdownlint-cli2 .
|
||||||
uv run twine check --strict dist/*
|
uv run twine check --strict dist/*
|
||||||
|
prek validate-config prek.toml
|
||||||
|
|
||||||
# pretend we are CI
|
release *release_name:
|
||||||
ci: lint
|
|
||||||
prek run --all-files
|
|
||||||
# woodpecker-cli exec "whatever"
|
|
||||||
|
|
||||||
[private]
|
|
||||||
bump segment:
|
|
||||||
uv version --bump {{ segment }}
|
|
||||||
|
|
||||||
[private]
|
|
||||||
release segment *release_name:
|
|
||||||
#!/usr/bin/env fish
|
#!/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 ""
|
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 add pyproject.toml uv.lock
|
||||||
git commit -m "Release version $tag"
|
git commit -m "Release version $tag"
|
||||||
|
|
@ -78,7 +62,15 @@ release segment *release_name:
|
||||||
git push
|
git push
|
||||||
git push origin tag "v$tag"
|
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
|
uv build --wheel --clear
|
||||||
|
|
||||||
fj release create "v$tag: $release_name" --prerelease --tag "v$tag" --attach dist/*.whl --body "$body"
|
# upload to Package Registry
|
||||||
# uv run twine upload --config-file ~/.config/pypirc --repository code.cbo dist/*.whl
|
upload:
|
||||||
|
uv run twine upload --repository code.cbo --config-file ~/.config/pypirc dist/*.whl
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
[env]
|
[env]
|
||||||
'_'.python.venv = { path = ".venv", create = true }
|
'_'.python.venv = { path = ".venv", create = true }
|
||||||
DATABASE_URL = "sqlite:///database.db"
|
DATABASE_URL = "sqlite:///database.db"
|
||||||
|
PREK_COLOR = "never"
|
||||||
|
|
||||||
[tools]
|
[tools]
|
||||||
difftastic = "latest"
|
difftastic = "latest"
|
||||||
|
|
|
||||||
15
prek.toml
15
prek.toml
|
|
@ -1,5 +1,7 @@
|
||||||
#:tombi schema.strict = false
|
#:tombi schema.strict = false
|
||||||
|
|
||||||
|
# see also: https://prek.j178.dev/builtin/
|
||||||
|
|
||||||
exclude = { glob = ["__snapshots__/**", "__pycache__", "dist/**"] }
|
exclude = { glob = ["__snapshots__/**", "__pycache__", "dist/**"] }
|
||||||
|
|
||||||
[[repos]]
|
[[repos]]
|
||||||
|
|
@ -7,19 +9,10 @@ repo = "builtin"
|
||||||
hooks = [
|
hooks = [
|
||||||
{ id = "check-case-conflict" },
|
{ id = "check-case-conflict" },
|
||||||
{ id = "check-merge-conflict" },
|
{ id = "check-merge-conflict" },
|
||||||
{ id = "check-json" },
|
|
||||||
{ id = "check-symlinks" },
|
|
||||||
{ id = "check-toml" },
|
{ id = "check-toml" },
|
||||||
{ id = "check-yaml", args = [
|
|
||||||
# "--allow-multiple-documents", #https://prek.j178.dev/builtin/#check-yaml
|
|
||||||
] },
|
|
||||||
{ id = "end-of-file-fixer" },
|
{ id = "end-of-file-fixer" },
|
||||||
{ id = "mixed-line-ending", args = [
|
{ id = "mixed-line-ending", args = [ "--fix=lf" ] },
|
||||||
"--fix=lf", # https://prek.j178.dev/builtin/#mixed-line-ending
|
{ id = "trailing-whitespace", args = [ "--markdown-linebreak-ext=md" ] },
|
||||||
] },
|
|
||||||
{ id = "trailing-whitespace", args = [
|
|
||||||
"--markdown-linebreak-ext=md", # https://prek.j178.dev/builtin/#trailing-whitespace
|
|
||||||
] },
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[repos]]
|
[[repos]]
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
[project]
|
[project]
|
||||||
name = "teilchensammler-cli"
|
name = "teilchensammler-cli"
|
||||||
version = "0.4.9"
|
version = "0.5.0"
|
||||||
description = "Build up and maintain an inventory of electronics parts and tools."
|
description = "Build up and maintain an inventory of electronics parts and tools."
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.14,<4.0"
|
requires-python = ">=3.14,<4.0"
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,16 @@ This is where the application is implemented.
|
||||||
|
|
||||||
import logging
|
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.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 .database import create_db_and_tables
|
||||||
from .tui import AddInventoryScreen
|
from .models import add_to_database, load_initial_data, make_teilchen_input
|
||||||
|
|
||||||
logging.basicConfig(
|
logging.basicConfig(
|
||||||
level="NOTSET",
|
level="NOTSET",
|
||||||
|
|
@ -16,6 +21,8 @@ logging.basicConfig(
|
||||||
)
|
)
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
TEILCHEN_DATA_HEADER = "pk Name Description Number Tags".split()
|
||||||
|
|
||||||
|
|
||||||
class SammlerApp(App):
|
class SammlerApp(App):
|
||||||
async def on_mount(self) -> None:
|
async def on_mount(self) -> None:
|
||||||
|
|
@ -23,6 +30,68 @@ class SammlerApp(App):
|
||||||
self.push_screen(AddInventoryScreen())
|
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
|
# so we can import it without running it
|
||||||
app = SammlerApp()
|
app = SammlerApp()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,14 @@ import uuid
|
||||||
|
|
||||||
from sqlmodel import (
|
from sqlmodel import (
|
||||||
Field,
|
Field,
|
||||||
SQLModel,
|
|
||||||
Session,
|
|
||||||
select,
|
|
||||||
Sequence,
|
Sequence,
|
||||||
|
Session,
|
||||||
|
SQLModel,
|
||||||
|
select,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from .database import engine
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -35,6 +37,7 @@ async def make_teilchen_input(text: str) -> TeilchenCreate | None:
|
||||||
Returns `None` otherwise.
|
Returns `None` otherwise.
|
||||||
"""
|
"""
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from natsort import natsorted
|
from natsort import natsorted
|
||||||
|
|
||||||
text = text.strip()
|
text = text.strip()
|
||||||
|
|
@ -88,3 +91,13 @@ async def load_initial_data() -> Sequence[Teilchen]:
|
||||||
) # ty:ignore[no-matching-overload]
|
) # ty:ignore[no-matching-overload]
|
||||||
all_teilchen = session.exec(statement).all()
|
all_teilchen = session.exec(statement).all()
|
||||||
return all_teilchen
|
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
|
||||||
|
|
|
||||||
|
|
@ -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()
|
|
||||||
2
uv.lock
generated
2
uv.lock
generated
|
|
@ -1153,7 +1153,7 @@ wheels = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "teilchensammler-cli"
|
name = "teilchensammler-cli"
|
||||||
version = "0.4.9"
|
version = "0.5.0"
|
||||||
source = { editable = "." }
|
source = { editable = "." }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "ciso8601" },
|
{ name = "ciso8601" },
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue