mirror of
https://github.com/arcan1s/ahriman.git
synced 2025-04-24 15:27:17 +00:00
implement rss generation
This commit is contained in:
parent
fc508e19b8
commit
d2a7e3cd53
@ -251,6 +251,7 @@ Section name must be either ``email`` (plus optional architecture name, e.g. ``e
|
|||||||
* ``password`` - SMTP password to authenticate, string, optional.
|
* ``password`` - SMTP password to authenticate, string, optional.
|
||||||
* ``port`` - SMTP port for sending emails, integer, required.
|
* ``port`` - SMTP port for sending emails, integer, required.
|
||||||
* ``receivers`` - SMTP receiver addresses, space separated list of strings, required.
|
* ``receivers`` - SMTP receiver addresses, space separated list of strings, required.
|
||||||
|
* ``rss_url`` - link to RSS feed, string, optional.
|
||||||
* ``sender`` - SMTP sender address, string, required.
|
* ``sender`` - SMTP sender address, string, required.
|
||||||
* ``ssl`` - SSL mode for SMTP connection, one of ``ssl``, ``starttls``, ``disabled``, optional, default ``disabled``.
|
* ``ssl`` - SSL mode for SMTP connection, one of ``ssl``, ``starttls``, ``disabled``, optional, default ``disabled``.
|
||||||
* ``template`` - Jinja2 template name, string, required.
|
* ``template`` - Jinja2 template name, string, required.
|
||||||
@ -266,7 +267,8 @@ Section name must be either ``html`` (plus optional architecture name, e.g. ``ht
|
|||||||
* ``type`` - type of the report, string, optional, must be set to ``html`` if exists.
|
* ``type`` - type of the report, string, optional, must be set to ``html`` if exists.
|
||||||
* ``homepage`` - link to homepage, string, optional.
|
* ``homepage`` - link to homepage, string, optional.
|
||||||
* ``link_path`` - prefix for HTML links, string, required.
|
* ``link_path`` - prefix for HTML links, string, required.
|
||||||
* ``path`` - path to html report file, string, required.
|
* ``path`` - path to HTML report file, string, required.
|
||||||
|
* ``rss_url`` - link to RSS feed, string, optional.
|
||||||
* ``template`` - Jinja2 template name, string, required.
|
* ``template`` - Jinja2 template name, string, required.
|
||||||
* ``templates`` - path to templates directories, space separated list of paths, required.
|
* ``templates`` - path to templates directories, space separated list of paths, required.
|
||||||
|
|
||||||
@ -281,6 +283,20 @@ Section name must be either ``remote-call`` (plus optional architecture name, e.
|
|||||||
* ``manual`` - update manually built packages, boolean, optional, default ``no``.
|
* ``manual`` - update manually built packages, boolean, optional, default ``no``.
|
||||||
* ``wait_timeout`` - maximum amount of time in seconds to be waited before remote process will be terminated, integer, optional, default ``-1``.
|
* ``wait_timeout`` - maximum amount of time in seconds to be waited before remote process will be terminated, integer, optional, default ``-1``.
|
||||||
|
|
||||||
|
``rss`` type
|
||||||
|
^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Section name must be either ``rss`` (plus optional architecture name, e.g. ``rss:x86_64``) or random name with ``type`` set.
|
||||||
|
|
||||||
|
* ``type`` - type of the report, string, optional, must be set to ``rss`` if exists.
|
||||||
|
* ``homepage`` - link to homepage, string, optional.
|
||||||
|
* ``link_path`` - prefix for HTML links, string, required.
|
||||||
|
* ``max_entries`` - maximal amount of entries to be included to the report, negative means no limit, integer, optional, default ``-1``.
|
||||||
|
* ``path`` - path to generated RSS file, string, required.
|
||||||
|
* ``rss_url`` - link to RSS feed, string, optional.
|
||||||
|
* ``template`` - Jinja2 template name, string, required.
|
||||||
|
* ``templates`` - path to templates directories, space separated list of paths, required.
|
||||||
|
|
||||||
``telegram`` type
|
``telegram`` type
|
||||||
^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
@ -291,6 +307,7 @@ Section name must be either ``telegram`` (plus optional architecture name, e.g.
|
|||||||
* ``chat_id`` - telegram chat id, either string with ``@`` or integer value, required.
|
* ``chat_id`` - telegram chat id, either string with ``@`` or integer value, required.
|
||||||
* ``homepage`` - link to homepage, string, optional.
|
* ``homepage`` - link to homepage, string, optional.
|
||||||
* ``link_path`` - prefix for HTML links, string, required.
|
* ``link_path`` - prefix for HTML links, string, required.
|
||||||
|
* ``rss_url`` - link to RSS feed, string, optional.
|
||||||
* ``template`` - Jinja2 template name, string, required.
|
* ``template`` - Jinja2 template name, string, required.
|
||||||
* ``template_type`` - ``parse_mode`` to be passed to telegram API, one of ``MarkdownV2``, ``HTML``, ``Markdown``, string, optional, default ``HTML``.
|
* ``template_type`` - ``parse_mode`` to be passed to telegram API, one of ``MarkdownV2``, ``HTML``, ``Markdown``, string, optional, default ``HTML``.
|
||||||
* ``templates`` - path to templates directories, space separated list of paths, required.
|
* ``templates`` - path to templates directories, space separated list of paths, required.
|
||||||
|
@ -17,7 +17,7 @@ mirror = https://geo.mirror.pkgbuild.com/$repo/os/$arch
|
|||||||
repositories = core extra multilib
|
repositories = core extra multilib
|
||||||
; Pacman's root directory. In the most cases it must point to the system root.
|
; Pacman's root directory. In the most cases it must point to the system root.
|
||||||
root = /
|
root = /
|
||||||
; Sync files databases too, which is required by deep dependencies check
|
; Sync files databases too, which is required by deep dependencies check.
|
||||||
sync_files_database = yes
|
sync_files_database = yes
|
||||||
; Use local packages cache. If this option is enabled, the service will be able to synchronize databases (available
|
; Use local packages cache. If this option is enabled, the service will be able to synchronize databases (available
|
||||||
; as additional option for some subcommands). If set to no, databases must be synchronized manually.
|
; as additional option for some subcommands). If set to no, databases must be synchronized manually.
|
||||||
@ -52,17 +52,17 @@ allow_read_only = yes
|
|||||||
[build]
|
[build]
|
||||||
; List of additional flags passed to archbuild command.
|
; List of additional flags passed to archbuild command.
|
||||||
;archbuild_flags =
|
;archbuild_flags =
|
||||||
; Path to build command
|
; Path to build command.
|
||||||
;build_command =
|
;build_command =
|
||||||
; List of packages to be ignored during automatic updates.
|
; List of packages to be ignored during automatic updates.
|
||||||
;ignore_packages =
|
;ignore_packages =
|
||||||
; Include debug packages
|
; Include debug packages.
|
||||||
;include_debug_packages = yes
|
;include_debug_packages = yes
|
||||||
; List of additional flags passed to makechrootpkg command.
|
; List of additional flags passed to makechrootpkg command.
|
||||||
;makechrootpkg_flags =
|
;makechrootpkg_flags =
|
||||||
; List of additional flags passed to makepkg command.
|
; List of additional flags passed to makepkg command.
|
||||||
makepkg_flags = --nocolor --ignorearch
|
makepkg_flags = --nocolor --ignorearch
|
||||||
; List of paths to be used for implicit dependency scan. Regular expressions are supported
|
; List of paths to be used for implicit dependency scan. Regular expressions are supported.
|
||||||
scan_paths = ^usr/lib(?!/cmake).*$
|
scan_paths = ^usr/lib(?!/cmake).*$
|
||||||
; List of enabled triggers in the order of calls.
|
; List of enabled triggers in the order of calls.
|
||||||
triggers = ahriman.core.gitremote.RemotePullTrigger ahriman.core.report.ReportTrigger ahriman.core.upload.UploadTrigger ahriman.core.gitremote.RemotePushTrigger
|
triggers = ahriman.core.gitremote.RemotePullTrigger ahriman.core.report.ReportTrigger ahriman.core.upload.UploadTrigger ahriman.core.gitremote.RemotePushTrigger
|
||||||
@ -212,14 +212,14 @@ target = console
|
|||||||
|
|
||||||
; Console reporting trigger configuration sample.
|
; Console reporting trigger configuration sample.
|
||||||
[console]
|
[console]
|
||||||
; Trigger type name
|
; Trigger type name.
|
||||||
;type = console
|
;type = console
|
||||||
; Use utf8 symbols in output.
|
; Use utf8 symbols in output.
|
||||||
use_utf = yes
|
use_utf = yes
|
||||||
|
|
||||||
; Email reporting trigger configuration sample.
|
; Email reporting trigger configuration sample.
|
||||||
[email]
|
[email]
|
||||||
; Trigger type name
|
; Trigger type name.
|
||||||
;type = email
|
;type = email
|
||||||
; Optional URL to the repository homepage.
|
; Optional URL to the repository homepage.
|
||||||
;homepage=
|
;homepage=
|
||||||
@ -235,6 +235,8 @@ use_utf = yes
|
|||||||
;port =
|
;port =
|
||||||
; List of emails to receive the reports.
|
; List of emails to receive the reports.
|
||||||
;receivers =
|
;receivers =
|
||||||
|
; Optional link to the RSS feed.
|
||||||
|
;rss_url =
|
||||||
; Sender email.
|
; Sender email.
|
||||||
;sender =
|
;sender =
|
||||||
; SMTP server SSL mode, one of ssl, starttls, disabled.
|
; SMTP server SSL mode, one of ssl, starttls, disabled.
|
||||||
@ -250,7 +252,7 @@ templates = /usr/share/ahriman/templates
|
|||||||
|
|
||||||
; HTML reporting trigger configuration sample.
|
; HTML reporting trigger configuration sample.
|
||||||
[html]
|
[html]
|
||||||
; Trigger type name
|
; Trigger type name.
|
||||||
;type = html
|
;type = html
|
||||||
; Optional URL to the repository homepage.
|
; Optional URL to the repository homepage.
|
||||||
;homepage=
|
;homepage=
|
||||||
@ -258,6 +260,8 @@ templates = /usr/share/ahriman/templates
|
|||||||
;link_path =
|
;link_path =
|
||||||
; Output path for the HTML report.
|
; Output path for the HTML report.
|
||||||
;path =
|
;path =
|
||||||
|
; Optional link to the RSS feed.
|
||||||
|
;rss_url =
|
||||||
; Template name to be used.
|
; Template name to be used.
|
||||||
template = repo-index.jinja2
|
template = repo-index.jinja2
|
||||||
; List of directories with templates.
|
; List of directories with templates.
|
||||||
@ -265,7 +269,7 @@ templates = /usr/share/ahriman/templates
|
|||||||
|
|
||||||
; Remote service callback trigger configuration sample.
|
; Remote service callback trigger configuration sample.
|
||||||
[remote-call]
|
[remote-call]
|
||||||
; Trigger type name
|
; Trigger type name.
|
||||||
;type = remote-call
|
;type = remote-call
|
||||||
; Call for AUR packages update.
|
; Call for AUR packages update.
|
||||||
;aur = no
|
;aur = no
|
||||||
@ -276,9 +280,26 @@ templates = /usr/share/ahriman/templates
|
|||||||
; Wait until remote process will be terminated in seconds.
|
; Wait until remote process will be terminated in seconds.
|
||||||
;wait_timeout = -1
|
;wait_timeout = -1
|
||||||
|
|
||||||
|
; RSS reporting trigger configuration sample.
|
||||||
|
[rss]
|
||||||
|
; Trigger type name.
|
||||||
|
;type = rss
|
||||||
|
; Optional URL to the repository homepage.
|
||||||
|
;homepage=
|
||||||
|
; Prefix for packages links. Link to a package will be formed as link_path / filename.
|
||||||
|
;link_path =
|
||||||
|
; Output path for the RSS report.
|
||||||
|
;path =
|
||||||
|
; Optional link to the RSS feed.
|
||||||
|
;rss_url =
|
||||||
|
; Template name to be used.
|
||||||
|
template = rss.jinja2
|
||||||
|
; List of directories with templates.
|
||||||
|
templates = /usr/share/ahriman/templates
|
||||||
|
|
||||||
; Telegram reporting trigger configuration sample.
|
; Telegram reporting trigger configuration sample.
|
||||||
[telegram]
|
[telegram]
|
||||||
; Trigger type name
|
; Trigger type name.
|
||||||
;type = telegram
|
;type = telegram
|
||||||
; Telegram bot API key.
|
; Telegram bot API key.
|
||||||
;api_key =
|
;api_key =
|
||||||
@ -288,6 +309,8 @@ templates = /usr/share/ahriman/templates
|
|||||||
;homepage=
|
;homepage=
|
||||||
; Prefix for packages links. Link to a package will be formed as link_path / filename.
|
; Prefix for packages links. Link to a package will be formed as link_path / filename.
|
||||||
;link_path =
|
;link_path =
|
||||||
|
; Optional link to the RSS feed.
|
||||||
|
;rss_url =
|
||||||
; Template name to be used.
|
; Template name to be used.
|
||||||
template = telegram-index.jinja2
|
template = telegram-index.jinja2
|
||||||
; Telegram specific template mode, one of MarkdownV2, HTML or Markdown.
|
; Telegram specific template mode, one of MarkdownV2, HTML or Markdown.
|
||||||
@ -304,7 +327,7 @@ target =
|
|||||||
|
|
||||||
; GitHub upload trigger configuration sample.
|
; GitHub upload trigger configuration sample.
|
||||||
[github]
|
[github]
|
||||||
; Trigger type name
|
; Trigger type name.
|
||||||
;type = github
|
;type = github
|
||||||
; GitHub repository owner username.
|
; GitHub repository owner username.
|
||||||
;owner =
|
;owner =
|
||||||
@ -321,14 +344,14 @@ target =
|
|||||||
|
|
||||||
; Remote instance upload trigger configuration sample.
|
; Remote instance upload trigger configuration sample.
|
||||||
[remote-service]
|
[remote-service]
|
||||||
; Trigger type name
|
; Trigger type name.
|
||||||
;type = remote-service
|
;type = remote-service
|
||||||
; HTTP request timeout in seconds.
|
; HTTP request timeout in seconds.
|
||||||
;timeout = 30
|
;timeout = 30
|
||||||
|
|
||||||
; rsync upload trigger configuration sample.
|
; rsync upload trigger configuration sample.
|
||||||
[rsync]
|
[rsync]
|
||||||
; Trigger type name
|
; Trigger type name.
|
||||||
;type = rsync
|
;type = rsync
|
||||||
; rsync command to run.
|
; rsync command to run.
|
||||||
command = rsync --archive --compress --partial --delete
|
command = rsync --archive --compress --partial --delete
|
||||||
@ -338,7 +361,7 @@ command = rsync --archive --compress --partial --delete
|
|||||||
|
|
||||||
; S3 upload trigger configuration sample.
|
; S3 upload trigger configuration sample.
|
||||||
[s3]
|
[s3]
|
||||||
; Trigger type name
|
; Trigger type name.
|
||||||
;type = s3
|
;type = s3
|
||||||
; AWS services access key.
|
; AWS services access key.
|
||||||
;access_key =
|
;access_key =
|
||||||
|
@ -7,6 +7,10 @@
|
|||||||
|
|
||||||
{% include "utils/style.jinja2" %}
|
{% include "utils/style.jinja2" %}
|
||||||
{% include "user-style.jinja2" ignore missing %}
|
{% include "user-style.jinja2" ignore missing %}
|
||||||
|
|
||||||
|
{% if rss_url is not none %}
|
||||||
|
<link rel="alternate" href="{{ rss_url }}" type="application/rss+xml">
|
||||||
|
{% endif %}
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
27
package/share/ahriman/templates/rss.jinja2
Normal file
27
package/share/ahriman/templates/rss.jinja2
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
|
||||||
|
<channel>
|
||||||
|
<title>{{ repository }}: Recent package updates</title>
|
||||||
|
{% if homepage is not none %}
|
||||||
|
<link>{{ homepage }}</link>
|
||||||
|
{% endif %}
|
||||||
|
<description>Recently updated packages in the {{ repository }}.</description>
|
||||||
|
{% if rss_url is not none %}
|
||||||
|
<atom:link href="{{ rss_url }}" rel="self"/>
|
||||||
|
{% endif %}
|
||||||
|
<language>en-us</language>
|
||||||
|
<lastBuildDate>{{ last_update }}</lastBuildDate>
|
||||||
|
|
||||||
|
{% for package in packages %}
|
||||||
|
<item>
|
||||||
|
<title>{{ package.name }} {{ package.version }} {{ package.architecture }}</title>
|
||||||
|
<link>{{ link_path }}/{{ package.filename }}</link>
|
||||||
|
<description>{{ package.description }}</description>
|
||||||
|
<pubDate>{{ package.build_date }}</pubDate>
|
||||||
|
<guid isPermaLink="false">{{ package.tag }}</guid>
|
||||||
|
<category>{{ repository }}</category>
|
||||||
|
<category>{{ package.architecture }}</category>
|
||||||
|
</item>
|
||||||
|
{% endfor %}
|
||||||
|
</channel>
|
||||||
|
</rss>
|
@ -171,7 +171,7 @@ class PackageArchive:
|
|||||||
|
|
||||||
result: dict[Path, list[FilesystemPackage]] = {}
|
result: dict[Path, list[FilesystemPackage]] = {}
|
||||||
# sort items from children directories to root
|
# sort items from children directories to root
|
||||||
for path, packages in reversed(sorted(source.items())):
|
for path, packages in sorted(source.items(), reverse=True):
|
||||||
# skip if this path belongs to the one of the base packages
|
# skip if this path belongs to the one of the base packages
|
||||||
if any(package.package_name in base_packages for package in packages):
|
if any(package.package_name in base_packages for package in packages):
|
||||||
continue
|
continue
|
||||||
|
@ -17,14 +17,16 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
|
import datetime
|
||||||
import jinja2
|
import jinja2
|
||||||
|
|
||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from ahriman.core.configuration import Configuration
|
from ahriman.core.configuration import Configuration
|
||||||
from ahriman.core.sign.gpg import GPG
|
from ahriman.core.sign.gpg import GPG
|
||||||
from ahriman.core.utils import pretty_datetime, pretty_size
|
from ahriman.core.utils import pretty_datetime, pretty_size, utcnow
|
||||||
from ahriman.models.repository_id import RepositoryId
|
from ahriman.models.repository_id import RepositoryId
|
||||||
from ahriman.models.result import Result
|
from ahriman.models.result import Result
|
||||||
from ahriman.models.sign_settings import SignSettings
|
from ahriman.models.sign_settings import SignSettings
|
||||||
@ -37,6 +39,7 @@ class JinjaTemplate:
|
|||||||
It uses jinja2 templates for report generation, the following variables are allowed:
|
It uses jinja2 templates for report generation, the following variables are allowed:
|
||||||
|
|
||||||
* homepage - link to homepage, string, optional
|
* homepage - link to homepage, string, optional
|
||||||
|
* last_update - report generation time, pretty printed datetime, required
|
||||||
* link_path - prefix fo packages to download, string, required
|
* link_path - prefix fo packages to download, string, required
|
||||||
* has_package_signed - ``True`` in case if package sign enabled, ``False`` otherwise, required
|
* has_package_signed - ``True`` in case if package sign enabled, ``False`` otherwise, required
|
||||||
* has_repo_signed - ``True`` in case if repository database sign enabled, ``False`` otherwise, required
|
* has_repo_signed - ``True`` in case if repository database sign enabled, ``False`` otherwise, required
|
||||||
@ -46,21 +49,24 @@ class JinjaTemplate:
|
|||||||
* build_date, pretty printed datetime, string
|
* build_date, pretty printed datetime, string
|
||||||
* depends, sorted list of strings
|
* depends, sorted list of strings
|
||||||
* description, string
|
* description, string
|
||||||
* filename, string,
|
* filename, string
|
||||||
* groups, sorted list of strings
|
* groups, sorted list of strings
|
||||||
* installed_size, pretty printed datetime, string
|
* installed_size, pretty printed size, string
|
||||||
* licenses, sorted list of strings
|
* licenses, sorted list of strings
|
||||||
* name, string
|
* name, string
|
||||||
|
* tag, string
|
||||||
* url, string
|
* url, string
|
||||||
* version, string
|
* version, string
|
||||||
* pgp_key - default PGP key ID, string, optional
|
* pgp_key - default PGP key ID, string, optional
|
||||||
* repository - repository name, string, required
|
* repository - repository name, string, required
|
||||||
|
* rss_url - optional link to the RSS feed, string, optional
|
||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
default_pgp_key(str | None): default PGP key
|
default_pgp_key(str | None): default PGP key
|
||||||
homepage(str | None): homepage link if any (for footer)
|
homepage(str | None): homepage link if any (for footer)
|
||||||
link_path(str): prefix fo packages to download
|
link_path(str): prefix fo packages to download
|
||||||
name(str): repository name
|
name(str): repository name
|
||||||
|
rss_url(str | None): link to the RSS feed
|
||||||
sign_targets(set[SignSettings]): targets to sign enabled in configuration
|
sign_targets(set[SignSettings]): targets to sign enabled in configuration
|
||||||
templates(list[Path]): list of directories with templates
|
templates(list[Path]): list of directories with templates
|
||||||
"""
|
"""
|
||||||
@ -80,8 +86,36 @@ class JinjaTemplate:
|
|||||||
self.homepage = configuration.get(section, "homepage", fallback=None)
|
self.homepage = configuration.get(section, "homepage", fallback=None)
|
||||||
self.link_path = configuration.get(section, "link_path")
|
self.link_path = configuration.get(section, "link_path")
|
||||||
self.name = repository_id.name
|
self.name = repository_id.name
|
||||||
|
self.rss_url = configuration.get(section, "rss_url", fallback=None)
|
||||||
self.sign_targets, self.default_pgp_key = GPG.sign_options(configuration)
|
self.sign_targets, self.default_pgp_key = GPG.sign_options(configuration)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def format_datetime(timestamp: datetime.datetime | float | int | None) -> str:
|
||||||
|
"""
|
||||||
|
convert datetime object to string
|
||||||
|
|
||||||
|
Args:
|
||||||
|
timestamp(datetime.datetime | float | int | None): datetime to convert
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: datetime as string representation
|
||||||
|
"""
|
||||||
|
return pretty_datetime(timestamp)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def sort_content(content: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
sort content before rendering
|
||||||
|
|
||||||
|
Args:
|
||||||
|
content(list[dict[str, str]]): content of the template
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
list[dict[str, str]]: sorted content according to comparator defined
|
||||||
|
"""
|
||||||
|
comparator: Callable[[dict[str, str]], str] = lambda item: item["filename"]
|
||||||
|
return sorted(content, key=comparator)
|
||||||
|
|
||||||
def make_html(self, result: Result, template_name: Path | str) -> str:
|
def make_html(self, result: Result, template_name: Path | str) -> str:
|
||||||
"""
|
"""
|
||||||
generate report for the specified packages
|
generate report for the specified packages
|
||||||
@ -104,7 +138,7 @@ class JinjaTemplate:
|
|||||||
{
|
{
|
||||||
"architecture": properties.architecture or "",
|
"architecture": properties.architecture or "",
|
||||||
"archive_size": pretty_size(properties.archive_size),
|
"archive_size": pretty_size(properties.archive_size),
|
||||||
"build_date": pretty_datetime(properties.build_date),
|
"build_date": self.format_datetime(properties.build_date),
|
||||||
"depends": properties.depends,
|
"depends": properties.depends,
|
||||||
"description": properties.description or "",
|
"description": properties.description or "",
|
||||||
"filename": properties.filename,
|
"filename": properties.filename,
|
||||||
@ -112,17 +146,20 @@ class JinjaTemplate:
|
|||||||
"installed_size": pretty_size(properties.installed_size),
|
"installed_size": pretty_size(properties.installed_size),
|
||||||
"licenses": properties.licenses,
|
"licenses": properties.licenses,
|
||||||
"name": package,
|
"name": package,
|
||||||
|
"tag": f"tag:{self.name}:{properties.architecture}:{package}:{base.version}:{properties.build_date}",
|
||||||
"url": properties.url or "",
|
"url": properties.url or "",
|
||||||
"version": base.version
|
"version": base.version,
|
||||||
} for base in result.success for package, properties in base.packages.items()
|
} for base in result.success for package, properties in base.packages.items()
|
||||||
]
|
]
|
||||||
comparator: Callable[[dict[str, str]], str] = lambda item: item["filename"]
|
|
||||||
|
|
||||||
return template.render(
|
return template.render(
|
||||||
homepage=self.homepage,
|
homepage=self.homepage,
|
||||||
|
last_update=self.format_datetime(utcnow()),
|
||||||
link_path=self.link_path,
|
link_path=self.link_path,
|
||||||
has_package_signed=SignSettings.Packages in self.sign_targets,
|
has_package_signed=SignSettings.Packages in self.sign_targets,
|
||||||
has_repo_signed=SignSettings.Repository in self.sign_targets,
|
has_repo_signed=SignSettings.Repository in self.sign_targets,
|
||||||
packages=sorted(content, key=comparator),
|
packages=self.sort_content(content),
|
||||||
pgp_key=self.default_pgp_key,
|
pgp_key=self.default_pgp_key,
|
||||||
repository=self.name)
|
repository=self.name,
|
||||||
|
rss_url=self.rss_url,
|
||||||
|
)
|
||||||
|
@ -66,7 +66,7 @@ class Report(LazyLogging):
|
|||||||
self.configuration = configuration
|
self.configuration = configuration
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def load(repository_id: RepositoryId, configuration: Configuration, target: str) -> Report:
|
def load(repository_id: RepositoryId, configuration: Configuration, target: str) -> Report: # pylint: disable=too-many-return-statements
|
||||||
"""
|
"""
|
||||||
load client from settings
|
load client from settings
|
||||||
|
|
||||||
@ -92,6 +92,9 @@ class Report(LazyLogging):
|
|||||||
case ReportSettings.Telegram:
|
case ReportSettings.Telegram:
|
||||||
from ahriman.core.report.telegram import Telegram
|
from ahriman.core.report.telegram import Telegram
|
||||||
return Telegram(repository_id, configuration, section)
|
return Telegram(repository_id, configuration, section)
|
||||||
|
case ReportSettings.RSS:
|
||||||
|
from ahriman.core.report.rss import RSS
|
||||||
|
return RSS(repository_id, configuration, section)
|
||||||
case ReportSettings.RemoteCall:
|
case ReportSettings.RemoteCall:
|
||||||
from ahriman.core.report.remote_call import RemoteCall
|
from ahriman.core.report.remote_call import RemoteCall
|
||||||
return RemoteCall(repository_id, configuration, section)
|
return RemoteCall(repository_id, configuration, section)
|
||||||
|
@ -116,6 +116,11 @@ class ReportTrigger(Trigger):
|
|||||||
"required": True,
|
"required": True,
|
||||||
"empty": False,
|
"empty": False,
|
||||||
},
|
},
|
||||||
|
"rss_url": {
|
||||||
|
"type": "string",
|
||||||
|
"empty": False,
|
||||||
|
"is_url": ["http", "https"],
|
||||||
|
},
|
||||||
"sender": {
|
"sender": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"required": True,
|
"required": True,
|
||||||
@ -187,6 +192,11 @@ class ReportTrigger(Trigger):
|
|||||||
"coerce": "absolute_path",
|
"coerce": "absolute_path",
|
||||||
"required": True,
|
"required": True,
|
||||||
},
|
},
|
||||||
|
"rss_url": {
|
||||||
|
"type": "string",
|
||||||
|
"empty": False,
|
||||||
|
"is_url": ["http", "https"],
|
||||||
|
},
|
||||||
"template": {
|
"template": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"excludes": ["template_path"],
|
"excludes": ["template_path"],
|
||||||
@ -243,6 +253,11 @@ class ReportTrigger(Trigger):
|
|||||||
"empty": False,
|
"empty": False,
|
||||||
"is_url": [],
|
"is_url": [],
|
||||||
},
|
},
|
||||||
|
"rss_url": {
|
||||||
|
"type": "string",
|
||||||
|
"empty": False,
|
||||||
|
"is_url": ["http", "https"],
|
||||||
|
},
|
||||||
"template": {
|
"template": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"excludes": ["template_path"],
|
"excludes": ["template_path"],
|
||||||
@ -304,7 +319,67 @@ class ReportTrigger(Trigger):
|
|||||||
"coerce": "integer",
|
"coerce": "integer",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
|
"rss": {
|
||||||
|
"type": "dict",
|
||||||
|
"schema": {
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"allowed": ["rss"],
|
||||||
|
},
|
||||||
|
"homepage": {
|
||||||
|
"type": "string",
|
||||||
|
"empty": False,
|
||||||
|
"is_url": ["http", "https"],
|
||||||
|
},
|
||||||
|
"link_path": {
|
||||||
|
"type": "string",
|
||||||
|
"required": True,
|
||||||
|
"empty": False,
|
||||||
|
"is_url": [],
|
||||||
|
},
|
||||||
|
"max_entries": {
|
||||||
|
"type": "integer",
|
||||||
|
"coerce": "integer",
|
||||||
|
},
|
||||||
|
"path": {
|
||||||
|
"type": "path",
|
||||||
|
"coerce": "absolute_path",
|
||||||
|
"required": True,
|
||||||
|
},
|
||||||
|
"rss_url": {
|
||||||
|
"type": "string",
|
||||||
|
"empty": False,
|
||||||
|
"is_url": ["http", "https"],
|
||||||
|
},
|
||||||
|
"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,
|
||||||
|
"path_type": "file",
|
||||||
|
},
|
||||||
|
"templates": {
|
||||||
|
"type": "list",
|
||||||
|
"coerce": "list",
|
||||||
|
"schema": {
|
||||||
|
"type": "path",
|
||||||
|
"coerce": "absolute_path",
|
||||||
|
"path_exists": True,
|
||||||
|
"path_type": "dir",
|
||||||
|
},
|
||||||
|
"empty": False,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, repository_id: RepositoryId, configuration: Configuration) -> None:
|
def __init__(self, repository_id: RepositoryId, configuration: Configuration) -> None:
|
||||||
|
130
src/ahriman/core/report/rss.py
Normal file
130
src/ahriman/core/report/rss.py
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
#
|
||||||
|
# Copyright (c) 2021-2024 ahriman team.
|
||||||
|
#
|
||||||
|
# This file is part of ahriman
|
||||||
|
# (see https://github.com/arcan1s/ahriman).
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# 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
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
from collections.abc import Callable
|
||||||
|
from email.utils import format_datetime, parsedate_to_datetime
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from ahriman.core import context
|
||||||
|
from ahriman.core.configuration import Configuration
|
||||||
|
from ahriman.core.report.jinja_template import JinjaTemplate
|
||||||
|
from ahriman.core.report.report import Report
|
||||||
|
from ahriman.core.status import Client
|
||||||
|
from ahriman.models.event import EventType
|
||||||
|
from ahriman.models.package import Package
|
||||||
|
from ahriman.models.repository_id import RepositoryId
|
||||||
|
from ahriman.models.result import Result
|
||||||
|
|
||||||
|
|
||||||
|
class RSS(Report, JinjaTemplate):
|
||||||
|
"""
|
||||||
|
RSS report generator
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
max_entries(int): the maximal amount of entries in RSS
|
||||||
|
report_path(Path): output path to RSS report
|
||||||
|
template(str): name of the template
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, repository_id: RepositoryId, configuration: Configuration, section: str) -> None:
|
||||||
|
"""
|
||||||
|
default constructor
|
||||||
|
|
||||||
|
Args:
|
||||||
|
repository_id(RepositoryId): repository unique identifier
|
||||||
|
configuration(Configuration): configuration instance
|
||||||
|
section(str): settings section name
|
||||||
|
"""
|
||||||
|
Report.__init__(self, repository_id, configuration)
|
||||||
|
JinjaTemplate.__init__(self, repository_id, configuration, section)
|
||||||
|
|
||||||
|
self.max_entries = configuration.getint(section, "max_entries", fallback=-1)
|
||||||
|
self.report_path = configuration.getpath(section, "path")
|
||||||
|
self.template = configuration.get(section, "template")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def format_datetime(timestamp: datetime.datetime | float | int | None) -> str:
|
||||||
|
"""
|
||||||
|
convert datetime object to string
|
||||||
|
|
||||||
|
Args:
|
||||||
|
timestamp(datetime.datetime | float | int | None): datetime to convert
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: datetime as string representation
|
||||||
|
"""
|
||||||
|
if timestamp is None:
|
||||||
|
return ""
|
||||||
|
if isinstance(timestamp, (int, float)):
|
||||||
|
timestamp = datetime.datetime.fromtimestamp(timestamp, datetime.UTC)
|
||||||
|
return format_datetime(timestamp)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def sort_content(content: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
sort content before rendering
|
||||||
|
|
||||||
|
Args:
|
||||||
|
content(list[dict[str, str]]): content of the template
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
list[dict[str, str]]: sorted content according to comparator defined
|
||||||
|
"""
|
||||||
|
comparator: Callable[[dict[str, str]], datetime.datetime] = \
|
||||||
|
lambda item: parsedate_to_datetime(item["build_date"])
|
||||||
|
return sorted(content, key=comparator, reverse=True)
|
||||||
|
|
||||||
|
def content(self, packages: list[Package]) -> Result:
|
||||||
|
"""
|
||||||
|
extract result to be written to template
|
||||||
|
|
||||||
|
Args:
|
||||||
|
packages(list[Package]): list of packages to generate report
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Result: result descriptor
|
||||||
|
"""
|
||||||
|
ctx = context.get()
|
||||||
|
reporter = ctx.get(Client)
|
||||||
|
events = reporter.event_get(EventType.PackageUpdated, None, limit=self.max_entries)
|
||||||
|
|
||||||
|
known_packages = {package.base: package for package in packages}
|
||||||
|
|
||||||
|
result = Result()
|
||||||
|
for event in events:
|
||||||
|
package = known_packages.get(event.object_id)
|
||||||
|
if package is None:
|
||||||
|
continue # package not found
|
||||||
|
result.add_updated(package)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
def generate(self, packages: list[Package], result: Result) -> None:
|
||||||
|
"""
|
||||||
|
generate report for the specified packages
|
||||||
|
|
||||||
|
Args:
|
||||||
|
packages(list[Package]): list of packages to generate report
|
||||||
|
result(Result): build result
|
||||||
|
"""
|
||||||
|
result = self.content(packages)
|
||||||
|
rss = self.make_html(result, self.template)
|
||||||
|
self.report_path.write_text(rss, encoding="utf8")
|
@ -32,6 +32,7 @@ class ReportSettings(StrEnum):
|
|||||||
Email(ReportSettings): (class attribute) email report generation
|
Email(ReportSettings): (class attribute) email report generation
|
||||||
Console(ReportSettings): (class attribute) print result to console
|
Console(ReportSettings): (class attribute) print result to console
|
||||||
Telegram(ReportSettings): (class attribute) markdown report to telegram channel
|
Telegram(ReportSettings): (class attribute) markdown report to telegram channel
|
||||||
|
RSS(ReportSettings): (class attribute) RSS report generation
|
||||||
RemoteCall(ReportSettings): (class attribute) remote ahriman server call
|
RemoteCall(ReportSettings): (class attribute) remote ahriman server call
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ -40,10 +41,11 @@ class ReportSettings(StrEnum):
|
|||||||
Email = "email"
|
Email = "email"
|
||||||
Console = "console"
|
Console = "console"
|
||||||
Telegram = "telegram"
|
Telegram = "telegram"
|
||||||
|
RSS = "rss"
|
||||||
RemoteCall = "remote-call"
|
RemoteCall = "remote-call"
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def from_option(value: str) -> ReportSettings:
|
def from_option(value: str) -> ReportSettings: # pylint: disable=too-many-return-statements
|
||||||
"""
|
"""
|
||||||
construct value from configuration
|
construct value from configuration
|
||||||
|
|
||||||
@ -62,6 +64,8 @@ class ReportSettings(StrEnum):
|
|||||||
return ReportSettings.Console
|
return ReportSettings.Console
|
||||||
case "telegram":
|
case "telegram":
|
||||||
return ReportSettings.Telegram
|
return ReportSettings.Telegram
|
||||||
|
case "rss":
|
||||||
|
return ReportSettings.RSS
|
||||||
case "ahriman" | "remote-call":
|
case "ahriman" | "remote-call":
|
||||||
return ReportSettings.RemoteCall
|
return ReportSettings.RemoteCall
|
||||||
case _:
|
case _:
|
||||||
|
@ -75,6 +75,7 @@ def test_schema(configuration: Configuration) -> None:
|
|||||||
assert schema.pop("remote-push")
|
assert schema.pop("remote-push")
|
||||||
assert schema.pop("remote-service")
|
assert schema.pop("remote-service")
|
||||||
assert schema.pop("report")
|
assert schema.pop("report")
|
||||||
|
assert schema.pop("rss")
|
||||||
assert schema.pop("rsync")
|
assert schema.pop("rsync")
|
||||||
assert schema.pop("s3")
|
assert schema.pop("s3")
|
||||||
assert schema.pop("telegram")
|
assert schema.pop("telegram")
|
||||||
|
@ -3,6 +3,7 @@ import pytest
|
|||||||
from ahriman.core.configuration import Configuration
|
from ahriman.core.configuration import Configuration
|
||||||
from ahriman.core.report.email import Email
|
from ahriman.core.report.email import Email
|
||||||
from ahriman.core.report.remote_call import RemoteCall
|
from ahriman.core.report.remote_call import RemoteCall
|
||||||
|
from ahriman.core.report.rss import RSS
|
||||||
from ahriman.core.report.telegram import Telegram
|
from ahriman.core.report.telegram import Telegram
|
||||||
|
|
||||||
|
|
||||||
@ -15,7 +16,7 @@ def email(configuration: Configuration) -> Email:
|
|||||||
configuration(Configuration): configuration fixture
|
configuration(Configuration): configuration fixture
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
RemoteCall: email trigger test instance
|
Email: email trigger test instance
|
||||||
"""
|
"""
|
||||||
_, repository_id = configuration.check_loaded()
|
_, repository_id = configuration.check_loaded()
|
||||||
return Email(repository_id, configuration, "email")
|
return Email(repository_id, configuration, "email")
|
||||||
@ -38,6 +39,21 @@ def remote_call(configuration: Configuration) -> RemoteCall:
|
|||||||
return RemoteCall(repository_id, configuration, "remote-call")
|
return RemoteCall(repository_id, configuration, "remote-call")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def rss(configuration: Configuration) -> RSS:
|
||||||
|
"""
|
||||||
|
fixture for rss trigger
|
||||||
|
|
||||||
|
Args:
|
||||||
|
configuration(Configuration): configuration fixture
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
RSS: rss trigger test instance
|
||||||
|
"""
|
||||||
|
_, repository_id = configuration.check_loaded()
|
||||||
|
return RSS(repository_id, configuration, "rss")
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def telegram(configuration: Configuration) -> Telegram:
|
def telegram(configuration: Configuration) -> Telegram:
|
||||||
"""
|
"""
|
||||||
@ -47,7 +63,7 @@ def telegram(configuration: Configuration) -> Telegram:
|
|||||||
configuration(Configuration): configuration fixture
|
configuration(Configuration): configuration fixture
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
RemoteCall: telegram trigger test instance
|
Telegram: telegram trigger test instance
|
||||||
"""
|
"""
|
||||||
_, repository_id = configuration.check_loaded()
|
_, repository_id = configuration.check_loaded()
|
||||||
return Telegram(repository_id, configuration, "telegram")
|
return Telegram(repository_id, configuration, "telegram")
|
||||||
|
@ -1,9 +1,24 @@
|
|||||||
from ahriman.core.configuration import Configuration
|
from ahriman.core.configuration import Configuration
|
||||||
from ahriman.core.report.jinja_template import JinjaTemplate
|
from ahriman.core.report.jinja_template import JinjaTemplate
|
||||||
|
from ahriman.core.utils import utcnow
|
||||||
from ahriman.models.package import Package
|
from ahriman.models.package import Package
|
||||||
from ahriman.models.result import Result
|
from ahriman.models.result import Result
|
||||||
|
|
||||||
|
|
||||||
|
def test_format_datetime() -> None:
|
||||||
|
"""
|
||||||
|
must format datetime
|
||||||
|
"""
|
||||||
|
assert JinjaTemplate.format_datetime(utcnow())
|
||||||
|
|
||||||
|
|
||||||
|
def sort_content() -> None:
|
||||||
|
"""
|
||||||
|
must sort content for the template
|
||||||
|
"""
|
||||||
|
assert JinjaTemplate.sort_content([{"filename": "2"}, {"filename": "1"}]) == [{"filename": "1"}, {"filename": "2"}]
|
||||||
|
|
||||||
|
|
||||||
def test_generate(configuration: Configuration, package_ahriman: Package) -> None:
|
def test_generate(configuration: Configuration, package_ahriman: Package) -> None:
|
||||||
"""
|
"""
|
||||||
must generate html report
|
must generate html report
|
||||||
|
@ -78,6 +78,17 @@ def test_report_remote_call(configuration: Configuration, result: Result, mocker
|
|||||||
report_mock.assert_called_once_with([], result)
|
report_mock.assert_called_once_with([], result)
|
||||||
|
|
||||||
|
|
||||||
|
def test_report_rss(configuration: Configuration, result: Result, mocker: MockerFixture) -> None:
|
||||||
|
"""
|
||||||
|
must instantiate rss trigger
|
||||||
|
"""
|
||||||
|
report_mock = mocker.patch("ahriman.core.report.rss.RSS.generate")
|
||||||
|
_, repository_id = configuration.check_loaded()
|
||||||
|
|
||||||
|
Report.load(repository_id, configuration, "rss").run(result, [])
|
||||||
|
report_mock.assert_called_once_with([], result)
|
||||||
|
|
||||||
|
|
||||||
def test_report_telegram(configuration: Configuration, result: Result, mocker: MockerFixture) -> None:
|
def test_report_telegram(configuration: Configuration, result: Result, mocker: MockerFixture) -> None:
|
||||||
"""
|
"""
|
||||||
must generate telegram report
|
must generate telegram report
|
||||||
|
79
tests/ahriman/core/report/test_rss.py
Normal file
79
tests/ahriman/core/report/test_rss.py
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
import pytest
|
||||||
|
|
||||||
|
from email.utils import parsedate_to_datetime
|
||||||
|
from pytest_mock import MockerFixture
|
||||||
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
|
from ahriman.core.report.rss import RSS
|
||||||
|
from ahriman.core.status import Client
|
||||||
|
from ahriman.core.utils import utcnow
|
||||||
|
from ahriman.models.event import Event, EventType
|
||||||
|
from ahriman.models.package import Package
|
||||||
|
from ahriman.models.result import Result
|
||||||
|
|
||||||
|
|
||||||
|
def test_format_datetime() -> None:
|
||||||
|
"""
|
||||||
|
must format timestamp to rfc format
|
||||||
|
"""
|
||||||
|
timestamp = utcnow().replace(microsecond=0)
|
||||||
|
assert parsedate_to_datetime(RSS.format_datetime(timestamp.timestamp())) == timestamp
|
||||||
|
|
||||||
|
|
||||||
|
def test_format_datetime_datetime() -> None:
|
||||||
|
"""
|
||||||
|
must format datetime to rfc format
|
||||||
|
"""
|
||||||
|
timestamp = utcnow().replace(microsecond=0)
|
||||||
|
assert parsedate_to_datetime(RSS.format_datetime(timestamp)) == timestamp
|
||||||
|
|
||||||
|
|
||||||
|
def test_format_datetime_empty() -> None:
|
||||||
|
"""
|
||||||
|
must generate empty string from None timestamp
|
||||||
|
"""
|
||||||
|
assert RSS.format_datetime(None) == ""
|
||||||
|
|
||||||
|
|
||||||
|
def test_sort_content() -> None:
|
||||||
|
"""
|
||||||
|
must sort content for the template
|
||||||
|
"""
|
||||||
|
assert RSS.sort_content([
|
||||||
|
{"filename": "2", "build_date": "Thu, 29 Aug 2024 16:36:55 -0000"},
|
||||||
|
{"filename": "1", "build_date": "Thu, 29 Aug 2024 16:36:54 -0000"},
|
||||||
|
{"filename": "3", "build_date": "Thu, 29 Aug 2024 16:36:56 -0000"},
|
||||||
|
]) == [
|
||||||
|
{"filename": "3", "build_date": "Thu, 29 Aug 2024 16:36:56 -0000"},
|
||||||
|
{"filename": "2", "build_date": "Thu, 29 Aug 2024 16:36:55 -0000"},
|
||||||
|
{"filename": "1", "build_date": "Thu, 29 Aug 2024 16:36:54 -0000"},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def test_content(rss: RSS, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||||
|
"""
|
||||||
|
must generate RSS content correctly
|
||||||
|
"""
|
||||||
|
client_mock = MagicMock()
|
||||||
|
client_mock.event_get.return_value = [
|
||||||
|
Event(EventType.PackageUpdated, package_ahriman.base),
|
||||||
|
Event(EventType.PackageUpdated, "random"),
|
||||||
|
Event(EventType.PackageUpdated, package_ahriman.base),
|
||||||
|
]
|
||||||
|
context_mock = mocker.patch("ahriman.core._Context.get", return_value=client_mock)
|
||||||
|
|
||||||
|
assert rss.content([package_ahriman]).success == [package_ahriman]
|
||||||
|
context_mock.assert_called_once_with(Client)
|
||||||
|
client_mock.event_get.assert_called_once_with(EventType.PackageUpdated, None, limit=rss.max_entries)
|
||||||
|
|
||||||
|
|
||||||
|
def test_generate(rss: RSS, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||||
|
"""
|
||||||
|
must generate report
|
||||||
|
"""
|
||||||
|
content_mock = mocker.patch("ahriman.core.report.rss.RSS.content", return_value=Result())
|
||||||
|
write_mock = mocker.patch("pathlib.Path.write_text")
|
||||||
|
|
||||||
|
rss.generate([package_ahriman], Result())
|
||||||
|
content_mock.assert_called_once_with([package_ahriman])
|
||||||
|
write_mock.assert_called_once_with(pytest.helpers.anyvar(int), encoding="utf8")
|
@ -489,6 +489,7 @@ def test_walk(resource_path_root: Path) -> None:
|
|||||||
resource_path_root / "web" / "templates" / "email-index.jinja2",
|
resource_path_root / "web" / "templates" / "email-index.jinja2",
|
||||||
resource_path_root / "web" / "templates" / "error.jinja2",
|
resource_path_root / "web" / "templates" / "error.jinja2",
|
||||||
resource_path_root / "web" / "templates" / "repo-index.jinja2",
|
resource_path_root / "web" / "templates" / "repo-index.jinja2",
|
||||||
|
resource_path_root / "web" / "templates" / "rss.jinja2",
|
||||||
resource_path_root / "web" / "templates" / "shell",
|
resource_path_root / "web" / "templates" / "shell",
|
||||||
resource_path_root / "web" / "templates" / "telegram-index.jinja2",
|
resource_path_root / "web" / "templates" / "telegram-index.jinja2",
|
||||||
])
|
])
|
||||||
|
@ -24,6 +24,9 @@ def test_from_option_valid() -> None:
|
|||||||
assert ReportSettings.from_option("telegram") == ReportSettings.Telegram
|
assert ReportSettings.from_option("telegram") == ReportSettings.Telegram
|
||||||
assert ReportSettings.from_option("TElegraM") == ReportSettings.Telegram
|
assert ReportSettings.from_option("TElegraM") == ReportSettings.Telegram
|
||||||
|
|
||||||
|
assert ReportSettings.from_option("rss") == ReportSettings.RSS
|
||||||
|
assert ReportSettings.from_option("RSS") == ReportSettings.RSS
|
||||||
|
|
||||||
assert ReportSettings.from_option("remote-call") == ReportSettings.RemoteCall
|
assert ReportSettings.from_option("remote-call") == ReportSettings.RemoteCall
|
||||||
assert ReportSettings.from_option("reMOte-cALL") == ReportSettings.RemoteCall
|
assert ReportSettings.from_option("reMOte-cALL") == ReportSettings.RemoteCall
|
||||||
assert ReportSettings.from_option("ahriman") == ReportSettings.RemoteCall
|
assert ReportSettings.from_option("ahriman") == ReportSettings.RemoteCall
|
||||||
|
@ -81,6 +81,13 @@ templates = ../web/templates
|
|||||||
[remote-call]
|
[remote-call]
|
||||||
manual = yes
|
manual = yes
|
||||||
|
|
||||||
|
[rss]
|
||||||
|
path =
|
||||||
|
homepage =
|
||||||
|
link_path =
|
||||||
|
template = rss.jinja2
|
||||||
|
templates = ../web/templates
|
||||||
|
|
||||||
[telegram]
|
[telegram]
|
||||||
api_key = apikey
|
api_key = apikey
|
||||||
chat_id = @ahrimantestchat
|
chat_id = @ahrimantestchat
|
||||||
|
Loading…
Reference in New Issue
Block a user