Compare commits
	
		
			56 Commits
		
	
	
		
			v0.0.14
			...
			c36d46cf96
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| c36d46cf96 | |||
| 6355f25089 | |||
| a83561b6a5 | |||
| 4b06809a39 | |||
| 401c41160c | |||
| 5e1eeabd04 | |||
| db19133254 | |||
| e8202060d8 | |||
| c4a92c67d4 | |||
| 85878f69d3 | |||
| 6bccce1db4 | |||
| b5474b1eb4 | |||
| 51fdea781b | |||
| 4c1f2ea90f | |||
| 7fa7f8ba82 | |||
| 25d001c1c9 | |||
| f86b3003b1 | |||
| bd79dcf401 | |||
| d1fe02fa57 | |||
| 1ca7801fba | |||
| 661d79ce24 | |||
| bece64c132 | |||
| 6d29b98cf7 | |||
| d286041864 | |||
| 392a522723 | |||
| e259184a89 | |||
| 65ab4de561 | |||
| 1cdab8dfed | |||
| 237e2c338d | |||
| 703ab8e8c4 | |||
| 06fcab4ce7 | |||
| 7741c7368b | |||
| 69f4af0a4d | |||
| bcf627f176 | |||
| 6ec95e4bd9 | |||
| 578da7ff52 | |||
| c51caf5c52 | |||
| 09dba577c6 | |||
| ca82bf3024 | |||
| c0023db6cd | |||
| 152e5077ec | |||
| 15ba8700e8 | |||
| a8aefc0524 | |||
| 9540030579 | |||
| 4f9d4260b8 | |||
| 38b5e6f581 | |||
| 408bd12302 | |||
| fb83d544de | |||
| 2cb963d4b2 | |||
| e74d74cdf6 | |||
| 5b3d53d253 | |||
| 36e704f735 | |||
| 07356d5e55 | |||
| 52bd6aca93 | |||
| 2f1770b43b | |||
| 9d5b5b51ff | 
| @@ -16,11 +16,12 @@ | |||||||
|  |  | ||||||
| name: E2E | name: E2E | ||||||
|  |  | ||||||
|  | # on: | ||||||
|  | #   push: | ||||||
|  | #     branches: [ main ] | ||||||
|  | #   pull_request: | ||||||
| on: | on: | ||||||
|   push: |   workflow_dispatch: | ||||||
|     branches: [ main ] |  | ||||||
|   pull_request: |  | ||||||
|  |  | ||||||
|  |  | ||||||
| jobs: | jobs: | ||||||
|   tests: |   tests: | ||||||
| @@ -39,19 +40,18 @@ jobs: | |||||||
|         uses: https://github.com/actions/setup-go@v5 |         uses: https://github.com/actions/setup-go@v5 | ||||||
|         with: |         with: | ||||||
|           go-version: '1.24' |           go-version: '1.24' | ||||||
|  |           cache: false | ||||||
|  |  | ||||||
|       - name: Cache Podman images |       # - name: Cache Podman images | ||||||
|         uses: actions/cache@v4 |       #   uses: actions/cache@v4 | ||||||
|         with: |       #   with: | ||||||
|           path: | |       #     path: | | ||||||
|             ~/.local/share/containers/storage |       #       ~/.local/share/containers/storage | ||||||
|             /var/lib/containers/storage |       #       /var/lib/containers/storage | ||||||
|           key: ${{ runner.os }}-primes             |       #     key: ${{ runner.os }}-primes             | ||||||
|  |  | ||||||
|       - name: Run E2E tests |       - name: Run E2E tests | ||||||
|         env: |         env: | ||||||
|           DOCKER_HOST: unix:///tmp/podman.sock |  | ||||||
|           IGNORE_ROOT_CHECK: 1 |           IGNORE_ROOT_CHECK: 1 | ||||||
|         run: | |         run: | | ||||||
|           podman system service -t 0 unix:///tmp/podman.sock & |  | ||||||
|           make e2e-test |           make e2e-test | ||||||
|   | |||||||
| @@ -26,6 +26,8 @@ on: | |||||||
| jobs: | jobs: | ||||||
|   pre-commit: |   pre-commit: | ||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
|  |     container: | ||||||
|  |       image: docker.gitea.com/runner-images:ubuntu-latest | ||||||
|  |  | ||||||
|     steps: |     steps: | ||||||
|       - name: Checkout |       - name: Checkout | ||||||
|   | |||||||
| @@ -45,9 +45,15 @@ jobs: | |||||||
|           echo "Version - $version" |           echo "Version - $version" | ||||||
|           echo "VERSION=$version" >> $GITHUB_ENV |           echo "VERSION=$version" >> $GITHUB_ENV | ||||||
|  |  | ||||||
|       - name: Build alr binary |       - name: Prepare for install | ||||||
|         run: | |         run: | | ||||||
|           CGO_ENABLED=0 go build -ldflags "-X gitea.plemya-x.ru/Plemya-x/ALR/internal/config.Version=${{ env.VERSION }}" -o alr |           apt-get update && apt-get install -y libcap2-bin bindfs | ||||||
|  |  | ||||||
|  |       - name: Build alr | ||||||
|  |         env: | ||||||
|  |           IGNORE_ROOT_CHECK: 1 | ||||||
|  |         run: | | ||||||
|  |           make build | ||||||
|  |  | ||||||
|       - name: Create tar.gz |       - name: Create tar.gz | ||||||
|         run: | |         run: | | ||||||
| @@ -64,4 +70,51 @@ jobs: | |||||||
|           body: ${{ steps.changes.outputs.changes }} |           body: ${{ steps.changes.outputs.changes }} | ||||||
|           files: |- |           files: |- | ||||||
|             alr-${{ env.VERSION }}-linux-x86_64.tar.gz |             alr-${{ env.VERSION }}-linux-x86_64.tar.gz | ||||||
|              |  | ||||||
|  |       - name: Checkout alr-default repository | ||||||
|  |         uses: actions/checkout@v4 | ||||||
|  |         with: | ||||||
|  |           repository: Plemya-x/alr-default | ||||||
|  |           token: ${{ secrets.GITEAPUBLIC }} | ||||||
|  |           path: alr-default | ||||||
|  |  | ||||||
|  |       - name: Update version in alr-bin | ||||||
|  |         run: | | ||||||
|  |           # Замените значения в файле с конфигурацией | ||||||
|  |           sed -i "s/version='[0-9]\+\.[0-9]\+\.[0-9]\+'/version='${{ env.VERSION }}'/g" alr-default/alr-bin/alr.sh | ||||||
|  |           sed -i "s/release='[0-9]\+'/release='1'/g" alr-default/alr-bin/alr.sh | ||||||
|  |  | ||||||
|  | #      - name: Install alr | ||||||
|  | #        run: | | ||||||
|  | #          make install | ||||||
|  | # | ||||||
|  | #          # temporary fix | ||||||
|  | #          groupadd wheel | ||||||
|  | #          usermod -aG wheel root | ||||||
|  |  | ||||||
|  | #      - name: Build packages | ||||||
|  | #        run: | | ||||||
|  | #          SCRIPT_PATH=alr-default/alr-bin/alr.sh | ||||||
|  | #          ALR_DISTRO=altlinux ALR_PKG_FORMAT=rpm alr build -s "$SCRIPT_PATH" | ||||||
|  | #          ALR_PKG_FORMAT=rpm alr build -s "$SCRIPT_PATH" | ||||||
|  | #          ALR_PKG_FORMAT=deb alr build -s "$SCRIPT_PATH" | ||||||
|  | #          ALR_PKG_FORMAT=archlinux alr build -s "$SCRIPT_PATH" | ||||||
|  |  | ||||||
|  | #      - name: Upload assets | ||||||
|  | #        uses: akkuman/gitea-release-action@v1 | ||||||
|  | #        with: | ||||||
|  | #          body: ${{ steps.changes.outputs.changes }} | ||||||
|  | #          files: |- | ||||||
|  | #            alr-bin+alr-default_${{ env.VERSION }}-1.red80_amd64.deb \ | ||||||
|  | #            alr-bin+alr-default-${{ env.VERSION }}-1-x86_64.pkg.tar.zst \ | ||||||
|  | #            alr-bin+alr-default-${{ env.VERSION }}-1.red80.x86_64.rpm \ | ||||||
|  | #            alr-bin+alr-default-${{ env.VERSION }}-alt1.x86_64.rpm | ||||||
|  |  | ||||||
|  |       - name: Commit changes | ||||||
|  |         run: | | ||||||
|  |           cd alr-default | ||||||
|  |           git config user.name "gitea" | ||||||
|  |           git config user.email "admin@plemya-x.ru" | ||||||
|  |           git add . | ||||||
|  |           git commit -m "Обновление версии до ${{ env.VERSION }}" | ||||||
|  |           git push | ||||||
|   | |||||||
							
								
								
									
										69
									
								
								.gitea/workflows/update-alr-git.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								.gitea/workflows/update-alr-git.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,69 @@ | |||||||
|  | # ALR - Any Linux Repository | ||||||
|  | # Copyright (C) 2025 The ALR Authors | ||||||
|  | # | ||||||
|  | # 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: Update alr-git | ||||||
|  |  | ||||||
|  | on: | ||||||
|  |   push: | ||||||
|  |     branches: | ||||||
|  |       - master | ||||||
|  |  | ||||||
|  | jobs: | ||||||
|  |   changelog: | ||||||
|  |     runs-on: ubuntu-latest | ||||||
|  |     steps: | ||||||
|  |       - name: Install the latest version of uv | ||||||
|  |         uses: astral-sh/setup-uv@v6 | ||||||
|  |  | ||||||
|  |       - name: Setup alr-spec | ||||||
|  |         run: | | ||||||
|  |           uv tool install alr-spec==0.0.5 | ||||||
|  |        | ||||||
|  |       - name: Install alr | ||||||
|  |         run: | | ||||||
|  |           apt-get update && apt-get install -y libcap2-bin | ||||||
|  |           curl -fsS https://gitea.plemya-x.ru/Plemya-x/ALR/raw/branch/master/scripts/install.sh | bash           | ||||||
|  |  | ||||||
|  |       - name: Checkout this repository | ||||||
|  |         uses: actions/checkout@v4 | ||||||
|  |         with: | ||||||
|  |           fetch-depth: 0 | ||||||
|  |  | ||||||
|  |       - name: Set ALR version | ||||||
|  |         run: | | ||||||
|  |           echo "NEW_ALR_VERSION=$(alr helper git-version)" >> $GITHUB_ENV | ||||||
|  |  | ||||||
|  |       - name: Checkout alr-default repository | ||||||
|  |         uses: actions/checkout@v4 | ||||||
|  |         with: | ||||||
|  |           repository: Plemya-x/alr-default | ||||||
|  |           token: ${{ secrets.GITEAPUBLIC }} | ||||||
|  |           path: alr-default | ||||||
|  |  | ||||||
|  |       - name: Update version | ||||||
|  |         working-directory: ./alr-default/alr-git | ||||||
|  |         run: | | ||||||
|  |           alr-spec set-field version $NEW_ALR_VERSION | ||||||
|  |           alr-spec set-field release 1 | ||||||
|  |  | ||||||
|  |       - name: Commit changes | ||||||
|  |         run: | | ||||||
|  |           cd alr-default | ||||||
|  |           git config user.name "gitea" | ||||||
|  |           git config user.email "admin@plemya-x.ru" | ||||||
|  |           git add . | ||||||
|  |           git commit -m "Обновление версии до $NEW_ALR_VERSION" | ||||||
|  |           git push | ||||||
							
								
								
									
										9
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										9
									
								
								Makefile
									
									
									
									
									
								
							| @@ -21,10 +21,11 @@ build: check-no-root $(BIN) | |||||||
|  |  | ||||||
| export CGO_ENABLED := 0 | export CGO_ENABLED := 0 | ||||||
| $(BIN): | $(BIN): | ||||||
|  | 	go generate ./... | ||||||
| 	go build -ldflags="-X 'gitea.plemya-x.ru/Plemya-x/ALR/internal/config.Version=$(GIT_VERSION)'" -o $@ | 	go build -ldflags="-X 'gitea.plemya-x.ru/Plemya-x/ALR/internal/config.Version=$(GIT_VERSION)'" -o $@ | ||||||
|  |  | ||||||
| check-no-root: | check-no-root: | ||||||
| 	@if [[ "$(IGNORE_ROOT_CHECK)" != "1" ]] && [[ "$$(whoami)" == 'root' ]]; then \ | 	@if [ "$$IGNORE_ROOT_CHECK" != "1" ] && [ "`whoami`" = "root" ]; then \ | ||||||
| 		echo "This target shouldn't run as root" 1>&2; \ | 		echo "This target shouldn't run as root" 1>&2; \ | ||||||
| 		echo "Set IGNORE_ROOT_CHECK=1 to override" 1>&2; \ | 		echo "Set IGNORE_ROOT_CHECK=1 to override" 1>&2; \ | ||||||
| 		exit 1; \ | 		exit 1; \ | ||||||
| @@ -39,6 +40,12 @@ install: \ | |||||||
| $(INSTALED_BIN): $(BIN) | $(INSTALED_BIN): $(BIN) | ||||||
| 	install -Dm755 $< $@ | 	install -Dm755 $< $@ | ||||||
| 	setcap cap_setuid,cap_setgid+ep $(INSTALED_BIN) | 	setcap cap_setuid,cap_setgid+ep $(INSTALED_BIN) | ||||||
|  | 	@if id alr >/dev/null 2>&1; then \ | ||||||
|  | 		echo "User 'alr' already exists. Skipping."; \ | ||||||
|  | 	else \ | ||||||
|  | 		useradd -r -s /usr/sbin/nologin alr; \ | ||||||
|  | 	fi | ||||||
|  | 	install -d -o alr -g alr -m 755 /var/cache/alr /etc/alr | ||||||
|  |  | ||||||
| $(INSTALLED_BASH_COMPLETION): $(BASH_COMPLETION) | $(INSTALLED_BASH_COMPLETION): $(BASH_COMPLETION) | ||||||
| 	install -Dm755 $< $@ | 	install -Dm755 $< $@ | ||||||
|   | |||||||
							
								
								
									
										16
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										16
									
								
								README.md
									
									
									
									
									
								
							| @@ -20,10 +20,10 @@ ALR написан на чистом Go и после сборки не имее | |||||||
| Установочный скрипт автоматически загрузит и установит соответствующий пакет ALR в вашей системе. Чтобы использовать его, просто выполните следующую команду: | Установочный скрипт автоматически загрузит и установит соответствующий пакет ALR в вашей системе. Чтобы использовать его, просто выполните следующую команду: | ||||||
|  |  | ||||||
| ```bash | ```bash | ||||||
| curl -fsSL plemya-x.ru/alr/install.sh | bash | curl -fsSL https://gitea.plemya-x.ru/Plemya-x/ALR/raw/branch/master/scripts/install.sh | bash | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| **ВАЖНО**: При этом скрипт будет загружен и запущен с <https://plemya-x.ru/alr/install.sh>. Пожалуйста, просматривайте любые скрипты, которые вы скачиваете из Интернета (включая этот), прежде чем запускать их. | **ВАЖНО**: При этом скрипт будет загружен и запущен [скрипт](https://gitea.plemya-x.ru/Plemya-x/ALR/src/branch/master/scripts/install.sh). Пожалуйста, просматривайте любые скрипты, которые вы скачиваете из Интернета (включая этот), прежде чем запускать их. | ||||||
|  |  | ||||||
| ### Сборка из исходного кода | ### Сборка из исходного кода | ||||||
|  |  | ||||||
| @@ -52,9 +52,17 @@ ALR был создан потому, что упаковка программн | |||||||
|  |  | ||||||
| Репозитории alr - это git-хранилища, которые содержат каталог для каждого пакета с файлом `alr.sh` внутри. Файл `alr.sh` содержит все инструкции по сборке пакета и информацию о нем. Скрипты `alr.sh` аналогичны скриптам Aur PKGBUILD.  | Репозитории 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 [Plemya-x/alr-default](https://gitea.plemya-x.ru/Plemya-x/alr-default.git) | ||||||
| ``` | ``` | ||||||
| alr addrepo --name alr-repo --url https://gitea.plemya-x.ru/Plemya-x/alr-repo.git | alr repo add alr-default https://gitea.plemya-x.ru/Plemya-x/alr-default.git | ||||||
|  | ``` | ||||||
|  | Репозиторий пакетов [Plemya-x/alr-repo](https://gitea.plemya-x.ru/Plemya-x/alr-repo.git) можно подключить так: | ||||||
|  | ``` | ||||||
|  | alr repo add  alr-repo https://gitea.plemya-x.ru/Plemya-x/alr-repo.git | ||||||
|  | ``` | ||||||
|  | Репозиторий Linux-Gaming [Plemya-x/alr-LG](https://gitea.plemya-x.ru/Plemya-x/alr-LG.git) можно подключить так: | ||||||
|  | ``` | ||||||
|  | alr repo add alr-LG https://gitea.plemya-x.ru/Plemya-x/alr-LG.git | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| --- | --- | ||||||
|   | |||||||
| @@ -11,7 +11,7 @@ | |||||||
|     <g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11"> |     <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="15" fill="#010101" fill-opacity=".3">coverage</text> | ||||||
|         <text x="33.5" y="14">coverage</text> |         <text x="33.5" y="14">coverage</text> | ||||||
|         <text x="86" y="15" fill="#010101" fill-opacity=".3">16.9%</text> |         <text x="86" y="15" fill="#010101" fill-opacity=".3">19.3%</text> | ||||||
|         <text x="86" y="14">16.9%</text> |         <text x="86" y="14">19.3%</text> | ||||||
|     </g> |     </g> | ||||||
| </svg> | </svg> | ||||||
|   | |||||||
| Before Width: | Height: | Size: 926 B After Width: | Height: | Size: 926 B | 
							
								
								
									
										12
									
								
								build.go
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								build.go
									
									
									
									
									
								
							| @@ -28,12 +28,12 @@ import ( | |||||||
| 	"github.com/leonelquinteros/gotext" | 	"github.com/leonelquinteros/gotext" | ||||||
| 	"github.com/urfave/cli/v2" | 	"github.com/urfave/cli/v2" | ||||||
|  |  | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/build" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils" | ||||||
| 	appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder" | 	appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/osutils" | 	"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/internal/utils" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/build" | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func BuildCmd() *cli.Command { | func BuildCmd() *cli.Command { | ||||||
| @@ -97,7 +97,7 @@ func BuildCmd() *cli.Command { | |||||||
| 			var script string | 			var script string | ||||||
| 			var packages []string | 			var packages []string | ||||||
|  |  | ||||||
| 			var res *build.BuildResult | 			var res []*build.BuiltDep | ||||||
|  |  | ||||||
| 			var scriptArgs *build.BuildPackageFromScriptArgs | 			var scriptArgs *build.BuildPackageFromScriptArgs | ||||||
| 			var dbArgs *build.BuildPackageFromDbArgs | 			var dbArgs *build.BuildPackageFromDbArgs | ||||||
| @@ -222,9 +222,9 @@ func BuildCmd() *cli.Command { | |||||||
| 				return cliutils.FormatCliExit(gotext.Get("Error building package"), err) | 				return cliutils.FormatCliExit(gotext.Get("Error building package"), err) | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			for _, pkgPath := range res.PackagePaths { | 			for _, pkg := range res { | ||||||
| 				name := filepath.Base(pkgPath) | 				name := filepath.Base(pkg.Path) | ||||||
| 				err = osutils.Move(pkgPath, filepath.Join(wd, name)) | 				err = osutils.Move(pkg.Path, filepath.Join(wd, name)) | ||||||
| 				if err != nil { | 				if err != nil { | ||||||
| 					return cliutils.FormatCliExit(gotext.Get("Error moving the package"), err) | 					return cliutils.FormatCliExit(gotext.Get("Error moving the package"), err) | ||||||
| 				} | 				} | ||||||
|   | |||||||
							
								
								
									
										227
									
								
								config.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										227
									
								
								config.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,227 @@ | |||||||
|  | // ALR - Any Linux Repository | ||||||
|  | // Copyright (C) 2025 The ALR Authors | ||||||
|  | // | ||||||
|  | // 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" | ||||||
|  | 	"strconv" | ||||||
|  | 	"strings" | ||||||
|  |  | ||||||
|  | 	"github.com/goccy/go-yaml" | ||||||
|  | 	"github.com/leonelquinteros/gotext" | ||||||
|  | 	"github.com/urfave/cli/v2" | ||||||
|  |  | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils" | ||||||
|  | 	appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder" | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func ConfigCmd() *cli.Command { | ||||||
|  | 	return &cli.Command{ | ||||||
|  | 		Name:  "config", | ||||||
|  | 		Usage: gotext.Get("Manage config"), | ||||||
|  | 		Subcommands: []*cli.Command{ | ||||||
|  | 			ShowCmd(), | ||||||
|  | 			SetConfig(), | ||||||
|  | 			GetConfig(), | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func ShowCmd() *cli.Command { | ||||||
|  | 	return &cli.Command{ | ||||||
|  | 		Name:  "show", | ||||||
|  | 		Usage: gotext.Get("Show config"), | ||||||
|  | 		BashComplete: cliutils.BashCompleteWithError(func(c *cli.Context) error { | ||||||
|  | 			return nil | ||||||
|  | 		}), | ||||||
|  | 		Action: func(c *cli.Context) error { | ||||||
|  | 			deps, err := appbuilder. | ||||||
|  | 				New(c.Context). | ||||||
|  | 				WithConfig(). | ||||||
|  | 				Build() | ||||||
|  | 			if err != nil { | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  | 			defer deps.Defer() | ||||||
|  |  | ||||||
|  | 			content, err := deps.Cfg.ToYAML() | ||||||
|  | 			if err != nil { | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  | 			fmt.Println(content) | ||||||
|  | 			return nil | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | var configKeys = []string{ | ||||||
|  | 	"rootCmd", | ||||||
|  | 	"useRootCmd", | ||||||
|  | 	"pagerStyle", | ||||||
|  | 	"autoPull", | ||||||
|  | 	"logLevel", | ||||||
|  | 	"ignorePkgUpdates", | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func SetConfig() *cli.Command { | ||||||
|  | 	return &cli.Command{ | ||||||
|  | 		Name:      "set", | ||||||
|  | 		Usage:     gotext.Get("Set config value"), | ||||||
|  | 		ArgsUsage: gotext.Get("<key> <value>"), | ||||||
|  | 		BashComplete: cliutils.BashCompleteWithError(func(c *cli.Context) error { | ||||||
|  | 			if c.Args().Len() == 0 { | ||||||
|  | 				for _, key := range configKeys { | ||||||
|  | 					fmt.Println(key) | ||||||
|  | 				} | ||||||
|  | 				return nil | ||||||
|  | 			} | ||||||
|  | 			return nil | ||||||
|  | 		}), | ||||||
|  | 		Action: utils.RootNeededAction(func(c *cli.Context) error { | ||||||
|  | 			if c.Args().Len() < 2 { | ||||||
|  | 				return cliutils.FormatCliExit("missing args", nil) | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			key := c.Args().Get(0) | ||||||
|  | 			value := c.Args().Get(1) | ||||||
|  |  | ||||||
|  | 			deps, err := appbuilder. | ||||||
|  | 				New(c.Context). | ||||||
|  | 				WithConfig(). | ||||||
|  | 				Build() | ||||||
|  | 			if err != nil { | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  | 			defer deps.Defer() | ||||||
|  |  | ||||||
|  | 			switch key { | ||||||
|  | 			case "rootCmd": | ||||||
|  | 				deps.Cfg.System.SetRootCmd(value) | ||||||
|  | 			case "useRootCmd": | ||||||
|  | 				boolValue, err := strconv.ParseBool(value) | ||||||
|  | 				if err != nil { | ||||||
|  | 					return cliutils.FormatCliExit(gotext.Get("invalid boolean value for %s: %s", key, value), err) | ||||||
|  | 				} | ||||||
|  | 				deps.Cfg.System.SetUseRootCmd(boolValue) | ||||||
|  | 			case "pagerStyle": | ||||||
|  | 				deps.Cfg.System.SetPagerStyle(value) | ||||||
|  | 			case "autoPull": | ||||||
|  | 				boolValue, err := strconv.ParseBool(value) | ||||||
|  | 				if err != nil { | ||||||
|  | 					return cliutils.FormatCliExit(gotext.Get("invalid boolean value for %s: %s", key, value), err) | ||||||
|  | 				} | ||||||
|  | 				deps.Cfg.System.SetAutoPull(boolValue) | ||||||
|  | 			case "logLevel": | ||||||
|  | 				deps.Cfg.System.SetLogLevel(value) | ||||||
|  | 			case "ignorePkgUpdates": | ||||||
|  | 				var updates []string | ||||||
|  | 				if value != "" { | ||||||
|  | 					updates = strings.Split(value, ",") | ||||||
|  | 					for i, update := range updates { | ||||||
|  | 						updates[i] = strings.TrimSpace(update) | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 				deps.Cfg.System.SetIgnorePkgUpdates(updates) | ||||||
|  | 			case "repo", "repos": | ||||||
|  | 				return cliutils.FormatCliExit(gotext.Get("use 'repo add/remove' commands to manage repositories"), nil) | ||||||
|  | 			default: | ||||||
|  | 				return cliutils.FormatCliExit(gotext.Get("unknown config key: %s", key), nil) | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			if err := deps.Cfg.System.Save(); err != nil { | ||||||
|  | 				return cliutils.FormatCliExit(gotext.Get("failed to save config"), err) | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			fmt.Println(gotext.Get("Successfully set %s = %s", key, value)) | ||||||
|  | 			return nil | ||||||
|  | 		}), | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func GetConfig() *cli.Command { | ||||||
|  | 	return &cli.Command{ | ||||||
|  | 		Name:      "get", | ||||||
|  | 		Usage:     gotext.Get("Get config value"), | ||||||
|  | 		ArgsUsage: gotext.Get("<key>"), | ||||||
|  | 		BashComplete: cliutils.BashCompleteWithError(func(c *cli.Context) error { | ||||||
|  | 			if c.Args().Len() == 0 { | ||||||
|  | 				for _, key := range configKeys { | ||||||
|  | 					fmt.Println(key) | ||||||
|  | 				} | ||||||
|  | 				return nil | ||||||
|  | 			} | ||||||
|  | 			return nil | ||||||
|  | 		}), | ||||||
|  | 		Action: func(c *cli.Context) error { | ||||||
|  | 			deps, err := appbuilder. | ||||||
|  | 				New(c.Context). | ||||||
|  | 				WithConfig(). | ||||||
|  | 				Build() | ||||||
|  | 			if err != nil { | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  | 			defer deps.Defer() | ||||||
|  |  | ||||||
|  | 			if c.Args().Len() == 0 { | ||||||
|  | 				content, err := deps.Cfg.ToYAML() | ||||||
|  | 				if err != nil { | ||||||
|  | 					return cliutils.FormatCliExit("failed to serialize config", err) | ||||||
|  | 				} | ||||||
|  | 				fmt.Print(content) | ||||||
|  | 				return nil | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			key := c.Args().Get(0) | ||||||
|  |  | ||||||
|  | 			switch key { | ||||||
|  | 			case "rootCmd": | ||||||
|  | 				fmt.Println(deps.Cfg.RootCmd()) | ||||||
|  | 			case "useRootCmd": | ||||||
|  | 				fmt.Println(deps.Cfg.UseRootCmd()) | ||||||
|  | 			case "pagerStyle": | ||||||
|  | 				fmt.Println(deps.Cfg.PagerStyle()) | ||||||
|  | 			case "autoPull": | ||||||
|  | 				fmt.Println(deps.Cfg.AutoPull()) | ||||||
|  | 			case "logLevel": | ||||||
|  | 				fmt.Println(deps.Cfg.LogLevel()) | ||||||
|  | 			case "ignorePkgUpdates": | ||||||
|  | 				updates := deps.Cfg.IgnorePkgUpdates() | ||||||
|  | 				if len(updates) == 0 { | ||||||
|  | 					fmt.Println("[]") | ||||||
|  | 				} else { | ||||||
|  | 					fmt.Println(strings.Join(updates, ", ")) | ||||||
|  | 				} | ||||||
|  | 			case "repo", "repos": | ||||||
|  | 				repos := deps.Cfg.Repos() | ||||||
|  | 				if len(repos) == 0 { | ||||||
|  | 					fmt.Println("[]") | ||||||
|  | 				} else { | ||||||
|  | 					repoData, err := yaml.Marshal(repos) | ||||||
|  | 					if err != nil { | ||||||
|  | 						return cliutils.FormatCliExit("failed to serialize repos", err) | ||||||
|  | 					} | ||||||
|  | 					fmt.Print(string(repoData)) | ||||||
|  | 				} | ||||||
|  | 			default: | ||||||
|  | 				return cliutils.FormatCliExit(gotext.Get("unknown config key: %s", key), nil) | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			return nil | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @@ -66,7 +66,7 @@ func TestE2EAlrAddRepo(t *testing.T) { | |||||||
| 				"cat /etc/alr/alr.toml", | 				"cat /etc/alr/alr.toml", | ||||||
| 			), e2e.WithExecOptionStdout(&buf)) | 			), e2e.WithExecOptionStdout(&buf)) | ||||||
| 			assert.NoError(t, err) | 			assert.NoError(t, err) | ||||||
| 			assert.Contains(t, buf.String(), "rootCmd") | 			assert.Contains(t, buf.String(), "repo = []") | ||||||
| 		}, | 		}, | ||||||
| 	) | 	) | ||||||
| } | } | ||||||
|   | |||||||
| @@ -182,7 +182,7 @@ func runTestCommands(t *testing.T, r e2e.Runnable, timeout time.Duration, expect | |||||||
| } | } | ||||||
|  |  | ||||||
| const REPO_NAME_FOR_E2E_TESTS = "alr-repo" | const REPO_NAME_FOR_E2E_TESTS = "alr-repo" | ||||||
| const REPO_URL_FOR_E2E_TESTS = "https://gitea.plemya-x.ru/Maks1mS/repo-for-tests.git" | const REPO_URL_FOR_E2E_TESTS = "https://gitea.plemya-x.ru/Plemya-x/repo-for-tests.git" | ||||||
|  |  | ||||||
| func defaultPrepare(t *testing.T, r e2e.Runnable) { | func defaultPrepare(t *testing.T, r e2e.Runnable) { | ||||||
| 	execShouldNoError(t, r, | 	execShouldNoError(t, r, | ||||||
|   | |||||||
							
								
								
									
										41
									
								
								e2e-tests/firejailed_package_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								e2e-tests/firejailed_package_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,41 @@ | |||||||
|  | // ALR - Any Linux Repository | ||||||
|  | // Copyright (C) 2025 The ALR Authors | ||||||
|  | // | ||||||
|  | // 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/>. | ||||||
|  |  | ||||||
|  | //go:build e2e | ||||||
|  |  | ||||||
|  | package e2etests_test | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"testing" | ||||||
|  |  | ||||||
|  | 	"github.com/efficientgo/e2e" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func TestE2EFirejailedPackage(t *testing.T) { | ||||||
|  | 	dockerMultipleRun( | ||||||
|  | 		t, | ||||||
|  | 		"firejailed-package", | ||||||
|  | 		COMMON_SYSTEMS, | ||||||
|  | 		func(t *testing.T, r e2e.Runnable) { | ||||||
|  | 			defaultPrepare(t, r) | ||||||
|  | 			execShouldNoError(t, r, "alr", "build", "-p", fmt.Sprintf("%s/firejailed-pkg", REPO_NAME_FOR_E2E_TESTS)) | ||||||
|  | 			execShouldError(t, r, "alr", "build", "-p", fmt.Sprintf("%s/firejailed-pkg-incorrect", REPO_NAME_FOR_E2E_TESTS)) | ||||||
|  | 			execShouldNoError(t, r, "sh", "-c", "dpkg -c *.deb | grep -q '/usr/lib/alr/firejailed/_usr_bin_danger.sh'") | ||||||
|  | 			execShouldNoError(t, r, "sh", "-c", "dpkg -c *.deb | grep -q '/usr/lib/alr/firejailed/_usr_bin_danger.sh.profile'") | ||||||
|  | 		}, | ||||||
|  | 	) | ||||||
|  | } | ||||||
| @@ -31,8 +31,8 @@ func TestE2EGroupAndSummaryField(t *testing.T) { | |||||||
| 		RPM_SYSTEMS, | 		RPM_SYSTEMS, | ||||||
| 		func(t *testing.T, r e2e.Runnable) { | 		func(t *testing.T, r e2e.Runnable) { | ||||||
| 			defaultPrepare(t, r) | 			defaultPrepare(t, r) | ||||||
| 			execShouldNoError(t, r, "sh", "-c", "alr search --name test-group-and-summary --format \"{{.Group}}\" | grep ^System/Base$") | 			execShouldNoError(t, r, "sh", "-c", "alr search --name test-group-and-summary --format \"{{.Group.Resolved}}\" | grep ^System/Base$") | ||||||
| 			execShouldNoError(t, r, "sh", "-c", "alr search --name test-group-and-summary --format \"{{.Summary}}\" | grep \"^Custom summary$\"") | 			execShouldNoError(t, r, "sh", "-c", "alr search --name test-group-and-summary --format \"{{.Summary.Resolved}}\" | grep \"^Custom summary$\"") | ||||||
| 		}, | 		}, | ||||||
| 	) | 	) | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										53
									
								
								e2e-tests/issue_76_single_package_repo_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								e2e-tests/issue_76_single_package_repo_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,53 @@ | |||||||
|  | // ALR - Any Linux Repository | ||||||
|  | // Copyright (C) 2025 The ALR Authors | ||||||
|  | // | ||||||
|  | // 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/>. | ||||||
|  |  | ||||||
|  | //go:build e2e | ||||||
|  |  | ||||||
|  | package e2etests_test | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"testing" | ||||||
|  |  | ||||||
|  | 	"github.com/efficientgo/e2e" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func Test75SinglePackageRepo(t *testing.T) { | ||||||
|  | 	dockerMultipleRun( | ||||||
|  | 		t, | ||||||
|  | 		"issue-76-single-package-repo", | ||||||
|  | 		COMMON_SYSTEMS, | ||||||
|  | 		func(t *testing.T, r e2e.Runnable) { | ||||||
|  | 			execShouldNoError(t, r, | ||||||
|  | 				"sudo", | ||||||
|  | 				"alr", | ||||||
|  | 				"repo", | ||||||
|  | 				"add", | ||||||
|  | 				REPO_NAME_FOR_E2E_TESTS, | ||||||
|  | 				"https://gitea.plemya-x.ru/Maks1mS/test-single-package-alr-repo.git", | ||||||
|  | 			) | ||||||
|  | 			execShouldNoError(t, r, "sudo", "alr", "repo", "set-ref", REPO_NAME_FOR_E2E_TESTS, "1075c918be") | ||||||
|  | 			execShouldNoError(t, r, "alr", "ref") | ||||||
|  | 			execShouldNoError(t, r, "sudo", "alr", "in", "test-single-repo") | ||||||
|  | 			execShouldNoError(t, r, "sh", "-c", "alr list -U") | ||||||
|  | 			execShouldNoError(t, r, "sh", "-c", "test $(alr list -U | wc -l) -eq 0 || exit 1") | ||||||
|  | 			execShouldNoError(t, r, "sudo", "alr", "repo", "set-ref", REPO_NAME_FOR_E2E_TESTS, "5e361c50d7") | ||||||
|  | 			execShouldNoError(t, r, "sudo", "alr", "ref") | ||||||
|  | 			execShouldNoError(t, r, "sh", "-c", "test $(alr list -U | wc -l) -eq 1 || exit 1") | ||||||
|  | 			execShouldNoError(t, r, "sudo", "alr", "up") | ||||||
|  | 			execShouldNoError(t, r, "sh", "-c", "test $(alr list -U | wc -l) -eq 0 || exit 1") | ||||||
|  | 		}, | ||||||
|  | 	) | ||||||
|  | } | ||||||
							
								
								
									
										53
									
								
								e2e-tests/issue_78_mirrors_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								e2e-tests/issue_78_mirrors_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,53 @@ | |||||||
|  | // ALR - Any Linux Repository | ||||||
|  | // Copyright (C) 2025 The ALR Authors | ||||||
|  | // | ||||||
|  | // 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/>. | ||||||
|  |  | ||||||
|  | //go:build e2e | ||||||
|  |  | ||||||
|  | package e2etests_test | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"testing" | ||||||
|  |  | ||||||
|  | 	"github.com/efficientgo/e2e" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func TestE2EIssue78Mirrors(t *testing.T) { | ||||||
|  | 	dockerMultipleRun( | ||||||
|  | 		t, | ||||||
|  | 		"issue-78-mirrors", | ||||||
|  | 		COMMON_SYSTEMS, | ||||||
|  | 		func(t *testing.T, r e2e.Runnable) { | ||||||
|  | 			defaultPrepare(t, r) | ||||||
|  | 			execShouldNoError(t, r, "sudo", "alr", "repo", "mirror", "add", REPO_NAME_FOR_E2E_TESTS, "https://gitea.plemya-x.ru/Maks1mS/repo-for-tests.git") | ||||||
|  | 			execShouldNoError(t, r, "sudo", "alr", "repo", "set-url", REPO_NAME_FOR_E2E_TESTS, "https://example.com") | ||||||
|  | 			execShouldNoError(t, r, "sudo", "alr", "ref") | ||||||
|  | 			execShouldNoError(t, r, "sudo", "alr", "repo", "mirror", "clear", REPO_NAME_FOR_E2E_TESTS) | ||||||
|  | 			execShouldError(t, r, "sudo", "alr", "ref") | ||||||
|  |  | ||||||
|  | 			execShouldNoError(t, r, "sudo", "alr", "repo", "mirror", "add", REPO_NAME_FOR_E2E_TESTS, "https://gitea.plemya-x.ru/Maks1mS/repo-for-tests.git") | ||||||
|  | 			execShouldNoError(t, r, "sudo", "alr", "repo", "mirror", "rm", "--partial", REPO_NAME_FOR_E2E_TESTS, "gitea.plemya-x.ru/Maks1mS") | ||||||
|  | 			execShouldError(t, r, "sudo", "alr", "ref") | ||||||
|  |  | ||||||
|  | 			execShouldNoError(t, r, "sudo", "alr", "repo", "mirror", "add", REPO_NAME_FOR_E2E_TESTS, "https://gitea.plemya-x.ru/Maks1mS/repo-for-tests.git") | ||||||
|  | 			execShouldNoError(t, r, "sudo", "alr", "repo", "mirror", "rm", REPO_NAME_FOR_E2E_TESTS, "https://gitea.plemya-x.ru/Maks1mS/repo-for-tests.git") | ||||||
|  | 			execShouldError(t, r, "sudo", "alr", "ref") | ||||||
|  |  | ||||||
|  | 			execShouldNoError(t, r, "sudo", "alr", "repo", "mirror", "add", REPO_NAME_FOR_E2E_TESTS, "https://gitea.plemya-x.ru/Maks1mS/repo-for-tests.git") | ||||||
|  | 			execShouldNoError(t, r, "sudo", "alr", "repo", "mirror", "rm", REPO_NAME_FOR_E2E_TESTS, "https://gitea.plemya-x.ru/Maks1mS/repo-for-tests.git") | ||||||
|  | 			execShouldError(t, r, "sudo", "alr", "ref") | ||||||
|  | 		}, | ||||||
|  | 	) | ||||||
|  | } | ||||||
							
								
								
									
										49
									
								
								e2e-tests/issue_94_twice_build_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								e2e-tests/issue_94_twice_build_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,49 @@ | |||||||
|  | // ALR - Any Linux Repository | ||||||
|  | // Copyright (C) 2025 The ALR Authors | ||||||
|  | // | ||||||
|  | // 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/>. | ||||||
|  |  | ||||||
|  | //go:build e2e | ||||||
|  |  | ||||||
|  | package e2etests_test | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"bytes" | ||||||
|  | 	"strings" | ||||||
|  | 	"testing" | ||||||
|  |  | ||||||
|  | 	"github.com/efficientgo/e2e" | ||||||
|  | 	"github.com/stretchr/testify/assert" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func TestE2EIssue94TwiceBuild(t *testing.T) { | ||||||
|  | 	dockerMultipleRun( | ||||||
|  | 		t, | ||||||
|  | 		"issue-94-twice-build", | ||||||
|  | 		COMMON_SYSTEMS, | ||||||
|  | 		func(t *testing.T, r e2e.Runnable) { | ||||||
|  | 			defaultPrepare(t, r) | ||||||
|  |  | ||||||
|  | 			var stderr bytes.Buffer | ||||||
|  | 			err := r.Exec( | ||||||
|  | 				e2e.NewCommand("sudo", "alr", "in", "test-94-app"), | ||||||
|  | 				e2e.WithExecOptionStderr(&stderr), | ||||||
|  | 			) | ||||||
|  | 			assert.NoError(t, err, "command failed") | ||||||
|  |  | ||||||
|  | 			output := stderr.String() | ||||||
|  | 			assert.Equal(t, 1, strings.Count(output, "Building package name=test-94-dep")) | ||||||
|  | 		}, | ||||||
|  | 	) | ||||||
|  | } | ||||||
							
								
								
									
										47
									
								
								e2e-tests/issue_95_config_command_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								e2e-tests/issue_95_config_command_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,47 @@ | |||||||
|  | // ALR - Any Linux Repository | ||||||
|  | // Copyright (C) 2025 The ALR Authors | ||||||
|  | // | ||||||
|  | // 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/>. | ||||||
|  |  | ||||||
|  | //go:build e2e | ||||||
|  |  | ||||||
|  | package e2etests_test | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"testing" | ||||||
|  |  | ||||||
|  | 	"github.com/efficientgo/e2e" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func TestE2EIssue95ConfigCommand(t *testing.T) { | ||||||
|  | 	dockerMultipleRun( | ||||||
|  | 		t, | ||||||
|  | 		"issue-95-config-command", | ||||||
|  | 		COMMON_SYSTEMS, | ||||||
|  | 		func(t *testing.T, r e2e.Runnable) { | ||||||
|  | 			defaultPrepare(t, r) | ||||||
|  | 			execShouldNoError(t, r, "sh", "-c", "alr config show | grep \"autoPull: true\"") | ||||||
|  | 			execShouldNoError(t, r, "sh", "-c", "alr config get | grep \"autoPull: true\"") | ||||||
|  | 			execShouldError(t, r, "sh", "-c", "cat /etc/alr/alr.toml | grep \"autoPull\"") | ||||||
|  | 			execShouldNoError(t, r, "alr", "config", "get", "autoPull") | ||||||
|  | 			execShouldError(t, r, "alr", "config", "set", "autoPull") | ||||||
|  | 			execShouldNoError(t, r, "sudo", "alr", "config", "set", "autoPull", "false") | ||||||
|  | 			execShouldNoError(t, r, "sh", "-c", "alr config show | grep \"autoPull: false\"") | ||||||
|  | 			execShouldNoError(t, r, "sh", "-c", "alr config get | grep \"autoPull: false\"") | ||||||
|  | 			execShouldNoError(t, r, "sh", "-c", "cat /etc/alr/alr.toml | grep \"autoPull = false\"") | ||||||
|  | 			execShouldNoError(t, r, "alr", "config", "set", "autoPull", "true") | ||||||
|  | 			execShouldNoError(t, r, "sh", "-c", "cat /etc/alr/alr.toml | grep \"autoPull = true\"") | ||||||
|  | 		}, | ||||||
|  | 	) | ||||||
|  | } | ||||||
							
								
								
									
										30
									
								
								fix.go
									
									
									
									
									
								
							
							
						
						
									
										30
									
								
								fix.go
									
									
									
									
									
								
							| @@ -20,6 +20,7 @@ | |||||||
| package main | package main | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
|  | 	"io/fs" | ||||||
| 	"log/slog" | 	"log/slog" | ||||||
| 	"os" | 	"os" | ||||||
| 	"path/filepath" | 	"path/filepath" | ||||||
| @@ -57,7 +58,6 @@ func FixCmd() *cli.Command { | |||||||
| 			paths := cfg.GetPaths() | 			paths := cfg.GetPaths() | ||||||
|  |  | ||||||
| 			slog.Info(gotext.Get("Clearing cache directory")) | 			slog.Info(gotext.Get("Clearing cache directory")) | ||||||
| 			// Remove all nested directories of paths.CacheDir |  | ||||||
|  |  | ||||||
| 			dir, err := os.Open(paths.CacheDir) | 			dir, err := os.Open(paths.CacheDir) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| @@ -71,7 +71,13 @@ func FixCmd() *cli.Command { | |||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			for _, entry := range entries { | 			for _, entry := range entries { | ||||||
| 				err = os.RemoveAll(filepath.Join(paths.CacheDir, entry)) | 				fullPath := filepath.Join(paths.CacheDir, entry) | ||||||
|  |  | ||||||
|  | 				if err := makeWritableRecursive(fullPath); err != nil { | ||||||
|  | 					slog.Debug("Failed to make path writable", "path", fullPath, "error", err) | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				err = os.RemoveAll(fullPath) | ||||||
| 				if err != nil { | 				if err != nil { | ||||||
| 					return cliutils.FormatCliExit(gotext.Get("Unable to remove cache item (%s)", entry), err) | 					return cliutils.FormatCliExit(gotext.Get("Unable to remove cache item (%s)", entry), err) | ||||||
| 				} | 				} | ||||||
| @@ -101,3 +107,23 @@ func FixCmd() *cli.Command { | |||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func makeWritableRecursive(path string) error { | ||||||
|  | 	return filepath.WalkDir(path, func(path string, d fs.DirEntry, err error) error { | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		info, err := d.Info() | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		newMode := info.Mode() | 0o200 | ||||||
|  | 		if d.IsDir() { | ||||||
|  | 			newMode |= 0o100 | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		return os.Chmod(path, newMode) | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								gen.go
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								gen.go
									
									
									
									
									
								
							| @@ -25,7 +25,7 @@ import ( | |||||||
| 	"github.com/leonelquinteros/gotext" | 	"github.com/leonelquinteros/gotext" | ||||||
| 	"github.com/urfave/cli/v2" | 	"github.com/urfave/cli/v2" | ||||||
|  |  | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/gen" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/gen" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func GenCmd() *cli.Command { | func GenCmd() *cli.Command { | ||||||
|   | |||||||
							
								
								
									
										251
									
								
								generators/alrsh-package/main.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										251
									
								
								generators/alrsh-package/main.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,251 @@ | |||||||
|  | // ALR - Any Linux Repository | ||||||
|  | // Copyright (C) 2025 The ALR Authors | ||||||
|  | // | ||||||
|  | // 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 ( | ||||||
|  | 	"bytes" | ||||||
|  | 	"fmt" | ||||||
|  | 	"go/ast" | ||||||
|  | 	"go/format" | ||||||
|  | 	"go/parser" | ||||||
|  | 	"go/token" | ||||||
|  | 	"log" | ||||||
|  | 	"os" | ||||||
|  | 	"reflect" | ||||||
|  | 	"strings" | ||||||
|  | 	"text/template" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func resolvedStructGenerator(buf *bytes.Buffer, fields []*ast.Field) { | ||||||
|  | 	contentTemplate := template.Must(template.New("").Parse(` | ||||||
|  | type {{ .EntityNameLower }}Resolved struct { | ||||||
|  | {{ .StructFields }} | ||||||
|  | } | ||||||
|  | `)) | ||||||
|  |  | ||||||
|  | 	var structFieldsBuilder strings.Builder | ||||||
|  |  | ||||||
|  | 	for _, field := range fields { | ||||||
|  | 		for _, name := range field.Names { | ||||||
|  | 			// Поле с типом | ||||||
|  | 			fieldTypeStr := exprToString(field.Type) | ||||||
|  |  | ||||||
|  | 			// Структура поля | ||||||
|  | 			var buf bytes.Buffer | ||||||
|  | 			buf.WriteString("\t") | ||||||
|  | 			buf.WriteString(name.Name) | ||||||
|  | 			buf.WriteString(" ") | ||||||
|  | 			buf.WriteString(fieldTypeStr) | ||||||
|  |  | ||||||
|  | 			// Обработка json-тега | ||||||
|  | 			jsonTag := "" | ||||||
|  | 			if field.Tag != nil { | ||||||
|  | 				raw := strings.Trim(field.Tag.Value, "`") | ||||||
|  | 				tag := reflect.StructTag(raw) | ||||||
|  | 				if val := tag.Get("json"); val != "" { | ||||||
|  | 					jsonTag = val | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 			if jsonTag == "" { | ||||||
|  | 				jsonTag = strings.ToLower(name.Name) | ||||||
|  | 			} | ||||||
|  | 			buf.WriteString(fmt.Sprintf(" `json:\"%s\"`", jsonTag)) | ||||||
|  | 			buf.WriteString("\n") | ||||||
|  | 			structFieldsBuilder.Write(buf.Bytes()) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	params := struct { | ||||||
|  | 		EntityNameLower string | ||||||
|  | 		StructFields    string | ||||||
|  | 	}{ | ||||||
|  | 		EntityNameLower: "package", | ||||||
|  | 		StructFields:    structFieldsBuilder.String(), | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	err := contentTemplate.Execute(buf, params) | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Fatalf("execute template: %v", err) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func toResolvedFuncGenerator(buf *bytes.Buffer, fields []*ast.Field) { | ||||||
|  | 	contentTemplate := template.Must(template.New("").Parse(` | ||||||
|  | func {{ .EntityName }}ToResolved(src *{{ .EntityName }}) {{ .EntityNameLower }}Resolved { | ||||||
|  | 	return {{ .EntityNameLower }}Resolved{ | ||||||
|  | {{ .Assignments }} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | `)) | ||||||
|  |  | ||||||
|  | 	var assignmentsBuilder strings.Builder | ||||||
|  |  | ||||||
|  | 	for _, field := range fields { | ||||||
|  | 		for _, name := range field.Names { | ||||||
|  | 			var assignBuf bytes.Buffer | ||||||
|  | 			assignBuf.WriteString("\t\t") | ||||||
|  | 			assignBuf.WriteString(name.Name) | ||||||
|  | 			assignBuf.WriteString(": ") | ||||||
|  | 			if isOverridableField(field.Type) { | ||||||
|  | 				assignBuf.WriteString(fmt.Sprintf("src.%s.Resolved()", name.Name)) | ||||||
|  | 			} else { | ||||||
|  | 				assignBuf.WriteString(fmt.Sprintf("src.%s", name.Name)) | ||||||
|  | 			} | ||||||
|  | 			assignBuf.WriteString(",\n") | ||||||
|  | 			assignmentsBuilder.Write(assignBuf.Bytes()) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	params := struct { | ||||||
|  | 		EntityName      string | ||||||
|  | 		EntityNameLower string | ||||||
|  | 		Assignments     string | ||||||
|  | 	}{ | ||||||
|  | 		EntityName:      "Package", | ||||||
|  | 		EntityNameLower: "package", | ||||||
|  | 		Assignments:     assignmentsBuilder.String(), | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	err := contentTemplate.Execute(buf, params) | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Fatalf("execute template: %v", err) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func resolveFuncGenerator(buf *bytes.Buffer, fields []*ast.Field) { | ||||||
|  | 	contentTemplate := template.Must(template.New("").Parse(` | ||||||
|  | func Resolve{{ .EntityName }}(pkg *{{ .EntityName }}, overrides []string) { | ||||||
|  | {{.Code}}} | ||||||
|  | `)) | ||||||
|  |  | ||||||
|  | 	var codeBuilder strings.Builder | ||||||
|  |  | ||||||
|  | 	for _, field := range fields { | ||||||
|  | 		for _, name := range field.Names { | ||||||
|  | 			if isOverridableField(field.Type) { | ||||||
|  | 				var buf bytes.Buffer | ||||||
|  | 				buf.WriteString(fmt.Sprintf("\t\tpkg.%s.Resolve(overrides)\n", name.Name)) | ||||||
|  | 				codeBuilder.Write(buf.Bytes()) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	params := struct { | ||||||
|  | 		EntityName string | ||||||
|  | 		Code       string | ||||||
|  | 	}{ | ||||||
|  | 		EntityName: "Package", | ||||||
|  | 		Code:       codeBuilder.String(), | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	err := contentTemplate.Execute(buf, params) | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Fatalf("execute template: %v", err) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func main() { | ||||||
|  | 	path := os.Getenv("GOFILE") | ||||||
|  | 	if path == "" { | ||||||
|  | 		log.Fatal("GOFILE must be set") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	fset := token.NewFileSet() | ||||||
|  | 	node, err := parser.ParseFile(fset, path, nil, parser.AllErrors) | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Fatalf("parsing file: %v", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	entityName := "Package" // имя структуры, которую анализируем | ||||||
|  |  | ||||||
|  | 	found := false | ||||||
|  |  | ||||||
|  | 	fields := make([]*ast.Field, 0) | ||||||
|  |  | ||||||
|  | 	// Ищем структуру с нужным именем | ||||||
|  | 	for _, decl := range node.Decls { | ||||||
|  | 		genDecl, ok := decl.(*ast.GenDecl) | ||||||
|  | 		if !ok || genDecl.Tok != token.TYPE { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 		for _, spec := range genDecl.Specs { | ||||||
|  | 			typeSpec := spec.(*ast.TypeSpec) | ||||||
|  | 			if typeSpec.Name.Name != entityName { | ||||||
|  | 				continue | ||||||
|  | 			} | ||||||
|  | 			structType, ok := typeSpec.Type.(*ast.StructType) | ||||||
|  | 			if !ok { | ||||||
|  | 				continue | ||||||
|  | 			} | ||||||
|  | 			fields = structType.Fields.List | ||||||
|  | 			found = true | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if !found { | ||||||
|  | 		log.Fatalf("struct %s not found", entityName) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	var buf bytes.Buffer | ||||||
|  |  | ||||||
|  | 	buf.WriteString("// DO NOT EDIT MANUALLY. This file is generated.\n") | ||||||
|  | 	buf.WriteString("package alrsh") | ||||||
|  |  | ||||||
|  | 	resolvedStructGenerator(&buf, fields) | ||||||
|  | 	toResolvedFuncGenerator(&buf, fields) | ||||||
|  | 	resolveFuncGenerator(&buf, fields) | ||||||
|  |  | ||||||
|  | 	// Форматируем вывод | ||||||
|  | 	formatted, err := format.Source(buf.Bytes()) | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Fatalf("formatting: %v", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	outPath := strings.TrimSuffix(path, ".go") + "_gen.go" | ||||||
|  | 	outFile, err := os.Create(outPath) | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Fatalf("create file: %v", err) | ||||||
|  | 	} | ||||||
|  | 	_, err = outFile.Write(formatted) | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Fatalf("writing output: %v", err) | ||||||
|  | 	} | ||||||
|  | 	outFile.Close() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func exprToString(expr ast.Expr) string { | ||||||
|  | 	if t, ok := expr.(*ast.IndexExpr); ok { | ||||||
|  | 		if ident, ok := t.X.(*ast.Ident); ok && ident.Name == "OverridableField" { | ||||||
|  | 			return exprToString(t.Index) // T | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	var buf bytes.Buffer | ||||||
|  | 	if err := format.Node(&buf, token.NewFileSet(), expr); err != nil { | ||||||
|  | 		return "<invalid>" | ||||||
|  | 	} | ||||||
|  | 	return buf.String() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func isOverridableField(expr ast.Expr) bool { | ||||||
|  | 	indexExpr, ok := expr.(*ast.IndexExpr) | ||||||
|  | 	if !ok { | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | 	ident, ok := indexExpr.X.(*ast.Ident) | ||||||
|  | 	return ok && ident.Name == "OverridableField" | ||||||
|  | } | ||||||
							
								
								
									
										44
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										44
									
								
								go.mod
									
									
									
									
									
								
							| @@ -8,8 +8,8 @@ require ( | |||||||
| 	gitea.plemya-x.ru/Plemya-x/fakeroot v0.0.2-0.20250408104831-427aaa7713c3 | 	gitea.plemya-x.ru/Plemya-x/fakeroot v0.0.2-0.20250408104831-427aaa7713c3 | ||||||
| 	github.com/AlecAivazis/survey/v2 v2.3.7 | 	github.com/AlecAivazis/survey/v2 v2.3.7 | ||||||
| 	github.com/PuerkitoBio/purell v1.2.0 | 	github.com/PuerkitoBio/purell v1.2.0 | ||||||
| 	github.com/alecthomas/assert/v2 v2.2.1 |  | ||||||
| 	github.com/alecthomas/chroma/v2 v2.9.1 | 	github.com/alecthomas/chroma/v2 v2.9.1 | ||||||
|  | 	github.com/bmatcuk/doublestar/v4 v4.8.1 | ||||||
| 	github.com/caarlos0/env v3.5.0+incompatible | 	github.com/caarlos0/env v3.5.0+incompatible | ||||||
| 	github.com/charmbracelet/bubbles v0.20.0 | 	github.com/charmbracelet/bubbles v0.20.0 | ||||||
| 	github.com/charmbracelet/bubbletea v1.2.4 | 	github.com/charmbracelet/bubbletea v1.2.4 | ||||||
| @@ -18,18 +18,18 @@ require ( | |||||||
| 	github.com/efficientgo/e2e v0.14.1-0.20240418111536-97db25a0c6c0 | 	github.com/efficientgo/e2e v0.14.1-0.20240418111536-97db25a0c6c0 | ||||||
| 	github.com/go-git/go-billy/v5 v5.6.0 | 	github.com/go-git/go-billy/v5 v5.6.0 | ||||||
| 	github.com/go-git/go-git/v5 v5.13.0 | 	github.com/go-git/go-git/v5 v5.13.0 | ||||||
|  | 	github.com/goccy/go-yaml v1.18.0 | ||||||
| 	github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 | 	github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 | ||||||
| 	github.com/goreleaser/nfpm/v2 v2.41.0 | 	github.com/goreleaser/nfpm/v2 v2.41.0 | ||||||
| 	github.com/hashicorp/go-hclog v0.14.1 | 	github.com/hashicorp/go-hclog v0.14.1 | ||||||
| 	github.com/hashicorp/go-plugin v1.6.3 | 	github.com/hashicorp/go-plugin v1.6.3 | ||||||
| 	github.com/jeandeaual/go-locale v0.0.0-20241217141322-fcc2cadd6f08 | 	github.com/jeandeaual/go-locale v0.0.0-20241217141322-fcc2cadd6f08 | ||||||
| 	github.com/jmoiron/sqlx v1.3.5 |  | ||||||
| 	github.com/leonelquinteros/gotext v1.7.0 | 	github.com/leonelquinteros/gotext v1.7.0 | ||||||
| 	github.com/mattn/go-isatty v0.0.20 | 	github.com/mattn/go-isatty v0.0.20 | ||||||
| 	github.com/mholt/archiver/v4 v4.0.0-alpha.8 | 	github.com/mholt/archiver/v4 v4.0.0-alpha.8 | ||||||
| 	github.com/mitchellh/mapstructure v1.5.0 | 	github.com/mitchellh/mapstructure v1.5.0 | ||||||
| 	github.com/muesli/reflow v0.3.0 | 	github.com/muesli/reflow v0.3.0 | ||||||
| 	github.com/pelletier/go-toml/v2 v2.1.0 | 	github.com/pelletier/go-toml/v2 v2.2.4 | ||||||
| 	github.com/stretchr/testify v1.10.0 | 	github.com/stretchr/testify v1.10.0 | ||||||
| 	github.com/tailscale/goexpect v0.0.0-20210902213824-6e8c725cea41 | 	github.com/tailscale/goexpect v0.0.0-20210902213824-6e8c725cea41 | ||||||
| 	github.com/urfave/cli/v2 v2.25.7 | 	github.com/urfave/cli/v2 v2.25.7 | ||||||
| @@ -37,11 +37,11 @@ require ( | |||||||
| 	go.elara.ws/vercmp v0.0.0-20230622214216-0b2b067575c4 | 	go.elara.ws/vercmp v0.0.0-20230622214216-0b2b067575c4 | ||||||
| 	golang.org/x/crypto v0.36.0 | 	golang.org/x/crypto v0.36.0 | ||||||
| 	golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 | 	golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 | ||||||
| 	golang.org/x/sys v0.31.0 | 	golang.org/x/sys v0.33.0 | ||||||
| 	golang.org/x/text v0.23.0 | 	golang.org/x/text v0.23.0 | ||||||
| 	gopkg.in/yaml.v3 v3.0.1 |  | ||||||
| 	modernc.org/sqlite v1.25.0 | 	modernc.org/sqlite v1.25.0 | ||||||
| 	mvdan.cc/sh/v3 v3.10.0 | 	mvdan.cc/sh/v3 v3.10.0 | ||||||
|  | 	xorm.io/xorm v1.3.9 | ||||||
| ) | ) | ||||||
|  |  | ||||||
| require ( | require ( | ||||||
| @@ -52,7 +52,6 @@ require ( | |||||||
| 	github.com/Masterminds/sprig/v3 v3.2.3 // indirect | 	github.com/Masterminds/sprig/v3 v3.2.3 // indirect | ||||||
| 	github.com/Microsoft/go-winio v0.6.1 // indirect | 	github.com/Microsoft/go-winio v0.6.1 // indirect | ||||||
| 	github.com/ProtonMail/go-crypto v1.1.3 // indirect | 	github.com/ProtonMail/go-crypto v1.1.3 // indirect | ||||||
| 	github.com/alecthomas/repr v0.2.0 // indirect |  | ||||||
| 	github.com/andybalholm/brotli v1.0.4 // indirect | 	github.com/andybalholm/brotli v1.0.4 // indirect | ||||||
| 	github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect | 	github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect | ||||||
| 	github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb // indirect | 	github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb // indirect | ||||||
| @@ -63,7 +62,7 @@ require ( | |||||||
| 	github.com/charmbracelet/harmonica v0.2.0 // indirect | 	github.com/charmbracelet/harmonica v0.2.0 // indirect | ||||||
| 	github.com/charmbracelet/x/ansi v0.4.5 // indirect | 	github.com/charmbracelet/x/ansi v0.4.5 // indirect | ||||||
| 	github.com/charmbracelet/x/term v0.2.1 // indirect | 	github.com/charmbracelet/x/term v0.2.1 // indirect | ||||||
| 	github.com/cloudflare/circl v1.3.8 // indirect | 	github.com/cloudflare/circl v1.6.1 // indirect | ||||||
| 	github.com/connesc/cipherio v0.2.1 // indirect | 	github.com/connesc/cipherio v0.2.1 // indirect | ||||||
| 	github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect | 	github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect | ||||||
| 	github.com/creack/pty v1.1.24 // indirect | 	github.com/creack/pty v1.1.24 // indirect | ||||||
| @@ -76,28 +75,41 @@ require ( | |||||||
| 	github.com/emirpasic/gods v1.18.1 // indirect | 	github.com/emirpasic/gods v1.18.1 // indirect | ||||||
| 	github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect | 	github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect | ||||||
| 	github.com/fatih/color v1.7.0 // indirect | 	github.com/fatih/color v1.7.0 // indirect | ||||||
|  | 	github.com/fatih/structs v1.1.0 // indirect | ||||||
|  | 	github.com/fsnotify/fsnotify v1.9.0 // indirect | ||||||
| 	github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect | 	github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect | ||||||
| 	github.com/go-logfmt/logfmt v0.6.0 // indirect | 	github.com/go-logfmt/logfmt v0.6.0 // indirect | ||||||
|  | 	github.com/go-viper/mapstructure/v2 v2.2.1 // indirect | ||||||
| 	github.com/gobwas/glob v0.2.3 // indirect | 	github.com/gobwas/glob v0.2.3 // indirect | ||||||
|  | 	github.com/goccy/go-json v0.8.1 // indirect | ||||||
| 	github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect | 	github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect | ||||||
| 	github.com/golang/protobuf v1.5.3 // indirect | 	github.com/golang/protobuf v1.5.3 // indirect | ||||||
| 	github.com/golang/snappy v0.0.4 // indirect | 	github.com/golang/snappy v0.0.4 // indirect | ||||||
| 	github.com/google/goterm v0.0.0-20190703233501-fc88cf888a3f // indirect | 	github.com/google/goterm v0.0.0-20190703233501-fc88cf888a3f // indirect | ||||||
| 	github.com/google/rpmpack v0.6.1-0.20240329070804-c2247cbb881a // indirect | 	github.com/google/rpmpack v0.6.1-0.20240329070804-c2247cbb881a // indirect | ||||||
| 	github.com/google/uuid v1.4.0 // indirect | 	github.com/google/uuid v1.6.0 // indirect | ||||||
| 	github.com/goreleaser/chglog v0.6.1 // indirect | 	github.com/goreleaser/chglog v0.6.1 // indirect | ||||||
| 	github.com/goreleaser/fileglob v1.3.0 // indirect | 	github.com/goreleaser/fileglob v1.3.0 // indirect | ||||||
| 	github.com/hashicorp/errwrap v1.0.0 // indirect | 	github.com/hashicorp/errwrap v1.0.0 // indirect | ||||||
| 	github.com/hashicorp/go-multierror v1.1.1 // indirect | 	github.com/hashicorp/go-multierror v1.1.1 // indirect | ||||||
| 	github.com/hashicorp/yamux v0.1.1 // indirect | 	github.com/hashicorp/yamux v0.1.1 // indirect | ||||||
| 	github.com/hexops/gotextdiff v1.0.3 // indirect |  | ||||||
| 	github.com/huandu/xstrings v1.3.3 // indirect | 	github.com/huandu/xstrings v1.3.3 // indirect | ||||||
| 	github.com/imdario/mergo v0.3.16 // indirect | 	github.com/imdario/mergo v0.3.16 // indirect | ||||||
| 	github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect | 	github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect | ||||||
|  | 	github.com/json-iterator/go v1.1.12 // indirect | ||||||
| 	github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect | 	github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect | ||||||
| 	github.com/kevinburke/ssh_config v1.2.0 // indirect | 	github.com/kevinburke/ssh_config v1.2.0 // indirect | ||||||
| 	github.com/klauspost/compress v1.17.11 // indirect | 	github.com/klauspost/compress v1.17.11 // indirect | ||||||
| 	github.com/klauspost/pgzip v1.2.6 // indirect | 	github.com/klauspost/pgzip v1.2.6 // indirect | ||||||
|  | 	github.com/knadh/koanf/maps v0.1.2 // indirect | ||||||
|  | 	github.com/knadh/koanf/parsers/json v1.0.0 // indirect | ||||||
|  | 	github.com/knadh/koanf/parsers/toml/v2 v2.2.0 // indirect | ||||||
|  | 	github.com/knadh/koanf/providers/confmap v1.0.0 // indirect | ||||||
|  | 	github.com/knadh/koanf/providers/env v1.1.0 // indirect | ||||||
|  | 	github.com/knadh/koanf/providers/file v1.2.0 // indirect | ||||||
|  | 	github.com/knadh/koanf/providers/rawbytes v1.0.0 // indirect | ||||||
|  | 	github.com/knadh/koanf/providers/structs v1.0.0 // indirect | ||||||
|  | 	github.com/knadh/koanf/v2 v2.2.1 // indirect | ||||||
| 	github.com/lucasb-eyer/go-colorful v1.2.0 // indirect | 	github.com/lucasb-eyer/go-colorful v1.2.0 // indirect | ||||||
| 	github.com/mattn/go-colorable v0.1.13 // indirect | 	github.com/mattn/go-colorable v0.1.13 // indirect | ||||||
| 	github.com/mattn/go-localereader v0.0.1 // indirect | 	github.com/mattn/go-localereader v0.0.1 // indirect | ||||||
| @@ -105,6 +117,8 @@ require ( | |||||||
| 	github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect | 	github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect | ||||||
| 	github.com/mitchellh/copystructure v1.2.0 // indirect | 	github.com/mitchellh/copystructure v1.2.0 // indirect | ||||||
| 	github.com/mitchellh/reflectwalk v1.0.2 // indirect | 	github.com/mitchellh/reflectwalk v1.0.2 // indirect | ||||||
|  | 	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect | ||||||
|  | 	github.com/modern-go/reflect2 v1.0.2 // indirect | ||||||
| 	github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect | 	github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect | ||||||
| 	github.com/muesli/cancelreader v0.2.2 // indirect | 	github.com/muesli/cancelreader v0.2.2 // indirect | ||||||
| 	github.com/muesli/termenv v0.15.2 // indirect | 	github.com/muesli/termenv v0.15.2 // indirect | ||||||
| @@ -117,9 +131,10 @@ require ( | |||||||
| 	github.com/rivo/uniseg v0.4.7 // indirect | 	github.com/rivo/uniseg v0.4.7 // indirect | ||||||
| 	github.com/russross/blackfriday/v2 v2.1.0 // indirect | 	github.com/russross/blackfriday/v2 v2.1.0 // indirect | ||||||
| 	github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect | 	github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect | ||||||
| 	github.com/shopspring/decimal v1.2.0 // indirect | 	github.com/shopspring/decimal v1.3.1 // indirect | ||||||
| 	github.com/skeema/knownhosts v1.3.0 // indirect | 	github.com/skeema/knownhosts v1.3.0 // indirect | ||||||
| 	github.com/spf13/cast v1.6.0 // indirect | 	github.com/spf13/cast v1.7.1 // indirect | ||||||
|  | 	github.com/syndtr/goleveldb v1.0.0 // indirect | ||||||
| 	github.com/therootcompany/xz v1.0.1 // indirect | 	github.com/therootcompany/xz v1.0.1 // indirect | ||||||
| 	github.com/ulikunitz/xz v0.5.12 // indirect | 	github.com/ulikunitz/xz v0.5.12 // indirect | ||||||
| 	github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect | 	github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect | ||||||
| @@ -129,13 +144,15 @@ require ( | |||||||
| 	go4.org v0.0.0-20200411211856-f5505b9728dd // indirect | 	go4.org v0.0.0-20200411211856-f5505b9728dd // indirect | ||||||
| 	golang.org/x/mod v0.19.0 // indirect | 	golang.org/x/mod v0.19.0 // indirect | ||||||
| 	golang.org/x/net v0.38.0 // indirect | 	golang.org/x/net v0.38.0 // indirect | ||||||
|  | 	golang.org/x/oauth2 v0.25.0 // indirect | ||||||
| 	golang.org/x/sync v0.12.0 // indirect | 	golang.org/x/sync v0.12.0 // indirect | ||||||
| 	golang.org/x/term v0.30.0 // indirect | 	golang.org/x/term v0.30.0 // indirect | ||||||
| 	golang.org/x/tools v0.23.0 // indirect | 	golang.org/x/tools v0.23.0 // indirect | ||||||
| 	google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 // indirect | 	google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8 // indirect | ||||||
| 	google.golang.org/grpc v1.58.3 // indirect | 	google.golang.org/grpc v1.67.3 // indirect | ||||||
| 	google.golang.org/protobuf v1.36.1 // indirect | 	google.golang.org/protobuf v1.36.1 // indirect | ||||||
| 	gopkg.in/warnings.v0 v0.1.2 // indirect | 	gopkg.in/warnings.v0 v0.1.2 // indirect | ||||||
|  | 	gopkg.in/yaml.v3 v3.0.1 // indirect | ||||||
| 	lukechampine.com/uint128 v1.2.0 // indirect | 	lukechampine.com/uint128 v1.2.0 // indirect | ||||||
| 	modernc.org/cc/v3 v3.40.0 // indirect | 	modernc.org/cc/v3 v3.40.0 // indirect | ||||||
| 	modernc.org/ccgo/v3 v3.16.13 // indirect | 	modernc.org/ccgo/v3 v3.16.13 // indirect | ||||||
| @@ -145,4 +162,5 @@ require ( | |||||||
| 	modernc.org/opt v0.1.3 // indirect | 	modernc.org/opt v0.1.3 // indirect | ||||||
| 	modernc.org/strutil v1.1.3 // indirect | 	modernc.org/strutil v1.1.3 // indirect | ||||||
| 	modernc.org/token v1.0.1 // indirect | 	modernc.org/token v1.0.1 // indirect | ||||||
|  | 	xorm.io/builder v0.3.11-0.20220531020008-1bd24a7dc978 // indirect | ||||||
| ) | ) | ||||||
|   | |||||||
							
								
								
									
										116
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										116
									
								
								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 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= | ||||||
| dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= | dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= | ||||||
| dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= | dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= | ||||||
|  | gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s= | ||||||
|  | gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU= | ||||||
| gitea.plemya-x.ru/Plemya-x/fakeroot v0.0.2-0.20250408104831-427aaa7713c3 h1:56BjRJJ2Sv50DfSvNUydUMJwwFuiBMWC1uYtH2GYjk8= | gitea.plemya-x.ru/Plemya-x/fakeroot v0.0.2-0.20250408104831-427aaa7713c3 h1:56BjRJJ2Sv50DfSvNUydUMJwwFuiBMWC1uYtH2GYjk8= | ||||||
| gitea.plemya-x.ru/Plemya-x/fakeroot v0.0.2-0.20250408104831-427aaa7713c3/go.mod h1:iKQM6uttMJgE5CFrPw6SQqAV7TKtlJNICRAie/dTciw= | gitea.plemya-x.ru/Plemya-x/fakeroot v0.0.2-0.20250408104831-427aaa7713c3/go.mod h1:iKQM6uttMJgE5CFrPw6SQqAV7TKtlJNICRAie/dTciw= | ||||||
| github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkkhIiSjQ= | github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkkhIiSjQ= | ||||||
| @@ -65,6 +67,8 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= | |||||||
| github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= | github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= | ||||||
| github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb h1:m935MPodAbYS46DG4pJSv7WO+VECIWUQ7OJYSoTrMh4= | github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb h1:m935MPodAbYS46DG4pJSv7WO+VECIWUQ7OJYSoTrMh4= | ||||||
| github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI= | github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI= | ||||||
|  | github.com/bmatcuk/doublestar/v4 v4.8.1 h1:54Bopc5c2cAvhLRAzqOGCYHYyhcDHsFF4wWIR5wKP38= | ||||||
|  | github.com/bmatcuk/doublestar/v4 v4.8.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= | ||||||
| github.com/bodgit/plumbing v1.2.0 h1:gg4haxoKphLjml+tgnecR4yLBV5zo4HAZGCtAh3xCzM= | github.com/bodgit/plumbing v1.2.0 h1:gg4haxoKphLjml+tgnecR4yLBV5zo4HAZGCtAh3xCzM= | ||||||
| github.com/bodgit/plumbing v1.2.0/go.mod h1:b9TeRi7Hvc6Y05rjm8VML3+47n4XTZPtQ/5ghqic2n8= | github.com/bodgit/plumbing v1.2.0/go.mod h1:b9TeRi7Hvc6Y05rjm8VML3+47n4XTZPtQ/5ghqic2n8= | ||||||
| github.com/bodgit/sevenzip v1.3.0 h1:1ljgELgtHqvgIp8W8kgeEGHIWP4ch3xGI8uOBZgLVKY= | github.com/bodgit/sevenzip v1.3.0 h1:1ljgELgtHqvgIp8W8kgeEGHIWP4ch3xGI8uOBZgLVKY= | ||||||
| @@ -80,8 +84,8 @@ github.com/caarlos0/testfs v0.4.4/go.mod h1:bRN55zgG4XCUVVHZCeU+/Tz1Q6AxEJOEJTli | |||||||
| github.com/cavaliergopher/cpio v1.0.1 h1:KQFSeKmZhv0cr+kawA3a0xTQCU4QxXF1vhU7P7av2KM= | github.com/cavaliergopher/cpio v1.0.1 h1:KQFSeKmZhv0cr+kawA3a0xTQCU4QxXF1vhU7P7av2KM= | ||||||
| github.com/cavaliergopher/cpio v1.0.1/go.mod h1:pBdaqQjnvXxdS/6CvNDwIANIFSP0xRKI16PX4xejRQc= | github.com/cavaliergopher/cpio v1.0.1/go.mod h1:pBdaqQjnvXxdS/6CvNDwIANIFSP0xRKI16PX4xejRQc= | ||||||
| github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= | ||||||
| github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= | github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= | ||||||
| github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= | github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= | ||||||
| github.com/charmbracelet/bubbles v0.20.0 h1:jSZu6qD8cRQ6k9OMfR1WlM+ruM8fkPWkHvQWD9LIutE= | 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/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 h1:KN8aCViA0eps9SCOThb2/XPIlea3ANJLUkv3KnQRNCE= | ||||||
| @@ -100,8 +104,8 @@ github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWR | |||||||
| github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= | ||||||
| github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= | ||||||
| github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= | ||||||
| github.com/cloudflare/circl v1.3.8 h1:j+V8jJt09PoeMFIu2uh5JUyEaIHTXVOHslFoLNAKqwI= | github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0= | ||||||
| github.com/cloudflare/circl v1.3.8/go.mod h1:PDRU+oXvdD7KCtgKxW95M5Z8BpSCJXQORiZFnBQS5QU= | github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= | ||||||
| github.com/connesc/cipherio v0.2.1 h1:FGtpTPMbKNNWByNrr9aEBtaJtXjqOzkIXNYJp6OEycw= | github.com/connesc/cipherio v0.2.1 h1:FGtpTPMbKNNWByNrr9aEBtaJtXjqOzkIXNYJp6OEycw= | ||||||
| github.com/connesc/cipherio v0.2.1/go.mod h1:ukY0MWJDFnJEbXMQtOcn2VmTpRfzcTz4OoVrWGGJZcA= | github.com/connesc/cipherio v0.2.1/go.mod h1:ukY0MWJDFnJEbXMQtOcn2VmTpRfzcTz4OoVrWGGJZcA= | ||||||
| github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4= | github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4= | ||||||
| @@ -135,8 +139,13 @@ github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6 | |||||||
| github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM= | github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM= | ||||||
| github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= | github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= | ||||||
| github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= | github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= | ||||||
|  | github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= | ||||||
|  | github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= | ||||||
| github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= | github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= | ||||||
| github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= | github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= | ||||||
|  | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= | ||||||
|  | github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= | ||||||
|  | github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= | ||||||
| github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= | github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= | ||||||
| github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= | github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= | ||||||
| github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= | github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= | ||||||
| @@ -153,10 +162,16 @@ github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi | |||||||
| github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= | 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 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI= | ||||||
| github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow= | 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.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc= | ||||||
| github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= | github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= | ||||||
|  | github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= | ||||||
|  | github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= | ||||||
| github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= | github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= | ||||||
| github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= | github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= | ||||||
|  | github.com/goccy/go-json v0.8.1 h1:4/Wjm0JIJaTDm8K1KcGrLHJoa8EsJ13YWeX+6Kfq6uI= | ||||||
|  | github.com/goccy/go-json v0.8.1/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= | ||||||
|  | github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= | ||||||
|  | github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= | ||||||
| github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= | ||||||
| github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= | github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= | ||||||
| github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= | github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= | ||||||
| @@ -176,6 +191,7 @@ github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaW | |||||||
| github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= | ||||||
| github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= | github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= | ||||||
| github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= | github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= | ||||||
|  | github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= | ||||||
| github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= | github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= | ||||||
| github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= | github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= | ||||||
| github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= | ||||||
| @@ -187,6 +203,7 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ | |||||||
| github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= | ||||||
| github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= | ||||||
| github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= | ||||||
|  | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= | ||||||
| github.com/google/goterm v0.0.0-20190703233501-fc88cf888a3f h1:5CjVwnuUcp5adK4gmY6i72gpVFVnZDP2h5TmPScB6u4= | github.com/google/goterm v0.0.0-20190703233501-fc88cf888a3f h1:5CjVwnuUcp5adK4gmY6i72gpVFVnZDP2h5TmPScB6u4= | ||||||
| github.com/google/goterm v0.0.0-20190703233501-fc88cf888a3f/go.mod h1:nOFQdrUlIlx6M6ODdSpBj1NVA+VgLC6kmw60mkw34H4= | github.com/google/goterm v0.0.0-20190703233501-fc88cf888a3f/go.mod h1:nOFQdrUlIlx6M6ODdSpBj1NVA+VgLC6kmw60mkw34H4= | ||||||
| github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= | github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= | ||||||
| @@ -201,8 +218,8 @@ github.com/google/rpmpack v0.6.1-0.20240329070804-c2247cbb881a/go.mod h1:uqVAUVQ | |||||||
| github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= | 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/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= | ||||||
| github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= | github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= | ||||||
| github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= | ||||||
| github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= | ||||||
| github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= | github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= | ||||||
| github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= | github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= | ||||||
| github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g= | github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g= | ||||||
| @@ -229,6 +246,8 @@ github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUq | |||||||
| github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= | github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= | ||||||
| github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog= | github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog= | ||||||
| github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68= | github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68= | ||||||
|  | github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= | ||||||
|  | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= | ||||||
| github.com/huandu/xstrings v1.3.3 h1:/Gcsuc1x8JVbJ9/rlye4xZnVAbEkGauT8lbebqcQws4= | github.com/huandu/xstrings v1.3.3 h1:/Gcsuc1x8JVbJ9/rlye4xZnVAbEkGauT8lbebqcQws4= | ||||||
| github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= | github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= | ||||||
| github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= | github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= | ||||||
| @@ -241,10 +260,10 @@ github.com/jeandeaual/go-locale v0.0.0-20241217141322-fcc2cadd6f08 h1:wMeVzrPO3m | |||||||
| github.com/jeandeaual/go-locale v0.0.0-20241217141322-fcc2cadd6f08/go.mod h1:ZDXo8KHryOWSIqnsb/CiDq7hQUYryCgdVnxbj8tDG7o= | github.com/jeandeaual/go-locale v0.0.0-20241217141322-fcc2cadd6f08/go.mod h1:ZDXo8KHryOWSIqnsb/CiDq7hQUYryCgdVnxbj8tDG7o= | ||||||
| github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c= | github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c= | ||||||
| github.com/jhump/protoreflect v1.15.1/go.mod h1:jD/2GMKKE6OqX8qTjhADU1e6DShO+gavG9e0Q693nKo= | github.com/jhump/protoreflect v1.15.1/go.mod h1:jD/2GMKKE6OqX8qTjhADU1e6DShO+gavG9e0Q693nKo= | ||||||
| github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g= |  | ||||||
| github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= |  | ||||||
| github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= | github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= | ||||||
| github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= | github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= | ||||||
|  | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= | ||||||
|  | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= | ||||||
| github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= | github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= | ||||||
| github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= | github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= | ||||||
| github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= | github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= | ||||||
| @@ -260,6 +279,24 @@ github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90 | |||||||
| github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= | github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= | ||||||
| github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU= | github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU= | ||||||
| github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= | github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= | ||||||
|  | github.com/knadh/koanf/maps v0.1.2 h1:RBfmAW5CnZT+PJ1CVc1QSJKf4Xu9kxfQgYVQSu8hpbo= | ||||||
|  | github.com/knadh/koanf/maps v0.1.2/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI= | ||||||
|  | github.com/knadh/koanf/parsers/json v1.0.0 h1:1pVR1JhMwbqSg5ICzU+surJmeBbdT4bQm7jjgnA+f8o= | ||||||
|  | github.com/knadh/koanf/parsers/json v1.0.0/go.mod h1:zb5WtibRdpxSoSJfXysqGbVxvbszdlroWDHGdDkkEYU= | ||||||
|  | github.com/knadh/koanf/parsers/toml/v2 v2.2.0 h1:2nV7tHYJ5OZy2BynQ4mOJ6k5bDqbbCzRERLUKBytz3A= | ||||||
|  | github.com/knadh/koanf/parsers/toml/v2 v2.2.0/go.mod h1:JpjTeK1Ge1hVX0wbof5DMCuDBriR8bWgeQP98eeOZpI= | ||||||
|  | github.com/knadh/koanf/providers/confmap v1.0.0 h1:mHKLJTE7iXEys6deO5p6olAiZdG5zwp8Aebir+/EaRE= | ||||||
|  | github.com/knadh/koanf/providers/confmap v1.0.0/go.mod h1:txHYHiI2hAtF0/0sCmcuol4IDcuQbKTybiB1nOcUo1A= | ||||||
|  | github.com/knadh/koanf/providers/env v1.1.0 h1:U2VXPY0f+CsNDkvdsG8GcsnK4ah85WwWyJgef9oQMSc= | ||||||
|  | github.com/knadh/koanf/providers/env v1.1.0/go.mod h1:QhHHHZ87h9JxJAn2czdEl6pdkNnDh/JS1Vtsyt65hTY= | ||||||
|  | github.com/knadh/koanf/providers/file v1.2.0 h1:hrUJ6Y9YOA49aNu/RSYzOTFlqzXSCpmYIDXI7OJU6+U= | ||||||
|  | github.com/knadh/koanf/providers/file v1.2.0/go.mod h1:bp1PM5f83Q+TOUu10J/0ApLBd9uIzg+n9UgthfY+nRA= | ||||||
|  | github.com/knadh/koanf/providers/rawbytes v1.0.0 h1:MrKDh/HksJlKJmaZjgs4r8aVBb/zsJyc/8qaSnzcdNI= | ||||||
|  | github.com/knadh/koanf/providers/rawbytes v1.0.0/go.mod h1:KxwYJf1uezTKy6PBtfE+m725NGp4GPVA7XoNTJ/PtLo= | ||||||
|  | github.com/knadh/koanf/providers/structs v1.0.0 h1:DznjB7NQykhqCar2LvNug3MuxEQsZ5KvfgMbio+23u4= | ||||||
|  | github.com/knadh/koanf/providers/structs v1.0.0/go.mod h1:kjo5TFtgpaZORlpoJqcbeLowM2cINodv8kX+oFAeQ1w= | ||||||
|  | github.com/knadh/koanf/v2 v2.2.1 h1:jaleChtw85y3UdBnI0wCqcg1sj1gPoz6D3caGNHtrNE= | ||||||
|  | github.com/knadh/koanf/v2 v2.2.1/go.mod h1:PSFru3ufQgTsI7IF+95rf9s8XA1+aHxKuO/W+dPoHEY= | ||||||
| github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= | ||||||
| github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= | ||||||
| github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= | ||||||
| @@ -269,8 +306,6 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= | |||||||
| github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= | ||||||
| github.com/leonelquinteros/gotext v1.7.0 h1:jcJmF4AXqyamP7vuw2MMIKs+O3jAEmvrc5JQiI8Ht/8= | github.com/leonelquinteros/gotext v1.7.0 h1:jcJmF4AXqyamP7vuw2MMIKs+O3jAEmvrc5JQiI8Ht/8= | ||||||
| github.com/leonelquinteros/gotext v1.7.0/go.mod h1:qJdoQuERPpccw7L70uoU+K/BvTfRBHYsisCQyFLXyvw= | 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 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= | ||||||
| github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= | github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= | ||||||
| github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE= | github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE= | ||||||
| @@ -289,7 +324,6 @@ github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+Ei | |||||||
| github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= | github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= | ||||||
| github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= | 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-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 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y= | ||||||
| github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= | github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= | ||||||
| github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= | github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= | ||||||
| @@ -306,6 +340,11 @@ github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RR | |||||||
| github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= | github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= | ||||||
| github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= | github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= | ||||||
| github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= | github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= | ||||||
|  | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= | ||||||
|  | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= | ||||||
|  | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= | ||||||
|  | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= | ||||||
|  | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= | ||||||
| github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= | 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/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= | ||||||
| github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= | github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= | ||||||
| @@ -320,10 +359,16 @@ github.com/nwaples/rardecode/v2 v2.0.0-beta.2 h1:e3mzJFJs4k83GXBEiTaQ5HgSc/kOK8q | |||||||
| github.com/nwaples/rardecode/v2 v2.0.0-beta.2/go.mod h1:yntwv/HfMc/Hbvtq9I19D1n58te3h6KsqCf3GxyfBGY= | github.com/nwaples/rardecode/v2 v2.0.0-beta.2/go.mod h1:yntwv/HfMc/Hbvtq9I19D1n58te3h6KsqCf3GxyfBGY= | ||||||
| github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= | github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= | ||||||
| github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= | github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= | ||||||
|  | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= | ||||||
|  | github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs= | ||||||
|  | github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= | ||||||
|  | github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= | ||||||
| github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= | github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= | ||||||
| github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= | github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= | ||||||
| github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= | github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= | ||||||
| github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= | github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= | ||||||
|  | github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= | ||||||
|  | github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= | ||||||
| github.com/pierrec/lz4/v4 v4.1.15 h1:MO0/ucJhngq7299dKLwIMtgTfbkoSPF6AoMYDd8Q4q0= | github.com/pierrec/lz4/v4 v4.1.15 h1:MO0/ucJhngq7299dKLwIMtgTfbkoSPF6AoMYDd8Q4q0= | ||||||
| github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= | github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= | ||||||
| github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= | github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= | ||||||
| @@ -358,8 +403,9 @@ github.com/sassoftware/go-rpmutils v0.4.0 h1:ojND82NYBxgwrV+mX1CWsd5QJvvEZTKddtC | |||||||
| github.com/sassoftware/go-rpmutils v0.4.0/go.mod h1:3goNWi7PGAT3/dlql2lv3+MSN5jNYPjT5mVcQcIsYzI= | github.com/sassoftware/go-rpmutils v0.4.0/go.mod h1:3goNWi7PGAT3/dlql2lv3+MSN5jNYPjT5mVcQcIsYzI= | ||||||
| github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= | github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= | ||||||
| github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= | github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= | ||||||
| github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= |  | ||||||
| github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= | github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= | ||||||
|  | github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= | ||||||
|  | github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= | ||||||
| github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= | github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= | ||||||
| github.com/skeema/knownhosts v1.3.0 h1:AM+y0rI04VksttfwjkSTNQorvGqmwATnvnAHpSgc0LY= | github.com/skeema/knownhosts v1.3.0 h1:AM+y0rI04VksttfwjkSTNQorvGqmwATnvnAHpSgc0LY= | ||||||
| github.com/skeema/knownhosts v1.3.0/go.mod h1:sPINvnADmT/qYH1kfv+ePMmOBTH6Tbl7b5LvTDjFK7M= | github.com/skeema/knownhosts v1.3.0/go.mod h1:sPINvnADmT/qYH1kfv+ePMmOBTH6Tbl7b5LvTDjFK7M= | ||||||
| @@ -368,20 +414,19 @@ github.com/smarty/assertions v1.15.0/go.mod h1:yABtdzeQs6l1brC900WlRNwj6ZR55d7B+ | |||||||
| github.com/smartystreets/goconvey v1.8.1 h1:qGjIddxOk4grTu9JPOU31tVfq3cNdBlNa5sSznIX1xY= | github.com/smartystreets/goconvey v1.8.1 h1:qGjIddxOk4grTu9JPOU31tVfq3cNdBlNa5sSznIX1xY= | ||||||
| github.com/smartystreets/goconvey v1.8.1/go.mod h1:+/u4qLyY6x1jReYOp7GOM2FSt8aP9CzCZL03bI28W60= | github.com/smartystreets/goconvey v1.8.1/go.mod h1:+/u4qLyY6x1jReYOp7GOM2FSt8aP9CzCZL03bI28W60= | ||||||
| github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= | github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= | ||||||
| github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= | github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= | ||||||
| github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= | github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= | ||||||
| github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | ||||||
| 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.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= | ||||||
|  | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= | ||||||
| github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= | ||||||
| github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= | ||||||
| github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= | ||||||
| github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= | ||||||
| github.com/stretchr/testify v1.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.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= | 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/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= | ||||||
|  | github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE= | ||||||
|  | github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= | ||||||
| github.com/tailscale/goexpect v0.0.0-20210902213824-6e8c725cea41 h1:/V2rCMMWcsjYaYO2MeovLw+ClP63OtXgCF2Y1eb8+Ns= | github.com/tailscale/goexpect v0.0.0-20210902213824-6e8c725cea41 h1:/V2rCMMWcsjYaYO2MeovLw+ClP63OtXgCF2Y1eb8+Ns= | ||||||
| github.com/tailscale/goexpect v0.0.0-20210902213824-6e8c725cea41/go.mod h1:/roCdA6gg6lQyw/Oz6gIIGu3ggJKYhF+WC/AQReE5XQ= | github.com/tailscale/goexpect v0.0.0-20210902213824-6e8c725cea41/go.mod h1:/roCdA6gg6lQyw/Oz6gIIGu3ggJKYhF+WC/AQReE5XQ= | ||||||
| github.com/therootcompany/xz v1.0.1 h1:CmOtsn1CbtmyYiusbfmhmkpAAETj0wBIH6kCYaX+xzw= | github.com/therootcompany/xz v1.0.1 h1:CmOtsn1CbtmyYiusbfmhmkpAAETj0wBIH6kCYaX+xzw= | ||||||
| @@ -453,6 +498,7 @@ golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= | |||||||
| golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= | golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= | ||||||
| golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | ||||||
| golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | ||||||
|  | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | ||||||
| golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | ||||||
| golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | ||||||
| golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= | ||||||
| @@ -476,8 +522,8 @@ golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4Iltr | |||||||
| golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= | ||||||
| golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= | golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= | ||||||
| golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= | golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= | ||||||
| golang.org/x/oauth2 v0.10.0 h1:zHCpF2Khkwy4mMB4bv0U37YtJdTGW8jI0glAApi0Kh8= | golang.org/x/oauth2 v0.25.0 h1:CY4y7XT9v0cRI9oupztF8AgiIu99L/ksR/Xp/6jrZ70= | ||||||
| golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI= | golang.org/x/oauth2 v0.25.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= | ||||||
| golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||||||
| golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||||||
| golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||||||
| @@ -488,6 +534,7 @@ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJ | |||||||
| golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= | golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= | ||||||
| golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= | golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= | ||||||
| golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||||||
|  | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||||||
| golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||||||
| golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||||||
| golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||||
| @@ -516,6 +563,8 @@ golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | |||||||
| golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||||
| golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= | golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= | ||||||
| golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= | golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= | ||||||
|  | golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= | ||||||
|  | golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= | ||||||
| golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= | ||||||
| golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= | ||||||
| golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= | golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= | ||||||
| @@ -576,8 +625,6 @@ google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7 | |||||||
| google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= | google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= | ||||||
| google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= | google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= | ||||||
| google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= | google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= | ||||||
| google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= |  | ||||||
| google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= |  | ||||||
| google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= | ||||||
| google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= | google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= | ||||||
| google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= | google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= | ||||||
| @@ -591,8 +638,8 @@ google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvx | |||||||
| google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= | google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= | ||||||
| google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= | google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= | ||||||
| google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= | google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= | ||||||
| google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 h1:bVf09lpb+OJbByTj913DRJioFFAjf/ZGxEz7MajTp2U= | google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8 h1:TqExAhdPaB60Ux47Cn0oLV07rGnxZzIsaRhQaqS666A= | ||||||
| google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM= | google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8/go.mod h1:lcTa1sDdWEIHMWlITnIczmw5w60CF9ffkb8Z+DVmmjA= | ||||||
| google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= | ||||||
| google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= | google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= | ||||||
| google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= | google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= | ||||||
| @@ -600,8 +647,8 @@ google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyac | |||||||
| google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= | google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= | ||||||
| google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= | ||||||
| google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= | google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= | ||||||
| google.golang.org/grpc v1.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ= | google.golang.org/grpc v1.67.3 h1:OgPcDAFKHnH8X3O4WcO4XUc8GRDeKsKReqbQtiCj7N8= | ||||||
| google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= | google.golang.org/grpc v1.67.3/go.mod h1:YGaHCc6Oap+FzBJTZLBzkGSYt/cvGPFTPxkn7QfSU8s= | ||||||
| google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= | ||||||
| google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= | ||||||
| google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk= | google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk= | ||||||
| @@ -612,8 +659,13 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8 | |||||||
| gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= | ||||||
| gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= | ||||||
| gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= | ||||||
|  | gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= | ||||||
|  | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= | ||||||
|  | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= | ||||||
|  | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= | ||||||
| gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= | gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= | ||||||
| gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= | gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= | ||||||
|  | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= | ||||||
| gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= | ||||||
| gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= | ||||||
| gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= | ||||||
| @@ -659,3 +711,7 @@ 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/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= | ||||||
| rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= | rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= | ||||||
| rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= | rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= | ||||||
|  | xorm.io/builder v0.3.11-0.20220531020008-1bd24a7dc978 h1:bvLlAPW1ZMTWA32LuZMBEGHAUOcATZjzHcotf3SWweM= | ||||||
|  | xorm.io/builder v0.3.11-0.20220531020008-1bd24a7dc978/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE= | ||||||
|  | xorm.io/xorm v1.3.9 h1:TUovzS0ko+IQ1XnNLfs5dqK1cJl1H5uHpWbWqAQ04nU= | ||||||
|  | xorm.io/xorm v1.3.9/go.mod h1:LsCCffeeYp63ssk0pKumP6l96WZcHix7ChpurcLNuMw= | ||||||
|   | |||||||
							
								
								
									
										55
									
								
								info.go
									
									
									
									
									
								
							
							
						
						
									
										55
									
								
								info.go
									
									
									
									
									
								
							| @@ -23,16 +23,16 @@ import ( | |||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"os" | 	"os" | ||||||
|  |  | ||||||
|  | 	"github.com/goccy/go-yaml" | ||||||
| 	"github.com/jeandeaual/go-locale" | 	"github.com/jeandeaual/go-locale" | ||||||
| 	"github.com/leonelquinteros/gotext" | 	"github.com/leonelquinteros/gotext" | ||||||
| 	"github.com/urfave/cli/v2" | 	"github.com/urfave/cli/v2" | ||||||
| 	"gopkg.in/yaml.v3" |  | ||||||
|  |  | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils" | ||||||
| 	appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder" | 	appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder" | ||||||
| 	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/internal/overrides" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils" | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro" | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| @@ -67,15 +67,8 @@ func InfoCmd() *cli.Command { | |||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				return cliutils.FormatCliExit(gotext.Get("Error getting packages"), err) | 				return cliutils.FormatCliExit(gotext.Get("Error getting packages"), err) | ||||||
| 			} | 			} | ||||||
| 			defer result.Close() |  | ||||||
|  |  | ||||||
| 			for result.Next() { |  | ||||||
| 				var pkg database.Package |  | ||||||
| 				err = result.StructScan(&pkg) |  | ||||||
| 				if err != nil { |  | ||||||
| 					return cliutils.FormatCliExit(gotext.Get("Error iterating over packages"), err) |  | ||||||
| 				} |  | ||||||
|  |  | ||||||
|  | 			for _, pkg := range result { | ||||||
| 				fmt.Println(pkg.Name) | 				fmt.Println(pkg.Name) | ||||||
| 			} | 			} | ||||||
| 			return nil | 			return nil | ||||||
| @@ -96,6 +89,7 @@ func InfoCmd() *cli.Command { | |||||||
| 				New(ctx). | 				New(ctx). | ||||||
| 				WithConfig(). | 				WithConfig(). | ||||||
| 				WithDB(). | 				WithDB(). | ||||||
|  | 				WithDistroInfo(). | ||||||
| 				WithRepos(). | 				WithRepos(). | ||||||
| 				Build() | 				Build() | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| @@ -127,34 +121,27 @@ func InfoCmd() *cli.Command { | |||||||
| 				systemLang = "en" | 				systemLang = "en" | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			if !all { | 			info, err := distro.ParseOSRelease(ctx) | ||||||
| 				info, err := distro.ParseOSRelease(ctx) | 			if err != nil { | ||||||
| 				if err != nil { | 				return cliutils.FormatCliExit(gotext.Get("Error parsing os-release file"), err) | ||||||
| 					return cliutils.FormatCliExit(gotext.Get("Error parsing os-release file"), err) | 			} | ||||||
| 				} | 			names, err = overrides.Resolve( | ||||||
| 				names, err = overrides.Resolve( | 				info, | ||||||
| 					info, | 				overrides.DefaultOpts. | ||||||
| 					overrides.DefaultOpts. | 					WithLanguages([]string{systemLang}), | ||||||
| 						WithLanguages([]string{systemLang}), | 			) | ||||||
| 				) | 			if err != nil { | ||||||
| 				if err != nil { | 				return cliutils.FormatCliExit(gotext.Get("Error resolving overrides"), err) | ||||||
| 					return cliutils.FormatCliExit(gotext.Get("Error resolving overrides"), err) |  | ||||||
| 				} |  | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			for _, pkg := range pkgs { | 			for _, pkg := range pkgs { | ||||||
| 				if !all { | 				alrsh.ResolvePackage(&pkg, names) | ||||||
| 					err = yaml.NewEncoder(os.Stdout).Encode(overrides.ResolvePackage(&pkg, names)) | 				view := alrsh.NewPackageView(pkg) | ||||||
| 					if err != nil { | 				view.Resolved = !all | ||||||
| 						return cliutils.FormatCliExit(gotext.Get("Error encoding script variables"), err) | 				err = yaml.NewEncoder(os.Stdout, yaml.UseJSONMarshaler(), yaml.OmitEmpty()).Encode(view) | ||||||
| 					} | 				if err != nil { | ||||||
| 				} else { | 					return cliutils.FormatCliExit(gotext.Get("Error encoding script variables"), err) | ||||||
| 					err = yaml.NewEncoder(os.Stdout).Encode(pkg) |  | ||||||
| 					if err != nil { |  | ||||||
| 						return cliutils.FormatCliExit(gotext.Get("Error encoding script variables"), err) |  | ||||||
| 					} |  | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 				fmt.Println("---") | 				fmt.Println("---") | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										28
									
								
								install.go
									
									
									
									
									
								
							
							
						
						
									
										28
									
								
								install.go
									
									
									
									
									
								
							| @@ -25,13 +25,12 @@ import ( | |||||||
| 	"github.com/leonelquinteros/gotext" | 	"github.com/leonelquinteros/gotext" | ||||||
| 	"github.com/urfave/cli/v2" | 	"github.com/urfave/cli/v2" | ||||||
|  |  | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/build" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils" | ||||||
| 	appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder" | 	appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder" | ||||||
| 	database "gitea.plemya-x.ru/Plemya-x/ALR/internal/db" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/manager" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/types" |  | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/build" | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager" |  | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func InstallCmd() *cli.Command { | func InstallCmd() *cli.Command { | ||||||
| @@ -98,7 +97,7 @@ func InstallCmd() *cli.Command { | |||||||
| 				return err | 				return err | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			err = builder.InstallPkgs( | 			_, err = builder.InstallPkgs( | ||||||
| 				ctx, | 				ctx, | ||||||
| 				&build.BuildArgs{ | 				&build.BuildArgs{ | ||||||
| 					Opts: &types.BuildOpts{ | 					Opts: &types.BuildOpts{ | ||||||
| @@ -136,15 +135,8 @@ func InstallCmd() *cli.Command { | |||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				return cliutils.FormatCliExit(gotext.Get("Error getting packages"), err) | 				return cliutils.FormatCliExit(gotext.Get("Error getting packages"), err) | ||||||
| 			} | 			} | ||||||
| 			defer result.Close() |  | ||||||
|  |  | ||||||
| 			for result.Next() { |  | ||||||
| 				var pkg database.Package |  | ||||||
| 				err = result.StructScan(&pkg) |  | ||||||
| 				if err != nil { |  | ||||||
| 					return cliutils.FormatCliExit(gotext.Get("Error iterating over packages"), err) |  | ||||||
| 				} |  | ||||||
|  |  | ||||||
|  | 			for _, pkg := range result { | ||||||
| 				fmt.Println(pkg.Name) | 				fmt.Println(pkg.Name) | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| @@ -190,20 +182,12 @@ func RemoveCmd() *cli.Command { | |||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				return cliutils.FormatCliExit(gotext.Get("Error getting packages"), err) | 				return cliutils.FormatCliExit(gotext.Get("Error getting packages"), err) | ||||||
| 			} | 			} | ||||||
| 			defer result.Close() |  | ||||||
|  |  | ||||||
| 			for result.Next() { |  | ||||||
| 				var pkg database.Package |  | ||||||
| 				err = result.StructScan(&pkg) |  | ||||||
| 				if err != nil { |  | ||||||
| 					return cliutils.FormatCliExit(gotext.Get("Error iterating over packages"), err) |  | ||||||
| 				} |  | ||||||
|  |  | ||||||
|  | 			for _, pkg := range result { | ||||||
| 				_, ok := installedAlrPackages[fmt.Sprintf("%s/%s", pkg.Repository, pkg.Name)] | 				_, ok := installedAlrPackages[fmt.Sprintf("%s/%s", pkg.Repository, pkg.Name)] | ||||||
| 				if !ok { | 				if !ok { | ||||||
| 					continue | 					continue | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 				fmt.Println(pkg.Name) | 				fmt.Println(pkg.Name) | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -32,14 +32,14 @@ import ( | |||||||
| 	"github.com/leonelquinteros/gotext" | 	"github.com/leonelquinteros/gotext" | ||||||
| 	"github.com/urfave/cli/v2" | 	"github.com/urfave/cli/v2" | ||||||
|  |  | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/build" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils" | ||||||
| 	appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder" | 	appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/config" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/config" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/constants" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/constants" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/logger" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/logger" | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/manager" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/build" |  | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager" |  | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func InternalBuildCmd() *cli.Command { | func InternalBuildCmd() *cli.Command { | ||||||
|   | |||||||
| @@ -24,18 +24,17 @@ import ( | |||||||
| 	"context" | 	"context" | ||||||
| 	"encoding/gob" | 	"encoding/gob" | ||||||
| 	"errors" | 	"errors" | ||||||
|  | 	"fmt" | ||||||
| 	"log/slog" | 	"log/slog" | ||||||
| 
 | 
 | ||||||
| 	"github.com/leonelquinteros/gotext" | 	"github.com/leonelquinteros/gotext" | ||||||
| 	"mvdan.cc/sh/v3/syntax" |  | ||||||
| 	"mvdan.cc/sh/v3/syntax/typedjson" |  | ||||||
| 
 | 
 | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils" | 	"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/config" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/db" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/manager" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/types" | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro" | 	"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/types" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| type BuildInput struct { | type BuildInput struct { | ||||||
| @@ -133,54 +132,33 @@ type RepositoryProvider interface { | |||||||
| 
 | 
 | ||||||
| // ================================================ | // ================================================ | ||||||
| 
 | 
 | ||||||
| type ScriptFile struct { | type BuiltDep struct { | ||||||
| 	File *syntax.File | 	Name string | ||||||
| 	Path string | 	Path string | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (s *ScriptFile) GobEncode() ([]byte, error) { | func Map[T, R any](items []T, f func(T) R) []R { | ||||||
| 	var buf bytes.Buffer | 	res := make([]R, len(items)) | ||||||
| 	enc := gob.NewEncoder(&buf) | 	for i, item := range items { | ||||||
| 	if err := enc.Encode(s.Path); err != nil { | 		res[i] = f(item) | ||||||
| 		return nil, err |  | ||||||
| 	} | 	} | ||||||
| 	var fileBuf bytes.Buffer | 	return res | ||||||
| 	if err := typedjson.Encode(&fileBuf, s.File); err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 	fileData := fileBuf.Bytes() |  | ||||||
| 	if err := enc.Encode(fileData); err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 	return buf.Bytes(), nil |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (s *ScriptFile) GobDecode(data []byte) error { | func GetBuiltPaths(deps []*BuiltDep) []string { | ||||||
| 	buf := bytes.NewBuffer(data) | 	return Map(deps, func(dep *BuiltDep) string { | ||||||
| 	dec := gob.NewDecoder(buf) | 		return dep.Path | ||||||
| 	if err := dec.Decode(&s.Path); err != nil { | 	}) | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 	var fileData []byte |  | ||||||
| 	if err := dec.Decode(&fileData); err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 	fileReader := bytes.NewReader(fileData) |  | ||||||
| 	file, err := typedjson.Decode(fileReader) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 	s.File = file.(*syntax.File) |  | ||||||
| 	return nil |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type BuildResult struct { | func GetBuiltName(deps []*BuiltDep) []string { | ||||||
| 	PackagePaths []string | 	return Map(deps, func(dep *BuiltDep) string { | ||||||
| 	PackageNames []string | 		return dep.Name | ||||||
|  | 	}) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type PackageFinder interface { | type PackageFinder interface { | ||||||
| 	FindPkgs(ctx context.Context, pkgs []string) (map[string][]db.Package, []string, error) | 	FindPkgs(ctx context.Context, pkgs []string) (map[string][]alrsh.Package, []string, error) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type Config interface { | type Config interface { | ||||||
| @@ -195,12 +173,12 @@ type FunctionsOutput struct { | |||||||
| // EXECUTORS | // EXECUTORS | ||||||
| 
 | 
 | ||||||
| type ScriptResolverExecutor interface { | type ScriptResolverExecutor interface { | ||||||
| 	ResolveScript(ctx context.Context, pkg *db.Package) *ScriptInfo | 	ResolveScript(ctx context.Context, pkg *alrsh.Package) *ScriptInfo | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type ScriptExecutor interface { | type ScriptExecutor interface { | ||||||
| 	ReadScript(ctx context.Context, scriptPath string) (*ScriptFile, error) | 	ReadScript(ctx context.Context, scriptPath string) (*alrsh.ScriptFile, error) | ||||||
| 	ExecuteFirstPass(ctx context.Context, input *BuildInput, sf *ScriptFile) (string, []*types.BuildVars, error) | 	ExecuteFirstPass(ctx context.Context, input *BuildInput, sf *alrsh.ScriptFile) (string, []*alrsh.Package, error) | ||||||
| 	PrepareDirs( | 	PrepareDirs( | ||||||
| 		ctx context.Context, | 		ctx context.Context, | ||||||
| 		input *BuildInput, | 		input *BuildInput, | ||||||
| @@ -209,33 +187,35 @@ type ScriptExecutor interface { | |||||||
| 	ExecuteSecondPass( | 	ExecuteSecondPass( | ||||||
| 		ctx context.Context, | 		ctx context.Context, | ||||||
| 		input *BuildInput, | 		input *BuildInput, | ||||||
| 		sf *ScriptFile, | 		sf *alrsh.ScriptFile, | ||||||
| 		varsOfPackages []*types.BuildVars, | 		varsOfPackages []*alrsh.Package, | ||||||
| 		repoDeps []string, | 		repoDeps []string, | ||||||
| 		builtNames []string, | 		builtDeps []*BuiltDep, | ||||||
| 		basePkg string, | 		basePkg string, | ||||||
| 	) (*SecondPassResult, error) | 	) ([]*BuiltDep, error) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type CacheExecutor interface { | type CacheExecutor interface { | ||||||
| 	CheckForBuiltPackage(ctx context.Context, input *BuildInput, vars *types.BuildVars) (string, bool, error) | 	CheckForBuiltPackage(ctx context.Context, input *BuildInput, vars *alrsh.Package) (string, bool, error) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type ScriptViewerExecutor interface { | type ScriptViewerExecutor interface { | ||||||
| 	ViewScript(ctx context.Context, input *BuildInput, sf *ScriptFile, basePkg string) error | 	ViewScript(ctx context.Context, input *BuildInput, sf *alrsh.ScriptFile, basePkg string) error | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type CheckerExecutor interface { | type CheckerExecutor interface { | ||||||
| 	PerformChecks( | 	PerformChecks( | ||||||
| 		ctx context.Context, | 		ctx context.Context, | ||||||
| 		input *BuildInput, | 		input *BuildInput, | ||||||
| 		vars *types.BuildVars, | 		vars *alrsh.Package, | ||||||
| 	) (bool, error) | 	) (bool, error) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type InstallerExecutor interface { | type InstallerExecutor interface { | ||||||
| 	InstallLocal(paths []string, opts *manager.Opts) error | 	InstallLocal(paths []string, opts *manager.Opts) error | ||||||
| 	Install(pkgs []string, opts *manager.Opts) error | 	Install(pkgs []string, opts *manager.Opts) error | ||||||
|  | 	Remove(pkgs []string, opts *manager.Opts) error | ||||||
|  | 
 | ||||||
| 	RemoveAlreadyInstalled(pkgs []string) ([]string, error) | 	RemoveAlreadyInstalled(pkgs []string) ([]string, error) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @@ -307,7 +287,7 @@ func (b *BuildArgs) PkgFormat() string { | |||||||
| 
 | 
 | ||||||
| type BuildPackageFromDbArgs struct { | type BuildPackageFromDbArgs struct { | ||||||
| 	BuildArgs | 	BuildArgs | ||||||
| 	Package  *db.Package | 	Package  *alrsh.Package | ||||||
| 	Packages []string | 	Packages []string | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @@ -320,7 +300,7 @@ type BuildPackageFromScriptArgs struct { | |||||||
| func (b *Builder) BuildPackageFromDb( | func (b *Builder) BuildPackageFromDb( | ||||||
| 	ctx context.Context, | 	ctx context.Context, | ||||||
| 	args *BuildPackageFromDbArgs, | 	args *BuildPackageFromDbArgs, | ||||||
| ) (*BuildResult, error) { | ) ([]*BuiltDep, error) { | ||||||
| 	scriptInfo := b.scriptResolver.ResolveScript(ctx, args.Package) | 	scriptInfo := b.scriptResolver.ResolveScript(ctx, args.Package) | ||||||
| 
 | 
 | ||||||
| 	return b.BuildPackage(ctx, &BuildInput{ | 	return b.BuildPackage(ctx, &BuildInput{ | ||||||
| @@ -336,7 +316,7 @@ func (b *Builder) BuildPackageFromDb( | |||||||
| func (b *Builder) BuildPackageFromScript( | func (b *Builder) BuildPackageFromScript( | ||||||
| 	ctx context.Context, | 	ctx context.Context, | ||||||
| 	args *BuildPackageFromScriptArgs, | 	args *BuildPackageFromScriptArgs, | ||||||
| ) (*BuildResult, error) { | ) ([]*BuiltDep, error) { | ||||||
| 	return b.BuildPackage(ctx, &BuildInput{ | 	return b.BuildPackage(ctx, &BuildInput{ | ||||||
| 		script:     args.Script, | 		script:     args.Script, | ||||||
| 		repository: "default", | 		repository: "default", | ||||||
| @@ -350,43 +330,46 @@ func (b *Builder) BuildPackageFromScript( | |||||||
| func (b *Builder) BuildPackage( | func (b *Builder) BuildPackage( | ||||||
| 	ctx context.Context, | 	ctx context.Context, | ||||||
| 	input *BuildInput, | 	input *BuildInput, | ||||||
| ) (*BuildResult, error) { | ) ([]*BuiltDep, error) { | ||||||
| 	scriptPath := input.script | 	scriptPath := input.script | ||||||
| 
 | 
 | ||||||
| 	slog.Debug("ReadScript") | 	slog.Debug("ReadScript") | ||||||
| 	sf, err := b.scriptExecutor.ReadScript(ctx, scriptPath) | 	sf, err := b.scriptExecutor.ReadScript(ctx, scriptPath) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, fmt.Errorf("failed reading script: %w", err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	slog.Debug("ExecuteFirstPass") | 	slog.Debug("ExecuteFirstPass") | ||||||
| 	basePkg, varsOfPackages, err := b.scriptExecutor.ExecuteFirstPass(ctx, input, sf) | 	basePkg, varsOfPackages, err := b.scriptExecutor.ExecuteFirstPass(ctx, input, sf) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, fmt.Errorf("failed ExecuteFirstPass: %w", err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	builtPaths := make([]string, 0) | 	var builtDeps []*BuiltDep | ||||||
| 
 | 
 | ||||||
| 	if !input.opts.Clean { | 	if !input.opts.Clean { | ||||||
| 		var remainingVars []*types.BuildVars | 		var remainingVars []*alrsh.Package | ||||||
| 		for _, vars := range varsOfPackages { | 		for _, vars := range varsOfPackages { | ||||||
| 			builtPkgPath, ok, err := b.cacheExecutor.CheckForBuiltPackage(ctx, input, vars) | 			builtPkgPath, ok, err := b.cacheExecutor.CheckForBuiltPackage(ctx, input, vars) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				return nil, err | 				return nil, err | ||||||
| 			} | 			} | ||||||
| 			if ok { | 			if ok { | ||||||
| 				builtPaths = append(builtPaths, builtPkgPath) | 				builtDeps = append(builtDeps, &BuiltDep{ | ||||||
|  | 					Path: builtPkgPath, | ||||||
|  | 				}) | ||||||
| 			} else { | 			} else { | ||||||
| 				remainingVars = append(remainingVars, vars) | 				remainingVars = append(remainingVars, vars) | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		if len(remainingVars) == 0 { | 		if len(remainingVars) == 0 { | ||||||
| 			return &BuildResult{builtPaths, nil}, nil | 			return builtDeps, nil | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	slog.Debug("ViewScript") | 	slog.Debug("ViewScript") | ||||||
|  | 	slog.Debug("", "varsOfPackages", varsOfPackages[0]) | ||||||
| 	err = b.scriptViewerExecutor.ViewScript(ctx, input, sf, basePkg) | 	err = b.scriptViewerExecutor.ViewScript(ctx, input, sf, basePkg) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| @@ -410,11 +393,11 @@ func (b *Builder) BuildPackage( | |||||||
| 	sources := []string{} | 	sources := []string{} | ||||||
| 	checksums := []string{} | 	checksums := []string{} | ||||||
| 	for _, vars := range varsOfPackages { | 	for _, vars := range varsOfPackages { | ||||||
| 		buildDepends = append(buildDepends, vars.BuildDepends...) | 		buildDepends = append(buildDepends, vars.BuildDepends.Resolved()...) | ||||||
| 		optDepends = append(optDepends, vars.OptDepends...) | 		optDepends = append(optDepends, vars.OptDepends.Resolved()...) | ||||||
| 		depends = append(depends, vars.Depends...) | 		depends = append(depends, vars.Depends.Resolved()...) | ||||||
| 		sources = append(sources, vars.Sources...) | 		sources = append(sources, vars.Sources.Resolved()...) | ||||||
| 		checksums = append(checksums, vars.Checksums...) | 		checksums = append(checksums, vars.Checksums.Resolved()...) | ||||||
| 	} | 	} | ||||||
| 	buildDepends = removeDuplicates(buildDepends) | 	buildDepends = removeDuplicates(buildDepends) | ||||||
| 	optDepends = removeDuplicates(optDepends) | 	optDepends = removeDuplicates(optDepends) | ||||||
| @@ -427,19 +410,32 @@ func (b *Builder) BuildPackage( | |||||||
| 	sources, checksums = removeDuplicatesSources(sources, checksums) | 	sources, checksums = removeDuplicatesSources(sources, checksums) | ||||||
| 
 | 
 | ||||||
| 	slog.Debug("installBuildDeps") | 	slog.Debug("installBuildDeps") | ||||||
| 	err = b.installBuildDeps(ctx, input, buildDepends) | 	alrBuildDeps, installedBuildDeps, err := b.installBuildDeps(ctx, input, buildDepends) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	slog.Debug("installOptDeps") | 	slog.Debug("installOptDeps") | ||||||
| 	err = b.installOptDeps(ctx, input, optDepends) | 	_, err = b.installOptDeps(ctx, input, optDepends) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	depNames := make(map[string]struct{}) | ||||||
|  | 	for _, dep := range alrBuildDeps { | ||||||
|  | 		depNames[dep.Name] = struct{}{} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// We filter so as not to re-build what has already been built at the `installBuildDeps` stage. | ||||||
|  | 	var filteredDepends []string | ||||||
|  | 	for _, d := range depends { | ||||||
|  | 		if _, found := depNames[d]; !found { | ||||||
|  | 			filteredDepends = append(filteredDepends, d) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	slog.Debug("BuildALRDeps") | 	slog.Debug("BuildALRDeps") | ||||||
| 	builtPaths, builtNames, repoDeps, err := b.BuildALRDeps(ctx, input, depends) | 	newBuiltDeps, repoDeps, err := b.BuildALRDeps(ctx, input, filteredDepends) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| @@ -450,8 +446,6 @@ func (b *Builder) BuildPackage( | |||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// builtPaths = append(builtPaths, newBuildPaths...) |  | ||||||
| 
 |  | ||||||
| 	slog.Info(gotext.Get("Downloading sources")) | 	slog.Info(gotext.Get("Downloading sources")) | ||||||
| 	slog.Debug("DownloadSources") | 	slog.Debug("DownloadSources") | ||||||
| 	err = b.sourceExecutor.DownloadSources( | 	err = b.sourceExecutor.DownloadSources( | ||||||
| @@ -467,6 +461,8 @@ func (b *Builder) BuildPackage( | |||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	builtDeps = removeDuplicates(append(builtDeps, newBuiltDeps...)) | ||||||
|  | 
 | ||||||
| 	slog.Debug("ExecuteSecondPass") | 	slog.Debug("ExecuteSecondPass") | ||||||
| 	res, err := b.scriptExecutor.ExecuteSecondPass( | 	res, err := b.scriptExecutor.ExecuteSecondPass( | ||||||
| 		ctx, | 		ctx, | ||||||
| @@ -474,25 +470,51 @@ func (b *Builder) BuildPackage( | |||||||
| 		sf, | 		sf, | ||||||
| 		varsOfPackages, | 		varsOfPackages, | ||||||
| 		repoDeps, | 		repoDeps, | ||||||
| 		builtNames, | 		builtDeps, | ||||||
| 		basePkg, | 		basePkg, | ||||||
| 	) | 	) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	pkgPaths := removeDuplicates(append(builtPaths, res.BuiltPaths...)) | 	builtDeps = removeDuplicates(append(builtDeps, res...)) | ||||||
| 	pkgNames := removeDuplicates(append(builtNames, res.BuiltNames...)) |  | ||||||
| 
 | 
 | ||||||
| 	return &BuildResult{ | 	err = b.removeBuildDeps(ctx, input, installedBuildDeps) | ||||||
| 		PackagePaths: pkgPaths, | 	if err != nil { | ||||||
| 		PackageNames: pkgNames, | 		return nil, err | ||||||
| 	}, nil | 	} | ||||||
|  | 
 | ||||||
|  | 	return builtDeps, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (b *Builder) removeBuildDeps(ctx context.Context, input interface { | ||||||
|  | 	BuildOptsProvider | ||||||
|  | }, deps []string, | ||||||
|  | ) error { | ||||||
|  | 	if len(deps) > 0 { | ||||||
|  | 		remove, err := cliutils.YesNoPrompt(ctx, gotext.Get("Would you like to remove the build dependencies?"), input.BuildOpts().Interactive, false) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if remove { | ||||||
|  | 			err = b.installerExecutor.Remove( | ||||||
|  | 				deps, | ||||||
|  | 				&manager.Opts{ | ||||||
|  | 					NoConfirm: !input.BuildOpts().Interactive, | ||||||
|  | 				}, | ||||||
|  | 			) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type InstallPkgsArgs struct { | type InstallPkgsArgs struct { | ||||||
| 	BuildArgs | 	BuildArgs | ||||||
| 	AlrPkgs    []db.Package | 	AlrPkgs    []alrsh.Package | ||||||
| 	NativePkgs []string | 	NativePkgs []string | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @@ -503,7 +525,7 @@ func (b *Builder) InstallALRPackages( | |||||||
| 		BuildOptsProvider | 		BuildOptsProvider | ||||||
| 		PkgFormatProvider | 		PkgFormatProvider | ||||||
| 	}, | 	}, | ||||||
| 	alrPkgs []db.Package, | 	alrPkgs []alrsh.Package, | ||||||
| ) error { | ) error { | ||||||
| 	for _, pkg := range alrPkgs { | 	for _, pkg := range alrPkgs { | ||||||
| 		res, err := b.BuildPackageFromDb( | 		res, err := b.BuildPackageFromDb( | ||||||
| @@ -523,7 +545,7 @@ func (b *Builder) InstallALRPackages( | |||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		err = b.installerExecutor.InstallLocal( | 		err = b.installerExecutor.InstallLocal( | ||||||
| 			res.PackagePaths, | 			GetBuiltPaths(res), | ||||||
| 			&manager.Opts{ | 			&manager.Opts{ | ||||||
| 				NoConfirm: !input.BuildOpts().Interactive, | 				NoConfirm: !input.BuildOpts().Interactive, | ||||||
| 			}, | 			}, | ||||||
| @@ -544,13 +566,13 @@ func (b *Builder) BuildALRDeps( | |||||||
| 		PkgFormatProvider | 		PkgFormatProvider | ||||||
| 	}, | 	}, | ||||||
| 	depends []string, | 	depends []string, | ||||||
| ) (builtPaths, builtNames, repoDeps []string, err error) { | ) (buildDeps []*BuiltDep, repoDeps []string, err error) { | ||||||
| 	if len(depends) > 0 { | 	if len(depends) > 0 { | ||||||
| 		slog.Info(gotext.Get("Installing dependencies")) | 		slog.Info(gotext.Get("Installing dependencies")) | ||||||
| 
 | 
 | ||||||
| 		found, notFound, err := b.repos.FindPkgs(ctx, depends) // Поиск зависимостей | 		found, notFound, err := b.repos.FindPkgs(ctx, depends) // Поиск зависимостей | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return nil, nil, nil, err | 			return nil, nil, fmt.Errorf("failed FindPkgs: %w", err) | ||||||
| 		} | 		} | ||||||
| 		repoDeps = notFound | 		repoDeps = notFound | ||||||
| 
 | 
 | ||||||
| @@ -562,7 +584,7 @@ func (b *Builder) BuildALRDeps( | |||||||
| 			input.BuildOpts().Interactive, | 			input.BuildOpts().Interactive, | ||||||
| 		) | 		) | ||||||
| 		type item struct { | 		type item struct { | ||||||
| 			pkg      *db.Package | 			pkg      *alrsh.Package | ||||||
| 			packages []string | 			packages []string | ||||||
| 		} | 		} | ||||||
| 		pkgsMap := make(map[string]*item) | 		pkgsMap := make(map[string]*item) | ||||||
| @@ -597,20 +619,17 @@ func (b *Builder) BuildALRDeps( | |||||||
| 				}, | 				}, | ||||||
| 			) | 			) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				return nil, nil, nil, err | 				return nil, nil, fmt.Errorf("failed build package from db: %w", err) | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			builtPaths = append(builtPaths, res.PackagePaths...) | 			buildDeps = append(buildDeps, res...) | ||||||
| 			builtNames = append(builtNames, res.PackageNames...) |  | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// Удаляем возможные дубликаты, которые могут быть введены, если |  | ||||||
| 	// несколько зависимостей зависят от одних и тех же пакетов. |  | ||||||
| 	repoDeps = removeDuplicates(repoDeps) | 	repoDeps = removeDuplicates(repoDeps) | ||||||
| 	builtPaths = removeDuplicates(builtPaths) | 	buildDeps = removeDuplicates(buildDeps) | ||||||
| 	builtNames = removeDuplicates(builtNames) | 
 | ||||||
| 	return builtPaths, builtNames, repoDeps, nil | 	return buildDeps, repoDeps, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (i *Builder) installBuildDeps( | func (i *Builder) installBuildDeps( | ||||||
| @@ -621,19 +640,22 @@ func (i *Builder) installBuildDeps( | |||||||
| 		PkgFormatProvider | 		PkgFormatProvider | ||||||
| 	}, | 	}, | ||||||
| 	pkgs []string, | 	pkgs []string, | ||||||
| ) error { | ) ([]*BuiltDep, []string, error) { | ||||||
|  | 	var builtDeps []*BuiltDep | ||||||
|  | 	var deps []string | ||||||
|  | 	var err error | ||||||
| 	if len(pkgs) > 0 { | 	if len(pkgs) > 0 { | ||||||
| 		deps, err := i.installerExecutor.RemoveAlreadyInstalled(pkgs) | 		deps, err = i.installerExecutor.RemoveAlreadyInstalled(pkgs) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return err | 			return nil, nil, err | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		err = i.InstallPkgs(ctx, input, deps) // Устанавливаем выбранные пакеты | 		builtDeps, err = i.InstallPkgs(ctx, input, deps) // Устанавливаем выбранные пакеты | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return err | 			return nil, nil, err | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	return nil | 	return builtDeps, deps, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (i *Builder) installOptDeps( | func (i *Builder) installOptDeps( | ||||||
| @@ -644,10 +666,11 @@ func (i *Builder) installOptDeps( | |||||||
| 		PkgFormatProvider | 		PkgFormatProvider | ||||||
| 	}, | 	}, | ||||||
| 	pkgs []string, | 	pkgs []string, | ||||||
| ) error { | ) ([]*BuiltDep, error) { | ||||||
|  | 	var builtDeps []*BuiltDep | ||||||
| 	optDeps, err := i.installerExecutor.RemoveAlreadyInstalled(pkgs) | 	optDeps, err := i.installerExecutor.RemoveAlreadyInstalled(pkgs) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 	if len(optDeps) > 0 { | 	if len(optDeps) > 0 { | ||||||
| 		optDeps, err := cliutils.ChooseOptDepends( | 		optDeps, err := cliutils.ChooseOptDepends( | ||||||
| @@ -657,19 +680,19 @@ func (i *Builder) installOptDeps( | |||||||
| 			input.BuildOpts().Interactive, | 			input.BuildOpts().Interactive, | ||||||
| 		) // Пользователя просят выбрать опциональные зависимости | 		) // Пользователя просят выбрать опциональные зависимости | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return err | 			return nil, err | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		if len(optDeps) == 0 { | 		if len(optDeps) == 0 { | ||||||
| 			return nil | 			return builtDeps, nil | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		err = i.InstallPkgs(ctx, input, optDeps) // Устанавливаем выбранные пакеты | 		builtDeps, err = i.InstallPkgs(ctx, input, optDeps) // Устанавливаем выбранные пакеты | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return err | 			return nil, err | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	return nil | 	return builtDeps, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (i *Builder) InstallPkgs( | func (i *Builder) InstallPkgs( | ||||||
| @@ -680,18 +703,18 @@ func (i *Builder) InstallPkgs( | |||||||
| 		PkgFormatProvider | 		PkgFormatProvider | ||||||
| 	}, | 	}, | ||||||
| 	pkgs []string, | 	pkgs []string, | ||||||
| ) error { | ) ([]*BuiltDep, error) { | ||||||
| 	builtPaths, _, repoDeps, err := i.BuildALRDeps(ctx, input, pkgs) | 	builtDeps, repoDeps, err := i.BuildALRDeps(ctx, input, pkgs) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if len(builtPaths) > 0 { | 	if len(builtDeps) > 0 { | ||||||
| 		err = i.installerExecutor.InstallLocal(builtPaths, &manager.Opts{ | 		err = i.installerExecutor.InstallLocal(GetBuiltPaths(builtDeps), &manager.Opts{ | ||||||
| 			NoConfirm: !input.BuildOpts().Interactive, | 			NoConfirm: !input.BuildOpts().Interactive, | ||||||
| 		}) | 		}) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return err | 			return nil, err | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| @@ -700,9 +723,9 @@ func (i *Builder) InstallPkgs( | |||||||
| 			NoConfirm: !input.BuildOpts().Interactive, | 			NoConfirm: !input.BuildOpts().Interactive, | ||||||
| 		}) | 		}) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return err | 			return nil, err | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	return nil | 	return builtDeps, nil | ||||||
| } | } | ||||||
| @@ -28,9 +28,9 @@ import ( | |||||||
| 
 | 
 | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/config" | 	"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/db" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/types" | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro" | 	"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/internal/manager" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| type TestPackageFinder struct { | type TestPackageFinder struct { | ||||||
| @@ -23,7 +23,7 @@ import ( | |||||||
| 
 | 
 | ||||||
| 	"github.com/goreleaser/nfpm/v2" | 	"github.com/goreleaser/nfpm/v2" | ||||||
| 
 | 
 | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/types" | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| type Cache struct { | type Cache struct { | ||||||
| @@ -33,7 +33,7 @@ type Cache struct { | |||||||
| func (c *Cache) CheckForBuiltPackage( | func (c *Cache) CheckForBuiltPackage( | ||||||
| 	ctx context.Context, | 	ctx context.Context, | ||||||
| 	input *BuildInput, | 	input *BuildInput, | ||||||
| 	vars *types.BuildVars, | 	vars *alrsh.Package, | ||||||
| ) (string, bool, error) { | ) (string, bool, error) { | ||||||
| 	filename, err := pkgFileName(input, vars) | 	filename, err := pkgFileName(input, vars) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| @@ -56,7 +56,7 @@ func pkgFileName( | |||||||
| 		PkgFormatProvider | 		PkgFormatProvider | ||||||
| 		RepositoryProvider | 		RepositoryProvider | ||||||
| 	}, | 	}, | ||||||
| 	vars *types.BuildVars, | 	vars *alrsh.Package, | ||||||
| ) (string, error) { | ) (string, error) { | ||||||
| 	pkgInfo := getBasePkgInfo(vars, input) | 	pkgInfo := getBasePkgInfo(vars, input) | ||||||
| 
 | 
 | ||||||
| @@ -24,8 +24,8 @@ import ( | |||||||
| 
 | 
 | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/cpu" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/cpu" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/types" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/manager" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager" | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| type Checker struct { | type Checker struct { | ||||||
| @@ -35,7 +35,7 @@ type Checker struct { | |||||||
| func (c *Checker) PerformChecks( | func (c *Checker) PerformChecks( | ||||||
| 	ctx context.Context, | 	ctx context.Context, | ||||||
| 	input *BuildInput, | 	input *BuildInput, | ||||||
| 	vars *types.BuildVars, | 	vars *alrsh.Package, | ||||||
| ) (bool, error) { | ) (bool, error) { | ||||||
| 	if !cpu.IsCompatibleWith(cpu.Arch(), vars.Architectures) { // Проверяем совместимость архитектуры | 	if !cpu.IsCompatibleWith(cpu.Arch(), vars.Architectures) { // Проверяем совместимость архитектуры | ||||||
| 		cont, err := cliutils.YesNoPrompt( | 		cont, err := cliutils.YesNoPrompt( | ||||||
| @@ -19,7 +19,7 @@ package build | |||||||
| import ( | import ( | ||||||
| 	"path/filepath" | 	"path/filepath" | ||||||
| 
 | 
 | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/types" | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| type BaseDirProvider interface { | type BaseDirProvider interface { | ||||||
| @@ -27,7 +27,7 @@ import ( | |||||||
| 	"github.com/goreleaser/nfpm/v2" | 	"github.com/goreleaser/nfpm/v2" | ||||||
| 	"github.com/leonelquinteros/gotext" | 	"github.com/leonelquinteros/gotext" | ||||||
| 
 | 
 | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/types" | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func rpmFindDependenciesALTLinux(ctx context.Context, pkgInfo *nfpm.Info, dirs types.Directories, command string, envs []string, updateFunc func(string)) error { | func rpmFindDependenciesALTLinux(ctx context.Context, pkgInfo *nfpm.Info, dirs types.Directories, command string, envs []string, updateFunc func(string)) error { | ||||||
| @@ -23,7 +23,7 @@ import ( | |||||||
| 	"github.com/goreleaser/nfpm/v2" | 	"github.com/goreleaser/nfpm/v2" | ||||||
| 	"github.com/leonelquinteros/gotext" | 	"github.com/leonelquinteros/gotext" | ||||||
| 
 | 
 | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/types" | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| type EmptyFindProvReq struct{} | type EmptyFindProvReq struct{} | ||||||
| @@ -28,7 +28,7 @@ import ( | |||||||
| 	"github.com/goreleaser/nfpm/v2" | 	"github.com/goreleaser/nfpm/v2" | ||||||
| 	"github.com/leonelquinteros/gotext" | 	"github.com/leonelquinteros/gotext" | ||||||
| 
 | 
 | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/types" | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| type FedoraFindProvReq struct{} | type FedoraFindProvReq struct{} | ||||||
| @@ -21,8 +21,8 @@ import ( | |||||||
| 
 | 
 | ||||||
| 	"github.com/goreleaser/nfpm/v2" | 	"github.com/goreleaser/nfpm/v2" | ||||||
| 
 | 
 | ||||||
| 	"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/distro" | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| type ProvReqFinder interface { | type ProvReqFinder interface { | ||||||
							
								
								
									
										325
									
								
								internal/build/firejail.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										325
									
								
								internal/build/firejail.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,325 @@ | |||||||
|  | // ALR - Any Linux Repository | ||||||
|  | // Copyright (C) 2025 The ALR Authors | ||||||
|  | // | ||||||
|  | // 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 ( | ||||||
|  | 	"errors" | ||||||
|  | 	"fmt" | ||||||
|  | 	"io" | ||||||
|  | 	"log/slog" | ||||||
|  | 	"os" | ||||||
|  | 	"path/filepath" | ||||||
|  | 	"strings" | ||||||
|  |  | ||||||
|  | 	"github.com/goreleaser/nfpm/v2/files" | ||||||
|  | 	"github.com/leonelquinteros/gotext" | ||||||
|  |  | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh" | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | const ( | ||||||
|  | 	firejailedDir     = "/usr/lib/alr/firejailed" | ||||||
|  | 	defaultDirMode    = 0o755 | ||||||
|  | 	defaultScriptMode = 0o755 | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | var ( | ||||||
|  | 	ErrInvalidDestination = errors.New("invalid destination path") | ||||||
|  | 	ErrMissingProfile     = errors.New("default profile is missing") | ||||||
|  | 	ErrEmptyPackageName   = errors.New("package name cannot be empty") | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | var binaryDirectories = []string{ | ||||||
|  | 	"/usr/bin/", | ||||||
|  | 	"/bin/", | ||||||
|  | 	"/usr/local/bin/", | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func moveWithSymlinkHandling(src, dst string) error { | ||||||
|  | 	srcInfo, err := os.Lstat(src) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return fmt.Errorf("failed to get source info: %w", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if err := os.MkdirAll(filepath.Dir(dst), 0o755); err != nil { | ||||||
|  | 		return fmt.Errorf("failed to create destination directory: %w", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if srcInfo.Mode()&os.ModeSymlink != 0 { | ||||||
|  | 		return moveSymlink(src, dst) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if err := os.Rename(src, dst); err != nil { | ||||||
|  | 		return copyAndRemove(src, dst) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func moveSymlink(src, dst string) error { | ||||||
|  | 	target, err := os.Readlink(src) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return fmt.Errorf("failed to read symlink: %w", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if err := os.Symlink(target, dst); err != nil { | ||||||
|  | 		return fmt.Errorf("failed to create symlink: %w", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if err := os.Remove(src); err != nil { | ||||||
|  | 		os.Remove(dst) | ||||||
|  | 		return fmt.Errorf("failed to remove original symlink: %w", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func copyAndRemove(src, dst string) error { | ||||||
|  | 	srcFile, err := os.Open(src) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return fmt.Errorf("failed to open source: %w", err) | ||||||
|  | 	} | ||||||
|  | 	defer srcFile.Close() | ||||||
|  |  | ||||||
|  | 	dstFile, err := os.Create(dst) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return fmt.Errorf("failed to create destination: %w", err) | ||||||
|  | 	} | ||||||
|  | 	defer dstFile.Close() | ||||||
|  |  | ||||||
|  | 	if _, err := io.Copy(dstFile, srcFile); err != nil { | ||||||
|  | 		return fmt.Errorf("failed to copy content: %w", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	srcInfo, err := srcFile.Stat() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return fmt.Errorf("failed to get source stats: %w", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if err := dstFile.Chmod(srcInfo.Mode()); err != nil { | ||||||
|  | 		return fmt.Errorf("failed to set permissions: %w", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if err := os.Remove(src); err != nil { | ||||||
|  | 		return fmt.Errorf("failed to remove source: %w", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func moveFileWithErrorHandling(src, dst string) error { | ||||||
|  | 	err := moveWithSymlinkHandling(src, dst) | ||||||
|  | 	if err != nil { | ||||||
|  | 		if os.IsPermission(err) { | ||||||
|  | 			return fmt.Errorf("permission denied: %w", err) | ||||||
|  | 		} | ||||||
|  | 		if os.IsNotExist(err) { | ||||||
|  | 			return fmt.Errorf("source file does not exist: %w", err) | ||||||
|  | 		} | ||||||
|  | 		return fmt.Errorf("failed to move file: %w", err) | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func applyFirejailIntegration( | ||||||
|  | 	vars *alrsh.Package, | ||||||
|  | 	dirs types.Directories, | ||||||
|  | 	contents []*files.Content, | ||||||
|  | ) ([]*files.Content, error) { | ||||||
|  | 	slog.Info(gotext.Get("Applying FireJail integration"), "package", vars.Name) | ||||||
|  |  | ||||||
|  | 	if err := createFirejailedDirectory(dirs.PkgDir); err != nil { | ||||||
|  | 		return nil, fmt.Errorf("failed to create firejailed directory: %w", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	newContents, err := processBinaryFiles(vars, contents, dirs) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, fmt.Errorf("failed to process binary files: %w", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return append(contents, newContents...), nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func createFirejailedDirectory(pkgDir string) error { | ||||||
|  | 	firejailedPath := filepath.Join(pkgDir, firejailedDir) | ||||||
|  | 	return os.MkdirAll(firejailedPath, defaultDirMode) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func processBinaryFiles(pkg *alrsh.Package, contents []*files.Content, dirs types.Directories) ([]*files.Content, error) { | ||||||
|  | 	var newContents []*files.Content | ||||||
|  |  | ||||||
|  | 	for _, content := range contents { | ||||||
|  | 		if content.Type == "dir" { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if !isBinaryFile(content.Destination) { | ||||||
|  | 			slog.Debug("content not binary file", "content", content) | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		slog.Debug("process content", "content", content) | ||||||
|  |  | ||||||
|  | 		newContent, err := createFirejailedBinary(pkg, content, dirs) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, fmt.Errorf("failed to create firejailed binary for %s: %w", content.Destination, err) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if newContent != nil { | ||||||
|  | 			newContents = append(newContents, newContent...) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return newContents, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func isBinaryFile(destination string) bool { | ||||||
|  | 	for _, binDir := range binaryDirectories { | ||||||
|  | 		if strings.HasPrefix(destination, binDir) { | ||||||
|  | 			return true | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return false | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func createFirejailedBinary( | ||||||
|  | 	pkg *alrsh.Package, | ||||||
|  | 	content *files.Content, | ||||||
|  | 	dirs types.Directories, | ||||||
|  | ) ([]*files.Content, error) { | ||||||
|  | 	origFilePath, err := generateFirejailedPath(content.Destination) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	profiles := pkg.FireJailProfiles.Resolved() | ||||||
|  | 	sourceProfilePath, ok := profiles[content.Destination] | ||||||
|  |  | ||||||
|  | 	if !ok { | ||||||
|  | 		sourceProfilePath, ok = profiles["default"] | ||||||
|  | 		if !ok { | ||||||
|  | 			return nil, errors.New("default profile is missing") | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	sourceProfilePath = filepath.Join(dirs.ScriptDir, sourceProfilePath) | ||||||
|  | 	dest, err := createFirejailProfilePath(content.Destination) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	err = createProfile(filepath.Join(dirs.PkgDir, dest), sourceProfilePath) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if err := moveFileWithErrorHandling(filepath.Join(dirs.PkgDir, content.Destination), filepath.Join(dirs.PkgDir, origFilePath)); err != nil { | ||||||
|  | 		return nil, fmt.Errorf("failed to move original binary: %w", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	content.Type = "file" | ||||||
|  | 	content.Source = filepath.Join(dirs.PkgDir, content.Destination) | ||||||
|  |  | ||||||
|  | 	// Create wrapper script | ||||||
|  | 	if err := createWrapperScript(filepath.Join(dirs.PkgDir, content.Destination), origFilePath, dest); err != nil { | ||||||
|  | 		return nil, fmt.Errorf("failed to create wrapper script: %w", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	profile, err := getContentFromPath(dest, dirs.PkgDir) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	bin, err := getContentFromPath(origFilePath, dirs.PkgDir) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return []*files.Content{ | ||||||
|  | 		bin, | ||||||
|  | 		profile, | ||||||
|  | 	}, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func getContentFromPath(path, base string) (*files.Content, error) { | ||||||
|  | 	absPath := filepath.Join(base, path) | ||||||
|  |  | ||||||
|  | 	fi, err := os.Lstat(absPath) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, fmt.Errorf("failed to get file info: %w", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return &files.Content{ | ||||||
|  | 		Source:      absPath, | ||||||
|  | 		Destination: path, | ||||||
|  | 		FileInfo: &files.ContentFileInfo{ | ||||||
|  | 			MTime: fi.ModTime(), | ||||||
|  | 			Mode:  fi.Mode(), | ||||||
|  | 			Size:  fi.Size(), | ||||||
|  | 		}, | ||||||
|  | 	}, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func generateSafeName(destination string) (string, error) { | ||||||
|  | 	cleanPath := strings.TrimPrefix(destination, ".") | ||||||
|  | 	if cleanPath == "" { | ||||||
|  | 		return "", fmt.Errorf("invalid destination path: %s", destination) | ||||||
|  | 	} | ||||||
|  | 	return strings.ReplaceAll(cleanPath, "/", "_"), nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func generateFirejailedPath(destination string) (string, error) { | ||||||
|  | 	safeName, err := generateSafeName(destination) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return "", err | ||||||
|  | 	} | ||||||
|  | 	return filepath.Join(firejailedDir, safeName), nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func createProfile(destProfilePath, profilePath string) error { | ||||||
|  | 	srcFile, err := os.Open(profilePath) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	defer srcFile.Close() | ||||||
|  |  | ||||||
|  | 	destFile, err := os.Create(destProfilePath) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	defer destFile.Close() | ||||||
|  |  | ||||||
|  | 	_, err = io.Copy(destFile, srcFile) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	return destFile.Sync() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func createWrapperScript(scriptPath, origFilePath, profilePath string) error { | ||||||
|  | 	scriptContent := fmt.Sprintf("#!/bin/bash\nexec firejail --profile=%q %q \"$@\"\n", profilePath, origFilePath) | ||||||
|  | 	return os.WriteFile(scriptPath, []byte(scriptContent), defaultDirMode) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func createFirejailProfilePath(binaryPath string) (string, error) { | ||||||
|  | 	name, err := generateSafeName(binaryPath) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return "", err | ||||||
|  | 	} | ||||||
|  | 	return filepath.Join(firejailedDir, fmt.Sprintf("%s.profile", name)), nil | ||||||
|  | } | ||||||
							
								
								
									
										322
									
								
								internal/build/firejail_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										322
									
								
								internal/build/firejail_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,322 @@ | |||||||
|  | // ALR - Any Linux Repository | ||||||
|  | // Copyright (C) 2025 The ALR Authors | ||||||
|  | // | ||||||
|  | // 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 ( | ||||||
|  | 	"os" | ||||||
|  | 	"path/filepath" | ||||||
|  | 	"testing" | ||||||
|  |  | ||||||
|  | 	"github.com/goreleaser/nfpm/v2/files" | ||||||
|  | 	"github.com/stretchr/testify/assert" | ||||||
|  |  | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh" | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func TestIsBinaryFile(t *testing.T) { | ||||||
|  | 	tests := []struct { | ||||||
|  | 		name        string | ||||||
|  | 		destination string | ||||||
|  | 		expected    bool | ||||||
|  | 	}{ | ||||||
|  | 		{"usr/bin binary", "/usr/bin/test", true}, | ||||||
|  | 		{"bin binary", "/bin/test", true}, | ||||||
|  | 		{"usr/local/bin binary", "/usr/local/bin/test", true}, | ||||||
|  | 		{"lib file", "/usr/lib/test.so", false}, | ||||||
|  | 		{"etc file", "/etc/config", false}, | ||||||
|  | 		{"empty destination", "", false}, | ||||||
|  | 		{"root level file", "./test", false}, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for _, tt := range tests { | ||||||
|  | 		t.Run(tt.name, func(t *testing.T) { | ||||||
|  | 			result := isBinaryFile(tt.destination) | ||||||
|  | 			assert.Equal(t, tt.expected, result) | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestGenerateSafeName(t *testing.T) { | ||||||
|  | 	tests := []struct { | ||||||
|  | 		name        string | ||||||
|  | 		destination string | ||||||
|  | 		expected    string | ||||||
|  | 		expectError bool | ||||||
|  | 	}{ | ||||||
|  | 		{"usr/bin path", "./usr/bin/test", "_usr_bin_test", false}, | ||||||
|  | 		{"bin path", "./bin/test", "_bin_test", false}, | ||||||
|  | 		{"nested path", "./usr/local/bin/app", "_usr_local_bin_app", false}, | ||||||
|  | 		{"path with spaces", "./usr/bin/my app", "_usr_bin_my app", false}, | ||||||
|  | 		{"empty after trim", ".", "", true}, | ||||||
|  | 		{"empty string", "", "", true}, | ||||||
|  | 		{"only dots", "..", ".", false}, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for _, tt := range tests { | ||||||
|  | 		t.Run(tt.name, func(t *testing.T) { | ||||||
|  | 			result, err := generateSafeName(tt.destination) | ||||||
|  | 			if tt.expectError { | ||||||
|  | 				assert.Error(t, err) | ||||||
|  | 			} else { | ||||||
|  | 				assert.NoError(t, err) | ||||||
|  | 				assert.Equal(t, tt.expected, result) | ||||||
|  | 			} | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestCreateWrapperScript(t *testing.T) { | ||||||
|  | 	tests := []struct { | ||||||
|  | 		name            string | ||||||
|  | 		origFilePath    string | ||||||
|  | 		profilePath     string | ||||||
|  | 		expectedContent string | ||||||
|  | 	}{ | ||||||
|  | 		{ | ||||||
|  | 			"basic wrapper", | ||||||
|  | 			"/usr/lib/alr/firejailed/_usr_bin_test", | ||||||
|  | 			"/usr/lib/alr/firejailed/_usr_bin_test.profile", | ||||||
|  | 			"#!/bin/bash\nexec firejail --profile=\"/usr/lib/alr/firejailed/_usr_bin_test.profile\" \"/usr/lib/alr/firejailed/_usr_bin_test\" \"$@\"\n", | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			"path with spaces", | ||||||
|  | 			"/usr/lib/alr/firejailed/_usr_bin_my_app", | ||||||
|  | 			"/usr/lib/alr/firejailed/_usr_bin_my_app.profile", | ||||||
|  | 			"#!/bin/bash\nexec firejail --profile=\"/usr/lib/alr/firejailed/_usr_bin_my_app.profile\" \"/usr/lib/alr/firejailed/_usr_bin_my_app\" \"$@\"\n", | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for _, tt := range tests { | ||||||
|  | 		t.Run(tt.name, func(t *testing.T) { | ||||||
|  | 			tmpDir := t.TempDir() | ||||||
|  | 			scriptPath := filepath.Join(tmpDir, "wrapper.sh") | ||||||
|  |  | ||||||
|  | 			err := createWrapperScript(scriptPath, tt.origFilePath, tt.profilePath) | ||||||
|  |  | ||||||
|  | 			assert.NoError(t, err) | ||||||
|  | 			assert.FileExists(t, scriptPath) | ||||||
|  |  | ||||||
|  | 			content, err := os.ReadFile(scriptPath) | ||||||
|  | 			assert.NoError(t, err) | ||||||
|  | 			assert.Equal(t, tt.expectedContent, string(content)) | ||||||
|  |  | ||||||
|  | 			// Check file permissions | ||||||
|  | 			info, err := os.Stat(scriptPath) | ||||||
|  | 			assert.NoError(t, err) | ||||||
|  | 			assert.Equal(t, os.FileMode(defaultDirMode), info.Mode()) | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestCreateFirejailedBinary(t *testing.T) { | ||||||
|  | 	tests := []struct { | ||||||
|  | 		name        string | ||||||
|  | 		setupFunc   func(string) (*alrsh.Package, *files.Content, types.Directories) | ||||||
|  | 		expectError bool | ||||||
|  | 		errorMsg    string | ||||||
|  | 	}{ | ||||||
|  | 		{ | ||||||
|  | 			"successful creation with default profile", | ||||||
|  | 			func(tmpDir string) (*alrsh.Package, *files.Content, types.Directories) { | ||||||
|  | 				pkgDir := filepath.Join(tmpDir, "pkg") | ||||||
|  | 				scriptDir := filepath.Join(tmpDir, "scripts") | ||||||
|  | 				os.MkdirAll(pkgDir, 0o755) | ||||||
|  | 				os.MkdirAll(scriptDir, 0o755) | ||||||
|  |  | ||||||
|  | 				binDir := filepath.Join(pkgDir, "usr", "bin") | ||||||
|  | 				os.MkdirAll(binDir, 0o755) | ||||||
|  |  | ||||||
|  | 				srcBinary := filepath.Join(binDir, "test-binary") | ||||||
|  | 				os.WriteFile(srcBinary, []byte("#!/bin/bash\necho test"), 0o755) | ||||||
|  |  | ||||||
|  | 				defaultProfile := filepath.Join(scriptDir, "default.profile") | ||||||
|  | 				os.WriteFile(defaultProfile, []byte("include /etc/firejail/default.profile\nnet none"), 0o644) | ||||||
|  |  | ||||||
|  | 				pkg := &alrsh.Package{ | ||||||
|  | 					Name: "test-pkg", | ||||||
|  | 					FireJailProfiles: alrsh.OverridableFromMap(map[string]map[string]string{ | ||||||
|  | 						"": {"default": "default.profile"}, | ||||||
|  | 					}), | ||||||
|  | 				} | ||||||
|  | 				alrsh.ResolvePackage(pkg, []string{""}) | ||||||
|  |  | ||||||
|  | 				content := &files.Content{ | ||||||
|  | 					Source:      srcBinary, | ||||||
|  | 					Destination: "/usr/bin/test-binary", | ||||||
|  | 					Type:        "file", | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				dirs := types.Directories{PkgDir: pkgDir, ScriptDir: scriptDir} | ||||||
|  | 				return pkg, content, dirs | ||||||
|  | 			}, | ||||||
|  | 			false, | ||||||
|  | 			"", | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			"successful creation with specific profile", | ||||||
|  | 			func(tmpDir string) (*alrsh.Package, *files.Content, types.Directories) { | ||||||
|  | 				pkgDir := filepath.Join(tmpDir, "pkg") | ||||||
|  | 				scriptDir := filepath.Join(tmpDir, "scripts") | ||||||
|  | 				os.MkdirAll(pkgDir, 0o755) | ||||||
|  | 				os.MkdirAll(scriptDir, 0o755) | ||||||
|  |  | ||||||
|  | 				binDir := filepath.Join(pkgDir, "usr", "bin") | ||||||
|  | 				os.MkdirAll(binDir, 0o755) | ||||||
|  |  | ||||||
|  | 				srcBinary := filepath.Join(binDir, "special-binary") | ||||||
|  | 				os.WriteFile(srcBinary, []byte("#!/bin/bash\necho special"), 0o755) | ||||||
|  |  | ||||||
|  | 				defaultProfile := filepath.Join(scriptDir, "default.profile") | ||||||
|  | 				os.WriteFile(defaultProfile, []byte("include /etc/firejail/default.profile"), 0o644) | ||||||
|  |  | ||||||
|  | 				specialProfile := filepath.Join(scriptDir, "special.profile") | ||||||
|  | 				os.WriteFile(specialProfile, []byte("include /etc/firejail/default.profile\nnet none\nprivate-tmp"), 0o644) | ||||||
|  |  | ||||||
|  | 				pkg := &alrsh.Package{ | ||||||
|  | 					Name: "test-pkg", | ||||||
|  | 					FireJailProfiles: alrsh.OverridableFromMap(map[string]map[string]string{ | ||||||
|  | 						"": {"default": "default.profile", "/usr/bin/special-binary": "special.profile"}, | ||||||
|  | 					}), | ||||||
|  | 				} | ||||||
|  | 				alrsh.ResolvePackage(pkg, []string{""}) | ||||||
|  |  | ||||||
|  | 				content := &files.Content{ | ||||||
|  | 					Source:      srcBinary, | ||||||
|  | 					Destination: "/usr/bin/special-binary", | ||||||
|  | 					Type:        "file", | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				dirs := types.Directories{PkgDir: pkgDir, ScriptDir: scriptDir} | ||||||
|  | 				return pkg, content, dirs | ||||||
|  | 			}, | ||||||
|  | 			false, | ||||||
|  | 			"", | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			"missing default profile", | ||||||
|  | 			func(tmpDir string) (*alrsh.Package, *files.Content, types.Directories) { | ||||||
|  | 				pkgDir := filepath.Join(tmpDir, "pkg") | ||||||
|  | 				scriptDir := filepath.Join(tmpDir, "scripts") | ||||||
|  | 				os.MkdirAll(pkgDir, 0o755) | ||||||
|  | 				os.MkdirAll(scriptDir, 0o755) | ||||||
|  |  | ||||||
|  | 				srcBinary := filepath.Join(tmpDir, "test-binary") | ||||||
|  | 				os.WriteFile(srcBinary, []byte("#!/bin/bash\necho test"), 0o755) | ||||||
|  |  | ||||||
|  | 				pkg := &alrsh.Package{ | ||||||
|  | 					Name:             "test-pkg", | ||||||
|  | 					FireJailProfiles: alrsh.OverridableFromMap(map[string]map[string]string{"": {}}), | ||||||
|  | 				} | ||||||
|  | 				alrsh.ResolvePackage(pkg, []string{""}) | ||||||
|  |  | ||||||
|  | 				content := &files.Content{Source: srcBinary, Destination: "./usr/bin/test-binary", Type: "file"} | ||||||
|  | 				dirs := types.Directories{PkgDir: pkgDir, ScriptDir: scriptDir} | ||||||
|  | 				return pkg, content, dirs | ||||||
|  | 			}, | ||||||
|  | 			true, | ||||||
|  | 			"default profile is missing", | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			"profile file not found", | ||||||
|  | 			func(tmpDir string) (*alrsh.Package, *files.Content, types.Directories) { | ||||||
|  | 				pkgDir := filepath.Join(tmpDir, "pkg") | ||||||
|  | 				scriptDir := filepath.Join(tmpDir, "scripts") | ||||||
|  | 				os.MkdirAll(pkgDir, 0o755) | ||||||
|  | 				os.MkdirAll(scriptDir, 0o755) | ||||||
|  |  | ||||||
|  | 				srcBinary := filepath.Join(tmpDir, "test-binary") | ||||||
|  | 				os.WriteFile(srcBinary, []byte("#!/bin/bash\necho test"), 0o755) | ||||||
|  |  | ||||||
|  | 				pkg := &alrsh.Package{ | ||||||
|  | 					Name:             "test-pkg", | ||||||
|  | 					FireJailProfiles: alrsh.OverridableFromMap(map[string]map[string]string{"": {"default": "nonexistent.profile"}}), | ||||||
|  | 				} | ||||||
|  | 				alrsh.ResolvePackage(pkg, []string{""}) | ||||||
|  |  | ||||||
|  | 				content := &files.Content{Source: srcBinary, Destination: "./usr/bin/test-binary", Type: "file"} | ||||||
|  | 				dirs := types.Directories{PkgDir: pkgDir, ScriptDir: scriptDir} | ||||||
|  | 				return pkg, content, dirs | ||||||
|  | 			}, | ||||||
|  | 			true, | ||||||
|  | 			"", | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			"invalid destination path", | ||||||
|  | 			func(tmpDir string) (*alrsh.Package, *files.Content, types.Directories) { | ||||||
|  | 				pkgDir := filepath.Join(tmpDir, "pkg") | ||||||
|  | 				scriptDir := filepath.Join(tmpDir, "scripts") | ||||||
|  | 				os.MkdirAll(pkgDir, 0o755) | ||||||
|  | 				os.MkdirAll(scriptDir, 0o755) | ||||||
|  |  | ||||||
|  | 				srcBinary := filepath.Join(tmpDir, "test-binary") | ||||||
|  | 				os.WriteFile(srcBinary, []byte("#!/bin/bash\necho test"), 0o755) | ||||||
|  |  | ||||||
|  | 				defaultProfile := filepath.Join(scriptDir, "default.profile") | ||||||
|  | 				os.WriteFile(defaultProfile, []byte("include /etc/firejail/default.profile"), 0o644) | ||||||
|  |  | ||||||
|  | 				pkg := &alrsh.Package{Name: "test-pkg", FireJailProfiles: alrsh.OverridableFromMap(map[string]map[string]string{"": {"default": "default.profile"}})} | ||||||
|  | 				alrsh.ResolvePackage(pkg, []string{""}) | ||||||
|  |  | ||||||
|  | 				content := &files.Content{Source: srcBinary, Destination: ".", Type: "file"} | ||||||
|  | 				dirs := types.Directories{PkgDir: pkgDir, ScriptDir: scriptDir} | ||||||
|  | 				return pkg, content, dirs | ||||||
|  | 			}, | ||||||
|  | 			true, | ||||||
|  | 			"", | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for _, tt := range tests { | ||||||
|  | 		t.Run(tt.name, func(t *testing.T) { | ||||||
|  | 			tmpDir := t.TempDir() | ||||||
|  | 			pkg, content, dirs := tt.setupFunc(tmpDir) | ||||||
|  |  | ||||||
|  | 			err := createFirejailedDirectory(dirs.PkgDir) | ||||||
|  | 			assert.NoError(t, err) | ||||||
|  |  | ||||||
|  | 			result, err := createFirejailedBinary(pkg, content, dirs) | ||||||
|  |  | ||||||
|  | 			if tt.expectError { | ||||||
|  | 				assert.Error(t, err) | ||||||
|  | 				if tt.errorMsg != "" { | ||||||
|  | 					assert.Contains(t, err.Error(), tt.errorMsg) | ||||||
|  | 				} | ||||||
|  | 				assert.Nil(t, result) | ||||||
|  | 			} else { | ||||||
|  | 				assert.NoError(t, err) | ||||||
|  | 				assert.Len(t, result, 2) | ||||||
|  |  | ||||||
|  | 				binContent := result[0] | ||||||
|  | 				assert.Contains(t, binContent.Destination, "usr/lib/alr/firejailed/") | ||||||
|  | 				assert.FileExists(t, binContent.Source) | ||||||
|  |  | ||||||
|  | 				profileContent := result[1] | ||||||
|  | 				assert.Contains(t, profileContent.Destination, "usr/lib/alr/firejailed/") | ||||||
|  | 				assert.Contains(t, profileContent.Destination, ".profile") | ||||||
|  | 				assert.FileExists(t, profileContent.Source) | ||||||
|  |  | ||||||
|  | 				assert.FileExists(t, content.Source) | ||||||
|  | 				wrapperBytes, err := os.ReadFile(content.Source) | ||||||
|  | 				assert.NoError(t, err) | ||||||
|  | 				wrapper := string(wrapperBytes) | ||||||
|  | 				assert.Contains(t, wrapper, "#!/bin/bash") | ||||||
|  | 				assert.Contains(t, wrapper, "firejail --profile=") | ||||||
|  | 			} | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @@ -17,7 +17,7 @@ | |||||||
| package build | package build | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/manager" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func NewInstaller(mgr manager.Manager) *Installer { | func NewInstaller(mgr manager.Manager) *Installer { | ||||||
| @@ -36,6 +36,10 @@ func (i *Installer) Install(pkgs []string, opts *manager.Opts) error { | |||||||
| 	return i.mgr.Install(opts, pkgs...) | 	return i.mgr.Install(opts, pkgs...) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | func (i *Installer) Remove(pkgs []string, opts *manager.Opts) error { | ||||||
|  | 	return i.mgr.Remove(opts, pkgs...) | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func (i *Installer) RemoveAlreadyInstalled(pkgs []string) ([]string, error) { | func (i *Installer) RemoveAlreadyInstalled(pkgs []string) ([]string, error) { | ||||||
| 	filteredPackages := []string{} | 	filteredPackages := []string{} | ||||||
| 
 | 
 | ||||||
| @@ -17,7 +17,7 @@ | |||||||
| package build | package build | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/manager" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func NewMainBuilder( | func NewMainBuilder( | ||||||
| @@ -28,7 +28,7 @@ import ( | |||||||
| 	"github.com/hashicorp/go-plugin" | 	"github.com/hashicorp/go-plugin" | ||||||
| 
 | 
 | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/logger" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/logger" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/manager" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| type InstallerPlugin struct { | type InstallerPlugin struct { | ||||||
| @@ -70,6 +70,17 @@ func (s *InstallerRPCServer) Install(args *InstallArgs, reply *struct{}) error { | |||||||
| 	return s.Impl.Install(args.PackagesOrPaths, args.Opts) | 	return s.Impl.Install(args.PackagesOrPaths, args.Opts) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | func (r *InstallerRPC) Remove(pkgs []string, opts *manager.Opts) error { | ||||||
|  | 	return r.client.Call("Plugin.Remove", &InstallArgs{ | ||||||
|  | 		PackagesOrPaths: pkgs, | ||||||
|  | 		Opts:            opts, | ||||||
|  | 	}, nil) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (s *InstallerRPCServer) Remove(args *InstallArgs, reply *struct{}) error { | ||||||
|  | 	return s.Impl.Remove(args.PackagesOrPaths, args.Opts) | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func (r *InstallerRPC) RemoveAlreadyInstalled(paths []string) ([]string, error) { | func (r *InstallerRPC) RemoveAlreadyInstalled(paths []string) ([]string, error) { | ||||||
| 	var val []string | 	var val []string | ||||||
| 	err := r.client.Call("Plugin.RemoveAlreadyInstalled", paths, &val) | 	err := r.client.Call("Plugin.RemoveAlreadyInstalled", paths, &val) | ||||||
| @@ -28,7 +28,7 @@ import ( | |||||||
| 	"github.com/hashicorp/go-plugin" | 	"github.com/hashicorp/go-plugin" | ||||||
| 
 | 
 | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/logger" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/logger" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/types" | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| var HandshakeConfig = plugin.HandshakeConfig{ | var HandshakeConfig = plugin.HandshakeConfig{ | ||||||
| @@ -50,13 +50,13 @@ type ScriptExecutorRPCServer struct { | |||||||
| // ReadScript | // ReadScript | ||||||
| // | // | ||||||
| 
 | 
 | ||||||
| func (s *ScriptExecutorRPC) ReadScript(ctx context.Context, scriptPath string) (*ScriptFile, error) { | func (s *ScriptExecutorRPC) ReadScript(ctx context.Context, scriptPath string) (*alrsh.ScriptFile, error) { | ||||||
| 	var resp *ScriptFile | 	var resp *alrsh.ScriptFile | ||||||
| 	err := s.client.Call("Plugin.ReadScript", scriptPath, &resp) | 	err := s.client.Call("Plugin.ReadScript", scriptPath, &resp) | ||||||
| 	return resp, err | 	return resp, err | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (s *ScriptExecutorRPCServer) ReadScript(scriptPath string, resp *ScriptFile) error { | func (s *ScriptExecutorRPCServer) ReadScript(scriptPath string, resp *alrsh.ScriptFile) error { | ||||||
| 	file, err := s.Impl.ReadScript(context.Background(), scriptPath) | 	file, err := s.Impl.ReadScript(context.Background(), scriptPath) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
| @@ -72,15 +72,15 @@ func (s *ScriptExecutorRPCServer) ReadScript(scriptPath string, resp *ScriptFile | |||||||
| 
 | 
 | ||||||
| type ExecuteFirstPassArgs struct { | type ExecuteFirstPassArgs struct { | ||||||
| 	Input *BuildInput | 	Input *BuildInput | ||||||
| 	Sf    *ScriptFile | 	Sf    *alrsh.ScriptFile | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type ExecuteFirstPassResp struct { | type ExecuteFirstPassResp struct { | ||||||
| 	BasePkg        string | 	BasePkg        string | ||||||
| 	VarsOfPackages []*types.BuildVars | 	VarsOfPackages []*alrsh.Package | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (s *ScriptExecutorRPC) ExecuteFirstPass(ctx context.Context, input *BuildInput, sf *ScriptFile) (string, []*types.BuildVars, error) { | func (s *ScriptExecutorRPC) ExecuteFirstPass(ctx context.Context, input *BuildInput, sf *alrsh.ScriptFile) (string, []*alrsh.Package, error) { | ||||||
| 	var resp *ExecuteFirstPassResp | 	var resp *ExecuteFirstPassResp | ||||||
| 	err := s.client.Call("Plugin.ExecuteFirstPass", &ExecuteFirstPassArgs{ | 	err := s.client.Call("Plugin.ExecuteFirstPass", &ExecuteFirstPassArgs{ | ||||||
| 		Input: input, | 		Input: input, | ||||||
| @@ -148,29 +148,29 @@ func (s *ScriptExecutorRPCServer) PrepareDirs(args *PrepareDirsArgs, reply *stru | |||||||
| 
 | 
 | ||||||
| type ExecuteSecondPassArgs struct { | type ExecuteSecondPassArgs struct { | ||||||
| 	Input          *BuildInput | 	Input          *BuildInput | ||||||
| 	Sf             *ScriptFile | 	Sf             *alrsh.ScriptFile | ||||||
| 	VarsOfPackages []*types.BuildVars | 	VarsOfPackages []*alrsh.Package | ||||||
| 	RepoDeps       []string | 	RepoDeps       []string | ||||||
| 	BuiltNames     []string | 	BuiltDeps      []*BuiltDep | ||||||
| 	BasePkg        string | 	BasePkg        string | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (s *ScriptExecutorRPC) ExecuteSecondPass( | func (s *ScriptExecutorRPC) ExecuteSecondPass( | ||||||
| 	ctx context.Context, | 	ctx context.Context, | ||||||
| 	input *BuildInput, | 	input *BuildInput, | ||||||
| 	sf *ScriptFile, | 	sf *alrsh.ScriptFile, | ||||||
| 	varsOfPackages []*types.BuildVars, | 	varsOfPackages []*alrsh.Package, | ||||||
| 	repoDeps []string, | 	repoDeps []string, | ||||||
| 	builtNames []string, | 	builtDeps []*BuiltDep, | ||||||
| 	basePkg string, | 	basePkg string, | ||||||
| ) (*SecondPassResult, error) { | ) ([]*BuiltDep, error) { | ||||||
| 	var resp *SecondPassResult | 	var resp []*BuiltDep | ||||||
| 	err := s.client.Call("Plugin.ExecuteSecondPass", &ExecuteSecondPassArgs{ | 	err := s.client.Call("Plugin.ExecuteSecondPass", &ExecuteSecondPassArgs{ | ||||||
| 		Input:          input, | 		Input:          input, | ||||||
| 		Sf:             sf, | 		Sf:             sf, | ||||||
| 		VarsOfPackages: varsOfPackages, | 		VarsOfPackages: varsOfPackages, | ||||||
| 		RepoDeps:       repoDeps, | 		RepoDeps:       repoDeps, | ||||||
| 		BuiltNames:     builtNames, | 		BuiltDeps:      builtDeps, | ||||||
| 		BasePkg:        basePkg, | 		BasePkg:        basePkg, | ||||||
| 	}, &resp) | 	}, &resp) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| @@ -179,20 +179,20 @@ func (s *ScriptExecutorRPC) ExecuteSecondPass( | |||||||
| 	return resp, nil | 	return resp, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (s *ScriptExecutorRPCServer) ExecuteSecondPass(args *ExecuteSecondPassArgs, resp *SecondPassResult) error { | func (s *ScriptExecutorRPCServer) ExecuteSecondPass(args *ExecuteSecondPassArgs, resp *[]*BuiltDep) error { | ||||||
| 	res, err := s.Impl.ExecuteSecondPass( | 	res, err := s.Impl.ExecuteSecondPass( | ||||||
| 		context.Background(), | 		context.Background(), | ||||||
| 		args.Input, | 		args.Input, | ||||||
| 		args.Sf, | 		args.Sf, | ||||||
| 		args.VarsOfPackages, | 		args.VarsOfPackages, | ||||||
| 		args.RepoDeps, | 		args.RepoDeps, | ||||||
| 		args.BuiltNames, | 		args.BuiltDeps, | ||||||
| 		args.BasePkg, | 		args.BasePkg, | ||||||
| 	) | 	) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| 	*resp = *res | 	*resp = res | ||||||
| 	return err | 	return err | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @@ -19,7 +19,6 @@ package build | |||||||
| import ( | import ( | ||||||
| 	"bytes" | 	"bytes" | ||||||
| 	"context" | 	"context" | ||||||
| 	"errors" |  | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"log/slog" | 	"log/slog" | ||||||
| 	"os" | 	"os" | ||||||
| @@ -36,12 +35,12 @@ import ( | |||||||
| 	"mvdan.cc/sh/v3/interp" | 	"mvdan.cc/sh/v3/interp" | ||||||
| 	"mvdan.cc/sh/v3/syntax" | 	"mvdan.cc/sh/v3/syntax" | ||||||
| 
 | 
 | ||||||
|  | 	finddeps "gitea.plemya-x.ru/Plemya-x/ALR/internal/build/find_deps" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/decoder" | 	"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/shutils/handlers" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/helpers" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/helpers" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/types" | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh" | ||||||
| 	finddeps "gitea.plemya-x.ru/Plemya-x/ALR/pkg/build/find_deps" | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro" |  | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| type LocalScriptExecutor struct { | type LocalScriptExecutor struct { | ||||||
| @@ -54,107 +53,12 @@ func NewLocalScriptExecutor(cfg Config) *LocalScriptExecutor { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (e *LocalScriptExecutor) ReadScript(ctx context.Context, scriptPath string) (*ScriptFile, error) { | func (e *LocalScriptExecutor) ReadScript(ctx context.Context, scriptPath string) (*alrsh.ScriptFile, error) { | ||||||
| 	fl, err := readScript(scriptPath) | 	return alrsh.ReadFromLocal(scriptPath) | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 	return &ScriptFile{ |  | ||||||
| 		Path: scriptPath, |  | ||||||
| 		File: fl, |  | ||||||
| 	}, nil |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (e *LocalScriptExecutor) ExecuteFirstPass(ctx context.Context, input *BuildInput, sf *ScriptFile) (string, []*types.BuildVars, error) { | func (e *LocalScriptExecutor) ExecuteFirstPass(ctx context.Context, input *BuildInput, sf *alrsh.ScriptFile) (string, []*alrsh.Package, error) { | ||||||
| 	varsOfPackages := []*types.BuildVars{} | 	return sf.ParseBuildVars(ctx, input.info, input.packages) | ||||||
| 
 |  | ||||||
| 	scriptDir := filepath.Dir(sf.Path) |  | ||||||
| 	env := createBuildEnvVars(input.info, types.Directories{ScriptDir: scriptDir}) |  | ||||||
| 
 |  | ||||||
| 	runner, err := interp.New( |  | ||||||
| 		interp.Env(expand.ListEnviron(env...)),                               // Устанавливаем окружение |  | ||||||
| 		interp.StdIO(os.Stdin, os.Stderr, os.Stderr),                         // Устанавливаем стандартный ввод-вывод |  | ||||||
| 		interp.ExecHandler(helpers.Restricted.ExecHandler(handlers.NopExec)), // Ограничиваем выполнение |  | ||||||
| 		interp.ReadDirHandler2(handlers.RestrictedReadDir(scriptDir)),        // Ограничиваем чтение директорий |  | ||||||
| 		interp.StatHandler(handlers.RestrictedStat(scriptDir)),               // Ограничиваем доступ к статистике файлов |  | ||||||
| 		interp.OpenHandler(handlers.RestrictedOpen(scriptDir)),               // Ограничиваем открытие файлов |  | ||||||
| 		interp.Dir(scriptDir), |  | ||||||
| 	) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return "", nil, err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	err = runner.Run(ctx, sf.File) // Запускаем скрипт |  | ||||||
| 	if err != nil { |  | ||||||
| 		return "", nil, err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	dec := decoder.New(input.info, runner) // Создаём новый декодер |  | ||||||
| 
 |  | ||||||
| 	type packages struct { |  | ||||||
| 		BasePkgName string   `sh:"basepkg_name"` |  | ||||||
| 		Names       []string `sh:"name"` |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	var pkgs packages |  | ||||||
| 	err = dec.DecodeVars(&pkgs) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return "", nil, err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if len(pkgs.Names) == 0 { |  | ||||||
| 		return "", nil, errors.New("package name is missing") |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	var vars types.BuildVars |  | ||||||
| 
 |  | ||||||
| 	if len(pkgs.Names) == 1 { |  | ||||||
| 		err = dec.DecodeVars(&vars) // Декодируем переменные |  | ||||||
| 		if err != nil { |  | ||||||
| 			return "", nil, err |  | ||||||
| 		} |  | ||||||
| 		varsOfPackages = append(varsOfPackages, &vars) |  | ||||||
| 
 |  | ||||||
| 		return vars.Name, varsOfPackages, nil |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	var pkgNames []string |  | ||||||
| 
 |  | ||||||
| 	if len(input.packages) != 0 { |  | ||||||
| 		pkgNames = input.packages |  | ||||||
| 	} else { |  | ||||||
| 		pkgNames = pkgs.Names |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	for _, pkgName := range pkgNames { |  | ||||||
| 		var preVars types.BuildVarsPre |  | ||||||
| 		funcName := fmt.Sprintf("meta_%s", pkgName) |  | ||||||
| 		meta, ok := dec.GetFuncWithSubshell(funcName) |  | ||||||
| 		if !ok { |  | ||||||
| 			return "", nil, fmt.Errorf("func %s is missing", funcName) |  | ||||||
| 		} |  | ||||||
| 		r, err := meta(ctx) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return "", nil, err |  | ||||||
| 		} |  | ||||||
| 		d := decoder.New(&distro.OSRelease{}, r) |  | ||||||
| 		err = d.DecodeVars(&preVars) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return "", nil, err |  | ||||||
| 		} |  | ||||||
| 		vars := preVars.ToBuildVars() |  | ||||||
| 		vars.Name = pkgName |  | ||||||
| 		vars.Base = pkgs.BasePkgName |  | ||||||
| 
 |  | ||||||
| 		varsOfPackages = append(varsOfPackages, &vars) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return pkgs.BasePkgName, varsOfPackages, nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| type SecondPassResult struct { |  | ||||||
| 	BuiltPaths []string |  | ||||||
| 	BuiltNames []string |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (e *LocalScriptExecutor) PrepareDirs( | func (e *LocalScriptExecutor) PrepareDirs( | ||||||
| @@ -182,13 +86,13 @@ func (e *LocalScriptExecutor) PrepareDirs( | |||||||
| func (e *LocalScriptExecutor) ExecuteSecondPass( | func (e *LocalScriptExecutor) ExecuteSecondPass( | ||||||
| 	ctx context.Context, | 	ctx context.Context, | ||||||
| 	input *BuildInput, | 	input *BuildInput, | ||||||
| 	sf *ScriptFile, | 	sf *alrsh.ScriptFile, | ||||||
| 	varsOfPackages []*types.BuildVars, | 	varsOfPackages []*alrsh.Package, | ||||||
| 	repoDeps []string, | 	repoDeps []string, | ||||||
| 	builtNames []string, | 	builtDeps []*BuiltDep, | ||||||
| 	basePkg string, | 	basePkg string, | ||||||
| ) (*SecondPassResult, error) { | ) ([]*BuiltDep, error) { | ||||||
| 	dirs, err := getDirs(e.cfg, sf.Path, basePkg) | 	dirs, err := getDirs(e.cfg, sf.Path(), basePkg) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| @@ -206,14 +110,14 @@ func (e *LocalScriptExecutor) ExecuteSecondPass( | |||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	err = runner.Run(ctx, sf.File) | 	err = runner.Run(ctx, sf.File()) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	dec := decoder.New(input.info, runner) | 	dec := decoder.New(input.info, runner) | ||||||
| 
 | 
 | ||||||
| 	var builtPaths []string | 	// var builtPaths []string | ||||||
| 
 | 
 | ||||||
| 	err = e.ExecuteFunctions(ctx, dirs, dec) | 	err = e.ExecuteFunctions(ctx, dirs, dec) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| @@ -222,7 +126,7 @@ func (e *LocalScriptExecutor) ExecuteSecondPass( | |||||||
| 
 | 
 | ||||||
| 	for _, vars := range varsOfPackages { | 	for _, vars := range varsOfPackages { | ||||||
| 		packageName := "" | 		packageName := "" | ||||||
| 		if vars.Base != "" { | 		if vars.BasePkgName != "" { | ||||||
| 			packageName = vars.Name | 			packageName = vars.Name | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| @@ -247,7 +151,7 @@ func (e *LocalScriptExecutor) ExecuteSecondPass( | |||||||
| 			dirs, | 			dirs, | ||||||
| 			append( | 			append( | ||||||
| 				repoDeps, | 				repoDeps, | ||||||
| 				builtNames..., | 				GetBuiltName(builtDeps)..., | ||||||
| 			), | 			), | ||||||
| 			funcOut.Contents, | 			funcOut.Contents, | ||||||
| 		) | 		) | ||||||
| @@ -273,14 +177,13 @@ func (e *LocalScriptExecutor) ExecuteSecondPass( | |||||||
| 			return nil, err | 			return nil, err | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		builtPaths = append(builtPaths, pkgPath) | 		builtDeps = append(builtDeps, &BuiltDep{ | ||||||
| 		builtNames = append(builtNames, vars.Name) | 			Name: vars.Name, | ||||||
|  | 			Path: pkgPath, | ||||||
|  | 		}) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	return &SecondPassResult{ | 	return builtDeps, nil | ||||||
| 		BuiltPaths: builtPaths, |  | ||||||
| 		BuiltNames: builtNames, |  | ||||||
| 	}, nil |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func buildPkgMetadata( | func buildPkgMetadata( | ||||||
| @@ -291,24 +194,24 @@ func buildPkgMetadata( | |||||||
| 		PkgFormatProvider | 		PkgFormatProvider | ||||||
| 		RepositoryProvider | 		RepositoryProvider | ||||||
| 	}, | 	}, | ||||||
| 	vars *types.BuildVars, | 	vars *alrsh.Package, | ||||||
| 	dirs types.Directories, | 	dirs types.Directories, | ||||||
| 	deps []string, | 	deps []string, | ||||||
| 	preferedContents *[]string, | 	preferedContents *[]string, | ||||||
| ) (*nfpm.Info, error) { | ) (*nfpm.Info, error) { | ||||||
| 	pkgInfo := getBasePkgInfo(vars, input) | 	pkgInfo := getBasePkgInfo(vars, input) | ||||||
| 	pkgInfo.Description = vars.Description | 	pkgInfo.Description = vars.Description.Resolved() | ||||||
| 	pkgInfo.Platform = "linux" | 	pkgInfo.Platform = "linux" | ||||||
| 	pkgInfo.Homepage = vars.Homepage | 	pkgInfo.Homepage = vars.Homepage.Resolved() | ||||||
| 	pkgInfo.License = strings.Join(vars.Licenses, ", ") | 	pkgInfo.License = strings.Join(vars.Licenses, ", ") | ||||||
| 	pkgInfo.Maintainer = vars.Maintainer | 	pkgInfo.Maintainer = vars.Maintainer.Resolved() | ||||||
| 	pkgInfo.Overridables = nfpm.Overridables{ | 	pkgInfo.Overridables = nfpm.Overridables{ | ||||||
| 		Conflicts: append(vars.Conflicts, vars.Name), | 		Conflicts: append(vars.Conflicts, vars.Name), | ||||||
| 		Replaces:  vars.Replaces, | 		Replaces:  vars.Replaces, | ||||||
| 		Provides:  append(vars.Provides, vars.Name), | 		Provides:  append(vars.Provides, vars.Name), | ||||||
| 		Depends:   deps, | 		Depends:   deps, | ||||||
| 	} | 	} | ||||||
| 	pkgInfo.Section = vars.Group | 	pkgInfo.Section = vars.Group.Resolved() | ||||||
| 
 | 
 | ||||||
| 	pkgFormat := input.PkgFormat() | 	pkgFormat := input.PkgFormat() | ||||||
| 	info := input.OSRelease() | 	info := input.OSRelease() | ||||||
| @@ -321,12 +224,12 @@ func buildPkgMetadata( | |||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if pkgFormat == "rpm" { | 	if pkgFormat == "rpm" { | ||||||
| 		pkgInfo.RPM.Group = vars.Group | 		pkgInfo.RPM.Group = vars.Group.Resolved() | ||||||
| 
 | 
 | ||||||
| 		if vars.Summary != "" { | 		if vars.Summary.Resolved() != "" { | ||||||
| 			pkgInfo.RPM.Summary = vars.Summary | 			pkgInfo.RPM.Summary = vars.Summary.Resolved() | ||||||
| 		} else { | 		} else { | ||||||
| 			lines := strings.SplitN(vars.Description, "\n", 2) | 			lines := strings.SplitN(vars.Description.Resolved(), "\n", 2) | ||||||
| 			pkgInfo.RPM.Summary = lines[0] | 			pkgInfo.RPM.Summary = lines[0] | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| @@ -345,19 +248,29 @@ func buildPkgMetadata( | |||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 	pkgInfo.Overridables.Contents = contents |  | ||||||
| 
 | 
 | ||||||
| 	if len(vars.AutoProv) == 1 && decoder.IsTruthy(vars.AutoProv[0]) { | 	normalizeContents(contents) | ||||||
| 		f := finddeps.New(info, pkgFormat) | 
 | ||||||
| 		err = f.FindProvides(ctx, pkgInfo, dirs, vars.AutoProvSkipList) | 	if vars.FireJailed.Resolved() { | ||||||
|  | 		contents, err = applyFirejailIntegration(vars, dirs, contents) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return nil, err | 			return nil, err | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if len(vars.AutoReq) == 1 && decoder.IsTruthy(vars.AutoReq[0]) { | 	pkgInfo.Overridables.Contents = contents | ||||||
|  | 
 | ||||||
|  | 	if len(vars.AutoProv.Resolved()) == 1 && decoder.IsTruthy(vars.AutoProv.Resolved()[0]) { | ||||||
| 		f := finddeps.New(info, pkgFormat) | 		f := finddeps.New(info, pkgFormat) | ||||||
| 		err = f.FindRequires(ctx, pkgInfo, dirs, vars.AutoReqSkipList) | 		err = f.FindProvides(ctx, pkgInfo, dirs, vars.AutoProvSkipList.Resolved()) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if len(vars.AutoReq.Resolved()) == 1 && decoder.IsTruthy(vars.AutoReq.Resolved()[0]) { | ||||||
|  | 		f := finddeps.New(info, pkgFormat) | ||||||
|  | 		err = f.FindRequires(ctx, pkgInfo, dirs, vars.AutoReqSkipList.Resolved()) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return nil, err | 			return nil, err | ||||||
| 		} | 		} | ||||||
| @@ -18,9 +18,10 @@ package build | |||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"context" | 	"context" | ||||||
|  | 	"os" | ||||||
| 	"path/filepath" | 	"path/filepath" | ||||||
| 
 | 
 | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/db" | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| type ScriptResolver struct { | type ScriptResolver struct { | ||||||
| @@ -34,16 +35,27 @@ type ScriptInfo struct { | |||||||
| 
 | 
 | ||||||
| func (s *ScriptResolver) ResolveScript( | func (s *ScriptResolver) ResolveScript( | ||||||
| 	ctx context.Context, | 	ctx context.Context, | ||||||
| 	pkg *db.Package, | 	pkg *alrsh.Package, | ||||||
| ) *ScriptInfo { | ) *ScriptInfo { | ||||||
| 	var repository, script string | 	var repository, script string | ||||||
| 
 | 
 | ||||||
| 	repodir := s.cfg.GetPaths().RepoDir | 	repodir := s.cfg.GetPaths().RepoDir | ||||||
| 	repository = pkg.Repository | 	repository = pkg.Repository | ||||||
| 	if pkg.BasePkgName != "" { | 
 | ||||||
| 		script = filepath.Join(repodir, repository, pkg.BasePkgName, "alr.sh") | 	// First, we check if there is a root alr.sh in the repository | ||||||
|  | 	rootScriptPath := filepath.Join(repodir, repository, "alr.sh") | ||||||
|  | 	if _, err := os.Stat(rootScriptPath); err == nil { | ||||||
|  | 		// A repository with a single alr.sh at the root | ||||||
|  | 		script = rootScriptPath | ||||||
| 	} else { | 	} else { | ||||||
| 		script = filepath.Join(repodir, repository, pkg.Name, "alr.sh") | 		// Multi-package repository - we are looking for alr.sh in the subfolder | ||||||
|  | 		var scriptPath string | ||||||
|  | 		if pkg.BasePkgName != "" { | ||||||
|  | 			scriptPath = filepath.Join(repodir, repository, pkg.BasePkgName, "alr.sh") | ||||||
|  | 		} else { | ||||||
|  | 			scriptPath = filepath.Join(repodir, repository, pkg.Name, "alr.sh") | ||||||
|  | 		} | ||||||
|  | 		script = scriptPath | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	return &ScriptInfo{ | 	return &ScriptInfo{ | ||||||
| @@ -20,6 +20,7 @@ import ( | |||||||
| 	"context" | 	"context" | ||||||
| 
 | 
 | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils" | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| type ScriptViewerConfig interface { | type ScriptViewerConfig interface { | ||||||
| @@ -33,12 +34,12 @@ type ScriptViewer struct { | |||||||
| func (s *ScriptViewer) ViewScript( | func (s *ScriptViewer) ViewScript( | ||||||
| 	ctx context.Context, | 	ctx context.Context, | ||||||
| 	input *BuildInput, | 	input *BuildInput, | ||||||
| 	sf *ScriptFile, | 	a *alrsh.ScriptFile, | ||||||
| 	basePkg string, | 	basePkg string, | ||||||
| ) error { | ) error { | ||||||
| 	return cliutils.PromptViewScript( | 	return cliutils.PromptViewScript( | ||||||
| 		ctx, | 		ctx, | ||||||
| 		sf.Path, | 		a.Path(), | ||||||
| 		basePkg, | 		basePkg, | ||||||
| 		s.config.PagerStyle(), | 		s.config.PagerStyle(), | ||||||
| 		input.opts.Interactive, | 		input.opts.Interactive, | ||||||
| @@ -23,8 +23,8 @@ import ( | |||||||
| 	"os" | 	"os" | ||||||
| 	"strings" | 	"strings" | ||||||
| 
 | 
 | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/dl" | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/dl" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/dlcache" | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/dlcache" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| type SourceDownloader struct { | type SourceDownloader struct { | ||||||
| @@ -74,7 +74,7 @@ func (s *SourceDownloader) DownloadSources( | |||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		opts.DlCache = dlcache.New(s.cfg) | 		opts.DlCache = dlcache.New(s.cfg.GetPaths().CacheDir) | ||||||
| 
 | 
 | ||||||
| 		err := dl.Download(ctx, opts) | 		err := dl.Download(ctx, opts) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| @@ -33,34 +33,18 @@ import ( | |||||||
| 	_ "github.com/goreleaser/nfpm/v2/arch" | 	_ "github.com/goreleaser/nfpm/v2/arch" | ||||||
| 	_ "github.com/goreleaser/nfpm/v2/deb" | 	_ "github.com/goreleaser/nfpm/v2/deb" | ||||||
| 	_ "github.com/goreleaser/nfpm/v2/rpm" | 	_ "github.com/goreleaser/nfpm/v2/rpm" | ||||||
| 	"mvdan.cc/sh/v3/syntax" |  | ||||||
| 
 | 
 | ||||||
| 	"github.com/goreleaser/nfpm/v2" | 	"github.com/goreleaser/nfpm/v2" | ||||||
| 	"github.com/goreleaser/nfpm/v2/files" | 	"github.com/goreleaser/nfpm/v2/files" | ||||||
| 
 | 
 | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/cpu" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/cpu" | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/manager" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/overrides" | 	"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/alrsh" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro" | 	"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/types" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // Функция 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 подготавливает директории для сборки. | // Функция prepareDirs подготавливает директории для сборки. | ||||||
| func prepareDirs(dirs types.Directories) error { | func prepareDirs(dirs types.Directories) error { | ||||||
| 	err := os.RemoveAll(dirs.BaseDir) // Удаляем базовую директорию, если она существует | 	err := os.RemoveAll(dirs.BaseDir) // Удаляем базовую директорию, если она существует | ||||||
| @@ -76,7 +60,7 @@ func prepareDirs(dirs types.Directories) error { | |||||||
| 
 | 
 | ||||||
| // Функция buildContents создает секцию содержимого пакета, которая содержит файлы, | // Функция buildContents создает секцию содержимого пакета, которая содержит файлы, | ||||||
| // которые будут включены в конечный пакет. | // которые будут включены в конечный пакет. | ||||||
| func buildContents(vars *types.BuildVars, dirs types.Directories, preferedContents *[]string) ([]*files.Content, error) { | func buildContents(vars *alrsh.Package, dirs types.Directories, preferedContents *[]string) ([]*files.Content, error) { | ||||||
| 	contents := []*files.Content{} | 	contents := []*files.Content{} | ||||||
| 
 | 
 | ||||||
| 	processPath := func(path, trimmed string, prefered bool) error { | 	processPath := func(path, trimmed string, prefered bool) error { | ||||||
| @@ -139,7 +123,7 @@ func buildContents(vars *types.BuildVars, dirs types.Directories, preferedConten | |||||||
| 			}, | 			}, | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		if slices.Contains(vars.Backup, trimmed) { | 		if slices.Contains(vars.Backup.Resolved(), trimmed) { | ||||||
| 			fileContent.Type = "config|noreplace" | 			fileContent.Type = "config|noreplace" | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| @@ -170,9 +154,15 @@ func buildContents(vars *types.BuildVars, dirs types.Directories, preferedConten | |||||||
| 	return contents, nil | 	return contents, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | func normalizeContents(contents []*files.Content) { | ||||||
|  | 	for _, content := range contents { | ||||||
|  | 		content.Destination = filepath.Join("/", content.Destination) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
| var RegexpALRPackageName = regexp.MustCompile(`^(?P<package>[^+]+)\+alr-(?P<repo>.+)$`) | var RegexpALRPackageName = regexp.MustCompile(`^(?P<package>[^+]+)\+alr-(?P<repo>.+)$`) | ||||||
| 
 | 
 | ||||||
| func getBasePkgInfo(vars *types.BuildVars, input interface { | func getBasePkgInfo(vars *alrsh.Package, input interface { | ||||||
| 	RepositoryProvider | 	RepositoryProvider | ||||||
| 	OsInfoProvider | 	OsInfoProvider | ||||||
| }, | }, | ||||||
| @@ -228,39 +218,39 @@ func createBuildEnvVars(info *distro.OSRelease, dirs types.Directories) []string | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Функция setScripts добавляет скрипты-перехватчики к метаданным пакета. | // Функция setScripts добавляет скрипты-перехватчики к метаданным пакета. | ||||||
| func setScripts(vars *types.BuildVars, info *nfpm.Info, scriptDir string) { | func setScripts(vars *alrsh.Package, info *nfpm.Info, scriptDir string) { | ||||||
| 	if vars.Scripts.PreInstall != "" { | 	if vars.Scripts.Resolved().PreInstall != "" { | ||||||
| 		info.Scripts.PreInstall = filepath.Join(scriptDir, vars.Scripts.PreInstall) | 		info.Scripts.PreInstall = filepath.Join(scriptDir, vars.Scripts.Resolved().PreInstall) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if vars.Scripts.PostInstall != "" { | 	if vars.Scripts.Resolved().PostInstall != "" { | ||||||
| 		info.Scripts.PostInstall = filepath.Join(scriptDir, vars.Scripts.PostInstall) | 		info.Scripts.PostInstall = filepath.Join(scriptDir, vars.Scripts.Resolved().PostInstall) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if vars.Scripts.PreRemove != "" { | 	if vars.Scripts.Resolved().PreRemove != "" { | ||||||
| 		info.Scripts.PreRemove = filepath.Join(scriptDir, vars.Scripts.PreRemove) | 		info.Scripts.PreRemove = filepath.Join(scriptDir, vars.Scripts.Resolved().PreRemove) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if vars.Scripts.PostRemove != "" { | 	if vars.Scripts.Resolved().PostRemove != "" { | ||||||
| 		info.Scripts.PostRemove = filepath.Join(scriptDir, vars.Scripts.PostRemove) | 		info.Scripts.PostRemove = filepath.Join(scriptDir, vars.Scripts.Resolved().PostRemove) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if vars.Scripts.PreUpgrade != "" { | 	if vars.Scripts.Resolved().PreUpgrade != "" { | ||||||
| 		info.ArchLinux.Scripts.PreUpgrade = filepath.Join(scriptDir, vars.Scripts.PreUpgrade) | 		info.ArchLinux.Scripts.PreUpgrade = filepath.Join(scriptDir, vars.Scripts.Resolved().PreUpgrade) | ||||||
| 		info.APK.Scripts.PreUpgrade = filepath.Join(scriptDir, vars.Scripts.PreUpgrade) | 		info.APK.Scripts.PreUpgrade = filepath.Join(scriptDir, vars.Scripts.Resolved().PreUpgrade) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if vars.Scripts.PostUpgrade != "" { | 	if vars.Scripts.Resolved().PostUpgrade != "" { | ||||||
| 		info.ArchLinux.Scripts.PostUpgrade = filepath.Join(scriptDir, vars.Scripts.PostUpgrade) | 		info.ArchLinux.Scripts.PostUpgrade = filepath.Join(scriptDir, vars.Scripts.Resolved().PostUpgrade) | ||||||
| 		info.APK.Scripts.PostUpgrade = filepath.Join(scriptDir, vars.Scripts.PostUpgrade) | 		info.APK.Scripts.PostUpgrade = filepath.Join(scriptDir, vars.Scripts.Resolved().PostUpgrade) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if vars.Scripts.PreTrans != "" { | 	if vars.Scripts.Resolved().PreTrans != "" { | ||||||
| 		info.RPM.Scripts.PreTrans = filepath.Join(scriptDir, vars.Scripts.PreTrans) | 		info.RPM.Scripts.PreTrans = filepath.Join(scriptDir, vars.Scripts.Resolved().PreTrans) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if vars.Scripts.PostTrans != "" { | 	if vars.Scripts.Resolved().PostTrans != "" { | ||||||
| 		info.RPM.Scripts.PostTrans = filepath.Join(scriptDir, vars.Scripts.PostTrans) | 		info.RPM.Scripts.PostTrans = filepath.Join(scriptDir, vars.Scripts.Resolved().PostTrans) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @@ -288,14 +278,14 @@ func packageNames(pkgs []db.Package) []string { | |||||||
| */ | */ | ||||||
| 
 | 
 | ||||||
| // Функция removeDuplicates убирает любые дубликаты из предоставленного среза. | // Функция removeDuplicates убирает любые дубликаты из предоставленного среза. | ||||||
| func removeDuplicates(slice []string) []string { | func removeDuplicates[T comparable](slice []T) []T { | ||||||
| 	seen := map[string]struct{}{} | 	seen := map[T]struct{}{} | ||||||
| 	result := []string{} | 	result := []T{} | ||||||
| 
 | 
 | ||||||
| 	for _, s := range slice { | 	for _, item := range slice { | ||||||
| 		if _, ok := seen[s]; !ok { | 		if _, ok := seen[item]; !ok { | ||||||
| 			seen[s] = struct{}{} | 			seen[item] = struct{}{} | ||||||
| 			result = append(result, s) | 			result = append(result, item) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| @@ -26,9 +26,9 @@ import ( | |||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils" | 	"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/config" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/db" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/db" | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/manager" | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/repos" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro" | 	"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" |  | ||||||
| ) | ) | ||||||
|  |  | ||||||
| type AppDeps struct { | type AppDeps struct { | ||||||
| @@ -123,8 +123,15 @@ func (b *AppBuilder) withRepos(enablePull, forcePull bool) *AppBuilder { | |||||||
|  |  | ||||||
| 	cfg := b.deps.Cfg | 	cfg := b.deps.Cfg | ||||||
| 	db := b.deps.DB | 	db := b.deps.DB | ||||||
| 	if cfg == nil || db == nil { | 	info := b.deps.Info | ||||||
| 		b.err = errors.New("config and db are required before initializing repos") |  | ||||||
|  | 	if info == nil { | ||||||
|  | 		b.WithDistroInfo() | ||||||
|  | 		info = b.deps.Info | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if cfg == nil || db == nil || info == nil { | ||||||
|  | 		b.err = errors.New("config, db and info are required before initializing repos") | ||||||
| 		return b | 		return b | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -28,8 +28,8 @@ import ( | |||||||
| 	"github.com/AlecAivazis/survey/v2" | 	"github.com/AlecAivazis/survey/v2" | ||||||
| 	"github.com/leonelquinteros/gotext" | 	"github.com/leonelquinteros/gotext" | ||||||
|  |  | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/db" |  | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/pager" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/pager" | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // YesNoPrompt asks the user a yes or no question, using def as the default answer | // YesNoPrompt asks the user a yes or no question, using def as the default answer | ||||||
| @@ -102,8 +102,8 @@ func ShowScript(path, name, style string) error { | |||||||
|  |  | ||||||
| // FlattenPkgs attempts to flatten the a map of slices of packages into a single slice | // FlattenPkgs attempts to flatten the a map of slices of packages into a single slice | ||||||
| // of packages by prompting the user if multiple packages match. | // of packages by prompting the user if multiple packages match. | ||||||
| func FlattenPkgs(ctx context.Context, found map[string][]db.Package, verb string, interactive bool) []db.Package { | func FlattenPkgs(ctx context.Context, found map[string][]alrsh.Package, verb string, interactive bool) []alrsh.Package { | ||||||
| 	var outPkgs []db.Package | 	var outPkgs []alrsh.Package | ||||||
| 	for _, pkgs := range found { | 	for _, pkgs := range found { | ||||||
| 		if len(pkgs) > 1 && interactive { | 		if len(pkgs) > 1 && interactive { | ||||||
| 			choice, err := PkgPrompt(ctx, pkgs, verb, interactive) | 			choice, err := PkgPrompt(ctx, pkgs, verb, interactive) | ||||||
| @@ -120,7 +120,7 @@ func FlattenPkgs(ctx context.Context, found map[string][]db.Package, verb string | |||||||
| } | } | ||||||
|  |  | ||||||
| // PkgPrompt asks the user to choose between multiple packages. | // PkgPrompt asks the user to choose between multiple packages. | ||||||
| func PkgPrompt(ctx context.Context, options []db.Package, verb string, interactive bool) (db.Package, error) { | func PkgPrompt(ctx context.Context, options []alrsh.Package, verb string, interactive bool) (alrsh.Package, error) { | ||||||
| 	if !interactive { | 	if !interactive { | ||||||
| 		return options[0], nil | 		return options[0], nil | ||||||
| 	} | 	} | ||||||
| @@ -138,7 +138,7 @@ func PkgPrompt(ctx context.Context, options []db.Package, verb string, interacti | |||||||
| 	var choice int | 	var choice int | ||||||
| 	err := survey.AskOne(prompt, &choice) | 	err := survey.AskOne(prompt, &choice) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return db.Package{}, err | 		return alrsh.Package{}, err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return options[choice], nil | 	return options[choice], nil | ||||||
|   | |||||||
| @@ -20,89 +20,79 @@ | |||||||
| package config | package config | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"log/slog" | 	"fmt" | ||||||
| 	"os" |  | ||||||
| 	"path/filepath" | 	"path/filepath" | ||||||
| 	"reflect" |  | ||||||
|  |  | ||||||
| 	"github.com/caarlos0/env" | 	"github.com/goccy/go-yaml" | ||||||
| 	"github.com/pelletier/go-toml/v2" | 	"github.com/knadh/koanf/providers/confmap" | ||||||
|  | 	"github.com/knadh/koanf/v2" | ||||||
|  |  | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/constants" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/constants" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/types" | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| type ALRConfig struct { | type ALRConfig struct { | ||||||
| 	cfg   *types.Config | 	cfg   *types.Config | ||||||
| 	paths *Paths | 	paths *Paths | ||||||
| } |  | ||||||
|  |  | ||||||
| var defaultConfig = &types.Config{ | 	System *SystemConfig | ||||||
| 	RootCmd:          "sudo", | 	env    *EnvConfig | ||||||
| 	UseRootCmd:       true, |  | ||||||
| 	PagerStyle:       "native", |  | ||||||
| 	IgnorePkgUpdates: []string{}, |  | ||||||
| 	AutoPull:         true, |  | ||||||
| 	Repos:            []types.Repo{}, |  | ||||||
| } | } | ||||||
|  |  | ||||||
| func New() *ALRConfig { | func New() *ALRConfig { | ||||||
| 	return &ALRConfig{} | 	return &ALRConfig{ | ||||||
|  | 		System: NewSystemConfig(), | ||||||
|  | 		env:    NewEnvConfig(), | ||||||
|  | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| func readConfig(path string) (*types.Config, error) { | func defaultConfigKoanf() *koanf.Koanf { | ||||||
| 	file, err := os.Open(path) | 	k := koanf.New(".") | ||||||
| 	if err != nil { | 	defaults := map[string]interface{}{ | ||||||
| 		return nil, err | 		"rootCmd":          "sudo", | ||||||
|  | 		"useRootCmd":       true, | ||||||
|  | 		"pagerStyle":       "native", | ||||||
|  | 		"ignorePkgUpdates": []string{}, | ||||||
|  | 		"logLevel":         "info", | ||||||
|  | 		"autoPull":         true, | ||||||
|  | 		"repos":            []types.Repo{}, | ||||||
| 	} | 	} | ||||||
| 	defer file.Close() | 	if err := k.Load(confmap.Provider(defaults, "."), nil); err != nil { | ||||||
|  | 		panic(k) | ||||||
| 	config := types.Config{} |  | ||||||
|  |  | ||||||
| 	if err := toml.NewDecoder(file).Decode(&config); err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return &config, nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func mergeStructs(dst, src interface{}) { |  | ||||||
| 	srcVal := reflect.ValueOf(src) |  | ||||||
| 	if srcVal.IsNil() { |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	srcVal = srcVal.Elem() |  | ||||||
| 	dstVal := reflect.ValueOf(dst).Elem() |  | ||||||
|  |  | ||||||
| 	for i := range srcVal.NumField() { |  | ||||||
| 		srcField := srcVal.Field(i) |  | ||||||
| 		srcFieldName := srcVal.Type().Field(i).Name |  | ||||||
|  |  | ||||||
| 		dstField := dstVal.FieldByName(srcFieldName) |  | ||||||
| 		if dstField.IsValid() && dstField.CanSet() { |  | ||||||
| 			dstField.Set(srcField) |  | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
|  | 	return k | ||||||
| } | } | ||||||
|  |  | ||||||
| func (c *ALRConfig) Load() error { | func (c *ALRConfig) Load() error { | ||||||
| 	systemConfig, err := readConfig( | 	config := types.Config{} | ||||||
| 		constants.SystemConfigPath, |  | ||||||
| 	) | 	merged := koanf.New(".") | ||||||
| 	if err != nil { |  | ||||||
| 		slog.Debug("Cannot read system config", "err", err) | 	if err := c.System.Load(); err != nil { | ||||||
|  | 		return fmt.Errorf("failed to load system config: %w", err) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	config := &types.Config{} | 	if err := c.env.Load(); err != nil { | ||||||
|  | 		return fmt.Errorf("failed to load env config: %w", err) | ||||||
| 	mergeStructs(config, defaultConfig) |  | ||||||
| 	mergeStructs(config, systemConfig) |  | ||||||
| 	err = env.Parse(config) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	c.cfg = config | 	systemK := c.System.koanf() | ||||||
|  | 	envK := c.env.koanf() | ||||||
|  |  | ||||||
|  | 	if err := merged.Merge(defaultConfigKoanf()); err != nil { | ||||||
|  | 		return fmt.Errorf("failed to merge default config: %w", err) | ||||||
|  | 	} | ||||||
|  | 	if err := merged.Merge(systemK); err != nil { | ||||||
|  | 		return fmt.Errorf("failed to merge system config: %w", err) | ||||||
|  | 	} | ||||||
|  | 	if err := merged.Merge(envK); err != nil { | ||||||
|  | 		return fmt.Errorf("failed to merge env config: %w", err) | ||||||
|  | 	} | ||||||
|  | 	if err := merged.Unmarshal("", &config); err != nil { | ||||||
|  | 		return fmt.Errorf("failed to unmarshal merged config: %w", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	c.cfg = &config | ||||||
|  |  | ||||||
| 	c.paths = &Paths{} | 	c.paths = &Paths{} | ||||||
| 	c.paths.UserConfigPath = constants.SystemConfigPath | 	c.paths.UserConfigPath = constants.SystemConfigPath | ||||||
| @@ -110,52 +100,24 @@ func (c *ALRConfig) Load() error { | |||||||
| 	c.paths.RepoDir = filepath.Join(c.paths.CacheDir, "repo") | 	c.paths.RepoDir = filepath.Join(c.paths.CacheDir, "repo") | ||||||
| 	c.paths.PkgsDir = filepath.Join(c.paths.CacheDir, "pkgs") | 	c.paths.PkgsDir = filepath.Join(c.paths.CacheDir, "pkgs") | ||||||
| 	c.paths.DBPath = filepath.Join(c.paths.CacheDir, "db") | 	c.paths.DBPath = filepath.Join(c.paths.CacheDir, "db") | ||||||
| 	// c.initPaths() |  | ||||||
|  |  | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func (c *ALRConfig) RootCmd() string { | func (c *ALRConfig) ToYAML() (string, error) { | ||||||
| 	return c.cfg.RootCmd | 	data, err := yaml.Marshal(c.cfg) | ||||||
| } |  | ||||||
|  |  | ||||||
| func (c *ALRConfig) PagerStyle() string { |  | ||||||
| 	return c.cfg.PagerStyle |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (c *ALRConfig) AutoPull() bool { |  | ||||||
| 	return c.cfg.AutoPull |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (c *ALRConfig) Repos() []types.Repo { |  | ||||||
| 	return c.cfg.Repos |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (c *ALRConfig) SetRepos(repos []types.Repo) { |  | ||||||
| 	c.cfg.Repos = repos |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (c *ALRConfig) IgnorePkgUpdates() []string { |  | ||||||
| 	return c.cfg.IgnorePkgUpdates |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (c *ALRConfig) LogLevel() string { |  | ||||||
| 	return c.cfg.LogLevel |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (c *ALRConfig) UseRootCmd() bool { |  | ||||||
| 	return c.cfg.UseRootCmd |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (c *ALRConfig) GetPaths() *Paths { |  | ||||||
| 	return c.paths |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (c *ALRConfig) SaveUserConfig() error { |  | ||||||
| 	f, err := os.Create(c.paths.UserConfigPath) |  | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return "", err | ||||||
| 	} | 	} | ||||||
|  | 	return string(data), nil | ||||||
| 	return toml.NewEncoder(f).Encode(c.cfg) |  | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (c *ALRConfig) RootCmd() string             { return c.cfg.RootCmd } | ||||||
|  | func (c *ALRConfig) PagerStyle() string          { return c.cfg.PagerStyle } | ||||||
|  | func (c *ALRConfig) AutoPull() bool              { return c.cfg.AutoPull } | ||||||
|  | func (c *ALRConfig) Repos() []types.Repo         { return c.cfg.Repos } | ||||||
|  | func (c *ALRConfig) SetRepos(repos []types.Repo) { c.System.SetRepos(repos) } | ||||||
|  | func (c *ALRConfig) IgnorePkgUpdates() []string  { return c.cfg.IgnorePkgUpdates } | ||||||
|  | func (c *ALRConfig) LogLevel() string            { return c.cfg.LogLevel } | ||||||
|  | func (c *ALRConfig) UseRootCmd() bool            { return c.cfg.UseRootCmd } | ||||||
|  | func (c *ALRConfig) GetPaths() *Paths            { return c.paths } | ||||||
|   | |||||||
							
								
								
									
										76
									
								
								internal/config/env_config.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								internal/config/env_config.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,76 @@ | |||||||
|  | // ALR - Any Linux Repository | ||||||
|  | // Copyright (C) 2025 The ALR Authors | ||||||
|  | // | ||||||
|  | // 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 ( | ||||||
|  | 	"strings" | ||||||
|  |  | ||||||
|  | 	"github.com/knadh/koanf/providers/env" | ||||||
|  | 	"github.com/knadh/koanf/v2" | ||||||
|  | 	"golang.org/x/text/cases" | ||||||
|  | 	"golang.org/x/text/language" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type EnvConfig struct { | ||||||
|  | 	k *koanf.Koanf | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func NewEnvConfig() *EnvConfig { | ||||||
|  | 	return &EnvConfig{ | ||||||
|  | 		k: koanf.New("."), | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *EnvConfig) koanf() *koanf.Koanf { | ||||||
|  | 	return c.k | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *EnvConfig) Load() error { | ||||||
|  | 	allowedKeys := map[string]struct{}{ | ||||||
|  | 		"ALR_LOG_LEVEL":   {}, | ||||||
|  | 		"ALR_PAGER_STYLE": {}, | ||||||
|  | 		"ALR_AUTO_PULL":   {}, | ||||||
|  | 	} | ||||||
|  | 	err := c.k.Load(env.Provider("ALR_", ".", func(s string) string { | ||||||
|  | 		_, ok := allowedKeys[s] | ||||||
|  | 		if !ok { | ||||||
|  | 			return "" | ||||||
|  | 		} | ||||||
|  | 		withoutPrefix := strings.TrimPrefix(s, "ALR_") | ||||||
|  | 		lowered := strings.ToLower(withoutPrefix) | ||||||
|  | 		dotted := strings.ReplaceAll(lowered, "__", ".") | ||||||
|  | 		parts := strings.Split(dotted, ".") | ||||||
|  | 		for i, part := range parts { | ||||||
|  | 			if strings.Contains(part, "_") { | ||||||
|  | 				parts[i] = toCamelCase(part) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		return strings.Join(parts, ".") | ||||||
|  | 	}), nil) | ||||||
|  |  | ||||||
|  | 	return err | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func toCamelCase(s string) string { | ||||||
|  | 	parts := strings.Split(s, "_") | ||||||
|  | 	for i := 1; i < len(parts); i++ { | ||||||
|  | 		if len(parts[i]) > 0 { | ||||||
|  | 			parts[i] = cases.Title(language.Und, cases.NoLower).String(parts[i]) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return strings.Join(parts, "") | ||||||
|  | } | ||||||
| @@ -21,9 +21,10 @@ package config | |||||||
|  |  | ||||||
| // Paths contains various paths used by ALR | // Paths contains various paths used by ALR | ||||||
| type Paths struct { | type Paths struct { | ||||||
| 	UserConfigPath string | 	SystemConfigPath string | ||||||
| 	CacheDir       string | 	UserConfigPath   string | ||||||
| 	RepoDir        string | 	CacheDir         string | ||||||
| 	PkgsDir        string | 	RepoDir          string | ||||||
| 	DBPath         string | 	PkgsDir          string | ||||||
|  | 	DBPath           string | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										144
									
								
								internal/config/system_config.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										144
									
								
								internal/config/system_config.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,144 @@ | |||||||
|  | // ALR - Any Linux Repository | ||||||
|  | // Copyright (C) 2025 The ALR Authors | ||||||
|  | // | ||||||
|  | // 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 ( | ||||||
|  | 	"encoding/json" | ||||||
|  | 	"errors" | ||||||
|  | 	"fmt" | ||||||
|  | 	"os" | ||||||
|  |  | ||||||
|  | 	ktoml "github.com/knadh/koanf/parsers/toml/v2" | ||||||
|  | 	"github.com/knadh/koanf/providers/file" | ||||||
|  | 	"github.com/knadh/koanf/v2" | ||||||
|  |  | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/constants" | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type SystemConfig struct { | ||||||
|  | 	k   *koanf.Koanf | ||||||
|  | 	cfg *types.Config | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func NewSystemConfig() *SystemConfig { | ||||||
|  | 	return &SystemConfig{ | ||||||
|  | 		k:   koanf.New("."), | ||||||
|  | 		cfg: &types.Config{}, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *SystemConfig) koanf() *koanf.Koanf { | ||||||
|  | 	return c.k | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *SystemConfig) Load() error { | ||||||
|  | 	if _, err := os.Stat(constants.SystemConfigPath); errors.Is(err, os.ErrNotExist) { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if err := c.k.Load(file.Provider(constants.SystemConfigPath), ktoml.Parser()); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return c.k.Unmarshal("", c.cfg) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *SystemConfig) Save() error { | ||||||
|  | 	bytes, err := c.k.Marshal(ktoml.Parser()) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return fmt.Errorf("failed to marshal config: %w", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	file, err := os.Create(constants.SystemConfigPath) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return fmt.Errorf("failed to create config file: %w", err) | ||||||
|  | 	} | ||||||
|  | 	defer func() { | ||||||
|  | 		if cerr := file.Close(); cerr != nil && err == nil { | ||||||
|  | 			err = cerr | ||||||
|  | 		} | ||||||
|  | 	}() | ||||||
|  |  | ||||||
|  | 	if _, err := file.Write(bytes); err != nil { | ||||||
|  | 		return fmt.Errorf("failed to write config: %w", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if err := file.Sync(); err != nil { | ||||||
|  | 		return fmt.Errorf("failed to sync config: %w", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *SystemConfig) SetRootCmd(v string) { | ||||||
|  | 	err := c.k.Set("rootCmd", v) | ||||||
|  | 	if err != nil { | ||||||
|  | 		panic(err) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *SystemConfig) SetUseRootCmd(v bool) { | ||||||
|  | 	err := c.k.Set("useRootCmd", v) | ||||||
|  | 	if err != nil { | ||||||
|  | 		panic(err) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *SystemConfig) SetPagerStyle(v string) { | ||||||
|  | 	err := c.k.Set("pagerStyle", v) | ||||||
|  | 	if err != nil { | ||||||
|  | 		panic(err) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *SystemConfig) SetIgnorePkgUpdates(v []string) { | ||||||
|  | 	err := c.k.Set("ignorePkgUpdates", v) | ||||||
|  | 	if err != nil { | ||||||
|  | 		panic(err) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *SystemConfig) SetAutoPull(v bool) { | ||||||
|  | 	err := c.k.Set("autoPull", v) | ||||||
|  | 	if err != nil { | ||||||
|  | 		panic(err) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *SystemConfig) SetLogLevel(v string) { | ||||||
|  | 	err := c.k.Set("logLevel", v) | ||||||
|  | 	if err != nil { | ||||||
|  | 		panic(err) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *SystemConfig) SetRepos(v []types.Repo) { | ||||||
|  | 	b, err := json.Marshal(v) | ||||||
|  | 	if err != nil { | ||||||
|  | 		panic(err) | ||||||
|  | 	} | ||||||
|  | 	var m []interface{} | ||||||
|  | 	err = json.Unmarshal(b, &m) | ||||||
|  | 	if err != nil { | ||||||
|  | 		panic(err) | ||||||
|  | 	} | ||||||
|  | 	err = c.k.Set("repo", m) | ||||||
|  | 	if err != nil { | ||||||
|  | 		panic(err) | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @@ -23,41 +23,18 @@ import ( | |||||||
| 	"context" | 	"context" | ||||||
| 	"log/slog" | 	"log/slog" | ||||||
|  |  | ||||||
| 	"github.com/jmoiron/sqlx" |  | ||||||
| 	"github.com/leonelquinteros/gotext" | 	"github.com/leonelquinteros/gotext" | ||||||
|  | 	_ "modernc.org/sqlite" | ||||||
|  | 	"xorm.io/xorm" | ||||||
|  |  | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/config" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/config" | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // CurrentVersion is the current version of the database. | const CurrentVersion = 5 | ||||||
| // The database is reset if its version doesn't match this. |  | ||||||
| const CurrentVersion = 4 |  | ||||||
|  |  | ||||||
| // Package is a ALR package's database representation | type Version struct { | ||||||
| type Package struct { | 	Version int `xorm:"'version'"` | ||||||
| 	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"` |  | ||||||
| 	Epoch         uint                      `sh:"epoch" db:"epoch"` |  | ||||||
| 	Summary       JSON[map[string]string]   `db:"summary"` |  | ||||||
| 	Description   JSON[map[string]string]   `db:"description"` |  | ||||||
| 	Group         JSON[map[string]string]   `db:"group_name"` |  | ||||||
| 	Homepage      JSON[map[string]string]   `db:"homepage"` |  | ||||||
| 	Maintainer    JSON[map[string]string]   `db:"maintainer"` |  | ||||||
| 	Architectures JSON[[]string]            `sh:"architectures" db:"architectures"` |  | ||||||
| 	Licenses      JSON[[]string]            `sh:"license" db:"licenses"` |  | ||||||
| 	Provides      JSON[[]string]            `sh:"provides" db:"provides"` |  | ||||||
| 	Conflicts     JSON[[]string]            `sh:"conflicts" db:"conflicts"` |  | ||||||
| 	Replaces      JSON[[]string]            `sh:"replaces" db:"replaces"` |  | ||||||
| 	Depends       JSON[map[string][]string] `db:"depends"` |  | ||||||
| 	BuildDepends  JSON[map[string][]string] `db:"builddepends"` |  | ||||||
| 	OptDepends    JSON[map[string][]string] `db:"optdepends"` |  | ||||||
| 	Repository    string                    `db:"repository"` |  | ||||||
| } |  | ||||||
|  |  | ||||||
| type version struct { |  | ||||||
| 	Version int `db:"version"` |  | ||||||
| } | } | ||||||
|  |  | ||||||
| type Config interface { | type Config interface { | ||||||
| @@ -65,7 +42,7 @@ type Config interface { | |||||||
| } | } | ||||||
|  |  | ||||||
| type Database struct { | type Database struct { | ||||||
| 	conn   *sqlx.DB | 	engine *xorm.Engine | ||||||
| 	config Config | 	config Config | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -75,181 +52,100 @@ func New(config Config) *Database { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| func (d *Database) Init(ctx context.Context) error { | func (d *Database) Connect() error { | ||||||
| 	err := d.Connect(ctx) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 	return d.initDB(ctx) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (d *Database) Connect(ctx context.Context) error { |  | ||||||
| 	dsn := d.config.GetPaths().DBPath | 	dsn := d.config.GetPaths().DBPath | ||||||
| 	db, err := sqlx.Open("sqlite", dsn) | 	engine, err := xorm.NewEngine("sqlite", dsn) | ||||||
|  | 	// engine.SetLogLevel(log.LOG_DEBUG) | ||||||
|  | 	// engine.ShowSQL(true) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| 	d.conn = db | 	d.engine = engine | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func (d *Database) GetConn() *sqlx.DB { | func (d *Database) Init(ctx context.Context) error { | ||||||
| 	return d.conn | 	if err := d.Connect(); err != nil { | ||||||
| } | 		return err | ||||||
|  | 	} | ||||||
| func (d *Database) initDB(ctx context.Context) error { | 	if err := d.engine.Sync2(new(alrsh.Package), new(Version)); err != nil { | ||||||
| 	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, |  | ||||||
| 			release       INT  NOT NULL, |  | ||||||
| 			epoch         INT, |  | ||||||
| 			summary       TEXT CHECK(summary = 'null' OR (JSON_VALID(summary) AND JSON_TYPE(summary) = 'object')), |  | ||||||
| 			description   TEXT CHECK(description = 'null' OR (JSON_VALID(description) AND JSON_TYPE(description) = 'object')), |  | ||||||
| 			group_name    TEXT CHECK(group_name = 'null' OR (JSON_VALID(group_name) AND JSON_TYPE(group_name) = 'object')), |  | ||||||
| 			homepage      TEXT CHECK(homepage = 'null' OR (JSON_VALID(homepage) AND JSON_TYPE(homepage) = 'object')), |  | ||||||
| 			maintainer    TEXT CHECK(maintainer = 'null' OR (JSON_VALID(maintainer) AND JSON_TYPE(maintainer) = 'object')), |  | ||||||
| 			architectures TEXT CHECK(architectures = 'null' OR (JSON_VALID(architectures) AND JSON_TYPE(architectures) = 'array')), |  | ||||||
| 			licenses      TEXT CHECK(licenses = 'null' OR (JSON_VALID(licenses) AND JSON_TYPE(licenses) = 'array')), |  | ||||||
| 			provides      TEXT CHECK(provides = 'null' OR (JSON_VALID(provides) AND JSON_TYPE(provides) = 'array')), |  | ||||||
| 			conflicts     TEXT CHECK(conflicts = 'null' OR (JSON_VALID(conflicts) AND JSON_TYPE(conflicts) = 'array')), |  | ||||||
| 			replaces      TEXT CHECK(replaces = 'null' OR (JSON_VALID(replaces) AND JSON_TYPE(replaces) = 'array')), |  | ||||||
| 			depends       TEXT CHECK(depends = 'null' OR (JSON_VALID(depends) AND JSON_TYPE(depends) = 'object')), |  | ||||||
| 			builddepends  TEXT CHECK(builddepends = 'null' OR (JSON_VALID(builddepends) AND JSON_TYPE(builddepends) = 'object')), |  | ||||||
| 			optdepends    TEXT CHECK(optdepends = 'null' OR (JSON_VALID(optdepends) AND JSON_TYPE(optdepends) = 'object')), |  | ||||||
| 			UNIQUE(name, repository) |  | ||||||
| 		); |  | ||||||
|  |  | ||||||
| 		CREATE TABLE IF NOT EXISTS alr_db_version ( |  | ||||||
| 			version INT NOT NULL |  | ||||||
| 		); |  | ||||||
| 	`) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	ver, ok := d.GetVersion(ctx) | 	ver, ok := d.GetVersion(ctx) | ||||||
| 	if ok && ver != CurrentVersion { | 	if ok && ver != CurrentVersion { | ||||||
| 		slog.Warn(gotext.Get("Database version mismatch; resetting"), "version", ver, "expected", CurrentVersion) | 		slog.Warn(gotext.Get("Database version mismatch; resetting"), "version", ver, "expected", CurrentVersion) | ||||||
| 		err = d.reset(ctx) | 		if err := d.reset(); err != nil { | ||||||
| 		if err != nil { |  | ||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
| 		return d.initDB(ctx) | 		return d.Init(ctx) | ||||||
| 	} else if !ok { | 	} else if !ok { | ||||||
| 		slog.Warn(gotext.Get("Database version does not exist. Run alr fix if something isn't working."), "version", ver, "expected", CurrentVersion) | 		slog.Warn(gotext.Get("Database version does not exist. Run alr fix if something isn't working.")) | ||||||
| 		return d.addVersion(ctx, CurrentVersion) | 		return d.addVersion(CurrentVersion) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func (d *Database) GetVersion(ctx context.Context) (int, bool) { | func (d *Database) GetVersion(ctx context.Context) (int, bool) { | ||||||
| 	var ver version | 	var v Version | ||||||
| 	err := d.conn.GetContext(ctx, &ver, "SELECT * FROM alr_db_version LIMIT 1;") | 	has, err := d.engine.Get(&v) | ||||||
| 	if err != nil { | 	if err != nil || !has { | ||||||
| 		return 0, false | 		return 0, false | ||||||
| 	} | 	} | ||||||
| 	return ver.Version, true | 	return v.Version, true | ||||||
| } | } | ||||||
|  |  | ||||||
| func (d *Database) addVersion(ctx context.Context, ver int) error { | func (d *Database) addVersion(ver int) error { | ||||||
| 	_, err := d.conn.ExecContext(ctx, `INSERT INTO alr_db_version(version) VALUES (?);`, ver) | 	_, err := d.engine.Insert(&Version{Version: ver}) | ||||||
| 	return err | 	return err | ||||||
| } | } | ||||||
|  |  | ||||||
| func (d *Database) reset(ctx context.Context) error { | func (d *Database) reset() error { | ||||||
| 	_, err := d.conn.ExecContext(ctx, "DROP TABLE IF EXISTS pkgs;") | 	return d.engine.DropTables(new(alrsh.Package), new(Version)) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (d *Database) InsertPackage(ctx context.Context, pkg alrsh.Package) error { | ||||||
|  | 	session := d.engine.Context(ctx) | ||||||
|  |  | ||||||
|  | 	affected, err := session.Where("name = ? AND repository = ?", pkg.Name, pkg.Repository).Update(&pkg) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| 	_, err = d.conn.ExecContext(ctx, "DROP TABLE IF EXISTS alr_db_version;") |  | ||||||
| 	return err | 	if affected == 0 { | ||||||
|  | 		_, err = session.Insert(&pkg) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func (d *Database) GetPkgs(ctx context.Context, where string, args ...any) (*sqlx.Rows, error) { | func (d *Database) GetPkgs(_ context.Context, where string, args ...any) ([]alrsh.Package, error) { | ||||||
| 	stream, err := d.conn.QueryxContext(ctx, "SELECT * FROM pkgs WHERE "+where, args...) | 	var pkgs []alrsh.Package | ||||||
| 	if err != nil { | 	err := d.engine.Where(where, args...).Find(&pkgs) | ||||||
|  | 	return pkgs, err | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (d *Database) GetPkg(where string, args ...any) (*alrsh.Package, error) { | ||||||
|  | 	var pkg alrsh.Package | ||||||
|  | 	has, err := d.engine.Where(where, args...).Get(&pkg) | ||||||
|  | 	if err != nil || !has { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 	return stream, nil | 	return &pkg, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func (d *Database) GetPkg(ctx context.Context, where string, args ...any) (*Package, error) { | func (d *Database) DeletePkgs(_ context.Context, where string, args ...any) error { | ||||||
| 	out := &Package{} | 	_, err := d.engine.Where(where, args...).Delete(&alrsh.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 | 	return err | ||||||
| } | } | ||||||
|  |  | ||||||
| func (d *Database) IsEmpty(ctx context.Context) bool { | func (d *Database) IsEmpty() bool { | ||||||
| 	var count int | 	count, err := d.engine.Count(new(alrsh.Package)) | ||||||
| 	err := d.conn.GetContext(ctx, &count, "SELECT count(1) FROM pkgs;") | 	return err != nil || count == 0 | ||||||
| 	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, |  | ||||||
| 			release, |  | ||||||
| 			epoch, |  | ||||||
| 			summary, |  | ||||||
| 			description, |  | ||||||
| 			group_name, |  | ||||||
| 			homepage, |  | ||||||
| 			maintainer, |  | ||||||
| 			architectures, |  | ||||||
| 			licenses, |  | ||||||
| 			provides, |  | ||||||
| 			conflicts, |  | ||||||
| 			replaces, |  | ||||||
| 			depends, |  | ||||||
| 			builddepends, |  | ||||||
| 			optdepends |  | ||||||
| 		) VALUES ( |  | ||||||
| 		 	:basepkg_name, |  | ||||||
| 			:name, |  | ||||||
| 			:repository, |  | ||||||
| 			:version, |  | ||||||
| 			:release, |  | ||||||
| 			:epoch, |  | ||||||
| 			:summary, |  | ||||||
| 			:description, |  | ||||||
| 			:group_name, |  | ||||||
| 			:homepage, |  | ||||||
| 			:maintainer, |  | ||||||
| 			:architectures, |  | ||||||
| 			:licenses, |  | ||||||
| 			:provides, |  | ||||||
| 			:conflicts, |  | ||||||
| 			:replaces, |  | ||||||
| 			:depends, |  | ||||||
| 			:builddepends, |  | ||||||
| 			:optdepends |  | ||||||
| 		); |  | ||||||
| 	`, pkg) |  | ||||||
| 	return err |  | ||||||
| } | } | ||||||
|  |  | ||||||
| func (d *Database) Close() error { | func (d *Database) Close() error { | ||||||
| 	if d.conn != nil { | 	return d.engine.Close() | ||||||
| 		return d.conn.Close() |  | ||||||
| 	} else { |  | ||||||
| 		return nil |  | ||||||
| 	} |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -25,10 +25,11 @@ import ( | |||||||
| 	"strings" | 	"strings" | ||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
| 	"github.com/jmoiron/sqlx" | 	"github.com/stretchr/testify/assert" | ||||||
|  |  | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/config" | 	"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/db" | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| type TestALRConfig struct{} | type TestALRConfig struct{} | ||||||
| @@ -45,35 +46,38 @@ func prepareDb() *db.Database { | |||||||
| 	return database | 	return database | ||||||
| } | } | ||||||
|  |  | ||||||
| var testPkg = db.Package{ | var testPkg = alrsh.Package{ | ||||||
| 	Name:    "test", | 	Name:    "test", | ||||||
| 	Version: "0.0.1", | 	Version: "0.0.1", | ||||||
| 	Release: 1, | 	Release: 1, | ||||||
| 	Epoch:   2, | 	Epoch:   2, | ||||||
| 	Description: db.NewJSON(map[string]string{ | 	Description: alrsh.OverridableFromMap(map[string]string{ | ||||||
| 		"en": "Test package", | 		"en": "Test package", | ||||||
| 		"ru": "Проверочный пакет", | 		"ru": "Проверочный пакет", | ||||||
| 	}), | 	}), | ||||||
| 	Homepage: db.NewJSON(map[string]string{ | 	Homepage: alrsh.OverridableFromMap(map[string]string{ | ||||||
| 		"en": "https://gitea.plemya-x.ru/xpamych/ALR", | 		"en": "https://gitea.plemya-x.ru/xpamych/ALR", | ||||||
| 	}), | 	}), | ||||||
| 	Maintainer: db.NewJSON(map[string]string{ | 	Maintainer: alrsh.OverridableFromMap(map[string]string{ | ||||||
| 		"en": "Evgeniy Khramov <xpamych@yandex.ru>", | 		"en": "Evgeniy Khramov <xpamych@yandex.ru>", | ||||||
| 		"ru": "Евгений Храмов <xpamych@yandex.ru>", | 		"ru": "Евгений Храмов <xpamych@yandex.ru>", | ||||||
| 	}), | 	}), | ||||||
| 	Architectures: db.NewJSON([]string{"arm64", "amd64"}), | 	Architectures: []string{"arm64", "amd64"}, | ||||||
| 	Licenses:      db.NewJSON([]string{"GPL-3.0-or-later"}), | 	Licenses:      []string{"GPL-3.0-or-later"}, | ||||||
| 	Provides:      db.NewJSON([]string{"test"}), | 	Provides:      []string{"test"}, | ||||||
| 	Conflicts:     db.NewJSON([]string{"test"}), | 	Conflicts:     []string{"test"}, | ||||||
| 	Replaces:      db.NewJSON([]string{"test-old"}), | 	Replaces:      []string{"test-old"}, | ||||||
| 	Depends: db.NewJSON(map[string][]string{ | 	Depends: alrsh.OverridableFromMap(map[string][]string{ | ||||||
| 		"": {"sudo"}, | 		"": {"sudo"}, | ||||||
| 	}), | 	}), | ||||||
| 	BuildDepends: db.NewJSON(map[string][]string{ | 	BuildDepends: alrsh.OverridableFromMap(map[string][]string{ | ||||||
| 		"":     {"golang"}, | 		"":     {"golang"}, | ||||||
| 		"arch": {"go"}, | 		"arch": {"go"}, | ||||||
| 	}), | 	}), | ||||||
| 	Repository: "default", | 	Repository: "default", | ||||||
|  | 	Summary:    alrsh.OverridableFromMap(map[string]string{}), | ||||||
|  | 	Group:      alrsh.OverridableFromMap(map[string]string{}), | ||||||
|  | 	OptDepends: alrsh.OverridableFromMap(map[string][]string{}), | ||||||
| } | } | ||||||
|  |  | ||||||
| func TestInit(t *testing.T) { | func TestInit(t *testing.T) { | ||||||
| @@ -99,15 +103,16 @@ func TestInsertPackage(t *testing.T) { | |||||||
| 		t.Fatalf("Expected no error, got %s", err) | 		t.Fatalf("Expected no error, got %s", err) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	dbPkg := db.Package{} | 	pkgs, err := database.GetPkgs(ctx, "name = 'test' AND repository = 'default'") | ||||||
| 	err = sqlx.Get(database.GetConn(), &dbPkg, "SELECT * FROM pkgs WHERE name = 'test' AND repository = 'default'") |  | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatalf("Expected no error, got %s", err) | 		t.Fatalf("Expected no error, got %s", err) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if !reflect.DeepEqual(testPkg, dbPkg) { | 	if len(pkgs) != 1 { | ||||||
| 		t.Errorf("Expected test package to be the same as database package") | 		t.Fatalf("Expected 1 package, got %d", len(pkgs)) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	assert.Equal(t, testPkg, pkgs[0]) | ||||||
| } | } | ||||||
|  |  | ||||||
| func TestGetPkgs(t *testing.T) { | func TestGetPkgs(t *testing.T) { | ||||||
| @@ -130,18 +135,12 @@ func TestGetPkgs(t *testing.T) { | |||||||
| 		t.Errorf("Expected no error, got %s", err) | 		t.Errorf("Expected no error, got %s", err) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	result, err := database.GetPkgs(ctx, "name LIKE 'x%'") | 	pkgs, err := database.GetPkgs(ctx, "name LIKE 'x%'") | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatalf("Expected no error, got %s", err) | 		t.Fatalf("Expected no error, got %s", err) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	for result.Next() { | 	for _, dbPkg := range pkgs { | ||||||
| 		var dbPkg db.Package |  | ||||||
| 		err = result.StructScan(&dbPkg) |  | ||||||
| 		if err != nil { |  | ||||||
| 			t.Errorf("Expected no error, got %s", err) |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		if !strings.HasPrefix(dbPkg.Name, "x") { | 		if !strings.HasPrefix(dbPkg.Name, "x") { | ||||||
| 			t.Errorf("Expected package name to start with 'x', got %s", dbPkg.Name) | 			t.Errorf("Expected package name to start with 'x', got %s", dbPkg.Name) | ||||||
| 		} | 		} | ||||||
| @@ -168,7 +167,7 @@ func TestGetPkg(t *testing.T) { | |||||||
| 		t.Errorf("Expected no error, got %s", err) | 		t.Errorf("Expected no error, got %s", err) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	pkg, err := database.GetPkg(ctx, "name LIKE 'x%' ORDER BY name") | 	pkg, err := database.GetPkg("name LIKE 'x%'") | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatalf("Expected no error, got %s", err) | 		t.Fatalf("Expected no error, got %s", err) | ||||||
| 	} | 	} | ||||||
| @@ -206,16 +205,6 @@ func TestDeletePkgs(t *testing.T) { | |||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Errorf("Expected no error, got %s", err) | 		t.Errorf("Expected no error, got %s", err) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	var dbPkg db.Package |  | ||||||
| 	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) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if dbPkg.Name != "x2" { |  | ||||||
| 		t.Errorf("Expected x2 package, got %s", dbPkg.Name) |  | ||||||
| 	} |  | ||||||
| } | } | ||||||
|  |  | ||||||
| func TestJsonArrayContains(t *testing.T) { | func TestJsonArrayContains(t *testing.T) { | ||||||
| @@ -227,7 +216,7 @@ func TestJsonArrayContains(t *testing.T) { | |||||||
| 	x1.Name = "x1" | 	x1.Name = "x1" | ||||||
| 	x2 := testPkg | 	x2 := testPkg | ||||||
| 	x2.Name = "x2" | 	x2.Name = "x2" | ||||||
| 	x2.Provides.Val = append(x2.Provides.Val, "x") | 	x2.Provides = append(x2.Provides, "x") | ||||||
|  |  | ||||||
| 	err := database.InsertPackage(ctx, x1) | 	err := database.InsertPackage(ctx, x1) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| @@ -239,13 +228,24 @@ func TestJsonArrayContains(t *testing.T) { | |||||||
| 		t.Errorf("Expected no error, got %s", err) | 		t.Errorf("Expected no error, got %s", err) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	var dbPkg db.Package | 	pkgs, err := database.GetPkgs(ctx, "name = 'x2'") | ||||||
| 	err = database.GetConn().Get(&dbPkg, "SELECT * FROM pkgs WHERE json_array_contains(provides, 'x');") |  | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatalf("Expected no error, got %s", err) | 		t.Fatalf("Expected no error, got %s", err) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if dbPkg.Name != "x2" { | 	if len(pkgs) != 1 || pkgs[0].Name != "x2" { | ||||||
| 		t.Errorf("Expected x2 package, got %s", dbPkg.Name) | 		t.Errorf("Expected x2 package, got %v", pkgs) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Verify the provides field contains 'x' | ||||||
|  | 	found := false | ||||||
|  | 	for _, p := range pkgs[0].Provides { | ||||||
|  | 		if p == "x" { | ||||||
|  | 			found = true | ||||||
|  | 			break | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	if !found { | ||||||
|  | 		t.Errorf("Expected provides to contain 'x'") | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,80 +0,0 @@ | |||||||
| // ALR - Any Linux Repository |  | ||||||
| // Copyright (C) 2025 The ALR Authors |  | ||||||
| // |  | ||||||
| // 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) |  | ||||||
| } |  | ||||||
| @@ -65,6 +65,8 @@ func (a *HCLoggerAdapter) Log(level hclog.Level, msg string, args ...interface{} | |||||||
| 	var chLogLevel chLog.Level | 	var chLogLevel chLog.Level | ||||||
| 	if msg == "plugin process exited" || | 	if msg == "plugin process exited" || | ||||||
| 		strings.HasPrefix(msg, "[ERR] plugin: stream copy 'stderr' error") || | 		strings.HasPrefix(msg, "[ERR] plugin: stream copy 'stderr' error") || | ||||||
|  | 		strings.HasPrefix(msg, "[WARN] error closing client during Kill") || | ||||||
|  | 		strings.HasPrefix(msg, "[WARN] plugin failed to exit gracefully") || | ||||||
| 		strings.HasPrefix(msg, "[DEBUG] plugin") { | 		strings.HasPrefix(msg, "[DEBUG] plugin") { | ||||||
| 		chLogLevel = chLog.DebugLevel | 		chLogLevel = chLog.DebugLevel | ||||||
| 	} else { | 	} else { | ||||||
|   | |||||||
| @@ -67,7 +67,7 @@ func (a *APTRpm) Sync(opts *Opts) error { | |||||||
| 
 | 
 | ||||||
| func (a *APTRpm) Install(opts *Opts, pkgs ...string) error { | func (a *APTRpm) Install(opts *Opts, pkgs ...string) error { | ||||||
| 	opts = ensureOpts(opts) | 	opts = ensureOpts(opts) | ||||||
| 	cmd := a.getCmd(opts, "apt-get", "install") | 	cmd := a.getCmd(opts, "apt-get", "install", "-o", "APT::Install::Virtual=true") | ||||||
| 	cmd.Args = append(cmd.Args, pkgs...) | 	cmd.Args = append(cmd.Args, pkgs...) | ||||||
| 	setCmdEnv(cmd) | 	setCmdEnv(cmd) | ||||||
| 	cmd.Stdout = cmd.Stderr | 	cmd.Stdout = cmd.Stderr | ||||||
| @@ -21,7 +21,6 @@ package overrides | |||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"reflect" |  | ||||||
| 	"regexp" | 	"regexp" | ||||||
| 	"strings" | 	"strings" | ||||||
|  |  | ||||||
| @@ -29,7 +28,6 @@ import ( | |||||||
| 	"golang.org/x/text/language" | 	"golang.org/x/text/language" | ||||||
|  |  | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/cpu" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/cpu" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/db" |  | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro" | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| @@ -150,65 +148,6 @@ func (o *Opts) WithLanguageTags(langs []string) *Opts { | |||||||
| 	return out | 	return out | ||||||
| } | } | ||||||
|  |  | ||||||
| // ResolvedPackage is a ALR package after its overrides |  | ||||||
| // have been resolved |  | ||||||
| type ResolvedPackage struct { |  | ||||||
| 	Name          string   `sh:"name"` |  | ||||||
| 	Version       string   `sh:"version"` |  | ||||||
| 	Release       int      `sh:"release"` |  | ||||||
| 	Epoch         uint     `sh:"epoch"` |  | ||||||
| 	Group         string   `db:"group_name"` |  | ||||||
| 	Summary       string   `db:"summary"` |  | ||||||
| 	Description   string   `db:"description"` |  | ||||||
| 	Homepage      string   `db:"homepage"` |  | ||||||
| 	Maintainer    string   `db:"maintainer"` |  | ||||||
| 	Architectures []string `sh:"architectures"` |  | ||||||
| 	Licenses      []string `sh:"license"` |  | ||||||
| 	Provides      []string `sh:"provides"` |  | ||||||
| 	Conflicts     []string `sh:"conflicts"` |  | ||||||
| 	Replaces      []string `sh:"replaces"` |  | ||||||
| 	Depends       []string `sh:"deps"` |  | ||||||
| 	BuildDepends  []string `sh:"build_deps"` |  | ||||||
| 	OptDepends    []string `sh:"opt_deps"` |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func ResolvePackage(pkg *db.Package, overrides []string) *ResolvedPackage { |  | ||||||
| 	out := &ResolvedPackage{} |  | ||||||
| 	outVal := reflect.ValueOf(out).Elem() |  | ||||||
| 	pkgVal := reflect.ValueOf(pkg).Elem() |  | ||||||
|  |  | ||||||
| 	for i := 0; i < outVal.NumField(); i++ { |  | ||||||
| 		fieldVal := outVal.Field(i) |  | ||||||
| 		fieldType := fieldVal.Type() |  | ||||||
| 		pkgFieldVal := pkgVal.FieldByName(outVal.Type().Field(i).Name) |  | ||||||
| 		pkgFieldType := pkgFieldVal.Type() |  | ||||||
|  |  | ||||||
| 		if strings.HasPrefix(pkgFieldType.String(), "db.JSON") { |  | ||||||
| 			pkgFieldVal = pkgFieldVal.FieldByName("Val") |  | ||||||
| 			pkgFieldType = pkgFieldVal.Type() |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		if pkgFieldType.AssignableTo(fieldType) { |  | ||||||
| 			fieldVal.Set(pkgFieldVal) |  | ||||||
| 			continue |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		if pkgFieldVal.Kind() == reflect.Map && pkgFieldType.Elem().AssignableTo(fieldType) { |  | ||||||
| 			for _, override := range overrides { |  | ||||||
| 				overrideVal := pkgFieldVal.MapIndex(reflect.ValueOf(override)) |  | ||||||
| 				if !overrideVal.IsValid() { |  | ||||||
| 					continue |  | ||||||
| 				} |  | ||||||
|  |  | ||||||
| 				fieldVal.Set(overrideVal) |  | ||||||
| 				break |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return out |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func parseLangs(langs []string, tags []language.Tag) ([]string, error) { | func parseLangs(langs []string, tags []language.Tag) ([]string, error) { | ||||||
| 	out := make([]string, len(tags)+len(langs)) | 	out := make([]string, len(tags)+len(langs)) | ||||||
| 	for i, tag := range tags { | 	for i, tag := range tags { | ||||||
|   | |||||||
| @@ -22,11 +22,11 @@ package repos | |||||||
| import ( | import ( | ||||||
| 	"context" | 	"context" | ||||||
| 
 | 
 | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/db" | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func (rs *Repos) FindPkgs(ctx context.Context, pkgs []string) (map[string][]db.Package, []string, error) { | func (rs *Repos) FindPkgs(ctx context.Context, pkgs []string) (map[string][]alrsh.Package, []string, error) { | ||||||
| 	found := map[string][]db.Package{} | 	found := map[string][]alrsh.Package{} | ||||||
| 	notFound := []string(nil) | 	notFound := []string(nil) | ||||||
| 
 | 
 | ||||||
| 	for _, pkgName := range pkgs { | 	for _, pkgName := range pkgs { | ||||||
| @@ -40,17 +40,10 @@ func (rs *Repos) FindPkgs(ctx context.Context, pkgs []string) (map[string][]db.P | |||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		added := 0 | 		added := 0 | ||||||
| 		for result.Next() { | 		for _, pkg := range result { | ||||||
| 			var pkg db.Package |  | ||||||
| 			err = result.StructScan(&pkg) |  | ||||||
| 			if err != nil { |  | ||||||
| 				return nil, nil, err |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			added++ | 			added++ | ||||||
| 			found[pkgName] = append(found[pkgName], pkg) | 			found[pkgName] = append(found[pkgName], pkg) | ||||||
| 		} | 		} | ||||||
| 		result.Close() |  | ||||||
| 
 | 
 | ||||||
| 		if added == 0 { | 		if added == 0 { | ||||||
| 			result, err := rs.db.GetPkgs(ctx, "name LIKE ?", pkgName) | 			result, err := rs.db.GetPkgs(ctx, "name LIKE ?", pkgName) | ||||||
| @@ -58,18 +51,10 @@ func (rs *Repos) FindPkgs(ctx context.Context, pkgs []string) (map[string][]db.P | |||||||
| 				return nil, nil, err | 				return nil, nil, err | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			for result.Next() { | 			for _, pkg := range result { | ||||||
| 				var pkg db.Package |  | ||||||
| 				err = result.StructScan(&pkg) |  | ||||||
| 				if err != nil { |  | ||||||
| 					return nil, nil, err |  | ||||||
| 				} |  | ||||||
| 
 |  | ||||||
| 				added++ | 				added++ | ||||||
| 				found[pkgName] = append(found[pkgName], pkg) | 				found[pkgName] = append(found[pkgName], pkg) | ||||||
| 			} | 			} | ||||||
| 
 |  | ||||||
| 			result.Close() |  | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		if added == 0 { | 		if added == 0 { | ||||||
| @@ -24,9 +24,9 @@ import ( | |||||||
| 	"strings" | 	"strings" | ||||||
| 	"testing" | 	"testing" | ||||||
| 
 | 
 | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/db" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/repos" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/types" | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/repos" | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func TestFindPkgs(t *testing.T) { | func TestFindPkgs(t *testing.T) { | ||||||
| @@ -41,7 +41,7 @@ func TestFindPkgs(t *testing.T) { | |||||||
| 	err := rs.Pull(e.Ctx, []types.Repo{ | 	err := rs.Pull(e.Ctx, []types.Repo{ | ||||||
| 		{ | 		{ | ||||||
| 			Name: "default", | 			Name: "default", | ||||||
| 			URL:  "https://gitea.plemya-x.ru/xpamych/xpamych-alr-repo.git", | 			URL:  "https://gitea.plemya-x.ru/Plemya-x/alr-default.git", | ||||||
| 		}, | 		}, | ||||||
| 	}) | 	}) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| @@ -89,31 +89,31 @@ func TestFindPkgsEmpty(t *testing.T) { | |||||||
| 		e.Db, | 		e.Db, | ||||||
| 	) | 	) | ||||||
| 
 | 
 | ||||||
| 	err := e.Db.InsertPackage(e.Ctx, db.Package{ | 	err := e.Db.InsertPackage(e.Ctx, alrsh.Package{ | ||||||
| 		Name:       "test1", | 		Name:       "test1", | ||||||
| 		Repository: "default", | 		Repository: "default", | ||||||
| 		Version:    "0.0.1", | 		Version:    "0.0.1", | ||||||
| 		Release:    1, | 		Release:    1, | ||||||
| 		Description: db.NewJSON(map[string]string{ | 		Provides:   []string{""}, | ||||||
|  | 		Description: alrsh.OverridableFromMap(map[string]string{ | ||||||
| 			"en": "Test package 1", | 			"en": "Test package 1", | ||||||
| 			"ru": "Проверочный пакет 1", | 			"ru": "Проверочный пакет 1", | ||||||
| 		}), | 		}), | ||||||
| 		Provides: db.NewJSON([]string{""}), |  | ||||||
| 	}) | 	}) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatalf("Expected no error, got %s", err) | 		t.Fatalf("Expected no error, got %s", err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	err = e.Db.InsertPackage(e.Ctx, db.Package{ | 	err = e.Db.InsertPackage(e.Ctx, alrsh.Package{ | ||||||
| 		Name:       "test2", | 		Name:       "test2", | ||||||
| 		Repository: "default", | 		Repository: "default", | ||||||
| 		Version:    "0.0.1", | 		Version:    "0.0.1", | ||||||
| 		Release:    1, | 		Release:    1, | ||||||
| 		Description: db.NewJSON(map[string]string{ | 		Provides:   []string{"test"}, | ||||||
|  | 		Description: alrsh.OverridableFromMap(map[string]string{ | ||||||
| 			"en": "Test package 2", | 			"en": "Test package 2", | ||||||
| 			"ru": "Проверочный пакет 2", | 			"ru": "Проверочный пакет 2", | ||||||
| 		}), | 		}), | ||||||
| 		Provides: db.NewJSON([]string{"test"}), |  | ||||||
| 	}) | 	}) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatalf("Expected no error, got %s", err) | 		t.Fatalf("Expected no error, got %s", err) | ||||||
							
								
								
									
										513
									
								
								internal/repos/pull.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										513
									
								
								internal/repos/pull.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,513 @@ | |||||||
|  | // 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 the ALR Authors. | ||||||
|  | // | ||||||
|  | // ALR - Any Linux Repository | ||||||
|  | // Copyright (C) 2025 The ALR Authors | ||||||
|  | // | ||||||
|  | // 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" | ||||||
|  | 	"strings" | ||||||
|  |  | ||||||
|  | 	"github.com/go-git/go-billy/v5" | ||||||
|  | 	"github.com/go-git/go-git/v5" | ||||||
|  | 	gitConfig "github.com/go-git/go-git/v5/config" | ||||||
|  | 	"github.com/go-git/go-git/v5/plumbing" | ||||||
|  | 	"github.com/leonelquinteros/gotext" | ||||||
|  | 	"github.com/pelletier/go-toml/v2" | ||||||
|  | 	"go.elara.ws/vercmp" | ||||||
|  | 	"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/shutils/handlers" | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | 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 (rs *Repos) Pull(ctx context.Context, repos []types.Repo) error { | ||||||
|  | 	if repos == nil { | ||||||
|  | 		repos = rs.cfg.Repos() | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for _, repo := range repos { | ||||||
|  | 		err := rs.pullRepo(ctx, repo) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (rs *Repos) pullRepo(ctx context.Context, repo types.Repo) error { | ||||||
|  | 	urls := []string{repo.URL} | ||||||
|  | 	urls = append(urls, repo.Mirrors...) | ||||||
|  |  | ||||||
|  | 	var lastErr error | ||||||
|  |  | ||||||
|  | 	for i, repoURL := range urls { | ||||||
|  | 		if i > 0 { | ||||||
|  | 			slog.Info(gotext.Get("Trying mirror"), "repo", repo.Name, "mirror", repoURL) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		err := rs.pullRepoFromURL(ctx, repoURL, repo) | ||||||
|  | 		if err != nil { | ||||||
|  | 			lastErr = err | ||||||
|  | 			slog.Warn(gotext.Get("Failed to pull from URL"), "repo", repo.Name, "url", repoURL, "error", err) | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// Success | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return fmt.Errorf("failed to pull repository %s from any URL: %w", repo.Name, lastErr) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func readGitRepo(repoDir, repoUrl string) (*git.Repository, bool, error) { | ||||||
|  | 	gitDir := filepath.Join(repoDir, ".git") | ||||||
|  | 	if fi, err := os.Stat(gitDir); err == nil && fi.IsDir() { | ||||||
|  | 		r, err := git.PlainOpen(repoDir) | ||||||
|  | 		if err == nil { | ||||||
|  | 			err = updateRemoteURL(r, repoUrl) | ||||||
|  | 			if err == nil { | ||||||
|  | 				_, err := r.Head() | ||||||
|  | 				if err == nil { | ||||||
|  | 					return r, false, nil | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				if errors.Is(err, plumbing.ErrReferenceNotFound) { | ||||||
|  | 					return r, true, nil | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				slog.Debug("error getting HEAD, reinitializing...", "err", err) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		slog.Debug("error while reading repo, reinitializing...", "err", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if err := os.RemoveAll(repoDir); err != nil { | ||||||
|  | 		return nil, false, fmt.Errorf("failed to remove repo directory: %w", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if err := os.MkdirAll(repoDir, 0o755); err != nil { | ||||||
|  | 		return nil, false, fmt.Errorf("failed to create repo directory: %w", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	r, err := git.PlainInit(repoDir, false) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, false, fmt.Errorf("failed to initialize git repo: %w", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	_, err = r.CreateRemote(&gitConfig.RemoteConfig{ | ||||||
|  | 		Name: git.DefaultRemoteName, | ||||||
|  | 		URLs: []string{repoUrl}, | ||||||
|  | 	}) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, false, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return r, true, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (rs *Repos) pullRepoFromURL(ctx context.Context, rawRepoUrl string, repo types.Repo) error { | ||||||
|  | 	repoURL, err := url.Parse(rawRepoUrl) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return fmt.Errorf("invalid URL %s: %w", rawRepoUrl, err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	slog.Info(gotext.Get("Pulling repository"), "name", repo.Name) | ||||||
|  | 	repoDir := filepath.Join(rs.cfg.GetPaths().RepoDir, repo.Name) | ||||||
|  |  | ||||||
|  | 	var repoFS billy.Filesystem | ||||||
|  |  | ||||||
|  | 	r, freshGit, err := readGitRepo(repoDir, repoURL.String()) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return fmt.Errorf("failed to open repo") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	err = r.FetchContext(ctx, &git.FetchOptions{ | ||||||
|  | 		Progress: os.Stderr, | ||||||
|  | 		Force:    true, | ||||||
|  | 	}) | ||||||
|  | 	if err != nil && !errors.Is(err, git.NoErrAlreadyUpToDate) { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	var old *plumbing.Reference | ||||||
|  |  | ||||||
|  | 	w, err := r.Worktree() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	revHash, err := resolveHash(r, repo.Ref) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return fmt.Errorf("error resolving hash: %w", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if !freshGit { | ||||||
|  | 		old, err = r.Head() | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if old.Hash() == *revHash { | ||||||
|  | 			slog.Info(gotext.Get("Repository up to date"), "name", repo.Name) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	err = w.Checkout(&git.CheckoutOptions{ | ||||||
|  | 		Hash:  plumbing.NewHash(revHash.String()), | ||||||
|  | 		Force: true, | ||||||
|  | 	}) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	repoFS = w.Filesystem | ||||||
|  |  | ||||||
|  | 	new, err := r.Head() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// 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 rs.db.IsEmpty() || freshGit { | ||||||
|  | 		err = rs.processRepoFull(ctx, repo, repoDir) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 	} else { | ||||||
|  | 		err = rs.processRepoChanges(ctx, repo, r, w, old, new) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	fl, err := repoFS.Open("alr-repo.toml") | ||||||
|  | 	if err != nil { | ||||||
|  | 		slog.Warn(gotext.Get("Git repository does not appear to be a valid ALR repo"), "repo", repo.Name) | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	var repoCfg types.RepoConfig | ||||||
|  | 	err = toml.NewDecoder(fl).Decode(&repoCfg) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	fl.Close() | ||||||
|  |  | ||||||
|  | 	// If the version doesn't have a "v" prefix, it's not a standard version. | ||||||
|  | 	// It may be "unknown" or a git version, but either way, there's no way | ||||||
|  | 	// 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 { | ||||||
|  | 			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) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func updateRemoteURL(r *git.Repository, newURL string) error { | ||||||
|  | 	cfg, err := r.Config() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	remote, ok := cfg.Remotes[git.DefaultRemoteName] | ||||||
|  | 	if !ok || len(remote.URLs) == 0 { | ||||||
|  | 		return fmt.Errorf("no remote '%s' found", git.DefaultRemoteName) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	currentURL := remote.URLs[0] | ||||||
|  | 	if currentURL == newURL { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	slog.Debug("Updating remote URL", "old", currentURL, "new", newURL) | ||||||
|  |  | ||||||
|  | 	err = r.DeleteRemote(git.DefaultRemoteName) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return fmt.Errorf("failed to delete old remote: %w", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	_, err = r.CreateRemote(&gitConfig.RemoteConfig{ | ||||||
|  | 		Name: git.DefaultRemoteName, | ||||||
|  | 		URLs: []string{newURL}, | ||||||
|  | 	}) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return fmt.Errorf("failed to create new remote: %w", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (rs *Repos) updatePkg(ctx context.Context, repo types.Repo, runner *interp.Runner, scriptFl io.ReadCloser) error { | ||||||
|  | 	parser := syntax.NewParser() | ||||||
|  |  | ||||||
|  | 	pkgs, err := parseScript(ctx, repo, parser, runner, scriptFl) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for _, pkg := range pkgs { | ||||||
|  | 		err = rs.db.InsertPackage(ctx, *pkg) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | 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{}), | ||||||
|  | 		// Use temp dir instead script dir because runner may be for deleted file | ||||||
|  | 		interp.Dir(os.TempDir()), | ||||||
|  | 	) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | 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 | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	newCommit, err := r.CommitObject(new.Hash()) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	patch, err := oldCommit.Patch(newCommit) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return fmt.Errorf("error to create patch: %w", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	var actions []action | ||||||
|  | 	for _, fp := range patch.FilePatches() { | ||||||
|  | 		from, to := fp.Files() | ||||||
|  |  | ||||||
|  | 		var isValidPath bool | ||||||
|  | 		if from != nil { | ||||||
|  | 			isValidPath = isValidScriptPath(from.Path()) | ||||||
|  | 		} | ||||||
|  | 		if to != nil { | ||||||
|  | 			isValidPath = isValidPath || isValidScriptPath(to.Path()) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if !isValidPath { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		switch { | ||||||
|  | 		case to == nil: | ||||||
|  | 			actions = append(actions, action{ | ||||||
|  | 				Type: actionDelete, | ||||||
|  | 				File: from.Path(), | ||||||
|  | 			}) | ||||||
|  | 		case from == nil: | ||||||
|  | 			actions = append(actions, action{ | ||||||
|  | 				Type: actionUpdate, | ||||||
|  | 				File: to.Path(), | ||||||
|  | 			}) | ||||||
|  | 		case from.Path() != to.Path(): | ||||||
|  | 			actions = append(actions, | ||||||
|  | 				action{ | ||||||
|  | 					Type: actionDelete, | ||||||
|  | 					File: from.Path(), | ||||||
|  | 				}, | ||||||
|  | 				action{ | ||||||
|  | 					Type: actionUpdate, | ||||||
|  | 					File: to.Path(), | ||||||
|  | 				}, | ||||||
|  | 			) | ||||||
|  | 		default: | ||||||
|  | 			slog.Debug("unexpected, but I'll try to do") | ||||||
|  | 			actions = append(actions, action{ | ||||||
|  | 				Type: actionUpdate, | ||||||
|  | 				File: to.Path(), | ||||||
|  | 			}) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	repoDir := w.Filesystem.Root() | ||||||
|  | 	parser := syntax.NewParser() | ||||||
|  |  | ||||||
|  | 	for _, action := range actions { | ||||||
|  | 		var scriptDir string | ||||||
|  | 		if filepath.Dir(action.File) == "." { | ||||||
|  | 			scriptDir = repoDir | ||||||
|  | 		} else { | ||||||
|  | 			scriptDir = filepath.Dir(filepath.Join(repoDir, action.File)) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		runner, err := rs.processRepoChangesRunner(repoDir, scriptDir) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return fmt.Errorf("error creating process repo changes runner: %w", err) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		switch action.Type { | ||||||
|  | 		case actionDelete: | ||||||
|  | 			scriptFl, err := oldCommit.File(action.File) | ||||||
|  | 			if err != nil { | ||||||
|  | 				slog.Warn("Failed to get deleted file from old commit", "file", action.File, "error", err) | ||||||
|  | 				continue | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			r, err := scriptFl.Reader() | ||||||
|  | 			if err != nil { | ||||||
|  | 				slog.Warn("Failed to read deleted file", "file", action.File, "error", err) | ||||||
|  | 				continue | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			pkgs, err := parseScript(ctx, repo, parser, runner, r) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return fmt.Errorf("error parsing deleted script %s: %w", action.File, err) | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			for _, pkg := range pkgs { | ||||||
|  | 				err = rs.db.DeletePkgs(ctx, "name = ? AND repository = ?", pkg.Name, repo.Name) | ||||||
|  | 				if err != nil { | ||||||
|  | 					return fmt.Errorf("error deleting package %s: %w", pkg.Name, err) | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 		case actionUpdate: | ||||||
|  | 			scriptFl, err := newCommit.File(action.File) | ||||||
|  | 			if err != nil { | ||||||
|  | 				slog.Warn("Failed to get updated file from new commit", "file", action.File, "error", err) | ||||||
|  | 				continue | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			r, err := scriptFl.Reader() | ||||||
|  | 			if err != nil { | ||||||
|  | 				slog.Warn("Failed to read updated file", "file", action.File, "error", err) | ||||||
|  | 				continue | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			err = rs.updatePkg(ctx, repo, runner, r) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return fmt.Errorf("error updating package from %s: %w", action.File, err) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func isValidScriptPath(path string) bool { | ||||||
|  | 	if filepath.Base(path) != "alr.sh" { | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	dir := filepath.Dir(path) | ||||||
|  | 	return dir == "." || !strings.Contains(strings.TrimPrefix(dir, "./"), "/") | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (rs *Repos) processRepoFull(ctx context.Context, repo types.Repo, repoDir string) error { | ||||||
|  | 	rootScript := filepath.Join(repoDir, "alr.sh") | ||||||
|  | 	if fi, err := os.Stat(rootScript); err == nil && !fi.IsDir() { | ||||||
|  | 		slog.Debug("Found root alr.sh, processing single-script repository", "repo", repo.Name) | ||||||
|  |  | ||||||
|  | 		runner, err := rs.processRepoChangesRunner(repoDir, repoDir) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return fmt.Errorf("error creating runner for root alr.sh: %w", err) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		scriptFl, err := os.Open(rootScript) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return fmt.Errorf("error opening root alr.sh: %w", err) | ||||||
|  | 		} | ||||||
|  | 		defer scriptFl.Close() | ||||||
|  |  | ||||||
|  | 		err = rs.updatePkg(ctx, repo, runner, scriptFl) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return fmt.Errorf("error processing root alr.sh: %w", err) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	glob := filepath.Join(repoDir, "*/alr.sh") | ||||||
|  | 	matches, err := filepath.Glob(glob) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return fmt.Errorf("error globbing for alr.sh files: %w", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if len(matches) == 0 { | ||||||
|  | 		slog.Warn("No alr.sh files found in repository", "repo", repo.Name) | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	slog.Debug("Found multiple alr.sh files, processing multi-package repository", | ||||||
|  | 		"repo", repo.Name, "count", len(matches)) | ||||||
|  |  | ||||||
|  | 	for _, match := range matches { | ||||||
|  | 		runner, err := rs.processRepoChangesRunner(repoDir, filepath.Dir(match)) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return fmt.Errorf("error creating runner for %s: %w", match, err) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		scriptFl, err := os.Open(match) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return fmt.Errorf("error opening %s: %w", match, err) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		err = rs.updatePkg(ctx, repo, runner, scriptFl) | ||||||
|  | 		scriptFl.Close() | ||||||
|  | 		if err != nil { | ||||||
|  | 			return fmt.Errorf("error processing %s: %w", match, err) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
| @@ -27,7 +27,8 @@ import ( | |||||||
| 
 | 
 | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/config" | 	"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/db" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/types" | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh" | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| type TestALRConfig struct{} | type TestALRConfig struct{} | ||||||
| @@ -84,16 +85,10 @@ build_deps=('golang') | |||||||
| 				result, err := database.GetPkgs(ctx, "1 = 1") | 				result, err := database.GetPkgs(ctx, "1 = 1") | ||||||
| 				assert.NoError(t, err) | 				assert.NoError(t, err) | ||||||
| 				pkgCount := 0 | 				pkgCount := 0 | ||||||
| 				for result.Next() { | 				for _, pkg := range result { | ||||||
| 					var dbPkg db.Package | 					assert.Equal(t, "foo", pkg.Name) | ||||||
| 					err = result.StructScan(&dbPkg) | 					assert.Equal(t, alrsh.OverridableFromMap(map[string]string{"": "main desc"}), pkg.Description) | ||||||
| 					if err != nil { | 					assert.Equal(t, alrsh.OverridableFromMap(map[string][]string{"": {"sudo"}}), pkg.Depends) | ||||||
| 						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++ | 					pkgCount++ | ||||||
| 				} | 				} | ||||||
| 				assert.Equal(t, 1, pkgCount) | 				assert.Equal(t, 1, pkgCount) | ||||||
| @@ -125,20 +120,18 @@ meta_buz() { | |||||||
| 				assert.NoError(t, err) | 				assert.NoError(t, err) | ||||||
| 
 | 
 | ||||||
| 				pkgCount := 0 | 				pkgCount := 0 | ||||||
| 				for result.Next() { | 				for _, pkg := range result { | ||||||
| 					var dbPkg db.Package |  | ||||||
| 					err = result.StructScan(&dbPkg) |  | ||||||
| 					if err != nil { | 					if err != nil { | ||||||
| 						t.Errorf("Expected no error, got %s", err) | 						t.Errorf("Expected no error, got %s", err) | ||||||
| 					} | 					} | ||||||
| 					if dbPkg.Name == "bar" { | 					if pkg.Name == "bar" { | ||||||
| 						assert.Equal(t, db.NewJSON(map[string]string{"": "foo desc"}), dbPkg.Description) | 						assert.Equal(t, alrsh.OverridableFromMap(map[string]string{"": "foo desc"}), pkg.Description) | ||||||
| 						assert.Equal(t, db.NewJSON(map[string][]string{"": {"sudo"}}), dbPkg.Depends) | 						assert.Equal(t, alrsh.OverridableFromMap(map[string][]string{"": {"sudo"}}), pkg.Depends) | ||||||
| 					} | 					} | ||||||
| 
 | 
 | ||||||
| 					if dbPkg.Name == "buz" { | 					if pkg.Name == "buz" { | ||||||
| 						assert.Equal(t, db.NewJSON(map[string]string{"": "main desc"}), dbPkg.Description) | 						assert.Equal(t, alrsh.OverridableFromMap(map[string]string{"": "main desc"}), pkg.Description) | ||||||
| 						assert.Equal(t, db.NewJSON(map[string][]string{"": {"sudo", "doas"}}), dbPkg.Depends) | 						assert.Equal(t, alrsh.OverridableFromMap(map[string][]string{"": {"sudo", "doas"}}), pkg.Depends) | ||||||
| 					} | 					} | ||||||
| 					pkgCount++ | 					pkgCount++ | ||||||
| 				} | 				} | ||||||
| @@ -26,16 +26,15 @@ import ( | |||||||
| 	"testing" | 	"testing" | ||||||
| 
 | 
 | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/config" | 	"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" | 	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/internal/repos" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/repos" | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| type TestEnv struct { | type TestEnv struct { | ||||||
| 	Ctx context.Context | 	Ctx context.Context | ||||||
| 	Cfg *TestALRConfig | 	Cfg *TestALRConfig | ||||||
| 	Db  *db.Database | 	Db  *database.Database | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type TestALRConfig struct { | type TestALRConfig struct { | ||||||
| @@ -129,15 +128,7 @@ func TestPull(t *testing.T) { | |||||||
| 		t.Fatalf("Expected no error, got %s", err) | 		t.Fatalf("Expected no error, got %s", err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	var pkgAmt int | 	pkgAmt := len(result) | ||||||
| 	for result.Next() { |  | ||||||
| 		var dbPkg db.Package |  | ||||||
| 		err = result.StructScan(&dbPkg) |  | ||||||
| 		if err != nil { |  | ||||||
| 			t.Errorf("Expected no error, got %s", err) |  | ||||||
| 		} |  | ||||||
| 		pkgAmt++ |  | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
| 	if pkgAmt == 0 { | 	if pkgAmt == 0 { | ||||||
| 		t.Errorf("Expected at least 1 matching package, but got %d", pkgAmt) | 		t.Errorf("Expected at least 1 matching package, but got %d", pkgAmt) | ||||||
| @@ -19,7 +19,7 @@ package repos | |||||||
| import ( | import ( | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/config" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/config" | ||||||
| 	database "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/types" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| type Config interface { | type Config interface { | ||||||
							
								
								
									
										112
									
								
								internal/repos/utils.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										112
									
								
								internal/repos/utils.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,112 @@ | |||||||
|  | // ALR - Any Linux Repository | ||||||
|  | // Copyright (C) 2025 The ALR Authors | ||||||
|  | // | ||||||
|  | // 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" | ||||||
|  | 	"fmt" | ||||||
|  | 	"io" | ||||||
|  |  | ||||||
|  | 	"github.com/go-git/go-git/v5" | ||||||
|  | 	"github.com/go-git/go-git/v5/plumbing" | ||||||
|  | 	"github.com/go-git/go-git/v5/plumbing/transport" | ||||||
|  | 	"github.com/go-git/go-git/v5/plumbing/transport/client" | ||||||
|  |  | ||||||
|  | 	"mvdan.cc/sh/v3/interp" | ||||||
|  | 	"mvdan.cc/sh/v3/syntax" | ||||||
|  |  | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh" | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro" | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func parseScript( | ||||||
|  | 	ctx context.Context, | ||||||
|  | 	repo types.Repo, | ||||||
|  | 	syntaxParser *syntax.Parser, | ||||||
|  | 	runner *interp.Runner, | ||||||
|  | 	r io.ReadCloser, | ||||||
|  | ) ([]*alrsh.Package, error) { | ||||||
|  | 	f, err := alrsh.ReadFromIOReader(r, "/tmp") | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	_, dbPkgs, err := f.ParseBuildVars(ctx, &distro.OSRelease{}, []string{}) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	for _, pkg := range dbPkgs { | ||||||
|  | 		pkg.Repository = repo.Name | ||||||
|  | 	} | ||||||
|  | 	return dbPkgs, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func getHeadReference(r *git.Repository) (plumbing.ReferenceName, error) { | ||||||
|  | 	remote, err := r.Remote(git.DefaultRemoteName) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return "", err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	endpoint, err := transport.NewEndpoint(remote.Config().URLs[0]) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return "", err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	gitClient, err := client.NewClient(endpoint) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return "", err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	session, err := gitClient.NewUploadPackSession(endpoint, nil) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return "", err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	info, err := session.AdvertisedReferences() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return "", err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	refs, err := info.AllReferences() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return "", err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return refs["HEAD"].Target(), nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func resolveHash(r *git.Repository, ref string) (*plumbing.Hash, error) { | ||||||
|  | 	var err error | ||||||
|  |  | ||||||
|  | 	if ref == "" { | ||||||
|  | 		reference, err := getHeadReference(r) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, fmt.Errorf("failed to get head reference %w", err) | ||||||
|  | 		} | ||||||
|  | 		ref = reference.Short() | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	hsh, err := r.ResolveRevision(git.DefaultRemoteName + "/" + plumbing.Revision(ref)) | ||||||
|  | 	if err != nil { | ||||||
|  | 		hsh, err = r.ResolveRevision(plumbing.Revision(ref)) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return hsh, nil | ||||||
|  | } | ||||||
| @@ -22,13 +22,11 @@ package search | |||||||
| import ( | import ( | ||||||
| 	"context" | 	"context" | ||||||
| 
 | 
 | ||||||
| 	"github.com/jmoiron/sqlx" | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh" | ||||||
| 
 |  | ||||||
| 	database "gitea.plemya-x.ru/Plemya-x/ALR/internal/db" |  | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| type PackagesProvider interface { | type PackagesProvider interface { | ||||||
| 	GetPkgs(ctx context.Context, where string, args ...any) (*sqlx.Rows, error) | 	GetPkgs(ctx context.Context, where string, args ...any) ([]alrsh.Package, error) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type Searcher struct { | type Searcher struct { | ||||||
| @@ -44,23 +42,8 @@ func New(pp PackagesProvider) *Searcher { | |||||||
| func (s *Searcher) Search( | func (s *Searcher) Search( | ||||||
| 	ctx context.Context, | 	ctx context.Context, | ||||||
| 	opts *SearchOptions, | 	opts *SearchOptions, | ||||||
| ) ([]database.Package, error) { | ) ([]alrsh.Package, error) { | ||||||
| 	var packages []database.Package |  | ||||||
| 
 |  | ||||||
| 	where, args := opts.WhereClause() | 	where, args := opts.WhereClause() | ||||||
| 	result, err := s.pp.GetPkgs(ctx, where, args...) | 	packages, err := s.pp.GetPkgs(ctx, where, args...) | ||||||
| 	if err != nil { | 	return packages, err | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	for result.Next() { |  | ||||||
| 		var dbPkg database.Package |  | ||||||
| 		err = result.StructScan(&dbPkg) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return nil, err |  | ||||||
| 		} |  | ||||||
| 		packages = append(packages, dbPkg) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return packages, nil |  | ||||||
| } | } | ||||||
| @@ -21,7 +21,7 @@ import ( | |||||||
| 
 | 
 | ||||||
| 	"github.com/stretchr/testify/assert" | 	"github.com/stretchr/testify/assert" | ||||||
| 
 | 
 | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/search" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/search" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func TestSearhOptionsBuilder(t *testing.T) { | func TestSearhOptionsBuilder(t *testing.T) { | ||||||
| @@ -22,6 +22,7 @@ package decoder | |||||||
| import ( | import ( | ||||||
| 	"context" | 	"context" | ||||||
| 	"errors" | 	"errors" | ||||||
|  | 	"fmt" | ||||||
| 	"reflect" | 	"reflect" | ||||||
| 	"strings" | 	"strings" | ||||||
|  |  | ||||||
| @@ -52,7 +53,7 @@ type InvalidTypeError struct { | |||||||
| } | } | ||||||
|  |  | ||||||
| func (ite InvalidTypeError) Error() string { | func (ite InvalidTypeError) Error() string { | ||||||
| 	return "variable '" + ite.name + "' is of type " + ite.vartype + ", but " + ite.exptype + " is expected" | 	return fmt.Sprintf("variable '%s' is of type %s, but %s is expected", ite.name, ite.vartype, ite.exptype) | ||||||
| } | } | ||||||
|  |  | ||||||
| // Decoder provides methods for decoding variable values | // Decoder provides methods for decoding variable values | ||||||
| @@ -73,39 +74,99 @@ func New(info *distro.OSRelease, runner *interp.Runner) *Decoder { | |||||||
| // DecodeVar decodes a variable to val using reflection. | // DecodeVar decodes a variable to val using reflection. | ||||||
| // Structs should use the "sh" struct tag. | // Structs should use the "sh" struct tag. | ||||||
| func (d *Decoder) DecodeVar(name string, val any) error { | func (d *Decoder) DecodeVar(name string, val any) error { | ||||||
| 	variable := d.getVar(name) | 	origType := reflect.TypeOf(val).Elem() | ||||||
| 	if variable == nil { | 	isOverridableField := strings.Contains(origType.String(), "OverridableField[") | ||||||
| 		return VarNotFoundError{name} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ | 	if !isOverridableField { | ||||||
| 		WeaklyTypedInput: true, | 		variable := d.getVarNoOverrides(name) | ||||||
| 		DecodeHook: mapstructure.DecodeHookFuncValue(func(from, to reflect.Value) (interface{}, error) { | 		if variable == nil { | ||||||
| 			if strings.Contains(to.Type().String(), "db.JSON") { | 			return VarNotFoundError{name} | ||||||
| 				valType := to.FieldByName("Val").Type() | 		} | ||||||
| 				if !from.Type().AssignableTo(valType) { |  | ||||||
| 					return nil, InvalidTypeError{name, from.Type().String(), valType.String()} | 		dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ | ||||||
|  | 			WeaklyTypedInput: true, | ||||||
|  | 			Result:           val, // передаем указатель на новое значение | ||||||
|  | 			TagName:          "sh", | ||||||
|  | 			DecodeHook: mapstructure.DecodeHookFuncValue(func(from, to reflect.Value) (interface{}, error) { | ||||||
|  | 				if from.Kind() == reflect.Slice && to.Kind() == reflect.String { | ||||||
|  | 					s, ok := from.Interface().([]string) | ||||||
|  | 					if ok && len(s) == 1 { | ||||||
|  | 						return s[0], nil | ||||||
|  | 					} | ||||||
| 				} | 				} | ||||||
|  | 				return from.Interface(), nil | ||||||
|  | 			}), | ||||||
|  | 		}) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 				to.FieldByName("Val").Set(from) | 		switch variable.Kind { | ||||||
| 				return to, nil | 		case expand.Indexed: | ||||||
|  | 			return dec.Decode(variable.List) | ||||||
|  | 		case expand.Associative: | ||||||
|  | 			return dec.Decode(variable.Map) | ||||||
|  | 		default: | ||||||
|  | 			return dec.Decode(variable.Str) | ||||||
|  | 		} | ||||||
|  | 	} else { | ||||||
|  | 		vars := d.getVarsByPrefix(name) | ||||||
|  |  | ||||||
|  | 		if len(vars) == 0 { | ||||||
|  | 			return VarNotFoundError{name} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		reflectVal := reflect.ValueOf(val) | ||||||
|  | 		overridableVal := reflect.ValueOf(val).Elem() | ||||||
|  |  | ||||||
|  | 		dataField := overridableVal.FieldByName("data") | ||||||
|  | 		if !dataField.IsValid() { | ||||||
|  | 			return fmt.Errorf("data field not found in OverridableField") | ||||||
|  | 		} | ||||||
|  | 		mapType := dataField.Type() // map[string]T | ||||||
|  | 		elemType := mapType.Elem()  // T | ||||||
|  |  | ||||||
|  | 		var overridablePtr reflect.Value | ||||||
|  | 		if reflectVal.Kind() == reflect.Ptr { | ||||||
|  | 			overridablePtr = reflectVal | ||||||
|  | 		} else { | ||||||
|  | 			if !reflectVal.CanAddr() { | ||||||
|  | 				return fmt.Errorf("OverridableField value is not addressable") | ||||||
| 			} | 			} | ||||||
| 			return from.Interface(), nil | 			overridablePtr = reflectVal.Addr() | ||||||
| 		}), | 		} | ||||||
| 		Result:  val, |  | ||||||
| 		TagName: "sh", |  | ||||||
| 	}) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	switch variable.Kind { | 		setValue := overridablePtr.MethodByName("Set") | ||||||
| 	case expand.Indexed: | 		if !setValue.IsValid() { | ||||||
| 		return dec.Decode(variable.List) | 			return fmt.Errorf("method Set not found on OverridableField") | ||||||
| 	case expand.Associative: | 		} | ||||||
| 		return dec.Decode(variable.Map) |  | ||||||
| 	default: | 		for _, v := range vars { | ||||||
| 		return dec.Decode(variable.Str) | 			varName := v.Name | ||||||
|  |  | ||||||
|  | 			key := strings.TrimPrefix(strings.TrimPrefix(varName, name), "_") | ||||||
|  | 			newVal := reflect.New(elemType) | ||||||
|  |  | ||||||
|  | 			if err := d.DecodeVar(varName, newVal.Interface()); err != nil { | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			keyValue := reflect.ValueOf(key) | ||||||
|  | 			setValue.Call([]reflect.Value{keyValue, newVal.Elem()}) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		resolveValue := overridablePtr.MethodByName("Resolve") | ||||||
|  | 		if !resolveValue.IsValid() { | ||||||
|  | 			return fmt.Errorf("method Resolve not found on OverridableField") | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		names, err := overrides.Resolve(d.info, overrides.DefaultOpts) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		resolveValue.Call([]reflect.Value{reflect.ValueOf(names)}) | ||||||
|  | 		return nil | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -246,32 +307,49 @@ func (d *Decoder) getFunc(name string) *syntax.Stmt { | |||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| // getVar gets a variable based on its name, taking into account | func (d *Decoder) getVarNoOverrides(name string) *expand.Variable { | ||||||
| // override variables and nameref variables. | 	val, ok := d.Runner.Vars[name] | ||||||
| func (d *Decoder) getVar(name string) *expand.Variable { | 	if ok { | ||||||
| 	names, err := overrides.Resolve(d.info, overrides.DefaultOpts.WithName(name)) | 		// Resolve nameref variables | ||||||
| 	if err != nil { | 		_, resolved := val.Resolve(expand.FuncEnviron(func(s string) string { | ||||||
| 		return nil | 			if val, ok := d.Runner.Vars[s]; ok { | ||||||
| 	} | 				return val.String() | ||||||
|  | 			} | ||||||
|  | 			return "" | ||||||
|  | 		})) | ||||||
|  | 		val = resolved | ||||||
|  |  | ||||||
| 	for _, varName := range names { | 		return &val | ||||||
| 		val, ok := d.Runner.Vars[varName] |  | ||||||
| 		if ok { |  | ||||||
| 			// Resolve nameref variables |  | ||||||
| 			_, resolved := val.Resolve(expand.FuncEnviron(func(s string) string { |  | ||||||
| 				if val, ok := d.Runner.Vars[s]; ok { |  | ||||||
| 					return val.String() |  | ||||||
| 				} |  | ||||||
| 				return "" |  | ||||||
| 			})) |  | ||||||
| 			val = resolved |  | ||||||
|  |  | ||||||
| 			return &val |  | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | type vars struct { | ||||||
|  | 	Name  string | ||||||
|  | 	Value *expand.Variable | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (d *Decoder) getVarsByPrefix(prefix string) []*vars { | ||||||
|  | 	result := make([]*vars, 0) | ||||||
|  | 	for name, val := range d.Runner.Vars { | ||||||
|  | 		if !strings.HasPrefix(name, prefix) { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 		switch prefix { | ||||||
|  | 		case "auto_req": | ||||||
|  | 			if strings.HasPrefix(name, "auto_req_skiplist") { | ||||||
|  | 				continue | ||||||
|  | 			} | ||||||
|  | 		case "auto_prov": | ||||||
|  | 			if strings.HasPrefix(name, "auto_prov_skiplist") { | ||||||
|  | 				continue | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		result = append(result, &vars{name, &val}) | ||||||
|  | 	} | ||||||
|  | 	return result | ||||||
|  | } | ||||||
|  |  | ||||||
| func IsTruthy(value string) bool { | func IsTruthy(value string) bool { | ||||||
| 	value = strings.ToLower(strings.TrimSpace(value)) | 	value = strings.ToLower(strings.TrimSpace(value)) | ||||||
| 	return value == "true" || value == "yes" || value == "1" | 	return value == "true" || value == "yes" || value == "1" | ||||||
|   | |||||||
| @@ -32,24 +32,25 @@ import ( | |||||||
| 	"mvdan.cc/sh/v3/syntax" | 	"mvdan.cc/sh/v3/syntax" | ||||||
|  |  | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/decoder" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/decoder" | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro" | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| type BuildVars struct { | type BuildVars struct { | ||||||
| 	Name          string   `sh:"name,required"` | 	Name          string                           `sh:"name,required"` | ||||||
| 	Version       string   `sh:"version,required"` | 	Version       string                           `sh:"version,required"` | ||||||
| 	Release       int      `sh:"release,required"` | 	Release       int                              `sh:"release,required"` | ||||||
| 	Epoch         uint     `sh:"epoch"` | 	Epoch         uint                             `sh:"epoch"` | ||||||
| 	Description   string   `sh:"desc"` | 	Description   alrsh.OverridableField[string]   `sh:"desc"` | ||||||
| 	Homepage      string   `sh:"homepage"` | 	Homepage      string                           `sh:"homepage"` | ||||||
| 	Maintainer    string   `sh:"maintainer"` | 	Maintainer    string                           `sh:"maintainer"` | ||||||
| 	Architectures []string `sh:"architectures"` | 	Architectures []string                         `sh:"architectures"` | ||||||
| 	Licenses      []string `sh:"license"` | 	Licenses      []string                         `sh:"license"` | ||||||
| 	Provides      []string `sh:"provides"` | 	Provides      []string                         `sh:"provides"` | ||||||
| 	Conflicts     []string `sh:"conflicts"` | 	Conflicts     []string                         `sh:"conflicts"` | ||||||
| 	Depends       []string `sh:"deps"` | 	Depends       []string                         `sh:"deps"` | ||||||
| 	BuildDepends  []string `sh:"build_deps"` | 	BuildDepends  alrsh.OverridableField[[]string] `sh:"build_deps"` | ||||||
| 	Replaces      []string `sh:"replaces"` | 	Replaces      alrsh.OverridableField[[]string] `sh:"replaces"` | ||||||
| } | } | ||||||
|  |  | ||||||
| const testScript = ` | const testScript = ` | ||||||
| @@ -113,22 +114,34 @@ func TestDecodeVars(t *testing.T) { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	expected := BuildVars{ | 	expected := BuildVars{ | ||||||
| 		Name:          "test", | 		Name:    "test", | ||||||
| 		Version:       "0.0.1", | 		Version: "0.0.1", | ||||||
| 		Release:       1, | 		Release: 1, | ||||||
| 		Epoch:         2, | 		Epoch:   2, | ||||||
| 		Description:   "Test package", | 		Description: alrsh.OverridableFromMap(map[string]string{ | ||||||
|  | 			"": "Test package", | ||||||
|  | 		}), | ||||||
| 		Homepage:      "https://gitea.plemya-x.ru/xpamych/ALR", | 		Homepage:      "https://gitea.plemya-x.ru/xpamych/ALR", | ||||||
| 		Maintainer:    "Евгений Храмов <xpamych@yandex.ru>", | 		Maintainer:    "Евгений Храмов <xpamych@yandex.ru>", | ||||||
| 		Architectures: []string{"arm64", "amd64"}, | 		Architectures: []string{"arm64", "amd64"}, | ||||||
| 		Licenses:      []string{"GPL-3.0-or-later"}, | 		Licenses:      []string{"GPL-3.0-or-later"}, | ||||||
| 		Provides:      []string{"test"}, | 		Provides:      []string{"test"}, | ||||||
| 		Conflicts:     []string{"test"}, | 		Conflicts:     []string{"test"}, | ||||||
| 		Replaces:      []string{"test-legacy"}, | 		Replaces: alrsh.OverridableFromMap(map[string][]string{ | ||||||
| 		Depends:       []string{"sudo"}, | 			"":        {"test-old"}, | ||||||
| 		BuildDepends:  []string{"go"}, | 			"test_os": {"test-legacy"}, | ||||||
|  | 		}), | ||||||
|  | 		Depends: []string{"sudo"}, | ||||||
|  | 		BuildDepends: alrsh.OverridableFromMap(map[string][]string{ | ||||||
|  | 			"":     {"golang"}, | ||||||
|  | 			"arch": {"go"}, | ||||||
|  | 		}), | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	expected.Description.SetResolved("Test package") | ||||||
|  | 	expected.Replaces.SetResolved([]string{"test-legacy"}) | ||||||
|  | 	expected.BuildDepends.SetResolved([]string{"go"}) | ||||||
|  |  | ||||||
| 	if !reflect.DeepEqual(bv, expected) { | 	if !reflect.DeepEqual(bv, expected) { | ||||||
| 		t.Errorf("Expected %v, got %v", expected, bv) | 		t.Errorf("Expected %v, got %v", expected, bv) | ||||||
| 	} | 	} | ||||||
|   | |||||||
							
								
								
									
										53
									
								
								internal/shutils/helpers/dirlfs.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								internal/shutils/helpers/dirlfs.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,53 @@ | |||||||
|  | // ALR - Any Linux Repository | ||||||
|  | // Copyright (C) 2025 The ALR Authors | ||||||
|  | // | ||||||
|  | // 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 ( | ||||||
|  | 	"io/fs" | ||||||
|  | 	"os" | ||||||
|  | 	"path/filepath" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // dirLfs implements fs.FS like os.DirFS but uses LStat instead of Stat. | ||||||
|  | // This means symbolic links are treated as links themselves rather than | ||||||
|  | // being followed to their targets. | ||||||
|  | type dirLfs struct { | ||||||
|  | 	fs.FS | ||||||
|  | 	dir string | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func NewDirLFS(dir string) *dirLfs { | ||||||
|  | 	return &dirLfs{ | ||||||
|  | 		FS:  os.DirFS(dir), | ||||||
|  | 		dir: dir, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (d *dirLfs) Stat(name string) (fs.FileInfo, error) { | ||||||
|  | 	if !fs.ValidPath(name) { | ||||||
|  | 		return nil, &fs.PathError{Op: "stat", Path: name, Err: fs.ErrInvalid} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	fullPath := filepath.Join(d.dir, filepath.FromSlash(name)) | ||||||
|  |  | ||||||
|  | 	info, err := os.Lstat(fullPath) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, &fs.PathError{Op: "stat", Path: name, Err: err} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return info, nil | ||||||
|  | } | ||||||
							
								
								
									
										179
									
								
								internal/shutils/helpers/files_find.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										179
									
								
								internal/shutils/helpers/files_find.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,179 @@ | |||||||
|  | // ALR - Any Linux Repository | ||||||
|  | // Copyright (C) 2025 The ALR Authors | ||||||
|  | // | ||||||
|  | // 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 ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"os" | ||||||
|  | 	"path" | ||||||
|  | 	"path/filepath" | ||||||
|  |  | ||||||
|  | 	"github.com/bmatcuk/doublestar/v4" | ||||||
|  | 	"mvdan.cc/sh/v3/interp" | ||||||
|  | 	"mvdan.cc/sh/v3/syntax" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func matchNamePattern(name, pattern string) bool { | ||||||
|  | 	matched, err := filepath.Match(pattern, name) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | 	return matched | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func validateDir(dirPath, commandName string) error { | ||||||
|  | 	info, err := os.Stat(dirPath) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return fmt.Errorf("%s: %w", commandName, err) | ||||||
|  | 	} | ||||||
|  | 	if !info.IsDir() { | ||||||
|  | 		return fmt.Errorf("%s: %s is not a directory", commandName, dirPath) | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func outputFiles(hc interp.HandlerContext, files []string) error { | ||||||
|  | 	for _, file := range files { | ||||||
|  | 		v, err := syntax.Quote(file, syntax.LangAuto) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		fmt.Fprintln(hc.Stdout, v) | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func makeRelativePath(basePath, fullPath string) (string, error) { | ||||||
|  | 	relPath, err := filepath.Rel(basePath, fullPath) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return "", err | ||||||
|  | 	} | ||||||
|  | 	return "./" + relPath, 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) | ||||||
|  |  | ||||||
|  | 	if err := validateDir(realPath, "files-find-lang"); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	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 := makeRelativePath(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) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return outputFiles(hc, langFiles) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | 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) | ||||||
|  |  | ||||||
|  | 	if err := validateDir(docRealPath, "files-find-doc"); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	var docFiles []string | ||||||
|  |  | ||||||
|  | 	entries, err := os.ReadDir(docRealPath) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return fmt.Errorf("files-find-doc: %w", 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 fmt.Errorf("files-find-doc: %w", err) | ||||||
|  | 			} | ||||||
|  | 			if targetInfo.IsDir() { | ||||||
|  | 				err := filepath.Walk(targetPath, func(subPath string, subInfo os.FileInfo, subErr error) error { | ||||||
|  | 					if subErr != nil { | ||||||
|  | 						return subErr | ||||||
|  | 					} | ||||||
|  | 					relPath, err := makeRelativePath(hc.Dir, subPath) | ||||||
|  | 					if err != nil { | ||||||
|  | 						return err | ||||||
|  | 					} | ||||||
|  | 					docFiles = append(docFiles, relPath) | ||||||
|  | 					return nil | ||||||
|  | 				}) | ||||||
|  | 				if err != nil { | ||||||
|  | 					return fmt.Errorf("files-find-doc: %w", err) | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return outputFiles(hc, docFiles) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func filesFindCmd(hc interp.HandlerContext, cmd string, args []string) error { | ||||||
|  | 	if len(args) == 0 { | ||||||
|  | 		return fmt.Errorf("files-find: at least one glob pattern is required") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	var foundFiles []string | ||||||
|  |  | ||||||
|  | 	for _, globPattern := range args { | ||||||
|  | 		searchPath := path.Join(hc.Dir, globPattern) | ||||||
|  |  | ||||||
|  | 		basepath, pattern := doublestar.SplitPattern(searchPath) | ||||||
|  | 		fsys := NewDirLFS(basepath) | ||||||
|  | 		matches, err := doublestar.Glob(fsys, pattern, doublestar.WithNoFollow(), doublestar.WithFailOnPatternNotExist()) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return fmt.Errorf("files-find: glob pattern error: %w", err) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		for _, match := range matches { | ||||||
|  | 			relPath, err := makeRelativePath(hc.Dir, path.Join(basepath, match)) | ||||||
|  | 			if err != nil { | ||||||
|  | 				continue | ||||||
|  | 			} | ||||||
|  | 			foundFiles = append(foundFiles, relPath) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return outputFiles(hc, foundFiles) | ||||||
|  | } | ||||||
| @@ -24,7 +24,6 @@ import ( | |||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"io" | 	"io" | ||||||
| 	"os" | 	"os" | ||||||
| 	"path" |  | ||||||
| 	"path/filepath" | 	"path/filepath" | ||||||
| 	"strconv" | 	"strconv" | ||||||
| 	"strings" | 	"strings" | ||||||
| @@ -57,6 +56,7 @@ var Helpers = handlers.ExecFuncs{ | |||||||
| 	"install-library":      installLibraryCmd, | 	"install-library":      installLibraryCmd, | ||||||
| 	"git-version":          gitVersionCmd, | 	"git-version":          gitVersionCmd, | ||||||
|  |  | ||||||
|  | 	"files-find":      filesFindCmd, | ||||||
| 	"files-find-lang": filesFindLangCmd, | 	"files-find-lang": filesFindLangCmd, | ||||||
| 	"files-find-doc":  filesFindDocCmd, | 	"files-find-doc":  filesFindDocCmd, | ||||||
| } | } | ||||||
| @@ -65,6 +65,7 @@ var Helpers = handlers.ExecFuncs{ | |||||||
| // that don't modify any state | // that don't modify any state | ||||||
| var Restricted = handlers.ExecFuncs{ | var Restricted = handlers.ExecFuncs{ | ||||||
| 	"git-version":     gitVersionCmd, | 	"git-version":     gitVersionCmd, | ||||||
|  | 	"files-find":      filesFindCmd, | ||||||
| 	"files-find-lang": filesFindLangCmd, | 	"files-find-lang": filesFindLangCmd, | ||||||
| 	"files-find-doc":  filesFindDocCmd, | 	"files-find-doc":  filesFindDocCmd, | ||||||
| } | } | ||||||
| @@ -265,114 +266,6 @@ func gitVersionCmd(hc interp.HandlerContext, cmd string, args []string) error { | |||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func filesFindLangCmd(hc interp.HandlerContext, cmd string, args []string) error { |  | ||||||
| 	namePattern := "*.mo" |  | ||||||
| 	if len(args) > 0 { |  | ||||||
| 		namePattern = args[0] + ".mo" |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	localePath := "./usr/share/locale/" |  | ||||||
| 	realPath := path.Join(hc.Dir, localePath) |  | ||||||
|  |  | ||||||
| 	info, err := os.Stat(realPath) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return fmt.Errorf("files-find-lang: %w", err) |  | ||||||
| 	} |  | ||||||
| 	if !info.IsDir() { |  | ||||||
| 		return fmt.Errorf("files-find-lang: %s is not a directory", localePath) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	var langFiles []string |  | ||||||
| 	err = filepath.Walk(realPath, func(p string, info os.FileInfo, err error) error { |  | ||||||
| 		if err != nil { |  | ||||||
| 			return err |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		if !info.IsDir() && matchNamePattern(info.Name(), namePattern) { |  | ||||||
| 			relPath, relErr := filepath.Rel(hc.Dir, p) |  | ||||||
| 			if relErr != nil { |  | ||||||
| 				return relErr |  | ||||||
| 			} |  | ||||||
| 			langFiles = append(langFiles, "./"+relPath) |  | ||||||
| 		} |  | ||||||
| 		return nil |  | ||||||
| 	}) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return fmt.Errorf("files-find-lang: %w", err) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	for _, file := range langFiles { |  | ||||||
| 		fmt.Fprintln(hc.Stdout, file) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func filesFindDocCmd(hc interp.HandlerContext, cmd string, args []string) error { |  | ||||||
| 	namePattern := "*" |  | ||||||
| 	if len(args) > 0 { |  | ||||||
| 		namePattern = args[0] |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	docPath := "./usr/share/doc/" |  | ||||||
| 	docRealPath := path.Join(hc.Dir, docPath) |  | ||||||
|  |  | ||||||
| 	info, err := os.Stat(docRealPath) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return fmt.Errorf("files-find-doc: %w", err) |  | ||||||
| 	} |  | ||||||
| 	if !info.IsDir() { |  | ||||||
| 		return fmt.Errorf("files-find-doc: %s is not a directory", docPath) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	var docFiles []string |  | ||||||
|  |  | ||||||
| 	entries, err := os.ReadDir(docRealPath) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 	for _, entry := range entries { |  | ||||||
| 		if matchNamePattern(entry.Name(), namePattern) { |  | ||||||
| 			targetPath := filepath.Join(docRealPath, entry.Name()) |  | ||||||
| 			targetInfo, err := os.Stat(targetPath) |  | ||||||
| 			if err != nil { |  | ||||||
| 				return err |  | ||||||
| 			} |  | ||||||
| 			if targetInfo.IsDir() { |  | ||||||
| 				err := filepath.Walk(targetPath, func(subPath string, subInfo os.FileInfo, subErr error) error { |  | ||||||
| 					relPath, err := filepath.Rel(hc.Dir, subPath) |  | ||||||
| 					if err != nil { |  | ||||||
| 						return err |  | ||||||
| 					} |  | ||||||
| 					docFiles = append(docFiles, "./"+relPath) |  | ||||||
| 					return nil |  | ||||||
| 				}) |  | ||||||
| 				if err != nil { |  | ||||||
| 					return err |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if err != nil { |  | ||||||
| 		return fmt.Errorf("files-find-doc: %w", err) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	for _, file := range docFiles { |  | ||||||
| 		fmt.Fprintln(hc.Stdout, file) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func matchNamePattern(name, pattern string) bool { |  | ||||||
| 	matched, err := filepath.Match(pattern, name) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return false |  | ||||||
| 	} |  | ||||||
| 	return matched |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func helperInstall(from, to string, perms os.FileMode) error { | func helperInstall(from, to string, perms os.FileMode) error { | ||||||
| 	err := os.MkdirAll(filepath.Dir(to), 0o755) | 	err := os.MkdirAll(filepath.Dir(to), 0o755) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|   | |||||||
| @@ -24,6 +24,8 @@ import ( | |||||||
| 	"strings" | 	"strings" | ||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
|  | 	"github.com/bmatcuk/doublestar/v4" | ||||||
|  | 	"github.com/google/shlex" | ||||||
| 	"github.com/stretchr/testify/assert" | 	"github.com/stretchr/testify/assert" | ||||||
| 	"mvdan.cc/sh/v3/interp" | 	"mvdan.cc/sh/v3/interp" | ||||||
| 	"mvdan.cc/sh/v3/syntax" | 	"mvdan.cc/sh/v3/syntax" | ||||||
| @@ -31,12 +33,19 @@ import ( | |||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/handlers" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/handlers" | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  | type symlink struct { | ||||||
|  | 	linkPath   string | ||||||
|  | 	targetPath string | ||||||
|  | } | ||||||
|  |  | ||||||
| type testCase struct { | type testCase struct { | ||||||
| 	name           string | 	name             string | ||||||
| 	dirsToCreate   []string | 	dirsToCreate     []string | ||||||
| 	filesToCreate  []string | 	filesToCreate    []string | ||||||
| 	expectedOutput []string | 	expectedOutput   []string | ||||||
| 	args           string | 	symlinksToCreate []symlink | ||||||
|  | 	args             string | ||||||
|  | 	expectedError    error | ||||||
| } | } | ||||||
|  |  | ||||||
| func TestFindFilesDoc(t *testing.T) { | func TestFindFilesDoc(t *testing.T) { | ||||||
| @@ -125,7 +134,8 @@ files-find-doc ` + tc.args | |||||||
| 			err = runner.Run(context.Background(), script) | 			err = runner.Run(context.Background(), script) | ||||||
| 			assert.NoError(t, err) | 			assert.NoError(t, err) | ||||||
|  |  | ||||||
| 			contents := strings.Fields(strings.TrimSpace(buf.String())) | 			contents, err := shlex.Split(buf.String()) | ||||||
|  | 			assert.NoError(t, err) | ||||||
| 			assert.ElementsMatch(t, tc.expectedOutput, contents) | 			assert.ElementsMatch(t, tc.expectedOutput, contents) | ||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
| @@ -209,7 +219,120 @@ files-find-lang ` + tc.args | |||||||
| 			err = runner.Run(context.Background(), script) | 			err = runner.Run(context.Background(), script) | ||||||
| 			assert.NoError(t, err) | 			assert.NoError(t, err) | ||||||
|  |  | ||||||
| 			contents := strings.Fields(strings.TrimSpace(buf.String())) | 			contents, err := shlex.Split(buf.String()) | ||||||
|  | 			assert.NoError(t, err) | ||||||
|  | 			assert.ElementsMatch(t, tc.expectedOutput, contents) | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestFindFiles(t *testing.T) { | ||||||
|  | 	tests := []testCase{ | ||||||
|  | 		{ | ||||||
|  | 			name: "With file and dir symlinks", | ||||||
|  | 			dirsToCreate: []string{ | ||||||
|  | 				"usr/share/locale/ru/LC_MESSAGES", | ||||||
|  | 				"usr/share/locale/tr/LC_MESSAGES", | ||||||
|  | 				"opt/app", | ||||||
|  | 				"opt/app/internal", | ||||||
|  | 				"opt/app/with space", | ||||||
|  | 				"usr/bin", | ||||||
|  | 			}, | ||||||
|  | 			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", | ||||||
|  | 				"opt/app/internal/test", | ||||||
|  | 				"opt/app/with space/file", | ||||||
|  | 			}, | ||||||
|  | 			symlinksToCreate: []symlink{ | ||||||
|  | 				{ | ||||||
|  | 					linkPath:   "/opt/app/etc", | ||||||
|  | 					targetPath: "/etc", | ||||||
|  | 				}, | ||||||
|  | 				{ | ||||||
|  | 					linkPath:   "/usr/bin/file", | ||||||
|  | 					targetPath: "/not-existing", | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 			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", | ||||||
|  | 				"./opt/app/etc", | ||||||
|  | 				"./opt/app/internal", | ||||||
|  | 				"./opt/app/internal/test", | ||||||
|  | 				"./opt/app/with space", | ||||||
|  | 				"./opt/app/with space/file", | ||||||
|  | 				"./usr/bin/file", | ||||||
|  | 			}, | ||||||
|  | 			args:          "\"/usr/share/locale/*/LC_MESSAGES/*.mo\" \"/opt/app/**/*\" \"/usr/bin/file\"", | ||||||
|  | 			expectedError: nil, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name:          "Not existing paths should throw error", | ||||||
|  | 			args:          "\"/opt/test/not-existing\"", | ||||||
|  | 			expectedError: doublestar.ErrPatternNotExist, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for _, tc := range tests { | ||||||
|  | 		t.Run(tc.name, func(t *testing.T) { | ||||||
|  | 			tempDir, err := os.MkdirTemp("", "test-files-find") | ||||||
|  | 			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) | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			for _, sl := range tc.symlinksToCreate { | ||||||
|  | 				linkFullPath := filepath.Join(tempDir, sl.linkPath) | ||||||
|  | 				targetFullPath := sl.targetPath | ||||||
|  |  | ||||||
|  | 				// make sure parent dir exists | ||||||
|  | 				err := os.MkdirAll(filepath.Dir(linkFullPath), 0o755) | ||||||
|  | 				assert.NoError(t, err) | ||||||
|  |  | ||||||
|  | 				err = os.Symlink(targetFullPath, linkFullPath) | ||||||
|  | 				assert.NoError(t, err) | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			helpers := handlers.ExecFuncs{ | ||||||
|  | 				"files-find": filesFindCmd, | ||||||
|  | 			} | ||||||
|  | 			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 ` + tc.args | ||||||
|  |  | ||||||
|  | 			script, err := syntax.NewParser().Parse(strings.NewReader(scriptContent), "") | ||||||
|  | 			assert.NoError(t, err) | ||||||
|  |  | ||||||
|  | 			err = runner.Run(context.Background(), script) | ||||||
|  | 			if tc.expectedError != nil { | ||||||
|  | 				assert.ErrorAs(t, err, &tc.expectedError) | ||||||
|  | 			} else { | ||||||
|  | 				assert.NoError(t, err) | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			contents, err := shlex.Split(buf.String()) | ||||||
|  | 			assert.NoError(t, err) | ||||||
| 			assert.ElementsMatch(t, tc.expectedOutput, contents) | 			assert.ElementsMatch(t, tc.expectedOutput, contents) | ||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -58,11 +58,55 @@ msgstr "" | |||||||
| msgid "Done" | msgid "Done" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: fix.go:38 | #: config.go:36 | ||||||
|  | msgid "Manage config" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: config.go:48 | ||||||
|  | msgid "Show config" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: config.go:84 | ||||||
|  | msgid "Set config value" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: config.go:85 | ||||||
|  | msgid "<key> <value>" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: config.go:118 config.go:126 | ||||||
|  | msgid "invalid boolean value for %s: %s" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: config.go:141 | ||||||
|  | msgid "use 'repo add/remove' commands to manage repositories" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: config.go:143 config.go:221 | ||||||
|  | msgid "unknown config key: %s" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: config.go:147 | ||||||
|  | msgid "failed to save config" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: config.go:150 | ||||||
|  | msgid "Successfully set %s = %s" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: config.go:159 | ||||||
|  | msgid "Get config value" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: config.go:160 | ||||||
|  | msgid "<key>" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: fix.go:39 | ||||||
| msgid "Attempt to fix problems with ALR" | msgid "Attempt to fix problems with ALR" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: fix.go:59 | #: fix.go:60 | ||||||
| msgid "Clearing cache directory" | msgid "Clearing cache directory" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| @@ -74,15 +118,15 @@ msgstr "" | |||||||
| msgid "Unable to read cache directory contents" | msgid "Unable to read cache directory contents" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: fix.go:76 | #: fix.go:82 | ||||||
| msgid "Unable to remove cache item (%s)" | msgid "Unable to remove cache item (%s)" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: fix.go:80 | #: fix.go:86 | ||||||
| msgid "Rebuilding cache" | msgid "Rebuilding cache" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: fix.go:84 | #: fix.go:90 | ||||||
| msgid "Unable to create new cache directory" | msgid "Unable to create new cache directory" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| @@ -126,58 +170,124 @@ msgstr "" | |||||||
| msgid "Error getting packages" | msgid "Error getting packages" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: info.go:76 | #: info.go:83 | ||||||
| msgid "Error iterating over packages" |  | ||||||
| msgstr "" |  | ||||||
|  |  | ||||||
| #: info.go:90 |  | ||||||
| msgid "Command info expected at least 1 argument, got %d" | msgid "Command info expected at least 1 argument, got %d" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: info.go:110 | #: info.go:104 | ||||||
| msgid "Error finding packages" | msgid "Error finding packages" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: info.go:124 | #: info.go:118 | ||||||
| msgid "Can't detect system language" | msgid "Can't detect system language" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: info.go:141 | #: info.go:134 | ||||||
| msgid "Error resolving overrides" | msgid "Error resolving overrides" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: info.go:149 info.go:154 | #: info.go:143 | ||||||
| msgid "Error encoding script variables" | msgid "Error encoding script variables" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: install.go:40 | #: install.go:39 | ||||||
| msgid "Install a new package" | msgid "Install a new package" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: install.go:52 | #: install.go:51 | ||||||
| msgid "Command install expected at least 1 argument, got %d" | msgid "Command install expected at least 1 argument, got %d" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: install.go:114 | #: install.go:113 | ||||||
| msgid "Error when installing the package" | msgid "Error when installing the package" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: install.go:159 | #: install.go:151 | ||||||
| msgid "Remove an installed package" | msgid "Remove an installed package" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: install.go:178 | #: install.go:170 | ||||||
| msgid "Error listing installed packages" | msgid "Error listing installed packages" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: install.go:215 | #: install.go:199 | ||||||
| msgid "Command remove expected at least 1 argument, got %d" | msgid "Command remove expected at least 1 argument, got %d" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: install.go:230 | #: install.go:214 | ||||||
| msgid "Error removing packages" | msgid "Error removing packages" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
|  | #: internal/build/build.go:378 | ||||||
|  | msgid "Building package" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: internal/build/build.go:407 | ||||||
|  | msgid "The checksums array must be the same length as sources" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: internal/build/build.go:449 | ||||||
|  | msgid "Downloading sources" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: internal/build/build.go:495 | ||||||
|  | msgid "Would you like to remove the build dependencies?" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: internal/build/build.go:571 | ||||||
|  | msgid "Installing dependencies" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: internal/build/checker.go:43 | ||||||
|  | msgid "" | ||||||
|  | "Your system's CPU architecture doesn't match this package. Do you want to " | ||||||
|  | "build anyway?" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: internal/build/checker.go:67 | ||||||
|  | msgid "This package is already installed" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: internal/build/find_deps/alt_linux.go:35 | ||||||
|  | msgid "Command not found on the system" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: internal/build/find_deps/alt_linux.go:86 | ||||||
|  | msgid "Provided dependency found" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: internal/build/find_deps/alt_linux.go:93 | ||||||
|  | msgid "Required dependency found" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: internal/build/find_deps/empty.go:32 | ||||||
|  | msgid "AutoProv is not implemented for this package format, so it's skipped" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: internal/build/find_deps/empty.go:37 | ||||||
|  | msgid "AutoReq is not implemented for this package format, so it's skipped" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: internal/build/firejail.go:144 | ||||||
|  | msgid "Applying FireJail integration" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: internal/build/script_executor.go:145 | ||||||
|  | msgid "Building package metadata" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: internal/build/script_executor.go:285 | ||||||
|  | msgid "Executing prepare()" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: internal/build/script_executor.go:294 | ||||||
|  | msgid "Executing build()" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: internal/build/script_executor.go:323 internal/build/script_executor.go:343 | ||||||
|  | msgid "Executing %s()" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
| #: internal/cliutils/app_builder/builder.go:75 | #: internal/cliutils/app_builder/builder.go:75 | ||||||
| msgid "Error loading config" | msgid "Error loading config" | ||||||
| msgstr "" | msgstr "" | ||||||
| @@ -186,15 +296,15 @@ msgstr "" | |||||||
| msgid "Error initialization database" | msgid "Error initialization database" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: internal/cliutils/app_builder/builder.go:135 | #: internal/cliutils/app_builder/builder.go:142 | ||||||
| msgid "Error pulling repositories" | msgid "Error pulling repositories" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: internal/cliutils/app_builder/builder.go:152 | #: internal/cliutils/app_builder/builder.go:159 | ||||||
| msgid "Error parsing os release" | msgid "Error parsing os release" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: internal/cliutils/app_builder/builder.go:165 | #: internal/cliutils/app_builder/builder.go:172 | ||||||
| msgid "Unable to detect a supported package manager on the system" | msgid "Unable to detect a supported package manager on the system" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| @@ -284,43 +394,45 @@ msgid "" | |||||||
| "instead!" | "instead!" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: internal/db/db.go:137 | #: internal/db/db.go:76 | ||||||
| msgid "Database version mismatch; resetting" | msgid "Database version mismatch; resetting" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: internal/db/db.go:144 | #: internal/db/db.go:82 | ||||||
| msgid "" | msgid "" | ||||||
| "Database version does not exist. Run alr fix if something isn't working." | "Database version does not exist. Run alr fix if something isn't working." | ||||||
| msgstr "" | 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:41 | #: internal/logger/log.go:41 | ||||||
| msgid "ERROR" | msgid "ERROR" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
|  | #: internal/repos/pull.go:88 | ||||||
|  | msgid "Trying mirror" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: internal/repos/pull.go:94 | ||||||
|  | msgid "Failed to pull from URL" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: internal/repos/pull.go:158 | ||||||
|  | msgid "Pulling repository" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: internal/repos/pull.go:195 | ||||||
|  | msgid "Repository up to date" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: internal/repos/pull.go:230 | ||||||
|  | msgid "Git repository does not appear to be a valid ALR repo" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: internal/repos/pull.go:246 | ||||||
|  | msgid "" | ||||||
|  | "ALR repo's minimum ALR version is greater than the current version. Try " | ||||||
|  | "updating ALR if something doesn't work." | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
| #: internal/utils/cmd.go:97 | #: internal/utils/cmd.go:97 | ||||||
| msgid "Error on dropping capabilities" | msgid "Error on dropping capabilities" | ||||||
| msgstr "" | msgstr "" | ||||||
| @@ -349,11 +461,11 @@ msgstr "" | |||||||
| msgid "No packages for upgrade" | msgid "No packages for upgrade" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: list.go:102 list.go:187 | #: list.go:102 list.go:184 | ||||||
| msgid "Error parsing format template" | msgid "Error parsing format template" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: list.go:108 list.go:191 | #: list.go:108 list.go:188 | ||||||
| msgid "Error executing template" | msgid "Error executing template" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| @@ -369,155 +481,140 @@ msgstr "" | |||||||
| msgid "Enable interactive questions and prompts" | msgid "Enable interactive questions and prompts" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: main.go:146 | #: main.go:147 | ||||||
| msgid "Show help" | msgid "Show help" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: main.go:150 | #: main.go:151 | ||||||
| msgid "Error while running app" | msgid "Error while running app" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: pkg/build/build.go:395 | #: pkg/dl/dl.go:170 | ||||||
| msgid "Building package" | msgid "Source can be updated, updating if required" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: pkg/build/build.go:424 | #: pkg/dl/dl.go:196 | ||||||
| msgid "The checksums array must be the same length as sources" | msgid "Source found in cache and linked to destination" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: pkg/build/build.go:455 | #: pkg/dl/dl.go:203 | ||||||
| msgid "Downloading sources" | msgid "Source updated and linked to destination" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: pkg/build/build.go:549 | #: pkg/dl/dl.go:217 | ||||||
| msgid "Installing dependencies" | msgid "Downloading source" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: pkg/build/checker.go:43 | #: pkg/dl/progress_tui.go:100 | ||||||
| msgid "" | msgid "%s: done!\n" | ||||||
| "Your system's CPU architecture doesn't match this package. Do you want to " |  | ||||||
| "build anyway?" |  | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: pkg/build/checker.go:67 | #: pkg/dl/progress_tui.go:104 | ||||||
| msgid "This package is already installed" | msgid "%s %s downloading at %s/s\n" | ||||||
| msgstr "" |  | ||||||
|  |  | ||||||
| #: pkg/build/find_deps/alt_linux.go:35 |  | ||||||
| msgid "Command not found on the system" |  | ||||||
| msgstr "" |  | ||||||
|  |  | ||||||
| #: pkg/build/find_deps/alt_linux.go:86 |  | ||||||
| msgid "Provided dependency found" |  | ||||||
| msgstr "" |  | ||||||
|  |  | ||||||
| #: pkg/build/find_deps/alt_linux.go:93 |  | ||||||
| msgid "Required dependency found" |  | ||||||
| msgstr "" |  | ||||||
|  |  | ||||||
| #: pkg/build/find_deps/empty.go:32 |  | ||||||
| msgid "AutoProv is not implemented for this package format, so it's skipped" |  | ||||||
| msgstr "" |  | ||||||
|  |  | ||||||
| #: pkg/build/find_deps/empty.go:37 |  | ||||||
| msgid "AutoReq is not implemented for this package format, so it's skipped" |  | ||||||
| msgstr "" |  | ||||||
|  |  | ||||||
| #: pkg/build/script_executor.go:241 |  | ||||||
| msgid "Building package metadata" |  | ||||||
| msgstr "" |  | ||||||
|  |  | ||||||
| #: pkg/build/script_executor.go:372 |  | ||||||
| msgid "Executing prepare()" |  | ||||||
| msgstr "" |  | ||||||
|  |  | ||||||
| #: pkg/build/script_executor.go:381 |  | ||||||
| msgid "Executing build()" |  | ||||||
| msgstr "" |  | ||||||
|  |  | ||||||
| #: pkg/build/script_executor.go:410 pkg/build/script_executor.go:430 |  | ||||||
| msgid "Executing %s()" |  | ||||||
| msgstr "" |  | ||||||
|  |  | ||||||
| #: pkg/repos/pull.go:77 |  | ||||||
| msgid "Pulling repository" |  | ||||||
| msgstr "" |  | ||||||
|  |  | ||||||
| #: pkg/repos/pull.go:113 |  | ||||||
| msgid "Repository up to date" |  | ||||||
| msgstr "" |  | ||||||
|  |  | ||||||
| #: pkg/repos/pull.go:204 |  | ||||||
| msgid "Git repository does not appear to be a valid ALR repo" |  | ||||||
| msgstr "" |  | ||||||
|  |  | ||||||
| #: pkg/repos/pull.go:220 |  | ||||||
| msgid "" |  | ||||||
| "ALR repo's minimum ALR version is greater than the current version. Try " |  | ||||||
| "updating ALR if something doesn't work." |  | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: refresh.go:30 | #: refresh.go:30 | ||||||
| msgid "Pull all repositories that have changed" | msgid "Pull all repositories that have changed" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: repo.go:39 | #: repo.go:41 | ||||||
| msgid "Manage repos" | msgid "Manage repos" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: repo.go:51 repo.go:269 | #: repo.go:55 repo.go:625 | ||||||
| msgid "Remove an existing repository" | msgid "Remove an existing repository" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: repo.go:53 | #: repo.go:57 repo.go:521 | ||||||
| msgid "<name>" | msgid "<name>" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: repo.go:83 | #: repo.go:102 repo.go:465 repo.go:568 | ||||||
| msgid "Repo \"%s\" does not exist" | msgid "Repo \"%s\" does not exist" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: repo.go:90 | #: repo.go:109 | ||||||
| msgid "Error removing repo directory" | msgid "Error removing repo directory" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: repo.go:94 repo.go:161 repo.go:219 | #: repo.go:113 repo.go:180 repo.go:253 repo.go:316 repo.go:389 repo.go:504 | ||||||
|  | #: repo.go:576 | ||||||
| msgid "Error saving config" | msgid "Error saving config" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: repo.go:113 | #: repo.go:132 | ||||||
| msgid "Error removing packages from database" | msgid "Error removing packages from database" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: repo.go:124 repo.go:239 | #: repo.go:143 repo.go:595 | ||||||
| msgid "Add a new repository" | msgid "Add a new repository" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: repo.go:125 | #: repo.go:144 repo.go:270 repo.go:345 repo.go:402 | ||||||
| msgid "<name> <url>" | msgid "<name> <url>" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: repo.go:150 | #: repo.go:169 | ||||||
| msgid "Repo \"%s\" already exists" | msgid "Repo \"%s\" already exists" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: repo.go:187 | #: repo.go:206 | ||||||
| msgid "Set the reference of the repository" | msgid "Set the reference of the repository" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: repo.go:188 | #: repo.go:207 | ||||||
| msgid "<name> <ref>" | msgid "<name> <ref>" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: repo.go:246 | #: repo.go:269 | ||||||
|  | msgid "Set the main url of the repository" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: repo.go:332 | ||||||
|  | msgid "Manage mirrors of repos" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: repo.go:344 | ||||||
|  | msgid "Add a mirror URL to repository" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: repo.go:401 | ||||||
|  | msgid "Remove mirror from the repository" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: repo.go:420 | ||||||
|  | msgid "Ignore if mirror does not exist" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: repo.go:425 | ||||||
|  | msgid "Match partial URL (e.g., github.com instead of full URL)" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: repo.go:490 | ||||||
|  | msgid "No mirrors containing \"%s\" found in repo \"%s\"" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: repo.go:492 | ||||||
|  | msgid "URL \"%s\" does not exist in repo \"%s\"" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: repo.go:508 repo.go:580 | ||||||
|  | msgid "Removed %d mirrors from repo \"%s\"\n" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: repo.go:520 | ||||||
|  | msgid "Remove all mirrors from the repository" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: repo.go:602 | ||||||
| msgid "Name of the new repo" | msgid "Name of the new repo" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: repo.go:252 | #: repo.go:608 | ||||||
| msgid "URL of the new repo" | msgid "URL of the new repo" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: repo.go:276 | #: repo.go:632 | ||||||
| msgid "Name of the repo to be deleted" | msgid "Name of the repo to be deleted" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| @@ -545,14 +642,14 @@ msgstr "" | |||||||
| msgid "Error while executing search" | msgid "Error while executing search" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: upgrade.go:47 | #: upgrade.go:48 | ||||||
| msgid "Upgrade all installed packages" | msgid "Upgrade all installed packages" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: upgrade.go:105 upgrade.go:122 | #: upgrade.go:106 upgrade.go:123 | ||||||
| msgid "Error checking for updates" | msgid "Error checking for updates" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: upgrade.go:125 | #: upgrade.go:126 | ||||||
| msgid "There is nothing to do." | msgid "There is nothing to do." | ||||||
| msgstr "" | msgstr "" | ||||||
|   | |||||||
| @@ -5,15 +5,15 @@ | |||||||
| msgid "" | msgid "" | ||||||
| msgstr "" | msgstr "" | ||||||
| "Project-Id-Version: unnamed project\n" | "Project-Id-Version: unnamed project\n" | ||||||
| "PO-Revision-Date: 2025-05-16 20:47+0300\n" | "PO-Revision-Date: 2025-06-29 21:05+0300\n" | ||||||
| "Last-Translator: Maxim Slipenko <maks1ms@alt-gnome.ru>\n" | "Last-Translator: Maxim Slipenko <maks1ms@alt-gnome.ru>\n" | ||||||
| "Language-Team: Russian\n" | "Language-Team: Russian\n" | ||||||
| "Language: ru\n" | "Language: ru\n" | ||||||
| "MIME-Version: 1.0\n" | "MIME-Version: 1.0\n" | ||||||
| "Content-Type: text/plain; charset=UTF-8\n" | "Content-Type: text/plain; charset=UTF-8\n" | ||||||
| "Content-Transfer-Encoding: 8bit\n" | "Content-Transfer-Encoding: 8bit\n" | ||||||
| "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && " | "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" | ||||||
| "n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" | "%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" | ||||||
| "X-Generator: Gtranslator 48.0\n" | "X-Generator: Gtranslator 48.0\n" | ||||||
|  |  | ||||||
| #: build.go:42 | #: build.go:42 | ||||||
| @@ -65,11 +65,55 @@ msgstr "Ошибка при перемещении пакета" | |||||||
| msgid "Done" | msgid "Done" | ||||||
| msgstr "Сделано" | msgstr "Сделано" | ||||||
|  |  | ||||||
| #: fix.go:38 | #: config.go:36 | ||||||
|  | msgid "Manage config" | ||||||
|  | msgstr "Управление конфигурацией" | ||||||
|  |  | ||||||
|  | #: config.go:48 | ||||||
|  | msgid "Show config" | ||||||
|  | msgstr "Показать конфигурацию" | ||||||
|  |  | ||||||
|  | #: config.go:84 | ||||||
|  | msgid "Set config value" | ||||||
|  | msgstr "Установить значение в конфигурации" | ||||||
|  |  | ||||||
|  | #: config.go:85 | ||||||
|  | msgid "<key> <value>" | ||||||
|  | msgstr "<ключ> <значение>" | ||||||
|  |  | ||||||
|  | #: config.go:118 config.go:126 | ||||||
|  | msgid "invalid boolean value for %s: %s" | ||||||
|  | msgstr "неверное булево значение для %s: %s" | ||||||
|  |  | ||||||
|  | #: config.go:141 | ||||||
|  | msgid "use 'repo add/remove' commands to manage repositories" | ||||||
|  | msgstr "используйте команды 'repo add/remove' для управления репозиториями" | ||||||
|  |  | ||||||
|  | #: config.go:143 config.go:221 | ||||||
|  | msgid "unknown config key: %s" | ||||||
|  | msgstr "неизвестный ключ конфигурации: %s" | ||||||
|  |  | ||||||
|  | #: config.go:147 | ||||||
|  | msgid "failed to save config" | ||||||
|  | msgstr "не удалось сохранить конфигурацию" | ||||||
|  |  | ||||||
|  | #: config.go:150 | ||||||
|  | msgid "Successfully set %s = %s" | ||||||
|  | msgstr "Успешно установлено %s = %s" | ||||||
|  |  | ||||||
|  | #: config.go:159 | ||||||
|  | msgid "Get config value" | ||||||
|  | msgstr "Получить значение из конфигурации" | ||||||
|  |  | ||||||
|  | #: config.go:160 | ||||||
|  | msgid "<key>" | ||||||
|  | msgstr "<ключ>" | ||||||
|  |  | ||||||
|  | #: fix.go:39 | ||||||
| msgid "Attempt to fix problems with ALR" | msgid "Attempt to fix problems with ALR" | ||||||
| msgstr "Попытка устранить проблемы с ALR" | msgstr "Попытка устранить проблемы с ALR" | ||||||
|  |  | ||||||
| #: fix.go:59 | #: fix.go:60 | ||||||
| msgid "Clearing cache directory" | msgid "Clearing cache directory" | ||||||
| msgstr "Очистка каталога кэша" | msgstr "Очистка каталога кэша" | ||||||
|  |  | ||||||
| @@ -81,15 +125,15 @@ msgstr "Невозможно открыть каталог кэша" | |||||||
| msgid "Unable to read cache directory contents" | msgid "Unable to read cache directory contents" | ||||||
| msgstr "Невозможно прочитать содержимое каталога кэша" | msgstr "Невозможно прочитать содержимое каталога кэша" | ||||||
|  |  | ||||||
| #: fix.go:76 | #: fix.go:82 | ||||||
| msgid "Unable to remove cache item (%s)" | msgid "Unable to remove cache item (%s)" | ||||||
| msgstr "Невозможно удалить элемент кэша (%s)" | msgstr "Невозможно удалить элемент кэша (%s)" | ||||||
|  |  | ||||||
| #: fix.go:80 | #: fix.go:86 | ||||||
| msgid "Rebuilding cache" | msgid "Rebuilding cache" | ||||||
| msgstr "Восстановление кэша" | msgstr "Восстановление кэша" | ||||||
|  |  | ||||||
| #: fix.go:84 | #: fix.go:90 | ||||||
| msgid "Unable to create new cache directory" | msgid "Unable to create new cache directory" | ||||||
| msgstr "Не удалось создать новый каталог кэша" | msgstr "Не удалось создать новый каталог кэша" | ||||||
|  |  | ||||||
| @@ -133,58 +177,128 @@ msgstr "Показывать всю информацию, не только дл | |||||||
| msgid "Error getting packages" | msgid "Error getting packages" | ||||||
| msgstr "Ошибка при получении пакетов" | msgstr "Ошибка при получении пакетов" | ||||||
|  |  | ||||||
| #: info.go:76 | #: info.go:83 | ||||||
| msgid "Error iterating over packages" |  | ||||||
| msgstr "Ошибка при переборе пакетов" |  | ||||||
|  |  | ||||||
| #: info.go:90 |  | ||||||
| msgid "Command info expected at least 1 argument, got %d" | msgid "Command info expected at least 1 argument, got %d" | ||||||
| msgstr "Для команды info ожидался хотя бы 1 аргумент, получено %d" | msgstr "Для команды info ожидался хотя бы 1 аргумент, получено %d" | ||||||
|  |  | ||||||
| #: info.go:110 | #: info.go:104 | ||||||
| msgid "Error finding packages" | msgid "Error finding packages" | ||||||
| msgstr "Ошибка при поиске пакетов" | msgstr "Ошибка при поиске пакетов" | ||||||
|  |  | ||||||
| #: info.go:124 | #: info.go:118 | ||||||
| msgid "Can't detect system language" | msgid "Can't detect system language" | ||||||
| msgstr "Ошибка при определении языка системы" | msgstr "Ошибка при определении языка системы" | ||||||
|  |  | ||||||
| #: info.go:141 | #: info.go:134 | ||||||
| msgid "Error resolving overrides" | msgid "Error resolving overrides" | ||||||
| msgstr "Ошибка устранения переорпеделений" | msgstr "Ошибка устранения переорпеделений" | ||||||
|  |  | ||||||
| #: info.go:149 info.go:154 | #: info.go:143 | ||||||
| msgid "Error encoding script variables" | msgid "Error encoding script variables" | ||||||
| msgstr "Ошибка кодирования переменных скрита" | msgstr "Ошибка кодирования переменных скрита" | ||||||
|  |  | ||||||
| #: install.go:40 | #: install.go:39 | ||||||
| msgid "Install a new package" | msgid "Install a new package" | ||||||
| msgstr "Установить новый пакет" | msgstr "Установить новый пакет" | ||||||
|  |  | ||||||
| #: install.go:52 | #: install.go:51 | ||||||
| msgid "Command install expected at least 1 argument, got %d" | msgid "Command install expected at least 1 argument, got %d" | ||||||
| msgstr "Для команды install ожидался хотя бы 1 аргумент, получено %d" | msgstr "Для команды install ожидался хотя бы 1 аргумент, получено %d" | ||||||
|  |  | ||||||
| #: install.go:114 | #: install.go:113 | ||||||
| msgid "Error when installing the package" | msgid "Error when installing the package" | ||||||
| msgstr "Ошибка при установке пакета" | msgstr "Ошибка при установке пакета" | ||||||
|  |  | ||||||
| #: install.go:159 | #: install.go:151 | ||||||
| msgid "Remove an installed package" | msgid "Remove an installed package" | ||||||
| msgstr "Удалить установленный пакет" | msgstr "Удалить установленный пакет" | ||||||
|  |  | ||||||
| #: install.go:178 | #: install.go:170 | ||||||
| msgid "Error listing installed packages" | msgid "Error listing installed packages" | ||||||
| msgstr "Ошибка при составлении списка установленных пакетов" | msgstr "Ошибка при составлении списка установленных пакетов" | ||||||
|  |  | ||||||
| #: install.go:215 | #: install.go:199 | ||||||
| msgid "Command remove expected at least 1 argument, got %d" | msgid "Command remove expected at least 1 argument, got %d" | ||||||
| msgstr "Для команды remove ожидался хотя бы 1 аргумент, получено %d" | msgstr "Для команды remove ожидался хотя бы 1 аргумент, получено %d" | ||||||
|  |  | ||||||
| #: install.go:230 | #: install.go:214 | ||||||
| msgid "Error removing packages" | msgid "Error removing packages" | ||||||
| msgstr "Ошибка при удалении пакетов" | msgstr "Ошибка при удалении пакетов" | ||||||
|  |  | ||||||
|  | #: internal/build/build.go:378 | ||||||
|  | msgid "Building package" | ||||||
|  | msgstr "Сборка пакета" | ||||||
|  |  | ||||||
|  | #: internal/build/build.go:407 | ||||||
|  | msgid "The checksums array must be the same length as sources" | ||||||
|  | msgstr "Массив контрольных сумм должен быть той же длины, что и источники" | ||||||
|  |  | ||||||
|  | #: internal/build/build.go:449 | ||||||
|  | msgid "Downloading sources" | ||||||
|  | msgstr "Скачивание источников" | ||||||
|  |  | ||||||
|  | #: internal/build/build.go:495 | ||||||
|  | msgid "Would you like to remove the build dependencies?" | ||||||
|  | msgstr "Хотели бы вы удалить зависимости сборки?" | ||||||
|  |  | ||||||
|  | #: internal/build/build.go:571 | ||||||
|  | msgid "Installing dependencies" | ||||||
|  | msgstr "Установка зависимостей" | ||||||
|  |  | ||||||
|  | #: internal/build/checker.go:43 | ||||||
|  | msgid "" | ||||||
|  | "Your system's CPU architecture doesn't match this package. Do you want to " | ||||||
|  | "build anyway?" | ||||||
|  | msgstr "" | ||||||
|  | "Архитектура процессора вашей системы не соответствует этому пакету. Вы все " | ||||||
|  | "равно хотите выполнить сборку?" | ||||||
|  |  | ||||||
|  | #: internal/build/checker.go:67 | ||||||
|  | msgid "This package is already installed" | ||||||
|  | msgstr "Этот пакет уже установлен" | ||||||
|  |  | ||||||
|  | #: internal/build/find_deps/alt_linux.go:35 | ||||||
|  | msgid "Command not found on the system" | ||||||
|  | msgstr "Команда не найдена в системе" | ||||||
|  |  | ||||||
|  | #: internal/build/find_deps/alt_linux.go:86 | ||||||
|  | msgid "Provided dependency found" | ||||||
|  | msgstr "Найденная предоставленная зависимость" | ||||||
|  |  | ||||||
|  | #: internal/build/find_deps/alt_linux.go:93 | ||||||
|  | msgid "Required dependency found" | ||||||
|  | msgstr "Найдена требуемая зависимость" | ||||||
|  |  | ||||||
|  | #: internal/build/find_deps/empty.go:32 | ||||||
|  | msgid "AutoProv is not implemented for this package format, so it's skipped" | ||||||
|  | msgstr "" | ||||||
|  | "AutoProv не реализовано для этого формата пакета, поэтому будет пропущено" | ||||||
|  |  | ||||||
|  | #: internal/build/find_deps/empty.go:37 | ||||||
|  | msgid "AutoReq is not implemented for this package format, so it's skipped" | ||||||
|  | msgstr "" | ||||||
|  | "AutoReq не реализовано для этого формата пакета, поэтому будет пропущено" | ||||||
|  |  | ||||||
|  | #: internal/build/firejail.go:144 | ||||||
|  | msgid "Applying FireJail integration" | ||||||
|  | msgstr "Применение интеграции FireJail" | ||||||
|  |  | ||||||
|  | #: internal/build/script_executor.go:145 | ||||||
|  | msgid "Building package metadata" | ||||||
|  | msgstr "Сборка метаданных пакета" | ||||||
|  |  | ||||||
|  | #: internal/build/script_executor.go:285 | ||||||
|  | msgid "Executing prepare()" | ||||||
|  | msgstr "Выполнение prepare()" | ||||||
|  |  | ||||||
|  | #: internal/build/script_executor.go:294 | ||||||
|  | msgid "Executing build()" | ||||||
|  | msgstr "Выполнение build()" | ||||||
|  |  | ||||||
|  | #: internal/build/script_executor.go:323 internal/build/script_executor.go:343 | ||||||
|  | msgid "Executing %s()" | ||||||
|  | msgstr "Выполнение %s()" | ||||||
|  |  | ||||||
| #: internal/cliutils/app_builder/builder.go:75 | #: internal/cliutils/app_builder/builder.go:75 | ||||||
| msgid "Error loading config" | msgid "Error loading config" | ||||||
| msgstr "Ошибка при загрузке" | msgstr "Ошибка при загрузке" | ||||||
| @@ -193,15 +307,15 @@ msgstr "Ошибка при загрузке" | |||||||
| msgid "Error initialization database" | msgid "Error initialization database" | ||||||
| msgstr "Ошибка инициализации базы данных" | msgstr "Ошибка инициализации базы данных" | ||||||
|  |  | ||||||
| #: internal/cliutils/app_builder/builder.go:135 | #: internal/cliutils/app_builder/builder.go:142 | ||||||
| msgid "Error pulling repositories" | msgid "Error pulling repositories" | ||||||
| msgstr "Ошибка при извлечении репозиториев" | msgstr "Ошибка при извлечении репозиториев" | ||||||
|  |  | ||||||
| #: internal/cliutils/app_builder/builder.go:152 | #: internal/cliutils/app_builder/builder.go:159 | ||||||
| msgid "Error parsing os release" | msgid "Error parsing os release" | ||||||
| msgstr "Ошибка при разборе файла выпуска операционной системы" | msgstr "Ошибка при разборе файла выпуска операционной системы" | ||||||
|  |  | ||||||
| #: internal/cliutils/app_builder/builder.go:165 | #: internal/cliutils/app_builder/builder.go:172 | ||||||
| msgid "Unable to detect a supported package manager on the system" | msgid "Unable to detect a supported package manager on the system" | ||||||
| msgstr "Не удалось обнаружить поддерживаемый менеджер пакетов в системе" | msgstr "Не удалось обнаружить поддерживаемый менеджер пакетов в системе" | ||||||
|  |  | ||||||
| @@ -290,47 +404,51 @@ msgid "" | |||||||
| "This command is deprecated and would be removed in the future, use \"%s\" " | "This command is deprecated and would be removed in the future, use \"%s\" " | ||||||
| "instead!" | "instead!" | ||||||
| msgstr "" | msgstr "" | ||||||
| "Эта команда устарела и будет удалена в будущем, используйте вместо нее " | "Эта команда устарела и будет удалена в будущем, используйте вместо нее \"%s" | ||||||
| "\"%s\"!" | "\"!" | ||||||
|  |  | ||||||
| #: internal/db/db.go:137 | #: internal/db/db.go:76 | ||||||
| msgid "Database version mismatch; resetting" | msgid "Database version mismatch; resetting" | ||||||
| msgstr "Несоответствие версий базы данных; сброс настроек" | msgstr "Несоответствие версий базы данных; сброс настроек" | ||||||
|  |  | ||||||
| #: internal/db/db.go:144 | #: internal/db/db.go:82 | ||||||
| msgid "" | msgid "" | ||||||
| "Database version does not exist. Run alr fix if something isn't working." | "Database version does not exist. Run alr fix if something isn't working." | ||||||
| msgstr "" | msgstr "" | ||||||
| "Версия базы данных не существует. Запустите alr fix, если что-то не работает." | "Версия базы данных не существует. Запустите 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:41 | #: internal/logger/log.go:41 | ||||||
| msgid "ERROR" | msgid "ERROR" | ||||||
| msgstr "ОШИБКА" | msgstr "ОШИБКА" | ||||||
|  |  | ||||||
|  | #: internal/repos/pull.go:88 | ||||||
|  | msgid "Trying mirror" | ||||||
|  | msgstr "Пробую зеркало" | ||||||
|  |  | ||||||
|  | #: internal/repos/pull.go:94 | ||||||
|  | msgid "Failed to pull from URL" | ||||||
|  | msgstr "Не удалось извлечь из URL" | ||||||
|  |  | ||||||
|  | #: internal/repos/pull.go:158 | ||||||
|  | msgid "Pulling repository" | ||||||
|  | msgstr "Скачивание репозитория" | ||||||
|  |  | ||||||
|  | #: internal/repos/pull.go:195 | ||||||
|  | msgid "Repository up to date" | ||||||
|  | msgstr "Репозиторий уже обновлён" | ||||||
|  |  | ||||||
|  | #: internal/repos/pull.go:230 | ||||||
|  | msgid "Git repository does not appear to be a valid ALR repo" | ||||||
|  | msgstr "Репозиторий Git не поддерживается репозиторием ALR" | ||||||
|  |  | ||||||
|  | #: internal/repos/pull.go:246 | ||||||
|  | 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, если что-то не работает." | ||||||
|  |  | ||||||
| #: internal/utils/cmd.go:97 | #: internal/utils/cmd.go:97 | ||||||
| msgid "Error on dropping capabilities" | msgid "Error on dropping capabilities" | ||||||
| msgstr "Ошибка при понижении привилегий" | msgstr "Ошибка при понижении привилегий" | ||||||
| @@ -359,11 +477,11 @@ msgstr "Ошибка при получении пакетов для обнов | |||||||
| msgid "No packages for upgrade" | msgid "No packages for upgrade" | ||||||
| msgstr "Нет пакетов к обновлению" | msgstr "Нет пакетов к обновлению" | ||||||
|  |  | ||||||
| #: list.go:102 list.go:187 | #: list.go:102 list.go:184 | ||||||
| msgid "Error parsing format template" | msgid "Error parsing format template" | ||||||
| msgstr "Ошибка при разборе шаблона" | msgstr "Ошибка при разборе шаблона" | ||||||
|  |  | ||||||
| #: list.go:108 list.go:191 | #: list.go:108 list.go:188 | ||||||
| msgid "Error executing template" | msgid "Error executing template" | ||||||
| msgstr "Ошибка при выполнении шаблона" | msgstr "Ошибка при выполнении шаблона" | ||||||
|  |  | ||||||
| @@ -379,161 +497,140 @@ msgstr "Аргументы, которые будут переданы мене | |||||||
| msgid "Enable interactive questions and prompts" | msgid "Enable interactive questions and prompts" | ||||||
| msgstr "Включение интерактивных вопросов и запросов" | msgstr "Включение интерактивных вопросов и запросов" | ||||||
|  |  | ||||||
| #: main.go:146 | #: main.go:147 | ||||||
| msgid "Show help" | msgid "Show help" | ||||||
| msgstr "Показать справку" | msgstr "Показать справку" | ||||||
|  |  | ||||||
| #: main.go:150 | #: main.go:151 | ||||||
| msgid "Error while running app" | msgid "Error while running app" | ||||||
| msgstr "Ошибка при запуске приложения" | msgstr "Ошибка при запуске приложения" | ||||||
|  |  | ||||||
| #: pkg/build/build.go:395 | #: pkg/dl/dl.go:170 | ||||||
| msgid "Building package" | msgid "Source can be updated, updating if required" | ||||||
| msgstr "Сборка пакета" | msgstr "Исходный код можно обновлять, обновляя при необходимости" | ||||||
|  |  | ||||||
| #: pkg/build/build.go:424 | #: pkg/dl/dl.go:196 | ||||||
| msgid "The checksums array must be the same length as sources" | msgid "Source found in cache and linked to destination" | ||||||
| msgstr "Массив контрольных сумм должен быть той же длины, что и источники" | msgstr "Источник найден в кэше и связан с пунктом назначения" | ||||||
|  |  | ||||||
| #: pkg/build/build.go:455 | #: pkg/dl/dl.go:203 | ||||||
| msgid "Downloading sources" | msgid "Source updated and linked to destination" | ||||||
| msgstr "Скачивание источников" | msgstr "Источник обновлён и связан с пунктом назначения" | ||||||
|  |  | ||||||
| #: pkg/build/build.go:549 | #: pkg/dl/dl.go:217 | ||||||
| msgid "Installing dependencies" | msgid "Downloading source" | ||||||
| msgstr "Установка зависимостей" | msgstr "Скачивание источника" | ||||||
|  |  | ||||||
| #: pkg/build/checker.go:43 | #: pkg/dl/progress_tui.go:100 | ||||||
| msgid "" | msgid "%s: done!\n" | ||||||
| "Your system's CPU architecture doesn't match this package. Do you want to " | msgstr "%s: выполнено!\n" | ||||||
| "build anyway?" |  | ||||||
| msgstr "" |  | ||||||
| "Архитектура процессора вашей системы не соответствует этому пакету. Вы все " |  | ||||||
| "равно хотите выполнить сборку?" |  | ||||||
|  |  | ||||||
| #: pkg/build/checker.go:67 | #: pkg/dl/progress_tui.go:104 | ||||||
| msgid "This package is already installed" | msgid "%s %s downloading at %s/s\n" | ||||||
| msgstr "Этот пакет уже установлен" | msgstr "%s %s загружается — %s/с\n" | ||||||
|  |  | ||||||
| #: pkg/build/find_deps/alt_linux.go:35 |  | ||||||
| msgid "Command not found on the system" |  | ||||||
| msgstr "Команда не найдена в системе" |  | ||||||
|  |  | ||||||
| #: pkg/build/find_deps/alt_linux.go:86 |  | ||||||
| msgid "Provided dependency found" |  | ||||||
| msgstr "Найденная предоставленная зависимость" |  | ||||||
|  |  | ||||||
| #: pkg/build/find_deps/alt_linux.go:93 |  | ||||||
| msgid "Required dependency found" |  | ||||||
| msgstr "Найдена требуемая зависимость" |  | ||||||
|  |  | ||||||
| #: pkg/build/find_deps/empty.go:32 |  | ||||||
| msgid "AutoProv is not implemented for this package format, so it's skipped" |  | ||||||
| msgstr "" |  | ||||||
| "AutoProv не реализовано для этого формата пакета, поэтому будет пропущено" |  | ||||||
|  |  | ||||||
| #: pkg/build/find_deps/empty.go:37 |  | ||||||
| msgid "AutoReq is not implemented for this package format, so it's skipped" |  | ||||||
| msgstr "" |  | ||||||
| "AutoReq не реализовано для этого формата пакета, поэтому будет пропущено" |  | ||||||
|  |  | ||||||
| #: pkg/build/script_executor.go:241 |  | ||||||
| msgid "Building package metadata" |  | ||||||
| msgstr "Сборка метаданных пакета" |  | ||||||
|  |  | ||||||
| #: pkg/build/script_executor.go:372 |  | ||||||
| msgid "Executing prepare()" |  | ||||||
| msgstr "Выполнение prepare()" |  | ||||||
|  |  | ||||||
| #: pkg/build/script_executor.go:381 |  | ||||||
| msgid "Executing build()" |  | ||||||
| msgstr "Выполнение build()" |  | ||||||
|  |  | ||||||
| #: pkg/build/script_executor.go:410 pkg/build/script_executor.go:430 |  | ||||||
| msgid "Executing %s()" |  | ||||||
| msgstr "Выполнение %s()" |  | ||||||
|  |  | ||||||
| #: pkg/repos/pull.go:77 |  | ||||||
| msgid "Pulling repository" |  | ||||||
| msgstr "Скачивание репозитория" |  | ||||||
|  |  | ||||||
| #: pkg/repos/pull.go:113 |  | ||||||
| msgid "Repository up to date" |  | ||||||
| msgstr "Репозиторий уже обновлён" |  | ||||||
|  |  | ||||||
| #: pkg/repos/pull.go:204 |  | ||||||
| msgid "Git repository does not appear to be a valid ALR repo" |  | ||||||
| msgstr "Репозиторий Git не поддерживается репозиторием ALR" |  | ||||||
|  |  | ||||||
| #: pkg/repos/pull.go:220 |  | ||||||
| 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, если что-то не работает." |  | ||||||
|  |  | ||||||
| #: refresh.go:30 | #: refresh.go:30 | ||||||
| msgid "Pull all repositories that have changed" | msgid "Pull all repositories that have changed" | ||||||
| msgstr "Скачать все изменённые репозитории" | msgstr "Скачать все изменённые репозитории" | ||||||
|  |  | ||||||
| #: repo.go:39 | #: repo.go:41 | ||||||
| msgid "Manage repos" | msgid "Manage repos" | ||||||
| msgstr "Управление репозиториями" | msgstr "Управление репозиториями" | ||||||
|  |  | ||||||
| #: repo.go:51 repo.go:269 | #: repo.go:55 repo.go:625 | ||||||
| msgid "Remove an existing repository" | msgid "Remove an existing repository" | ||||||
| msgstr "Удалить существующий репозиторий" | msgstr "Удалить существующий репозиторий" | ||||||
|  |  | ||||||
| #: repo.go:53 | #: repo.go:57 repo.go:521 | ||||||
| msgid "<name>" | msgid "<name>" | ||||||
| msgstr "<имя>" | msgstr "<имя>" | ||||||
|  |  | ||||||
| #: repo.go:83 | #: repo.go:102 repo.go:465 repo.go:568 | ||||||
| msgid "Repo \"%s\" does not exist" | msgid "Repo \"%s\" does not exist" | ||||||
| msgstr "Репозитория \"%s\" не существует" | msgstr "Репозитория \"%s\" не существует" | ||||||
|  |  | ||||||
| #: repo.go:90 | #: repo.go:109 | ||||||
| msgid "Error removing repo directory" | msgid "Error removing repo directory" | ||||||
| msgstr "Ошибка при удалении каталога репозитория" | msgstr "Ошибка при удалении каталога репозитория" | ||||||
|  |  | ||||||
| #: repo.go:94 repo.go:161 repo.go:219 | #: repo.go:113 repo.go:180 repo.go:253 repo.go:316 repo.go:389 repo.go:504 | ||||||
|  | #: repo.go:576 | ||||||
| msgid "Error saving config" | msgid "Error saving config" | ||||||
| msgstr "Ошибка при сохранении конфигурации" | msgstr "Ошибка при сохранении конфигурации" | ||||||
|  |  | ||||||
| #: repo.go:113 | #: repo.go:132 | ||||||
| msgid "Error removing packages from database" | msgid "Error removing packages from database" | ||||||
| msgstr "Ошибка при удалении пакетов из базы данных" | msgstr "Ошибка при удалении пакетов из базы данных" | ||||||
|  |  | ||||||
| #: repo.go:124 repo.go:239 | #: repo.go:143 repo.go:595 | ||||||
| msgid "Add a new repository" | msgid "Add a new repository" | ||||||
| msgstr "Добавить новый репозиторий" | msgstr "Добавить новый репозиторий" | ||||||
|  |  | ||||||
| #: repo.go:125 | #: repo.go:144 repo.go:270 repo.go:345 repo.go:402 | ||||||
| msgid "<name> <url>" | msgid "<name> <url>" | ||||||
| msgstr "<имя> <url>" | msgstr "<имя> <url>" | ||||||
|  |  | ||||||
| #: repo.go:150 | #: repo.go:169 | ||||||
| msgid "Repo \"%s\" already exists" | msgid "Repo \"%s\" already exists" | ||||||
| msgstr "Репозиторий \"%s\" уже существует" | msgstr "Репозиторий \"%s\" уже существует" | ||||||
|  |  | ||||||
| #: repo.go:187 | #: repo.go:206 | ||||||
| msgid "Set the reference of the repository" | msgid "Set the reference of the repository" | ||||||
| msgstr "Установить ссылку на версию репозитория" | msgstr "Установить ссылку на версию репозитория" | ||||||
|  |  | ||||||
| #: repo.go:188 | #: repo.go:207 | ||||||
| msgid "<name> <ref>" | msgid "<name> <ref>" | ||||||
| msgstr "<имя> <ссылка_на_версию>" | msgstr "<имя> <ссылка_на_версию>" | ||||||
|  |  | ||||||
| #: repo.go:246 | #: repo.go:269 | ||||||
|  | msgid "Set the main url of the repository" | ||||||
|  | msgstr "Установить главный URL репозитория" | ||||||
|  |  | ||||||
|  | #: repo.go:332 | ||||||
|  | msgid "Manage mirrors of repos" | ||||||
|  | msgstr "Управление зеркалами репозитория" | ||||||
|  |  | ||||||
|  | #: repo.go:344 | ||||||
|  | msgid "Add a mirror URL to repository" | ||||||
|  | msgstr "Добавить зеркало репозитория" | ||||||
|  |  | ||||||
|  | #: repo.go:401 | ||||||
|  | msgid "Remove mirror from the repository" | ||||||
|  | msgstr "Удалить зеркало из репозитория" | ||||||
|  |  | ||||||
|  | #: repo.go:420 | ||||||
|  | msgid "Ignore if mirror does not exist" | ||||||
|  | msgstr "Игнорировать, если зеркала не существует" | ||||||
|  |  | ||||||
|  | #: repo.go:425 | ||||||
|  | msgid "Match partial URL (e.g., github.com instead of full URL)" | ||||||
|  | msgstr "Соответствует частичному URL (например, github.com вместо полного URL)" | ||||||
|  |  | ||||||
|  | #: repo.go:490 | ||||||
|  | msgid "No mirrors containing \"%s\" found in repo \"%s\"" | ||||||
|  | msgstr "В репозитории \"%s\" не найдено зеркал, содержащих \"%s\"" | ||||||
|  |  | ||||||
|  | #: repo.go:492 | ||||||
|  | msgid "URL \"%s\" does not exist in repo \"%s\"" | ||||||
|  | msgstr "URL \"%s\" не существует в репозитории \"%s\"" | ||||||
|  |  | ||||||
|  | #: repo.go:508 repo.go:580 | ||||||
|  | msgid "Removed %d mirrors from repo \"%s\"\n" | ||||||
|  | msgstr "Удалены зеркала %d из репозитория \"%s\"\n" | ||||||
|  |  | ||||||
|  | #: repo.go:520 | ||||||
|  | msgid "Remove all mirrors from the repository" | ||||||
|  | msgstr "Удалить все зеркала из репозитория" | ||||||
|  |  | ||||||
|  | #: repo.go:602 | ||||||
| msgid "Name of the new repo" | msgid "Name of the new repo" | ||||||
| msgstr "Название нового репозитория" | msgstr "Название нового репозитория" | ||||||
|  |  | ||||||
| #: repo.go:252 | #: repo.go:608 | ||||||
| msgid "URL of the new repo" | msgid "URL of the new repo" | ||||||
| msgstr "URL-адрес нового репозитория" | msgstr "URL-адрес нового репозитория" | ||||||
|  |  | ||||||
| #: repo.go:276 | #: repo.go:632 | ||||||
| msgid "Name of the repo to be deleted" | msgid "Name of the repo to be deleted" | ||||||
| msgstr "Название репозитория  удалён" | msgstr "Название репозитория  удалён" | ||||||
|  |  | ||||||
| @@ -561,18 +658,25 @@ msgstr "Иcкать по provides" | |||||||
| msgid "Error while executing search" | msgid "Error while executing search" | ||||||
| msgstr "Ошибка при выполнении поиска" | msgstr "Ошибка при выполнении поиска" | ||||||
|  |  | ||||||
| #: upgrade.go:47 | #: upgrade.go:48 | ||||||
| msgid "Upgrade all installed packages" | msgid "Upgrade all installed packages" | ||||||
| msgstr "Обновить все установленные пакеты" | msgstr "Обновить все установленные пакеты" | ||||||
|  |  | ||||||
| #: upgrade.go:105 upgrade.go:122 | #: upgrade.go:106 upgrade.go:123 | ||||||
| msgid "Error checking for updates" | msgid "Error checking for updates" | ||||||
| msgstr "Ошибка при проверке обновлений" | msgstr "Ошибка при проверке обновлений" | ||||||
|  |  | ||||||
| #: upgrade.go:125 | #: upgrade.go:126 | ||||||
| msgid "There is nothing to do." | msgid "There is nothing to do." | ||||||
| msgstr "Здесь нечего делать." | msgstr "Здесь нечего делать." | ||||||
|  |  | ||||||
|  | #, fuzzy | ||||||
|  | #~ msgid "Failed to clear contents of cache directory" | ||||||
|  | #~ msgstr "Не удалось создать каталог кэша репозитория" | ||||||
|  |  | ||||||
|  | #~ msgid "Error iterating over packages" | ||||||
|  | #~ msgstr "Ошибка при переборе пакетов" | ||||||
|  |  | ||||||
| #~ msgid "Error pulling repos" | #~ msgid "Error pulling repos" | ||||||
| #~ msgstr "Ошибка при извлечении репозиториев" | #~ msgstr "Ошибка при извлечении репозиториев" | ||||||
|  |  | ||||||
| @@ -588,9 +692,6 @@ msgstr "Здесь нечего делать." | |||||||
| #~ msgid "Unable to create config directory" | #~ msgid "Unable to create config directory" | ||||||
| #~ msgstr "Не удалось создать каталог конфигурации ALR" | #~ msgstr "Не удалось создать каталог конфигурации ALR" | ||||||
|  |  | ||||||
| #~ msgid "Unable to create repo cache directory" |  | ||||||
| #~ msgstr "Не удалось создать каталог кэша репозитория" |  | ||||||
|  |  | ||||||
| #~ msgid "Unable to create package cache directory" | #~ msgid "Unable to create package cache directory" | ||||||
| #~ msgstr "Не удалось создать каталог кэша пакетов" | #~ msgstr "Не удалось создать каталог кэша пакетов" | ||||||
|  |  | ||||||
| @@ -610,9 +711,6 @@ msgstr "Здесь нечего делать." | |||||||
| #~ msgid "Installing build dependencies" | #~ msgid "Installing build dependencies" | ||||||
| #~ msgstr "Установка зависимостей сборки" | #~ msgstr "Установка зависимостей сборки" | ||||||
|  |  | ||||||
| #~ msgid "Would you like to remove the build dependencies?" |  | ||||||
| #~ msgstr "Хотели бы вы удалить зависимости сборки?" |  | ||||||
|  |  | ||||||
| #~ msgid "Error installing native packages" | #~ msgid "Error installing native packages" | ||||||
| #~ msgstr "Ошибка при установке нативных пакетов" | #~ msgstr "Ошибка при установке нативных пакетов" | ||||||
|  |  | ||||||
| @@ -629,9 +727,6 @@ msgstr "Здесь нечего делать." | |||||||
| #~ msgid "Unable to detect user config directory" | #~ msgid "Unable to detect user config directory" | ||||||
| #~ msgstr "Не удалось обнаружить каталог конфигурации пользователя" | #~ msgstr "Не удалось обнаружить каталог конфигурации пользователя" | ||||||
|  |  | ||||||
| #~ msgid "Unable to create ALR config file" |  | ||||||
| #~ msgstr "Не удалось создать конфигурационный файл ALR" |  | ||||||
|  |  | ||||||
| #~ msgid "Error encoding default configuration" | #~ msgid "Error encoding default configuration" | ||||||
| #~ msgstr "Ошибка кодирования конфигурации по умолчанию" | #~ msgstr "Ошибка кодирования конфигурации по умолчанию" | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										15
									
								
								list.go
									
									
									
									
									
								
							
							
						
						
									
										15
									
								
								list.go
									
									
									
									
									
								
							| @@ -29,12 +29,12 @@ import ( | |||||||
| 	"github.com/leonelquinteros/gotext" | 	"github.com/leonelquinteros/gotext" | ||||||
| 	"github.com/urfave/cli/v2" | 	"github.com/urfave/cli/v2" | ||||||
|  |  | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/build" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils" | ||||||
| 	appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder" | 	appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder" | ||||||
| 	database "gitea.plemya-x.ru/Plemya-x/ALR/internal/db" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/manager" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/build" | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager" |  | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func ListCmd() *cli.Command { | func ListCmd() *cli.Command { | ||||||
| @@ -69,9 +69,9 @@ func ListCmd() *cli.Command { | |||||||
| 				WithConfig(). | 				WithConfig(). | ||||||
| 				WithDB(). | 				WithDB(). | ||||||
| 				WithManager(). | 				WithManager(). | ||||||
|  | 				WithDistroInfo(). | ||||||
| 				// autoPull only | 				// autoPull only | ||||||
| 				WithRepos(). | 				WithRepos(). | ||||||
| 				WithDistroInfo(). |  | ||||||
| 				Build() | 				Build() | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				return err | 				return err | ||||||
| @@ -125,7 +125,6 @@ func ListCmd() *cli.Command { | |||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				return cliutils.FormatCliExit(gotext.Get("Error getting packages"), err) | 				return cliutils.FormatCliExit(gotext.Get("Error getting packages"), err) | ||||||
| 			} | 			} | ||||||
| 			defer result.Close() |  | ||||||
|  |  | ||||||
| 			installedAlrPackages := map[string]string{} | 			installedAlrPackages := map[string]string{} | ||||||
| 			if c.Bool("installed") { | 			if c.Bool("installed") { | ||||||
| @@ -150,9 +149,7 @@ func ListCmd() *cli.Command { | |||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			for result.Next() { | 			for _, pkg := range result { | ||||||
| 				var pkg database.Package |  | ||||||
| 				err := result.StructScan(&pkg) |  | ||||||
| 				if err != nil { | 				if err != nil { | ||||||
| 					return cli.Exit(err, 1) | 					return cli.Exit(err, 1) | ||||||
| 				} | 				} | ||||||
| @@ -162,7 +159,7 @@ func ListCmd() *cli.Command { | |||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 				type packageInfo struct { | 				type packageInfo struct { | ||||||
| 					Package *database.Package | 					Package *alrsh.Package | ||||||
| 					Version string | 					Version string | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										3
									
								
								main.go
									
									
									
									
									
								
							
							
						
						
									
										3
									
								
								main.go
									
									
									
									
									
								
							| @@ -33,8 +33,8 @@ import ( | |||||||
|  |  | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils" | 	"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/config" | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/manager" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/translations" | 	"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" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/logger" | ||||||
| ) | ) | ||||||
| @@ -83,6 +83,7 @@ func GetApp() *cli.App { | |||||||
| 			VersionCmd(), | 			VersionCmd(), | ||||||
| 			SearchCmd(), | 			SearchCmd(), | ||||||
| 			RepoCmd(), | 			RepoCmd(), | ||||||
|  | 			ConfigCmd(), | ||||||
| 			// Internal commands | 			// Internal commands | ||||||
| 			InternalBuildCmd(), | 			InternalBuildCmd(), | ||||||
| 			InternalInstallCmd(), | 			InternalInstallCmd(), | ||||||
|   | |||||||
| @@ -52,9 +52,9 @@ | |||||||
| ./internal/translations/files/lure.en.toml | ./internal/translations/files/lure.en.toml | ||||||
| ./internal/translations/files/lure.ru.toml | ./internal/translations/files/lure.ru.toml | ||||||
| ./internal/translations/translations.go | ./internal/translations/translations.go | ||||||
| ./internal/types/build.go | ./pkg/types/build.go | ||||||
| ./internal/types/config.go | ./pkg/types/config.go | ||||||
| ./internal/types/repo.go | ./pkg/types/repo.go | ||||||
| ./list.go | ./list.go | ||||||
| ./main.go | ./main.go | ||||||
| ./pkg/build/build.go | ./pkg/build/build.go | ||||||
|   | |||||||
							
								
								
									
										205
									
								
								pkg/alrsh/alrsh.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										205
									
								
								pkg/alrsh/alrsh.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,205 @@ | |||||||
|  | // ALR - Any Linux Repository | ||||||
|  | // Copyright (C) 2025 The ALR Authors | ||||||
|  | // | ||||||
|  | // 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 alrsh | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 	"errors" | ||||||
|  | 	"fmt" | ||||||
|  | 	"os" | ||||||
|  | 	"path/filepath" | ||||||
|  | 	"runtime" | ||||||
|  | 	"strconv" | ||||||
|  | 	"strings" | ||||||
|  |  | ||||||
|  | 	"mvdan.cc/sh/v3/expand" | ||||||
|  | 	"mvdan.cc/sh/v3/interp" | ||||||
|  | 	"mvdan.cc/sh/v3/syntax" | ||||||
|  |  | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/cpu" | ||||||
|  | 	"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/shutils/helpers" | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro" | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type ScriptFile struct { | ||||||
|  | 	file *syntax.File | ||||||
|  | 	path string | ||||||
|  | } | ||||||
|  |  | ||||||
|  | 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 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *ScriptFile) ParseBuildVars(ctx context.Context, info *distro.OSRelease, packages []string) (string, []*Package, error) { | ||||||
|  | 	runner, err := s.createRunner(info) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return "", nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if err := runScript(ctx, runner, s.file); err != nil { | ||||||
|  | 		return "", nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	dec := newDecoder(info, runner) | ||||||
|  |  | ||||||
|  | 	pkgNames, err := ParseNames(dec) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return "", nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if len(pkgNames.Names) == 0 { | ||||||
|  | 		return "", nil, errors.New("package name is missing") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	targetPackages := packages | ||||||
|  | 	if len(targetPackages) == 0 { | ||||||
|  | 		targetPackages = pkgNames.Names | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	varsOfPackages, err := s.createPackagesForBuildVars(ctx, dec, pkgNames, targetPackages) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return "", nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	baseName := pkgNames.BasePkgName | ||||||
|  | 	if len(pkgNames.Names) == 1 { | ||||||
|  | 		baseName = pkgNames.Names[0] | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return baseName, varsOfPackages, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *ScriptFile) createRunner(info *distro.OSRelease) (*interp.Runner, error) { | ||||||
|  | 	scriptDir := filepath.Dir(s.path) | ||||||
|  | 	env := createBuildEnvVars(info, types.Directories{ScriptDir: scriptDir}) | ||||||
|  |  | ||||||
|  | 	return interp.New( | ||||||
|  | 		interp.Env(expand.ListEnviron(env...)), | ||||||
|  | 		interp.StdIO(os.Stdin, os.Stderr, os.Stderr), | ||||||
|  | 		interp.ExecHandler(helpers.Restricted.ExecHandler(handlers.NopExec)), | ||||||
|  | 		interp.ReadDirHandler2(handlers.RestrictedReadDir(scriptDir)), | ||||||
|  | 		interp.StatHandler(handlers.RestrictedStat(scriptDir)), | ||||||
|  | 		interp.OpenHandler(handlers.RestrictedOpen(scriptDir)), | ||||||
|  | 		interp.Dir(scriptDir), | ||||||
|  | 	) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *ScriptFile) createPackagesForBuildVars( | ||||||
|  | 	ctx context.Context, | ||||||
|  | 	dec *decoder.Decoder, | ||||||
|  | 	pkgNames *PackageNames, | ||||||
|  | 	targetPackages []string, | ||||||
|  | ) ([]*Package, error) { | ||||||
|  | 	var varsOfPackages []*Package | ||||||
|  |  | ||||||
|  | 	if len(pkgNames.Names) == 1 { | ||||||
|  | 		var pkg Package | ||||||
|  | 		pkg.Name = pkgNames.Names[0] | ||||||
|  | 		if err := dec.DecodeVars(&pkg); err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 		varsOfPackages = append(varsOfPackages, &pkg) | ||||||
|  | 		return varsOfPackages, nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for _, pkgName := range targetPackages { | ||||||
|  | 		pkg, err := s.createPackageFromMeta(ctx, dec, pkgName, pkgNames.BasePkgName) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 		varsOfPackages = append(varsOfPackages, pkg) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return varsOfPackages, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *ScriptFile) createPackageFromMeta( | ||||||
|  | 	ctx context.Context, | ||||||
|  | 	dec *decoder.Decoder, | ||||||
|  | 	pkgName, basePkgName string, | ||||||
|  | ) (*Package, error) { | ||||||
|  | 	funcName := fmt.Sprintf("meta_%s", pkgName) | ||||||
|  | 	meta, ok := dec.GetFuncWithSubshell(funcName) | ||||||
|  | 	if !ok { | ||||||
|  | 		return nil, fmt.Errorf("func %s is missing", funcName) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	metaRunner, err := meta(ctx) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	metaDecoder := decoder.New(&distro.OSRelease{}, metaRunner) | ||||||
|  |  | ||||||
|  | 	var vars Package | ||||||
|  | 	if err := metaDecoder.DecodeVars(&vars); err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	vars.Name = pkgName | ||||||
|  | 	vars.BasePkgName = basePkgName | ||||||
|  |  | ||||||
|  | 	return &vars, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func runScript(ctx context.Context, runner *interp.Runner, fl *syntax.File) error { | ||||||
|  | 	runner.Reset() | ||||||
|  | 	return runner.Run(ctx, fl) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func newDecoder(info *distro.OSRelease, runner *interp.Runner) *decoder.Decoder { | ||||||
|  | 	d := decoder.New(info, runner) | ||||||
|  | 	// d.Overrides = false | ||||||
|  | 	// d.LikeDistros = false | ||||||
|  | 	return d | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (a *ScriptFile) Path() string { | ||||||
|  | 	return a.path | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (a *ScriptFile) File() *syntax.File { | ||||||
|  | 	return a.file | ||||||
|  | } | ||||||
							
								
								
									
										61
									
								
								pkg/alrsh/gob.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								pkg/alrsh/gob.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,61 @@ | |||||||
|  | // ALR - Any Linux Repository | ||||||
|  | // Copyright (C) 2025 The ALR Authors | ||||||
|  | // | ||||||
|  | // 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 alrsh | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"bytes" | ||||||
|  | 	"encoding/gob" | ||||||
|  |  | ||||||
|  | 	"mvdan.cc/sh/v3/syntax" | ||||||
|  | 	"mvdan.cc/sh/v3/syntax/typedjson" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func (s *ScriptFile) GobEncode() ([]byte, error) { | ||||||
|  | 	var buf bytes.Buffer | ||||||
|  | 	enc := gob.NewEncoder(&buf) | ||||||
|  | 	if err := enc.Encode(s.path); err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	var fileBuf bytes.Buffer | ||||||
|  | 	if err := typedjson.Encode(&fileBuf, s.file); err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	fileData := fileBuf.Bytes() | ||||||
|  | 	if err := enc.Encode(fileData); err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	return buf.Bytes(), nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *ScriptFile) GobDecode(data []byte) error { | ||||||
|  | 	buf := bytes.NewBuffer(data) | ||||||
|  | 	dec := gob.NewDecoder(buf) | ||||||
|  | 	if err := dec.Decode(&s.path); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	var fileData []byte | ||||||
|  | 	if err := dec.Decode(&fileData); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	fileReader := bytes.NewReader(fileData) | ||||||
|  | 	file, err := typedjson.Decode(fileReader) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	s.file = file.(*syntax.File) | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
							
								
								
									
										191
									
								
								pkg/alrsh/overridable.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										191
									
								
								pkg/alrsh/overridable.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,191 @@ | |||||||
|  | // ALR - Any Linux Repository | ||||||
|  | // Copyright (C) 2025 The ALR Authors | ||||||
|  | // | ||||||
|  | // 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 alrsh | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"bytes" | ||||||
|  | 	"encoding/gob" | ||||||
|  | 	"encoding/json" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type OverridableField[T any] struct { | ||||||
|  | 	data map[string]T | ||||||
|  | 	// It can't be a pointer | ||||||
|  | 	// | ||||||
|  | 	// See https://gitea.com/xorm/xorm/issues/2431 | ||||||
|  | 	resolved T | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (f *OverridableField[T]) Set(key string, value T) { | ||||||
|  | 	if f.data == nil { | ||||||
|  | 		f.data = make(map[string]T) | ||||||
|  | 	} | ||||||
|  | 	f.data[key] = value | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (f *OverridableField[T]) Get(key string) T { | ||||||
|  | 	if f.data == nil { | ||||||
|  | 		f.data = make(map[string]T) | ||||||
|  | 	} | ||||||
|  | 	return f.data[key] | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (f *OverridableField[T]) Has(key string) (T, bool) { | ||||||
|  | 	if f.data == nil { | ||||||
|  | 		f.data = make(map[string]T) | ||||||
|  | 	} | ||||||
|  | 	val, ok := f.data[key] | ||||||
|  | 	return val, ok | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (f *OverridableField[T]) SetResolved(value T) { | ||||||
|  | 	f.resolved = value | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (f *OverridableField[T]) Resolved() T { | ||||||
|  | 	return f.resolved | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (f *OverridableField[T]) All() map[string]T { | ||||||
|  | 	return f.data | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (o *OverridableField[T]) Resolve(overrides []string) { | ||||||
|  | 	for _, override := range overrides { | ||||||
|  | 		if v, ok := o.Has(override); ok { | ||||||
|  | 			o.SetResolved(v) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Database serialization (JSON) | ||||||
|  | func (f *OverridableField[T]) ToDB() ([]byte, error) { | ||||||
|  | 	var data map[string]T | ||||||
|  |  | ||||||
|  | 	if f.data == nil { | ||||||
|  | 		data = make(map[string]T) | ||||||
|  | 	} else { | ||||||
|  | 		data = f.data | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return json.Marshal(data) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (f *OverridableField[T]) FromDB(data []byte) error { | ||||||
|  | 	if len(data) == 0 { | ||||||
|  | 		*f = OverridableField[T]{data: make(map[string]T)} | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	var temp map[string]T | ||||||
|  | 	if err := json.Unmarshal(data, &temp); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if temp == nil { | ||||||
|  | 		temp = make(map[string]T) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	*f = OverridableField[T]{data: temp} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Gob serialization | ||||||
|  | type overridableFieldGobPayload[T any] struct { | ||||||
|  | 	Data     map[string]T | ||||||
|  | 	Resolved T | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (f *OverridableField[T]) GobEncode() ([]byte, error) { | ||||||
|  | 	var buf bytes.Buffer | ||||||
|  | 	enc := gob.NewEncoder(&buf) | ||||||
|  |  | ||||||
|  | 	payload := overridableFieldGobPayload[T]{ | ||||||
|  | 		Data:     f.data, | ||||||
|  | 		Resolved: f.resolved, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if err := enc.Encode(payload); err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	return buf.Bytes(), nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (f *OverridableField[T]) GobDecode(data []byte) error { | ||||||
|  | 	dec := gob.NewDecoder(bytes.NewBuffer(data)) | ||||||
|  |  | ||||||
|  | 	var payload overridableFieldGobPayload[T] | ||||||
|  | 	if err := dec.Decode(&payload); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	f.data = payload.Data | ||||||
|  | 	f.resolved = payload.Resolved | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type overridableFieldJSONPayload[T any] struct { | ||||||
|  | 	Resolved *T           `json:"resolved,omitempty,omitzero"` | ||||||
|  | 	Data     map[string]T `json:"overrides,omitempty,omitzero"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (f OverridableField[T]) MarshalJSON() ([]byte, error) { | ||||||
|  | 	data := make(map[string]T) | ||||||
|  |  | ||||||
|  | 	for k, v := range f.data { | ||||||
|  | 		if k == "" { | ||||||
|  | 			data["default"] = v | ||||||
|  | 		} else { | ||||||
|  | 			data[k] = v | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	payload := overridableFieldJSONPayload[T]{ | ||||||
|  | 		Data:     data, | ||||||
|  | 		Resolved: &f.resolved, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return json.Marshal(payload) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (f *OverridableField[T]) UnmarshalJSON(data []byte) error { | ||||||
|  | 	var payload overridableFieldJSONPayload[T] | ||||||
|  | 	if err := json.Unmarshal(data, &payload); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if payload.Data == nil { | ||||||
|  | 		payload.Data = make(map[string]T) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	f.data = payload.Data | ||||||
|  | 	if payload.Resolved != nil { | ||||||
|  | 		f.resolved = *payload.Resolved | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func OverridableFromMap[T any](data map[string]T) OverridableField[T] { | ||||||
|  | 	if data == nil { | ||||||
|  | 		data = make(map[string]T) | ||||||
|  | 	} | ||||||
|  | 	return OverridableField[T]{ | ||||||
|  | 		data: data, | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										156
									
								
								pkg/alrsh/package.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										156
									
								
								pkg/alrsh/package.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,156 @@ | |||||||
|  | // ALR - Any Linux Repository | ||||||
|  | // Copyright (C) 2025 The ALR Authors | ||||||
|  | // | ||||||
|  | // 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/>. | ||||||
|  |  | ||||||
|  | //go:generate bash -c "go run ../../generators/alrsh-package && cd ../.. && make update-license" | ||||||
|  |  | ||||||
|  | package alrsh | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"encoding/json" | ||||||
|  | 	"fmt" | ||||||
|  | 	"reflect" | ||||||
|  | 	"strings" | ||||||
|  |  | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/decoder" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type PackageNames struct { | ||||||
|  | 	BasePkgName string   `sh:"basepkg_name"` | ||||||
|  | 	Names       []string `sh:"name"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func ParseNames(dec *decoder.Decoder) (*PackageNames, error) { | ||||||
|  | 	var pkgs PackageNames | ||||||
|  | 	err := dec.DecodeVars(&pkgs) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, fmt.Errorf("fail parse names: %w", err) | ||||||
|  | 	} | ||||||
|  | 	return &pkgs, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type Package struct { | ||||||
|  | 	Repository  string `xorm:"pk 'repository'" json:"repository"` | ||||||
|  | 	Name        string `xorm:"pk 'name'" json:"name"` | ||||||
|  | 	BasePkgName string `xorm:"notnull 'basepkg_name'" json:"basepkg_name"` | ||||||
|  |  | ||||||
|  | 	Version       string   `sh:"version" xorm:"notnull 'version'" json:"version"` | ||||||
|  | 	Release       int      `sh:"release" xorm:"notnull 'release'" json:"release"` | ||||||
|  | 	Epoch         uint     `sh:"epoch" xorm:"'epoch'" json:"epoch"` | ||||||
|  | 	Architectures []string `sh:"architectures" xorm:"json 'architectures'" json:"architectures"` | ||||||
|  | 	Licenses      []string `sh:"license" xorm:"json 'licenses'" json:"license"` | ||||||
|  | 	Provides      []string `sh:"provides" xorm:"json 'provides'" json:"provides"` | ||||||
|  | 	Conflicts     []string `sh:"conflicts" xorm:"json 'conflicts'" json:"conflicts"` | ||||||
|  | 	Replaces      []string `sh:"replaces" xorm:"json 'replaces'" json:"replaces"` | ||||||
|  |  | ||||||
|  | 	Summary          OverridableField[string]   `sh:"summary" xorm:"'summary'" json:"summary"` | ||||||
|  | 	Description      OverridableField[string]   `sh:"desc" xorm:"'description'" json:"description"` | ||||||
|  | 	Group            OverridableField[string]   `sh:"group" xorm:"'group_name'" json:"group"` | ||||||
|  | 	Homepage         OverridableField[string]   `sh:"homepage" xorm:"'homepage'" json:"homepage"` | ||||||
|  | 	Maintainer       OverridableField[string]   `sh:"maintainer" xorm:"'maintainer'" json:"maintainer"` | ||||||
|  | 	Depends          OverridableField[[]string] `sh:"deps" xorm:"'depends'" json:"deps"` | ||||||
|  | 	BuildDepends     OverridableField[[]string] `sh:"build_deps" xorm:"'builddepends'" json:"build_deps"` | ||||||
|  | 	OptDepends       OverridableField[[]string] `sh:"opt_deps" xorm:"'optdepends'" json:"opt_deps,omitempty"` | ||||||
|  | 	Sources          OverridableField[[]string] `sh:"sources" xorm:"-" json:"sources"` | ||||||
|  | 	Checksums        OverridableField[[]string] `sh:"checksums" xorm:"-" json:"checksums,omitempty"` | ||||||
|  | 	Backup           OverridableField[[]string] `sh:"backup" xorm:"-" json:"backup"` | ||||||
|  | 	Scripts          OverridableField[Scripts]  `sh:"scripts" xorm:"-" json:"scripts,omitempty"` | ||||||
|  | 	AutoReq          OverridableField[[]string] `sh:"auto_req" xorm:"-" json:"auto_req"` | ||||||
|  | 	AutoProv         OverridableField[[]string] `sh:"auto_prov" xorm:"-" json:"auto_prov"` | ||||||
|  | 	AutoReqSkipList  OverridableField[[]string] `sh:"auto_req_skiplist" xorm:"-" json:"auto_req_skiplist,omitempty"` | ||||||
|  | 	AutoProvSkipList OverridableField[[]string] `sh:"auto_prov_skiplist" xorm:"-" json:"auto_prov_skiplist,omitempty"` | ||||||
|  |  | ||||||
|  | 	FireJailed       OverridableField[bool]              `sh:"firejailed" xorm:"-" json:"firejailed"` | ||||||
|  | 	FireJailProfiles OverridableField[map[string]string] `sh:"firejail_profiles" xorm:"-" json:"firejail_profiles,omitempty"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type Scripts struct { | ||||||
|  | 	PreInstall  string `sh:"preinstall"` | ||||||
|  | 	PostInstall string `sh:"postinstall"` | ||||||
|  | 	PreRemove   string `sh:"preremove"` | ||||||
|  | 	PostRemove  string `sh:"postremove"` | ||||||
|  | 	PreUpgrade  string `sh:"preupgrade"` | ||||||
|  | 	PostUpgrade string `sh:"postupgrade"` | ||||||
|  | 	PreTrans    string `sh:"pretrans"` | ||||||
|  | 	PostTrans   string `sh:"posttrans"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (p Package) MarshalJSONWithOptions(includeOverrides bool) ([]byte, error) { | ||||||
|  | 	// Сначала сериализуем обычным способом для получения базовой структуры | ||||||
|  | 	type PackageAlias Package | ||||||
|  | 	baseData, err := json.Marshal(PackageAlias(p)) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Десериализуем в map для модификации | ||||||
|  | 	var result map[string]json.RawMessage | ||||||
|  | 	if err := json.Unmarshal(baseData, &result); err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Теперь заменяем OverridableField поля | ||||||
|  | 	v := reflect.ValueOf(p) | ||||||
|  | 	t := reflect.TypeOf(p) | ||||||
|  |  | ||||||
|  | 	for i := 0; i < v.NumField(); i++ { | ||||||
|  | 		field := v.Field(i) | ||||||
|  | 		fieldType := t.Field(i) | ||||||
|  |  | ||||||
|  | 		jsonTag := fieldType.Tag.Get("json") | ||||||
|  | 		if jsonTag == "" || jsonTag == "-" { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		fieldName := jsonTag | ||||||
|  | 		if commaIdx := strings.Index(jsonTag, ","); commaIdx != -1 { | ||||||
|  | 			fieldName = jsonTag[:commaIdx] | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if field.Type().Name() == "OverridableField" || | ||||||
|  | 			(field.Type().Kind() == reflect.Struct && | ||||||
|  | 				strings.Contains(field.Type().String(), "OverridableField")) { | ||||||
|  |  | ||||||
|  | 			fieldPtr := field.Addr() | ||||||
|  |  | ||||||
|  | 			resolvedMethod := fieldPtr.MethodByName("Resolved") | ||||||
|  | 			if resolvedMethod.IsValid() { | ||||||
|  | 				resolved := resolvedMethod.Call(nil)[0] | ||||||
|  |  | ||||||
|  | 				fieldData := map[string]interface{}{ | ||||||
|  | 					"resolved": resolved.Interface(), | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				if includeOverrides { | ||||||
|  | 					allMethod := field.MethodByName("All") | ||||||
|  | 					if allMethod.IsValid() { | ||||||
|  | 						overrides := allMethod.Call(nil)[0] | ||||||
|  | 						if !overrides.IsNil() && overrides.Len() > 0 { | ||||||
|  | 							fieldData["overrides"] = overrides.Interface() | ||||||
|  | 						} | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				fieldJSON, err := json.Marshal(fieldData) | ||||||
|  | 				if err != nil { | ||||||
|  | 					return nil, err | ||||||
|  | 				} | ||||||
|  | 				result[fieldName] = json.RawMessage(fieldJSON) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return json.Marshal(result) | ||||||
|  | } | ||||||
							
								
								
									
										105
									
								
								pkg/alrsh/package_gen.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										105
									
								
								pkg/alrsh/package_gen.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,105 @@ | |||||||
|  | // ALR - Any Linux Repository | ||||||
|  | // Copyright (C) 2025 The ALR Authors | ||||||
|  | // | ||||||
|  | // 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/>. | ||||||
|  |  | ||||||
|  | // DO NOT EDIT MANUALLY. This file is generated. | ||||||
|  | package alrsh | ||||||
|  |  | ||||||
|  | type packageResolved struct { | ||||||
|  | 	Repository       string            `json:"repository"` | ||||||
|  | 	Name             string            `json:"name"` | ||||||
|  | 	BasePkgName      string            `json:"basepkg_name"` | ||||||
|  | 	Version          string            `json:"version"` | ||||||
|  | 	Release          int               `json:"release"` | ||||||
|  | 	Epoch            uint              `json:"epoch"` | ||||||
|  | 	Architectures    []string          `json:"architectures"` | ||||||
|  | 	Licenses         []string          `json:"license"` | ||||||
|  | 	Provides         []string          `json:"provides"` | ||||||
|  | 	Conflicts        []string          `json:"conflicts"` | ||||||
|  | 	Replaces         []string          `json:"replaces"` | ||||||
|  | 	Summary          string            `json:"summary"` | ||||||
|  | 	Description      string            `json:"description"` | ||||||
|  | 	Group            string            `json:"group"` | ||||||
|  | 	Homepage         string            `json:"homepage"` | ||||||
|  | 	Maintainer       string            `json:"maintainer"` | ||||||
|  | 	Depends          []string          `json:"deps"` | ||||||
|  | 	BuildDepends     []string          `json:"build_deps"` | ||||||
|  | 	OptDepends       []string          `json:"opt_deps,omitempty"` | ||||||
|  | 	Sources          []string          `json:"sources"` | ||||||
|  | 	Checksums        []string          `json:"checksums,omitempty"` | ||||||
|  | 	Backup           []string          `json:"backup"` | ||||||
|  | 	Scripts          Scripts           `json:"scripts,omitempty"` | ||||||
|  | 	AutoReq          []string          `json:"auto_req"` | ||||||
|  | 	AutoProv         []string          `json:"auto_prov"` | ||||||
|  | 	AutoReqSkipList  []string          `json:"auto_req_skiplist,omitempty"` | ||||||
|  | 	AutoProvSkipList []string          `json:"auto_prov_skiplist,omitempty"` | ||||||
|  | 	FireJailed       bool              `json:"firejailed"` | ||||||
|  | 	FireJailProfiles map[string]string `json:"firejail_profiles,omitempty"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func PackageToResolved(src *Package) packageResolved { | ||||||
|  | 	return packageResolved{ | ||||||
|  | 		Repository:       src.Repository, | ||||||
|  | 		Name:             src.Name, | ||||||
|  | 		BasePkgName:      src.BasePkgName, | ||||||
|  | 		Version:          src.Version, | ||||||
|  | 		Release:          src.Release, | ||||||
|  | 		Epoch:            src.Epoch, | ||||||
|  | 		Architectures:    src.Architectures, | ||||||
|  | 		Licenses:         src.Licenses, | ||||||
|  | 		Provides:         src.Provides, | ||||||
|  | 		Conflicts:        src.Conflicts, | ||||||
|  | 		Replaces:         src.Replaces, | ||||||
|  | 		Summary:          src.Summary.Resolved(), | ||||||
|  | 		Description:      src.Description.Resolved(), | ||||||
|  | 		Group:            src.Group.Resolved(), | ||||||
|  | 		Homepage:         src.Homepage.Resolved(), | ||||||
|  | 		Maintainer:       src.Maintainer.Resolved(), | ||||||
|  | 		Depends:          src.Depends.Resolved(), | ||||||
|  | 		BuildDepends:     src.BuildDepends.Resolved(), | ||||||
|  | 		OptDepends:       src.OptDepends.Resolved(), | ||||||
|  | 		Sources:          src.Sources.Resolved(), | ||||||
|  | 		Checksums:        src.Checksums.Resolved(), | ||||||
|  | 		Backup:           src.Backup.Resolved(), | ||||||
|  | 		Scripts:          src.Scripts.Resolved(), | ||||||
|  | 		AutoReq:          src.AutoReq.Resolved(), | ||||||
|  | 		AutoProv:         src.AutoProv.Resolved(), | ||||||
|  | 		AutoReqSkipList:  src.AutoReqSkipList.Resolved(), | ||||||
|  | 		AutoProvSkipList: src.AutoProvSkipList.Resolved(), | ||||||
|  | 		FireJailed:       src.FireJailed.Resolved(), | ||||||
|  | 		FireJailProfiles: src.FireJailProfiles.Resolved(), | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func ResolvePackage(pkg *Package, overrides []string) { | ||||||
|  | 	pkg.Summary.Resolve(overrides) | ||||||
|  | 	pkg.Description.Resolve(overrides) | ||||||
|  | 	pkg.Group.Resolve(overrides) | ||||||
|  | 	pkg.Homepage.Resolve(overrides) | ||||||
|  | 	pkg.Maintainer.Resolve(overrides) | ||||||
|  | 	pkg.Depends.Resolve(overrides) | ||||||
|  | 	pkg.BuildDepends.Resolve(overrides) | ||||||
|  | 	pkg.OptDepends.Resolve(overrides) | ||||||
|  | 	pkg.Sources.Resolve(overrides) | ||||||
|  | 	pkg.Checksums.Resolve(overrides) | ||||||
|  | 	pkg.Backup.Resolve(overrides) | ||||||
|  | 	pkg.Scripts.Resolve(overrides) | ||||||
|  | 	pkg.AutoReq.Resolve(overrides) | ||||||
|  | 	pkg.AutoProv.Resolve(overrides) | ||||||
|  | 	pkg.AutoReqSkipList.Resolve(overrides) | ||||||
|  | 	pkg.AutoProvSkipList.Resolve(overrides) | ||||||
|  | 	pkg.FireJailed.Resolve(overrides) | ||||||
|  | 	pkg.FireJailProfiles.Resolve(overrides) | ||||||
|  | } | ||||||
							
								
								
									
										57
									
								
								pkg/alrsh/read.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								pkg/alrsh/read.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,57 @@ | |||||||
|  | // ALR - Any Linux Repository | ||||||
|  | // Copyright (C) 2025 The ALR Authors | ||||||
|  | // | ||||||
|  | // 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 alrsh | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"io" | ||||||
|  | 	"io/fs" | ||||||
|  | 	"os" | ||||||
|  |  | ||||||
|  | 	"mvdan.cc/sh/v3/syntax" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type localFs struct{} | ||||||
|  |  | ||||||
|  | func (fs *localFs) Open(name string) (fs.File, error) { | ||||||
|  | 	return os.Open(name) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func ReadFromIOReader(r io.Reader, script string) (*ScriptFile, error) { | ||||||
|  | 	file, err := syntax.NewParser().Parse(r, "alr.sh") | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	return &ScriptFile{ | ||||||
|  | 		file: file, | ||||||
|  | 		path: script, | ||||||
|  | 	}, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func ReadFromFS(fsys fs.FS, script string) (*ScriptFile, error) { | ||||||
|  | 	fl, err := fsys.Open(script) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, fmt.Errorf("failed to open alr.sh: %w", err) | ||||||
|  | 	} | ||||||
|  | 	defer fl.Close() | ||||||
|  |  | ||||||
|  | 	return ReadFromIOReader(fl, script) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func ReadFromLocal(script string) (*ScriptFile, error) { | ||||||
|  | 	return ReadFromFS(&localFs{}, script) | ||||||
|  | } | ||||||
							
								
								
									
										37
									
								
								pkg/alrsh/view.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								pkg/alrsh/view.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,37 @@ | |||||||
|  | // ALR - Any Linux Repository | ||||||
|  | // Copyright (C) 2025 The ALR Authors | ||||||
|  | // | ||||||
|  | // 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 alrsh | ||||||
|  |  | ||||||
|  | import "encoding/json" | ||||||
|  |  | ||||||
|  | type PackageView struct { | ||||||
|  | 	pkg Package | ||||||
|  |  | ||||||
|  | 	Resolved bool | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func NewPackageView(v Package) PackageView { | ||||||
|  | 	return PackageView{pkg: v} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (p PackageView) MarshalJSON() ([]byte, error) { | ||||||
|  | 	if p.Resolved { | ||||||
|  | 		return json.Marshal(PackageToResolved(&p.pkg)) | ||||||
|  | 	} else { | ||||||
|  | 		return json.Marshal(p.pkg) | ||||||
|  | 	} | ||||||
|  | } | ||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user