Add packaging, date-based versioning, and full install docs

Versions are now the release date and a revision within that day, padded to
two digits so they sort as text: 2026-08-21_01.

Neither packaging system accepts that form, so it is converted at the edge
rather than kept as a second version string that could drift out of step:
PEP 440 forbids dashes and underscores in a release segment, and a Debian
version may not contain an underscore at all. The date and revision in
__init__.py are the single source; pyproject reads the converted form, and
the tests check that pip and dpkg both order releases correctly.

packaging/build-deb.sh builds a .deb with plain dpkg-deb. Every dependency
is already in Debian, so apt resolves the lot; the package also blacklists
the DVB-T driver that would otherwise claim the receiver. Deliberately not
debhelper: the payload is pure Python with nothing to compile, and this way
the build needs nothing installed beyond dpkg.

The speech recognisers are not packaged for Debian and can only come from
pip, so they are suggested rather than depended on -- transcription is off
by default and reports plainly when no recogniser is present.

README now documents every dependency with its package name on Debian,
Fedora and Arch, how to let a user reach the receiver, and how to check the
install worked.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
The Dust Council 2026-08-21 21:05:51 -07:00
parent db3e0c79b9
commit 52fe16123f
5 changed files with 313 additions and 30 deletions

85
tests/test_version.py Normal file
View file

@ -0,0 +1,85 @@
"""The date-and-revision version scheme, and the forms packaging tools need."""
import re
import subprocess
import pytest
import bandsaunter
def test_displayed_version_is_date_and_padded_revision():
assert re.fullmatch(r"\d{4}-\d{2}-\d{2}_\d{2,}", bandsaunter.__version__), \
bandsaunter.__version__
def test_the_revision_is_padded_to_two_digits():
assert bandsaunter.__version__.split("_")[1] == \
f"{bandsaunter.VERSION_REVISION:02d}"
assert len(bandsaunter.__version__.split("_")[1]) >= 2
def test_the_date_is_a_real_date():
from datetime import date
year, month, day = bandsaunter.VERSION_DATE.split("-")
date(int(year), int(month), int(day)) # raises if not
def test_versions_sort_as_text_in_release_order():
"""Padding is the point: without it, revision 10 sorts before revision 2."""
def displayed(d, r):
return f"{d}_{r:02d}"
ordered = [displayed("2026-08-21", 1), displayed("2026-08-21", 2),
displayed("2026-08-21", 10), displayed("2026-09-01", 1),
displayed("2026-12-31", 3), displayed("2027-01-01", 1)]
assert sorted(ordered) == ordered
def test_the_pep440_form_is_valid_where_the_displayed_one_is_not():
from packaging.version import InvalidVersion, Version
with pytest.raises(InvalidVersion):
Version(bandsaunter.__version__) # dashes and underscore
assert Version(bandsaunter.pep440_version())
def test_the_pep440_form_keeps_release_order():
from packaging.version import Version
assert Version("2026.8.21.1") < Version("2026.8.21.2")
assert Version("2026.8.21.2") < Version("2026.8.21.10")
assert Version("2026.8.21.10") < Version("2026.9.1.1")
assert Version("2026.12.31.1") < Version("2027.1.1.1")
def test_the_debian_form_is_acceptable_to_dpkg():
"""Debian versions may not contain underscores."""
debian = bandsaunter.debian_version()
assert "_" not in debian
assert re.fullmatch(r"[0-9][0-9.+~:-]*", debian), debian
@pytest.mark.skipif(not __import__("shutil").which("dpkg"),
reason="dpkg not available")
def test_dpkg_agrees_the_debian_versions_order_correctly():
def older(a, b):
return subprocess.run(["dpkg", "--compare-versions", a, "lt", b]
).returncode == 0
assert older("2026.08.21.01", "2026.08.21.02")
assert older("2026.08.21.02", "2026.08.21.10")
assert older("2026.08.21.10", "2026.09.01.01")
assert older("2026.12.31.01", "2027.01.01.01")
def test_all_three_forms_describe_the_same_release():
date_part = bandsaunter.VERSION_DATE
rev = bandsaunter.VERSION_REVISION
assert bandsaunter.__version__ == f"{date_part}_{rev:02d}"
year, month, day = date_part.split("-")
assert bandsaunter.pep440_version() == \
f"{int(year)}.{int(month)}.{int(day)}.{rev}"
assert bandsaunter.debian_version() == \
f"{date_part.replace('-', '.')}.{rev:02d}"
def test_the_installed_package_reports_the_displayed_version():
out = subprocess.run(["python3", "-m", "bandsaunter", "--version"],
capture_output=True, text=True, timeout=60)
assert bandsaunter.__version__ in out.stdout