"""Path management utilities for Simulchip.
This module provides centralized path generation and management for:
- Collection files
- Deck PDFs
- Cache directories
- Configuration files
"""
# Standard library imports
from pathlib import Path
from typing import Any, Optional
from .utils import sanitize_filename
# Default paths
DEFAULT_COLLECTION_FILENAME = "collection.toml"
DEFAULT_SIMULCHIP_DIR = Path.home() / ".simulchip"
DEFAULT_COLLECTION_PATH = DEFAULT_SIMULCHIP_DIR / DEFAULT_COLLECTION_FILENAME
DEFAULT_CACHE_DIR = DEFAULT_SIMULCHIP_DIR / "cache"
DEFAULT_DECKS_DIR = Path.cwd() / "decks"
[docs]
def get_default_collection_path() -> Path:
"""Get the default collection file path.
Returns:
Path to the default collection file (~/.simulchip/collection.toml)
"""
return DEFAULT_COLLECTION_PATH
[docs]
def get_default_cache_dir() -> Path:
"""Get the default cache directory path.
Returns:
Path to the default cache directory (~/.simulchip/cache)
"""
return DEFAULT_CACHE_DIR
[docs]
def get_deck_pdf_path(
identity_title: str, deck_name: str, side: str, base_dir: Optional[Path] = None
) -> Path:
"""Generate the standard path for a deck PDF.
Creates a path structure like:
decks/(corporation|runner)/(identity-slug)/(deck-name).pdf
Args:
identity_title: The identity card title (e.g., "Zahya Sadeghi: Versatile Smuggler")
deck_name: The deck name
side: The side ("corp", "corporation", or "runner")
base_dir: Base directory for decks (defaults to ./decks)
Returns:
Path object for the deck PDF
Examples:
>>> get_deck_pdf_path("Zahya Sadeghi: Versatile Smuggler", "My Deck", "runner")
PosixPath('decks/runner/zahya-sadeghi-versatile-smuggler/my-deck.pdf')
"""
if base_dir is None:
base_dir = DEFAULT_DECKS_DIR
# Create identity slug from title
identity_slug = create_identity_slug(identity_title)
# Sanitize the deck name for filesystem
safe_deck_name = sanitize_filename(deck_name.lower().replace(" ", "-"))
# Determine side directory
side_dir = "corporation" if side.lower() in ["corp", "corporation"] else "runner"
# Create path: base_dir/(corporation|runner)/(identity-slug)/(deck-name).pdf
return base_dir / side_dir / identity_slug / f"{safe_deck_name}.pdf"
[docs]
def create_identity_slug(identity_title: str) -> str:
"""Create a filesystem-safe slug from an identity title.
Args:
identity_title: The identity card title
Returns:
A filesystem-safe slug
Examples:
>>> create_identity_slug("Zahya Sadeghi: Versatile Smuggler")
'zahya-sadeghi-versatile-smuggler'
>>> create_identity_slug("Haas-Bioroid: Engineering the Future")
'haas-bioroid-engineering-the-future'
"""
# Remove colons and replace spaces with hyphens
slug = identity_title.lower().replace(":", "").replace(" ", "-")
# Use sanitize_filename to ensure it's filesystem-safe
return sanitize_filename(slug)
[docs]
def ensure_path_exists(path: Path) -> None:
"""Ensure a path and its parent directories exist.
Args:
path: Path to create (if it's a file, only parent dirs are created)
"""
if path.suffix: # It's a file
path.parent.mkdir(parents=True, exist_ok=True)
else: # It's a directory
path.mkdir(parents=True, exist_ok=True)
[docs]
def get_cache_subdirectory(subdir: str) -> Path:
"""Get a subdirectory within the cache directory.
Args:
subdir: Name of the subdirectory
Returns:
Path to the cache subdirectory
"""
return DEFAULT_CACHE_DIR / subdir
[docs]
def get_cache_locations() -> list[Path]:
"""Get all potential cache directory locations.
Returns:
List of paths where cache files might be stored
"""
return [
DEFAULT_CACHE_DIR, # ~/.simulchip/cache
Path.cwd() / ".cache", # ./cache (CacheManager default)
]
[docs]
def reset_simulchip_data(
collection_path: Optional[Path] = None,
reset_collection: bool = True,
reset_cache: bool = True,
) -> dict[str, list[str]]:
"""Reset Simulchip data files.
Args:
collection_path: Path to collection file (uses default if None)
reset_collection: Whether to reset the collection file
reset_cache: Whether to clear cache directories
Returns:
Dictionary with 'removed' and 'errors' lists describing what happened
"""
# Standard library imports
import shutil
if collection_path is None:
collection_path = DEFAULT_COLLECTION_PATH
removed = []
errors = []
# Reset collection file
if reset_collection and collection_path.exists():
try:
collection_path.unlink()
removed.append(f"Collection file: {collection_path}")
except Exception as e:
errors.append(f"Failed to remove collection file: {e}")
# Reset cache directories
if reset_cache:
for cache_dir in get_cache_locations():
if cache_dir.exists():
try:
shutil.rmtree(cache_dir)
removed.append(f"Cache directory: {cache_dir}")
except Exception as e:
errors.append(f"Failed to remove cache {cache_dir}: {e}")
return {"removed": removed, "errors": errors}
[docs]
def generate_default_output_path(comparison_result: Any) -> Path:
"""Generate default output path for a deck comparison result.
Args:
comparison_result: Deck comparison result with identity and decklist_name
Returns:
Path for the deck PDF
"""
from .utils import get_faction_side
# Determine if corp or runner based on identity
identity_faction = comparison_result.identity.faction_code
side = get_faction_side(identity_faction)
return get_deck_pdf_path(
comparison_result.identity.title, comparison_result.decklist_name, side
)