arcology/generators/models.py

278 lines
9.4 KiB
Python

# [[file:../generators.org::*document the Generator pattern described by the Abstract Base Class.][document the Generator pattern described by the Abstract Base Class.:1]]
from __future__ import annotations
from typing import List
from django.db import models
from django.conf import settings
from django_prometheus.models import ExportModelOperationsMixin as EMOM
import arroyo.arroyo_rs as native
import roam.models
import pathlib
import graphlib
import logging
logger = logging.getLogger(__name__)
class GeneratorRole(EMOM('generatorRole'), models.Model):
name = models.CharField(max_length=512, primary_key=True)
@classmethod
def get_or_create_many(cls, names: List[str]) -> List[GeneratorRole]:
return [
cls.objects.get_or_create(
name=name
)[0]
for name in names
]
class Generator(EMOM('generator'), models.Model):
class Meta:
abstract = True
ARROYO_KEYWORD_NAME = "THE_ABC"
org_file = models.ForeignKey(
roam.models.File,
on_delete=models.CASCADE,
)
destination = models.CharField(max_length=512)
roles = models.ManyToManyField(
GeneratorRole,
related_name="THE_ABC",
)
excluded_roles = models.ManyToManyField(
GeneratorRole,
related_name="THE_ABC",
)
def to_babel(self):
raise NotImplemented
def _to_babel_list(self, **kwargs):
formatter = kwargs.get('formatter', "{}")
return formatter.format(self.destination)
def _to_babel_inclusion(self, **kwargs):
formatter = kwargs.get('formatter', "{}")
path = formatter.format(self.destination)
with open(path, 'r') as f:
return f.read()
def __str__(self):
return self.__repr__()
def __repr__(self):
return f"<{self.__class__.__name__}: {self.org_file.path} -> {self.destination}>"
@classmethod
def collect_for_babel(cls, **kwargs):
return cls.objects.filter(roles__in=[kwargs['role']])
@classmethod
def create_from_arroyo(cls, doc: native.Document) -> List[Generator]:
role_names = doc.collect_keywords("ARROYO_SYSTEM_ROLE") + doc.collect_keywords("ARROYO_ROLE")
excluded_role_names = doc.collect_keywords("ARROYO_SYSTEM_EXCLUDE") + doc.collect_keywords("ARROYO_EXCLUDE_ROLE")
f = roam.models.File.objects.get(path=doc.path)
destinations = doc.collect_keywords(cls.ARROYO_KEYWORD_NAME)
if len(destinations) == 0:
# logger.debug(f"{cls} Skip {doc.path}")
return []
logger.debug(f"{cls} {doc.path} -> {destinations}")
excluded_roles = GeneratorRole.get_or_create_many(excluded_role_names)
if role_names == []:
roles = [role for role in GeneratorRole.objects.all() if role not in excluded_roles]
else:
roles = GeneratorRole.get_or_create_many(role_names)
ret = []
for destination in destinations:
obj = cls.objects.get_or_create(
org_file=f,
destination=destination,
)[0]
obj.roles.set(roles)
obj.excluded_roles.set(excluded_roles)
ret.append(obj)
return ret
# document the Generator pattern described by the Abstract Base Class.:1 ends here
# [[file:../generators.org::*Generate an Emacs configuration][Generate an Emacs configuration:1]]
class DependencyRelationship(EMOM('dependencyRelationship'), models.Model):
# extract enough information to run https://docs.python.org/3/library/graphlib.html on the emacs snippets...
dependency = models.ForeignKey(
"EmacsSnippet",
on_delete=models.CASCADE,
blank=True,
related_name="dependent_relations",
)
dependent = models.ForeignKey(
"EmacsSnippet",
on_delete=models.CASCADE,
blank=True,
related_name="dependancy_relations",
)
dependency_path = models.CharField(max_length=512)
dependent_path = models.CharField(max_length=512)
@classmethod
def extract_ordering_from_arroyo(cls, ts: graphlib.TopologicalSorter, doc: native.Document):
base_path_abs = pathlib.Path(settings.ARCOLOGY_BASE_DIR).expanduser()
dependencies = doc.collect_keywords("ARROYO_MODULE_WANTS")
dependents = doc.collect_keywords("ARROYO_MODULE_WANTED")
ts.add(doc.path)
for dep in dependencies:
dep_path = str(base_path_abs.joinpath(dep))
logger.debug(f"EXTRACT {doc.path} wants {dep_path}")
ts.add(doc.path, dep_path)
for dep in dependents:
dep_path = str(base_path_abs.joinpath(dep))
logger.debug(f"EXTRACT(rdep) {dep_path} wants {doc.path}")
ts.add(doc.path, dep_path)
@classmethod
def collect_for_babel(cls) -> List[EmacsSnippet]:
ts = graphlib.TopologicalSorter()
mapping = dict()
# add all the nodes from EmacsSnippet
for snippet in EmacsSnippet.objects.all():
path = snippet.org_file.path
ts.add(path)
mapping[path] = snippet
# add all the DependencyRelationships
for rel in cls.objects.all():
pred = rel.dependency
succ = rel.dependent
ts.add(succ.org_file.path, pred.org_file.path)
return list(map(lambda it: mapping[it], ts.static_order()))
class EmacsSnippet(EMOM('emacs_snippet'), Generator):
ARROYO_KEYWORD_NAME = "ARROYO_EMACS_MODULE"
roles = models.ManyToManyField(
GeneratorRole,
related_name="emacs_modules",
)
excluded_roles = models.ManyToManyField(
GeneratorRole,
related_name="excluded_emacs_modules",
)
dependencies = models.ManyToManyField(
"EmacsSnippet",
related_name="dependents",
through="DependencyRelationship",
through_fields=("dependency", "dependent"),
)
def to_babel(self) -> str:
formatter = f"{settings.ARCOLOGY_EMACS_SNIPPETS_DIR}/{{}}.el"
return self._to_babel_inclusion(formatter=formatter)
@classmethod
def collect_for_babel(cls, **kwargs):
return DependencyRelationship.collect_for_babel()
@classmethod
def create_from_arroyo(cls, doc: native.Document) -> List[Generator]:
"""
create_from_arroyo is overridden to persist ordering rules
"""
new_snippets = super(EmacsSnippet, cls).create_from_arroyo(doc)
base_path_abs = pathlib.Path(settings.ARCOLOGY_BASE_DIR).expanduser()
# convert these to relation model objects..., but which?
# back to the problem of defining relations to objects which may not yet exist...
deps = doc.collect_keywords("ARROYO_MODULE_WANTS")
reverse_deps = doc.collect_keywords("ARROYO_MODULE_WANTED")
for obj in new_snippets:
for dep in deps:
dep_path = base_path_abs.joinpath(dep)
logger.debug(f"create dep from {doc.path} to {dep_path}")
the_dep = EmacsSnippet.objects.get(org_file__path=dep_path)
DependencyRelationship.objects.create(
dependency=the_dep,
dependent=obj,
)
for dep in reverse_deps:
dep_path = base_path_abs.joinpath(dep)
logger.debug(f"create rdep to {dep_path} from {doc.path}")
the_dep = EmacsSnippet.objects.get(org_file__path=dep_path)
DependencyRelationship.objects.create(
dependency=obj,
dependent=the_dep,
)
return new_snippets
# Generate an Emacs configuration:1 ends here
# [[file:../generators.org::*Emacs custom package overrides][Emacs custom package overrides:1]]
class EmacsEpkg(EMOM('emacs_epkg'), Generator):
ARROYO_KEYWORD_NAME = "ARROYO_HOME_EPKGS"
roles = models.ManyToManyField(
GeneratorRole,
related_name="emacs_packages",
)
excluded_roles = models.ManyToManyField(
GeneratorRole,
related_name="excluded_emacs_packages",
)
def to_babel(self) -> str:
formatter = f"{settings.ARROYO_BASE_DIR}/{{}}"
return self._to_babel_inclusion(formatter=formatter)
# Emacs custom package overrides:1 ends here
# [[file:../generators.org::*Declarative NixOS module imports][Declarative NixOS module imports:1]]
class NixosModule(EMOM('nixos_module'), Generator):
ARROYO_KEYWORD_NAME = "ARROYO_NIXOS_MODULE"
roles = models.ManyToManyField(
GeneratorRole,
related_name="nixos_modules",
)
excluded_roles = models.ManyToManyField(
GeneratorRole,
related_name="excluded_nixos_modules",
)
def to_babel(self) -> str:
# XXX this is going to need to be configurable at some point.....
formatter = "../../{}"
return self._to_babel_list(formatter=formatter)
# Declarative NixOS module imports:1 ends here
# [[file:../generators.org::*Declarative =home-manager= module imports][Declarative =home-manager= module imports:1]]
class HomeManagerModule(EMOM('home_manager_module'), Generator):
ARROYO_KEYWORD_NAME = "ARROYO_HOME_MODULE"
roles = models.ManyToManyField(
GeneratorRole,
related_name="home_modules",
)
excluded_roles = models.ManyToManyField(
GeneratorRole,
related_name="excluded_home_modules",
)
def to_babel(self) -> str:
formatter = "{}"
return self._to_babel_list(formatter=formatter)
# Declarative =home-manager= module imports:1 ends here