94 Commits

Author SHA1 Message Date
9cf8af08ab Merge pull request 'fix: remove duplicates correctly' (#46) from Maks1mS/ALR:fix/remove-duplicates-correctly into master
Reviewed-on: #46
2025-03-13 13:44:40 +00:00
86940e8962 tests: add TestRemoveDuplicatesSources 2025-03-13 16:38:36 +03:00
db244204c7 fix: remove duplicates correctly 2025-03-13 16:24:37 +03:00
9cb0a5e9ad Merge pull request 'chore: update package name in Makefile' (#45) from Maks1mS/ALR:fix/correct-set-version into master
Reviewed-on: #45
2025-03-12 15:18:01 +00:00
1a57ccdb83 chore: update package name in Makefile 2025-03-12 15:11:19 +03:00
615cd83fb7 Merge pull request 'fix: resolve absolute path of ScriptDir' (#44) from Maks1mS/ALR:fix/use-abs-scriptdir-path into master
Reviewed-on: #44
2025-03-11 17:30:49 +00:00
27e2f54653 fix: resolve absolute path of ScriptDir 2025-03-11 20:28:41 +03:00
af57165c89 Небольшие правки и исправления. 2025-03-10 14:50:02 +03:00
3770c82240 Merge pull request 'fix: add db.Init() in bash completion' (#42) from Maks1mS/ALR:fix/add-db-init-in-completion into master
Reviewed-on: #42
2025-03-09 20:59:05 +00:00
2dff463303 i18n: update ru translation 2025-03-09 17:32:19 +03:00
9085e38454 fix: add db.Init() in bash completion 2025-03-09 17:30:02 +03:00
a7d016abc9 Замена устаревшего метода установки в шаблоне alr gen pip 2025-03-02 13:50:11 +03:00
4a5cca2d0f Замена устаревшего метода установки в шаблоне alr gen pip 2025-03-02 13:34:22 +03:00
71000fd3cd Merge pull request 'fix: use unique names for packages' (#40) from Maks1mS/ALR:fix/use-unique-package-name into master
Reviewed-on: #40
2025-03-02 06:49:52 +00:00
71968bbe13 fix: fix config saving 2025-02-28 21:14:59 +03:00
29c1a31066 fix: fix list and upgrade commands with new naming 2025-02-28 21:14:21 +03:00
8f94b61a0e fix: use +alr-{reponame} suffix 2025-02-28 20:15:02 +03:00
ae8e2d2807 Merge pull request 'chore: make the application more internationalized' (#39) from Maks1mS/ALR:chore/i18n into master
Reviewed-on: #39

    добавлен badge для русского языка
    переведены все строки
    переведены выводы help
2025-02-28 07:09:22 +00:00
0fa288b8a2 chore: make the application more internationalized 2025-02-27 16:41:18 +03:00
dcac0b9ee5 i18n: translate all strings 2025-02-27 11:43:20 +03:00
4e6e1f524a chore: make the application more internationalized 2025-02-27 11:18:13 +03:00
47a9b9a96c Merge pull request 'chore: refactor code' (#38) from Maks1mS/ALR:chore/refactor-code into master
Reviewed-on: #38

    remove legacy code
    refactor search and add tests
2025-02-22 15:34:57 +00:00
9bb14312bd chore: refactor code
- remove legacy code
- refactor search and add tests
2025-02-22 09:44:59 +03:00
88b8d2fbf3 Merge pull request 'feat: add search command' (#37) from Maks1mS/ALR:feat/add-search-command into master
Reviewed-on: #37
Добавлена команда search.
2025-02-18 18:18:48 +00:00
04523775f1 feat: add search command 2025-02-18 17:55:25 +03:00
adc4a42800 Merge pull request 'fix: remove default repo and disable autoPull by default' (#36) from Maks1mS/ALR:fix/change-default-config into master
Reviewed-on: #36
2025-02-13 15:46:13 +00:00
81651af20d Merge pull request 'feat: add support for multiple packages in one alr.sh' (#35) from Maks1mS/ALR:feat/add-multiple-package-build into master
Reviewed-on: #35
В этом PR добавлена поддержка сборки нескольких пакетов из одного alr.sh, которая скорее всего потребует доработки в дальнейшем.
Пример репозитория с пакетами, содержащие несколько package_: https://gitea.plemya-x.ru/Maks1mS/multipackage-test-repo
2025-02-13 15:45:28 +00:00
f04ebbaf14 chore: update info about xpamych-alr-repo 2025-02-12 19:19:08 +03:00
be1a137eab fix: remove default repo and disable autoPull by default 2025-02-12 19:13:50 +03:00
719a5b7fe7 Merge branch 'master' into feat/add-multiple-package-build 2025-02-12 18:52:16 +03:00
e05bb07f23 feat: add support for multiple packages in one alr.sh 2025-02-12 18:34:35 +03:00
ec053f7e6a Корректировка скрипта первичной установки с учётом ALT linux 2025-02-09 12:44:56 +03:00
ad1696d507 Перевод fakeroot на gitea - fix 2025-02-09 12:13:28 +03:00
a57602a278 Перевод fakeroot на gitea 2025-02-06 14:17:57 +03:00
606cd5473a Merge pull request 'chore: add tests for dl' (#34) from Maks1mS/ALR:chore/add-tests into master
Reviewed-on: #34
2025-01-31 16:33:16 +00:00
d2bcb4e345 chore: remove comment 2025-01-31 18:14:28 +03:00
1fcb88976c tests: add more tests for dl 2025-01-31 18:12:22 +03:00
55feea9b25 chore: remove code duplication 2025-01-31 17:05:14 +03:00
0d1db212e1 tests: add tests for dl 2025-01-31 16:56:26 +03:00
99ec48c4c6 Merge pull request 'chore: fix formatting and add pre-commit' (#33) from Maks1mS/ALR:chore/fix-fmt into master
Reviewed-on: #33
2025-01-31 10:43:56 +00:00
d201aae6e0 chore: fix formatting 2025-01-30 10:10:42 +03:00
4463a32ae7 Merge pull request 'fix: do not expand variables in output of files()' (#31) from Maks1mS/ALR:fix-files into master
Reviewed-on: #31
2025-01-29 07:42:28 +00:00
52fd4a5a00 fix: do not expand variables in output of files() 2025-01-29 00:19:02 +03:00
c437346957 Изменение работы логики аргумента -p (fix) 2025-01-28 18:37:41 +03:00
bf1fc0d878 Merge pull request 'fix: quit after done in progress' (#28) from Maks1mS/ALR:fix/quit-from-progress-after-done into master
Reviewed-on: #28
2025-01-26 14:13:30 +00:00
c3f879b379 fix: quit after done in progress 2025-01-26 17:11:25 +03:00
aa90dfa983 Merge pull request 'fix: add download cancel via context and update progressbar' (#27) from Maks1mS/ALR:fix/migrate-to-new-progressbar into master
Reviewed-on: #27
2025-01-26 13:39:32 +00:00
bba1ed52c5 fix: add download cancel via context and update progressbar 2025-01-26 16:33:00 +03:00
dc1fac29d5 Изменение работы логики аргумента -p 2025-01-26 11:36:03 +03:00
99857efb01 Merge pull request 'po(RU): fix translation' (#26) from x1z53/ALR:master into master
Reviewed-on: #26
2025-01-25 11:35:41 +00:00
19bb87981c po(RU): fix translation 2025-01-25 14:34:45 +03:00
1c78adcca1 Merge pull request 'fix: use platform specific Release in upgrade' (#22) from Maks1mS/ALR:fix/use-platform-specific-release into master
Reviewed-on: #22
2025-01-25 09:30:11 +00:00
a98bd44305 Merge pull request 'fix: use shell.Fields instead strings.Fields' (#21) from Maks1mS/ALR:fix/use-shell-fields into master
Reviewed-on: #21
2025-01-25 08:54:50 +00:00
3deb6c9455 tests: add tests for ReleasePlatformSpecific 2025-01-25 11:54:02 +03:00
981f49587b fix: use platform specific release in compare 2025-01-25 11:16:33 +03:00
35656d63a1 fix: use shell.Fields 2025-01-25 09:39:33 +03:00
6410f7547b Merge pull request 'po(RU): update translation' (#20) from x1z53/ALR:master into master
Reviewed-on: #20
2025-01-24 19:04:33 +00:00
53e783df31 po(RU): update translation 2025-01-24 21:20:45 +03:00
fcc9ef5474 Merge pull request 'feat: add files() function' (#19) from Maks1mS/ALR:feat/files-function into master
Reviewed-on: #19
2025-01-24 16:40:21 +00:00
f6ba4a1c26 feat: add files() function
also add files-find-lang and files-find-doc helpers
2025-01-24 19:23:41 +03:00
b5bf6ab61d Merge pull request 'fix: completely remove old logger' (#18) from Maks1mS/ALR:fix/remove-old-logger into master
Reviewed-on: #18
2025-01-23 08:43:59 +00:00
18e90e4afc fix: completely remove old logger 2025-01-23 11:42:48 +03:00
a09863dfcb Merge pull request 'feat: add autoPull in config' (#17) from Maks1mS/ALR:feat/add-auto-pull-to-config into master
Reviewed-on: #17
2025-01-23 07:37:52 +00:00
fd643ea6cd chore: run make fmt and make i18n 2025-01-22 18:12:26 +03:00
309ecf784f feat: add autoPull in config 2025-01-22 18:10:57 +03:00
30f95a4cbf chore: make usage strings translatable 2025-01-22 17:16:15 +03:00
b9bf908007 chore: remove legacy translation system 2025-01-22 16:53:30 +03:00
a6076b1253 chore: replace old logger with new 2025-01-22 16:37:16 +03:00
ac35b4d71d feat: add new logger and translation 2025-01-22 14:40:11 +03:00
945f920654 Merge pull request 'fix: rename module from plemya-x.ru/alr to gitea.plemya-x.ru/Plemya-x/ALR' (#12) from Maks1mS/ALR:fix/rename-module into master
Reviewed-on: #12
2025-01-20 21:17:58 +00:00
84ac2377fb fix: rename module from plemya-x.ru/alr to gitea.plemya-x.ru/Plemya-x/ALR 2025-01-20 19:58:24 +03:00
de1db25202 Merge pull request 'fix: removeAlreadyInstalled before FindPkgs' (#11) from Maks1mS/ALR:fix/resolve-deps into master
Reviewed-on: #11
2025-01-19 08:55:09 +00:00
2d6504b329 fix: removeAlreadyInstalled before FindPkgs 2025-01-19 11:49:00 +03:00
4ca557402a Merge pull request 'chore: add fmt and update-license' (#10) from Maks1mS/ALR:chore/linting into master
Reviewed-on: #10
2025-01-19 07:41:15 +00:00
e497d41030 chore: run make update-license fmt 2025-01-18 19:30:02 +03:00
d46414a67c Merge branch 'master' into chore/linting 2025-01-18 19:22:18 +03:00
29e2f85eeb Исправление ссылки на скрипт установки 2025-01-18 18:30:26 +03:00
c9c872abbc Merge remote-tracking branch 'gitea/master' 2025-01-18 18:29:50 +03:00
fb93864d09 Revert "Исправление ссылки на скрипт установки"
This reverts commit 9fcd618a83.
2025-01-18 18:29:42 +03:00
9fcd618a83 Исправление ссылки на скрипт установки 2025-01-18 18:29:17 +03:00
1fb9c6b574 Merge pull request 'refactor(db, config, repos): migrate from functions to struct' (#9) from Maks1mS/ALR:refactor/db into master
Reviewed-on: #9
2025-01-18 15:27:10 +00:00
fb5c875713 Merge pull request 'fix: add auto_req and auto_prov' (#7) from Maks1mS/ALR:fix/make-findprovides-findrequires-optional into master
Reviewed-on: #7
2025-01-18 15:24:07 +00:00
3f428ab7b5 chore: add golangci-lint 2025-01-14 15:45:29 +03:00
5b7af1f6b5 chore: refactor license update script 2025-01-14 15:28:54 +03:00
3224d7c6e4 chore: add license update script 2025-01-14 15:24:24 +03:00
e1829c4824 refactor: migrate repos find to struct 2025-01-14 13:18:51 +03:00
12d83f2015 test: fix decoder and handlers tests 2025-01-14 13:04:10 +03:00
6bc6bfdcd9 refactor: migrate dlcache to struct 2025-01-14 12:59:00 +03:00
eeb25c239b refactor: move defaultConfig from config_legacy to config 2025-01-14 11:54:00 +03:00
91937a1fc5 refactor: migrate repo to struct 2025-01-14 11:49:42 +03:00
e827fb8049 refactor: use context logger 2025-01-14 10:41:38 +03:00
a13acc5ed0 refactor: migrate list command to struct API 2025-01-14 10:35:23 +03:00
52d3ab7791 refactor: migrate db and config packages to use struct-based API
Removed global variables in favor of instance variables. This makes the code more maintainable and making it easier to write unit tests without relying on global state.

Marked the old functions with global state as obsolete, redirecting them to use a new API based on struct in order to rewrite the code using these functions gradually.
2025-01-14 10:11:17 +03:00
a345a24b95 fix: add auto_req and auto_prov 2024-12-27 21:15:37 +03:00
102 changed files with 8054 additions and 3910 deletions

2
.gitignore vendored
View File

@ -6,3 +6,5 @@
.fleet .fleet
.idea .idea
.gigaide .gigaide
*.out

50
.golangci.yml Normal file
View File

@ -0,0 +1,50 @@
# ALR - Any Linux Repository
# Copyright (C) 2025 Евгений Храмов
#
# 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/>.
run:
timeout: 5m
linters-settings:
goimports:
local-prefixes: "gitea.plemya-x.ru/Plemya-x/ALR"
gofmt:
simplify: true
gofumpt:
extra-rules: true
linters:
enable:
- gofmt
- gofumpt
- goimports
- gocritic
- govet
- staticcheck
- unused
- errcheck
- typecheck
# - forbidigo
issues:
fix: true
exclude-rules:
- path: _test\.go
linters:
- errcheck
# TODO: remove
- linters:
- staticcheck
text: "SA1019: interp.ExecHandler"

View File

@ -1,3 +1,22 @@
# This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
# It has been modified as part of "ALR - Any Linux Repository" by Евгений Храмов.
#
# ALR - Any Linux Repository
# Copyright (C) 2025 Евгений Храмов
#
# 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/>.
before: before:
hooks: hooks:
- go mod tidy - go mod tidy

42
.pre-commit-config.yaml Normal file
View File

@ -0,0 +1,42 @@
# ALR - Any Linux Repository
# Copyright (C) 2025 Евгений Храмов
#
# 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/>.
repos:
- repo: local
hooks:
- id: test-coverage
name: Run test coverage
entry: make test-coverage
language: system
pass_filenames: false
- id: fmt
name: Format code
entry: make fmt
language: system
pass_filenames: false
- id: update-license
name: Update license
entry: make update-license
language: system
pass_filenames: false
- id: i18n
name: Update i18n
entry: make i18n
language: system
pass_filenames: false

View File

@ -1,3 +1,22 @@
# This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
# It has been modified as part of "ALR - Any Linux Repository" by Евгений Храмов.
#
# ALR - Any Linux Repository
# Copyright (C) 2025 Евгений Храмов
#
# 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/>.
platform: linux/amd64 platform: linux/amd64
pipeline: pipeline:
release: release:

View File

@ -11,13 +11,17 @@ ZSH_COMPLETION := $(COMPLETIONS_DIR)/zsh
INSTALLED_BASH_COMPLETION := $(DESTDIR)$(PREFIX)/share/bash-completion/completions/$(NAME) INSTALLED_BASH_COMPLETION := $(DESTDIR)$(PREFIX)/share/bash-completion/completions/$(NAME)
INSTALLED_ZSH_COMPLETION := $(DESTDIR)$(PREFIX)/share/zsh/site-functions/_$(NAME) INSTALLED_ZSH_COMPLETION := $(DESTDIR)$(PREFIX)/share/zsh/site-functions/_$(NAME)
ADD_LICENSE_BIN := go run github.com/google/addlicense@4caba19b7ed7818bb86bc4cd20411a246aa4a524
GOLANGCI_LINT_BIN := go run github.com/golangci/golangci-lint/cmd/golangci-lint@v1.63.4
XGOTEXT_BIN := go run github.com/Tom5521/xgotext@v1.2.0
.PHONY: build install clean clear uninstall check-no-root .PHONY: build install clean clear uninstall check-no-root
build: check-no-root $(BIN) build: check-no-root $(BIN)
export CGO_ENABLED := 0 export CGO_ENABLED := 0
$(BIN): $(BIN):
go build -ldflags="-X 'gitea.plemya-x.ru/xpamych/ALR/internal/config.Version=$(GIT_VERSION)'" -o $@ go build -ldflags="-X 'gitea.plemya-x.ru/Plemya-x/ALR/internal/config.Version=$(GIT_VERSION)'" -o $@
check-no-root: check-no-root:
@if [[ "$$(whoami)" == 'root' ]]; then \ @if [[ "$$(whoami)" == 'root' ]]; then \
@ -48,3 +52,22 @@ uninstall:
clean clear: clean clear:
rm -f $(BIN) rm -f $(BIN)
OLD_FILES=$$(< old-files)
IGNORE_OLD_FILES := $(foreach file,$(shell cat old-files),-ignore $(file))
update-license:
$(ADD_LICENSE_BIN) -v -f license-header-old-files.tmpl $(OLD_FILES)
$(ADD_LICENSE_BIN) -v -f license-header.tmpl $(IGNORE_OLD_FILES) .
fmt:
$(GOLANGCI_LINT_BIN) run --fix
i18n:
$(XGOTEXT_BIN) --output ./internal/translations/default.pot
msguniq --use-first -o ./internal/translations/default.pot ./internal/translations/default.pot
msgmerge --backup=off -U ./internal/translations/po/ru/default.po ./internal/translations/default.pot
bash scripts/i18n-badge.sh
test-coverage:
go test ./... -v -coverpkg=./... -coverprofile=coverage.out
bash scripts/coverage-badge.sh

View File

@ -3,11 +3,13 @@
</p> </p>
<b></b> <b></b>
[![Go Report Card](https://goreportcard.com/badge/gitea.plemya-x.ru/Plemya-x/ALR)](https://goreportcard.com/report/gitea.plemya-x.ru/Plemya-x/ALR) ![Test coverage](./assets/coverage-badge.svg) ![ru translate](./assets/i18n-ru-badge.svg)
# ALR (Any Linux Repository) # ALR (Any Linux Repository)
ALR - это независимая от дистрибутива система сборки для Linux, аналогичная [AUR](https://wiki.archlinux.org/title/Arch_User_Repository). В настоящее время она находится в стадии бета-тестирования. Исправлено большинство основных ошибок и добавлено большинство важных функций. alr готов к общему использованию, но все еще может время от времени ломаться или заменяться. ALR - это независимая от дистрибутива система сборки для Linux (форк [LURE](https://github.com/lure-sh/lure), аналогичная [AUR](https://wiki.archlinux.org/title/Arch_User_Repository). В настоящее время она находится в стадии бета-тестирования. Исправлено большинство основных ошибок и добавлено большинство важных функций. ALR готов к общему использованию, но все еще может время от времени ломаться или изменяться.
ALR написан на чистом Go и после сборки не имеет зависимостей. Единственное, для повышения привилегий ALR требуется команда, такая как `sudo`, `doas` и т.д., а также поддерживаемый менеджер пакетов. В настоящее время ALR поддерживает `apt`, `pacman`, `apk`, `dnf`, `yum`, and `zypper`. Если в вашей системе существует поддерживаемый менеджер пакетов, он будет обнаружен и использован автоматически. ALR написан на чистом Go и после сборки не имеет зависимостей. Для повышения привилегий ALR требуется команда, такая как `sudo`, `doas` и т.д., а также поддерживаемый менеджер пакетов. В настоящее время ALR поддерживает `apt`, `apt-get` `pacman`, `apk`, `dnf`, `yum`, and `zypper`. Если в вашей системе используется поддерживаемый менеджер пакетов, то он будет обнаружен и использован автоматически.
--- ---
@ -21,14 +23,14 @@ ALR написан на чистом Go и после сборки не имее
curl -fsSL plemya-x.ru/alr/install.sh | bash curl -fsSL plemya-x.ru/alr/install.sh | bash
``` ```
**ВАЖНО**: При этом скрипт будет загружен и запущен с <https://gitea.plemya-x.ru/xpamych/ALR/install>. Пожалуйста, просматривайте любые скрипты, которые вы скачиваете из Интернета (включая этот), прежде чем запускать их. **ВАЖНО**: При этом скрипт будет загружен и запущен с <https://plemya-x.ru/alr/install.sh>. Пожалуйста, просматривайте любые скрипты, которые вы скачиваете из Интернета (включая этот), прежде чем запускать их.
### Сборка из исходного кода ### Сборка из исходного кода
Чтобы собрать ALR из исходного кода, вам понадобится версия Go 1.18 или новее. Как только Go будет установлен, клонируйте это репозиторий и запустите: Чтобы собрать ALR из исходного кода, вам понадобится версия Go 1.18 или новее. Как только Go будет установлен, клонируйте это репозиторий и запустите:
```shell ```shell
make build make build -B
sudo make install sudo make install
``` ```
@ -42,20 +44,23 @@ ALR был создан потому, что упаковка программн
## Документация ## Документация
Документация по всем этим вопросам находится в [Wiki](https://gitea.plemya-x.ru/xpamych/ALR/wiki/Home). Документация находится в [Wiki](https://disc.plemya-x.ru/c/alr/wiki-alr).
--- ---
## Репозитории ## Репозитории
Репозитории alr - это git-хранилища, которые содержат каталог для каждого пакета с файлом `alr.sh` внутри. Файл `alr.sh` содержит все инструкции по сборке пакета и информацию о нем. Скрипты `alr.sh` аналогичны скриптам Aur PKGBUILD. Репозиторий [по-умолчанию](https://gitea.plemya-x.ru/xpamych/xpamych-alr-repo.git). Репозитории alr - это git-хранилища, которые содержат каталог для каждого пакета с файлом `alr.sh` внутри. Файл `alr.sh` содержит все инструкции по сборке пакета и информацию о нем. Скрипты `alr.sh` аналогичны скриптам Aur PKGBUILD.
Например, репозиторий [Plemya-x/alr-repo](https://gitea.plemya-x.ru/Plemya-x/alr-repo.git) можно подключить так:
```
alr addrepo --name alr-repo --url https://gitea.plemya-x.ru/Plemya-x/alr-repo.git
```
--- ---
## Соцсети ## Соцсети
VK - https://vk.com/plemya_kh VK - https://vk.com/plemya_kh
Discord - https://discord.com/channels/817759634105827358/1261631565084233749
Telegram - https://t.me/plemyakh Telegram - https://t.me/plemyakh
## Спасибы ## Спасибы
@ -68,3 +73,6 @@ Telegram - https://t.me/plemyakh
- <https://github.com/goreleaser/nfpm> - <https://github.com/goreleaser/nfpm>
- <https://github.com/charmbracelet/bubbletea> - <https://github.com/charmbracelet/bubbletea>
- <https://gitlab.com/cznic/sqlite> - <https://gitlab.com/cznic/sqlite>
Благодарим за активное участие в развитии проекта:
- Maks1mS <maxim@slipenko.com>

17
assets/coverage-badge.svg Normal file
View File

@ -0,0 +1,17 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="109" height="20">
<linearGradient id="smooth" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/>
<stop offset="1" stop-opacity=".1"/></linearGradient>
<mask id="round">
<rect width="109" height="20" rx="3" fill="#fff"/>
</mask>
<g mask="url(#round)"><rect width="65" height="20" fill="#555"/>
<rect x="65" width="44" height="20" fill="#e05d44"/>
<rect width="109" height="20" fill="url(#smooth)"/>
</g>
<g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11">
<text x="33.5" y="15" fill="#010101" fill-opacity=".3">coverage</text>
<text x="33.5" y="14">coverage</text>
<text x="86" y="15" fill="#010101" fill-opacity=".3">19.8%</text>
<text x="86" y="14">19.8%</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 926 B

18
assets/i18n-ru-badge.svg Normal file
View File

@ -0,0 +1,18 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="129" height="20">
<linearGradient id="smooth" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/>
<stop offset="1" stop-opacity=".1"/></linearGradient>
<mask id="round">
<rect width="129" height="20" rx="3" fill="#fff"/>
</mask>
<g mask="url(#round)">
<rect width="75" height="20" fill="#555"/>
<rect x="75" width="64" height="20" fill="#4c1"/>
<rect width="129" height="20" fill="url(#smooth)"/>
</g>
<g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11">
<text x="37" y="15" fill="#010101" fill-opacity=".3">ru translate</text>
<text x="37" y="14">ru translate</text>
<text x="100" y="15" fill="#010101" fill-opacity=".3">100.00%</text>
<text x="100" y="14">100.00%</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 942 B

172
build.go
View File

@ -1,100 +1,184 @@
/* // This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
* ALR - Any Linux Repository // It has been modified as part of "ALR - Any Linux Repository" by Евгений Храмов.
* Copyright (C) 2024 Евгений Храмов //
* // ALR - Any Linux Repository
* This program is free software: you can redistribute it and/or modify // Copyright (C) 2025 Евгений Храмов
* it under the terms of the GNU General Public License as published by //
* the Free Software Foundation, either version 3 of the License, or // This program is free software: you can redistribute it and/or modify
* (at your option) any later version. // it under the terms of the GNU General Public License as published by
* // the Free Software Foundation, either version 3 of the License, or
* This program is distributed in the hope that it will be useful, // (at your option) any later version.
* but WITHOUT ANY WARRANTY; without even the implied warranty of //
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // This program is distributed in the hope that it will be useful,
* GNU General Public License for more details. // but WITHOUT ANY WARRANTY; without even the implied warranty of
* // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* You should have received a copy of the GNU General Public License // GNU General Public License for more details.
* along with this program. If not, see <http://www.gnu.org/licenses/>. //
*/ // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package main package main
import ( import (
"log/slog"
"os" "os"
"path/filepath" "path/filepath"
"strings"
"github.com/leonelquinteros/gotext"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"plemya-x.ru/alr/internal/config"
"plemya-x.ru/alr/internal/osutils" "gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
"plemya-x.ru/alr/internal/types" database "gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
"plemya-x.ru/alr/pkg/build" "gitea.plemya-x.ru/Plemya-x/ALR/internal/osutils"
"plemya-x.ru/alr/pkg/loggerctx" "gitea.plemya-x.ru/Plemya-x/ALR/internal/types"
"plemya-x.ru/alr/pkg/manager" "gitea.plemya-x.ru/Plemya-x/ALR/pkg/build"
"plemya-x.ru/alr/pkg/repos" "gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/repos"
) )
var buildCmd = &cli.Command{ func BuildCmd() *cli.Command {
return &cli.Command{
Name: "build", Name: "build",
Usage: "Build a local package", Usage: gotext.Get("Build a local package"),
Flags: []cli.Flag{ Flags: []cli.Flag{
&cli.StringFlag{ &cli.StringFlag{
Name: "script", Name: "script",
Aliases: []string{"s"}, Aliases: []string{"s"},
Value: "alr.sh", Value: "alr.sh",
Usage: "Path to the build script", Usage: gotext.Get("Path to the build script"),
},
&cli.StringFlag{
Name: "subpackage",
Aliases: []string{"sb"},
Usage: gotext.Get("Specify subpackage in script (for multi package script only)"),
}, },
&cli.StringFlag{ &cli.StringFlag{
Name: "package", Name: "package",
Aliases: []string{"p"}, Aliases: []string{"p"},
Usage: "Name of the package to build and its repo (example: default/go-bin)", Usage: gotext.Get("Name of the package to build and its repo (example: default/go-bin)"),
}, },
&cli.BoolFlag{ &cli.BoolFlag{
Name: "clean", Name: "clean",
Aliases: []string{"c"}, Aliases: []string{"c"},
Usage: "Build package from scratch even if there's an already built package available", Usage: gotext.Get("Build package from scratch even if there's an already built package available"),
}, },
}, },
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
ctx := c.Context ctx := c.Context
log := loggerctx.From(ctx) cfg := config.New()
db := database.New(cfg)
script := c.String("script") rs := repos.New(cfg, db)
if c.String("package") != "" { err := db.Init(ctx)
script = filepath.Join(config.GetPaths(ctx).RepoDir, c.String("package"), "alr.sh")
}
err := repos.Pull(ctx, config.Config(ctx).Repos)
if err != nil { if err != nil {
log.Fatal("Error pulling repositories").Err(err).Send() slog.Error(gotext.Get("Error initialization database"), "err", err)
os.Exit(1)
} }
var script string
var packages []string
repository := "default"
repoDir := cfg.GetPaths(ctx).RepoDir
switch {
case c.IsSet("script"):
script = c.String("script")
packages = append(packages, c.String("script-package"))
case c.IsSet("package"):
// TODO: handle multiple packages
packageInput := c.String("package")
arr := strings.Split(packageInput, "/")
var packageSearch string
if len(arr) == 2 {
packageSearch = arr[1]
} else {
packageSearch = arr[0]
}
pkgs, _, _ := rs.FindPkgs(ctx, []string{packageSearch})
pkg, ok := pkgs[packageSearch]
if len(pkg) < 1 || !ok {
slog.Error(gotext.Get("Package not found"))
os.Exit(1)
}
repository = pkg[0].Repository
if pkg[0].BasePkgName != "" {
script = filepath.Join(repoDir, repository, pkg[0].BasePkgName, "alr.sh")
packages = append(packages, pkg[0].Name)
} else {
script = filepath.Join(repoDir, repository, pkg[0].Name, "alr.sh")
}
default:
script = filepath.Join(repoDir, "alr.sh")
}
// Проверка автоматического пулла репозиториев
if cfg.AutoPull(ctx) {
err := rs.Pull(ctx, cfg.Repos(ctx))
if err != nil {
slog.Error(gotext.Get("Error pulling repositories"), "err", err)
os.Exit(1)
}
}
// Обнаружение менеджера пакетов
mgr := manager.Detect() mgr := manager.Detect()
if mgr == nil { if mgr == nil {
log.Fatal("Unable to detect a supported package manager on the system").Send() slog.Error(gotext.Get("Unable to detect a supported package manager on the system"))
os.Exit(1)
} }
pkgPaths, _, err := build.BuildPackage(ctx, types.BuildOpts{ info, err := distro.ParseOSRelease(ctx)
if err != nil {
slog.Error(gotext.Get("Error parsing os release"), "err", err)
os.Exit(1)
}
builder := build.NewBuilder(
ctx,
types.BuildOpts{
Packages: packages,
Repository: repository,
Script: script, Script: script,
Manager: mgr, Manager: mgr,
Clean: c.Bool("clean"), Clean: c.Bool("clean"),
Interactive: c.Bool("interactive"), Interactive: c.Bool("interactive"),
}) },
rs,
info,
cfg,
)
// Сборка пакета
pkgPaths, _, err := builder.BuildPackage(ctx)
if err != nil { if err != nil {
log.Fatal("Error building package").Err(err).Send() slog.Error(gotext.Get("Error building package"), "err", err)
os.Exit(1)
} }
// Получение текущей рабочей директории
wd, err := os.Getwd() wd, err := os.Getwd()
if err != nil { if err != nil {
log.Fatal("Error getting working directory").Err(err).Send() slog.Error(gotext.Get("Error getting working directory"), "err", err)
os.Exit(1)
} }
// Перемещение собранных пакетов в рабочую директорию
for _, pkgPath := range pkgPaths { for _, pkgPath := range pkgPaths {
name := filepath.Base(pkgPath) name := filepath.Base(pkgPath)
err = osutils.Move(pkgPath, filepath.Join(wd, name)) err = osutils.Move(pkgPath, filepath.Join(wd, name))
if err != nil { if err != nil {
log.Fatal("Error moving the package").Err(err).Send() slog.Error(gotext.Get("Error moving the package"), "err", err)
os.Exit(1)
} }
} }
return nil return nil
}, },
}
} }

82
fix.go
View File

@ -1,64 +1,78 @@
/* // This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
* ALR - Any Linux Repository // It has been modified as part of "ALR - Any Linux Repository" by Евгений Храмов.
* Copyright (C) 2024 Евгений Храмов //
* // ALR - Any Linux Repository
* This program is free software: you can redistribute it and/or modify // Copyright (C) 2025 Евгений Храмов
* it under the terms of the GNU General Public License as published by //
* the Free Software Foundation, either version 3 of the License, or // This program is free software: you can redistribute it and/or modify
* (at your option) any later version. // it under the terms of the GNU General Public License as published by
* // the Free Software Foundation, either version 3 of the License, or
* This program is distributed in the hope that it will be useful, // (at your option) any later version.
* but WITHOUT ANY WARRANTY; without even the implied warranty of //
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // This program is distributed in the hope that it will be useful,
* GNU General Public License for more details. // but WITHOUT ANY WARRANTY; without even the implied warranty of
* // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* You should have received a copy of the GNU General Public License // GNU General Public License for more details.
* along with this program. If not, see <http://www.gnu.org/licenses/>. //
*/ // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package main package main
import ( import (
"log/slog"
"os" "os"
"github.com/leonelquinteros/gotext"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"plemya-x.ru/alr/internal/config"
"plemya-x.ru/alr/internal/db" "gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
"plemya-x.ru/alr/pkg/loggerctx" database "gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
"plemya-x.ru/alr/pkg/repos" "gitea.plemya-x.ru/Plemya-x/ALR/pkg/repos"
) )
var fixCmd = &cli.Command{ func FixCmd() *cli.Command {
return &cli.Command{
Name: "fix", Name: "fix",
Usage: "Attempt to fix problems with ALR", Usage: gotext.Get("Attempt to fix problems with ALR"),
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
ctx := c.Context ctx := c.Context
log := loggerctx.From(ctx) cfg := config.New()
paths := cfg.GetPaths(ctx)
db.Close() slog.Info(gotext.Get("Removing cache directory"))
paths := config.GetPaths(ctx)
log.Info("Removing cache directory").Send()
err := os.RemoveAll(paths.CacheDir) err := os.RemoveAll(paths.CacheDir)
if err != nil { if err != nil {
log.Fatal("Unable to remove cache directory").Err(err).Send() slog.Error(gotext.Get("Unable to remove cache directory"), "err", err)
os.Exit(1)
} }
log.Info("Rebuilding cache").Send() slog.Info(gotext.Get("Rebuilding cache"))
err = os.MkdirAll(paths.CacheDir, 0o755) err = os.MkdirAll(paths.CacheDir, 0o755)
if err != nil { if err != nil {
log.Fatal("Unable to create new cache directory").Err(err).Send() slog.Error(gotext.Get("Unable to create new cache directory"), "err", err)
os.Exit(1)
} }
err = repos.Pull(ctx, config.Config(ctx).Repos) cfg = config.New()
db := database.New(cfg)
err = db.Init(ctx)
if err != nil { if err != nil {
log.Fatal("Error pulling repos").Err(err).Send() slog.Error(gotext.Get("Error initialization database"), "err", err)
os.Exit(1)
}
rs := repos.New(cfg, db)
err = rs.Pull(ctx, cfg.Repos(ctx))
if err != nil {
slog.Error(gotext.Get("Error pulling repos"), "err", err)
os.Exit(1)
} }
log.Info("Done").Send() slog.Info(gotext.Get("Done"))
return nil return nil
}, },
}
} }

39
gen.go
View File

@ -1,24 +1,42 @@
// This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
// It has been modified as part of "ALR - Any Linux Repository" by Евгений Храмов.
//
// ALR - Any Linux Repository
// Copyright (C) 2025 Евгений Храмов
//
// 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/>.
package main package main
import ( import (
"os" "os"
"github.com/leonelquinteros/gotext"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"plemya-x.ru/alr/pkg/gen"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/gen"
) )
var genCmd = &cli.Command{ func GenCmd() *cli.Command {
return &cli.Command{
Name: "generate", Name: "generate",
Usage: "Generate a ALR script from a template", Usage: gotext.Get("Generate a ALR script from a template"),
Aliases: []string{"gen"}, Aliases: []string{"gen"},
Subcommands: []*cli.Command{ Subcommands: []*cli.Command{
genPipCmd, {
},
}
var genPipCmd = &cli.Command{
Name: "pip", Name: "pip",
Usage: "Generate a ALR script for a pip module", Usage: gotext.Get("Generate a ALR script for a pip module"),
Flags: []cli.Flag{ Flags: []cli.Flag{
&cli.StringFlag{ &cli.StringFlag{
Name: "name", Name: "name",
@ -42,4 +60,7 @@ var genPipCmd = &cli.Command{
Description: c.String("description"), Description: c.String("description"),
}) })
}, },
},
},
}
} }

61
go.mod
View File

@ -1,39 +1,41 @@
module plemya-x.ru/alr module gitea.plemya-x.ru/Plemya-x/ALR
go 1.21 go 1.22
toolchain go1.21.3 toolchain go1.23.5
require ( require (
gitea.plemya-x.ru/Plemya-x/fakeroot v0.0.1
github.com/AlecAivazis/survey/v2 v2.3.7 github.com/AlecAivazis/survey/v2 v2.3.7
github.com/PuerkitoBio/purell v1.2.0 github.com/PuerkitoBio/purell v1.2.0
github.com/alecthomas/chroma/v2 v2.9.1 github.com/alecthomas/chroma/v2 v2.9.1
github.com/charmbracelet/bubbles v0.16.1 github.com/charmbracelet/bubbles v0.20.0
github.com/charmbracelet/bubbletea v0.24.2 github.com/charmbracelet/bubbletea v1.2.4
github.com/charmbracelet/lipgloss v0.8.0 github.com/charmbracelet/lipgloss v1.0.0
github.com/charmbracelet/log v0.4.0
github.com/go-git/go-billy/v5 v5.5.0 github.com/go-git/go-billy/v5 v5.5.0
github.com/go-git/go-git/v5 v5.12.0 github.com/go-git/go-git/v5 v5.12.0
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
github.com/goreleaser/nfpm/v2 v2.41.0 github.com/goreleaser/nfpm/v2 v2.41.0
github.com/jeandeaual/go-locale v0.0.0-20241217141322-fcc2cadd6f08
github.com/jmoiron/sqlx v1.3.5 github.com/jmoiron/sqlx v1.3.5
github.com/mattn/go-isatty v0.0.19 github.com/leonelquinteros/gotext v1.7.0
github.com/mattn/go-isatty v0.0.20
github.com/mholt/archiver/v4 v4.0.0-alpha.8 github.com/mholt/archiver/v4 v4.0.0-alpha.8
github.com/mitchellh/mapstructure v1.5.0 github.com/mitchellh/mapstructure v1.5.0
github.com/muesli/reflow v0.3.0 github.com/muesli/reflow v0.3.0
github.com/pelletier/go-toml/v2 v2.1.0 github.com/pelletier/go-toml/v2 v2.1.0
github.com/schollz/progressbar/v3 v3.13.1 github.com/stretchr/testify v1.10.0
github.com/urfave/cli/v2 v2.25.7 github.com/urfave/cli/v2 v2.25.7
github.com/vmihailenco/msgpack/v5 v5.3.5 github.com/vmihailenco/msgpack/v5 v5.3.5
go.elara.ws/logger v0.0.0-20230421022458-e80700db2090
go.elara.ws/translate v0.0.0-20230421025926-32ccfcd110e6
go.elara.ws/vercmp v0.0.0-20230622214216-0b2b067575c4 go.elara.ws/vercmp v0.0.0-20230622214216-0b2b067575c4
golang.org/x/crypto v0.23.0 golang.org/x/crypto v0.27.0
golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb
golang.org/x/sys v0.20.0 golang.org/x/sys v0.29.0
golang.org/x/text v0.15.0 golang.org/x/text v0.21.0
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
modernc.org/sqlite v1.25.0 modernc.org/sqlite v1.25.0
mvdan.cc/sh/v3 v3.7.0 mvdan.cc/sh/v3 v3.10.0
plemya-x.ru/fakeroot v0.0.0-20240601131003-c638a3543283
) )
require ( require (
@ -51,22 +53,26 @@ require (
github.com/bodgit/sevenzip v1.3.0 // indirect github.com/bodgit/sevenzip v1.3.0 // indirect
github.com/bodgit/windows v1.0.0 // indirect github.com/bodgit/windows v1.0.0 // indirect
github.com/cavaliergopher/cpio v1.0.1 // indirect github.com/cavaliergopher/cpio v1.0.1 // indirect
github.com/charmbracelet/harmonica v0.2.0 // indirect
github.com/charmbracelet/x/ansi v0.4.5 // indirect
github.com/charmbracelet/x/term v0.2.1 // indirect
github.com/cloudflare/circl v1.3.8 // indirect github.com/cloudflare/circl v1.3.8 // indirect
github.com/connesc/cipherio v0.2.1 // indirect github.com/connesc/cipherio v0.2.1 // indirect
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
github.com/cyphar/filepath-securejoin v0.2.4 // indirect github.com/cyphar/filepath-securejoin v0.2.4 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dlclark/regexp2 v1.10.0 // indirect github.com/dlclark/regexp2 v1.10.0 // indirect
github.com/dsnet/compress v0.0.1 // indirect github.com/dsnet/compress v0.0.1 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect github.com/dustin/go-humanize v1.0.1 // indirect
github.com/emirpasic/gods v1.18.1 // indirect github.com/emirpasic/gods v1.18.1 // indirect
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-logfmt/logfmt v0.6.0 // indirect
github.com/gobwas/glob v0.2.3 // indirect github.com/gobwas/glob v0.2.3 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/snappy v0.0.4 // indirect github.com/golang/snappy v0.0.4 // indirect
github.com/google/rpmpack v0.6.1-0.20240329070804-c2247cbb881a // indirect github.com/google/rpmpack v0.6.1-0.20240329070804-c2247cbb881a // indirect
github.com/google/uuid v1.4.0 // indirect github.com/google/uuid v1.4.0 // indirect
github.com/gookit/color v1.5.1 // indirect
github.com/goreleaser/chglog v0.6.1 // indirect github.com/goreleaser/chglog v0.6.1 // indirect
github.com/goreleaser/fileglob v1.3.0 // indirect github.com/goreleaser/fileglob v1.3.0 // indirect
github.com/hashicorp/errwrap v1.0.0 // indirect github.com/hashicorp/errwrap v1.0.0 // indirect
@ -79,21 +85,21 @@ require (
github.com/klauspost/compress v1.17.11 // indirect github.com/klauspost/compress v1.17.11 // indirect
github.com/klauspost/pgzip v1.2.6 // indirect github.com/klauspost/pgzip v1.2.6 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-colorable v0.1.2 // indirect github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-localereader v0.0.1 // indirect github.com/mattn/go-localereader v0.0.1 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b // indirect github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
github.com/muesli/cancelreader v0.2.2 // indirect github.com/muesli/cancelreader v0.2.2 // indirect
github.com/muesli/termenv v0.15.2 // indirect github.com/muesli/termenv v0.15.2 // indirect
github.com/nwaples/rardecode/v2 v2.0.0-beta.2 // indirect github.com/nwaples/rardecode/v2 v2.0.0-beta.2 // indirect
github.com/pierrec/lz4/v4 v4.1.15 // indirect github.com/pierrec/lz4/v4 v4.1.15 // indirect
github.com/pjbgf/sha1cd v0.3.0 // indirect github.com/pjbgf/sha1cd v0.3.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/rivo/uniseg v0.4.4 // indirect github.com/rivo/uniseg v0.4.7 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
github.com/shopspring/decimal v1.2.0 // indirect github.com/shopspring/decimal v1.2.0 // indirect
@ -103,15 +109,14 @@ require (
github.com/ulikunitz/xz v0.5.12 // indirect github.com/ulikunitz/xz v0.5.12 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
gitlab.com/digitalxero/go-conventional-commit v1.0.7 // indirect gitlab.com/digitalxero/go-conventional-commit v1.0.7 // indirect
go4.org v0.0.0-20200411211856-f5505b9728dd // indirect go4.org v0.0.0-20200411211856-f5505b9728dd // indirect
golang.org/x/mod v0.14.0 // indirect golang.org/x/mod v0.18.0 // indirect
golang.org/x/net v0.23.0 // indirect golang.org/x/net v0.26.0 // indirect
golang.org/x/sync v0.5.0 // indirect golang.org/x/sync v0.10.0 // indirect
golang.org/x/term v0.20.0 // indirect golang.org/x/term v0.28.0 // indirect
golang.org/x/tools v0.16.0 // indirect golang.org/x/tools v0.22.0 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect
lukechampine.com/uint128 v1.2.0 // indirect lukechampine.com/uint128 v1.2.0 // indirect
modernc.org/cc/v3 v3.40.0 // indirect modernc.org/cc/v3 v3.40.0 // indirect

126
go.sum
View File

@ -17,6 +17,8 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo
dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
gitea.plemya-x.ru/Plemya-x/fakeroot v0.0.1 h1:c7F4OsyQbiVpSOrYGMrNsRL37BwoOfrgoKxAwULBKZo=
gitea.plemya-x.ru/Plemya-x/fakeroot v0.0.1/go.mod h1:iKQM6uttMJgE5CFrPw6SQqAV7TKtlJNICRAie/dTciw=
github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkkhIiSjQ= github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkkhIiSjQ=
github.com/AlecAivazis/survey/v2 v2.3.7/go.mod h1:xUTIdE4KCOIjsBAE1JYsUPoCqYdZ1reCfTwbto0Fduo= github.com/AlecAivazis/survey/v2 v2.3.7/go.mod h1:xUTIdE4KCOIjsBAE1JYsUPoCqYdZ1reCfTwbto0Fduo=
github.com/AlekSi/pointer v1.2.0 h1:glcy/gc4h8HnG2Z3ZECSzZ1IX1x2JxRVuDzaJwQE0+w= github.com/AlekSi/pointer v1.2.0 h1:glcy/gc4h8HnG2Z3ZECSzZ1IX1x2JxRVuDzaJwQE0+w=
@ -73,12 +75,20 @@ github.com/caarlos0/testfs v0.4.4/go.mod h1:bRN55zgG4XCUVVHZCeU+/Tz1Q6AxEJOEJTli
github.com/cavaliergopher/cpio v1.0.1 h1:KQFSeKmZhv0cr+kawA3a0xTQCU4QxXF1vhU7P7av2KM= github.com/cavaliergopher/cpio v1.0.1 h1:KQFSeKmZhv0cr+kawA3a0xTQCU4QxXF1vhU7P7av2KM=
github.com/cavaliergopher/cpio v1.0.1/go.mod h1:pBdaqQjnvXxdS/6CvNDwIANIFSP0xRKI16PX4xejRQc= github.com/cavaliergopher/cpio v1.0.1/go.mod h1:pBdaqQjnvXxdS/6CvNDwIANIFSP0xRKI16PX4xejRQc=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/charmbracelet/bubbles v0.16.1 h1:6uzpAAaT9ZqKssntbvZMlksWHruQLNxg49H5WdeuYSY= github.com/charmbracelet/bubbles v0.20.0 h1:jSZu6qD8cRQ6k9OMfR1WlM+ruM8fkPWkHvQWD9LIutE=
github.com/charmbracelet/bubbles v0.16.1/go.mod h1:2QCp9LFlEsBQMvIYERr7Ww2H2bA7xen1idUDIzm/+Xc= github.com/charmbracelet/bubbles v0.20.0/go.mod h1:39slydyswPy+uVOHZ5x/GjwVAFkCsV8IIVy+4MhzwwU=
github.com/charmbracelet/bubbletea v0.24.2 h1:uaQIKx9Ai6Gdh5zpTbGiWpytMU+CfsPp06RaW2cx/SY= github.com/charmbracelet/bubbletea v1.2.4 h1:KN8aCViA0eps9SCOThb2/XPIlea3ANJLUkv3KnQRNCE=
github.com/charmbracelet/bubbletea v0.24.2/go.mod h1:XdrNrV4J8GiyshTtx3DNuYkR1FDaJmO3l2nejekbsgg= github.com/charmbracelet/bubbletea v1.2.4/go.mod h1:Qr6fVQw+wX7JkWWkVyXYk/ZUQ92a6XNekLXa3rR18MM=
github.com/charmbracelet/lipgloss v0.8.0 h1:IS00fk4XAHcf8uZKc3eHeMUTCxUH6NkaTrdyCQk84RU= github.com/charmbracelet/harmonica v0.2.0 h1:8NxJWRWg/bzKqqEaaeFNipOu77YR5t8aSwG4pgaUBiQ=
github.com/charmbracelet/lipgloss v0.8.0/go.mod h1:p4eYUZZJ/0oXTuCQKFF8mqyKCz0ja6y+7DniDDw5KKU= github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao=
github.com/charmbracelet/lipgloss v1.0.0 h1:O7VkGDvqEdGi93X+DeqsQ7PKHDgtQfF8j8/O2qFMQNg=
github.com/charmbracelet/lipgloss v1.0.0/go.mod h1:U5fy9Z+C38obMs+T+tJqst9VGzlOYGj4ri9reL3qUlo=
github.com/charmbracelet/log v0.4.0 h1:G9bQAcx8rWA2T3pWvx7YtPTPwgqpk7D68BX21IRW8ZM=
github.com/charmbracelet/log v0.4.0/go.mod h1:63bXt/djrizTec0l11H20t8FDSvA4CRZJ1KH22MdptM=
github.com/charmbracelet/x/ansi v0.4.5 h1:LqK4vwBNaXw2AyGIICa5/29Sbdq58GbGdFngSexTdRM=
github.com/charmbracelet/x/ansi v0.4.5/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw=
github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
@ -88,13 +98,11 @@ github.com/cloudflare/circl v1.3.8 h1:j+V8jJt09PoeMFIu2uh5JUyEaIHTXVOHslFoLNAKqw
github.com/cloudflare/circl v1.3.8/go.mod h1:PDRU+oXvdD7KCtgKxW95M5Z8BpSCJXQORiZFnBQS5QU= github.com/cloudflare/circl v1.3.8/go.mod h1:PDRU+oXvdD7KCtgKxW95M5Z8BpSCJXQORiZFnBQS5QU=
github.com/connesc/cipherio v0.2.1 h1:FGtpTPMbKNNWByNrr9aEBtaJtXjqOzkIXNYJp6OEycw= github.com/connesc/cipherio v0.2.1 h1:FGtpTPMbKNNWByNrr9aEBtaJtXjqOzkIXNYJp6OEycw=
github.com/connesc/cipherio v0.2.1/go.mod h1:ukY0MWJDFnJEbXMQtOcn2VmTpRfzcTz4OoVrWGGJZcA= github.com/connesc/cipherio v0.2.1/go.mod h1:ukY0MWJDFnJEbXMQtOcn2VmTpRfzcTz4OoVrWGGJZcA=
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 h1:q2hJAaP1k2wIvVRd/hEHD7lacgqrCPS+k8g1MndzfWY=
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk=
github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4= github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/creack/pty v1.1.23 h1:4M6+isWdcStXEf15G/RbrMPOQj1dZ7HPZCGwE4kOeP0=
github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/creack/pty v1.1.23/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg=
github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -113,6 +121,8 @@ github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/gliderlabs/ssh v0.3.7 h1:iV3Bqi942d9huXnzEF2Mt+CY9gLu8DNM4Obd+8bODRE= github.com/gliderlabs/ssh v0.3.7 h1:iV3Bqi942d9huXnzEF2Mt+CY9gLu8DNM4Obd+8bODRE=
@ -127,6 +137,10 @@ github.com/go-git/go-git/v5 v5.12.0 h1:7Md+ndsjrzZxbddRDZjF14qK+NN56sy6wkqaVrjZt
github.com/go-git/go-git/v5 v5.12.0/go.mod h1:FTM9VKtnI2m65hNI/TenDDDnUf2Q9FHnXYjuz9i5OEY= github.com/go-git/go-git/v5 v5.12.0/go.mod h1:FTM9VKtnI2m65hNI/TenDDDnUf2Q9FHnXYjuz9i5OEY=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=
github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI=
github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow=
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
@ -166,13 +180,13 @@ github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S3
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/rpmpack v0.6.1-0.20240329070804-c2247cbb881a h1:JJBdjSfqSy3mnDT0940ASQFghwcZ4y4cb6ttjAoXqwE= github.com/google/rpmpack v0.6.1-0.20240329070804-c2247cbb881a h1:JJBdjSfqSy3mnDT0940ASQFghwcZ4y4cb6ttjAoXqwE=
github.com/google/rpmpack v0.6.1-0.20240329070804-c2247cbb881a/go.mod h1:uqVAUVQLq8UY2hCDfmJ/+rtO3aw7qyhc90rCVEabEfI= github.com/google/rpmpack v0.6.1-0.20240329070804-c2247cbb881a/go.mod h1:uqVAUVQLq8UY2hCDfmJ/+rtO3aw7qyhc90rCVEabEfI=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gookit/color v1.5.1 h1:Vjg2VEcdHpwq+oY63s/ksHrgJYCTo0bwWvmmYWdE9fQ=
github.com/gookit/color v1.5.1/go.mod h1:wZFzea4X8qN6vHOSP2apMb4/+w/orMznEzYsIHPaqKM=
github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g= github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g=
github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k= github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k=
github.com/goreleaser/chglog v0.6.1 h1:NZKiX8l0FTQPRzBgKST7knvNZmZ04f7PEGkN2wInfhE= github.com/goreleaser/chglog v0.6.1 h1:NZKiX8l0FTQPRzBgKST7knvNZmZ04f7PEGkN2wInfhE=
@ -199,13 +213,14 @@ github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4=
github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/jeandeaual/go-locale v0.0.0-20241217141322-fcc2cadd6f08 h1:wMeVzrPO3mfHIWLZtDcSaGAe2I4PW9B/P5nMkRSwCAc=
github.com/jeandeaual/go-locale v0.0.0-20241217141322-fcc2cadd6f08/go.mod h1:ZDXo8KHryOWSIqnsb/CiDq7hQUYryCgdVnxbj8tDG7o=
github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g= github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g=
github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
@ -224,24 +239,26 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leonelquinteros/gotext v1.7.0 h1:jcJmF4AXqyamP7vuw2MMIKs+O3jAEmvrc5JQiI8Ht/8=
github.com/leonelquinteros/gotext v1.7.0/go.mod h1:qJdoQuERPpccw7L70uoU+K/BvTfRBHYsisCQyFLXyvw=
github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0= github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE= github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE=
github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y= github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
@ -249,8 +266,6 @@ github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1f
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
github.com/mholt/archiver/v4 v4.0.0-alpha.8 h1:tRGQuDVPh66WCOelqe6LIGh0gwmfwxUrSSDunscGsRM= github.com/mholt/archiver/v4 v4.0.0-alpha.8 h1:tRGQuDVPh66WCOelqe6LIGh0gwmfwxUrSSDunscGsRM=
github.com/mholt/archiver/v4 v4.0.0-alpha.8/go.mod h1:5f7FUYGXdJWUjESffJaYR4R60VhnHxb2X3T1teMyv5A= github.com/mholt/archiver/v4 v4.0.0-alpha.8/go.mod h1:5f7FUYGXdJWUjESffJaYR4R60VhnHxb2X3T1teMyv5A=
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ=
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw=
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
@ -259,8 +274,8 @@ github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RR
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b h1:1XF24mVaiu7u+CFywTdcDo2ie1pzzhwjt6RHqzpMU34= github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho= github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
@ -287,18 +302,16 @@ github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk= github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk=
github.com/sassoftware/go-rpmutils v0.4.0 h1:ojND82NYBxgwrV+mX1CWsd5QJvvEZTKddtCdFLPWhpg= github.com/sassoftware/go-rpmutils v0.4.0 h1:ojND82NYBxgwrV+mX1CWsd5QJvvEZTKddtCdFLPWhpg=
github.com/sassoftware/go-rpmutils v0.4.0/go.mod h1:3goNWi7PGAT3/dlql2lv3+MSN5jNYPjT5mVcQcIsYzI= github.com/sassoftware/go-rpmutils v0.4.0/go.mod h1:3goNWi7PGAT3/dlql2lv3+MSN5jNYPjT5mVcQcIsYzI=
github.com/schollz/progressbar/v3 v3.13.1 h1:o8rySDYiQ59Mwzy2FELeHY5ZARXZTVJC7iHD6PEFUiE=
github.com/schollz/progressbar/v3 v3.13.1/go.mod h1:xvrbki8kfT1fzWzBT/UZd9L6GA+jdL7HAgq2RFnO6fQ=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
@ -317,16 +330,14 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/therootcompany/xz v1.0.1 h1:CmOtsn1CbtmyYiusbfmhmkpAAETj0wBIH6kCYaX+xzw= github.com/therootcompany/xz v1.0.1 h1:CmOtsn1CbtmyYiusbfmhmkpAAETj0wBIH6kCYaX+xzw=
github.com/therootcompany/xz v1.0.1/go.mod h1:3K3UH1yCKgBneZYhuQUvJ9HPD19UEXEI0BWbMn8qNMY= github.com/therootcompany/xz v1.0.1/go.mod h1:3K3UH1yCKgBneZYhuQUvJ9HPD19UEXEI0BWbMn8qNMY=
github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8=
@ -342,17 +353,11 @@ github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo= github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo=
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos= github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos=
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 h1:QldyIu/L63oPpyvQmHgvgickp1Yw510KJOqX7H24mg8=
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
gitlab.com/digitalxero/go-conventional-commit v1.0.7 h1:8/dO6WWG+98PMhlZowt/YjuiKhqhGlOCwlIV8SqqGh8= gitlab.com/digitalxero/go-conventional-commit v1.0.7 h1:8/dO6WWG+98PMhlZowt/YjuiKhqhGlOCwlIV8SqqGh8=
gitlab.com/digitalxero/go-conventional-commit v1.0.7/go.mod h1:05Xc2BFsSyC5tKhK0y+P3bs0AwUtNuTp+mTpbCU/DZ0= gitlab.com/digitalxero/go-conventional-commit v1.0.7/go.mod h1:05Xc2BFsSyC5tKhK0y+P3bs0AwUtNuTp+mTpbCU/DZ0=
go.elara.ws/logger v0.0.0-20230421022458-e80700db2090 h1:RVC8XvWo6Yw4HUshqx4TSzuBDScDghafU6QFRJ4xPZg=
go.elara.ws/logger v0.0.0-20230421022458-e80700db2090/go.mod h1:qng49owViqsW5Aey93lwBXONw20oGbJIoLVscB16mPM=
go.elara.ws/translate v0.0.0-20230421025926-32ccfcd110e6 h1:4xCBxLPBn3Y2DuIcj8zQ1tQOFLrpu6tEIGUWn/Q6zPM=
go.elara.ws/translate v0.0.0-20230421025926-32ccfcd110e6/go.mod h1:NmfCFqwq7X/aqa/ZVkIysj17JyMEY4Bb5E921kMswNo=
go.elara.ws/vercmp v0.0.0-20230622214216-0b2b067575c4 h1:Ep54XceQlKhcCHl9awG+wWP4kz4kIP3c3Lzw/Gc/zwY= go.elara.ws/vercmp v0.0.0-20230622214216-0b2b067575c4 h1:Ep54XceQlKhcCHl9awG+wWP4kz4kIP3c3Lzw/Gc/zwY=
go.elara.ws/vercmp v0.0.0-20230622214216-0b2b067575c4/go.mod h1:/7PNW7nFnDR5W7UXZVc04gdVLR/wBNgkm33KgIz0OBk= go.elara.ws/vercmp v0.0.0-20230622214216-0b2b067575c4/go.mod h1:/7PNW7nFnDR5W7UXZVc04gdVLR/wBNgkm33KgIz0OBk=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
@ -370,8 +375,8 @@ golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@ -401,8 +406,8 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -423,8 +428,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@ -438,8 +443,8 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -457,27 +462,26 @@ golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw= golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -488,8 +492,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@ -518,8 +522,8 @@ golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapK
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.16.0 h1:GO788SKMRunPIBCXiQyo2AaexLstOrVhuAL5YwsckQM= golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
golang.org/x/tools v0.16.0/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@ -603,10 +607,8 @@ modernc.org/token v1.0.1 h1:A3qvTqOwexpfZZeyI0FeGPDlSWX5pjZu9hF4lU+EKWg=
modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
modernc.org/z v1.7.3 h1:zDJf6iHjrnB+WRD88stbXokugjyc0/pB91ri1gO6LZY= modernc.org/z v1.7.3 h1:zDJf6iHjrnB+WRD88stbXokugjyc0/pB91ri1gO6LZY=
modernc.org/z v1.7.3/go.mod h1:Ipv4tsdxZRbQyLq9Q1M6gdbkxYzdlrciF2Hi/lS7nWE= modernc.org/z v1.7.3/go.mod h1:Ipv4tsdxZRbQyLq9Q1M6gdbkxYzdlrciF2Hi/lS7nWE=
mvdan.cc/sh/v3 v3.7.0 h1:lSTjdP/1xsddtaKfGg7Myu7DnlHItd3/M2tomOcNNBg= mvdan.cc/sh/v3 v3.10.0 h1:v9z7N1DLZ7owyLM/SXZQkBSXcwr2IGMm2LY2pmhVXj4=
mvdan.cc/sh/v3 v3.7.0/go.mod h1:K2gwkaesF/D7av7Kxl0HbF5kGOd2ArupNTX3X44+8l8= mvdan.cc/sh/v3 v3.10.0/go.mod h1:z/mSSVyLFGZzqb3ZIKojjyqIx/xbmz/UHdCSv9HmqXY=
plemya-x.ru/fakeroot v0.0.0-20240601131003-c638a3543283 h1:BXCLPeA8g2M6qYngicyxyB/2Bo4J54Q9Rb+8jMmE3ik=
plemya-x.ru/fakeroot v0.0.0-20240601131003-c638a3543283/go.mod h1:itzL9Jx52VXOhRaucFHuMpa3y7iwjnuLGdNvypoh/S4=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=

View File

@ -1,35 +1,68 @@
// This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
// It has been modified as part of "ALR - Any Linux Repository" by Евгений Храмов.
//
// ALR - Any Linux Repository
// Copyright (C) 2025 Евгений Храмов
//
// 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/>.
package main package main
import ( import (
"fmt" "fmt"
"log/slog"
"os" "os"
"strings" "strings"
"github.com/leonelquinteros/gotext"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"plemya-x.ru/alr/internal/cpu"
"plemya-x.ru/alr/internal/shutils/helpers"
"plemya-x.ru/alr/pkg/distro"
"plemya-x.ru/alr/pkg/loggerctx"
"mvdan.cc/sh/v3/expand" "mvdan.cc/sh/v3/expand"
"mvdan.cc/sh/v3/interp" "mvdan.cc/sh/v3/interp"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cpu"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/helpers"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro"
) )
var helperCmd = &cli.Command{ func HelperCmd() *cli.Command {
helperListCmd := &cli.Command{
Name: "list",
Usage: gotext.Get("List all the available helper commands"),
Aliases: []string{"ls"},
Action: func(ctx *cli.Context) error {
for name := range helpers.Helpers {
fmt.Println(name)
}
return nil
},
}
return &cli.Command{
Name: "helper", Name: "helper",
Usage: "Run a ALR helper command", Usage: gotext.Get("Run a ALR helper command"),
ArgsUsage: `<helper_name|"list">`, ArgsUsage: `<helper_name|"list">`,
Subcommands: []*cli.Command{helperListCmd}, Subcommands: []*cli.Command{helperListCmd},
Flags: []cli.Flag{ Flags: []cli.Flag{
&cli.StringFlag{ &cli.StringFlag{
Name: "dest-dir", Name: "dest-dir",
Aliases: []string{"d"}, Aliases: []string{"d"},
Usage: "The directory that the install commands will install to", Usage: gotext.Get("The directory that the install commands will install to"),
Value: "dest", Value: "dest",
}, },
}, },
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
ctx := c.Context ctx := c.Context
log := loggerctx.From(ctx)
if c.Args().Len() < 1 { if c.Args().Len() < 1 {
cli.ShowSubcommandHelpAndExit(c, 1) cli.ShowSubcommandHelpAndExit(c, 1)
@ -37,17 +70,20 @@ var helperCmd = &cli.Command{
helper, ok := helpers.Helpers[c.Args().First()] helper, ok := helpers.Helpers[c.Args().First()]
if !ok { if !ok {
log.Fatal("No such helper command").Str("name", c.Args().First()).Send() slog.Error(gotext.Get("No such helper command"), "name", c.Args().First())
os.Exit(1)
} }
wd, err := os.Getwd() wd, err := os.Getwd()
if err != nil { if err != nil {
log.Fatal("Error getting working directory").Err(err).Send() slog.Error(gotext.Get("Error getting working directory"), "err", err)
os.Exit(1)
} }
info, err := distro.ParseOSRelease(ctx) info, err := distro.ParseOSRelease(ctx)
if err != nil { if err != nil {
log.Fatal("Error getting working directory").Err(err).Send() slog.Error(gotext.Get("Error getting working directory"), "err", err)
os.Exit(1)
} }
hc := interp.HandlerContext{ hc := interp.HandlerContext{
@ -71,16 +107,5 @@ var helperCmd = &cli.Command{
fmt.Println(name) fmt.Println(name)
} }
}, },
}
var helperListCmd = &cli.Command{
Name: "list",
Usage: "List all the available helper commands",
Aliases: []string{"ls"},
Action: func(ctx *cli.Context) error {
for name := range helpers.Helpers {
fmt.Println(name)
} }
return nil
},
} }

132
info.go
View File

@ -1,64 +1,111 @@
/* // This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
* ALR - Any Linux Repository // It has been modified as part of "ALR - Any Linux Repository" by Евгений Храмов.
* Copyright (C) 2024 Евгений Храмов //
* // ALR - Any Linux Repository
* This program is free software: you can redistribute it and/or modify // Copyright (C) 2025 Евгений Храмов
* it under the terms of the GNU General Public License as published by //
* the Free Software Foundation, either version 3 of the License, or // This program is free software: you can redistribute it and/or modify
* (at your option) any later version. // it under the terms of the GNU General Public License as published by
* // the Free Software Foundation, either version 3 of the License, or
* This program is distributed in the hope that it will be useful, // (at your option) any later version.
* but WITHOUT ANY WARRANTY; without even the implied warranty of //
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // This program is distributed in the hope that it will be useful,
* GNU General Public License for more details. // but WITHOUT ANY WARRANTY; without even the implied warranty of
* // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* You should have received a copy of the GNU General Public License // GNU General Public License for more details.
* along with this program. If not, see <http://www.gnu.org/licenses/>. //
*/ // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package main package main
import ( import (
"fmt" "fmt"
"log/slog"
"os" "os"
"github.com/jeandeaual/go-locale"
"github.com/leonelquinteros/gotext"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"plemya-x.ru/alr/internal/cliutils"
"plemya-x.ru/alr/internal/config"
"plemya-x.ru/alr/internal/overrides"
"plemya-x.ru/alr/pkg/distro"
"plemya-x.ru/alr/pkg/loggerctx"
"plemya-x.ru/alr/pkg/repos"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
database "gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/overrides"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/repos"
) )
var infoCmd = &cli.Command{ func InfoCmd() *cli.Command {
return &cli.Command{
Name: "info", Name: "info",
Usage: "Print information about a package", Usage: gotext.Get("Print information about a package"),
Flags: []cli.Flag{ Flags: []cli.Flag{
&cli.BoolFlag{ &cli.BoolFlag{
Name: "all", Name: "all",
Aliases: []string{"a"}, Aliases: []string{"a"},
Usage: "Show all information, not just for the current distro", Usage: gotext.Get("Show all information, not just for the current distro"),
}, },
}, },
BashComplete: func(c *cli.Context) {
ctx := c.Context
cfg := config.New()
db := database.New(cfg)
err := db.Init(ctx)
if err != nil {
slog.Error(gotext.Get("Error initialization database"), "err", err)
os.Exit(1)
}
result, err := db.GetPkgs(c.Context, "true")
if err != nil {
slog.Error(gotext.Get("Error getting packages"), "err", err)
os.Exit(1)
}
defer result.Close()
for result.Next() {
var pkg database.Package
err = result.StructScan(&pkg)
if err != nil {
slog.Error(gotext.Get("Error iterating over packages"), "err", err)
os.Exit(1)
}
fmt.Println(pkg.Name)
}
},
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
ctx := c.Context ctx := c.Context
log := loggerctx.From(ctx)
cfg := config.New()
db := database.New(cfg)
err := db.Init(ctx)
if err != nil {
slog.Error(gotext.Get("Error initialization database"), "err", err)
os.Exit(1)
}
rs := repos.New(cfg, db)
args := c.Args() args := c.Args()
if args.Len() < 1 { if args.Len() < 1 {
log.Fatalf("Command info expected at least 1 argument, got %d", args.Len()).Send() slog.Error(gotext.Get("Command info expected at least 1 argument, got %d", args.Len()))
os.Exit(1)
} }
err := repos.Pull(ctx, config.Config(ctx).Repos) if cfg.AutoPull(ctx) {
err := rs.Pull(ctx, cfg.Repos(ctx))
if err != nil { if err != nil {
log.Fatal("Error pulling repositories").Err(err).Send() slog.Error(gotext.Get("Error pulling repos"), "err", err)
os.Exit(1)
}
} }
found, _, err := repos.FindPkgs(ctx, args.Slice()) found, _, err := rs.FindPkgs(ctx, args.Slice())
if err != nil { if err != nil {
log.Fatal("Error finding packages").Err(err).Send() slog.Error(gotext.Get("Error finding packages"), "err", err)
os.Exit(1)
} }
if len(found) == 0 { if len(found) == 0 {
@ -70,18 +117,26 @@ var infoCmd = &cli.Command{
var names []string var names []string
all := c.Bool("all") all := c.Bool("all")
systemLang, err := locale.GetLanguage()
if err != nil {
slog.Error("Can't detect system language", "err", err)
os.Exit(1)
}
if !all { if !all {
info, err := distro.ParseOSRelease(ctx) info, err := distro.ParseOSRelease(ctx)
if err != nil { if err != nil {
log.Fatal("Error parsing os-release file").Err(err).Send() slog.Error(gotext.Get("Error parsing os-release file"), "err", err)
os.Exit(1)
} }
names, err = overrides.Resolve( names, err = overrides.Resolve(
info, info,
overrides.DefaultOpts. overrides.DefaultOpts.
WithLanguages([]string{config.SystemLang()}), WithLanguages([]string{systemLang}),
) )
if err != nil { if err != nil {
log.Fatal("Error resolving overrides").Err(err).Send() slog.Error(gotext.Get("Error resolving overrides"), "err", err)
os.Exit(1)
} }
} }
@ -89,12 +144,14 @@ var infoCmd = &cli.Command{
if !all { if !all {
err = yaml.NewEncoder(os.Stdout).Encode(overrides.ResolvePackage(&pkg, names)) err = yaml.NewEncoder(os.Stdout).Encode(overrides.ResolvePackage(&pkg, names))
if err != nil { if err != nil {
log.Fatal("Error encoding script variables").Err(err).Send() slog.Error(gotext.Get("Error encoding script variables"), "err", err)
os.Exit(1)
} }
} else { } else {
err = yaml.NewEncoder(os.Stdout).Encode(pkg) err = yaml.NewEncoder(os.Stdout).Encode(pkg)
if err != nil { if err != nil {
log.Fatal("Error encoding script variables").Err(err).Send() slog.Error(gotext.Get("Error encoding script variables"), "err", err)
os.Exit(1)
} }
} }
@ -103,4 +160,5 @@ var infoCmd = &cli.Command{
return nil return nil
}, },
}
} }

View File

@ -1,74 +1,115 @@
/* // This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
* ALR - Any Linux Repository // It has been modified as part of "ALR - Any Linux Repository" by Евгений Храмов.
* Copyright (C) 2024 Евгений Храмов //
* // ALR - Any Linux Repository
* This program is free software: you can redistribute it and/or modify // Copyright (C) 2025 Евгений Храмов
* it under the terms of the GNU General Public License as published by //
* the Free Software Foundation, either version 3 of the License, or // This program is free software: you can redistribute it and/or modify
* (at your option) any later version. // it under the terms of the GNU General Public License as published by
* // the Free Software Foundation, either version 3 of the License, or
* This program is distributed in the hope that it will be useful, // (at your option) any later version.
* but WITHOUT ANY WARRANTY; without even the implied warranty of //
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // This program is distributed in the hope that it will be useful,
* GNU General Public License for more details. // but WITHOUT ANY WARRANTY; without even the implied warranty of
* // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* You should have received a copy of the GNU General Public License // GNU General Public License for more details.
* along with this program. If not, see <http://www.gnu.org/licenses/>. //
*/ // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package main package main
import ( import (
"fmt" "fmt"
"log/slog"
"os"
"github.com/leonelquinteros/gotext"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"plemya-x.ru/alr/internal/cliutils"
"plemya-x.ru/alr/internal/config" "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils"
"plemya-x.ru/alr/internal/db" "gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
"plemya-x.ru/alr/internal/types" database "gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
"plemya-x.ru/alr/pkg/build" "gitea.plemya-x.ru/Plemya-x/ALR/internal/types"
"plemya-x.ru/alr/pkg/loggerctx" "gitea.plemya-x.ru/Plemya-x/ALR/pkg/build"
"plemya-x.ru/alr/pkg/manager" "gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro"
"plemya-x.ru/alr/pkg/repos" "gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/repos"
) )
var installCmd = &cli.Command{ func InstallCmd() *cli.Command {
return &cli.Command{
Name: "install", Name: "install",
Usage: "Install a new package", Usage: gotext.Get("Install a new package"),
Aliases: []string{"in"}, Aliases: []string{"in"},
Flags: []cli.Flag{ Flags: []cli.Flag{
&cli.BoolFlag{ &cli.BoolFlag{
Name: "clean", Name: "clean",
Aliases: []string{"c"}, Aliases: []string{"c"},
Usage: "Build package from scratch even if there's an already built package available", Usage: gotext.Get("Build package from scratch even if there's an already built package available"),
}, },
}, },
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
ctx := c.Context ctx := c.Context
log := loggerctx.From(ctx)
args := c.Args() args := c.Args()
if args.Len() < 1 { if args.Len() < 1 {
log.Fatalf("Command install expected at least 1 argument, got %d", args.Len()).Send() slog.Error(gotext.Get("Command install expected at least 1 argument, got %d", args.Len()))
os.Exit(1)
} }
mgr := manager.Detect() mgr := manager.Detect()
if mgr == nil { if mgr == nil {
log.Fatal("Unable to detect a supported package manager on the system").Send() slog.Error(gotext.Get("Unable to detect a supported package manager on the system"))
os.Exit(1)
} }
err := repos.Pull(ctx, config.Config(ctx).Repos) cfg := config.New()
db := database.New(cfg)
rs := repos.New(cfg, db)
err := db.Init(ctx)
if err != nil { if err != nil {
log.Fatal("Error pulling repositories").Err(err).Send() slog.Error(gotext.Get("Error initialization database"), "err", err)
os.Exit(1)
} }
found, notFound, err := repos.FindPkgs(ctx, args.Slice()) if cfg.AutoPull(ctx) {
err := rs.Pull(ctx, cfg.Repos(ctx))
if err != nil { if err != nil {
log.Fatal("Error finding packages").Err(err).Send() slog.Error(gotext.Get("Error pulling repositories"), "err", err)
os.Exit(1)
}
}
found, notFound, err := rs.FindPkgs(ctx, args.Slice())
if err != nil {
slog.Error(gotext.Get("Error finding packages"), "err", err)
os.Exit(1)
} }
pkgs := cliutils.FlattenPkgs(ctx, found, "install", c.Bool("interactive")) pkgs := cliutils.FlattenPkgs(ctx, found, "install", c.Bool("interactive"))
build.InstallPkgs(ctx, pkgs, notFound, types.BuildOpts{
opts := types.BuildOpts{
Manager: mgr,
Clean: c.Bool("clean"),
Interactive: c.Bool("interactive"),
}
info, err := distro.ParseOSRelease(ctx)
if err != nil {
slog.Error(gotext.Get("Error parsing os release"), "err", err)
os.Exit(1)
}
builder := build.NewBuilder(
ctx,
opts,
rs,
info,
cfg,
)
builder.InstallPkgs(ctx, pkgs, notFound, types.BuildOpts{
Manager: mgr, Manager: mgr,
Clean: c.Bool("clean"), Clean: c.Bool("clean"),
Interactive: c.Bool("interactive"), Interactive: c.Bool("interactive"),
@ -76,47 +117,59 @@ var installCmd = &cli.Command{
return nil return nil
}, },
BashComplete: func(c *cli.Context) { BashComplete: func(c *cli.Context) {
log := loggerctx.From(c.Context) cfg := config.New()
db := database.New(cfg)
err := db.Init(c.Context)
if err != nil {
slog.Error(gotext.Get("Error initialization database"), "err", err)
os.Exit(1)
}
result, err := db.GetPkgs(c.Context, "true") result, err := db.GetPkgs(c.Context, "true")
if err != nil { if err != nil {
log.Fatal("Error getting packages").Err(err).Send() slog.Error(gotext.Get("Error getting packages"), "err", err)
os.Exit(1)
} }
defer result.Close() defer result.Close()
for result.Next() { for result.Next() {
var pkg db.Package var pkg database.Package
err = result.StructScan(&pkg) err = result.StructScan(&pkg)
if err != nil { if err != nil {
log.Fatal("Error iterating over packages").Err(err).Send() slog.Error(gotext.Get("Error iterating over packages"), "err", err)
os.Exit(1)
} }
fmt.Println(pkg.Name) fmt.Println(pkg.Name)
} }
}, },
}
} }
var removeCmd = &cli.Command{ func RemoveCmd() *cli.Command {
return &cli.Command{
Name: "remove", Name: "remove",
Usage: "Remove an installed package", Usage: gotext.Get("Remove an installed package"),
Aliases: []string{"rm"}, Aliases: []string{"rm"},
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
log := loggerctx.From(c.Context)
args := c.Args() args := c.Args()
if args.Len() < 1 { if args.Len() < 1 {
log.Fatalf("Command remove expected at least 1 argument, got %d", args.Len()).Send() slog.Error(gotext.Get("Command remove expected at least 1 argument, got %d", args.Len()))
os.Exit(1)
} }
mgr := manager.Detect() mgr := manager.Detect()
if mgr == nil { if mgr == nil {
log.Fatal("Unable to detect a supported package manager on the system").Send() slog.Error(gotext.Get("Unable to detect a supported package manager on the system"))
os.Exit(1)
} }
err := mgr.Remove(nil, c.Args().Slice()...) err := mgr.Remove(nil, c.Args().Slice()...)
if err != nil { if err != nil {
log.Fatal("Error removing packages").Err(err).Send() slog.Error(gotext.Get("Error removing packages"), "err", err)
os.Exit(1)
} }
return nil return nil
}, },
}
} }

View File

@ -1,34 +1,35 @@
/* // This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
* ALR - Any Linux Repository // It has been modified as part of "ALR - Any Linux Repository" by Евгений Храмов.
* Copyright (C) 2024 Евгений Храмов //
* // ALR - Any Linux Repository
* This program is free software: you can redistribute it and/or modify // Copyright (C) 2025 Евгений Храмов
* it under the terms of the GNU General Public License as published by //
* the Free Software Foundation, either version 3 of the License, or // This program is free software: you can redistribute it and/or modify
* (at your option) any later version. // it under the terms of the GNU General Public License as published by
* // the Free Software Foundation, either version 3 of the License, or
* This program is distributed in the hope that it will be useful, // (at your option) any later version.
* but WITHOUT ANY WARRANTY; without even the implied warranty of //
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // This program is distributed in the hope that it will be useful,
* GNU General Public License for more details. // but WITHOUT ANY WARRANTY; without even the implied warranty of
* // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* You should have received a copy of the GNU General Public License // GNU General Public License for more details.
* along with this program. If not, see <http://www.gnu.org/licenses/>. //
*/ // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package cliutils package cliutils
import ( import (
"context" "context"
"log/slog"
"os" "os"
"strings" "strings"
"github.com/AlecAivazis/survey/v2" "github.com/AlecAivazis/survey/v2"
"plemya-x.ru/alr/internal/config" "github.com/leonelquinteros/gotext"
"plemya-x.ru/alr/internal/db"
"plemya-x.ru/alr/internal/pager" "gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
"plemya-x.ru/alr/internal/translations" "gitea.plemya-x.ru/Plemya-x/ALR/internal/pager"
"plemya-x.ru/alr/pkg/loggerctx"
) )
// YesNoPrompt asks the user a yes or no question, using def as the default answer // YesNoPrompt asks the user a yes or no question, using def as the default answer
@ -37,7 +38,7 @@ func YesNoPrompt(ctx context.Context, msg string, interactive, def bool) (bool,
var answer bool var answer bool
err := survey.AskOne( err := survey.AskOne(
&survey.Confirm{ &survey.Confirm{
Message: translations.Translator(ctx).TranslateTo(msg, config.Language(ctx)), Message: msg,
Default: def, Default: def,
}, },
&answer, &answer,
@ -52,14 +53,11 @@ func YesNoPrompt(ctx context.Context, msg string, interactive, def bool) (bool,
// shows it if they answer yes, then asks if they'd still like to // shows it if they answer yes, then asks if they'd still like to
// continue, and exits if they answer no. // continue, and exits if they answer no.
func PromptViewScript(ctx context.Context, script, name, style string, interactive bool) error { func PromptViewScript(ctx context.Context, script, name, style string, interactive bool) error {
log := loggerctx.From(ctx)
if !interactive { if !interactive {
return nil return nil
} }
scriptPrompt := translations.Translator(ctx).TranslateTo("Would you like to view the build script for", config.Language(ctx)) + " " + name view, err := YesNoPrompt(ctx, gotext.Get("Would you like to view the build script for %s", name), interactive, false)
view, err := YesNoPrompt(ctx, scriptPrompt, interactive, false)
if err != nil { if err != nil {
return err return err
} }
@ -70,13 +68,14 @@ func PromptViewScript(ctx context.Context, script, name, style string, interacti
return err return err
} }
cont, err := YesNoPrompt(ctx, "Would you still like to continue?", interactive, false) cont, err := YesNoPrompt(ctx, gotext.Get("Would you still like to continue?"), interactive, false)
if err != nil { if err != nil {
return err return err
} }
if !cont { if !cont {
log.Fatal(translations.Translator(ctx).TranslateTo("User chose not to continue after reading script", config.Language(ctx))).Send() slog.Error(gotext.Get("User chose not to continue after reading script"))
os.Exit(1)
} }
} }
@ -104,13 +103,13 @@ func ShowScript(path, name, style string) error {
// FlattenPkgs attempts to flatten the a map of slices of packages into a single slice // FlattenPkgs attempts to flatten the a map of slices of packages into a single slice
// of packages by prompting the user if multiple packages match. // of packages by prompting the user if multiple packages match.
func FlattenPkgs(ctx context.Context, found map[string][]db.Package, verb string, interactive bool) []db.Package { func FlattenPkgs(ctx context.Context, found map[string][]db.Package, verb string, interactive bool) []db.Package {
log := loggerctx.From(ctx)
var outPkgs []db.Package var outPkgs []db.Package
for _, pkgs := range found { for _, pkgs := range found {
if len(pkgs) > 1 && interactive { if len(pkgs) > 1 && interactive {
choice, err := PkgPrompt(ctx, pkgs, verb, interactive) choice, err := PkgPrompt(ctx, pkgs, verb, interactive)
if err != nil { if err != nil {
log.Fatal("Error prompting for choice of package").Send() slog.Error(gotext.Get("Error prompting for choice of package"))
os.Exit(1)
} }
outPkgs = append(outPkgs, choice) outPkgs = append(outPkgs, choice)
} else if len(pkgs) == 1 || !interactive { } else if len(pkgs) == 1 || !interactive {
@ -133,7 +132,7 @@ func PkgPrompt(ctx context.Context, options []db.Package, verb string, interacti
prompt := &survey.Select{ prompt := &survey.Select{
Options: names, Options: names,
Message: translations.Translator(ctx).TranslateTo("Choose which package to "+verb, config.Language(ctx)), Message: gotext.Get("Choose which package to %s", verb),
} }
var choice int var choice int
@ -154,7 +153,7 @@ func ChooseOptDepends(ctx context.Context, options []string, verb string, intera
prompt := &survey.MultiSelect{ prompt := &survey.MultiSelect{
Options: options, Options: options,
Message: translations.Translator(ctx).TranslateTo("Choose which optional package(s) to install", config.Language(ctx)), Message: gotext.Get("Choose which optional package(s) to install"),
} }
var choices []int var choices []int

View File

@ -0,0 +1,102 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 Евгений Храмов
//
// 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/>.
package cliutils
import (
"fmt"
"github.com/leonelquinteros/gotext"
)
// Templates are based on https://github.com/urfave/cli/blob/3b17080d70a630feadadd23dd036cad121dd9a50/template.go
//nolint:unused
var (
helpNameTemplate = `{{$v := offset .HelpName 6}}{{wrap .HelpName 3}}{{if .Usage}} - {{wrap .Usage $v}}{{end}}`
descriptionTemplate = `{{wrap .Description 3}}`
authorsTemplate = `{{with $length := len .Authors}}{{if ne 1 $length}}S{{end}}{{end}}:
{{range $index, $author := .Authors}}{{if $index}}
{{end}}{{$author}}{{end}}`
visibleCommandTemplate = `{{ $cv := offsetCommands .VisibleCommands 5}}{{range .VisibleCommands}}
{{$s := join .Names ", "}}{{$s}}{{ $sp := subtract $cv (offset $s 3) }}{{ indent $sp ""}}{{wrap .Usage $cv}}{{end}}`
visibleCommandCategoryTemplate = `{{range .VisibleCategories}}{{if .Name}}
{{.Name}}:{{range .VisibleCommands}}
{{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{else}}{{template "visibleCommandTemplate" .}}{{end}}{{end}}`
visibleFlagCategoryTemplate = `{{range .VisibleFlagCategories}}
{{if .Name}}{{.Name}}
{{end}}{{$flglen := len .Flags}}{{range $i, $e := .Flags}}{{if eq (subtract $flglen $i) 1}}{{$e}}
{{else}}{{$e}}
{{end}}{{end}}{{end}}`
visibleFlagTemplate = `{{range $i, $e := .VisibleFlags}}
{{wrap $e.String 6}}{{end}}`
copyrightTemplate = `{{wrap .Copyright 3}}`
)
func GetAppCliTemplate() string {
return fmt.Sprintf(`%s:
{{template "helpNameTemplate" .}}
%s:
{{if .UsageText}}{{wrap .UsageText 3}}{{else}}{{.HelpName}} {{if .VisibleFlags}}[%s]{{end}}{{if .Commands}} %s [%s]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[%s...]{{end}}{{end}}{{if .Version}}{{if not .HideVersion}}
%s:
{{.Version}}{{end}}{{end}}{{if .Description}}
%s:
{{template "descriptionTemplate" .}}{{end}}
{{- if len .Authors}}
%s{{template "authorsTemplate" .}}{{end}}{{if .VisibleCommands}}
%s:{{template "visibleCommandCategoryTemplate" .}}{{end}}{{if .VisibleFlagCategories}}
%s:{{template "visibleFlagCategoryTemplate" .}}{{else if .VisibleFlags}}
%s:{{template "visibleFlagTemplate" .}}{{end}}{{if .Copyright}}
%s:
{{template "copyrightTemplate" .}}{{end}}
`, gotext.Get("NAME"), gotext.Get("USAGE"), gotext.Get("global options"), gotext.Get("command"), gotext.Get("command options"), gotext.Get("arguments"), gotext.Get("VERSION"), gotext.Get("DESCRIPTION"), gotext.Get("AUTHOR"), gotext.Get("COMMANDS"), gotext.Get("GLOBAL OPTIONS"), gotext.Get("GLOBAL OPTIONS"), gotext.Get("COPYRIGHT"))
}
func GetCommandHelpTemplate() string {
return fmt.Sprintf(`%s:
{{template "helpNameTemplate" .}}
%s:
{{if .UsageText}}{{wrap .UsageText 3}}{{else}}{{.HelpName}}{{if .VisibleFlags}} [%s]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[%s...]{{end}}{{end}}{{if .Category}}
%s:
{{.Category}}{{end}}{{if .Description}}
%s:
{{template "descriptionTemplate" .}}{{end}}{{if .VisibleFlagCategories}}
%s:{{template "visibleFlagCategoryTemplate" .}}{{else if .VisibleFlags}}
%s:{{template "visibleFlagTemplate" .}}{{end}}
`, gotext.Get("NAME"),
gotext.Get("USAGE"),
gotext.Get("command options"),
gotext.Get("arguments"),
gotext.Get("CATEGORY"),
gotext.Get("DESCRIPTION"),
gotext.Get("OPTIONS"),
gotext.Get("OPTIONS"),
)
}

View File

@ -1,79 +1,199 @@
/* // This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
* ALR - Any Linux Repository // It has been modified as part of "ALR - Any Linux Repository" by Евгений Храмов.
* Copyright (C) 2024 Евгений Храмов //
* // ALR - Any Linux Repository
* This program is free software: you can redistribute it and/or modify // Copyright (C) 2025 Евгений Храмов
* it under the terms of the GNU General Public License as published by //
* the Free Software Foundation, either version 3 of the License, or // This program is free software: you can redistribute it and/or modify
* (at your option) any later version. // it under the terms of the GNU General Public License as published by
* // the Free Software Foundation, either version 3 of the License, or
* This program is distributed in the hope that it will be useful, // (at your option) any later version.
* but WITHOUT ANY WARRANTY; without even the implied warranty of //
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // This program is distributed in the hope that it will be useful,
* GNU General Public License for more details. // but WITHOUT ANY WARRANTY; without even the implied warranty of
* // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* You should have received a copy of the GNU General Public License // GNU General Public License for more details.
* along with this program. If not, see <http://www.gnu.org/licenses/>. //
*/ // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package config package config
import ( import (
"context" "context"
"log/slog"
"os" "os"
"path/filepath"
"sync" "sync"
"github.com/pelletier/go-toml/v2" "github.com/pelletier/go-toml/v2"
"plemya-x.ru/alr/internal/types"
"plemya-x.ru/alr/pkg/loggerctx" "github.com/leonelquinteros/gotext"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/types"
) )
type ALRConfig struct {
cfg *types.Config
paths *Paths
cfgOnce sync.Once
pathsOnce sync.Once
}
var defaultConfig = &types.Config{ var defaultConfig = &types.Config{
RootCmd: "sudo", RootCmd: "sudo",
PagerStyle: "native", PagerStyle: "native",
IgnorePkgUpdates: []string{}, IgnorePkgUpdates: []string{},
Repos: []types.Repo{ AutoPull: false,
{ Repos: []types.Repo{},
Name: "default",
URL: "https://gitea.plemya-x.ru/xpamych/xpamych-alr-repo.git",
},
},
} }
var ( func New() *ALRConfig {
configMtx sync.Mutex return &ALRConfig{}
config *types.Config }
)
// Config returns a ALR configuration struct. func (c *ALRConfig) Load(ctx context.Context) {
// The first time it's called, it'll load the config from a file. cfgFl, err := os.Open(c.GetPaths(ctx).ConfigPath)
// Subsequent calls will just return the same value.
func Config(ctx context.Context) *types.Config {
configMtx.Lock()
defer configMtx.Unlock()
log := loggerctx.From(ctx)
if config == nil {
cfgFl, err := os.Open(GetPaths(ctx).ConfigPath)
if err != nil { if err != nil {
log.Warn("Error opening config file, using defaults").Err(err).Send() slog.Warn(gotext.Get("Error opening config file, using defaults"), "err", err)
return defaultConfig c.cfg = defaultConfig
return
} }
defer cfgFl.Close() defer cfgFl.Close()
// Copy the default configuration into config // Copy the default configuration into config
defCopy := *defaultConfig defCopy := *defaultConfig
config = &defCopy config := &defCopy
config.Repos = nil config.Repos = nil
err = toml.NewDecoder(cfgFl).Decode(config) err = toml.NewDecoder(cfgFl).Decode(config)
if err != nil { if err != nil {
log.Warn("Error decoding config file, using defaults").Err(err).Send() slog.Warn(gotext.Get("Error decoding config file, using defaults"), "err", err)
// Set config back to nil so that we try again next time c.cfg = defaultConfig
config = nil return
return defaultConfig
} }
c.cfg = config
}
func (c *ALRConfig) initPaths() {
paths := &Paths{}
cfgDir, err := os.UserConfigDir()
if err != nil {
slog.Error(gotext.Get("Unable to detect user config directory"), "err", err)
os.Exit(1)
} }
return config paths.ConfigDir = filepath.Join(cfgDir, "alr")
err = os.MkdirAll(paths.ConfigDir, 0o755)
if err != nil {
slog.Error(gotext.Get("Unable to create ALR config directory"), "err", err)
os.Exit(1)
}
paths.ConfigPath = filepath.Join(paths.ConfigDir, "alr.toml")
if _, err := os.Stat(paths.ConfigPath); err != nil {
cfgFl, err := os.Create(paths.ConfigPath)
if err != nil {
slog.Error(gotext.Get("Unable to create ALR config file"), "err", err)
os.Exit(1)
}
err = toml.NewEncoder(cfgFl).Encode(&defaultConfig)
if err != nil {
slog.Error(gotext.Get("Error encoding default configuration"), "err", err)
os.Exit(1)
}
cfgFl.Close()
}
cacheDir, err := os.UserCacheDir()
if err != nil {
slog.Error(gotext.Get("Unable to detect cache directory"), "err", err)
os.Exit(1)
}
paths.CacheDir = filepath.Join(cacheDir, "alr")
paths.RepoDir = filepath.Join(paths.CacheDir, "repo")
paths.PkgsDir = filepath.Join(paths.CacheDir, "pkgs")
err = os.MkdirAll(paths.RepoDir, 0o755)
if err != nil {
slog.Error(gotext.Get("Unable to create repo cache directory"), "err", err)
os.Exit(1)
}
err = os.MkdirAll(paths.PkgsDir, 0o755)
if err != nil {
slog.Error(gotext.Get("Unable to create package cache directory"), "err", err)
os.Exit(1)
}
paths.DBPath = filepath.Join(paths.CacheDir, "db")
c.paths = paths
}
func (c *ALRConfig) GetPaths(ctx context.Context) *Paths {
c.pathsOnce.Do(func() {
c.initPaths()
})
return c.paths
}
func (c *ALRConfig) Repos(ctx context.Context) []types.Repo {
c.cfgOnce.Do(func() {
c.Load(ctx)
})
return c.cfg.Repos
}
func (c *ALRConfig) SetRepos(ctx context.Context, repos []types.Repo) {
c.cfgOnce.Do(func() {
c.Load(ctx)
})
c.cfg.Repos = repos
}
func (c *ALRConfig) IgnorePkgUpdates(ctx context.Context) []string {
c.cfgOnce.Do(func() {
c.Load(ctx)
})
return c.cfg.IgnorePkgUpdates
}
func (c *ALRConfig) AutoPull(ctx context.Context) bool {
c.cfgOnce.Do(func() {
c.Load(ctx)
})
return c.cfg.AutoPull
}
func (c *ALRConfig) PagerStyle(ctx context.Context) string {
c.cfgOnce.Do(func() {
c.Load(ctx)
})
return c.cfg.PagerStyle
}
func (c *ALRConfig) AllowRunAsRoot(ctx context.Context) bool {
c.cfgOnce.Do(func() {
c.Load(ctx)
})
return c.cfg.Unsafe.AllowRunAsRoot
}
func (c *ALRConfig) RootCmd(ctx context.Context) string {
c.cfgOnce.Do(func() {
c.Load(ctx)
})
return c.cfg.RootCmd
}
func (c *ALRConfig) Save(f *os.File) error {
return toml.NewEncoder(f).Encode(c.cfg)
} }

View File

@ -1,67 +0,0 @@
/*
* ALR - Any Linux Repository
* Copyright (C) 2024 Евгений Храмов
*
* 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/>.
*/
package config
import (
"context"
"os"
"strings"
"sync"
"plemya-x.ru/alr/pkg/loggerctx"
"golang.org/x/text/language"
)
var (
langMtx sync.Mutex
lang language.Tag
langSet bool
)
// Language returns the system language.
// The first time it's called, it'll detect the langauge based on
// the $LANG environment variable.
// Subsequent calls will just return the same value.
func Language(ctx context.Context) language.Tag {
langMtx.Lock()
defer langMtx.Unlock()
log := loggerctx.From(ctx)
if !langSet {
syslang := SystemLang()
tag, err := language.Parse(syslang)
if err != nil {
log.Fatal("Error parsing system language").Err(err).Send()
}
base, _ := tag.Base()
lang = language.Make(base.String())
langSet = true
}
return lang
}
// SystemLang returns the system language based on
// the $LANG environment variable.
func SystemLang() string {
lang := os.Getenv("LANG")
lang, _, _ = strings.Cut(lang, ".")
if lang == "" || lang == "C" {
lang = "en"
}
return lang
}

View File

@ -1,33 +1,24 @@
/* // This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
* ALR - Any Linux Repository // It has been modified as part of "ALR - Any Linux Repository" by Евгений Храмов.
* Copyright (C) 2024 Евгений Храмов //
* // ALR - Any Linux Repository
* This program is free software: you can redistribute it and/or modify // Copyright (C) 2025 Евгений Храмов
* it under the terms of the GNU General Public License as published by //
* the Free Software Foundation, either version 3 of the License, or // This program is free software: you can redistribute it and/or modify
* (at your option) any later version. // it under the terms of the GNU General Public License as published by
* // the Free Software Foundation, either version 3 of the License, or
* This program is distributed in the hope that it will be useful, // (at your option) any later version.
* but WITHOUT ANY WARRANTY; without even the implied warranty of //
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // This program is distributed in the hope that it will be useful,
* GNU General Public License for more details. // but WITHOUT ANY WARRANTY; without even the implied warranty of
* // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* You should have received a copy of the GNU General Public License // GNU General Public License for more details.
* along with this program. If not, see <http://www.gnu.org/licenses/>. //
*/ // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package config package config
import (
"context"
"os"
"path/filepath"
"sync"
"github.com/pelletier/go-toml/v2"
"plemya-x.ru/alr/pkg/loggerctx"
)
// Paths contains various paths used by ALR // Paths contains various paths used by ALR
type Paths struct { type Paths struct {
ConfigDir string ConfigDir string
@ -37,72 +28,3 @@ type Paths struct {
PkgsDir string PkgsDir string
DBPath string DBPath string
} }
var (
pathsMtx sync.Mutex
paths *Paths
)
// GetPaths returns a Paths struct.
// The first time it's called, it'll generate the struct
// using information from the system.
// Subsequent calls will return the same value.
func GetPaths(ctx context.Context) *Paths {
pathsMtx.Lock()
defer pathsMtx.Unlock()
log := loggerctx.From(ctx)
if paths == nil {
paths = &Paths{}
cfgDir, err := os.UserConfigDir()
if err != nil {
log.Fatal("Unable to detect user config directory").Err(err).Send()
}
paths.ConfigDir = filepath.Join(cfgDir, "alr")
err = os.MkdirAll(paths.ConfigDir, 0o755)
if err != nil {
log.Fatal("Unable to create ALR config directory").Err(err).Send()
}
paths.ConfigPath = filepath.Join(paths.ConfigDir, "alr.toml")
if _, err := os.Stat(paths.ConfigPath); err != nil {
cfgFl, err := os.Create(paths.ConfigPath)
if err != nil {
log.Fatal("Unable to create ALR config file").Err(err).Send()
}
err = toml.NewEncoder(cfgFl).Encode(&defaultConfig)
if err != nil {
log.Fatal("Error encoding default configuration").Err(err).Send()
}
cfgFl.Close()
}
cacheDir, err := os.UserCacheDir()
if err != nil {
log.Fatal("Unable to detect cache directory").Err(err).Send()
}
paths.CacheDir = filepath.Join(cacheDir, "alr")
paths.RepoDir = filepath.Join(paths.CacheDir, "repo")
paths.PkgsDir = filepath.Join(paths.CacheDir, "pkgs")
err = os.MkdirAll(paths.RepoDir, 0o755)
if err != nil {
log.Fatal("Unable to create repo cache directory").Err(err).Send()
}
err = os.MkdirAll(paths.PkgsDir, 0o755)
if err != nil {
log.Fatal("Unable to create package cache directory").Err(err).Send()
}
paths.DBPath = filepath.Join(paths.CacheDir, "db")
}
return paths
}

View File

@ -1,3 +1,22 @@
// This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
// It has been modified as part of "ALR - Any Linux Repository" by Евгений Храмов.
//
// ALR - Any Linux Repository
// Copyright (C) 2025 Евгений Храмов
//
// 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/>.
package config package config
// Version contains the version of ALR. If the version // Version contains the version of ALR. If the version

View File

@ -1,20 +1,21 @@
/* // This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
* ALR - Any Linux Repository // It has been modified as part of "ALR - Any Linux Repository" by Евгений Храмов.
* Copyright (C) 2024 Евгений Храмов //
* // ALR - Any Linux Repository
* This program is free software: you can redistribute it and/or modify // Copyright (C) 2025 Евгений Храмов
* it under the terms of the GNU General Public License as published by //
* the Free Software Foundation, either version 3 of the License, or // This program is free software: you can redistribute it and/or modify
* (at your option) any later version. // it under the terms of the GNU General Public License as published by
* // the Free Software Foundation, either version 3 of the License, or
* This program is distributed in the hope that it will be useful, // (at your option) any later version.
* but WITHOUT ANY WARRANTY; without even the implied warranty of //
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // This program is distributed in the hope that it will be useful,
* GNU General Public License for more details. // but WITHOUT ANY WARRANTY; without even the implied warranty of
* // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* You should have received a copy of the GNU General Public License // GNU General Public License for more details.
* along with this program. If not, see <http://www.gnu.org/licenses/>. //
*/ // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package cpu package cpu
@ -37,11 +38,12 @@ func armVariant() string {
return armEnv return armEnv
} }
if cpu.ARM.HasVFPv3 { switch {
case cpu.ARM.HasVFPv3:
return "arm7" return "arm7"
} else if cpu.ARM.HasVFP { case cpu.ARM.HasVFP:
return "arm6" return "arm6"
} else { default:
return "arm5" return "arm5"
} }
} }

View File

@ -1,49 +1,41 @@
/* // This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
* ALR - Any Linux Repository // It has been modified as part of "ALR - Any Linux Repository" by Евгений Храмов.
* Copyright (C) 2024 Евгений Храмов //
* // ALR - Any Linux Repository
* This program is free software: you can redistribute it and/or modify // Copyright (C) 2025 Евгений Храмов
* it under the terms of the GNU General Public License as published by //
* the Free Software Foundation, either version 3 of the License, or // This program is free software: you can redistribute it and/or modify
* (at your option) any later version. // it under the terms of the GNU General Public License as published by
* // the Free Software Foundation, either version 3 of the License, or
* This program is distributed in the hope that it will be useful, // (at your option) any later version.
* but WITHOUT ANY WARRANTY; without even the implied warranty of //
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // This program is distributed in the hope that it will be useful,
* GNU General Public License for more details. // but WITHOUT ANY WARRANTY; without even the implied warranty of
* // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* You should have received a copy of the GNU General Public License // GNU General Public License for more details.
* along with this program. If not, see <http://www.gnu.org/licenses/>. //
*/ // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package db package db
import ( import (
"context" "context"
"database/sql" "log/slog"
"database/sql/driver"
"encoding/json"
"errors"
"fmt"
"sync"
"github.com/jmoiron/sqlx" "github.com/jmoiron/sqlx"
"plemya-x.ru/alr/internal/config" "github.com/leonelquinteros/gotext"
"plemya-x.ru/alr/pkg/loggerctx"
"golang.org/x/exp/slices" "gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
"modernc.org/sqlite"
) )
// CurrentVersion is the current version of the database. // CurrentVersion is the current version of the database.
// The database is reset if its version doesn't match this. // The database is reset if its version doesn't match this.
const CurrentVersion = 2 const CurrentVersion = 3
func init() {
sqlite.MustRegisterScalarFunction("json_array_contains", 2, jsonArrayContains)
}
// Package is a ALR package's database representation // Package is a ALR package's database representation
type Package struct { type Package struct {
BasePkgName string `sh:"base" db:"basepkg_name"`
Name string `sh:"name,required" db:"name"` Name string `sh:"name,required" db:"name"`
Version string `sh:"version,required" db:"version"` Version string `sh:"version,required" db:"version"`
Release int `sh:"release,required" db:"release"` Release int `sh:"release,required" db:"release"`
@ -66,68 +58,49 @@ type version struct {
Version int `db:"version"` Version int `db:"version"`
} }
var ( type Config interface {
mu sync.Mutex GetPaths(ctx context.Context) *config.Paths
}
type Database struct {
conn *sqlx.DB conn *sqlx.DB
closed = true config Config
) }
// DB returns the ALR database. func New(config Config) *Database {
// The first time it's called, it opens the SQLite database file. return &Database{
// Subsequent calls return the same connection. config: config,
func DB(ctx context.Context) *sqlx.DB {
log := loggerctx.From(ctx)
if conn != nil && !closed {
return getConn()
} }
_, err := open(ctx, config.GetPaths(ctx).DBPath) }
func (d *Database) Init(ctx context.Context) error {
err := d.Connect(ctx)
if err != nil { if err != nil {
log.Fatal("Error opening database").Err(err).Send() return err
} }
return getConn() return d.initDB(ctx)
} }
func getConn() *sqlx.DB { func (d *Database) Connect(ctx context.Context) error {
mu.Lock() dsn := d.config.GetPaths(ctx).DBPath
defer mu.Unlock()
return conn
}
func open(ctx context.Context, dsn string) (*sqlx.DB, error) {
db, err := sqlx.Open("sqlite", dsn) db, err := sqlx.Open("sqlite", dsn)
if err != nil { if err != nil {
return nil, err return err
} }
d.conn = db
mu.Lock()
conn = db
closed = false
mu.Unlock()
err = initDB(ctx, dsn)
if err != nil {
return nil, err
}
return db, nil
}
// Close closes the database
func Close() error {
closed = true
if conn != nil {
return conn.Close()
} else {
return nil return nil
}
} }
// initDB initializes the database func (d *Database) GetConn() *sqlx.DB {
func initDB(ctx context.Context, dsn string) error { return d.conn
log := loggerctx.From(ctx) }
conn = conn.Unsafe()
func (d *Database) initDB(ctx context.Context) error {
d.conn = d.conn.Unsafe()
conn := d.conn
_, err := conn.ExecContext(ctx, ` _, err := conn.ExecContext(ctx, `
CREATE TABLE IF NOT EXISTS pkgs ( CREATE TABLE IF NOT EXISTS pkgs (
basepkg_name TEXT NOT NULL,
name TEXT NOT NULL, name TEXT NOT NULL,
repository TEXT NOT NULL, repository TEXT NOT NULL,
version TEXT NOT NULL, version TEXT NOT NULL,
@ -155,59 +128,77 @@ func initDB(ctx context.Context, dsn string) error {
return err return err
} }
ver, ok := GetVersion(ctx) ver, ok := d.GetVersion(ctx)
if ok && ver != CurrentVersion { if ok && ver != CurrentVersion {
log.Warn("Database version mismatch; resetting").Int("version", ver).Int("expected", CurrentVersion).Send() slog.Warn(gotext.Get("Database version mismatch; resetting"), "version", ver, "expected", CurrentVersion)
reset(ctx) err = d.reset(ctx)
return initDB(ctx, dsn) if err != nil {
return err
}
return d.initDB(ctx)
} else if !ok { } else if !ok {
log.Warn("Database version does not exist. Run alr fix if something isn't working.").Send() slog.Warn(gotext.Get("Database version does not exist. Run alr fix if something isn't working."), "version", ver, "expected", CurrentVersion)
return addVersion(ctx, CurrentVersion) return d.addVersion(ctx, CurrentVersion)
} }
return nil return nil
} }
// reset drops all the database tables func (d *Database) GetVersion(ctx context.Context) (int, bool) {
func reset(ctx context.Context) error {
_, err := DB(ctx).ExecContext(ctx, "DROP TABLE IF EXISTS pkgs;")
if err != nil {
return err
}
_, err = DB(ctx).ExecContext(ctx, "DROP TABLE IF EXISTS alr_db_version;")
return err
}
// IsEmpty returns true if the database has no packages in it, otherwise it returns false.
func IsEmpty(ctx context.Context) bool {
var count int
err := DB(ctx).GetContext(ctx, &count, "SELECT count(1) FROM pkgs;")
if err != nil {
return true
}
return count == 0
}
// GetVersion returns the database version and a boolean indicating
// whether the database contained a version number
func GetVersion(ctx context.Context) (int, bool) {
var ver version var ver version
err := DB(ctx).GetContext(ctx, &ver, "SELECT * FROM alr_db_version LIMIT 1;") err := d.conn.GetContext(ctx, &ver, "SELECT * FROM alr_db_version LIMIT 1;")
if err != nil { if err != nil {
return 0, false return 0, false
} }
return ver.Version, true return ver.Version, true
} }
func addVersion(ctx context.Context, ver int) error { func (d *Database) addVersion(ctx context.Context, ver int) error {
_, err := DB(ctx).ExecContext(ctx, `INSERT INTO alr_db_version(version) VALUES (?);`, ver) _, err := d.conn.ExecContext(ctx, `INSERT INTO alr_db_version(version) VALUES (?);`, ver)
return err return err
} }
// InsertPackage adds a package to the database func (d *Database) reset(ctx context.Context) error {
func InsertPackage(ctx context.Context, pkg Package) error { _, err := d.conn.ExecContext(ctx, "DROP TABLE IF EXISTS pkgs;")
_, err := DB(ctx).NamedExecContext(ctx, ` if err != nil {
return err
}
_, err = d.conn.ExecContext(ctx, "DROP TABLE IF EXISTS alr_db_version;")
return err
}
func (d *Database) GetPkgs(ctx context.Context, where string, args ...any) (*sqlx.Rows, error) {
stream, err := d.conn.QueryxContext(ctx, "SELECT * FROM pkgs WHERE "+where, args...)
if err != nil {
return nil, err
}
return stream, nil
}
func (d *Database) GetPkg(ctx context.Context, where string, args ...any) (*Package, error) {
out := &Package{}
err := d.conn.GetContext(ctx, out, "SELECT * FROM pkgs WHERE "+where+" LIMIT 1", args...)
return out, err
}
func (d *Database) DeletePkgs(ctx context.Context, where string, args ...any) error {
_, err := d.conn.ExecContext(ctx, "DELETE FROM pkgs WHERE "+where, args...)
return err
}
func (d *Database) IsEmpty(ctx context.Context) bool {
var count int
err := d.conn.GetContext(ctx, &count, "SELECT count(1) FROM pkgs;")
if err != nil {
return true
}
return count == 0
}
func (d *Database) InsertPackage(ctx context.Context, pkg Package) error {
_, err := d.conn.NamedExecContext(ctx, `
INSERT OR REPLACE INTO pkgs ( INSERT OR REPLACE INTO pkgs (
basepkg_name,
name, name,
repository, repository,
version, version,
@ -225,6 +216,7 @@ func InsertPackage(ctx context.Context, pkg Package) error {
builddepends, builddepends,
optdepends optdepends
) VALUES ( ) VALUES (
:basepkg_name,
:name, :name,
:repository, :repository,
:version, :version,
@ -246,101 +238,10 @@ func InsertPackage(ctx context.Context, pkg Package) error {
return err return err
} }
// GetPkgs returns a result containing packages that match the where conditions func (d *Database) Close() error {
func GetPkgs(ctx context.Context, where string, args ...any) (*sqlx.Rows, error) { if d.conn != nil {
stream, err := DB(ctx).QueryxContext(ctx, "SELECT * FROM pkgs WHERE "+where, args...) return d.conn.Close()
if err != nil { } else {
return nil, err
}
return stream, nil
}
// GetPkg returns a single package that matches the where conditions
func GetPkg(ctx context.Context, where string, args ...any) (*Package, error) {
out := &Package{}
err := DB(ctx).GetContext(ctx, out, "SELECT * FROM pkgs WHERE "+where+" LIMIT 1", args...)
return out, err
}
// DeletePkgs deletes all packages matching the where conditions
func DeletePkgs(ctx context.Context, where string, args ...any) error {
_, err := DB(ctx).ExecContext(ctx, "DELETE FROM pkgs WHERE "+where, args...)
return err
}
// jsonArrayContains is an SQLite function that checks if a JSON array
// in the database contains a given value
func jsonArrayContains(ctx *sqlite.FunctionContext, args []driver.Value) (driver.Value, error) {
value, ok := args[0].(string)
if !ok {
return nil, errors.New("both arguments to json_array_contains must be strings")
}
item, ok := args[1].(string)
if !ok {
return nil, errors.New("both arguments to json_array_contains must be strings")
}
var array []string
err := json.Unmarshal([]byte(value), &array)
if err != nil {
return nil, err
}
return slices.Contains(array, item), nil
}
// JSON represents a JSON value in the database
type JSON[T any] struct {
Val T
}
// NewJSON creates a new database JSON value
func NewJSON[T any](v T) JSON[T] {
return JSON[T]{Val: v}
}
func (s *JSON[T]) Scan(val any) error {
if val == nil {
return nil return nil
} }
switch val := val.(type) {
case string:
err := json.Unmarshal([]byte(val), &s.Val)
if err != nil {
return err
}
case sql.NullString:
if val.Valid {
err := json.Unmarshal([]byte(val.String), &s.Val)
if err != nil {
return err
}
}
default:
return errors.New("sqlite json types must be strings")
}
return nil
}
func (s JSON[T]) Value() (driver.Value, error) {
data, err := json.Marshal(s.Val)
if err != nil {
return nil, err
}
return string(data), nil
}
func (s JSON[T]) MarshalYAML() (any, error) {
return s.Val, nil
}
func (s JSON[T]) String() string {
return fmt.Sprint(s.Val)
}
func (s JSON[T]) GoString() string {
return fmt.Sprintf("%#v", s.Val)
} }

View File

@ -1,32 +1,50 @@
/* // This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
* ALR - Any Linux Repository // It has been modified as part of "ALR - Any Linux Repository" by Евгений Храмов.
* Copyright (C) 2024 Евгений Храмов //
* // ALR - Any Linux Repository
* This program is free software: you can redistribute it and/or modify // Copyright (C) 2025 Евгений Храмов
* it under the terms of the GNU General Public License as published by //
* the Free Software Foundation, either version 3 of the License, or // This program is free software: you can redistribute it and/or modify
* (at your option) any later version. // it under the terms of the GNU General Public License as published by
* // the Free Software Foundation, either version 3 of the License, or
* This program is distributed in the hope that it will be useful, // (at your option) any later version.
* but WITHOUT ANY WARRANTY; without even the implied warranty of //
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // This program is distributed in the hope that it will be useful,
* GNU General Public License for more details. // but WITHOUT ANY WARRANTY; without even the implied warranty of
* // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* You should have received a copy of the GNU General Public License // GNU General Public License for more details.
* along with this program. If not, see <http://www.gnu.org/licenses/>. //
*/ // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package db_test package db_test
import ( import (
"context"
"reflect" "reflect"
"strings" "strings"
"testing" "testing"
"github.com/jmoiron/sqlx" "github.com/jmoiron/sqlx"
"plemya-x.ru/alr/internal/db"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
) )
type TestALRConfig struct{}
func (c *TestALRConfig) GetPaths(ctx context.Context) *config.Paths {
return &config.Paths{
DBPath: ":memory:",
}
}
func prepareDb() *db.Database {
database := db.New(&TestALRConfig{})
database.Init(context.Background())
return database
}
var testPkg = db.Package{ var testPkg = db.Package{
Name: "test", Name: "test",
Version: "0.0.1", Version: "0.0.1",
@ -59,18 +77,11 @@ var testPkg = db.Package{
} }
func TestInit(t *testing.T) { func TestInit(t *testing.T) {
_, err := db.Open(":memory:") ctx := context.Background()
if err != nil { database := prepareDb()
t.Fatalf("Expected no error, got %s", err) defer database.Close()
}
defer db.Close()
_, err = db.DB().Exec("SELECT * FROM pkgs") ver, ok := database.GetVersion(ctx)
if err != nil {
t.Fatalf("Expected no error, got %s", err)
}
ver, ok := db.GetVersion()
if !ok { if !ok {
t.Errorf("Expected version to be present") t.Errorf("Expected version to be present")
} else if ver != db.CurrentVersion { } else if ver != db.CurrentVersion {
@ -79,19 +90,17 @@ func TestInit(t *testing.T) {
} }
func TestInsertPackage(t *testing.T) { func TestInsertPackage(t *testing.T) {
_, err := db.Open(":memory:") ctx := context.Background()
if err != nil { database := prepareDb()
t.Fatalf("Expected no error, got %s", err) defer database.Close()
}
defer db.Close()
err = db.InsertPackage(testPkg) err := database.InsertPackage(ctx, testPkg)
if err != nil { if err != nil {
t.Fatalf("Expected no error, got %s", err) t.Fatalf("Expected no error, got %s", err)
} }
dbPkg := db.Package{} dbPkg := db.Package{}
err = sqlx.Get(db.DB(), &dbPkg, "SELECT * FROM pkgs WHERE name = 'test' AND repository = 'default'") err = sqlx.Get(database.GetConn(), &dbPkg, "SELECT * FROM pkgs WHERE name = 'test' AND repository = 'default'")
if err != nil { if err != nil {
t.Fatalf("Expected no error, got %s", err) t.Fatalf("Expected no error, got %s", err)
} }
@ -102,28 +111,26 @@ func TestInsertPackage(t *testing.T) {
} }
func TestGetPkgs(t *testing.T) { func TestGetPkgs(t *testing.T) {
_, err := db.Open(":memory:") ctx := context.Background()
if err != nil { database := prepareDb()
t.Fatalf("Expected no error, got %s", err) defer database.Close()
}
defer db.Close()
x1 := testPkg x1 := testPkg
x1.Name = "x1" x1.Name = "x1"
x2 := testPkg x2 := testPkg
x2.Name = "x2" x2.Name = "x2"
err = db.InsertPackage(x1) err := database.InsertPackage(ctx, x1)
if err != nil { if err != nil {
t.Errorf("Expected no error, got %s", err) t.Errorf("Expected no error, got %s", err)
} }
err = db.InsertPackage(x2) err = database.InsertPackage(ctx, x2)
if err != nil { if err != nil {
t.Errorf("Expected no error, got %s", err) t.Errorf("Expected no error, got %s", err)
} }
result, err := db.GetPkgs("name LIKE 'x%'") result, err := database.GetPkgs(ctx, "name LIKE 'x%'")
if err != nil { if err != nil {
t.Fatalf("Expected no error, got %s", err) t.Fatalf("Expected no error, got %s", err)
} }
@ -142,28 +149,26 @@ func TestGetPkgs(t *testing.T) {
} }
func TestGetPkg(t *testing.T) { func TestGetPkg(t *testing.T) {
_, err := db.Open(":memory:") ctx := context.Background()
if err != nil { database := prepareDb()
t.Fatalf("Expected no error, got %s", err) defer database.Close()
}
defer db.Close()
x1 := testPkg x1 := testPkg
x1.Name = "x1" x1.Name = "x1"
x2 := testPkg x2 := testPkg
x2.Name = "x2" x2.Name = "x2"
err = db.InsertPackage(x1) err := database.InsertPackage(ctx, x1)
if err != nil { if err != nil {
t.Errorf("Expected no error, got %s", err) t.Errorf("Expected no error, got %s", err)
} }
err = db.InsertPackage(x2) err = database.InsertPackage(ctx, x2)
if err != nil { if err != nil {
t.Errorf("Expected no error, got %s", err) t.Errorf("Expected no error, got %s", err)
} }
pkg, err := db.GetPkg("name LIKE 'x%' ORDER BY name") pkg, err := database.GetPkg(ctx, "name LIKE 'x%' ORDER BY name")
if err != nil { if err != nil {
t.Fatalf("Expected no error, got %s", err) t.Fatalf("Expected no error, got %s", err)
} }
@ -178,34 +183,32 @@ func TestGetPkg(t *testing.T) {
} }
func TestDeletePkgs(t *testing.T) { func TestDeletePkgs(t *testing.T) {
_, err := db.Open(":memory:") ctx := context.Background()
if err != nil { database := prepareDb()
t.Fatalf("Expected no error, got %s", err) defer database.Close()
}
defer db.Close()
x1 := testPkg x1 := testPkg
x1.Name = "x1" x1.Name = "x1"
x2 := testPkg x2 := testPkg
x2.Name = "x2" x2.Name = "x2"
err = db.InsertPackage(x1) err := database.InsertPackage(ctx, x1)
if err != nil { if err != nil {
t.Errorf("Expected no error, got %s", err) t.Errorf("Expected no error, got %s", err)
} }
err = db.InsertPackage(x2) err = database.InsertPackage(ctx, x2)
if err != nil { if err != nil {
t.Errorf("Expected no error, got %s", err) t.Errorf("Expected no error, got %s", err)
} }
err = db.DeletePkgs("name = 'x1'") err = database.DeletePkgs(ctx, "name = 'x1'")
if err != nil { if err != nil {
t.Errorf("Expected no error, got %s", err) t.Errorf("Expected no error, got %s", err)
} }
var dbPkg db.Package var dbPkg db.Package
err = db.DB().Get(&dbPkg, "SELECT * FROM pkgs WHERE name LIKE 'x%' ORDER BY name LIMIT 1;") err = database.GetConn().Get(&dbPkg, "SELECT * FROM pkgs WHERE name LIKE 'x%' ORDER BY name LIMIT 1;")
if err != nil { if err != nil {
t.Errorf("Expected no error, got %s", err) t.Errorf("Expected no error, got %s", err)
} }
@ -216,11 +219,9 @@ func TestDeletePkgs(t *testing.T) {
} }
func TestJsonArrayContains(t *testing.T) { func TestJsonArrayContains(t *testing.T) {
_, err := db.Open(":memory:") ctx := context.Background()
if err != nil { database := prepareDb()
t.Fatalf("Expected no error, got %s", err) defer database.Close()
}
defer db.Close()
x1 := testPkg x1 := testPkg
x1.Name = "x1" x1.Name = "x1"
@ -228,18 +229,18 @@ func TestJsonArrayContains(t *testing.T) {
x2.Name = "x2" x2.Name = "x2"
x2.Provides.Val = append(x2.Provides.Val, "x") x2.Provides.Val = append(x2.Provides.Val, "x")
err = db.InsertPackage(x1) err := database.InsertPackage(ctx, x1)
if err != nil { if err != nil {
t.Errorf("Expected no error, got %s", err) t.Errorf("Expected no error, got %s", err)
} }
err = db.InsertPackage(x2) err = database.InsertPackage(ctx, x2)
if err != nil { if err != nil {
t.Errorf("Expected no error, got %s", err) t.Errorf("Expected no error, got %s", err)
} }
var dbPkg db.Package var dbPkg db.Package
err = db.DB().Get(&dbPkg, "SELECT * FROM pkgs WHERE json_array_contains(provides, 'x');") err = database.GetConn().Get(&dbPkg, "SELECT * FROM pkgs WHERE json_array_contains(provides, 'x');")
if err != nil { if err != nil {
t.Fatalf("Expected no error, got %s", err) t.Fatalf("Expected no error, got %s", err)
} }

80
internal/db/json.go Normal file
View File

@ -0,0 +1,80 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 Евгений Храмов
//
// 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/>.
package db
import (
"database/sql"
"database/sql/driver"
"encoding/json"
"errors"
"fmt"
)
// JSON represents a JSON value in the database
type JSON[T any] struct {
Val T
}
// NewJSON creates a new database JSON value
func NewJSON[T any](v T) JSON[T] {
return JSON[T]{Val: v}
}
func (s *JSON[T]) Scan(val any) error {
if val == nil {
return nil
}
switch val := val.(type) {
case string:
err := json.Unmarshal([]byte(val), &s.Val)
if err != nil {
return err
}
case sql.NullString:
if val.Valid {
err := json.Unmarshal([]byte(val.String), &s.Val)
if err != nil {
return err
}
}
default:
return errors.New("sqlite json types must be strings")
}
return nil
}
func (s JSON[T]) Value() (driver.Value, error) {
data, err := json.Marshal(s.Val)
if err != nil {
return nil, err
}
return string(data), nil
}
func (s JSON[T]) MarshalYAML() (any, error) {
return s.Val, nil
}
func (s JSON[T]) String() string {
return fmt.Sprint(s.Val)
}
func (s JSON[T]) GoString() string {
return fmt.Sprintf("%#v", s.Val)
}

52
internal/db/utils.go Normal file
View File

@ -0,0 +1,52 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 Евгений Храмов
//
// 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/>.
package db
import (
"database/sql/driver"
"encoding/json"
"errors"
"golang.org/x/exp/slices"
"modernc.org/sqlite"
)
func init() {
sqlite.MustRegisterScalarFunction("json_array_contains", 2, jsonArrayContains)
}
// jsonArrayContains is an SQLite function that checks if a JSON array
// in the database contains a given value
func jsonArrayContains(ctx *sqlite.FunctionContext, args []driver.Value) (driver.Value, error) {
value, ok := args[0].(string)
if !ok {
return nil, errors.New("both arguments to json_array_contains must be strings")
}
item, ok := args[1].(string)
if !ok {
return nil, errors.New("both arguments to json_array_contains must be strings")
}
var array []string
err := json.Unmarshal([]byte(value), &array)
if err != nil {
return nil, err
}
return slices.Contains(array, item), nil
}

View File

@ -1,20 +1,21 @@
/* // This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
* ALR - Any Linux Repository // It has been modified as part of "ALR - Any Linux Repository" by Евгений Храмов.
* Copyright (C) 2024 Евгений Храмов //
* // ALR - Any Linux Repository
* This program is free software: you can redistribute it and/or modify // Copyright (C) 2025 Евгений Храмов
* it under the terms of the GNU General Public License as published by //
* the Free Software Foundation, either version 3 of the License, or // This program is free software: you can redistribute it and/or modify
* (at your option) any later version. // it under the terms of the GNU General Public License as published by
* // the Free Software Foundation, either version 3 of the License, or
* This program is distributed in the hope that it will be useful, // (at your option) any later version.
* but WITHOUT ANY WARRANTY; without even the implied warranty of //
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // This program is distributed in the hope that it will be useful,
* GNU General Public License for more details. // but WITHOUT ANY WARRANTY; without even the implied warranty of
* // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* You should have received a copy of the GNU General Public License // GNU General Public License for more details.
* along with this program. If not, see <http://www.gnu.org/licenses/>. //
*/ // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
// Пакет dl содержит абстракции для загрузки файлов и каталогов // Пакет dl содержит абстракции для загрузки файлов и каталогов
// из различных источников. // из различных источников.
@ -30,17 +31,17 @@ import (
"fmt" "fmt"
"hash" "hash"
"io" "io"
"log/slog"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/PuerkitoBio/purell" "github.com/PuerkitoBio/purell"
"github.com/leonelquinteros/gotext"
"github.com/vmihailenco/msgpack/v5" "github.com/vmihailenco/msgpack/v5"
"golang.org/x/crypto/blake2b" "golang.org/x/crypto/blake2b"
"golang.org/x/crypto/blake2s" "golang.org/x/crypto/blake2s"
"golang.org/x/exp/slices" "golang.org/x/exp/slices"
"plemya-x.ru/alr/internal/dlcache"
"plemya-x.ru/alr/pkg/loggerctx"
) )
// Константа для имени файла манифеста кэша // Константа для имени файла манифеста кэша
@ -79,6 +80,11 @@ func (t Type) String() string {
return "<unknown>" return "<unknown>"
} }
type DlCache interface {
Get(context.Context, string) (string, bool)
New(context.Context, string) (string, error)
}
// Структура Options содержит параметры для загрузки файлов и каталогов // Структура Options содержит параметры для загрузки файлов и каталогов
type Options struct { type Options struct {
Hash []byte Hash []byte
@ -90,6 +96,7 @@ type Options struct {
PostprocDisabled bool PostprocDisabled bool
Progress io.Writer Progress io.Writer
LocalDir string LocalDir string
DlCache DlCache
} }
// Метод для создания нового хеша на основе указанного алгоритма хеширования // Метод для создания нового хеша на основе указанного алгоритма хеширования
@ -130,7 +137,7 @@ type Manifest struct {
type Downloader interface { type Downloader interface {
Name() string Name() string
MatchURL(string) bool MatchURL(string) bool
Download(Options) (Type, string, error) Download(context.Context, Options) (Type, string, error)
} }
// Интерфейс UpdatingDownloader расширяет Downloader методом Update // Интерфейс UpdatingDownloader расширяет Downloader методом Update
@ -141,7 +148,6 @@ type UpdatingDownloader interface {
// Функция Download загружает файл или каталог с использованием указанных параметров // Функция Download загружает файл или каталог с использованием указанных параметров
func Download(ctx context.Context, opts Options) (err error) { func Download(ctx context.Context, opts Options) (err error) {
log := loggerctx.From(ctx)
normalized, err := normalizeURL(opts.URL) normalized, err := normalizeURL(opts.URL)
if err != nil { if err != nil {
return err return err
@ -151,16 +157,20 @@ func Download(ctx context.Context, opts Options) (err error) {
d := getDownloader(opts.URL) d := getDownloader(opts.URL)
if opts.CacheDisabled { if opts.CacheDisabled {
_, _, err = d.Download(opts) _, _, err = d.Download(ctx, opts)
return err return err
} }
var t Type var t Type
cacheDir, ok := dlcache.Get(ctx, opts.URL) cacheDir, ok := opts.DlCache.Get(ctx, opts.URL)
if ok { if ok {
var updated bool var updated bool
if d, ok := d.(UpdatingDownloader); ok { if d, ok := d.(UpdatingDownloader); ok {
log.Info("Source can be updated, updating if required").Str("source", opts.Name).Str("downloader", d.Name()).Send() slog.Info(
gotext.Get("Source can be updated, updating if required"),
"source", opts.Name,
"downloader", d.Name(),
)
updated, err = d.Update(Options{ updated, err = d.Update(Options{
Hash: opts.Hash, Hash: opts.Hash,
@ -187,10 +197,18 @@ func Download(ctx context.Context, opts Options) (err error) {
} }
if ok && !updated { if ok && !updated {
log.Info("Source found in cache and linked to destination").Str("source", opts.Name).Stringer("type", t).Send() slog.Info(
gotext.Get("Source found in cache and linked to destination"),
"source", opts.Name,
"type", t,
)
return nil return nil
} else if ok { } else if ok {
log.Info("Source updated and linked to destination").Str("source", opts.Name).Stringer("type", t).Send() slog.Info(
gotext.Get("Source updated and linked to destination"),
"source", opts.Name,
"type", t,
)
return nil return nil
} }
} else { } else {
@ -201,14 +219,14 @@ func Download(ctx context.Context, opts Options) (err error) {
} }
} }
log.Info("Downloading source").Str("source", opts.Name).Str("downloader", d.Name()).Send() slog.Info(gotext.Get("Downloading source"), "source", opts.Name, "downloader", d.Name())
cacheDir, err = dlcache.New(ctx, opts.URL) cacheDir, err = opts.DlCache.New(ctx, opts.URL)
if err != nil { if err != nil {
return err return err
} }
t, name, err := d.Download(Options{ t, name, err := d.Download(ctx, Options{
Hash: opts.Hash, Hash: opts.Hash,
HashAlgorithm: opts.HashAlgorithm, HashAlgorithm: opts.HashAlgorithm,
Name: opts.Name, Name: opts.Name,
@ -299,8 +317,6 @@ func linkDir(src, dest string) error {
return nil return nil
} }
rel, err := filepath.Rel(src, path) rel, err := filepath.Rel(src, path)
if err != nil { if err != nil {
return err return err

176
internal/dl/dl_test.go Normal file
View File

@ -0,0 +1,176 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 Евгений Храмов
//
// 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/>.
package dl_test
import (
"context"
"fmt"
"log"
"net/http"
"net/http/httptest"
"net/http/httputil"
"net/url"
"os"
"path"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/dl"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/dlcache"
)
type TestALRConfig struct{}
func (c *TestALRConfig) GetPaths(ctx context.Context) *config.Paths {
return &config.Paths{
CacheDir: "/tmp",
}
}
func TestDownloadWithoutCache(t *testing.T) {
type testCase struct {
name string
path string
expected func(*testing.T, error, string)
}
prepareServer := func() *httptest.Server {
// URL вашего Git-сервера
gitServerURL, err := url.Parse("https://gitea.plemya-x.ru")
if err != nil {
log.Fatalf("Failed to parse git server URL: %v", err)
}
proxy := httputil.NewSingleHostReverseProxy(gitServerURL)
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch {
case r.URL.Path == "/file-downloader/file":
w.WriteHeader(http.StatusOK)
w.Write([]byte("Hello, World!"))
case strings.HasPrefix(r.URL.Path, "/git-downloader/git"):
r.URL.Host = gitServerURL.Host
r.URL.Scheme = gitServerURL.Scheme
r.Host = gitServerURL.Host
r.URL.Path, _ = strings.CutPrefix(r.URL.Path, "/git-downloader/git")
proxy.ServeHTTP(w, r)
default:
w.WriteHeader(http.StatusNotFound)
}
}))
}
for _, tc := range []testCase{
{
name: "simple file download",
path: "%s/file-downloader/file",
expected: func(t *testing.T, err error, tmpdir string) {
assert.NoError(t, err)
_, err = os.Stat(path.Join(tmpdir, "file"))
assert.NoError(t, err)
},
},
{
name: "git download",
path: "git+%s/git-downloader/git/Plemya-x/alr-repo",
expected: func(t *testing.T, err error, tmpdir string) {
assert.NoError(t, err)
_, err = os.Stat(path.Join(tmpdir, "alr-repo.toml"))
assert.NoError(t, err)
},
},
} {
t.Run(tc.name, func(t *testing.T) {
server := prepareServer()
defer server.Close()
tmpdir, err := os.MkdirTemp("", "test-download")
assert.NoError(t, err)
defer os.RemoveAll(tmpdir)
opts := dl.Options{
CacheDisabled: true,
URL: fmt.Sprintf(tc.path, server.URL),
Destination: tmpdir,
}
err = dl.Download(context.Background(), opts)
tc.expected(t, err, tmpdir)
})
}
}
func TestDownloadFileWithCache(t *testing.T) {
type testCase struct {
name string
}
for _, tc := range []testCase{
{
name: "simple download",
},
} {
t.Run(tc.name, func(t *testing.T) {
called := 0
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch {
case r.URL.Path == "/file":
called += 1
w.WriteHeader(http.StatusOK)
w.Write([]byte("Hello, World!"))
default:
w.WriteHeader(http.StatusNotFound)
}
}))
defer server.Close()
tmpdir, err := os.MkdirTemp("", "test-download")
assert.NoError(t, err)
defer os.RemoveAll(tmpdir)
cfg := &TestALRConfig{}
opts := dl.Options{
CacheDisabled: false,
URL: server.URL + "/file",
Destination: tmpdir,
DlCache: dlcache.New(cfg),
}
outputFile := path.Join(tmpdir, "file")
err = dl.Download(context.Background(), opts)
assert.NoError(t, err)
_, err = os.Stat(outputFile)
assert.NoError(t, err)
err = os.Remove(outputFile)
assert.NoError(t, err)
err = dl.Download(context.Background(), opts)
assert.NoError(t, err)
assert.Equal(t, 1, called)
})
}
}

View File

@ -1,26 +1,28 @@
/* // This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
* ALR - Any Linux Repository // It has been modified as part of "ALR - Any Linux Repository" by Евгений Храмов.
* Copyright (C) 2024 Евгений Храмов //
* // ALR - Any Linux Repository
* This program is free software: you can redistribute it and/or modify // Copyright (C) 2025 Евгений Храмов
* it under the terms of the GNU General Public License as published by //
* the Free Software Foundation, either version 3 of the License, or // This program is free software: you can redistribute it and/or modify
* (at your option) any later version. // it under the terms of the GNU General Public License as published by
* // the Free Software Foundation, either version 3 of the License, or
* This program is distributed in the hope that it will be useful, // (at your option) any later version.
* but WITHOUT ANY WARRANTY; without even the implied warranty of //
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // This program is distributed in the hope that it will be useful,
* GNU General Public License for more details. // but WITHOUT ANY WARRANTY; without even the implied warranty of
* // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* You should have received a copy of the GNU General Public License // GNU General Public License for more details.
* along with this program. If not, see <http://www.gnu.org/licenses/>. //
*/ // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package dl package dl
import ( import (
"bytes" "bytes"
"context" "context"
"fmt"
"io" "io"
"mime" "mime"
"net/http" "net/http"
@ -29,11 +31,8 @@ import (
"path" "path"
"path/filepath" "path/filepath"
"strings" "strings"
"time"
"github.com/mholt/archiver/v4" "github.com/mholt/archiver/v4"
"github.com/schollz/progressbar/v3"
"plemya-x.ru/alr/internal/shutils/handlers"
) )
// FileDownloader загружает файлы с использованием HTTP // FileDownloader загружает файлы с использованием HTTP
@ -52,7 +51,7 @@ func (FileDownloader) MatchURL(string) bool {
// Download загружает файл с использованием HTTP. Если файл // Download загружает файл с использованием HTTP. Если файл
// сжат в поддерживаемом формате, он будет распакован // сжат в поддерживаемом формате, он будет распакован
func (FileDownloader) Download(opts Options) (Type, string, error) { func (FileDownloader) Download(ctx context.Context, opts Options) (Type, string, error) {
// Разбор URL // Разбор URL
u, err := url.Parse(opts.URL) u, err := url.Parse(opts.URL)
if err != nil { if err != nil {
@ -92,8 +91,12 @@ func (FileDownloader) Download(opts Options) (Type, string, error) {
} }
r = localFl r = localFl
} else { } else {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil)
if err != nil {
return 0, "", fmt.Errorf("failed to create request: %w", err)
}
// Выполнение HTTP GET запроса // Выполнение HTTP GET запроса
res, err := http.Get(u.String()) res, err := http.DefaultClient.Do(req)
if err != nil { if err != nil {
return 0, "", err return 0, "", err
} }
@ -112,30 +115,15 @@ func (FileDownloader) Download(opts Options) (Type, string, error) {
if err != nil { if err != nil {
return 0, "", err return 0, "", err
} }
defer fl.Close()
var bar io.WriteCloser var out io.WriteCloser
// Настройка индикатора прогресса // Настройка индикатора прогресса
if opts.Progress != nil { if opts.Progress != nil {
bar = progressbar.NewOptions64( out = NewProgressWriter(fl, size, name, opts.Progress)
size,
progressbar.OptionSetDescription(name),
progressbar.OptionSetWriter(opts.Progress),
progressbar.OptionShowBytes(true),
progressbar.OptionSetWidth(10),
progressbar.OptionThrottle(65*time.Millisecond),
progressbar.OptionShowCount(),
progressbar.OptionOnCompletion(func() {
_, _ = io.WriteString(opts.Progress, "\n")
}),
progressbar.OptionSpinnerType(14),
progressbar.OptionFullWidth(),
progressbar.OptionSetRenderBlankState(true),
)
defer bar.Close()
} else { } else {
bar = handlers.NopRWC{} out = fl
} }
defer out.Close()
h, err := opts.NewHash() h, err := opts.NewHash()
if err != nil { if err != nil {
@ -145,9 +133,9 @@ func (FileDownloader) Download(opts Options) (Type, string, error) {
var w io.Writer var w io.Writer
// Настройка MultiWriter для записи в файл, хеш и индикатор прогресса // Настройка MultiWriter для записи в файл, хеш и индикатор прогресса
if opts.Hash != nil { if opts.Hash != nil {
w = io.MultiWriter(fl, h, bar) w = io.MultiWriter(h, out)
} else { } else {
w = io.MultiWriter(fl, bar) w = io.MultiWriter(out)
} }
// Копирование содержимого из источника в файл назначения // Копирование содержимого из источника в файл назначения

View File

@ -1,24 +1,26 @@
/* // This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
* ALR - Any Linux Repository // It has been modified as part of "ALR - Any Linux Repository" by Евгений Храмов.
* Copyright (C) 2024 Евгений Храмов //
* // ALR - Any Linux Repository
* This program is free software: you can redistribute it and/or modify // Copyright (C) 2025 Евгений Храмов
* it under the terms of the GNU General Public License as published by //
* the Free Software Foundation, either version 3 of the License, or // This program is free software: you can redistribute it and/or modify
* (at your option) any later version. // it under the terms of the GNU General Public License as published by
* // the Free Software Foundation, either version 3 of the License, or
* This program is distributed in the hope that it will be useful, // (at your option) any later version.
* but WITHOUT ANY WARRANTY; without even the implied warranty of //
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // This program is distributed in the hope that it will be useful,
* GNU General Public License for more details. // but WITHOUT ANY WARRANTY; without even the implied warranty of
* // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* You should have received a copy of the GNU General Public License // GNU General Public License for more details.
* along with this program. If not, see <http://www.gnu.org/licenses/>. //
*/ // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package dl package dl
import ( import (
"context"
"errors" "errors"
"net/url" "net/url"
"path" "path"
@ -46,7 +48,7 @@ func (GitDownloader) MatchURL(u string) bool {
// Download uses git to clone the repository from the specified URL. // Download uses git to clone the repository from the specified URL.
// It allows specifying the revision, depth and recursion options // It allows specifying the revision, depth and recursion options
// via query string // via query string
func (GitDownloader) Download(opts Options) (Type, string, error) { func (GitDownloader) Download(ctx context.Context, opts Options) (Type, string, error) {
u, err := url.Parse(opts.URL) u, err := url.Parse(opts.URL)
if err != nil { if err != nil {
return 0, "", err return 0, "", err
@ -88,7 +90,7 @@ func (GitDownloader) Download(opts Options) (Type, string, error) {
co.RecurseSubmodules = git.DefaultSubmoduleRecursionDepth co.RecurseSubmodules = git.DefaultSubmoduleRecursionDepth
} }
r, err := git.PlainClone(opts.Destination, false, co) r, err := git.PlainCloneContext(ctx, opts.Destination, false, co)
if err != nil { if err != nil {
return 0, "", err return 0, "", err
} }

247
internal/dl/progress_tui.go Normal file
View File

@ -0,0 +1,247 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 Евгений Храмов
//
// 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/>.
package dl
import (
"fmt"
"io"
"math"
"os"
"time"
"github.com/charmbracelet/bubbles/progress"
"github.com/charmbracelet/bubbles/spinner"
tea "github.com/charmbracelet/bubbletea"
"github.com/leonelquinteros/gotext"
)
type model struct {
progress progress.Model
spinner spinner.Model
percent float64
speed float64
done bool
useSpinner bool
filename string
total int64
downloaded int64
elapsed time.Duration
remaining time.Duration
width int
}
func (m model) Init() tea.Cmd {
if m.useSpinner {
return m.spinner.Tick
}
return nil
}
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
if m.done {
return m, tea.Quit
}
switch msg := msg.(type) {
case progressUpdate:
m.percent = msg.percent
m.speed = msg.speed
m.downloaded = msg.downloaded
m.total = msg.total
m.elapsed = time.Duration(msg.elapsed) * time.Second
m.remaining = time.Duration(msg.remaining) * time.Second
if m.percent >= 1.0 {
m.done = true
return m, tea.Quit
}
return m, nil
case tea.WindowSizeMsg:
m.width = msg.Width
return m, nil
case progress.FrameMsg:
if !m.useSpinner {
progressModel, cmd := m.progress.Update(msg)
m.progress = progressModel.(progress.Model)
return m, cmd
}
case spinner.TickMsg:
if m.useSpinner {
spinnerModel, cmd := m.spinner.Update(msg)
m.spinner = spinnerModel
return m, cmd
}
case tea.KeyMsg:
if msg.String() == "q" {
return m, tea.Quit
}
}
return m, nil
}
func (m model) View() string {
if m.done {
return gotext.Get("%s: done!\n", m.filename)
}
if m.useSpinner {
return gotext.Get(
"%s %s downloading at %s/s\n",
m.filename,
m.spinner.View(),
prettyByteSize(int64(m.speed)),
)
}
leftPart := m.filename
rightPart := fmt.Sprintf("%.2f%% (%s/%s, %s/s) [%v:%v]\n", m.percent*100,
prettyByteSize(m.downloaded),
prettyByteSize(m.total),
prettyByteSize(int64(m.speed)),
m.elapsed,
m.remaining,
)
m.progress.Width = m.width - len(leftPart) - len(rightPart) - 6
bar := m.progress.ViewAs(m.percent)
return fmt.Sprintf(
"%s %s %s",
leftPart,
bar,
rightPart,
)
}
func prettyByteSize(b int64) string {
bf := float64(b)
for _, unit := range []string{"", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi"} {
if math.Abs(bf) < 1024.0 {
return fmt.Sprintf("%3.1f%sB", bf, unit)
}
bf /= 1024.0
}
return fmt.Sprintf("%.1fYiB", bf)
}
type progressUpdate struct {
percent float64
speed float64
total int64
downloaded int64
elapsed float64
remaining float64
}
type ProgressWriter struct {
baseWriter io.WriteCloser
total int64
downloaded int64
startTime time.Time
onProgress func(progressUpdate)
lastReported time.Time
doneChan chan struct{}
}
func (pw *ProgressWriter) Write(p []byte) (int, error) {
n, err := pw.baseWriter.Write(p)
if err != nil {
return n, err
}
pw.downloaded += int64(n)
now := time.Now()
elapsed := now.Sub(pw.startTime).Seconds()
speed := float64(pw.downloaded) / elapsed
var remaining, percent float64
if pw.total > 0 {
remaining = (float64(pw.total) - float64(pw.downloaded)) / speed
percent = float64(pw.downloaded) / float64(pw.total)
}
if now.Sub(pw.lastReported) > 100*time.Millisecond {
pw.onProgress(progressUpdate{
percent: percent,
speed: speed,
total: pw.total,
downloaded: pw.downloaded,
elapsed: elapsed,
remaining: remaining,
})
pw.lastReported = now
}
return n, nil
}
func (pw *ProgressWriter) Close() error {
pw.onProgress(progressUpdate{
percent: 1,
speed: 0,
downloaded: pw.downloaded,
})
<-pw.doneChan
return nil
}
func NewProgressWriter(base io.WriteCloser, max int64, filename string, out io.Writer) *ProgressWriter {
var m *model
if max == -1 {
m = &model{
spinner: spinner.New(),
useSpinner: true,
filename: filename,
}
m.spinner.Spinner = spinner.Dot
} else {
m = &model{
progress: progress.New(
progress.WithDefaultGradient(),
progress.WithoutPercentage(),
),
useSpinner: false,
filename: filename,
}
}
p := tea.NewProgram(m,
tea.WithInput(nil),
tea.WithOutput(out),
)
pw := &ProgressWriter{
baseWriter: base,
total: max,
startTime: time.Now(),
doneChan: make(chan struct{}),
onProgress: func(update progressUpdate) {
p.Send(update)
},
}
go func() {
defer close(pw.doneChan)
if _, err := p.Run(); err != nil {
fmt.Fprintf(os.Stderr, "Error running progress writer: %v\n", err)
os.Exit(1)
}
}()
return pw
}

View File

@ -1,6 +1,26 @@
// This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
// It has been modified as part of "ALR - Any Linux Repository" by Евгений Храмов.
//
// ALR - Any Linux Repository
// Copyright (C) 2025 Евгений Храмов
//
// 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/>.
package dl package dl
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
"os" "os"
@ -30,7 +50,7 @@ func (TorrentDownloader) MatchURL(u string) bool {
} }
// Download downloads a file over the BitTorrent protocol. // Download downloads a file over the BitTorrent protocol.
func (TorrentDownloader) Download(opts Options) (Type, string, error) { func (TorrentDownloader) Download(ctx context.Context, opts Options) (Type, string, error) {
aria2Path, err := exec.LookPath("aria2c") aria2Path, err := exec.LookPath("aria2c")
if err != nil { if err != nil {
return 0, "", ErrAria2NotFound return 0, "", ErrAria2NotFound
@ -38,7 +58,7 @@ func (TorrentDownloader) Download(opts Options) (Type, string, error) {
opts.URL = strings.TrimPrefix(opts.URL, "torrent+") opts.URL = strings.TrimPrefix(opts.URL, "torrent+")
cmd := exec.Command(aria2Path, "--summary-interval=0", "--log-level=warn", "--seed-time=0", "--dir="+opts.Destination, opts.URL) cmd := exec.CommandContext(ctx, aria2Path, "--summary-interval=0", "--log-level=warn", "--seed-time=0", "--dir="+opts.Destination, opts.URL)
cmd.Stdout = os.Stdout cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr
err = cmd.Run() err = cmd.Run()

View File

@ -1,48 +1,61 @@
/* // This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
* ALR - Any Linux Repository // It has been modified as part of "ALR - Any Linux Repository" by Евгений Храмов.
* Copyright (C) 2024 Евгений Храмов //
* // ALR - Any Linux Repository
* This program is free software: you can redistribute it and/or modify // Copyright (C) 2025 Евгений Храмов
* it under the terms of the GNU General Public License as published by //
* the Free Software Foundation, either version 3 of the License, or // This program is free software: you can redistribute it and/or modify
* (at your option) any later version. // it under the terms of the GNU General Public License as published by
* // the Free Software Foundation, either version 3 of the License, or
* This program is distributed in the hope that it will be useful, // (at your option) any later version.
* but WITHOUT ANY WARRANTY; without even the implied warranty of //
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // This program is distributed in the hope that it will be useful,
* GNU General Public License for more details. // but WITHOUT ANY WARRANTY; without even the implied warranty of
* // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* You should have received a copy of the GNU General Public License // GNU General Public License for more details.
* along with this program. If not, see <http://www.gnu.org/licenses/>. //
*/ // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package dlcache package dlcache
import ( import (
"context" "context"
"crypto/sha1"
"encoding/hex"
"io"
"os" "os"
"path/filepath" "path/filepath"
"plemya-x.ru/alr/internal/config" "gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
) )
// BasePath returns the base path of the download cache type Config interface {
func BasePath(ctx context.Context) string { GetPaths(ctx context.Context) *config.Paths
return filepath.Join(config.GetPaths(ctx).CacheDir, "dl") }
type DownloadCache struct {
cfg Config
}
func New(cfg Config) *DownloadCache {
return &DownloadCache{
cfg,
}
}
func (dc *DownloadCache) BasePath(ctx context.Context) string {
return filepath.Join(
dc.cfg.GetPaths(ctx).CacheDir, "dl",
)
} }
// New creates a new directory with the given ID in the cache. // New creates a new directory with the given ID in the cache.
// If a directory with the same ID already exists, // If a directory with the same ID already exists,
// it will be deleted before creating a new one. // it will be deleted before creating a new one.
func New(ctx context.Context, id string) (string, error) { func (dc *DownloadCache) New(ctx context.Context, id string) (string, error) {
h, err := hashID(id) h, err := hashID(id)
if err != nil { if err != nil {
return "", err return "", err
} }
itemPath := filepath.Join(BasePath(ctx), h) itemPath := filepath.Join(dc.BasePath(ctx), h)
fi, err := os.Stat(itemPath) fi, err := os.Stat(itemPath)
if err == nil || (fi != nil && !fi.IsDir()) { if err == nil || (fi != nil && !fi.IsDir()) {
@ -65,12 +78,12 @@ func New(ctx context.Context, id string) (string, error) {
// returns the directory and true. If it // returns the directory and true. If it
// does not exist, it returns an empty string // does not exist, it returns an empty string
// and false. // and false.
func Get(ctx context.Context, id string) (string, bool) { func (dc *DownloadCache) Get(ctx context.Context, id string) (string, bool) {
h, err := hashID(id) h, err := hashID(id)
if err != nil { if err != nil {
return "", false return "", false
} }
itemPath := filepath.Join(BasePath(ctx), h) itemPath := filepath.Join(dc.BasePath(ctx), h)
_, err = os.Stat(itemPath) _, err = os.Stat(itemPath)
if err != nil { if err != nil {
@ -79,15 +92,3 @@ func Get(ctx context.Context, id string) (string, bool) {
return itemPath, true return itemPath, true
} }
// hashID hashes the input ID with SHA1
// and returns the hex string of the hashed
// ID.
func hashID(id string) (string, error) {
h := sha1.New()
_, err := io.WriteString(h, id)
if err != nil {
return "", err
}
return hex.EncodeToString(h.Sum(nil)), nil
}

View File

@ -1,20 +1,21 @@
/* // This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
* ALR - Any Linux Repository // It has been modified as part of "ALR - Any Linux Repository" by Евгений Храмов.
* Copyright (C) 2024 Евгений Храмов //
* // ALR - Any Linux Repository
* This program is free software: you can redistribute it and/or modify // Copyright (C) 2025 Евгений Храмов
* it under the terms of the GNU General Public License as published by //
* the Free Software Foundation, either version 3 of the License, or // This program is free software: you can redistribute it and/or modify
* (at your option) any later version. // it under the terms of the GNU General Public License as published by
* // the Free Software Foundation, either version 3 of the License, or
* This program is distributed in the hope that it will be useful, // (at your option) any later version.
* but WITHOUT ANY WARRANTY; without even the implied warranty of //
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // This program is distributed in the hope that it will be useful,
* GNU General Public License for more details. // but WITHOUT ANY WARRANTY; without even the implied warranty of
* // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* You should have received a copy of the GNU General Public License // GNU General Public License for more details.
* along with this program. If not, see <http://www.gnu.org/licenses/>. //
*/ // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package dlcache_test package dlcache_test
@ -27,26 +28,53 @@ import (
"path/filepath" "path/filepath"
"testing" "testing"
"plemya-x.ru/alr/internal/config" "gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
"plemya-x.ru/alr/internal/dlcache" "gitea.plemya-x.ru/Plemya-x/ALR/internal/dlcache"
) )
func init() { type TestALRConfig struct {
CacheDir string
}
func (c *TestALRConfig) GetPaths(ctx context.Context) *config.Paths {
return &config.Paths{
CacheDir: c.CacheDir,
}
}
func prepare(t *testing.T) *TestALRConfig {
t.Helper()
dir, err := os.MkdirTemp("/tmp", "alr-dlcache-test.*") dir, err := os.MkdirTemp("/tmp", "alr-dlcache-test.*")
if err != nil { if err != nil {
panic(err) panic(err)
} }
config.GetPaths(context.Background()).RepoDir = dir
return &TestALRConfig{
CacheDir: dir,
}
}
func cleanup(t *testing.T, cfg *TestALRConfig) {
t.Helper()
os.Remove(cfg.CacheDir)
} }
func TestNew(t *testing.T) { func TestNew(t *testing.T) {
cfg := prepare(t)
defer cleanup(t, cfg)
dc := dlcache.New(cfg)
ctx := context.Background()
const id = "https://example.com" const id = "https://example.com"
dir, err := dlcache.New(id) dir, err := dc.New(ctx, id)
if err != nil { if err != nil {
t.Errorf("Expected no error, got %s", err) t.Errorf("Expected no error, got %s", err)
} }
exp := filepath.Join(dlcache.BasePath(), sha1sum(id)) exp := filepath.Join(dc.BasePath(ctx), sha1sum(id))
if dir != exp { if dir != exp {
t.Errorf("Expected %s, got %s", exp, dir) t.Errorf("Expected %s, got %s", exp, dir)
} }
@ -60,7 +88,7 @@ func TestNew(t *testing.T) {
t.Errorf("Expected cache item to be a directory") t.Errorf("Expected cache item to be a directory")
} }
dir2, ok := dlcache.Get(id) dir2, ok := dc.Get(ctx, id)
if !ok { if !ok {
t.Errorf("Expected Get() to return valid value") t.Errorf("Expected Get() to return valid value")
} }

32
internal/dlcache/utils.go Normal file
View File

@ -0,0 +1,32 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 Евгений Храмов
//
// 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/>.
package dlcache
import (
"crypto/sha1"
"encoding/hex"
"io"
)
func hashID(id string) (string, error) {
h := sha1.New()
_, err := io.WriteString(h, id)
if err != nil {
return "", err
}
return hex.EncodeToString(h.Sum(nil)), nil
}

96
internal/logger/log.go Normal file
View File

@ -0,0 +1,96 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 Евгений Храмов
//
// 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/>.
package logger
import (
"context"
"log/slog"
"os"
"github.com/charmbracelet/lipgloss"
"github.com/charmbracelet/log"
"github.com/leonelquinteros/gotext"
)
type Logger struct {
lOut slog.Handler
lErr slog.Handler
}
func setupOutLogger() *log.Logger {
styles := log.DefaultStyles()
logger := log.New(os.Stdout)
styles.Levels[log.InfoLevel] = lipgloss.NewStyle().
SetString("-->").
Foreground(lipgloss.Color("35"))
logger.SetStyles(styles)
return logger
}
func setupErrorLogger() *log.Logger {
styles := log.DefaultStyles()
styles.Levels[log.ErrorLevel] = lipgloss.NewStyle().
SetString(gotext.Get("ERROR")).
Padding(0, 1, 0, 1).
Background(lipgloss.Color("204")).
Foreground(lipgloss.Color("0"))
logger := log.New(os.Stderr)
logger.SetStyles(styles)
return logger
}
func New() *Logger {
standardLogger := setupOutLogger()
errLogger := setupErrorLogger()
return &Logger{
lOut: standardLogger,
lErr: errLogger,
}
}
func (l *Logger) Enabled(ctx context.Context, level slog.Level) bool {
if level <= slog.LevelInfo {
return l.lOut.Enabled(ctx, level)
}
return l.lErr.Enabled(ctx, level)
}
func (l *Logger) Handle(ctx context.Context, rec slog.Record) error {
if rec.Level <= slog.LevelInfo {
return l.lOut.Handle(ctx, rec)
}
return l.lErr.Handle(ctx, rec)
}
func (l *Logger) WithAttrs(attrs []slog.Attr) slog.Handler {
sl := *l
sl.lOut = l.lOut.WithAttrs(attrs)
sl.lErr = l.lErr.WithAttrs(attrs)
return &sl
}
func (l *Logger) WithGroup(name string) slog.Handler {
sl := *l
sl.lOut = l.lOut.WithGroup(name)
sl.lErr = l.lErr.WithGroup(name)
return &sl
}
func SetupDefault() {
logger := slog.New(New())
slog.SetDefault(logger)
}

View File

@ -1,20 +1,21 @@
/* // This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
* ALR - Any Linux Repository // It has been modified as part of "ALR - Any Linux Repository" by Евгений Храмов.
* Copyright (C) 2024 Евгений Храмов //
* // ALR - Any Linux Repository
* This program is free software: you can redistribute it and/or modify // Copyright (C) 2025 Евгений Храмов
* it under the terms of the GNU General Public License as published by //
* the Free Software Foundation, either version 3 of the License, or // This program is free software: you can redistribute it and/or modify
* (at your option) any later version. // it under the terms of the GNU General Public License as published by
* // the Free Software Foundation, either version 3 of the License, or
* This program is distributed in the hope that it will be useful, // (at your option) any later version.
* but WITHOUT ANY WARRANTY; without even the implied warranty of //
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // This program is distributed in the hope that it will be useful,
* GNU General Public License for more details. // but WITHOUT ANY WARRANTY; without even the implied warranty of
* // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* You should have received a copy of the GNU General Public License // GNU General Public License for more details.
* along with this program. If not, see <http://www.gnu.org/licenses/>. //
*/ // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package osutils package osutils
@ -54,12 +55,12 @@ func copyDirOrFile(sourcePath, destPath string) error {
return err return err
} }
if sourceInfo.IsDir() { switch {
case sourceInfo.IsDir():
return copyDir(sourcePath, destPath, sourceInfo) return copyDir(sourcePath, destPath, sourceInfo)
} else if sourceInfo.Mode().IsRegular() { case sourceInfo.Mode().IsRegular():
return copyFile(sourcePath, destPath, sourceInfo) return copyFile(sourcePath, destPath, sourceInfo)
} else { default:
// ignore non-regular files
return nil return nil
} }
} }

View File

@ -1,32 +1,36 @@
/* // This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
* ALR - Any Linux Repository // It has been modified as part of "ALR - Any Linux Repository" by Евгений Храмов.
* Copyright (C) 2024 Евгений Храмов //
* // ALR - Any Linux Repository
* This program is free software: you can redistribute it and/or modify // Copyright (C) 2025 Евгений Храмов
* it under the terms of the GNU General Public License as published by //
* the Free Software Foundation, either version 3 of the License, or // This program is free software: you can redistribute it and/or modify
* (at your option) any later version. // it under the terms of the GNU General Public License as published by
* // the Free Software Foundation, either version 3 of the License, or
* This program is distributed in the hope that it will be useful, // (at your option) any later version.
* but WITHOUT ANY WARRANTY; without even the implied warranty of //
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // This program is distributed in the hope that it will be useful,
* GNU General Public License for more details. // but WITHOUT ANY WARRANTY; without even the implied warranty of
* // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* You should have received a copy of the GNU General Public License // GNU General Public License for more details.
* along with this program. If not, see <http://www.gnu.org/licenses/>. //
*/ // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package overrides package overrides
import ( import (
"fmt"
"reflect" "reflect"
"regexp"
"strings" "strings"
"plemya-x.ru/alr/internal/cpu"
"plemya-x.ru/alr/internal/db"
"plemya-x.ru/alr/pkg/distro"
"golang.org/x/exp/slices" "golang.org/x/exp/slices"
"golang.org/x/text/language" "golang.org/x/text/language"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cpu"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro"
) )
type Opts struct { type Opts struct {
@ -221,3 +225,19 @@ func parseLangs(langs []string, tags []language.Tag) ([]string, error) {
out = slices.Compact(out) out = slices.Compact(out)
return out, nil return out, nil
} }
func ReleasePlatformSpecific(release int, info *distro.OSRelease) string {
if info.ID == "altlinux" {
return fmt.Sprintf("alt%d", release)
}
if info.ID == "fedora" || slices.Contains(info.Like, "fedora") {
re := regexp.MustCompile(`platform:(\S+)`)
match := re.FindStringSubmatch(info.PlatformID)
if len(match) > 1 {
return fmt.Sprintf("%d.%s", release, match[1])
}
}
return fmt.Sprintf("%d", release)
}

View File

@ -1,20 +1,21 @@
/* // This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
* ALR - Any Linux Repository // It has been modified as part of "ALR - Any Linux Repository" by Евгений Храмов.
* Copyright (C) 2024 Евгений Храмов //
* // ALR - Any Linux Repository
* This program is free software: you can redistribute it and/or modify // Copyright (C) 2025 Евгений Храмов
* it under the terms of the GNU General Public License as published by //
* the Free Software Foundation, either version 3 of the License, or // This program is free software: you can redistribute it and/or modify
* (at your option) any later version. // it under the terms of the GNU General Public License as published by
* // the Free Software Foundation, either version 3 of the License, or
* This program is distributed in the hope that it will be useful, // (at your option) any later version.
* but WITHOUT ANY WARRANTY; without even the implied warranty of //
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // This program is distributed in the hope that it will be useful,
* GNU General Public License for more details. // but WITHOUT ANY WARRANTY; without even the implied warranty of
* // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* You should have received a copy of the GNU General Public License // GNU General Public License for more details.
* along with this program. If not, see <http://www.gnu.org/licenses/>. //
*/ // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package overrides_test package overrides_test
@ -23,9 +24,11 @@ import (
"reflect" "reflect"
"testing" "testing"
"plemya-x.ru/alr/internal/overrides" "github.com/stretchr/testify/assert"
"plemya-x.ru/alr/pkg/distro"
"golang.org/x/text/language" "golang.org/x/text/language"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/overrides"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro"
) )
var info = &distro.OSRelease{ var info = &distro.OSRelease{
@ -193,3 +196,42 @@ func TestResolveLangs(t *testing.T) {
t.Errorf("expected %v, got %v", expected, names) t.Errorf("expected %v, got %v", expected, names)
} }
} }
func TestReleasePlatformSpecific(t *testing.T) {
type testCase struct {
info *distro.OSRelease
expected string
}
for _, tc := range []testCase{
{
info: &distro.OSRelease{
ID: "centos",
Like: []string{"rhel", "fedora"},
PlatformID: "platform:el8",
},
expected: "1.el8",
},
{
info: &distro.OSRelease{
ID: "fedora",
PlatformID: "platform:f42",
},
expected: "1.f42",
},
{
info: &distro.OSRelease{
ID: "altlinux",
},
expected: "alt1",
},
{
info: &distro.OSRelease{
ID: "ubuntu",
},
expected: "1",
},
} {
assert.Equal(t, tc.expected, overrides.ReleasePlatformSpecific(1, tc.info))
}
}

View File

@ -1,20 +1,21 @@
/* // This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
* ALR - Any Linux Repository // It has been modified as part of "ALR - Any Linux Repository" by Евгений Храмов.
* Copyright (C) 2024 Евгений Храмов //
* // ALR - Any Linux Repository
* This program is free software: you can redistribute it and/or modify // Copyright (C) 2025 Евгений Храмов
* it under the terms of the GNU General Public License as published by //
* the Free Software Foundation, either version 3 of the License, or // This program is free software: you can redistribute it and/or modify
* (at your option) any later version. // it under the terms of the GNU General Public License as published by
* // the Free Software Foundation, either version 3 of the License, or
* This program is distributed in the hope that it will be useful, // (at your option) any later version.
* but WITHOUT ANY WARRANTY; without even the implied warranty of //
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // This program is distributed in the hope that it will be useful,
* GNU General Public License for more details. // but WITHOUT ANY WARRANTY; without even the implied warranty of
* // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* You should have received a copy of the GNU General Public License // GNU General Public License for more details.
* along with this program. If not, see <http://www.gnu.org/licenses/>. //
*/ // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package pager package pager

View File

@ -1,20 +1,21 @@
/* // This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
* ALR - Any Linux Repository // It has been modified as part of "ALR - Any Linux Repository" by Евгений Храмов.
* Copyright (C) 2024 Евгений Храмов //
* // ALR - Any Linux Repository
* This program is free software: you can redistribute it and/or modify // Copyright (C) 2025 Евгений Храмов
* it under the terms of the GNU General Public License as published by //
* the Free Software Foundation, either version 3 of the License, or // This program is free software: you can redistribute it and/or modify
* (at your option) any later version. // it under the terms of the GNU General Public License as published by
* // the Free Software Foundation, either version 3 of the License, or
* This program is distributed in the hope that it will be useful, // (at your option) any later version.
* but WITHOUT ANY WARRANTY; without even the implied warranty of //
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // This program is distributed in the hope that it will be useful,
* GNU General Public License for more details. // but WITHOUT ANY WARRANTY; without even the implied warranty of
* // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* You should have received a copy of the GNU General Public License // GNU General Public License for more details.
* along with this program. If not, see <http://www.gnu.org/licenses/>. //
*/ // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package pager package pager
@ -40,7 +41,7 @@ func init() {
b2 := lipgloss.RoundedBorder() b2 := lipgloss.RoundedBorder()
b2.Left = "\u2524" b2.Left = "\u2524"
infoStyle = titleStyle.Copy().BorderStyle(b2) infoStyle = titleStyle.BorderStyle(b2)
} }
type Pager struct { type Pager struct {

View File

@ -1,20 +1,21 @@
/* // This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
* ALR - Any Linux Repository // It has been modified as part of "ALR - Any Linux Repository" by Евгений Храмов.
* Copyright (C) 2024 Евгений Храмов //
* // ALR - Any Linux Repository
* This program is free software: you can redistribute it and/or modify // Copyright (C) 2025 Евгений Храмов
* it under the terms of the GNU General Public License as published by //
* the Free Software Foundation, either version 3 of the License, or // This program is free software: you can redistribute it and/or modify
* (at your option) any later version. // it under the terms of the GNU General Public License as published by
* // the Free Software Foundation, either version 3 of the License, or
* This program is distributed in the hope that it will be useful, // (at your option) any later version.
* but WITHOUT ANY WARRANTY; without even the implied warranty of //
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // This program is distributed in the hope that it will be useful,
* GNU General Public License for more details. // but WITHOUT ANY WARRANTY; without even the implied warranty of
* // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* You should have received a copy of the GNU General Public License // GNU General Public License for more details.
* along with this program. If not, see <http://www.gnu.org/licenses/>. //
*/ // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package decoder package decoder
@ -25,12 +26,13 @@ import (
"strings" "strings"
"github.com/mitchellh/mapstructure" "github.com/mitchellh/mapstructure"
"plemya-x.ru/alr/internal/overrides"
"plemya-x.ru/alr/pkg/distro"
"golang.org/x/exp/slices" "golang.org/x/exp/slices"
"mvdan.cc/sh/v3/expand" "mvdan.cc/sh/v3/expand"
"mvdan.cc/sh/v3/interp" "mvdan.cc/sh/v3/interp"
"mvdan.cc/sh/v3/syntax" "mvdan.cc/sh/v3/syntax"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/overrides"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro"
) )
var ErrNotPointerToStruct = errors.New("val must be a pointer to a struct") var ErrNotPointerToStruct = errors.New("val must be a pointer to a struct")
@ -162,11 +164,20 @@ func (d *Decoder) DecodeVars(val any) error {
return nil return nil
} }
type ScriptFunc func(ctx context.Context, opts ...interp.RunnerOption) error type (
ScriptFunc func(ctx context.Context, opts ...interp.RunnerOption) error
ScriptFuncWithSubshell func(ctx context.Context, opts ...interp.RunnerOption) (*interp.Runner, error)
)
// GetFunc returns a function corresponding to a bash function // GetFunc returns a function corresponding to a bash function
// with the given name // with the given name
func (d *Decoder) GetFunc(name string) (ScriptFunc, bool) { func (d *Decoder) GetFunc(name string) (ScriptFunc, bool) {
return d.GetFuncP(name, nil)
}
type PrepareFunc func(context.Context, *interp.Runner) error
func (d *Decoder) GetFuncP(name string, prepare PrepareFunc) (ScriptFunc, bool) {
fn := d.getFunc(name) fn := d.getFunc(name)
if fn == nil { if fn == nil {
return nil, false return nil, false
@ -175,12 +186,38 @@ func (d *Decoder) GetFunc(name string) (ScriptFunc, bool) {
return func(ctx context.Context, opts ...interp.RunnerOption) error { return func(ctx context.Context, opts ...interp.RunnerOption) error {
sub := d.Runner.Subshell() sub := d.Runner.Subshell()
for _, opt := range opts { for _, opt := range opts {
opt(sub) err := opt(sub)
if err != nil {
return err
}
}
if prepare != nil {
if err := prepare(ctx, sub); err != nil {
return err
}
} }
return sub.Run(ctx, fn) return sub.Run(ctx, fn)
}, true }, true
} }
func (d *Decoder) GetFuncWithSubshell(name string) (ScriptFuncWithSubshell, bool) {
fn := d.getFunc(name)
if fn == nil {
return nil, false
}
return func(ctx context.Context, opts ...interp.RunnerOption) (*interp.Runner, error) {
sub := d.Runner.Subshell()
for _, opt := range opts {
err := opt(sub)
if err != nil {
return nil, err
}
}
return sub, sub.Run(ctx, fn)
}, true
}
func (d *Decoder) getFunc(name string) *syntax.Stmt { func (d *Decoder) getFunc(name string) *syntax.Stmt {
names, err := overrides.Resolve(d.info, overrides.DefaultOpts.WithName(name)) names, err := overrides.Resolve(d.info, overrides.DefaultOpts.WithName(name))
if err != nil { if err != nil {
@ -221,3 +258,8 @@ func (d *Decoder) getVar(name string) *expand.Variable {
} }
return nil return nil
} }
func IsTruthy(value string) bool {
value = strings.ToLower(strings.TrimSpace(value))
return value == "true" || value == "yes" || value == "1"
}

View File

@ -1,20 +1,21 @@
/* // This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
* ALR - Any Linux Repository // It has been modified as part of "ALR - Any Linux Repository" by Евгений Храмов.
* Copyright (C) 2024 Евгений Храмов //
* // ALR - Any Linux Repository
* This program is free software: you can redistribute it and/or modify // Copyright (C) 2025 Евгений Храмов
* it under the terms of the GNU General Public License as published by //
* the Free Software Foundation, either version 3 of the License, or // This program is free software: you can redistribute it and/or modify
* (at your option) any later version. // it under the terms of the GNU General Public License as published by
* // the Free Software Foundation, either version 3 of the License, or
* This program is distributed in the hope that it will be useful, // (at your option) any later version.
* but WITHOUT ANY WARRANTY; without even the implied warranty of //
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // This program is distributed in the hope that it will be useful,
* GNU General Public License for more details. // but WITHOUT ANY WARRANTY; without even the implied warranty of
* // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* You should have received a copy of the GNU General Public License // GNU General Public License for more details.
* along with this program. If not, see <http://www.gnu.org/licenses/>. //
*/ // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package decoder_test package decoder_test
@ -27,10 +28,11 @@ import (
"strings" "strings"
"testing" "testing"
"plemya-x.ru/alr/internal/shutils/decoder"
"plemya-x.ru/alr/pkg/distro"
"mvdan.cc/sh/v3/interp" "mvdan.cc/sh/v3/interp"
"mvdan.cc/sh/v3/syntax" "mvdan.cc/sh/v3/syntax"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/decoder"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro"
) )
type BuildVars struct { type BuildVars struct {
@ -56,7 +58,7 @@ const testScript = `
release=1 release=1
epoch=2 epoch=2
desc="Test package" desc="Test package"
homepage='//https://gitea.plemya-x.ru/xpamych/ALR' homepage='https://gitea.plemya-x.ru/xpamych/ALR'
maintainer='Евгений Храмов <xpamych@yandex.ru>' maintainer='Евгений Храмов <xpamych@yandex.ru>'
architectures=('arm64' 'amd64') architectures=('arm64' 'amd64')
license=('GPL-3.0-or-later') license=('GPL-3.0-or-later')

View File

@ -1,20 +1,21 @@
/* // This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
* ALR - Any Linux Repository // It has been modified as part of "ALR - Any Linux Repository" by Евгений Храмов.
* Copyright (C) 2024 Евгений Храмов //
* // ALR - Any Linux Repository
* This program is free software: you can redistribute it and/or modify // Copyright (C) 2025 Евгений Храмов
* it under the terms of the GNU General Public License as published by //
* the Free Software Foundation, either version 3 of the License, or // This program is free software: you can redistribute it and/or modify
* (at your option) any later version. // it under the terms of the GNU General Public License as published by
* // the Free Software Foundation, either version 3 of the License, or
* This program is distributed in the hope that it will be useful, // (at your option) any later version.
* but WITHOUT ANY WARRANTY; without even the implied warranty of //
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // This program is distributed in the hope that it will be useful,
* GNU General Public License for more details. // but WITHOUT ANY WARRANTY; without even the implied warranty of
* // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* You should have received a copy of the GNU General Public License // GNU General Public License for more details.
* along with this program. If not, see <http://www.gnu.org/licenses/>. //
*/ // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package handlers package handlers

View File

@ -1,20 +1,21 @@
/* // This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
* ALR - Any Linux Repository // It has been modified as part of "ALR - Any Linux Repository" by Евгений Храмов.
* Copyright (C) 2024 Евгений Храмов //
* // ALR - Any Linux Repository
* This program is free software: you can redistribute it and/or modify // Copyright (C) 2025 Евгений Храмов
* it under the terms of the GNU General Public License as published by //
* the Free Software Foundation, either version 3 of the License, or // This program is free software: you can redistribute it and/or modify
* (at your option) any later version. // it under the terms of the GNU General Public License as published by
* // the Free Software Foundation, either version 3 of the License, or
* This program is distributed in the hope that it will be useful, // (at your option) any later version.
* but WITHOUT ANY WARRANTY; without even the implied warranty of //
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // This program is distributed in the hope that it will be useful,
* GNU General Public License for more details. // but WITHOUT ANY WARRANTY; without even the implied warranty of
* // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* You should have received a copy of the GNU General Public License // GNU General Public License for more details.
* along with this program. If not, see <http://www.gnu.org/licenses/>. //
*/ // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package handlers_test package handlers_test
@ -23,11 +24,12 @@ import (
"strings" "strings"
"testing" "testing"
"plemya-x.ru/alr/internal/shutils/handlers"
"plemya-x.ru/alr/internal/shutils/decoder"
"plemya-x.ru/alr/pkg/distro"
"mvdan.cc/sh/v3/interp" "mvdan.cc/sh/v3/interp"
"mvdan.cc/sh/v3/syntax" "mvdan.cc/sh/v3/syntax"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/decoder"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/handlers"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro"
) )
const testScript = ` const testScript = `
@ -89,7 +91,7 @@ func TestExecFuncs(t *testing.T) {
t.Fatalf("Expected test() function to exist") t.Fatalf("Expected test() function to exist")
} }
eh := shutils.ExecFuncs{ eh := handlers.ExecFuncs{
"test-cmd": func(hc interp.HandlerContext, name string, args []string) error { "test-cmd": func(hc interp.HandlerContext, name string, args []string) error {
if name != "test-cmd" { if name != "test-cmd" {
t.Errorf("Expected name to be 'test-cmd', got '%s'", name) t.Errorf("Expected name to be 'test-cmd', got '%s'", name)

View File

@ -1,3 +1,22 @@
// This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
// It has been modified as part of "ALR - Any Linux Repository" by Евгений Храмов.
//
// ALR - Any Linux Repository
// Copyright (C) 2025 Евгений Храмов
//
// 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/>.
package handlers package handlers
import ( import (
@ -10,7 +29,7 @@ import (
"syscall" "syscall"
"time" "time"
"plemya-x.ru/fakeroot" "gitea.plemya-x.ru/Plemya-x/fakeroot"
"mvdan.cc/sh/v3/expand" "mvdan.cc/sh/v3/expand"
"mvdan.cc/sh/v3/interp" "mvdan.cc/sh/v3/interp"
) )

View File

@ -1,30 +1,32 @@
/* // This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
* ALR - Any Linux Repository // It has been modified as part of "ALR - Any Linux Repository" by Евгений Храмов.
* Copyright (C) 2024 Евгений Храмов //
* // ALR - Any Linux Repository
* This program is free software: you can redistribute it and/or modify // Copyright (C) 2025 Евгений Храмов
* it under the terms of the GNU General Public License as published by //
* the Free Software Foundation, either version 3 of the License, or // This program is free software: you can redistribute it and/or modify
* (at your option) any later version. // it under the terms of the GNU General Public License as published by
* // the Free Software Foundation, either version 3 of the License, or
* This program is distributed in the hope that it will be useful, // (at your option) any later version.
* but WITHOUT ANY WARRANTY; without even the implied warranty of //
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // This program is distributed in the hope that it will be useful,
* GNU General Public License for more details. // but WITHOUT ANY WARRANTY; without even the implied warranty of
* // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* You should have received a copy of the GNU General Public License // GNU General Public License for more details.
* along with this program. If not, see <http://www.gnu.org/licenses/>. //
*/ // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package handlers package handlers
import ( import (
"context" "context"
"io" "io"
"io/fs"
"os" "os"
) )
func NopReadDir(context.Context, string) ([]os.FileInfo, error) { func NopReadDir(context.Context, string) ([]fs.DirEntry, error) {
return nil, os.ErrNotExist return nil, os.ErrNotExist
} }

View File

@ -1,20 +1,21 @@
/* // This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
* ALR - Any Linux Repository // It has been modified as part of "ALR - Any Linux Repository" by Евгений Храмов.
* Copyright (C) 2024 Евгений Храмов //
* // ALR - Any Linux Repository
* This program is free software: you can redistribute it and/or modify // Copyright (C) 2025 Евгений Храмов
* it under the terms of the GNU General Public License as published by //
* the Free Software Foundation, either version 3 of the License, or // This program is free software: you can redistribute it and/or modify
* (at your option) any later version. // it under the terms of the GNU General Public License as published by
* // the Free Software Foundation, either version 3 of the License, or
* This program is distributed in the hope that it will be useful, // (at your option) any later version.
* but WITHOUT ANY WARRANTY; without even the implied warranty of //
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // This program is distributed in the hope that it will be useful,
* GNU General Public License for more details. // but WITHOUT ANY WARRANTY; without even the implied warranty of
* // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* You should have received a copy of the GNU General Public License // GNU General Public License for more details.
* along with this program. If not, see <http://www.gnu.org/licenses/>. //
*/ // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package handlers_test package handlers_test
@ -25,9 +26,10 @@ import (
"strings" "strings"
"testing" "testing"
"plemya-x.ru/alr/internal/shutils/handlers"
"mvdan.cc/sh/v3/interp" "mvdan.cc/sh/v3/interp"
"mvdan.cc/sh/v3/syntax" "mvdan.cc/sh/v3/syntax"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/handlers"
) )
func TestNopExec(t *testing.T) { func TestNopExec(t *testing.T) {

View File

@ -1,20 +1,21 @@
/* // This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
* ALR - Any Linux Repository // It has been modified as part of "ALR - Any Linux Repository" by Евгений Храмов.
* Copyright (C) 2024 Евгений Храмов //
* // ALR - Any Linux Repository
* This program is free software: you can redistribute it and/or modify // Copyright (C) 2025 Евгений Храмов
* it under the terms of the GNU General Public License as published by //
* the Free Software Foundation, either version 3 of the License, or // This program is free software: you can redistribute it and/or modify
* (at your option) any later version. // it under the terms of the GNU General Public License as published by
* // the Free Software Foundation, either version 3 of the License, or
* This program is distributed in the hope that it will be useful, // (at your option) any later version.
* but WITHOUT ANY WARRANTY; without even the implied warranty of //
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // This program is distributed in the hope that it will be useful,
* GNU General Public License for more details. // but WITHOUT ANY WARRANTY; without even the implied warranty of
* // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* You should have received a copy of the GNU General Public License // GNU General Public License for more details.
* along with this program. If not, see <http://www.gnu.org/licenses/>. //
*/ // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package handlers package handlers
@ -30,12 +31,12 @@ import (
"mvdan.cc/sh/v3/interp" "mvdan.cc/sh/v3/interp"
) )
func RestrictedReadDir(allowedPrefixes ...string) interp.ReadDirHandlerFunc { func RestrictedReadDir(allowedPrefixes ...string) interp.ReadDirHandlerFunc2 {
return func(ctx context.Context, s string) ([]fs.FileInfo, error) { return func(ctx context.Context, s string) ([]fs.DirEntry, error) {
path := filepath.Clean(s) path := filepath.Clean(s)
for _, allowedPrefix := range allowedPrefixes { for _, allowedPrefix := range allowedPrefixes {
if strings.HasPrefix(path, allowedPrefix) { if strings.HasPrefix(path, allowedPrefix) {
return interp.DefaultReadDirHandler()(ctx, s) return interp.DefaultReadDirHandler2()(ctx, s)
} }
} }

View File

@ -1,20 +1,21 @@
/* // This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
* ALR - Any Linux Repository // It has been modified as part of "ALR - Any Linux Repository" by Евгений Храмов.
* Copyright (C) 2024 Евгений Храмов //
* // ALR - Any Linux Repository
* This program is free software: you can redistribute it and/or modify // Copyright (C) 2025 Евгений Храмов
* it under the terms of the GNU General Public License as published by //
* the Free Software Foundation, either version 3 of the License, or // This program is free software: you can redistribute it and/or modify
* (at your option) any later version. // it under the terms of the GNU General Public License as published by
* // the Free Software Foundation, either version 3 of the License, or
* This program is distributed in the hope that it will be useful, // (at your option) any later version.
* but WITHOUT ANY WARRANTY; without even the implied warranty of //
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // This program is distributed in the hope that it will be useful,
* GNU General Public License for more details. // but WITHOUT ANY WARRANTY; without even the implied warranty of
* // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* You should have received a copy of the GNU General Public License // GNU General Public License for more details.
* along with this program. If not, see <http://www.gnu.org/licenses/>. //
*/ // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package helpers package helpers
@ -23,6 +24,7 @@ import (
"fmt" "fmt"
"io" "io"
"os" "os"
"path"
"path/filepath" "path/filepath"
"strconv" "strconv"
"strings" "strings"
@ -31,8 +33,9 @@ import (
"github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing/object" "github.com/go-git/go-git/v5/plumbing/object"
"golang.org/x/exp/slices" "golang.org/x/exp/slices"
"plemya-x.ru/alr/internal/shutils/handlers"
"mvdan.cc/sh/v3/interp" "mvdan.cc/sh/v3/interp"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/handlers"
) )
var ( var (
@ -53,12 +56,17 @@ var Helpers = handlers.ExecFuncs{
"install-completion": installCompletionCmd, "install-completion": installCompletionCmd,
"install-library": installLibraryCmd, "install-library": installLibraryCmd,
"git-version": gitVersionCmd, "git-version": gitVersionCmd,
"files-find-lang": filesFindLangCmd,
"files-find-doc": filesFindDocCmd,
} }
// Restricted contains restricted read-only helper commands // Restricted contains restricted read-only helper commands
// that don't modify any state // that don't modify any state
var Restricted = handlers.ExecFuncs{ var Restricted = handlers.ExecFuncs{
"git-version": gitVersionCmd, "git-version": gitVersionCmd,
"files-find-lang": filesFindLangCmd,
"files-find-doc": filesFindDocCmd,
} }
func installHelperCmd(prefix string, perms os.FileMode) handlers.ExecFunc { func installHelperCmd(prefix string, perms os.FileMode) handlers.ExecFunc {
@ -237,10 +245,13 @@ func gitVersionCmd(hc interp.HandlerContext, cmd string, args []string) error {
return fmt.Errorf("git-version: %w", err) return fmt.Errorf("git-version: %w", err)
} }
commits.ForEach(func(*object.Commit) error { err = commits.ForEach(func(*object.Commit) error {
revNum++ revNum++
return nil return nil
}) })
if err != nil {
return fmt.Errorf("git-version: %w", err)
}
HEAD, err := r.Head() HEAD, err := r.Head()
if err != nil { if err != nil {
@ -254,6 +265,114 @@ func gitVersionCmd(hc interp.HandlerContext, cmd string, args []string) error {
return nil return nil
} }
func filesFindLangCmd(hc interp.HandlerContext, cmd string, args []string) error {
namePattern := "*.mo"
if len(args) > 0 {
namePattern = args[0] + ".mo"
}
localePath := "./usr/share/locale/"
realPath := path.Join(hc.Dir, localePath)
info, err := os.Stat(realPath)
if err != nil {
return fmt.Errorf("files-find-lang: %w", err)
}
if !info.IsDir() {
return fmt.Errorf("files-find-lang: %s is not a directory", localePath)
}
var langFiles []string
err = filepath.Walk(realPath, func(p string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if !info.IsDir() && matchNamePattern(info.Name(), namePattern) {
relPath, relErr := filepath.Rel(hc.Dir, p)
if relErr != nil {
return relErr
}
langFiles = append(langFiles, "./"+relPath)
}
return nil
})
if err != nil {
return fmt.Errorf("files-find-lang: %w", err)
}
for _, file := range langFiles {
fmt.Fprintln(hc.Stdout, file)
}
return nil
}
func filesFindDocCmd(hc interp.HandlerContext, cmd string, args []string) error {
namePattern := "*"
if len(args) > 0 {
namePattern = args[0]
}
docPath := "./usr/share/doc/"
docRealPath := path.Join(hc.Dir, docPath)
info, err := os.Stat(docRealPath)
if err != nil {
return fmt.Errorf("files-find-doc: %w", err)
}
if !info.IsDir() {
return fmt.Errorf("files-find-doc: %s is not a directory", docPath)
}
var docFiles []string
entries, err := os.ReadDir(docRealPath)
if err != nil {
return err
}
for _, entry := range entries {
if matchNamePattern(entry.Name(), namePattern) {
targetPath := filepath.Join(docRealPath, entry.Name())
targetInfo, err := os.Stat(targetPath)
if err != nil {
return err
}
if targetInfo.IsDir() {
err := filepath.Walk(targetPath, func(subPath string, subInfo os.FileInfo, subErr error) error {
relPath, err := filepath.Rel(hc.Dir, subPath)
if err != nil {
return err
}
docFiles = append(docFiles, "./"+relPath)
return nil
})
if err != nil {
return err
}
}
}
}
if err != nil {
return fmt.Errorf("files-find-doc: %w", err)
}
for _, file := range docFiles {
fmt.Fprintln(hc.Stdout, file)
}
return nil
}
func matchNamePattern(name, pattern string) bool {
matched, err := filepath.Match(pattern, name)
if err != nil {
return false
}
return matched
}
func helperInstall(from, to string, perms os.FileMode) error { func helperInstall(from, to string, perms os.FileMode) error {
err := os.MkdirAll(filepath.Dir(to), 0o755) err := os.MkdirAll(filepath.Dir(to), 0o755)
if err != nil { if err != nil {

View File

@ -0,0 +1,216 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 Евгений Храмов
//
// 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/>.
package helpers
import (
"bytes"
"context"
"os"
"path/filepath"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"mvdan.cc/sh/v3/interp"
"mvdan.cc/sh/v3/syntax"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/handlers"
)
type testCase struct {
name string
dirsToCreate []string
filesToCreate []string
expectedOutput []string
args string
}
func TestFindFilesDoc(t *testing.T) {
tests := []testCase{
{
name: "All dirs",
dirsToCreate: []string{
"usr/share/doc/yandex-browser-stable/subdir",
"usr/share/doc/firefox",
},
filesToCreate: []string{
"usr/share/doc/yandex-browser-stable/README.md",
"usr/share/doc/yandex-browser-stable/subdir/nested-file.txt",
"usr/share/doc/firefox/README.md",
},
expectedOutput: []string{
"./usr/share/doc/yandex-browser-stable",
"./usr/share/doc/yandex-browser-stable/README.md",
"./usr/share/doc/yandex-browser-stable/subdir",
"./usr/share/doc/yandex-browser-stable/subdir/nested-file.txt",
"./usr/share/doc/firefox",
"./usr/share/doc/firefox/README.md",
},
args: "",
},
{
name: "Only selected dir",
dirsToCreate: []string{
"usr/share/doc/yandex-browser-stable/subdir",
"usr/share/doc/firefox",
"usr/share/doc/foo/yandex-browser-stable",
},
filesToCreate: []string{
"usr/share/doc/yandex-browser-stable/README.md",
"usr/share/doc/yandex-browser-stable/subdir/nested-file.txt",
"usr/share/doc/firefox/README.md",
"usr/share/doc/firefox/yandex-browser-stable",
"usr/share/doc/foo/yandex-browser-stable/README.md",
},
expectedOutput: []string{
"./usr/share/doc/yandex-browser-stable",
"./usr/share/doc/yandex-browser-stable/README.md",
"./usr/share/doc/yandex-browser-stable/subdir",
"./usr/share/doc/yandex-browser-stable/subdir/nested-file.txt",
},
args: "yandex-browser-stable",
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
tempDir, err := os.MkdirTemp("", "test-files-find-doc")
assert.NoError(t, err)
defer os.RemoveAll(tempDir)
for _, dir := range tc.dirsToCreate {
dirPath := filepath.Join(tempDir, dir)
err := os.MkdirAll(dirPath, 0o755)
assert.NoError(t, err)
}
for _, file := range tc.filesToCreate {
filePath := filepath.Join(tempDir, file)
err := os.WriteFile(filePath, []byte("test content"), 0o644)
assert.NoError(t, err)
}
helpers := handlers.ExecFuncs{
"files-find-doc": filesFindDocCmd,
}
buf := &bytes.Buffer{}
runner, err := interp.New(
interp.Dir(tempDir),
interp.StdIO(os.Stdin, buf, os.Stderr),
interp.ExecHandler(helpers.ExecHandler(interp.DefaultExecHandler(1000))),
)
assert.NoError(t, err)
scriptContent := `
shopt -s globstar
files-find-doc ` + tc.args
script, err := syntax.NewParser().Parse(strings.NewReader(scriptContent), "")
assert.NoError(t, err)
err = runner.Run(context.Background(), script)
assert.NoError(t, err)
contents := strings.Fields(strings.TrimSpace(buf.String()))
assert.ElementsMatch(t, tc.expectedOutput, contents)
})
}
}
func TestFindLang(t *testing.T) {
tests := []testCase{
{
name: "All dirs",
dirsToCreate: []string{
"usr/share/locale/ru/LC_MESSAGES",
"usr/share/locale/tr/LC_MESSAGES",
},
filesToCreate: []string{
"usr/share/locale/ru/LC_MESSAGES/yandex-disk.mo",
"usr/share/locale/ru/LC_MESSAGES/yandex-disk-indicator.mo",
"usr/share/locale/tr/LC_MESSAGES/yandex-disk.mo",
},
expectedOutput: []string{
"./usr/share/locale/ru/LC_MESSAGES/yandex-disk.mo",
"./usr/share/locale/ru/LC_MESSAGES/yandex-disk-indicator.mo",
"./usr/share/locale/tr/LC_MESSAGES/yandex-disk.mo",
},
args: "",
},
{
name: "All dirs",
dirsToCreate: []string{
"usr/share/locale/ru/LC_MESSAGES",
"usr/share/locale/tr/LC_MESSAGES",
},
filesToCreate: []string{
"usr/share/locale/ru/LC_MESSAGES/yandex-disk.mo",
"usr/share/locale/ru/LC_MESSAGES/yandex-disk-indicator.mo",
"usr/share/locale/tr/LC_MESSAGES/yandex-disk.mo",
},
expectedOutput: []string{
"./usr/share/locale/ru/LC_MESSAGES/yandex-disk.mo",
"./usr/share/locale/tr/LC_MESSAGES/yandex-disk.mo",
},
args: "yandex-disk",
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
tempDir, err := os.MkdirTemp("", "test-files-find-lang")
assert.NoError(t, err)
defer os.RemoveAll(tempDir)
for _, dir := range tc.dirsToCreate {
dirPath := filepath.Join(tempDir, dir)
err := os.MkdirAll(dirPath, 0o755)
assert.NoError(t, err)
}
for _, file := range tc.filesToCreate {
filePath := filepath.Join(tempDir, file)
err := os.WriteFile(filePath, []byte("test content"), 0o644)
assert.NoError(t, err)
}
helpers := handlers.ExecFuncs{
"files-find-lang": filesFindLangCmd,
}
buf := &bytes.Buffer{}
runner, err := interp.New(
interp.Dir(tempDir),
interp.StdIO(os.Stdin, buf, os.Stderr),
interp.ExecHandler(helpers.ExecHandler(interp.DefaultExecHandler(1000))),
)
assert.NoError(t, err)
scriptContent := `
shopt -s globstar
files-find-lang ` + tc.args
script, err := syntax.NewParser().Parse(strings.NewReader(scriptContent), "")
assert.NoError(t, err)
err = runner.Run(context.Background(), script)
assert.NoError(t, err)
contents := strings.Fields(strings.TrimSpace(buf.String()))
assert.ElementsMatch(t, tc.expectedOutput, contents)
})
}
}

View File

@ -0,0 +1,550 @@
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Last-Translator: Automatically generated\n"
"Language-Team: none\n"
"Language: en\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: build.go:44
msgid "Build a local package"
msgstr ""
#: build.go:50
msgid "Path to the build script"
msgstr ""
#: build.go:55
msgid "Specify subpackage in script (for multi package script only)"
msgstr ""
#: build.go:60
msgid "Name of the package to build and its repo (example: default/go-bin)"
msgstr ""
#: build.go:65
msgid ""
"Build package from scratch even if there's an already built package available"
msgstr ""
#: build.go:75
msgid "Error initialization database"
msgstr ""
#: build.go:104
msgid "Package not found"
msgstr ""
#: build.go:124
msgid "Error pulling repositories"
msgstr ""
#: build.go:132
msgid "Unable to detect a supported package manager on the system"
msgstr ""
#: build.go:138
msgid "Error parsing os release"
msgstr ""
#: build.go:160
msgid "Error building package"
msgstr ""
#: build.go:167
msgid "Error getting working directory"
msgstr ""
#: build.go:176
msgid "Error moving the package"
msgstr ""
#: fix.go:37
msgid "Attempt to fix problems with ALR"
msgstr ""
#: fix.go:43
msgid "Removing cache directory"
msgstr ""
#: fix.go:47
msgid "Unable to remove cache directory"
msgstr ""
#: fix.go:51
msgid "Rebuilding cache"
msgstr ""
#: fix.go:55
msgid "Unable to create new cache directory"
msgstr ""
#: fix.go:69
msgid "Error pulling repos"
msgstr ""
#: fix.go:73
msgid "Done"
msgstr ""
#: gen.go:34
msgid "Generate a ALR script from a template"
msgstr ""
#: gen.go:39
msgid "Generate a ALR script for a pip module"
msgstr ""
#: helper.go:41
msgid "List all the available helper commands"
msgstr ""
#: helper.go:53
msgid "Run a ALR helper command"
msgstr ""
#: helper.go:60
msgid "The directory that the install commands will install to"
msgstr ""
#: helper.go:73
msgid "No such helper command"
msgstr ""
#: info.go:43
msgid "Print information about a package"
msgstr ""
#: info.go:48
msgid "Show all information, not just for the current distro"
msgstr ""
#: info.go:63
msgid "Error getting packages"
msgstr ""
#: info.go:72
msgid "Error iterating over packages"
msgstr ""
#: info.go:93
msgid "Command info expected at least 1 argument, got %d"
msgstr ""
#: info.go:107
msgid "Error finding packages"
msgstr ""
#: info.go:129
msgid "Error parsing os-release file"
msgstr ""
#: info.go:138
msgid "Error resolving overrides"
msgstr ""
#: info.go:147 info.go:153
msgid "Error encoding script variables"
msgstr ""
#: install.go:43
msgid "Install a new package"
msgstr ""
#: install.go:57
msgid "Command install expected at least 1 argument, got %d"
msgstr ""
#: install.go:151
msgid "Remove an installed package"
msgstr ""
#: install.go:156
msgid "Command remove expected at least 1 argument, got %d"
msgstr ""
#: install.go:168
msgid "Error removing packages"
msgstr ""
#: internal/cliutils/prompt.go:60
msgid "Would you like to view the build script for %s"
msgstr ""
#: internal/cliutils/prompt.go:71
msgid "Would you still like to continue?"
msgstr ""
#: internal/cliutils/prompt.go:77
msgid "User chose not to continue after reading script"
msgstr ""
#: internal/cliutils/prompt.go:111
msgid "Error prompting for choice of package"
msgstr ""
#: internal/cliutils/prompt.go:135
msgid "Choose which package to %s"
msgstr ""
#: internal/cliutils/prompt.go:156
msgid "Choose which optional package(s) to install"
msgstr ""
#: internal/cliutils/template.go:74 internal/cliutils/template.go:93
msgid "NAME"
msgstr ""
#: internal/cliutils/template.go:74 internal/cliutils/template.go:94
msgid "USAGE"
msgstr ""
#: internal/cliutils/template.go:74
msgid "global options"
msgstr ""
#: internal/cliutils/template.go:74
msgid "command"
msgstr ""
#: internal/cliutils/template.go:74 internal/cliutils/template.go:95
msgid "command options"
msgstr ""
#: internal/cliutils/template.go:74 internal/cliutils/template.go:96
msgid "arguments"
msgstr ""
#: internal/cliutils/template.go:74
msgid "VERSION"
msgstr ""
#: internal/cliutils/template.go:74 internal/cliutils/template.go:98
msgid "DESCRIPTION"
msgstr ""
#: internal/cliutils/template.go:74
msgid "AUTHOR"
msgstr ""
#: internal/cliutils/template.go:74
msgid "COMMANDS"
msgstr ""
#: internal/cliutils/template.go:74
msgid "GLOBAL OPTIONS"
msgstr ""
#: internal/cliutils/template.go:74
msgid "COPYRIGHT"
msgstr ""
#: internal/cliutils/template.go:97
msgid "CATEGORY"
msgstr ""
#: internal/cliutils/template.go:99 internal/cliutils/template.go:100
msgid "OPTIONS"
msgstr ""
#: internal/config/config.go:59
msgid "Error opening config file, using defaults"
msgstr ""
#: internal/config/config.go:72
msgid "Error decoding config file, using defaults"
msgstr ""
#: internal/config/config.go:84
msgid "Unable to detect user config directory"
msgstr ""
#: internal/config/config.go:92
msgid "Unable to create ALR config directory"
msgstr ""
#: internal/config/config.go:101
msgid "Unable to create ALR config file"
msgstr ""
#: internal/config/config.go:107
msgid "Error encoding default configuration"
msgstr ""
#: internal/config/config.go:116
msgid "Unable to detect cache directory"
msgstr ""
#: internal/config/config.go:126
msgid "Unable to create repo cache directory"
msgstr ""
#: internal/config/config.go:132
msgid "Unable to create package cache directory"
msgstr ""
#: internal/db/db.go:133
msgid "Database version mismatch; resetting"
msgstr ""
#: internal/db/db.go:140
msgid ""
"Database version does not exist. Run alr fix if something isn't working."
msgstr ""
#: internal/dl/dl.go:170
msgid "Source can be updated, updating if required"
msgstr ""
#: internal/dl/dl.go:201
msgid "Source found in cache and linked to destination"
msgstr ""
#: internal/dl/dl.go:208
msgid "Source updated and linked to destination"
msgstr ""
#: internal/dl/dl.go:222
msgid "Downloading source"
msgstr ""
#: internal/dl/progress_tui.go:100
msgid "%s: done!\n"
msgstr ""
#: internal/dl/progress_tui.go:104
msgid "%s %s downloading at %s/s\n"
msgstr ""
#: internal/logger/log.go:47
msgid "ERROR"
msgstr ""
#: list.go:41
msgid "List ALR repo packages"
msgstr ""
#: list.go:92
msgid "Error listing installed packages"
msgstr ""
#: main.go:45
msgid "Print the current ALR version and exit"
msgstr ""
#: main.go:61
msgid "Arguments to be passed on to the package manager"
msgstr ""
#: main.go:67
msgid "Enable interactive questions and prompts"
msgstr ""
#: main.go:92
msgid ""
"Running ALR as root is forbidden as it may cause catastrophic damage to your "
"system"
msgstr ""
#: main.go:125
msgid "Show help"
msgstr ""
#: main.go:129
msgid "Error while running app"
msgstr ""
#: pkg/build/build.go:156
msgid "Failed to prompt user to view build script"
msgstr ""
#: pkg/build/build.go:160
msgid "Building package"
msgstr ""
#: pkg/build/build.go:208
msgid "The checksums array must be the same length as sources"
msgstr ""
#: pkg/build/build.go:235
msgid "Downloading sources"
msgstr ""
#: pkg/build/build.go:257
msgid "Building package metadata"
msgstr ""
#: pkg/build/build.go:279
msgid "Compressing package"
msgstr ""
#: pkg/build/build.go:438
msgid ""
"Your system's CPU architecture doesn't match this package. Do you want to "
"build anyway?"
msgstr ""
#: pkg/build/build.go:452
msgid "This package is already installed"
msgstr ""
#: pkg/build/build.go:476
msgid "Installing build dependencies"
msgstr ""
#: pkg/build/build.go:517
msgid "Installing dependencies"
msgstr ""
#: pkg/build/build.go:598
msgid "Would you like to remove the build dependencies?"
msgstr ""
#: pkg/build/build.go:661
msgid "Executing prepare()"
msgstr ""
#: pkg/build/build.go:671
msgid "Executing build()"
msgstr ""
#: pkg/build/build.go:701 pkg/build/build.go:721
msgid "Executing %s()"
msgstr ""
#: pkg/build/build.go:780
msgid "Error installing native packages"
msgstr ""
#: pkg/build/build.go:804
msgid "Error installing package"
msgstr ""
#: pkg/build/build.go:863
msgid "AutoProv is not implemented for this package format, so it's skipped"
msgstr ""
#: pkg/build/build.go:874
msgid "AutoReq is not implemented for this package format, so it's skipped"
msgstr ""
#: pkg/build/findDeps.go:35
msgid "Command not found on the system"
msgstr ""
#: pkg/build/findDeps.go:82
msgid "Provided dependency found"
msgstr ""
#: pkg/build/findDeps.go:89
msgid "Required dependency found"
msgstr ""
#: pkg/repos/pull.go:79
msgid "Pulling repository"
msgstr ""
#: pkg/repos/pull.go:103
msgid "Repository up to date"
msgstr ""
#: pkg/repos/pull.go:160
msgid "Git repository does not appear to be a valid ALR repo"
msgstr ""
#: pkg/repos/pull.go:176
msgid ""
"ALR repo's minimum ALR version is greater than the current version. Try "
"updating ALR if something doesn't work."
msgstr ""
#: repo.go:41
msgid "Add a new repository"
msgstr ""
#: repo.go:48
msgid "Name of the new repo"
msgstr ""
#: repo.go:54
msgid "URL of the new repo"
msgstr ""
#: repo.go:82 repo.go:147
msgid "Error opening config file"
msgstr ""
#: repo.go:88 repo.go:153
msgid "Error encoding config"
msgstr ""
#: repo.go:113
msgid "Remove an existing repository"
msgstr ""
#: repo.go:120
msgid "Name of the repo to be deleted"
msgstr ""
#: repo.go:139
msgid "Repo does not exist"
msgstr ""
#: repo.go:159
msgid "Error removing repo directory"
msgstr ""
#: repo.go:170
msgid "Error removing packages from database"
msgstr ""
#: repo.go:182
msgid "Pull all repositories that have changed"
msgstr ""
#: search.go:36
msgid "Search packages"
msgstr ""
#: search.go:42
msgid "Search by name"
msgstr ""
#: search.go:47
msgid "Search by description"
msgstr ""
#: search.go:52
msgid "Search by repository"
msgstr ""
#: search.go:57
msgid "Search by provides"
msgstr ""
#: search.go:62
msgid "Format output using a Go template"
msgstr ""
#: search.go:82 search.go:99
msgid "Error parsing format template"
msgstr ""
#: search.go:107
msgid "Error executing template"
msgstr ""
#: upgrade.go:47
msgid "Upgrade all installed packages"
msgstr ""
#: upgrade.go:90
msgid "Error checking for updates"
msgstr ""
#: upgrade.go:112
msgid "There is nothing to do."
msgstr ""

View File

@ -1,155 +0,0 @@
[[translation]]
id = 1228660974
value = 'Pulling repository'
[[translation]]
id = 2779805870
value = 'Repository up to date'
[[translation]]
id = 1433222829
value = 'Would you like to view the build script for'
[[translation]]
id = 2470847050
value = 'Failed to prompt user to view build script'
[[translation]]
id = 855659503
value = 'Would you still like to continue?'
[[translation]]
id = 1997041569
value = 'User chose not to continue after reading script'
[[translation]]
id = 2347700990
value = 'Building package'
[[translation]]
id = 2105058868
value = 'Downloading sources'
[[translation]]
id = 1884485082
value = 'Downloading source'
[[translation]]
id = 1519177982
value = 'Error building package'
[[translation]]
id = 2125220917
value = 'Choose which package(s) to install'
[[translation]]
id = 812531604
value = 'Error prompting for choice of package'
[[translation]]
id = 1040982801
value = 'Updating version'
[[translation]]
id = 1014897988
value = 'Remove build dependencies?'
[[translation]]
id = 2205430948
value = 'Installing build dependencies'
[[translation]]
id = 2522710805
value = 'Installing dependencies'
[[translation]]
id = 3602138206
value = 'Error installing package'
[[translation]]
id = 2235794125
value = 'Would you like to remove build dependencies?'
[[translation]]
id = 2562049386
value = "Your system's CPU architecture doesn't match this package. Do you want to build anyway?"
[[translation]]
id = 4006393493
value = 'The checksums array must be the same length as sources'
[[translation]]
id = 3759891273
value = 'The package() function is required'
[[translation]]
id = 1057080231
value = 'Executing package()'
[[translation]]
id = 2687735200
value = 'Executing prepare()'
[[translation]]
id = 535572372
value = 'Executing version()'
[[translation]]
id = 436644691
value = 'Executing build()'
[[translation]]
id = 1393316459
value = 'This package is already installed'
[[translation]]
id = 1267660189
value = 'Source can be updated, updating if required'
[[translation]]
id = 21753247
value = 'Source found in cache, linked to destination'
[[translation]]
id = 257354570
value = 'Compressing package'
[[translation]]
id = 2952487371
value = 'Building package metadata'
[[translation]]
id = 3121791194
value = 'Running ALR as root is forbidden as it may cause catastrophic damage to your system'
[[translation]]
id = 1256604213
value = 'Waiting for torrent metadata'
[[translation]]
id = 432261354
value = 'Downloading torrent file'
[[translation]]
id = 1579384326
value = 'name'
[[translation]]
id = 3206337475
value = 'version'
[[translation]]
id = 1810056261
value = 'new'
[[translation]]
id = 1602912115
value = 'source'
[[translation]]
id = 2363381545
value = 'type'
[[translation]]
id = 3419504365
value = 'downloader'

View File

@ -1,151 +0,0 @@
[[translation]]
id = 1228660974
value = 'Скачивание репозитория'
[[translation]]
id = 2779805870
value = 'Репозиторий уже обновлен'
[[translation]]
id = 1433222829
value = 'Показать скрипт для пакета'
[[translation]]
id = 2470847050
value = 'Не удалось предложить просмотреть скрипт'
[[translation]]
id = 855659503
value = 'Продолжить?'
[[translation]]
id = 1997041569
value = 'Пользователь решил не продолжать после просмотра скрипта'
[[translation]]
id = 2347700990
value = 'Сборка пакета'
[[translation]]
id = 2105058868
value = 'Скачивание файлов'
[[translation]]
id = 1884485082
value = 'Скачивание источника'
[[translation]]
id = 1519177982
value = 'Ошибка при сборке пакета'
[[translation]]
id = 2125220917
value = 'Выберите, какие пакеты установить'
[[translation]]
id = 812531604
value = 'Ошибка при запросе выбора пакета'
[[translation]]
id = 1040982801
value = 'Обновление версии'
[[translation]]
id = 2235794125
value = 'Удалить зависимости сборки?'
[[translation]]
id = 2205430948
value = 'Установка зависимостей сборки'
[[translation]]
id = 2522710805
value = 'Установка зависимостей'
[[translation]]
id = 3602138206
value = 'Ошибка при установке пакета'
[[translation]]
id = 1057080231
value = 'Вызов функции package()'
[[translation]]
id = 2687735200
value = 'Вызов функции prepare()'
[[translation]]
id = 535572372
value = 'Вызов функции version()'
[[translation]]
id = 436644691
value = 'Вызов функции build()'
[[translation]]
id = 2562049386
value = "Архитектура процессора вашей системы не соответствует этому пакету. Продолжать несмотря на это?"
[[translation]]
id = 3759891273
value = 'Функция package() необходима'
[[translation]]
id = 4006393493
value = 'Массив checksums должен быть той же длины, что и sources'
[[translation]]
id = 1393316459
value = 'Этот пакет уже установлен'
[[translation]]
id = 1267660189
value = 'Источник может быть обновлен, если требуется, обновляем'
[[translation]]
id = 21753247
value = 'Источник найден в кэше'
[[translation]]
id = 257354570
value = 'Сжатие пакета'
[[translation]]
id = 2952487371
value = 'Создание метаданных пакета'
[[translation]]
id = 3121791194
value = 'Запуск ALR от имени root запрещен, так как это может привести к катастрофическому повреждению вашей системы'
[[translation]]
id = 1256604213
value = 'Ожидание метаданных торрента'
[[translation]]
id = 432261354
value = 'Скачивание торрент-файла'
[[translation]]
id = 1579384326
value = 'название'
[[translation]]
id = 3206337475
value = 'версия'
[[translation]]
id = 1810056261
value = 'новая'
[[translation]]
id = 1602912115
value = 'источник'
[[translation]]
id = 2363381545
value = 'вид'
[[translation]]
id = 3419504365
value = 'протокол-скачивание'

View File

@ -0,0 +1,585 @@
#
# x1z53 <x1z53@yandex.ru>, 2025.
# Maxim Slipenko <maks1ms@alt-gnome.ru>, 2025.
#
msgid ""
msgstr ""
"Project-Id-Version: unnamed project\n"
"PO-Revision-Date: 2025-03-09 17:31+0300\n"
"Last-Translator: Maxim Slipenko <maks1ms@alt-gnome.ru>\n"
"Language-Team: Russian\n"
"Language: ru\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
"X-Generator: Gtranslator 47.1\n"
#: build.go:44
msgid "Build a local package"
msgstr "Сборка локального пакета"
#: build.go:50
msgid "Path to the build script"
msgstr "Путь к скрипту сборки"
#: build.go:55
msgid "Specify subpackage in script (for multi package script only)"
msgstr "Укажите подпакет в скрипте (только для многопакетного скрипта)"
#: build.go:60
msgid "Name of the package to build and its repo (example: default/go-bin)"
msgstr "Имя пакета для сборки и его репозиторий (пример: default/go-bin)"
#: build.go:65
msgid ""
"Build package from scratch even if there's an already built package available"
msgstr "Создайте пакет с нуля, даже если уже имеется готовый пакет"
#: build.go:75
msgid "Error initialization database"
msgstr "Ошибка инициализации базы данных"
#: build.go:104
msgid "Package not found"
msgstr "Пакет не найден"
#: build.go:124
msgid "Error pulling repositories"
msgstr "Ошибка при извлечении репозиториев"
#: build.go:132
msgid "Unable to detect a supported package manager on the system"
msgstr "Не удалось обнаружить поддерживаемый менеджер пакетов в системе"
#: build.go:138
msgid "Error parsing os release"
msgstr "Ошибка при разборе файла выпуска операционной системы"
#: build.go:160
msgid "Error building package"
msgstr "Ошибка при сборке пакета"
#: build.go:167
msgid "Error getting working directory"
msgstr "Ошибка при получении рабочего каталога"
#: build.go:176
msgid "Error moving the package"
msgstr "Ошибка при перемещении пакета"
#: fix.go:37
msgid "Attempt to fix problems with ALR"
msgstr "Попытка устранить проблемы с ALR"
#: fix.go:43
msgid "Removing cache directory"
msgstr "Удаление каталога кэша"
#: fix.go:47
msgid "Unable to remove cache directory"
msgstr "Не удалось удалить каталог кэша"
#: fix.go:51
msgid "Rebuilding cache"
msgstr "Восстановление кэша"
#: fix.go:55
msgid "Unable to create new cache directory"
msgstr "Не удалось создать новый каталог кэша"
#: fix.go:69
msgid "Error pulling repos"
msgstr "Ошибка при извлечении репозиториев"
#: fix.go:73
msgid "Done"
msgstr "Сделано"
#: gen.go:34
msgid "Generate a ALR script from a template"
msgstr "Генерация скрипта ALR из шаблона"
#: gen.go:39
msgid "Generate a ALR script for a pip module"
msgstr "Генерация скрипта ALR для модуля pip"
#: helper.go:41
msgid "List all the available helper commands"
msgstr "Список всех доступных вспомогательных команды"
#: helper.go:53
msgid "Run a ALR helper command"
msgstr "Запустить вспомогательную команду ALR"
#: helper.go:60
msgid "The directory that the install commands will install to"
msgstr "Каталог, в который будут устанавливать команды установки"
#: helper.go:73
msgid "No such helper command"
msgstr "Такой вспомогательной команды нет"
#: info.go:43
msgid "Print information about a package"
msgstr "Отобразить информацию о пакете"
#: info.go:48
msgid "Show all information, not just for the current distro"
msgstr "Показывать всю информацию, не только для текущего дистрибутива"
#: info.go:63
msgid "Error getting packages"
msgstr "Ошибка при получении пакетов"
#: info.go:72
msgid "Error iterating over packages"
msgstr "Ошибка при переборе пакетов"
#: info.go:93
msgid "Command info expected at least 1 argument, got %d"
msgstr "Для команды info ожидался хотя бы 1 аргумент, получено %d"
#: info.go:107
msgid "Error finding packages"
msgstr "Ошибка при поиске пакетов"
#: info.go:129
msgid "Error parsing os-release file"
msgstr "Ошибка при разборе файла выпуска операционной системы"
#: info.go:138
msgid "Error resolving overrides"
msgstr "Ошибка устранения переорпеделений"
#: info.go:147 info.go:153
msgid "Error encoding script variables"
msgstr "Ошибка кодирования переменных скрита"
#: install.go:43
msgid "Install a new package"
msgstr "Установить новый пакет"
#: install.go:57
msgid "Command install expected at least 1 argument, got %d"
msgstr "Для команды install ожидался хотя бы 1 аргумент, получено %d"
#: install.go:151
msgid "Remove an installed package"
msgstr "Удалить установленный пакет"
#: install.go:156
msgid "Command remove expected at least 1 argument, got %d"
msgstr "Для команды remove ожидался хотя бы 1 аргумент, получено %d"
#: install.go:168
msgid "Error removing packages"
msgstr "Ошибка при удалении пакетов"
#: internal/cliutils/prompt.go:60
msgid "Would you like to view the build script for %s"
msgstr "Показать скрипт для пакета %s"
#: internal/cliutils/prompt.go:71
msgid "Would you still like to continue?"
msgstr "Продолжить?"
#: internal/cliutils/prompt.go:77
msgid "User chose not to continue after reading script"
msgstr "Пользователь решил не продолжать после просмотра скрипта"
#: internal/cliutils/prompt.go:111
msgid "Error prompting for choice of package"
msgstr "Ошибка при запросе выбора пакета"
#: internal/cliutils/prompt.go:135
msgid "Choose which package to %s"
msgstr "Выберите, какой пакет использовать для %s"
#: internal/cliutils/prompt.go:156
msgid "Choose which optional package(s) to install"
msgstr "Выберите, какой дополнительный пакет(ы) следует установить"
#: internal/cliutils/template.go:74 internal/cliutils/template.go:93
msgid "NAME"
msgstr "НАЗВАНИЕ"
#: internal/cliutils/template.go:74 internal/cliutils/template.go:94
msgid "USAGE"
msgstr "ИСПОЛЬЗОВАНИЕ"
#: internal/cliutils/template.go:74
msgid "global options"
msgstr "глобальные опции"
#: internal/cliutils/template.go:74
msgid "command"
msgstr "команда"
#: internal/cliutils/template.go:74 internal/cliutils/template.go:95
msgid "command options"
msgstr "опции команды"
#: internal/cliutils/template.go:74 internal/cliutils/template.go:96
msgid "arguments"
msgstr "аргументы"
#: internal/cliutils/template.go:74
msgid "VERSION"
msgstr "ВЕРСИЯ"
#: internal/cliutils/template.go:74 internal/cliutils/template.go:98
msgid "DESCRIPTION"
msgstr "ОПИСАНИЕ"
#: internal/cliutils/template.go:74
msgid "AUTHOR"
msgstr "АВТОР"
#: internal/cliutils/template.go:74
msgid "COMMANDS"
msgstr "КОМАНДЫ"
#: internal/cliutils/template.go:74
msgid "GLOBAL OPTIONS"
msgstr "ГЛОБАЛЬНЫЕ ОПЦИИ"
#: internal/cliutils/template.go:74
msgid "COPYRIGHT"
msgstr "АВТОРСКОЕ ПРАВО"
#: internal/cliutils/template.go:97
msgid "CATEGORY"
msgstr "КАТЕГОРИЯ"
#: internal/cliutils/template.go:99 internal/cliutils/template.go:100
msgid "OPTIONS"
msgstr "ПАРАМЕТРЫ"
#: internal/config/config.go:59
msgid "Error opening config file, using defaults"
msgstr ""
"Ошибка при открытии конфигурационного файла, используются значения по "
"умолчанию"
#: internal/config/config.go:72
msgid "Error decoding config file, using defaults"
msgstr ""
"Ошибка при декодировании конфигурационного файла, используются значения по "
"умолчанию"
#: internal/config/config.go:84
msgid "Unable to detect user config directory"
msgstr "Не удалось обнаружить каталог конфигурации пользователя"
#: internal/config/config.go:92
msgid "Unable to create ALR config directory"
msgstr "Не удалось создать каталог конфигурации ALR"
#: internal/config/config.go:101
msgid "Unable to create ALR config file"
msgstr "Не удалось создать конфигурационный файл ALR"
#: internal/config/config.go:107
msgid "Error encoding default configuration"
msgstr "Ошибка кодирования конфигурации по умолчанию"
#: internal/config/config.go:116
msgid "Unable to detect cache directory"
msgstr "Не удалось обнаружить каталог кэша"
#: internal/config/config.go:126
msgid "Unable to create repo cache directory"
msgstr "Не удалось создать каталог кэша репозитория"
#: internal/config/config.go:132
msgid "Unable to create package cache directory"
msgstr "Не удалось создать каталог кэша пакетов"
#: internal/db/db.go:133
msgid "Database version mismatch; resetting"
msgstr "Несоответствие версий базы данных; сброс настроек"
#: internal/db/db.go:140
msgid ""
"Database version does not exist. Run alr fix if something isn't working."
msgstr ""
"Версия базы данных не существует. Запустите alr fix, если что-то не работает."
#: internal/dl/dl.go:170
msgid "Source can be updated, updating if required"
msgstr "Исходный код можно обновлять, обновляя при необходимости"
#: internal/dl/dl.go:201
msgid "Source found in cache and linked to destination"
msgstr "Источник найден в кэше и связан с пунктом назначения"
#: internal/dl/dl.go:208
msgid "Source updated and linked to destination"
msgstr "Источник обновлён и связан с пунктом назначения"
#: internal/dl/dl.go:222
msgid "Downloading source"
msgstr "Скачивание источника"
#: internal/dl/progress_tui.go:100
msgid "%s: done!\n"
msgstr "%s: выполнено!\n"
#: internal/dl/progress_tui.go:104
msgid "%s %s downloading at %s/s\n"
msgstr "%s %s загружается — %s/с\n"
#: internal/logger/log.go:47
msgid "ERROR"
msgstr "ОШИБКА"
#: list.go:41
msgid "List ALR repo packages"
msgstr "Список пакетов репозитория ALR"
#: list.go:92
msgid "Error listing installed packages"
msgstr "Ошибка при составлении списка установленных пакетов"
#: main.go:45
msgid "Print the current ALR version and exit"
msgstr "Показать текущую версию ALR и выйти"
#: main.go:61
msgid "Arguments to be passed on to the package manager"
msgstr "Аргументы, которые будут переданы менеджеру пакетов"
#: main.go:67
msgid "Enable interactive questions and prompts"
msgstr "Включение интерактивных вопросов и запросов"
#: main.go:92
msgid ""
"Running ALR as root is forbidden as it may cause catastrophic damage to your "
"system"
msgstr ""
"Запуск ALR от имени root запрещён, так как это может привести к "
"катастрофическому повреждению вашей системы"
#: main.go:125
msgid "Show help"
msgstr "Показать справку"
#: main.go:129
msgid "Error while running app"
msgstr "Ошибка при запуске приложения"
#: pkg/build/build.go:156
msgid "Failed to prompt user to view build script"
msgstr "Не удалось предложить пользователю просмотреть скрипт сборки"
#: pkg/build/build.go:160
msgid "Building package"
msgstr "Сборка пакета"
#: pkg/build/build.go:208
msgid "The checksums array must be the same length as sources"
msgstr "Массив контрольных сумм должен быть той же длины, что и источники"
#: pkg/build/build.go:235
msgid "Downloading sources"
msgstr "Скачивание источников"
#: pkg/build/build.go:257
msgid "Building package metadata"
msgstr "Сборка метаданных пакета"
#: pkg/build/build.go:279
msgid "Compressing package"
msgstr "Сжатие пакета"
#: pkg/build/build.go:438
msgid ""
"Your system's CPU architecture doesn't match this package. Do you want to "
"build anyway?"
msgstr ""
"Архитектура процессора вашей системы не соответствует этому пакету. Вы все "
"равно хотите выполнить сборку?"
#: pkg/build/build.go:452
msgid "This package is already installed"
msgstr "Этот пакет уже установлен"
#: pkg/build/build.go:476
msgid "Installing build dependencies"
msgstr "Установка зависимостей сборки"
#: pkg/build/build.go:517
msgid "Installing dependencies"
msgstr "Установка зависимостей"
#: pkg/build/build.go:598
msgid "Would you like to remove the build dependencies?"
msgstr "Хотели бы вы удалить зависимости сборки?"
#: pkg/build/build.go:661
msgid "Executing prepare()"
msgstr "Исполнение prepare()"
#: pkg/build/build.go:671
msgid "Executing build()"
msgstr "Исполнение build()"
#: pkg/build/build.go:701 pkg/build/build.go:721
msgid "Executing %s()"
msgstr "Исполнение %s()"
#: pkg/build/build.go:780
msgid "Error installing native packages"
msgstr "Ошибка при установке нативных пакетов"
#: pkg/build/build.go:804
msgid "Error installing package"
msgstr "Ошибка при установке пакета"
#: pkg/build/build.go:863
msgid "AutoProv is not implemented for this package format, so it's skipped"
msgstr ""
"AutoProv не реализовано для этого формата пакета, поэтому будет пропущено"
#: pkg/build/build.go:874
msgid "AutoReq is not implemented for this package format, so it's skipped"
msgstr ""
"AutoReq не реализовано для этого формата пакета, поэтому будет пропущено"
#: pkg/build/findDeps.go:35
msgid "Command not found on the system"
msgstr "Команда не найдена в системе"
#: pkg/build/findDeps.go:82
msgid "Provided dependency found"
msgstr "Найденная предоставленная зависимость"
#: pkg/build/findDeps.go:89
msgid "Required dependency found"
msgstr "Найдена требуемая зависимость"
#: pkg/repos/pull.go:79
msgid "Pulling repository"
msgstr "Скачивание репозитория"
#: pkg/repos/pull.go:103
msgid "Repository up to date"
msgstr "Репозиторий уже обновлён"
#: pkg/repos/pull.go:160
msgid "Git repository does not appear to be a valid ALR repo"
msgstr "Репозиторий Git не поддерживается репозиторием ALR"
#: pkg/repos/pull.go:176
msgid ""
"ALR repo's minimum ALR version is greater than the current version. Try "
"updating ALR if something doesn't work."
msgstr ""
"Минимальная версия ALR для ALR-репозитория выше текущей версии. Попробуйте "
"обновить ALR, если что-то не работает."
#: repo.go:41
msgid "Add a new repository"
msgstr "Добавить новый репозиторий"
#: repo.go:48
msgid "Name of the new repo"
msgstr "Название нового репозитория"
#: repo.go:54
msgid "URL of the new repo"
msgstr "URL-адрес нового репозитория"
#: repo.go:82 repo.go:147
msgid "Error opening config file"
msgstr "Ошибка при открытии конфигурационного файла"
#: repo.go:88 repo.go:153
msgid "Error encoding config"
msgstr "Ошибка при кодировании конфигурации"
#: repo.go:113
msgid "Remove an existing repository"
msgstr "Удалить существующий репозиторий"
#: repo.go:120
msgid "Name of the repo to be deleted"
msgstr "Название репозитория удалён"
#: repo.go:139
msgid "Repo does not exist"
msgstr "Репозитория не существует"
#: repo.go:159
msgid "Error removing repo directory"
msgstr "Ошибка при удалении каталога репозитория"
#: repo.go:170
msgid "Error removing packages from database"
msgstr "Ошибка при удалении пакетов из базы данных"
#: repo.go:182
msgid "Pull all repositories that have changed"
msgstr "Скачать все изменённые репозитории"
#: search.go:36
msgid "Search packages"
msgstr "Поиск пакетов"
#: search.go:42
msgid "Search by name"
msgstr "Искать по имени"
#: search.go:47
msgid "Search by description"
msgstr "Искать по описанию"
#: search.go:52
msgid "Search by repository"
msgstr "Искать по репозиторию"
#: search.go:57
msgid "Search by provides"
msgstr "Иcкать по provides"
#: search.go:62
msgid "Format output using a Go template"
msgstr "Формат выходных данных с использованием шаблона Go"
#: search.go:82 search.go:99
msgid "Error parsing format template"
msgstr "Ошибка при разборе шаблона"
#: search.go:107
msgid "Error executing template"
msgstr "Ошибка при выполнении шаблона"
#: upgrade.go:47
msgid "Upgrade all installed packages"
msgstr "Обновить все установленные пакеты"
#: upgrade.go:90
msgid "Error checking for updates"
msgstr "Ошибка при проверке обновлений"
#: upgrade.go:112
msgid "There is nothing to do."
msgstr "Здесь нечего делать."
#~ msgid "Error parsing system language"
#~ msgstr "Ошибка при парсинге языка системы"
#~ msgid "Error opening database"
#~ msgstr "Ошибка при открытии базы данных"
#~ msgid "Executing version()"
#~ msgstr "Исполнение версия()"
#~ msgid "Updating version"
#~ msgstr "Обновление версии"
#~ msgid "Executing package()"
#~ msgstr "Исполнение package()"

View File

@ -1,56 +1,52 @@
/* // This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
* ALR - Any Linux Repository // It has been modified as part of "ALR - Any Linux Repository" by Евгений Храмов.
* Copyright (C) 2024 Евгений Храмов //
* // ALR - Any Linux Repository
* This program is free software: you can redistribute it and/or modify // Copyright (C) 2025 Евгений Храмов
* it under the terms of the GNU General Public License as published by //
* the Free Software Foundation, either version 3 of the License, or // This program is free software: you can redistribute it and/or modify
* (at your option) any later version. // it under the terms of the GNU General Public License as published by
* // the Free Software Foundation, either version 3 of the License, or
* This program is distributed in the hope that it will be useful, // (at your option) any later version.
* but WITHOUT ANY WARRANTY; without even the implied warranty of //
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // This program is distributed in the hope that it will be useful,
* GNU General Public License for more details. // but WITHOUT ANY WARRANTY; without even the implied warranty of
* // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* You should have received a copy of the GNU General Public License // GNU General Public License for more details.
* along with this program. If not, see <http://www.gnu.org/licenses/>. //
*/ // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package translations package translations
import ( import (
"context"
"embed" "embed"
"sync" "io/fs"
"os"
"path"
"go.elara.ws/logger" "github.com/jeandeaual/go-locale"
"plemya-x.ru/alr/pkg/loggerctx" "github.com/leonelquinteros/gotext"
"go.elara.ws/translate"
"golang.org/x/text/language"
) )
//go:embed files //go:embed po
var translationFS embed.FS var poFS embed.FS
var ( func Setup() {
mu sync.Mutex userLanguage, err := locale.GetLanguage()
translator *translate.Translator
)
func Translator(ctx context.Context) *translate.Translator {
mu.Lock()
defer mu.Unlock()
log := loggerctx.From(ctx)
if translator == nil {
t, err := translate.NewFromFS(translationFS)
if err != nil { if err != nil {
log.Fatal("Error creating new translator").Err(err).Send() panic(err)
} }
translator = &t
}
return translator
}
func NewLogger(ctx context.Context, l logger.Logger, lang language.Tag) *translate.TranslatedLogger { _, err = fs.Stat(poFS, path.Join("po", userLanguage))
return translate.NewLogger(l, *Translator(ctx), lang) if err != nil {
if os.IsNotExist(err) {
return
}
panic(err)
}
loc := gotext.NewLocaleFSWithPath(userLanguage, &poFS, "po")
loc.SetDomain("default")
gotext.SetLocales([]*gotext.Locale{loc})
} }

View File

@ -1,32 +1,84 @@
/* // This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
* ALR - Any Linux Repository // It has been modified as part of "ALR - Any Linux Repository" by Евгений Храмов.
* Copyright (C) 2024 Евгений Храмов //
* // ALR - Any Linux Repository
* This program is free software: you can redistribute it and/or modify // Copyright (C) 2025 Евгений Храмов
* it under the terms of the GNU General Public License as published by //
* the Free Software Foundation, either version 3 of the License, or // This program is free software: you can redistribute it and/or modify
* (at your option) any later version. // it under the terms of the GNU General Public License as published by
* // the Free Software Foundation, either version 3 of the License, or
* This program is distributed in the hope that it will be useful, // (at your option) any later version.
* but WITHOUT ANY WARRANTY; without even the implied warranty of //
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // This program is distributed in the hope that it will be useful,
* GNU General Public License for more details. // but WITHOUT ANY WARRANTY; without even the implied warranty of
* // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* You should have received a copy of the GNU General Public License // GNU General Public License for more details.
* along with this program. If not, see <http://www.gnu.org/licenses/>. //
*/ // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package types package types
import "plemya-x.ru/alr/pkg/manager" import "gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager"
type BuildOpts struct { type BuildOpts struct {
Script string Script string
Repository string
Packages []string
Manager manager.Manager Manager manager.Manager
Clean bool Clean bool
Interactive bool Interactive bool
} }
type BuildVarsPre struct {
Version string `sh:"version,required"`
Release int `sh:"release,required"`
Epoch uint `sh:"epoch"`
Description string `sh:"desc"`
Homepage string `sh:"homepage"`
Maintainer string `sh:"maintainer"`
Architectures []string `sh:"architectures"`
Licenses []string `sh:"license"`
Provides []string `sh:"provides"`
Conflicts []string `sh:"conflicts"`
Depends []string `sh:"deps"`
BuildDepends []string `sh:"build_deps"`
OptDepends []string `sh:"opt_deps"`
Replaces []string `sh:"replaces"`
Sources []string `sh:"sources"`
Checksums []string `sh:"checksums"`
Backup []string `sh:"backup"`
Scripts Scripts `sh:"scripts"`
AutoReq []string `sh:"auto_req"`
AutoProv []string `sh:"auto_prov"`
}
func (bv *BuildVarsPre) ToBuildVars() BuildVars {
return BuildVars{
Name: "",
Version: bv.Version,
Release: bv.Release,
Epoch: bv.Epoch,
Description: bv.Description,
Homepage: bv.Homepage,
Maintainer: bv.Maintainer,
Architectures: bv.Architectures,
Licenses: bv.Licenses,
Provides: bv.Provides,
Conflicts: bv.Conflicts,
Depends: bv.Depends,
BuildDepends: bv.BuildDepends,
OptDepends: bv.OptDepends,
Replaces: bv.Replaces,
Sources: bv.Sources,
Checksums: bv.Checksums,
Backup: bv.Backup,
Scripts: bv.Scripts,
AutoReq: bv.AutoReq,
AutoProv: bv.AutoProv,
}
}
// BuildVars represents the script variables required // BuildVars represents the script variables required
// to build a package // to build a package
type BuildVars struct { type BuildVars struct {
@ -49,6 +101,9 @@ type BuildVars struct {
Checksums []string `sh:"checksums"` Checksums []string `sh:"checksums"`
Backup []string `sh:"backup"` Backup []string `sh:"backup"`
Scripts Scripts `sh:"scripts"` Scripts Scripts `sh:"scripts"`
AutoReq []string `sh:"auto_req"`
AutoProv []string `sh:"auto_prov"`
Base string
} }
type Scripts struct { type Scripts struct {

View File

@ -1,20 +1,21 @@
/* // This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
* ALR - Any Linux Repository // It has been modified as part of "ALR - Any Linux Repository" by Евгений Храмов.
* Copyright (C) 2024 Евгений Храмов //
* // ALR - Any Linux Repository
* This program is free software: you can redistribute it and/or modify // Copyright (C) 2025 Евгений Храмов
* it under the terms of the GNU General Public License as published by //
* the Free Software Foundation, either version 3 of the License, or // This program is free software: you can redistribute it and/or modify
* (at your option) any later version. // it under the terms of the GNU General Public License as published by
* // the Free Software Foundation, either version 3 of the License, or
* This program is distributed in the hope that it will be useful, // (at your option) any later version.
* but WITHOUT ANY WARRANTY; without even the implied warranty of //
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // This program is distributed in the hope that it will be useful,
* GNU General Public License for more details. // but WITHOUT ANY WARRANTY; without even the implied warranty of
* // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* You should have received a copy of the GNU General Public License // GNU General Public License for more details.
* along with this program. If not, see <http://www.gnu.org/licenses/>. //
*/ // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package types package types
@ -25,6 +26,7 @@ type Config struct {
IgnorePkgUpdates []string `toml:"ignorePkgUpdates"` IgnorePkgUpdates []string `toml:"ignorePkgUpdates"`
Repos []Repo `toml:"repo"` Repos []Repo `toml:"repo"`
Unsafe Unsafe `toml:"unsafe"` Unsafe Unsafe `toml:"unsafe"`
AutoPull bool `toml:"autoPull"`
} }
// Repo represents a ALR repo within a configuration file // Repo represents a ALR repo within a configuration file

View File

@ -1,20 +1,21 @@
/* // This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
* ALR - Any Linux Repository // It has been modified as part of "ALR - Any Linux Repository" by Евгений Храмов.
* Copyright (C) 2024 Евгений Храмов //
* // ALR - Any Linux Repository
* This program is free software: you can redistribute it and/or modify // Copyright (C) 2025 Евгений Храмов
* it under the terms of the GNU General Public License as published by //
* the Free Software Foundation, either version 3 of the License, or // This program is free software: you can redistribute it and/or modify
* (at your option) any later version. // it under the terms of the GNU General Public License as published by
* // the Free Software Foundation, either version 3 of the License, or
* This program is distributed in the hope that it will be useful, // (at your option) any later version.
* but WITHOUT ANY WARRANTY; without even the implied warranty of //
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // This program is distributed in the hope that it will be useful,
* GNU General Public License for more details. // but WITHOUT ANY WARRANTY; without even the implied warranty of
* // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* You should have received a copy of the GNU General Public License // GNU General Public License for more details.
* along with this program. If not, see <http://www.gnu.org/licenses/>. //
*/ // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package types package types

View File

@ -0,0 +1,18 @@
This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
It has been modified as part of "ALR - Any Linux Repository" by Евгений Храмов.
ALR - Any Linux Repository
Copyright (C) {{ .Year }} Евгений Храмов
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/>.

15
license-header.tmpl Normal file
View File

@ -0,0 +1,15 @@
ALR - Any Linux Repository
Copyright (C) {{ .Year }} Евгений Храмов
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/>.

104
list.go
View File

@ -1,38 +1,44 @@
/* // This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
* ALR - Any Linux Repository // It has been modified as part of "ALR - Any Linux Repository" by Евгений Храмов.
* Copyright (C) 2024 Евгений Храмов //
* // ALR - Any Linux Repository
* This program is free software: you can redistribute it and/or modify // Copyright (C) 2025 Евгений Храмов
* it under the terms of the GNU General Public License as published by //
* the Free Software Foundation, either version 3 of the License, or // This program is free software: you can redistribute it and/or modify
* (at your option) any later version. // it under the terms of the GNU General Public License as published by
* // the Free Software Foundation, either version 3 of the License, or
* This program is distributed in the hope that it will be useful, // (at your option) any later version.
* but WITHOUT ANY WARRANTY; without even the implied warranty of //
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // This program is distributed in the hope that it will be useful,
* GNU General Public License for more details. // but WITHOUT ANY WARRANTY; without even the implied warranty of
* // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* You should have received a copy of the GNU General Public License // GNU General Public License for more details.
* along with this program. If not, see <http://www.gnu.org/licenses/>. //
*/ // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package main package main
import ( import (
"fmt" "fmt"
"log/slog"
"os"
"github.com/leonelquinteros/gotext"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"plemya-x.ru/alr/internal/config"
"plemya-x.ru/alr/internal/db"
"plemya-x.ru/alr/pkg/loggerctx"
"plemya-x.ru/alr/pkg/manager"
"plemya-x.ru/alr/pkg/repos"
"golang.org/x/exp/slices" "golang.org/x/exp/slices"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
database "gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/build"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/repos"
) )
var listCmd = &cli.Command{ func ListCmd() *cli.Command {
return &cli.Command{
Name: "list", Name: "list",
Usage: "List ALR repo packages", Usage: gotext.Get("List ALR repo packages"),
Aliases: []string{"ls"}, Aliases: []string{"ls"},
Flags: []cli.Flag{ Flags: []cli.Flag{
&cli.BoolFlag{ &cli.BoolFlag{
@ -42,11 +48,21 @@ var listCmd = &cli.Command{
}, },
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
ctx := c.Context ctx := c.Context
log := loggerctx.From(ctx) cfg := config.New()
db := database.New(cfg)
err := repos.Pull(ctx, config.Config(ctx).Repos) err := db.Init(ctx)
if err != nil { if err != nil {
log.Fatal("Error pulling repositories").Err(err).Send() slog.Error(gotext.Get("Error initialization database"), "err", err)
os.Exit(1)
}
rs := repos.New(cfg, db)
if cfg.AutoPull(ctx) {
err = rs.Pull(ctx, cfg.Repos(ctx))
if err != nil {
slog.Error(gotext.Get("Error pulling repositories"), "err", err)
os.Exit(1)
}
} }
where := "true" where := "true"
@ -58,37 +74,49 @@ var listCmd = &cli.Command{
result, err := db.GetPkgs(ctx, where, args...) result, err := db.GetPkgs(ctx, where, args...)
if err != nil { if err != nil {
log.Fatal("Error getting packages").Err(err).Send() slog.Error(gotext.Get("Error getting packages"), "err", err)
os.Exit(1)
} }
defer result.Close() defer result.Close()
var installed map[string]string installedAlrPackages := map[string]string{}
if c.Bool("installed") { if c.Bool("installed") {
mgr := manager.Detect() mgr := manager.Detect()
if mgr == nil { if mgr == nil {
log.Fatal("Unable to detect a supported package manager on the system").Send() slog.Error(gotext.Get("Unable to detect a supported package manager on the system"))
os.Exit(1)
} }
installed, err = mgr.ListInstalled(&manager.Opts{AsRoot: false}) installed, err := mgr.ListInstalled(&manager.Opts{AsRoot: false})
if err != nil { if err != nil {
log.Fatal("Error listing installed packages").Err(err).Send() slog.Error(gotext.Get("Error listing installed packages"), "err", err)
os.Exit(1)
}
for pkgName, version := range installed {
matches := build.RegexpALRPackageName.FindStringSubmatch(pkgName)
if matches != nil {
packageName := matches[build.RegexpALRPackageName.SubexpIndex("package")]
repoName := matches[build.RegexpALRPackageName.SubexpIndex("repo")]
installedAlrPackages[fmt.Sprintf("%s/%s", repoName, packageName)] = version
}
} }
} }
for result.Next() { for result.Next() {
var pkg db.Package var pkg database.Package
err := result.StructScan(&pkg) err := result.StructScan(&pkg)
if err != nil { if err != nil {
return err return err
} }
if slices.Contains(config.Config(ctx).IgnorePkgUpdates, pkg.Name) { if slices.Contains(cfg.IgnorePkgUpdates(ctx), pkg.Name) {
continue continue
} }
version := pkg.Version version := pkg.Version
if c.Bool("installed") { if c.Bool("installed") {
instVersion, ok := installed[pkg.Name] instVersion, ok := installedAlrPackages[fmt.Sprintf("%s/%s", pkg.Repository, pkg.Name)]
if !ok { if !ok {
continue continue
} else { } else {
@ -100,9 +128,11 @@ var listCmd = &cli.Command{
} }
if err != nil { if err != nil {
log.Fatal("Error iterating over packages").Err(err).Send() slog.Error(gotext.Get("Error iterating over packages"), "err", err)
os.Exit(1)
} }
return nil return nil
}, },
}
} }

132
main.go
View File

@ -1,78 +1,96 @@
/* // This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
* ALR - Any Linux Repository // It has been modified as part of "ALR - Any Linux Repository" by Евгений Храмов.
* Copyright (C) 2024 Евгений Храмов //
* // ALR - Any Linux Repository
* This program is free software: you can redistribute it and/or modify // Copyright (C) 2025 Евгений Храмов
* it under the terms of the GNU General Public License as published by //
* the Free Software Foundation, either version 3 of the License, or // This program is free software: you can redistribute it and/or modify
* (at your option) any later version. // it under the terms of the GNU General Public License as published by
* // the Free Software Foundation, either version 3 of the License, or
* This program is distributed in the hope that it will be useful, // (at your option) any later version.
* but WITHOUT ANY WARRANTY; without even the implied warranty of //
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // This program is distributed in the hope that it will be useful,
* GNU General Public License for more details. // but WITHOUT ANY WARRANTY; without even the implied warranty of
* // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* You should have received a copy of the GNU General Public License // GNU General Public License for more details.
* along with this program. If not, see <http://www.gnu.org/licenses/>. //
*/ // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package main package main
import ( import (
"context" "context"
"log/slog"
"os" "os"
"os/signal" "os/signal"
"strings" "strings"
"syscall" "syscall"
"github.com/leonelquinteros/gotext"
"github.com/mattn/go-isatty" "github.com/mattn/go-isatty"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"go.elara.ws/logger"
"plemya-x.ru/alr/internal/config" "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils"
"plemya-x.ru/alr/internal/db" "gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
"plemya-x.ru/alr/internal/translations" "gitea.plemya-x.ru/Plemya-x/ALR/internal/translations"
"plemya-x.ru/alr/pkg/loggerctx" "gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager"
"plemya-x.ru/alr/pkg/manager"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/logger"
) )
var app = &cli.App{ func VersionCmd() *cli.Command {
return &cli.Command{
Name: "version",
Usage: gotext.Get("Print the current ALR version and exit"),
Action: func(ctx *cli.Context) error {
println(config.Version)
return nil
},
}
}
func GetApp() *cli.App {
return &cli.App{
Name: "alr", Name: "alr",
Usage: "Any Linux Repository", Usage: "Any Linux Repository",
Flags: []cli.Flag{ Flags: []cli.Flag{
&cli.StringFlag{ &cli.StringFlag{
Name: "pm-args", Name: "pm-args",
Aliases: []string{"P"}, Aliases: []string{"P"},
Usage: "Arguments to be passed on to the package manager", Usage: gotext.Get("Arguments to be passed on to the package manager"),
}, },
&cli.BoolFlag{ &cli.BoolFlag{
Name: "interactive", Name: "interactive",
Aliases: []string{"i"}, Aliases: []string{"i"},
Value: isatty.IsTerminal(os.Stdin.Fd()), Value: isatty.IsTerminal(os.Stdin.Fd()),
Usage: "Enable interactive questions and prompts", Usage: gotext.Get("Enable interactive questions and prompts"),
}, },
}, },
Commands: []*cli.Command{ Commands: []*cli.Command{
installCmd, InstallCmd(),
removeCmd, RemoveCmd(),
upgradeCmd, UpgradeCmd(),
infoCmd, InfoCmd(),
listCmd, ListCmd(),
buildCmd, BuildCmd(),
addrepoCmd, AddRepoCmd(),
removerepoCmd, RemoveRepoCmd(),
refreshCmd, RefreshCmd(),
fixCmd, FixCmd(),
genCmd, GenCmd(),
helperCmd, HelperCmd(),
versionCmd, VersionCmd(),
SearchCmd(),
}, },
Before: func(c *cli.Context) error { Before: func(c *cli.Context) error {
ctx := c.Context ctx := c.Context
log := loggerctx.From(ctx) cfg := config.New()
cmd := c.Args().First() cmd := c.Args().First()
if cmd != "helper" && !config.Config(ctx).Unsafe.AllowRunAsRoot && os.Geteuid() == 0 { if cmd != "helper" && !cfg.AllowRunAsRoot(ctx) && os.Geteuid() == 0 {
log.Fatal("Running ALR as root is forbidden as it may cause catastrophic damage to your system").Send() slog.Error(gotext.Get("Running ALR as root is forbidden as it may cause catastrophic damage to your system"))
os.Exit(1)
} }
if trimmed := strings.TrimSpace(c.String("pm-args")); trimmed != "" { if trimmed := strings.TrimSpace(c.String("pm-args")); trimmed != "" {
@ -82,34 +100,32 @@ var app = &cli.App{
return nil return nil
}, },
After: func(ctx *cli.Context) error {
return db.Close()
},
EnableBashCompletion: true, EnableBashCompletion: true,
} }
var versionCmd = &cli.Command{
Name: "version",
Usage: "Print the current ALR version and exit",
Action: func(ctx *cli.Context) error {
println(config.Version)
return nil
},
} }
func main() { func main() {
translations.Setup()
logger.SetupDefault()
app := GetApp()
cfg := config.New()
ctx := context.Background() ctx := context.Background()
log := translations.NewLogger(ctx, logger.NewCLI(os.Stderr), config.Language(ctx))
ctx = loggerctx.With(ctx, log)
// Set the root command to the one set in the ALR config // Set the root command to the one set in the ALR config
manager.DefaultRootCmd = config.Config(ctx).RootCmd manager.DefaultRootCmd = cfg.RootCmd(ctx)
ctx, cancel := signal.NotifyContext(ctx, syscall.SIGINT, syscall.SIGTERM) ctx, cancel := signal.NotifyContext(ctx, syscall.SIGINT, syscall.SIGTERM)
defer cancel() defer cancel()
// Make the application more internationalized
cli.AppHelpTemplate = cliutils.GetAppCliTemplate()
cli.CommandHelpTemplate = cliutils.GetCommandHelpTemplate()
cli.HelpFlag.(*cli.BoolFlag).Usage = gotext.Get("Show help")
err := app.RunContext(ctx, os.Args) err := app.RunContext(ctx, os.Args)
if err != nil { if err != nil {
log.Error("Error while running app").Err(err).Send() slog.Error(gotext.Get("Error while running app"), "err", err)
} }
} }

83
old-files Normal file
View File

@ -0,0 +1,83 @@
./.github/FUNDING.yml
./.gitignore
./.goreleaser.yaml
./.woodpecker.yml
./LICENSE
./Makefile
./README.md
./assets/logo.png
./build.go
./docs/README.md
./docs/configuration.md
./docs/packages/README.md
./docs/packages/adding-packages.md
./docs/packages/build-scripts.md
./docs/packages/conventions.md
./docs/usage.md
./fix.go
./gen.go
./go.mod
./go.sum
./helper.go
./info.go
./install.go
./internal/cliutils/prompt.go
./internal/config/config.go
./internal/config/lang.go
./internal/config/paths.go
./internal/config/version.go
./internal/cpu/cpu.go
./internal/db/db.go
./internal/db/db_test.go
./internal/dl/dl.go
./internal/dl/file.go
./internal/dl/git.go
./internal/dl/torrent.go
./internal/dlcache/dlcache.go
./internal/dlcache/dlcache_test.go
./internal/osutils/move.go
./internal/overrides/overrides.go
./internal/overrides/overrides_test.go
./internal/pager/highlighting.go
./internal/pager/pager.go
./internal/shutils/decoder/decoder.go
./internal/shutils/decoder/decoder_test.go
./internal/shutils/handlers/exec.go
./internal/shutils/handlers/exec_test.go
./internal/shutils/handlers/fakeroot.go
./internal/shutils/handlers/nop.go
./internal/shutils/handlers/nop_test.go
./internal/shutils/handlers/restricted.go
./internal/shutils/helpers/helpers.go
./internal/translations/files/lure.en.toml
./internal/translations/files/lure.ru.toml
./internal/translations/translations.go
./internal/types/build.go
./internal/types/config.go
./internal/types/repo.go
./list.go
./main.go
./pkg/build/build.go
./pkg/build/install.go
./pkg/distro/osrelease.go
./pkg/gen/funcs.go
./pkg/gen/pip.go
./pkg/gen/tmpls/pip.tmpl.sh
./pkg/loggerctx/log.go
./pkg/manager/apk.go
./pkg/manager/apt.go
./pkg/manager/dnf.go
./pkg/manager/managers.go
./pkg/manager/pacman.go
./pkg/manager/yum.go
./pkg/manager/zypper.go
./pkg/repos/find.go
./pkg/repos/find_test.go
./pkg/repos/pull.go
./pkg/repos/pull_test.go
./pkg/search/search.go
./repo.go
./scripts/completion/bash
./scripts/completion/zsh
./scripts/install.sh
./upgrade.go

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,286 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 Евгений Храмов
//
// 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/>.
package build
import (
"context"
"fmt"
"os"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"mvdan.cc/sh/v3/syntax"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/types"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager"
)
type TestPackageFinder struct {
FindPkgsFunc func(ctx context.Context, pkgs []string) (map[string][]db.Package, []string, error)
}
func (pf *TestPackageFinder) FindPkgs(ctx context.Context, pkgs []string) (map[string][]db.Package, []string, error) {
if pf.FindPkgsFunc != nil {
return pf.FindPkgsFunc(ctx, pkgs)
}
return map[string][]db.Package{}, []string{}, nil
}
type TestManager struct {
NameFunc func() string
FormatFunc func() string
ExistsFunc func() bool
SetRootCmdFunc func(cmd string)
SyncFunc func(opts *manager.Opts) error
InstallFunc func(opts *manager.Opts, pkgs ...string) error
RemoveFunc func(opts *manager.Opts, pkgs ...string) error
UpgradeFunc func(opts *manager.Opts, pkgs ...string) error
InstallLocalFunc func(opts *manager.Opts, files ...string) error
UpgradeAllFunc func(opts *manager.Opts) error
ListInstalledFunc func(opts *manager.Opts) (map[string]string, error)
IsInstalledFunc func(pkg string) (bool, error)
}
func (m *TestManager) Name() string {
if m.NameFunc != nil {
return m.NameFunc()
}
return "TestManager"
}
func (m *TestManager) Format() string {
if m.FormatFunc != nil {
return m.FormatFunc()
}
return "testpkg"
}
func (m *TestManager) Exists() bool {
if m.ExistsFunc != nil {
return m.ExistsFunc()
}
return true
}
func (m *TestManager) SetRootCmd(cmd string) {
if m.SetRootCmdFunc != nil {
m.SetRootCmdFunc(cmd)
}
}
func (m *TestManager) Sync(opts *manager.Opts) error {
if m.SyncFunc != nil {
return m.SyncFunc(opts)
}
return nil
}
func (m *TestManager) Install(opts *manager.Opts, pkgs ...string) error {
if m.InstallFunc != nil {
return m.InstallFunc(opts, pkgs...)
}
return nil
}
func (m *TestManager) Remove(opts *manager.Opts, pkgs ...string) error {
if m.RemoveFunc != nil {
return m.RemoveFunc(opts, pkgs...)
}
return nil
}
func (m *TestManager) Upgrade(opts *manager.Opts, pkgs ...string) error {
if m.UpgradeFunc != nil {
return m.UpgradeFunc(opts, pkgs...)
}
return nil
}
func (m *TestManager) InstallLocal(opts *manager.Opts, files ...string) error {
if m.InstallLocalFunc != nil {
return m.InstallLocalFunc(opts, files...)
}
return nil
}
func (m *TestManager) UpgradeAll(opts *manager.Opts) error {
if m.UpgradeAllFunc != nil {
return m.UpgradeAllFunc(opts)
}
return nil
}
func (m *TestManager) ListInstalled(opts *manager.Opts) (map[string]string, error) {
if m.ListInstalledFunc != nil {
return m.ListInstalledFunc(opts)
}
return map[string]string{}, nil
}
func (m *TestManager) IsInstalled(pkg string) (bool, error) {
if m.IsInstalledFunc != nil {
return m.IsInstalledFunc(pkg)
}
return true, nil
}
type TestConfig struct{}
func (c *TestConfig) PagerStyle(ctx context.Context) string {
return "native"
}
func (c *TestConfig) GetPaths(ctx context.Context) *config.Paths {
return &config.Paths{
CacheDir: "/tmp",
}
}
func TestExecuteFirstPassIsSecure(t *testing.T) {
cfg := &TestConfig{}
pf := &TestPackageFinder{}
info := &distro.OSRelease{}
m := &TestManager{}
opts := types.BuildOpts{
Manager: m,
Interactive: false,
}
ctx := context.Background()
b := NewBuilder(
ctx,
opts,
pf,
info,
cfg,
)
tmpFile, err := os.CreateTemp("", "testfile-")
assert.NoError(t, err)
tmpFilePath := tmpFile.Name()
defer os.Remove(tmpFilePath)
_, err = os.Stat(tmpFilePath)
assert.NoError(t, err)
testScript := fmt.Sprintf(`name='test'
version=1.0.0
release=1
rm -f %s`, tmpFilePath)
fl, err := syntax.NewParser().Parse(strings.NewReader(testScript), "alr.sh")
assert.NoError(t, err)
_, _, err = b.executeFirstPass(fl)
assert.NoError(t, err)
_, err = os.Stat(tmpFilePath)
assert.NoError(t, err)
}
func TestExecuteFirstPassIsCorrect(t *testing.T) {
type testCase struct {
Name string
Script string
Opts types.BuildOpts
Expected func(t *testing.T, vars []*types.BuildVars)
}
for _, tc := range []testCase{{
Name: "single package",
Script: `name='test'
version='1.0.0'
release=1
epoch=2
desc="Test package"
homepage='https://example.com'
maintainer='Ivan Ivanov'
`,
Opts: types.BuildOpts{
Manager: &TestManager{},
Interactive: false,
},
Expected: func(t *testing.T, vars []*types.BuildVars) {
assert.Equal(t, 1, len(vars))
assert.Equal(t, vars[0].Name, "test")
assert.Equal(t, vars[0].Version, "1.0.0")
assert.Equal(t, vars[0].Release, int(1))
assert.Equal(t, vars[0].Epoch, uint(2))
assert.Equal(t, vars[0].Description, "Test package")
},
}, {
Name: "multiple packages",
Script: `name=(
foo
bar
)
version='0.0.1'
release=1
epoch=2
desc="Test package"
meta_foo() {
desc="Foo package"
}
meta_bar() {
}
`,
Opts: types.BuildOpts{
Packages: []string{"foo"},
Manager: &TestManager{},
Interactive: false,
},
Expected: func(t *testing.T, vars []*types.BuildVars) {
assert.Equal(t, 1, len(vars))
assert.Equal(t, vars[0].Name, "foo")
assert.Equal(t, vars[0].Description, "Foo package")
},
}} {
t.Run(tc.Name, func(t *testing.T) {
cfg := &TestConfig{}
pf := &TestPackageFinder{}
info := &distro.OSRelease{}
ctx := context.Background()
b := NewBuilder(
ctx,
tc.Opts,
pf,
info,
cfg,
)
fl, err := syntax.NewParser().Parse(strings.NewReader(tc.Script), "alr.sh")
assert.NoError(t, err)
_, allVars, err := b.executeFirstPass(fl)
assert.NoError(t, err)
tc.Expected(t, allVars)
})
}
}

View File

@ -1,22 +1,38 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 Евгений Храмов
//
// 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/>.
package build package build
import ( import (
"bytes" "bytes"
"context" "context"
"log/slog"
"os/exec" "os/exec"
"path" "path"
"strings" "strings"
"github.com/goreleaser/nfpm/v2" "github.com/goreleaser/nfpm/v2"
"plemya-x.ru/alr/internal/types" "github.com/leonelquinteros/gotext"
"plemya-x.ru/alr/pkg/loggerctx"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/types"
) )
func rpmFindDependencies(ctx context.Context, pkgInfo *nfpm.Info, dirs types.Directories, command string, updateFunc func(string)) error { func rpmFindDependencies(ctx context.Context, pkgInfo *nfpm.Info, dirs types.Directories, command string, updateFunc func(string)) error {
log := loggerctx.From(ctx)
if _, err := exec.LookPath(command); err != nil { if _, err := exec.LookPath(command); err != nil {
log.Info("Command not found on the system").Str("command", command).Send() slog.Info(gotext.Get("Command not found on the system"), "command", command)
return nil return nil
} }
@ -47,7 +63,7 @@ func rpmFindDependencies(ctx context.Context, pkgInfo *nfpm.Info, dirs types.Dir
cmd.Stdout = &out cmd.Stdout = &out
cmd.Stderr = &stderr cmd.Stderr = &stderr
if err := cmd.Run(); err != nil { if err := cmd.Run(); err != nil {
log.Error(stderr.String()) slog.Error(stderr.String())
return err return err
} }
@ -62,19 +78,15 @@ func rpmFindDependencies(ctx context.Context, pkgInfo *nfpm.Info, dirs types.Dir
} }
func rpmFindProvides(ctx context.Context, pkgInfo *nfpm.Info, dirs types.Directories) error { func rpmFindProvides(ctx context.Context, pkgInfo *nfpm.Info, dirs types.Directories) error {
log := loggerctx.From(ctx)
return rpmFindDependencies(ctx, pkgInfo, dirs, "/usr/lib/rpm/find-provides", func(dep string) { return rpmFindDependencies(ctx, pkgInfo, dirs, "/usr/lib/rpm/find-provides", func(dep string) {
log.Info("Provided dependency found").Str("dep", dep).Send() slog.Info(gotext.Get("Provided dependency found"), "dep", dep)
pkgInfo.Overridables.Provides = append(pkgInfo.Overridables.Provides, dep) pkgInfo.Overridables.Provides = append(pkgInfo.Overridables.Provides, dep)
}) })
} }
func rpmFindRequires(ctx context.Context, pkgInfo *nfpm.Info, dirs types.Directories) error { func rpmFindRequires(ctx context.Context, pkgInfo *nfpm.Info, dirs types.Directories) error {
log := loggerctx.From(ctx)
return rpmFindDependencies(ctx, pkgInfo, dirs, "/usr/lib/rpm/find-requires", func(dep string) { return rpmFindDependencies(ctx, pkgInfo, dirs, "/usr/lib/rpm/find-requires", func(dep string) {
log.Info("Required dependency found").Str("dep", dep).Send() slog.Info(gotext.Get("Required dependency found"), "dep", dep)
pkgInfo.Overridables.Depends = append(pkgInfo.Overridables.Depends, dep) pkgInfo.Overridables.Depends = append(pkgInfo.Overridables.Depends, dep)
}) })
} }

View File

@ -1,91 +0,0 @@
/*
* ALR - Any Linux Repository
* ALR - Любой Linux Репозиторий
* Copyright (C) 2024 Евгений Храмов
*
* 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
* на условиях GNU General Public License, опубликованной
* the Free Software Foundation, either version 3 of the License, or
* Free Software Foundation, либо версии 3 лицензии, либо
* (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.
* Подробности смотрите в GNU General Public License.
*
* You should have received a copy of the GNU General Public License
* Вы должны были получить копию GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* вместе с этой программой. Если нет, посмотрите <http://www.gnu.org/licenses/>.
*/
package build
import (
"context"
"path/filepath"
"plemya-x.ru/alr/internal/config"
"plemya-x.ru/alr/internal/db"
"plemya-x.ru/alr/internal/types"
"plemya-x.ru/alr/pkg/loggerctx"
)
// InstallPkgs устанавливает нативные пакеты с использованием менеджера пакетов,
// затем строит и устанавливает пакеты ALR
func InstallPkgs(ctx context.Context, alrPkgs []db.Package, nativePkgs []string, opts types.BuildOpts) {
log := loggerctx.From(ctx) // Инициализируем логгер из контекста
if len(nativePkgs) > 0 {
err := opts.Manager.Install(nil, nativePkgs...)
// Если есть нативные пакеты, выполняем их установку
if err != nil {
log.Fatal("Error installing native packages").Err(err).Send()
// Логируем и завершаем выполнение при ошибке
}
}
InstallScripts(ctx, GetScriptPaths(ctx, alrPkgs), opts)
// Устанавливаем скрипты сборки через функцию InstallScripts
}
// GetScriptPaths возвращает срез путей к скриптам, соответствующий
// данным пакетам
func GetScriptPaths(ctx context.Context, pkgs []db.Package) []string {
var scripts []string
for _, pkg := range pkgs {
// Для каждого пакета создаем путь к скрипту сборки
scriptPath := filepath.Join(config.GetPaths(ctx).RepoDir, pkg.Repository, pkg.Name, "alr.sh")
scripts = append(scripts, scriptPath)
}
return scripts
}
// InstallScripts строит и устанавливает переданные alr скрипты сборки
func InstallScripts(ctx context.Context, scripts []string, opts types.BuildOpts) {
log := loggerctx.From(ctx) // Получаем логгер из контекста
for _, script := range scripts {
opts.Script = script // Устанавливаем текущий скрипт в опции
builtPkgs, _, err := BuildPackage(ctx, opts)
// Выполняем сборку пакета
if err != nil {
log.Fatal("Error building package").Err(err).Send()
// Логируем и завершаем выполнение при ошибке сборки
}
err = opts.Manager.InstallLocal(nil, builtPkgs...)
// Устанавливаем локально собранные пакеты
if err != nil {
log.Fatal("Error installing package").Err(err).Send()
// Логируем и завершаем выполнение при ошибке установки
}
}
}

336
pkg/build/utils.go Normal file
View File

@ -0,0 +1,336 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 Евгений Храмов
//
// 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/>.
package build
import (
"fmt"
"io"
"os"
"path/filepath"
"regexp"
"runtime"
"slices"
"strconv"
"strings"
// Импортируем пакеты для поддержки различных форматов пакетов (APK, DEB, RPM и ARCH).
_ "github.com/goreleaser/nfpm/v2/apk"
_ "github.com/goreleaser/nfpm/v2/arch"
_ "github.com/goreleaser/nfpm/v2/deb"
_ "github.com/goreleaser/nfpm/v2/rpm"
"mvdan.cc/sh/v3/syntax"
"github.com/goreleaser/nfpm/v2"
"github.com/goreleaser/nfpm/v2/files"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cpu"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/overrides"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/types"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager"
)
// Функция readScript анализирует скрипт сборки с использованием встроенной реализации bash
func readScript(script string) (*syntax.File, error) {
fl, err := os.Open(script) // Открываем файл скрипта
if err != nil {
return nil, err
}
defer fl.Close() // Закрываем файл после выполнения
file, err := syntax.NewParser().Parse(fl, "alr.sh") // Парсим скрипт с помощью синтаксического анализатора
if err != nil {
return nil, err
}
return file, nil // Возвращаем синтаксическое дерево
}
// Функция prepareDirs подготавливает директории для сборки.
func prepareDirs(dirs types.Directories) error {
err := os.RemoveAll(dirs.BaseDir) // Удаляем базовую директорию, если она существует
if err != nil {
return err
}
err = os.MkdirAll(dirs.SrcDir, 0o755) // Создаем директорию для источников
if err != nil {
return err
}
return os.MkdirAll(dirs.PkgDir, 0o755) // Создаем директорию для пакетов
}
// Функция buildContents создает секцию содержимого пакета, которая содержит файлы,
// которые будут включены в конечный пакет.
func buildContents(vars *types.BuildVars, dirs types.Directories, preferedContents *[]string) ([]*files.Content, error) {
contents := []*files.Content{}
processPath := func(path, trimmed string, prefered bool) error {
fi, err := os.Lstat(path)
if err != nil {
return err
}
if fi.IsDir() {
f, err := os.Open(path)
if err != nil {
return err
}
defer f.Close()
if !prefered {
_, err = f.Readdirnames(1)
if err != io.EOF {
return nil
}
}
contents = append(contents, &files.Content{
Source: path,
Destination: trimmed,
Type: "dir",
FileInfo: &files.ContentFileInfo{
MTime: fi.ModTime(),
},
})
return nil
}
if fi.Mode()&os.ModeSymlink != 0 {
link, err := os.Readlink(path)
if err != nil {
return err
}
link = strings.TrimPrefix(link, dirs.PkgDir)
contents = append(contents, &files.Content{
Source: link,
Destination: trimmed,
Type: "symlink",
FileInfo: &files.ContentFileInfo{
MTime: fi.ModTime(),
Mode: fi.Mode(),
},
})
return nil
}
fileContent := &files.Content{
Source: path,
Destination: trimmed,
FileInfo: &files.ContentFileInfo{
MTime: fi.ModTime(),
Mode: fi.Mode(),
Size: fi.Size(),
},
}
if slices.Contains(vars.Backup, trimmed) {
fileContent.Type = "config|noreplace"
}
contents = append(contents, fileContent)
return nil
}
if preferedContents != nil {
for _, trimmed := range *preferedContents {
path := filepath.Join(dirs.PkgDir, trimmed)
if err := processPath(path, trimmed, true); err != nil {
return nil, err
}
}
} else {
err := filepath.Walk(dirs.PkgDir, func(path string, fi os.FileInfo, err error) error {
if err != nil {
return err
}
trimmed := strings.TrimPrefix(path, dirs.PkgDir)
return processPath(path, trimmed, false)
})
if err != nil {
return nil, err
}
}
return contents, nil
}
var RegexpALRPackageName = regexp.MustCompile(`^(?P<package>[^+]+)\+alr-(?P<repo>.+)$`)
func getBasePkgInfo(vars *types.BuildVars, info *distro.OSRelease, opts *types.BuildOpts) *nfpm.Info {
return &nfpm.Info{
Name: fmt.Sprintf("%s+alr-%s", vars.Name, opts.Repository),
Arch: cpu.Arch(),
Version: vars.Version,
Release: overrides.ReleasePlatformSpecific(vars.Release, info),
Epoch: strconv.FormatUint(uint64(vars.Epoch), 10),
}
}
// Функция getPkgFormat возвращает формат пакета из менеджера пакетов,
// или ALR_PKG_FORMAT, если он установлен.
func getPkgFormat(mgr manager.Manager) string {
pkgFormat := mgr.Format()
if format, ok := os.LookupEnv("ALR_PKG_FORMAT"); ok {
pkgFormat = format
}
return pkgFormat
}
// Функция createBuildEnvVars создает переменные окружения, которые будут установлены
// в скрипте сборки при его выполнении.
func createBuildEnvVars(info *distro.OSRelease, dirs types.Directories) []string {
env := os.Environ()
env = append(
env,
"DISTRO_NAME="+info.Name,
"DISTRO_PRETTY_NAME="+info.PrettyName,
"DISTRO_ID="+info.ID,
"DISTRO_VERSION_ID="+info.VersionID,
"DISTRO_ID_LIKE="+strings.Join(info.Like, " "),
"ARCH="+cpu.Arch(),
"NCPU="+strconv.Itoa(runtime.NumCPU()),
)
if dirs.ScriptDir != "" {
env = append(env, "scriptdir="+dirs.ScriptDir)
}
if dirs.PkgDir != "" {
env = append(env, "pkgdir="+dirs.PkgDir)
}
if dirs.SrcDir != "" {
env = append(env, "srcdir="+dirs.SrcDir)
}
return env
}
// Функция setScripts добавляет скрипты-перехватчики к метаданным пакета.
func setScripts(vars *types.BuildVars, info *nfpm.Info, scriptDir string) {
if vars.Scripts.PreInstall != "" {
info.Scripts.PreInstall = filepath.Join(scriptDir, vars.Scripts.PreInstall)
}
if vars.Scripts.PostInstall != "" {
info.Scripts.PostInstall = filepath.Join(scriptDir, vars.Scripts.PostInstall)
}
if vars.Scripts.PreRemove != "" {
info.Scripts.PreRemove = filepath.Join(scriptDir, vars.Scripts.PreRemove)
}
if vars.Scripts.PostRemove != "" {
info.Scripts.PostRemove = filepath.Join(scriptDir, vars.Scripts.PostRemove)
}
if vars.Scripts.PreUpgrade != "" {
info.ArchLinux.Scripts.PreUpgrade = filepath.Join(scriptDir, vars.Scripts.PreUpgrade)
info.APK.Scripts.PreUpgrade = filepath.Join(scriptDir, vars.Scripts.PreUpgrade)
}
if vars.Scripts.PostUpgrade != "" {
info.ArchLinux.Scripts.PostUpgrade = filepath.Join(scriptDir, vars.Scripts.PostUpgrade)
info.APK.Scripts.PostUpgrade = filepath.Join(scriptDir, vars.Scripts.PostUpgrade)
}
if vars.Scripts.PreTrans != "" {
info.RPM.Scripts.PreTrans = filepath.Join(scriptDir, vars.Scripts.PreTrans)
}
if vars.Scripts.PostTrans != "" {
info.RPM.Scripts.PostTrans = filepath.Join(scriptDir, vars.Scripts.PostTrans)
}
}
/*
// Функция setVersion изменяет переменную версии в скрипте runner.
// Она используется для установки версии на вывод функции version().
func setVersion(ctx context.Context, r *interp.Runner, to string) error {
fl, err := syntax.NewParser().Parse(strings.NewReader("version='"+to+"'"), "")
if err != nil {
return err
}
return r.Run(ctx, fl)
}
*/
// Returns not installed dependencies
func removeAlreadyInstalled(opts types.BuildOpts, dependencies []string) ([]string, error) {
filteredPackages := []string{}
for _, dep := range dependencies {
installed, err := opts.Manager.IsInstalled(dep)
if err != nil {
return nil, err
}
if installed {
continue
}
filteredPackages = append(filteredPackages, dep)
}
return filteredPackages, nil
}
// Функция packageNames возвращает имена всех предоставленных пакетов.
func packageNames(pkgs []db.Package) []string {
names := make([]string, len(pkgs))
for i, p := range pkgs {
names[i] = p.Name
}
return names
}
// Функция removeDuplicates убирает любые дубликаты из предоставленного среза.
func removeDuplicates(slice []string) []string {
seen := map[string]struct{}{}
result := []string{}
for _, s := range slice {
if _, ok := seen[s]; !ok {
seen[s] = struct{}{}
result = append(result, s)
}
}
return result
}
func removeDuplicatesSources(sources, checksums []string) ([]string, []string) {
seen := map[string]string{}
keys := make([]string, 0)
for i, s := range sources {
if val, ok := seen[s]; !ok || strings.EqualFold(val, "SKIP") {
if !ok {
keys = append(keys, s)
}
seen[s] = checksums[i]
}
}
newSources := make([]string, len(keys))
newChecksums := make([]string, len(keys))
for i, k := range keys {
newSources[i] = k
newChecksums[i] = seen[k]
}
return newSources, newChecksums
}

View File

@ -0,0 +1,47 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 Евгений Храмов
//
// 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/>.
package build
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestRemoveDuplicatesSources(t *testing.T) {
type testCase struct {
Name string
Sources []string
Checksums []string
NewSources []string
NewChecksums []string
}
for _, tc := range []testCase{{
Name: "prefer non-skip values",
Sources: []string{"a", "b", "c", "a"},
Checksums: []string{"skip", "skip", "skip", "1"},
NewSources: []string{"a", "b", "c"},
NewChecksums: []string{"1", "skip", "skip"},
}} {
t.Run(tc.Name, func(t *testing.T) {
s, c := removeDuplicatesSources(tc.Sources, tc.Checksums)
assert.Equal(t, s, tc.NewSources)
assert.Equal(t, c, tc.NewChecksums)
})
}
}

View File

@ -1,20 +1,21 @@
/* // This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
* ALR - Any Linux Repository // It has been modified as part of "ALR - Any Linux Repository" by Евгений Храмов.
* Copyright (C) 2024 Евгений Храмов //
* // ALR - Any Linux Repository
* This program is free software: you can redistribute it and/or modify // Copyright (C) 2025 Евгений Храмов
* it under the terms of the GNU General Public License as published by //
* the Free Software Foundation, either version 3 of the License, or // This program is free software: you can redistribute it and/or modify
* (at your option) any later version. // it under the terms of the GNU General Public License as published by
* // the Free Software Foundation, either version 3 of the License, or
* This program is distributed in the hope that it will be useful, // (at your option) any later version.
* but WITHOUT ANY WARRANTY; without even the implied warranty of //
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // This program is distributed in the hope that it will be useful,
* GNU General Public License for more details. // but WITHOUT ANY WARRANTY; without even the implied warranty of
* // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* You should have received a copy of the GNU General Public License // GNU General Public License for more details.
* along with this program. If not, see <http://www.gnu.org/licenses/>. //
*/ // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package distro package distro
@ -23,10 +24,11 @@ import (
"os" "os"
"strings" "strings"
"plemya-x.ru/alr/internal/shutils/handlers"
"mvdan.cc/sh/v3/expand" "mvdan.cc/sh/v3/expand"
"mvdan.cc/sh/v3/interp" "mvdan.cc/sh/v3/interp"
"mvdan.cc/sh/v3/syntax" "mvdan.cc/sh/v3/syntax"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/handlers"
) )
// OSRelease contains information from an os-release file // OSRelease contains information from an os-release file
@ -42,6 +44,7 @@ type OSRelease struct {
SupportURL string SupportURL string
BugReportURL string BugReportURL string
Logo string Logo string
PlatformID string
} }
var parsed *OSRelease var parsed *OSRelease
@ -76,7 +79,7 @@ func ParseOSRelease(ctx context.Context) (*OSRelease, error) {
runner, err := interp.New( runner, err := interp.New(
interp.OpenHandler(handlers.NopOpen), interp.OpenHandler(handlers.NopOpen),
interp.ExecHandler(handlers.NopExec), interp.ExecHandler(handlers.NopExec),
interp.ReadDirHandler(handlers.NopReadDir), interp.ReadDirHandler2(handlers.NopReadDir),
interp.StatHandler(handlers.NopStat), interp.StatHandler(handlers.NopStat),
interp.Env(expand.ListEnviron()), interp.Env(expand.ListEnviron()),
) )
@ -100,6 +103,7 @@ func ParseOSRelease(ctx context.Context) (*OSRelease, error) {
SupportURL: runner.Vars["SUPPORT_URL"].Str, SupportURL: runner.Vars["SUPPORT_URL"].Str,
BugReportURL: runner.Vars["BUG_REPORT_URL"].Str, BugReportURL: runner.Vars["BUG_REPORT_URL"].Str,
Logo: runner.Vars["LOGO"].Str, Logo: runner.Vars["LOGO"].Str,
PlatformID: runner.Vars["PLATFORM_ID"].Str,
} }
distroUpdated := false distroUpdated := false

View File

@ -1,3 +1,22 @@
// This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
// It has been modified as part of "ALR - Any Linux Repository" by Евгений Храмов.
//
// ALR - Any Linux Repository
// Copyright (C) 2025 Евгений Храмов
//
// 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/>.
package gen package gen
import ( import (

View File

@ -1,3 +1,22 @@
// This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
// It has been modified as part of "ALR - Any Linux Repository" by Евгений Храмов.
//
// ALR - Any Linux Repository
// Copyright (C) 2025 Евгений Храмов
//
// 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/>.
package gen package gen
import ( import (
@ -12,6 +31,7 @@ import (
// Используем директиву //go:embed для встраивания содержимого файла шаблона в строку pipTmpl // Используем директиву //go:embed для встраивания содержимого файла шаблона в строку pipTmpl
// Встраивание файла tmpls/pip.tmpl.sh // Встраивание файла tmpls/pip.tmpl.sh
//
//go:embed tmpls/pip.tmpl.sh //go:embed tmpls/pip.tmpl.sh
var pipTmpl string var pipTmpl string

View File

@ -1,3 +1,22 @@
# This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
# It has been modified as part of "ALR - Any Linux Repository" by Евгений Храмов.
#
# ALR - Any Linux Repository
# Copyright (C) 2025 Евгений Храмов
#
# 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/>.
name='{{.Info.Name | tolower}}' name='{{.Info.Name | tolower}}'
version='{{.Info.Version}}' version='{{.Info.Version}}'
release='1' release='1'
@ -13,19 +32,19 @@ deps=("python3")
deps_arch=("python") deps_arch=("python")
deps_alpine=("python3") deps_alpine=("python3")
build_deps=("python3" "python3-setuptools") build_deps=("python3" "python3-pip")
build_deps_arch=("python" "python-setuptools") build_deps_arch=("python" "python-pip")
build_deps_alpine=("python3" "py3-setuptools") build_deps_alpine=("python3" "py3-pip")
sources=("https://files.pythonhosted.org/packages/source/{{.SourceURL.Filename | firstchar}}/{{.Info.Name}}/{{.SourceURL.Filename}}") sources=("https://files.pythonhosted.org/packages/source/{{.SourceURL.Filename | firstchar}}/{{.Info.Name}}/{{.SourceURL.Filename}}")
checksums=('blake2b-256:{{.SourceURL.Digests.blake2b_256}}') checksums=('blake2b-256:{{.SourceURL.Digests.blake2b_256}}')
build() { build() {
cd "$srcdir/{{.Info.Name}}-${version}" cd "$srcdir/{{.Info.Name}}-${version}"
python3 setup.py build python3 -m build
} }
package() { package() {
cd "$srcdir/{{.Info.Name}}-${version}" cd "$srcdir/{{.Info.Name}}-${version}"
python3 setup.py install --root="${pkgdir}/" --optimize=1 || return 1 pip install --root="${pkgdir}/" . --no-deps --disable-pip-version-check
} }

View File

@ -1,29 +0,0 @@
package loggerctx
import (
"context"
"go.elara.ws/logger"
)
// loggerCtxKey is used as the context key for loggers
type loggerCtxKey struct{}
// With returns a copy of ctx containing log
func With(ctx context.Context, log logger.Logger) context.Context {
return context.WithValue(ctx, loggerCtxKey{}, log)
}
// From attempts to get a logger from ctx. If ctx doesn't
// contain a logger, it returns a nop logger.
func From(ctx context.Context) logger.Logger {
if val := ctx.Value(loggerCtxKey{}); val != nil {
if log, ok := val.(logger.Logger); ok && log != nil {
return log
} else {
return logger.NewNop()
}
} else {
return logger.NewNop()
}
}

View File

@ -1,20 +1,21 @@
/* // This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
* ALR - Any Linux Repository // It has been modified as part of "ALR - Any Linux Repository" by Евгений Храмов.
* Copyright (C) 2024 Евгений Храмов //
* // ALR - Any Linux Repository
* This program is free software: you can redistribute it and/or modify // Copyright (C) 2025 Евгений Храмов
* it under the terms of the GNU General Public License as published by //
* the Free Software Foundation, either version 3 of the License, or // This program is free software: you can redistribute it and/or modify
* (at your option) any later version. // it under the terms of the GNU General Public License as published by
* // the Free Software Foundation, either version 3 of the License, or
* This program is distributed in the hope that it will be useful, // (at your option) any later version.
* but WITHOUT ANY WARRANTY; without even the implied warranty of //
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // This program is distributed in the hope that it will be useful,
* GNU General Public License for more details. // but WITHOUT ANY WARRANTY; without even the implied warranty of
* // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* You should have received a copy of the GNU General Public License // GNU General Public License for more details.
* along with this program. If not, see <http://www.gnu.org/licenses/>. //
*/ // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package manager package manager
@ -148,6 +149,21 @@ func (a *APK) ListInstalled(opts *Opts) (map[string]string, error) {
return out, nil return out, nil
} }
func (a *APK) IsInstalled(pkg string) (bool, error) {
cmd := exec.Command("apk", "info", "--installed", pkg)
output, err := cmd.CombinedOutput()
if err != nil {
if exitErr, ok := err.(*exec.ExitError); ok {
// Exit code 1 means the package is not installed
if exitErr.ExitCode() == 1 {
return false, nil
}
}
return false, fmt.Errorf("apk: isinstalled: %w, output: %s", err, output)
}
return true, nil
}
func (a *APK) getCmd(opts *Opts, mgrCmd string, args ...string) *exec.Cmd { func (a *APK) getCmd(opts *Opts, mgrCmd string, args ...string) *exec.Cmd {
var cmd *exec.Cmd var cmd *exec.Cmd
if opts.AsRoot { if opts.AsRoot {

View File

@ -1,20 +1,21 @@
/* // This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
* ALR - Any Linux Repository // It has been modified as part of "ALR - Any Linux Repository" by Евгений Храмов.
* Copyright (C) 2024 Евгений Храмов //
* // ALR - Any Linux Repository
* This program is free software: you can redistribute it and/or modify // Copyright (C) 2025 Евгений Храмов
* it under the terms of the GNU General Public License as published by //
* the Free Software Foundation, either version 3 of the License, or // This program is free software: you can redistribute it and/or modify
* (at your option) any later version. // it under the terms of the GNU General Public License as published by
* // the Free Software Foundation, either version 3 of the License, or
* This program is distributed in the hope that it will be useful, // (at your option) any later version.
* but WITHOUT ANY WARRANTY; without even the implied warranty of //
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // This program is distributed in the hope that it will be useful,
* GNU General Public License for more details. // but WITHOUT ANY WARRANTY; without even the implied warranty of
* // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* You should have received a copy of the GNU General Public License // GNU General Public License for more details.
* along with this program. If not, see <http://www.gnu.org/licenses/>. //
*/ // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package manager package manager
@ -134,6 +135,21 @@ func (a *APT) ListInstalled(opts *Opts) (map[string]string, error) {
return out, nil return out, nil
} }
func (a *APT) IsInstalled(pkg string) (bool, error) {
cmd := exec.Command("dpkg-query", "-l", pkg)
output, err := cmd.CombinedOutput()
if err != nil {
if exitErr, ok := err.(*exec.ExitError); ok {
// Exit code 1 means the package is not installed
if exitErr.ExitCode() == 1 {
return false, nil
}
}
return false, fmt.Errorf("apt: isinstalled: %w, output: %s", err, output)
}
return true, nil
}
func (a *APT) getCmd(opts *Opts, mgrCmd string, args ...string) *exec.Cmd { func (a *APT) getCmd(opts *Opts, mgrCmd string, args ...string) *exec.Cmd {
var cmd *exec.Cmd var cmd *exec.Cmd
if opts.AsRoot { if opts.AsRoot {

View File

@ -1,25 +1,22 @@
/* // ALR - Any Linux Repository
* ALR - Any Linux Repository // Copyright (C) 2025 Евгений Храмов
* Copyright (C) 2024 Евгений Храмов //
* // This program is free software: you can redistribute it and/or modify
* 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
* it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or
* the Free Software Foundation, either version 3 of the License, or // (at your option) any later version.
* (at your option) any later version. //
* // This program is distributed in the hope that it will be useful,
* This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of
* but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details.
* GNU General Public License for more details. //
* // 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/>.
*/
package manager package manager
import ( import (
"bufio"
"fmt" "fmt"
"os/exec" "os/exec"
"strings" "strings"
@ -27,6 +24,7 @@ import (
// APTRpm represents the APT-RPM package manager // APTRpm represents the APT-RPM package manager
type APTRpm struct { type APTRpm struct {
CommonRPM
rootCmd string rootCmd string
} }
@ -107,37 +105,6 @@ func (a *APTRpm) UpgradeAll(opts *Opts) error {
} }
return nil return nil
} }
func (y *APTRpm) ListInstalled(opts *Opts) (map[string]string, error) {
out := map[string]string{}
cmd := exec.Command("rpm", "-qa", "--queryformat", "%{NAME}\u200b%|EPOCH?{%{EPOCH}:}:{}|%{VERSION}-%{RELEASE}\\n")
stdout, err := cmd.StdoutPipe()
if err != nil {
return nil, err
}
err = cmd.Start()
if err != nil {
return nil, err
}
scanner := bufio.NewScanner(stdout)
for scanner.Scan() {
name, version, ok := strings.Cut(scanner.Text(), "\u200b")
if !ok {
continue
}
version = strings.TrimPrefix(version, "0:")
out[name] = version
}
err = scanner.Err()
if err != nil {
return nil, err
}
return out, nil
}
func (a *APTRpm) getCmd(opts *Opts, mgrCmd string, args ...string) *exec.Cmd { func (a *APTRpm) getCmd(opts *Opts, mgrCmd string, args ...string) *exec.Cmd {
var cmd *exec.Cmd var cmd *exec.Cmd

72
pkg/manager/common_rpm.go Normal file
View File

@ -0,0 +1,72 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 Евгений Храмов
//
// 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/>.
package manager
import (
"bufio"
"fmt"
"os/exec"
"strings"
)
type CommonRPM struct{}
func (c *CommonRPM) ListInstalled(opts *Opts) (map[string]string, error) {
out := map[string]string{}
cmd := exec.Command("rpm", "-qa", "--queryformat", "%{NAME}\u200b%|EPOCH?{%{EPOCH}:}:{}|%{VERSION}-%{RELEASE}\\n")
stdout, err := cmd.StdoutPipe()
if err != nil {
return nil, err
}
err = cmd.Start()
if err != nil {
return nil, err
}
scanner := bufio.NewScanner(stdout)
for scanner.Scan() {
name, version, ok := strings.Cut(scanner.Text(), "\u200b")
if !ok {
continue
}
version = strings.TrimPrefix(version, "0:")
out[name] = version
}
err = scanner.Err()
if err != nil {
return nil, err
}
return out, nil
}
func (a *CommonRPM) IsInstalled(pkg string) (bool, error) {
cmd := exec.Command("rpm", "-q", "--whatprovides", pkg)
output, err := cmd.CombinedOutput()
if err != nil {
if exitErr, ok := err.(*exec.ExitError); ok {
if exitErr.ExitCode() == 1 {
return false, nil
}
}
return false, fmt.Errorf("rpm: isinstalled: %w, output: %s", err, output)
}
return true, nil
}

View File

@ -19,14 +19,13 @@
package manager package manager
import ( import (
"bufio"
"fmt" "fmt"
"os/exec" "os/exec"
"strings"
) )
// DNF представляет менеджер пакетов DNF // DNF представляет менеджер пакетов DNF
type DNF struct { type DNF struct {
CommonRPM
rootCmd string // rootCmd хранит команду, используемую для выполнения команд с правами root rootCmd string // rootCmd хранит команду, используемую для выполнения команд с правами root
} }
@ -120,39 +119,6 @@ func (d *DNF) UpgradeAll(opts *Opts) error {
return nil return nil
} }
// ListInstalled возвращает список установленных пакетов и их версий
func (d *DNF) ListInstalled(opts *Opts) (map[string]string, error) {
out := map[string]string{}
cmd := exec.Command("rpm", "-qa", "--queryformat", "%{NAME}\u200b%|EPOCH?{%{EPOCH}:}:{}|%{VERSION}-%{RELEASE}\\n")
stdout, err := cmd.StdoutPipe()
if err != nil {
return nil, err
}
err = cmd.Start()
if err != nil {
return nil, err
}
scanner := bufio.NewScanner(stdout)
for scanner.Scan() {
name, version, ok := strings.Cut(scanner.Text(), "\u200b")
if !ok {
continue
}
version = strings.TrimPrefix(version, "0:")
out[name] = version
}
err = scanner.Err()
if err != nil {
return nil, err
}
return out, nil
}
// getCmd создает и возвращает команду exec.Cmd для менеджера пакетов DNF // getCmd создает и возвращает команду exec.Cmd для менеджера пакетов DNF
func (d *DNF) getCmd(opts *Opts, mgrCmd string, args ...string) *exec.Cmd { func (d *DNF) getCmd(opts *Opts, mgrCmd string, args ...string) *exec.Cmd {
var cmd *exec.Cmd var cmd *exec.Cmd

View File

@ -1,20 +1,21 @@
/* // This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
* ALR - Any Linux Repository // It has been modified as part of "ALR - Any Linux Repository" by Евгений Храмов.
* Copyright (C) 2024 Евгений Храмов //
* // ALR - Any Linux Repository
* This program is free software: you can redistribute it and/or modify // Copyright (C) 2025 Евгений Храмов
* it under the terms of the GNU General Public License as published by //
* the Free Software Foundation, either version 3 of the License, or // This program is free software: you can redistribute it and/or modify
* (at your option) any later version. // it under the terms of the GNU General Public License as published by
* // the Free Software Foundation, either version 3 of the License, or
* This program is distributed in the hope that it will be useful, // (at your option) any later version.
* but WITHOUT ANY WARRANTY; without even the implied warranty of //
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // This program is distributed in the hope that it will be useful,
* GNU General Public License for more details. // but WITHOUT ANY WARRANTY; without even the implied warranty of
* // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* You should have received a copy of the GNU General Public License // GNU General Public License for more details.
* along with this program. If not, see <http://www.gnu.org/licenses/>. //
*/ // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package manager package manager
@ -79,6 +80,8 @@ type Manager interface {
UpgradeAll(*Opts) error UpgradeAll(*Opts) error
// ListInstalled returns all installed packages mapped to their versions // ListInstalled returns all installed packages mapped to their versions
ListInstalled(*Opts) (map[string]string, error) ListInstalled(*Opts) (map[string]string, error)
//
IsInstalled(string) (bool, error)
} }
// Detect returns the package manager detected on the system // Detect returns the package manager detected on the system

View File

@ -1,20 +1,21 @@
/* // This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
* ALR - Any Linux Repository // It has been modified as part of "ALR - Any Linux Repository" by Евгений Храмов.
* Copyright (C) 2024 Евгений Храмов //
* // ALR - Any Linux Repository
* This program is free software: you can redistribute it and/or modify // Copyright (C) 2025 Евгений Храмов
* it under the terms of the GNU General Public License as published by //
* the Free Software Foundation, either version 3 of the License, or // This program is free software: you can redistribute it and/or modify
* (at your option) any later version. // it under the terms of the GNU General Public License as published by
* // the Free Software Foundation, either version 3 of the License, or
* This program is distributed in the hope that it will be useful, // (at your option) any later version.
* but WITHOUT ANY WARRANTY; without even the implied warranty of //
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // This program is distributed in the hope that it will be useful,
* GNU General Public License for more details. // but WITHOUT ANY WARRANTY; without even the implied warranty of
* // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* You should have received a copy of the GNU General Public License // GNU General Public License for more details.
* along with this program. If not, see <http://www.gnu.org/licenses/>. //
*/ // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package manager package manager
@ -141,6 +142,21 @@ func (p *Pacman) ListInstalled(opts *Opts) (map[string]string, error) {
return out, nil return out, nil
} }
func (p *Pacman) IsInstalled(pkg string) (bool, error) {
cmd := exec.Command("pacman", "-Q", pkg)
output, err := cmd.CombinedOutput()
if err != nil {
// Pacman returns exit code 1 if the package is not found
if exitErr, ok := err.(*exec.ExitError); ok {
if exitErr.ExitCode() == 1 {
return false, nil
}
}
return false, fmt.Errorf("pacman: isinstalled: %w, output: %s", err, output)
}
return true, nil
}
func (p *Pacman) getCmd(opts *Opts, mgrCmd string, args ...string) *exec.Cmd { func (p *Pacman) getCmd(opts *Opts, mgrCmd string, args ...string) *exec.Cmd {
var cmd *exec.Cmd var cmd *exec.Cmd
if opts.AsRoot { if opts.AsRoot {

View File

@ -1,32 +1,33 @@
/* // This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
* ALR - Any Linux Repository // It has been modified as part of "ALR - Any Linux Repository" by Евгений Храмов.
* Copyright (C) 2024 Евгений Храмов //
* // ALR - Any Linux Repository
* This program is free software: you can redistribute it and/or modify // Copyright (C) 2025 Евгений Храмов
* it under the terms of the GNU General Public License as published by //
* the Free Software Foundation, either version 3 of the License, or // This program is free software: you can redistribute it and/or modify
* (at your option) any later version. // it under the terms of the GNU General Public License as published by
* // the Free Software Foundation, either version 3 of the License, or
* This program is distributed in the hope that it will be useful, // (at your option) any later version.
* but WITHOUT ANY WARRANTY; without even the implied warranty of //
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // This program is distributed in the hope that it will be useful,
* GNU General Public License for more details. // but WITHOUT ANY WARRANTY; without even the implied warranty of
* // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* You should have received a copy of the GNU General Public License // GNU General Public License for more details.
* along with this program. If not, see <http://www.gnu.org/licenses/>. //
*/ // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package manager package manager
import ( import (
"bufio"
"fmt" "fmt"
"os/exec" "os/exec"
"strings"
) )
// YUM represents the YUM package manager // YUM represents the YUM package manager
type YUM struct { type YUM struct {
CommonRPM
rootCmd string rootCmd string
} }
@ -110,38 +111,6 @@ func (y *YUM) UpgradeAll(opts *Opts) error {
return nil return nil
} }
func (y *YUM) ListInstalled(opts *Opts) (map[string]string, error) {
out := map[string]string{}
cmd := exec.Command("rpm", "-qa", "--queryformat", "%{NAME}\u200b%|EPOCH?{%{EPOCH}:}:{}|%{VERSION}-%{RELEASE}\\n")
stdout, err := cmd.StdoutPipe()
if err != nil {
return nil, err
}
err = cmd.Start()
if err != nil {
return nil, err
}
scanner := bufio.NewScanner(stdout)
for scanner.Scan() {
name, version, ok := strings.Cut(scanner.Text(), "\u200b")
if !ok {
continue
}
version = strings.TrimPrefix(version, "0:")
out[name] = version
}
err = scanner.Err()
if err != nil {
return nil, err
}
return out, nil
}
func (y *YUM) getCmd(opts *Opts, mgrCmd string, args ...string) *exec.Cmd { func (y *YUM) getCmd(opts *Opts, mgrCmd string, args ...string) *exec.Cmd {
var cmd *exec.Cmd var cmd *exec.Cmd
if opts.AsRoot { if opts.AsRoot {

View File

@ -1,32 +1,32 @@
/* // This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
* ALR - Any Linux Repository // It has been modified as part of "ALR - Any Linux Repository" by Евгений Храмов.
* Copyright (C) 2024 Евгений Храмов //
* // ALR - Any Linux Repository
* This program is free software: you can redistribute it and/or modify // Copyright (C) 2025 Евгений Храмов
* it under the terms of the GNU General Public License as published by //
* the Free Software Foundation, either version 3 of the License, or // This program is free software: you can redistribute it and/or modify
* (at your option) any later version. // it under the terms of the GNU General Public License as published by
* // the Free Software Foundation, either version 3 of the License, or
* This program is distributed in the hope that it will be useful, // (at your option) any later version.
* but WITHOUT ANY WARRANTY; without even the implied warranty of //
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // This program is distributed in the hope that it will be useful,
* GNU General Public License for more details. // but WITHOUT ANY WARRANTY; without even the implied warranty of
* // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* You should have received a copy of the GNU General Public License // GNU General Public License for more details.
* along with this program. If not, see <http://www.gnu.org/licenses/>. //
*/ // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package manager package manager
import ( import (
"bufio"
"fmt" "fmt"
"os/exec" "os/exec"
"strings"
) )
// Zypper represents the Zypper package manager // Zypper represents the Zypper package manager
type Zypper struct { type Zypper struct {
CommonRPM
rootCmd string rootCmd string
} }
@ -110,38 +110,6 @@ func (z *Zypper) UpgradeAll(opts *Opts) error {
return nil return nil
} }
func (z *Zypper) ListInstalled(opts *Opts) (map[string]string, error) {
out := map[string]string{}
cmd := exec.Command("rpm", "-qa", "--queryformat", "%{NAME}\u200b%|EPOCH?{%{EPOCH}:}:{}|%{VERSION}-%{RELEASE}\\n")
stdout, err := cmd.StdoutPipe()
if err != nil {
return nil, err
}
err = cmd.Start()
if err != nil {
return nil, err
}
scanner := bufio.NewScanner(stdout)
for scanner.Scan() {
name, version, ok := strings.Cut(scanner.Text(), "\u200b")
if !ok {
continue
}
version = strings.TrimPrefix(version, "0:")
out[name] = version
}
err = scanner.Err()
if err != nil {
return nil, err
}
return out, nil
}
func (z *Zypper) getCmd(opts *Opts, mgrCmd string, args ...string) *exec.Cmd { func (z *Zypper) getCmd(opts *Opts, mgrCmd string, args ...string) *exec.Cmd {
var cmd *exec.Cmd var cmd *exec.Cmd
if opts.AsRoot { if opts.AsRoot {

View File

@ -1,33 +1,31 @@
/* // This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
* ALR - Any Linux Repository // It has been modified as part of "ALR - Any Linux Repository" by Евгений Храмов.
* Copyright (C) 2024 Евгений Храмов //
* // ALR - Any Linux Repository
* This program is free software: you can redistribute it and/or modify // Copyright (C) 2025 Евгений Храмов
* it under the terms of the GNU General Public License as published by //
* the Free Software Foundation, either version 3 of the License, or // This program is free software: you can redistribute it and/or modify
* (at your option) any later version. // it under the terms of the GNU General Public License as published by
* // the Free Software Foundation, either version 3 of the License, or
* This program is distributed in the hope that it will be useful, // (at your option) any later version.
* but WITHOUT ANY WARRANTY; without even the implied warranty of //
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // This program is distributed in the hope that it will be useful,
* GNU General Public License for more details. // but WITHOUT ANY WARRANTY; without even the implied warranty of
* // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* You should have received a copy of the GNU General Public License // GNU General Public License for more details.
* along with this program. If not, see <http://www.gnu.org/licenses/>. //
*/ // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package repos package repos
import ( import (
"context" "context"
"plemya-x.ru/alr/internal/db" "gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
) )
// FindPkgs looks for packages matching the inputs inside the database. func (rs *Repos) FindPkgs(ctx context.Context, pkgs []string) (map[string][]db.Package, []string, error) {
// It returns a map that maps the package name input to any packages found for it.
// It also returns a slice that contains the names of all packages that were not found.
func FindPkgs(ctx context.Context, pkgs []string) (map[string][]db.Package, []string, error) {
found := map[string][]db.Package{} found := map[string][]db.Package{}
notFound := []string(nil) notFound := []string(nil)
@ -36,7 +34,7 @@ func FindPkgs(ctx context.Context, pkgs []string) (map[string][]db.Package, []st
continue continue
} }
result, err := db.GetPkgs(ctx, "json_array_contains(provides, ?)", pkgName) result, err := rs.db.GetPkgs(ctx, "json_array_contains(provides, ?)", pkgName)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -55,7 +53,7 @@ func FindPkgs(ctx context.Context, pkgs []string) (map[string][]db.Package, []st
result.Close() result.Close()
if added == 0 { if added == 0 {
result, err := db.GetPkgs(ctx, "name LIKE ?", pkgName) result, err := rs.db.GetPkgs(ctx, "name LIKE ?", pkgName)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }

View File

@ -1,47 +1,44 @@
/* // This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
* ALR - Any Linux Repository // It has been modified as part of "ALR - Any Linux Repository" by Евгений Храмов.
* Copyright (C) 2024 Евгений Храмов //
* // ALR - Any Linux Repository
* This program is free software: you can redistribute it and/or modify // Copyright (C) 2025 Евгений Храмов
* it under the terms of the GNU General Public License as published by //
* the Free Software Foundation, either version 3 of the License, or // This program is free software: you can redistribute it and/or modify
* (at your option) any later version. // it under the terms of the GNU General Public License as published by
* // the Free Software Foundation, either version 3 of the License, or
* This program is distributed in the hope that it will be useful, // (at your option) any later version.
* but WITHOUT ANY WARRANTY; without even the implied warranty of //
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // This program is distributed in the hope that it will be useful,
* GNU General Public License for more details. // but WITHOUT ANY WARRANTY; without even the implied warranty of
* // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* You should have received a copy of the GNU General Public License // GNU General Public License for more details.
* along with this program. If not, see <http://www.gnu.org/licenses/>. //
*/ // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package repos_test package repos_test
import ( import (
"context"
"reflect" "reflect"
"strings" "strings"
"testing" "testing"
"plemya-x.ru/alr/internal/db" "gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
"plemya-x.ru/alr/internal/types" "gitea.plemya-x.ru/Plemya-x/ALR/internal/types"
"plemya-x.ru/alr/pkg/repos" "gitea.plemya-x.ru/Plemya-x/ALR/pkg/repos"
) )
func TestFindPkgs(t *testing.T) { func TestFindPkgs(t *testing.T) {
_, err := db.Open(":memory:") e := prepare(t)
if err != nil { defer cleanup(t, e)
t.Fatalf("Expected no error, got %s", err)
}
defer db.Close()
setCfgDirs(t) rs := repos.New(
defer removeCacheDir(t) e.Cfg,
e.Db,
)
ctx := context.Background() err := rs.Pull(e.Ctx, []types.Repo{
err = repos.Pull(ctx, []types.Repo{
{ {
Name: "default", Name: "default",
URL: "https://gitea.plemya-x.ru/xpamych/xpamych-alr-repo.git", URL: "https://gitea.plemya-x.ru/xpamych/xpamych-alr-repo.git",
@ -51,7 +48,10 @@ func TestFindPkgs(t *testing.T) {
t.Fatalf("Expected no error, got %s", err) t.Fatalf("Expected no error, got %s", err)
} }
found, notFound, err := repos.FindPkgs([]string{"itd", "nonexistentpackage1", "nonexistentpackage2"}) found, notFound, err := rs.FindPkgs(
e.Ctx,
[]string{"alr", "nonexistentpackage1", "nonexistentpackage2"},
)
if err != nil { if err != nil {
t.Fatalf("Expected no error, got %s", err) t.Fatalf("Expected no error, got %s", err)
} }
@ -64,33 +64,32 @@ func TestFindPkgs(t *testing.T) {
t.Errorf("Expected 1 package found, got %d", len(found)) t.Errorf("Expected 1 package found, got %d", len(found))
} }
itdPkgs, ok := found["itd"] alrPkgs, ok := found["alr"]
if !ok { if !ok {
t.Fatalf("Expected 'itd' packages to be found") t.Fatalf("Expected 'alr' packages to be found")
} }
if len(itdPkgs) < 2 { if len(alrPkgs) < 2 {
t.Errorf("Expected two 'itd' packages to be found") t.Errorf("Expected two 'alr' packages to be found")
} }
for i, pkg := range itdPkgs { for i, pkg := range alrPkgs {
if !strings.HasPrefix(pkg.Name, "itd") { if !strings.HasPrefix(pkg.Name, "alr") {
t.Errorf("Expected package name of all found packages to start with 'itd', got %s on element %d", pkg.Name, i) t.Errorf("Expected package name of all found packages to start with 'alr', got %s on element %d", pkg.Name, i)
} }
} }
} }
func TestFindPkgsEmpty(t *testing.T) { func TestFindPkgsEmpty(t *testing.T) {
_, err := db.Open(":memory:") e := prepare(t)
if err != nil { defer cleanup(t, e)
t.Fatalf("Expected no error, got %s", err)
}
defer db.Close()
setCfgDirs(t) rs := repos.New(
defer removeCacheDir(t) e.Cfg,
e.Db,
)
err = db.InsertPackage(db.Package{ err := e.Db.InsertPackage(e.Ctx, db.Package{
Name: "test1", Name: "test1",
Repository: "default", Repository: "default",
Version: "0.0.1", Version: "0.0.1",
@ -105,7 +104,7 @@ func TestFindPkgsEmpty(t *testing.T) {
t.Fatalf("Expected no error, got %s", err) t.Fatalf("Expected no error, got %s", err)
} }
err = db.InsertPackage(db.Package{ err = e.Db.InsertPackage(e.Ctx, db.Package{
Name: "test2", Name: "test2",
Repository: "default", Repository: "default",
Version: "0.0.1", Version: "0.0.1",
@ -120,7 +119,7 @@ func TestFindPkgsEmpty(t *testing.T) {
t.Fatalf("Expected no error, got %s", err) t.Fatalf("Expected no error, got %s", err)
} }
found, notFound, err := repos.FindPkgs([]string{"test", ""}) found, notFound, err := rs.FindPkgs(e.Ctx, []string{"test", ""})
if err != nil { if err != nil {
t.Fatalf("Expected no error, got %s", err) t.Fatalf("Expected no error, got %s", err)
} }

View File

@ -1,61 +1,73 @@
/* // This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
* ALR - Any Linux Repository // It has been modified as part of "ALR - Any Linux Repository" by Евгений Храмов.
* Copyright (C) 2024 Евгений Храмов //
* // ALR - Any Linux Repository
* This program is free software: you can redistribute it and/or modify // Copyright (C) 2025 Евгений Храмов
* it under the terms of the GNU General Public License as published by //
* the Free Software Foundation, either version 3 of the License, or // This program is free software: you can redistribute it and/or modify
* (at your option) any later version. // it under the terms of the GNU General Public License as published by
* // the Free Software Foundation, either version 3 of the License, or
* This program is distributed in the hope that it will be useful, // (at your option) any later version.
* but WITHOUT ANY WARRANTY; without even the implied warranty of //
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // This program is distributed in the hope that it will be useful,
* GNU General Public License for more details. // but WITHOUT ANY WARRANTY; without even the implied warranty of
* // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* You should have received a copy of the GNU General Public License // GNU General Public License for more details.
* along with this program. If not, see <http://www.gnu.org/licenses/>. //
*/ // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package repos package repos
import ( import (
"context" "context"
"errors" "errors"
"fmt"
"io" "io"
"log/slog"
"net/url" "net/url"
"os" "os"
"path/filepath" "path/filepath"
"reflect"
"strings" "strings"
"github.com/go-git/go-billy/v5" "github.com/go-git/go-billy/v5"
"github.com/go-git/go-billy/v5/osfs" "github.com/go-git/go-billy/v5/osfs"
"github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/format/diff" "github.com/leonelquinteros/gotext"
"github.com/pelletier/go-toml/v2" "github.com/pelletier/go-toml/v2"
"go.elara.ws/vercmp" "go.elara.ws/vercmp"
"plemya-x.ru/alr/internal/config"
"plemya-x.ru/alr/internal/db"
"plemya-x.ru/alr/internal/shutils/decoder"
"plemya-x.ru/alr/internal/shutils/handlers"
"plemya-x.ru/alr/internal/types"
"plemya-x.ru/alr/pkg/distro"
"plemya-x.ru/alr/pkg/loggerctx"
"mvdan.cc/sh/v3/expand" "mvdan.cc/sh/v3/expand"
"mvdan.cc/sh/v3/interp" "mvdan.cc/sh/v3/interp"
"mvdan.cc/sh/v3/syntax" "mvdan.cc/sh/v3/syntax"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/decoder"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/handlers"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/types"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro"
) )
type actionType uint8
const (
actionDelete actionType = iota
actionUpdate
)
type action struct {
Type actionType
File string
}
// Pull pulls the provided repositories. If a repo doesn't exist, it will be cloned // Pull pulls the provided repositories. If a repo doesn't exist, it will be cloned
// and its packages will be written to the DB. If it does exist, it will be pulled. // and its packages will be written to the DB. If it does exist, it will be pulled.
// In this case, only changed packages will be processed if possible. // In this case, only changed packages will be processed if possible.
// If repos is set to nil, the repos in the ALR config will be used. // If repos is set to nil, the repos in the ALR config will be used.
func Pull(ctx context.Context, repos []types.Repo) error { func (rs *Repos) Pull(ctx context.Context, repos []types.Repo) error {
log := loggerctx.From(ctx)
if repos == nil { if repos == nil {
repos = config.Config(ctx).Repos repos = rs.cfg.Repos(ctx)
} }
for _, repo := range repos { for _, repo := range repos {
@ -64,8 +76,8 @@ func Pull(ctx context.Context, repos []types.Repo) error {
return err return err
} }
log.Info("Pulling repository").Str("name", repo.Name).Send() slog.Info(gotext.Get("Pulling repository"), "name", repo.Name)
repoDir := filepath.Join(config.GetPaths(ctx).RepoDir, repo.Name) repoDir := filepath.Join(rs.cfg.GetPaths(ctx).RepoDir, repo.Name)
var repoFS billy.Filesystem var repoFS billy.Filesystem
gitDir := filepath.Join(repoDir, ".git") gitDir := filepath.Join(repoDir, ".git")
@ -88,14 +100,14 @@ func Pull(ctx context.Context, repos []types.Repo) error {
err = w.PullContext(ctx, &git.PullOptions{Progress: os.Stderr}) err = w.PullContext(ctx, &git.PullOptions{Progress: os.Stderr})
if errors.Is(err, git.NoErrAlreadyUpToDate) { if errors.Is(err, git.NoErrAlreadyUpToDate) {
log.Info("Repository up to date").Str("name", repo.Name).Send() slog.Info(gotext.Get("Repository up to date"), "name", repo.Name)
} else if err != nil { } else if err != nil {
return err return err
} }
repoFS = w.Filesystem repoFS = w.Filesystem
// Make sure the DB is created even if the repo is up to date // Make sure the DB is created even if the repo is up to date
if !errors.Is(err, git.NoErrAlreadyUpToDate) || db.IsEmpty(ctx) { if !errors.Is(err, git.NoErrAlreadyUpToDate) || rs.db.IsEmpty(ctx) {
new, err := r.Head() new, err := r.Head()
if err != nil { if err != nil {
return err return err
@ -104,13 +116,13 @@ func Pull(ctx context.Context, repos []types.Repo) error {
// If the DB was not present at startup, that means it's // If the DB was not present at startup, that means it's
// empty. In this case, we need to update the DB fully // empty. In this case, we need to update the DB fully
// rather than just incrementally. // rather than just incrementally.
if db.IsEmpty(ctx) { if rs.db.IsEmpty(ctx) {
err = processRepoFull(ctx, repo, repoDir) err = rs.processRepoFull(ctx, repo, repoDir)
if err != nil { if err != nil {
return err return err
} }
} else { } else {
err = processRepoChanges(ctx, repo, r, w, old, new) err = rs.processRepoChanges(ctx, repo, r, w, old, new)
if err != nil { if err != nil {
return err return err
} }
@ -135,7 +147,7 @@ func Pull(ctx context.Context, repos []types.Repo) error {
return err return err
} }
err = processRepoFull(ctx, repo, repoDir) err = rs.processRepoFull(ctx, repo, repoDir)
if err != nil { if err != nil {
return err return err
} }
@ -145,7 +157,7 @@ func Pull(ctx context.Context, repos []types.Repo) error {
fl, err := repoFS.Open("alr-repo.toml") fl, err := repoFS.Open("alr-repo.toml")
if err != nil { if err != nil {
log.Warn("Git repository does not appear to be a valid ALR repo").Str("repo", repo.Name).Send() slog.Warn(gotext.Get("Git repository does not appear to be a valid ALR repo"), "repo", repo.Name)
continue continue
} }
@ -161,7 +173,7 @@ func Pull(ctx context.Context, repos []types.Repo) error {
// to compare it to the repo version, so only compare versions with the "v". // to compare it to the repo version, so only compare versions with the "v".
if strings.HasPrefix(config.Version, "v") { if strings.HasPrefix(config.Version, "v") {
if vercmp.Compare(config.Version, repoCfg.Repo.MinVersion) == -1 { if vercmp.Compare(config.Version, repoCfg.Repo.MinVersion) == -1 {
log.Warn("ALR repo's minumum ALR version is greater than the current version. Try updating ALR if something doesn't work.").Str("repo", repo.Name).Send() slog.Warn(gotext.Get("ALR repo's minimum ALR version is greater than the current version. Try updating ALR if something doesn't work."), "repo", repo.Name)
} }
} }
} }
@ -169,19 +181,97 @@ func Pull(ctx context.Context, repos []types.Repo) error {
return nil return nil
} }
type actionType uint8 func (rs *Repos) updatePkg(ctx context.Context, repo types.Repo, runner *interp.Runner, scriptFl io.ReadCloser) error {
parser := syntax.NewParser()
const ( defer scriptFl.Close()
actionDelete actionType = iota fl, err := parser.Parse(scriptFl, "alr.sh")
actionUpdate if err != nil {
) return err
}
type action struct { runner.Reset()
Type actionType err = runner.Run(ctx, fl)
File string if err != nil {
return err
}
type packages struct {
BasePkgName string `sh:"basepkg_name"`
Names []string `sh:"name"`
}
var pkgs packages
d := decoder.New(&distro.OSRelease{}, runner)
d.Overrides = false
d.LikeDistros = false
err = d.DecodeVars(&pkgs)
if err != nil {
return err
}
if len(pkgs.Names) > 1 {
if pkgs.BasePkgName == "" {
pkgs.BasePkgName = pkgs.Names[0]
}
for _, pkgName := range pkgs.Names {
pkgInfo := PackageInfo{}
funcName := fmt.Sprintf("meta_%s", pkgName)
runner.Reset()
err = runner.Run(ctx, fl)
if err != nil {
return err
}
meta, ok := d.GetFuncWithSubshell(funcName)
if !ok {
return errors.New("func is missing")
}
r, err := meta(ctx)
if err != nil {
return err
}
d := decoder.New(&distro.OSRelease{}, r)
d.Overrides = false
d.LikeDistros = false
err = d.DecodeVars(&pkgInfo)
if err != nil {
return err
}
pkg := pkgInfo.ToPackage(repo.Name)
resolveOverrides(r, pkg)
pkg.Name = pkgName
pkg.BasePkgName = pkgs.BasePkgName
err = rs.db.InsertPackage(ctx, *pkg)
if err != nil {
return err
}
}
return nil
}
pkg := EmptyPackage(repo.Name)
err = d.DecodeVars(pkg)
if err != nil {
return err
}
resolveOverrides(runner, pkg)
return rs.db.InsertPackage(ctx, *pkg)
} }
func processRepoChanges(ctx context.Context, repo types.Repo, r *git.Repository, w *git.Worktree, old, new *plumbing.Reference) error { func (rs *Repos) processRepoChangesRunner(repoDir, scriptDir string) (*interp.Runner, error) {
env := append(os.Environ(), "scriptdir="+scriptDir)
return interp.New(
interp.Env(expand.ListEnviron(env...)),
interp.ExecHandler(handlers.NopExec),
interp.ReadDirHandler2(handlers.RestrictedReadDir(repoDir)),
interp.StatHandler(handlers.RestrictedStat(repoDir)),
interp.OpenHandler(handlers.RestrictedOpen(repoDir)),
interp.StdIO(handlers.NopRWC{}, handlers.NopRWC{}, handlers.NopRWC{}),
)
}
func (rs *Repos) processRepoChanges(ctx context.Context, repo types.Repo, r *git.Repository, w *git.Worktree, old, new *plumbing.Reference) error {
oldCommit, err := r.CommitObject(old.Hash()) oldCommit, err := r.CommitObject(old.Hash())
if err != nil { if err != nil {
return err return err
@ -205,18 +295,18 @@ func processRepoChanges(ctx context.Context, repo types.Repo, r *git.Repository,
continue continue
} }
if to == nil { switch {
case to == nil:
actions = append(actions, action{ actions = append(actions, action{
Type: actionDelete, Type: actionDelete,
File: from.Path(), File: from.Path(),
}) })
} else if from == nil { case from == nil:
actions = append(actions, action{ actions = append(actions, action{
Type: actionUpdate, Type: actionUpdate,
File: to.Path(), File: to.Path(),
}) })
} else { case from.Path() != to.Path():
if from.Path() != to.Path() {
actions = append(actions, actions = append(actions,
action{ action{
Type: actionDelete, Type: actionDelete,
@ -227,28 +317,19 @@ func processRepoChanges(ctx context.Context, repo types.Repo, r *git.Repository,
File: to.Path(), File: to.Path(),
}, },
) )
} else { default:
actions = append(actions, action{ actions = append(actions, action{
Type: actionUpdate, Type: actionUpdate,
File: to.Path(), File: to.Path(),
}) })
} }
} }
}
repoDir := w.Filesystem.Root() repoDir := w.Filesystem.Root()
parser := syntax.NewParser() parser := syntax.NewParser()
for _, action := range actions { for _, action := range actions {
env := append(os.Environ(), "scriptdir="+filepath.Dir(filepath.Join(repoDir, action.File))) runner, err := rs.processRepoChangesRunner(repoDir, filepath.Dir(filepath.Join(repoDir, action.File)))
runner, err := interp.New(
interp.Env(expand.ListEnviron(env...)),
interp.ExecHandler(handlers.NopExec),
interp.ReadDirHandler(handlers.RestrictedReadDir(repoDir)),
interp.StatHandler(handlers.RestrictedStat(repoDir)),
interp.OpenHandler(handlers.RestrictedOpen(repoDir)),
interp.StdIO(handlers.NopRWC{}, handlers.NopRWC{}, handlers.NopRWC{}),
)
if err != nil { if err != nil {
return err return err
} }
@ -275,7 +356,7 @@ func processRepoChanges(ctx context.Context, repo types.Repo, r *git.Repository,
return err return err
} }
err = db.DeletePkgs(ctx, "name = ? AND repository = ?", pkg.Name, repo.Name) err = rs.db.DeletePkgs(ctx, "name = ? AND repository = ?", pkg.Name, repo.Name)
if err != nil { if err != nil {
return err return err
} }
@ -294,23 +375,7 @@ func processRepoChanges(ctx context.Context, repo types.Repo, r *git.Repository,
return nil return nil
} }
pkg := db.Package{ err = rs.updatePkg(ctx, repo, runner, r)
Description: db.NewJSON(map[string]string{}),
Homepage: db.NewJSON(map[string]string{}),
Maintainer: db.NewJSON(map[string]string{}),
Depends: db.NewJSON(map[string][]string{}),
BuildDepends: db.NewJSON(map[string][]string{}),
Repository: repo.Name,
}
err = parseScript(ctx, parser, runner, r, &pkg)
if err != nil {
return err
}
resolveOverrides(runner, &pkg)
err = db.InsertPackage(ctx, pkg)
if err != nil { if err != nil {
return err return err
} }
@ -320,41 +385,15 @@ func processRepoChanges(ctx context.Context, repo types.Repo, r *git.Repository,
return nil return nil
} }
// isValid makes sure the path of the file being updated is valid. func (rs *Repos) processRepoFull(ctx context.Context, repo types.Repo, repoDir string) error {
// It checks to make sure the file is not within a nested directory
// and that it is called alr.sh.
func isValid(from, to diff.File) bool {
var path string
if from != nil {
path = from.Path()
}
if to != nil {
path = to.Path()
}
match, _ := filepath.Match("*/*.sh", path)
return match
}
func processRepoFull(ctx context.Context, repo types.Repo, repoDir string) error {
glob := filepath.Join(repoDir, "/*/alr.sh") glob := filepath.Join(repoDir, "/*/alr.sh")
matches, err := filepath.Glob(glob) matches, err := filepath.Glob(glob)
if err != nil { if err != nil {
return err return err
} }
parser := syntax.NewParser()
for _, match := range matches { for _, match := range matches {
env := append(os.Environ(), "scriptdir="+filepath.Dir(match)) runner, err := rs.processRepoChangesRunner(repoDir, filepath.Dir(match))
runner, err := interp.New(
interp.Env(expand.ListEnviron(env...)),
interp.ExecHandler(handlers.NopExec),
interp.ReadDirHandler(handlers.RestrictedReadDir(repoDir)),
interp.StatHandler(handlers.RestrictedStat(repoDir)),
interp.OpenHandler(handlers.RestrictedOpen(repoDir)),
interp.StdIO(handlers.NopRWC{}, handlers.NopRWC{}, handlers.NopRWC{}),
)
if err != nil { if err != nil {
return err return err
} }
@ -364,23 +403,7 @@ func processRepoFull(ctx context.Context, repo types.Repo, repoDir string) error
return err return err
} }
pkg := db.Package{ err = rs.updatePkg(ctx, repo, runner, scriptFl)
Description: db.NewJSON(map[string]string{}),
Homepage: db.NewJSON(map[string]string{}),
Maintainer: db.NewJSON(map[string]string{}),
Depends: db.NewJSON(map[string][]string{}),
BuildDepends: db.NewJSON(map[string][]string{}),
Repository: repo.Name,
}
err = parseScript(ctx, parser, runner, scriptFl, &pkg)
if err != nil {
return err
}
resolveOverrides(runner, &pkg)
err = db.InsertPackage(ctx, pkg)
if err != nil { if err != nil {
return err return err
} }
@ -388,54 +411,3 @@ func processRepoFull(ctx context.Context, repo types.Repo, repoDir string) error
return nil return nil
} }
func parseScript(ctx context.Context, parser *syntax.Parser, runner *interp.Runner, r io.ReadCloser, pkg *db.Package) error {
defer r.Close()
fl, err := parser.Parse(r, "alr.sh")
if err != nil {
return err
}
runner.Reset()
err = runner.Run(ctx, fl)
if err != nil {
return err
}
d := decoder.New(&distro.OSRelease{}, runner)
d.Overrides = false
d.LikeDistros = false
return d.DecodeVars(pkg)
}
var overridable = map[string]string{
"deps": "Depends",
"build_deps": "BuildDepends",
"desc": "Description",
"homepage": "Homepage",
"maintainer": "Maintainer",
}
func resolveOverrides(runner *interp.Runner, pkg *db.Package) {
pkgVal := reflect.ValueOf(pkg).Elem()
for name, val := range runner.Vars {
for prefix, field := range overridable {
if strings.HasPrefix(name, prefix) {
override := strings.TrimPrefix(name, prefix)
override = strings.TrimPrefix(override, "_")
field := pkgVal.FieldByName(field)
varVal := field.FieldByName("Val")
varType := varVal.Type()
switch varType.Elem().String() {
case "[]string":
varVal.SetMapIndex(reflect.ValueOf(override), reflect.ValueOf(val.List))
case "string":
varVal.SetMapIndex(reflect.ValueOf(override), reflect.ValueOf(val.Str))
}
break
}
}
}
}

View File

@ -0,0 +1,173 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 Евгений Храмов
//
// 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/>.
package repos
import (
"context"
"io"
"os"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/types"
)
type TestALRConfig struct{}
func (c *TestALRConfig) GetPaths(ctx context.Context) *config.Paths {
return &config.Paths{
DBPath: ":memory:",
}
}
func (c *TestALRConfig) Repos(ctx context.Context) []types.Repo {
return []types.Repo{
{
Name: "test",
URL: "https://test",
},
}
}
func createReadCloserFromString(input string) io.ReadCloser {
reader := strings.NewReader(input)
return struct {
io.Reader
io.Closer
}{
Reader: reader,
Closer: io.NopCloser(reader),
}
}
func TestUpdatePkg(t *testing.T) {
type testCase struct {
name string
file string
verify func(context.Context, *db.Database)
}
repo := types.Repo{
Name: "test",
URL: "https://test",
}
for _, tc := range []testCase{
{
name: "single package",
file: `name=foo
version='0.0.1'
release=1
desc="main desc"
deps=('sudo')
build_deps=('golang')
`,
verify: func(ctx context.Context, database *db.Database) {
result, err := database.GetPkgs(ctx, "1 = 1")
assert.NoError(t, err)
pkgCount := 0
for result.Next() {
var dbPkg db.Package
err = result.StructScan(&dbPkg)
if err != nil {
t.Errorf("Expected no error, got %s", err)
}
assert.Equal(t, "foo", dbPkg.Name)
assert.Equal(t, db.NewJSON(map[string]string{"": "main desc"}), dbPkg.Description)
assert.Equal(t, db.NewJSON(map[string][]string{"": {"sudo"}}), dbPkg.Depends)
pkgCount++
}
assert.Equal(t, 1, pkgCount)
},
},
{
name: "multiple package",
file: `basepkg_name=foo
name=(
bar
buz
)
version='0.0.1'
release=1
desc="main desc"
deps=('sudo')
build_deps=('golang')
meta_bar() {
desc="foo desc"
}
meta_buz() {
deps+=('doas')
}
`,
verify: func(ctx context.Context, database *db.Database) {
result, err := database.GetPkgs(ctx, "1 = 1")
assert.NoError(t, err)
pkgCount := 0
for result.Next() {
var dbPkg db.Package
err = result.StructScan(&dbPkg)
if err != nil {
t.Errorf("Expected no error, got %s", err)
}
if dbPkg.Name == "bar" {
assert.Equal(t, db.NewJSON(map[string]string{"": "foo desc"}), dbPkg.Description)
assert.Equal(t, db.NewJSON(map[string][]string{"": {"sudo"}}), dbPkg.Depends)
}
if dbPkg.Name == "buz" {
assert.Equal(t, db.NewJSON(map[string]string{"": "main desc"}), dbPkg.Description)
assert.Equal(t, db.NewJSON(map[string][]string{"": {"sudo", "doas"}}), dbPkg.Depends)
}
pkgCount++
}
assert.Equal(t, 2, pkgCount)
},
},
} {
t.Run(tc.name, func(t *testing.T) {
cfg := &TestALRConfig{}
ctx := context.Background()
database := db.New(&TestALRConfig{})
database.Init(ctx)
rs := New(cfg, database)
path, err := os.MkdirTemp("", "test-update-pkg")
assert.NoError(t, err)
defer os.RemoveAll(path)
runner, err := rs.processRepoChangesRunner(path, path)
assert.NoError(t, err)
err = rs.updatePkg(ctx, repo, runner, createReadCloserFromString(
tc.file,
))
assert.NoError(t, err)
tc.verify(ctx, database)
})
}
}

View File

@ -1,20 +1,21 @@
/* // This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
* alr - Any Linux Repository // It has been modified as part of "ALR - Any Linux Repository" by Евгений Храмов.
* Copyright (C) 2024 Евгений Храмов //
* // ALR - Any Linux Repository
* This program is free software: you can redistribute it and/or modify // Copyright (C) 2025 Евгений Храмов
* it under the terms of the GNU General Public License as published by //
* the Free Software Foundation, either version 3 of the License, or // This program is free software: you can redistribute it and/or modify
* (at your option) any later version. // it under the terms of the GNU General Public License as published by
* // the Free Software Foundation, either version 3 of the License, or
* This program is distributed in the hope that it will be useful, // (at your option) any later version.
* but WITHOUT ANY WARRANTY; without even the implied warranty of //
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // This program is distributed in the hope that it will be useful,
* GNU General Public License for more details. // but WITHOUT ANY WARRANTY; without even the implied warranty of
* // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* You should have received a copy of the GNU General Public License // GNU General Public License for more details.
* along with this program. If not, see <http://www.gnu.org/licenses/>. //
*/ // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package repos_test package repos_test
@ -24,71 +25,106 @@ import (
"path/filepath" "path/filepath"
"testing" "testing"
"plemya-x.ru/alr/internal/config" "gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
"plemya-x.ru/alr/internal/db" "gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
"plemya-x.ru/alr/internal/types" database "gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
"plemya-x.ru/alr/pkg/repos" "gitea.plemya-x.ru/Plemya-x/ALR/internal/types"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/repos"
) )
func setCfgDirs(t *testing.T) { type TestEnv struct {
t.Helper() Ctx context.Context
Cfg *TestALRConfig
paths := config.GetPaths() Db *db.Database
var err error
paths.CacheDir, err = os.MkdirTemp("/tmp", "alr-pull-test.*")
if err != nil {
t.Fatalf("Expected no error, got %s", err)
}
paths.RepoDir = filepath.Join(paths.CacheDir, "repo")
paths.PkgsDir = filepath.Join(paths.CacheDir, "pkgs")
err = os.MkdirAll(paths.RepoDir, 0o755)
if err != nil {
t.Fatalf("Expected no error, got %s", err)
}
err = os.MkdirAll(paths.PkgsDir, 0o755)
if err != nil {
t.Fatalf("Expected no error, got %s", err)
}
paths.DBPath = filepath.Join(paths.CacheDir, "db")
} }
func removeCacheDir(t *testing.T) { type TestALRConfig struct {
t.Helper() CacheDir string
RepoDir string
PkgsDir string
}
err := os.RemoveAll(config.GetPaths().CacheDir) func (c *TestALRConfig) GetPaths(ctx context.Context) *config.Paths {
if err != nil { return &config.Paths{
t.Fatalf("Expected no error, got %s", err) DBPath: ":memory:",
CacheDir: c.CacheDir,
RepoDir: c.RepoDir,
PkgsDir: c.PkgsDir,
} }
} }
func TestPull(t *testing.T) { func (c *TestALRConfig) Repos(ctx context.Context) []types.Repo {
_, err := db.Open(":memory:") return []types.Repo{}
}
func prepare(t *testing.T) *TestEnv {
t.Helper()
cacheDir, err := os.MkdirTemp("/tmp", "alr-pull-test.*")
if err != nil { if err != nil {
t.Fatalf("Expected no error, got %s", err) t.Fatalf("Expected no error, got %s", err)
} }
defer db.Close()
setCfgDirs(t) repoDir := filepath.Join(cacheDir, "repo")
defer removeCacheDir(t) err = os.MkdirAll(repoDir, 0o755)
if err != nil {
t.Fatalf("Expected no error, got %s", err)
}
pkgsDir := filepath.Join(cacheDir, "pkgs")
err = os.MkdirAll(pkgsDir, 0o755)
if err != nil {
t.Fatalf("Expected no error, got %s", err)
}
cfg := &TestALRConfig{
CacheDir: cacheDir,
RepoDir: repoDir,
PkgsDir: pkgsDir,
}
ctx := context.Background() ctx := context.Background()
err = repos.Pull(ctx, []types.Repo{ db := database.New(cfg)
db.Init(ctx)
return &TestEnv{
Cfg: cfg,
Db: db,
Ctx: ctx,
}
}
func cleanup(t *testing.T, e *TestEnv) {
t.Helper()
err := os.RemoveAll(e.Cfg.CacheDir)
if err != nil {
t.Fatalf("Expected no error, got %s", err)
}
e.Db.Close()
}
func TestPull(t *testing.T) {
e := prepare(t)
defer cleanup(t, e)
rs := repos.New(
e.Cfg,
e.Db,
)
err := rs.Pull(e.Ctx, []types.Repo{
{ {
Name: "default", Name: "default",
URL: "https://gitea.plemya-x.ru/xpamych/ALR.git", URL: "https://gitea.plemya-x.ru/Plemya-x/xpamych-alr-repo.git",
}, },
}) })
if err != nil { if err != nil {
t.Fatalf("Expected no error, got %s", err) t.Fatalf("Expected no error, got %s", err)
} }
result, err := db.GetPkgs("name LIKE 'itd%'") result, err := e.Db.GetPkgs(e.Ctx, "true")
if err != nil { if err != nil {
t.Fatalf("Expected no error, got %s", err) t.Fatalf("Expected no error, got %s", err)
} }
@ -103,7 +139,7 @@ func TestPull(t *testing.T) {
pkgAmt++ pkgAmt++
} }
if pkgAmt < 2 { if pkgAmt == 0 {
t.Errorf("Expected 2 packages to match, got %d", pkgAmt) t.Errorf("Expected at least 1 matching package, but got %d", pkgAmt)
} }
} }

45
pkg/repos/repos.go Normal file
View File

@ -0,0 +1,45 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 Евгений Храмов
//
// 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/>.
package repos
import (
"context"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
database "gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/types"
)
type Config interface {
GetPaths(ctx context.Context) *config.Paths
Repos(ctx context.Context) []types.Repo
}
type Repos struct {
cfg Config
db *database.Database
}
func New(
cfg Config,
db *database.Database,
) *Repos {
return &Repos{
cfg,
db,
}
}

141
pkg/repos/utils.go Normal file
View File

@ -0,0 +1,141 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 Евгений Храмов
//
// 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/>.
package repos
import (
"context"
"io"
"path/filepath"
"reflect"
"strings"
"github.com/go-git/go-git/v5/plumbing/format/diff"
"mvdan.cc/sh/v3/interp"
"mvdan.cc/sh/v3/syntax"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/decoder"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro"
)
// isValid makes sure the path of the file being updated is valid.
// It checks to make sure the file is not within a nested directory
// and that it is called alr.sh.
func isValid(from, to diff.File) bool {
var path string
if from != nil {
path = from.Path()
}
if to != nil {
path = to.Path()
}
match, _ := filepath.Match("*/*.sh", path)
return match
}
func parseScript(ctx context.Context, parser *syntax.Parser, runner *interp.Runner, r io.ReadCloser, pkg *db.Package) error {
defer r.Close()
fl, err := parser.Parse(r, "alr.sh")
if err != nil {
return err
}
runner.Reset()
err = runner.Run(ctx, fl)
if err != nil {
return err
}
d := decoder.New(&distro.OSRelease{}, runner)
d.Overrides = false
d.LikeDistros = false
return d.DecodeVars(pkg)
}
type PackageInfo struct {
Version string `sh:"version,required"`
Release int `sh:"release,required"`
Epoch uint `sh:"epoch"`
Architectures db.JSON[[]string] `sh:"architectures"`
Licenses db.JSON[[]string] `sh:"license"`
Provides db.JSON[[]string] `sh:"provides"`
Conflicts db.JSON[[]string] `sh:"conflicts"`
Replaces db.JSON[[]string] `sh:"replaces"`
}
func (inf *PackageInfo) ToPackage(repoName string) *db.Package {
return &db.Package{
Version: inf.Version,
Release: inf.Release,
Epoch: inf.Epoch,
Architectures: inf.Architectures,
Licenses: inf.Licenses,
Provides: inf.Provides,
Conflicts: inf.Conflicts,
Replaces: inf.Replaces,
Description: db.NewJSON(map[string]string{}),
Homepage: db.NewJSON(map[string]string{}),
Maintainer: db.NewJSON(map[string]string{}),
Depends: db.NewJSON(map[string][]string{}),
BuildDepends: db.NewJSON(map[string][]string{}),
Repository: repoName,
}
}
func EmptyPackage(repoName string) *db.Package {
return &db.Package{
Description: db.NewJSON(map[string]string{}),
Homepage: db.NewJSON(map[string]string{}),
Maintainer: db.NewJSON(map[string]string{}),
Depends: db.NewJSON(map[string][]string{}),
BuildDepends: db.NewJSON(map[string][]string{}),
Repository: repoName,
}
}
var overridable = map[string]string{
"deps": "Depends",
"build_deps": "BuildDepends",
"desc": "Description",
"homepage": "Homepage",
"maintainer": "Maintainer",
}
func resolveOverrides(runner *interp.Runner, pkg *db.Package) {
pkgVal := reflect.ValueOf(pkg).Elem()
for name, val := range runner.Vars {
for prefix, field := range overridable {
if strings.HasPrefix(name, prefix) {
override := strings.TrimPrefix(name, prefix)
override = strings.TrimPrefix(override, "_")
field := pkgVal.FieldByName(field)
varVal := field.FieldByName("Val")
varType := varVal.Type()
switch varType.Elem().String() {
case "[]string":
varVal.SetMapIndex(reflect.ValueOf(override), reflect.ValueOf(val.List))
case "string":
varVal.SetMapIndex(reflect.ValueOf(override), reflect.ValueOf(val.Str))
}
break
}
}
}
}

View File

@ -1,167 +1,66 @@
// This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
// It has been modified as part of "ALR - Any Linux Repository" by Евгений Храмов.
//
// ALR - Any Linux Repository
// Copyright (C) 2025 Евгений Храмов
//
// 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/>.
package search package search
import ( import (
"context" "context"
"errors"
"io"
"io/fs"
"os"
"path/filepath"
"strconv"
"strings"
"plemya-x.ru/alr/internal/config" "github.com/jmoiron/sqlx"
"plemya-x.ru/alr/internal/db"
database "gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
) )
// Filter represents search filters. type PackagesProvider interface {
type Filter int GetPkgs(ctx context.Context, where string, args ...any) (*sqlx.Rows, error)
// Filters
const (
FilterNone Filter = iota
FilterInRepo
FilterSupportsArch
)
// SoryBy represents a value that packages can be sorted by.
type SortBy int
// Sort values
const (
SortByNone = iota
SortByName
SortByRepo
SortByVersion
)
// Package represents a package from ALR's database
type Package struct {
Name string
Version string
Release int
Epoch uint
Description map[string]string
Homepage map[string]string
Maintainer map[string]string
Architectures []string
Licenses []string
Provides []string
Conflicts []string
Replaces []string
Depends map[string][]string
BuildDepends map[string][]string
OptDepends map[string][]string
Repository string
} }
func convertPkg(p db.Package) Package { type Searcher struct {
return Package{ pp PackagesProvider
Name: p.Name, }
Version: p.Version,
Release: p.Release, func New(pp PackagesProvider) *Searcher {
Epoch: p.Epoch, return &Searcher{
Description: p.Description.Val, pp: pp,
Homepage: p.Homepage.Val,
Maintainer: p.Maintainer.Val,
Architectures: p.Architectures.Val,
Licenses: p.Licenses.Val,
Provides: p.Provides.Val,
Conflicts: p.Conflicts.Val,
Replaces: p.Replaces.Val,
Depends: p.Depends.Val,
BuildDepends: p.BuildDepends.Val,
OptDepends: p.OptDepends.Val,
Repository: p.Repository,
} }
} }
// Options contains the options for a search. func (s *Searcher) Search(
type Options struct { ctx context.Context,
Filter Filter opts *SearchOptions,
FilterValue string ) ([]database.Package, error) {
SortBy SortBy var packages []database.Package
Limit int64
Query string
}
// Search searches for packages in the database based on the given options. where, args := opts.WhereClause()
func Search(ctx context.Context, opts Options) ([]Package, error) { result, err := s.pp.GetPkgs(ctx, where, args...)
query := "(name LIKE ? OR description LIKE ? OR json_array_contains(provides, ?))"
args := []any{"%" + opts.Query + "%", "%" + opts.Query + "%", opts.Query}
if opts.Filter != FilterNone {
switch opts.Filter {
case FilterInRepo:
query += " AND repository = ?"
case FilterSupportsArch:
query += " AND json_array_contains(architectures, ?)"
}
args = append(args, opts.FilterValue)
}
if opts.SortBy != SortByNone {
switch opts.SortBy {
case SortByName:
query += " ORDER BY name"
case SortByRepo:
query += " ORDER BY repository"
case SortByVersion:
query += " ORDER BY version"
}
}
if opts.Limit != 0 {
query += " LIMIT " + strconv.FormatInt(opts.Limit, 10)
}
result, err := db.GetPkgs(ctx, query, args...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
var out []Package
for result.Next() { for result.Next() {
pkg := db.Package{} var dbPkg database.Package
err = result.StructScan(&pkg) err = result.StructScan(&dbPkg)
if err != nil { if err != nil {
return nil, err return nil, err
} }
out = append(out, convertPkg(pkg)) packages = append(packages, dbPkg)
} }
return out, err return packages, nil
}
// GetPkg gets a single package from the database and returns it.
func GetPkg(ctx context.Context, repo, name string) (Package, error) {
pkg, err := db.GetPkg(ctx, "name = ? AND repository = ?", name, repo)
return convertPkg(*pkg), err
}
var (
// ErrInvalidArgument is an error returned by GetScript when one of its arguments
// contain invalid characters
ErrInvalidArgument = errors.New("name and repository must not contain . or /")
// ErrScriptNotFound is returned by GetScript if it can't find the script requested
// by the user.
ErrScriptNotFound = errors.New("requested script not found")
)
// GetScript returns a reader containing the build script for a given package.
func GetScript(ctx context.Context, repo, name string) (io.ReadCloser, error) {
if strings.Contains(name, "./") || strings.ContainsAny(repo, "./") {
return nil, ErrInvalidArgument
}
scriptPath := filepath.Join(config.GetPaths(ctx).RepoDir, repo, name, "alr.sh")
fl, err := os.Open(scriptPath)
if errors.Is(err, fs.ErrNotExist) {
return nil, ErrScriptNotFound
} else if err != nil {
return nil, err
}
return fl, nil
} }

View File

@ -0,0 +1,86 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 Евгений Храмов
//
// 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/>.
package search
import (
"fmt"
"strings"
)
type SearchOptions struct {
conditions []string
args []any
}
func (o *SearchOptions) WhereClause() (string, []any) {
if len(o.conditions) == 0 {
return "", nil
}
return strings.Join(o.conditions, " AND "), o.args
}
type SearchOptionsBuilder struct {
options SearchOptions
}
func NewSearchOptions() *SearchOptionsBuilder {
return &SearchOptionsBuilder{}
}
func (b *SearchOptionsBuilder) withGeneralLike(key, value string) *SearchOptionsBuilder {
if value != "" {
b.options.conditions = append(b.options.conditions, fmt.Sprintf("%s LIKE ?", key))
b.options.args = append(b.options.args, "%"+value+"%")
}
return b
}
func (b *SearchOptionsBuilder) withGeneralEqual(key string, value any) *SearchOptionsBuilder {
if value != "" {
b.options.conditions = append(b.options.conditions, fmt.Sprintf("%s = ?", key))
b.options.args = append(b.options.args, value)
}
return b
}
func (b *SearchOptionsBuilder) withGeneralJsonArrayContains(key string, value any) *SearchOptionsBuilder {
if value != "" {
b.options.conditions = append(b.options.conditions, fmt.Sprintf("json_array_contains(%s, ?)", key))
b.options.args = append(b.options.args, value)
}
return b
}
func (b *SearchOptionsBuilder) WithName(name string) *SearchOptionsBuilder {
return b.withGeneralLike("name", name)
}
func (b *SearchOptionsBuilder) WithDescription(description string) *SearchOptionsBuilder {
return b.withGeneralLike("description", description)
}
func (b *SearchOptionsBuilder) WithRepository(repository string) *SearchOptionsBuilder {
return b.withGeneralEqual("repository", repository)
}
func (b *SearchOptionsBuilder) WithProvides(provides string) *SearchOptionsBuilder {
return b.withGeneralJsonArrayContains("provides", provides)
}
func (b *SearchOptionsBuilder) Build() *SearchOptions {
return &b.options
}

65
pkg/search/search_test.go Normal file
View File

@ -0,0 +1,65 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 Евгений Храмов
//
// 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/>.
package search_test
import (
"testing"
"github.com/stretchr/testify/assert"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/search"
)
func TestSearhOptionsBuilder(t *testing.T) {
type testCase struct {
name string
prepare func() *search.SearchOptions
expectedWhere string
expectedArgs []any
}
for _, tc := range []testCase{
{
name: "Empty fields",
prepare: func() *search.SearchOptions {
return search.NewSearchOptions().
Build()
},
expectedWhere: "",
expectedArgs: []any{},
},
{
name: "All fields",
prepare: func() *search.SearchOptions {
return search.NewSearchOptions().
WithName("foo").
WithDescription("bar").
WithRepository("buz").
WithProvides("test").
Build()
},
expectedWhere: "name LIKE ? AND description LIKE ? AND repository = ? AND json_array_contains(provides, ?)",
expectedArgs: []any{"%foo%", "%bar%", "buz", "test"},
},
} {
t.Run(tc.name, func(t *testing.T) {
whereClause, args := tc.prepare().WhereClause()
assert.Equal(t, tc.expectedWhere, whereClause)
assert.ElementsMatch(t, tc.expectedArgs, args)
})
}
}

151
repo.go
View File

@ -1,162 +1,201 @@
/* // This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
* ALR - Any Linux Repository // It has been modified as part of "ALR - Any Linux Repository" by Евгений Храмов.
* Copyright (C) 2024 Евгений Храмов //
* // ALR - Any Linux Repository
* This program is free software: you can redistribute it and/or modify // Copyright (C) 2025 Евгений Храмов
* it under the terms of the GNU General Public License as published by //
* the Free Software Foundation, either version 3 of the License, or // This program is free software: you can redistribute it and/or modify
* (at your option) any later version. // it under the terms of the GNU General Public License as published by
* // the Free Software Foundation, either version 3 of the License, or
* This program is distributed in the hope that it will be useful, // (at your option) any later version.
* but WITHOUT ANY WARRANTY; without even the implied warranty of //
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // This program is distributed in the hope that it will be useful,
* GNU General Public License for more details. // but WITHOUT ANY WARRANTY; without even the implied warranty of
* // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* You should have received a copy of the GNU General Public License // GNU General Public License for more details.
* along with this program. If not, see <http://www.gnu.org/licenses/>. //
*/ // You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package main package main
import ( import (
"log/slog"
"os" "os"
"path/filepath" "path/filepath"
"github.com/leonelquinteros/gotext"
"github.com/pelletier/go-toml/v2" "github.com/pelletier/go-toml/v2"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"plemya-x.ru/alr/internal/config"
"plemya-x.ru/alr/internal/db"
"plemya-x.ru/alr/internal/types"
"plemya-x.ru/alr/pkg/loggerctx"
"plemya-x.ru/alr/pkg/repos"
"golang.org/x/exp/slices" "golang.org/x/exp/slices"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
database "gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/types"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/repos"
) )
var addrepoCmd = &cli.Command{ func AddRepoCmd() *cli.Command {
return &cli.Command{
Name: "addrepo", Name: "addrepo",
Usage: "Add a new repository", Usage: gotext.Get("Add a new repository"),
Aliases: []string{"ar"}, Aliases: []string{"ar"},
Flags: []cli.Flag{ Flags: []cli.Flag{
&cli.StringFlag{ &cli.StringFlag{
Name: "name", Name: "name",
Aliases: []string{"n"}, Aliases: []string{"n"},
Required: true, Required: true,
Usage: "Name of the new repo", Usage: gotext.Get("Name of the new repo"),
}, },
&cli.StringFlag{ &cli.StringFlag{
Name: "url", Name: "url",
Aliases: []string{"u"}, Aliases: []string{"u"},
Required: true, Required: true,
Usage: "URL of the new repo", Usage: gotext.Get("URL of the new repo"),
}, },
}, },
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
ctx := c.Context ctx := c.Context
log := loggerctx.From(ctx)
name := c.String("name") name := c.String("name")
repoURL := c.String("url") repoURL := c.String("url")
cfg := config.Config(ctx) cfg := config.New()
reposSlice := cfg.Repos(ctx)
for _, repo := range cfg.Repos { for _, repo := range reposSlice {
if repo.URL == repoURL { if repo.URL == repoURL {
log.Fatal("Repo already exists").Str("name", repo.Name).Send() slog.Error("Repo already exists", "name", repo.Name)
os.Exit(1)
} }
} }
cfg.Repos = append(cfg.Repos, types.Repo{ reposSlice = append(reposSlice, types.Repo{
Name: name, Name: name,
URL: repoURL, URL: repoURL,
}) })
cfgFl, err := os.Create(config.GetPaths(ctx).ConfigPath) cfg.SetRepos(ctx, reposSlice)
cfgFl, err := os.Create(cfg.GetPaths(ctx).ConfigPath)
if err != nil { if err != nil {
log.Fatal("Error opening config file").Err(err).Send() slog.Error(gotext.Get("Error opening config file"), "err", err)
os.Exit(1)
} }
err = toml.NewEncoder(cfgFl).Encode(cfg) err = cfg.Save(cfgFl)
if err != nil { if err != nil {
log.Fatal("Error encoding config").Err(err).Send() slog.Error(gotext.Get("Error encoding config"), "err", err)
os.Exit(1)
} }
err = repos.Pull(ctx, cfg.Repos) db := database.New(cfg)
err = db.Init(ctx)
if err != nil { if err != nil {
log.Fatal("Error pulling repos").Err(err).Send() slog.Error(gotext.Get("Error pulling repos"), "err", err)
}
rs := repos.New(cfg, db)
err = rs.Pull(ctx, cfg.Repos(ctx))
if err != nil {
slog.Error(gotext.Get("Error pulling repos"), "err", err)
os.Exit(1)
} }
return nil return nil
}, },
}
} }
var removerepoCmd = &cli.Command{ func RemoveRepoCmd() *cli.Command {
return &cli.Command{
Name: "removerepo", Name: "removerepo",
Usage: "Remove an existing repository", Usage: gotext.Get("Remove an existing repository"),
Aliases: []string{"rr"}, Aliases: []string{"rr"},
Flags: []cli.Flag{ Flags: []cli.Flag{
&cli.StringFlag{ &cli.StringFlag{
Name: "name", Name: "name",
Aliases: []string{"n"}, Aliases: []string{"n"},
Required: true, Required: true,
Usage: "Name of the repo to be deleted", Usage: gotext.Get("Name of the repo to be deleted"),
}, },
}, },
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
ctx := c.Context ctx := c.Context
log := loggerctx.From(ctx)
name := c.String("name") name := c.String("name")
cfg := config.Config(ctx) cfg := config.New()
found := false found := false
index := 0 index := 0
for i, repo := range cfg.Repos { reposSlice := cfg.Repos(ctx)
for i, repo := range reposSlice {
if repo.Name == name { if repo.Name == name {
index = i index = i
found = true found = true
} }
} }
if !found { if !found {
log.Fatal("Repo does not exist").Str("name", name).Send() slog.Error(gotext.Get("Repo does not exist"), "name", name)
os.Exit(1)
} }
cfg.Repos = slices.Delete(cfg.Repos, index, index+1) cfg.SetRepos(ctx, slices.Delete(reposSlice, index, index+1))
cfgFl, err := os.Create(config.GetPaths(ctx).ConfigPath) cfgFl, err := os.Create(cfg.GetPaths(ctx).ConfigPath)
if err != nil { if err != nil {
log.Fatal("Error opening config file").Err(err).Send() slog.Error(gotext.Get("Error opening config file"), "err", err)
os.Exit(1)
} }
err = toml.NewEncoder(cfgFl).Encode(&cfg) err = toml.NewEncoder(cfgFl).Encode(&cfg)
if err != nil { if err != nil {
log.Fatal("Error encoding config").Err(err).Send() slog.Error(gotext.Get("Error encoding config"), "err", err)
os.Exit(1)
} }
err = os.RemoveAll(filepath.Join(config.GetPaths(ctx).RepoDir, name)) err = os.RemoveAll(filepath.Join(cfg.GetPaths(ctx).RepoDir, name))
if err != nil { if err != nil {
log.Fatal("Error removing repo directory").Err(err).Send() slog.Error(gotext.Get("Error removing repo directory"), "err", err)
os.Exit(1)
} }
db := database.New(cfg)
err = db.Init(ctx)
if err != nil {
os.Exit(1)
}
err = db.DeletePkgs(ctx, "repository = ?", name) err = db.DeletePkgs(ctx, "repository = ?", name)
if err != nil { if err != nil {
log.Fatal("Error removing packages from database").Err(err).Send() slog.Error(gotext.Get("Error removing packages from database"), "err", err)
os.Exit(1)
} }
return nil return nil
}, },
}
} }
var refreshCmd = &cli.Command{ func RefreshCmd() *cli.Command {
return &cli.Command{
Name: "refresh", Name: "refresh",
Usage: "Pull all repositories that have changed", Usage: gotext.Get("Pull all repositories that have changed"),
Aliases: []string{"ref"}, Aliases: []string{"ref"},
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
ctx := c.Context ctx := c.Context
log := loggerctx.From(ctx) cfg := config.New()
err := repos.Pull(ctx, config.Config(ctx).Repos) db := database.New(cfg)
err := db.Init(ctx)
if err != nil { if err != nil {
log.Fatal("Error pulling repos").Err(err).Send() os.Exit(1)
}
rs := repos.New(cfg, db)
err = rs.Pull(ctx, cfg.Repos(ctx))
if err != nil {
slog.Error(gotext.Get("Error pulling repos"), "err", err)
os.Exit(1)
} }
return nil return nil
}, },
}
} }

46
scripts/coverage-badge.sh Executable file
View File

@ -0,0 +1,46 @@
# ALR - Any Linux Repository
# Copyright (C) 2025 Евгений Храмов
#
# 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/>.
#!/bin/bash
COVERAGE=$(go tool cover -func=coverage.out | grep total | awk '{print $3}' | sed 's/%//')
COLOR="#4c1"
if (( $(echo "$COVERAGE < 50" | bc -l) )); then
COLOR="#e05d44"
elif (( $(echo "$COVERAGE < 80" | bc -l) )); then
COLOR="#dfb317"
fi
cat <<EOF > assets/coverage-badge.svg
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="109" height="20">
<linearGradient id="smooth" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/>
<stop offset="1" stop-opacity=".1"/></linearGradient>
<mask id="round">
<rect width="109" height="20" rx="3" fill="#fff"/>
</mask>
<g mask="url(#round)"><rect width="65" height="20" fill="#555"/>
<rect x="65" width="44" height="20" fill="${COLOR}"/>
<rect width="109" height="20" fill="url(#smooth)"/>
</g>
<g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11">
<text x="33.5" y="15" fill="#010101" fill-opacity=".3">coverage</text>
<text x="33.5" y="14">coverage</text>
<text x="86" y="15" fill="#010101" fill-opacity=".3">${COVERAGE}%</text>
<text x="86" y="14">${COVERAGE}%</text>
</g>
</svg>
EOF

84
scripts/i18n-badge.sh Executable file
View File

@ -0,0 +1,84 @@
#!/bin/bash
# ALR - Any Linux Repository
# Copyright (C) 2025 Евгений Храмов
#
# 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/>.
TRANSLATIONS_DIR="internal/translations/po"
if [ ! -d "$TRANSLATIONS_DIR" ]; then
echo "Error: directory '$TRANSLATIONS_DIR' not found"
exit 1
fi
declare -A TOTAL_STRINGS_MAP
declare -A TRANSLATED_STRINGS_MAP
for PO_FILE in $(find "$TRANSLATIONS_DIR" -type f -name "*.po"); do
LANG_DIR=$(dirname "$PO_FILE")
LANG=$(basename "$LANG_DIR")
STATS=$(LC_ALL=C msgfmt --statistics -o /dev/null "$PO_FILE" 2>&1)
NUMBERS=($(echo "$STATS" | grep -o '[0-9]\+'))
case ${#NUMBERS[@]} in
1) TRANSLATED_STRINGS=${NUMBERS[0]}; UNTRANSLATED_STRINGS=0 ;; # all translated
2) TRANSLATED_STRINGS=${NUMBERS[0]}; UNTRANSLATED_STRINGS=${NUMBERS[1]} ;; # no fuzzy
3) TRANSLATED_STRINGS=${NUMBERS[0]}; UNTRANSLATED_STRINGS=${NUMBERS[2]} ;; # with fuzzy
*) TRANSLATED_STRINGS=0; UNTRANSLATED_STRINGS=0 ;;
esac
TOTAL_STRINGS=$((TRANSLATED_STRINGS + UNTRANSLATED_STRINGS))
TOTAL_STRINGS_MAP[$LANG]=$((TOTAL_STRINGS_MAP[$LANG] + TOTAL_STRINGS))
TRANSLATED_STRINGS_MAP[$LANG]=$((TRANSLATED_STRINGS_MAP[$LANG] + TRANSLATED_STRINGS))
done
for LANG in "${!TOTAL_STRINGS_MAP[@]}"; do
TOTAL=${TOTAL_STRINGS_MAP[$LANG]}
TRANSLATED=${TRANSLATED_STRINGS_MAP[$LANG]}
if [ "$TOTAL" -eq 0 ]; then
PERCENTAGE="0.00"
else
PERCENTAGE=$(echo "scale=2; ($TRANSLATED / $TOTAL) * 100" | bc)
fi
COLOR="#4c1"
if (( $(echo "$PERCENTAGE < 50" | bc -l) )); then
COLOR="#e05d44"
elif (( $(echo "$PERCENTAGE < 80" | bc -l) )); then
COLOR="#dfb317"
fi
cat <<EOF > assets/i18n-$LANG-badge.svg
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="129" height="20">
<linearGradient id="smooth" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/>
<stop offset="1" stop-opacity=".1"/></linearGradient>
<mask id="round">
<rect width="129" height="20" rx="3" fill="#fff"/>
</mask>
<g mask="url(#round)">
<rect width="75" height="20" fill="#555"/>
<rect x="75" width="64" height="20" fill="${COLOR}"/>
<rect width="129" height="20" fill="url(#smooth)"/>
</g>
<g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11">
<text x="37" y="15" fill="#010101" fill-opacity=".3">$LANG translate</text>
<text x="37" y="14">$LANG translate</text>
<text x="100" y="15" fill="#010101" fill-opacity=".3">${PERCENTAGE}%</text>
<text x="100" y="14">${PERCENTAGE}%</text>
</g>
</svg>
EOF
done

View File

@ -1,3 +1,22 @@
# This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
# It has been modified as part of "ALR - Any Linux Repository" by Евгений Храмов.
#
# ALR - Any Linux Repository
# Copyright (C) 2025 Евгений Храмов
#
# 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/>.
info() { info() {
echo $'\x1b[32m[ИНФО]\x1b[0m' $@ echo $'\x1b[32m[ИНФО]\x1b[0m' $@
} }
@ -59,6 +78,10 @@ elif command -v apk &>/dev/null; then
info "Обнаружен apk" info "Обнаружен apk"
pkgFormat="apk" pkgFormat="apk"
pkgMgr="apk" pkgMgr="apk"
elif command -v apt-get &>/dev/null; then
info "Обнаружен apt-get"
pkgFormat="rpm"
pkgMgr="apt-get"
else else
warn "Не обнаружен поддерживаемый менеджер пакетов!" warn "Не обнаружен поддерживаемый менеджер пакетов!"
noPkgMgr=true noPkgMgr=true
@ -79,7 +102,10 @@ if [ -z "$noPkgMgr" ]; then
elif [ "$pkgMgr" == "apt" ]; then elif [ "$pkgMgr" == "apt" ]; then
latestFile=$(echo "$fileList" | grep -E 'alr-bin-.*.amd64.deb' | sort -V | tail -n 1) latestFile=$(echo "$fileList" | grep -E 'alr-bin-.*.amd64.deb' | sort -V | tail -n 1)
elif [[ "$pkgMgr" == "dnf" || "$pkgMgr" == "yum" || "$pkgMgr" == "zypper" ]]; then elif [[ "$pkgMgr" == "dnf" || "$pkgMgr" == "yum" || "$pkgMgr" == "zypper" ]]; then
latestFile=$(echo "$fileList" | grep -E 'alr-bin-.*.x86_64.rpm' | sort -V | tail -n 1) latestFile=$(echo "$fileList" | grep -E 'alr-bin-.*\.x86_64\.rpm' | grep -v 'alt1' | sort -V | tail -n 1)
elif [[ "$pkgMgr" == "apt-get" ]]; then
latestFile=$(echo "$fileList" | grep -E 'alr-bin-.*alt1.x86_64.rpm' | sort -V | tail -n 1)
else else
error "Не поддерживаемый менеджер пакетов для автоматической установки" error "Не поддерживаемый менеджер пакетов для автоматической установки"
fi fi

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