[bin] Simplify stowage

This commit is contained in:
2026-01-09 22:22:34 +00:00
parent 613a3c143d
commit 307ec18764

View File

@@ -34,96 +34,50 @@ import shutil
import sys import sys
from collections.abc import Callable from collections.abc import Callable
from pathlib import Path from pathlib import Path
from typing import IO, List from typing import List
from _colorize import ANSIColors, can_colorize # ty: ignore[unresolved-import]
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class Colours(object): # Make use of Python internal _colorize module for ANSI colors
"""ANSI color codes for terminal output""" # this will break, but we want keep to zero depdencies for the script.
def get_color(name: str):
# Reset """Get a color code by name from the Python stdlib."""
RESET = "\033[0m" if not can_colorize():
return ""
# Regular colors return getattr(ANSIColors, name.replace(" ", "_").upper(), "")
BLACK = "\033[30m"
RED = "\033[31m"
GREEN = "\033[32m"
YELLOW = "\033[33m"
BLUE = "\033[34m"
MAGENTA = "\033[35m"
CYAN = "\033[36m"
WHITE = "\033[37m"
# Copied from _colorize in Python stdlib
def can_colorize(*, file: IO[str] | IO[bytes] | None = None) -> bool:
def _safe_getenv(k: str, fallback: str | None = None) -> str | None:
try:
return os.environ.get(k, fallback)
except Exception:
return fallback
if file is None:
file = sys.stdout
if not sys.flags.ignore_environment:
if _safe_getenv("PYTHON_COLORS") == "0":
return False
if _safe_getenv("PYTHON_COLORS") == "1":
return True
if _safe_getenv("NO_COLOR"):
return False
if _safe_getenv("FORCE_COLOR"):
return True
if _safe_getenv("TERM") == "dumb":
return False
if not hasattr(file, "fileno"):
return False
if sys.platform == "win32":
try:
import nt
if not nt._supports_virtual_terminal():
return False
except (ImportError, AttributeError):
return False
try:
return os.isatty(file.fileno())
except OSError:
return hasattr(file, "isatty") and file.isatty()
@staticmethod
def by_name(name):
if not Colours.can_colorize():
return ""
return getattr(Colours, name.upper(), "")
# Colours to use for each action # Colours to use for each action
ACTION_COLOURS = { ACTION_COLORS = {
"LINK": "GREEN", "LINK": "GREEN",
"UNLINK": "RED", "UNLINK": "RED",
"DIR": "GREEN", "DIR": "GREEN",
"RMDIR": "RED", "RMDIR": "RED",
"SKIP": "YELLOW", "SKIP": "INTENSE_BLACK",
} }
def print_action(action: str, msg: str) -> None: def print_action(action: str, msg: str) -> None:
"""Print an action message.""" """Print an action message."""
colour = "" colour, reset = "", ""
if action in ACTION_COLOURS: if action in ACTION_COLORS:
colour = Colours.by_name(ACTION_COLOURS[action]) colour = get_color(ACTION_COLORS[action])
print(f"{colour}{action}{Colours.by_name('RESET')} {msg}") reset = get_color("RESET")
print(f"{colour}{action}{reset} {msg}")
def add_file_to_package( def add_file_to_package(
file_path: Path, package_name: str, args: argparse.Namespace file_path: Path, package_name: str, args: argparse.Namespace
) -> bool: ) -> bool:
"""Add a file to a package by moving it and creating a symlink.""" """Add a file to a package by moving it and creating a symlink."""
if package_name not in get_packages(args.repository):
logger.error("no such package: %s", package_name)
return False
target = args.target.resolve() target = args.target.resolve()
package = Path(args.repository, package_name).resolve() package = Path(args.repository, package_name).resolve()
@@ -168,12 +122,13 @@ def install_package(
package: str, args: argparse.Namespace, is_excluded: Callable[[str], bool] package: str, args: argparse.Namespace, is_excluded: Callable[[str], bool]
) -> bool: ) -> bool:
"""Install a package by creating symlinks from repository to target.""" """Install a package by creating symlinks from repository to target."""
package_dir = args.repository / package
if not package_dir.is_dir(): if package not in get_packages(args.repository):
logger.warning("no such package: %s; skipping", package) logger.error("no such package: %s", package)
return False return False
# Walk the package # Walk the package
package_dir = args.repository / package
for root, _, files in os.walk(package_dir, followlinks=True): for root, _, files in os.walk(package_dir, followlinks=True):
root_path = Path(root) root_path = Path(root)
files = [filename for filename in files if not is_excluded(filename)] files = [filename for filename in files if not is_excluded(filename)]
@@ -230,6 +185,10 @@ def uninstall_package(
"""Uninstalls a package by removing symlinks.""" """Uninstalls a package by removing symlinks."""
dirs: List[Path] = [] dirs: List[Path] = []
if package not in get_packages(args.repository):
logger.error("no such package: %s", package)
return False
package_dir = args.repository / package package_dir = args.repository / package
if not package_dir.is_dir(): if not package_dir.is_dir():
logger.warning("no such package: %s; skipping", package) logger.warning("no such package: %s; skipping", package)
@@ -322,6 +281,10 @@ def cleanup_package(package: str, args: argparse.Namespace) -> None:
- discover the directories used in the package - discover the directories used in the package
- iterate the directories and remove any broken symlinks that point back to the package - iterate the directories and remove any broken symlinks that point back to the package
""" """
if package not in get_packages(args.repository):
logger.error("no such package: %s", package)
return
package_dir = args.repository / package package_dir = args.repository / package
if not package_dir.is_dir(): if not package_dir.is_dir():
return return
@@ -461,9 +424,7 @@ def main() -> None:
if len(packages): if len(packages):
print(f"Packages in repository: {repo_path}") print(f"Packages in repository: {repo_path}")
for package in packages: for package in packages:
print( print(f"{get_color('GREEN')}-{get_color('RESET')} {package}")
f"{Colours.by_name('GREEN')}-{Colours.by_name('RESET')} {package}"
)
else: else:
logger.info("no packages found in repository") logger.info("no packages found in repository")