Compare commits

...

169 Commits

Author SHA1 Message Date
arcanis e2e013a33b straight forward conftest 2026-07-13 16:33:45 +03:00
arcanis da0853b6f1 generic fixtures 2026-07-13 13:01:54 +03:00
arcanis 7db38ff9e4 reorder tests 2026-07-13 13:00:13 +03:00
arcanis 92ac036f78 migrate to hatch 2026-07-11 12:24:53 +03:00
arcanis aef7fa5f93 build: update frontend dependencies 2026-07-06 16:16:31 +03:00
arcanis c412b08135 fix: remove staleTime defaults for query client 2026-07-06 15:33:20 +03:00
arcanis a3b6372c11 fix: override head method for sse endpoint to make it actually working 2026-07-06 14:44:29 +03:00
arcanis 78288befb8 build: use python 3.13 lowerbound 2026-07-06 13:56:02 +03:00
arcanis 49cd3d43c8 fix: handle errors in sse stream 2026-06-09 01:01:08 +03:00
arcanis d9b52806c0 fix: clear subscriber map on shutdown
Even though it is not a case in the application, the interface could be
used externally
2026-06-07 11:58:04 +03:00
arcanis 774db2d780 type: fix typing ignore with the last typing update 2026-05-31 20:04:25 +03:00
arcanis 68afda4c71 feat: allow readonly events with read permission 2026-05-26 20:51:06 +03:00
arcanis fa9fa73078 docs: bump python version to 2.13 2026-05-08 10:25:24 +03:00
arcanis 3e1e24cb50 feat: SSE support (#162)
* event bus implementation

* update tests

* docs update

* review fixes

* update configs

* fix typo

* frontend changes

* install missing pacakge

* queue processing simplification
2026-05-08 10:20:03 +03:00
arcanis 18fe38c30b build: use build_date argument for docker image instead of guessing from pacman root 2026-05-05 13:45:42 +03:00
arcanis 6ee8b26bd5 feat: show aur url in interface 2026-04-22 06:56:28 +03:00
arcanis fce49f22c9 type: fix mypy warnings in updated version 2026-04-03 13:36:41 +03:00
arcanis af8e2c9e9b build: update rtd.io image 2026-03-30 19:20:03 +03:00
arcanis 1c312bb528 feat: add error boundary 2026-03-24 01:17:42 +02:00
arcanis e39194e9f6 feat: etag support 2026-03-23 23:58:56 +02:00
arcanis 21cc029c18 refactor: use usedforsecurity flag for md5 calculations 2026-03-23 23:07:31 +02:00
arcanis 40671b99d5 feat: allow to configure cors 2026-03-23 16:39:07 +02:00
arcanis 3ad2c494af docs: update docstrings 2026-03-22 17:23:16 +02:00
arcanis 34014d1cdd build: add more rules for typescript 2026-03-22 17:23:16 +02:00
arcanis 93ed2b864b docs: describe rollback procedure in docs 2026-03-22 17:23:16 +02:00
arcanis cca931ccd0 fix: handle errors on pkgbuild reading 2026-03-22 13:10:09 +02:00
arcanis 5e090cebdb refactor: reorder arguments in web ui 2026-03-22 04:03:41 +02:00
arcanis d7984c12f0 feat: package rollback support (#161)
* implement support of rollback handler

* react interface for the rollback
2026-03-22 00:36:48 +02:00
arcanis 96684d7ddd refactor: move package archive lockup to package info trait 2026-03-18 18:48:27 +02:00
arcanis 2e9837b70d build: update frontend dependencies 2026-03-15 19:18:58 +02:00
arcanis ac4a8fb2cd fix: hide ignore_list because it is ambiguous now 2026-03-15 19:06:53 +02:00
arcanis 1db8eb0ac4 fix: preserve hold status on status updates 2026-03-15 19:03:05 +02:00
arcanis dc394f7df9 feat: dynamic package hold (#160)
* add dynamic hold implementation to backend

* update frontend to support new status

* force reporter loader

* handle missing packages explicitly

* handle missing packages explicitly
2026-03-15 18:47:02 +02:00
arcanis 058f784b05 fix: center icons on buttons 2026-03-15 17:07:39 +02:00
arcanis f688768ca7 fix: fallback to timestamp in case if crypto context is not available
Previous tests have been done in either secure (HTTPS) or loopback
(localhost) environments, so I missed the case, that crypto methods
might not be available

Fixes #159
2026-03-13 17:00:06 +02:00
arcanis a09ad7617d feat: support archive listing 2026-03-12 22:26:35 +02:00
arcanis 81aeb56ba3 refactor: lightweight Package.from_archive method 2026-03-11 23:00:55 +02:00
arcanis 2cd4ef5e86 refactor: separate package_archives method from trigger 2026-03-11 13:36:59 +02:00
arcanis 998ed48dde feat: add pkgbuild subscommands 2026-03-11 13:31:19 +02:00
arcanis 021d88dc4c feat: store and show pkgbuild (implements #157) 2026-03-11 01:05:06 +02:00
arcanis 9012ee7144 feat: add dark theme support and increase contrast 2026-03-08 22:59:31 +02:00
arcanis 945ddb2942 Release 2.20.0 2026-03-08 16:51:28 +02:00
arcanis 9cd0926588 docs: add http client description 2026-03-08 16:49:46 +02:00
arcanis f5c12d03ec Release 2.20.0rc7 2026-03-08 12:18:03 +02:00
arcanis 606ee2fb0e docs: architecture spec update 2026-03-08 12:16:26 +02:00
arcanis 875e9cf58a docs: update web preview picture 2026-03-08 03:33:49 +02:00
arcanis a809a4b67f feat: support request-id header 2026-03-08 02:51:30 +02:00
arcanis 437ae2c16c style: some minor style improvements 2026-03-06 21:38:57 +02:00
arcanis 10661f3127 fix: catch some StopIteration exceptions 2026-03-06 17:04:19 +02:00
arcanis 88ac3d82a0 feat: explicitly disable archive rotation if option is set to 0 2026-03-06 12:59:48 +02:00
arcanis 386765ab4e fix: fix dashboard visual 2026-03-06 01:48:11 +02:00
arcanis 78998182b8 Release 2.20.0rc6 2026-03-06 01:32:13 +02:00
arcanis 2563391aaf build: correct order of building 2026-03-06 01:31:53 +02:00
arcanis 90a78e99e7 Release 2.20.0rc5 2026-03-06 01:06:15 +02:00
arcanis a05eab9042 feat: brand-new interface (#158)
This was initally generated by ai, but later has been heavily edited.
The reason why it has been implemented is that there are plans to
implement more features to ui, but it becomes hard to add new features
to plain js, so I decided to rewrite it in typescript.

Yet because it is still ai slop, it is still possible to enable old
interface via configuration, even though new interface is turned on by
default to get feedback
2026-03-06 00:59:10 +02:00
arcanis db46147f0d fix: mount archive to chroot
while file:// repository is automatically mounted by devtools, it
doesn't mount any directory which contains source of symlinks

This commit adds implicit mount of the archive directory (ro) into
chroot
2026-03-03 15:50:21 +02:00
arcanis 49ebbc34fa fix: do not update package status if it is unchanged
In order to prevent timestamp bumps, filter by status is added
2026-02-24 15:33:06 +02:00
arcanis e376f1307f docs: remove required flag from email.template_full option 2026-02-22 02:57:34 +02:00
arcanis 415fcf58ce Release 2.20.0rc4 2026-02-21 12:52:51 +02:00
arcanis 17a2d6362c style: replace """ with " for single line strings 2026-02-21 12:51:58 +02:00
arcanis e6275de4ed fix: handle class overrides for retry polices correctly
Previous implementation didn't work as intended because there was still
override in init. Current implementation instead of playing with
guessing separates default and instance setttings. Also update test
cases to handle this scenario correctly
2026-02-21 12:40:11 +02:00
arcanis 4d009cba6d Release 2.20.0rc3 2026-02-20 20:56:05 +02:00
arcanis f6defbf90d fix: rollback samesite option to Lax, because of broken OAuth 2026-02-20 20:54:37 +02:00
arcanis c4fefa6071 Release 2.20.0rc2 2026-02-20 16:30:37 +02:00
arcanis b0f1828ae7 feat: add retry policy 2026-02-20 16:24:14 +02:00
arcanis dec025b45a feat: raise OptionError on missing OAuth provider class instead of generic AttributeError 2026-02-19 10:19:03 +02:00
arcanis 89008e5350 fix: use context manager for selector and smtp session 2026-02-19 10:19:03 +02:00
arcanis 422196d413 fix: force data filter for tar archive extraction
(python3.14 default anyway)
2026-02-19 10:19:03 +02:00
arcanis 6fe2eade26 feat: (more) secure cookies 2026-02-19 10:19:03 +02:00
arcanis 5266f54257 fix: speedup table reload by updating only changed statuses
it has been found that on big (>100) repos it starts lagging on reload.
This commit adds guard to avoid updating rows whose package statuses
were not changed
2026-02-19 10:19:03 +02:00
arcanis bbf9e38fda Release 2.20.0rc1 2026-02-18 13:34:08 +02:00
arcanis ba80a91d95 feat: implement CSRF protection 2026-02-18 13:34:08 +02:00
arcanis 536d040a6a feat: handle only unknownpackageerror on aur load 2026-02-18 13:34:08 +02:00
arcanis bed8752f3a fix: filter logs by repository (twice) before rotation 2026-02-18 13:34:05 +02:00
arcanis 4093ca8986 fix: do not clear queue on queue fetch failures 2026-02-18 13:34:03 +02:00
arcanis f027155885 docs: correct docstring for list_flatmap method 2026-02-18 13:34:03 +02:00
arcanis 443d4ae667 fix: correct vcs definition for cvs packages 2026-02-18 13:34:00 +02:00
arcanis c8f7fa8c51 fix: load gitremote triggers configuration schema from non-standard
paths
2026-02-18 13:33:57 +02:00
arcanis 93c36fb429 docs: update documentation for the lasts archive feature 2026-02-16 22:07:34 +02:00
arcanis 2d6d42f969 feat: archive package tree implementation (#153)
* store built packages in archive tree instead of repository

* write tests to support new changes

* implement atomic_move method, move files only with lock

* use generic packages tree for all repos

* lookup through archive packages before build

* add archive trigger

* add archive trigger

* regenerate docs

* gpg loader fix

* support requires repostory flag

* drop excess REQUIRES_REPOSITORY

* simplify symlionk creation

* remove generators

* fix sttyle

* add separate function for symlinks creation

* fix rebase

* add note about slicing

* smol refactoring of archive_tree class

* remove duplicate code

* fix typos

* few review fixes

* monor fixes and typos

* clean empty directories

* remove side effect from getter

* drop recursive remove

* ensure_exists now accepts only argument

* add package like guard to symlinks fix

* speedup archive_lookup processing by iterrupting cycle

* remove custom filelock

* fix naming

* remove remove flag from repo

* review fixes

* restore wrapper around filelock

* extract repository explorer to separate class

* docs update

* fix ide findings
2026-02-16 00:12:51 +02:00
arcanis 6a2454548d refactor: drop some methods from package class into separated wrappers 2026-02-11 03:05:49 +02:00
arcanis 389bad6725 fix: use effective uid instead of uid 2026-02-03 16:38:13 +02:00
arcanis 5738b8b911 fix: rewrite preserver_owner method complitely
Previous implementation was somewhat working in the most) scenarios, but
was super slow to handle permissions. However, it is actually very
limited operations in which the application can do anything, so it is
much easier to just drop privileged user to normal one
2026-02-03 15:27:19 +02:00
arcanis 5ac2e3de19 fix: handle permissionerror during walking over tree
Previously it tried to look into 700 directories (e.g. .gnupg) which
breaks running as non-ahriman user
2026-02-02 22:16:15 +02:00
arcanis 799dc73d8a feat: update package status to success in case if package is up-to-date 2026-01-26 01:56:30 +02:00
arcanis 4e79cbf71a fix: fallback to package name for missing bases in archive
package zoom is being generated without base, leading to None there

Closes #155
2026-01-22 16:34:19 +02:00
arcanis 4fa5d55317 type: replace generator return type with iterator 2026-01-15 15:22:03 +02:00
arcanis a7fa3b90e4 type: fix typing in some modules 2026-01-14 14:28:31 +02:00
arcanis ce07cda8ab fix: pass underlying exception on extensionerror 2026-01-14 14:06:34 +02:00
arcanis 00c4f32294 fix: correct exception type on repository id comparison 2026-01-14 13:59:07 +02:00
arcanis 49cf91ea52 chore: copyright update 2026-01-08 02:50:37 +02:00
arcanis 4a8430dc67 build: allow to rebuild images manually 2026-01-08 02:45:31 +02:00
arcanis 46af782db2 build: drop shtab wrapper after their release 2025-11-18 20:47:20 +02:00
arcanis 6443e02352 type: use as keyword in case match 2025-10-26 09:36:54 +02:00
arcanis 999ad39d6f feat: add trigger loader guard 2025-09-17 14:45:09 +03:00
arcanis dfab5f56b2 feat: use atexit instead of del for triggers 2025-08-11 14:53:10 +03:00
arcanis 10798b9ba3 fix: correctly process trigger repo specific settings in validator (see #154) 2025-08-01 16:53:15 +03:00
arcanis 358e3dc4d2 feat: expose repository name and architecure in configuration if available
In some cases there are reference to current repository settings. In
order to handle it correctly two ro options have been added

Related to #154
2025-07-31 14:14:22 +03:00
arcanis c13cd029bc feat: fully readable configuration from environment 2025-07-23 14:49:38 +03:00
arcanis ae32cc8fbb type: use custom comparable for comparable functions 2025-07-15 21:20:49 +03:00
arcanis dff5b775a9 refactor: move logs rotation to separated trigger which is enabled by default
Previous solution, well, worked kinda fine-ish, though we have much
better mechanisms to do so
2025-07-15 11:26:00 +03:00
arcanis db3f20546e fix: do not update datalist if search substring hasn't changed 2025-07-14 21:30:27 +03:00
arcanis 53368468a4 fix: block autoupdate on any modal opened 2025-07-14 21:12:33 +03:00
arcanis 228c2cce51 style: use parebtgeses-less exceptions in side effects (tests only) 2025-07-14 20:33:54 +03:00
arcanis f5aec4e5c1 fix: fix search result sorting based if there is exact match or
starts with (closes #152)
2025-07-14 01:12:27 +03:00
arcanis 9217c8c759 feat: add reload command and api endpoints 2025-07-13 15:35:49 +03:00
arcanis 6392520e06 style: reorder schemas properties to alphabet order 2025-07-13 15:34:22 +03:00
arcanis c6306631e6 fix: careful handling of file permissions during initialization
It has been found that during cold start (e.g. in docker container),
some permissions are invalid. In order to handle that, some operations
are not guarded with RepositoryPaths.preserve_root guard

In addition, it has been also found that in some cases (e.g. web server
start) migrations are performed on empty repository identifier which may
lead to wrong data (see also 435375721d),
as well as some unexpected results during database operations. In order
to handle that, now all watcher instances have their own databases (and
configurations)
2025-07-11 17:13:37 +03:00
arcanis 97b906c536 revert: type: fix broken types in dependencies
This reverts commit bd770aac2f.
2025-07-11 03:10:32 +03:00
arcanis 435375721d fix: fix migrations on empty repositories 2025-07-08 16:46:34 +03:00
arcanis 4c5caba6b7 fix: trim provides/depends versions and lookup provides through pkgname
(#150)

Current implementation did it in wrong way. First of all, there was a
lookup through pkgbase instead of pkgname, which lead to errors, because
aur api doesn't allow to search by pkgbase (as well as provides is
basically pkgname instead)

It also was found that dependencies resolution lookup has been performed
by using raw packages array, which can include versions, descriptions
etc
2025-07-08 16:22:41 +03:00
arcanis b83df9d2c5 refactor: reorganize js methods 2025-07-07 20:33:01 +03:00
arcanis f2ea76aab9 feat: add silent logs reload 2025-07-07 17:02:08 +03:00
arcanis 471b1c1331 feat: add cookies support and improve autorefresh UX
This commit also includes changing of load logic to update row by row
instead of full table toggle. It also changes behaviour on openned
dropdowns blocking refresh
2025-07-07 12:48:00 +03:00
arcanis bd770aac2f type: fix broken types in dependencies
aiosignal 1.4.0 has been released including feature https://github.com/aio-libs/aiosignal/pull/699
However, aiohttp still uses old types which were not merged as well as
were not even implemented (the closest is
https://github.com/aio-libs/aiohttp/pull/11160 and related issue
https://github.com/aio-libs/aiohttp/issues/11036, whoever it still
doesn't change signatures for _AppSignal)

Let's just mark those calls as ignore
2025-07-04 13:03:56 +03:00
arcanis 6abe35ef8c fix: separate ua by spaces 2025-07-02 14:50:21 +03:00
arcanis fdc27a9ebf feat: filter out obvious duplicates from multisearch 2025-07-01 09:59:29 +03:00
arcanis b729096a25 feat: block refresh upon opening dashboard or changing table position 2025-07-01 06:58:03 +03:00
arcanis 390b9da29e feat: allow to use 0 as auto refresh interval with special meaning (#148) 2025-07-01 03:37:49 +03:00
arcanis 256376df85 feat: add autorefresh button to the main page (#149)
* also add configuration options and change behaviour accordingly
2025-07-01 03:22:01 +03:00
arcanis 939a94d889 feat: add autoupdate button to package info (#148) 2025-06-29 22:22:54 +03:00
arcanis 2b1b17a1a3 Release 2.19.0 2025-06-29 03:00:41 +03:00
arcanis 9e6705056a build: use archlinux images for release build 2025-06-29 02:59:28 +03:00
arcanis b3a3a81f70 feat: add ability to refresh databases through web interface 2025-06-29 02:44:57 +03:00
arcanis 3e5dbbd6cd feat: extend user-agent 2025-06-28 23:08:31 +03:00
arcanis f41e44895d fix: support provides in aur (#146)
* support provides in aur

* process provides during tree resolution

* stylish
2025-06-28 22:39:54 +03:00
arcanis 765bbf486f feat: port to new AUR API 2025-06-28 22:07:59 +03:00
arcanis a3c54afb82 fix: process unicode errors in command execution 2025-06-28 20:26:47 +03:00
arcanis 7f223ecc0a docs: extract version for the manpage 2025-06-25 02:14:57 +03:00
arcanis 7769a4a6e0 Release 2.18.3 2025-06-20 17:20:19 +03:00
arcanis 066d1b1dde refactor: rework few tests and build system
This commit includes the following changes
* Bump github actions
* Update tests github action to check documentation and streamline
  process
* Update test cases to use temporary directories as roots
* Simplify tox.ini
2025-06-20 17:04:57 +03:00
arcanis 1f22a27360 build: remove defaults from pylint config 2025-06-18 16:20:14 +03:00
arcanis 75682bc7be feat: add support of openmetrics (#144)
* feat: add openmetrics support & endpoint

* add support of named resources

* update docstrings

* generate docs

* add another test for http api
2025-06-18 14:42:09 +03:00
arcanis e5d824b03f build: add regress weekly pipeline
This commit also adds manual dispatch for tests and setup
2025-06-16 21:15:52 +03:00
arcanis 8d0d597473 Release 2.18.2 2025-06-16 19:03:05 +03:00
arcanis 995b396360 bug: fix invalid logs rotation 2025-06-16 16:36:34 +03:00
arcanis 7f813cf0c3 Release 2.18.1 2025-06-16 15:33:24 +03:00
arcanis d4eb55ef95 bug: correctly close sqlite3 connection
After the last updates, tests produce warnings that the connection to
database is leaked, which appears to be correct. This commit changes
behaviour to closing connection explicitly via contextlib
2025-06-16 15:24:57 +03:00
arcanis 09350e88ab style: fix few typos 2025-06-14 23:34:53 +03:00
arcanis 2feaa14f46 Release 2.18.0 2025-06-13 16:37:58 +03:00
arcanis 9653fc4f4a type: support new configparser signatures 2025-05-31 02:16:07 +03:00
arcanis bcd46c66e8 test: use new-style fixtures instead of event_loop for asyncio 2025-05-12 15:57:05 +03:00
arcanis 6ea56faede build: fix tox environment creation with the latest updates 2025-05-09 15:00:40 +03:00
arcanis 9e346530f2 refactor: use backslashreplace error handling instead of guessing encoding 2025-05-08 14:03:47 +03:00
arcanis d283dccc1e type: update for new cors release 2025-03-17 13:56:59 +02:00
arcanis 8a4e900ab9 docs: update docs
This commit includes following changes
* add newly added option to configuration referenec
* remove few legacy options from configuration schemas used for
  validation, which might lead to errors during validation.
  Note, however, that settings will be still read by the service
* add link to aurcache
* hide service-setup command description under spoiler
2025-03-17 13:43:04 +02:00
arcanis fa6cf8ce36 website: use date instead of version for listing logs
website: make dropdown from logs versions to add some space
2025-03-13 15:45:31 +02:00
arcanis a706fbb751 bug: handle dependencies iteratively (fix #141)
It has been found that if there are missing dependencies than whole
process will break instead of just skipping packages. During package
addition it is fine-ish, but it will break updates run
2025-03-13 15:45:27 +02:00
arcanis 9a23f5c79d refactor: streamline migrations 2025-03-09 23:22:24 +02:00
arcanis aaab9069bf docs: rebuild indices 2025-03-09 15:43:41 +02:00
arcanis f00b575641 type: use ClassVar decorator for class attributes 2025-03-09 15:43:27 +02:00
arcanis 6f57ed550b feat: refine log system (#142)
* refine package logging

* add interface

* revert version selection

* replace tuple with model

* rename column in logs table, add coverters

* generate process identifier for child proocesses
2025-03-09 14:46:33 +02:00
arcanis 08640d9108 feat: add dashboard (#139) 2025-02-24 00:10:15 +02:00
arcanis 65324633b4 feat: add counters to repository stats overview 2025-02-24 00:10:15 +02:00
arcanis ed67898012 fix: parse non-utf pkgbuilds as well (#140)
it has been reported that duriing reading pkgbuilds with latin-1 charset
the execption will be raised. Well, it is one more point to rewrite
parser to use own impl instead of shlex and parse raw byte array instead
2025-02-24 00:10:15 +02:00
arcanis a1a8dd68e8 type: remove unused ignore directive 2025-02-24 00:10:15 +02:00
arcanis a9505386c2 fix: force dry run build on task initialization for VCS packages
Previously if package is VCS and version in PKGBUILD doesn't match to
AUR one, then makepkg will update pkgbuild ignoring all previous pkgrel
patches

With this change during task init dry ryn process is always run for vcs
packages
2025-02-24 00:10:15 +02:00
arcanis a07b20bf50 Release 2.17.1 2025-01-06 01:14:28 +02:00
arcanis ed70897c39 fix: suppress traceback in shell if no ipython installed
Old implementation was showing import error, new implementation instead
hides it behind separated call and if-else check
2025-01-06 01:07:13 +02:00
arcanis 0423c3e67c Release 2.17.0 2024-12-29 18:07:23 +02:00
arcanis 571f62327f build: remove unused line from dockerfile 2024-12-24 16:49:30 +02:00
arcanis 286ff4bcef fix: update packages properties after rebuild
This case leads to issue when it is impossible to update list of
implicit dependencies correctly in case of multi-packages
2024-12-24 15:13:18 +02:00
arcanis 0660c33de3 chore: copyright update 2024-12-23 16:03:26 +02:00
arcanis c8421e97ee fix: fix pkgbuild parsing in case if comment mark is followed by token
without whitespaces

In this case, the next line was ignored
2024-12-23 15:55:07 +02:00
arcanis bc2288afc1 fix: suppress codefactor warning 2024-12-23 01:52:23 +02:00
arcanis 503c8b0355 feat: make apispec dependency optional (#138) 2024-12-22 20:33:31 +02:00
arcanis 6738f9206d type: remove unused typeguard 2024-12-21 17:02:09 +02:00
arcanis f865e998b0 feat: add link to logo 2024-12-19 12:47:17 +02:00
arcanis 4880ca4fee feat: use IPython shell if available 2024-12-18 15:41:36 +02:00
999 changed files with 25605 additions and 8629 deletions
-6
View File
@@ -1,6 +0,0 @@
skips:
- B101
- B104
- B105
- B106
- B404
+3
View File
@@ -12,3 +12,6 @@ __pycache__/
*.pyc *.pyc
*.pyd *.pyd
*.pyo *.pyo
node_modules/
package-lock.json
+17 -6
View File
@@ -7,6 +7,11 @@ on:
tags: tags:
- '*' - '*'
- '!*rc*' - '!*rc*'
workflow_dispatch:
permissions:
contents: read
packages: write
jobs: jobs:
docker-image: docker-image:
@@ -17,18 +22,22 @@ jobs:
packages: write packages: write
steps: steps:
- uses: docker/setup-qemu-action@v2 - uses: docker/setup-qemu-action@v3
- uses: docker/setup-buildx-action@v2 - uses: docker/setup-buildx-action@v3
- name: Set image date
id: args
run: echo "::set-output name=date::$(date -d yesterday +'%Y-%m-%d')"
- name: Login to docker hub - name: Login to docker hub
uses: docker/login-action@v2 uses: docker/login-action@v3
with: with:
username: ${{ secrets.DOCKERHUB_USERNAME }} username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }} password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to github container registry - name: Login to github container registry
uses: docker/login-action@v2 uses: docker/login-action@v3
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.repository_owner }} username: ${{ github.repository_owner }}
@@ -36,7 +45,7 @@ jobs:
- name: Extract docker metadata - name: Extract docker metadata
id: meta id: meta
uses: docker/metadata-action@v3 uses: docker/metadata-action@v5
with: with:
images: | images: |
arcan1s/ahriman arcan1s/ahriman
@@ -46,8 +55,10 @@ jobs:
type=edge type=edge
- name: Build an image and push - name: Build an image and push
uses: docker/build-push-action@v4 uses: docker/build-push-action@v6
with: with:
build-args: |
BUILD_DATE=${{ steps.args.outputs.date }}
file: docker/Dockerfile file: docker/Dockerfile
push: true push: true
tags: ${{ steps.meta.outputs.tags }} tags: ${{ steps.meta.outputs.tags }}
+7 -3
View File
@@ -1,6 +1,12 @@
name: Regress name: Regress
on: workflow_dispatch on:
schedule:
- cron: 1 0 * * 0
workflow_dispatch:
permissions:
contents: read
jobs: jobs:
run-regress-tests: run-regress-tests:
@@ -31,8 +37,6 @@ jobs:
- repo:/var/lib/ahriman - repo:/var/lib/ahriman
steps: steps:
- uses: actions/checkout@v3
- run: pacman -Sy - run: pacman -Sy
- name: Init repository - name: Init repository
+13 -7
View File
@@ -5,13 +5,24 @@ on:
tags: tags:
- '*' - '*'
permissions:
contents: write
jobs: jobs:
make-release: make-release:
runs-on: ubuntu-latest runs-on: ubuntu-latest
container:
image: archlinux:base
options: -w /build
volumes:
- ${{ github.workspace }}:/build
steps: steps:
- uses: actions/checkout@v3 - run: pacman --noconfirm -Syu base-devel git python-tox
- uses: actions/checkout@v4
- name: Extract version - name: Extract version
id: version id: version
@@ -24,18 +35,13 @@ jobs:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
filter: 'Release \d+\.\d+\.\d+' filter: 'Release \d+\.\d+\.\d+'
- name: Install dependencies
uses: ConorMacBride/install-package@v1.1.0
with:
apt: tox
- name: Create archive - name: Create archive
run: tox -e archive run: tox -e archive
env: env:
VERSION: ${{ steps.version.outputs.VERSION }} VERSION: ${{ steps.version.outputs.VERSION }}
- name: Publish release - name: Publish release
uses: softprops/action-gh-release@v1 uses: softprops/action-gh-release@v2
with: with:
body: | body: |
${{ steps.changelog.outputs.compareurl }} ${{ steps.changelog.outputs.compareurl }}
+4 -4
View File
@@ -10,15 +10,15 @@ echo -e '[arcanisrepo]\nServer = https://repo.arcanis.me/$arch\nSigLevel = Never
# refresh the image # refresh the image
pacman -Syyu --noconfirm pacman -Syyu --noconfirm
# main dependencies # main dependencies
pacman -S --noconfirm devtools git pyalpm python-bcrypt python-inflection python-pyelftools python-requests python-systemd sudo pacman -S --noconfirm devtools git npm pyalpm python-bcrypt python-filelock python-inflection python-pyelftools python-requests python-systemd sudo
# make dependencies # make dependencies
pacman -S --noconfirm --asdeps base-devel python-build python-flit python-installer python-tox python-wheel pacman -S --noconfirm --asdeps base-devel python-build python-hatchling python-installer python-tox python-wheel
# optional dependencies # optional dependencies
if [[ -z $MINIMAL_INSTALL ]]; then if [[ -z $MINIMAL_INSTALL ]]; then
# web server # web server
pacman -S --noconfirm python-aioauth-client python-aiohttp python-aiohttp-apispec-git python-aiohttp-cors python-aiohttp-jinja2 python-aiohttp-security python-aiohttp-session python-cryptography python-jinja pacman -S --noconfirm python-aioauth-client python-aiohttp python-aiohttp-apispec-git python-aiohttp-cors python-aiohttp-jinja2 python-aiohttp-security python-aiohttp-session python-aiohttp-sse-git python-cryptography python-jinja
# additional features # additional features
pacman -S --noconfirm gnupg python-boto3 python-cerberus python-matplotlib rsync pacman -S --noconfirm gnupg ipython python-boto3 python-cerberus python-matplotlib rsync
fi fi
# 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
cp "docker/systemd-nspawn.sh" "/usr/local/bin/systemd-nspawn" cp "docker/systemd-nspawn.sh" "/usr/local/bin/systemd-nspawn"
+6 -2
View File
@@ -7,6 +7,10 @@ on:
pull_request: pull_request:
branches: branches:
- master - master
workflow_dispatch:
permissions:
contents: read
jobs: jobs:
run-setup-minimal: run-setup-minimal:
@@ -20,7 +24,7 @@ jobs:
- ${{ github.workspace }}:/build - ${{ github.workspace }}:/build
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Setup the minimal service in arch linux container - name: Setup the minimal service in arch linux container
run: .github/workflows/setup.sh minimal run: .github/workflows/setup.sh minimal
@@ -36,7 +40,7 @@ jobs:
options: --privileged -w /build options: --privileged -w /build
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Setup the service in arch linux container - name: Setup the service in arch linux container
run: .github/workflows/setup.sh run: .github/workflows/setup.sh
-10
View File
@@ -1,10 +0,0 @@
#!/bin/bash
# Install dependencies and run test in container
set -ex
# install dependencies
pacman --noconfirm -Syyu base-devel python-tox
# run test and check targets
tox
+16 -3
View File
@@ -9,6 +9,10 @@ on:
- master - master
schedule: schedule:
- cron: 1 0 * * * - cron: 1 0 * * *
workflow_dispatch:
permissions:
contents: read
jobs: jobs:
run-tests: run-tests:
@@ -22,7 +26,16 @@ jobs:
- ${{ github.workspace }}:/build - ${{ github.workspace }}:/build
steps: steps:
- uses: actions/checkout@v3 - run: pacman --noconfirm -Syu base-devel git npm python-tox
- name: Run check and tests in arch linux container - run: git config --global --add safe.directory *
run: .github/workflows/tests.sh
- uses: actions/checkout@v4
- name: Run check and tests
run: tox
- name: Generate documentation and check if there are untracked changes
run: |
tox -e docs
[ -z "$(git status --porcelain docs/*.rst)" ]
+9
View File
@@ -99,3 +99,12 @@ status_cache.json
*.db *.db
docs/html/ docs/html/
# Frontend
node_modules/
package-lock.json
ahriman-web/package/share/ahriman/templates/static/index.js
ahriman-web/package/share/ahriman/templates/static/index.css
# local configs
/*.ini
+45
View File
@@ -0,0 +1,45 @@
[tool.pylint.main]
init-hook = "sys.path.append('tools')"
load-plugins = [
"pylint.extensions.docparams",
"pylint.extensions.bad_builtin",
"pylint_plugins.definition_order",
"pylint_plugins.import_order",
]
[tool.pylint.classes]
bad-functions = [
"print",
]
[tool.pylint.design]
max-parents = 15
[tool.pylint."messages control"]
disable = [
"raw-checker-failed",
"bad-inline-option",
"locally-disabled",
"file-ignored",
"suppressed-message",
"useless-suppression",
"deprecated-pragma",
"use-symbolic-message-instead",
"use-implicit-booleaness-not-comparison-to-string",
"use-implicit-booleaness-not-comparison-to-zero",
"missing-module-docstring",
"line-too-long",
"no-name-in-module",
"import-outside-toplevel",
"invalid-name",
"raise-missing-from",
"wrong-import-order",
"too-few-public-methods",
"too-many-instance-attributes",
"broad-exception-caught",
"fixme",
"too-many-arguments",
"duplicate-code",
"cyclic-import",
"too-many-positional-arguments",
]
-651
View File
@@ -1,651 +0,0 @@
[MAIN]
# Analyse import fallback blocks. This can be used to support both Python 2 and
# 3 compatible code, which means that the block might have code that exists
# only in one or another interpreter, leading to false positives when analysed.
analyse-fallback-blocks=no
# Clear in-memory caches upon conclusion of linting. Useful if running pylint
# in a server-like mode.
clear-cache-post-run=no
# Load and enable all available extensions. Use --list-extensions to see a list
# all available extensions.
#enable-all-extensions=
# In error mode, messages with a category besides ERROR or FATAL are
# suppressed, and no reports are done by default. Error mode is compatible with
# disabling specific errors.
#errors-only=
# Always return a 0 (non-error) status code, even if lint errors are found.
# This is primarily useful in continuous integration scripts.
#exit-zero=
# A comma-separated list of package or module names from where C extensions may
# be loaded. Extensions are loading into the active Python interpreter and may
# run arbitrary code.
extension-pkg-allow-list=
# A comma-separated list of package or module names from where C extensions may
# be loaded. Extensions are loading into the active Python interpreter and may
# run arbitrary code. (This is an alternative name to extension-pkg-allow-list
# for backward compatibility.)
extension-pkg-whitelist=
# Return non-zero exit code if any of these messages/categories are detected,
# even if score is above --fail-under value. Syntax same as enable. Messages
# specified are enabled, while categories only check already-enabled messages.
fail-on=
# Specify a score threshold under which the program will exit with error.
fail-under=10
# Interpret the stdin as a python script, whose filename needs to be passed as
# the module_or_package argument.
#from-stdin=
# Files or directories to be skipped. They should be base names, not paths.
ignore=CVS
# Add files or directories matching the regular expressions patterns to the
# ignore-list. The regex matches against paths and can be in Posix or Windows
# format. Because '\\' represents the directory delimiter on Windows systems,
# it can't be used as an escape character.
ignore-paths=
# Files or directories matching the regular expression patterns are skipped.
# The regex matches against base names, not paths. The default value ignores
# Emacs file locks
ignore-patterns=^\.#
# List of module names for which member attributes should not be checked
# (useful for modules/projects where namespaces are manipulated during runtime
# and thus existing member attributes cannot be deduced by static analysis). It
# supports qualified module names, as well as Unix pattern matching.
ignored-modules=
# Python code to execute, usually for sys.path manipulation such as
# pygtk.require().
init-hook='import sys; sys.path.append("pylint_plugins")'
# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the
# number of processors available to use, and will cap the count on Windows to
# avoid hangs.
jobs=1
# Control the amount of potential inferred values when inferring a single
# object. This can help the performance when dealing with large functions or
# complex, nested conditions.
limit-inference-results=100
# List of plugins (as comma separated values of python module names) to load,
# usually to register additional checkers.
load-plugins=pylint.extensions.docparams,
pylint.extensions.bad_builtin,
definition_order,
import_order,
# Pickle collected data for later comparisons.
persistent=yes
# Minimum Python version to use for version dependent checks. Will default to
# the version used to run pylint.
py-version=3.11
# Discover python modules and packages in the file system subtree.
recursive=no
# Add paths to the list of the source roots. Supports globbing patterns. The
# source root is an absolute path or a path relative to the current working
# directory used to determine a package namespace for modules located under the
# source root.
source-roots=
# When enabled, pylint would attempt to guess common misconfiguration and emit
# user-friendly hints instead of false-positive error messages.
suggestion-mode=yes
# Allow loading of arbitrary C extensions. Extensions are imported into the
# active Python interpreter and may run arbitrary code.
unsafe-load-any-extension=no
# In verbose mode, extra non-checker-related info will be displayed.
#verbose=
[BASIC]
# Naming style matching correct argument names.
argument-naming-style=snake_case
# Regular expression matching correct argument names. Overrides argument-
# naming-style. If left empty, argument names will be checked with the set
# naming style.
#argument-rgx=
# Naming style matching correct attribute names.
attr-naming-style=snake_case
# Regular expression matching correct attribute names. Overrides attr-naming-
# style. If left empty, attribute names will be checked with the set naming
# style.
#attr-rgx=
bad-functions=print,
# Bad variable names which should always be refused, separated by a comma.
bad-names=foo,
bar,
baz,
toto,
tutu,
tata
# Bad variable names regexes, separated by a comma. If names match any regex,
# they will always be refused
bad-names-rgxs=
# Naming style matching correct class attribute names.
class-attribute-naming-style=any
# Regular expression matching correct class attribute names. Overrides class-
# attribute-naming-style. If left empty, class attribute names will be checked
# with the set naming style.
#class-attribute-rgx=
# Naming style matching correct class constant names.
class-const-naming-style=UPPER_CASE
# Regular expression matching correct class constant names. Overrides class-
# const-naming-style. If left empty, class constant names will be checked with
# the set naming style.
#class-const-rgx=
# Naming style matching correct class names.
class-naming-style=PascalCase
# Regular expression matching correct class names. Overrides class-naming-
# style. If left empty, class names will be checked with the set naming style.
#class-rgx=
# Naming style matching correct constant names.
const-naming-style=UPPER_CASE
# Regular expression matching correct constant names. Overrides const-naming-
# style. If left empty, constant names will be checked with the set naming
# style.
#const-rgx=
# Minimum line length for functions/classes that require docstrings, shorter
# ones are exempt.
docstring-min-length=-1
# Naming style matching correct function names.
function-naming-style=snake_case
# Regular expression matching correct function names. Overrides function-
# naming-style. If left empty, function names will be checked with the set
# naming style.
#function-rgx=
# Good variable names which should always be accepted, separated by a comma.
good-names=i,
j,
k,
ex,
Run,
_
# Good variable names regexes, separated by a comma. If names match any regex,
# they will always be accepted
good-names-rgxs=
# Include a hint for the correct naming format with invalid-name.
include-naming-hint=no
# Naming style matching correct inline iteration names.
inlinevar-naming-style=any
# Regular expression matching correct inline iteration names. Overrides
# inlinevar-naming-style. If left empty, inline iteration names will be checked
# with the set naming style.
#inlinevar-rgx=
# Naming style matching correct method names.
method-naming-style=snake_case
# Regular expression matching correct method names. Overrides method-naming-
# style. If left empty, method names will be checked with the set naming style.
#method-rgx=
# Naming style matching correct module names.
module-naming-style=snake_case
# Regular expression matching correct module names. Overrides module-naming-
# style. If left empty, module names will be checked with the set naming style.
#module-rgx=
# Colon-delimited sets of names that determine each other's naming style when
# the name regexes allow several styles.
name-group=
# Regular expression which should only match function or class names that do
# not require a docstring.
no-docstring-rgx=
# List of decorators that produce properties, such as abc.abstractproperty. Add
# to this list to register other decorators that produce valid properties.
# These decorators are taken in consideration only for invalid-name.
property-classes=abc.abstractproperty
# Regular expression matching correct type alias names. If left empty, type
# alias names will be checked with the set naming style.
#typealias-rgx=
# Regular expression matching correct type variable names. If left empty, type
# variable names will be checked with the set naming style.
#typevar-rgx=
# Naming style matching correct variable names.
variable-naming-style=snake_case
# Regular expression matching correct variable names. Overrides variable-
# naming-style. If left empty, variable names will be checked with the set
# naming style.
#variable-rgx=
[CLASSES]
# Warn about protected attribute access inside special methods
check-protected-access-in-special-methods=no
# List of method names used to declare (i.e. assign) instance attributes.
defining-attr-methods=__init__,
__new__,
setUp,
asyncSetUp,
__post_init__
# List of member names, which should be excluded from the protected access
# warning.
exclude-protected=_asdict,_fields,_replace,_source,_make,os._exit
# List of valid names for the first argument in a class method.
valid-classmethod-first-arg=cls
# List of valid names for the first argument in a metaclass class method.
valid-metaclass-classmethod-first-arg=mcs
[DESIGN]
# List of regular expressions of class ancestor names to ignore when counting
# public methods (see R0903)
exclude-too-few-public-methods=
# List of qualified class names to ignore when counting class parents (see
# R0901)
ignored-parents=
# Maximum number of arguments for function / method.
max-args=5
# Maximum number of attributes for a class (see R0902).
max-attributes=7
# Maximum number of boolean expressions in an if statement (see R0916).
max-bool-expr=5
# Maximum number of branch for function / method body.
max-branches=12
# Maximum number of locals for function / method body.
max-locals=15
# Maximum number of parents for a class (see R0901).
max-parents=7
# Maximum number of public methods for a class (see R0904).
max-public-methods=20
# Maximum number of return / yield for function / method body.
max-returns=6
# Maximum number of statements in function / method body.
max-statements=50
# Minimum number of public methods for a class (see R0903).
min-public-methods=2
[EXCEPTIONS]
# Exceptions that will emit a warning when caught.
overgeneral-exceptions=builtins.BaseException,builtins.Exception
[FORMAT]
# Expected format of line ending, e.g. empty (any line ending), LF or CRLF.
expected-line-ending-format=
# Regexp for a line that is allowed to be longer than the limit.
ignore-long-lines=^\s*(# )?<?https?://\S+>?$
# Number of spaces of indent required inside a hanging or continued line.
indent-after-paren=4
# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
# tab).
indent-string=' '
# Maximum number of characters on a single line.
max-line-length=100
# Maximum number of lines in a module.
max-module-lines=1000
# Allow the body of a class to be on the same line as the declaration if body
# contains single statement.
single-line-class-stmt=no
# Allow the body of an if to be on the same line as the test if there is no
# else.
single-line-if-stmt=no
[IMPORTS]
# List of modules that can be imported at any level, not just the top level
# one.
allow-any-import-level=
# Allow explicit reexports by alias from a package __init__.
allow-reexport-from-package=no
# Allow wildcard imports from modules that define __all__.
allow-wildcard-with-all=no
# Deprecated modules which should not be used, separated by a comma.
deprecated-modules=
# Output a graph (.gv or any supported image format) of external dependencies
# to the given file (report RP0402 must not be disabled).
ext-import-graph=
# Output a graph (.gv or any supported image format) of all (i.e. internal and
# external) dependencies to the given file (report RP0402 must not be
# disabled).
import-graph=
# Output a graph (.gv or any supported image format) of internal dependencies
# to the given file (report RP0402 must not be disabled).
int-import-graph=
# Force import order to recognize a module as part of the standard
# compatibility libraries.
known-standard-library=
# Force import order to recognize a module as part of a third party library.
known-third-party=enchant
# Couples of modules and preferred modules, separated by a comma.
preferred-modules=
[LOGGING]
# The type of string formatting that logging methods do. `old` means using %
# formatting, `new` is for `{}` formatting.
logging-format-style=old
# Logging modules to check that the string format arguments are in logging
# function parameter format.
logging-modules=logging
[MESSAGES CONTROL]
# Only show warnings with the listed confidence levels. Leave empty to show
# all. Valid levels: HIGH, CONTROL_FLOW, INFERENCE, INFERENCE_FAILURE,
# UNDEFINED.
confidence=HIGH,
CONTROL_FLOW,
INFERENCE,
INFERENCE_FAILURE,
UNDEFINED
# Disable the message, report, category or checker with the given id(s). You
# can either give multiple identifiers separated by comma (,) or put this
# option multiple times (only on the command line, not in the configuration
# file where it should appear only once). You can also use "--disable=all" to
# disable everything first and then re-enable specific checks. For example, if
# you want to run only the similarities checker, you can use "--disable=all
# --enable=similarities". If you want to run only the classes checker, but have
# no Warning level messages displayed, use "--disable=all --enable=classes
# --disable=W".
disable=raw-checker-failed,
bad-inline-option,
locally-disabled,
file-ignored,
suppressed-message,
useless-suppression,
deprecated-pragma,
use-symbolic-message-instead,
missing-module-docstring,
line-too-long,
no-name-in-module,
import-outside-toplevel,
invalid-name,
raise-missing-from,
wrong-import-order,
too-few-public-methods,
too-many-instance-attributes,
broad-except,
fixme,
too-many-arguments,
duplicate-code,
cyclic-import,
too-many-positional-arguments,
# Enable the message, report, category or checker with the given id(s). You can
# either give multiple identifier separated by comma (,) or put this option
# multiple time (only on the command line, not in the configuration file where
# it should appear only once). See also the "--disable" option for examples.
enable=c-extension-no-member
[METHOD_ARGS]
# List of qualified names (i.e., library.method) which require a timeout
# parameter e.g. 'requests.api.get,requests.api.post'
timeout-methods=requests.api.delete,requests.api.get,requests.api.head,requests.api.options,requests.api.patch,requests.api.post,requests.api.put,requests.api.request
[MISCELLANEOUS]
# List of note tags to take in consideration, separated by a comma.
notes=FIXME,
XXX,
TODO
# Regular expression of note tags to take in consideration.
notes-rgx=
[REFACTORING]
# Maximum number of nested blocks for function / method body
max-nested-blocks=5
# Complete name of functions that never returns. When checking for
# inconsistent-return-statements if a never returning function is called then
# it will be considered as an explicit return statement and no message will be
# printed.
never-returning-functions=sys.exit,argparse.parse_error
[REPORTS]
# Python expression which should return a score less than or equal to 10. You
# have access to the variables 'fatal', 'error', 'warning', 'refactor',
# 'convention', and 'info' which contain the number of messages in each
# category, as well as 'statement' which is the total number of statements
# analyzed. This score is used by the global evaluation report (RP0004).
evaluation=max(0, 0 if fatal else 10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10))
# Template used to display messages. This is a python new-style format string
# used to format the message information. See doc for all details.
msg-template=
# Set the output format. Available formats are text, parseable, colorized, json
# and msvs (visual studio). You can also give a reporter class, e.g.
# mypackage.mymodule.MyReporterClass.
#output-format=
# Tells whether to display a full report or only the messages.
reports=no
# Activate the evaluation score.
score=yes
[SIMILARITIES]
# Comments are removed from the similarity computation
ignore-comments=yes
# Docstrings are removed from the similarity computation
ignore-docstrings=yes
# Imports are removed from the similarity computation
ignore-imports=yes
# Signatures are removed from the similarity computation
ignore-signatures=yes
# Minimum lines number of a similarity.
min-similarity-lines=4
[SPELLING]
# Limits count of emitted suggestions for spelling mistakes.
max-spelling-suggestions=4
# Spelling dictionary name. No available dictionaries : You need to install
# both the python package and the system dependency for enchant to work..
spelling-dict=
# List of comma separated words that should be considered directives if they
# appear at the beginning of a comment and should not be checked.
spelling-ignore-comment-directives=fmt: on,fmt: off,noqa:,noqa,nosec,isort:skip,mypy:
# List of comma separated words that should not be checked.
spelling-ignore-words=
# A path to a file that contains the private dictionary; one word per line.
spelling-private-dict-file=
# Tells whether to store unknown words to the private dictionary (see the
# --spelling-private-dict-file option) instead of raising a message.
spelling-store-unknown-words=no
[STRING]
# This flag controls whether inconsistent-quotes generates a warning when the
# character used as a quote delimiter is used inconsistently within a module.
check-quote-consistency=no
# This flag controls whether the implicit-str-concat should generate a warning
# on implicit string concatenation in sequences defined over several lines.
check-str-concat-over-line-jumps=no
[TYPECHECK]
# List of decorators that produce context managers, such as
# contextlib.contextmanager. Add to this list to register other decorators that
# produce valid context managers.
contextmanager-decorators=contextlib.contextmanager
# List of members which are set dynamically and missed by pylint inference
# system, and so shouldn't trigger E1101 when accessed. Python regular
# expressions are accepted.
generated-members=
# Tells whether to warn about missing members when the owner of the attribute
# is inferred to be None.
ignore-none=yes
# This flag controls whether pylint should warn about no-member and similar
# checks whenever an opaque object is returned when inferring. The inference
# can return multiple potential results while evaluating a Python object, but
# some branches might not be evaluated, which results in partial inference. In
# that case, it might be useful to still emit no-member and other checks for
# the rest of the inferred objects.
ignore-on-opaque-inference=yes
# List of symbolic message names to ignore for Mixin members.
ignored-checks-for-mixins=no-member,
not-async-context-manager,
not-context-manager,
attribute-defined-outside-init
# List of class names for which member attributes should not be checked (useful
# for classes with dynamically set attributes). This supports the use of
# qualified names.
ignored-classes=optparse.Values,thread._local,_thread._local,argparse.Namespace
# Show a hint with possible names when a member name was not found. The aspect
# of finding the hint is based on edit distance.
missing-member-hint=yes
# The minimum edit distance a name should have in order to be considered a
# similar match for a missing member name.
missing-member-hint-distance=1
# The total number of similar names that should be taken in consideration when
# showing a hint for a missing member.
missing-member-max-choices=1
# Regex pattern to define which classes are considered mixins.
mixin-class-rgx=.*[Mm]ixin
# List of decorators that change the signature of a decorated function.
signature-mutators=
[VARIABLES]
# List of additional names supposed to be defined in builtins. Remember that
# you should avoid defining new builtins when possible.
additional-builtins=
# Tells whether unused global variables should be treated as a violation.
allow-global-unused-variables=yes
# List of names allowed to shadow builtins
allowed-redefined-builtins=
# List of strings which can identify a callback function by name. A callback
# name must start or end with one of those strings.
callbacks=cb_,
_cb
# A regular expression matching the name of dummy variables (i.e. expected to
# not be used).
dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_
# Argument names that match this expression will be ignored.
ignored-argument-names=_.*|^ignored_|^unused_
# Tells whether we should check for unused import in __init__ files.
init-import=no
# List of qualified module names which can have objects that can redefine
# builtins.
redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io
+7
View File
@@ -0,0 +1,7 @@
[pytest]
addopts = --cov=ahriman --cov-report=term-missing:skip-covered --no-cov-on-fail --cov-fail-under=100 --spec
asyncio_default_fixture_loop_scope = function
asyncio_mode = auto
pythonpath = tests
resource-path.directory-name-test-resources = ../../tests/testresources
spec_test_format = {result} {docstring_summary}
+3 -9
View File
@@ -1,21 +1,15 @@
version: 2 version: 2
build: build:
os: ubuntu-20.04 os: ubuntu-lts-latest
tools: tools:
python: "3.12" python: "3.13"
apt_packages: apt_packages:
- graphviz - graphviz
python: python:
install: install:
- method: pip - requirements: docs/requirements.txt
path: .
extra_requirements:
- docs
- s3
- validator
- web
formats: formats:
- pdf - pdf
+24 -23
View File
@@ -80,7 +80,7 @@ Again, the most checks can be performed by `tox` command, though some additional
>>> clazz = Clazz() >>> clazz = Clazz()
""" """
CLAZZ_ATTRIBUTE = 42 CLAZZ_ATTRIBUTE: ClassVar[int] = 42
def __init__(self, *args: Any, **kwargs: Any) -> None: def __init__(self, *args: Any, **kwargs: Any) -> None:
""" """
@@ -96,6 +96,7 @@ Again, the most checks can be performed by `tox` command, though some additional
* Type annotations are the must, even for local functions. For the function argument `self` (for instance methods) and `cls` (for class methods) should not be annotated. * Type annotations are the must, even for local functions. For the function argument `self` (for instance methods) and `cls` (for class methods) should not be annotated.
* For collection types built-in classes must be used if possible (e.g. `dict` instead of `typing.Dict`, `tuple` instead of `typing.Tuple`). In case if built-in type is not available, but `collections.abc` provides interface, it must be used (e.g. `collections.abc.Awaitable` instead of `typing.Awaitable`, `collections.abc.Iterable` instead of `typing.Iterable`). For union classes, the bar operator (`|`) must be used (e.g. `float | int` instead of `typing.Union[float, int]`), which also includes `typing.Optional` (e.g. `str | None` instead of `Optional[str]`). * For collection types built-in classes must be used if possible (e.g. `dict` instead of `typing.Dict`, `tuple` instead of `typing.Tuple`). In case if built-in type is not available, but `collections.abc` provides interface, it must be used (e.g. `collections.abc.Awaitable` instead of `typing.Awaitable`, `collections.abc.Iterable` instead of `typing.Iterable`). For union classes, the bar operator (`|`) must be used (e.g. `float | int` instead of `typing.Union[float, int]`), which also includes `typing.Optional` (e.g. `str | None` instead of `Optional[str]`).
* `classmethod` should (almost) always return `Self`. In case of mypy warning (e.g. if there is a branch in which function doesn't return the instance of `cls`) consider using `staticmethod` instead. * `classmethod` should (almost) always return `Self`. In case of mypy warning (e.g. if there is a branch in which function doesn't return the instance of `cls`) consider using `staticmethod` instead.
* Class attributes must be decorated as `ClassVar[...]`.
* Recommended order of function definitions in class: * Recommended order of function definitions in class:
```python ```python
@@ -124,7 +125,7 @@ Again, the most checks can be performed by `tox` command, though some additional
def __hash__(self) -> int: ... # basically any magic (or look-alike) method def __hash__(self) -> int: ... # basically any magic (or look-alike) method
``` ```
Methods inside one group should be ordered alphabetically, the only exceptions are `__init__` (`__post_init__` for dataclasses), `__new__` and `__del__` methods which should be defined first. For test methods it is recommended to follow the order in which functions are defined. Methods inside one group should be ordered alphabetically, the only exceptions are `__init__` (`__post_init__` for dataclasses), `__new__` and `__del__` methods which should be defined first. For test methods it is recommended to follow the order in which functions are defined. Same idea applies to frontend classes.
Though, we would like to highlight abstract methods (i.e. ones which raise `NotImplementedError`), we still keep in global order at the moment. Though, we would like to highlight abstract methods (i.e. ones which raise `NotImplementedError`), we still keep in global order at the moment.
@@ -164,10 +165,16 @@ Again, the most checks can be performed by `tox` command, though some additional
# Blank line again and package imports # Blank line again and package imports
from ahriman.core.configuration import Configuration from ahriman.core.configuration import Configuration
# Multiline import example
from ahriman.core.database.operations import (
AuthOperations,
BuildOperations,
)
``` ```
* One file should define only one class, exception is class satellites in case if file length remains less than 400 lines. * Imports goes in alphabetical order, no relative imports allowed. Same rule applies to frontend classes.
* It is possible to create file which contains some functions (e.g. `ahriman.core.util`), but in this case you would need to define `__all__` attribute. * One file should define only one class, exception is class satellites in case if file length remains less than 400 lines. Same rule applies to frontend classes.
* It is possible to create file which contains some functions (e.g. `ahriman.core.utils`), but in this case you would need to define `__all__` attribute.
* The file size mentioned above must be applicable in general. In case of big classes consider splitting them into traits. Note, however, that `pylint` includes comments and docstrings into counter, thus you need to check file size by other tools. * The file size mentioned above must be applicable in general. In case of big classes consider splitting them into traits. Note, however, that `pylint` includes comments and docstrings into counter, thus you need to check file size by other tools.
* No global variable is allowed outside of `ahriman` module. `ahriman.core.context` is also special case. * No global variable is allowed outside of `ahriman` module. `ahriman.core.context` is also special case.
* Single quotes are not allowed. The reason behind this restriction is the fact that docstrings must be written by using double quotes only, and we would like to make style consistent. * Single quotes are not allowed. The reason behind this restriction is the fact that docstrings must be written by using double quotes only, and we would like to make style consistent.
@@ -175,11 +182,10 @@ Again, the most checks can be performed by `tox` command, though some additional
* Web API methods must be documented by using `aiohttp_apispec` library. The schema testing mostly should be implemented in related view class tests. Recommended example for documentation (excluding comments): * Web API methods must be documented by using `aiohttp_apispec` library. The schema testing mostly should be implemented in related view class tests. Recommended example for documentation (excluding comments):
```python ```python
import aiohttp_apispec
from marshmallow import Schema, fields from marshmallow import Schema, fields
from ahriman.web.schemas import AuthSchema, ErrorSchema, PackageNameSchema, PaginationSchema from ahriman.web.apispec.decorators import apidocs
from ahriman.web.schemas import PackageNameSchema, PaginationSchema
from ahriman.web.views.base import BaseView from ahriman.web.views.base import BaseView
@@ -198,36 +204,31 @@ Again, the most checks can be performed by `tox` command, though some additional
POST_PERMISSION = ... POST_PERMISSION = ...
ROUTES = ... ROUTES = ...
@aiohttp_apispec.docs( @apidocs(
tags=["Tag"], tags=["Tag"],
summary="Do foo", summary="Do foo",
description="Extended description of the method which does foo", description="Extended description of the method which does foo",
responses={ error_400_enabled=True, # exception raised by this method
200: {"description": "Success response", "schema": ResponseSchema}, error_404_description="Repository is unknown",
204: {"description": "Success response"}, # example without json schema response schema=ResponseSchema, # leave empty if no responses here
400: {"description": "Bad data is supplied", "schema": ErrorSchema}, # exception raised by this method match_schema=PackageNameSchema,
401: {"description": "Authorization required", "schema": ErrorSchema}, # should be always presented query_schema=PaginationSchema,
403: {"description": "Access is forbidden", "schema": ErrorSchema}, # should be always presented body_schema=RequestSchema(many=True),
404: {"description": "Repository is unknown", "schema": ErrorSchema}, # include if BaseView.service() method is called
500: {"description": "Internal server error", "schema": ErrorSchema}, # should be always presented
},
security=[{"token": [POST_PERMISSION]}],
) )
@aiohttp_apispec.cookies_schema(AuthSchema) # should be always presented
@aiohttp_apispec.match_info_schema(PackageNameSchema)
@aiohttp_apispec.querystring_schema(PaginationSchema)
@aiohttp_apispec.json_schema(RequestSchema(many=True))
async def post(self) -> None: ... async def post(self) -> None: ...
``` ```
* It is allowed to change web API to add new fields or remove optional ones. However, in case of model changes, new API version must be introduced. * It is allowed to change web API to add new fields or remove optional ones. However, in case of model changes, new API version must be introduced.
* On the other hand, it is allowed to change method signatures, however, it is recommended to add new parameters as optional if possible. Deprecated API can be dropped during major release. * On the other hand, it is allowed to change method signatures, however, it is recommended to add new parameters as optional if possible. Deprecated API can be dropped during major release.
* Enumerations (`Enum` classes) are allowed and recommended. However, it is recommended to use `StrEnum` class if there are from/to string conversions and `IntEnum` otherwise. * Enumerations (`Enum` classes) are allowed and recommended. However, it is recommended to use `StrEnum` class if there are from/to string conversions and `IntEnum` otherwise.
* `Generator` return type is not allowed. Generator functions must return generic `Iterator` object. Documentation should be described as `Yields`, however, because of pylint checks. Unfortunately, `Iterable` return type is not available for generators also, because of specific `contextlib.contextmanager` case.
### Other checks ### Other checks
The projects also uses typing checks (provided by `mypy`) and some linter checks provided by `pylint` and `bandit`. Those checks must be passed successfully for any open pull requests. The projects also uses typing checks (provided by `mypy`) and some linter checks provided by `pylint` and `bandit`. Those checks must be passed successfully for any open pull requests.
Frontend checks normally are performed by `eslint` (e.g. `npx run eslint`).
## Developers how to ## Developers how to
### Run automated checks ### Run automated checks
@@ -23,6 +23,14 @@ sync_files_database = yes
; 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.
use_ahriman_cache = yes use_ahriman_cache = yes
[aur]
; Maximum amount of retries of HTTP requests.
max_retries = 3
; Retry exponential backoff.
retry_backoff = 1.0
; HTTP request timeout in seconds.
;timeout = 30
[build] [build]
; List of additional flags passed to archbuild command. ; List of additional flags passed to archbuild command.
;archbuild_flags = ;archbuild_flags =
@@ -43,9 +51,13 @@ triggers[] = ahriman.core.gitremote.RemotePullTrigger
triggers[] = ahriman.core.report.ReportTrigger triggers[] = ahriman.core.report.ReportTrigger
triggers[] = ahriman.core.upload.UploadTrigger triggers[] = ahriman.core.upload.UploadTrigger
triggers[] = ahriman.core.gitremote.RemotePushTrigger triggers[] = ahriman.core.gitremote.RemotePushTrigger
triggers[] = ahriman.core.housekeeping.LogsRotationTrigger
triggers[] = ahriman.core.housekeeping.ArchiveRotationTrigger
; List of well-known triggers. Used only for configuration purposes. ; List of well-known triggers. Used only for configuration purposes.
triggers_known[] = ahriman.core.gitremote.RemotePullTrigger triggers_known[] = ahriman.core.gitremote.RemotePullTrigger
triggers_known[] = ahriman.core.gitremote.RemotePushTrigger triggers_known[] = ahriman.core.gitremote.RemotePushTrigger
triggers_known[] = ahriman.core.housekeeping.ArchiveRotationTrigger
triggers_known[] = ahriman.core.housekeeping.LogsRotationTrigger
triggers_known[] = ahriman.core.report.ReportTrigger triggers_known[] = ahriman.core.report.ReportTrigger
triggers_known[] = ahriman.core.upload.UploadTrigger triggers_known[] = ahriman.core.upload.UploadTrigger
; Maximal age in seconds of the VCS packages before their version will be updated with its remote source. ; Maximal age in seconds of the VCS packages before their version will be updated with its remote source.
@@ -69,8 +81,12 @@ enabled = yes
; In case if unix sockets are used, it might point to the valid socket with encoded path, e.g.: ; In case if unix sockets are used, it might point to the valid socket with encoded path, e.g.:
; address = http+unix://%2Fvar%2Flib%2Fahriman%2Fsocket ; address = http+unix://%2Fvar%2Flib%2Fahriman%2Fsocket
;address = http://${web:host}:${web:port} ;address = http://${web:host}:${web:port}
; Maximum amount of retries of HTTP requests.
;max_retries = 0
; Optional password for authentication (if enabled). ; Optional password for authentication (if enabled).
;password = ;password =
; Retry exponential backoff.
;retry_backoff = 0.0
; Do not log HTTP errors if occurs. ; Do not log HTTP errors if occurs.
suppress_http_log_errors = yes suppress_http_log_errors = yes
; HTTP request timeout in seconds. ; HTTP request timeout in seconds.
@@ -212,6 +228,10 @@ templates[] = ${prefix}/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 =
; Maximum amount of retries of HTTP requests.
;max_retries = 0
; Retry exponential backoff.
;retry_backoff = 0.0
; Optional link to the RSS feed. ; Optional link to the RSS feed.
;rss_url = ;rss_url =
; Template name to be used. ; Template name to be used.
@@ -232,12 +252,16 @@ target =
[github] [github]
; Trigger type name. ; Trigger type name.
;type = github ;type = github
; Maximum amount of retries of HTTP requests.
;max_retries = 0
; GitHub repository owner username. ; GitHub repository owner username.
;owner = ;owner =
; GitHub API key. public_repo (repo) scope is required. ; GitHub API key. public_repo (repo) scope is required.
;password = ;password =
; GitHub repository name. ; GitHub repository name.
;repository = ;repository =
; Retry exponential backoff.
;retry_backoff = 0.0
; HTTP request timeout in seconds. ; HTTP request timeout in seconds.
;timeout = 30 ;timeout = 30
; Include repository name to release name (recommended). ; Include repository name to release name (recommended).
@@ -249,6 +273,10 @@ target =
[remote-service] [remote-service]
; Trigger type name. ; Trigger type name.
;type = remote-service ;type = remote-service
; Maximum amount of retries of HTTP requests.
;max_retries = 0
; Retry exponential backoff.
;retry_backoff = 0.0
; HTTP request timeout in seconds. ; HTTP request timeout in seconds.
;timeout = 30 ;timeout = 30
@@ -0,0 +1,7 @@
[archive]
; Keep amount of last built packages in archive. 0 means keep all packages
keep_built_packages = 1
[logs-rotation]
; Keep last build logs for each package
keep_last_logs = 5
@@ -26,10 +26,12 @@ formatter = syslog_format
args = ("/dev/log",) args = ("/dev/log",)
[formatter_generic_format] [formatter_generic_format]
format = [%(levelname)s %(asctime)s] [%(name)s]: %(message)s format = [{levelname} {asctime}] [{name}]: {message}
style = {
[formatter_syslog_format] [formatter_syslog_format]
format = [%(levelname)s] [%(name)s]: %(message)s format = [{levelname}] [{name}]: {message}
style = {
[logger_root] [logger_root]
level = DEBUG level = DEBUG
@@ -53,8 +53,7 @@ SigLevel = Database{% if has_repo_signed %}Required{% else %}Never{% endif %} Pa
data-show-search-clear-button="true" data-show-search-clear-button="true"
data-sortable="true" data-sortable="true"
data-sort-name="base" data-sort-name="base"
data-sort-order="asc" data-sort-order="asc">
data-toggle="table">
<thead class="table-primary"> <thead class="table-primary">
<tr> <tr>
<th data-sortable="true" data-switchable="false" data-field="name" data-filter-control="input" data-filter-control-placeholder="(any package)">package</th> <th data-sortable="true" data-switchable="false" data-field="name" data-filter-control="input" data-filter-control-placeholder="(any package)">package</th>
@@ -128,36 +127,38 @@ SigLevel = Database{% if has_repo_signed %}Required{% else %}Never{% endif %} Pa
} }
ready(_ => { ready(_ => {
table.on("created-controls.bs.table", _ => { table.bootstrapTable({
new easepick.create({ onCreatedControls: _ => {
element: document.querySelector(".bootstrap-table-filter-control-timestamp"), new easepick.create({
css: [ element: document.querySelector(".bootstrap-table-filter-control-timestamp"),
"https://cdn.jsdelivr.net/npm/@easepick/bundle@1.2.1/dist/index.css", css: [
], "https://cdn.jsdelivr.net/npm/@easepick/bundle@1.2.1/dist/index.css",
grid: 2, ],
calendars: 2, grid: 2,
autoApply: false, calendars: 2,
locale: { autoApply: false,
cancel: "Clear", locale: {
}, cancel: "Clear",
RangePlugin: { },
tooltip: false, RangePlugin: {
}, tooltip: false,
plugins: [ },
"RangePlugin", plugins: [
], "RangePlugin",
setup: picker => { ],
picker.on("select", _ => { table.bootstrapTable("triggerSearch"); }); setup: picker => {
// replace "Cancel" behaviour to "Clear" picker.on("select", _ => { table.bootstrapTable("triggerSearch"); });
picker.onClickCancelButton = element => { // replace "Cancel" behaviour to "Clear"
if (picker.isCancelButton(element)) { picker.onClickCancelButton = element => {
picker.clear(); if (picker.isCancelButton(element)) {
picker.hide(); picker.clear();
table.bootstrapTable("triggerSearch"); picker.hide();
} table.bootstrapTable("triggerSearch");
}; }
}, };
}); },
});
},
}); });
}); });
</script> </script>
@@ -1,23 +1,24 @@
<script src="https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js" crossorigin="anonymous" type="application/javascript"></script> <script src="https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js" crossorigin="anonymous" type="application/javascript"></script>
<script src="https://cdn.jsdelivr.net/npm/js-md5@0.8.3/src/md5.min.js" crossorigin="anonymous" type="application/javascript"></script> <script src="https://cdn.jsdelivr.net/npm/js-md5@0.8.3/src/md5.min.js" crossorigin="anonymous" type="application/javascript"></script>
<script src="https://cdn.jsdelivr.net/npm/tableexport.jquery.plugin@1.30.0/tableExport.min.js" crossorigin="anonymous" type="application/javascript"></script> <script src="https://cdn.jsdelivr.net/npm/tableexport.jquery.plugin@1.33.0/tableExport.min.js" crossorigin="anonymous" type="application/javascript"></script>
<script src="https://cdn.jsdelivr.net/npm/jquery-resizable-columns@0.2.3/dist/jquery.resizableColumns.min.js" crossorigin="anonymous" type="application/javascript"></script> <script src="https://cdn.jsdelivr.net/npm/jquery-resizable-columns@0.2.3/dist/jquery.resizableColumns.min.js" crossorigin="anonymous" type="application/javascript"></script>
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.8/dist/umd/popper.min.js" crossorigin="anonymous" type="application/javascript"></script> <script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.8/dist/umd/popper.min.js" crossorigin="anonymous" type="application/javascript"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.min.js" crossorigin="anonymous" type="application/javascript"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.7/dist/js/bootstrap.min.js" crossorigin="anonymous" type="application/javascript"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap-table@1.23.2/dist/bootstrap-table.min.js" crossorigin="anonymous" type="application/javascript"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap-table@1.24.1/dist/bootstrap-table.min.js" crossorigin="anonymous" type="application/javascript"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap-table@1.23.2/dist/extensions/export/bootstrap-table-export.min.js" crossorigin="anonymous" type="application/javascript"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap-table@1.24.1/dist/extensions/export/bootstrap-table-export.min.js" crossorigin="anonymous" type="application/javascript"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap-table@1.23.2/dist/extensions/resizable/bootstrap-table-resizable.js" crossorigin="anonymous" type="application/javascript"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap-table@1.24.1/dist/extensions/resizable/bootstrap-table-resizable.js" crossorigin="anonymous" type="application/javascript"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap-table@1.23.2/dist/extensions/filter-control/bootstrap-table-filter-control.js" crossorigin="anonymous" type="application/javascript"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap-table@1.24.1/dist/extensions/filter-control/bootstrap-table-filter-control.js" crossorigin="anonymous" type="application/javascript"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap-table@1.24.1/dist/extensions/cookie/bootstrap-table-cookie.min.js" crossorigin="anonymous" type="application/javascript"></script>
<script src="https://cdn.jsdelivr.net/npm/@easepick/bundle@1.2.1/dist/index.umd.min.js" crossorigin="anonymous" type="application/javascript"></script> <script src="https://cdn.jsdelivr.net/npm/@easepick/bundle@1.2.1/dist/index.umd.min.js" crossorigin="anonymous" type="application/javascript"></script>
<script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.10.0/build/highlight.min.js" crossorigin="anonymous" type="application/javascript"></script> <script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.11.1/build/highlight.min.js" crossorigin="anonymous" type="application/javascript"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.4/dist/chart.umd.min.js" crossorigin="anonymous" type="application/javascript"></script> <script src="https://cdn.jsdelivr.net/npm/chart.js@4.5.0/dist/chart.umd.min.js" crossorigin="anonymous" type="application/javascript"></script>
<script> <script>
async function copyToClipboard(text, button) { async function copyToClipboard(text, button) {
@@ -58,6 +59,28 @@
return value.includes(dataList[index].toLowerCase()); return value.includes(dataList[index].toLowerCase());
} }
function hasActiveSelection() {
return !document.getSelection().isCollapsed; // not sure if it is a valid way, but I guess so
}
function hasActiveDropdown() {
return Array.from(document.querySelectorAll(".dropdown-menu"))
.some(el => el.classList.contains("show"));
}
function hasActiveModal() {
return Array.from(document.querySelectorAll(".modal"))
.some(el => el.classList.contains("show"));
}
function headerClass(status) {
if (status === "pending") return ["bg-warning"];
if (status === "building") return ["bg-warning"];
if (status === "failed") return ["bg-danger", "text-white"];
if (status === "success") return ["bg-success", "text-white"];
return ["bg-secondary", "text-white"];
}
function listToTable(data) { function listToTable(data) {
return Array.from(new Set(data)) return Array.from(new Set(data))
.sort() .sort()
@@ -98,6 +121,12 @@
.catch(error => onFailure && onFailure(error)); .catch(error => onFailure && onFailure(error));
} }
function readOptional(extractor, callback) {
for (let value = extractor(); !!value; value = null) {
callback(value);
}
}
function ready(fn) { function ready(fn) {
if (document.readyState === "complete" || document.readyState === "interactive") { if (document.readyState === "complete" || document.readyState === "interactive") {
setTimeout(fn, 1); setTimeout(fn, 1);
@@ -106,6 +135,11 @@
} }
} }
function restoreAutoReloadSettings(toggle, intervalSelector) {
readOptional(() => localStorage.getItem(`ahriman-${toggle.id}-refresh-enabled`), value => toggle.checked = value === "true");
readOptional(() => localStorage.getItem(`ahriman-${toggle.id}-refresh-interval`), value => toggleActiveElement(intervalSelector, "interval", value));
}
function safe(string) { function safe(string) {
return String(string) return String(string)
.replace(/&/g, "&amp;") .replace(/&/g, "&amp;")
@@ -125,7 +159,89 @@
return element; return element;
} }
Date.prototype.toISOStringShort = function() { function toggleActiveElement(selector, dataType, value) {
const targetElement = selector.querySelector(`a[data-${dataType}="${value}"]`);
if (targetElement?.classList?.contains("active")) {
return; // element is already active, skip processing
}
Array.from(selector.children).forEach(il => {
Array.from(il.children).forEach(el => el.classList.remove("active"));
});
targetElement?.classList?.add("active");
}
function toggleAutoReload(toggle, interval, intervalSelector, callback) {
if (interval) {
toggle.checked = true; // toggle reload
} else {
interval = intervalSelector.querySelector(".active")?.dataset?.interval; // find active element
}
let intervalId = null;
if (interval) {
if (toggle.checked) {
// refresh UI
toggleActiveElement(intervalSelector, "interval", interval);
// finally create timer task
intervalId = setInterval(callback, interval);
}
} else {
toggle.checked = false; // no active interval found, disable toggle
}
localStorage.setItem(`ahriman-${toggle.id}-refresh-enabled`, toggle.checked);
localStorage.setItem(`ahriman-${toggle.id}-refresh-interval`, interval);
return intervalId;
}
function updateTable(table, rows, rowChangedKey) {
// instead of using load method here, we just update rows manually to avoid table reinitialization
const currentData = table.bootstrapTable("getData").reduce((accumulator, row) => {
accumulator[row.id] = {state: row["0"], key: rowChangedKey(row)};
return accumulator;
}, {});
// insert or update rows, skipping ones whose status hasn't changed
rows.forEach(row => {
if (Object.hasOwn(currentData, row.id)) {
if (rowChangedKey(row) === currentData[row.id].key) {
return;
}
row["0"] = currentData[row.id].state; // copy checkbox state
table.bootstrapTable("updateByUniqueId", {
id: row.id,
row: row,
replace: true,
});
} else {
table.bootstrapTable("insertRow", {index: 0, row: row});
}
});
// remove old rows
const newData = rows.map(value => value.id);
Object.keys(currentData).forEach(id => {
if (!newData.includes(id)) {
table.bootstrapTable("removeByUniqueId", id);
}
});
}
Array.prototype.equals = function (right, comparator) {
let index = this.length;
if (index !== right.length) {
return false;
}
while (index--) {
if (!comparator(this[index], right[index])) {
return false;
}
}
return true;
}
Date.prototype.toISOStringShort = function () {
const pad = number => String(number).padStart(2, "0"); const pad = number => String(number).padStart(2, "0");
return `${this.getFullYear()}-${pad(this.getMonth() + 1)}-${pad(this.getDate())} ${pad(this.getHours())}:${pad(this.getMinutes())}:${pad(this.getSeconds())}`; return `${this.getFullYear()}-${pad(this.getMonth() + 1)}-${pad(this.getDate())} ${pad(this.getHours())}:${pad(this.getMinutes())}:${pad(this.getSeconds())}`;
} }
@@ -1,15 +1,15 @@
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" crossorigin="anonymous" type="text/css"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.7/dist/css/bootstrap.min.css" crossorigin="anonymous" type="text/css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css" crossorigin="anonymous" type="text/css"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.13.1/font/bootstrap-icons.css" crossorigin="anonymous" type="text/css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-table@1.23.2/dist/bootstrap-table.min.css" crossorigin="anonymous" type="text/css"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-table@1.24.1/dist/bootstrap-table.min.css" crossorigin="anonymous" type="text/css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/jquery-resizable-columns@0.2.3/dist/jquery.resizableColumns.css" crossorigin="anonymous" type="text/css"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/jquery-resizable-columns@0.2.3/dist/jquery.resizableColumns.css" crossorigin="anonymous" type="text/css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-table@1.23.2/dist/extensions/filter-control/bootstrap-table-filter-control.css" crossorigin="anonymous" type="text/css"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-table@1.24.1/dist/extensions/filter-control/bootstrap-table-filter-control.css" crossorigin="anonymous" type="text/css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootswatch@5.3.3/dist/cosmo/bootstrap.min.css" crossorigin="anonymous" type="text/css"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootswatch@5.3.7/dist/cosmo/bootstrap.min.css" crossorigin="anonymous" type="text/css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.10.0/build/styles/github.min.css" crossorigin="anonymous" type="text/css"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.11.1/build/styles/github.min.css" crossorigin="anonymous" type="text/css">
<style> <style>
.pre-scrollable { .pre-scrollable {
@@ -1,6 +1,6 @@
# AUTOMATICALLY GENERATED by `shtab` # AUTOMATICALLY GENERATED by `shtab`
_shtab_ahriman_subparsers=('add' 'aur-search' 'check' 'clean' 'config' 'config-validate' 'copy' 'daemon' 'help' 'help-commands-unsafe' 'help-updates' 'help-version' 'init' 'key-import' 'package-add' 'package-changes' 'package-changes-remove' 'package-copy' 'package-remove' 'package-status' 'package-status-remove' 'package-status-update' 'package-update' 'patch-add' 'patch-list' 'patch-remove' 'patch-set-add' 'rebuild' 'remove' 'remove-unknown' 'repo-backup' 'repo-check' 'repo-clean' 'repo-config' 'repo-config-validate' 'repo-create-keyring' 'repo-create-mirrorlist' 'repo-daemon' 'repo-init' 'repo-rebuild' 'repo-remove-unknown' 'repo-report' 'repo-restore' 'repo-setup' 'repo-sign' 'repo-statistics' 'repo-status-update' 'repo-sync' 'repo-tree' 'repo-triggers' 'repo-update' 'report' 'run' 'search' 'service-clean' 'service-config' 'service-config-validate' 'service-key-import' 'service-repositories' 'service-run' 'service-setup' 'service-shell' 'service-tree-migrate' 'setup' 'shell' 'sign' 'status' 'status-update' 'sync' 'update' 'user-add' 'user-list' 'user-remove' 'version' 'web') _shtab_ahriman_subparsers=('add' 'aur-search' 'check' 'clean' 'config' 'config-validate' 'copy' 'daemon' 'help' 'help-commands-unsafe' 'help-updates' 'help-version' 'init' 'key-import' 'package-add' 'package-changes' 'package-changes-remove' 'package-copy' 'package-remove' 'package-status' 'package-status-remove' 'package-status-update' 'package-update' 'patch-add' 'patch-list' 'patch-remove' 'patch-set-add' 'rebuild' 'remove' 'remove-unknown' 'repo-backup' 'repo-check' 'repo-clean' 'repo-config' 'repo-config-validate' 'repo-create-keyring' 'repo-create-mirrorlist' 'repo-daemon' 'repo-init' 'repo-rebuild' 'repo-remove-unknown' 'repo-report' 'repo-restore' 'repo-setup' 'repo-sign' 'repo-statistics' 'repo-status-update' 'repo-sync' 'repo-tree' 'repo-triggers' 'repo-update' 'report' 'run' 'search' 'service-clean' 'service-config' 'service-config-validate' 'service-key-import' 'service-repositories' 'service-run' 'service-setup' 'service-shell' 'service-tree-migrate' 'setup' 'shell' 'sign' 'status' 'status-update' 'sync' 'update' 'user-add' 'user-list' 'user-remove' 'version' 'web' 'web-reload')
_shtab_ahriman_option_strings=('-h' '--help' '-a' '--architecture' '-c' '--configuration' '--force' '-l' '--lock' '--log-handler' '-q' '--quiet' '--report' '--no-report' '-r' '--repository' '--unsafe' '-V' '--version' '--wait-timeout') _shtab_ahriman_option_strings=('-h' '--help' '-a' '--architecture' '-c' '--configuration' '--force' '-l' '--lock' '--log-handler' '-q' '--quiet' '--report' '--no-report' '-r' '--repository' '--unsafe' '-V' '--version' '--wait-timeout')
_shtab_ahriman_add_option_strings=('-h' '--help' '--changes' '--no-changes' '--dependencies' '--no-dependencies' '-e' '--exit-code' '--increment' '--no-increment' '-n' '--now' '-y' '--refresh' '-s' '--source' '-u' '--username' '-v' '--variable') _shtab_ahriman_add_option_strings=('-h' '--help' '--changes' '--no-changes' '--dependencies' '--no-dependencies' '-e' '--exit-code' '--increment' '--no-increment' '-n' '--now' '-y' '--refresh' '-s' '--source' '-u' '--username' '-v' '--variable')
@@ -64,10 +64,10 @@ _shtab_ahriman_service_key_import_option_strings=('-h' '--help' '--key-server')
_shtab_ahriman_service_repositories_option_strings=('-h' '--help' '--id-only' '--no-id-only') _shtab_ahriman_service_repositories_option_strings=('-h' '--help' '--id-only' '--no-id-only')
_shtab_ahriman_service_run_option_strings=('-h' '--help') _shtab_ahriman_service_run_option_strings=('-h' '--help')
_shtab_ahriman_service_setup_option_strings=('-h' '--help' '--build-as-user' '--from-configuration' '--generate-salt' '--no-generate-salt' '--makeflags-jobs' '--no-makeflags-jobs' '--mirror' '--multilib' '--no-multilib' '--packager' '--server' '--sign-key' '--sign-target' '--web-port' '--web-unix-socket') _shtab_ahriman_service_setup_option_strings=('-h' '--help' '--build-as-user' '--from-configuration' '--generate-salt' '--no-generate-salt' '--makeflags-jobs' '--no-makeflags-jobs' '--mirror' '--multilib' '--no-multilib' '--packager' '--server' '--sign-key' '--sign-target' '--web-port' '--web-unix-socket')
_shtab_ahriman_service_shell_option_strings=('-h' '--help') _shtab_ahriman_service_shell_option_strings=('-h' '--help' '-o' '--output')
_shtab_ahriman_service_tree_migrate_option_strings=('-h' '--help') _shtab_ahriman_service_tree_migrate_option_strings=('-h' '--help')
_shtab_ahriman_setup_option_strings=('-h' '--help' '--build-as-user' '--from-configuration' '--generate-salt' '--no-generate-salt' '--makeflags-jobs' '--no-makeflags-jobs' '--mirror' '--multilib' '--no-multilib' '--packager' '--server' '--sign-key' '--sign-target' '--web-port' '--web-unix-socket') _shtab_ahriman_setup_option_strings=('-h' '--help' '--build-as-user' '--from-configuration' '--generate-salt' '--no-generate-salt' '--makeflags-jobs' '--no-makeflags-jobs' '--mirror' '--multilib' '--no-multilib' '--packager' '--server' '--sign-key' '--sign-target' '--web-port' '--web-unix-socket')
_shtab_ahriman_shell_option_strings=('-h' '--help') _shtab_ahriman_shell_option_strings=('-h' '--help' '-o' '--output')
_shtab_ahriman_sign_option_strings=('-h' '--help') _shtab_ahriman_sign_option_strings=('-h' '--help')
_shtab_ahriman_status_option_strings=('-h' '--help' '--ahriman' '-e' '--exit-code' '--info' '--no-info' '-s' '--status') _shtab_ahriman_status_option_strings=('-h' '--help' '--ahriman' '-e' '--exit-code' '--info' '--no-info' '-s' '--status')
_shtab_ahriman_status_update_option_strings=('-h' '--help' '-s' '--status') _shtab_ahriman_status_update_option_strings=('-h' '--help' '-s' '--status')
@@ -78,10 +78,11 @@ _shtab_ahriman_user_list_option_strings=('-h' '--help' '-e' '--exit-code' '-R' '
_shtab_ahriman_user_remove_option_strings=('-h' '--help') _shtab_ahriman_user_remove_option_strings=('-h' '--help')
_shtab_ahriman_version_option_strings=('-h' '--help') _shtab_ahriman_version_option_strings=('-h' '--help')
_shtab_ahriman_web_option_strings=('-h' '--help') _shtab_ahriman_web_option_strings=('-h' '--help')
_shtab_ahriman_web_reload_option_strings=('-h' '--help')
_shtab_ahriman_pos_0_choices=('add' 'aur-search' 'check' 'clean' 'config' 'config-validate' 'copy' 'daemon' 'help' 'help-commands-unsafe' 'help-updates' 'help-version' 'init' 'key-import' 'package-add' 'package-changes' 'package-changes-remove' 'package-copy' 'package-remove' 'package-status' 'package-status-remove' 'package-status-update' 'package-update' 'patch-add' 'patch-list' 'patch-remove' 'patch-set-add' 'rebuild' 'remove' 'remove-unknown' 'repo-backup' 'repo-check' 'repo-clean' 'repo-config' 'repo-config-validate' 'repo-create-keyring' 'repo-create-mirrorlist' 'repo-daemon' 'repo-init' 'repo-rebuild' 'repo-remove-unknown' 'repo-report' 'repo-restore' 'repo-setup' 'repo-sign' 'repo-statistics' 'repo-status-update' 'repo-sync' 'repo-tree' 'repo-triggers' 'repo-update' 'report' 'run' 'search' 'service-clean' 'service-config' 'service-config-validate' 'service-key-import' 'service-repositories' 'service-run' 'service-setup' 'service-shell' 'service-tree-migrate' 'setup' 'shell' 'sign' 'status' 'status-update' 'sync' 'update' 'user-add' 'user-list' 'user-remove' 'version' 'web') _shtab_ahriman_pos_0_choices=('add' 'aur-search' 'check' 'clean' 'config' 'config-validate' 'copy' 'daemon' 'help' 'help-commands-unsafe' 'help-updates' 'help-version' 'init' 'key-import' 'package-add' 'package-changes' 'package-changes-remove' 'package-copy' 'package-remove' 'package-status' 'package-status-remove' 'package-status-update' 'package-update' 'patch-add' 'patch-list' 'patch-remove' 'patch-set-add' 'rebuild' 'remove' 'remove-unknown' 'repo-backup' 'repo-check' 'repo-clean' 'repo-config' 'repo-config-validate' 'repo-create-keyring' 'repo-create-mirrorlist' 'repo-daemon' 'repo-init' 'repo-rebuild' 'repo-remove-unknown' 'repo-report' 'repo-restore' 'repo-setup' 'repo-sign' 'repo-statistics' 'repo-status-update' 'repo-sync' 'repo-tree' 'repo-triggers' 'repo-update' 'report' 'run' 'search' 'service-clean' 'service-config' 'service-config-validate' 'service-key-import' 'service-repositories' 'service-run' 'service-setup' 'service-shell' 'service-tree-migrate' 'setup' 'shell' 'sign' 'status' 'status-update' 'sync' 'update' 'user-add' 'user-list' 'user-remove' 'version' 'web' 'web-reload')
_shtab_ahriman___log_handler_choices=('console' 'syslog' 'journald') _shtab_ahriman___log_handler_choices=('console' 'syslog' 'journald')
_shtab_ahriman_add__s_choices=('auto' 'archive' 'aur' 'directory' 'local' 'remote' 'repository') _shtab_ahriman_add__s_choices=('auto' 'archive' 'aur' 'directory' 'local' 'remote' 'repository')
_shtab_ahriman_add___source_choices=('auto' 'archive' 'aur' 'directory' 'local' 'remote' 'repository') _shtab_ahriman_add___source_choices=('auto' 'archive' 'aur' 'directory' 'local' 'remote' 'repository')
@@ -572,6 +573,8 @@ _shtab_ahriman_version__h_nargs=0
_shtab_ahriman_version___help_nargs=0 _shtab_ahriman_version___help_nargs=0
_shtab_ahriman_web__h_nargs=0 _shtab_ahriman_web__h_nargs=0
_shtab_ahriman_web___help_nargs=0 _shtab_ahriman_web___help_nargs=0
_shtab_ahriman_web_reload__h_nargs=0
_shtab_ahriman_web_reload___help_nargs=0
# $1=COMP_WORDS[1] # $1=COMP_WORDS[1]
@@ -635,6 +638,7 @@ _set_new_action() {
# ${!x} -> ${hello} -> "world" # ${!x} -> ${hello} -> "world"
_shtab_ahriman() { _shtab_ahriman() {
local completing_word="${COMP_WORDS[COMP_CWORD]}" local completing_word="${COMP_WORDS[COMP_CWORD]}"
local previous_word="${COMP_WORDS[COMP_CWORD-1]}"
local completed_positional_actions local completed_positional_actions
local current_action local current_action
local current_action_args_start_index local current_action_args_start_index
@@ -673,6 +677,7 @@ _shtab_ahriman() {
if [[ "$current_action_nargs" != "*" ]] && \ if [[ "$current_action_nargs" != "*" ]] && \
[[ "$current_action_nargs" != "+" ]] && \ [[ "$current_action_nargs" != "+" ]] && \
[[ "$current_action_nargs" != "?" ]] && \
[[ "$current_action_nargs" != *"..." ]] && \ [[ "$current_action_nargs" != *"..." ]] && \
(( $word_index + 1 - $current_action_args_start_index - $pos_only >= \ (( $word_index + 1 - $current_action_args_start_index - $pos_only >= \
$current_action_nargs )); then $current_action_nargs )); then
@@ -691,6 +696,10 @@ _shtab_ahriman() {
if [[ $pos_only = 0 && "${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}") )
elif [[ "${previous_word}" == ">" || "${previous_word}" == ">>" ||
"${previous_word}" =~ ^[12]">" || "${previous_word}" =~ ^[12]">>" ]]; then
# handle redirection operators
COMPREPLY=( $(compgen -f -- "${completing_word}") )
else else
# use choices & compgen # use choices & compgen
local IFS=$'\n' # items may contain spaces, so delimit using newline local IFS=$'\n' # items may contain spaces, so delimit using newline
@@ -1,9 +1,9 @@
.TH AHRIMAN "1" "2024\-12\-01" "ahriman" "Generated Python Manual" .TH AHRIMAN "1" "2026\-03\-08" "ahriman 2.20.0" "ArcH linux ReposItory MANager"
.SH NAME .SH NAME
ahriman ahriman \- ArcH linux ReposItory MANager
.SH SYNOPSIS .SH SYNOPSIS
.B ahriman .B ahriman
[-h] [-a ARCHITECTURE] [-c CONFIGURATION] [--force] [-l LOCK] [--log-handler {console,syslog,journald}] [-q] [--report | --no-report] [-r REPOSITORY] [--unsafe] [-V] [--wait-timeout WAIT_TIMEOUT] {add,aur-search,check,clean,config,config-validate,copy,daemon,help,help-commands-unsafe,help-updates,help-version,init,key-import,package-add,package-changes,package-changes-remove,package-copy,package-remove,package-status,package-status-remove,package-status-update,package-update,patch-add,patch-list,patch-remove,patch-set-add,rebuild,remove,remove-unknown,repo-backup,repo-check,repo-clean,repo-config,repo-config-validate,repo-create-keyring,repo-create-mirrorlist,repo-daemon,repo-init,repo-rebuild,repo-remove-unknown,repo-report,repo-restore,repo-setup,repo-sign,repo-statistics,repo-status-update,repo-sync,repo-tree,repo-triggers,repo-update,report,run,search,service-clean,service-config,service-config-validate,service-key-import,service-repositories,service-run,service-setup,service-shell,service-tree-migrate,setup,shell,sign,status,status-update,sync,update,user-add,user-list,user-remove,version,web} ... [-h] [-a ARCHITECTURE] [-c CONFIGURATION] [--force] [-l LOCK] [--log-handler {console,syslog,journald}] [-q] [--report | --no-report] [-r REPOSITORY] [--unsafe] [-V] [--wait-timeout WAIT_TIMEOUT] {add,aur-search,check,clean,config,config-validate,copy,daemon,help,help-commands-unsafe,help-updates,help-version,init,key-import,package-add,package-changes,package-changes-remove,package-copy,package-remove,package-status,package-status-remove,package-status-update,package-update,patch-add,patch-list,patch-remove,patch-set-add,rebuild,remove,remove-unknown,repo-backup,repo-check,repo-clean,repo-config,repo-config-validate,repo-create-keyring,repo-create-mirrorlist,repo-daemon,repo-init,repo-rebuild,repo-remove-unknown,repo-report,repo-restore,repo-setup,repo-sign,repo-statistics,repo-status-update,repo-sync,repo-tree,repo-triggers,repo-update,report,run,search,service-clean,service-config,service-config-validate,service-key-import,service-repositories,service-run,service-setup,service-shell,service-tree-migrate,setup,shell,sign,status,status-update,sync,update,user-add,user-list,user-remove,version,web,web-reload} ...
.SH DESCRIPTION .SH DESCRIPTION
ArcH linux ReposItory MANager ArcH linux ReposItory MANager
@@ -193,11 +193,14 @@ remove user
.TP .TP
\fBahriman\fR \fI\,web\/\fR \fBahriman\fR \fI\,web\/\fR
web server web server
.TP
\fBahriman\fR \fI\,web\-reload\/\fR
reload configuration
.SH COMMAND \fI\,'ahriman aur\-search'\/\fR .SH COMMAND \fI\,'ahriman aur\-search'\/\fR
usage: ahriman aur\-search [\-h] [\-e] [\-\-info | \-\-no\-info] usage: ahriman aur\-search [\-h] [\-e] [\-\-info | \-\-no\-info]
[\-\-sort\-by {description,first_submitted,id,last_modified,maintainer,name,num_votes,out_of_date,package_base,package_base_id,popularity,repository,submitter,url,url_path,version}] [\-\-sort\-by {description,first_submitted,id,last_modified,maintainer,name,num_votes,out_of_date,package_base,package_base_id,popularity,repository,submitter,url,url_path,version}]
search [search ...] search [search ...]
search for package in AUR using API search for package in AUR using API
@@ -220,7 +223,7 @@ sort field by this field. In case if two packages have the same value of the spe
by name by name
.SH COMMAND \fI\,'ahriman help'\/\fR .SH COMMAND \fI\,'ahriman help'\/\fR
usage: ahriman help [\-h] [subcommand] usage: ahriman help [\-h] [subcommand]
show help message for application or command and exit show help message for application or command and exit
@@ -229,7 +232,7 @@ show help message for application or command and exit
show help message for specific command show help message for specific command
.SH COMMAND \fI\,'ahriman help\-commands\-unsafe'\/\fR .SH COMMAND \fI\,'ahriman help\-commands\-unsafe'\/\fR
usage: ahriman help\-commands\-unsafe [\-h] [subcommand ...] usage: ahriman help\-commands\-unsafe [\-h] [subcommand ...]
list unsafe commands as defined in default args list unsafe commands as defined in default args
@@ -239,7 +242,7 @@ instead of showing commands, just test command line for unsafe subcommand and re
otherwise otherwise
.SH COMMAND \fI\,'ahriman help\-updates'\/\fR .SH COMMAND \fI\,'ahriman help\-updates'\/\fR
usage: ahriman help\-updates [\-h] [\-e] usage: ahriman help\-updates [\-h] [\-e]
request AUR for current version and compare with current service version request AUR for current version and compare with current service version
@@ -249,15 +252,15 @@ request AUR for current version and compare with current service version
return non\-zero exit code if updates available return non\-zero exit code if updates available
.SH COMMAND \fI\,'ahriman help\-version'\/\fR .SH COMMAND \fI\,'ahriman help\-version'\/\fR
usage: ahriman help\-version [\-h] usage: ahriman help\-version [\-h]
print application and its dependencies versions print application and its dependencies versions
.SH COMMAND \fI\,'ahriman package\-add'\/\fR .SH COMMAND \fI\,'ahriman package\-add'\/\fR
usage: ahriman package\-add [\-h] [\-\-changes | \-\-no\-changes] [\-\-dependencies | \-\-no\-dependencies] [\-e] usage: ahriman package\-add [\-h] [\-\-changes | \-\-no\-changes] [\-\-dependencies | \-\-no\-dependencies] [\-e]
[\-\-increment | \-\-no\-increment] [\-n] [\-y] [\-\-increment | \-\-no\-increment] [\-n] [\-y]
[\-s {auto,archive,aur,directory,local,remote,repository}] [\-u USERNAME] [\-v VARIABLE] [\-s {auto,archive,aur,directory,local,remote,repository}] [\-u USERNAME] [\-v VARIABLE]
package [package ...] package [package ...]
add existing or new package to the build queue add existing or new package to the build queue
@@ -303,7 +306,7 @@ build as user
apply specified makepkg variables to the next build apply specified makepkg variables to the next build
.SH COMMAND \fI\,'ahriman package\-changes'\/\fR .SH COMMAND \fI\,'ahriman package\-changes'\/\fR
usage: ahriman package\-changes [\-h] [\-e] package usage: ahriman package\-changes [\-h] [\-e] package
retrieve package changes stored in database retrieve package changes stored in database
@@ -317,7 +320,7 @@ package base
return non\-zero exit status if result is empty return non\-zero exit status if result is empty
.SH COMMAND \fI\,'ahriman package\-changes\-remove'\/\fR .SH COMMAND \fI\,'ahriman package\-changes\-remove'\/\fR
usage: ahriman package\-changes\-remove [\-h] package usage: ahriman package\-changes\-remove [\-h] package
remove the package changes stored remotely remove the package changes stored remotely
@@ -326,7 +329,7 @@ remove the package changes stored remotely
package base package base
.SH COMMAND \fI\,'ahriman package\-copy'\/\fR .SH COMMAND \fI\,'ahriman package\-copy'\/\fR
usage: ahriman package\-copy [\-h] [\-e] [\-\-remove] source package [package ...] usage: ahriman package\-copy [\-h] [\-e] [\-\-remove] source package [package ...]
copy package and its metadata from another repository copy package and its metadata from another repository
@@ -348,7 +351,7 @@ return non\-zero exit status if result is empty
remove package from the source repository after remove package from the source repository after
.SH COMMAND \fI\,'ahriman package\-remove'\/\fR .SH COMMAND \fI\,'ahriman package\-remove'\/\fR
usage: ahriman package\-remove [\-h] package [package ...] usage: ahriman package\-remove [\-h] package [package ...]
remove package from the repository remove package from the repository
@@ -357,8 +360,8 @@ remove package from the repository
package name or base package name or base
.SH COMMAND \fI\,'ahriman package\-status'\/\fR .SH COMMAND \fI\,'ahriman package\-status'\/\fR
usage: ahriman package\-status [\-h] [\-\-ahriman] [\-e] [\-\-info | \-\-no\-info] [\-s {unknown,pending,building,failed,success}] usage: ahriman package\-status [\-h] [\-\-ahriman] [\-e] [\-\-info | \-\-no\-info] [\-s {unknown,pending,building,failed,success}]
[package ...] [package ...]
request status of the package request status of the package
@@ -384,7 +387,7 @@ show additional package information
filter packages by status filter packages by status
.SH COMMAND \fI\,'ahriman package\-status\-remove'\/\fR .SH COMMAND \fI\,'ahriman package\-status\-remove'\/\fR
usage: ahriman package\-status\-remove [\-h] package [package ...] usage: ahriman package\-status\-remove [\-h] package [package ...]
remove the package from the status page remove the package from the status page
@@ -393,7 +396,7 @@ remove the package from the status page
remove specified packages from status page remove specified packages from status page
.SH COMMAND \fI\,'ahriman package\-status\-update'\/\fR .SH COMMAND \fI\,'ahriman package\-status\-update'\/\fR
usage: ahriman package\-status\-update [\-h] [\-s {unknown,pending,building,failed,success}] [package ...] usage: ahriman package\-status\-update [\-h] [\-s {unknown,pending,building,failed,success}] [package ...]
update package status on the status page update package status on the status page
@@ -407,7 +410,7 @@ set status for specified packages. If no packages supplied, service status will
new package build status new package build status
.SH COMMAND \fI\,'ahriman patch\-add'\/\fR .SH COMMAND \fI\,'ahriman patch\-add'\/\fR
usage: ahriman patch\-add [\-h] package variable [patch] usage: ahriman patch\-add [\-h] package variable [patch]
create or update patched PKGBUILD function or variable create or update patched PKGBUILD function or variable
@@ -424,7 +427,7 @@ PKGBUILD variable or function name. If variable is a function, it must end with
path to file which contains function or variable value. If not set, the value will be read from stdin path to file which contains function or variable value. If not set, the value will be read from stdin
.SH COMMAND \fI\,'ahriman patch\-list'\/\fR .SH COMMAND \fI\,'ahriman patch\-list'\/\fR
usage: ahriman patch\-list [\-h] [\-e] [\-v VARIABLE] package usage: ahriman patch\-list [\-h] [\-e] [\-v VARIABLE] package
list available patches for the package list available patches for the package
@@ -442,7 +445,7 @@ return non\-zero exit status if result is empty
if set, show only patches for specified PKGBUILD variables if set, show only patches for specified PKGBUILD variables
.SH COMMAND \fI\,'ahriman patch\-remove'\/\fR .SH COMMAND \fI\,'ahriman patch\-remove'\/\fR
usage: ahriman patch\-remove [\-h] [\-v VARIABLE] package usage: ahriman patch\-remove [\-h] [\-v VARIABLE] package
remove patches for the package remove patches for the package
@@ -457,7 +460,7 @@ should be used for single\-function patches in case if you wold like to remove o
if not set, it will remove all patches related to the package if not set, it will remove all patches related to the package
.SH COMMAND \fI\,'ahriman patch\-set\-add'\/\fR .SH COMMAND \fI\,'ahriman patch\-set\-add'\/\fR
usage: ahriman patch\-set\-add [\-h] [\-t TRACK] package usage: ahriman patch\-set\-add [\-h] [\-t TRACK] package
create or update source patches create or update source patches
@@ -471,7 +474,7 @@ path to directory with changed files for patch addition/update
files which has to be tracked files which has to be tracked
.SH COMMAND \fI\,'ahriman repo\-backup'\/\fR .SH COMMAND \fI\,'ahriman repo\-backup'\/\fR
usage: ahriman repo\-backup [\-h] path usage: ahriman repo\-backup [\-h] path
backup repository settings and database backup repository settings and database
@@ -480,9 +483,9 @@ backup repository settings and database
path of the output archive path of the output archive
.SH COMMAND \fI\,'ahriman repo\-check'\/\fR .SH COMMAND \fI\,'ahriman repo\-check'\/\fR
usage: ahriman repo\-check [\-h] [\-\-changes | \-\-no\-changes] [\-\-check\-files | \-\-no\-check\-files] [\-e] [\-\-vcs | \-\-no\-vcs] usage: ahriman repo\-check [\-h] [\-\-changes | \-\-no\-changes] [\-\-check\-files | \-\-no\-check\-files] [\-e] [\-\-vcs | \-\-no\-vcs]
[\-y] [\-y]
[package ...] [package ...]
check for packages updates. Same as repo\-update \-\-dry\-run \-\-no\-manual check for packages updates. Same as repo\-update \-\-dry\-run \-\-no\-manual
@@ -512,20 +515,20 @@ fetch actual version of VCS packages
download fresh package databases from the mirror before actions, \-yy to force refresh even if up to date download fresh package databases from the mirror before actions, \-yy to force refresh even if up to date
.SH COMMAND \fI\,'ahriman repo\-create\-keyring'\/\fR .SH COMMAND \fI\,'ahriman repo\-create\-keyring'\/\fR
usage: ahriman repo\-create\-keyring [\-h] usage: ahriman repo\-create\-keyring [\-h]
create package which contains list of trusted keys as set by configuration. Note, that this action will only create package, the package itself has to be built manually create package which contains list of trusted keys as set by configuration. Note, that this action will only create package, the package itself has to be built manually
.SH COMMAND \fI\,'ahriman repo\-create\-mirrorlist'\/\fR .SH COMMAND \fI\,'ahriman repo\-create\-mirrorlist'\/\fR
usage: ahriman repo\-create\-mirrorlist [\-h] usage: ahriman repo\-create\-mirrorlist [\-h]
create package which contains list of available mirrors as set by configuration. Note, that this action will only create package, the package itself has to be built manually create package which contains list of available mirrors as set by configuration. Note, that this action will only create package, the package itself has to be built manually
.SH COMMAND \fI\,'ahriman repo\-daemon'\/\fR .SH COMMAND \fI\,'ahriman repo\-daemon'\/\fR
usage: ahriman repo\-daemon [\-h] [\-i INTERVAL] [\-\-aur | \-\-no\-aur] [\-\-changes | \-\-no\-changes] usage: ahriman repo\-daemon [\-h] [\-i INTERVAL] [\-\-aur | \-\-no\-aur] [\-\-changes | \-\-no\-changes]
[\-\-check\-files | \-\-no\-check\-files] [\-\-dependencies | \-\-no\-dependencies] [\-\-dry\-run] [\-\-check\-files | \-\-no\-check\-files] [\-\-dependencies | \-\-no\-dependencies] [\-\-dry\-run]
[\-\-increment | \-\-no\-increment] [\-\-local | \-\-no\-local] [\-\-manual | \-\-no\-manual] [\-\-increment | \-\-no\-increment] [\-\-local | \-\-no\-local] [\-\-manual | \-\-no\-manual]
[\-\-partitions | \-\-no\-partitions] [\-u USERNAME] [\-\-vcs | \-\-no\-vcs] [\-y] [\-\-partitions | \-\-no\-partitions] [\-u USERNAME] [\-\-vcs | \-\-no\-vcs] [\-y]
start process which periodically will run update process start process which periodically will run update process
@@ -583,8 +586,8 @@ fetch actual version of VCS packages
download fresh package databases from the mirror before actions, \-yy to force refresh even if up to date download fresh package databases from the mirror before actions, \-yy to force refresh even if up to date
.SH COMMAND \fI\,'ahriman repo\-rebuild'\/\fR .SH COMMAND \fI\,'ahriman repo\-rebuild'\/\fR
usage: ahriman repo\-rebuild [\-h] [\-\-depends\-on DEPENDS_ON] [\-\-dry\-run] [\-\-from\-database] [\-\-increment | \-\-no\-increment] usage: ahriman repo\-rebuild [\-h] [\-\-depends\-on DEPENDS_ON] [\-\-dry\-run] [\-\-from\-database] [\-\-increment | \-\-no\-increment]
[\-e] [\-s {unknown,pending,building,failed,success}] [\-u USERNAME] [\-e] [\-s {unknown,pending,building,failed,success}] [\-u USERNAME]
force rebuild whole repository force rebuild whole repository
@@ -620,7 +623,7 @@ filter packages by status. Requires \-\-from\-database to be set
build as user build as user
.SH COMMAND \fI\,'ahriman repo\-remove\-unknown'\/\fR .SH COMMAND \fI\,'ahriman repo\-remove\-unknown'\/\fR
usage: ahriman repo\-remove\-unknown [\-h] [\-\-dry\-run] usage: ahriman repo\-remove\-unknown [\-h] [\-\-dry\-run]
remove packages which are missing in AUR and do not have local PKGBUILDs remove packages which are missing in AUR and do not have local PKGBUILDs
@@ -630,12 +633,12 @@ remove packages which are missing in AUR and do not have local PKGBUILDs
just perform check for packages without removal just perform check for packages without removal
.SH COMMAND \fI\,'ahriman repo\-report'\/\fR .SH COMMAND \fI\,'ahriman repo\-report'\/\fR
usage: ahriman repo\-report [\-h] usage: ahriman repo\-report [\-h]
generate repository report according to current settings generate repository report according to current settings
.SH COMMAND \fI\,'ahriman repo\-restore'\/\fR .SH COMMAND \fI\,'ahriman repo\-restore'\/\fR
usage: ahriman repo\-restore [\-h] [\-o OUTPUT] path usage: ahriman repo\-restore [\-h] [\-o OUTPUT] path
restore settings and database restore settings and database
@@ -649,7 +652,7 @@ path of the input archive
root path of the extracted files root path of the extracted files
.SH COMMAND \fI\,'ahriman repo\-sign'\/\fR .SH COMMAND \fI\,'ahriman repo\-sign'\/\fR
usage: ahriman repo\-sign [\-h] [package ...] usage: ahriman repo\-sign [\-h] [package ...]
(re\-)sign packages and repository database according to current settings (re\-)sign packages and repository database according to current settings
@@ -658,10 +661,10 @@ usage: ahriman repo\-sign [\-h] [package ...]
sign only specified packages sign only specified packages
.SH COMMAND \fI\,'ahriman repo\-statistics'\/\fR .SH COMMAND \fI\,'ahriman repo\-statistics'\/\fR
usage: ahriman repo\-statistics [\-h] [\-\-chart CHART] usage: ahriman repo\-statistics [\-h] [\-\-chart CHART]
[\-e {package\-outdated,package\-removed,package\-update\-failed,package\-updated}] [\-e {package\-outdated,package\-removed,package\-update\-failed,package\-updated}]
[\-\-from\-date FROM_DATE] [\-\-limit LIMIT] [\-\-offset OFFSET] [\-\-to\-date TO_DATE] [\-\-from\-date FROM_DATE] [\-\-limit LIMIT] [\-\-offset OFFSET] [\-\-to\-date TO_DATE]
[package] [package]
fetch repository statistics fetch repository statistics
@@ -695,7 +698,7 @@ skip specified amount of events
only fetch events which are older than the date only fetch events which are older than the date
.SH COMMAND \fI\,'ahriman repo\-status\-update'\/\fR .SH COMMAND \fI\,'ahriman repo\-status\-update'\/\fR
usage: ahriman repo\-status\-update [\-h] [\-s {unknown,pending,building,failed,success}] usage: ahriman repo\-status\-update [\-h] [\-s {unknown,pending,building,failed,success}]
update repository status on the status page update repository status on the status page
@@ -705,12 +708,12 @@ update repository status on the status page
new status new status
.SH COMMAND \fI\,'ahriman repo\-sync'\/\fR .SH COMMAND \fI\,'ahriman repo\-sync'\/\fR
usage: ahriman repo\-sync [\-h] usage: ahriman repo\-sync [\-h]
sync repository files to remote server according to current settings sync repository files to remote server according to current settings
.SH COMMAND \fI\,'ahriman repo\-tree'\/\fR .SH COMMAND \fI\,'ahriman repo\-tree'\/\fR
usage: ahriman repo\-tree [\-h] [\-p PARTITIONS] usage: ahriman repo\-tree [\-h] [\-p PARTITIONS]
dump repository tree based on packages dependencies dump repository tree based on packages dependencies
@@ -720,7 +723,7 @@ dump repository tree based on packages dependencies
also divide packages by independent partitions also divide packages by independent partitions
.SH COMMAND \fI\,'ahriman repo\-triggers'\/\fR .SH COMMAND \fI\,'ahriman repo\-triggers'\/\fR
usage: ahriman repo\-triggers [\-h] [trigger ...] usage: ahriman repo\-triggers [\-h] [trigger ...]
run triggers on empty build result as configured by settings run triggers on empty build result as configured by settings
@@ -729,10 +732,10 @@ run triggers on empty build result as configured by settings
instead of running all triggers as set by configuration, just process specified ones in order of mention instead of running all triggers as set by configuration, just process specified ones in order of mention
.SH COMMAND \fI\,'ahriman repo\-update'\/\fR .SH COMMAND \fI\,'ahriman repo\-update'\/\fR
usage: ahriman repo\-update [\-h] [\-\-aur | \-\-no\-aur] [\-\-changes | \-\-no\-changes] [\-\-check\-files | \-\-no\-check\-files] usage: ahriman repo\-update [\-h] [\-\-aur | \-\-no\-aur] [\-\-changes | \-\-no\-changes] [\-\-check\-files | \-\-no\-check\-files]
[\-\-dependencies | \-\-no\-dependencies] [\-\-dry\-run] [\-e] [\-\-increment | \-\-no\-increment] [\-\-dependencies | \-\-no\-dependencies] [\-\-dry\-run] [\-e] [\-\-increment | \-\-no\-increment]
[\-\-local | \-\-no\-local] [\-\-manual | \-\-no\-manual] [\-u USERNAME] [\-\-vcs | \-\-no\-vcs] [\-y] [\-\-local | \-\-no\-local] [\-\-manual | \-\-no\-manual] [\-u USERNAME] [\-\-vcs | \-\-no\-vcs] [\-y]
[package ...] [package ...]
check for packages updates and run build process if requested check for packages updates and run build process if requested
@@ -790,8 +793,8 @@ fetch actual version of VCS packages
download fresh package databases from the mirror before actions, \-yy to force refresh even if up to date download fresh package databases from the mirror before actions, \-yy to force refresh even if up to date
.SH COMMAND \fI\,'ahriman service\-clean'\/\fR .SH COMMAND \fI\,'ahriman service\-clean'\/\fR
usage: ahriman service\-clean [\-h] [\-\-cache | \-\-no\-cache] [\-\-chroot | \-\-no\-chroot] [\-\-manual | \-\-no\-manual] usage: ahriman service\-clean [\-h] [\-\-cache | \-\-no\-cache] [\-\-chroot | \-\-no\-chroot] [\-\-manual | \-\-no\-manual]
[\-\-packages | \-\-no\-packages] [\-\-pacman | \-\-no\-pacman] [\-\-packages | \-\-no\-packages] [\-\-pacman | \-\-no\-pacman]
remove local caches remove local caches
@@ -817,7 +820,7 @@ clear directory with built packages
clear directory with pacman local database cache clear directory with pacman local database cache
.SH COMMAND \fI\,'ahriman service\-config'\/\fR .SH COMMAND \fI\,'ahriman service\-config'\/\fR
usage: ahriman service\-config [\-h] [\-\-info | \-\-no\-info] [\-\-secure | \-\-no\-secure] [section] [key] usage: ahriman service\-config [\-h] [\-\-info | \-\-no\-info] [\-\-secure | \-\-no\-secure] [section] [key]
dump configuration for the specified architecture dump configuration for the specified architecture
@@ -839,7 +842,7 @@ show additional information, e.g. configuration files
hide passwords and secrets from output hide passwords and secrets from output
.SH COMMAND \fI\,'ahriman service\-config\-validate'\/\fR .SH COMMAND \fI\,'ahriman service\-config\-validate'\/\fR
usage: ahriman service\-config\-validate [\-h] [\-e] usage: ahriman service\-config\-validate [\-h] [\-e]
validate configuration and print found errors validate configuration and print found errors
@@ -849,7 +852,7 @@ validate configuration and print found errors
return non\-zero exit status if configuration is invalid return non\-zero exit status if configuration is invalid
.SH COMMAND \fI\,'ahriman service\-key\-import'\/\fR .SH COMMAND \fI\,'ahriman service\-key\-import'\/\fR
usage: ahriman service\-key\-import [\-h] [\-\-key\-server KEY_SERVER] key usage: ahriman service\-key\-import [\-h] [\-\-key\-server KEY_SERVER] key
import PGP key from public sources to the repository user import PGP key from public sources to the repository user
@@ -863,7 +866,7 @@ PGP key to import from public server
key server for key import key server for key import
.SH COMMAND \fI\,'ahriman service\-repositories'\/\fR .SH COMMAND \fI\,'ahriman service\-repositories'\/\fR
usage: ahriman service\-repositories [\-h] [\-\-id\-only | \-\-no\-id\-only] usage: ahriman service\-repositories [\-h] [\-\-id\-only | \-\-no\-id\-only]
list all available repositories list all available repositories
@@ -873,7 +876,7 @@ list all available repositories
show machine readable identifier instead show machine readable identifier instead
.SH COMMAND \fI\,'ahriman service\-run'\/\fR .SH COMMAND \fI\,'ahriman service\-run'\/\fR
usage: ahriman service\-run [\-h] command [command ...] usage: ahriman service\-run [\-h] command [command ...]
run multiple commands on success run of the previous command run multiple commands on success run of the previous command
@@ -882,11 +885,11 @@ run multiple commands on success run of the previous command
command to be run (quoted) without ``ahriman`` command to be run (quoted) without ``ahriman``
.SH COMMAND \fI\,'ahriman service\-setup'\/\fR .SH COMMAND \fI\,'ahriman service\-setup'\/\fR
usage: ahriman service\-setup [\-h] [\-\-build\-as\-user BUILD_AS_USER] [\-\-from\-configuration FROM_CONFIGURATION] usage: ahriman service\-setup [\-h] [\-\-build\-as\-user BUILD_AS_USER] [\-\-from\-configuration FROM_CONFIGURATION]
[\-\-generate\-salt | \-\-no\-generate\-salt] [\-\-makeflags\-jobs | \-\-no\-makeflags\-jobs] [\-\-generate\-salt | \-\-no\-generate\-salt] [\-\-makeflags\-jobs | \-\-no\-makeflags\-jobs]
[\-\-mirror MIRROR] [\-\-multilib | \-\-no\-multilib] \-\-packager PACKAGER [\-\-server SERVER] [\-\-mirror MIRROR] [\-\-multilib | \-\-no\-multilib] \-\-packager PACKAGER [\-\-server SERVER]
[\-\-sign\-key SIGN_KEY] [\-\-sign\-target {disabled,packages,repository}] [\-\-web\-port WEB_PORT] [\-\-sign\-key SIGN_KEY] [\-\-sign\-target {disabled,packages,repository}] [\-\-web\-port WEB_PORT]
[\-\-web\-unix\-socket WEB_UNIX_SOCKET] [\-\-web\-unix\-socket WEB_UNIX_SOCKET]
create initial service configuration, requires root create initial service configuration, requires root
@@ -940,7 +943,7 @@ port of the web service
path to unix socket used for interprocess communications path to unix socket used for interprocess communications
.SH COMMAND \fI\,'ahriman service\-shell'\/\fR .SH COMMAND \fI\,'ahriman service\-shell'\/\fR
usage: ahriman service\-shell [\-h] [code] usage: ahriman service\-shell [\-h] [\-o OUTPUT] [code]
drop into python shell drop into python shell
@@ -948,14 +951,19 @@ drop into python shell
\fBcode\fR \fBcode\fR
instead of dropping into shell, just execute the specified code instead of dropping into shell, just execute the specified code
.SH OPTIONS \fI\,'ahriman service\-shell'\/\fR
.TP
\fB\-o\fR \fI\,OUTPUT\/\fR, \fB\-\-output\fR \fI\,OUTPUT\/\fR
output commands and result to the file
.SH COMMAND \fI\,'ahriman service\-tree\-migrate'\/\fR .SH COMMAND \fI\,'ahriman service\-tree\-migrate'\/\fR
usage: ahriman service\-tree\-migrate [\-h] usage: ahriman service\-tree\-migrate [\-h]
migrate repository tree between versions migrate repository tree between versions
.SH COMMAND \fI\,'ahriman user\-add'\/\fR .SH COMMAND \fI\,'ahriman user\-add'\/\fR
usage: ahriman user\-add [\-h] [\-\-key KEY] [\-\-packager PACKAGER] [\-p PASSWORD] [\-R {unauthorized,read,reporter,full}] usage: ahriman user\-add [\-h] [\-\-key KEY] [\-\-packager PACKAGER] [\-p PASSWORD] [\-R {unauthorized,read,reporter,full}]
username username
update user for web services with the given password and role. In case if password was not entered it will be asked interactively update user for web services with the given password and role. In case if password was not entered it will be asked interactively
@@ -982,7 +990,7 @@ authorization type.
user access level user access level
.SH COMMAND \fI\,'ahriman user\-list'\/\fR .SH COMMAND \fI\,'ahriman user\-list'\/\fR
usage: ahriman user\-list [\-h] [\-e] [\-R {unauthorized,read,reporter,full}] [username] usage: ahriman user\-list [\-h] [\-e] [\-R {unauthorized,read,reporter,full}] [username]
list users from the user mapping and their roles list users from the user mapping and their roles
@@ -1000,7 +1008,7 @@ return non\-zero exit status if result is empty
filter users by role filter users by role
.SH COMMAND \fI\,'ahriman user\-remove'\/\fR .SH COMMAND \fI\,'ahriman user\-remove'\/\fR
usage: ahriman user\-remove [\-h] username usage: ahriman user\-remove [\-h] username
remove user from the user mapping and update the configuration remove user from the user mapping and update the configuration
@@ -1009,10 +1017,15 @@ remove user from the user mapping and update the configuration
username for web service username for web service
.SH COMMAND \fI\,'ahriman web'\/\fR .SH COMMAND \fI\,'ahriman web'\/\fR
usage: ahriman web [\-h] usage: ahriman web [\-h]
start web server start web server
.SH COMMAND \fI\,'ahriman web\-reload'\/\fR
usage: ahriman web\-reload [\-h]
reload web server configuration
.SH COMMENTS .SH COMMENTS
Quick setup command (replace repository name, architecture and packager as needed): Quick setup command (replace repository name, architecture and packager as needed):
@@ -80,6 +80,7 @@ _shtab_ahriman_commands() {
"user-remove:remove user from the user mapping and update the configuration" "user-remove:remove user from the user mapping and update the configuration"
"version:print application and its dependencies versions" "version:print application and its dependencies versions"
"web:start web server" "web:start web server"
"web-reload:reload web server configuration"
) )
_describe 'ahriman commands' _commands _describe 'ahriman commands' _commands
} }
@@ -99,6 +100,9 @@ _shtab_ahriman_options=(
"--wait-timeout[wait for lock to be free. Negative value will lead to immediate application run even if there is lock file. In case of zero value, the application will wait infinitely (default\: -1)]:wait_timeout:" "--wait-timeout[wait for lock to be free. Negative value will lead to immediate application run even if there is lock file. In case of zero value, the application will wait infinitely (default\: -1)]:wait_timeout:"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_defaults_added=0
_shtab_ahriman_add_options=( _shtab_ahriman_add_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
{--changes,--no-changes}"[calculate changes from the latest known commit if available (default\: True)]:changes:" {--changes,--no-changes}"[calculate changes from the latest known commit if available (default\: True)]:changes:"
@@ -113,6 +117,9 @@ _shtab_ahriman_add_options=(
"(*):package source (base name, path to local files, remote URL):" "(*):package source (base name, path to local files, remote URL):"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_add_defaults_added=0
_shtab_ahriman_aur_search_options=( _shtab_ahriman_aur_search_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
{-e,--exit-code}"[return non-zero exit status if result is empty (default\: False)]" {-e,--exit-code}"[return non-zero exit status if result is empty (default\: False)]"
@@ -121,6 +128,9 @@ _shtab_ahriman_aur_search_options=(
"(*):search terms, can be specified multiple times, the result will match all terms:" "(*):search terms, can be specified multiple times, the result will match all terms:"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_aur_search_defaults_added=0
_shtab_ahriman_check_options=( _shtab_ahriman_check_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
{--changes,--no-changes}"[calculate changes from the latest known commit if available (default\: True)]:changes:" {--changes,--no-changes}"[calculate changes from the latest known commit if available (default\: True)]:changes:"
@@ -131,6 +141,9 @@ _shtab_ahriman_check_options=(
"(*)::filter check by package base (default\: None):" "(*)::filter check by package base (default\: None):"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_check_defaults_added=0
_shtab_ahriman_clean_options=( _shtab_ahriman_clean_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
{--cache,--no-cache}"[clear directory with package caches (default\: False)]:cache:" {--cache,--no-cache}"[clear directory with package caches (default\: False)]:cache:"
@@ -140,6 +153,9 @@ _shtab_ahriman_clean_options=(
{--pacman,--no-pacman}"[clear directory with pacman local database cache (default\: False)]:pacman:" {--pacman,--no-pacman}"[clear directory with pacman local database cache (default\: False)]:pacman:"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_clean_defaults_added=0
_shtab_ahriman_config_options=( _shtab_ahriman_config_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
{--info,--no-info}"[show additional information, e.g. configuration files (default\: True)]:info:" {--info,--no-info}"[show additional information, e.g. configuration files (default\: True)]:info:"
@@ -148,11 +164,17 @@ _shtab_ahriman_config_options=(
":filter settings by key (default\: None):" ":filter settings by key (default\: None):"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_config_defaults_added=0
_shtab_ahriman_config_validate_options=( _shtab_ahriman_config_validate_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
{-e,--exit-code}"[return non-zero exit status if configuration is invalid (default\: False)]" {-e,--exit-code}"[return non-zero exit status if configuration is invalid (default\: False)]"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_config_validate_defaults_added=0
_shtab_ahriman_copy_options=( _shtab_ahriman_copy_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
{-e,--exit-code}"[return non-zero exit status if result is empty (default\: False)]" {-e,--exit-code}"[return non-zero exit status if result is empty (default\: False)]"
@@ -161,6 +183,9 @@ _shtab_ahriman_copy_options=(
"(*):package base:" "(*):package base:"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_copy_defaults_added=0
_shtab_ahriman_daemon_options=( _shtab_ahriman_daemon_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
{-i,--interval}"[interval between runs in seconds (default\: 43200)]:interval:" {-i,--interval}"[interval between runs in seconds (default\: 43200)]:interval:"
@@ -178,25 +203,40 @@ _shtab_ahriman_daemon_options=(
"*"{-y,--refresh}"[download fresh package databases from the mirror before actions, -yy to force refresh even if up to date (default\: False)]" "*"{-y,--refresh}"[download fresh package databases from the mirror before actions, -yy to force refresh even if up to date (default\: False)]"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_daemon_defaults_added=0
_shtab_ahriman_help_options=( _shtab_ahriman_help_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
":show help message for specific command (default\: None):" ":show help message for specific command (default\: None):"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_help_defaults_added=0
_shtab_ahriman_help_commands_unsafe_options=( _shtab_ahriman_help_commands_unsafe_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
"(*)::instead of showing commands, just test command line for unsafe subcommand and return 0 in case if command is safe and 1 otherwise (default\: None):" "(*)::instead of showing commands, just test command line for unsafe subcommand and return 0 in case if command is safe and 1 otherwise (default\: None):"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_help_commands_unsafe_defaults_added=0
_shtab_ahriman_help_updates_options=( _shtab_ahriman_help_updates_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
{-e,--exit-code}"[return non-zero exit code if updates available (default\: False)]" {-e,--exit-code}"[return non-zero exit code if updates available (default\: False)]"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_help_updates_defaults_added=0
_shtab_ahriman_help_version_options=( _shtab_ahriman_help_version_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_help_version_defaults_added=0
_shtab_ahriman_init_options=( _shtab_ahriman_init_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
"--build-as-user[force makepkg user to the specific one (default\: None)]:build_as_user:" "--build-as-user[force makepkg user to the specific one (default\: None)]:build_as_user:"
@@ -205,7 +245,7 @@ _shtab_ahriman_init_options=(
{--makeflags-jobs,--no-makeflags-jobs}"[append MAKEFLAGS variable with parallelism set to number of cores (default\: True)]:makeflags_jobs:" {--makeflags-jobs,--no-makeflags-jobs}"[append MAKEFLAGS variable with parallelism set to number of cores (default\: True)]:makeflags_jobs:"
"--mirror[use the specified explicitly mirror instead of including mirrorlist (default\: None)]:mirror:" "--mirror[use the specified explicitly mirror instead of including mirrorlist (default\: None)]:mirror:"
{--multilib,--no-multilib}"[add or do not multilib repository (default\: True)]:multilib:" {--multilib,--no-multilib}"[add or do not multilib repository (default\: True)]:multilib:"
"--packager[packager name and email (default\: None)]:packager:" "--packager[packager name and email]:packager:"
"--server[server to be used for devtools. If none set, local files will be used (default\: None)]:server:" "--server[server to be used for devtools. If none set, local files will be used (default\: None)]:server:"
"--sign-key[sign key id (default\: None)]:sign_key:" "--sign-key[sign key id (default\: None)]:sign_key:"
"*--sign-target[sign options (default\: None)]:sign_target:(disabled packages repository)" "*--sign-target[sign options (default\: None)]:sign_target:(disabled packages repository)"
@@ -213,12 +253,18 @@ _shtab_ahriman_init_options=(
"--web-unix-socket[path to unix socket used for interprocess communications (default\: None)]:web_unix_socket:" "--web-unix-socket[path to unix socket used for interprocess communications (default\: None)]:web_unix_socket:"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_init_defaults_added=0
_shtab_ahriman_key_import_options=( _shtab_ahriman_key_import_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
"--key-server[key server for key import (default\: keyserver.ubuntu.com)]:key_server:" "--key-server[key server for key import (default\: keyserver.ubuntu.com)]:key_server:"
":PGP key to import from public server:" ":PGP key to import from public server:"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_key_import_defaults_added=0
_shtab_ahriman_package_add_options=( _shtab_ahriman_package_add_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
{--changes,--no-changes}"[calculate changes from the latest known commit if available (default\: True)]:changes:" {--changes,--no-changes}"[calculate changes from the latest known commit if available (default\: True)]:changes:"
@@ -233,17 +279,26 @@ _shtab_ahriman_package_add_options=(
"(*):package source (base name, path to local files, remote URL):" "(*):package source (base name, path to local files, remote URL):"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_package_add_defaults_added=0
_shtab_ahriman_package_changes_options=( _shtab_ahriman_package_changes_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
{-e,--exit-code}"[return non-zero exit status if result is empty (default\: False)]" {-e,--exit-code}"[return non-zero exit status if result is empty (default\: False)]"
":package base:" ":package base:"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_package_changes_defaults_added=0
_shtab_ahriman_package_changes_remove_options=( _shtab_ahriman_package_changes_remove_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
":package base:" ":package base:"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_package_changes_remove_defaults_added=0
_shtab_ahriman_package_copy_options=( _shtab_ahriman_package_copy_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
{-e,--exit-code}"[return non-zero exit status if result is empty (default\: False)]" {-e,--exit-code}"[return non-zero exit status if result is empty (default\: False)]"
@@ -252,11 +307,17 @@ _shtab_ahriman_package_copy_options=(
"(*):package base:" "(*):package base:"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_package_copy_defaults_added=0
_shtab_ahriman_package_remove_options=( _shtab_ahriman_package_remove_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
"(*):package name or base:" "(*):package name or base:"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_package_remove_defaults_added=0
_shtab_ahriman_package_status_options=( _shtab_ahriman_package_status_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
"--ahriman[get service status itself (default\: False)]" "--ahriman[get service status itself (default\: False)]"
@@ -266,17 +327,26 @@ _shtab_ahriman_package_status_options=(
"(*)::filter status by package base (default\: None):" "(*)::filter status by package base (default\: None):"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_package_status_defaults_added=0
_shtab_ahriman_package_status_remove_options=( _shtab_ahriman_package_status_remove_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
"(*):remove specified packages from status page:" "(*):remove specified packages from status page:"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_package_status_remove_defaults_added=0
_shtab_ahriman_package_status_update_options=( _shtab_ahriman_package_status_update_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
{-s,--status}"[new package build status (default\: success)]:status:(unknown pending building failed success)" {-s,--status}"[new package build status (default\: success)]:status:(unknown pending building failed success)"
"(*)::set status for specified packages. If no packages supplied, service status will be updated (default\: None):" "(*)::set status for specified packages. If no packages supplied, service status will be updated (default\: None):"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_package_status_update_defaults_added=0
_shtab_ahriman_package_update_options=( _shtab_ahriman_package_update_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
{--changes,--no-changes}"[calculate changes from the latest known commit if available (default\: True)]:changes:" {--changes,--no-changes}"[calculate changes from the latest known commit if available (default\: True)]:changes:"
@@ -291,6 +361,9 @@ _shtab_ahriman_package_update_options=(
"(*):package source (base name, path to local files, remote URL):" "(*):package source (base name, path to local files, remote URL):"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_package_update_defaults_added=0
_shtab_ahriman_patch_add_options=( _shtab_ahriman_patch_add_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
":package base:" ":package base:"
@@ -298,6 +371,9 @@ _shtab_ahriman_patch_add_options=(
":path to file which contains function or variable value. If not set, the value will be read from stdin (default\: None):" ":path to file which contains function or variable value. If not set, the value will be read from stdin (default\: None):"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_patch_add_defaults_added=0
_shtab_ahriman_patch_list_options=( _shtab_ahriman_patch_list_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
{-e,--exit-code}"[return non-zero exit status if result is empty (default\: False)]" {-e,--exit-code}"[return non-zero exit status if result is empty (default\: False)]"
@@ -305,18 +381,27 @@ _shtab_ahriman_patch_list_options=(
":package base:" ":package base:"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_patch_list_defaults_added=0
_shtab_ahriman_patch_remove_options=( _shtab_ahriman_patch_remove_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
"*"{-v,--variable}"[should be used for single-function patches in case if you wold like to remove only specified PKGBUILD variables. In case if not set, it will remove all patches related to the package (default\: None)]:variable:" "*"{-v,--variable}"[should be used for single-function patches in case if you wold like to remove only specified PKGBUILD variables. In case if not set, it will remove all patches related to the package (default\: None)]:variable:"
":package base:" ":package base:"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_patch_remove_defaults_added=0
_shtab_ahriman_patch_set_add_options=( _shtab_ahriman_patch_set_add_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
"*"{-t,--track}"[files which has to be tracked (default\: \[\'\*.diff\', \'\*.patch\'\])]:track:" "*"{-t,--track}"[files which has to be tracked (default\: \[\'\*.diff\', \'\*.patch\'\])]:track:"
":path to directory with changed files for patch addition\/update:" ":path to directory with changed files for patch addition\/update:"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_patch_set_add_defaults_added=0
_shtab_ahriman_rebuild_options=( _shtab_ahriman_rebuild_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
"*--depends-on[only rebuild packages that depend on specified packages (default\: None)]:depends_on:" "*--depends-on[only rebuild packages that depend on specified packages (default\: None)]:depends_on:"
@@ -328,21 +413,33 @@ _shtab_ahriman_rebuild_options=(
{-u,--username}"[build as user (default\: None)]:username:" {-u,--username}"[build as user (default\: None)]:username:"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_rebuild_defaults_added=0
_shtab_ahriman_remove_options=( _shtab_ahriman_remove_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
"(*):package name or base:" "(*):package name or base:"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_remove_defaults_added=0
_shtab_ahriman_remove_unknown_options=( _shtab_ahriman_remove_unknown_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
"--dry-run[just perform check for packages without removal (default\: False)]" "--dry-run[just perform check for packages without removal (default\: False)]"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_remove_unknown_defaults_added=0
_shtab_ahriman_repo_backup_options=( _shtab_ahriman_repo_backup_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
":path of the output archive:" ":path of the output archive:"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_repo_backup_defaults_added=0
_shtab_ahriman_repo_check_options=( _shtab_ahriman_repo_check_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
{--changes,--no-changes}"[calculate changes from the latest known commit if available (default\: True)]:changes:" {--changes,--no-changes}"[calculate changes from the latest known commit if available (default\: True)]:changes:"
@@ -353,6 +450,9 @@ _shtab_ahriman_repo_check_options=(
"(*)::filter check by package base (default\: None):" "(*)::filter check by package base (default\: None):"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_repo_check_defaults_added=0
_shtab_ahriman_repo_clean_options=( _shtab_ahriman_repo_clean_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
{--cache,--no-cache}"[clear directory with package caches (default\: False)]:cache:" {--cache,--no-cache}"[clear directory with package caches (default\: False)]:cache:"
@@ -362,6 +462,9 @@ _shtab_ahriman_repo_clean_options=(
{--pacman,--no-pacman}"[clear directory with pacman local database cache (default\: False)]:pacman:" {--pacman,--no-pacman}"[clear directory with pacman local database cache (default\: False)]:pacman:"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_repo_clean_defaults_added=0
_shtab_ahriman_repo_config_options=( _shtab_ahriman_repo_config_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
{--info,--no-info}"[show additional information, e.g. configuration files (default\: True)]:info:" {--info,--no-info}"[show additional information, e.g. configuration files (default\: True)]:info:"
@@ -370,19 +473,31 @@ _shtab_ahriman_repo_config_options=(
":filter settings by key (default\: None):" ":filter settings by key (default\: None):"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_repo_config_defaults_added=0
_shtab_ahriman_repo_config_validate_options=( _shtab_ahriman_repo_config_validate_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
{-e,--exit-code}"[return non-zero exit status if configuration is invalid (default\: False)]" {-e,--exit-code}"[return non-zero exit status if configuration is invalid (default\: False)]"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_repo_config_validate_defaults_added=0
_shtab_ahriman_repo_create_keyring_options=( _shtab_ahriman_repo_create_keyring_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_repo_create_keyring_defaults_added=0
_shtab_ahriman_repo_create_mirrorlist_options=( _shtab_ahriman_repo_create_mirrorlist_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_repo_create_mirrorlist_defaults_added=0
_shtab_ahriman_repo_daemon_options=( _shtab_ahriman_repo_daemon_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
{-i,--interval}"[interval between runs in seconds (default\: 43200)]:interval:" {-i,--interval}"[interval between runs in seconds (default\: 43200)]:interval:"
@@ -400,6 +515,9 @@ _shtab_ahriman_repo_daemon_options=(
"*"{-y,--refresh}"[download fresh package databases from the mirror before actions, -yy to force refresh even if up to date (default\: False)]" "*"{-y,--refresh}"[download fresh package databases from the mirror before actions, -yy to force refresh even if up to date (default\: False)]"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_repo_daemon_defaults_added=0
_shtab_ahriman_repo_init_options=( _shtab_ahriman_repo_init_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
"--build-as-user[force makepkg user to the specific one (default\: None)]:build_as_user:" "--build-as-user[force makepkg user to the specific one (default\: None)]:build_as_user:"
@@ -408,7 +526,7 @@ _shtab_ahriman_repo_init_options=(
{--makeflags-jobs,--no-makeflags-jobs}"[append MAKEFLAGS variable with parallelism set to number of cores (default\: True)]:makeflags_jobs:" {--makeflags-jobs,--no-makeflags-jobs}"[append MAKEFLAGS variable with parallelism set to number of cores (default\: True)]:makeflags_jobs:"
"--mirror[use the specified explicitly mirror instead of including mirrorlist (default\: None)]:mirror:" "--mirror[use the specified explicitly mirror instead of including mirrorlist (default\: None)]:mirror:"
{--multilib,--no-multilib}"[add or do not multilib repository (default\: True)]:multilib:" {--multilib,--no-multilib}"[add or do not multilib repository (default\: True)]:multilib:"
"--packager[packager name and email (default\: None)]:packager:" "--packager[packager name and email]:packager:"
"--server[server to be used for devtools. If none set, local files will be used (default\: None)]:server:" "--server[server to be used for devtools. If none set, local files will be used (default\: None)]:server:"
"--sign-key[sign key id (default\: None)]:sign_key:" "--sign-key[sign key id (default\: None)]:sign_key:"
"*--sign-target[sign options (default\: None)]:sign_target:(disabled packages repository)" "*--sign-target[sign options (default\: None)]:sign_target:(disabled packages repository)"
@@ -416,6 +534,9 @@ _shtab_ahriman_repo_init_options=(
"--web-unix-socket[path to unix socket used for interprocess communications (default\: None)]:web_unix_socket:" "--web-unix-socket[path to unix socket used for interprocess communications (default\: None)]:web_unix_socket:"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_repo_init_defaults_added=0
_shtab_ahriman_repo_rebuild_options=( _shtab_ahriman_repo_rebuild_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
"*--depends-on[only rebuild packages that depend on specified packages (default\: None)]:depends_on:" "*--depends-on[only rebuild packages that depend on specified packages (default\: None)]:depends_on:"
@@ -427,21 +548,33 @@ _shtab_ahriman_repo_rebuild_options=(
{-u,--username}"[build as user (default\: None)]:username:" {-u,--username}"[build as user (default\: None)]:username:"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_repo_rebuild_defaults_added=0
_shtab_ahriman_repo_remove_unknown_options=( _shtab_ahriman_repo_remove_unknown_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
"--dry-run[just perform check for packages without removal (default\: False)]" "--dry-run[just perform check for packages without removal (default\: False)]"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_repo_remove_unknown_defaults_added=0
_shtab_ahriman_repo_report_options=( _shtab_ahriman_repo_report_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_repo_report_defaults_added=0
_shtab_ahriman_repo_restore_options=( _shtab_ahriman_repo_restore_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
{-o,--output}"[root path of the extracted files (default\: \/)]:output:" {-o,--output}"[root path of the extracted files (default\: \/)]:output:"
":path of the input archive:" ":path of the input archive:"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_repo_restore_defaults_added=0
_shtab_ahriman_repo_setup_options=( _shtab_ahriman_repo_setup_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
"--build-as-user[force makepkg user to the specific one (default\: None)]:build_as_user:" "--build-as-user[force makepkg user to the specific one (default\: None)]:build_as_user:"
@@ -450,7 +583,7 @@ _shtab_ahriman_repo_setup_options=(
{--makeflags-jobs,--no-makeflags-jobs}"[append MAKEFLAGS variable with parallelism set to number of cores (default\: True)]:makeflags_jobs:" {--makeflags-jobs,--no-makeflags-jobs}"[append MAKEFLAGS variable with parallelism set to number of cores (default\: True)]:makeflags_jobs:"
"--mirror[use the specified explicitly mirror instead of including mirrorlist (default\: None)]:mirror:" "--mirror[use the specified explicitly mirror instead of including mirrorlist (default\: None)]:mirror:"
{--multilib,--no-multilib}"[add or do not multilib repository (default\: True)]:multilib:" {--multilib,--no-multilib}"[add or do not multilib repository (default\: True)]:multilib:"
"--packager[packager name and email (default\: None)]:packager:" "--packager[packager name and email]:packager:"
"--server[server to be used for devtools. If none set, local files will be used (default\: None)]:server:" "--server[server to be used for devtools. If none set, local files will be used (default\: None)]:server:"
"--sign-key[sign key id (default\: None)]:sign_key:" "--sign-key[sign key id (default\: None)]:sign_key:"
"*--sign-target[sign options (default\: None)]:sign_target:(disabled packages repository)" "*--sign-target[sign options (default\: None)]:sign_target:(disabled packages repository)"
@@ -458,11 +591,17 @@ _shtab_ahriman_repo_setup_options=(
"--web-unix-socket[path to unix socket used for interprocess communications (default\: None)]:web_unix_socket:" "--web-unix-socket[path to unix socket used for interprocess communications (default\: None)]:web_unix_socket:"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_repo_setup_defaults_added=0
_shtab_ahriman_repo_sign_options=( _shtab_ahriman_repo_sign_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
"(*)::sign only specified packages (default\: None):" "(*)::sign only specified packages (default\: None):"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_repo_sign_defaults_added=0
_shtab_ahriman_repo_statistics_options=( _shtab_ahriman_repo_statistics_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
"--chart[create updates chart and save it to the specified path (default\: None)]:chart:" "--chart[create updates chart and save it to the specified path (default\: None)]:chart:"
@@ -474,25 +613,40 @@ _shtab_ahriman_repo_statistics_options=(
":fetch only events for the specified package (default\: None):" ":fetch only events for the specified package (default\: None):"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_repo_statistics_defaults_added=0
_shtab_ahriman_repo_status_update_options=( _shtab_ahriman_repo_status_update_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
{-s,--status}"[new status (default\: success)]:status:(unknown pending building failed success)" {-s,--status}"[new status (default\: success)]:status:(unknown pending building failed success)"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_repo_status_update_defaults_added=0
_shtab_ahriman_repo_sync_options=( _shtab_ahriman_repo_sync_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_repo_sync_defaults_added=0
_shtab_ahriman_repo_tree_options=( _shtab_ahriman_repo_tree_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
{-p,--partitions}"[also divide packages by independent partitions (default\: 1)]:partitions:" {-p,--partitions}"[also divide packages by independent partitions (default\: 1)]:partitions:"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_repo_tree_defaults_added=0
_shtab_ahriman_repo_triggers_options=( _shtab_ahriman_repo_triggers_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
"(*)::instead of running all triggers as set by configuration, just process specified ones in order of mention (default\: None):" "(*)::instead of running all triggers as set by configuration, just process specified ones in order of mention (default\: None):"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_repo_triggers_defaults_added=0
_shtab_ahriman_repo_update_options=( _shtab_ahriman_repo_update_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
{--aur,--no-aur}"[enable or disable checking for AUR updates (default\: True)]:aur:" {--aur,--no-aur}"[enable or disable checking for AUR updates (default\: True)]:aur:"
@@ -510,15 +664,24 @@ _shtab_ahriman_repo_update_options=(
"(*)::filter check by package base (default\: None):" "(*)::filter check by package base (default\: None):"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_repo_update_defaults_added=0
_shtab_ahriman_report_options=( _shtab_ahriman_report_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_report_defaults_added=0
_shtab_ahriman_run_options=( _shtab_ahriman_run_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
"(*):command to be run (quoted) without \`\`ahriman\`\`:" "(*):command to be run (quoted) without \`\`ahriman\`\`:"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_run_defaults_added=0
_shtab_ahriman_search_options=( _shtab_ahriman_search_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
{-e,--exit-code}"[return non-zero exit status if result is empty (default\: False)]" {-e,--exit-code}"[return non-zero exit status if result is empty (default\: False)]"
@@ -527,6 +690,9 @@ _shtab_ahriman_search_options=(
"(*):search terms, can be specified multiple times, the result will match all terms:" "(*):search terms, can be specified multiple times, the result will match all terms:"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_search_defaults_added=0
_shtab_ahriman_service_clean_options=( _shtab_ahriman_service_clean_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
{--cache,--no-cache}"[clear directory with package caches (default\: False)]:cache:" {--cache,--no-cache}"[clear directory with package caches (default\: False)]:cache:"
@@ -536,6 +702,9 @@ _shtab_ahriman_service_clean_options=(
{--pacman,--no-pacman}"[clear directory with pacman local database cache (default\: False)]:pacman:" {--pacman,--no-pacman}"[clear directory with pacman local database cache (default\: False)]:pacman:"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_service_clean_defaults_added=0
_shtab_ahriman_service_config_options=( _shtab_ahriman_service_config_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
{--info,--no-info}"[show additional information, e.g. configuration files (default\: True)]:info:" {--info,--no-info}"[show additional information, e.g. configuration files (default\: True)]:info:"
@@ -544,27 +713,42 @@ _shtab_ahriman_service_config_options=(
":filter settings by key (default\: None):" ":filter settings by key (default\: None):"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_service_config_defaults_added=0
_shtab_ahriman_service_config_validate_options=( _shtab_ahriman_service_config_validate_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
{-e,--exit-code}"[return non-zero exit status if configuration is invalid (default\: False)]" {-e,--exit-code}"[return non-zero exit status if configuration is invalid (default\: False)]"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_service_config_validate_defaults_added=0
_shtab_ahriman_service_key_import_options=( _shtab_ahriman_service_key_import_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
"--key-server[key server for key import (default\: keyserver.ubuntu.com)]:key_server:" "--key-server[key server for key import (default\: keyserver.ubuntu.com)]:key_server:"
":PGP key to import from public server:" ":PGP key to import from public server:"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_service_key_import_defaults_added=0
_shtab_ahriman_service_repositories_options=( _shtab_ahriman_service_repositories_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
{--id-only,--no-id-only}"[show machine readable identifier instead (default\: False)]:id_only:" {--id-only,--no-id-only}"[show machine readable identifier instead (default\: False)]:id_only:"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_service_repositories_defaults_added=0
_shtab_ahriman_service_run_options=( _shtab_ahriman_service_run_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
"(*):command to be run (quoted) without \`\`ahriman\`\`:" "(*):command to be run (quoted) without \`\`ahriman\`\`:"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_service_run_defaults_added=0
_shtab_ahriman_service_setup_options=( _shtab_ahriman_service_setup_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
"--build-as-user[force makepkg user to the specific one (default\: None)]:build_as_user:" "--build-as-user[force makepkg user to the specific one (default\: None)]:build_as_user:"
@@ -573,7 +757,7 @@ _shtab_ahriman_service_setup_options=(
{--makeflags-jobs,--no-makeflags-jobs}"[append MAKEFLAGS variable with parallelism set to number of cores (default\: True)]:makeflags_jobs:" {--makeflags-jobs,--no-makeflags-jobs}"[append MAKEFLAGS variable with parallelism set to number of cores (default\: True)]:makeflags_jobs:"
"--mirror[use the specified explicitly mirror instead of including mirrorlist (default\: None)]:mirror:" "--mirror[use the specified explicitly mirror instead of including mirrorlist (default\: None)]:mirror:"
{--multilib,--no-multilib}"[add or do not multilib repository (default\: True)]:multilib:" {--multilib,--no-multilib}"[add or do not multilib repository (default\: True)]:multilib:"
"--packager[packager name and email (default\: None)]:packager:" "--packager[packager name and email]:packager:"
"--server[server to be used for devtools. If none set, local files will be used (default\: None)]:server:" "--server[server to be used for devtools. If none set, local files will be used (default\: None)]:server:"
"--sign-key[sign key id (default\: None)]:sign_key:" "--sign-key[sign key id (default\: None)]:sign_key:"
"*--sign-target[sign options (default\: None)]:sign_target:(disabled packages repository)" "*--sign-target[sign options (default\: None)]:sign_target:(disabled packages repository)"
@@ -581,15 +765,25 @@ _shtab_ahriman_service_setup_options=(
"--web-unix-socket[path to unix socket used for interprocess communications (default\: None)]:web_unix_socket:" "--web-unix-socket[path to unix socket used for interprocess communications (default\: None)]:web_unix_socket:"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_service_setup_defaults_added=0
_shtab_ahriman_service_shell_options=( _shtab_ahriman_service_shell_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
{-o,--output}"[output commands and result to the file (default\: None)]:output:"
":instead of dropping into shell, just execute the specified code (default\: None):" ":instead of dropping into shell, just execute the specified code (default\: None):"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_service_shell_defaults_added=0
_shtab_ahriman_service_tree_migrate_options=( _shtab_ahriman_service_tree_migrate_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_service_tree_migrate_defaults_added=0
_shtab_ahriman_setup_options=( _shtab_ahriman_setup_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
"--build-as-user[force makepkg user to the specific one (default\: None)]:build_as_user:" "--build-as-user[force makepkg user to the specific one (default\: None)]:build_as_user:"
@@ -598,7 +792,7 @@ _shtab_ahriman_setup_options=(
{--makeflags-jobs,--no-makeflags-jobs}"[append MAKEFLAGS variable with parallelism set to number of cores (default\: True)]:makeflags_jobs:" {--makeflags-jobs,--no-makeflags-jobs}"[append MAKEFLAGS variable with parallelism set to number of cores (default\: True)]:makeflags_jobs:"
"--mirror[use the specified explicitly mirror instead of including mirrorlist (default\: None)]:mirror:" "--mirror[use the specified explicitly mirror instead of including mirrorlist (default\: None)]:mirror:"
{--multilib,--no-multilib}"[add or do not multilib repository (default\: True)]:multilib:" {--multilib,--no-multilib}"[add or do not multilib repository (default\: True)]:multilib:"
"--packager[packager name and email (default\: None)]:packager:" "--packager[packager name and email]:packager:"
"--server[server to be used for devtools. If none set, local files will be used (default\: None)]:server:" "--server[server to be used for devtools. If none set, local files will be used (default\: None)]:server:"
"--sign-key[sign key id (default\: None)]:sign_key:" "--sign-key[sign key id (default\: None)]:sign_key:"
"*--sign-target[sign options (default\: None)]:sign_target:(disabled packages repository)" "*--sign-target[sign options (default\: None)]:sign_target:(disabled packages repository)"
@@ -606,16 +800,26 @@ _shtab_ahriman_setup_options=(
"--web-unix-socket[path to unix socket used for interprocess communications (default\: None)]:web_unix_socket:" "--web-unix-socket[path to unix socket used for interprocess communications (default\: None)]:web_unix_socket:"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_setup_defaults_added=0
_shtab_ahriman_shell_options=( _shtab_ahriman_shell_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
{-o,--output}"[output commands and result to the file (default\: None)]:output:"
":instead of dropping into shell, just execute the specified code (default\: None):" ":instead of dropping into shell, just execute the specified code (default\: None):"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_shell_defaults_added=0
_shtab_ahriman_sign_options=( _shtab_ahriman_sign_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
"(*)::sign only specified packages (default\: None):" "(*)::sign only specified packages (default\: None):"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_sign_defaults_added=0
_shtab_ahriman_status_options=( _shtab_ahriman_status_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
"--ahriman[get service status itself (default\: False)]" "--ahriman[get service status itself (default\: False)]"
@@ -625,16 +829,25 @@ _shtab_ahriman_status_options=(
"(*)::filter status by package base (default\: None):" "(*)::filter status by package base (default\: None):"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_status_defaults_added=0
_shtab_ahriman_status_update_options=( _shtab_ahriman_status_update_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
{-s,--status}"[new package build status (default\: success)]:status:(unknown pending building failed success)" {-s,--status}"[new package build status (default\: success)]:status:(unknown pending building failed success)"
"(*)::set status for specified packages. If no packages supplied, service status will be updated (default\: None):" "(*)::set status for specified packages. If no packages supplied, service status will be updated (default\: None):"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_status_update_defaults_added=0
_shtab_ahriman_sync_options=( _shtab_ahriman_sync_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_sync_defaults_added=0
_shtab_ahriman_update_options=( _shtab_ahriman_update_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
{--aur,--no-aur}"[enable or disable checking for AUR updates (default\: True)]:aur:" {--aur,--no-aur}"[enable or disable checking for AUR updates (default\: True)]:aur:"
@@ -652,6 +865,9 @@ _shtab_ahriman_update_options=(
"(*)::filter check by package base (default\: None):" "(*)::filter check by package base (default\: None):"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_update_defaults_added=0
_shtab_ahriman_user_add_options=( _shtab_ahriman_user_add_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
"--key[optional PGP key used by this user. The private key must be imported (default\: None)]:key:" "--key[optional PGP key used by this user. The private key must be imported (default\: None)]:key:"
@@ -661,6 +877,9 @@ _shtab_ahriman_user_add_options=(
":username for web service:" ":username for web service:"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_user_add_defaults_added=0
_shtab_ahriman_user_list_options=( _shtab_ahriman_user_list_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
{-e,--exit-code}"[return non-zero exit status if result is empty (default\: False)]" {-e,--exit-code}"[return non-zero exit status if result is empty (default\: False)]"
@@ -668,25 +887,48 @@ _shtab_ahriman_user_list_options=(
":filter users by username (default\: None):" ":filter users by username (default\: None):"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_user_list_defaults_added=0
_shtab_ahriman_user_remove_options=( _shtab_ahriman_user_remove_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
":username for web service:" ":username for web service:"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_user_remove_defaults_added=0
_shtab_ahriman_version_options=( _shtab_ahriman_version_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_version_defaults_added=0
_shtab_ahriman_web_options=( _shtab_ahriman_web_options=(
"(- : *)"{-h,--help}"[show this help message and exit]" "(- : *)"{-h,--help}"[show this help message and exit]"
) )
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_web_defaults_added=0
_shtab_ahriman_web_reload_options=(
"(- : *)"{-h,--help}"[show this help message and exit]"
)
# guard to ensure default positional specs are added only once per session
_shtab_ahriman_web_reload_defaults_added=0
_shtab_ahriman() { _shtab_ahriman() {
local context state line curcontext="$curcontext" one_or_more='(-)*' remainder='(*)' local context state line curcontext="$curcontext" one_or_more='(*)' remainder='(-)*' default='*::: :->ahriman'
if ((${_shtab_ahriman_options[(I)${(q)one_or_more}*]} + ${_shtab_ahriman_options[(I)${(q)remainder}*]} == 0)); then # noqa: E501 # Add default positional/remainder specs only if none exist, and only once per session
_shtab_ahriman_options+=(': :_shtab_ahriman_commands' '*::: :->ahriman') if (( ! _shtab_ahriman_defaults_added )); then
if (( ${_shtab_ahriman_options[(I)${(q)one_or_more}*]} + ${_shtab_ahriman_options[(I)${(q)remainder}*]} + ${_shtab_ahriman_options[(I)${(q)default}]} == 0 )); then
_shtab_ahriman_options+=(': :_shtab_ahriman_commands' '*::: :->ahriman')
fi
_shtab_ahriman_defaults_added=1
fi fi
_arguments -C -s $_shtab_ahriman_options _arguments -C -s $_shtab_ahriman_options
@@ -771,6 +1013,7 @@ _shtab_ahriman() {
user-remove) _arguments -C -s $_shtab_ahriman_user_remove_options ;; user-remove) _arguments -C -s $_shtab_ahriman_user_remove_options ;;
version) _arguments -C -s $_shtab_ahriman_version_options ;; version) _arguments -C -s $_shtab_ahriman_version_options ;;
web) _arguments -C -s $_shtab_ahriman_web_options ;; web) _arguments -C -s $_shtab_ahriman_web_options ;;
web-reload) _arguments -C -s $_shtab_ahriman_web_reload_options ;;
esac esac
esac esac
} }
+68
View File
@@ -0,0 +1,68 @@
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "ahriman-core"
description = "ArcH linux ReposItory MANager, core package"
readme = "../README.md"
requires-python = ">=3.13"
license = {file = "../COPYING"}
authors = [
{name = "ahriman team"},
]
dependencies = [
"bcrypt",
"filelock",
"inflection",
"pyelftools",
"requests",
]
dynamic = ["version"]
[project.optional-dependencies]
journald = [
"systemd-python",
]
# FIXME technically this dependency is required, but in some cases we do not have access to
# the libalpm which is required in order to install the package. Thus in case if we do not
# really need to run the application we can move it to "optional" dependencies
pacman = [
"pyalpm",
]
reports = [
"Jinja2",
]
s3 = [
"boto3",
]
shell = [
"IPython",
]
stats = [
"matplotlib",
]
unixsocket = [
"requests-unixsocket2",
]
validator = [
"cerberus",
]
[project.scripts]
ahriman = "ahriman.application.ahriman:run"
[project.urls]
Documentation = "https://ahriman.readthedocs.io/"
Repository = "https://github.com/arcan1s/ahriman"
Changelog = "https://github.com/arcan1s/ahriman/releases"
[tool.hatch.version]
path = "src/ahriman/__init__.py"
[tool.hatch.build.targets.wheel]
packages = ["src/ahriman"]
[tool.hatch.build.targets.wheel.shared-data]
"package/lib" = "lib"
"package/share" = "share"
@@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2024 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).
@@ -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.16.0" __version__ = "2.20.0"
@@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2024 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).
@@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2024 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).
@@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2024 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).
@@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2024 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).
@@ -117,7 +117,7 @@ class Application(ApplicationPackages, ApplicationRepository):
Args: Args:
packages(list[Package]): list of source packages of which dependencies have to be processed packages(list[Package]): list of source packages of which dependencies have to be processed
process_dependencies(bool): if no set, dependencies will not be processed process_dependencies(bool): if set to ``False``, dependencies will not be processed
Returns: Returns:
list[Package]: updated packages list. Packager for dependencies will be copied from the original package list[Package]: updated packages list. Packager for dependencies will be copied from the original package
@@ -130,37 +130,47 @@ class Application(ApplicationPackages, ApplicationRepository):
>>> packages = application.with_dependencies(packages, process_dependencies=True) >>> packages = application.with_dependencies(packages, process_dependencies=True)
>>> application.print_updates(packages, log_fn=print) >>> application.print_updates(packages, log_fn=print)
""" """
def missing_dependencies(source: Iterable[Package]) -> dict[str, str | None]:
# append list of known packages with packages which are in current sources
satisfied_packages = known_packages | {
single
for package in source
for single in package.packages_full
}
return {
dependency: package.packager
for package in source
for dependency in package.depends_build
if dependency not in satisfied_packages
}
if not process_dependencies or not packages: if not process_dependencies or not packages:
return packages return packages
def missing_dependencies(sources: Iterable[Package]) -> dict[str, str | None]:
# append list of known packages with packages which are in current sources
satisfied_packages = known_packages | {
single
for source in sources
for single in source.packages_full
}
return {
dependency: source.packager
for source in sources
for dependency in source.depends_build
if dependency not in satisfied_packages
}
def new_packages(root: Package) -> dict[str, Package]:
portion = {root.base: root}
while missing := missing_dependencies(portion.values()):
for package_name, packager in missing.items():
if (source_dir := self.repository.paths.cache_for(package_name)).is_dir():
# there is local cache, load package from it
leaf = Package.from_build(source_dir, self.repository.repository_id.architecture, packager)
else:
leaf = Package.from_aur(package_name, packager, include_provides=True)
portion[leaf.base] = leaf
# register package in the database
self.reporter.set_unknown(leaf)
return portion
known_packages = self._known_packages() known_packages = self._known_packages()
with_dependencies = {package.base: package for package in packages} with_dependencies: dict[str, Package] = {}
for package in packages:
while missing := missing_dependencies(with_dependencies.values()): with self.in_package_context(package.base, package.version): # use the same context for the logger
for package_name, username in missing.items(): try:
if (source_dir := self.repository.paths.cache_for(package_name)).is_dir(): with_dependencies |= new_packages(package)
# there is local cache, load package from it except Exception:
package = Package.from_build(source_dir, self.repository.architecture, username) self.logger.exception("could not process dependencies of %s, skip the package", package.base)
else:
package = Package.from_aur(package_name, username)
with_dependencies[package.base] = package
# register package in the database
self.repository.reporter.set_unknown(package)
return list(with_dependencies.values()) return list(with_dependencies.values())
@@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2024 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).
@@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2024 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).
@@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2024 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).
@@ -22,6 +22,7 @@ from collections.abc import Iterable
from ahriman.application.application.application_properties import ApplicationProperties from ahriman.application.application.application_properties import ApplicationProperties
from ahriman.application.application.workers import Updater from ahriman.application.application.workers import Updater
from ahriman.core.build_tools.sources import Sources from ahriman.core.build_tools.sources import Sources
from ahriman.core.exceptions import UnknownPackageError
from ahriman.models.package import Package from ahriman.models.package import Package
from ahriman.models.packagers import Packagers from ahriman.models.packagers import Packagers
from ahriman.models.result import Result from ahriman.models.result import Result
@@ -44,8 +45,8 @@ class ApplicationRepository(ApplicationProperties):
if last_commit_sha is None: if last_commit_sha is None:
continue # skip check in case if we can't calculate diff continue # skip check in case if we can't calculate diff
changes = self.repository.package_changes(package, last_commit_sha) if (changes := self.repository.package_changes(package, last_commit_sha)) is not None:
self.repository.reporter.package_changes_update(package.base, changes) self.reporter.package_changes_update(package.base, changes)
def clean(self, *, cache: bool, chroot: bool, manual: bool, packages: bool, pacman: bool) -> None: def clean(self, *, cache: bool, chroot: bool, manual: bool, packages: bool, pacman: bool) -> None:
""" """
@@ -116,7 +117,7 @@ class ApplicationRepository(ApplicationProperties):
for single in probe.packages: for single in probe.packages:
try: try:
_ = Package.from_aur(single, None) _ = Package.from_aur(single, None)
except Exception: except UnknownPackageError:
packages.append(single) packages.append(single)
return packages return packages
@@ -155,12 +156,15 @@ class ApplicationRepository(ApplicationProperties):
result = Result() result = Result()
# process already built packages if any # process already built packages if any
built_packages = self.repository.packages_built() if built_packages := self.repository.packages_built(): # 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) self.on_result(build_result)
result.merge(build_result) result.merge(build_result)
# filter packages which were prebuilt
succeeded = {package.base for package in build_result.success}
updates = [package for package in updates if package.base not in succeeded]
builder = Updater.load(self.repository_id, self.configuration, self.repository) builder = Updater.load(self.repository_id, self.configuration, self.repository)
# ok so for now we split all packages into chunks and process each chunk accordingly # ok so for now we split all packages into chunks and process each chunk accordingly
@@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2024 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).
@@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2024 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).
@@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2024 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).
@@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2024 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).
@@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2024 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).
@@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2024 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).
@@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2024 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).
@@ -21,10 +21,10 @@ import argparse
from ahriman.application.application import Application from ahriman.application.application import Application
from ahriman.application.handlers.handler import Handler, SubParserAction from ahriman.application.handlers.handler import Handler, SubParserAction
from ahriman.application.handlers.update import Update
from ahriman.core.configuration import Configuration from ahriman.core.configuration import Configuration
from ahriman.core.utils import enum_values, extract_user from ahriman.core.utils import enum_values, extract_user
from ahriman.models.package_source import PackageSource from ahriman.models.package_source import PackageSource
from ahriman.models.packagers import Packagers
from ahriman.models.pkgbuild_patch import PkgbuildPatch from ahriman.models.pkgbuild_patch import PkgbuildPatch
from ahriman.models.repository_id import RepositoryId from ahriman.models.repository_id import RepositoryId
@@ -48,26 +48,7 @@ class Add(Handler):
""" """
application = Application(repository_id, configuration, report=report, refresh_pacman_database=args.refresh) application = Application(repository_id, configuration, report=report, refresh_pacman_database=args.refresh)
application.on_start() application.on_start()
Add.perform_action(application, args)
application.add(args.package, args.source, args.username)
patches = [PkgbuildPatch.from_env(patch) for patch in args.variable] if args.variable is not None else []
for package in args.package: # for each requested package insert patch
for patch in patches:
application.reporter.package_patches_update(package, patch)
if not args.now:
return
packages = application.updates(args.package, aur=False, local=False, manual=True, vcs=False, check_files=False)
if args.changes: # generate changes if requested
application.changes(packages)
packages = application.with_dependencies(packages, process_dependencies=args.dependencies)
packagers = Packagers(args.username, {package.base: package.packager for package in packages})
application.print_updates(packages, log_fn=application.logger.info)
result = application.update(packages, packagers, bump_pkgrel=args.increment)
Add.check_status(args.exit_code, not result.is_empty)
@staticmethod @staticmethod
def _set_package_add_parser(root: SubParserAction) -> argparse.ArgumentParser: def _set_package_add_parser(root: SubParserAction) -> argparse.ArgumentParser:
@@ -103,14 +84,34 @@ class Add(Handler):
parser.add_argument("--increment", help="increment package release (pkgrel) version on duplicate", parser.add_argument("--increment", help="increment package release (pkgrel) version on duplicate",
action=argparse.BooleanOptionalAction, default=True) action=argparse.BooleanOptionalAction, default=True)
parser.add_argument("-n", "--now", help="run update function after", action="store_true") parser.add_argument("-n", "--now", help="run update function after", action="store_true")
parser.add_argument("-y", "--refresh", help="download fresh package databases from the mirror before actions, "
"-yy to force refresh even if up to date",
action="count", default=False)
parser.add_argument("-s", "--source", help="explicitly specify the package source for this command", parser.add_argument("-s", "--source", help="explicitly specify the package source for this command",
type=PackageSource, choices=enum_values(PackageSource), default=PackageSource.Auto) type=PackageSource, choices=enum_values(PackageSource), default=PackageSource.Auto)
parser.add_argument("-u", "--username", help="build as user", default=extract_user()) parser.add_argument("-u", "--username", help="build as user", default=extract_user())
parser.add_argument("-v", "--variable", help="apply specified makepkg variables to the next build", parser.add_argument("-v", "--variable", help="apply specified makepkg variables to the next build",
action="append") action="append")
parser.add_argument("-y", "--refresh", help="download fresh package databases from the mirror before actions, "
"-yy to force refresh even if up to date",
action="count", default=False)
parser.set_defaults(aur=False, check_files=False, dry_run=False, local=False, manual=True, vcs=False)
return parser return parser
@staticmethod
def perform_action(application: Application, args: argparse.Namespace) -> None:
"""
perform add action
Args:
application(Application): application instance
args(argparse.Namespace): command line args
"""
application.add(args.package, args.source, args.username)
patches = [PkgbuildPatch.from_env(patch) for patch in args.variable] if args.variable is not None else []
for package in args.package: # for each requested package insert patch
for patch in patches:
application.reporter.package_patches_update(package, patch)
if not args.now:
return
Update.perform_action(application, args)
arguments = [_set_package_add_parser] arguments = [_set_package_add_parser]
@@ -0,0 +1,81 @@
#
# Copyright (c) 2021-2026 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 argparse
from ahriman.application.application import Application
from ahriman.application.handlers.handler import Handler, SubParserAction
from ahriman.core.configuration import Configuration
from ahriman.core.formatters import PackagePrinter
from ahriman.models.action import Action
from ahriman.models.build_status import BuildStatus, BuildStatusEnum
from ahriman.models.repository_id import RepositoryId
class Archives(Handler):
"""
package archives handler
"""
ALLOW_MULTI_ARCHITECTURE_RUN = False # conflicting io
@classmethod
def run(cls, args: argparse.Namespace, repository_id: RepositoryId, configuration: Configuration, *,
report: bool) -> None:
"""
callback for command line
Args:
args(argparse.Namespace): command line args
repository_id(RepositoryId): repository unique identifier
configuration(Configuration): configuration instance
report(bool): force enable or disable reporting
"""
application = Application(repository_id, configuration, report=True)
match args.action:
case Action.List:
archives = application.repository.package_archives(args.package)
for package in archives:
PackagePrinter(package, BuildStatus(BuildStatusEnum.Success))(verbose=args.info)
Archives.check_status(args.exit_code, bool(archives))
@staticmethod
def _set_package_archives_parser(root: SubParserAction) -> argparse.ArgumentParser:
"""
add parser for package archives subcommand
Args:
root(SubParserAction): subparsers for the commands
Returns:
argparse.ArgumentParser: created argument parser
"""
parser = root.add_parser("package-archives", help="list package archive versions",
description="list available archive versions for the package")
parser.add_argument("package", help="package base")
parser.add_argument("-e", "--exit-code", help="return non-zero exit status if result is empty",
action="store_true")
parser.add_argument("--info", help="show additional package information",
action=argparse.BooleanOptionalAction, default=False)
parser.set_defaults(action=Action.List, lock=None, quiet=True, report=False, unsafe=True)
return parser
arguments = [_set_package_archives_parser]
@@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2024 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).
@@ -81,11 +81,13 @@ class Backup(Handler):
Returns: Returns:
set[Path]: map of the filesystem paths set[Path]: map of the filesystem paths
""" """
paths = set(configuration.include.glob("*.ini")) # configuration files
root, _ = configuration.check_loaded() root, _ = configuration.check_loaded()
paths.add(root) # the configuration itself paths = set(configuration.includes)
paths.add(SQLite.database_path(configuration)) # database paths.add(root)
# database
paths.add(SQLite.database_path(configuration))
# local caches # local caches
repository_paths = configuration.repository_paths repository_paths = configuration.repository_paths
@@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2024 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).
@@ -47,14 +47,13 @@ class Change(Handler):
configuration(Configuration): configuration instance configuration(Configuration): configuration instance
report(bool): force enable or disable reporting report(bool): force enable or disable reporting
""" """
application = Application(repository_id, configuration, report=True) client = Application(repository_id, configuration, report=True).reporter
client = application.repository.reporter
match args.action: match args.action:
case Action.List: case Action.List:
changes = client.package_changes_get(args.package) changes = client.package_changes_get(args.package)
ChangesPrinter(changes)(verbose=True, separator="") ChangesPrinter(changes)(verbose=True, separator="")
Change.check_status(args.exit_code, not changes.is_empty) Change.check_status(args.exit_code, changes.changes is not None)
case Action.Remove: case Action.Remove:
client.package_changes_update(args.package, Changes()) client.package_changes_update(args.package, Changes())
@@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2024 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).
@@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2024 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).
@@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2024 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).
@@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2024 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).
@@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2024 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).
@@ -20,17 +20,17 @@
import argparse import argparse
import logging import logging
from collections.abc import Callable, Iterable from collections.abc import Callable
from multiprocessing import Pool from multiprocessing import Pool
from typing import TypeVar from typing import ClassVar, TypeVar
from ahriman.application.lock import Lock from ahriman.application.lock import Lock
from ahriman.core.configuration import Configuration from ahriman.core.configuration import Configuration
from ahriman.core.exceptions import ExitCode, MissingArchitectureError, MultipleArchitecturesError from ahriman.core.exceptions import ExitCode, MissingArchitectureError, MultipleArchitecturesError
from ahriman.core.log.log_loader import LogLoader from ahriman.core.log.log_loader import LogLoader
from ahriman.core.repository import Explorer
from ahriman.core.types import ExplicitBool from ahriman.core.types import ExplicitBool
from ahriman.models.repository_id import RepositoryId from ahriman.models.repository_id import RepositoryId
from ahriman.models.repository_paths import RepositoryPaths
# this workaround is for several things # this workaround is for several things
@@ -53,13 +53,13 @@ class Handler:
Wrapper for all command line actions, though each derived class implements :func:`run()` method, it usually Wrapper for all command line actions, though each derived class implements :func:`run()` method, it usually
must not be called directly. The recommended way is to call :func:`execute()` class method, e.g.:: must not be called directly. The recommended way is to call :func:`execute()` class method, e.g.::
>>> from ahriman.application.handlers import Add >>> from ahriman.application.handlers.add import Add
>>> >>>
>>> Add.execute(args) >>> Add.execute(args)
""" """
ALLOW_MULTI_ARCHITECTURE_RUN = True ALLOW_MULTI_ARCHITECTURE_RUN: ClassVar[bool] = True
arguments: list[Callable[[SubParserAction], argparse.ArgumentParser]] arguments: ClassVar[list[Callable[[SubParserAction], argparse.ArgumentParser]]]
@classmethod @classmethod
def call(cls, args: argparse.Namespace, repository_id: RepositoryId) -> bool: def call(cls, args: argparse.Namespace, repository_id: RepositoryId) -> bool:
@@ -169,11 +169,6 @@ class Handler:
Raises: Raises:
MissingArchitectureError: if no architecture set and automatic detection is not allowed or failed MissingArchitectureError: if no architecture set and automatic detection is not allowed or failed
""" """
configuration = Configuration()
configuration.load(args.configuration)
# pylint, wtf???
root = configuration.getpath("repository", "root") # pylint: disable=assignment-from-no-return
# preparse systemd repository-id argument # preparse systemd repository-id argument
# we are using unescaped values, so / is not allowed here, because it is impossible to separate if from dashes # we are using unescaped values, so / is not allowed here, because it is impossible to separate if from dashes
if args.repository_id is not None: if args.repository_id is not None:
@@ -184,27 +179,10 @@ class Handler:
if repository_parts: if repository_parts:
args.repository = "-".join(repository_parts) # replace slash with dash args.repository = "-".join(repository_parts) # replace slash with dash
# extract repository names first configuration = Configuration()
if (from_args := args.repository) is not None: configuration.load(args.configuration)
repositories: Iterable[str] = [from_args] repositories = Explorer.repositories_extract(configuration, args.repository, args.architecture)
elif from_filesystem := RepositoryPaths.known_repositories(root):
repositories = from_filesystem
else: # try to read configuration now
repositories = [configuration.get("repository", "name")]
# extract architecture names if not repositories:
if (architecture := args.architecture) is not None:
parsed = set(
RepositoryId(architecture, repository)
for repository in repositories
)
else: # try to read from file system
parsed = set(
RepositoryId(architecture, repository)
for repository in repositories
for architecture in RepositoryPaths.known_architectures(root, repository)
)
if not parsed:
raise MissingArchitectureError(args.command) raise MissingArchitectureError(args.command)
return sorted(parsed) return sorted(repositories)
@@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2024 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).
@@ -0,0 +1,93 @@
#
# Copyright (c) 2021-2026 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 argparse
from ahriman.application.application import Application
from ahriman.application.handlers.handler import Handler, SubParserAction
from ahriman.core.configuration import Configuration
from ahriman.models.action import Action
from ahriman.models.repository_id import RepositoryId
class Hold(Handler):
"""
package hold handler
"""
@classmethod
def run(cls, args: argparse.Namespace, repository_id: RepositoryId, configuration: Configuration, *,
report: bool) -> None:
"""
callback for command line
Args:
args(argparse.Namespace): command line args
repository_id(RepositoryId): repository unique identifier
configuration(Configuration): configuration instance
report(bool): force enable or disable reporting
"""
client = Application(repository_id, configuration, report=True).reporter
match args.action:
case Action.Remove:
for package in args.package:
client.package_hold_update(package, enabled=False)
case Action.Update:
for package in args.package:
client.package_hold_update(package, enabled=True)
@staticmethod
def _set_package_hold_parser(root: SubParserAction) -> argparse.ArgumentParser:
"""
add parser for hold package subcommand
Args:
root(SubParserAction): subparsers for the commands
Returns:
argparse.ArgumentParser: created argument parser
"""
parser = root.add_parser("package-hold", help="hold package",
description="hold package from automatic updates")
parser.add_argument("package", help="package base", nargs="+")
parser.set_defaults(action=Action.Update, lock=None, quiet=True, report=False, unsafe=True)
return parser
@staticmethod
def _set_package_unhold_parser(root: SubParserAction) -> argparse.ArgumentParser:
"""
add parser for unhold package subcommand
Args:
root(SubParserAction): subparsers for the commands
Returns:
argparse.ArgumentParser: created argument parser
"""
parser = root.add_parser("package-unhold", help="unhold package",
description="remove package hold, allowing automatic updates")
parser.add_argument("package", help="package base", nargs="+")
parser.set_defaults(action=Action.Remove, lock=None, quiet=True, report=False, unsafe=True)
return parser
arguments = [
_set_package_hold_parser,
_set_package_unhold_parser,
]
@@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2024 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).
@@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2024 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).
@@ -0,0 +1,100 @@
#
# Copyright (c) 2021-2026 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 argparse
from dataclasses import replace
from ahriman.application.application import Application
from ahriman.application.handlers.handler import Handler, SubParserAction
from ahriman.core.configuration import Configuration
from ahriman.core.formatters import PkgbuildPrinter
from ahriman.models.action import Action
from ahriman.models.repository_id import RepositoryId
class Pkgbuild(Handler):
"""
package pkgbuild handler
"""
ALLOW_MULTI_ARCHITECTURE_RUN = False # conflicting io
@classmethod
def run(cls, args: argparse.Namespace, repository_id: RepositoryId, configuration: Configuration, *,
report: bool) -> None:
"""
callback for command line
Args:
args(argparse.Namespace): command line args
repository_id(RepositoryId): repository unique identifier
configuration(Configuration): configuration instance
report(bool): force enable or disable reporting
"""
client = Application(repository_id, configuration, report=True).reporter
match args.action:
case Action.List:
changes = client.package_changes_get(args.package)
PkgbuildPrinter(changes)(verbose=True, separator="")
Pkgbuild.check_status(args.exit_code, changes.pkgbuild is not None)
case Action.Remove:
changes = client.package_changes_get(args.package)
client.package_changes_update(args.package, replace(changes, pkgbuild=None))
@staticmethod
def _set_package_pkgbuild_parser(root: SubParserAction) -> argparse.ArgumentParser:
"""
add parser for package pkgbuild subcommand
Args:
root(SubParserAction): subparsers for the commands
Returns:
argparse.ArgumentParser: created argument parser
"""
parser = root.add_parser("package-pkgbuild", help="get package pkgbuild",
description="retrieve package PKGBUILD stored in database",
epilog="This command requests package status from the web interface "
"if it is available.")
parser.add_argument("package", help="package base")
parser.add_argument("-e", "--exit-code", help="return non-zero exit status if result is empty",
action="store_true")
parser.set_defaults(action=Action.List, lock=None, quiet=True, report=False, unsafe=True)
return parser
@staticmethod
def _set_package_pkgbuild_remove_parser(root: SubParserAction) -> argparse.ArgumentParser:
"""
add parser for package pkgbuild remove subcommand
Args:
root(SubParserAction): subparsers for the commands
Returns:
argparse.ArgumentParser: created argument parser
"""
parser = root.add_parser("package-pkgbuild-remove", help="remove package pkgbuild",
description="remove the package PKGBUILD stored remotely")
parser.add_argument("package", help="package base")
parser.set_defaults(action=Action.Remove, exit_code=False, lock=None, quiet=True, report=False, unsafe=True)
return parser
arguments = [_set_package_pkgbuild_parser, _set_package_pkgbuild_remove_parser]
@@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2024 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).
@@ -0,0 +1,69 @@
#
# Copyright (c) 2021-2026 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 argparse
from ahriman.application.application import Application
from ahriman.application.handlers.handler import Handler, SubParserAction
from ahriman.core.configuration import Configuration
from ahriman.models.repository_id import RepositoryId
class Reload(Handler):
"""
web server reload handler
"""
ALLOW_MULTI_ARCHITECTURE_RUN = False # system-wide action
@classmethod
def run(cls, args: argparse.Namespace, repository_id: RepositoryId, configuration: Configuration, *,
report: bool) -> None:
"""
callback for command line
Args:
args(argparse.Namespace): command line args
repository_id(RepositoryId): repository unique identifier
configuration(Configuration): configuration instance
report(bool): force enable or disable reporting
"""
client = Application(repository_id, configuration, report=True).reporter
client.configuration_reload()
@staticmethod
def _set_web_reload_parser(root: SubParserAction) -> argparse.ArgumentParser:
"""
add parser for web reload subcommand
Args:
root(SubParserAction): subparsers for the commands
Returns:
argparse.ArgumentParser: created argument parser
"""
parser = root.add_parser("web-reload", help="reload configuration",
description="reload web server configuration",
epilog="This method forces the web server to reload its configuration. "
"Note, however, that this method does not apply all configuration changes "
"(like ports, authentication, etc)")
parser.set_defaults(architecture="", lock=None, quiet=True, report=False, repository="", unsafe=True)
return parser
arguments = [_set_web_reload_parser]
@@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2024 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).
@@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2024 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).
@@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2024 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).
@@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2024 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).
@@ -47,7 +47,7 @@ class Restore(Handler):
report(bool): force enable or disable reporting report(bool): force enable or disable reporting
""" """
with tarfile.open(args.path) as archive: with tarfile.open(args.path) as archive:
archive.extractall(path=args.output) # nosec archive.extractall(path=args.output, filter="data")
@staticmethod @staticmethod
def _set_repo_restore_parser(root: SubParserAction) -> argparse.ArgumentParser: def _set_repo_restore_parser(root: SubParserAction) -> argparse.ArgumentParser:
@@ -0,0 +1,131 @@
#
# Copyright (c) 2021-2026 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 argparse
from dataclasses import replace
from pathlib import Path
from ahriman.application.application import Application
from ahriman.application.handlers.add import Add
from ahriman.application.handlers.handler import Handler, SubParserAction
from ahriman.core.configuration import Configuration
from ahriman.core.exceptions import UnknownPackageError
from ahriman.core.utils import extract_user
from ahriman.models.package import Package
from ahriman.models.package_source import PackageSource
from ahriman.models.repository_id import RepositoryId
class Rollback(Handler):
"""
package rollback handler
"""
@classmethod
def run(cls, args: argparse.Namespace, repository_id: RepositoryId, configuration: Configuration, *,
report: bool) -> None:
"""
callback for command line
Args:
args(argparse.Namespace): command line args
repository_id(RepositoryId): repository unique identifier
configuration(Configuration): configuration instance
report(bool): force enable or disable reporting
"""
application = Application(repository_id, configuration, report=report)
application.on_start()
package = Rollback.package_load(application, args.package, args.version)
artifacts = Rollback.package_artifacts(application, package)
args.package = [str(artifact) for artifact in artifacts]
Add.perform_action(application, args)
if args.hold:
application.reporter.package_hold_update(package.base, enabled=True)
@staticmethod
def _set_package_rollback_parser(root: SubParserAction) -> argparse.ArgumentParser:
"""
add parser for package rollback subcommand
Args:
root(SubParserAction): subparsers for the commands
Returns:
argparse.ArgumentParser: created argument parser
"""
parser = root.add_parser("package-rollback", help="rollback package",
description="rollback package to specified version from archives")
parser.add_argument("package", help="package base")
parser.add_argument("version", help="package version")
parser.add_argument("--hold", help="hold package afterwards",
action=argparse.BooleanOptionalAction, default=True)
parser.add_argument("-u", "--username", help="build as user", default=extract_user())
parser.set_defaults(aur=False, changes=False, check_files=False, dependencies=False, dry_run=False,
exit_code=True, increment=False, now=True, local=False, manual=False, refresh=False,
source=PackageSource.Archive, variable=None, vcs=False)
return parser
@staticmethod
def package_artifacts(application: Application, package: Package) -> list[Path]:
"""
look for requested package artifacts and return paths to them
Args:
application(Application): application instance
package(Package): package descriptor
Returns:
list[Path]: paths to found artifacts
Raises:
UnknownPackageError: if artifacts do not exist
"""
# lookup for built artifacts
artifacts = application.repository.package_archives_lookup(package)
if not artifacts:
raise UnknownPackageError(package.base)
return artifacts
@staticmethod
def package_load(application: Application, package_base: str, version: str) -> Package:
"""
load package from repository, while setting requested version
Args:
application(Application): application instance
package_base(str): package base
version(str): package version
Returns:
Package: loaded package
Raises:
UnknownPackageError: if package does not exist
"""
try:
package, _ = next(iter(application.reporter.package_get(package_base)))
return replace(package, version=version)
except StopIteration:
raise UnknownPackageError(package_base) from None
arguments = [_set_package_rollback_parser]
@@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2024 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).
@@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2024 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).
@@ -21,12 +21,14 @@ import argparse
from collections.abc import Callable, Iterable from collections.abc import Callable, Iterable
from dataclasses import fields from dataclasses import fields
from typing import ClassVar
from ahriman.application.handlers.handler import Handler, SubParserAction from ahriman.application.handlers.handler import Handler, SubParserAction
from ahriman.core.alpm.remote import AUR, Official from ahriman.core.alpm.remote import AUR, Official
from ahriman.core.configuration import Configuration from ahriman.core.configuration import Configuration
from ahriman.core.exceptions import OptionError from ahriman.core.exceptions import OptionError
from ahriman.core.formatters import AurPrinter from ahriman.core.formatters import AurPrinter
from ahriman.core.types import Comparable
from ahriman.models.aur_package import AURPackage from ahriman.models.aur_package import AURPackage
from ahriman.models.repository_id import RepositoryId from ahriman.models.repository_id import RepositoryId
@@ -40,7 +42,7 @@ class Search(Handler):
""" """
ALLOW_MULTI_ARCHITECTURE_RUN = False # system-wide action ALLOW_MULTI_ARCHITECTURE_RUN = False # system-wide action
SORT_FIELDS = { SORT_FIELDS: ClassVar[set[str]] = {
field.name field.name
for field in fields(AURPackage) for field in fields(AURPackage)
if field.default_factory is not list if field.default_factory is not list
@@ -114,7 +116,7 @@ class Search(Handler):
raise OptionError(sort_by) raise OptionError(sort_by)
# always sort by package name at the last # always sort by package name at the last
# well technically it is not a string, but we can deal with it # well technically it is not a string, but we can deal with it
comparator: Callable[[AURPackage], tuple[str, str]] =\ comparator: Callable[[AURPackage], Comparable] = \
lambda package: (getattr(package, sort_by), package.name) lambda package: (getattr(package, sort_by), package.name)
return sorted(packages, key=comparator) return sorted(packages, key=comparator)
@@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2024 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).
@@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2024 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).
@@ -21,6 +21,7 @@ import argparse
from pathlib import Path from pathlib import Path
from pwd import getpwuid from pwd import getpwuid
from typing import ClassVar
from urllib.parse import quote_plus as url_encode from urllib.parse import quote_plus as url_encode
from ahriman.application.application import Application from ahriman.application.application import Application
@@ -46,9 +47,9 @@ class Setup(Handler):
ALLOW_MULTI_ARCHITECTURE_RUN = False # conflicting io ALLOW_MULTI_ARCHITECTURE_RUN = False # conflicting io
ARCHBUILD_COMMAND_PATH = Path("/") / "usr" / "bin" / "archbuild" ARCHBUILD_COMMAND_PATH: ClassVar[Path] = Path("/") / "usr" / "bin" / "archbuild"
MIRRORLIST_PATH = Path("/") / "etc" / "pacman.d" / "mirrorlist" MIRRORLIST_PATH: ClassVar[Path] = Path("/") / "etc" / "pacman.d" / "mirrorlist"
SUDOERS_DIR_PATH = Path("/") / "etc" / "sudoers.d" SUDOERS_DIR_PATH: ClassVar[Path] = Path("/") / "etc" / "sudoers.d"
@classmethod @classmethod
def run(cls, args: argparse.Namespace, repository_id: RepositoryId, configuration: Configuration, *, def run(cls, args: argparse.Namespace, repository_id: RepositoryId, configuration: Configuration, *,
@@ -71,6 +72,7 @@ class Setup(Handler):
application = Application(repository_id, configuration, report=report) application = Application(repository_id, configuration, report=report)
# basically we create configuration here as root, but it is ok, because those files are only used for reading
Setup.configuration_create_makepkg(args.packager, args.makeflags_jobs, application.repository.paths) Setup.configuration_create_makepkg(args.packager, args.makeflags_jobs, application.repository.paths)
Setup.executable_create(application.repository.paths, repository_id) Setup.executable_create(application.repository.paths, repository_id)
repository_server = f"file://{application.repository.paths.repository}" if args.server is None else args.server repository_server = f"file://{application.repository.paths.repository}" if args.server is None else args.server
@@ -78,9 +80,11 @@ class Setup(Handler):
repository_id, args.from_configuration, args.mirror, args.multilib, repository_server) repository_id, args.from_configuration, args.mirror, args.multilib, repository_server)
Setup.configuration_create_sudo(application.repository.paths, repository_id) Setup.configuration_create_sudo(application.repository.paths, repository_id)
application.repository.repo.init() # finish initialization
# lazy database sync with application.repository.paths.preserve_owner():
application.repository.pacman.handle # pylint: disable=pointless-statement application.repository.repo.init()
# lazy database sync
application.repository.pacman.handle # pylint: disable=pointless-statement
@staticmethod @staticmethod
def _set_service_setup_parser(root: SubParserAction) -> argparse.ArgumentParser: def _set_service_setup_parser(root: SubParserAction) -> argparse.ArgumentParser:
@@ -247,7 +251,7 @@ class Setup(Handler):
content = f"PACKAGER='{packager}'\n" content = f"PACKAGER='{packager}'\n"
if makeflags_jobs: if makeflags_jobs:
content += """MAKEFLAGS="-j$(nproc)"\n""" content += "MAKEFLAGS=\"-j$(nproc)\"\n"
uid, _ = paths.root_owner uid, _ = paths.root_owner
home_dir = Path(getpwuid(uid).pw_dir) home_dir = Path(getpwuid(uid).pw_dir)
@@ -279,6 +283,5 @@ class Setup(Handler):
command = Setup.build_command(paths.root, repository_id) command = Setup.build_command(paths.root, repository_id)
command.unlink(missing_ok=True) command.unlink(missing_ok=True)
command.symlink_to(Setup.ARCHBUILD_COMMAND_PATH) command.symlink_to(Setup.ARCHBUILD_COMMAND_PATH)
paths.chown(command) # we would like to keep owner inside ahriman's home
arguments = [_set_service_setup_parser] arguments = [_set_service_setup_parser]
@@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2024 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).
@@ -18,12 +18,12 @@
# 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 argparse import argparse
import code
import sys import sys
from pathlib import Path from pathlib import Path
from ahriman.application.handlers.handler import Handler, SubParserAction from ahriman.application.handlers.handler import Handler, SubParserAction
from ahriman.application.interactive_shell import InteractiveShell
from ahriman.core.configuration import Configuration from ahriman.core.configuration import Configuration
from ahriman.core.formatters import StringPrinter from ahriman.core.formatters import StringPrinter
from ahriman.models.repository_id import RepositoryId from ahriman.models.repository_id import RepositoryId
@@ -58,11 +58,13 @@ class Shell(Handler):
"configuration": configuration, "configuration": configuration,
"repository_id": repository_id, "repository_id": repository_id,
} }
console = InteractiveShell(locals=local_variables)
if args.code is None: match args.code:
code.interact(local=local_variables) case None:
else: console.interact()
code.InteractiveConsole(locals=local_variables).runcode(args.code) case snippet:
console.runcode(snippet)
@staticmethod @staticmethod
def _set_service_shell_parser(root: SubParserAction) -> argparse.ArgumentParser: def _set_service_shell_parser(root: SubParserAction) -> argparse.ArgumentParser:
@@ -79,6 +81,7 @@ class Shell(Handler):
description="drop into python shell") description="drop into python shell")
parser.add_argument("code", help="instead of dropping into shell, just execute the specified code", nargs="?") parser.add_argument("code", help="instead of dropping into shell, just execute the specified code", nargs="?")
parser.add_argument("-v", "--verbose", help=argparse.SUPPRESS, action="store_true") parser.add_argument("-v", "--verbose", help=argparse.SUPPRESS, action="store_true")
parser.add_argument("-o", "--output", help="output commands and result to the file", type=Path)
parser.set_defaults(lock=None, report=False) parser.set_defaults(lock=None, report=False)
return parser return parser
@@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2024 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).
@@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2024 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).
@@ -27,7 +27,7 @@ from pathlib import Path
from ahriman.application.application import Application from ahriman.application.application import Application
from ahriman.application.handlers.handler import Handler, SubParserAction from ahriman.application.handlers.handler import Handler, SubParserAction
from ahriman.core.configuration import Configuration from ahriman.core.configuration import Configuration
from ahriman.core.formatters import EventStatsPrinter, PackageStatsPrinter from ahriman.core.formatters import EventStatsPrinter, PackageStatsPrinter, RepositoryStatsPrinter
from ahriman.core.utils import enum_values, pretty_datetime from ahriman.core.utils import enum_values, pretty_datetime
from ahriman.models.event import Event, EventType from ahriman.models.event import Event, EventType
from ahriman.models.repository_id import RepositoryId from ahriman.models.repository_id import RepositoryId
@@ -64,6 +64,7 @@ class Statistics(Handler):
match args.package: match args.package:
case None: case None:
RepositoryStatsPrinter(repository_id, application.reporter.statistics())(verbose=True)
Statistics.stats_per_package(args.event, events, args.chart) Statistics.stats_per_package(args.event, events, args.chart)
case _: case _:
Statistics.stats_for_package(args.event, events, args.chart) Statistics.stats_for_package(args.event, events, args.chart)
@@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2024 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).
@@ -25,6 +25,7 @@ from ahriman.application.application import Application
from ahriman.application.handlers.handler import Handler, SubParserAction from ahriman.application.handlers.handler import Handler, SubParserAction
from ahriman.core.configuration import Configuration from ahriman.core.configuration import Configuration
from ahriman.core.formatters import PackagePrinter, StatusPrinter from ahriman.core.formatters import PackagePrinter, StatusPrinter
from ahriman.core.types import Comparable
from ahriman.core.utils import enum_values from ahriman.core.utils import enum_values
from ahriman.models.build_status import BuildStatus, BuildStatusEnum from ahriman.models.build_status import BuildStatus, BuildStatusEnum
from ahriman.models.package import Package from ahriman.models.package import Package
@@ -51,7 +52,7 @@ class Status(Handler):
report(bool): force enable or disable reporting report(bool): force enable or disable reporting
""" """
# we are using reporter here # we are using reporter here
client = Application(repository_id, configuration, report=True).repository.reporter client = Application(repository_id, configuration, report=True).reporter
if args.ahriman: if args.ahriman:
service_status = client.status_get() service_status = client.status_get()
StatusPrinter(service_status.status)(verbose=args.info) StatusPrinter(service_status.status)(verbose=args.info)
@@ -64,8 +65,8 @@ class Status(Handler):
Status.check_status(args.exit_code, packages) Status.check_status(args.exit_code, packages)
comparator: Callable[[tuple[Package, BuildStatus]], str] = lambda item: item[0].base comparator: Callable[[tuple[Package, BuildStatus]], Comparable] = lambda item: item[0].base
filter_fn: Callable[[tuple[Package, BuildStatus]], bool] =\ filter_fn: Callable[[tuple[Package, BuildStatus]], bool] = \
lambda item: args.status is None or item[1].status == args.status lambda item: args.status is None or item[1].status == args.status
for package, package_status in sorted(filter(filter_fn, packages), key=comparator): for package, package_status in sorted(filter(filter_fn, packages), key=comparator):
PackagePrinter(package, package_status)(verbose=args.info) PackagePrinter(package, package_status)(verbose=args.info)
@@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2024 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).
@@ -47,8 +47,7 @@ class StatusUpdate(Handler):
configuration(Configuration): configuration instance configuration(Configuration): configuration instance
report(bool): force enable or disable reporting report(bool): force enable or disable reporting
""" """
application = Application(repository_id, configuration, report=True) client = Application(repository_id, configuration, report=True).reporter
client = application.repository.reporter
match args.action: match args.action:
case Action.Update if args.package: case Action.Update if args.package:
@@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2024 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).
@@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2024 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).
@@ -21,6 +21,7 @@ import argparse
from ahriman.application.handlers.handler import Handler, SubParserAction from ahriman.application.handlers.handler import Handler, SubParserAction
from ahriman.core.configuration import Configuration from ahriman.core.configuration import Configuration
from ahriman.core.utils import symlink_relative, walk
from ahriman.models.repository_id import RepositoryId from ahriman.models.repository_id import RepositoryId
from ahriman.models.repository_paths import RepositoryPaths from ahriman.models.repository_paths import RepositoryPaths
@@ -49,6 +50,7 @@ class TreeMigrate(Handler):
target_tree.tree_create() target_tree.tree_create()
# perform migration # perform migration
TreeMigrate.tree_move(current_tree, target_tree) TreeMigrate.tree_move(current_tree, target_tree)
TreeMigrate.symlinks_fix(target_tree)
@staticmethod @staticmethod
def _set_service_tree_migrate_parser(root: SubParserAction) -> argparse.ArgumentParser: def _set_service_tree_migrate_parser(root: SubParserAction) -> argparse.ArgumentParser:
@@ -66,6 +68,22 @@ class TreeMigrate(Handler):
parser.set_defaults(lock=None, quiet=True, report=False) parser.set_defaults(lock=None, quiet=True, report=False)
return parser return parser
@staticmethod
def symlinks_fix(paths: RepositoryPaths) -> None:
"""
fix package archive symlinks
Args:
paths(RepositoryPaths): new repository paths
"""
archives = {path.name: path for path in walk(paths.archive)}
for symlink in walk(paths.repository):
if symlink.exists(): # no need to check for symlinks as we have just walked through the tree
continue
if (source_archive := archives.get(symlink.name)) is not None:
symlink.unlink()
symlink_relative(symlink, source_archive)
@staticmethod @staticmethod
def tree_move(from_tree: RepositoryPaths, to_tree: RepositoryPaths) -> None: def tree_move(from_tree: RepositoryPaths, to_tree: RepositoryPaths) -> None:
""" """
@@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2024 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).
@@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2024 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).
@@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2024 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).
@@ -48,22 +48,7 @@ class Update(Handler):
""" """
application = Application(repository_id, configuration, report=report, refresh_pacman_database=args.refresh) application = Application(repository_id, configuration, report=report, refresh_pacman_database=args.refresh)
application.on_start() application.on_start()
Update.perform_action(application, args)
packages = application.updates(args.package, aur=args.aur, local=args.local, manual=args.manual, vcs=args.vcs,
check_files=args.check_files)
if args.changes: # generate changes if requested
application.changes(packages)
if args.dry_run: # exit from application if no build requested
Update.check_status(args.exit_code, packages) # status code check
return
packages = application.with_dependencies(packages, process_dependencies=args.dependencies)
packagers = Packagers(args.username, {package.base: package.packager for package in packages})
application.print_updates(packages, log_fn=application.logger.info)
result = application.update(packages, packagers, bump_pkgrel=args.increment)
Update.check_status(args.exit_code, not result.is_empty)
@staticmethod @staticmethod
def _set_repo_check_parser(root: SubParserAction) -> argparse.ArgumentParser: def _set_repo_check_parser(root: SubParserAction) -> argparse.ArgumentParser:
@@ -153,6 +138,31 @@ class Update(Handler):
return print(line) if dry_run else application.logger.info(line) # pylint: disable=bad-builtin return print(line) if dry_run else application.logger.info(line) # pylint: disable=bad-builtin
return inner return inner
@staticmethod
def perform_action(application: Application, args: argparse.Namespace) -> None:
"""
perform update action
Args:
application(Application): application instance
args(argparse.Namespace): command line args
"""
packages = application.updates(args.package, aur=args.aur, local=args.local, manual=args.manual, vcs=args.vcs,
check_files=args.check_files)
if args.changes: # generate changes if requested
application.changes(packages)
if args.dry_run: # exit from application if no build requested
Update.check_status(args.exit_code, packages) # status code check
return
packages = application.with_dependencies(packages, process_dependencies=args.dependencies)
packagers = Packagers(args.username, {package.base: package.packager for package in packages})
application.print_updates(packages, log_fn=application.logger.info)
result = application.update(packages, packagers, bump_pkgrel=args.increment)
Update.check_status(args.exit_code, not result.is_empty)
arguments = [ arguments = [
_set_repo_check_parser, _set_repo_check_parser,
_set_repo_update_parser, _set_repo_update_parser,
@@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2024 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).
@@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2024 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).
@@ -52,7 +52,7 @@ class Validate(Handler):
""" """
from ahriman.core.configuration.validator import Validator from ahriman.core.configuration.validator import Validator
schema = Validate.schema(repository_id, configuration) schema = Validate.schema(configuration)
validator = Validator(configuration=configuration, schema=schema) validator = Validator(configuration=configuration, schema=schema)
if validator.validate(configuration.dump()): if validator.validate(configuration.dump()):
@@ -83,12 +83,11 @@ class Validate(Handler):
return parser return parser
@staticmethod @staticmethod
def schema(repository_id: RepositoryId, configuration: Configuration) -> ConfigurationSchema: def schema(configuration: Configuration) -> ConfigurationSchema:
""" """
get schema with triggers get schema with triggers
Args: Args:
repository_id(RepositoryId): repository unique identifier
configuration(Configuration): configuration instance configuration(Configuration): configuration instance
Returns: Returns:
@@ -107,12 +106,12 @@ class Validate(Handler):
continue continue
# default settings if any # default settings if any
for schema_name, schema in trigger_class.configuration_schema(repository_id, None).items(): for schema_name, schema in trigger_class.configuration_schema(None).items():
erased = Validate.schema_erase_required(copy.deepcopy(schema)) erased = Validate.schema_erase_required(copy.deepcopy(schema))
root[schema_name] = Validate.schema_merge(root.get(schema_name, {}), erased) root[schema_name] = Validate.schema_merge(root.get(schema_name, {}), erased)
# settings according to enabled triggers # settings according to enabled triggers
for schema_name, schema in trigger_class.configuration_schema(repository_id, configuration).items(): for schema_name, schema in trigger_class.configuration_schema(configuration).items():
root[schema_name] = Validate.schema_merge(root.get(schema_name, {}), copy.deepcopy(schema)) root[schema_name] = Validate.schema_merge(root.get(schema_name, {}), copy.deepcopy(schema))
return root return root
@@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2024 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).
@@ -21,8 +21,9 @@ import argparse
import re import re
import sys import sys
from collections.abc import Generator from collections.abc import Iterator
from importlib import metadata from importlib import metadata
from typing import ClassVar
from ahriman import __version__ from ahriman import __version__
from ahriman.application.handlers.handler import Handler, SubParserAction from ahriman.application.handlers.handler import Handler, SubParserAction
@@ -36,11 +37,11 @@ class Versions(Handler):
version handler version handler
Attributes: Attributes:
PEP423_PACKAGE_NAME(str): (class attribute) special regex for valid PEP423 package name PEP423_PACKAGE_NAME(re.Pattern[str]): (class attribute) special regex for valid PEP423 package name
""" """
ALLOW_MULTI_ARCHITECTURE_RUN = False # system-wide action ALLOW_MULTI_ARCHITECTURE_RUN = False # system-wide action
PEP423_PACKAGE_NAME = re.compile(r"^[A-Za-z0-9._-]+") PEP423_PACKAGE_NAME: ClassVar[re.Pattern[str]] = re.compile(r"^[A-Za-z0-9._-]+")
@classmethod @classmethod
def run(cls, args: argparse.Namespace, repository_id: RepositoryId, configuration: Configuration, *, def run(cls, args: argparse.Namespace, repository_id: RepositoryId, configuration: Configuration, *,
@@ -76,7 +77,7 @@ class Versions(Handler):
return parser return parser
@staticmethod @staticmethod
def package_dependencies(root: str) -> Generator[tuple[str, str], None, None]: def package_dependencies(root: str) -> Iterator[tuple[str, str]]:
""" """
extract list of ahriman package dependencies installed into system with their versions extract list of ahriman package dependencies installed into system with their versions
@@ -86,7 +87,7 @@ class Versions(Handler):
Yields: Yields:
tuple[str, str]: map of installed dependency to its version tuple[str, str]: map of installed dependency to its version
""" """
def dependencies_by_key(key: str) -> Generator[str, None, None]: def dependencies_by_key(key: str) -> Iterator[str]:
# in importlib it returns requires in the following format # in importlib it returns requires in the following format
# ["pytest (>=3.0.0) ; extra == 'test'", "pytest-cov ; extra == 'test'"] # ["pytest (>=3.0.0) ; extra == 'test'", "pytest-cov ; extra == 'test'"]
try: try:
@@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2024 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).
@@ -0,0 +1,60 @@
#
# Copyright (c) 2021-2026 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/>.
#
from code import InteractiveConsole
from importlib.util import find_spec
from typing import Any
class InteractiveShell(InteractiveConsole):
"""
wrapper around :class:`code.InteractiveConsole` to pass :func:`interact()` to IPython shell
"""
@staticmethod
def has_ipython() -> bool:
"""
check if IPython shell is available
Returns:
bool: ``True`` if IPython shell is available, ``False`` otherwise
"""
try:
return find_spec("IPython.terminal.embed") is not None
except ModuleNotFoundError:
return False
def interact(self, *args: Any, **kwargs: Any) -> None:
"""
pass controller to IPython shell
Args:
*args(Any): positional arguments
**kwargs(Any): keyword arguments
"""
if self.has_ipython():
from IPython.terminal.embed import InteractiveShellEmbed
shell = InteractiveShellEmbed(user_ns=self.locals) # type: ignore[no-untyped-call]
shell.show_banner() # type: ignore[no-untyped-call]
shell.interact() # type: ignore[no-untyped-call]
else:
# fallback to default
import readline # pylint: disable=unused-import
InteractiveConsole.interact(self, *args, **kwargs)
@@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2024 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).
@@ -158,7 +158,7 @@ class Lock(LazyLogging):
""" """
check if current user is actually owner of ahriman root check if current user is actually owner of ahriman root
""" """
check_user(self.paths, unsafe=self.unsafe) check_user(self.paths.root, unsafe=self.unsafe)
self.paths.tree_create() self.paths.tree_create()
def check_version(self) -> None: def check_version(self) -> None:
@@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2024 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).
@@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2024 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).
@@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2024 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).
@@ -21,13 +21,14 @@ import itertools
import shutil import shutil
import tarfile import tarfile
from collections.abc import Generator, Iterable from collections.abc import Iterable, Iterator
from functools import cached_property from functools import cached_property
from pathlib import Path from pathlib import Path
from pyalpm import DB, Handle, Package, SIG_DATABASE_OPTIONAL, SIG_PACKAGE_OPTIONAL # type: ignore[import-not-found] from pyalpm import DB, Package, SIG_DATABASE_OPTIONAL, SIG_PACKAGE_OPTIONAL # type: ignore[import-not-found]
from string import Template from string import Template
from ahriman.core.alpm.pacman_database import PacmanDatabase from ahriman.core.alpm.pacman_database import PacmanDatabase
from ahriman.core.alpm.pacman_handle import PacmanHandle
from ahriman.core.configuration import Configuration from ahriman.core.configuration import Configuration
from ahriman.core.log import LazyLogging from ahriman.core.log import LazyLogging
from ahriman.core.utils import trim_package from ahriman.core.utils import trim_package
@@ -61,16 +62,16 @@ class Pacman(LazyLogging):
self.refresh_database = refresh_database self.refresh_database = refresh_database
@cached_property @cached_property
def handle(self) -> Handle: def handle(self) -> PacmanHandle:
""" """
pyalpm handle pyalpm handle
Returns: Returns:
Handle: generated pyalpm handle instance PacmanHandle: generated pyalpm handle instance
""" """
return self.__create_handle(refresh_database=self.refresh_database) return self.__create_handle(refresh_database=self.refresh_database)
def __create_handle(self, *, refresh_database: PacmanSynchronization) -> Handle: def __create_handle(self, *, refresh_database: PacmanSynchronization) -> PacmanHandle:
""" """
create lazy handle function create lazy handle function
@@ -78,14 +79,14 @@ class Pacman(LazyLogging):
refresh_database(PacmanSynchronization): synchronize local cache to remote refresh_database(PacmanSynchronization): synchronize local cache to remote
Returns: Returns:
Handle: fully initialized pacman handle PacmanHandle: fully initialized pacman handle
""" """
pacman_root = self.configuration.getpath("alpm", "database") pacman_root = self.configuration.getpath("alpm", "database")
use_ahriman_cache = self.configuration.getboolean("alpm", "use_ahriman_cache") use_ahriman_cache = self.configuration.getboolean("alpm", "use_ahriman_cache")
database_path = self.repository_paths.pacman if use_ahriman_cache else pacman_root database_path = self.repository_paths.pacman if use_ahriman_cache else pacman_root
root = self.configuration.getpath("alpm", "root") root = self.configuration.getpath("alpm", "root")
handle = Handle(str(root), str(database_path)) handle = PacmanHandle(str(root), str(database_path))
for repository in self.configuration.getlist("alpm", "repositories"): for repository in self.configuration.getlist("alpm", "repositories"):
database = self.database_init(handle, repository, self.repository_id.architecture) database = self.database_init(handle, repository, self.repository_id.architecture)
@@ -99,12 +100,12 @@ class Pacman(LazyLogging):
return handle return handle
def database_copy(self, handle: Handle, database: DB, pacman_root: Path, *, use_ahriman_cache: bool) -> None: def database_copy(self, handle: PacmanHandle, database: DB, pacman_root: Path, *, use_ahriman_cache: bool) -> None:
""" """
copy database from the operating system root to the ahriman local home copy database from the operating system root to the ahriman local home
Args: Args:
handle(Handle): pacman handle which will be used for database copying handle(PacmanHandle): pacman handle which will be used for database copying
database(DB): pacman database instance to be copied database(DB): pacman database instance to be copied
pacman_root(Path): operating system pacman root pacman_root(Path): operating system pacman root
use_ahriman_cache(bool): use local ahriman cache instead of system one use_ahriman_cache(bool): use local ahriman cache instead of system one
@@ -130,15 +131,15 @@ class Pacman(LazyLogging):
return # database for some reason deos not exist return # database for some reason deos not exist
self.logger.info("copy pacman database %s from operating system root to ahriman's home %s", src, dst) self.logger.info("copy pacman database %s from operating system root to ahriman's home %s", src, dst)
shutil.copy(src, dst) with self.repository_paths.preserve_owner():
self.repository_paths.chown(dst) shutil.copy(src, dst)
def database_init(self, handle: Handle, repository: str, architecture: str) -> DB: def database_init(self, handle: PacmanHandle, repository: str, architecture: str) -> DB:
""" """
create database instance from pacman handler and set its properties create database instance from pacman handler and set its properties
Args: Args:
handle(Handle): pacman handle which will be used for database initializing handle(PacmanHandle): pacman handle which will be used for database initializing
repository(str): pacman repository name (e.g. core) repository(str): pacman repository name (e.g. core)
architecture(str): repository architecture architecture(str): repository architecture
@@ -164,12 +165,12 @@ class Pacman(LazyLogging):
return database return database
def database_sync(self, handle: Handle, *, force: bool) -> None: def database_sync(self, handle: PacmanHandle, *, force: bool) -> None:
""" """
sync local database sync local database
Args: Args:
handle(Handle): pacman handle which will be used for database sync handle(PacmanHandle): pacman handle which will be used for database sync
force(bool): force database synchronization (same as ``pacman -Syy``) force(bool): force database synchronization (same as ``pacman -Syy``)
""" """
self.logger.info("refresh ahriman's home pacman database (force refresh %s)", force) self.logger.info("refresh ahriman's home pacman database (force refresh %s)", force)
@@ -188,7 +189,7 @@ class Pacman(LazyLogging):
Returns: Returns:
dict[str, set[str]]: map of package name to its list of files dict[str, set[str]]: map of package name to its list of files
""" """
def extract(tar: tarfile.TarFile, versions: dict[str, str]) -> Generator[tuple[str, set[str]], None, None]: def extract(tar: tarfile.TarFile, versions: dict[str, str]) -> Iterator[tuple[str, set[str]]]:
for package_name, version in versions.items(): for package_name, version in versions.items():
path = Path(f"{package_name}-{version}") / "files" path = Path(f"{package_name}-{version}") / "files"
try: try:
@@ -223,7 +224,7 @@ class Pacman(LazyLogging):
return result return result
def package(self, package_name: str) -> Generator[Package, None, None]: def package(self, package_name: str) -> Iterator[Package]:
""" """
retrieve list of the packages from the repository by name retrieve list of the packages from the repository by name
@@ -255,3 +256,20 @@ class Pacman(LazyLogging):
result.update(trim_package(provides) for provides in package.provides) result.update(trim_package(provides) for provides in package.provides)
return result return result
def provided_by(self, package_name: str) -> Iterator[Package]:
"""
search through databases and emit packages which provides the ``package_name``
Args:
package_name(str): package name to search
Yields:
Package: list of packages which were returned by the query
"""
def is_package_provided(package: Package) -> bool:
provides = [trim_package(name) for name in package.provides]
return package_name in provides
for database in self.handle.get_syncdbs():
yield from filter(is_package_provided, database.search(package_name))
@@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2024 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).
@@ -23,6 +23,7 @@ import shutil
from email.utils import parsedate_to_datetime from email.utils import parsedate_to_datetime
from pathlib import Path from pathlib import Path
from pyalpm import DB # type: ignore[import-not-found] from pyalpm import DB # type: ignore[import-not-found]
from typing import ClassVar
from urllib.parse import urlparse from urllib.parse import urlparse
from ahriman.core.configuration import Configuration from ahriman.core.configuration import Configuration
@@ -41,7 +42,7 @@ class PacmanDatabase(SyncHttpClient):
sync_files_database(bool): sync files database sync_files_database(bool): sync files database
""" """
LAST_MODIFIED_HEADER = "Last-Modified" LAST_MODIFIED_HEADER: ClassVar[str] = "Last-Modified"
def __init__(self, database: DB, configuration: Configuration) -> None: def __init__(self, database: DB, configuration: Configuration) -> None:
""" """
@@ -137,8 +138,14 @@ class PacmanDatabase(SyncHttpClient):
Args: Args:
force(bool): force database synchronization (same as ``pacman -Syy``) force(bool): force database synchronization (same as ``pacman -Syy``)
Raises:
PacmanError: on operation error (invalid scheme or incomplete configuration)
""" """
server = next(iter(self.database.servers)) try:
server = next(iter(self.database.servers))
except StopIteration:
raise PacmanError("No configured servers available for database") from None
filename = f"{self.database.name}.files.tar.gz" filename = f"{self.database.name}.files.tar.gz"
url = f"{server}/{filename}" url = f"{server}/{filename}"
@@ -0,0 +1,81 @@
#
# Copyright (c) 2021-2026 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/>.
#
from pathlib import Path
from pyalpm import Handle, Package # type: ignore[import-not-found]
from tempfile import TemporaryDirectory
from typing import Any, ClassVar, Self
class PacmanHandle:
"""
lightweight wrapper for pacman handle to be used for direct alpm operations (e.g. package load)
Attributes:
handle(Handle): pyalpm handle instance
"""
_ephemeral: ClassVar[Self | None] = None
def __init__(self, *args: Any, **kwargs: Any) -> None:
"""
Args:
*args(Any): positional arguments for :class:`pyalpm.Handle`
**kwargs(Any): keyword arguments for :class:`pyalpm.Handle`
"""
self.handle = Handle(*args, **kwargs)
@classmethod
def ephemeral(cls) -> Self:
"""
create temporary instance with no access to real databases
Returns:
Self: loaded class
"""
if cls._ephemeral is None:
# handle creates alpm version file, but we don't use it
# so it is ok to just remove it
with TemporaryDirectory(ignore_cleanup_errors=True) as dir_name:
cls._ephemeral = cls("/", dir_name)
return cls._ephemeral
def package_load(self, path: Path) -> Package:
"""
load package from path to the archive
Args:
path(Path): path to package archive
Returns:
Package: package instance
"""
return self.handle.load_pkg(str(path))
def __getattr__(self, item: str) -> Any:
"""
proxy methods for :class:`pyalpm.Handle`, because it doesn't allow subclassing
Args:
item(str): property name
Returns:
Any: attribute by its name
"""
return self.handle.__getattribute__(item)
@@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2024 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).
@@ -21,7 +21,7 @@ import itertools
import re import re
import shlex import shlex
from collections.abc import Generator from collections.abc import Iterator
from enum import StrEnum from enum import StrEnum
from typing import IO from typing import IO
@@ -34,13 +34,14 @@ class PkgbuildToken(StrEnum):
well-known tokens dictionary well-known tokens dictionary
Attributes: Attributes:
ArrayEnds(PkgbuildToken): (class attribute) array ends token ArrayEnds(PkgbuildToken): array ends token
ArrayStarts(PkgbuildToken): (class attribute) array starts token ArrayStarts(PkgbuildToken): array starts token
Comma(PkgbuildToken): (class attribute) comma token Comma(PkgbuildToken): comma token
Comment(PkgbuildToken): (class attribute) comment token Comment(PkgbuildToken): comment token
FunctionDeclaration(PkgbuildToken): (class attribute) function declaration token FunctionDeclaration(PkgbuildToken): function declaration token
FunctionEnds(PkgbuildToken): (class attribute) function ends token FunctionEnds(PkgbuildToken): function ends token
FunctionStarts(PkgbuildToken): (class attribute) function starts token FunctionStarts(PkgbuildToken): function starts token
NewLine(PkgbuildToken): new line token
""" """
ArrayStarts = "(" ArrayStarts = "("
@@ -54,6 +55,8 @@ class PkgbuildToken(StrEnum):
FunctionStarts = "{" FunctionStarts = "{"
FunctionEnds = "}" FunctionEnds = "}"
NewLine = "\n"
class PkgbuildParser(shlex.shlex): class PkgbuildParser(shlex.shlex):
""" """
@@ -101,7 +104,7 @@ class PkgbuildParser(shlex.shlex):
# ignore substitution and extend bash symbols # ignore substitution and extend bash symbols
self.wordchars += "${}#:+-@!" self.wordchars += "${}#:+-@!"
# in case of default behaviour, it will ignore, for example, segment part of url outside of quotes # in case of default behavior, it will ignore, for example, segment part of url outside of quotes
self.commenters = "" self.commenters = ""
@staticmethod @staticmethod
@@ -174,31 +177,18 @@ class PkgbuildParser(shlex.shlex):
Returns: Returns:
bool: ``True`` if the previous element of the stream is a quote or escaped and ``False`` otherwise bool: ``True`` if the previous element of the stream is a quote or escaped and ``False`` otherwise
""" """
# wrapper around reading utf symbols from random position of the stream
def read_last() -> tuple[int, str]:
while (position := self._io.tell()) > 0:
try:
return position, self._io.read(1)
except UnicodeDecodeError:
self._io.seek(position - 1)
raise PkgbuildParserError("reached starting position, no valid symbols found")
current_position = self._io.tell() current_position = self._io.tell()
last_char = penultimate_char = None last_char = penultimate_char = None
index = current_position - 1 index = current_position - 1
while index > 0: while index > 0:
self._io.seek(index) index, last_char = self._read_last(index)
index, last_char = read_last()
if last_char.isspace(): if last_char.isspace():
index -= 1 index -= 1
continue continue
if index > 1: if index > 1:
self._io.seek(index - 1) _, penultimate_char = self._read_last(index - 1)
_, penultimate_char = read_last()
break break
@@ -219,7 +209,7 @@ class PkgbuildParser(shlex.shlex):
Raises: Raises:
PkgbuildParserError: if array is not closed PkgbuildParserError: if array is not closed
""" """
def extract() -> Generator[str, None, None]: def extract() -> Iterator[str]:
while token := self.get_token(): while token := self.get_token():
match token: match token:
case _ if self._is_escaped(): case _ if self._is_escaped():
@@ -227,7 +217,7 @@ class PkgbuildParser(shlex.shlex):
case PkgbuildToken.ArrayEnds: case PkgbuildToken.ArrayEnds:
break break
case comment if comment.startswith(PkgbuildToken.Comment): case comment if comment.startswith(PkgbuildToken.Comment):
self.instream.readline() self._read_comment()
continue continue
yield token yield token
@@ -268,7 +258,7 @@ class PkgbuildParser(shlex.shlex):
if counter == 0: if counter == 0:
break break
case comment if comment.startswith(PkgbuildToken.Comment): case comment if comment.startswith(PkgbuildToken.Comment):
self.instream.readline() self._read_comment()
if not 0 < start_position < end_position: if not 0 < start_position < end_position:
raise PkgbuildParserError("function body wasn't found") raise PkgbuildParserError("function body wasn't found")
@@ -286,7 +276,7 @@ class PkgbuildParser(shlex.shlex):
return content return content
def _parse_token(self, token: str) -> Generator[PkgbuildPatch, None, None]: def _parse_token(self, token: str) -> Iterator[PkgbuildPatch]:
""" """
parse single token to the PKGBUILD field parse single token to the PKGBUILD field
@@ -304,7 +294,7 @@ class PkgbuildParser(shlex.shlex):
return return
if token.startswith(PkgbuildToken.Comment): if token.startswith(PkgbuildToken.Comment):
self.instream.readline() self._read_comment()
return return
match self.get_token(): match self.get_token():
@@ -332,7 +322,45 @@ class PkgbuildParser(shlex.shlex):
case other if other is not None: case other if other is not None:
yield from self._parse_token(other) yield from self._parse_token(other)
def parse(self) -> Generator[PkgbuildPatch, None, None]: def _read_comment(self) -> None:
"""
read comment from the current position. This method doesn't check comment itself, just read the stream
until the comment line ends
"""
_, last_symbol = self._read_last()
if last_symbol != PkgbuildToken.NewLine:
self.instream.readline()
def _read_last(self, initial_index: int | None = None) -> tuple[int, str]:
"""
wrapper around read to read the last symbol from the input stream. This method is designed to process UTF-8
symbols correctly. This method does not reset current stream position
Args:
initial_index(int | None, optional): initial index to start reading from. If none set, the previous position
will be used (Default value = None)
Returns:
tuple[int, str]: last symbol and its position in the stream
Raises:
PkgbuildParserError: in case if stream reached starting position, but no valid symbols were found
"""
if initial_index is None:
initial_index = self._io.tell() - 1
if initial_index < 0:
raise PkgbuildParserError("stream is on starting position")
self._io.seek(initial_index)
while (position := self._io.tell()) > 0:
try:
return position, self._io.read(1)
except UnicodeDecodeError:
self._io.seek(position - 1)
raise PkgbuildParserError("reached starting position, no valid symbols found")
def parse(self) -> Iterator[PkgbuildPatch]:
""" """
parse source stream and yield parsed entries parse source stream and yield parsed entries
@@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2024 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).
@@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2024 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).
@@ -17,7 +17,7 @@
# 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/>.
# #
from typing import Any from typing import Any, ClassVar
from ahriman.core.alpm.pacman import Pacman from ahriman.core.alpm.pacman import Pacman
from ahriman.core.alpm.remote.remote import Remote from ahriman.core.alpm.remote.remote import Remote
@@ -35,16 +35,16 @@ class AUR(Remote):
DEFAULT_RPC_VERSION(str): (class attribute) default AUR RPC version DEFAULT_RPC_VERSION(str): (class attribute) default AUR RPC version
""" """
DEFAULT_AUR_URL = "https://aur.archlinux.org" DEFAULT_AUR_URL: ClassVar[str] = "https://aur.archlinux.org"
DEFAULT_RPC_URL = f"{DEFAULT_AUR_URL}/rpc" DEFAULT_RPC_URL: ClassVar[str] = f"{DEFAULT_AUR_URL}/rpc"
DEFAULT_RPC_VERSION = "5" DEFAULT_RPC_VERSION: ClassVar[str] = "5"
@classmethod @classmethod
def remote_git_url(cls, package_base: str, repository: str) -> str: def remote_git_url(cls, package_base: str, repository: str) -> str:
""" """
generate remote git url from the package base generate remote git url from the package base
Args Args:
package_base(str): package base package_base(str): package base
repository(str): repository name repository(str): repository name
@@ -58,7 +58,7 @@ class AUR(Remote):
""" """
generate remote web url from the package base generate remote web url from the package base
Args Args:
package_base(str): package base package_base(str): package base
Returns: Returns:
@@ -72,7 +72,7 @@ class AUR(Remote):
parse RPC response to package list parse RPC response to package list
Args: Args:
response(dict[str, Any]): RPC response json response(dict[str, Any]): RPC response JSON
Returns: Returns:
list[AURPackage]: list of parsed packages list[AURPackage]: list of parsed packages
@@ -97,20 +97,17 @@ class AUR(Remote):
Returns: Returns:
list[AURPackage]: response parsed to package list list[AURPackage]: response parsed to package list
Raises:
PackageInfoError: if multiple arguments are passed
""" """
query: list[tuple[str, str]] = [ if len(args) != 1:
("type", request_type), raise PackageInfoError("AUR API requires exactly one argument to search")
("v", self.DEFAULT_RPC_VERSION),
]
arg_query = "arg[]" if len(args) > 1 else "arg" url = f"{self.DEFAULT_RPC_URL}/v{self.DEFAULT_RPC_VERSION}/{request_type}/{args[0]}"
for arg in args: query = list(kwargs.items())
query.append((arg_query, arg))
for key, value in kwargs.items(): response = self.make_request("GET", url, params=query)
query.append((key, value))
response = self.make_request("GET", self.DEFAULT_RPC_URL, params=query)
return self.parse_response(response.json()) return self.parse_response(response.json())
def package_info(self, package_name: str, *, pacman: Pacman | None) -> AURPackage: def package_info(self, package_name: str, *, pacman: Pacman | None) -> AURPackage:
@@ -133,15 +130,36 @@ class AUR(Remote):
except StopIteration: except StopIteration:
raise UnknownPackageError(package_name) from None raise UnknownPackageError(package_name) from None
def package_search(self, *keywords: str, pacman: Pacman | None) -> list[AURPackage]: def package_provided_by(self, package_name: str, *, pacman: Pacman | None) -> list[AURPackage]:
"""
get package list which provide the specified package name
Args:
package_name(str): package name to search
pacman(Pacman | None): alpm wrapper instance, required for official repositories search
Returns:
list[AURPackage]: list of packages which match the criteria
"""
return [
package
# search api provides reduced models
for stub in self.package_search(package_name, pacman=pacman, search_by="provides")
# verity that found package actually provides it
if package_name in (package := self.package_info(stub.name, pacman=pacman)).provides
]
def package_search(self, *keywords: str, pacman: Pacman | None, search_by: str | None) -> list[AURPackage]:
""" """
search package in AUR web search package in AUR web
Args: Args:
*keywords(str): keywords to search *keywords(str): keywords to search
pacman(Pacman | None): alpm wrapper instance, required for official repositories search pacman(Pacman | None): alpm wrapper instance, required for official repositories search
search_by(str | None): search by keywords
Returns: Returns:
list[AURPackage]: list of packages which match the criteria list[AURPackage]: list of packages which match the criteria
""" """
return self.aur_request("search", *keywords, by="name-desc") search_by = search_by or "name-desc"
return self.aur_request("search", *keywords, by=search_by)
@@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2024 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).
@@ -17,7 +17,7 @@
# 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/>.
# #
from typing import Any from typing import Any, ClassVar
from ahriman.core.alpm.pacman import Pacman from ahriman.core.alpm.pacman import Pacman
from ahriman.core.alpm.remote.remote import Remote from ahriman.core.alpm.remote.remote import Remote
@@ -36,17 +36,17 @@ class Official(Remote):
DEFAULT_RPC_URL(str): (class attribute) default archlinux repositories RPC url DEFAULT_RPC_URL(str): (class attribute) default archlinux repositories RPC url
""" """
DEFAULT_ARCHLINUX_GIT_URL = "https://gitlab.archlinux.org" DEFAULT_ARCHLINUX_GIT_URL: ClassVar[str] = "https://gitlab.archlinux.org"
DEFAULT_ARCHLINUX_URL = "https://archlinux.org" DEFAULT_ARCHLINUX_URL: ClassVar[str] = "https://archlinux.org"
DEFAULT_SEARCH_REPOSITORIES = ["Core", "Extra", "Multilib"] DEFAULT_SEARCH_REPOSITORIES: ClassVar[list[str]] = ["Core", "Extra", "Multilib"]
DEFAULT_RPC_URL = "https://archlinux.org/packages/search/json" DEFAULT_RPC_URL: ClassVar[str] = "https://archlinux.org/packages/search/json"
@classmethod @classmethod
def remote_git_url(cls, package_base: str, repository: str) -> str: def remote_git_url(cls, package_base: str, repository: str) -> str:
""" """
generate remote git url from the package base generate remote git url from the package base
Args Args:
package_base(str): package base package_base(str): package base
repository(str): repository name repository(str): repository name
@@ -60,7 +60,7 @@ class Official(Remote):
""" """
generate remote web url from the package base generate remote web url from the package base
Args Args:
package_base(str): package base package_base(str): package base
Returns: Returns:
@@ -74,7 +74,7 @@ class Official(Remote):
parse RPC response to package list parse RPC response to package list
Args: Args:
response(dict[str, Any]): RPC response json response(dict[str, Any]): RPC response JSON
Returns: Returns:
list[AURPackage]: list of parsed packages list[AURPackage]: list of parsed packages
@@ -127,15 +127,17 @@ class Official(Remote):
except StopIteration: except StopIteration:
raise UnknownPackageError(package_name) from None raise UnknownPackageError(package_name) from None
def package_search(self, *keywords: str, pacman: Pacman | None) -> list[AURPackage]: def package_search(self, *keywords: str, pacman: Pacman | None, search_by: str | None) -> list[AURPackage]:
""" """
search package in AUR web search package in AUR web
Args: Args:
*keywords(str): keywords to search *keywords(str): keywords to search
pacman(Pacman | None): alpm wrapper instance, required for official repositories search pacman(Pacman | None): alpm wrapper instance, required for official repositories search
search_by(str | None): search by keywords
Returns: Returns:
list[AURPackage]: list of packages which match the criteria list[AURPackage]: list of packages which match the criteria
""" """
return self.arch_request(*keywords, by="q") search_by = search_by or "q"
return self.arch_request(*keywords, by=search_by)
@@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2024 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).
@@ -32,7 +32,7 @@ class OfficialSyncdb(Official):
updates. updates.
This approach also has limitations, because we don't require superuser rights (neither going to download database This approach also has limitations, because we don't require superuser rights (neither going to download database
separately), the database file might be outdated and must be handled manually (or kind of). This behaviour might be separately), the database file might be outdated and must be handled manually (or kind of). This behavior might be
changed in the future. changed in the future.
Still we leave search function based on the official repositories RPC. Still we leave search function based on the official repositories RPC.
@@ -59,3 +59,22 @@ class OfficialSyncdb(Official):
return next(AURPackage.from_pacman(package) for package in pacman.package(package_name)) return next(AURPackage.from_pacman(package) for package in pacman.package(package_name))
except StopIteration: except StopIteration:
raise UnknownPackageError(package_name) from None raise UnknownPackageError(package_name) from None
def package_provided_by(self, package_name: str, *, pacman: Pacman | None) -> list[AURPackage]:
"""
get package list which provide the specified package name
Args:
package_name(str): package name to search
pacman(Pacman | None): alpm wrapper instance, required for official repositories search
Returns:
list[AURPackage]: list of packages which match the criteria
"""
if pacman is None:
return []
return [
AURPackage.from_pacman(package)
for package in pacman.provided_by(package_name)
]
@@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2024 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).
@@ -18,6 +18,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
from ahriman.core.alpm.pacman import Pacman from ahriman.core.alpm.pacman import Pacman
from ahriman.core.exceptions import UnknownPackageError
from ahriman.core.http import SyncHttpClient from ahriman.core.http import SyncHttpClient
from ahriman.models.aur_package import AURPackage from ahriman.models.aur_package import AURPackage
@@ -35,28 +36,42 @@ class Remote(SyncHttpClient):
>>> package = AUR.info("ahriman") >>> package = AUR.info("ahriman")
>>> search_result = Official.multisearch("pacman", "manager", pacman=pacman) >>> search_result = Official.multisearch("pacman", "manager", pacman=pacman)
Differnece between :func:`search()` and :func:`multisearch()` is that :func:`search()` passes all arguments to Difference between :func:`search()` and :func:`multisearch()` is that :func:`search()` passes all arguments to
underlying wrapper directly, whereas :func:`multisearch()` splits search one by one and finds intersection underlying wrapper directly, whereas :func:`multisearch()` splits search one by one and finds intersection
between search results. between search results.
""" """
@classmethod @classmethod
def info(cls, package_name: str, *, pacman: Pacman | None = None) -> AURPackage: def info(cls, package_name: str, *, pacman: Pacman | None = None, include_provides: bool = False) -> AURPackage:
""" """
get package info by its name get package info by its name. If ``include_provides`` is set to ``True``, then, in addition, this method
will perform search by :attr:`ahriman.models.aur_package.AURPackage.provides` and return first package found.
Note, however, that in this case some implementation might not provide this method and search result will might
not be stable
Args: Args:
package_name(str): package name to search package_name(str): package name to search
pacman(Pacman | None, optional): alpm wrapper instance, required for official repositories search pacman(Pacman | None, optional): alpm wrapper instance, required for official repositories search
(Default value = None) (Default value = None)
include_provides(bool, optional): search by provides if no exact match found (Default value = False)
Returns: Returns:
AURPackage: package which match the package name AURPackage: package which match the package name
Raises:
UnknownPackageError: if requested package not found
""" """
return cls().package_info(package_name, pacman=pacman) instance = cls()
try:
return instance.package_info(package_name, pacman=pacman)
except UnknownPackageError:
if include_provides and (provided_by := instance.package_provided_by(package_name, pacman=pacman)):
return next(iter(provided_by))
raise
@classmethod @classmethod
def multisearch(cls, *keywords: str, pacman: Pacman | None = None) -> list[AURPackage]: def multisearch(cls, *keywords: str, pacman: Pacman | None = None,
search_by: str | None = None) -> list[AURPackage]:
""" """
search in remote repository by using API with multiple words. This method is required in order to handle search in remote repository by using API with multiple words. This method is required in order to handle
https://bugs.archlinux.org/task/49133. In addition, short words will be dropped https://bugs.archlinux.org/task/49133. In addition, short words will be dropped
@@ -65,6 +80,7 @@ class Remote(SyncHttpClient):
*keywords(str): search terms, e.g. "ahriman", "is", "cool" *keywords(str): search terms, e.g. "ahriman", "is", "cool"
pacman(Pacman | None, optional): alpm wrapper instance, required for official repositories search pacman(Pacman | None, optional): alpm wrapper instance, required for official repositories search
(Default value = None) (Default value = None)
search_by(str | None, optional): search by keywords (Default value = None)
Returns: Returns:
list[AURPackage]: list of packages each of them matches all search terms list[AURPackage]: list of packages each of them matches all search terms
@@ -72,12 +88,21 @@ class Remote(SyncHttpClient):
instance = cls() instance = cls()
packages: dict[str, AURPackage] = {} packages: dict[str, AURPackage] = {}
for term in filter(lambda word: len(word) >= 3, keywords): for term in filter(lambda word: len(word) >= 3, keywords):
portion = instance.search(term, pacman=pacman) portion = instance.package_search(term, pacman=pacman, search_by=search_by)
packages = { packages = {
package.name: package # not mistake to group them by name package.name: package # not mistake to group them by name
for package in portion for package in portion
if package.name in packages or not packages if package.name in packages or not packages
} }
# simple check for duplicates. This method will remove all packages under base if there is
# a package named exactly as its base
packages = {
package.name: package
for package in packages.values()
if package.package_base not in packages or package.package_base == package.name
}
return list(packages.values()) return list(packages.values())
@classmethod @classmethod
@@ -85,7 +110,7 @@ class Remote(SyncHttpClient):
""" """
generate remote git url from the package base generate remote git url from the package base
Args Args:
package_base(str): package base package_base(str): package base
repository(str): repository name repository(str): repository name
@@ -102,7 +127,7 @@ class Remote(SyncHttpClient):
""" """
generate remote web url from the package base generate remote web url from the package base
Args Args:
package_base(str): package base package_base(str): package base
Returns: Returns:
@@ -114,7 +139,7 @@ class Remote(SyncHttpClient):
raise NotImplementedError raise NotImplementedError
@classmethod @classmethod
def search(cls, *keywords: str, pacman: Pacman | None = None) -> list[AURPackage]: def search(cls, *keywords: str, pacman: Pacman | None = None, search_by: str | None = None) -> list[AURPackage]:
""" """
search package in AUR web search package in AUR web
@@ -122,11 +147,12 @@ class Remote(SyncHttpClient):
*keywords(str): search terms, e.g. "ahriman", "is", "cool" *keywords(str): search terms, e.g. "ahriman", "is", "cool"
pacman(Pacman | None, optional): alpm wrapper instance, required for official repositories search pacman(Pacman | None, optional): alpm wrapper instance, required for official repositories search
(Default value = None) (Default value = None)
search_by(str | None, optional): search by keywords (Default value = None)
Returns: Returns:
list[AURPackage]: list of packages which match the criteria list[AURPackage]: list of packages which match the criteria
""" """
return cls().package_search(*keywords, pacman=pacman) return cls().package_search(*keywords, pacman=pacman, search_by=search_by)
def package_info(self, package_name: str, *, pacman: Pacman | None) -> AURPackage: def package_info(self, package_name: str, *, pacman: Pacman | None) -> AURPackage:
""" """
@@ -144,13 +170,28 @@ class Remote(SyncHttpClient):
""" """
raise NotImplementedError raise NotImplementedError
def package_search(self, *keywords: str, pacman: Pacman | None) -> list[AURPackage]: def package_provided_by(self, package_name: str, *, pacman: Pacman | None) -> list[AURPackage]:
"""
get package list which provide the specified package name
Args:
package_name(str): package name to search
pacman(Pacman | None): alpm wrapper instance, required for official repositories search
Returns:
list[AURPackage]: list of packages which match the criteria
"""
del package_name, pacman
return []
def package_search(self, *keywords: str, pacman: Pacman | None, search_by: str | None) -> list[AURPackage]:
""" """
search package in AUR web search package in AUR web
Args: Args:
*keywords(str): keywords to search *keywords(str): keywords to search
pacman(Pacman | None): alpm wrapper instance, required for official repositories search pacman(Pacman | None): alpm wrapper instance, required for official repositories search
search_by(str | None): search by keywords
Returns: Returns:
list[AURPackage]: list of packages which match the criteria list[AURPackage]: list of packages which match the criteria
@@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2024 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).
@@ -31,20 +31,21 @@ class Repo(LazyLogging):
Attributes: Attributes:
name(str): repository name name(str): repository name
paths(RepositoryPaths): repository paths instance root(Path): repository root
sign_args(list[str]): additional args which have to be used to sign repository archive sign_args(list[str]): additional args which have to be used to sign repository archive
uid(int): uid of the repository owner user uid(int): uid of the repository owner user
""" """
def __init__(self, name: str, paths: RepositoryPaths, sign_args: list[str]) -> None: def __init__(self, name: str, paths: RepositoryPaths, sign_args: list[str], root: Path | None = None) -> None:
""" """
Args: Args:
name(str): repository name name(str): repository name
paths(RepositoryPaths): repository paths instance paths(RepositoryPaths): repository paths instance
sign_args(list[str]): additional args which have to be used to sign repository archive sign_args(list[str]): additional args which have to be used to sign repository archive
root(Path | None, optional): repository root. If none set, the default will be used (Default value = None)
""" """
self.name = name self.name = name
self.paths = paths self.root = root or paths.repository
self.uid, _ = paths.root_owner self.uid, _ = paths.root_owner
self.sign_args = sign_args self.sign_args = sign_args
@@ -56,7 +57,7 @@ class Repo(LazyLogging):
Returns: Returns:
Path: path to repository database Path: path to repository database
""" """
return self.paths.repository / f"{self.name}.db.tar.gz" return self.root / f"{self.name}.db.tar.gz"
def add(self, path: Path) -> None: def add(self, path: Path) -> None:
""" """
@@ -66,35 +67,37 @@ 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.root,
logger=self.logger, logger=self.logger,
user=self.uid) user=self.uid,
)
def init(self) -> None: def init(self) -> None:
""" """
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), check_output("repo-add", *self.sign_args, str(self.repo_path),
cwd=self.paths.repository, logger=self.logger, user=self.uid) cwd=self.root, logger=self.logger, user=self.uid)
def remove(self, package: str, filename: Path) -> None: def remove(self, package_name: str, filename: Path) -> None:
""" """
remove package from repository remove package from repository
Args: Args:
package(str): package name to remove package_name(str): package name to remove
filename(Path): package filename to remove filename(Path): package filename to remove
""" """
# remove package and signature (if any) from filesystem # remove package and signature (if any) from filesystem
for full_path in self.paths.repository.glob(f"{filename}*"): for full_path in self.root.glob(f"{filename.name}*"):
full_path.unlink() full_path.unlink()
# remove package from registry # remove package from registry
check_output( check_output(
"repo-remove", *self.sign_args, str(self.repo_path), package, "repo-remove", *self.sign_args, str(self.repo_path), package_name,
exception=BuildError.from_process(package), exception=BuildError.from_process(package_name),
cwd=self.paths.repository, cwd=self.root,
logger=self.logger, logger=self.logger,
user=self.uid) user=self.uid,
)
@@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2024 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).

Some files were not shown because too many files have changed in this diff Show More