Compare commits

...

19 Commits

Author SHA1 Message Date
07b77be6b8 Release 2.13.7 2024-05-09 13:26:40 +03:00
2b33510ada fix: parse array variable from command 2024-05-09 13:21:42 +03:00
6d05389639 Release 2.13.6 2024-05-05 21:59:30 +03:00
daf9841717 fix: update integrity checksums for momentjs and daterangepicker 2024-05-05 21:17:30 +03:00
0d243a781a refactor: update code to the latest python (3.12+) 2024-05-05 21:17:30 +03:00
cf2e66a934 fix: remove debug packages together with normal ones (#124) 2024-05-05 21:17:30 +03:00
f01f35238d Release 2.13.5 2024-04-04 13:33:03 +03:00
d30d512eb6 fix: update Repo.init to the latest pacman release 2024-04-04 13:16:05 +03:00
0437f90e5a build: install base-devel package 2024-04-04 13:16:03 +03:00
3cab65855a fix: lazy web component initialization
In some cases (probably slow internet) in place initialization can cause
exception, because elements are not available yet. This commit moves
events initialization to $()
2024-04-04 13:14:17 +03:00
ecfb615f97 feat: add ability to disable debug packages distribution
The feature is implemented as supplying !debug option to makepkg when
generating package list. In this case debug packages still will be
built, however, they will not be added to the repository
2024-04-04 13:14:17 +03:00
243983ee64 docs: update docs 2024-02-10 03:12:09 +02:00
812c03d1eb Release 2.13.4 2024-02-09 17:47:01 +02:00
01597c531b fix: return only built packages from task
Since the last updates makepkg --packagelist also adds debug packages
which causes errors
2024-02-09 17:37:50 +02:00
4fec42eac8 refactor: rename packages http methods to own package
docs: update docs import
2024-01-22 02:20:11 +02:00
7574b8e5ce Release 2.13.3 2024-01-13 01:24:30 +02:00
0f2e7f45da fix: replace logo and name in title to just icon 2024-01-12 01:25:46 +02:00
5956a8720b Release 2.13.2 2024-01-08 22:48:55 +02:00
8dd4ced5e9 fix: report only unique result entries
since builder intro the triggers are called with merged result, thus it
would lead to duplicated callouts
2024-01-08 22:46:42 +02:00
68 changed files with 5183 additions and 4935 deletions

View File

@ -12,7 +12,7 @@ pacman -Syu --noconfirm
# main dependencies # main dependencies
pacman -Sy --noconfirm devtools git pyalpm python-cerberus python-inflection python-passlib python-requests python-srcinfo python-systemd sudo pacman -Sy --noconfirm devtools git pyalpm python-cerberus python-inflection python-passlib python-requests python-srcinfo python-systemd sudo
# make dependencies # make dependencies
pacman -Sy --noconfirm python-build python-flit python-installer python-tox python-wheel pacman -Sy --noconfirm --asdeps base-devel python-build python-flit python-installer python-tox python-wheel
# optional dependencies # optional dependencies
if [[ -z $MINIMAL_INSTALL ]]; then if [[ -z $MINIMAL_INSTALL ]]; then
# VCS support # VCS support
@ -32,10 +32,13 @@ mv dist/ahriman-*.tar.gz package/archlinux
chmod +777 package/archlinux # because fuck you that's why chmod +777 package/archlinux # because fuck you that's why
cd package/archlinux cd package/archlinux
sudo -u nobody -- makepkg -cf --skipchecksums --noconfirm sudo -u nobody -- makepkg -cf --skipchecksums --noconfirm
sudo -u nobody -- makepkg --packagelist | pacman -U --noconfirm - sudo -u nobody -- makepkg --packagelist | grep -v -- -debug- | pacman -U --noconfirm -
# create machine-id which is required by build tools # create machine-id which is required by build tools
systemd-machine-id-setup systemd-machine-id-setup
# remove unused dependencies
pacman -Qdtq | pacman -Rscn --noconfirm -
# initial setup command as root # initial setup command as root
[[ -z $MINIMAL_INSTALL ]] && WEB_ARGS=("--web-port" "8080") [[ -z $MINIMAL_INSTALL ]] && WEB_ARGS=("--web-port" "8080")
ahriman -a x86_64 -r "github" service-setup --packager "ahriman bot <ahriman@example.com>" "${WEB_ARGS[@]}" ahriman -a x86_64 -r "github" service-setup --packager "ahriman bot <ahriman@example.com>" "${WEB_ARGS[@]}"
@ -50,7 +53,7 @@ if [[ -z $MINIMAL_INSTALL ]]; then
WEB_PID=$! WEB_PID=$!
fi fi
# add the first package # add the first package
sudo -u ahriman -- ahriman package-add --now ahriman sudo -u ahriman -- ahriman --log-handler console package-add --now ahriman
# check if package was actually installed # check if package was actually installed
test -n "$(find "/var/lib/ahriman/repository/github/x86_64" -name "ahriman*pkg*")" test -n "$(find "/var/lib/ahriman/repository/github/x86_64" -name "ahriman*pkg*")"
# run package check # run package check

View File

@ -3,7 +3,7 @@ version: 2
build: build:
os: ubuntu-20.04 os: ubuntu-20.04
tools: tools:
python: "3.11" python: "3.12"
python: python:
install: install:

View File

@ -33,9 +33,9 @@ COPY "docker/install-aur-package.sh" "/usr/local/bin/install-aur-package"
## install package dependencies ## install package dependencies
## darcs is not installed by reasons, because it requires a lot haskell packages which dramatically increase image size ## darcs is not installed by reasons, because it requires a lot haskell packages which dramatically increase image size
RUN pacman -Sy --noconfirm --asdeps devtools git pyalpm python-cerberus python-inflection python-passlib python-requests python-srcinfo && \ RUN pacman -Sy --noconfirm --asdeps devtools git pyalpm python-cerberus python-inflection python-passlib python-requests python-srcinfo && \
pacman -Sy --noconfirm --asdeps python-build python-flit python-installer python-wheel && \ pacman -Sy --noconfirm --asdeps base-devel python-build python-flit python-installer python-wheel && \
pacman -Sy --noconfirm --asdeps breezy git mercurial python-aiohttp python-boto3 python-cryptography python-jinja python-requests-unixsocket python-systemd rsync subversion && \ pacman -Sy --noconfirm --asdeps breezy git mercurial python-aiohttp python-boto3 python-cryptography python-jinja python-requests-unixsocket python-systemd rsync subversion && \
runuser -u build -- install-aur-package python-aioauth-client python-aiohttp-apispec-git python-aiohttp-cors \ runuser -u build -- install-aur-package python-aioauth-client python-webargs python-aiohttp-apispec-git python-aiohttp-cors \
python-aiohttp-jinja2 python-aiohttp-session python-aiohttp-security python-aiohttp-jinja2 python-aiohttp-session python-aiohttp-security
## FIXME since 1.0.4 devtools requires dbus to be run, which doesn't work now in container ## FIXME since 1.0.4 devtools requires dbus to be run, which doesn't work now in container

View File

@ -6,7 +6,7 @@ for PACKAGE in "$@"; do
BUILD_DIR="$(mktemp -d)" BUILD_DIR="$(mktemp -d)"
git clone https://aur.archlinux.org/"$PACKAGE".git "$BUILD_DIR" git clone https://aur.archlinux.org/"$PACKAGE".git "$BUILD_DIR"
cd "$BUILD_DIR" cd "$BUILD_DIR"
makepkg --noconfirm --install --rmdeps --syncdeps makepkg --nocheck --noconfirm --install --rmdeps --syncdeps
cd / cd /
rm -r "$BUILD_DIR" rm -r "$BUILD_DIR"
done done

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 1.2 MiB

After

Width:  |  Height:  |  Size: 1.1 MiB

View File

@ -0,0 +1,61 @@
ahriman.web.views.v1.packages package
=====================================
Submodules
----------
ahriman.web.views.v1.packages.changes module
--------------------------------------------
.. automodule:: ahriman.web.views.v1.packages.changes
:members:
:no-undoc-members:
:show-inheritance:
ahriman.web.views.v1.packages.logs module
-----------------------------------------
.. automodule:: ahriman.web.views.v1.packages.logs
:members:
:no-undoc-members:
:show-inheritance:
ahriman.web.views.v1.packages.package module
--------------------------------------------
.. automodule:: ahriman.web.views.v1.packages.package
:members:
:no-undoc-members:
:show-inheritance:
ahriman.web.views.v1.packages.packages module
---------------------------------------------
.. automodule:: ahriman.web.views.v1.packages.packages
:members:
:no-undoc-members:
:show-inheritance:
ahriman.web.views.v1.packages.patch module
------------------------------------------
.. automodule:: ahriman.web.views.v1.packages.patch
:members:
:no-undoc-members:
:show-inheritance:
ahriman.web.views.v1.packages.patches module
--------------------------------------------
.. automodule:: ahriman.web.views.v1.packages.patches
:members:
:no-undoc-members:
:show-inheritance:
Module contents
---------------
.. automodule:: ahriman.web.views.v1.packages
:members:
:no-undoc-members:
:show-inheritance:

View File

@ -8,6 +8,7 @@ Subpackages
:maxdepth: 4 :maxdepth: 4
ahriman.web.views.v1.distributed ahriman.web.views.v1.distributed
ahriman.web.views.v1.packages
ahriman.web.views.v1.service ahriman.web.views.v1.service
ahriman.web.views.v1.status ahriman.web.views.v1.status
ahriman.web.views.v1.user ahriman.web.views.v1.user

View File

@ -4,14 +4,6 @@ ahriman.web.views.v1.status package
Submodules Submodules
---------- ----------
ahriman.web.views.v1.status.changes module
------------------------------------------
.. automodule:: ahriman.web.views.v1.status.changes
:members:
:no-undoc-members:
:show-inheritance:
ahriman.web.views.v1.status.info module ahriman.web.views.v1.status.info module
--------------------------------------- ---------------------------------------
@ -20,46 +12,6 @@ ahriman.web.views.v1.status.info module
:no-undoc-members: :no-undoc-members:
:show-inheritance: :show-inheritance:
ahriman.web.views.v1.status.logs module
---------------------------------------
.. automodule:: ahriman.web.views.v1.status.logs
:members:
:no-undoc-members:
:show-inheritance:
ahriman.web.views.v1.status.package module
------------------------------------------
.. automodule:: ahriman.web.views.v1.status.package
:members:
:no-undoc-members:
:show-inheritance:
ahriman.web.views.v1.status.packages module
-------------------------------------------
.. automodule:: ahriman.web.views.v1.status.packages
:members:
:no-undoc-members:
:show-inheritance:
ahriman.web.views.v1.status.patch module
----------------------------------------
.. automodule:: ahriman.web.views.v1.status.patch
:members:
:no-undoc-members:
:show-inheritance:
ahriman.web.views.v1.status.patches module
------------------------------------------
.. automodule:: ahriman.web.views.v1.status.patches
:members:
:no-undoc-members:
:show-inheritance:
ahriman.web.views.v1.status.repositories module ahriman.web.views.v1.status.repositories module
----------------------------------------------- -----------------------------------------------

View File

@ -0,0 +1,21 @@
ahriman.web.views.v2.packages package
=====================================
Submodules
----------
ahriman.web.views.v2.packages.logs module
-----------------------------------------
.. automodule:: ahriman.web.views.v2.packages.logs
:members:
:no-undoc-members:
:show-inheritance:
Module contents
---------------
.. automodule:: ahriman.web.views.v2.packages
:members:
:no-undoc-members:
:show-inheritance:

View File

@ -7,7 +7,7 @@ Subpackages
.. toctree:: .. toctree::
:maxdepth: 4 :maxdepth: 4
ahriman.web.views.v2.status ahriman.web.views.v2.packages
Module contents Module contents
--------------- ---------------

View File

@ -1,21 +0,0 @@
ahriman.web.views.v2.status package
===================================
Submodules
----------
ahriman.web.views.v2.status.logs module
---------------------------------------
.. automodule:: ahriman.web.views.v2.status.logs
:members:
:no-undoc-members:
:show-inheritance:
Module contents
---------------
.. automodule:: ahriman.web.views.v2.status
:members:
:no-undoc-members:
:show-inheritance:

View File

@ -81,6 +81,7 @@ Build related configuration. Group name can refer to architecture, e.g. ``build:
* ``archbuild_flags`` - additional flags passed to ``archbuild`` command, space separated list of strings, optional. * ``archbuild_flags`` - additional flags passed to ``archbuild`` command, space separated list of strings, optional.
* ``build_command`` - default build command, string, required. * ``build_command`` - default build command, string, required.
* ``ignore_packages`` - list packages to ignore during a regular update (manual update will still work), space separated list of strings, optional. * ``ignore_packages`` - list packages to ignore during a regular update (manual update will still work), space separated list of strings, optional.
* ``include_debug_packages`` - distribute debug packages, boolean, optional, default ``yes``.
* ``makepkg_flags`` - additional flags passed to ``makepkg`` command, space separated list of strings, optional. * ``makepkg_flags`` - additional flags passed to ``makepkg`` command, space separated list of strings, optional.
* ``makechrootpkg_flags`` - additional flags passed to ``makechrootpkg`` command, space separated list of strings, optional. * ``makechrootpkg_flags`` - additional flags passed to ``makechrootpkg`` command, space separated list of strings, optional.
* ``triggers`` - list of ``ahriman.core.triggers.Trigger`` class implementation (e.g. ``ahriman.core.report.ReportTrigger ahriman.core.upload.UploadTrigger``) which will be loaded and run at the end of processing, space separated list of strings, optional. You can also specify triggers by their paths, e.g. ``/usr/lib/python3.10/site-packages/ahriman/core/report/report.py.ReportTrigger``. Triggers are run in the order of definition. * ``triggers`` - list of ``ahriman.core.triggers.Trigger`` class implementation (e.g. ``ahriman.core.report.ReportTrigger ahriman.core.upload.UploadTrigger``) which will be loaded and run at the end of processing, space separated list of strings, optional. You can also specify triggers by their paths, e.g. ``/usr/lib/python3.10/site-packages/ahriman/core/report/report.py.ReportTrigger``. Triggers are run in the order of definition.

View File

@ -208,6 +208,18 @@ This command will prompt for new value of the PKGBUILD variable ``version``. You
sudo -u ahriman ahriman patch-add ahriman version version.patch sudo -u ahriman ahriman patch-add ahriman version version.patch
The command also supports arrays, but in this case you need to specify full array, e.g.
.. code-block:: shell
sudo -u ahriman ahriman patch-add ahriman depends
Post new function or variable value below. Press Ctrl-D to finish:
(python python-aiohttp)
^D
will set depends PKGBUILD variable (exactly) to array ``["python", "python-aiohttp"]``.
Alternatively you can create full-diff patches, which are calculated by using ``git diff`` from current PKGBUILD master branch: Alternatively you can create full-diff patches, which are calculated by using ``git diff`` from current PKGBUILD master branch:
#. #.

View File

@ -1,7 +1,7 @@
# Maintainer: Evgeniy Alekseev # Maintainer: Evgeniy Alekseev
pkgname='ahriman' pkgname='ahriman'
pkgver=2.13.1 pkgver=2.13.7
pkgrel=1 pkgrel=1
pkgdesc="ArcH linux ReposItory MANager" pkgdesc="ArcH linux ReposItory MANager"
arch=('any') arch=('any')

View File

@ -46,8 +46,12 @@ 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
;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 = 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.

View File

@ -15,7 +15,7 @@
<div class="container"> <div class="container">
<nav class="navbar navbar-expand-lg"> <nav class="navbar navbar-expand-lg">
<div class="navbar-brand"><img src="/static/logo.svg" width="30" height="30" alt=""> ahriman</div> <div class="navbar-brand"><img src="/static/logo.svg" width="30" height="30" alt=""></div>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#repositories-navbar-supported-content" aria-controls="repositories-navbar-supported-content" aria-expanded="false" aria-label="Toggle navigation"> <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#repositories-navbar-supported-content" aria-controls="repositories-navbar-supported-content" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span> <span class="navbar-toggler-icon"></span>
</button> </button>

View File

@ -38,10 +38,6 @@
<script> <script>
const keyImportModal = $("#key-import-modal"); const keyImportModal = $("#key-import-modal");
const keyImportForm = $("#key-import-form"); const keyImportForm = $("#key-import-form");
keyImportModal.on("hidden.bs.modal", () => {
keyImportBodyInput.text("");
keyImportForm.trigger("reset");
});
const keyImportBodyInput = $("#key-import-body-input"); const keyImportBodyInput = $("#key-import-body-input");
const keyImportCopyButton = $("#key-import-copy-button"); const keyImportCopyButton = $("#key-import-copy-button");
@ -90,4 +86,11 @@
}); });
} }
} }
$(() => {
keyImportModal.on("hidden.bs.modal", () => {
keyImportBodyInput.text("");
keyImportForm.trigger("reset");
});
});
</script> </script>

