# Copyright (C) 2025 Aryadev Chavali # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the Unlicense for details. # You may distribute and modify this code under the terms of the Unlicense, # which you should have received a copy of along with this program. If not, # please go to . import tempfile import shutil import subprocess import os from time import sleep from typing import Callable, Union from json import dump, load from dataclasses import dataclass def clearscreen(): if os.name == 'nt': os.system('cls') else: os.system('clear') def cmd(command, cwd = "./"): result = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, cwd=cwd) return (result.returncode, result.stdout) def pull_latest_git_hash(url: str, branch: str): return cmd(["git", "ls-remote", url, f"refs/heads/{branch}"])[1].strip().split()[0] @dataclass class GitState: url: str branch: str git_hash: str def __init__(self, url: str, branch: str): self.url = url self.branch = branch self.git_hash = pull_latest_git_hash(url, branch) def new_update(self): return self.git_hash != pull_latest_git_hash(self.url, self.branch) def update(self): self.git_hash = pull_latest_git_hash(self.url, self.branch) @dataclass class Task: name: str git: GitState build_command: [str] @staticmethod def from_json(json: dict[str, Union[str, [str]]]): return Task(json["name"], GitState(json["url"], json["branch"]), json["build"]); def to_json(self) -> dict[str, str]: obj = dict() obj["name"] = self.name obj["url"] = self.git.url obj["branch"] = self.git.branch obj["build"] = self.build_command return obj def setup(self, directory): # Pull URL into temp directory return cmd(["git", "clone", "--progress", self.git.url, directory]) def build(self, directory): # Run self.build_command, returning exit code and any output return cmd(self.build_command, directory) def teardown(self, directory): # delete /tmp/{self.name} if it exists shutil.rmtree(directory) def on_update(self): # Presuming a new update, perform a complete sanitised build. temp_directory = tempfile.mkdtemp() self.git.update() print(f"[{self.name}:setup]: Starting setup") code, _ = self.setup(temp_directory) print(f"[{self.name}:setup]: Exited with {code}") print(f"[{self.name}:build]: Starting build") code, output = self.build(temp_directory) print(f"[{self.name}:build]: Exited with {code}") self.teardown(temp_directory) return (code, output.split("\n")) @dataclass class Config: config_path: str tasks: [Task] def __init__(self, path: str): self.config_path = path data = None with open(path, "r") as fp: data = load(fp) self.tasks = [Task.from_json(json) for json in data] def write(self): config_json = [task.to_json() for task in tasks] with open(config_path, "w") as fp: dump(config_json, fp) def poll_task_updates(self): results = dict() for i, task in enumerate(self.tasks): if task.git.new_update(): results[task.name] = task.on_update() self.tasks[i] = task return results def poll_task_updates_on_timer(self): while True: sleep(1) updates = self.poll_task_updates() if len(updates) != 0: for name in updates: code, output = updates[name] status = "PASSED" if code == 0 else "FAILED" print(f"[{name}]: {status}") if __name__ == "__main__": x = Config("./dbcd.json") print(x) x.poll_task_updates_on_timer()