Source code for vibeqc.banner
"""Runtime banner and linked-library version reporter.
Printed at the top of any serious vibe-qc text output so a user can always
identify exactly which build of the program produced a given log.
Programmatic API
----------------
``banner()``
Return the full multi-line banner as a string.
``print_banner()``
Convenience: ``print(banner(), **kwargs)``.
``library_versions()``
Dict of the versions of linked native dependencies (libint, libxc,
spglib) plus the package version. Useful in tests and bug reports.
The ``format_scf_trace()`` helper in :mod:`vibeqc.scf_log` prepends the
banner automatically so a persisted SCF run log carries its provenance.
"""
from __future__ import annotations
import sys
from typing import Mapping, TextIO
# Package version — reads the installed metadata so it tracks pyproject.toml
# automatically at build/install time. Falls back to an explicit label if the
# package isn't found (e.g. running from a source tree without installation).
try:
from importlib.metadata import PackageNotFoundError, version as _pkg_version
except ImportError: # pragma: no cover — Python 3.11+ always ships this
_pkg_version = None
PackageNotFoundError = Exception # type: ignore[misc, assignment]
__all__ = [
"VIBEQC_VERSION",
"banner",
"print_banner",
"library_versions",
]
def _compute_version() -> str:
"""Read the installed distribution metadata. The distribution is
named ``vibe-qc`` on PyPI since the rename; older local installs
may still be listed as the legacy ``vibeqc``, so we try both."""
if _pkg_version is None:
return "0.0.0+unknown"
for dist_name in ("vibe-qc", "vibeqc"):
try:
return _pkg_version(dist_name)
except PackageNotFoundError:
continue
return "0.0.0+unknown"
VIBEQC_VERSION: str = _compute_version()
[docs]
def library_versions() -> Mapping[str, str]:
"""Return the version strings of vibe-qc itself and its native
dependencies (libint, libxc, spglib). Keys are always present;
values are ``"unknown"`` if a probe fails.
The ``"vibe-qc"`` key carries the project version. The legacy
``"vibeqc"`` key is kept as an alias so code that predates the
rename keeps working without change.
"""
versions: dict[str, str] = {
"vibe-qc": VIBEQC_VERSION,
"vibeqc": VIBEQC_VERSION, # alias, retained for back-compat
}
# Probe the compiled extension. Each of the three libraries exposes a
# version accessor; every version is known at link time.
try:
from . import _vibeqc_core as _core
except ImportError:
versions.update({"libint": "unknown", "libxc": "unknown",
"spglib": "unknown"})
return versions
try:
versions["libint"] = _core.libint_version()
except Exception:
versions["libint"] = "unknown"
try:
versions["libxc"] = getattr(_core, "libxc_version",
lambda: "unknown")()
except Exception:
versions["libxc"] = "unknown"
try:
versions["spglib"] = _core.spglib_version()
except Exception:
versions["spglib"] = "unknown"
return versions
_COPYRIGHT_LINE = "© Michael F. Peintinger · MPL 2.0"
[docs]
def banner(*, width: int = 70) -> str:
"""Return the full banner as a single string, no trailing newline.
``width`` controls the horizontal extent; defaults to a terminal-
friendly 70 characters. The banner is pure ASCII + two box-drawing
characters so it renders cleanly in any modern UTF-8 terminal.
"""
versions = library_versions()
libs = (
f"libint {versions['libint']} · "
f"libxc {versions['libxc']} · "
f"spglib {versions['spglib']}"
)
header = (
f"vibe-qc {VIBEQC_VERSION} — Quantum chemistry for molecules and solids"
)
# Adjust width if either line is wider than requested — banner should
# never truncate content.
width = max(width, len(header) + 4, len(_COPYRIGHT_LINE) + 4, len(libs) + 4)
bar = "═" * (width - 2)
top = f"╔{bar}╗"
bot = f"╚{bar}╝"
def _row(text: str) -> str:
pad = (width - 2) - len(text)
return f"║ {text}{' ' * (pad - 1)}║"
return "\n".join([
top,
_row(header),
_row(_COPYRIGHT_LINE),
_row(f"linked: {libs}"),
bot,
])
[docs]
def print_banner(*, file: TextIO = sys.stdout, width: int = 70) -> None:
"""Print the banner to ``file`` (default: stdout)."""
print(banner(width=width), file=file)