View File

@ -36,9 +36,6 @@
<script> <script>
const loginModal = $("#login-modal"); const loginModal = $("#login-modal");
const loginForm = $("#login-form"); const loginForm = $("#login-form");
loginModal.on("hidden.bs.modal", () => {
loginForm.trigger("reset");
});
const loginPasswordInput = $("#login-password"); const loginPasswordInput = $("#login-password");
const loginUsernameInput = $("#login-username"); const loginUsernameInput = $("#login-username");
@ -77,4 +74,10 @@
showHidePasswordButton.addClass("bi-eye"); showHidePasswordButton.addClass("bi-eye");
} }
} }
$(() => {
loginModal.on("hidden.bs.modal", () => {
loginForm.trigger("reset");
});
});
</script> </script>

View File

@ -43,41 +43,10 @@
<script> <script>
const packageAddModal = $("#package-add-modal"); const packageAddModal = $("#package-add-modal");
const packageAddForm = $("#package-add-form"); const packageAddForm = $("#package-add-form");
packageAddModal.on("shown.bs.modal", () => {
$(`#package-add-repository-input option[value="${repository.architecture}-${repository.repository}"]`).prop("selected", true);
});
packageAddModal.on("hidden.bs.modal", () => {
packageAddVariablesDiv.empty();
packageAddForm.trigger("reset");
});
const packageAddInput = $("#package-add-input"); const packageAddInput = $("#package-add-input");
const packageAddRepositoryInput = $("#package-add-repository-input"); const packageAddRepositoryInput = $("#package-add-repository-input");
const packageAddKnownPackagesList = $("#package-add-known-packages-dlist"); const packageAddKnownPackagesList = $("#package-add-known-packages-dlist");
packageAddInput.keyup(() => {
clearTimeout(packageAddInput.data("timeout"));
packageAddInput.data("timeout", setTimeout($.proxy(() => {
const value = packageAddInput.val();
if (value.length >= 3) {
$.ajax({
url: "/api/v1/service/search",
data: {"for": value},
type: "GET",
dataType: "json",
success: response => {
const options = response.map(pkg => {
const option = document.createElement("option");
option.value = pkg.package;
option.innerText = `${pkg.package} (${pkg.description})`;
return option;
});
packageAddKnownPackagesList.empty().append(options);
},
});
}
}, this), 500));
});
const packageAddVariablesDiv = $("#package-add-variables-div"); const packageAddVariablesDiv = $("#package-add-variables-div");
@ -156,4 +125,39 @@
doPackageAction("/api/v1/service/request", [packages], repository, onSuccess, onFailure, patches); doPackageAction("/api/v1/service/request", [packages], repository, onSuccess, onFailure, patches);
} }
} }
$(() => {
packageAddModal.on("shown.bs.modal", () => {
$(`#package-add-repository-input option[value="${repository.architecture}-${repository.repository}"]`).prop("selected", true);
});
packageAddModal.on("hidden.bs.modal", () => {
packageAddVariablesDiv.empty();
packageAddForm.trigger("reset");
});
packageAddInput.keyup(() => {
clearTimeout(packageAddInput.data("timeout"));
packageAddInput.data("timeout", setTimeout($.proxy(() => {
const value = packageAddInput.val();
if (value.length >= 3) {
$.ajax({
url: "/api/v1/service/search",
data: {"for": value},
type: "GET",
dataType: "json",
success: response => {
const options = response.map(pkg => {
const option = document.createElement("option");
option.value = pkg.package;
option.innerText = `${pkg.package} (${pkg.description})`;
return option;
});
packageAddKnownPackagesList.empty().append(options);
},
});
}
}, this), 500));
});
});
</script> </script>

