From 8457869dcd52a6cec3f987bfbdcee6b58f5ae06e Mon Sep 17 00:00:00 2001 From: didericis Date: Mon, 11 May 2026 00:52:33 -0400 Subject: [PATCH] refactor(util): move expand_tilde to top-level claude_bottle/util.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit _expand_tilde was a path-string helper on DockerBottleBackend but nothing about it was Docker-specific — any future backend reading host paths from manifest entries would want it. Lift to claude_bottle/util.py (sibling of log.py / manifest.py) as a public expand_tilde() function. Docker-specific primitives stay in claude_bottle/backend/docker/util.py. --- claude_bottle/backend/docker/backend.py | 11 +++-------- claude_bottle/util.py | 18 ++++++++++++++++++ 2 files changed, 21 insertions(+), 8 deletions(-) create mode 100644 claude_bottle/util.py diff --git a/claude_bottle/backend/docker/backend.py b/claude_bottle/backend/docker/backend.py index 96f091d..f66972a 100644 --- a/claude_bottle/backend/docker/backend.py +++ b/claude_bottle/backend/docker/backend.py @@ -21,6 +21,7 @@ from ... import pipelock from ...env_resolve import env_resolve from ...log import die, info from ...manifest import SshEntry +from ...util import expand_tilde from .. import BottleBackend, BottleCleanupPlan, BottlePlan, BottleSpec from . import network as network_mod from . import util as docker_mod @@ -360,7 +361,7 @@ class DockerBottleBackend(BottleBackend): the y/N so the user doesn't get prompted for a plan with a missing key.""" for entry in entries: - key = self._expand_tilde(entry.IdentityFile) + key = expand_tilde(entry.IdentityFile) if not os.path.isfile(key): die(f"ssh key file not found for host '{entry.Host}': {key}") @@ -431,7 +432,7 @@ class DockerBottleBackend(BottleBackend): container_key_paths: list[str] = [] for entry in bottle.ssh: name = entry.Host - key = self._expand_tilde(entry.IdentityFile) + key = expand_tilde(entry.IdentityFile) hostname = entry.Hostname user = entry.User port = entry.Port @@ -540,12 +541,6 @@ class DockerBottleBackend(BottleBackend): check=True, ) - def _expand_tilde(self, path: str) -> str: - if path.startswith("~"): - home = os.environ.get("HOME", "") - return home + path[1:] - return path - def provision_git(self, plan: BottlePlan, target: str) -> None: """If --cwd was set and the host cwd has a .git directory, copy it into /home/node/workspace/.git and fix ownership. No-op diff --git a/claude_bottle/util.py b/claude_bottle/util.py new file mode 100644 index 0000000..b936c02 --- /dev/null +++ b/claude_bottle/util.py @@ -0,0 +1,18 @@ +"""Cross-cutting utility helpers used by multiple modules. + +Top-level (i.e. backend-agnostic) — Docker-specific helpers live in +claude_bottle/backend/docker/util.py.""" + +from __future__ import annotations + +import os + + +def expand_tilde(path: str) -> str: + """Expand a leading '~' to $HOME. Leaves paths without a leading + tilde unchanged. Falls back to the empty string if $HOME is unset + (callers should already have checked HOME if they care).""" + if path.startswith("~"): + home = os.environ.get("HOME", "") + return home + path[1:] + return path