#!/usr/bin/env python3 import argparse import logging import plistlib import sys import typing from pathlib import Path def parse_args(argv: list[str]) -> typing.Tuple[argparse.Namespace, list[str]]: def parse_env(env: str) -> typing.Tuple[str, str]: return env.split("=", maxsplit=1) def parse_path(path: str) -> Path: return Path(path).expanduser().resolve() arger = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter) arger.add_argument( "-e", "--env", dest="env", metavar="ENVVAR", type=parse_env, nargs="+", help="Environment variables as K=V list" ) arger.add_argument("-w", "--cwd", dest="cwd", default=Path.cwd(), type=parse_path, help="Working directory") arger.add_argument("-l", "--label", required=True, help="Label") arger.add_argument( "--install", action="store_true", help="Install the plist to ~/Library/LaunchAgents/", ) if not argv: arger.print_help() sys.exit(1) args, unknown = arger.parse_known_args() if unknown and unknown[0] == "--": unknown = unknown[1:] if not unknown: arger.print_help() sys.exit(1) return args, unknown def generate_plist(label: str, args: list[str], env: dict, cwd: Path) -> str: doc = { "Label": label, "ProgramArguments": args, "EnvironmentVariables": env, "WorkingDirectory": str(cwd), "RunAtLoad": True, "StandardOutPath": str(cwd / f"{label}.stdout.log"), "StandardErrorPath": str(cwd / f"{label}.stderr.log"), } return plistlib.dumps( doc, fmt=plistlib.FMT_XML, sort_keys=True, ).decode() def main(): args, unknown = parse_args(sys.argv[1:]) print(args, unknown) plist_doc = generate_plist( label=args.label, args=unknown, env=dict(args.env), cwd=args.cwd, ) print(plist_doc) if args.install: install_dir: Path = Path("~/Library/LaunchAgents/").expanduser() logging.info(f"Installing plist to {install_dir}") install_path = install_dir / f"{args.label}.plist" install_path.write_text(plist_doc) logging.info(f'Run "launchctl load -w {install_path}" to start the service') if __name__ == "__main__": logging.basicConfig(level=logging.INFO) main()