Compare commits
	
		
			100 Commits
		
	
	
		
			v0.0.3
			...
			7a3acfe5c1
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 7a3acfe5c1 | |||
| 9cf8af08ab | |||
| 86940e8962 | |||
| db244204c7 | |||
| 9cb0a5e9ad | |||
| 1a57ccdb83 | |||
| 615cd83fb7 | |||
| 27e2f54653 | |||
| af57165c89 | |||
| 3770c82240 | |||
| 2dff463303 | |||
| 9085e38454 | |||
| a7d016abc9 | |||
| 4a5cca2d0f | |||
| 71000fd3cd | |||
| 71968bbe13 | |||
| 29c1a31066 | |||
| 8f94b61a0e | |||
| ae8e2d2807 | |||
| 0fa288b8a2 | |||
| dcac0b9ee5 | |||
| 4e6e1f524a | |||
| 47a9b9a96c | |||
| 9bb14312bd | |||
| 88b8d2fbf3 | |||
| 04523775f1 | |||
| adc4a42800 | |||
| 81651af20d | |||
| f04ebbaf14 | |||
| be1a137eab | |||
| 719a5b7fe7 | |||
| e05bb07f23 | |||
| ec053f7e6a | |||
| ad1696d507 | |||
| a57602a278 | |||
| 606cd5473a | |||
| d2bcb4e345 | |||
| 1fcb88976c | |||
| 55feea9b25 | |||
| 0d1db212e1 | |||
| 99ec48c4c6 | |||
| d201aae6e0 | |||
| 4463a32ae7 | |||
| 52fd4a5a00 | |||
| c437346957 | |||
| bf1fc0d878 | |||
| c3f879b379 | |||
| aa90dfa983 | |||
| bba1ed52c5 | |||
| dc1fac29d5 | |||
| 99857efb01 | |||
| 19bb87981c | |||
| 1c78adcca1 | |||
| a98bd44305 | |||
| 3deb6c9455 | |||
| 981f49587b | |||
| 35656d63a1 | |||
| 6410f7547b | |||
| 53e783df31 | |||
| fcc9ef5474 | |||
| f6ba4a1c26 | |||
| b5bf6ab61d | |||
| 18e90e4afc | |||
| a09863dfcb | |||
| fd643ea6cd | |||
| 309ecf784f | |||
| 30f95a4cbf | |||
| b9bf908007 | |||
| a6076b1253 | |||
| ac35b4d71d | |||
| 945f920654 | |||
| 84ac2377fb | |||
| de1db25202 | |||
| 2d6504b329 | |||
| 4ca557402a | |||
| e497d41030 | |||
| d46414a67c | |||
| 29e2f85eeb | |||
| c9c872abbc | |||
| fb93864d09 | |||
| 9fcd618a83 | |||
| 1fb9c6b574 | |||
| fb5c875713 | |||
| 3f428ab7b5 | |||
| 5b7af1f6b5 | |||
| 3224d7c6e4 | |||
| e1829c4824 | |||
| 12d83f2015 | |||
| 6bc6bfdcd9 | |||
| eeb25c239b | |||
| 91937a1fc5 | |||
| e827fb8049 | |||
| a13acc5ed0 | |||
| 52d3ab7791 | |||
| a345a24b95 | |||
| 5d1d3d7c45 | |||
| a711edbcc0 | |||
| d5636e8094 | |||
| 5d17875813 | |||
| 41eec2fc98 | 
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -6,3 +6,5 @@ | ||||
| .fleet | ||||
| .idea | ||||
| .gigaide | ||||
|  | ||||
| *.out | ||||
							
								
								
									
										50
									
								
								.golangci.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								.golangci.yml
									
									
									
									
									
										Normal 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" | ||||
| @@ -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: | ||||
|   hooks: | ||||
|     - go mod tidy | ||||
|   | ||||
							
								
								
									
										42
									
								
								.pre-commit-config.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								.pre-commit-config.yaml
									
									
									
									
									
										Normal 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 | ||||
| @@ -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 | ||||
| pipeline: | ||||
|   release: | ||||
|   | ||||
							
								
								
									
										25
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										25
									
								
								Makefile
									
									
									
									
									
								
							| @@ -11,13 +11,17 @@ ZSH_COMPLETION := $(COMPLETIONS_DIR)/zsh | ||||