View File

@ -72,26 +72,6 @@
const packageInfoModal = $("#package-info-modal"); const packageInfoModal = $("#package-info-modal");
const packageInfoModalHeader = $("#package-info-modal-header"); const packageInfoModalHeader = $("#package-info-modal-header");
const packageInfo = $("#package-info"); const packageInfo = $("#package-info");
packageInfoModal.on("hidden.bs.modal", () => {
packageInfoAurUrl.empty();
packageInfoDepends.empty();
packageInfoGroups.empty();
packageInfoLicenses.empty();
packageInfoPackager.empty();
packageInfoPackages.empty();
packageInfoUpstreamUrl.empty();
packageInfoVersion.empty();
packageInfoVariablesBlock.attr("hidden", true);
packageInfoVariablesDiv.empty();
packageInfoLogsInput.empty();
packageInfoChangesInput.empty();
packageInfoModal.trigger("reset");
hideInfoControls(true);
});
const packageInfoLogsInput = $("#package-info-logs-input"); const packageInfoLogsInput = $("#package-info-logs-input");
const packageInfoLogsCopyButton = $("#package-info-logs-copy-button"); const packageInfoLogsCopyButton = $("#package-info-logs-copy-button");
@ -309,4 +289,27 @@
if (isPackageBaseSet) packageInfoModal.modal("show"); if (isPackageBaseSet) packageInfoModal.modal("show");
} }
$(() => {
packageInfoModal.on("hidden.bs.modal", () => {
packageInfoAurUrl.empty();
packageInfoDepends.empty();
packageInfoGroups.empty();
packageInfoLicenses.empty();
packageInfoPackager.empty();
packageInfoPackages.empty();
packageInfoUpstreamUrl.empty();
packageInfoVersion.empty();
packageInfoVariablesBlock.attr("hidden", true);
packageInfoVariablesDiv.empty();
packageInfoLogsInput.empty();
packageInfoChangesInput.empty();
packageInfoModal.trigger("reset");
hideInfoControls(true);
});
});
</script> </script>

View File

@ -35,11 +35,6 @@
<script> <script>
const packageRebuildModal = $("#package-rebuild-modal"); const packageRebuildModal = $("#package-rebuild-modal");
const packageRebuildForm = $("#package-rebuild-form"); const packageRebuildForm = $("#package-rebuild-form");
packageRebuildModal.on("shown.bs.modal", () => {
$(`#package-rebuild-repository-input option[value="${repository.architecture}-${repository.repository}"]`).prop("selected", true);
});
packageRebuildModal.on("hidden.bs.modal", () => { packageRebuildForm.trigger("reset"); });
const packageRebuildDependencyInput = $("#package-rebuild-dependency-input"); const packageRebuildDependencyInput = $("#package-rebuild-dependency-input");
const packageRebuildRepositoryInput = $("#package-rebuild-repository-input"); const packageRebuildRepositoryInput = $("#package-rebuild-repository-input");
@ -54,4 +49,12 @@
doPackageAction("/api/v1/service/rebuild", [packages], repository, onSuccess, onFailure); doPackageAction("/api/v1/service/rebuild", [packages], repository, onSuccess, onFailure);
} }
} }
$(() => {
packageRebuildModal.on("shown.bs.modal", () => {
$(`#package-rebuild-repository-input option[value="${repository.architecture}-${repository.repository}"]`).prop("selected", true);
});
packageRebuildModal.on("hidden.bs.modal", () => { packageRebuildForm.trigger("reset"); });
});
</script> </script>

View File

@ -9,46 +9,8 @@
const packageInfoUpdateButton = $("#package-info-update-button"); const packageInfoUpdateButton = $("#package-info-update-button");
let repository = null; let repository = null;
$("#repositories a").on("click", (event) => {
const element = event.target;
repository = {
architecture: element.dataset.architecture,
repository: element.dataset.repository,
};
packageUpdateButton.html(`<i class="bi bi-play"></i> update<span class="d-none d-sm-inline"> ${safe(repository.repository)} (${safe(repository.architecture)})</span>`);
$(`#${element.id}`).tab("show");
reload();
});
const table = $("#packages"); const table = $("#packages");
table.on("check.bs.table uncheck.bs.table check-all.bs.table uncheck-all.bs.table", () => {
packageRemoveButton.prop("disabled", !table.bootstrapTable("getSelections").length);
});
table.on("click-row.bs.table", (self, data, row, cell) => {
if (0 === cell || "base" === cell) {
const method = data[0] === true ? "uncheckBy" : "checkBy"; // fck javascript
table.bootstrapTable(method, {field: "id", values: [data.id]});
} else showPackageInfo(data.id);
});
table.on("created-controls.bs.table", () => {
const pickerInput = $(".bootstrap-table-filter-control-timestamp");
pickerInput.daterangepicker({
autoUpdateInput: false,
locale: {
cancelLabel: "Clear",
},
});
pickerInput.on("apply.daterangepicker", (event, picker) => {
pickerInput.val(`${picker.startDate.format("YYYY-MM-DD")} - ${picker.endDate.format("YYYY-MM-DD")}`);
table.bootstrapTable("triggerSearch");
});
pickerInput.on("cancel.daterangepicker", () => {
pickerInput.val("");
table.bootstrapTable("triggerSearch");
});
});
const statusBadge = $("#badge-status"); const statusBadge = $("#badge-status");
const versionBadge = $("#badge-version"); const versionBadge = $("#badge-version");
@ -221,7 +183,46 @@
} }
$(() => { $(() => {
table.bootstrapTable({}); $("#repositories a").on("click", event => {
const element = event.target;
repository = {
architecture: element.dataset.architecture,
repository: element.dataset.repository,
};
packageUpdateButton.html(`<i class="bi bi-play"></i> update<span class="d-none d-sm-inline"> ${safe(repository.repository)} (${safe(repository.architecture)})</span>`);
$(`#${element.id}`).tab("show");
reload();
});
table.on("check.bs.table uncheck.bs.table check-all.bs.table uncheck-all.bs.table", () => {
packageRemoveButton.prop("disabled", !table.bootstrapTable("getSelections").length);
});
table.on("click-row.bs.table", (self, data, row, cell) => {
if (0 === cell || "base" === cell) {
const method = data[0] === true ? "uncheckBy" : "checkBy"; // fck javascript
table.bootstrapTable(method, {field: "id", values: [data.id]});
} else showPackageInfo(data.id);
});
table.on("created-controls.bs.table", () => {
const pickerInput = $(".bootstrap-table-filter-control-timestamp");
pickerInput.daterangepicker({
autoUpdateInput: false,
locale: {
cancelLabel: "Clear",
},
});
pickerInput.on("apply.daterangepicker", (event, picker) => {
pickerInput.val(`${picker.startDate.format("YYYY-MM-DD")} - ${picker.endDate.format("YYYY-MM-DD")}`);
table.bootstrapTable("triggerSearch");
});
pickerInput.on("cancel.daterangepicker", () => {
pickerInput.val("");
table.bootstrapTable("triggerSearch");
});
});
statusBadge.popover(); statusBadge.popover();
selectRepository(); selectRepository();
}); });

