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,
|
||||
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}`
|
||||
|
||||
----
|
||||
|
||||
|
|
|
|||
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:
|
||||
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
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
[env]
|
||||
'_'.python.venv = { path = ".venv", create = true }
|
||||
DATABASE_URL = "sqlite:///database.db"
|
||||
PREK_COLOR = "never"
|
||||
|
||||
[tools]
|
||||
difftastic = "latest"
|
||||
|
|
|
|||
15
prek.toml
15
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]]
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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]]
|
||||
name = "teilchensammler-cli"
|
||||
version = "0.4.9"
|
||||
version = "0.5.0"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "ciso8601" },
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue