import argparse import os import json import logging from dataclasses import dataclass from typing import Dict, Any, Optional from pathlib import Path from dots_manager.shell import run_shell_command from dots_manager.utils import merge_dicts from dots_manager.kawaii_logger import setup_logger @dataclass(frozen=True) class Environment: platform: str system_name: str context: Dict[str, Any] logger: logging.Logger @staticmethod def from_argv(args: argparse.Namespace): logger = setup_logger(verbose=args.verbose, logger_name="dots_manager") platform = args.platform or run_shell_command( [str(args.helper_scripts / "platform.sh")], logger ) if not platform: raise ValueError("failed to determine platform... ") os.environ["PLATFORM"] = platform system_name: Optional[str] = args.system_name or run_shell_command( [str(args.helper_scripts / "system_name.sh")], logger ) if not system_name: raise ValueError("failed to determine system name... ") context = load_context(platform, system_name, args.context, logger) return Environment(platform, system_name, context, logger) @dataclass(frozen=True) class Constants: default_target_dir: Path = Path.home() dots_repo_dir: Path = Path.home() / Path("dotfiles") default_source_dir: Path = dots_repo_dir / Path("dots") default_compiled_dir: Path = dots_repo_dir / Path(".compiled_dotfiles") default_script_dir: Path = default_source_dir / Path("home/scripts") default_context: Path = dots_repo_dir / Path("context.json") template_extension: str = ".j2" max_workers: int = (os.cpu_count() or 1) * 2 global_context_key: str = "_global" platform_default_context_key: str = "_default" def load_context( platform: str, system_name: str, context_file: Path, logger: logging.Logger ): logger.info(f"reading context file: {context_file} ✧*:。゚✧") context = json.loads(context_file.read_text()) global_context = context.get(Constants.global_context_key, {}) platform_defaults = context.get(platform, {}).get( Constants.platform_default_context_key, {} ) defaults = merge_dicts(global_context, platform_defaults) system_config = context.get(platform, {}).get(system_name, {}) if not system_config: logger.warning( f"could not find context for 'contexts.{platform}.{system_name}' in {context_file.absolute()}" ) return { "platform": platform, "system_name": system_name, **merge_dicts(defaults, system_config), } def parse_arguments() -> argparse.Namespace: parser = argparse.ArgumentParser( description="૮ ․ ․ ྀིა emprespresso's dotfiles manager 🐧✧˖°" ) parser.add_argument( "--verbose", "-v", action="store_true", default=os.environ.get("DEBUG", "false").lower() in ("y", "yes", "true", "t"), help="enables verbose logging.", ) parser.add_argument("--compile", action="store_true", help="compile le dotfiles.") parser.add_argument( "--source", type=Path, default=Constants.default_source_dir, help=f"where to look for templated dotfile packages. default: '{Constants.default_source_dir}'.", ) parser.add_argument( "--output", type=Path, default=Constants.default_compiled_dir, help=f"where to store compiled, stowable dotfile packages. default: '{Constants.default_compiled_dir}'.", ) parser.add_argument( "--context", type=Path, default=Constants.default_context, help=f"path to contexts, stored as json. default: '{Constants.default_context}'.", ) parser.add_argument( "--helper-scripts", type=Path, default=Constants.default_script_dir, help="where to find executable scripts to determine system info (device name, platform, etc.).", ) parser.add_argument( "--system-name", type=str, default=None, help="the system's name. when unspecified, inferred via the hostname, or on osx, computername.", ) parser.add_argument( "--platform", type=str, default=None, help="the system's os platform (i.e. osx, linux, bsd, windows, etc.). when unspecified, inferred via $OSTYPE.", ) parser.add_argument( "--stow", action="store_true", help="action: stow compiled dotfiles" ) parser.add_argument( "--clean", action="store_true", help="action: clean stowed dotfiles" ) parser.add_argument( "--target", type=Path, default=Constants.default_target_dir, help=f"where the dotfile packages' symlinks will be stowed. default: '{Constants.default_target_dir}'", ) return parser.parse_args()