| INSTALLED_BASH_COMPLETION := $(DESTDIR)$(PREFIX)/share/bash-completion/completions/$(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 | ||||
|  | ||||
| build: check-no-root $(BIN) | ||||
|  | ||||
| export CGO_ENABLED := 0 | ||||
| $(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: | ||||
| 	@if [[ "$$(whoami)" == 'root' ]]; then \ | ||||
| @@ -48,3 +52,22 @@ uninstall: | ||||
|  | ||||
| clean clear: | ||||
| 	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 | ||||
|   | ||||
							
								
								
									
										24
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										24
									
								
								README.md
									
									
									
									
									
								
							| @@ -3,11 +3,13 @@ | ||||
| </p> | ||||
| <b></b> | ||||
|  | ||||
| [](https://goreportcard.com/report/gitea.plemya-x.ru/Plemya-x/ALR)   | ||||
|  | ||||
| # 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 | ||||
| ``` | ||||
|  | ||||
| **ВАЖНО**: При этом скрипт будет загружен и запущен с <https://gitea.plemya-x.ru/xpamych/ALR/install>. Пожалуйста, просматривайте любые скрипты, которые вы скачиваете из Интернета (включая этот), прежде чем запускать их. | ||||
| **ВАЖНО**: При этом скрипт будет загружен и запущен с <https://plemya-x.ru/alr/install.sh>. Пожалуйста, просматривайте любые скрипты, которые вы скачиваете из Интернета (включая этот), прежде чем запускать их. | ||||
|  | ||||
| ### Сборка из исходного кода | ||||
|  | ||||
| Чтобы собрать ALR из исходного кода, вам понадобится версия Go 1.18 или новее. Как только Go будет установлен, клонируйте это репозиторий и запустите: | ||||
|  | ||||
| ```shell | ||||
| make build | ||||
| make build -B | ||||
| 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 | ||||
|  | ||||
| Discord - https://discord.com/channels/817759634105827358/1261631565084233749 | ||||
|  | ||||
| Telegram - https://t.me/plemyakh | ||||
|  | ||||
| ## Спасибы | ||||
| @@ -68,3 +73,6 @@ Telegram - https://t.me/plemyakh | ||||
| - <https://github.com/goreleaser/nfpm> | ||||
| - <https://github.com/charmbracelet/bubbletea> | ||||
| - <https://gitlab.com/cznic/sqlite> | ||||
|  | ||||
| Благодарим за активное участие в развитии проекта: | ||||
| - Maks1mS <maxim@slipenko.com> | ||||
							
								
								
									
										17
									
								
								assets/coverage-badge.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								assets/coverage-badge.svg
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										18
									
								
								assets/i18n-ru-badge.svg
									
									
									
									
									
										Normal 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 | 
							
								
								
									
										254
									
								
								build.go
									
									
									
									
									
								
							
							
						
						
									
										254
									
								
								build.go
									
									
									
									
									
								
							| @@ -1,100 +1,184 @@ | ||||
| /* | ||||
|  * 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/>. | ||||
|  */ | ||||
| // 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 | ||||
|  | ||||
| import ( | ||||
| 	"log/slog" | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/leonelquinteros/gotext" | ||||
| 	"github.com/urfave/cli/v2" | ||||
| 	"plemya-x.ru/alr/internal/config" | ||||
| 	"plemya-x.ru/alr/internal/osutils" | ||||
| 	"plemya-x.ru/alr/internal/types" | ||||
| 	"plemya-x.ru/alr/pkg/build" | ||||
| 	"plemya-x.ru/alr/pkg/loggerctx" | ||||
| 	"plemya-x.ru/alr/pkg/manager" | ||||
| 	"plemya-x.ru/alr/pkg/repos" | ||||
|  | ||||
| 	"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/osutils" | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/types" | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/build" | ||||
| 	"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{ | ||||
| 	Name:  "build", | ||||
| 	Usage: "Build a local package", | ||||
| 	Flags: []cli.Flag{ | ||||
| 		&cli.StringFlag{ | ||||
| 			Name:    "script", | ||||
| 			Aliases: []string{"s"}, | ||||
| 			Value:   "alr.sh", | ||||
| 			Usage:   "Path to the build script", | ||||
| func BuildCmd() *cli.Command { | ||||
| 	return &cli.Command{ | ||||
| 		Name:  "build", | ||||
| 		Usage: gotext.Get("Build a local package"), | ||||
| 		Flags: []cli.Flag{ | ||||
| 			&cli.StringFlag{ | ||||
| 				Name:    "script", | ||||
| 				Aliases: []string{"s"}, | ||||
| 				Value:   "alr.sh", | ||||
| 				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{ | ||||
| 				Name:    "package", | ||||
| 				Aliases: []string{"p"}, | ||||
| 				Usage:   gotext.Get("Name of the package to build and its repo (example: default/go-bin)"), | ||||
| 			}, | ||||
| 			&cli.BoolFlag{ | ||||
| 				Name:    "clean", | ||||
| 				Aliases: []string{"c"}, | ||||
| 				Usage:   gotext.Get("Build package from scratch even if there's an already built package available"), | ||||
| 			}, | ||||
| 		}, | ||||
| 		&cli.StringFlag{ | ||||
| 			Name:    "package", | ||||
| 			Aliases: []string{"p"}, | ||||
| 			Usage:   "Name of the package to build and its repo (example: default/go-bin)", | ||||
| 		}, | ||||
| 		&cli.BoolFlag{ | ||||
| 			Name:    "clean", | ||||
| 			Aliases: []string{"c"}, | ||||
| 			Usage:   "Build package from scratch even if there's an already built package available", | ||||
| 		}, | ||||
| 	}, | ||||
| 	Action: func(c *cli.Context) error { | ||||
| 		ctx := c.Context | ||||
| 		log := loggerctx.From(ctx) | ||||
|  | ||||
| 		script := c.String("script") | ||||
| 		if c.String("package") != "" { | ||||
| 			script = filepath.Join(config.GetPaths(ctx).RepoDir, c.String("package"), "alr.sh") | ||||
| 		} | ||||
|  | ||||
| 		err := repos.Pull(ctx, config.Config(ctx).Repos) | ||||
| 		if err != nil { | ||||
| 			log.Fatal("Error pulling repositories").Err(err).Send() | ||||
| 		} | ||||
|  | ||||
| 		mgr := manager.Detect() | ||||
| 		if mgr == nil { | ||||
| 			log.Fatal("Unable to detect a supported package manager on the system").Send() | ||||
| 		} | ||||
|  | ||||
| 		pkgPaths, _, err := build.BuildPackage(ctx, types.BuildOpts{ | ||||
| 			Script:      script, | ||||
| 			Manager:     mgr, | ||||
| 			Clean:       c.Bool("clean"), | ||||
| 			Interactive: c.Bool("interactive"), | ||||
| 		}) | ||||
| 		if err != nil { | ||||
| 			log.Fatal("Error building package").Err(err).Send() | ||||
| 		} | ||||
|  | ||||
| 		wd, err := os.Getwd() | ||||
| 		if err != nil { | ||||
| 			log.Fatal("Error getting working directory").Err(err).Send() | ||||
| 		} | ||||
|  | ||||
| 		for _, pkgPath := range pkgPaths { | ||||
| 			name := filepath.Base(pkgPath) | ||||
| 			err = osutils.Move(pkgPath, filepath.Join(wd, name)) | ||||
| 		Action: func(c *cli.Context) error { | ||||
| 			ctx := c.Context | ||||
| 			cfg := config.New() | ||||
| 			db := database.New(cfg) | ||||
| 			rs := repos.New(cfg, db) | ||||
| 			err := db.Init(ctx) | ||||
| 			if err != nil { | ||||
| 				log.Fatal("Error moving the package").Err(err).Send() | ||||
| 				slog.Error(gotext.Get("Error initialization database"), "err", err) | ||||
| 				os.Exit(1) | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		return nil | ||||
| 	}, | ||||
| 			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() | ||||
| 			if mgr == nil { | ||||
| 				slog.Error(gotext.Get("Unable to detect a supported package manager on the system")) | ||||
| 				os.Exit(1) | ||||
| 			} | ||||
|  | ||||
| 			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, | ||||
| 					Manager:     mgr, | ||||
| 					Clean:       c.Bool("clean"), | ||||
| 					Interactive: c.Bool("interactive"), | ||||
| 				}, | ||||
| 				rs, | ||||
| 				info, | ||||
| 				cfg, | ||||
| 			) | ||||
|  | ||||
| 			// Сборка пакета | ||||
| 			pkgPaths, _, err := builder.BuildPackage(ctx) | ||||
| 			if err != nil { | ||||
| 				slog.Error(gotext.Get("Error building package"), "err", err) | ||||
| 				os.Exit(1) | ||||
| 			} | ||||
|  | ||||
| 			// Получение текущей рабочей директории | ||||
| 			wd, err := os.Getwd() | ||||
| 			if err != nil { | ||||
| 				slog.Error(gotext.Get("Error getting working directory"), "err", err) | ||||
| 				os.Exit(1) | ||||
| 			} | ||||
|  | ||||
| 			// Перемещение собранных пакетов в рабочую директорию | ||||
| 			for _, pkgPath := range pkgPaths { | ||||
| 				name := filepath.Base(pkgPath) | ||||
| 				err = osutils.Move(pkgPath, filepath.Join(wd, name)) | ||||
| 				if err != nil { | ||||
| 					slog.Error(gotext.Get("Error moving the package"), "err", err) | ||||
| 					os.Exit(1) | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			return nil | ||||
| 		}, | ||||
| 	} | ||||
| } | ||||
|   | ||||
							
								
								
									
										108
									
								
								fix.go
									
									
									
									
									
								
							
							
						
						
									
										108
									
								
								fix.go
									
									
									
									
									
								
							| @@ -1,64 +1,78 @@ | ||||
| /* | ||||
|  * 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/>. | ||||
|  */ | ||||
| // 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 | ||||
|  | ||||
| import ( | ||||
| 	"log/slog" | ||||
| 	"os" | ||||
|  | ||||
| 	"github.com/leonelquinteros/gotext" | ||||
| 	"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/repos" | ||||
|  | ||||
| 	"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/repos" | ||||
| ) | ||||
|  | ||||
| var fixCmd = &cli.Command{ | ||||
| 	Name:  "fix", | ||||
| 	Usage: "Attempt to fix problems with ALR", | ||||
| 	Action: func(c *cli.Context) error { | ||||
| 		ctx := c.Context | ||||
| 		log := loggerctx.From(ctx) | ||||
| func FixCmd() *cli.Command { | ||||
| 	return &cli.Command{ | ||||
| 		Name:  "fix", | ||||
| 		Usage: gotext.Get("Attempt to fix problems with ALR"), | ||||
| 		Action: func(c *cli.Context) error { | ||||
| 			ctx := c.Context | ||||
| 			cfg := config.New() | ||||
| 			paths := cfg.GetPaths(ctx) | ||||
|  | ||||
| 		db.Close() | ||||
| 		paths := config.GetPaths(ctx) | ||||
| 			slog.Info(gotext.Get("Removing cache directory")) | ||||
|  | ||||
| 		log.Info("Removing cache directory").Send() | ||||
| 			err := os.RemoveAll(paths.CacheDir) | ||||
| 			if err != nil { | ||||
| 				slog.Error(gotext.Get("Unable to remove cache directory"), "err", err) | ||||
| 				os.Exit(1) | ||||
| 			} | ||||
|  | ||||
| 		err := os.RemoveAll(paths.CacheDir) | ||||
| 		if err != nil { | ||||
| 			log.Fatal("Unable to remove cache directory").Err(err).Send() | ||||
| 		} | ||||
| 			slog.Info(gotext.Get("Rebuilding cache")) | ||||
|  | ||||
| 		log.Info("Rebuilding cache").Send() | ||||
| 			err = os.MkdirAll(paths.CacheDir, 0o755) | ||||
| 			if err != nil { | ||||
| 				slog.Error(gotext.Get("Unable to create new cache directory"), "err", err) | ||||
| 				os.Exit(1) | ||||
| 			} | ||||
|  | ||||
| 		err = os.MkdirAll(paths.CacheDir, 0o755) | ||||
| 		if err != nil { | ||||
| 			log.Fatal("Unable to create new cache directory").Err(err).Send() | ||||
| 		} | ||||
| 			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) | ||||
| 			err = rs.Pull(ctx, cfg.Repos(ctx)) | ||||
| 			if err != nil { | ||||
| 				slog.Error(gotext.Get("Error pulling repos"), "err", err) | ||||
| 				os.Exit(1) | ||||
| 			} | ||||
|  | ||||
| 		err = repos.Pull(ctx, config.Config(ctx).Repos) | ||||
| 		if err != nil { | ||||
| 			log.Fatal("Error pulling repos").Err(err).Send() | ||||
| 		} | ||||
| 			slog.Info(gotext.Get("Done")) | ||||
|  | ||||
| 		log.Info("Done").Send() | ||||
|  | ||||
| 		return nil | ||||
| 	}, | ||||
| 			return nil | ||||
| 		}, | ||||
| 	} | ||||
| } | ||||
|   | ||||
							
								
								
									
										93
									
								
								gen.go
									
									
									
									
									
								
							
							
						
						
									
										93
									
								
								gen.go
									
									
									
									
									
								
							| @@ -1,45 +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 main | ||||
|  | ||||
| import ( | ||||
| 	"os" | ||||
|  | ||||
| 	"github.com/leonelquinteros/gotext" | ||||
| 	"github.com/urfave/cli/v2" | ||||
| 	"plemya-x.ru/alr/pkg/gen" | ||||
|  | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/gen" | ||||
| ) | ||||
|  | ||||
| var genCmd = &cli.Command{ | ||||
| 	Name:    "generate", | ||||
| 	Usage:   "Generate a ALR script from a template", | ||||
| 	Aliases: []string{"gen"}, | ||||
| 	Subcommands: []*cli.Command{ | ||||
| 		genPipCmd, | ||||
| 	}, | ||||
| } | ||||
|  | ||||
| var genPipCmd = &cli.Command{ | ||||
| 	Name:  "pip", | ||||
| 	Usage: "Generate a ALR script for a pip module", | ||||
| 	Flags: []cli.Flag{ | ||||
| 		&cli.StringFlag{ | ||||
| 			Name:     "name", | ||||
| 			Aliases:  []string{"n"}, | ||||
| 			Required: true, | ||||
| 		}, | ||||
| 		&cli.StringFlag{ | ||||
| 			Name:     "version", | ||||
| 			Aliases:  []string{"v"}, | ||||
| 			Required: true, | ||||
| 		}, | ||||
| 		&cli.StringFlag{ | ||||
| 			Name:    "description", | ||||
| 			Aliases: []string{"d"}, | ||||
| 		}, | ||||
| 	}, | ||||
| 	Action: func(c *cli.Context) error { | ||||
| 		return gen.Pip(os.Stdout, gen.PipOptions{ | ||||
| 			Name:        c.String("name"), | ||||
| 			Version:     c.String("version"), | ||||
| 			Description: c.String("description"), | ||||
| 		}) | ||||
| 	}, | ||||
| func GenCmd() *cli.Command { | ||||
| 	return &cli.Command{ | ||||
| 		Name:    "generate", | ||||
| 		Usage:   gotext.Get("Generate a ALR script from a template"), | ||||
| 		Aliases: []string{"gen"}, | ||||
| 		Subcommands: []*cli.Command{ | ||||
| 			{ | ||||
| 				Name:  "pip", | ||||
| 				Usage: gotext.Get("Generate a ALR script for a pip module"), | ||||
| 				Flags: []cli.Flag{ | ||||
| 					&cli.StringFlag{ | ||||
| 						Name:     "name", | ||||
| 						Aliases:  []string{"n"}, | ||||
| 						Required: true, | ||||
| 					}, | ||||
| 					&cli.StringFlag{ | ||||
| 						Name:     "version", | ||||
| 						Aliases:  []string{"v"}, | ||||
| 						Required: true, | ||||
| 					}, | ||||
| 					&cli.StringFlag{ | ||||
| 						Name:    "description", | ||||
| 						Aliases: []string{"d"}, | ||||
| 					}, | ||||
| 				}, | ||||
| 				Action: func(c *cli.Context) error { | ||||
| 					return gen.Pip(os.Stdout, gen.PipOptions{ | ||||
| 						Name:        c.String("name"), | ||||
| 						Version:     c.String("version"), | ||||
| 						Description: c.String("description"), | ||||
| 					}) | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| 	} | ||||
| } | ||||
|   | ||||
							
								
								
									
										61
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										61
									
								
								go.mod
									
									
									
									
									
								
							| @@ -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 ( | ||||
| 	gitea.plemya-x.ru/Plemya-x/fakeroot v0.0.1 | ||||
| 	github.com/AlecAivazis/survey/v2 v2.3.7 | ||||
| 	github.com/PuerkitoBio/purell v1.2.0 | ||||
| 	github.com/alecthomas/chroma/v2 v2.9.1 | ||||
| 	github.com/charmbracelet/bubbles v0.16.1 | ||||
| 	github.com/charmbracelet/bubbletea v0.24.2 | ||||
| 	github.com/charmbracelet/lipgloss v0.8.0 | ||||
| 	github.com/charmbracelet/bubbles v0.20.0 | ||||
| 	github.com/charmbracelet/bubbletea v1.2.4 | ||||
| 	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-git/v5 v5.12.0 | ||||
| 	github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 | ||||
| 	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/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/mitchellh/mapstructure v1.5.0 | ||||
| 	github.com/muesli/reflow v0.3.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/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 | ||||
| 	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/sys v0.20.0 | ||||
| 	golang.org/x/text v0.15.0 | ||||
| 	golang.org/x/sys v0.29.0 | ||||
| 	golang.org/x/text v0.21.0 | ||||
| 	gopkg.in/yaml.v3 v3.0.1 | ||||
| 	modernc.org/sqlite v1.25.0 | ||||
| 	mvdan.cc/sh/v3 v3.7.0 | ||||
| 	plemya-x.ru/fakeroot v0.0.0-20240601131003-c638a3543283 | ||||
| 	mvdan.cc/sh/v3 v3.10.0 | ||||
| ) | ||||
|  | ||||
| require ( | ||||
| @@ -51,22 +53,26 @@ require ( | ||||
| 	github.com/bodgit/sevenzip v1.3.0 // indirect | ||||
| 	github.com/bodgit/windows v1.0.0 // 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/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/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/dsnet/compress v0.0.1 // indirect | ||||
| 	github.com/dustin/go-humanize v1.0.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-logfmt/logfmt v0.6.0 // indirect | ||||
| 	github.com/gobwas/glob v0.2.3 // indirect | ||||
| 	github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect | ||||
| 	github.com/golang/snappy v0.0.4 // indirect | ||||
| 	github.com/google/rpmpack v0.6.1-0.20240329070804-c2247cbb881a // 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/fileglob v1.3.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/pgzip v1.2.6 // 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-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/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect | ||||
| 	github.com/mitchellh/copystructure v1.2.0 // 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/termenv v0.15.2 // indirect | ||||
| 	github.com/nwaples/rardecode/v2 v2.0.0-beta.2 // indirect | ||||
| 	github.com/pierrec/lz4/v4 v4.1.15 // 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/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/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect | ||||
| 	github.com/shopspring/decimal v1.2.0 // indirect | ||||
| @@ -103,15 +109,14 @@ require ( | ||||
| 	github.com/ulikunitz/xz v0.5.12 // indirect | ||||
| 	github.com/vmihailenco/tagparser/v2 v2.0.0 // 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 | ||||
| 	gitlab.com/digitalxero/go-conventional-commit v1.0.7 // indirect | ||||
| 	go4.org v0.0.0-20200411211856-f5505b9728dd // indirect | ||||
| 	golang.org/x/mod v0.14.0 // indirect | ||||
| 	golang.org/x/net v0.23.0 // indirect | ||||
| 	golang.org/x/sync v0.5.0 // indirect | ||||
| 	golang.org/x/term v0.20.0 // indirect | ||||
| 	golang.org/x/tools v0.16.0 // indirect | ||||
| 	golang.org/x/mod v0.18.0 // indirect | ||||
| 	golang.org/x/net v0.26.0 // indirect | ||||
| 	golang.org/x/sync v0.10.0 // indirect | ||||
| 	golang.org/x/term v0.28.0 // indirect | ||||
| 	golang.org/x/tools v0.22.0 // indirect | ||||
| 	gopkg.in/warnings.v0 v0.1.2 // indirect | ||||
| 	lukechampine.com/uint128 v1.2.0 // indirect | ||||
| 	modernc.org/cc/v3 v3.40.0 // indirect | ||||
|   | ||||
							
								
								
									
										126
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										126
									
								
								go.sum
									
									
									
									
									
								
							| @@ -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/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= | ||||
| 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/go.mod h1:xUTIdE4KCOIjsBAE1JYsUPoCqYdZ1reCfTwbto0Fduo= | ||||
| 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/go.mod h1:pBdaqQjnvXxdS/6CvNDwIANIFSP0xRKI16PX4xejRQc= | ||||
| 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.16.1/go.mod h1:2QCp9LFlEsBQMvIYERr7Ww2H2bA7xen1idUDIzm/+Xc= | ||||
| github.com/charmbracelet/bubbletea v0.24.2 h1:uaQIKx9Ai6Gdh5zpTbGiWpytMU+CfsPp06RaW2cx/SY= | ||||
| github.com/charmbracelet/bubbletea v0.24.2/go.mod h1:XdrNrV4J8GiyshTtx3DNuYkR1FDaJmO3l2nejekbsgg= | ||||
| github.com/charmbracelet/lipgloss v0.8.0 h1:IS00fk4XAHcf8uZKc3eHeMUTCxUH6NkaTrdyCQk84RU= | ||||
| github.com/charmbracelet/lipgloss v0.8.0/go.mod h1:p4eYUZZJ/0oXTuCQKFF8mqyKCz0ja6y+7DniDDw5KKU= | ||||
| github.com/charmbracelet/bubbles v0.20.0 h1:jSZu6qD8cRQ6k9OMfR1WlM+ruM8fkPWkHvQWD9LIutE= | ||||
| github.com/charmbracelet/bubbles v0.20.0/go.mod h1:39slydyswPy+uVOHZ5x/GjwVAFkCsV8IIVy+4MhzwwU= | ||||
| github.com/charmbracelet/bubbletea v1.2.4 h1:KN8aCViA0eps9SCOThb2/XPIlea3ANJLUkv3KnQRNCE= | ||||
| github.com/charmbracelet/bubbletea v1.2.4/go.mod h1:Qr6fVQw+wX7JkWWkVyXYk/ZUQ92a6XNekLXa3rR18MM= | ||||
| github.com/charmbracelet/harmonica v0.2.0 h1:8NxJWRWg/bzKqqEaaeFNipOu77YR5t8aSwG4pgaUBiQ= | ||||
| 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/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= | ||||
| 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/connesc/cipherio v0.2.1 h1:FGtpTPMbKNNWByNrr9aEBtaJtXjqOzkIXNYJp6OEycw= | ||||
| 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/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= | ||||
| 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.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= | ||||
| github.com/creack/pty v1.1.23 h1:4M6+isWdcStXEf15G/RbrMPOQj1dZ7HPZCGwE4kOeP0= | ||||
| 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/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= | ||||
| 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/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/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/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= | ||||
| 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-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-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/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= | ||||
| 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/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/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.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= | ||||
| 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.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/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k= | ||||
| 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/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/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/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.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/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/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= | ||||
| 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.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= | ||||
| 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/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/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= | ||||
| github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE= | ||||
| 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.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.17/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.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= | ||||
| github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= | ||||
| github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= | ||||
| 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/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= | ||||
| 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.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= | ||||
| github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= | ||||
| github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= | ||||
| github.com/mattn/go-runewidth v0.0.16/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.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y= | ||||
| 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/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/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.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= | ||||
| 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.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= | ||||
| 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-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho= | ||||
| github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= | ||||
| 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/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= | ||||
| 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/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.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= | ||||
| github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= | ||||
| github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= | ||||
| 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.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= | ||||
| github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= | ||||
| github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= | ||||
| 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/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= | ||||
| 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/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/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= | ||||
| 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.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= | ||||
| 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.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.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.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= | ||||
| github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= | ||||
| github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= | ||||
| github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= | ||||
| 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/go.mod h1:3K3UH1yCKgBneZYhuQUvJ9HPD19UEXEI0BWbMn8qNMY= | ||||
| 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/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo= | ||||
| 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/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= | ||||
| 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/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/go.mod h1:/7PNW7nFnDR5W7UXZVc04gdVLR/wBNgkm33KgIz0OBk= | ||||
| 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.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.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= | ||||
| golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= | ||||
| golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= | ||||
| 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-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= | ||||
| 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.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.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= | ||||
| golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= | ||||
| golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= | ||||
| 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-20180826012351-8a410e7b638d/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.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.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= | ||||
| golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= | ||||
| golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= | ||||
| 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-20190226205417-e64efc72b421/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-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.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= | ||||
| golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= | ||||
| golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= | ||||
| 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-20190215142949-d0b11bdaac8a/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-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-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-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-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-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.3.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.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= | ||||
| golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= | ||||
| golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= | ||||
| 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-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= | ||||
| 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.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= | ||||
| golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw= | ||||
| golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= | ||||
| golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg= | ||||
| 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.3.0/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.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.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= | ||||
| golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= | ||||
| golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= | ||||
| 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-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= | ||||
| 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.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.16.0 h1:GO788SKMRunPIBCXiQyo2AaexLstOrVhuAL5YwsckQM= | ||||
| golang.org/x/tools v0.16.0/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= | ||||
| golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= | ||||
| 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-20191011141410-1b5146add898/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/z v1.7.3 h1:zDJf6iHjrnB+WRD88stbXokugjyc0/pB91ri1gO6LZY= | ||||
| 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.7.0/go.mod h1:K2gwkaesF/D7av7Kxl0HbF5kGOd2ArupNTX3X44+8l8= | ||||
| 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= | ||||
| mvdan.cc/sh/v3 v3.10.0 h1:v9z7N1DLZ7owyLM/SXZQkBSXcwr2IGMm2LY2pmhVXj4= | ||||
| mvdan.cc/sh/v3 v3.10.0/go.mod h1:z/mSSVyLFGZzqb3ZIKojjyqIx/xbmz/UHdCSv9HmqXY= | ||||
| 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/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= | ||||
|   | ||||
							
								
								
									
										157
									
								
								helper.go
									
									
									
									
									
								
							
							
						
						
									
										157
									
								
								helper.go
									
									
									
									
									
								
							| @@ -1,86 +1,111 @@ | ||||
| // 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 | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"log/slog" | ||||
| 	"os" | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/leonelquinteros/gotext" | ||||
| 	"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/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{ | ||||
| 	Name:        "helper", | ||||
| 	Usage:       "Run a ALR helper command", | ||||
| 	ArgsUsage:   `<helper_name|"list">`, | ||||
| 	Subcommands: []*cli.Command{helperListCmd}, | ||||
| 	Flags: []cli.Flag{ | ||||
| 		&cli.StringFlag{ | ||||
| 			Name:    "dest-dir", | ||||
| 			Aliases: []string{"d"}, | ||||
| 			Usage:   "The directory that the install commands will install to", | ||||
| 			Value:   "dest", | ||||
| 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 | ||||
| 		}, | ||||
| 	}, | ||||
| 	Action: func(c *cli.Context) error { | ||||
| 		ctx := c.Context | ||||
| 		log := loggerctx.From(ctx) | ||||
| 	} | ||||
|  | ||||
| 		if c.Args().Len() < 1 { | ||||
| 			cli.ShowSubcommandHelpAndExit(c, 1) | ||||
| 		} | ||||
| 	return &cli.Command{ | ||||
| 		Name:        "helper", | ||||
| 		Usage:       gotext.Get("Run a ALR helper command"), | ||||
| 		ArgsUsage:   `<helper_name|"list">`, | ||||
| 		Subcommands: []*cli.Command{helperListCmd}, | ||||
| 		Flags: []cli.Flag{ | ||||
| 			&cli.StringFlag{ | ||||
| 				Name:    "dest-dir", | ||||
| 				Aliases: []string{"d"}, | ||||
| 				Usage:   gotext.Get("The directory that the install commands will install to"), | ||||
| 				Value:   "dest", | ||||
| 			}, | ||||
| 		}, | ||||
| 		Action: func(c *cli.Context) error { | ||||
| 			ctx := c.Context | ||||
|  | ||||
| 		helper, ok := helpers.Helpers[c.Args().First()] | ||||
| 		if !ok { | ||||
| 			log.Fatal("No such helper command").Str("name", c.Args().First()).Send() | ||||
| 		} | ||||
| 			if c.Args().Len() < 1 { | ||||
| 				cli.ShowSubcommandHelpAndExit(c, 1) | ||||
| 			} | ||||
|  | ||||
| 		wd, err := os.Getwd() | ||||
| 		if err != nil { | ||||
| 			log.Fatal("Error getting working directory").Err(err).Send() | ||||
| 		} | ||||
| 			helper, ok := helpers.Helpers[c.Args().First()] | ||||
| 			if !ok { | ||||
| 				slog.Error(gotext.Get("No such helper command"), "name", c.Args().First()) | ||||
| 				os.Exit(1) | ||||
| 			} | ||||
|  | ||||
| 		info, err := distro.ParseOSRelease(ctx) | ||||
| 		if err != nil { | ||||
| 			log.Fatal("Error getting working directory").Err(err).Send() | ||||
| 		} | ||||
| 			wd, err := os.Getwd() | ||||
| 			if err != nil { | ||||
| 				slog.Error(gotext.Get("Error getting working directory"), "err", err) | ||||
| 				os.Exit(1) | ||||
| 			} | ||||
|  | ||||
| 		hc := interp.HandlerContext{ | ||||
| 			Env: expand.ListEnviron( | ||||
| 				"pkgdir="+c.String("dest-dir"), | ||||
| 				"DISTRO_ID="+info.ID, | ||||
| 				"DISTRO_ID_LIKE="+strings.Join(info.Like, " "), | ||||
| 				"ARCH="+cpu.Arch(), | ||||
| 			), | ||||
| 			Dir:    wd, | ||||
| 			Stdin:  os.Stdin, | ||||
| 			Stdout: os.Stdout, | ||||
| 			Stderr: os.Stderr, | ||||
| 		} | ||||
| 			info, err := distro.ParseOSRelease(ctx) | ||||
| 			if err != nil { | ||||
| 				slog.Error(gotext.Get("Error getting working directory"), "err", err) | ||||
| 				os.Exit(1) | ||||
| 			} | ||||
|  | ||||
| 		return helper(hc, c.Args().First(), c.Args().Slice()[1:]) | ||||
| 	}, | ||||
| 	CustomHelpTemplate: cli.CommandHelpTemplate, | ||||
| 	BashComplete: func(ctx *cli.Context) { | ||||
| 		for name := range helpers.Helpers { | ||||
| 			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 | ||||
| 	}, | ||||
| 			hc := interp.HandlerContext{ | ||||
| 				Env: expand.ListEnviron( | ||||
| 					"pkgdir="+c.String("dest-dir"), | ||||
| 					"DISTRO_ID="+info.ID, | ||||
| 					"DISTRO_ID_LIKE="+strings.Join(info.Like, " "), | ||||
| 					"ARCH="+cpu.Arch(), | ||||
| 				), | ||||
| 				Dir:    wd, | ||||
| 				Stdin:  os.Stdin, | ||||
| 				Stdout: os.Stdout, | ||||
| 				Stderr: os.Stderr, | ||||
| 			} | ||||
|  | ||||
| 			return helper(hc, c.Args().First(), c.Args().Slice()[1:]) | ||||
| 		}, | ||||
| 		CustomHelpTemplate: cli.CommandHelpTemplate, | ||||
| 		BashComplete: func(ctx *cli.Context) { | ||||
| 			for name := range helpers.Helpers { | ||||
| 				fmt.Println(name) | ||||
| 			} | ||||
| 		}, | ||||
| 	} | ||||
| } | ||||
|   | ||||
							
								
								
									
										222
									
								
								info.go
									
									
									
									
									
								
							
							
						
						
									
										222
									
								
								info.go
									
									
									
									
									
								
							| @@ -1,106 +1,164 @@ | ||||
| /* | ||||
|  * 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/>. | ||||
|  */ | ||||
| // 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 | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"log/slog" | ||||
| 	"os" | ||||
|  | ||||
| 	"github.com/jeandeaual/go-locale" | ||||
| 	"github.com/leonelquinteros/gotext" | ||||
| 	"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" | ||||
|  | ||||
| 	"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{ | ||||
| 	Name:  "info", | ||||
| 	Usage: "Print information about a package", | ||||
| 	Flags: []cli.Flag{ | ||||
| 		&cli.BoolFlag{ | ||||
| 			Name:    "all", | ||||
| 			Aliases: []string{"a"}, | ||||
| 			Usage:   "Show all information, not just for the current distro", | ||||
| func InfoCmd() *cli.Command { | ||||
| 	return &cli.Command{ | ||||
| 		Name:  "info", | ||||
| 		Usage: gotext.Get("Print information about a package"), | ||||
| 		Flags: []cli.Flag{ | ||||
| 			&cli.BoolFlag{ | ||||
| 				Name:    "all", | ||||
| 				Aliases: []string{"a"}, | ||||
| 				Usage:   gotext.Get("Show all information, not just for the current distro"), | ||||
| 			}, | ||||
| 		}, | ||||
| 	}, | ||||
| 	Action: func(c *cli.Context) error { | ||||
| 		ctx := c.Context | ||||
| 		log := loggerctx.From(ctx) | ||||
|  | ||||
| 		args := c.Args() | ||||
| 		if args.Len() < 1 { | ||||
| 			log.Fatalf("Command info expected at least 1 argument, got %d", args.Len()).Send() | ||||
| 		} | ||||
|  | ||||
| 		err := repos.Pull(ctx, config.Config(ctx).Repos) | ||||
| 		if err != nil { | ||||
| 			log.Fatal("Error pulling repositories").Err(err).Send() | ||||
| 		} | ||||
|  | ||||
| 		found, _, err := repos.FindPkgs(ctx, args.Slice()) | ||||
| 		if err != nil { | ||||
| 			log.Fatal("Error finding packages").Err(err).Send() | ||||
| 		} | ||||
|  | ||||
| 		if len(found) == 0 { | ||||
| 			os.Exit(1) | ||||
| 		} | ||||
|  | ||||
| 		pkgs := cliutils.FlattenPkgs(ctx, found, "show", c.Bool("interactive")) | ||||
|  | ||||
| 		var names []string | ||||
| 		all := c.Bool("all") | ||||
|  | ||||
| 		if !all { | ||||
| 			info, err := distro.ParseOSRelease(ctx) | ||||
| 		BashComplete: func(c *cli.Context) { | ||||
| 			ctx := c.Context | ||||
| 			cfg := config.New() | ||||
| 			db := database.New(cfg) | ||||
| 			err := db.Init(ctx) | ||||
| 			if err != nil { | ||||
| 				log.Fatal("Error parsing os-release file").Err(err).Send() | ||||
| 				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 { | ||||
| 			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) | ||||
| 			} | ||||
| 			rs := repos.New(cfg, db) | ||||
|  | ||||
| 			args := c.Args() | ||||
| 			if args.Len() < 1 { | ||||
| 				slog.Error(gotext.Get("Command info expected at least 1 argument, got %d", args.Len())) | ||||
| 				os.Exit(1) | ||||
| 			} | ||||
|  | ||||
| 			if cfg.AutoPull(ctx) { | ||||
| 				err := rs.Pull(ctx, cfg.Repos(ctx)) | ||||
| 				if err != nil { | ||||
| 					slog.Error(gotext.Get("Error pulling repos"), "err", err) | ||||
| 					os.Exit(1) | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			found, _, err := rs.FindPkgs(ctx, args.Slice()) | ||||
| 			if err != nil { | ||||
| 				slog.Error(gotext.Get("Error finding packages"), "err", err) | ||||
| 				os.Exit(1) | ||||
| 			} | ||||
|  | ||||
| 			if len(found) == 0 { | ||||
| 				os.Exit(1) | ||||
| 			} | ||||
|  | ||||
| 			pkgs := cliutils.FlattenPkgs(ctx, found, "show", c.Bool("interactive")) | ||||
|  | ||||
| 			var names []string | ||||
| 			all := c.Bool("all") | ||||
|  | ||||
| 			systemLang, err := locale.GetLanguage() | ||||
| 			if err != nil { | ||||
| 				slog.Error("Can't detect system language", "err", err) | ||||
| 				os.Exit(1) | ||||
| 			} | ||||
| 			names, err = overrides.Resolve( | ||||
| 				info, | ||||
| 				overrides.DefaultOpts. | ||||
| 					WithLanguages([]string{config.SystemLang()}), | ||||
| 			) | ||||
| 			if err != nil { | ||||
| 				log.Fatal("Error resolving overrides").Err(err).Send() | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		for _, pkg := range pkgs { | ||||
| 			if !all { | ||||
| 				err = yaml.NewEncoder(os.Stdout).Encode(overrides.ResolvePackage(&pkg, names)) | ||||
| 				info, err := distro.ParseOSRelease(ctx) | ||||
| 				if err != nil { | ||||
| 					log.Fatal("Error encoding script variables").Err(err).Send() | ||||
| 					slog.Error(gotext.Get("Error parsing os-release file"), "err", err) | ||||
| 					os.Exit(1) | ||||
| 				} | ||||
| 			} else { | ||||
| 				err = yaml.NewEncoder(os.Stdout).Encode(pkg) | ||||
| 				names, err = overrides.Resolve( | ||||
| 					info, | ||||
| 					overrides.DefaultOpts. | ||||
| 						WithLanguages([]string{systemLang}), | ||||
| 				) | ||||
| 				if err != nil { | ||||
| 					log.Fatal("Error encoding script variables").Err(err).Send() | ||||
| 					slog.Error(gotext.Get("Error resolving overrides"), "err", err) | ||||
| 					os.Exit(1) | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			fmt.Println("---") | ||||
| 		} | ||||
| 			for _, pkg := range pkgs { | ||||
| 				if !all { | ||||
| 					err = yaml.NewEncoder(os.Stdout).Encode(overrides.ResolvePackage(&pkg, names)) | ||||
| 					if err != nil { | ||||
| 						slog.Error(gotext.Get("Error encoding script variables"), "err", err) | ||||
| 						os.Exit(1) | ||||
| 					} | ||||
| 				} else { | ||||
| 					err = yaml.NewEncoder(os.Stdout).Encode(pkg) | ||||
| 					if err != nil { | ||||
| 						slog.Error(gotext.Get("Error encoding script variables"), "err", err) | ||||
| 						os.Exit(1) | ||||
| 					} | ||||
| 				} | ||||
|  | ||||
| 		return nil | ||||
| 	}, | ||||
| 				fmt.Println("---") | ||||
| 			} | ||||
|  | ||||
| 			return nil | ||||
| 		}, | ||||
| 	} | ||||
| } | ||||
|   | ||||
							
								
								
									
										259
									
								
								install.go
									
									
									
									
									
								
							
							
						
						
									
										259
									
								
								install.go
									
									
									
									
									
								
							| @@ -1,122 +1,175 @@ | ||||
| /* | ||||
|  * 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/>. | ||||
|  */ | ||||
| // 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 | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"log/slog" | ||||
| 	"os" | ||||
|  | ||||
| 	"github.com/leonelquinteros/gotext" | ||||
| 	"github.com/urfave/cli/v2" | ||||
| 	"plemya-x.ru/alr/internal/cliutils" | ||||
| 	"plemya-x.ru/alr/internal/config" | ||||
| 	"plemya-x.ru/alr/internal/db" | ||||
| 	"plemya-x.ru/alr/internal/types" | ||||
| 	"plemya-x.ru/alr/pkg/build" | ||||
| 	"plemya-x.ru/alr/pkg/loggerctx" | ||||
| 	"plemya-x.ru/alr/pkg/manager" | ||||
| 	"plemya-x.ru/alr/pkg/repos" | ||||
|  | ||||
| 	"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/types" | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/build" | ||||
| 	"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 installCmd = &cli.Command{ | ||||
| 	Name:    "install", | ||||
| 	Usage:   "Install a new package", | ||||
| 	Aliases: []string{"in"}, | ||||
| 	Flags: []cli.Flag{ | ||||
| 		&cli.BoolFlag{ | ||||
| 			Name:    "clean", | ||||
| 			Aliases: []string{"c"}, | ||||
| 			Usage:   "Build package from scratch even if there's an already built package available", | ||||
| func InstallCmd() *cli.Command { | ||||
| 	return &cli.Command{ | ||||
| 		Name:    "install", | ||||
| 		Usage:   gotext.Get("Install a new package"), | ||||
| 		Aliases: []string{"in"}, | ||||
| 		Flags: []cli.Flag{ | ||||
| 			&cli.BoolFlag{ | ||||
| 				Name:    "clean", | ||||
| 				Aliases: []string{"c"}, | ||||
| 				Usage:   gotext.Get("Build package from scratch even if there's an already built package available"), | ||||
| 			}, | ||||
| 		}, | ||||
| 	}, | ||||
| 	Action: func(c *cli.Context) error { | ||||
| 		ctx := c.Context | ||||
| 		log := loggerctx.From(ctx) | ||||
| 		Action: func(c *cli.Context) error { | ||||
| 			ctx := c.Context | ||||
|  | ||||
| 		args := c.Args() | ||||
| 		if args.Len() < 1 { | ||||
| 			log.Fatalf("Command install expected at least 1 argument, got %d", args.Len()).Send() | ||||
| 		} | ||||
|  | ||||
| 		mgr := manager.Detect() | ||||
| 		if mgr == nil { | ||||
| 			log.Fatal("Unable to detect a supported package manager on the system").Send() | ||||
| 		} | ||||
|  | ||||
| 		err := repos.Pull(ctx, config.Config(ctx).Repos) | ||||
| 		if err != nil { | ||||
| 			log.Fatal("Error pulling repositories").Err(err).Send() | ||||
| 		} | ||||
|  | ||||
| 		found, notFound, err := repos.FindPkgs(ctx, args.Slice()) | ||||
| 		if err != nil { | ||||
| 			log.Fatal("Error finding packages").Err(err).Send() | ||||
| 		} | ||||
|  | ||||
| 		pkgs := cliutils.FlattenPkgs(ctx, found, "install", c.Bool("interactive")) | ||||
| 		build.InstallPkgs(ctx, pkgs, notFound, types.BuildOpts{ | ||||
| 			Manager:     mgr, | ||||
| 			Clean:       c.Bool("clean"), | ||||
| 			Interactive: c.Bool("interactive"), | ||||
| 		}) | ||||
| 		return nil | ||||
| 	}, | ||||
| 	BashComplete: func(c *cli.Context) { | ||||
| 		log := loggerctx.From(c.Context) | ||||
| 		result, err := db.GetPkgs(c.Context, "true") | ||||
| 		if err != nil { | ||||
| 			log.Fatal("Error getting packages").Err(err).Send() | ||||
| 		} | ||||
| 		defer result.Close() | ||||
|  | ||||
| 		for result.Next() { | ||||
| 			var pkg db.Package | ||||
| 			err = result.StructScan(&pkg) | ||||
| 			if err != nil { | ||||
| 				log.Fatal("Error iterating over packages").Err(err).Send() | ||||
| 			args := c.Args() | ||||
| 			if args.Len() < 1 { | ||||
| 				slog.Error(gotext.Get("Command install expected at least 1 argument, got %d", args.Len())) | ||||
| 				os.Exit(1) | ||||
| 			} | ||||
|  | ||||
| 			fmt.Println(pkg.Name) | ||||
| 		} | ||||
| 	}, | ||||
| 			mgr := manager.Detect() | ||||
| 			if mgr == nil { | ||||
| 				slog.Error(gotext.Get("Unable to detect a supported package manager on the system")) | ||||
| 				os.Exit(1) | ||||
| 			} | ||||
|  | ||||
| 			cfg := config.New() | ||||
| 			db := database.New(cfg) | ||||
| 			rs := repos.New(cfg, db) | ||||
| 			err := db.Init(ctx) | ||||
| 			if err != nil { | ||||
| 				slog.Error(gotext.Get("Error initialization database"), "err", err) | ||||
| 				os.Exit(1) | ||||
| 			} | ||||
|  | ||||
| 			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) | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			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")) | ||||
|  | ||||
| 			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, | ||||
| 				Clean:       c.Bool("clean"), | ||||
| 				Interactive: c.Bool("interactive"), | ||||
| 			}) | ||||
| 			return nil | ||||
| 		}, | ||||
| 		BashComplete: func(c *cli.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") | ||||
| 			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) | ||||
| 			} | ||||
| 		}, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| var removeCmd = &cli.Command{ | ||||
| 	Name:    "remove", | ||||
| 	Usage:   "Remove an installed package", | ||||
| 	Aliases: []string{"rm"}, | ||||
| 	Action: func(c *cli.Context) error { | ||||
| 		log := loggerctx.From(c.Context) | ||||
| func RemoveCmd() *cli.Command { | ||||
| 	return &cli.Command{ | ||||
| 		Name:    "remove", | ||||
| 		Usage:   gotext.Get("Remove an installed package"), | ||||
| 		Aliases: []string{"rm"}, | ||||
| 		Action: func(c *cli.Context) error { | ||||
| 			args := c.Args() | ||||
| 			if args.Len() < 1 { | ||||
| 				slog.Error(gotext.Get("Command remove expected at least 1 argument, got %d", args.Len())) | ||||
| 				os.Exit(1) | ||||
| 			} | ||||
|  | ||||
| 		args := c.Args() | ||||
| 		if args.Len() < 1 { | ||||
| 			log.Fatalf("Command remove expected at least 1 argument, got %d", args.Len()).Send() | ||||
| 		} | ||||
| 			mgr := manager.Detect() | ||||
| 			if mgr == nil { | ||||
| 				slog.Error(gotext.Get("Unable to detect a supported package manager on the system")) | ||||
| 				os.Exit(1) | ||||
| 			} | ||||
|  | ||||
| 		mgr := manager.Detect() | ||||
| 		if mgr == nil { | ||||
| 			log.Fatal("Unable to detect a supported package manager on the system").Send() | ||||
| 		} | ||||
| 			err := mgr.Remove(nil, c.Args().Slice()...) | ||||
| 			if err != nil { | ||||
| 				slog.Error(gotext.Get("Error removing packages"), "err", err) | ||||
| 				os.Exit(1) | ||||
| 			} | ||||
|  | ||||
| 		err := mgr.Remove(nil, c.Args().Slice()...) | ||||
| 		if err != nil { | ||||
| 			log.Fatal("Error removing packages").Err(err).Send() | ||||
| 		} | ||||
|  | ||||
| 		return nil | ||||
| 	}, | ||||
| 			return nil | ||||
| 		}, | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -1,34 +1,35 @@ | ||||
| /* | ||||
|  * 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/>. | ||||
|  */ | ||||
| // 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 cliutils | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"log/slog" | ||||
| 	"os" | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/AlecAivazis/survey/v2" | ||||
| 	"plemya-x.ru/alr/internal/config" | ||||
| 	"plemya-x.ru/alr/internal/db" | ||||
| 	"plemya-x.ru/alr/internal/pager" | ||||
| 	"plemya-x.ru/alr/internal/translations" | ||||
| 	"plemya-x.ru/alr/pkg/loggerctx" | ||||
| 	"github.com/leonelquinteros/gotext" | ||||
|  | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/db" | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/pager" | ||||
| ) | ||||
|  | ||||
| // 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 | ||||
| 		err := survey.AskOne( | ||||
| 			&survey.Confirm{ | ||||
| 				Message: translations.Translator(ctx).TranslateTo(msg, config.Language(ctx)), | ||||
| 				Message: msg, | ||||
| 				Default: def, | ||||
| 			}, | ||||
| 			&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 | ||||
| // continue, and exits if they answer no. | ||||
| func PromptViewScript(ctx context.Context, script, name, style string, interactive bool) error { | ||||
| 	log := loggerctx.From(ctx) | ||||
|  | ||||
| 	if !interactive { | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	scriptPrompt := translations.Translator(ctx).TranslateTo("Would you like to view the build script for", config.Language(ctx)) + " " + name | ||||
| 	view, err := YesNoPrompt(ctx, scriptPrompt, interactive, false) | ||||
| 	view, err := YesNoPrompt(ctx, gotext.Get("Would you like to view the build script for %s", name), interactive, false) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| @@ -70,13 +68,14 @@ func PromptViewScript(ctx context.Context, script, name, style string, interacti | ||||
| 			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 { | ||||
| 			return err | ||||
| 		} | ||||
|  | ||||
| 		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 | ||||
| // 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 { | ||||
| 	log := loggerctx.From(ctx) | ||||
| 	var outPkgs []db.Package | ||||
| 	for _, pkgs := range found { | ||||
| 		if len(pkgs) > 1 && interactive { | ||||
| 			choice, err := PkgPrompt(ctx, pkgs, verb, interactive) | ||||
| 			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) | ||||
| 		} else if len(pkgs) == 1 || !interactive { | ||||
| @@ -133,7 +132,7 @@ func PkgPrompt(ctx context.Context, options []db.Package, verb string, interacti | ||||
|  | ||||
| 	prompt := &survey.Select{ | ||||
| 		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 | ||||
| @@ -154,7 +153,7 @@ func ChooseOptDepends(ctx context.Context, options []string, verb string, intera | ||||
|  | ||||
| 	prompt := &survey.MultiSelect{ | ||||
| 		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 | ||||
|   | ||||
							
								
								
									
										102
									
								
								internal/cliutils/template.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										102
									
								
								internal/cliutils/template.go
									
									
									
									
									
										Normal 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"), | ||||
| 	) | ||||
| } | ||||
| @@ -1,79 +1,199 @@ | ||||
| /* | ||||
|  * 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/>. | ||||
|  */ | ||||
| // 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 | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"log/slog" | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
| 	"sync" | ||||
|  | ||||
| 	"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{ | ||||
| 	RootCmd:          "sudo", | ||||
| 	PagerStyle:       "native", | ||||
| 	IgnorePkgUpdates: []string{}, | ||||
| 	Repos: []types.Repo{ | ||||
| 		{ | ||||
| 			Name: "default", | ||||
| 			URL:  "https://gitea.plemya-x.ru/xpamych/xpamych-alr-repo.git", | ||||
| 		}, | ||||
| 	}, | ||||
| 	AutoPull:         false, | ||||
| 	Repos:            []types.Repo{}, | ||||
| } | ||||
|  | ||||
| var ( | ||||
| 	configMtx sync.Mutex | ||||
| 	config    *types.Config | ||||
| ) | ||||
| func New() *ALRConfig { | ||||
| 	return &ALRConfig{} | ||||
| } | ||||
|  | ||||
| // Config returns a ALR configuration struct. | ||||
| // The first time it's called, it'll load the config from a file. | ||||
| // Subsequent calls will just return the same value. | ||||
| func Config(ctx context.Context) *types.Config { | ||||
| 	configMtx.Lock() | ||||
| 	defer configMtx.Unlock() | ||||
| 	log := loggerctx.From(ctx) | ||||
| func (c *ALRConfig) Load(ctx context.Context) { | ||||
| 	cfgFl, err := os.Open(c.GetPaths(ctx).ConfigPath) | ||||
| 	if err != nil { | ||||
| 		slog.Warn(gotext.Get("Error opening config file, using defaults"), "err", err) | ||||
| 		c.cfg = defaultConfig | ||||
| 		return | ||||
| 	} | ||||
| 	defer cfgFl.Close() | ||||
|  | ||||
| 	if config == nil { | ||||
| 		cfgFl, err := os.Open(GetPaths(ctx).ConfigPath) | ||||
| 		if err != nil { | ||||
| 			log.Warn("Error opening config file, using defaults").Err(err).Send() | ||||
| 			return defaultConfig | ||||
| 		} | ||||
| 		defer cfgFl.Close() | ||||
| 	// Copy the default configuration into config | ||||
| 	defCopy := *defaultConfig | ||||
| 	config := &defCopy | ||||
| 	config.Repos = nil | ||||
|  | ||||
| 		// Copy the default configuration into config | ||||
| 		defCopy := *defaultConfig | ||||
| 		config = &defCopy | ||||
| 		config.Repos = nil | ||||
| 	err = toml.NewDecoder(cfgFl).Decode(config) | ||||
| 	if err != nil { | ||||
| 		slog.Warn(gotext.Get("Error decoding config file, using defaults"), "err", err) | ||||
| 		c.cfg = defaultConfig | ||||
| 		return | ||||
| 	} | ||||
| 	c.cfg = config | ||||
| } | ||||
|  | ||||
| 		err = toml.NewDecoder(cfgFl).Decode(config) | ||||
| 		if err != nil { | ||||
| 			log.Warn("Error decoding config file, using defaults").Err(err).Send() | ||||
| 			// Set config back to nil so that we try again next time | ||||
| 			config = nil | ||||
| 			return defaultConfig | ||||
| 		} | ||||
| 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) | ||||
| } | ||||
|   | ||||
| @@ -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 | ||||
| } | ||||
| @@ -1,33 +1,24 @@ | ||||
| /* | ||||
|  * 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/>. | ||||
|  */ | ||||
| // 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 | ||||
|  | ||||
| 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 | ||||
| type Paths struct { | ||||
| 	ConfigDir  string | ||||
| @@ -37,72 +28,3 @@ type Paths struct { | ||||
| 	PkgsDir    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 | ||||
| } | ||||
|   | ||||
| @@ -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 | ||||
|  | ||||
| // Version contains the version of ALR. If the version | ||||
|   | ||||
| @@ -1,20 +1,21 @@ | ||||
| /* | ||||
|  * 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/>. | ||||
|  */ | ||||
| // 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 cpu | ||||
|  | ||||
| @@ -37,11 +38,12 @@ func armVariant() string { | ||||
| 		return armEnv | ||||
| 	} | ||||
|  | ||||
| 	if cpu.ARM.HasVFPv3 { | ||||
| 	switch { | ||||
| 	case cpu.ARM.HasVFPv3: | ||||
| 		return "arm7" | ||||
| 	} else if cpu.ARM.HasVFP { | ||||
| 	case cpu.ARM.HasVFP: | ||||
| 		return "arm6" | ||||
| 	} else { | ||||
| 	default: | ||||
| 		return "arm5" | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -1,49 +1,41 @@ | ||||
| /* | ||||
|  * 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/>. | ||||
|  */ | ||||
| // 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 db | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"database/sql" | ||||
| 	"database/sql/driver" | ||||
| 	"encoding/json" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"sync" | ||||
| 	"log/slog" | ||||
|  | ||||
| 	"github.com/jmoiron/sqlx" | ||||
| 	"plemya-x.ru/alr/internal/config" | ||||
| 	"plemya-x.ru/alr/pkg/loggerctx" | ||||
| 	"golang.org/x/exp/slices" | ||||
| 	"modernc.org/sqlite" | ||||
| 	"github.com/leonelquinteros/gotext" | ||||
|  | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/config" | ||||
| ) | ||||
|  | ||||
| // CurrentVersion is the current version of the database. | ||||
| // The database is reset if its version doesn't match this. | ||||
| const CurrentVersion = 2 | ||||
|  | ||||
| func init() { | ||||
| 	sqlite.MustRegisterScalarFunction("json_array_contains", 2, jsonArrayContains) | ||||
| } | ||||
| const CurrentVersion = 3 | ||||
|  | ||||
| // Package is a ALR package's database representation | ||||
| type Package struct { | ||||
| 	BasePkgName   string                    `sh:"base" db:"basepkg_name"` | ||||
| 	Name          string                    `sh:"name,required" db:"name"` | ||||
| 	Version       string                    `sh:"version,required" db:"version"` | ||||
| 	Release       int                       `sh:"release,required" db:"release"` | ||||
| @@ -66,68 +58,49 @@ type version struct { | ||||
| 	Version int `db:"version"` | ||||
| } | ||||
|  | ||||
| var ( | ||||
| 	mu     sync.Mutex | ||||
| type Config interface { | ||||
| 	GetPaths(ctx context.Context) *config.Paths | ||||
| } | ||||
|  | ||||
| type Database struct { | ||||
| 	conn   *sqlx.DB | ||||
| 	closed = true | ||||
| ) | ||||
| 	config Config | ||||
| } | ||||
|  | ||||
| // DB returns the ALR database. | ||||
| // The first time it's called, it opens the SQLite database file. | ||||
| // Subsequent calls return the same connection. | ||||
| func DB(ctx context.Context) *sqlx.DB { | ||||
| 	log := loggerctx.From(ctx) | ||||
| 	if conn != nil && !closed { | ||||
| 		return getConn() | ||||
| func New(config Config) *Database { | ||||
| 	return &Database{ | ||||
| 		config: config, | ||||
| 	} | ||||
| 	_, err := open(ctx, config.GetPaths(ctx).DBPath) | ||||
| } | ||||
|  | ||||
| func (d *Database) Init(ctx context.Context) error { | ||||
| 	err := d.Connect(ctx) | ||||
| 	if err != nil { | ||||
| 		log.Fatal("Error opening database").Err(err).Send() | ||||
| 		return err | ||||
| 	} | ||||
| 	return getConn() | ||||
| 	return d.initDB(ctx) | ||||
| } | ||||
|  | ||||
| func getConn() *sqlx.DB { | ||||
| 	mu.Lock() | ||||
| 	defer mu.Unlock() | ||||
| 	return conn | ||||
| } | ||||
|  | ||||
| func open(ctx context.Context, dsn string) (*sqlx.DB, error) { | ||||
| func (d *Database) Connect(ctx context.Context) error { | ||||
| 	dsn := d.config.GetPaths(ctx).DBPath | ||||
| 	db, err := sqlx.Open("sqlite", dsn) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	mu.Lock() | ||||
| 	conn = db | ||||
| 	closed = false | ||||
| 	mu.Unlock() | ||||
|  | ||||
| 	err = initDB(ctx, dsn) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	return db, nil | ||||
| 	d.conn = db | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // Close closes the database | ||||
| func Close() error { | ||||
| 	closed = true | ||||
| 	if conn != nil { | ||||
| 		return conn.Close() | ||||
| 	} else { | ||||
| 		return nil | ||||
| 	} | ||||
| func (d *Database) GetConn() *sqlx.DB { | ||||
| 	return d.conn | ||||
| } | ||||
|  | ||||
| // initDB initializes the database | ||||
| func initDB(ctx context.Context, dsn string) error { | ||||
| 	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, ` | ||||
| 		CREATE TABLE IF NOT EXISTS pkgs ( | ||||
| 			basepkg_name  TEXT NOT NULL, | ||||
| 			name          TEXT NOT NULL, | ||||
| 			repository    TEXT NOT NULL, | ||||
| 			version       TEXT NOT NULL, | ||||
| @@ -155,59 +128,77 @@ func initDB(ctx context.Context, dsn string) error { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	ver, ok := GetVersion(ctx) | ||||
| 	ver, ok := d.GetVersion(ctx) | ||||
| 	if ok && ver != CurrentVersion { | ||||
| 		log.Warn("Database version mismatch; resetting").Int("version", ver).Int("expected", CurrentVersion).Send() | ||||
| 		reset(ctx) | ||||
| 		return initDB(ctx, dsn) | ||||
| 		slog.Warn(gotext.Get("Database version mismatch; resetting"), "version", ver, "expected", CurrentVersion) | ||||
| 		err = d.reset(ctx) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		return d.initDB(ctx) | ||||
| 	} else if !ok { | ||||
| 		log.Warn("Database version does not exist. Run alr fix if something isn't working.").Send() | ||||
| 		return addVersion(ctx, CurrentVersion) | ||||
| 		slog.Warn(gotext.Get("Database version does not exist. Run alr fix if something isn't working."), "version", ver, "expected", CurrentVersion) | ||||
| 		return d.addVersion(ctx, CurrentVersion) | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // reset drops all the database tables | ||||
| 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) { | ||||
| func (d *Database) GetVersion(ctx context.Context) (int, bool) { | ||||
| 	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 { | ||||
| 		return 0, false | ||||
| 	} | ||||
| 	return ver.Version, true | ||||
| } | ||||
|  | ||||
| func addVersion(ctx context.Context, ver int) error { | ||||
| 	_, err := DB(ctx).ExecContext(ctx, `INSERT INTO alr_db_version(version) VALUES (?);`, ver) | ||||
| func (d *Database) addVersion(ctx context.Context, ver int) error { | ||||
| 	_, err := d.conn.ExecContext(ctx, `INSERT INTO alr_db_version(version) VALUES (?);`, ver) | ||||
| 	return err | ||||
| } | ||||
|  | ||||
| // InsertPackage adds a package to the database | ||||
| func InsertPackage(ctx context.Context, pkg Package) error { | ||||
| 	_, err := DB(ctx).NamedExecContext(ctx, ` | ||||
| func (d *Database) reset(ctx context.Context) error { | ||||
| 	_, err := d.conn.ExecContext(ctx, "DROP TABLE IF EXISTS pkgs;") | ||||
| 	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 ( | ||||
| 			basepkg_name, | ||||
| 			name, | ||||
| 			repository, | ||||
| 			version, | ||||
| @@ -225,6 +216,7 @@ func InsertPackage(ctx context.Context, pkg Package) error { | ||||
| 			builddepends, | ||||
| 			optdepends | ||||
| 		) VALUES ( | ||||
| 		 	:basepkg_name, | ||||
| 			:name, | ||||
| 			:repository, | ||||
| 			:version, | ||||
| @@ -246,101 +238,10 @@ func InsertPackage(ctx context.Context, pkg Package) error { | ||||
| 	return err | ||||
| } | ||||
|  | ||||
| // GetPkgs returns a result containing packages that match the where conditions | ||||
| func GetPkgs(ctx context.Context, where string, args ...any) (*sqlx.Rows, error) { | ||||
| 	stream, err := DB(ctx).QueryxContext(ctx, "SELECT * FROM pkgs WHERE "+where, args...) | ||||
| 	if err != nil { | ||||
| 		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 { | ||||
| func (d *Database) Close() error { | ||||
| 	if d.conn != nil { | ||||
| 		return d.conn.Close() | ||||
| 	} else { | ||||
| 		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) | ||||
| } | ||||
|   | ||||
| @@ -1,32 +1,50 @@ | ||||
| /* | ||||
|  * 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/>. | ||||
|  */ | ||||
| // 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 db_test | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"reflect" | ||||
| 	"strings" | ||||
| 	"testing" | ||||
|  | ||||
| 	"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{ | ||||
| 	Name:    "test", | ||||
| 	Version: "0.0.1", | ||||
| @@ -59,18 +77,11 @@ var testPkg = db.Package{ | ||||
| } | ||||
|  | ||||
| func TestInit(t *testing.T) { | ||||
| 	_, err := db.Open(":memory:") | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Expected no error, got %s", err) | ||||
| 	} | ||||
| 	defer db.Close() | ||||
| 	ctx := context.Background() | ||||
| 	database := prepareDb() | ||||
| 	defer database.Close() | ||||
|  | ||||
| 	_, err = db.DB().Exec("SELECT * FROM pkgs") | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Expected no error, got %s", err) | ||||
| 	} | ||||
|  | ||||
| 	ver, ok := db.GetVersion() | ||||
| 	ver, ok := database.GetVersion(ctx) | ||||
| 	if !ok { | ||||
| 		t.Errorf("Expected version to be present") | ||||
| 	} else if ver != db.CurrentVersion { | ||||
| @@ -79,19 +90,17 @@ func TestInit(t *testing.T) { | ||||
| } | ||||
|  | ||||
| func TestInsertPackage(t *testing.T) { | ||||
| 	_, err := db.Open(":memory:") | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Expected no error, got %s", err) | ||||
| 	} | ||||
| 	defer db.Close() | ||||
| 	ctx := context.Background() | ||||
| 	database := prepareDb() | ||||
| 	defer database.Close() | ||||
|  | ||||
| 	err = db.InsertPackage(testPkg) | ||||
| 	err := database.InsertPackage(ctx, testPkg) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Expected no error, got %s", err) | ||||
| 	} | ||||
|  | ||||
| 	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 { | ||||
| 		t.Fatalf("Expected no error, got %s", err) | ||||
| 	} | ||||
| @@ -102,28 +111,26 @@ func TestInsertPackage(t *testing.T) { | ||||
| } | ||||
|  | ||||
| func TestGetPkgs(t *testing.T) { | ||||
| 	_, err := db.Open(":memory:") | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Expected no error, got %s", err) | ||||
| 	} | ||||
| 	defer db.Close() | ||||
| 	ctx := context.Background() | ||||
| 	database := prepareDb() | ||||
| 	defer database.Close() | ||||
|  | ||||
| 	x1 := testPkg | ||||
| 	x1.Name = "x1" | ||||
| 	x2 := testPkg | ||||
| 	x2.Name = "x2" | ||||
|  | ||||
| 	err = db.InsertPackage(x1) | ||||
| 	err := database.InsertPackage(ctx, x1) | ||||
| 	if err != nil { | ||||
| 		t.Errorf("Expected no error, got %s", err) | ||||
| 	} | ||||
|  | ||||
| 	err = db.InsertPackage(x2) | ||||
| 	err = database.InsertPackage(ctx, x2) | ||||
| 	if err != nil { | ||||
| 		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 { | ||||
| 		t.Fatalf("Expected no error, got %s", err) | ||||
| 	} | ||||
| @@ -142,28 +149,26 @@ func TestGetPkgs(t *testing.T) { | ||||
| } | ||||
|  | ||||
| func TestGetPkg(t *testing.T) { | ||||
| 	_, err := db.Open(":memory:") | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Expected no error, got %s", err) | ||||
| 	} | ||||
| 	defer db.Close() | ||||
| 	ctx := context.Background() | ||||
| 	database := prepareDb() | ||||
| 	defer database.Close() | ||||
|  | ||||
| 	x1 := testPkg | ||||
| 	x1.Name = "x1" | ||||
| 	x2 := testPkg | ||||
| 	x2.Name = "x2" | ||||
|  | ||||
| 	err = db.InsertPackage(x1) | ||||
| 	err := database.InsertPackage(ctx, x1) | ||||
| 	if err != nil { | ||||
| 		t.Errorf("Expected no error, got %s", err) | ||||
| 	} | ||||
|  | ||||
| 	err = db.InsertPackage(x2) | ||||
| 	err = database.InsertPackage(ctx, x2) | ||||
| 	if err != nil { | ||||
| 		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 { | ||||
| 		t.Fatalf("Expected no error, got %s", err) | ||||
| 	} | ||||
| @@ -178,34 +183,32 @@ func TestGetPkg(t *testing.T) { | ||||
| } | ||||
|  | ||||
| func TestDeletePkgs(t *testing.T) { | ||||
| 	_, err := db.Open(":memory:") | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Expected no error, got %s", err) | ||||
| 	} | ||||
| 	defer db.Close() | ||||
| 	ctx := context.Background() | ||||
| 	database := prepareDb() | ||||
| 	defer database.Close() | ||||
|  | ||||
| 	x1 := testPkg | ||||
| 	x1.Name = "x1" | ||||
| 	x2 := testPkg | ||||
| 	x2.Name = "x2" | ||||
|  | ||||
| 	err = db.InsertPackage(x1) | ||||
| 	err := database.InsertPackage(ctx, x1) | ||||
| 	if err != nil { | ||||
| 		t.Errorf("Expected no error, got %s", err) | ||||
| 	} | ||||
|  | ||||
| 	err = db.InsertPackage(x2) | ||||
| 	err = database.InsertPackage(ctx, x2) | ||||
| 	if err != nil { | ||||
| 		t.Errorf("Expected no error, got %s", err) | ||||
| 	} | ||||
|  | ||||
| 	err = db.DeletePkgs("name = 'x1'") | ||||
| 	err = database.DeletePkgs(ctx, "name = 'x1'") | ||||
| 	if err != nil { | ||||
| 		t.Errorf("Expected no error, got %s", err) | ||||
| 	} | ||||
|  | ||||
| 	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 { | ||||
| 		t.Errorf("Expected no error, got %s", err) | ||||
| 	} | ||||
| @@ -216,11 +219,9 @@ func TestDeletePkgs(t *testing.T) { | ||||
| } | ||||
|  | ||||
| func TestJsonArrayContains(t *testing.T) { | ||||
| 	_, err := db.Open(":memory:") | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Expected no error, got %s", err) | ||||
| 	} | ||||
| 	defer db.Close() | ||||
| 	ctx := context.Background() | ||||
| 	database := prepareDb() | ||||
| 	defer database.Close() | ||||
|  | ||||
| 	x1 := testPkg | ||||
| 	x1.Name = "x1" | ||||
| @@ -228,18 +229,18 @@ func TestJsonArrayContains(t *testing.T) { | ||||
| 	x2.Name = "x2" | ||||
| 	x2.Provides.Val = append(x2.Provides.Val, "x") | ||||
|  | ||||
| 	err = db.InsertPackage(x1) | ||||
| 	err := database.InsertPackage(ctx, x1) | ||||
| 	if err != nil { | ||||
| 		t.Errorf("Expected no error, got %s", err) | ||||
| 	} | ||||
|  | ||||
| 	err = db.InsertPackage(x2) | ||||
| 	err = database.InsertPackage(ctx, x2) | ||||
| 	if err != nil { | ||||
| 		t.Errorf("Expected no error, got %s", err) | ||||
| 	} | ||||
|  | ||||
| 	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 { | ||||
| 		t.Fatalf("Expected no error, got %s", err) | ||||
| 	} | ||||
|   | ||||
							
								
								
									
										80
									
								
								internal/db/json.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								internal/db/json.go
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										52
									
								
								internal/db/utils.go
									
									
									
									
									
										Normal 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 | ||||
| } | ||||
| @@ -1,20 +1,21 @@ | ||||
| /* | ||||
| * 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/>. | ||||
| */ | ||||
| // 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/>. | ||||
|  | ||||
| // Пакет dl содержит абстракции для загрузки файлов и каталогов | ||||
| // из различных источников. | ||||
| @@ -30,17 +31,17 @@ import ( | ||||
| 	"fmt" | ||||
| 	"hash" | ||||
| 	"io" | ||||
| 	"log/slog" | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/PuerkitoBio/purell" | ||||
| 	"github.com/leonelquinteros/gotext" | ||||
| 	"github.com/vmihailenco/msgpack/v5" | ||||
| 	"golang.org/x/crypto/blake2b" | ||||
| 	"golang.org/x/crypto/blake2s" | ||||
| 	"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>" | ||||
| } | ||||
|  | ||||
| type DlCache interface { | ||||
| 	Get(context.Context, string) (string, bool) | ||||
| 	New(context.Context, string) (string, error) | ||||
| } | ||||
|  | ||||
| // Структура Options содержит параметры для загрузки файлов и каталогов | ||||
| type Options struct { | ||||
| 	Hash             []byte | ||||
| @@ -90,6 +96,7 @@ type Options struct { | ||||
| 	PostprocDisabled bool | ||||
| 	Progress         io.Writer | ||||
| 	LocalDir         string | ||||
| 	DlCache          DlCache | ||||
| } | ||||
|  | ||||
| // Метод для создания нового хеша на основе указанного алгоритма хеширования | ||||
| @@ -130,7 +137,7 @@ type Manifest struct { | ||||
| type Downloader interface { | ||||
| 	Name() string | ||||
| 	MatchURL(string) bool | ||||
| 	Download(Options) (Type, string, error) | ||||
| 	Download(context.Context, Options) (Type, string, error) | ||||
| } | ||||
|  | ||||
| // Интерфейс UpdatingDownloader расширяет Downloader методом Update | ||||
| @@ -141,7 +148,6 @@ type UpdatingDownloader interface { | ||||
|  | ||||
| // Функция Download загружает файл или каталог с использованием указанных параметров | ||||
| func Download(ctx context.Context, opts Options) (err error) { | ||||
| 	log := loggerctx.From(ctx) | ||||
| 	normalized, err := normalizeURL(opts.URL) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| @@ -151,16 +157,20 @@ func Download(ctx context.Context, opts Options) (err error) { | ||||
| 	d := getDownloader(opts.URL) | ||||
|  | ||||
| 	if opts.CacheDisabled { | ||||
| 		_, _, err = d.Download(opts) | ||||
| 		_, _, err = d.Download(ctx, opts) | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	var t Type | ||||
| 	cacheDir, ok := dlcache.Get(ctx, opts.URL) | ||||
| 	cacheDir, ok := opts.DlCache.Get(ctx, opts.URL) | ||||
| 	if ok { | ||||
| 		var updated bool | ||||
| 		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{ | ||||
| 				Hash:          opts.Hash, | ||||
| @@ -187,10 +197,18 @@ func Download(ctx context.Context, opts Options) (err error) { | ||||
| 			} | ||||
|  | ||||
| 			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 | ||||
| 			} 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 | ||||
| 			} | ||||
| 		} 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 { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	t, name, err := d.Download(Options{ | ||||
| 	t, name, err := d.Download(ctx, Options{ | ||||
| 		Hash:          opts.Hash, | ||||
| 		HashAlgorithm: opts.HashAlgorithm, | ||||
| 		Name:          opts.Name, | ||||
| @@ -299,8 +317,6 @@ func linkDir(src, dest string) error { | ||||
| 			return nil | ||||
| 		} | ||||
|  | ||||
|  | ||||
|  | ||||
| 		rel, err := filepath.Rel(src, path) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
|   | ||||
							
								
								
									
										176
									
								
								internal/dl/dl_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										176
									
								
								internal/dl/dl_test.go
									
									
									
									
									
										Normal 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) | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
| @@ -1,26 +1,28 @@ | ||||
| /* | ||||
|  * 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/>. | ||||
|  */ | ||||
| // 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 | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"context" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"mime" | ||||
| 	"net/http" | ||||
| @@ -29,11 +31,8 @@ import ( | ||||
| 	"path" | ||||
| 	"path/filepath" | ||||
| 	"strings" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/mholt/archiver/v4" | ||||
| 	"github.com/schollz/progressbar/v3" | ||||
| 	"plemya-x.ru/alr/internal/shutils/handlers" | ||||
| ) | ||||
|  | ||||
| // FileDownloader загружает файлы с использованием HTTP | ||||
| @@ -52,7 +51,7 @@ func (FileDownloader) MatchURL(string) bool { | ||||
|  | ||||
| // Download загружает файл с использованием HTTP. Если файл | ||||
| // сжат в поддерживаемом формате, он будет распакован | ||||
| func (FileDownloader) Download(opts Options) (Type, string, error) { | ||||
| func (FileDownloader) Download(ctx context.Context, opts Options) (Type, string, error) { | ||||
| 	// Разбор URL | ||||
| 	u, err := url.Parse(opts.URL) | ||||
| 	if err != nil { | ||||
| @@ -92,8 +91,12 @@ func (FileDownloader) Download(opts Options) (Type, string, error) { | ||||
| 		} | ||||
| 		r = localFl | ||||
| 	} 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 запроса | ||||
| 		res, err := http.Get(u.String()) | ||||
| 		res, err := http.DefaultClient.Do(req) | ||||
| 		if err != nil { | ||||
| 			return 0, "", err | ||||
| 		} | ||||
| @@ -112,30 +115,15 @@ func (FileDownloader) Download(opts Options) (Type, string, error) { | ||||
| 	if err != nil { | ||||
| 		return 0, "", err | ||||
| 	} | ||||
| 	defer fl.Close() | ||||
|  | ||||
| 	var bar io.WriteCloser | ||||
| 	var out io.WriteCloser | ||||
| 	// Настройка индикатора прогресса | ||||
| 	if opts.Progress != nil { | ||||
| 		bar = progressbar.NewOptions64( | ||||
| 			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() | ||||
| 		out = NewProgressWriter(fl, size, name, opts.Progress) | ||||
| 	} else { | ||||
| 		bar = handlers.NopRWC{} | ||||
| 		out = fl | ||||
| 	} | ||||
| 	defer out.Close() | ||||
|  | ||||
| 	h, err := opts.NewHash() | ||||
| 	if err != nil { | ||||
| @@ -145,9 +133,9 @@ func (FileDownloader) Download(opts Options) (Type, string, error) { | ||||
| 	var w io.Writer | ||||
| 	// Настройка MultiWriter для записи в файл, хеш и индикатор прогресса | ||||
| 	if opts.Hash != nil { | ||||
| 		w = io.MultiWriter(fl, h, bar) | ||||
| 		w = io.MultiWriter(h, out) | ||||
| 	} else { | ||||
| 		w = io.MultiWriter(fl, bar) | ||||
| 		w = io.MultiWriter(out) | ||||
| 	} | ||||
|  | ||||
| 	// Копирование содержимого из источника в файл назначения | ||||
| @@ -222,7 +210,7 @@ func extractFile(r io.Reader, format archiver.Format, name string, opts Options) | ||||
| 			} | ||||
|  | ||||
| 			if f.IsDir() { | ||||
| 				err = os.Mkdir(path, 0o755) | ||||
| 				err = os.MkdirAll(path, 0o755) | ||||
| 				if err != nil { | ||||
| 					return err | ||||
| 				} | ||||
|   | ||||
| @@ -1,24 +1,26 @@ | ||||
| /* | ||||
|  * 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/>. | ||||
|  */ | ||||
| // 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 | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"errors" | ||||
| 	"net/url" | ||||
| 	"path" | ||||
| @@ -46,7 +48,7 @@ func (GitDownloader) MatchURL(u string) bool { | ||||
| // Download uses git to clone the repository from the specified URL. | ||||
| // It allows specifying the revision, depth and recursion options | ||||
| // 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) | ||||
| 	if err != nil { | ||||
| 		return 0, "", err | ||||
| @@ -88,7 +90,7 @@ func (GitDownloader) Download(opts Options) (Type, string, error) { | ||||
| 		co.RecurseSubmodules = git.DefaultSubmoduleRecursionDepth | ||||
| 	} | ||||
|  | ||||
| 	r, err := git.PlainClone(opts.Destination, false, co) | ||||
| 	r, err := git.PlainCloneContext(ctx, opts.Destination, false, co) | ||||
| 	if err != nil { | ||||
| 		return 0, "", err | ||||
| 	} | ||||
|   | ||||
							
								
								
									
										247
									
								
								internal/dl/progress_tui.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										247
									
								
								internal/dl/progress_tui.go
									
									
									
									
									
										Normal 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 | ||||
| } | ||||
| @@ -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 | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"os" | ||||
| @@ -30,7 +50,7 @@ func (TorrentDownloader) MatchURL(u string) bool { | ||||
| } | ||||
|  | ||||
| // 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") | ||||
| 	if err != nil { | ||||
| 		return 0, "", ErrAria2NotFound | ||||
| @@ -38,7 +58,7 @@ func (TorrentDownloader) Download(opts Options) (Type, string, error) { | ||||
|  | ||||
| 	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.Stderr = os.Stderr | ||||
| 	err = cmd.Run() | ||||
|   | ||||
| @@ -1,48 +1,61 @@ | ||||
| /* | ||||
|  * 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/>. | ||||
|  */ | ||||
| // 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 dlcache | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"crypto/sha1" | ||||
| 	"encoding/hex" | ||||
| 	"io" | ||||
| 	"os" | ||||
| 	"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 | ||||
| func BasePath(ctx context.Context) string { | ||||
| 	return filepath.Join(config.GetPaths(ctx).CacheDir, "dl") | ||||
| type Config interface { | ||||
| 	GetPaths(ctx context.Context) *config.Paths | ||||
| } | ||||
|  | ||||
| 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. | ||||
| // If a directory with the same ID already exists, | ||||
| // 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) | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
| 	itemPath := filepath.Join(BasePath(ctx), h) | ||||
| 	itemPath := filepath.Join(dc.BasePath(ctx), h) | ||||
|  | ||||
| 	fi, err := os.Stat(itemPath) | ||||
| 	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 | ||||
| // does not exist, it returns an empty string | ||||
| // 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) | ||||
| 	if err != nil { | ||||
| 		return "", false | ||||
| 	} | ||||
| 	itemPath := filepath.Join(BasePath(ctx), h) | ||||
| 	itemPath := filepath.Join(dc.BasePath(ctx), h) | ||||
|  | ||||
| 	_, err = os.Stat(itemPath) | ||||
| 	if err != nil { | ||||
| @@ -79,15 +92,3 @@ func Get(ctx context.Context, id string) (string, bool) { | ||||
|  | ||||
| 	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 | ||||
| } | ||||
|   | ||||
| @@ -1,20 +1,21 @@ | ||||
| /* | ||||
|  * 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/>. | ||||
|  */ | ||||
| // 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 dlcache_test | ||||
|  | ||||
| @@ -27,26 +28,53 @@ import ( | ||||
| 	"path/filepath" | ||||
| 	"testing" | ||||
|  | ||||
| 	"plemya-x.ru/alr/internal/config" | ||||
| 	"plemya-x.ru/alr/internal/dlcache" | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/config" | ||||
| 	"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.*") | ||||
| 	if err != nil { | ||||
| 		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) { | ||||
| 	cfg := prepare(t) | ||||
| 	defer cleanup(t, cfg) | ||||
|  | ||||
| 	dc := dlcache.New(cfg) | ||||
|  | ||||
| 	ctx := context.Background() | ||||
|  | ||||
| 	const id = "https://example.com" | ||||
| 	dir, err := dlcache.New(id) | ||||
| 	dir, err := dc.New(ctx, id) | ||||
| 	if err != nil { | ||||
| 		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 { | ||||
| 		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") | ||||
| 	} | ||||
|  | ||||
| 	dir2, ok := dlcache.Get(id) | ||||
| 	dir2, ok := dc.Get(ctx, id) | ||||
| 	if !ok { | ||||
| 		t.Errorf("Expected Get() to return valid value") | ||||
| 	} | ||||
|   | ||||
							
								
								
									
										32
									
								
								internal/dlcache/utils.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								internal/dlcache/utils.go
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										96
									
								
								internal/logger/log.go
									
									
									
									
									
										Normal 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) | ||||
| } | ||||
| @@ -1,20 +1,21 @@ | ||||
| /* | ||||
|  * 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/>. | ||||
|  */ | ||||
| // 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 osutils | ||||
|  | ||||
| @@ -54,12 +55,12 @@ func copyDirOrFile(sourcePath, destPath string) error { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	if sourceInfo.IsDir() { | ||||
| 	switch { | ||||
| 	case sourceInfo.IsDir(): | ||||
| 		return copyDir(sourcePath, destPath, sourceInfo) | ||||
| 	} else if sourceInfo.Mode().IsRegular() { | ||||
| 	case sourceInfo.Mode().IsRegular(): | ||||
| 		return copyFile(sourcePath, destPath, sourceInfo) | ||||
| 	} else { | ||||
| 		// ignore non-regular files | ||||
| 	default: | ||||
| 		return nil | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -1,32 +1,36 @@ | ||||
| /* | ||||
|  * 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/>. | ||||
|  */ | ||||
| // 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 overrides | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"reflect" | ||||
| 	"regexp" | ||||
| 	"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/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 { | ||||
| @@ -221,3 +225,19 @@ func parseLangs(langs []string, tags []language.Tag) ([]string, error) { | ||||
| 	out = slices.Compact(out) | ||||
| 	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) | ||||
| } | ||||
|   | ||||
| @@ -1,20 +1,21 @@ | ||||
| /* | ||||
|  * 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/>. | ||||
|  */ | ||||
| // 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 overrides_test | ||||
|  | ||||
| @@ -23,9 +24,11 @@ import ( | ||||
| 	"reflect" | ||||
| 	"testing" | ||||
|  | ||||
| 	"plemya-x.ru/alr/internal/overrides" | ||||
| 	"plemya-x.ru/alr/pkg/distro" | ||||
| 	"github.com/stretchr/testify/assert" | ||||
| 	"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{ | ||||
| @@ -193,3 +196,42 @@ func TestResolveLangs(t *testing.T) { | ||||
| 		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)) | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -1,20 +1,21 @@ | ||||
| /* | ||||
|  * 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/>. | ||||
|  */ | ||||
| // 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 pager | ||||
|  | ||||
|   | ||||
| @@ -1,20 +1,21 @@ | ||||
| /* | ||||
|  * 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/>. | ||||
|  */ | ||||
| // 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 pager | ||||
|  | ||||
| @@ -40,7 +41,7 @@ func init() { | ||||
|  | ||||
| 	b2 := lipgloss.RoundedBorder() | ||||
| 	b2.Left = "\u2524" | ||||
| 	infoStyle = titleStyle.Copy().BorderStyle(b2) | ||||
| 	infoStyle = titleStyle.BorderStyle(b2) | ||||
| } | ||||
|  | ||||
| type Pager struct { | ||||
|   | ||||
| @@ -1,20 +1,21 @@ | ||||
| /* | ||||
|  * 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/>. | ||||
|  */ | ||||
| // 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 decoder | ||||
|  | ||||
| @@ -25,12 +26,13 @@ import ( | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/mitchellh/mapstructure" | ||||
| 	"plemya-x.ru/alr/internal/overrides" | ||||
| 	"plemya-x.ru/alr/pkg/distro" | ||||
| 	"golang.org/x/exp/slices" | ||||
| 	"mvdan.cc/sh/v3/expand" | ||||
| 	"mvdan.cc/sh/v3/interp" | ||||
| 	"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") | ||||
| @@ -162,11 +164,20 @@ func (d *Decoder) DecodeVars(val any) error { | ||||
| 	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 | ||||
| // with the given name | ||||
| 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) | ||||
| 	if fn == nil { | ||||
| 		return nil, false | ||||
| @@ -175,12 +186,38 @@ func (d *Decoder) GetFunc(name string) (ScriptFunc, bool) { | ||||
| 	return func(ctx context.Context, opts ...interp.RunnerOption) error { | ||||
| 		sub := d.Runner.Subshell() | ||||
| 		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) | ||||
| 	}, 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 { | ||||
| 	names, err := overrides.Resolve(d.info, overrides.DefaultOpts.WithName(name)) | ||||
| 	if err != nil { | ||||
| @@ -221,3 +258,8 @@ func (d *Decoder) getVar(name string) *expand.Variable { | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func IsTruthy(value string) bool { | ||||
| 	value = strings.ToLower(strings.TrimSpace(value)) | ||||
| 	return value == "true" || value == "yes" || value == "1" | ||||
| } | ||||
|   | ||||
| @@ -1,20 +1,21 @@ | ||||
| /* | ||||
|  * 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/>. | ||||
|  */ | ||||
| // 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 decoder_test | ||||
|  | ||||
| @@ -27,10 +28,11 @@ import ( | ||||
| 	"strings" | ||||
| 	"testing" | ||||
|  | ||||
| 	"plemya-x.ru/alr/internal/shutils/decoder" | ||||
| 	"plemya-x.ru/alr/pkg/distro" | ||||
| 	"mvdan.cc/sh/v3/interp" | ||||
| 	"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 { | ||||
| @@ -56,7 +58,7 @@ const testScript = ` | ||||
| 	release=1 | ||||
| 	epoch=2 | ||||
| 	desc="Test package" | ||||
| 	homepage='//https://gitea.plemya-x.ru/xpamych/ALR' | ||||
| 	homepage='https://gitea.plemya-x.ru/xpamych/ALR' | ||||
| 	maintainer='Евгений Храмов <xpamych@yandex.ru>' | ||||
| 	architectures=('arm64' 'amd64') | ||||
| 	license=('GPL-3.0-or-later') | ||||
|   | ||||
| @@ -1,20 +1,21 @@ | ||||
| /* | ||||
|  * 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/>. | ||||
|  */ | ||||
| // 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 | ||||
|  | ||||
|   | ||||
| @@ -1,20 +1,21 @@ | ||||
| /* | ||||
|  * 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/>. | ||||
|  */ | ||||
| // 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_test | ||||
|  | ||||
| @@ -23,11 +24,12 @@ import ( | ||||
| 	"strings" | ||||
| 	"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/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 = ` | ||||
| @@ -89,7 +91,7 @@ func TestExecFuncs(t *testing.T) { | ||||
| 		t.Fatalf("Expected test() function to exist") | ||||
| 	} | ||||
|  | ||||
| 	eh := shutils.ExecFuncs{ | ||||
| 	eh := handlers.ExecFuncs{ | ||||
| 		"test-cmd": func(hc interp.HandlerContext, name string, args []string) error { | ||||
| 			if name != "test-cmd" { | ||||
| 				t.Errorf("Expected name to be 'test-cmd', got '%s'", name) | ||||
|   | ||||
| @@ -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 | ||||
|  | ||||
| import ( | ||||
| @@ -10,7 +29,7 @@ import ( | ||||
| 	"syscall" | ||||
| 	"time" | ||||
|  | ||||
| 	"plemya-x.ru/fakeroot" | ||||
| 	"gitea.plemya-x.ru/Plemya-x/fakeroot" | ||||
| 	"mvdan.cc/sh/v3/expand" | ||||
| 	"mvdan.cc/sh/v3/interp" | ||||
| ) | ||||
|   | ||||
| @@ -1,30 +1,32 @@ | ||||
| /* | ||||
|  * 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/>. | ||||
|  */ | ||||
| // 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 | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"io" | ||||
| 	"io/fs" | ||||
| 	"os" | ||||
| ) | ||||
|  | ||||
| func NopReadDir(context.Context, string) ([]os.FileInfo, error) { | ||||
| func NopReadDir(context.Context, string) ([]fs.DirEntry, error) { | ||||
| 	return nil, os.ErrNotExist | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -1,20 +1,21 @@ | ||||
| /* | ||||
|  * 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/>. | ||||
|  */ | ||||
| // 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_test | ||||
|  | ||||
| @@ -25,9 +26,10 @@ import ( | ||||
| 	"strings" | ||||
| 	"testing" | ||||
|  | ||||
| 	"plemya-x.ru/alr/internal/shutils/handlers" | ||||
| 	"mvdan.cc/sh/v3/interp" | ||||
| 	"mvdan.cc/sh/v3/syntax" | ||||
|  | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/handlers" | ||||
| ) | ||||
|  | ||||
| func TestNopExec(t *testing.T) { | ||||
|   | ||||
| @@ -1,20 +1,21 @@ | ||||
| /* | ||||
|  * 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/>. | ||||
|  */ | ||||
| // 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 | ||||
|  | ||||
| @@ -30,12 +31,12 @@ import ( | ||||
| 	"mvdan.cc/sh/v3/interp" | ||||
| ) | ||||
|  | ||||
| func RestrictedReadDir(allowedPrefixes ...string) interp.ReadDirHandlerFunc { | ||||
| 	return func(ctx context.Context, s string) ([]fs.FileInfo, error) { | ||||
| func RestrictedReadDir(allowedPrefixes ...string) interp.ReadDirHandlerFunc2 { | ||||
| 	return func(ctx context.Context, s string) ([]fs.DirEntry, error) { | ||||
| 		path := filepath.Clean(s) | ||||
| 		for _, allowedPrefix := range allowedPrefixes { | ||||
| 			if strings.HasPrefix(path, allowedPrefix) { | ||||
| 				return interp.DefaultReadDirHandler()(ctx, s) | ||||
| 				return interp.DefaultReadDirHandler2()(ctx, s) | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
|   | ||||
| @@ -1,20 +1,21 @@ | ||||
| /* | ||||
|  * 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/>. | ||||
|  */ | ||||
| // 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 helpers | ||||
|  | ||||
| @@ -23,6 +24,7 @@ import ( | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"os" | ||||
| 	"path" | ||||
| 	"path/filepath" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| @@ -31,8 +33,9 @@ import ( | ||||
| 	"github.com/go-git/go-git/v5" | ||||
| 	"github.com/go-git/go-git/v5/plumbing/object" | ||||
| 	"golang.org/x/exp/slices" | ||||
| 	"plemya-x.ru/alr/internal/shutils/handlers" | ||||
| 	"mvdan.cc/sh/v3/interp" | ||||
|  | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/handlers" | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| @@ -53,12 +56,17 @@ var Helpers = handlers.ExecFuncs{ | ||||
| 	"install-completion":   installCompletionCmd, | ||||
| 	"install-library":      installLibraryCmd, | ||||
| 	"git-version":          gitVersionCmd, | ||||
|  | ||||
| 	"files-find-lang": filesFindLangCmd, | ||||
| 	"files-find-doc":  filesFindDocCmd, | ||||
| } | ||||
|  | ||||
| // Restricted contains restricted read-only helper commands | ||||
| // that don't modify any state | ||||
| 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 { | ||||
| @@ -237,10 +245,13 @@ func gitVersionCmd(hc interp.HandlerContext, cmd string, args []string) error { | ||||
| 		return fmt.Errorf("git-version: %w", err) | ||||
| 	} | ||||
|  | ||||
| 	commits.ForEach(func(*object.Commit) error { | ||||
| 	err = commits.ForEach(func(*object.Commit) error { | ||||
| 		revNum++ | ||||
| 		return nil | ||||
| 	}) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("git-version: %w", err) | ||||
| 	} | ||||
|  | ||||
| 	HEAD, err := r.Head() | ||||
| 	if err != nil { | ||||
| @@ -254,6 +265,114 @@ func gitVersionCmd(hc interp.HandlerContext, cmd string, args []string) error { | ||||
| 	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 { | ||||
| 	err := os.MkdirAll(filepath.Dir(to), 0o755) | ||||
| 	if err != nil { | ||||
|   | ||||
							
								
								
									
										216
									
								
								internal/shutils/helpers/helpers_internal_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										216
									
								
								internal/shutils/helpers/helpers_internal_test.go
									
									
									
									
									
										Normal 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) | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										550
									
								
								internal/translations/default.pot
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										550
									
								
								internal/translations/default.pot
									
									
									
									
									
										Normal 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 "" | ||||
| @@ -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' | ||||
| @@ -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 = 'протокол-скачивание' | ||||
							
								
								
									
										585
									
								
								internal/translations/po/ru/default.po
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										585
									
								
								internal/translations/po/ru/default.po
									
									
									
									
									
										Normal 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()" | ||||
| @@ -1,56 +1,52 @@ | ||||
| /* | ||||
|  * 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/>. | ||||
|  */ | ||||
| // 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 translations | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"embed" | ||||
| 	"sync" | ||||
| 	"io/fs" | ||||
| 	"os" | ||||
| 	"path" | ||||
|  | ||||
| 	"go.elara.ws/logger" | ||||
| 	"plemya-x.ru/alr/pkg/loggerctx" | ||||
| 	"go.elara.ws/translate" | ||||
| 	"golang.org/x/text/language" | ||||
| 	"github.com/jeandeaual/go-locale" | ||||
| 	"github.com/leonelquinteros/gotext" | ||||
| ) | ||||
|  | ||||
| //go:embed files | ||||
| var translationFS embed.FS | ||||
| //go:embed po | ||||
| var poFS embed.FS | ||||
|  | ||||
| var ( | ||||
| 	mu         sync.Mutex | ||||
| 	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 { | ||||
| 			log.Fatal("Error creating new translator").Err(err).Send() | ||||
| 		} | ||||
| 		translator = &t | ||||
| func Setup() { | ||||
| 	userLanguage, err := locale.GetLanguage() | ||||
| 	if err != nil { | ||||
| 		panic(err) | ||||
| 	} | ||||
| 	return translator | ||||
| } | ||||
|  | ||||
| func NewLogger(ctx context.Context, l logger.Logger, lang language.Tag) *translate.TranslatedLogger { | ||||
| 	return translate.NewLogger(l, *Translator(ctx), lang) | ||||
| 	_, err = fs.Stat(poFS, path.Join("po", userLanguage)) | ||||
| 	if err != nil { | ||||
| 		if os.IsNotExist(err) { | ||||
| 			return | ||||
| 		} | ||||
| 		panic(err) | ||||
| 	} | ||||
|  | ||||
| 	loc := gotext.NewLocaleFSWithPath(userLanguage, &poFS, "po") | ||||
| 	loc.SetDomain("default") | ||||
| 	gotext.SetLocales([]*gotext.Locale{loc}) | ||||
| } | ||||
|   | ||||
| @@ -1,32 +1,84 @@ | ||||
| /* | ||||
|  * 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/>. | ||||
|  */ | ||||
| // 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 types | ||||
|  | ||||
| import "plemya-x.ru/alr/pkg/manager" | ||||
| import "gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager" | ||||
|  | ||||
| type BuildOpts struct { | ||||
| 	Script      string | ||||
| 	Repository  string | ||||
| 	Packages    []string | ||||
| 	Manager     manager.Manager | ||||
| 	Clean       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 | ||||
| // to build a package | ||||
| type BuildVars struct { | ||||
| @@ -49,6 +101,9 @@ type BuildVars struct { | ||||
| 	Checksums     []string `sh:"checksums"` | ||||
| 	Backup        []string `sh:"backup"` | ||||
| 	Scripts       Scripts  `sh:"scripts"` | ||||
| 	AutoReq       []string `sh:"auto_req"` | ||||
| 	AutoProv      []string `sh:"auto_prov"` | ||||
| 	Base          string | ||||
| } | ||||
|  | ||||
| type Scripts struct { | ||||
|   | ||||
| @@ -1,20 +1,21 @@ | ||||
| /* | ||||
|  * 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/>. | ||||
|  */ | ||||
| // 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 types | ||||
|  | ||||
| @@ -25,6 +26,7 @@ type Config struct { | ||||
| 	IgnorePkgUpdates []string `toml:"ignorePkgUpdates"` | ||||
| 	Repos            []Repo   `toml:"repo"` | ||||
| 	Unsafe           Unsafe   `toml:"unsafe"` | ||||
| 	AutoPull         bool     `toml:"autoPull"` | ||||
| } | ||||
|  | ||||
| // Repo represents a ALR repo within a configuration file | ||||
|   | ||||
| @@ -1,20 +1,21 @@ | ||||
| /* | ||||
|  * 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/>. | ||||
|  */ | ||||
| // 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 types | ||||
|  | ||||
|   | ||||
							
								
								
									
										18
									
								
								license-header-old-files.tmpl
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								license-header-old-files.tmpl
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										15
									
								
								license-header.tmpl
									
									
									
									
									
										Normal 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/>. | ||||
							
								
								
									
										206
									
								
								list.go
									
									
									
									
									
								
							
							
						
						
									
										206
									
								
								list.go
									
									
									
									
									
								
							| @@ -1,108 +1,138 @@ | ||||
| /* | ||||
|  * 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/>. | ||||
|  */ | ||||
| // 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 | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"log/slog" | ||||
| 	"os" | ||||
|  | ||||
| 	"github.com/leonelquinteros/gotext" | ||||
| 	"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" | ||||
|  | ||||
| 	"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{ | ||||
| 	Name:    "list", | ||||
| 	Usage:   "List ALR repo packages", | ||||
| 	Aliases: []string{"ls"}, | ||||
| 	Flags: []cli.Flag{ | ||||
| 		&cli.BoolFlag{ | ||||
| 			Name:    "installed", | ||||
| 			Aliases: []string{"I"}, | ||||
| func ListCmd() *cli.Command { | ||||
| 	return &cli.Command{ | ||||
| 		Name:    "list", | ||||
| 		Usage:   gotext.Get("List ALR repo packages"), | ||||
| 		Aliases: []string{"ls"}, | ||||
| 		Flags: []cli.Flag{ | ||||
| 			&cli.BoolFlag{ | ||||
| 				Name:    "installed", | ||||
| 				Aliases: []string{"I"}, | ||||
| 			}, | ||||
| 		}, | ||||
| 	}, | ||||
| 	Action: func(c *cli.Context) error { | ||||
| 		ctx := c.Context | ||||
| 		log := loggerctx.From(ctx) | ||||
|  | ||||
| 		err := repos.Pull(ctx, config.Config(ctx).Repos) | ||||
| 		if err != nil { | ||||
| 			log.Fatal("Error pulling repositories").Err(err).Send() | ||||
| 		} | ||||
|  | ||||
| 		where := "true" | ||||
| 		args := []any(nil) | ||||
| 		if c.NArg() > 0 { | ||||
| 			where = "name LIKE ? OR json_array_contains(provides, ?)" | ||||
| 			args = []any{c.Args().First(), c.Args().First()} | ||||
| 		} | ||||
|  | ||||
| 		result, err := db.GetPkgs(ctx, where, args...) | ||||
| 		if err != nil { | ||||
| 			log.Fatal("Error getting packages").Err(err).Send() | ||||
| 		} | ||||
| 		defer result.Close() | ||||
|  | ||||
| 		var installed map[string]string | ||||
| 		if c.Bool("installed") { | ||||
| 			mgr := manager.Detect() | ||||
| 			if mgr == nil { | ||||
| 				log.Fatal("Unable to detect a supported package manager on the system").Send() | ||||
| 			} | ||||
|  | ||||
| 			installed, err = mgr.ListInstalled(&manager.Opts{AsRoot: false}) | ||||
| 		Action: func(c *cli.Context) error { | ||||
| 			ctx := c.Context | ||||
| 			cfg := config.New() | ||||
| 			db := database.New(cfg) | ||||
| 			err := db.Init(ctx) | ||||
| 			if err != nil { | ||||
| 				log.Fatal("Error listing installed packages").Err(err).Send() | ||||
| 				slog.Error(gotext.Get("Error initialization database"), "err", err) | ||||
| 				os.Exit(1) | ||||
| 			} | ||||
| 		} | ||||
| 			rs := repos.New(cfg, db) | ||||
|  | ||||
| 		for result.Next() { | ||||
| 			var pkg db.Package | ||||
| 			err := result.StructScan(&pkg) | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
|  | ||||
| 			if slices.Contains(config.Config(ctx).IgnorePkgUpdates, pkg.Name) { | ||||
| 				continue | ||||
| 			} | ||||
|  | ||||
| 			version := pkg.Version | ||||
| 			if c.Bool("installed") { | ||||
| 				instVersion, ok := installed[pkg.Name] | ||||
| 				if !ok { | ||||
| 					continue | ||||
| 				} else { | ||||
| 					version = instVersion | ||||
| 			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) | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			fmt.Printf("%s/%s %s\n", pkg.Repository, pkg.Name, version) | ||||
| 		} | ||||
| 			where := "true" | ||||
| 			args := []any(nil) | ||||
| 			if c.NArg() > 0 { | ||||
| 				where = "name LIKE ? OR json_array_contains(provides, ?)" | ||||
| 				args = []any{c.Args().First(), c.Args().First()} | ||||
| 			} | ||||
|  | ||||
| 		if err != nil { | ||||
| 			log.Fatal("Error iterating over packages").Err(err).Send() | ||||
| 		} | ||||
| 			result, err := db.GetPkgs(ctx, where, args...) | ||||
| 			if err != nil { | ||||
| 				slog.Error(gotext.Get("Error getting packages"), "err", err) | ||||
| 				os.Exit(1) | ||||
| 			} | ||||
| 			defer result.Close() | ||||
|  | ||||
| 		return nil | ||||
| 	}, | ||||
| 			installedAlrPackages := map[string]string{} | ||||
| 			if c.Bool("installed") { | ||||
| 				mgr := manager.Detect() | ||||
| 				if mgr == nil { | ||||
| 					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}) | ||||
| 				if err != nil { | ||||
| 					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() { | ||||
| 				var pkg database.Package | ||||
| 				err := result.StructScan(&pkg) | ||||
| 				if err != nil { | ||||
| 					return err | ||||
| 				} | ||||
|  | ||||
| 				if slices.Contains(cfg.IgnorePkgUpdates(ctx), pkg.Name) { | ||||
| 					continue | ||||
| 				} | ||||
|  | ||||
| 				version := pkg.Version | ||||
| 				if c.Bool("installed") { | ||||
| 					instVersion, ok := installedAlrPackages[fmt.Sprintf("%s/%s", pkg.Repository, pkg.Name)] | ||||
| 					if !ok { | ||||
| 						continue | ||||
| 					} else { | ||||
| 						version = instVersion | ||||
| 					} | ||||
| 				} | ||||
|  | ||||
| 				fmt.Printf("%s/%s %s\n", pkg.Repository, pkg.Name, version) | ||||
| 			} | ||||
|  | ||||
| 			if err != nil { | ||||
| 				slog.Error(gotext.Get("Error iterating over packages"), "err", err) | ||||
| 				os.Exit(1) | ||||
| 			} | ||||
|  | ||||
| 			return nil | ||||
| 		}, | ||||
| 	} | ||||
| } | ||||
|   | ||||
							
								
								
									
										184
									
								
								main.go
									
									
									
									
									
								
							
							
						
						
									
										184
									
								
								main.go
									
									
									
									
									
								
							| @@ -1,115 +1,131 @@ | ||||
| /* | ||||
|  * 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/>. | ||||
|  */ | ||||
| // 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 | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"log/slog" | ||||
| 	"os" | ||||
| 	"os/signal" | ||||
| 	"strings" | ||||
| 	"syscall" | ||||
|  | ||||
| 	"github.com/leonelquinteros/gotext" | ||||
| 	"github.com/mattn/go-isatty" | ||||
| 	"github.com/urfave/cli/v2" | ||||
| 	"go.elara.ws/logger" | ||||
| 	"plemya-x.ru/alr/internal/config" | ||||
| 	"plemya-x.ru/alr/internal/db" | ||||
| 	"plemya-x.ru/alr/internal/translations" | ||||
| 	"plemya-x.ru/alr/pkg/loggerctx" | ||||
| 	"plemya-x.ru/alr/pkg/manager" | ||||
|  | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils" | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/config" | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/translations" | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager" | ||||
|  | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/logger" | ||||
| ) | ||||
|  | ||||
| var app = &cli.App{ | ||||
| 	Name:  "alr", | ||||
| 	Usage: "Any Linux Repository", | ||||
| 	Flags: []cli.Flag{ | ||||
| 		&cli.StringFlag{ | ||||
| 			Name:    "pm-args", | ||||
| 			Aliases: []string{"P"}, | ||||
| 			Usage:   "Arguments to be passed on to the package manager", | ||||
| 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 | ||||
| 		}, | ||||
| 		&cli.BoolFlag{ | ||||
| 			Name:    "interactive", | ||||
| 			Aliases: []string{"i"}, | ||||
| 			Value:   isatty.IsTerminal(os.Stdin.Fd()), | ||||
| 			Usage:   "Enable interactive questions and prompts", | ||||
| 		}, | ||||
| 	}, | ||||
| 	Commands: []*cli.Command{ | ||||
| 		installCmd, | ||||
| 		removeCmd, | ||||
| 		upgradeCmd, | ||||
| 		infoCmd, | ||||
| 		listCmd, | ||||
| 		buildCmd, | ||||
| 		addrepoCmd, | ||||
| 		removerepoCmd, | ||||
| 		refreshCmd, | ||||
| 		fixCmd, | ||||
| 		genCmd, | ||||
| 		helperCmd, | ||||
| 		versionCmd, | ||||
| 	}, | ||||
| 	Before: func(c *cli.Context) error { | ||||
| 		ctx := c.Context | ||||
| 		log := loggerctx.From(ctx) | ||||
|  | ||||
| 		cmd := c.Args().First() | ||||
| 		if cmd != "helper" && !config.Config(ctx).Unsafe.AllowRunAsRoot && os.Geteuid() == 0 { | ||||
| 			log.Fatal("Running ALR as root is forbidden as it may cause catastrophic damage to your system").Send() | ||||
| 		} | ||||
|  | ||||
| 		if trimmed := strings.TrimSpace(c.String("pm-args")); trimmed != "" { | ||||
| 			args := strings.Split(trimmed, " ") | ||||
| 			manager.Args = append(manager.Args, args...) | ||||
| 		} | ||||
|  | ||||
| 		return nil | ||||
| 	}, | ||||
| 	After: func(ctx *cli.Context) error { | ||||
| 		return db.Close() | ||||
| 	}, | ||||
| 	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 GetApp() *cli.App { | ||||
| 	return &cli.App{ | ||||
| 		Name:  "alr", | ||||
| 		Usage: "Any Linux Repository", | ||||
| 		Flags: []cli.Flag{ | ||||
| 			&cli.StringFlag{ | ||||
| 				Name:    "pm-args", | ||||
| 				Aliases: []string{"P"}, | ||||
| 				Usage:   gotext.Get("Arguments to be passed on to the package manager"), | ||||
| 			}, | ||||
| 			&cli.BoolFlag{ | ||||
| 				Name:    "interactive", | ||||
| 				Aliases: []string{"i"}, | ||||
| 				Value:   isatty.IsTerminal(os.Stdin.Fd()), | ||||
| 				Usage:   gotext.Get("Enable interactive questions and prompts"), | ||||
| 			}, | ||||
| 		}, | ||||
| 		Commands: []*cli.Command{ | ||||
| 			InstallCmd(), | ||||
| 			RemoveCmd(), | ||||
| 			UpgradeCmd(), | ||||
| 			InfoCmd(), | ||||
| 			ListCmd(), | ||||
| 			BuildCmd(), | ||||
| 			AddRepoCmd(), | ||||
| 			RemoveRepoCmd(), | ||||
| 			RefreshCmd(), | ||||
| 			FixCmd(), | ||||
| 			GenCmd(), | ||||
| 			HelperCmd(), | ||||
| 			VersionCmd(), | ||||
| 			SearchCmd(), | ||||
| 		}, | ||||
| 		Before: func(c *cli.Context) error { | ||||
| 			ctx := c.Context | ||||
| 			cfg := config.New() | ||||
|  | ||||
| 			cmd := c.Args().First() | ||||
| 			if cmd != "helper" && !cfg.AllowRunAsRoot(ctx) && os.Geteuid() == 0 { | ||||
| 				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 != "" { | ||||
| 				args := strings.Split(trimmed, " ") | ||||
| 				manager.Args = append(manager.Args, args...) | ||||
| 			} | ||||
|  | ||||
| 			return nil | ||||
| 		}, | ||||
| 		EnableBashCompletion: true, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func main() { | ||||
| 	translations.Setup() | ||||
| 	logger.SetupDefault() | ||||
|  | ||||
| 	app := GetApp() | ||||
| 	cfg := config.New() | ||||
|  | ||||
| 	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 | ||||
| 	manager.DefaultRootCmd = config.Config(ctx).RootCmd | ||||
| 	manager.DefaultRootCmd = cfg.RootCmd(ctx) | ||||
|  | ||||
| 	ctx, cancel := signal.NotifyContext(ctx, syscall.SIGINT, syscall.SIGTERM) | ||||
| 	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) | ||||
| 	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
									
								
							
							
						
						
									
										83
									
								
								old-files
									
									
									
									
									
										Normal 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 | ||||
							
								
								
									
										1296
									
								
								pkg/build/build.go
									
									
									
									
									
								
							
							
						
						
									
										1296
									
								
								pkg/build/build.go
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										286
									
								
								pkg/build/build_internal_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										286
									
								
								pkg/build/build_internal_test.go
									
									
									
									
									
										Normal 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) | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										92
									
								
								pkg/build/findDeps.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								pkg/build/findDeps.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,92 @@ | ||||
| // 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 ( | ||||
| 	"bytes" | ||||
| 	"context" | ||||
| 	"log/slog" | ||||
| 	"os/exec" | ||||
| 	"path" | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/goreleaser/nfpm/v2" | ||||
| 	"github.com/leonelquinteros/gotext" | ||||
|  | ||||
| 	"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 { | ||||
| 	if _, err := exec.LookPath(command); err != nil { | ||||
| 		slog.Info(gotext.Get("Command not found on the system"), "command", command) | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	var paths []string | ||||
| 	for _, content := range pkgInfo.Contents { | ||||
| 		if content.Type != "dir" { | ||||
| 			paths = append(paths, | ||||
| 				path.Join(dirs.PkgDir, content.Destination), | ||||
| 			) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if len(paths) == 0 { | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	cmd := exec.Command(command) | ||||
| 	cmd.Stdin = bytes.NewBufferString(strings.Join(paths, "\n")) | ||||
| 	cmd.Env = append(cmd.Env, | ||||
| 		"RPM_BUILD_ROOT="+dirs.PkgDir, | ||||
| 		"RPM_FINDPROV_METHOD=", | ||||
| 		"RPM_FINDREQ_METHOD=", | ||||
| 		"RPM_DATADIR=", | ||||
| 		"RPM_SUBPACKAGE_NAME=", | ||||
| 	) | ||||
| 	var out bytes.Buffer | ||||
| 	var stderr bytes.Buffer | ||||
| 	cmd.Stdout = &out | ||||
| 	cmd.Stderr = &stderr | ||||
| 	if err := cmd.Run(); err != nil { | ||||
| 		slog.Error(stderr.String()) | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	dependencies := strings.Split(strings.TrimSpace(out.String()), "\n") | ||||
| 	for _, dep := range dependencies { | ||||
| 		if dep != "" { | ||||
| 			updateFunc(dep) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func rpmFindProvides(ctx context.Context, pkgInfo *nfpm.Info, dirs types.Directories) error { | ||||
| 	return rpmFindDependencies(ctx, pkgInfo, dirs, "/usr/lib/rpm/find-provides", func(dep string) { | ||||
| 		slog.Info(gotext.Get("Provided dependency found"), "dep", dep) | ||||
| 		pkgInfo.Overridables.Provides = append(pkgInfo.Overridables.Provides, dep) | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| func rpmFindRequires(ctx context.Context, pkgInfo *nfpm.Info, dirs types.Directories) error { | ||||
| 	return rpmFindDependencies(ctx, pkgInfo, dirs, "/usr/lib/rpm/find-requires", func(dep string) { | ||||
| 		slog.Info(gotext.Get("Required dependency found"), "dep", dep) | ||||
| 		pkgInfo.Overridables.Depends = append(pkgInfo.Overridables.Depends, dep) | ||||
| 	}) | ||||
| } | ||||
| @@ -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
									
								
							
							
						
						
									
										336
									
								
								pkg/build/utils.go
									
									
									
									
									
										Normal 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 | ||||
| } | ||||
							
								
								
									
										47
									
								
								pkg/build/utils_internal_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								pkg/build/utils_internal_test.go
									
									
									
									
									
										Normal 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) | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
| @@ -1,20 +1,21 @@ | ||||
| /* | ||||
|  * 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/>. | ||||
|  */ | ||||
| // 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 distro | ||||
|  | ||||
| @@ -23,10 +24,11 @@ import ( | ||||
| 	"os" | ||||
| 	"strings" | ||||
|  | ||||
| 	"plemya-x.ru/alr/internal/shutils/handlers" | ||||
| 	"mvdan.cc/sh/v3/expand" | ||||
| 	"mvdan.cc/sh/v3/interp" | ||||
| 	"mvdan.cc/sh/v3/syntax" | ||||
|  | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/handlers" | ||||
| ) | ||||
|  | ||||
| // OSRelease contains information from an os-release file | ||||
| @@ -42,6 +44,7 @@ type OSRelease struct { | ||||
| 	SupportURL       string | ||||
| 	BugReportURL     string | ||||
| 	Logo             string | ||||
| 	PlatformID       string | ||||
| } | ||||
|  | ||||
| var parsed *OSRelease | ||||
| @@ -76,7 +79,7 @@ func ParseOSRelease(ctx context.Context) (*OSRelease, error) { | ||||
| 	runner, err := interp.New( | ||||
| 		interp.OpenHandler(handlers.NopOpen), | ||||
| 		interp.ExecHandler(handlers.NopExec), | ||||
| 		interp.ReadDirHandler(handlers.NopReadDir), | ||||
| 		interp.ReadDirHandler2(handlers.NopReadDir), | ||||
| 		interp.StatHandler(handlers.NopStat), | ||||
| 		interp.Env(expand.ListEnviron()), | ||||
| 	) | ||||
| @@ -100,6 +103,7 @@ func ParseOSRelease(ctx context.Context) (*OSRelease, error) { | ||||
| 		SupportURL:       runner.Vars["SUPPORT_URL"].Str, | ||||
| 		BugReportURL:     runner.Vars["BUG_REPORT_URL"].Str, | ||||
| 		Logo:             runner.Vars["LOGO"].Str, | ||||
| 		PlatformID:       runner.Vars["PLATFORM_ID"].Str, | ||||
| 	} | ||||
|  | ||||
| 	distroUpdated := false | ||||
|   | ||||
| @@ -1,20 +1,39 @@ | ||||
| // 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 | ||||
|  | ||||
| import ( | ||||
|     "strings" | ||||
|     "text/template" | ||||
| 	"strings" | ||||
| 	"text/template" | ||||
| ) | ||||
|  | ||||
| // Определяем переменную funcs типа template.FuncMap, которая будет использоваться для | ||||
| // предоставления пользовательских функций в шаблонах | ||||
| var funcs = template.FuncMap{ | ||||
|     // Функция "tolower" использует strings.ToLower | ||||
|     // для преобразования строки в нижний регистр | ||||
|     "tolower": strings.ToLower, | ||||
| 	// Функция "tolower" использует strings.ToLower | ||||
| 	// для преобразования строки в нижний регистр | ||||
| 	"tolower": strings.ToLower, | ||||
|  | ||||
|     // Функция "firstchar" — это лямбда-функция, которая берет строку | ||||
|     // и возвращает её первый символ | ||||
|     "firstchar": func(s string) string { | ||||
|         return s[:1] | ||||
|     }, | ||||
| 	// Функция "firstchar" — это лямбда-функция, которая берет строку | ||||
| 	// и возвращает её первый символ | ||||
| 	"firstchar": func(s string) string { | ||||
| 		return s[:1] | ||||
| 	}, | ||||
| } | ||||
|   | ||||
							
								
								
									
										140
									
								
								pkg/gen/pip.go
									
									
									
									
									
								
							
							
						
						
									
										140
									
								
								pkg/gen/pip.go
									
									
									
									
									
								
							| @@ -1,98 +1,118 @@ | ||||
| // 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 | ||||
|  | ||||
| import ( | ||||
|     _ "embed" // Пакет для встраивания содержимого файлов в бинарники Go, использовав откладку //go:embed | ||||
|     "encoding/json" // Пакет для работы с JSON: декодирование и кодирование | ||||
|     "errors"    // Пакет для создания и обработки ошибок | ||||
|     "fmt"       // Пакет для форматированного ввода и вывода | ||||
|     "io"        // Пакет для интерфейсов ввода и вывода | ||||
|     "net/http"  // Пакет для HTTP-клиентов и серверов | ||||
|     "text/template" // Пакет для обработки текстовых шаблонов | ||||
| 	_ "embed"       // Пакет для встраивания содержимого файлов в бинарники Go, использовав откладку //go:embed | ||||
| 	"encoding/json" // Пакет для работы с JSON: декодирование и кодирование | ||||
| 	"errors"        // Пакет для создания и обработки ошибок | ||||
| 	"fmt"           // Пакет для форматированного ввода и вывода | ||||
| 	"io"            // Пакет для интерфейсов ввода и вывода | ||||
| 	"net/http"      // Пакет для HTTP-клиентов и серверов | ||||
| 	"text/template" // Пакет для обработки текстовых шаблонов | ||||
| ) | ||||
|  | ||||
| // Используем директиву //go:embed для встраивания содержимого файла шаблона в строку pipTmpl | ||||
| // Встраивание файла tmpls/pip.tmpl.sh | ||||
| // | ||||
| //go:embed tmpls/pip.tmpl.sh | ||||
| var pipTmpl string | ||||
|  | ||||
| // PipOptions содержит параметры, которые будут переданы в шаблон | ||||
| type PipOptions struct { | ||||
|     Name        string // Имя пакета | ||||
|     Version     string // Версия пакета | ||||
|     Description string // Описание пакета | ||||
| 	Name        string // Имя пакета | ||||
| 	Version     string // Версия пакета | ||||
| 	Description string // Описание пакета | ||||
| } | ||||
|  | ||||
| // pypiAPIResponse представляет структуру ответа от API PyPI | ||||
| type pypiAPIResponse struct { | ||||
|     Info pypiInfo  `json:"info"` // Информация о пакете | ||||
|     URLs []pypiURL `json:"urls"` // Список URL-адресов для загрузки пакета | ||||
| 	Info pypiInfo  `json:"info"` // Информация о пакете | ||||
| 	URLs []pypiURL `json:"urls"` // Список URL-адресов для загрузки пакета | ||||
| } | ||||
|  | ||||
| // Метод SourceURL ищет и возвращает URL исходного distribution для пакета, если он существует | ||||
| func (res pypiAPIResponse) SourceURL() (pypiURL, error) { | ||||
|     for _, url := range res.URLs { | ||||
|         if url.PackageType == "sdist" { | ||||
|             return url, nil | ||||
|         } | ||||
|     } | ||||
|     return pypiURL{}, errors.New("package doesn't have a source distribution") | ||||
| 	for _, url := range res.URLs { | ||||
| 		if url.PackageType == "sdist" { | ||||
| 			return url, nil | ||||
| 		} | ||||
| 	} | ||||
| 	return pypiURL{}, errors.New("package doesn't have a source distribution") | ||||
| } | ||||
|  | ||||
| // pypiInfo содержит основную информацию о пакете, такую как имя, версия и пр. | ||||
| type pypiInfo struct { | ||||
|     Name     string `json:"name"` | ||||
|     Version  string `json:"version"` | ||||
|     Summary  string `json:"summary"` | ||||
|     Homepage string `json:"home_page"` | ||||
|     License  string `json:"license"` | ||||
| 	Name     string `json:"name"` | ||||
| 	Version  string `json:"version"` | ||||
| 	Summary  string `json:"summary"` | ||||
| 	Homepage string `json:"home_page"` | ||||
| 	License  string `json:"license"` | ||||
| } | ||||
|  | ||||
| // pypiURL представляет информацию об одном из доступных для загрузки URL | ||||
| type pypiURL struct { | ||||
|     Digests     map[string]string `json:"digests"` // Контрольные суммы для файлов | ||||
|     Filename    string            `json:"filename"` // Имя файла | ||||
|     PackageType string            `json:"packagetype"` // Тип пакета (например sdist) | ||||
| 	Digests     map[string]string `json:"digests"`     // Контрольные суммы для файлов | ||||
| 	Filename    string            `json:"filename"`    // Имя файла | ||||
| 	PackageType string            `json:"packagetype"` // Тип пакета (например sdist) | ||||
| } | ||||
|  | ||||
| // Функция Pip загружает информацию о пакете из PyPI и использует шаблон для вывода информации | ||||
| func Pip(w io.Writer, opts PipOptions) error { | ||||
|     // Создаем новый шаблон с добавлением функций из FuncMap | ||||
|     tmpl, err := template.New("pip"). | ||||
|         Funcs(funcs). | ||||
|         Parse(pipTmpl) | ||||
|     if err != nil { | ||||
|         return err | ||||
|     } | ||||
| 	// Создаем новый шаблон с добавлением функций из FuncMap | ||||
| 	tmpl, err := template.New("pip"). | ||||
| 		Funcs(funcs). | ||||
| 		Parse(pipTmpl) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
|     // Формируем URL для запроса к PyPI на основании имени и версии пакета | ||||
|     url := fmt.Sprintf( | ||||
|         "https://pypi.org/pypi/%s/%s/json", | ||||
|         opts.Name, | ||||
|         opts.Version, | ||||
|     ) | ||||
| 	// Формируем URL для запроса к PyPI на основании имени и версии пакета | ||||
| 	url := fmt.Sprintf( | ||||
| 		"https://pypi.org/pypi/%s/%s/json", | ||||
| 		opts.Name, | ||||
| 		opts.Version, | ||||
| 	) | ||||
|  | ||||
|     // Выполняем HTTP GET запрос к PyPI | ||||
|     res, err := http.Get(url) | ||||
|     if err != nil { | ||||
|         return err | ||||
|     } | ||||
|     defer res.Body.Close() // Закрываем тело ответа после завершения работы | ||||
|     if res.StatusCode != 200 { | ||||
|         return fmt.Errorf("pypi: %s", res.Status) | ||||
|     } | ||||
| 	// Выполняем HTTP GET запрос к PyPI | ||||
| 	res, err := http.Get(url) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	defer res.Body.Close() // Закрываем тело ответа после завершения работы | ||||
| 	if res.StatusCode != 200 { | ||||
| 		return fmt.Errorf("pypi: %s", res.Status) | ||||
| 	} | ||||
|  | ||||
|     // Раскодируем ответ JSON от PyPI в структуру pypiAPIResponse | ||||
|     var resp pypiAPIResponse | ||||
|     err = json.NewDecoder(res.Body).Decode(&resp) | ||||
|     if err != nil { | ||||
|         return err | ||||
|     } | ||||
| 	// Раскодируем ответ JSON от PyPI в структуру pypiAPIResponse | ||||
| 	var resp pypiAPIResponse | ||||
| 	err = json.NewDecoder(res.Body).Decode(&resp) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
|     // Если в opts указано описание, используем его вместо описания из PyPI | ||||
|     if opts.Description != "" { | ||||
|         resp.Info.Summary = opts.Description | ||||
|     } | ||||
| 	// Если в opts указано описание, используем его вместо описания из PyPI | ||||
| 	if opts.Description != "" { | ||||
| 		resp.Info.Summary = opts.Description | ||||
| 	} | ||||
|  | ||||
|     // Выполняем шаблон с использованием данных из resp и записываем результат в w | ||||
|     return tmpl.Execute(w, resp) | ||||
| 	// Выполняем шаблон с использованием данных из resp и записываем результат в w | ||||
| 	return tmpl.Execute(w, resp) | ||||
| } | ||||
|   | ||||
| @@ -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}}' | ||||
| version='{{.Info.Version}}' | ||||
| release='1' | ||||
| @@ -13,19 +32,19 @@ deps=("python3") | ||||
| deps_arch=("python") | ||||
| deps_alpine=("python3") | ||||
|  | ||||
| build_deps=("python3" "python3-setuptools") | ||||
| build_deps_arch=("python" "python-setuptools") | ||||
| build_deps_alpine=("python3" "py3-setuptools") | ||||
| build_deps=("python3" "python3-pip") | ||||
| build_deps_arch=("python" "python-pip") | ||||
| build_deps_alpine=("python3" "py3-pip") | ||||
|  | ||||
| sources=("https://files.pythonhosted.org/packages/source/{{.SourceURL.Filename | firstchar}}/{{.Info.Name}}/{{.SourceURL.Filename}}") | ||||
| checksums=('blake2b-256:{{.SourceURL.Digests.blake2b_256}}') | ||||
|  | ||||
| build() { | ||||
| 	cd "$srcdir/{{.Info.Name}}-${version}" | ||||
| 	python3 setup.py build | ||||
|   python3 -m build | ||||
| } | ||||
|  | ||||
| package() { | ||||
| 	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 | ||||
| } | ||||
|   | ||||
| @@ -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() | ||||
| 	} | ||||
| } | ||||
| @@ -1,20 +1,21 @@ | ||||
| /* | ||||
|  * 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/>. | ||||
|  */ | ||||
| // 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 manager | ||||
|  | ||||
| @@ -148,6 +149,21 @@ func (a *APK) ListInstalled(opts *Opts) (map[string]string, error) { | ||||
| 	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 { | ||||
| 	var cmd *exec.Cmd | ||||
| 	if opts.AsRoot { | ||||
|   | ||||
| @@ -1,20 +1,21 @@ | ||||
| /* | ||||
|  * 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/>. | ||||
|  */ | ||||
| // 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 manager | ||||
|  | ||||
| @@ -134,6 +135,21 @@ func (a *APT) ListInstalled(opts *Opts) (map[string]string, error) { | ||||
| 	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 { | ||||
| 	var cmd *exec.Cmd | ||||
| 	if opts.AsRoot { | ||||
|   | ||||
| @@ -1,25 +1,22 @@ | ||||
| /* | ||||
|  * 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/>. | ||||
|  */ | ||||
| // 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" | ||||
| @@ -27,6 +24,7 @@ import ( | ||||
|  | ||||
| // APTRpm represents the APT-RPM package manager | ||||
| type APTRpm struct { | ||||
| 	CommonRPM | ||||
| 	rootCmd string | ||||
| } | ||||
|  | ||||
| @@ -107,37 +105,6 @@ func (a *APTRpm) UpgradeAll(opts *Opts) error { | ||||
| 	} | ||||
| 	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 { | ||||
| 	var cmd *exec.Cmd | ||||
|   | ||||
							
								
								
									
										72
									
								
								pkg/manager/common_rpm.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								pkg/manager/common_rpm.go
									
									
									
									
									
										Normal 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 | ||||
| } | ||||
| @@ -19,154 +19,120 @@ | ||||
| package manager | ||||
|  | ||||
| import ( | ||||
|     "bufio" | ||||
|     "fmt" | ||||
|     "os/exec" | ||||
|     "strings" | ||||
| 	"fmt" | ||||
| 	"os/exec" | ||||
| ) | ||||
|  | ||||
| // DNF представляет менеджер пакетов DNF | ||||
| type DNF struct { | ||||
|     rootCmd string  // rootCmd хранит команду, используемую для выполнения команд с правами root | ||||
| 	CommonRPM | ||||
| 	rootCmd string // rootCmd хранит команду, используемую для выполнения команд с правами root | ||||
| } | ||||
|  | ||||
| // Exists проверяет, доступен ли DNF в системе, возвращает true если да | ||||
| func (*DNF) Exists() bool { | ||||
|     _, err := exec.LookPath("dnf") | ||||
|     return err == nil | ||||
| 	_, err := exec.LookPath("dnf") | ||||
| 	return err == nil | ||||
| } | ||||
|  | ||||
| // Name возвращает имя менеджера пакетов, в данном случае "dnf" | ||||
| func (*DNF) Name() string { | ||||
|     return "dnf" | ||||
| 	return "dnf" | ||||
| } | ||||
|  | ||||
| // Format возвращает формат пакетов "rpm", используемый DNF | ||||
| func (*DNF) Format() string { | ||||
|     return "rpm" | ||||
| 	return "rpm" | ||||
| } | ||||
|  | ||||
| // SetRootCmd устанавливает команду, используемую для выполнения операций с правами root | ||||
| func (d *DNF) SetRootCmd(s string) { | ||||
|     d.rootCmd = s | ||||
| 	d.rootCmd = s | ||||
| } | ||||
|  | ||||
| // Sync выполняет upgrade всех установленных пакетов, обновляя их до более новых версий | ||||
| func (d *DNF) Sync(opts *Opts) error { | ||||
|     opts = ensureOpts(opts)  // Гарантирует, что opts не равен nil и содержит допустимые значения | ||||
|     cmd := d.getCmd(opts, "dnf", "upgrade") | ||||
|     setCmdEnv(cmd)  // Устанавливает переменные окружения для команды | ||||
|     err := cmd.Run()  // Выполняет команду | ||||
|     if err != nil { | ||||
|         return fmt.Errorf("dnf: sync: %w", err) | ||||
|     } | ||||
|     return nil | ||||
| 	opts = ensureOpts(opts) // Гарантирует, что opts не равен nil и содержит допустимые значения | ||||
| 	cmd := d.getCmd(opts, "dnf", "upgrade") | ||||
| 	setCmdEnv(cmd)   // Устанавливает переменные окружения для команды | ||||
| 	err := cmd.Run() // Выполняет команду | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("dnf: sync: %w", err) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // Install устанавливает указанные пакеты с помощью DNF | ||||
| func (d *DNF) Install(opts *Opts, pkgs ...string) error { | ||||
|     opts = ensureOpts(opts) | ||||
|     cmd := d.getCmd(opts, "dnf", "install", "--allowerasing") | ||||
|     cmd.Args = append(cmd.Args, pkgs...)  // Добавляем названия пакетов к команде | ||||
|     setCmdEnv(cmd) | ||||
|     err := cmd.Run() | ||||
|     if err != nil { | ||||
|         return fmt.Errorf("dnf: install: %w", err) | ||||
|     } | ||||
|     return nil | ||||
| 	opts = ensureOpts(opts) | ||||
| 	cmd := d.getCmd(opts, "dnf", "install", "--allowerasing") | ||||
| 	cmd.Args = append(cmd.Args, pkgs...) // Добавляем названия пакетов к команде | ||||
| 	setCmdEnv(cmd) | ||||
| 	err := cmd.Run() | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("dnf: install: %w", err) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // InstallLocal расширяет метод Install для установки пакетов, расположенных локально | ||||
| func (d *DNF) InstallLocal(opts *Opts, pkgs ...string) error { | ||||
|     opts = ensureOpts(opts) | ||||
|     return d.Install(opts, pkgs...) | ||||
| 	opts = ensureOpts(opts) | ||||
| 	return d.Install(opts, pkgs...) | ||||
| } | ||||
|  | ||||
| // Remove удаляет указанные пакеты с помощью DNF | ||||
| func (d *DNF) Remove(opts *Opts, pkgs ...string) error { | ||||
|     opts = ensureOpts(opts) | ||||
|     cmd := d.getCmd(opts, "dnf", "remove") | ||||
|     cmd.Args = append(cmd.Args, pkgs...) | ||||
|     setCmdEnv(cmd) | ||||
|     err := cmd.Run() | ||||
|     if err != nil { | ||||
|         return fmt.Errorf("dnf: remove: %w", err) | ||||
|     } | ||||
|     return nil | ||||
| 	opts = ensureOpts(opts) | ||||
| 	cmd := d.getCmd(opts, "dnf", "remove") | ||||
| 	cmd.Args = append(cmd.Args, pkgs...) | ||||
| 	setCmdEnv(cmd) | ||||
| 	err := cmd.Run() | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("dnf: remove: %w", err) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // Upgrade обновляет указанные пакеты до более новых версий | ||||
| func (d *DNF) Upgrade(opts *Opts, pkgs ...string) error { | ||||
|     opts = ensureOpts(opts) | ||||
|     cmd := d.getCmd(opts, "dnf", "upgrade") | ||||
|     cmd.Args = append(cmd.Args, pkgs...) | ||||
|     setCmdEnv(cmd) | ||||
|     err := cmd.Run() | ||||
|     if err != nil { | ||||
|         return fmt.Errorf("dnf: upgrade: %w", err) | ||||
|     } | ||||
|     return nil | ||||
| 	opts = ensureOpts(opts) | ||||
| 	cmd := d.getCmd(opts, "dnf", "upgrade") | ||||
| 	cmd.Args = append(cmd.Args, pkgs...) | ||||
| 	setCmdEnv(cmd) | ||||
| 	err := cmd.Run() | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("dnf: upgrade: %w", err) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // UpgradeAll обновляет все установленные пакеты | ||||
| func (d *DNF) UpgradeAll(opts *Opts) error { | ||||
|     opts = ensureOpts(opts) | ||||
|     cmd := d.getCmd(opts, "dnf", "upgrade") | ||||
|     setCmdEnv(cmd) | ||||
|     err := cmd.Run() | ||||
|     if err != nil { | ||||
|         return fmt.Errorf("dnf: upgradeall: %w", err) | ||||
|     } | ||||
|     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 | ||||
| 	opts = ensureOpts(opts) | ||||
| 	cmd := d.getCmd(opts, "dnf", "upgrade") | ||||
| 	setCmdEnv(cmd) | ||||
| 	err := cmd.Run() | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("dnf: upgradeall: %w", err) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // getCmd создает и возвращает команду exec.Cmd для менеджера пакетов DNF | ||||
| func (d *DNF) getCmd(opts *Opts, mgrCmd string, args ...string) *exec.Cmd { | ||||
|     var cmd *exec.Cmd | ||||
|     if opts.AsRoot { | ||||
|         cmd = exec.Command(getRootCmd(d.rootCmd), mgrCmd) | ||||
|         cmd.Args = append(cmd.Args, opts.Args...) | ||||
|         cmd.Args = append(cmd.Args, args...) | ||||
|     } else { | ||||
|         cmd = exec.Command(mgrCmd, args...) | ||||
|     } | ||||
| 	var cmd *exec.Cmd | ||||
| 	if opts.AsRoot { | ||||
| 		cmd = exec.Command(getRootCmd(d.rootCmd), mgrCmd) | ||||
| 		cmd.Args = append(cmd.Args, opts.Args...) | ||||
| 		cmd.Args = append(cmd.Args, args...) | ||||
| 	} else { | ||||
| 		cmd = exec.Command(mgrCmd, args...) | ||||
| 	} | ||||
|  | ||||
|     if opts.NoConfirm { | ||||
|         cmd.Args = append(cmd.Args, "-y")  // Добавляет параметр автоматического подтверждения (-y) | ||||
|     } | ||||
| 	if opts.NoConfirm { | ||||
| 		cmd.Args = append(cmd.Args, "-y") // Добавляет параметр автоматического подтверждения (-y) | ||||
| 	} | ||||
|  | ||||
|     return cmd | ||||
| 	return cmd | ||||
| } | ||||
|   | ||||
| @@ -1,20 +1,21 @@ | ||||
| /* | ||||
|  * 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/>. | ||||
|  */ | ||||
| // 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 manager | ||||
|  | ||||
| @@ -79,6 +80,8 @@ type Manager interface { | ||||
| 	UpgradeAll(*Opts) error | ||||
| 	// ListInstalled returns all installed packages mapped to their versions | ||||
| 	ListInstalled(*Opts) (map[string]string, error) | ||||
| 	// | ||||
| 	IsInstalled(string) (bool, error) | ||||
| } | ||||
|  | ||||
| // Detect returns the package manager detected on the system | ||||
|   | ||||
| @@ -1,20 +1,21 @@ | ||||
| /* | ||||
|  * 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/>. | ||||
|  */ | ||||
| // 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 manager | ||||
|  | ||||
| @@ -141,6 +142,21 @@ func (p *Pacman) ListInstalled(opts *Opts) (map[string]string, error) { | ||||
| 	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 { | ||||
| 	var cmd *exec.Cmd | ||||
| 	if opts.AsRoot { | ||||
|   | ||||
| @@ -1,32 +1,33 @@ | ||||
| /* | ||||
|  * 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/>. | ||||
|  */ | ||||
| // 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 manager | ||||
|  | ||||
| import ( | ||||
| 	"bufio" | ||||
| 	"fmt" | ||||
| 	"os/exec" | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| // YUM represents the YUM package manager | ||||
| type YUM struct { | ||||
| 	CommonRPM | ||||
|  | ||||
| 	rootCmd string | ||||
| } | ||||
|  | ||||
| @@ -110,38 +111,6 @@ func (y *YUM) UpgradeAll(opts *Opts) error { | ||||
| 	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 { | ||||
| 	var cmd *exec.Cmd | ||||
| 	if opts.AsRoot { | ||||
|   | ||||
| @@ -1,32 +1,32 @@ | ||||
| /* | ||||
|  * 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/>. | ||||
|  */ | ||||
| // 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 manager | ||||
|  | ||||
| import ( | ||||
| 	"bufio" | ||||
| 	"fmt" | ||||
| 	"os/exec" | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| // Zypper represents the Zypper package manager | ||||
| type Zypper struct { | ||||
| 	CommonRPM | ||||
| 	rootCmd string | ||||
| } | ||||
|  | ||||
| @@ -110,38 +110,6 @@ func (z *Zypper) UpgradeAll(opts *Opts) error { | ||||
| 	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 { | ||||
| 	var cmd *exec.Cmd | ||||
| 	if opts.AsRoot { | ||||
|   | ||||
| @@ -1,33 +1,31 @@ | ||||
| /* | ||||
|  * 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/>. | ||||
|  */ | ||||
| // 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 repos | ||||
|  | ||||
| import ( | ||||
| 	"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. | ||||
| // 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) { | ||||
| func (rs *Repos) FindPkgs(ctx context.Context, pkgs []string) (map[string][]db.Package, []string, error) { | ||||
| 	found := map[string][]db.Package{} | ||||
| 	notFound := []string(nil) | ||||
|  | ||||
| @@ -36,7 +34,7 @@ func FindPkgs(ctx context.Context, pkgs []string) (map[string][]db.Package, []st | ||||
| 			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 { | ||||
| 			return nil, nil, err | ||||
| 		} | ||||
| @@ -55,7 +53,7 @@ func FindPkgs(ctx context.Context, pkgs []string) (map[string][]db.Package, []st | ||||
| 		result.Close() | ||||
|  | ||||
| 		if added == 0 { | ||||
| 			result, err := db.GetPkgs(ctx, "name LIKE ?", pkgName) | ||||
| 			result, err := rs.db.GetPkgs(ctx, "name LIKE ?", pkgName) | ||||
| 			if err != nil { | ||||
| 				return nil, nil, err | ||||
| 			} | ||||
|   | ||||
| @@ -1,47 +1,44 @@ | ||||
| /* | ||||
|  * 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/>. | ||||
|  */ | ||||
| // 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 repos_test | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"reflect" | ||||
| 	"strings" | ||||
| 	"testing" | ||||
|  | ||||
| 	"plemya-x.ru/alr/internal/db" | ||||
| 	"plemya-x.ru/alr/internal/types" | ||||
| 	"plemya-x.ru/alr/pkg/repos" | ||||
| 	"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" | ||||
| ) | ||||
|  | ||||
| func TestFindPkgs(t *testing.T) { | ||||
| 	_, err := db.Open(":memory:") | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Expected no error, got %s", err) | ||||
| 	} | ||||
| 	defer db.Close() | ||||
| 	e := prepare(t) | ||||
| 	defer cleanup(t, e) | ||||
|  | ||||
| 	setCfgDirs(t) | ||||
| 	defer removeCacheDir(t) | ||||
| 	rs := repos.New( | ||||
| 		e.Cfg, | ||||
| 		e.Db, | ||||
| 	) | ||||
|  | ||||
| 	ctx := context.Background() | ||||
|  | ||||
| 	err = repos.Pull(ctx, []types.Repo{ | ||||
| 	err := rs.Pull(e.Ctx, []types.Repo{ | ||||
| 		{ | ||||
| 			Name: "default", | ||||
| 			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) | ||||
| 	} | ||||
|  | ||||
| 	found, notFound, err := repos.FindPkgs([]string{"itd", "nonexistentpackage1", "nonexistentpackage2"}) | ||||
| 	found, notFound, err := rs.FindPkgs( | ||||
| 		e.Ctx, | ||||
| 		[]string{"alr", "nonexistentpackage1", "nonexistentpackage2"}, | ||||
| 	) | ||||
| 	if err != nil { | ||||
| 		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)) | ||||
| 	} | ||||
|  | ||||
| 	itdPkgs, ok := found["itd"] | ||||
| 	alrPkgs, ok := found["alr"] | ||||
| 	if !ok { | ||||
| 		t.Fatalf("Expected 'itd' packages to be found") | ||||
| 		t.Fatalf("Expected 'alr' packages to be found") | ||||
| 	} | ||||
|  | ||||
| 	if len(itdPkgs) < 2 { | ||||
| 		t.Errorf("Expected two 'itd' packages to be found") | ||||
| 	if len(alrPkgs) < 2 { | ||||
| 		t.Errorf("Expected two 'alr' packages to be found") | ||||
| 	} | ||||
|  | ||||
| 	for i, pkg := range itdPkgs { | ||||
| 		if !strings.HasPrefix(pkg.Name, "itd") { | ||||
| 			t.Errorf("Expected package name of all found packages to start with 'itd', got %s on element %d", pkg.Name, i) | ||||
| 	for i, pkg := range alrPkgs { | ||||
| 		if !strings.HasPrefix(pkg.Name, "alr") { | ||||
| 			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) { | ||||
| 	_, err := db.Open(":memory:") | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Expected no error, got %s", err) | ||||
| 	} | ||||
| 	defer db.Close() | ||||
| 	e := prepare(t) | ||||
| 	defer cleanup(t, e) | ||||
|  | ||||
| 	setCfgDirs(t) | ||||
| 	defer removeCacheDir(t) | ||||
| 	rs := repos.New( | ||||
| 		e.Cfg, | ||||
| 		e.Db, | ||||
| 	) | ||||
|  | ||||
| 	err = db.InsertPackage(db.Package{ | ||||
| 	err := e.Db.InsertPackage(e.Ctx, db.Package{ | ||||
| 		Name:       "test1", | ||||
| 		Repository: "default", | ||||
| 		Version:    "0.0.1", | ||||
| @@ -105,7 +104,7 @@ func TestFindPkgsEmpty(t *testing.T) { | ||||
| 		t.Fatalf("Expected no error, got %s", err) | ||||
| 	} | ||||
|  | ||||
| 	err = db.InsertPackage(db.Package{ | ||||
| 	err = e.Db.InsertPackage(e.Ctx, db.Package{ | ||||
| 		Name:       "test2", | ||||
| 		Repository: "default", | ||||
| 		Version:    "0.0.1", | ||||
| @@ -120,7 +119,7 @@ func TestFindPkgsEmpty(t *testing.T) { | ||||
| 		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 { | ||||
| 		t.Fatalf("Expected no error, got %s", err) | ||||
| 	} | ||||
|   | ||||
| @@ -1,61 +1,73 @@ | ||||
| /* | ||||
|  * 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/>. | ||||
|  */ | ||||
| // 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 repos | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"log/slog" | ||||
| 	"net/url" | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
| 	"reflect" | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/go-git/go-billy/v5" | ||||
| 	"github.com/go-git/go-billy/v5/osfs" | ||||
| 	"github.com/go-git/go-git/v5" | ||||
| 	"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" | ||||
| 	"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/interp" | ||||
| 	"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 | ||||
| // 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. | ||||
| // If repos is set to nil, the repos in the ALR config will be used. | ||||
| func Pull(ctx context.Context, repos []types.Repo) error { | ||||
| 	log := loggerctx.From(ctx) | ||||
|  | ||||
| func (rs *Repos) Pull(ctx context.Context, repos []types.Repo) error { | ||||
| 	if repos == nil { | ||||
| 		repos = config.Config(ctx).Repos | ||||
| 		repos = rs.cfg.Repos(ctx) | ||||
| 	} | ||||
|  | ||||
| 	for _, repo := range repos { | ||||
| @@ -64,8 +76,8 @@ func Pull(ctx context.Context, repos []types.Repo) error { | ||||
| 			return err | ||||
| 		} | ||||
|  | ||||
| 		log.Info("Pulling repository").Str("name", repo.Name).Send() | ||||
| 		repoDir := filepath.Join(config.GetPaths(ctx).RepoDir, repo.Name) | ||||
| 		slog.Info(gotext.Get("Pulling repository"), "name", repo.Name) | ||||
| 		repoDir := filepath.Join(rs.cfg.GetPaths(ctx).RepoDir, repo.Name) | ||||
|  | ||||
| 		var repoFS billy.Filesystem | ||||
| 		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}) | ||||
| 			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 { | ||||
| 				return err | ||||
| 			} | ||||
| 			repoFS = w.Filesystem | ||||
|  | ||||
| 			// 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() | ||||
| 				if err != nil { | ||||
| 					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 | ||||
| 				// empty. In this case, we need to update the DB fully | ||||
| 				// rather than just incrementally. | ||||
| 				if db.IsEmpty(ctx) { | ||||
| 					err = processRepoFull(ctx, repo, repoDir) | ||||
| 				if rs.db.IsEmpty(ctx) { | ||||
| 					err = rs.processRepoFull(ctx, repo, repoDir) | ||||
| 					if err != nil { | ||||
| 						return err | ||||
| 					} | ||||
| 				} else { | ||||
| 					err = processRepoChanges(ctx, repo, r, w, old, new) | ||||
| 					err = rs.processRepoChanges(ctx, repo, r, w, old, new) | ||||
| 					if err != nil { | ||||
| 						return err | ||||
| 					} | ||||
| @@ -135,7 +147,7 @@ func Pull(ctx context.Context, repos []types.Repo) error { | ||||
| 				return err | ||||
| 			} | ||||
|  | ||||
| 			err = processRepoFull(ctx, repo, repoDir) | ||||
| 			err = rs.processRepoFull(ctx, repo, repoDir) | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| @@ -145,7 +157,7 @@ func Pull(ctx context.Context, repos []types.Repo) error { | ||||
|  | ||||
| 		fl, err := repoFS.Open("alr-repo.toml") | ||||
| 		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 | ||||
| 		} | ||||
|  | ||||
| @@ -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". | ||||
| 		if strings.HasPrefix(config.Version, "v") { | ||||
| 			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 | ||||
| } | ||||
|  | ||||
| type actionType uint8 | ||||
| func (rs *Repos) updatePkg(ctx context.Context, repo types.Repo, runner *interp.Runner, scriptFl io.ReadCloser) error { | ||||
| 	parser := syntax.NewParser() | ||||
|  | ||||
| const ( | ||||
| 	actionDelete actionType = iota | ||||
| 	actionUpdate | ||||
| ) | ||||
| 	defer scriptFl.Close() | ||||
| 	fl, err := parser.Parse(scriptFl, "alr.sh") | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| type action struct { | ||||
| 	Type actionType | ||||
| 	File string | ||||
| 	runner.Reset() | ||||
| 	err = runner.Run(ctx, fl) | ||||
| 	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()) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| @@ -205,34 +295,33 @@ func processRepoChanges(ctx context.Context, repo types.Repo, r *git.Repository, | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		if to == nil { | ||||
| 		switch { | ||||
| 		case to == nil: | ||||
| 			actions = append(actions, action{ | ||||
| 				Type: actionDelete, | ||||
| 				File: from.Path(), | ||||
| 			}) | ||||
| 		} else if from == nil { | ||||
| 		case from == nil: | ||||
| 			actions = append(actions, action{ | ||||
| 				Type: actionUpdate, | ||||
| 				File: to.Path(), | ||||
| 			}) | ||||
| 		} else { | ||||
| 			if from.Path() != to.Path() { | ||||
| 				actions = append(actions, | ||||
| 					action{ | ||||
| 						Type: actionDelete, | ||||
| 						File: from.Path(), | ||||
| 					}, | ||||
| 					action{ | ||||
| 						Type: actionUpdate, | ||||
| 						File: to.Path(), | ||||
| 					}, | ||||
| 				) | ||||
| 			} else { | ||||
| 				actions = append(actions, action{ | ||||
| 		case from.Path() != to.Path(): | ||||
| 			actions = append(actions, | ||||
| 				action{ | ||||
| 					Type: actionDelete, | ||||
| 					File: from.Path(), | ||||
| 				}, | ||||
| 				action{ | ||||
| 					Type: actionUpdate, | ||||
| 					File: to.Path(), | ||||
| 				}) | ||||
| 			} | ||||
| 				}, | ||||
| 			) | ||||
| 		default: | ||||
| 			actions = append(actions, action{ | ||||
| 				Type: actionUpdate, | ||||
| 				File: to.Path(), | ||||
| 			}) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| @@ -240,15 +329,7 @@ func processRepoChanges(ctx context.Context, repo types.Repo, r *git.Repository, | ||||
| 	parser := syntax.NewParser() | ||||
|  | ||||
| 	for _, action := range actions { | ||||
| 		env := append(os.Environ(), "scriptdir="+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{}), | ||||
| 		) | ||||
| 		runner, err := rs.processRepoChangesRunner(repoDir, filepath.Dir(filepath.Join(repoDir, action.File))) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| @@ -275,7 +356,7 @@ func processRepoChanges(ctx context.Context, repo types.Repo, r *git.Repository, | ||||
| 				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 { | ||||
| 				return err | ||||
| 			} | ||||
| @@ -294,23 +375,7 @@ func processRepoChanges(ctx context.Context, repo types.Repo, r *git.Repository, | ||||
| 				return nil | ||||
| 			} | ||||
|  | ||||
| 			pkg := 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:   repo.Name, | ||||
| 			} | ||||
|  | ||||
| 			err = parseScript(ctx, parser, runner, r, &pkg) | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
|  | ||||
| 			resolveOverrides(runner, &pkg) | ||||
|  | ||||
| 			err = db.InsertPackage(ctx, pkg) | ||||
| 			err = rs.updatePkg(ctx, repo, runner, r) | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| @@ -320,41 +385,15 @@ func processRepoChanges(ctx context.Context, repo types.Repo, r *git.Repository, | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // 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 processRepoFull(ctx context.Context, repo types.Repo, repoDir string) error { | ||||
| func (rs *Repos) processRepoFull(ctx context.Context, repo types.Repo, repoDir string) error { | ||||
| 	glob := filepath.Join(repoDir, "/*/alr.sh") | ||||
| 	matches, err := filepath.Glob(glob) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	parser := syntax.NewParser() | ||||
|  | ||||
| 	for _, match := range matches { | ||||
| 		env := append(os.Environ(), "scriptdir="+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{}), | ||||
| 		) | ||||
| 		runner, err := rs.processRepoChangesRunner(repoDir, filepath.Dir(match)) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| @@ -364,23 +403,7 @@ func processRepoFull(ctx context.Context, repo types.Repo, repoDir string) error | ||||
| 			return err | ||||
| 		} | ||||
|  | ||||
| 		pkg := 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:   repo.Name, | ||||
| 		} | ||||
|  | ||||
| 		err = parseScript(ctx, parser, runner, scriptFl, &pkg) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
|  | ||||
| 		resolveOverrides(runner, &pkg) | ||||
|  | ||||
| 		err = db.InsertPackage(ctx, pkg) | ||||
| 		err = rs.updatePkg(ctx, repo, runner, scriptFl) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| @@ -388,54 +411,3 @@ func processRepoFull(ctx context.Context, repo types.Repo, repoDir string) error | ||||
|  | ||||
| 	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 | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|   | ||||
							
								
								
									
										173
									
								
								pkg/repos/pull_internal_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										173
									
								
								pkg/repos/pull_internal_test.go
									
									
									
									
									
										Normal 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) | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
| @@ -1,20 +1,21 @@ | ||||
| /* | ||||
|  * 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/>. | ||||
|  */ | ||||
| // 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 repos_test | ||||
|  | ||||
| @@ -24,71 +25,106 @@ import ( | ||||
| 	"path/filepath" | ||||
| 	"testing" | ||||
|  | ||||
| 	"plemya-x.ru/alr/internal/config" | ||||
| 	"plemya-x.ru/alr/internal/db" | ||||
| 	"plemya-x.ru/alr/internal/types" | ||||
| 	"plemya-x.ru/alr/pkg/repos" | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/config" | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/db" | ||||
| 	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" | ||||
| ) | ||||
|  | ||||
| func setCfgDirs(t *testing.T) { | ||||
| 	t.Helper() | ||||
|  | ||||
| 	paths := config.GetPaths() | ||||
|  | ||||
| 	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") | ||||
| type TestEnv struct { | ||||
| 	Ctx context.Context | ||||
| 	Cfg *TestALRConfig | ||||
| 	Db  *db.Database | ||||
| } | ||||
|  | ||||
| func removeCacheDir(t *testing.T) { | ||||
| 	t.Helper() | ||||
| type TestALRConfig struct { | ||||
| 	CacheDir string | ||||
| 	RepoDir  string | ||||
| 	PkgsDir  string | ||||
| } | ||||
|  | ||||
| 	err := os.RemoveAll(config.GetPaths().CacheDir) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Expected no error, got %s", err) | ||||
| func (c *TestALRConfig) GetPaths(ctx context.Context) *config.Paths { | ||||
| 	return &config.Paths{ | ||||
| 		DBPath:   ":memory:", | ||||
| 		CacheDir: c.CacheDir, | ||||
| 		RepoDir:  c.RepoDir, | ||||
| 		PkgsDir:  c.PkgsDir, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestPull(t *testing.T) { | ||||
| 	_, err := db.Open(":memory:") | ||||
| func (c *TestALRConfig) Repos(ctx context.Context) []types.Repo { | ||||
| 	return []types.Repo{} | ||||
| } | ||||
|  | ||||
| func prepare(t *testing.T) *TestEnv { | ||||
| 	t.Helper() | ||||
|  | ||||
| 	cacheDir, err := os.MkdirTemp("/tmp", "alr-pull-test.*") | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("Expected no error, got %s", err) | ||||
| 	} | ||||
| 	defer db.Close() | ||||
|  | ||||
| 	setCfgDirs(t) | ||||
| 	defer removeCacheDir(t) | ||||
| 	repoDir := filepath.Join(cacheDir, "repo") | ||||
| 	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() | ||||
|  | ||||
| 	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", | ||||
| 			URL:  "https://gitea.plemya-x.ru/xpamych/ALR.git", | ||||
| 			URL:  "https://gitea.plemya-x.ru/Plemya-x/xpamych-alr-repo.git", | ||||
| 		}, | ||||
| 	}) | ||||
| 	if err != nil { | ||||
| 		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 { | ||||
| 		t.Fatalf("Expected no error, got %s", err) | ||||
| 	} | ||||
| @@ -103,7 +139,7 @@ func TestPull(t *testing.T) { | ||||
| 		pkgAmt++ | ||||
| 	} | ||||
|  | ||||
| 	if pkgAmt < 2 { | ||||
| 		t.Errorf("Expected 2 packages to match, got %d", pkgAmt) | ||||
| 	if pkgAmt == 0 { | ||||
| 		t.Errorf("Expected at least 1 matching package, but got %d", pkgAmt) | ||||
| 	} | ||||
| } | ||||
|   | ||||
							
								
								
									
										45
									
								
								pkg/repos/repos.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								pkg/repos/repos.go
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										141
									
								
								pkg/repos/utils.go
									
									
									
									
									
										Normal 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 | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| @@ -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 | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"errors" | ||||
| 	"io" | ||||
| 	"io/fs" | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
|  | ||||
| 	"plemya-x.ru/alr/internal/config" | ||||
| 	"plemya-x.ru/alr/internal/db" | ||||
| 	"github.com/jmoiron/sqlx" | ||||
|  | ||||
| 	database "gitea.plemya-x.ru/Plemya-x/ALR/internal/db" | ||||
| ) | ||||
|  | ||||
| // Filter represents search filters. | ||||
| type Filter int | ||||
|  | ||||
| // 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 | ||||
| type PackagesProvider interface { | ||||
| 	GetPkgs(ctx context.Context, where string, args ...any) (*sqlx.Rows, error) | ||||
| } | ||||
|  | ||||
| func convertPkg(p db.Package) Package { | ||||
| 	return Package{ | ||||
| 		Name:          p.Name, | ||||
| 		Version:       p.Version, | ||||
| 		Release:       p.Release, | ||||
| 		Epoch:         p.Epoch, | ||||
| 		Description:   p.Description.Val, | ||||
| 		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, | ||||
| type Searcher struct { | ||||
| 	pp PackagesProvider | ||||
| } | ||||
|  | ||||
| func New(pp PackagesProvider) *Searcher { | ||||
| 	return &Searcher{ | ||||
| 		pp: pp, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Options contains the options for a search. | ||||
| type Options struct { | ||||
| 	Filter      Filter | ||||
| 	FilterValue string | ||||
| 	SortBy      SortBy | ||||
| 	Limit       int64 | ||||
| 	Query       string | ||||
| } | ||||
| func (s *Searcher) Search( | ||||
| 	ctx context.Context, | ||||
| 	opts *SearchOptions, | ||||
| ) ([]database.Package, error) { | ||||
| 	var packages []database.Package | ||||
|  | ||||
| // Search searches for packages in the database based on the given options. | ||||
| func Search(ctx context.Context, opts Options) ([]Package, error) { | ||||
| 	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...) | ||||
| 	where, args := opts.WhereClause() | ||||
| 	result, err := s.pp.GetPkgs(ctx, where, args...) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	var out []Package | ||||
| 	for result.Next() { | ||||
| 		pkg := db.Package{} | ||||
| 		err = result.StructScan(&pkg) | ||||
| 		var dbPkg database.Package | ||||
| 		err = result.StructScan(&dbPkg) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		out = append(out, convertPkg(pkg)) | ||||
| 		packages = append(packages, dbPkg) | ||||
| 	} | ||||
|  | ||||
| 	return out, err | ||||
| } | ||||
|  | ||||
| // 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 | ||||
| 	return packages, nil | ||||
| } | ||||
|   | ||||
							
								
								
									
										86
									
								
								pkg/search/search_options_builder.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								pkg/search/search_options_builder.go
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										65
									
								
								pkg/search/search_test.go
									
									
									
									
									
										Normal 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) | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										307
									
								
								repo.go
									
									
									
									
									
								
							
							
						
						
									
										307
									
								
								repo.go
									
									
									
									
									
								
							| @@ -1,162 +1,201 @@ | ||||
| /* | ||||
|  * 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/>. | ||||
|  */ | ||||
| // 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 | ||||
|  | ||||
| import ( | ||||
| 	"log/slog" | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
|  | ||||
| 	"github.com/leonelquinteros/gotext" | ||||
| 	"github.com/pelletier/go-toml/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" | ||||
|  | ||||
| 	"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{ | ||||
| 	Name:    "addrepo", | ||||
| 	Usage:   "Add a new repository", | ||||
| 	Aliases: []string{"ar"}, | ||||
| 	Flags: []cli.Flag{ | ||||
| 		&cli.StringFlag{ | ||||
| 			Name:     "name", | ||||
| 			Aliases:  []string{"n"}, | ||||
| 			Required: true, | ||||
| 			Usage:    "Name of the new repo", | ||||
| func AddRepoCmd() *cli.Command { | ||||
| 	return &cli.Command{ | ||||
| 		Name:    "addrepo", | ||||
| 		Usage:   gotext.Get("Add a new repository"), | ||||
| 		Aliases: []string{"ar"}, | ||||
| 		Flags: []cli.Flag{ | ||||
| 			&cli.StringFlag{ | ||||
| 				Name:     "name", | ||||
| 				Aliases:  []string{"n"}, | ||||
| 				Required: true, | ||||
| 				Usage:    gotext.Get("Name of the new repo"), | ||||
| 			}, | ||||
| 			&cli.StringFlag{ | ||||
| 				Name:     "url", | ||||
| 				Aliases:  []string{"u"}, | ||||
| 				Required: true, | ||||
| 				Usage:    gotext.Get("URL of the new repo"), | ||||
| 			}, | ||||
| 		}, | ||||
| 		&cli.StringFlag{ | ||||
| 			Name:     "url", | ||||
| 			Aliases:  []string{"u"}, | ||||
| 			Required: true, | ||||
| 			Usage:    "URL of the new repo", | ||||
| 		}, | ||||
| 	}, | ||||
| 	Action: func(c *cli.Context) error { | ||||
| 		ctx := c.Context | ||||
| 		log := loggerctx.From(ctx) | ||||
| 		Action: func(c *cli.Context) error { | ||||
| 			ctx := c.Context | ||||
|  | ||||
| 		name := c.String("name") | ||||
| 		repoURL := c.String("url") | ||||
| 			name := c.String("name") | ||||
| 			repoURL := c.String("url") | ||||
|  | ||||
| 		cfg := config.Config(ctx) | ||||
| 			cfg := config.New() | ||||
| 			reposSlice := cfg.Repos(ctx) | ||||
|  | ||||
| 		for _, repo := range cfg.Repos { | ||||
| 			if repo.URL == repoURL { | ||||
| 				log.Fatal("Repo already exists").Str("name", repo.Name).Send() | ||||
| 			for _, repo := range reposSlice { | ||||
| 				if repo.URL == repoURL { | ||||
| 					slog.Error("Repo already exists", "name", repo.Name) | ||||
| 					os.Exit(1) | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		cfg.Repos = append(cfg.Repos, types.Repo{ | ||||
| 			Name: name, | ||||
| 			URL:  repoURL, | ||||
| 		}) | ||||
| 			reposSlice = append(reposSlice, types.Repo{ | ||||
| 				Name: name, | ||||
| 				URL:  repoURL, | ||||
| 			}) | ||||
|  | ||||
| 		cfgFl, err := os.Create(config.GetPaths(ctx).ConfigPath) | ||||
| 		if err != nil { | ||||
| 			log.Fatal("Error opening config file").Err(err).Send() | ||||
| 		} | ||||
| 			cfg.SetRepos(ctx, reposSlice) | ||||
|  | ||||
| 		err = toml.NewEncoder(cfgFl).Encode(cfg) | ||||
| 		if err != nil { | ||||
| 			log.Fatal("Error encoding config").Err(err).Send() | ||||
| 		} | ||||
|  | ||||
| 		err = repos.Pull(ctx, cfg.Repos) | ||||
| 		if err != nil { | ||||
| 			log.Fatal("Error pulling repos").Err(err).Send() | ||||
| 		} | ||||
|  | ||||
| 		return nil | ||||
| 	}, | ||||
| } | ||||
|  | ||||
| var removerepoCmd = &cli.Command{ | ||||
| 	Name:    "removerepo", | ||||
| 	Usage:   "Remove an existing repository", | ||||
| 	Aliases: []string{"rr"}, | ||||
| 	Flags: []cli.Flag{ | ||||
| 		&cli.StringFlag{ | ||||
| 			Name:     "name", | ||||
| 			Aliases:  []string{"n"}, | ||||
| 			Required: true, | ||||
| 			Usage:    "Name of the repo to be deleted", | ||||
| 		}, | ||||
| 	}, | ||||
| 	Action: func(c *cli.Context) error { | ||||
| 		ctx := c.Context | ||||
| 		log := loggerctx.From(ctx) | ||||
|  | ||||
| 		name := c.String("name") | ||||
| 		cfg := config.Config(ctx) | ||||
|  | ||||
| 		found := false | ||||
| 		index := 0 | ||||
| 		for i, repo := range cfg.Repos { | ||||
| 			if repo.Name == name { | ||||
| 				index = i | ||||
| 				found = true | ||||
| 			cfgFl, err := os.Create(cfg.GetPaths(ctx).ConfigPath) | ||||
| 			if err != nil { | ||||
| 				slog.Error(gotext.Get("Error opening config file"), "err", err) | ||||
| 				os.Exit(1) | ||||
| 			} | ||||
| 		} | ||||
| 		if !found { | ||||
| 			log.Fatal("Repo does not exist").Str("name", name).Send() | ||||
| 		} | ||||
|  | ||||
| 		cfg.Repos = slices.Delete(cfg.Repos, index, index+1) | ||||
| 			err = cfg.Save(cfgFl) | ||||
| 			if err != nil { | ||||
| 				slog.Error(gotext.Get("Error encoding config"), "err", err) | ||||
| 				os.Exit(1) | ||||
| 			} | ||||
|  | ||||
| 		cfgFl, err := os.Create(config.GetPaths(ctx).ConfigPath) | ||||
| 		if err != nil { | ||||
| 			log.Fatal("Error opening config file").Err(err).Send() | ||||
| 		} | ||||
| 			db := database.New(cfg) | ||||
| 			err = db.Init(ctx) | ||||
| 			if err != nil { | ||||
| 				slog.Error(gotext.Get("Error pulling repos"), "err", err) | ||||
| 			} | ||||
|  | ||||
| 		err = toml.NewEncoder(cfgFl).Encode(&cfg) | ||||
| 		if err != nil { | ||||
| 			log.Fatal("Error encoding config").Err(err).Send() | ||||
| 		} | ||||
| 			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) | ||||
| 			} | ||||
|  | ||||
| 		err = os.RemoveAll(filepath.Join(config.GetPaths(ctx).RepoDir, name)) | ||||
| 		if err != nil { | ||||
| 			log.Fatal("Error removing repo directory").Err(err).Send() | ||||
| 		} | ||||
|  | ||||
| 		err = db.DeletePkgs(ctx, "repository = ?", name) | ||||
| 		if err != nil { | ||||
| 			log.Fatal("Error removing packages from database").Err(err).Send() | ||||
| 		} | ||||
|  | ||||
| 		return nil | ||||
| 	}, | ||||
| 			return nil | ||||
| 		}, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| var refreshCmd = &cli.Command{ | ||||
| 	Name:    "refresh", | ||||
| 	Usage:   "Pull all repositories that have changed", | ||||
| 	Aliases: []string{"ref"}, | ||||
| 	Action: func(c *cli.Context) error { | ||||
| 		ctx := c.Context | ||||
| 		log := loggerctx.From(ctx) | ||||
| 		err := repos.Pull(ctx, config.Config(ctx).Repos) | ||||
| 		if err != nil { | ||||
| 			log.Fatal("Error pulling repos").Err(err).Send() | ||||
| 		} | ||||
| 		return nil | ||||
| 	}, | ||||
| func RemoveRepoCmd() *cli.Command { | ||||
| 	return &cli.Command{ | ||||
| 		Name:    "removerepo", | ||||
| 		Usage:   gotext.Get("Remove an existing repository"), | ||||
| 		Aliases: []string{"rr"}, | ||||
| 		Flags: []cli.Flag{ | ||||
| 			&cli.StringFlag{ | ||||
| 				Name:     "name", | ||||
| 				Aliases:  []string{"n"}, | ||||
| 				Required: true, | ||||
| 				Usage:    gotext.Get("Name of the repo to be deleted"), | ||||
| 			}, | ||||
| 		}, | ||||
| 		Action: func(c *cli.Context) error { | ||||
| 			ctx := c.Context | ||||
|  | ||||
| 			name := c.String("name") | ||||
| 			cfg := config.New() | ||||
|  | ||||
| 			found := false | ||||
| 			index := 0 | ||||
| 			reposSlice := cfg.Repos(ctx) | ||||
| 			for i, repo := range reposSlice { | ||||
| 				if repo.Name == name { | ||||
| 					index = i | ||||
| 					found = true | ||||
| 				} | ||||
| 			} | ||||
| 			if !found { | ||||
| 				slog.Error(gotext.Get("Repo does not exist"), "name", name) | ||||
| 				os.Exit(1) | ||||
| 			} | ||||
|  | ||||
| 			cfg.SetRepos(ctx, slices.Delete(reposSlice, index, index+1)) | ||||
|  | ||||
| 			cfgFl, err := os.Create(cfg.GetPaths(ctx).ConfigPath) | ||||
| 			if err != nil { | ||||
| 				slog.Error(gotext.Get("Error opening config file"), "err", err) | ||||
| 				os.Exit(1) | ||||
| 			} | ||||
|  | ||||
| 			err = toml.NewEncoder(cfgFl).Encode(&cfg) | ||||
| 			if err != nil { | ||||
| 				slog.Error(gotext.Get("Error encoding config"), "err", err) | ||||
| 				os.Exit(1) | ||||
| 			} | ||||
|  | ||||
| 			err = os.RemoveAll(filepath.Join(cfg.GetPaths(ctx).RepoDir, name)) | ||||
| 			if err != nil { | ||||
| 				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) | ||||
| 			if err != nil { | ||||
| 				slog.Error(gotext.Get("Error removing packages from database"), "err", err) | ||||
| 				os.Exit(1) | ||||
| 			} | ||||
|  | ||||
| 			return nil | ||||
| 		}, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func RefreshCmd() *cli.Command { | ||||
| 	return &cli.Command{ | ||||
| 		Name:    "refresh", | ||||
| 		Usage:   gotext.Get("Pull all repositories that have changed"), | ||||
| 		Aliases: []string{"ref"}, | ||||
| 		Action: func(c *cli.Context) error { | ||||
| 			ctx := c.Context | ||||
| 			cfg := config.New() | ||||
| 			db := database.New(cfg) | ||||
| 			err := db.Init(ctx) | ||||
| 			if err != nil { | ||||
| 				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 | ||||
| 		}, | ||||
| 	} | ||||
| } | ||||
|   | ||||
							
								
								
									
										46
									
								
								scripts/coverage-badge.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										46
									
								
								scripts/coverage-badge.sh
									
									
									
									
									
										Executable 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
									
								
							
							
						
						
									
										84
									
								
								scripts/i18n-badge.sh
									
									
									
									
									
										Executable 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 | ||||
| @@ -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() { | ||||
|   echo $'\x1b[32m[ИНФО]\x1b[0m' $@ | ||||
| } | ||||
| @@ -59,6 +78,10 @@ elif command -v apk &>/dev/null; then | ||||
|   info "Обнаружен apk" | ||||
|   pkgFormat="apk" | ||||
|   pkgMgr="apk" | ||||
| elif command -v apt-get &>/dev/null; then | ||||
|   info "Обнаружен apt-get" | ||||
|   pkgFormat="rpm" | ||||
|   pkgMgr="apt-get" | ||||
| else | ||||
|   warn "Не обнаружен поддерживаемый менеджер пакетов!" | ||||
|   noPkgMgr=true | ||||
| @@ -79,7 +102,10 @@ if [ -z "$noPkgMgr" ]; then | ||||
|   elif [ "$pkgMgr" == "apt" ]; then | ||||
|     latestFile=$(echo "$fileList" | grep -E 'alr-bin-.*.amd64.deb' | sort -V | tail -n 1) | ||||
|   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 | ||||
|     error "Не поддерживаемый менеджер пакетов для автоматической установки" | ||||
|   fi | ||||
|   | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user