summaryrefslogtreecommitdiff
path: root/dots.py
diff options
context:
space:
mode:
authorElizabeth <me@liz.coffee>2025-06-02 13:11:10 -0700
committerElizabeth <me@liz.coffee>2025-06-02 13:11:10 -0700
commitd098e94ad102da9d018acca72ca5a5c554d25a01 (patch)
treed6d23ee63ad5a1aa4017a605d9e09e75de2c5f49 /dots.py
parentede675866355d34ac9fdc1b8e047576f574bdfa2 (diff)
downloaddotfiles-d098e94ad102da9d018acca72ca5a5c554d25a01.tar.gz
dotfiles-d098e94ad102da9d018acca72ca5a5c554d25a01.zip
Update paths n stuff
Diffstat (limited to 'dots.py')
-rwxr-xr-xdots.py230
1 files changed, 0 insertions, 230 deletions
diff --git a/dots.py b/dots.py
deleted file mode 100755
index ac4080e..0000000
--- a/dots.py
+++ /dev/null
@@ -1,230 +0,0 @@
-#!/usr/bin/env python3
-
-from dataclasses import dataclass
-from pathlib import Path
-from typing import Dict, List, Optional, Any
-import argparse
-import json
-import logging
-import os
-import subprocess
-import sys
-import shutil
-from concurrent.futures import Executor, ThreadPoolExecutor, as_completed
-from functools import reduce
-import jinja2
-from kawaii_logger import setup_logger
-
-
-@dataclass(frozen=True)
-class Config:
- script_dir: Path = Path(__file__).parent / "home/scripts"
- compiled_dir: Path = Path(".compiled_dotfiles")
- contexts: Path = Path("context.json")
- default_target_dir: Path = Path.home()
- default_source_dir: Path = Path(".")
- max_workers: int = (os.cpu_count() or 1) * 2
-
-
-@dataclass(frozen=True)
-class Environment:
- platform: str
- system_name: str
- context: Dict[str, Any]
- verbose: bool
- logger: logging.Logger
-
-
-def main() -> None:
- args = parse_arguments()
- env = initialize_environment(args.verbose)
-
- if not (args.compile or args.stow or args.clean):
- env.logger.error("you gotta specify at least one action nya~ (⁎⁍̴̆‾⁍̴̆⁎)")
- sys.exit(1)
-
- if args.compile:
- env.logger.info(f"compiling {args.source} to {args.comp} ✨✧˖°")
- if not compile_dotfiles(Path(args.source), Path(args.comp), env):
- env.logger.error("uh oh! failed to compile dotfiles (ɐ•゚́•̀ɐ)")
- sys.exit(1)
-
- if args.stow:
- if not stow_dotfiles(Path(args.comp), Path(args.target), env, clean=False):
- env.logger.error("failed to stow dotfiles... nyaaa (╥゚╥)")
- sys.exit(1)
-
- if args.clean:
- env.logger.info(f"cleaning dotfiles from {args.target} (⌟‾╥ ‾╥)°")
- if not stow_dotfiles(Path(args.comp), Path(args.target), env, clean=True):
- env.logger.error("couldn’t clean dotfiles... sobs (ɐ•゚́•̀ɐ)")
- sys.exit(1)
-
- env.logger.info("yay~ all done!! ₕᵒ. .ᵒₕ♡")
- sys.exit(0)
-
-
-def initialize_environment(verbose: bool) -> Environment:
- logger = setup_logger(verbose)
- platform = get_platform()
- system_name = get_system_name()
- context = {
- **merge_dicts(
- load_context(platform, system_name, Config.contexts, logger),
- {"platform": platform, "system_name": system_name},
- )
- }
- return Environment(platform, system_name, context, verbose, logger)
-
-
-def copy_with_templates_rendered(
- executor: Executor, source: Path, destination: Path, env: Environment
-) -> bool:
- shutil.copytree(source, destination, dirs_exist_ok=True)
- env.logger.debug(f"copied {source} to {destination} ₰˜⋉♡")
-
- templates = [t for t in destination.glob("**/*.j2") if t.is_file()]
- if not templates:
- env.logger.debug(f"no templates to render in {source} (•ᴗ•)⁎")
- return True
-
- for template in templates:
- env.logger.debug(f"submitting template render for {template} ₰˜൨൨")
- executor.submit(replace_with_rendered_template, template, env)
-
- return True
-
-
-def replace_with_rendered_template(template: Path, env: Environment) -> bool:
- env.logger.debug(f"rendering template {template} ✧*ฺ")
- jinja_env = jinja2.Environment(
- loader=jinja2.BaseLoader,
- undefined=jinja2.StrictUndefined,
- trim_blocks=True,
- lstrip_blocks=True,
- )
- rendered = render(template, jinja_env, env)
- with open(template, "w") as t:
- t.write(rendered)
- env.logger.debug(f"removing .j2 suffix from {template.absolute()} ✧˖°")
- template.rename(template.with_suffix(""))
- return True
-
-
-def compile_dotfiles(source_dir: Path, target_dir: Path, env: Environment) -> bool:
- target_dir.mkdir(exist_ok=True, parents=True)
-
- with ThreadPoolExecutor(max_workers=Config.max_workers) as executor:
- dotfile_dirs = list_dotfiles(source_dir)
- env.logger.debug(f"found dotfile dirs: {dotfile_dirs}")
-
- futures = [
- executor.submit(
- copy_with_templates_rendered,
- executor,
- d,
- target_dir / d.name,
- env,
- )
- for d in dotfile_dirs
- ]
- env.logger.info(f"submitted {len(futures)} tasks to executor ₰˜.༄")
-
- return sum(1 for f in as_completed(futures) if f.result()) == len(futures)
-
-
-def render(source: Path, jinja_env: jinja2.Environment, env: Environment) -> Optional[str]:
- try:
- with open(source, "r") as f:
- content = f.read()
- env.logger.debug(f"reading template {source} ✿.。.:・")
- template = jinja_env.from_string(content)
- env.logger.debug(f"rendered template from {source} ~ nyaaa :3")
- return template.render(**env.context)
- except Exception as e:
- env.logger.error(f"couldn’t render {source}: {e} (;⌓̀_⌓́)")
- return None
-
-
-def stow_dotfiles(source_dir: Path, target_dir: Path, env: Environment, clean: bool = False) -> bool:
- if not stow_installed():
- env.logger.error("stow not installed (╥゚╥)")
- return False
-
- packages = list_dotfiles(source_dir)
- stow_cmd = ["-D"] if clean else ["--no-folding"]
-
- for pkg in packages:
- env.logger.info(f"running stow {stow_cmd} for {pkg.name} ₰˜݆༿")
- run_shell_command(["stow", "-d", source_dir, "-t", target_dir, *stow_cmd, pkg.name])
-
- return True
-
-
-def stow_installed() -> bool:
- return run_shell_command(["stow", "--version"]) != ""
-
-
-def run_shell_command(cmd: List[str]) -> str:
- result = subprocess.run(cmd, capture_output=True, text=True, check=True)
- return result.stdout.strip()
-
-
-def get_platform() -> str:
- return run_shell_command([str(Config.script_dir / "platform.sh")])
-
-
-def get_system_name() -> str:
- os.environ["PLATFORM"] = get_platform()
- return run_shell_command([str(Config.script_dir / "system_name.sh")])
-
-def list_dotfiles(p: Path) -> list[Path]:
- denylist = [".", "__"]
- return [d for d in p.iterdir() if d.is_dir() and all(y not in d.name for y in denylist)]
-
-def load_context(platform: str, system_name: str, context_file: Path, logger: logging.Logger) -> Dict[str, Any]:
- try:
- logger.info(f"reading context file: {context_file} ✧*:。゚✧")
- with open(context_file) as f:
- contexts = json.load(f)
-
- global_config = contexts.get("_global", {})
- platform_defaults = contexts.get(platform, {}).get("_default", {})
- defaults = merge_dicts(global_config, platform_defaults)
-
- system_config = contexts.get(platform, {}).get(system_name, {})
- if not system_config:
- logger.warning(f"couldn’t find system-specific config for {platform}.{system_name} (ɐ•゚́•̀ɐ)")
-
- return merge_dicts(defaults, system_config)
- except (FileNotFoundError, json.JSONDecodeError) as e:
- logger.error(f"error loading context: {e} ⋆ฺ°☁。⋆ฺ °★ °。")
- return {}
-
-
-def merge_dicts(*dicts: Dict[str, Any]) -> Dict[str, Any]:
- def merge(a: Dict[str, Any], b: Dict[str, Any]) -> Dict[str, Any]:
- out = dict(a)
- for k, v in b.items():
- if k in out and isinstance(out[k], dict) and isinstance(v, dict):
- out[k] = merge(out[k], v)
- else:
- out[k] = v
- return out
- return reduce(merge, dicts, {})
-
-
-def parse_arguments() -> argparse.Namespace:
- parser = argparse.ArgumentParser(description="cute dotfiles manager ✧˖°")
- parser.add_argument("--compile", action="store_true", help="compile dotfiles ✨")
- parser.add_argument("--stow", action="store_true", help="stow compiled dotfiles ♡")
- parser.add_argument("--clean", action="store_true", help="clean stowed dotfiles ˚°")
- parser.add_argument("--source", default=Config.default_source_dir, help="source dir for dotfiles")
- parser.add_argument("--comp", default=Config.compiled_dir, help="compiled template output dir")
- parser.add_argument("--target", default=Config.default_target_dir, help="stow target directory")
- parser.add_argument("--verbose", "-v", action="store_true", help="enable verbose logging ✧*ฺ", default=False)
- return parser.parse_args()
-
-
-if __name__ == "__main__":
- main()