mirror of
https://github.com/arcan1s/ahriman.git
synced 2025-04-24 15:27:17 +00:00
Improve template processing
This commit is contained in:
parent
a56fe28003
commit
a02baec662
@ -54,14 +54,17 @@ use_utf = yes
|
||||
|
||||
[email]
|
||||
no_empty_report = yes
|
||||
template_path = /usr/share/ahriman/templates/email-index.jinja2
|
||||
template = email-index.jinja2
|
||||
templates = /usr/share/ahriman/templates
|
||||
ssl = disabled
|
||||
|
||||
[html]
|
||||
template_path = /usr/share/ahriman/templates/repo-index.jinja2
|
||||
template = repo-index.jinja2
|
||||
templates = /usr/share/ahriman/templates
|
||||
|
||||
[telegram]
|
||||
template_path = /usr/share/ahriman/templates/telegram-index.jinja2
|
||||
template = telegram-index.jinja2
|
||||
templates = /usr/share/ahriman/templates
|
||||
|
||||
[upload]
|
||||
target =
|
||||
|
@ -82,6 +82,7 @@ class Configuration(configparser.RawConfigParser):
|
||||
converters={
|
||||
"list": shlex.split,
|
||||
"path": self._convert_path,
|
||||
"pathlist": lambda value: [self._convert_path(element) for element in shlex.split(value)],
|
||||
}
|
||||
)
|
||||
|
||||
@ -241,6 +242,8 @@ class Configuration(configparser.RawConfigParser):
|
||||
|
||||
def getpath(self, *args: Any, **kwargs: Any) -> Path: ... # type: ignore[empty-body]
|
||||
|
||||
def getpathlist(self, *args: Any, **kwargs: Any) -> list[Path]: ... # type: ignore[empty-body]
|
||||
|
||||
def gettype(self, section: str, repository_id: RepositoryId, *, fallback: str | None = None) -> tuple[str, str]:
|
||||
"""
|
||||
get type variable with fallback to old logic. Despite the fact that it has same semantics as other get* methods,
|
||||
|
@ -264,10 +264,14 @@ CONFIGURATION_SCHEMA: ConfigurationSchema = {
|
||||
"path_exists": True,
|
||||
},
|
||||
"templates": {
|
||||
"type": "path",
|
||||
"coerce": "absolute_path",
|
||||
"required": True,
|
||||
"path_exists": True,
|
||||
"type": "list",
|
||||
"coerce": "list",
|
||||
"schema": {
|
||||
"type": "path",
|
||||
"coerce": "absolute_path",
|
||||
"path_exists": True,
|
||||
},
|
||||
"empty": False,
|
||||
},
|
||||
"timeout": {
|
||||
"type": "integer",
|
||||
|
@ -37,7 +37,6 @@ class Email(Report, JinjaTemplate):
|
||||
email report generator
|
||||
|
||||
Attributes:
|
||||
full_template_path(Path): path to template for full package list
|
||||
host(str): SMTP host to connect
|
||||
no_empty_report(bool): skip empty report generation
|
||||
password(str | None): password to authenticate via SMTP
|
||||
@ -45,7 +44,8 @@ class Email(Report, JinjaTemplate):
|
||||
receivers(list[str]): list of receivers emails
|
||||
sender(str): sender email address
|
||||
ssl(SmtpSSLSettings): SSL mode for SMTP connection
|
||||
template_path(Path): path to template for built packages
|
||||
template(Path | str): path or name to template for built packages
|
||||
template_full(Path | str | None): path or name to template for full package list
|
||||
user(str | None): username to authenticate via SMTP
|
||||
"""
|
||||
|
||||
@ -61,8 +61,10 @@ class Email(Report, JinjaTemplate):
|
||||
Report.__init__(self, repository_id, configuration)
|
||||
JinjaTemplate.__init__(self, repository_id, configuration, section)
|
||||
|
||||
self.full_template_path = configuration.getpath(section, "full_template_path", fallback=None)
|
||||
self.template_path = configuration.getpath(section, "template_path")
|
||||
self.template = configuration.get(section, "template", fallback=None) or \
|
||||
configuration.getpath(section, "template_path")
|
||||
self.template_full = configuration.get(section, "template_full", fallback=None) or \
|
||||
configuration.getpath(section, "full_template_path", fallback=None)
|
||||
|
||||
# base smtp settings
|
||||
self.host = configuration.get(section, "host")
|
||||
@ -114,9 +116,10 @@ class Email(Report, JinjaTemplate):
|
||||
"""
|
||||
if self.no_empty_report and not result.success:
|
||||
return
|
||||
text = self.make_html(result, self.template_path)
|
||||
if self.full_template_path is not None:
|
||||
attachments = {"index.html": self.make_html(Result(success=packages), self.full_template_path)}
|
||||
else:
|
||||
attachments = {}
|
||||
|
||||
text = self.make_html(result, self.template)
|
||||
attachments = {}
|
||||
if self.template_full is not None:
|
||||
attachments = {"index.html": self.make_html(Result(success=packages), self.template_full)}
|
||||
|
||||
self._send(text, attachments)
|
||||
|
@ -31,7 +31,7 @@ class HTML(Report, JinjaTemplate):
|
||||
|
||||
Attributes:
|
||||
report_path(Path): output path to html report
|
||||
template_path(Path): path to template for full package list
|
||||
template(Path | str): name or path to template for full package list
|
||||
"""
|
||||
|
||||
def __init__(self, repository_id: RepositoryId, configuration: Configuration, section: str) -> None:
|
||||
@ -47,7 +47,8 @@ class HTML(Report, JinjaTemplate):
|
||||
JinjaTemplate.__init__(self, repository_id, configuration, section)
|
||||
|
||||
self.report_path = configuration.getpath(section, "path")
|
||||
self.template_path = configuration.getpath(section, "template_path")
|
||||
self.template = configuration.get(section, "template", fallback=None) or \
|
||||
configuration.getpath(section, "template_path")
|
||||
|
||||
def generate(self, packages: list[Package], result: Result) -> None:
|
||||
"""
|
||||
@ -57,5 +58,5 @@ class HTML(Report, JinjaTemplate):
|
||||
packages(list[Package]): list of packages to generate report
|
||||
result(Result): build result
|
||||
"""
|
||||
html = self.make_html(Result(success=packages), self.template_path)
|
||||
html = self.make_html(Result(success=packages), self.template)
|
||||
self.report_path.write_text(html, encoding="utf8")
|
||||
|
@ -57,11 +57,12 @@ class JinjaTemplate:
|
||||
* repository - repository name, string, required
|
||||
|
||||
Attributes:
|
||||
default_pgp_key(str | None): default PGP key
|
||||
homepage(str | None): homepage link if any (for footer)
|
||||
link_path(str): prefix fo packages to download
|
||||
name(str): repository name
|
||||
default_pgp_key(str | None): default PGP key
|
||||
sign_targets(set[SignSettings]): targets to sign enabled in configuration
|
||||
templates(list[Path]): list of directories with templates
|
||||
"""
|
||||
|
||||
def __init__(self, repository_id: RepositoryId, configuration: Configuration, section: str) -> None:
|
||||
@ -73,6 +74,8 @@ class JinjaTemplate:
|
||||
configuration(Configuration): configuration instance
|
||||
section(str): settings section name
|
||||
"""
|
||||
self.templates = configuration.getpathlist(section, "templates", fallback=[])
|
||||
|
||||
self.link_path = configuration.get(section, "link_path")
|
||||
|
||||
# base template vars
|
||||
@ -81,18 +84,23 @@ class JinjaTemplate:
|
||||
|
||||
self.sign_targets, self.default_pgp_key = GPG.sign_options(configuration)
|
||||
|
||||
def make_html(self, result: Result, template_path: Path) -> str:
|
||||
def make_html(self, result: Result, template_name: Path | str) -> str:
|
||||
"""
|
||||
generate report for the specified packages
|
||||
|
||||
Args:
|
||||
result(Result): build result
|
||||
template_path(Path): path to jinja template
|
||||
template_name(Path | str): name of the template or path to it (legacy configuration)
|
||||
"""
|
||||
templates = self.templates[:]
|
||||
if isinstance(template_name, Path):
|
||||
templates.append(template_name.parent)
|
||||
template_name = template_name.name
|
||||
|
||||
# idea comes from https://stackoverflow.com/a/38642558
|
||||
loader = jinja2.FileSystemLoader(searchpath=template_path.parent)
|
||||
loader = jinja2.FileSystemLoader(searchpath=templates)
|
||||
environment = jinja2.Environment(loader=loader, autoescape=True)
|
||||
template = environment.get_template(template_path.name)
|
||||
template = environment.get_template(template_name)
|
||||
|
||||
content = [
|
||||
{
|
||||
|
@ -67,6 +67,8 @@ class ReportTrigger(Trigger):
|
||||
"full_template_path": {
|
||||
"type": "path",
|
||||
"coerce": "absolute_path",
|
||||
"excludes": ["template_full"],
|
||||
"required": True,
|
||||
"path_exists": True,
|
||||
},
|
||||
"homepage": {
|
||||
@ -111,12 +113,36 @@ class ReportTrigger(Trigger):
|
||||
"type": "string",
|
||||
"allowed": ["ssl", "starttls", "disabled"],
|
||||
},
|
||||
"template": {
|
||||
"type": "string",
|
||||
"excludes": ["template_path"],
|
||||
"dependencies": ["templates"],
|
||||
"required": True,
|
||||
"empty": False,
|
||||
},
|
||||
"template_full": {
|
||||
"type": "string",
|
||||
"excludes": ["template_path"],
|
||||
"dependencies": ["templates"],
|
||||
"required": True,
|
||||
"empty": False,
|
||||
},
|
||||
"template_path": {
|
||||
"type": "path",
|
||||
"coerce": "absolute_path",
|
||||
"excludes": ["template"],
|
||||
"required": True,
|
||||
"path_exists": True,
|
||||
},
|
||||
"templates": {
|
||||
"type": "list",
|
||||
"coerce": "list",
|
||||
"schema": {
|
||||
"type": "path",
|
||||
"coerce": "absolute_path",
|
||||
"path_exists": True,
|
||||
},
|
||||
},
|
||||
"user": {
|
||||
"type": "string",
|
||||
},
|
||||
@ -143,12 +169,29 @@ class ReportTrigger(Trigger):
|
||||
"coerce": "absolute_path",
|
||||
"required": True,
|
||||
},
|
||||
"template": {
|
||||
"type": "string",
|
||||
"excludes": ["template_path"],
|
||||
"dependencies": ["templates"],
|
||||
"required": True,
|
||||
"empty": False,
|
||||
},
|
||||
"template_path": {
|
||||
"type": "path",
|
||||
"coerce": "absolute_path",
|
||||
"excludes": ["template"],
|
||||
"required": True,
|
||||
"path_exists": True,
|
||||
},
|
||||
"templates": {
|
||||
"type": "list",
|
||||
"coerce": "list",
|
||||
"schema": {
|
||||
"type": "path",
|
||||
"coerce": "absolute_path",
|
||||
"path_exists": True,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"telegram": {
|
||||
@ -175,9 +218,17 @@ class ReportTrigger(Trigger):
|
||||
"required": True,
|
||||
"is_url": [],
|
||||
},
|
||||
"template": {
|
||||
"type": "string",
|
||||
"excludes": ["template_path"],
|
||||
"dependencies": ["templates"],
|
||||
"required": True,
|
||||
"empty": False,
|
||||
},
|
||||
"template_path": {
|
||||
"type": "path",
|
||||
"coerce": "absolute_path",
|
||||
"excludes": ["template"],
|
||||
"required": True,
|
||||
"path_exists": True,
|
||||
},
|
||||
@ -185,6 +236,15 @@ class ReportTrigger(Trigger):
|
||||
"type": "string",
|
||||
"allowed": ["MarkdownV2", "HTML", "Markdown"],
|
||||
},
|
||||
"templates": {
|
||||
"type": "list",
|
||||
"coerce": "list",
|
||||
"schema": {
|
||||
"type": "path",
|
||||
"coerce": "absolute_path",
|
||||
"path_exists": True,
|
||||
},
|
||||
},
|
||||
"timeout": {
|
||||
"type": "integer",
|
||||
"coerce": "integer",
|
||||
|
@ -35,7 +35,7 @@ class Telegram(Report, JinjaTemplate, SyncHttpClient):
|
||||
TELEGRAM_MAX_CONTENT_LENGTH(int): (class attribute) max content length of the message
|
||||
api_key(str): bot api key
|
||||
chat_id(str): chat id to post message, either string with @ or integer
|
||||
template_path(Path): path to template for built packages
|
||||
template(Path | str): name or path to template for built packages
|
||||
template_type(str): template message type to be used in parse mode, one of MarkdownV2, HTML, Markdown
|
||||
"""
|
||||
|
||||
@ -57,7 +57,8 @@ class Telegram(Report, JinjaTemplate, SyncHttpClient):
|
||||
|
||||
self.api_key = configuration.get(section, "api_key")
|
||||
self.chat_id = configuration.get(section, "chat_id")
|
||||
self.template_path = configuration.getpath(section, "template_path")
|
||||
self.template = configuration.get(section, "template", fallback=None) or \
|
||||
configuration.getpath(section, "template_path")
|
||||
self.template_type = configuration.get(section, "template_type", fallback="HTML")
|
||||
|
||||
def _send(self, text: str) -> None:
|
||||
@ -83,7 +84,7 @@ class Telegram(Report, JinjaTemplate, SyncHttpClient):
|
||||
"""
|
||||
if not result.success:
|
||||
return
|
||||
text = self.make_html(result, self.template_path)
|
||||
text = self.make_html(result, self.template)
|
||||
# telegram content is limited by 4096 symbols, so we are going to split the message by new lines
|
||||
# to fit into this restriction
|
||||
while len(text) > self.TELEGRAM_MAX_CONTENT_LENGTH:
|
||||
|
@ -147,7 +147,8 @@ def setup_service(repository_id: RepositoryId, configuration: Configuration, spa
|
||||
setup_cors(application)
|
||||
|
||||
application.logger.info("setup templates")
|
||||
aiohttp_jinja2.setup(application, loader=jinja2.FileSystemLoader(configuration.getpath("web", "templates")))
|
||||
templates = configuration.getpathlist("web", "templates")
|
||||
aiohttp_jinja2.setup(application, loader=jinja2.FileSystemLoader(searchpath=templates))
|
||||
|
||||
application.logger.info("setup configuration")
|
||||
application["configuration"] = configuration
|
||||
|
@ -229,6 +229,19 @@ def test_getpath_without_fallback(configuration: Configuration) -> None:
|
||||
assert configuration.getpath("build", "option")
|
||||
|
||||
|
||||
def test_getpathlist(configuration: Configuration) -> None:
|
||||
"""
|
||||
must extract path list
|
||||
"""
|
||||
path = Path("/a/b/c")
|
||||
configuration.set_option("build", "path", f"""{path} {path.relative_to("/")}""")
|
||||
|
||||
result = configuration.getpathlist("build", "path")
|
||||
assert all(element.is_absolute() for element in result)
|
||||
assert path in result
|
||||
assert all(element.is_relative_to("/") for element in result)
|
||||
|
||||
|
||||
def test_gettype(configuration: Configuration) -> None:
|
||||
"""
|
||||
must extract type from variable
|
||||
|
@ -8,6 +8,35 @@ from ahriman.models.package import Package
|
||||
from ahriman.models.result import Result
|
||||
|
||||
|
||||
def test_template(configuration: Configuration) -> None:
|
||||
"""
|
||||
must correctly parse template name and path
|
||||
"""
|
||||
template = configuration.get("email", "template")
|
||||
root, repository_id = configuration.check_loaded()
|
||||
|
||||
assert Email(repository_id, configuration, "email").template == template
|
||||
|
||||
configuration.remove_option("email", "template")
|
||||
configuration.set_option("email", "template_path", template)
|
||||
assert Email(repository_id, configuration, "email").template == root.parent / template
|
||||
|
||||
|
||||
def test_template_full(configuration: Configuration) -> None:
|
||||
"""
|
||||
must correctly parse template name and path
|
||||
"""
|
||||
template = "template"
|
||||
root, repository_id = configuration.check_loaded()
|
||||
|
||||
configuration.set_option("email", "template_full", template)
|
||||
assert Email(repository_id, configuration, "email").template_full == template
|
||||
|
||||
configuration.remove_option("email", "template_full")
|
||||
configuration.set_option("email", "full_template_path", template)
|
||||
assert Email(repository_id, configuration, "email").template_full == root.parent / template
|
||||
|
||||
|
||||
def test_send(email: Email, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must send an email with attachment
|
||||
@ -115,7 +144,7 @@ def test_generate_with_built_and_full_path(email: Email, package_ahriman: Packag
|
||||
"""
|
||||
send_mock = mocker.patch("ahriman.core.report.email.Email._send")
|
||||
|
||||
email.full_template_path = email.template_path
|
||||
email.template_full = email.template
|
||||
email.generate([package_ahriman], result)
|
||||
send_mock.assert_called_once_with(pytest.helpers.anyvar(int), pytest.helpers.anyvar(int))
|
||||
|
||||
|
@ -8,6 +8,20 @@ from ahriman.models.package import Package
|
||||
from ahriman.models.result import Result
|
||||
|
||||
|
||||
def test_template(configuration: Configuration) -> None:
|
||||
"""
|
||||
must correctly parse template name and path
|
||||
"""
|
||||
template = configuration.get("html", "template")
|
||||
root, repository_id = configuration.check_loaded()
|
||||
|
||||
assert HTML(repository_id, configuration, "html").template == template
|
||||
|
||||
configuration.remove_option("html", "template")
|
||||
configuration.set_option("html", "template_path", template)
|
||||
assert HTML(repository_id, configuration, "html").template == root.parent / template
|
||||
|
||||
|
||||
def test_generate(configuration: Configuration, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must generate report
|
||||
|
@ -8,7 +8,17 @@ def test_generate(configuration: Configuration, package_ahriman: Package) -> Non
|
||||
"""
|
||||
must generate html report
|
||||
"""
|
||||
path = configuration.getpath("html", "template_path")
|
||||
name = configuration.getpath("html", "template")
|
||||
_, repository_id = configuration.check_loaded()
|
||||
report = JinjaTemplate(repository_id, configuration, "html")
|
||||
assert report.make_html(Result(success=[package_ahriman]), name)
|
||||
|
||||
|
||||
def test_generate_from_path(configuration: Configuration, package_ahriman: Package) -> None:
|
||||
"""
|
||||
must generate html report from path
|
||||
"""
|
||||
path = configuration.getpath("html", "templates") / configuration.get("html", "template")
|
||||
_, repository_id = configuration.check_loaded()
|
||||
report = JinjaTemplate(repository_id, configuration, "html")
|
||||
assert report.make_html(Result(success=[package_ahriman]), path)
|
||||
|
@ -10,6 +10,20 @@ from ahriman.models.package import Package
|
||||
from ahriman.models.result import Result
|
||||
|
||||
|
||||
def test_template(configuration: Configuration) -> None:
|
||||
"""
|
||||
must correctly parse template name and path
|
||||
"""
|
||||
template = configuration.get("telegram", "template")
|
||||
root, repository_id = configuration.check_loaded()
|
||||
|
||||
assert Telegram(repository_id, configuration, "telegram").template == template
|
||||
|
||||
configuration.remove_option("telegram", "template")
|
||||
configuration.set_option("telegram", "template_path", template)
|
||||
assert Telegram(repository_id, configuration, "telegram").template == root.parent / template
|
||||
|
||||
|
||||
def test_send(telegram: Telegram, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must send a message
|
||||
|
@ -63,7 +63,8 @@ no_empty_report = no
|
||||
port = 587
|
||||
receivers = mail@example.com
|
||||
sender = mail@example.com
|
||||
template_path = ../web/templates/repo-index.jinja2
|
||||
template = repo-index.jinja2
|
||||
templates = ../web/templates
|
||||
|
||||
[console]
|
||||
use_utf = yes
|
||||
@ -72,7 +73,8 @@ use_utf = yes
|
||||
path =
|
||||
homepage =
|
||||
link_path =
|
||||
template_path = ../web/templates/repo-index.jinja2
|
||||
template = repo-index.jinja2
|
||||
templates = ../web/templates
|
||||
|
||||
[remote-call]
|
||||
manual = yes
|
||||
@ -82,7 +84,8 @@ api_key = apikey
|
||||
chat_id = @ahrimantestchat
|
||||
homepage =
|
||||
link_path =
|
||||
template_path = ../web/templates/telegram-index.jinja2
|
||||
template = telegram-index.jinja2
|
||||
templates = ../web/templates
|
||||
|
||||
[upload]
|
||||
target =
|
||||
|
Loading…
Reference in New Issue
Block a user