A tool for moving and symlinking directories to a central location
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 

228 lines
6.8 KiB

from dataclasses import asdict, dataclass, field
from pathlib import Path
# from typing import Self
import json
from . import version as transpose_version
from .exceptions import TransposeError
from .utils import move, remove, symlink
@dataclass
class TransposeEntry:
name: str
path: str
@dataclass
class TransposeConfig:
entries: dict = field(default_factory=dict)
version: str = field(default=transpose_version)
def add(self, name: str, path: str) -> None:
"""
Add a new entry to the entries
Args:
name: The name of the entry (must not exist)
path: The path where the entry originally exists
Returns:
None
"""
if self.entries.get(name):
raise TransposeError(f"'{name}' already exists")
self.entries[name] = TransposeEntry(name=name, path=path)
def get(self, name: str) -> TransposeEntry:
"""
Get an entry by the name
Args:
name: The name of the entry (must exist)
Returns:
TransposeEntry
"""
try:
return self.entries[name]
except KeyError:
raise TransposeError(f"'{name}' does not exist in Transpose config entries")
def remove(self, name: str) -> None:
"""
Remove an entry by name
Args:
name: The name of the entry (must exist)
Returns:
None
"""
try:
del self.entries[name]
except KeyError:
raise TransposeError(f"'{name}' does not exist in Transpose config entries")
def update(self, name: str, path: str) -> None:
"""
Update an entry by name
Args:
name: The name of the entry (must exist)
path: The path where the entry originally exists
Returns:
None
"""
try:
self.entries[name].path = path
except KeyError:
raise TransposeError(f"'{name}' does not exist in Transpose config entries")
@staticmethod
def load(config_path: str): # -> Self:
in_config = json.load(open(config_path, "r"))
config = TransposeConfig()
try:
for name in in_config["entries"]:
config.add(name, in_config["entries"][name]["path"])
except (KeyError, TypeError) as e:
raise TransposeError(f"Unrecognized Transpose config file format: {e}")
return config
def save(self, config_path: str) -> None:
"""
Save the Config to a location in JSON format
Args:
path: The path to save the json file
Returns:
None
"""
config_path = Path(config_path)
config_path.parent.mkdir(parents=True, exist_ok=True)
with open(str(config_path), "w") as f:
json.dump(self.to_dict(), f)
def to_dict(self) -> dict:
return asdict(self)
class Transpose:
config: TransposeConfig
config_path: Path
store_path: Path
def __init__(self, config_path: str) -> None:
self.config = TransposeConfig.load(config_path)
self.config_path = Path(config_path)
self.store_path = self.config_path.parent
if not self.store_path.exists():
self.store_path.mkdir(parents=True)
def apply(self, name: str, force: bool = False) -> None:
"""
Create/recreate the symlink to an existing entry
Args:
name: The name of the entry (must exist)
force: If enabled and path already exists, move the path to '{path}-bak'
Returns:
None
"""
if not self.config.entries.get(name):
raise TransposeError(f"Entry does not exist: '{name}'")
if self.config.entries[name].path.exists():
if self.config.entries[name].path.is_symlink():
remove(self.config.entries[name].path)
elif force: # Backup the existing path, just in case
move(
self.config.entries[name].path,
self.config.entries[name].path.joinpath("-bak"),
)
else:
raise TransposeError(
f"Entry path already exists, cannot restore (force required): '{self.config.entries[name].path}'"
)
symlink(
target_path=self.store_path.joinpath(name),
symlink_path=self.config.entries[name].path,
)
def restore(self, name: str, force: bool = False) -> None:
"""
Remove the symlink and move the stored entry back to it's original path
Args:
name: The name of the entry (must exist)
force: If enabled and path already exists, move the path to '{path}-bak'
Returns:
None
"""
if not self.config.entries.get(name):
raise TransposeError(f"Could not locate entry by name: '{name}'")
if self.config.entries[name].path.exists():
if self.config.entries[name].path.is_symlink():
remove(self.config.entries[name].path)
elif force: # Backup the existing path, just in case
move(
self.config.entries[name].path,
self.config.entries[name].path.joinpath("-bak"),
)
else:
raise TransposeError(
f"Entry path already exists, cannot restore (force required): '{self.config.entries[name].path}'"
)
move(self.store_path.joinpath(name), self.config.entries[name].path)
self.config.remove(name)
self.config.save(self.config_path)
def store(self, name: str, source_path: str) -> None:
"""
Move the source path to the store path, create a symlink, and update the config
Args:
name: The name of the entry (must exist)
source_path: The directory or file to be stored
Returns:
None
"""
if self.config.entries.get(name):
raise TransposeError(
f"Entry already exists: '{name}' ({self.config.entries[name].path})"
)
storage_path = self.store_path.joinpath(name)
if storage_path.exists():
raise TransposeError(f"Store path already exists: '{storage_path}'")
source_path = self.config.entries[name].path
if not source_path.exists():
raise TransposeError(f"Source path does not exist: '{source_path}'")
if not source_path.is_dir() and not source_path.is_file():
raise TransposeError(
f"Source path must be a directory or file: '{source_path}'"
)
move(source=source_path, destination=storage_path)
symlink(target_path=storage_path, symlink_path=source_path)
self.config.add(name, source_path)
self.config.save(self.config_path)