278 lines
9.4 KiB
Python
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
|