Coverage for src/workstack/cli/shell_utils.py: 95%
37 statements
« prev ^ index » next coverage.py v7.11.0, created at 2025-10-19 09:31 -0400
« prev ^ index » next coverage.py v7.11.0, created at 2025-10-19 09:31 -0400
1"""Utilities for generating shell integration scripts."""
3import os
4import tempfile
5import time
6import uuid
7from datetime import datetime
8from pathlib import Path
10from workstack.cli.debug import debug_log
12STALE_SCRIPT_MAX_AGE_SECONDS = 3600
15def render_cd_script(path: Path, *, comment: str, success_message: str) -> str:
16 """Generate shell script to change directory with feedback.
18 Args:
19 path: Target directory path to cd into.
20 comment: Shell comment describing the operation.
21 success_message: Message to echo after successful cd.
23 Returns:
24 Shell script that changes directory and shows success message.
25 """
26 path_str = str(path)
27 quoted_path = "'" + path_str.replace("'", "'\\''") + "'"
28 lines = [
29 f"# {comment}",
30 f"cd {quoted_path}",
31 f'echo "{success_message}"',
32 ]
33 return "\n".join(lines) + "\n"
36def write_script_to_temp(
37 script_content: str,
38 *,
39 command_name: str,
40 comment: str | None = None,
41) -> Path:
42 """Write shell script to temp file with unique UUID.
44 Args:
45 script_content: The shell script to write
46 command_name: Command that generated this (e.g., 'sync', 'switch', 'create')
47 comment: Optional comment to include in script header
49 Returns:
50 Path to the temp file
52 Filename format: workstack-{command}-{uuid}.sh
53 """
54 unique_id = uuid.uuid4().hex[:8] # 8 chars sufficient (4 billion combinations)
55 temp_dir = Path(tempfile.gettempdir())
56 temp_file = temp_dir / f"workstack-{command_name}-{unique_id}.sh"
58 # Add metadata header
59 header = [
60 "#!/bin/bash",
61 f"# workstack {command_name}",
62 f"# Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}",
63 f"# UUID: {unique_id}",
64 f"# User: {os.getenv('USER', 'unknown')}",
65 f"# Working dir: {Path.cwd()}",
66 ]
68 if comment:
69 header.append(f"# {comment}")
71 header.append("") # Blank line before script
73 full_content = "\n".join(header) + "\n" + script_content
74 temp_file.write_text(full_content, encoding="utf-8")
76 # Make executable for good measure
77 temp_file.chmod(0o755)
79 debug_log(f"write_script_to_temp: Created {temp_file}")
80 debug_log(f"write_script_to_temp: Content:\n{full_content}")
82 return temp_file
85def cleanup_stale_scripts(*, max_age_seconds: int = STALE_SCRIPT_MAX_AGE_SECONDS) -> None:
86 """Remove workstack temp scripts older than max_age_seconds.
88 Args:
89 max_age_seconds: Maximum age before cleanup (default 1 hour)
90 """
91 temp_dir = Path(tempfile.gettempdir())
92 cutoff = time.time() - max_age_seconds
94 for script_file in temp_dir.glob("workstack-*.sh"):
95 if script_file.exists():
96 try:
97 if script_file.stat().st_mtime < cutoff:
98 script_file.unlink()
99 except (FileNotFoundError, PermissionError):
100 # Scripts may disappear between stat/unlink or be owned by another user.
101 continue