View File

@ -102,25 +102,6 @@ SigLevel = Database{% if has_repo_signed %}Required{% else %}Never{% endif %} Pa
<script> <script>
const table = $("#packages"); const table = $("#packages");
table.on("created-controls.bs.table", () => {
const pickerInput = $(".bootstrap-table-filter-control-timestamp");
pickerInput.daterangepicker({
autoUpdateInput: false,
locale: {
cancelLabel: "Clear",
},
});
pickerInput.on("apply.daterangepicker", (event, picker) => {
pickerInput.val(`${picker.startDate.format("YYYY-MM-DD")} - ${picker.endDate.format("YYYY-MM-DD")}`);
table.bootstrapTable("triggerSearch");
});
pickerInput.on("cancel.daterangepicker", () => {
pickerInput.val("");
table.bootstrapTable("triggerSearch");
});
});
const pacmanConf = $("#pacman-conf"); const pacmanConf = $("#pacman-conf");
const pacmanConfCopyButton = $("#copy-btn"); const pacmanConfCopyButton = $("#copy-btn");
@ -141,6 +122,28 @@ SigLevel = Database{% if has_repo_signed %}Required{% else %}Never{% endif %} Pa
function filterListLicenses() { function filterListLicenses() {
return extractDataList(table.bootstrapTable("getData"), "licenses"); return extractDataList(table.bootstrapTable("getData"), "licenses");
} }
$(() => {
table.on("created-controls.bs.table", () => {
const pickerInput = $(".bootstrap-table-filter-control-timestamp");
pickerInput.daterangepicker({
autoUpdateInput: false,
locale: {
cancelLabel: "Clear",
},
});
pickerInput.on("apply.daterangepicker", (event, picker) => {
pickerInput.val(`${picker.startDate.format("YYYY-MM-DD")} - ${picker.endDate.format("YYYY-MM-DD")}`);
table.bootstrapTable("triggerSearch");
});
pickerInput.on("cancel.daterangepicker", () => {
pickerInput.val("");
table.bootstrapTable("triggerSearch");
});
});
});
</script> </script>
</body> </body>

View File

@ -1,7 +1,7 @@
<script src="https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js" integrity="sha384-1H217gwSVyLSIfaLxHbE7dRb3v4mYCKbpQvzx0cegeju1MVsGrX5xXxAvs/HgeFs" crossorigin="anonymous" type="application/javascript"></script> <script src="https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js" integrity="sha384-1H217gwSVyLSIfaLxHbE7dRb3v4mYCKbpQvzx0cegeju1MVsGrX5xXxAvs/HgeFs" crossorigin="anonymous" type="application/javascript"></script>
<script src="https://cdn.jsdelivr.net/npm/moment@2.29.4/moment.min.js" integrity="sha384-8hHkOkbWN1TLWwet/jpbJ0zbx3FJDeYJgQ8dX1mRrv/vfCfHCqFSFZYCgaMML3z9" crossorigin="anonymous" type="application/javascript"></script> <script src="https://cdn.jsdelivr.net/npm/moment@2.29.4/moment.min.js" integrity="sha384-4ZTAzTbfB8H7hkWtXbyNDzDvxirmBT7EmURIvfOJ3Foympc+OD9p+bZNNENaJXgW" crossorigin="anonymous" type="application/javascript"></script>
<script src="https://cdn.jsdelivr.net/npm/daterangepicker@3.1.0/daterangepicker.min.js" integrity="sha384-u4eJN1VWrTf/FnYYQJo2kqJyVxEQf5UmWY4iUcNAoLenOEtEuCkfwc5bKvZOWBi5" crossorigin="anonymous" type="application/javascript"></script> <script src="https://cdn.jsdelivr.net/npm/daterangepicker@3.1.0/daterangepicker.min.js" integrity="sha384-Gn1XZMJEKL3ycoWq97jYAl+FP3vXQYE2ObBgzgcPMKOZdUZdF6ZuyUxbGC2bAnUT" crossorigin="anonymous" type="application/javascript"></script>
<script src="https://cdn.jsdelivr.net/npm/tableexport.jquery.plugin@1.28.0/tableExport.min.js" integrity="sha384-1Rz4Kz/y1rSWw+ZsjTcxB684XgofbO8iizY+UFIzCwFeQ+QUyhBNWBMh/STOyomI" crossorigin="anonymous" type="application/javascript"></script> <script src="https://cdn.jsdelivr.net/npm/tableexport.jquery.plugin@1.28.0/tableExport.min.js" integrity="sha384-1Rz4Kz/y1rSWw+ZsjTcxB684XgofbO8iizY+UFIzCwFeQ+QUyhBNWBMh/STOyomI" crossorigin="anonymous" type="application/javascript"></script>

View File

@ -584,7 +584,7 @@ _set_new_action() {
current_action_nargs=1 current_action_nargs=1
fi fi
current_action_args_start_index=$(( $word_index + 1 )) current_action_args_start_index=$(( $word_index + 1 - $pos_only ))
current_action_is_positional=$2 current_action_is_positional=$2
} }
@ -611,6 +611,7 @@ _shtab_ahriman() {
local prefix=_shtab_ahriman local prefix=_shtab_ahriman
local word_index=0 local word_index=0
local pos_only=0 # "--" delimeter not encountered yet
_set_parser_defaults _set_parser_defaults
word_index=1 word_index=1
@ -619,6 +620,7 @@ _shtab_ahriman() {
while [ $word_index -ne $COMP_CWORD ]; do while [ $word_index -ne $COMP_CWORD ]; do
local this_word="${COMP_WORDS[$word_index]}" local this_word="${COMP_WORDS[$word_index]}"
if [[ $pos_only = 1 || " $this_word " != " -- " ]]; then
if [[ -n $sub_parsers && " ${sub_parsers[@]} " == *" ${this_word} "* ]]; then if [[ -n $sub_parsers && " ${sub_parsers[@]} " == *" ${this_word} "* ]]; then
# valid subcommand: add it to the prefix & reset the current action # valid subcommand: add it to the prefix & reset the current action
prefix="${prefix}_$(_shtab_replace_nonword $this_word)" prefix="${prefix}_$(_shtab_replace_nonword $this_word)"
@ -635,18 +637,21 @@ _shtab_ahriman() {
if [[ "$current_action_nargs" != "*" ]] && \ if [[ "$current_action_nargs" != "*" ]] && \
[[ "$current_action_nargs" != "+" ]] && \ [[ "$current_action_nargs" != "+" ]] && \
[[ "$current_action_nargs" != *"..." ]] && \ [[ "$current_action_nargs" != *"..." ]] && \
(( $word_index + 1 - $current_action_args_start_index >= \ (( $word_index + 1 - $current_action_args_start_index - $pos_only >= \
$current_action_nargs )); then $current_action_nargs )); then
$current_action_is_positional && let "completed_positional_actions += 1" $current_action_is_positional && let "completed_positional_actions += 1"
_set_new_action "pos_${completed_positional_actions}" true _set_new_action "pos_${completed_positional_actions}" true
fi fi
else
pos_only=1 # "--" delimeter encountered
fi
let "word_index+=1" let "word_index+=1"
done done
# Generate the completions # Generate the completions
if [[ "${completing_word}" == -* ]]; then if [[ $pos_only = 0 && "${completing_word}" == -* ]]; then
# optional argument started: use option strings # optional argument started: use option strings
COMPREPLY=( $(compgen -W "${current_option_strings[*]}" -- "${completing_word}") ) COMPREPLY=( $(compgen -W "${current_option_strings[*]}" -- "${completing_word}") )
else else

View File

@ -1,4 +1,4 @@
.TH AHRIMAN "1" "2024\-01\-08" "ahriman" "Generated Python Manual" .TH AHRIMAN "1" "2024\-05\-09" "ahriman" "Generated Python Manual"
.SH NAME .SH NAME
ahriman ahriman
.SH SYNOPSIS .SH SYNOPSIS

View File

@ -736,4 +736,12 @@ _shtab_ahriman() {
typeset -A opt_args typeset -A opt_args
_shtab_ahriman "$@"
if [[ $zsh_eval_context[-1] == eval ]]; then
# eval/source/. command, register function for later
compdef _shtab_ahriman -N ahriman
else
# autoload from fpath, call function directly
_shtab_ahriman "$@"
fi

View File

@ -81,6 +81,7 @@ web = [
"aiohttp_security", "aiohttp_security",
"cryptography", "cryptography",
"requests-unixsocket", # required by unix socket support "requests-unixsocket", # required by unix socket support
"setuptools", # required by aiohttp-apispec
] ]
[tool.flit.sdist] [tool.flit.sdist]

View File

@ -17,4 +17,4 @@
# 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/>.
# #
__version__ = "2.13.1" __version__ = "2.13.7"

View File

@ -163,8 +163,8 @@ class ApplicationRepository(ApplicationProperties):
built_packages = self.repository.packages_built() built_packages = self.repository.packages_built()
if built_packages: # speedup a bit if built_packages: # speedup a bit
build_result = self.repository.process_update(built_packages, packagers) build_result = self.repository.process_update(built_packages, packagers)
self.on_result(build_result)
result.merge(build_result) result.merge(build_result)
self.on_result(result.merge(build_result))
builder = Updater.load(self.repository_id, self.configuration, self.repository) builder = Updater.load(self.repository_id, self.configuration, self.repository)
@ -173,7 +173,8 @@ class ApplicationRepository(ApplicationProperties):
for num, partition in enumerate(partitions): for num, partition in enumerate(partitions):
self.logger.info("processing chunk #%i %s", num, [package.base for package in partition]) self.logger.info("processing chunk #%i %s", num, [package.base for package in partition])
build_result = builder.update(partition, packagers, bump_pkgrel=bump_pkgrel) build_result = builder.update(partition, packagers, bump_pkgrel=bump_pkgrel)
self.on_result(result.merge(build_result)) self.on_result(build_result)
result.merge(build_result)
return result return result

View File

@ -102,8 +102,9 @@ class Patch(Handler):
patch = "".join(list(sys.stdin)) patch = "".join(list(sys.stdin))
else: else:
patch = patch_path.read_text(encoding="utf8") patch = patch_path.read_text(encoding="utf8")
patch = patch.strip() # remove spaces around the patch # remove spaces around the patch and parse to correct type
return PkgbuildPatch(variable, patch) parsed = PkgbuildPatch.parse(patch.strip())
return PkgbuildPatch(variable, parsed)
@staticmethod @staticmethod
def patch_set_create(application: Application, package_base: str, patch: PkgbuildPatch) -> None: def patch_set_create(application: Application, package_base: str, patch: PkgbuildPatch) -> None:

View File

@ -68,7 +68,7 @@ class Repo(LazyLogging):
path(Path): path to archive to add path(Path): path to archive to add
""" """
check_output( check_output(
"repo-add", *self.sign_args, "-R", str(self.repo_path), str(path), "repo-add", *self.sign_args, "--remove", str(self.repo_path), str(path),
exception=BuildError.from_process(path.name), exception=BuildError.from_process(path.name),
cwd=self.paths.repository, cwd=self.paths.repository,
logger=self.logger, logger=self.logger,
@ -78,8 +78,13 @@ class Repo(LazyLogging):
""" """
create empty repository database. It just calls add with empty arguments create empty repository database. It just calls add with empty arguments
""" """
check_output("repo-add", *self.sign_args, str(self.repo_path), # since pacman-6.1.0 repo-add doesn't create empty database in case if no packages supplied
cwd=self.paths.repository, logger=self.logger, user=self.uid) # this code creates empty files instead
if self.repo_path.exists():
return # database is already created, skip this part
self.repo_path.touch(exist_ok=True)
(self.paths.repository / f"{self.name}.db").symlink_to(self.repo_path)
def remove(self, package: str, filename: Path) -> None: def remove(self, package: str, filename: Path) -> None:
""" """

View File

@ -69,7 +69,8 @@ class OAuth(Mapping):
Returns: Returns:
str: login control as html code to insert str: login control as html code to insert
""" """
return f"""<a class="nav-link" href="/api/v1/login" title="login via OAuth2"><i class="bi bi-{self.icon}"></i> login</a>""" return f"""<a class="nav-link" href="/api/v1/login" title="login via OAuth2"><i class="bi bi-{
self.icon}"></i> login</a>"""
@staticmethod @staticmethod
def get_provider(name: str) -> type[aioauth_client.OAuth2Client]: def get_provider(name: str) -> type[aioauth_client.OAuth2Client]:

View File

@ -38,6 +38,7 @@ class Task(LazyLogging):
archbuild_flags(list[str]): command flags for archbuild command archbuild_flags(list[str]): command flags for archbuild command
architecture(str): repository architecture architecture(str): repository architecture
build_command(str): build command build_command(str): build command
include_debug_packages(bool): whether to include debug packages or not
makechrootpkg_flags(list[str]): command flags for makechrootpkg command makechrootpkg_flags(list[str]): command flags for makechrootpkg command
makepkg_flags(list[str]): command flags for makepkg command makepkg_flags(list[str]): command flags for makepkg command
package(Package): package definitions package(Package): package definitions
@ -63,6 +64,7 @@ class Task(LazyLogging):
self.archbuild_flags = configuration.getlist("build", "archbuild_flags", fallback=[]) self.archbuild_flags = configuration.getlist("build", "archbuild_flags", fallback=[])
self.build_command = configuration.get("build", "build_command") self.build_command = configuration.get("build", "build_command")
self.include_debug_packages = configuration.getboolean("build", "include_debug_packages", fallback=True)
self.makepkg_flags = configuration.getlist("build", "makepkg_flags", fallback=[]) self.makepkg_flags = configuration.getlist("build", "makepkg_flags", fallback=[])
self.makechrootpkg_flags = configuration.getlist("build", "makechrootpkg_flags", fallback=[]) self.makechrootpkg_flags = configuration.getlist("build", "makechrootpkg_flags", fallback=[])
@ -99,15 +101,20 @@ class Task(LazyLogging):
environment=environment, environment=environment,
) )
# well it is not actually correct, but we can deal with it package_list_command = ["makepkg", "--packagelist"]
if not self.include_debug_packages:
package_list_command.append("OPTIONS=(!debug)") # disable debug flag manually
packages = check_output( packages = check_output(
"makepkg", "--packagelist", *package_list_command,
exception=BuildError.from_process(self.package.base), exception=BuildError.from_process(self.package.base),
cwd=sources_dir, cwd=sources_dir,
logger=self.logger, logger=self.logger,
environment=environment, environment=environment,
).splitlines() ).splitlines()
return [Path(package) for package in packages] # some dirty magic here
# the filter is applied in order to make sure that result will only contain packages which were actually built
# e.g. in some cases packagelist command produces debug packages which were not actually built
return list(filter(lambda path: path.is_file(), map(Path, packages)))
def init(self, sources_dir: Path, database: SQLite, local_version: str | None) -> str | None: def init(self, sources_dir: Path, database: SQLite, local_version: str | None) -> str | None:
""" """

View File

@ -176,6 +176,10 @@ CONFIGURATION_SCHEMA: ConfigurationSchema = {
"empty": False, "empty": False,
}, },
}, },
"include_debug_packages": {
"type": "boolean",
"coerce": "boolean",
},
"makepkg_flags": { "makepkg_flags": {
"type": "list", "type": "list",
"coerce": "list", "coerce": "list",

View File

@ -117,7 +117,8 @@ class Executor(PackageInfo, Cleaner):
# build package list based on user input # build package list based on user input
result = Result() result = Result()
requested = set(packages) packages = set(packages) # remove duplicates
requested = packages | {f"{package}-debug" for package in packages} # append debug packages
for local in self.packages(): for local in self.packages():
if local.base in packages or all(package in requested for package in local.packages): if local.base in packages or all(package in requested for package in local.packages):
packages_to_remove.update({ packages_to_remove.update({
@ -136,7 +137,7 @@ class Executor(PackageInfo, Cleaner):
# check for packages which were requested to remove, but weren't found locally # check for packages which were requested to remove, but weren't found locally
# it might happen for example, if there were no success build before # it might happen for example, if there were no success build before
for unknown in requested: for unknown in packages:
if unknown in packages_to_remove or unknown in bases_to_remove: if unknown in packages_to_remove or unknown in bases_to_remove:
continue continue
bases_to_remove.append(unknown) bases_to_remove.append(unknown)

View File

@ -183,11 +183,12 @@ post_install() {{
Returns: Returns:
str: package() function for PKGBUILD str: package() function for PKGBUILD
""" """
# somehow autopep thinks that construction inside contains valid python code and reformats it
return f"""{{ return f"""{{
install -Dm644 "{Path("$srcdir") / f"{self.name}.gpg"}" "{Path("$pkgdir") / "usr" / "share" / "pacman" / "keyrings" / f"{self.name}.gpg"}" install -Dm644 "{Path("$srcdir") / f"{self.name}.gpg"}" "{Path("$pkgdir") / "usr" / "share" / "pacman" / "keyrings" / f"{self.name}.gpg"}"
install -Dm644 "{Path("$srcdir") / f"{self.name}-revoked"}" "{Path("$pkgdir") / "usr" / "share" / "pacman" / "keyrings" / f"{self.name}-revoked"}" install -Dm644 "{Path("$srcdir") / f"{self.name}-revoked"}" "{Path("$pkgdir") / "usr" / "share" / "pacman" / "keyrings" / f"{self.name}-revoked"}"
install -Dm644 "{Path("$srcdir") / f"{self.name}-trusted"}" "{Path("$pkgdir") / "usr" / "share" / "pacman" / "keyrings" / f"{self.name}-trusted"}" install -Dm644 "{Path("$srcdir") / f"{self.name}-trusted"}" "{Path("$pkgdir") / "usr" / "share" / "pacman" / "keyrings" / f"{self.name}-trusted"}"
}}""" }}""" # nopep8
def sources(self) -> dict[str, Callable[[Path], None]]: def sources(self) -> dict[str, Callable[[Path], None]]:
""" """

View File

@ -162,7 +162,8 @@ class GitHub(Upload, HttpUpload):
Returns: Returns:
dict[str, Any] | None: GitHub API release object if release found and None otherwise dict[str, Any] | None: GitHub API release object if release found and None otherwise
""" """
url = f"https://api.github.com/repos/{self.github_owner}/{self.github_repository}/releases/tags/{self.github_release_tag}" url = f"https://api.github.com/repos/{self.github_owner}/{
self.github_repository}/releases/tags/{self.github_release_tag}"
try: try:
response = self.make_request("GET", url) response = self.make_request("GET", url)
release: dict[str, Any] = response.json() release: dict[str, Any] = response.json()

View File

@ -56,7 +56,6 @@ __all__ = [
"srcinfo_property", "srcinfo_property",
"srcinfo_property_list", "srcinfo_property_list",
"trim_package", "trim_package",
"unquote",
"utcnow", "utcnow",
"walk", "walk",
] ]
@ -349,7 +348,7 @@ def pretty_datetime(timestamp: datetime.datetime | float | int | None) -> str:
if timestamp is None: if timestamp is None:
return "" return ""
if isinstance(timestamp, (int, float)): if isinstance(timestamp, (int, float)):
timestamp = datetime.datetime.utcfromtimestamp(timestamp) timestamp = datetime.datetime.fromtimestamp(timestamp, datetime.UTC)
return timestamp.strftime("%Y-%m-%d %H:%M:%S") return timestamp.strftime("%Y-%m-%d %H:%M:%S")
@ -466,38 +465,6 @@ def trim_package(package_name: str) -> str:
return package_name return package_name
def unquote(source: str) -> str:
"""
like :func:`shlex.quote()`, but opposite
Args:
source(str): source string to remove quotes
Returns:
str: string with quotes removed
Raises:
ValueError: if no closing quotation
"""
def generator() -> Generator[str, None, None]:
token = None
for char in source:
if token is not None:
if char == token:
token = None # closed quote
else:
yield char # character inside quotes
elif char in ("'", "\""):
token = char # first quote found
else:
yield char # normal character
if token is not None:
raise ValueError("No closing quotation")
return "".join(generator())
def utcnow() -> datetime.datetime: def utcnow() -> datetime.datetime:
""" """
get current time get current time
@ -505,7 +472,7 @@ def utcnow() -> datetime.datetime:
Returns: Returns:
datetime.datetime: current time in UTC datetime.datetime: current time in UTC
""" """
return datetime.datetime.utcnow() return datetime.datetime.now(datetime.UTC)
def walk(directory_path: Path) -> Generator[Path, None, None]: def walk(directory_path: Path) -> Generator[Path, None, None]:

View File

@ -137,8 +137,8 @@ class AURPackage:
description=package.desc, description=package.desc,
num_votes=0, num_votes=0,
popularity=0.0, popularity=0.0,
first_submitted=datetime.datetime.utcfromtimestamp(0), first_submitted=datetime.datetime.fromtimestamp(0, datetime.UTC),
last_modified=datetime.datetime.utcfromtimestamp(package.builddate), last_modified=datetime.datetime.fromtimestamp(package.builddate, datetime.UTC),
url_path="", url_path="",
url=package.url, url=package.url,
out_of_date=None, out_of_date=None,
@ -175,13 +175,11 @@ class AURPackage:
description=dump["pkgdesc"], description=dump["pkgdesc"],
num_votes=0, num_votes=0,
popularity=0.0, popularity=0.0,
first_submitted=datetime.datetime.utcfromtimestamp(0), first_submitted=datetime.datetime.fromtimestamp(0, datetime.UTC),
last_modified=datetime.datetime.strptime(dump["last_update"], "%Y-%m-%dT%H:%M:%S.%fZ"), last_modified=datetime.datetime.fromisoformat(dump["last_update"]),
url_path="", url_path="",
url=dump["url"], url=dump["url"],
out_of_date=datetime.datetime.strptime( out_of_date=datetime.datetime.fromisoformat(dump["flag_date"]) if dump.get("flag_date") else None,
dump["flag_date"],
"%Y-%m-%dT%H:%M:%S.%fZ") if dump["flag_date"] is not None else None,
maintainer=next(iter(dump["maintainers"]), None), maintainer=next(iter(dump["maintainers"]), None),
submitter=None, submitter=None,
repository=dump["repo"], repository=dump["repo"],
@ -208,9 +206,9 @@ class AURPackage:
""" """
identity_mapper: Callable[[Any], Any] = lambda value: value identity_mapper: Callable[[Any], Any] = lambda value: value
value_mapper: dict[str, Callable[[Any], Any]] = { value_mapper: dict[str, Callable[[Any], Any]] = {
"out_of_date": lambda value: datetime.datetime.utcfromtimestamp(value) if value is not None else None, "out_of_date": lambda value: datetime.datetime.fromtimestamp(value, datetime.UTC) if value is not None else None,
"first_submitted": datetime.datetime.utcfromtimestamp, "first_submitted": lambda value: datetime.datetime.fromtimestamp(value, datetime.UTC),
"last_modified": datetime.datetime.utcfromtimestamp, "last_modified": lambda value: datetime.datetime.fromtimestamp(value, datetime.UTC),
} }
result: dict[str, Any] = {} result: dict[str, Any] = {}

View File

@ -21,9 +21,9 @@ import shlex
from dataclasses import dataclass from dataclasses import dataclass
from pathlib import Path from pathlib import Path
from typing import Any, Self from typing import Any, Generator, Self
from ahriman.core.util import dataclass_view, unquote from ahriman.core.util import dataclass_view
@dataclass(frozen=True) @dataclass(frozen=True)
@ -81,14 +81,57 @@ class PkgbuildPatch:
Self: package properties Self: package properties
""" """
key, *value_parts = variable.split("=", maxsplit=1) key, *value_parts = variable.split("=", maxsplit=1)
raw_value = next(iter(value_parts), "") # extract raw value raw_value = next(iter(value_parts), "") # extract raw value
if raw_value.startswith("(") and raw_value.endswith(")"): return cls(key, cls.parse(raw_value))
value: str | list[str] = shlex.split(raw_value[1:-1]) # arrays for poor
else:
value = unquote(raw_value)
return cls(key, value) @staticmethod
def parse(source: str) -> str | list[str]:
"""
parse string value to the PKGBUILD patch value. This method simply takes string, tries to identify it as array
or just string and return the respective value. Functions should be processed correctly, however, not guaranteed
Args:
source(str): source string to parse
Returns:
str | list[str]: parsed value either string or list of strings
"""
if source.startswith("(") and source.endswith(")"):
return shlex.split(source[1:-1]) # arrays for poor
return PkgbuildPatch.unquote(source)
@staticmethod
def unquote(source: str) -> str:
"""
like :func:`shlex.quote()`, but opposite
Args:
source(str): source string to remove quotes
Returns:
str: string with quotes removed
Raises:
ValueError: if no closing quotation
"""
def generator() -> Generator[str, None, None]:
token = None
for char in source:
if token is not None:
if char == token:
token = None # closed quote
else:
yield char # character inside quotes
elif char in ("'", "\""):
token = char # first quote found
else:
yield char # normal character
if token is not None:
raise ValueError("No closing quotation")
return "".join(generator())
def serialize(self) -> str: def serialize(self) -> str:
""" """

View File

@ -0,0 +1,19 @@
#
# 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/>.
#

View File

@ -122,11 +122,10 @@ def test_patch_create_from_function(mocker: MockerFixture) -> None:
""" """
must create function patch from file must create function patch from file
""" """
path = Path("local")
patch = PkgbuildPatch("version", "patch") patch = PkgbuildPatch("version", "patch")
read_mock = mocker.patch("pathlib.Path.read_text", return_value=patch.value) read_mock = mocker.patch("pathlib.Path.read_text", return_value=patch.value)
assert Patch.patch_create_from_function(patch.key, path) == patch assert Patch.patch_create_from_function(patch.key, Path("local")) == patch
read_mock.assert_called_once_with(encoding="utf8") read_mock.assert_called_once_with(encoding="utf8")
@ -148,6 +147,15 @@ def test_patch_create_from_function_strip(mocker: MockerFixture) -> None:
assert Patch.patch_create_from_function(patch.key, None) == patch assert Patch.patch_create_from_function(patch.key, None) == patch
def test_patch_create_from_function_array(mocker: MockerFixture) -> None:
"""
must correctly read array variable
"""
patch = PkgbuildPatch("version", ["array", "patch"])
mocker.patch("pathlib.Path.read_text", return_value=f"({" ".join(patch.value)})")
assert Patch.patch_create_from_function(patch.key, Path("local")) == patch
def test_patch_set_list(application: Application, mocker: MockerFixture) -> None: def test_patch_set_list(application: Application, mocker: MockerFixture) -> None:
""" """
must list available patches for the command must list available patches for the command

View File

@ -133,8 +133,8 @@ def aur_package_ahriman() -> AURPackage:
description="ArcH linux ReposItory MANager", description="ArcH linux ReposItory MANager",
num_votes=0, num_votes=0,
popularity=0, popularity=0,
first_submitted=datetime.datetime.utcfromtimestamp(1618008285), first_submitted=datetime.datetime.fromtimestamp(1618008285, datetime.UTC),
last_modified=datetime.datetime.utcfromtimestamp(1673826351), last_modified=datetime.datetime.fromtimestamp(1673826351, datetime.UTC),
url_path="/cgit/aur.git/snapshot/ahriman.tar.gz", url_path="/cgit/aur.git/snapshot/ahriman.tar.gz",
url="https://github.com/arcan1s/ahriman", url="https://github.com/arcan1s/ahriman",
out_of_date=None, out_of_date=None,
@ -200,8 +200,8 @@ def aur_package_akonadi() -> AURPackage:
description="PIM layer, which provides an asynchronous API to access all kind of PIM data", description="PIM layer, which provides an asynchronous API to access all kind of PIM data",
num_votes=0, num_votes=0,
popularity=0.0, popularity=0.0,
first_submitted=datetime.datetime.utcfromtimestamp(0), first_submitted=datetime.datetime.fromtimestamp(0, datetime.UTC),
last_modified=datetime.datetime.utcfromtimestamp(1646555990.610), last_modified=datetime.datetime.fromtimestamp(1646555990.610, datetime.UTC),
url_path="", url_path="",
url="https://kontact.kde.org", url="https://kontact.kde.org",
out_of_date=None, out_of_date=None,

View File

@ -26,13 +26,28 @@ def test_repo_add(repo: Repo, mocker: MockerFixture) -> None:
def test_repo_init(repo: Repo, mocker: MockerFixture) -> None: def test_repo_init(repo: Repo, mocker: MockerFixture) -> None:
""" """
must call repo-add with empty package list on repo initializing must create empty database files
""" """
check_output_mock = mocker.patch("ahriman.core.alpm.repo.check_output") mocker.patch("pathlib.Path.exists", return_value=False)
touch_mock = mocker.patch("pathlib.Path.touch")
symlink_mock = mocker.patch("pathlib.Path.symlink_to")
repo.init() repo.init()
check_output_mock.assert_called_once() # it will be checked later touch_mock.assert_called_once_with(exist_ok=True)
assert check_output_mock.call_args[0][0] == "repo-add" symlink_mock.assert_called_once_with(repo.repo_path)
def test_repo_init_skip(repo: Repo, mocker: MockerFixture) -> None:
"""
must do not create files if database already exists
"""
mocker.patch("pathlib.Path.exists", return_value=True)
touch_mock = mocker.patch("pathlib.Path.touch")
symlink_mock = mocker.patch("pathlib.Path.symlink_to")
repo.init()
touch_mock.assert_not_called()
symlink_mock.assert_not_called()
def test_repo_remove(repo: Repo, mocker: MockerFixture) -> None: def test_repo_remove(repo: Repo, mocker: MockerFixture) -> None:

View File

@ -1,5 +1,8 @@
import pytest
from pathlib import Path from pathlib import Path
from pytest_mock import MockerFixture from pytest_mock import MockerFixture
from unittest.mock import call as MockCall
from ahriman.core.build_tools.task import Task from ahriman.core.build_tools.task import Task
from ahriman.core.database import SQLite from ahriman.core.database import SQLite
@ -9,9 +12,83 @@ def test_build(task_ahriman: Task, mocker: MockerFixture) -> None:
""" """
must build package must build package
""" """
local = Path("local")
check_output_mock = mocker.patch("ahriman.core.build_tools.task.check_output") check_output_mock = mocker.patch("ahriman.core.build_tools.task.check_output")
task_ahriman.build(Path("ahriman"))
check_output_mock.assert_called() task_ahriman.build(local)
check_output_mock.assert_has_calls([
MockCall(
"extra-x86_64-build", "-r", str(task_ahriman.paths.chroot), "--", "--", "--skippgpcheck",
exception=pytest.helpers.anyvar(int),
cwd=local,
logger=task_ahriman.logger,
user=task_ahriman.uid,
environment={},
),
MockCall(
"makepkg", "--packagelist",
exception=pytest.helpers.anyvar(int),
cwd=local,
logger=task_ahriman.logger,
environment={},
),
])
def test_build_environment(task_ahriman: Task, mocker: MockerFixture) -> None:
"""
must build package with environment variables set
"""
local = Path("local")
check_output_mock = mocker.patch("ahriman.core.build_tools.task.check_output")
environment = {"variable": "value"}
task_ahriman.build(local, **environment, empty=None)
check_output_mock.assert_has_calls([
MockCall(
"extra-x86_64-build", "-r", str(task_ahriman.paths.chroot), "--", "--", "--skippgpcheck",
exception=pytest.helpers.anyvar(int),
cwd=local,
logger=task_ahriman.logger,
user=task_ahriman.uid,
environment=environment,
),
MockCall(
"makepkg", "--packagelist",
exception=pytest.helpers.anyvar(int),
cwd=local,
logger=task_ahriman.logger,
environment=environment,
),
])
def test_build_no_debug(task_ahriman: Task, mocker: MockerFixture) -> None:
"""
must filter debug packages from result
"""
local = Path("local")
check_output_mock = mocker.patch("ahriman.core.build_tools.task.check_output")
task_ahriman.include_debug_packages = False
task_ahriman.build(local)
check_output_mock.assert_has_calls([
MockCall(
"extra-x86_64-build", "-r", str(task_ahriman.paths.chroot), "--", "--", "--skippgpcheck",
exception=pytest.helpers.anyvar(int),
cwd=local,
logger=task_ahriman.logger,
user=task_ahriman.uid,
environment={},
),
MockCall(
"makepkg", "--packagelist", "OPTIONS=(!debug)",
exception=pytest.helpers.anyvar(int),
cwd=local,
logger=task_ahriman.logger,
environment={},
),
])
def test_init(task_ahriman: Task, database: SQLite, mocker: MockerFixture) -> None: def test_init(task_ahriman: Task, database: SQLite, mocker: MockerFixture) -> None:

View File

@ -89,6 +89,28 @@ def test_process_remove_base(executor: Executor, package_ahriman: Package, mocke
commit_sha_mock.assert_called_once_with(package_ahriman.base) commit_sha_mock.assert_called_once_with(package_ahriman.base)
def test_process_remove_with_debug(executor: Executor, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must run remove debug packages too
"""
package_ahriman.packages = {
package_ahriman.base: package_ahriman.packages[package_ahriman.base],
f"{package_ahriman.base}-debug": package_ahriman.packages[package_ahriman.base],
}
mocker.patch("ahriman.core.repository.executor.Executor.packages", return_value=[package_ahriman])
mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_clear")
mocker.patch("ahriman.core.database.SQLite.package_clear")
mocker.patch("ahriman.core.status.client.Client.package_remove")
repo_remove_mock = mocker.patch("ahriman.core.alpm.repo.Repo.remove")
executor.process_remove([package_ahriman.base])
# must remove via alpm wrapper
repo_remove_mock.assert_has_calls([
MockCall(package_ahriman.base, package_ahriman.packages[package_ahriman.base].filepath),
MockCall(f"{package_ahriman.base}-debug", package_ahriman.packages[package_ahriman.base].filepath),
])
def test_process_remove_base_multiple(executor: Executor, package_python_schedule: Package, def test_process_remove_base_multiple(executor: Executor, package_python_schedule: Package,
mocker: MockerFixture) -> None: mocker: MockerFixture) -> None:
""" """

View File

@ -6,7 +6,6 @@ from pytest_mock import MockerFixture
from unittest.mock import MagicMock, call as MockCall from unittest.mock import MagicMock, call as MockCall
from ahriman.core.support.pkgbuild.pkgbuild_generator import PkgbuildGenerator from ahriman.core.support.pkgbuild.pkgbuild_generator import PkgbuildGenerator
from ahriman.core.util import utcnow
from ahriman.models.pkgbuild_patch import PkgbuildPatch from ahriman.models.pkgbuild_patch import PkgbuildPatch
@ -38,7 +37,7 @@ def test_pkgver(pkgbuild_generator: PkgbuildGenerator, mocker: MockerFixture) ->
must implement default version as current date must implement default version as current date
""" """
mocker.patch("ahriman.core.support.pkgbuild.pkgbuild_generator.utcnow", return_value=datetime.datetime(2002, 3, 11)) mocker.patch("ahriman.core.support.pkgbuild.pkgbuild_generator.utcnow", return_value=datetime.datetime(2002, 3, 11))
assert pkgbuild_generator.pkgver == utcnow().strftime("20020311") assert pkgbuild_generator.pkgver == "20020311"
def test_url(pkgbuild_generator: PkgbuildGenerator) -> None: def test_url(pkgbuild_generator: PkgbuildGenerator) -> None:

View File

@ -12,7 +12,7 @@ from unittest.mock import call as MockCall
from ahriman.core.exceptions import BuildError, CalledProcessError, OptionError, UnsafeRunError from ahriman.core.exceptions import BuildError, CalledProcessError, OptionError, UnsafeRunError
from ahriman.core.util import check_output, check_user, dataclass_view, enum_values, extract_user, filter_json, \ from ahriman.core.util import check_output, check_user, dataclass_view, enum_values, extract_user, filter_json, \
full_version, minmax, package_like, parse_version, partition, pretty_datetime, pretty_size, safe_filename, \ full_version, minmax, package_like, parse_version, partition, pretty_datetime, pretty_size, safe_filename, \
srcinfo_property, srcinfo_property_list, trim_package, unquote, utcnow, walk srcinfo_property, srcinfo_property_list, trim_package, utcnow, walk
from ahriman.models.package import Package from ahriman.models.package import Package
from ahriman.models.package_source import PackageSource from ahriman.models.package_source import PackageSource
from ahriman.models.repository_id import RepositoryId from ahriman.models.repository_id import RepositoryId
@ -445,26 +445,6 @@ def test_trim_package() -> None:
assert trim_package("package: a description") == "package" assert trim_package("package: a description") == "package"
def test_unquote() -> None:
"""
must remove quotation marks
"""
for source in (
"abc",
"ab'c",
"ab\"c",
):
assert unquote(shlex.quote(source)) == source
def test_unquote_error() -> None:
"""
must raise value error on invalid quotation
"""
with pytest.raises(ValueError):
unquote("ab'c")
def test_utcnow() -> None: def test_utcnow() -> None:
""" """
must generate correct timestamp must generate correct timestamp

View File

@ -1,3 +1,6 @@
import pytest
import shlex
from pathlib import Path from pathlib import Path
from pytest_mock import MockerFixture from pytest_mock import MockerFixture
from unittest.mock import MagicMock, call from unittest.mock import MagicMock, call
@ -48,6 +51,35 @@ def test_from_env() -> None:
assert PkgbuildPatch.from_env("KEY") == PkgbuildPatch("KEY", "") assert PkgbuildPatch.from_env("KEY") == PkgbuildPatch("KEY", "")
def test_parse() -> None:
"""
must parse string correctly
"""
assert PkgbuildPatch.parse("VALUE") == "VALUE"
assert PkgbuildPatch.parse("(ARRAY VALUE)") == ["ARRAY", "VALUE"]
assert PkgbuildPatch.parse("""("QU'OUTED" ARRAY VALUE)""") == ["QU'OUTED", "ARRAY", "VALUE"]
def test_unquote() -> None:
"""
must remove quotation marks
"""
for source in (
"abc",
"ab'c",
"ab\"c",
):
assert PkgbuildPatch.unquote(shlex.quote(source)) == source
def test_unquote_error() -> None:
"""
must raise value error on invalid quotation
"""
with pytest.raises(ValueError):
PkgbuildPatch.unquote("ab'c")
def test_serialize() -> None: def test_serialize() -> None:
""" """
must correctly serialize string values must correctly serialize string values

View File

@ -6,7 +6,7 @@ from ahriman.models.build_status import BuildStatusEnum
from ahriman.models.changes import Changes from ahriman.models.changes import Changes
from ahriman.models.package import Package from ahriman.models.package import Package
from ahriman.models.user_access import UserAccess from ahriman.models.user_access import UserAccess
from ahriman.web.views.v1.status.changes import ChangesView from ahriman.web.views.v1.packages.changes import ChangesView
async def test_get_permission() -> None: async def test_get_permission() -> None:

View File

@ -5,7 +5,7 @@ from aiohttp.test_utils import TestClient
from ahriman.models.build_status import BuildStatusEnum from ahriman.models.build_status import BuildStatusEnum
from ahriman.models.package import Package from ahriman.models.package import Package
from ahriman.models.user_access import UserAccess from ahriman.models.user_access import UserAccess
from ahriman.web.views.v1.status.logs import LogsView from ahriman.web.views.v1.packages.logs import LogsView
async def test_get_permission() -> None: async def test_get_permission() -> None:

View File

@ -6,7 +6,7 @@ from ahriman.models.build_status import BuildStatus, BuildStatusEnum
from ahriman.models.package import Package from ahriman.models.package import Package
from ahriman.models.repository_id import RepositoryId from ahriman.models.repository_id import RepositoryId
from ahriman.models.user_access import UserAccess from ahriman.models.user_access import UserAccess
from ahriman.web.views.v1.status.package import PackageView from ahriman.web.views.v1.packages.package import PackageView
async def test_get_permission() -> None: async def test_get_permission() -> None:

View File

@ -6,7 +6,7 @@ from pytest_mock import MockerFixture
from ahriman.models.build_status import BuildStatusEnum from ahriman.models.build_status import BuildStatusEnum
from ahriman.models.package import Package from ahriman.models.package import Package
from ahriman.models.user_access import UserAccess from ahriman.models.user_access import UserAccess
from ahriman.web.views.v1.status.packages import (PackagesView) from ahriman.web.views.v1.packages.packages import (PackagesView)
async def test_get_permission() -> None: async def test_get_permission() -> None:

View File

@ -6,7 +6,7 @@ from ahriman.models.build_status import BuildStatusEnum
from ahriman.models.package import Package from ahriman.models.package import Package
from ahriman.models.pkgbuild_patch import PkgbuildPatch from ahriman.models.pkgbuild_patch import PkgbuildPatch
from ahriman.models.user_access import UserAccess from ahriman.models.user_access import UserAccess
from ahriman.web.views.v1.status.patch import PatchView from ahriman.web.views.v1.packages.patch import PatchView
async def test_get_permission() -> None: async def test_get_permission() -> None:

View File

@ -6,7 +6,7 @@ from ahriman.models.build_status import BuildStatusEnum
from ahriman.models.package import Package from ahriman.models.package import Package
from ahriman.models.pkgbuild_patch import PkgbuildPatch from ahriman.models.pkgbuild_patch import PkgbuildPatch
from ahriman.models.user_access import UserAccess from ahriman.models.user_access import UserAccess
from ahriman.web.views.v1.status.patches import PatchesView from ahriman.web.views.v1.packages.patches import PatchesView
async def test_get_permission() -> None: async def test_get_permission() -> None:

View File

@ -5,7 +5,7 @@ from aiohttp.test_utils import TestClient
from ahriman.models.build_status import BuildStatusEnum from ahriman.models.build_status import BuildStatusEnum
from ahriman.models.package import Package from ahriman.models.package import Package
from ahriman.models.user_access import UserAccess from ahriman.models.user_access import UserAccess
from ahriman.web.views.v2.status.logs import LogsView from ahriman.web.views.v2.packages.logs import LogsView
async def test_get_permission() -> None: async def test_get_permission() -> None: