Compare commits
	
		
			33 Commits
		
	
	
		
			v0.0.6
			...
			be7323abbc
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | be7323abbc | ||
| 9cf8af08ab | |||
| 86940e8962 | |||
| db244204c7 | |||
| 9cb0a5e9ad | |||
| 1a57ccdb83 | |||
| 615cd83fb7 | |||
| 27e2f54653 | |||
| af57165c89 | |||
| 3770c82240 | |||
| 2dff463303 | |||
| 9085e38454 | |||
| a7d016abc9 | |||
| 4a5cca2d0f | |||
| 71000fd3cd | |||
| 71968bbe13 | |||
| 29c1a31066 | |||
| 8f94b61a0e | |||
| ae8e2d2807 | |||
| 0fa288b8a2 | |||
| dcac0b9ee5 | |||
| 4e6e1f524a | |||
| 47a9b9a96c | |||
| 9bb14312bd | |||
| 88b8d2fbf3 | |||
| 04523775f1 | |||
| adc4a42800 | |||
| 81651af20d | |||
| f04ebbaf14 | |||
| be1a137eab | |||
| 719a5b7fe7 | |||
| e05bb07f23 | |||
| ec053f7e6a | 
| @@ -47,4 +47,4 @@ issues: | |||||||
|     # TODO: remove |     # TODO: remove | ||||||
|     - linters: |     - linters: | ||||||
|         - staticcheck |         - staticcheck | ||||||
|       text: "SA1019:" |       text: "SA1019: interp.ExecHandler" | ||||||
							
								
								
									
										3
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										3
									
								
								Makefile
									
									
									
									
									
								
							| @@ -21,7 +21,7 @@ build: check-no-root $(BIN) | |||||||
|  |  | ||||||
| export CGO_ENABLED := 0 | export CGO_ENABLED := 0 | ||||||
| $(BIN): | $(BIN): | ||||||
| 	go build -ldflags="-X 'gitea.plemya-x.ru/xpamych/ALR/internal/config.Version=$(GIT_VERSION)'" -o $@ | 	go build -ldflags="-X 'gitea.plemya-x.ru/Plemya-x/ALR/internal/config.Version=$(GIT_VERSION)'" -o $@ | ||||||
|  |  | ||||||
| check-no-root: | check-no-root: | ||||||
| 	@if [[ "$$(whoami)" == 'root' ]]; then \ | 	@if [[ "$$(whoami)" == 'root' ]]; then \ | ||||||
| @@ -66,6 +66,7 @@ i18n: | |||||||
| 	$(XGOTEXT_BIN)  --output ./internal/translations/default.pot | 	$(XGOTEXT_BIN)  --output ./internal/translations/default.pot | ||||||
| 	msguniq --use-first -o ./internal/translations/default.pot ./internal/translations/default.pot | 	msguniq --use-first -o ./internal/translations/default.pot ./internal/translations/default.pot | ||||||
| 	msgmerge --backup=off -U ./internal/translations/po/ru/default.po ./internal/translations/default.pot | 	msgmerge --backup=off -U ./internal/translations/po/ru/default.po ./internal/translations/default.pot | ||||||
|  | 	bash scripts/i18n-badge.sh | ||||||
|  |  | ||||||
| test-coverage: | test-coverage: | ||||||
| 	go test ./... -v -coverpkg=./... -coverprofile=coverage.out | 	go test ./... -v -coverpkg=./... -coverprofile=coverage.out | ||||||
|   | |||||||
							
								
								
									
										24
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										24
									
								
								README.md
									
									
									
									
									
								
							| @@ -3,13 +3,13 @@ | |||||||
| </p> | </p> | ||||||
| <b></b> | <b></b> | ||||||
|  |  | ||||||
| [](https://goreportcard.com/report/gitea.plemya-x.ru/Plemya-x/ALR)  | [](https://goreportcard.com/report/gitea.plemya-x.ru/Plemya-x/ALR)   | ||||||
|  |  | ||||||
| # ALR (Any Linux Repository) | # ALR (Any Linux Repository) | ||||||
|  |  | ||||||
| ALR - это независимая от дистрибутива система сборки для Linux, аналогичная [AUR](https://wiki.archlinux.org/title/Arch_User_Repository). В настоящее время она находится в стадии бета-тестирования. Исправлено большинство основных ошибок и добавлено большинство важных функций. alr готов к общему использованию, но все еще может время от времени ломаться или заменяться. | ALR - это независимая от дистрибутива система сборки для Linux (форк [LURE](https://github.com/lure-sh/lure), аналогичная [AUR](https://wiki.archlinux.org/title/Arch_User_Repository). В настоящее время она находится в стадии бета-тестирования. Исправлено большинство основных ошибок и добавлено большинство важных функций. ALR готов к общему использованию, но все еще может время от времени ломаться или изменяться. | ||||||
|  |  | ||||||
| ALR написан на чистом Go и после сборки не имеет зависимостей. Единственное, для повышения привилегий ALR требуется команда, такая как `sudo`, `doas` и т.д., а также поддерживаемый менеджер пакетов. В настоящее время ALR поддерживает `apt`, `pacman`, `apk`, `dnf`, `yum`, and `zypper`. Если в вашей системе существует поддерживаемый менеджер пакетов, он будет обнаружен и использован автоматически. | ALR написан на чистом Go и после сборки не имеет зависимостей. Для повышения привилегий ALR требуется команда, такая как `sudo`, `doas` и т.д., а также поддерживаемый менеджер пакетов. В настоящее время ALR поддерживает `apt`, `apt-get` `pacman`, `apk`, `dnf`, `yum`, and `zypper`. Если в вашей системе используется поддерживаемый менеджер пакетов, то он будет обнаружен и использован автоматически. | ||||||
|  |  | ||||||
| --- | --- | ||||||
|  |  | ||||||
| @@ -23,14 +23,14 @@ ALR написан на чистом Go и после сборки не имее | |||||||
| curl -fsSL plemya-x.ru/alr/install.sh | bash | curl -fsSL plemya-x.ru/alr/install.sh | bash | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| **ВАЖНО**: При этом скрипт будет загружен и запущен с <https://gitea.plemya-x.ru/Plemya-x/ALR/src/branch/master/scripts/install.sh>. Пожалуйста, просматривайте любые скрипты, которые вы скачиваете из Интернета (включая этот), прежде чем запускать их. | **ВАЖНО**: При этом скрипт будет загружен и запущен с <https://plemya-x.ru/alr/install.sh>. Пожалуйста, просматривайте любые скрипты, которые вы скачиваете из Интернета (включая этот), прежде чем запускать их. | ||||||
|  |  | ||||||
| ### Сборка из исходного кода | ### Сборка из исходного кода | ||||||
|  |  | ||||||
| Чтобы собрать ALR из исходного кода, вам понадобится версия Go 1.18 или новее. Как только Go будет установлен, клонируйте это репозиторий и запустите: | Чтобы собрать ALR из исходного кода, вам понадобится версия Go 1.18 или новее. Как только Go будет установлен, клонируйте это репозиторий и запустите: | ||||||
|  |  | ||||||
| ```shell | ```shell | ||||||
| make build | make build -B | ||||||
| sudo make install | sudo make install | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| @@ -44,20 +44,23 @@ ALR был создан потому, что упаковка программн | |||||||
|  |  | ||||||
| ## Документация | ## Документация | ||||||
|  |  | ||||||
| Документация по всем этим вопросам находится в [Wiki](https://gitea.plemya-x.ru/xpamych/ALR/wiki/Home). | Документация находится в [Wiki](https://disc.plemya-x.ru/c/alr/wiki-alr). | ||||||
|  |  | ||||||
| --- | --- | ||||||
|  |  | ||||||
| ## Репозитории | ## Репозитории | ||||||
|  |  | ||||||
| Репозитории alr - это git-хранилища, которые содержат каталог для каждого пакета с файлом `alr.sh` внутри. Файл `alr.sh` содержит все инструкции по сборке пакета и информацию о нем. Скрипты `alr.sh` аналогичны скриптам Aur PKGBUILD. Репозиторий [по-умолчанию](https://gitea.plemya-x.ru/xpamych/xpamych-alr-repo.git). | Репозитории alr - это git-хранилища, которые содержат каталог для каждого пакета с файлом `alr.sh` внутри. Файл `alr.sh` содержит все инструкции по сборке пакета и информацию о нем. Скрипты `alr.sh` аналогичны скриптам Aur PKGBUILD.  | ||||||
|  |  | ||||||
|  | Например, репозиторий [Plemya-x/alr-repo](https://gitea.plemya-x.ru/Plemya-x/alr-repo.git) можно подключить так: | ||||||
|  | ``` | ||||||
|  | alr addrepo --name alr-repo --url https://gitea.plemya-x.ru/Plemya-x/alr-repo.git | ||||||
|  | ``` | ||||||
|  |  | ||||||
| --- | --- | ||||||
| ## Соцсети | ## Соцсети | ||||||
| VK - https://vk.com/plemya_kh | VK - https://vk.com/plemya_kh | ||||||
|  |  | ||||||
| Discord - https://discord.com/channels/817759634105827358/1261631565084233749 |  | ||||||
|  |  | ||||||
| Telegram - https://t.me/plemyakh | Telegram - https://t.me/plemyakh | ||||||
|  |  | ||||||
| ## Спасибы | ## Спасибы | ||||||
| @@ -70,3 +73,6 @@ Telegram - https://t.me/plemyakh | |||||||
| - <https://github.com/goreleaser/nfpm> | - <https://github.com/goreleaser/nfpm> | ||||||
| - <https://github.com/charmbracelet/bubbletea> | - <https://github.com/charmbracelet/bubbletea> | ||||||
| - <https://gitlab.com/cznic/sqlite> | - <https://gitlab.com/cznic/sqlite> | ||||||
|  |  | ||||||
|  | Благодарим за активное участие в развитии проекта: | ||||||
|  | - Maks1mS <maxim@slipenko.com> | ||||||
| @@ -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">19.2%</text> |         <text x="86" y="15" fill="#010101" fill-opacity=".3">19.8%</text> | ||||||
|         <text x="86" y="14">19.2%</text> |         <text x="86" y="14">19.8%</text> | ||||||
|     </g> |     </g> | ||||||
| </svg> | </svg> | ||||||
| Before Width: | Height: | Size: 926 B After Width: | Height: | Size: 926 B | 
							
								
								
									
										18
									
								
								assets/i18n-ru-badge.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								assets/i18n-ru-badge.svg
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | |||||||
|  | <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="129" height="20"> | ||||||
|  |     <linearGradient id="smooth" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/> | ||||||
|  |     <stop offset="1" stop-opacity=".1"/></linearGradient> | ||||||
|  |     <mask id="round"> | ||||||
|  |         <rect width="129" height="20" rx="3" fill="#fff"/> | ||||||
|  |     </mask> | ||||||
|  |     <g mask="url(#round)"> | ||||||
|  |         <rect width="75" height="20" fill="#555"/> | ||||||
|  |         <rect x="75" width="64" height="20" fill="#4c1"/> | ||||||
|  |         <rect width="129" height="20" fill="url(#smooth)"/> | ||||||
|  |     </g> | ||||||
|  |     <g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11"> | ||||||
|  |         <text x="37" y="15" fill="#010101" fill-opacity=".3">ru translate</text> | ||||||
|  |         <text x="37" y="14">ru translate</text> | ||||||
|  |         <text x="100" y="15" fill="#010101" fill-opacity=".3">100.00%</text> | ||||||
|  |         <text x="100" y="14">100.00%</text> | ||||||
|  |     </g> | ||||||
|  | </svg> | ||||||
| After Width: | Height: | Size: 942 B | 
							
								
								
									
										83
									
								
								build.go
									
									
									
									
									
								
							
							
						
						
									
										83
									
								
								build.go
									
									
									
									
									
								
							| @@ -23,14 +23,17 @@ import ( | |||||||
| 	"log/slog" | 	"log/slog" | ||||||
| 	"os" | 	"os" | ||||||
| 	"path/filepath" | 	"path/filepath" | ||||||
|  | 	"strings" | ||||||
|  |  | ||||||
| 	"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/config" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/config" | ||||||
|  | 	database "gitea.plemya-x.ru/Plemya-x/ALR/internal/db" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/osutils" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/osutils" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/types" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/types" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/build" | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/build" | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager" | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/repos" | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/repos" | ||||||
| ) | ) | ||||||
| @@ -46,6 +49,11 @@ func BuildCmd() *cli.Command { | |||||||
| 				Value:   "alr.sh", | 				Value:   "alr.sh", | ||||||
| 				Usage:   gotext.Get("Path to the build script"), | 				Usage:   gotext.Get("Path to the build script"), | ||||||
| 			}, | 			}, | ||||||
|  | 			&cli.StringFlag{ | ||||||
|  | 				Name:    "subpackage", | ||||||
|  | 				Aliases: []string{"sb"}, | ||||||
|  | 				Usage:   gotext.Get("Specify subpackage in script (for multi package script only)"), | ||||||
|  | 			}, | ||||||
| 			&cli.StringFlag{ | 			&cli.StringFlag{ | ||||||
| 				Name:    "package", | 				Name:    "package", | ||||||
| 				Aliases: []string{"p"}, | 				Aliases: []string{"p"}, | ||||||
| @@ -59,30 +67,59 @@ func BuildCmd() *cli.Command { | |||||||
| 		}, | 		}, | ||||||
| 		Action: func(c *cli.Context) error { | 		Action: func(c *cli.Context) error { | ||||||
| 			ctx := c.Context | 			ctx := c.Context | ||||||
|  | 			cfg := config.New() | ||||||
|  | 			db := database.New(cfg) | ||||||
|  | 			rs := repos.New(cfg, db) | ||||||
|  | 			err := db.Init(ctx) | ||||||
|  | 			if err != nil { | ||||||
|  | 				slog.Error(gotext.Get("Error initialization database"), "err", err) | ||||||
|  | 				os.Exit(1) | ||||||
|  | 			} | ||||||
|  |  | ||||||
| 			var script string | 			var script string | ||||||
|  | 			var packages []string | ||||||
|  | 			repository := "default" | ||||||
|  |  | ||||||
| 			// Проверяем, установлен ли флаг script (-s) | 			repoDir := cfg.GetPaths(ctx).RepoDir | ||||||
|  |  | ||||||
| 			switch { | 			switch { | ||||||
| 			case c.IsSet("script"): | 			case c.IsSet("script"): | ||||||
| 				script = c.String("script") | 				script = c.String("script") | ||||||
|  | 				packages = append(packages, c.String("script-package")) | ||||||
| 			case c.IsSet("package"): | 			case c.IsSet("package"): | ||||||
|  | 				// TODO: handle multiple packages | ||||||
| 				packageInput := c.String("package") | 				packageInput := c.String("package") | ||||||
| 				if filepath.Dir(packageInput) == "." { |  | ||||||
| 					// Не указана директория репозитория, используем 'default' как префикс | 				arr := strings.Split(packageInput, "/") | ||||||
| 					script = filepath.Join(config.GetPaths(ctx).RepoDir, "default", packageInput, "alr.sh") | 				var packageSearch string | ||||||
|  | 				if len(arr) == 2 { | ||||||
|  | 					packageSearch = arr[1] | ||||||
| 				} else { | 				} else { | ||||||
| 					// Используем путь с указанным репозиторием | 					packageSearch = arr[0] | ||||||
| 					script = filepath.Join(config.GetPaths(ctx).RepoDir, packageInput, "alr.sh") | 				} | ||||||
|  |  | ||||||
|  | 				pkgs, _, _ := rs.FindPkgs(ctx, []string{packageSearch}) | ||||||
|  | 				pkg, ok := pkgs[packageSearch] | ||||||
|  | 				if len(pkg) < 1 || !ok { | ||||||
|  | 					slog.Error(gotext.Get("Package not found")) | ||||||
|  | 					os.Exit(1) | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				repository = pkg[0].Repository | ||||||
|  |  | ||||||
|  | 				if pkg[0].BasePkgName != "" { | ||||||
|  | 					script = filepath.Join(repoDir, repository, pkg[0].BasePkgName, "alr.sh") | ||||||
|  | 					packages = append(packages, pkg[0].Name) | ||||||
|  | 				} else { | ||||||
|  | 					script = filepath.Join(repoDir, repository, pkg[0].Name, "alr.sh") | ||||||
| 				} | 				} | ||||||
| 			default: | 			default: | ||||||
| 				script = filepath.Join(config.GetPaths(ctx).RepoDir, "alr.sh") | 				script = filepath.Join(repoDir, "alr.sh") | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			// Проверка автоматического пулла репозиториев | 			// Проверка автоматического пулла репозиториев | ||||||
| 			if config.GetInstance(ctx).AutoPull(ctx) { | 			if cfg.AutoPull(ctx) { | ||||||
| 				err := repos.Pull(ctx, config.Config(ctx).Repos) | 				err := rs.Pull(ctx, cfg.Repos(ctx)) | ||||||
| 				if err != nil { | 				if err != nil { | ||||||
| 					slog.Error(gotext.Get("Error pulling repositories"), "err", err) | 					slog.Error(gotext.Get("Error pulling repositories"), "err", err) | ||||||
| 					os.Exit(1) | 					os.Exit(1) | ||||||
| @@ -96,13 +133,29 @@ func BuildCmd() *cli.Command { | |||||||
| 				os.Exit(1) | 				os.Exit(1) | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
|  | 			info, err := distro.ParseOSRelease(ctx) | ||||||
|  | 			if err != nil { | ||||||
|  | 				slog.Error(gotext.Get("Error parsing os release"), "err", err) | ||||||
|  | 				os.Exit(1) | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			builder := build.NewBuilder( | ||||||
|  | 				ctx, | ||||||
|  | 				types.BuildOpts{ | ||||||
|  | 					Packages:    packages, | ||||||
|  | 					Repository:  repository, | ||||||
|  | 					Script:      script, | ||||||
|  | 					Manager:     mgr, | ||||||
|  | 					Clean:       c.Bool("clean"), | ||||||
|  | 					Interactive: c.Bool("interactive"), | ||||||
|  | 				}, | ||||||
|  | 				rs, | ||||||
|  | 				info, | ||||||
|  | 				cfg, | ||||||
|  | 			) | ||||||
|  |  | ||||||
| 			// Сборка пакета | 			// Сборка пакета | ||||||
| 			pkgPaths, _, err := build.BuildPackage(ctx, types.BuildOpts{ | 			pkgPaths, _, err := builder.BuildPackage(ctx) | ||||||
| 				Script:      script, |  | ||||||
| 				Manager:     mgr, |  | ||||||
| 				Clean:       c.Bool("clean"), |  | ||||||
| 				Interactive: c.Bool("interactive"), |  | ||||||
| 			}) |  | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				slog.Error(gotext.Get("Error building package"), "err", err) | 				slog.Error(gotext.Get("Error building package"), "err", err) | ||||||
| 				os.Exit(1) | 				os.Exit(1) | ||||||
|   | |||||||
							
								
								
									
										17
									
								
								fix.go
									
									
									
									
									
								
							
							
						
						
									
										17
									
								
								fix.go
									
									
									
									
									
								
							| @@ -27,7 +27,7 @@ import ( | |||||||
| 	"github.com/urfave/cli/v2" | 	"github.com/urfave/cli/v2" | ||||||
|  |  | ||||||
| 	"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" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/repos" | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/repos" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| @@ -37,9 +37,8 @@ func FixCmd() *cli.Command { | |||||||
| 		Usage: gotext.Get("Attempt to fix problems with ALR"), | 		Usage: gotext.Get("Attempt to fix problems with ALR"), | ||||||
| 		Action: func(c *cli.Context) error { | 		Action: func(c *cli.Context) error { | ||||||
| 			ctx := c.Context | 			ctx := c.Context | ||||||
|  | 			cfg := config.New() | ||||||
| 			db.Close() | 			paths := cfg.GetPaths(ctx) | ||||||
| 			paths := config.GetPaths(ctx) |  | ||||||
|  |  | ||||||
| 			slog.Info(gotext.Get("Removing cache directory")) | 			slog.Info(gotext.Get("Removing cache directory")) | ||||||
|  |  | ||||||
| @@ -57,7 +56,15 @@ func FixCmd() *cli.Command { | |||||||
| 				os.Exit(1) | 				os.Exit(1) | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			err = repos.Pull(ctx, config.Config(ctx).Repos) | 			cfg = config.New() | ||||||
|  | 			db := database.New(cfg) | ||||||
|  | 			err = db.Init(ctx) | ||||||
|  | 			if err != nil { | ||||||
|  | 				slog.Error(gotext.Get("Error initialization database"), "err", err) | ||||||
|  | 				os.Exit(1) | ||||||
|  | 			} | ||||||
|  | 			rs := repos.New(cfg, db) | ||||||
|  | 			err = rs.Pull(ctx, cfg.Repos(ctx)) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				slog.Error(gotext.Get("Error pulling repos"), "err", err) | 				slog.Error(gotext.Get("Error pulling repos"), "err", err) | ||||||
| 				os.Exit(1) | 				os.Exit(1) | ||||||
|   | |||||||
							
								
								
									
										41
									
								
								info.go
									
									
									
									
									
								
							
							
						
						
									
										41
									
								
								info.go
									
									
									
									
									
								
							| @@ -24,13 +24,14 @@ import ( | |||||||
| 	"log/slog" | 	"log/slog" | ||||||
| 	"os" | 	"os" | ||||||
|  |  | ||||||
|  | 	"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" | 	"gopkg.in/yaml.v3" | ||||||
|  |  | ||||||
| 	"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" | 	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/pkg/distro" | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/repos" | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/repos" | ||||||
| @@ -47,11 +48,39 @@ func InfoCmd() *cli.Command { | |||||||
| 				Usage:   gotext.Get("Show all information, not just for the current distro"), | 				Usage:   gotext.Get("Show all information, not just for the current distro"), | ||||||
| 			}, | 			}, | ||||||
| 		}, | 		}, | ||||||
|  | 		BashComplete: func(c *cli.Context) { | ||||||
|  | 			ctx := c.Context | ||||||
|  | 			cfg := config.New() | ||||||
|  | 			db := database.New(cfg) | ||||||
|  | 			err := db.Init(ctx) | ||||||
|  | 			if err != nil { | ||||||
|  | 				slog.Error(gotext.Get("Error initialization database"), "err", err) | ||||||
|  | 				os.Exit(1) | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			result, err := db.GetPkgs(c.Context, "true") | ||||||
|  | 			if err != nil { | ||||||
|  | 				slog.Error(gotext.Get("Error getting packages"), "err", err) | ||||||
|  | 				os.Exit(1) | ||||||
|  | 			} | ||||||
|  | 			defer result.Close() | ||||||
|  |  | ||||||
|  | 			for result.Next() { | ||||||
|  | 				var pkg database.Package | ||||||
|  | 				err = result.StructScan(&pkg) | ||||||
|  | 				if err != nil { | ||||||
|  | 					slog.Error(gotext.Get("Error iterating over packages"), "err", err) | ||||||
|  | 					os.Exit(1) | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				fmt.Println(pkg.Name) | ||||||
|  | 			} | ||||||
|  | 		}, | ||||||
| 		Action: func(c *cli.Context) error { | 		Action: func(c *cli.Context) error { | ||||||
| 			ctx := c.Context | 			ctx := c.Context | ||||||
|  |  | ||||||
| 			cfg := config.New() | 			cfg := config.New() | ||||||
| 			db := db.New(cfg) | 			db := database.New(cfg) | ||||||
| 			err := db.Init(ctx) | 			err := db.Init(ctx) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				slog.Error(gotext.Get("Error initialization database"), "err", err) | 				slog.Error(gotext.Get("Error initialization database"), "err", err) | ||||||
| @@ -88,6 +117,12 @@ func InfoCmd() *cli.Command { | |||||||
| 			var names []string | 			var names []string | ||||||
| 			all := c.Bool("all") | 			all := c.Bool("all") | ||||||
|  |  | ||||||
|  | 			systemLang, err := locale.GetLanguage() | ||||||
|  | 			if err != nil { | ||||||
|  | 				slog.Error("Can't detect system language", "err", err) | ||||||
|  | 				os.Exit(1) | ||||||
|  | 			} | ||||||
|  |  | ||||||
| 			if !all { | 			if !all { | ||||||
| 				info, err := distro.ParseOSRelease(ctx) | 				info, err := distro.ParseOSRelease(ctx) | ||||||
| 				if err != nil { | 				if err != nil { | ||||||
| @@ -97,7 +132,7 @@ func InfoCmd() *cli.Command { | |||||||
| 				names, err = overrides.Resolve( | 				names, err = overrides.Resolve( | ||||||
| 					info, | 					info, | ||||||
| 					overrides.DefaultOpts. | 					overrides.DefaultOpts. | ||||||
| 						WithLanguages([]string{config.SystemLang()}), | 						WithLanguages([]string{systemLang}), | ||||||
| 				) | 				) | ||||||
| 				if err != nil { | 				if err != nil { | ||||||
| 					slog.Error(gotext.Get("Error resolving overrides"), "err", err) | 					slog.Error(gotext.Get("Error resolving overrides"), "err", err) | ||||||
|   | |||||||
							
								
								
									
										52
									
								
								install.go
									
									
									
									
									
								
							
							
						
						
									
										52
									
								
								install.go
									
									
									
									
									
								
							| @@ -29,9 +29,10 @@ 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" | 	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/types" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/build" | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/build" | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager" | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/repos" | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/repos" | ||||||
| ) | ) | ||||||
| @@ -45,7 +46,7 @@ func InstallCmd() *cli.Command { | |||||||
| 			&cli.BoolFlag{ | 			&cli.BoolFlag{ | ||||||
| 				Name:    "clean", | 				Name:    "clean", | ||||||
| 				Aliases: []string{"c"}, | 				Aliases: []string{"c"}, | ||||||
| 				Usage:   "Build package from scratch even if there's an already built package available", | 				Usage:   gotext.Get("Build package from scratch even if there's an already built package available"), | ||||||
| 			}, | 			}, | ||||||
| 		}, | 		}, | ||||||
| 		Action: func(c *cli.Context) error { | 		Action: func(c *cli.Context) error { | ||||||
| @@ -63,22 +64,52 @@ func InstallCmd() *cli.Command { | |||||||
| 				os.Exit(1) | 				os.Exit(1) | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			if config.GetInstance(ctx).AutoPull(ctx) { | 			cfg := config.New() | ||||||
| 				err := repos.Pull(ctx, config.Config(ctx).Repos) | 			db := database.New(cfg) | ||||||
|  | 			rs := repos.New(cfg, db) | ||||||
|  | 			err := db.Init(ctx) | ||||||
|  | 			if err != nil { | ||||||
|  | 				slog.Error(gotext.Get("Error initialization database"), "err", err) | ||||||
|  | 				os.Exit(1) | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			if cfg.AutoPull(ctx) { | ||||||
|  | 				err := rs.Pull(ctx, cfg.Repos(ctx)) | ||||||
| 				if err != nil { | 				if err != nil { | ||||||
| 					slog.Error(gotext.Get("Error pulling repositories"), "err", err) | 					slog.Error(gotext.Get("Error pulling repositories"), "err", err) | ||||||
| 					os.Exit(1) | 					os.Exit(1) | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			found, notFound, err := repos.FindPkgs(ctx, args.Slice()) | 			found, notFound, err := rs.FindPkgs(ctx, args.Slice()) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				slog.Error(gotext.Get("Error finding packages"), "err", err) | 				slog.Error(gotext.Get("Error finding packages"), "err", err) | ||||||
| 				os.Exit(1) | 				os.Exit(1) | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			pkgs := cliutils.FlattenPkgs(ctx, found, "install", c.Bool("interactive")) | 			pkgs := cliutils.FlattenPkgs(ctx, found, "install", c.Bool("interactive")) | ||||||
| 			build.InstallPkgs(ctx, pkgs, notFound, types.BuildOpts{ |  | ||||||
|  | 			opts := types.BuildOpts{ | ||||||
|  | 				Manager:     mgr, | ||||||
|  | 				Clean:       c.Bool("clean"), | ||||||
|  | 				Interactive: c.Bool("interactive"), | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			info, err := distro.ParseOSRelease(ctx) | ||||||
|  | 			if err != nil { | ||||||
|  | 				slog.Error(gotext.Get("Error parsing os release"), "err", err) | ||||||
|  | 				os.Exit(1) | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			builder := build.NewBuilder( | ||||||
|  | 				ctx, | ||||||
|  | 				opts, | ||||||
|  | 				rs, | ||||||
|  | 				info, | ||||||
|  | 				cfg, | ||||||
|  | 			) | ||||||
|  |  | ||||||
|  | 			builder.InstallPkgs(ctx, pkgs, notFound, types.BuildOpts{ | ||||||
| 				Manager:     mgr, | 				Manager:     mgr, | ||||||
| 				Clean:       c.Bool("clean"), | 				Clean:       c.Bool("clean"), | ||||||
| 				Interactive: c.Bool("interactive"), | 				Interactive: c.Bool("interactive"), | ||||||
| @@ -86,6 +117,13 @@ func InstallCmd() *cli.Command { | |||||||
| 			return nil | 			return nil | ||||||
| 		}, | 		}, | ||||||
| 		BashComplete: func(c *cli.Context) { | 		BashComplete: func(c *cli.Context) { | ||||||
|  | 			cfg := config.New() | ||||||
|  | 			db := database.New(cfg) | ||||||
|  | 			err := db.Init(c.Context) | ||||||
|  | 			if err != nil { | ||||||
|  | 				slog.Error(gotext.Get("Error initialization database"), "err", err) | ||||||
|  | 				os.Exit(1) | ||||||
|  | 			} | ||||||
| 			result, err := db.GetPkgs(c.Context, "true") | 			result, err := db.GetPkgs(c.Context, "true") | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				slog.Error(gotext.Get("Error getting packages"), "err", err) | 				slog.Error(gotext.Get("Error getting packages"), "err", err) | ||||||
| @@ -94,7 +132,7 @@ func InstallCmd() *cli.Command { | |||||||
| 			defer result.Close() | 			defer result.Close() | ||||||
|  |  | ||||||
| 			for result.Next() { | 			for result.Next() { | ||||||
| 				var pkg db.Package | 				var pkg database.Package | ||||||
| 				err = result.StructScan(&pkg) | 				err = result.StructScan(&pkg) | ||||||
| 				if err != nil { | 				if err != nil { | ||||||
| 					slog.Error(gotext.Get("Error iterating over packages"), "err", err) | 					slog.Error(gotext.Get("Error iterating over packages"), "err", err) | ||||||
|   | |||||||
							
								
								
									
										102
									
								
								internal/cliutils/template.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										102
									
								
								internal/cliutils/template.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,102 @@ | |||||||
|  | // ALR - Any Linux Repository | ||||||
|  | // Copyright (C) 2025 Евгений Храмов | ||||||
|  | // | ||||||
|  | // This program is free software: you can redistribute it and/or modify | ||||||
|  | // it under the terms of the GNU General Public License as published by | ||||||
|  | // the Free Software Foundation, either version 3 of the License, or | ||||||
|  | // (at your option) any later version. | ||||||
|  | // | ||||||
|  | // This program is distributed in the hope that it will be useful, | ||||||
|  | // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | // GNU General Public License for more details. | ||||||
|  | // | ||||||
|  | // You should have received a copy of the GNU General Public License | ||||||
|  | // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  |  | ||||||
|  | package cliutils | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  |  | ||||||
|  | 	"github.com/leonelquinteros/gotext" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // Templates are based on https://github.com/urfave/cli/blob/3b17080d70a630feadadd23dd036cad121dd9a50/template.go | ||||||
|  |  | ||||||
|  | //nolint:unused | ||||||
|  | var ( | ||||||
|  | 	helpNameTemplate    = `{{$v := offset .HelpName 6}}{{wrap .HelpName 3}}{{if .Usage}} - {{wrap .Usage $v}}{{end}}` | ||||||
|  | 	descriptionTemplate = `{{wrap .Description 3}}` | ||||||
|  | 	authorsTemplate     = `{{with $length := len .Authors}}{{if ne 1 $length}}S{{end}}{{end}}: | ||||||
|  |    {{range $index, $author := .Authors}}{{if $index}} | ||||||
|  |    {{end}}{{$author}}{{end}}` | ||||||
|  | 	visibleCommandTemplate = `{{ $cv := offsetCommands .VisibleCommands 5}}{{range .VisibleCommands}} | ||||||
|  |    {{$s := join .Names ", "}}{{$s}}{{ $sp := subtract $cv (offset $s 3) }}{{ indent $sp ""}}{{wrap .Usage $cv}}{{end}}` | ||||||
|  | 	visibleCommandCategoryTemplate = `{{range .VisibleCategories}}{{if .Name}} | ||||||
|  |    {{.Name}}:{{range .VisibleCommands}} | ||||||
|  |      {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{else}}{{template "visibleCommandTemplate" .}}{{end}}{{end}}` | ||||||
|  | 	visibleFlagCategoryTemplate = `{{range .VisibleFlagCategories}} | ||||||
|  |    {{if .Name}}{{.Name}} | ||||||
|  |  | ||||||
|  |    {{end}}{{$flglen := len .Flags}}{{range $i, $e := .Flags}}{{if eq (subtract $flglen $i) 1}}{{$e}} | ||||||
|  | {{else}}{{$e}} | ||||||
|  |    {{end}}{{end}}{{end}}` | ||||||
|  | 	visibleFlagTemplate = `{{range $i, $e := .VisibleFlags}} | ||||||
|  |    {{wrap $e.String 6}}{{end}}` | ||||||
|  | 	copyrightTemplate = `{{wrap .Copyright 3}}` | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func GetAppCliTemplate() string { | ||||||
|  | 	return fmt.Sprintf(`%s: | ||||||
|  | 	{{template "helpNameTemplate" .}} | ||||||
|  |  | ||||||
|  | %s: | ||||||
|  | 	{{if .UsageText}}{{wrap .UsageText 3}}{{else}}{{.HelpName}} {{if .VisibleFlags}}[%s]{{end}}{{if .Commands}} %s [%s]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[%s...]{{end}}{{end}}{{if .Version}}{{if not .HideVersion}} | ||||||
|  |  | ||||||
|  | %s: | ||||||
|  | 	{{.Version}}{{end}}{{end}}{{if .Description}} | ||||||
|  |  | ||||||
|  | %s: | ||||||
|  |    {{template "descriptionTemplate" .}}{{end}} | ||||||
|  | {{- if len .Authors}} | ||||||
|  |  | ||||||
|  | %s{{template "authorsTemplate" .}}{{end}}{{if .VisibleCommands}} | ||||||
|  |  | ||||||
|  | %s:{{template "visibleCommandCategoryTemplate" .}}{{end}}{{if .VisibleFlagCategories}} | ||||||
|  |  | ||||||
|  | %s:{{template "visibleFlagCategoryTemplate" .}}{{else if .VisibleFlags}} | ||||||
|  |  | ||||||
|  | %s:{{template "visibleFlagTemplate" .}}{{end}}{{if .Copyright}} | ||||||
|  |  | ||||||
|  | %s: | ||||||
|  |    {{template "copyrightTemplate" .}}{{end}} | ||||||
|  | `, gotext.Get("NAME"), gotext.Get("USAGE"), gotext.Get("global options"), gotext.Get("command"), gotext.Get("command options"), gotext.Get("arguments"), gotext.Get("VERSION"), gotext.Get("DESCRIPTION"), gotext.Get("AUTHOR"), gotext.Get("COMMANDS"), gotext.Get("GLOBAL OPTIONS"), gotext.Get("GLOBAL OPTIONS"), gotext.Get("COPYRIGHT")) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func GetCommandHelpTemplate() string { | ||||||
|  | 	return fmt.Sprintf(`%s: | ||||||
|  |    {{template "helpNameTemplate" .}} | ||||||
|  |  | ||||||
|  | %s: | ||||||
|  |    {{if .UsageText}}{{wrap .UsageText 3}}{{else}}{{.HelpName}}{{if .VisibleFlags}} [%s]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[%s...]{{end}}{{end}}{{if .Category}} | ||||||
|  |  | ||||||
|  | %s: | ||||||
|  |    {{.Category}}{{end}}{{if .Description}} | ||||||
|  |  | ||||||
|  | %s: | ||||||
|  |    {{template "descriptionTemplate" .}}{{end}}{{if .VisibleFlagCategories}} | ||||||
|  |  | ||||||
|  | %s:{{template "visibleFlagCategoryTemplate" .}}{{else if .VisibleFlags}} | ||||||
|  |  | ||||||
|  | %s:{{template "visibleFlagTemplate" .}}{{end}} | ||||||
|  | `, gotext.Get("NAME"), | ||||||
|  | 		gotext.Get("USAGE"), | ||||||
|  | 		gotext.Get("command options"), | ||||||
|  | 		gotext.Get("arguments"), | ||||||
|  | 		gotext.Get("CATEGORY"), | ||||||
|  | 		gotext.Get("DESCRIPTION"), | ||||||
|  | 		gotext.Get("OPTIONS"), | ||||||
|  | 		gotext.Get("OPTIONS"), | ||||||
|  | 	) | ||||||
|  | } | ||||||
| @@ -45,13 +45,8 @@ var defaultConfig = &types.Config{ | |||||||
| 	RootCmd:          "sudo", | 	RootCmd:          "sudo", | ||||||
| 	PagerStyle:       "native", | 	PagerStyle:       "native", | ||||||
| 	IgnorePkgUpdates: []string{}, | 	IgnorePkgUpdates: []string{}, | ||||||
| 	AutoPull:         true, | 	AutoPull:         false, | ||||||
| 	Repos: []types.Repo{ | 	Repos:            []types.Repo{}, | ||||||
| 		{ |  | ||||||
| 			Name: "default", |  | ||||||
| 			URL:  "https://gitea.plemya-x.ru/xpamych/xpamych-alr-repo.git", |  | ||||||
| 		}, |  | ||||||
| 	}, |  | ||||||
| } | } | ||||||
|  |  | ||||||
| func New() *ALRConfig { | func New() *ALRConfig { | ||||||
| @@ -157,6 +152,13 @@ func (c *ALRConfig) Repos(ctx context.Context) []types.Repo { | |||||||
| 	return c.cfg.Repos | 	return c.cfg.Repos | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (c *ALRConfig) SetRepos(ctx context.Context, repos []types.Repo) { | ||||||
|  | 	c.cfgOnce.Do(func() { | ||||||
|  | 		c.Load(ctx) | ||||||
|  | 	}) | ||||||
|  | 	c.cfg.Repos = repos | ||||||
|  | } | ||||||
|  |  | ||||||
| func (c *ALRConfig) IgnorePkgUpdates(ctx context.Context) []string { | func (c *ALRConfig) IgnorePkgUpdates(ctx context.Context) []string { | ||||||
| 	c.cfgOnce.Do(func() { | 	c.cfgOnce.Do(func() { | ||||||
| 		c.Load(ctx) | 		c.Load(ctx) | ||||||
| @@ -170,3 +172,28 @@ func (c *ALRConfig) AutoPull(ctx context.Context) bool { | |||||||
| 	}) | 	}) | ||||||
| 	return c.cfg.AutoPull | 	return c.cfg.AutoPull | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (c *ALRConfig) PagerStyle(ctx context.Context) string { | ||||||
|  | 	c.cfgOnce.Do(func() { | ||||||
|  | 		c.Load(ctx) | ||||||
|  | 	}) | ||||||
|  | 	return c.cfg.PagerStyle | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *ALRConfig) AllowRunAsRoot(ctx context.Context) bool { | ||||||
|  | 	c.cfgOnce.Do(func() { | ||||||
|  | 		c.Load(ctx) | ||||||
|  | 	}) | ||||||
|  | 	return c.cfg.Unsafe.AllowRunAsRoot | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *ALRConfig) RootCmd(ctx context.Context) string { | ||||||
|  | 	c.cfgOnce.Do(func() { | ||||||
|  | 		c.Load(ctx) | ||||||
|  | 	}) | ||||||
|  | 	return c.cfg.RootCmd | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *ALRConfig) Save(f *os.File) error { | ||||||
|  | 	return toml.NewEncoder(f).Encode(c.cfg) | ||||||
|  | } | ||||||
|   | |||||||
| @@ -1,70 +0,0 @@ | |||||||
| // This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan. |  | ||||||
| // It has been modified as part of "ALR - Any Linux Repository" by Евгений Храмов. |  | ||||||
| // |  | ||||||
| // ALR - Any Linux Repository |  | ||||||
| // Copyright (C) 2025 Евгений Храмов |  | ||||||
| // |  | ||||||
| // This program is free software: you can redistribute it and/or modify |  | ||||||
| // it under the terms of the GNU General Public License as published by |  | ||||||
| // the Free Software Foundation, either version 3 of the License, or |  | ||||||
| // (at your option) any later version. |  | ||||||
| // |  | ||||||
| // This program is distributed in the hope that it will be useful, |  | ||||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of |  | ||||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the |  | ||||||
| // GNU General Public License for more details. |  | ||||||
| // |  | ||||||
| // You should have received a copy of the GNU General Public License |  | ||||||
| // along with this program.  If not, see <http://www.gnu.org/licenses/>. |  | ||||||
|  |  | ||||||
| package config |  | ||||||
|  |  | ||||||
| import ( |  | ||||||
| 	"context" |  | ||||||
| 	"log/slog" |  | ||||||
| 	"os" |  | ||||||
| 	"strings" |  | ||||||
| 	"sync" |  | ||||||
|  |  | ||||||
| 	"github.com/leonelquinteros/gotext" |  | ||||||
| 	"golang.org/x/text/language" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| var ( |  | ||||||
| 	langMtx sync.Mutex |  | ||||||
| 	lang    language.Tag |  | ||||||
| 	langSet bool |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| // Language returns the system language. |  | ||||||
| // The first time it's called, it'll detect the langauge based on |  | ||||||
| // the $LANG environment variable. |  | ||||||
| // Subsequent calls will just return the same value. |  | ||||||
| func Language(ctx context.Context) language.Tag { |  | ||||||
| 	langMtx.Lock() |  | ||||||
| 	if !langSet { |  | ||||||
| 		syslang := SystemLang() |  | ||||||
| 		tag, err := language.Parse(syslang) |  | ||||||
| 		if err != nil { |  | ||||||
| 			slog.Error(gotext.Get("Error parsing system language"), "err", err) |  | ||||||
| 			langMtx.Unlock() |  | ||||||
| 			os.Exit(1) |  | ||||||
| 		} |  | ||||||
| 		base, _ := tag.Base() |  | ||||||
| 		lang = language.Make(base.String()) |  | ||||||
| 		langSet = true |  | ||||||
| 	} |  | ||||||
| 	langMtx.Unlock() |  | ||||||
| 	return lang |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // SystemLang returns the system language based on |  | ||||||
| // the $LANG environment variable. |  | ||||||
| func SystemLang() string { |  | ||||||
| 	lang := os.Getenv("LANG") |  | ||||||
| 	lang, _, _ = strings.Cut(lang, ".") |  | ||||||
| 	if lang == "" || lang == "C" { |  | ||||||
| 		lang = "en" |  | ||||||
| 	} |  | ||||||
| 	return lang |  | ||||||
| } |  | ||||||
| @@ -19,10 +19,6 @@ | |||||||
|  |  | ||||||
| package config | package config | ||||||
|  |  | ||||||
| import ( |  | ||||||
| 	"context" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| // Paths contains various paths used by ALR | // Paths contains various paths used by ALR | ||||||
| type Paths struct { | type Paths struct { | ||||||
| 	ConfigDir  string | 	ConfigDir  string | ||||||
| @@ -32,14 +28,3 @@ type Paths struct { | |||||||
| 	PkgsDir    string | 	PkgsDir    string | ||||||
| 	DBPath     string | 	DBPath     string | ||||||
| } | } | ||||||
|  |  | ||||||
| // GetPaths returns a Paths struct. |  | ||||||
| // The first time it's called, it'll generate the struct |  | ||||||
| // using information from the system. |  | ||||||
| // Subsequent calls will return the same value. |  | ||||||
| // |  | ||||||
| // Deprecated: use struct API |  | ||||||
| func GetPaths(ctx context.Context) *Paths { |  | ||||||
| 	alrConfig := GetInstance(ctx) |  | ||||||
| 	return alrConfig.GetPaths(ctx) |  | ||||||
| } |  | ||||||
|   | |||||||
| @@ -31,10 +31,11 @@ import ( | |||||||
|  |  | ||||||
| // CurrentVersion is the current version of the database. | // CurrentVersion is the current version of the database. | ||||||
| // The database is reset if its version doesn't match this. | // The database is reset if its version doesn't match this. | ||||||
| const CurrentVersion = 2 | const CurrentVersion = 3 | ||||||
|  |  | ||||||
| // Package is a ALR package's database representation | // Package is a ALR package's database representation | ||||||
| type Package struct { | type Package struct { | ||||||
|  | 	BasePkgName   string                    `sh:"base" db:"basepkg_name"` | ||||||
| 	Name          string                    `sh:"name,required" db:"name"` | 	Name          string                    `sh:"name,required" db:"name"` | ||||||
| 	Version       string                    `sh:"version,required" db:"version"` | 	Version       string                    `sh:"version,required" db:"version"` | ||||||
| 	Release       int                       `sh:"release,required" db:"release"` | 	Release       int                       `sh:"release,required" db:"release"` | ||||||
| @@ -99,6 +100,7 @@ func (d *Database) initDB(ctx context.Context) error { | |||||||
| 	conn := d.conn | 	conn := d.conn | ||||||
| 	_, err := conn.ExecContext(ctx, ` | 	_, err := conn.ExecContext(ctx, ` | ||||||
| 		CREATE TABLE IF NOT EXISTS pkgs ( | 		CREATE TABLE IF NOT EXISTS pkgs ( | ||||||
|  | 			basepkg_name  TEXT NOT NULL, | ||||||
| 			name          TEXT NOT NULL, | 			name          TEXT NOT NULL, | ||||||
| 			repository    TEXT NOT NULL, | 			repository    TEXT NOT NULL, | ||||||
| 			version       TEXT NOT NULL, | 			version       TEXT NOT NULL, | ||||||
| @@ -196,6 +198,7 @@ func (d *Database) IsEmpty(ctx context.Context) bool { | |||||||
| func (d *Database) InsertPackage(ctx context.Context, pkg Package) error { | func (d *Database) InsertPackage(ctx context.Context, pkg Package) error { | ||||||
| 	_, err := d.conn.NamedExecContext(ctx, ` | 	_, err := d.conn.NamedExecContext(ctx, ` | ||||||
| 		INSERT OR REPLACE INTO pkgs ( | 		INSERT OR REPLACE INTO pkgs ( | ||||||
|  | 			basepkg_name, | ||||||
| 			name, | 			name, | ||||||
| 			repository, | 			repository, | ||||||
| 			version, | 			version, | ||||||
| @@ -213,6 +216,7 @@ func (d *Database) InsertPackage(ctx context.Context, pkg Package) error { | |||||||
| 			builddepends, | 			builddepends, | ||||||
| 			optdepends | 			optdepends | ||||||
| 		) VALUES ( | 		) VALUES ( | ||||||
|  | 		 	:basepkg_name, | ||||||
| 			:name, | 			:name, | ||||||
| 			:repository, | 			:repository, | ||||||
| 			:version, | 			:version, | ||||||
|   | |||||||
| @@ -1,106 +0,0 @@ | |||||||
| // ALR - Any Linux Repository |  | ||||||
| // Copyright (C) 2025 Евгений Храмов |  | ||||||
| // |  | ||||||
| // This program is free software: you can redistribute it and/or modify |  | ||||||
| // it under the terms of the GNU General Public License as published by |  | ||||||
| // the Free Software Foundation, either version 3 of the License, or |  | ||||||
| // (at your option) any later version. |  | ||||||
| // |  | ||||||
| // This program is distributed in the hope that it will be useful, |  | ||||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of |  | ||||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the |  | ||||||
| // GNU General Public License for more details. |  | ||||||
| // |  | ||||||
| // You should have received a copy of the GNU General Public License |  | ||||||
| // along with this program.  If not, see <http://www.gnu.org/licenses/>. |  | ||||||
|  |  | ||||||
| package db |  | ||||||
|  |  | ||||||
| import ( |  | ||||||
| 	"context" |  | ||||||
| 	"log/slog" |  | ||||||
| 	"os" |  | ||||||
| 	"sync" |  | ||||||
|  |  | ||||||
| 	"github.com/jmoiron/sqlx" |  | ||||||
| 	"github.com/leonelquinteros/gotext" |  | ||||||
|  |  | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/config" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| // DB returns the ALR database. |  | ||||||
| // The first time it's called, it opens the SQLite database file. |  | ||||||
| // Subsequent calls return the same connection. |  | ||||||
| // |  | ||||||
| // Deprecated: use struct method |  | ||||||
| func DB(ctx context.Context) *sqlx.DB { |  | ||||||
| 	return GetInstance(ctx).GetConn() |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Close closes the database |  | ||||||
| // |  | ||||||
| // Deprecated: use struct method |  | ||||||
| func Close() error { |  | ||||||
| 	if database != nil { |  | ||||||
| 		return database.Close() |  | ||||||
| 	} |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // IsEmpty returns true if the database has no packages in it, otherwise it returns false. |  | ||||||
| // |  | ||||||
| // Deprecated: use struct method |  | ||||||
| func IsEmpty(ctx context.Context) bool { |  | ||||||
| 	return GetInstance(ctx).IsEmpty(ctx) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // InsertPackage adds a package to the database |  | ||||||
| // |  | ||||||
| // Deprecated: use struct method |  | ||||||
| func InsertPackage(ctx context.Context, pkg Package) error { |  | ||||||
| 	return GetInstance(ctx).InsertPackage(ctx, pkg) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // GetPkgs returns a result containing packages that match the where conditions |  | ||||||
| // |  | ||||||
| // Deprecated: use struct method |  | ||||||
| func GetPkgs(ctx context.Context, where string, args ...any) (*sqlx.Rows, error) { |  | ||||||
| 	return GetInstance(ctx).GetPkgs(ctx, where, args...) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // GetPkg returns a single package that matches the where conditions |  | ||||||
| // |  | ||||||
| // Deprecated: use struct method |  | ||||||
| func GetPkg(ctx context.Context, where string, args ...any) (*Package, error) { |  | ||||||
| 	return GetInstance(ctx).GetPkg(ctx, where, args...) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // DeletePkgs deletes all packages matching the where conditions |  | ||||||
| // |  | ||||||
| // Deprecated: use struct method |  | ||||||
| func DeletePkgs(ctx context.Context, where string, args ...any) error { |  | ||||||
| 	return GetInstance(ctx).DeletePkgs(ctx, where, args...) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // ======================= |  | ||||||
| // FOR LEGACY ONLY |  | ||||||
| // ======================= |  | ||||||
|  |  | ||||||
| var ( |  | ||||||
| 	dbOnce   sync.Once |  | ||||||
| 	database *Database |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| // Deprecated: For legacy only |  | ||||||
| func GetInstance(ctx context.Context) *Database { |  | ||||||
| 	dbOnce.Do(func() { |  | ||||||
| 		cfg := config.GetInstance(ctx) |  | ||||||
| 		database = New(cfg) |  | ||||||
| 		err := database.Init(ctx) |  | ||||||
| 		if err != nil { |  | ||||||
| 			slog.Error(gotext.Get("Error opening database"), "err", err) |  | ||||||
| 			os.Exit(1) |  | ||||||
| 		} |  | ||||||
| 	}) |  | ||||||
| 	return database |  | ||||||
| } |  | ||||||
| @@ -91,7 +91,7 @@ func TestDownloadWithoutCache(t *testing.T) { | |||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			name: "git download", | 			name: "git download", | ||||||
| 			path: "git+%s/git-downloader/git/Plemya-x/xpamych-alr-repo", | 			path: "git+%s/git-downloader/git/Plemya-x/alr-repo", | ||||||
| 			expected: func(t *testing.T, err error, tmpdir string) { | 			expected: func(t *testing.T, err error, tmpdir string) { | ||||||
| 				assert.NoError(t, err) | 				assert.NoError(t, err) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -123,6 +123,7 @@ func (FileDownloader) Download(ctx context.Context, opts Options) (Type, string, | |||||||
| 	} else { | 	} else { | ||||||
| 		out = fl | 		out = fl | ||||||
| 	} | 	} | ||||||
|  | 	defer out.Close() | ||||||
|  |  | ||||||
| 	h, err := opts.NewHash() | 	h, err := opts.NewHash() | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|   | |||||||
| @@ -32,14 +32,6 @@ import ( | |||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/dlcache" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/dlcache" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func init() { |  | ||||||
| 	dir, err := os.MkdirTemp("/tmp", "alr-dlcache-test.*") |  | ||||||
| 	if err != nil { |  | ||||||
| 		panic(err) |  | ||||||
| 	} |  | ||||||
| 	config.GetPaths(context.Background()).RepoDir = dir |  | ||||||
| } |  | ||||||
|  |  | ||||||
| type TestALRConfig struct { | type TestALRConfig struct { | ||||||
| 	CacheDir string | 	CacheDir string | ||||||
| } | } | ||||||
|   | |||||||
| @@ -41,7 +41,7 @@ func init() { | |||||||
|  |  | ||||||
| 	b2 := lipgloss.RoundedBorder() | 	b2 := lipgloss.RoundedBorder() | ||||||
| 	b2.Left = "\u2524" | 	b2.Left = "\u2524" | ||||||
| 	infoStyle = titleStyle.Copy().BorderStyle(b2) | 	infoStyle = titleStyle.BorderStyle(b2) | ||||||
| } | } | ||||||
|  |  | ||||||
| type Pager struct { | type Pager struct { | ||||||
|   | |||||||
| @@ -164,7 +164,10 @@ func (d *Decoder) DecodeVars(val any) error { | |||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| type ScriptFunc func(ctx context.Context, opts ...interp.RunnerOption) error | type ( | ||||||
|  | 	ScriptFunc             func(ctx context.Context, opts ...interp.RunnerOption) error | ||||||
|  | 	ScriptFuncWithSubshell func(ctx context.Context, opts ...interp.RunnerOption) (*interp.Runner, error) | ||||||
|  | ) | ||||||
|  |  | ||||||
| // GetFunc returns a function corresponding to a bash function | // GetFunc returns a function corresponding to a bash function | ||||||
| // with the given name | // with the given name | ||||||
| @@ -197,6 +200,24 @@ func (d *Decoder) GetFuncP(name string, prepare PrepareFunc) (ScriptFunc, bool) | |||||||
| 	}, true | 	}, true | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (d *Decoder) GetFuncWithSubshell(name string) (ScriptFuncWithSubshell, bool) { | ||||||
|  | 	fn := d.getFunc(name) | ||||||
|  | 	if fn == nil { | ||||||
|  | 		return nil, false | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return func(ctx context.Context, opts ...interp.RunnerOption) (*interp.Runner, error) { | ||||||
|  | 		sub := d.Runner.Subshell() | ||||||
|  | 		for _, opt := range opts { | ||||||
|  | 			err := opt(sub) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return nil, err | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		return sub, sub.Run(ctx, fn) | ||||||
|  | 	}, true | ||||||
|  | } | ||||||
|  |  | ||||||
| func (d *Decoder) getFunc(name string) *syntax.Stmt { | func (d *Decoder) getFunc(name string) *syntax.Stmt { | ||||||
| 	names, err := overrides.Resolve(d.info, overrides.DefaultOpts.WithName(name)) | 	names, err := overrides.Resolve(d.info, overrides.DefaultOpts.WithName(name)) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|   | |||||||
| @@ -29,9 +29,9 @@ import ( | |||||||
| 	"syscall" | 	"syscall" | ||||||
| 	"time" | 	"time" | ||||||
|  |  | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/fakeroot" | ||||||
| 	"mvdan.cc/sh/v3/expand" | 	"mvdan.cc/sh/v3/expand" | ||||||
| 	"mvdan.cc/sh/v3/interp" | 	"mvdan.cc/sh/v3/interp" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/fakeroot" |  | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // FakerootExecHandler was extracted from github.com/mvdan/sh/interp/handler.go | // FakerootExecHandler was extracted from github.com/mvdan/sh/interp/handler.go | ||||||
|   | |||||||
| @@ -22,10 +22,11 @@ package handlers | |||||||
| import ( | import ( | ||||||
| 	"context" | 	"context" | ||||||
| 	"io" | 	"io" | ||||||
|  | 	"io/fs" | ||||||
| 	"os" | 	"os" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func NopReadDir(context.Context, string) ([]os.FileInfo, error) { | func NopReadDir(context.Context, string) ([]fs.DirEntry, error) { | ||||||
| 	return nil, os.ErrNotExist | 	return nil, os.ErrNotExist | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -31,12 +31,12 @@ import ( | |||||||
| 	"mvdan.cc/sh/v3/interp" | 	"mvdan.cc/sh/v3/interp" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func RestrictedReadDir(allowedPrefixes ...string) interp.ReadDirHandlerFunc { | func RestrictedReadDir(allowedPrefixes ...string) interp.ReadDirHandlerFunc2 { | ||||||
| 	return func(ctx context.Context, s string) ([]fs.FileInfo, error) { | 	return func(ctx context.Context, s string) ([]fs.DirEntry, error) { | ||||||
| 		path := filepath.Clean(s) | 		path := filepath.Clean(s) | ||||||
| 		for _, allowedPrefix := range allowedPrefixes { | 		for _, allowedPrefix := range allowedPrefixes { | ||||||
| 			if strings.HasPrefix(path, allowedPrefix) { | 			if strings.HasPrefix(path, allowedPrefix) { | ||||||
| 				return interp.DefaultReadDirHandler()(ctx, s) | 				return interp.DefaultReadDirHandler2()(ctx, s) | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -9,40 +9,56 @@ msgstr "" | |||||||
| "Content-Transfer-Encoding: 8bit\n" | "Content-Transfer-Encoding: 8bit\n" | ||||||
| "Plural-Forms: nplurals=2; plural=(n != 1);\n" | "Plural-Forms: nplurals=2; plural=(n != 1);\n" | ||||||
|  |  | ||||||
| #: build.go:41 | #: build.go:44 | ||||||
| msgid "Build a local package" | msgid "Build a local package" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: build.go:47 | #: build.go:50 | ||||||
| msgid "Path to the build script" | msgid "Path to the build script" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: build.go:52 | #: build.go:55 | ||||||
|  | msgid "Specify subpackage in script (for multi package script only)" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: build.go:60 | ||||||
| msgid "Name of the package to build and its repo (example: default/go-bin)" | msgid "Name of the package to build and its repo (example: default/go-bin)" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: build.go:57 | #: build.go:65 | ||||||
| msgid "" | msgid "" | ||||||
| "Build package from scratch even if there's an already built package available" | "Build package from scratch even if there's an already built package available" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: build.go:87 | #: build.go:75 | ||||||
|  | msgid "Error initialization database" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: build.go:104 | ||||||
|  | msgid "Package not found" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: build.go:124 | ||||||
| msgid "Error pulling repositories" | msgid "Error pulling repositories" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: build.go:95 | #: build.go:132 | ||||||
| msgid "Unable to detect a supported package manager on the system" | msgid "Unable to detect a supported package manager on the system" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: build.go:107 | #: build.go:138 | ||||||
|  | msgid "Error parsing os release" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: build.go:160 | ||||||
| msgid "Error building package" | msgid "Error building package" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: build.go:114 | #: build.go:167 | ||||||
| msgid "Error getting working directory" | msgid "Error getting working directory" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: build.go:123 | #: build.go:176 | ||||||
| msgid "Error moving the package" | msgid "Error moving the package" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| @@ -50,27 +66,27 @@ msgstr "" | |||||||
| msgid "Attempt to fix problems with ALR" | msgid "Attempt to fix problems with ALR" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: fix.go:44 | #: fix.go:43 | ||||||
| msgid "Removing cache directory" | msgid "Removing cache directory" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: fix.go:48 | #: fix.go:47 | ||||||
| msgid "Unable to remove cache directory" | msgid "Unable to remove cache directory" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: fix.go:52 | #: fix.go:51 | ||||||
| msgid "Rebuilding cache" | msgid "Rebuilding cache" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: fix.go:56 | #: fix.go:55 | ||||||
| msgid "Unable to create new cache directory" | msgid "Unable to create new cache directory" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: fix.go:62 | #: fix.go:69 | ||||||
| msgid "Error pulling repos" | msgid "Error pulling repos" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: fix.go:66 | #: fix.go:73 | ||||||
| msgid "Done" | msgid "Done" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| @@ -98,63 +114,59 @@ msgstr "" | |||||||
| msgid "No such helper command" | msgid "No such helper command" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: info.go:42 | #: info.go:43 | ||||||
| msgid "Print information about a package" | msgid "Print information about a package" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: info.go:47 | #: info.go:48 | ||||||
| msgid "Show all information, not just for the current distro" | msgid "Show all information, not just for the current distro" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: info.go:57 | #: info.go:63 | ||||||
| msgid "Error initialization database" |  | ||||||
| msgstr "" |  | ||||||
|  |  | ||||||
| #: info.go:64 |  | ||||||
| msgid "Command info expected at least 1 argument, got %d" |  | ||||||
| msgstr "" |  | ||||||
|  |  | ||||||
| #: info.go:78 |  | ||||||
| msgid "Error finding packages" |  | ||||||
| msgstr "" |  | ||||||
|  |  | ||||||
| #: info.go:94 |  | ||||||
| msgid "Error parsing os-release file" |  | ||||||
| msgstr "" |  | ||||||
|  |  | ||||||
| #: info.go:103 |  | ||||||
| msgid "Error resolving overrides" |  | ||||||
| msgstr "" |  | ||||||
|  |  | ||||||
| #: info.go:112 info.go:118 |  | ||||||
| msgid "Error encoding script variables" |  | ||||||
| msgstr "" |  | ||||||
|  |  | ||||||
| #: install.go:42 |  | ||||||
| msgid "Install a new package" |  | ||||||
| msgstr "" |  | ||||||
|  |  | ||||||
| #: install.go:56 |  | ||||||
| msgid "Command install expected at least 1 argument, got %d" |  | ||||||
| msgstr "" |  | ||||||
|  |  | ||||||
| #: install.go:91 |  | ||||||
| msgid "Error getting packages" | msgid "Error getting packages" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: install.go:100 | #: info.go:72 | ||||||
| msgid "Error iterating over packages" | msgid "Error iterating over packages" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: install.go:113 | #: info.go:93 | ||||||
|  | msgid "Command info expected at least 1 argument, got %d" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: info.go:107 | ||||||
|  | msgid "Error finding packages" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: info.go:129 | ||||||
|  | msgid "Error parsing os-release file" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: info.go:138 | ||||||
|  | msgid "Error resolving overrides" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: info.go:147 info.go:153 | ||||||
|  | msgid "Error encoding script variables" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: install.go:43 | ||||||
|  | msgid "Install a new package" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: install.go:57 | ||||||
|  | msgid "Command install expected at least 1 argument, got %d" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: install.go:151 | ||||||
| msgid "Remove an installed package" | msgid "Remove an installed package" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: install.go:118 | #: install.go:156 | ||||||
| msgid "Command remove expected at least 1 argument, got %d" | msgid "Command remove expected at least 1 argument, got %d" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: install.go:130 | #: install.go:168 | ||||||
| msgid "Error removing packages" | msgid "Error removing packages" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| @@ -182,59 +194,107 @@ msgstr "" | |||||||
| msgid "Choose which optional package(s) to install" | msgid "Choose which optional package(s) to install" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: internal/config/config.go:64 | #: internal/cliutils/template.go:74 internal/cliutils/template.go:93 | ||||||
|  | msgid "NAME" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: internal/cliutils/template.go:74 internal/cliutils/template.go:94 | ||||||
|  | msgid "USAGE" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: internal/cliutils/template.go:74 | ||||||
|  | msgid "global options" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: internal/cliutils/template.go:74 | ||||||
|  | msgid "command" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: internal/cliutils/template.go:74 internal/cliutils/template.go:95 | ||||||
|  | msgid "command options" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: internal/cliutils/template.go:74 internal/cliutils/template.go:96 | ||||||
|  | msgid "arguments" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: internal/cliutils/template.go:74 | ||||||
|  | msgid "VERSION" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: internal/cliutils/template.go:74 internal/cliutils/template.go:98 | ||||||
|  | msgid "DESCRIPTION" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: internal/cliutils/template.go:74 | ||||||
|  | msgid "AUTHOR" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: internal/cliutils/template.go:74 | ||||||
|  | msgid "COMMANDS" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: internal/cliutils/template.go:74 | ||||||
|  | msgid "GLOBAL OPTIONS" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: internal/cliutils/template.go:74 | ||||||
|  | msgid "COPYRIGHT" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: internal/cliutils/template.go:97 | ||||||
|  | msgid "CATEGORY" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: internal/cliutils/template.go:99 internal/cliutils/template.go:100 | ||||||
|  | msgid "OPTIONS" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: internal/config/config.go:59 | ||||||
| msgid "Error opening config file, using defaults" | msgid "Error opening config file, using defaults" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: internal/config/config.go:77 | #: internal/config/config.go:72 | ||||||
| msgid "Error decoding config file, using defaults" | msgid "Error decoding config file, using defaults" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: internal/config/config.go:89 | #: internal/config/config.go:84 | ||||||
| msgid "Unable to detect user config directory" | msgid "Unable to detect user config directory" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: internal/config/config.go:97 | #: internal/config/config.go:92 | ||||||
| msgid "Unable to create ALR config directory" | msgid "Unable to create ALR config directory" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: internal/config/config.go:106 | #: internal/config/config.go:101 | ||||||
| msgid "Unable to create ALR config file" | msgid "Unable to create ALR config file" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: internal/config/config.go:112 | #: internal/config/config.go:107 | ||||||
| msgid "Error encoding default configuration" | msgid "Error encoding default configuration" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: internal/config/config.go:121 | #: internal/config/config.go:116 | ||||||
| msgid "Unable to detect cache directory" | msgid "Unable to detect cache directory" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: internal/config/config.go:131 | #: internal/config/config.go:126 | ||||||
| msgid "Unable to create repo cache directory" | msgid "Unable to create repo cache directory" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: internal/config/config.go:137 | #: internal/config/config.go:132 | ||||||
| msgid "Unable to create package cache directory" | msgid "Unable to create package cache directory" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: internal/config/lang.go:49 | #: internal/db/db.go:133 | ||||||
| msgid "Error parsing system language" |  | ||||||
| msgstr "" |  | ||||||
|  |  | ||||||
| #: internal/db/db.go:131 |  | ||||||
| msgid "Database version mismatch; resetting" | msgid "Database version mismatch; resetting" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: internal/db/db.go:138 | #: internal/db/db.go:140 | ||||||
| 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/db/db_legacy.go:101 |  | ||||||
| msgid "Error opening database" |  | ||||||
| msgstr "" |  | ||||||
|  |  | ||||||
| #: internal/dl/dl.go:170 | #: internal/dl/dl.go:170 | ||||||
| msgid "Source can be updated, updating if required" | msgid "Source can be updated, updating if required" | ||||||
| msgstr "" | msgstr "" | ||||||
| @@ -263,11 +323,11 @@ msgstr "" | |||||||
| msgid "ERROR" | msgid "ERROR" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: list.go:40 | #: list.go:41 | ||||||
| msgid "List ALR repo packages" | msgid "List ALR repo packages" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: list.go:91 | #: list.go:92 | ||||||
| msgid "Error listing installed packages" | msgid "Error listing installed packages" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| @@ -283,92 +343,92 @@ msgstr "" | |||||||
| msgid "Enable interactive questions and prompts" | msgid "Enable interactive questions and prompts" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: main.go:90 | #: main.go:92 | ||||||
| msgid "" | msgid "" | ||||||
| "Running ALR as root is forbidden as it may cause catastrophic damage to your " | "Running ALR as root is forbidden as it may cause catastrophic damage to your " | ||||||
| "system" | "system" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: main.go:124 | #: main.go:125 | ||||||
|  | msgid "Show help" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: main.go:129 | ||||||
| msgid "Error while running app" | msgid "Error while running app" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: pkg/build/build.go:108 | #: pkg/build/build.go:156 | ||||||
| msgid "Failed to prompt user to view build script" | msgid "Failed to prompt user to view build script" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: pkg/build/build.go:112 | #: pkg/build/build.go:160 | ||||||
| msgid "Building package" | msgid "Building package" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: pkg/build/build.go:156 | #: pkg/build/build.go:208 | ||||||
|  | msgid "The checksums array must be the same length as sources" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: pkg/build/build.go:235 | ||||||
| msgid "Downloading sources" | msgid "Downloading sources" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: pkg/build/build.go:168 | #: pkg/build/build.go:257 | ||||||
| msgid "Building package metadata" | msgid "Building package metadata" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: pkg/build/build.go:190 | #: pkg/build/build.go:279 | ||||||
| msgid "Compressing package" | msgid "Compressing package" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: pkg/build/build.go:316 | #: pkg/build/build.go:438 | ||||||
| msgid "" | msgid "" | ||||||
| "Your system's CPU architecture doesn't match this package. Do you want to " | "Your system's CPU architecture doesn't match this package. Do you want to " | ||||||
| "build anyway?" | "build anyway?" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: pkg/build/build.go:327 | #: pkg/build/build.go:452 | ||||||
| msgid "This package is already installed" | msgid "This package is already installed" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: pkg/build/build.go:355 | #: pkg/build/build.go:476 | ||||||
| msgid "Installing build dependencies" | msgid "Installing build dependencies" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: pkg/build/build.go:397 | #: pkg/build/build.go:517 | ||||||
| msgid "Installing dependencies" | msgid "Installing dependencies" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: pkg/build/build.go:443 | #: pkg/build/build.go:598 | ||||||
| msgid "Executing version()" |  | ||||||
| msgstr "" |  | ||||||
|  |  | ||||||
| #: pkg/build/build.go:463 |  | ||||||
| msgid "Updating version" |  | ||||||
| msgstr "" |  | ||||||
|  |  | ||||||
| #: pkg/build/build.go:468 |  | ||||||
| msgid "Executing prepare()" |  | ||||||
| msgstr "" |  | ||||||
|  |  | ||||||
| #: pkg/build/build.go:478 |  | ||||||
| msgid "Executing build()" |  | ||||||
| msgstr "" |  | ||||||
|  |  | ||||||
| #: pkg/build/build.go:488 |  | ||||||
| msgid "Executing package()" |  | ||||||
| msgstr "" |  | ||||||
|  |  | ||||||
| #: pkg/build/build.go:510 |  | ||||||
| msgid "Executing files()" |  | ||||||
| msgstr "" |  | ||||||
|  |  | ||||||
| #: pkg/build/build.go:588 |  | ||||||
| msgid "AutoProv is not implemented for this package format, so it's skipped" |  | ||||||
| msgstr "" |  | ||||||
|  |  | ||||||
| #: pkg/build/build.go:599 |  | ||||||
| msgid "AutoReq is not implemented for this package format, so it's skipped" |  | ||||||
| msgstr "" |  | ||||||
|  |  | ||||||
| #: pkg/build/build.go:706 |  | ||||||
| msgid "Would you like to remove the build dependencies?" | msgid "Would you like to remove the build dependencies?" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: pkg/build/build.go:812 | #: pkg/build/build.go:661 | ||||||
| msgid "The checksums array must be the same length as sources" | msgid "Executing prepare()" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: pkg/build/build.go:671 | ||||||
|  | msgid "Executing build()" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: pkg/build/build.go:701 pkg/build/build.go:721 | ||||||
|  | msgid "Executing %s()" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: pkg/build/build.go:780 | ||||||
|  | msgid "Error installing native packages" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: pkg/build/build.go:804 | ||||||
|  | msgid "Error installing package" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: pkg/build/build.go:863 | ||||||
|  | msgid "AutoProv is not implemented for this package format, so it's skipped" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: pkg/build/build.go:874 | ||||||
|  | msgid "AutoReq is not implemented for this package format, so it's skipped" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: pkg/build/findDeps.go:35 | #: pkg/build/findDeps.go:35 | ||||||
| @@ -383,27 +443,19 @@ msgstr "" | |||||||
| msgid "Required dependency found" | msgid "Required dependency found" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: pkg/build/install.go:42 | #: pkg/repos/pull.go:79 | ||||||
| msgid "Error installing native packages" |  | ||||||
| msgstr "" |  | ||||||
|  |  | ||||||
| #: pkg/build/install.go:79 |  | ||||||
| msgid "Error installing package" |  | ||||||
| msgstr "" |  | ||||||
|  |  | ||||||
| #: pkg/repos/pull.go:75 |  | ||||||
| msgid "Pulling repository" | msgid "Pulling repository" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: pkg/repos/pull.go:99 | #: pkg/repos/pull.go:103 | ||||||
| msgid "Repository up to date" | msgid "Repository up to date" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: pkg/repos/pull.go:156 | #: pkg/repos/pull.go:160 | ||||||
| msgid "Git repository does not appear to be a valid ALR repo" | msgid "Git repository does not appear to be a valid ALR repo" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: pkg/repos/pull.go:172 | #: pkg/repos/pull.go:176 | ||||||
| msgid "" | msgid "" | ||||||
| "ALR repo's minimum ALR version is greater than the current version. Try " | "ALR repo's minimum ALR version is greater than the current version. Try " | ||||||
| "updating ALR if something doesn't work." | "updating ALR if something doesn't work." | ||||||
| @@ -421,46 +473,78 @@ msgstr "" | |||||||
| msgid "URL of the new repo" | msgid "URL of the new repo" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: repo.go:79 repo.go:136 | #: repo.go:82 repo.go:147 | ||||||
| msgid "Error opening config file" | msgid "Error opening config file" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: repo.go:85 repo.go:142 | #: repo.go:88 repo.go:153 | ||||||
| msgid "Error encoding config" | msgid "Error encoding config" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: repo.go:103 | #: repo.go:113 | ||||||
| msgid "Remove an existing repository" | msgid "Remove an existing repository" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: repo.go:110 | #: repo.go:120 | ||||||
| msgid "Name of the repo to be deleted" | msgid "Name of the repo to be deleted" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: repo.go:128 | #: repo.go:139 | ||||||
| msgid "Repo does not exist" | msgid "Repo does not exist" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: repo.go:148 | #: repo.go:159 | ||||||
| msgid "Error removing repo directory" | msgid "Error removing repo directory" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: repo.go:154 | #: repo.go:170 | ||||||
| msgid "Error removing packages from database" | msgid "Error removing packages from database" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: repo.go:166 | #: repo.go:182 | ||||||
| msgid "Pull all repositories that have changed" | msgid "Pull all repositories that have changed" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
|  | #: search.go:36 | ||||||
|  | msgid "Search packages" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: search.go:42 | ||||||
|  | msgid "Search by name" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: search.go:47 | ||||||
|  | msgid "Search by description" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: search.go:52 | ||||||
|  | msgid "Search by repository" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: search.go:57 | ||||||
|  | msgid "Search by provides" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: search.go:62 | ||||||
|  | msgid "Format output using a Go template" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: search.go:82 search.go:99 | ||||||
|  | msgid "Error parsing format template" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
|  | #: search.go:107 | ||||||
|  | msgid "Error executing template" | ||||||
|  | msgstr "" | ||||||
|  |  | ||||||
| #: upgrade.go:47 | #: upgrade.go:47 | ||||||
| msgid "Upgrade all installed packages" | msgid "Upgrade all installed packages" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: upgrade.go:83 | #: upgrade.go:90 | ||||||
| msgid "Error checking for updates" | msgid "Error checking for updates" | ||||||
| msgstr "" | msgstr "" | ||||||
|  |  | ||||||
| #: upgrade.go:94 | #: upgrade.go:112 | ||||||
| msgid "There is nothing to do." | msgid "There is nothing to do." | ||||||
| msgstr "" | msgstr "" | ||||||
|   | |||||||
| @@ -1,12 +1,12 @@ | |||||||
| # | # | ||||||
| # Maxim Slipenko <maks1ms@alt-gnome.ru>, 2025. |  | ||||||
| # x1z53 <x1z53@yandex.ru>, 2025. | # x1z53 <x1z53@yandex.ru>, 2025. | ||||||
|  | # Maxim Slipenko <maks1ms@alt-gnome.ru>, 2025. | ||||||
| # | # | ||||||
| msgid "" | msgid "" | ||||||
| msgstr "" | msgstr "" | ||||||
| "Project-Id-Version: unnamed project\n" | "Project-Id-Version: unnamed project\n" | ||||||
| "PO-Revision-Date: 2025-01-24 21:20+0300\n" | "PO-Revision-Date: 2025-03-09 17:31+0300\n" | ||||||
| "Last-Translator: x1z53 <x1z53@yandex.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" | ||||||
| @@ -16,40 +16,56 @@ msgstr "" | |||||||
| "%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 47.1\n" | "X-Generator: Gtranslator 47.1\n" | ||||||
|  |  | ||||||
| #: build.go:41 | #: build.go:44 | ||||||
| msgid "Build a local package" | msgid "Build a local package" | ||||||
| msgstr "Сборка локального пакета" | msgstr "Сборка локального пакета" | ||||||
|  |  | ||||||
| #: build.go:47 | #: build.go:50 | ||||||
| msgid "Path to the build script" | msgid "Path to the build script" | ||||||
| msgstr "Путь к скрипту сборки" | msgstr "Путь к скрипту сборки" | ||||||
|  |  | ||||||
| #: build.go:52 | #: build.go:55 | ||||||
|  | msgid "Specify subpackage in script (for multi package script only)" | ||||||
|  | msgstr "Укажите подпакет в скрипте (только для многопакетного скрипта)" | ||||||
|  |  | ||||||
|  | #: build.go:60 | ||||||
| msgid "Name of the package to build and its repo (example: default/go-bin)" | msgid "Name of the package to build and its repo (example: default/go-bin)" | ||||||
| msgstr "Имя пакета для сборки и его репозиторий (пример: default/go-bin)" | msgstr "Имя пакета для сборки и его репозиторий (пример: default/go-bin)" | ||||||
|  |  | ||||||
| #: build.go:57 | #: build.go:65 | ||||||
| msgid "" | msgid "" | ||||||
| "Build package from scratch even if there's an already built package available" | "Build package from scratch even if there's an already built package available" | ||||||
| msgstr "Создайте пакет с нуля, даже если уже имеется готовый пакет" | msgstr "Создайте пакет с нуля, даже если уже имеется готовый пакет" | ||||||
|  |  | ||||||
| #: build.go:87 | #: build.go:75 | ||||||
|  | msgid "Error initialization database" | ||||||
|  | msgstr "Ошибка инициализации базы данных" | ||||||
|  |  | ||||||
|  | #: build.go:104 | ||||||
|  | msgid "Package not found" | ||||||
|  | msgstr "Пакет не найден" | ||||||
|  |  | ||||||
|  | #: build.go:124 | ||||||
| msgid "Error pulling repositories" | msgid "Error pulling repositories" | ||||||
| msgstr "Ошибка при извлечении репозиториев" | msgstr "Ошибка при извлечении репозиториев" | ||||||
|  |  | ||||||
| #: build.go:95 | #: build.go:132 | ||||||
| msgid "Unable to detect a supported package manager on the system" | msgid "Unable to detect a supported package manager on the system" | ||||||
| msgstr "Не удалось обнаружить поддерживаемый менеджер пакетов в системе" | msgstr "Не удалось обнаружить поддерживаемый менеджер пакетов в системе" | ||||||
|  |  | ||||||
| #: build.go:107 | #: build.go:138 | ||||||
|  | msgid "Error parsing os release" | ||||||
|  | msgstr "Ошибка при разборе файла выпуска операционной системы" | ||||||
|  |  | ||||||
|  | #: build.go:160 | ||||||
| msgid "Error building package" | msgid "Error building package" | ||||||
| msgstr "Ошибка при сборке пакета" | msgstr "Ошибка при сборке пакета" | ||||||
|  |  | ||||||
| #: build.go:114 | #: build.go:167 | ||||||
| msgid "Error getting working directory" | msgid "Error getting working directory" | ||||||
| msgstr "Ошибка при получении рабочего каталога" | msgstr "Ошибка при получении рабочего каталога" | ||||||
|  |  | ||||||
| #: build.go:123 | #: build.go:176 | ||||||
| msgid "Error moving the package" | msgid "Error moving the package" | ||||||
| msgstr "Ошибка при перемещении пакета" | msgstr "Ошибка при перемещении пакета" | ||||||
|  |  | ||||||
| @@ -57,27 +73,27 @@ msgstr "Ошибка при перемещении пакета" | |||||||
| msgid "Attempt to fix problems with ALR" | msgid "Attempt to fix problems with ALR" | ||||||
| msgstr "Попытка устранить проблемы с ALR" | msgstr "Попытка устранить проблемы с ALR" | ||||||
|  |  | ||||||
| #: fix.go:44 | #: fix.go:43 | ||||||
| msgid "Removing cache directory" | msgid "Removing cache directory" | ||||||
| msgstr "Удаление каталога кэша" | msgstr "Удаление каталога кэша" | ||||||
|  |  | ||||||
| #: fix.go:48 | #: fix.go:47 | ||||||
| msgid "Unable to remove cache directory" | msgid "Unable to remove cache directory" | ||||||
| msgstr "Не удалось удалить каталог кэша" | msgstr "Не удалось удалить каталог кэша" | ||||||
|  |  | ||||||
| #: fix.go:52 | #: fix.go:51 | ||||||
| msgid "Rebuilding cache" | msgid "Rebuilding cache" | ||||||
| msgstr "Восстановление кэша" | msgstr "Восстановление кэша" | ||||||
|  |  | ||||||
| #: fix.go:56 | #: fix.go:55 | ||||||
| msgid "Unable to create new cache directory" | msgid "Unable to create new cache directory" | ||||||
| msgstr "Не удалось создать новый каталог кэша" | msgstr "Не удалось создать новый каталог кэша" | ||||||
|  |  | ||||||
| #: fix.go:62 | #: fix.go:69 | ||||||
| msgid "Error pulling repos" | msgid "Error pulling repos" | ||||||
| msgstr "Ошибка при извлечении репозиториев" | msgstr "Ошибка при извлечении репозиториев" | ||||||
|  |  | ||||||
| #: fix.go:66 | #: fix.go:73 | ||||||
| msgid "Done" | msgid "Done" | ||||||
| msgstr "Сделано" | msgstr "Сделано" | ||||||
|  |  | ||||||
| @@ -105,63 +121,59 @@ msgstr "Каталог, в который будут устанавливать | |||||||
| msgid "No such helper command" | msgid "No such helper command" | ||||||
| msgstr "Такой вспомогательной команды нет" | msgstr "Такой вспомогательной команды нет" | ||||||
|  |  | ||||||
| #: info.go:42 | #: info.go:43 | ||||||
| msgid "Print information about a package" | msgid "Print information about a package" | ||||||
| msgstr "Отобразить информацию о пакете" | msgstr "Отобразить информацию о пакете" | ||||||
|  |  | ||||||
| #: info.go:47 | #: info.go:48 | ||||||
| msgid "Show all information, not just for the current distro" | msgid "Show all information, not just for the current distro" | ||||||
| msgstr "Показывать всю информацию, не только для текущего дистрибутива" | msgstr "Показывать всю информацию, не только для текущего дистрибутива" | ||||||
|  |  | ||||||
| #: info.go:57 | #: info.go:63 | ||||||
| msgid "Error initialization database" |  | ||||||
| msgstr "Ошибка инициализации базы данных" |  | ||||||
|  |  | ||||||
| #: info.go:64 |  | ||||||
| msgid "Command info expected at least 1 argument, got %d" |  | ||||||
| msgstr "Для команды info ожидался хотя бы 1 аргумент, получено %d" |  | ||||||
|  |  | ||||||
| #: info.go:78 |  | ||||||
| msgid "Error finding packages" |  | ||||||
| msgstr "Ошибка при поиске пакетов" |  | ||||||
|  |  | ||||||
| #: info.go:94 |  | ||||||
| msgid "Error parsing os-release file" |  | ||||||
| msgstr "Ошибка при разборе файла выпуска операционной системы" |  | ||||||
|  |  | ||||||
| #: info.go:103 |  | ||||||
| msgid "Error resolving overrides" |  | ||||||
| msgstr "Ошибка устранения переорпеделений" |  | ||||||
|  |  | ||||||
| #: info.go:112 info.go:118 |  | ||||||
| msgid "Error encoding script variables" |  | ||||||
| msgstr "Ошибка кодирования переменных скрита" |  | ||||||
|  |  | ||||||
| #: install.go:42 |  | ||||||
| msgid "Install a new package" |  | ||||||
| msgstr "Установить новый пакет" |  | ||||||
|  |  | ||||||
| #: install.go:56 |  | ||||||
| msgid "Command install expected at least 1 argument, got %d" |  | ||||||
| msgstr "Для команды install ожидался хотя бы 1 аргумент, получено %d" |  | ||||||
|  |  | ||||||
| #: install.go:91 |  | ||||||
| msgid "Error getting packages" | msgid "Error getting packages" | ||||||
| msgstr "Ошибка при получении пакетов" | msgstr "Ошибка при получении пакетов" | ||||||
|  |  | ||||||
| #: install.go:100 | #: info.go:72 | ||||||
| msgid "Error iterating over packages" | msgid "Error iterating over packages" | ||||||
| msgstr "Ошибка при переборе пакетов" | msgstr "Ошибка при переборе пакетов" | ||||||
|  |  | ||||||
| #: install.go:113 | #: info.go:93 | ||||||
|  | msgid "Command info expected at least 1 argument, got %d" | ||||||
|  | msgstr "Для команды info ожидался хотя бы 1 аргумент, получено %d" | ||||||
|  |  | ||||||
|  | #: info.go:107 | ||||||
|  | msgid "Error finding packages" | ||||||
|  | msgstr "Ошибка при поиске пакетов" | ||||||
|  |  | ||||||
|  | #: info.go:129 | ||||||
|  | msgid "Error parsing os-release file" | ||||||
|  | msgstr "Ошибка при разборе файла выпуска операционной системы" | ||||||
|  |  | ||||||
|  | #: info.go:138 | ||||||
|  | msgid "Error resolving overrides" | ||||||
|  | msgstr "Ошибка устранения переорпеделений" | ||||||
|  |  | ||||||
|  | #: info.go:147 info.go:153 | ||||||
|  | msgid "Error encoding script variables" | ||||||
|  | msgstr "Ошибка кодирования переменных скрита" | ||||||
|  |  | ||||||
|  | #: install.go:43 | ||||||
|  | msgid "Install a new package" | ||||||
|  | msgstr "Установить новый пакет" | ||||||
|  |  | ||||||
|  | #: install.go:57 | ||||||
|  | msgid "Command install expected at least 1 argument, got %d" | ||||||
|  | msgstr "Для команды install ожидался хотя бы 1 аргумент, получено %d" | ||||||
|  |  | ||||||
|  | #: install.go:151 | ||||||
| msgid "Remove an installed package" | msgid "Remove an installed package" | ||||||
| msgstr "Удалить установленный пакет" | msgstr "Удалить установленный пакет" | ||||||
|  |  | ||||||
| #: install.go:118 | #: install.go:156 | ||||||
| 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:130 | #: install.go:168 | ||||||
| msgid "Error removing packages" | msgid "Error removing packages" | ||||||
| msgstr "Ошибка при удалении пакетов" | msgstr "Ошибка при удалении пакетов" | ||||||
|  |  | ||||||
| @@ -189,64 +201,112 @@ msgstr "Выберите, какой пакет использовать для | |||||||
| msgid "Choose which optional package(s) to install" | msgid "Choose which optional package(s) to install" | ||||||
| msgstr "Выберите, какой дополнительный пакет(ы) следует установить" | msgstr "Выберите, какой дополнительный пакет(ы) следует установить" | ||||||
|  |  | ||||||
| #: internal/config/config.go:64 | #: internal/cliutils/template.go:74 internal/cliutils/template.go:93 | ||||||
|  | msgid "NAME" | ||||||
|  | msgstr "НАЗВАНИЕ" | ||||||
|  |  | ||||||
|  | #: internal/cliutils/template.go:74 internal/cliutils/template.go:94 | ||||||
|  | msgid "USAGE" | ||||||
|  | msgstr "ИСПОЛЬЗОВАНИЕ" | ||||||
|  |  | ||||||
|  | #: internal/cliutils/template.go:74 | ||||||
|  | msgid "global options" | ||||||
|  | msgstr "глобальные опции" | ||||||
|  |  | ||||||
|  | #: internal/cliutils/template.go:74 | ||||||
|  | msgid "command" | ||||||
|  | msgstr "команда" | ||||||
|  |  | ||||||
|  | #: internal/cliutils/template.go:74 internal/cliutils/template.go:95 | ||||||
|  | msgid "command options" | ||||||
|  | msgstr "опции команды" | ||||||
|  |  | ||||||
|  | #: internal/cliutils/template.go:74 internal/cliutils/template.go:96 | ||||||
|  | msgid "arguments" | ||||||
|  | msgstr "аргументы" | ||||||
|  |  | ||||||
|  | #: internal/cliutils/template.go:74 | ||||||
|  | msgid "VERSION" | ||||||
|  | msgstr "ВЕРСИЯ" | ||||||
|  |  | ||||||
|  | #: internal/cliutils/template.go:74 internal/cliutils/template.go:98 | ||||||
|  | msgid "DESCRIPTION" | ||||||
|  | msgstr "ОПИСАНИЕ" | ||||||
|  |  | ||||||
|  | #: internal/cliutils/template.go:74 | ||||||
|  | msgid "AUTHOR" | ||||||
|  | msgstr "АВТОР" | ||||||
|  |  | ||||||
|  | #: internal/cliutils/template.go:74 | ||||||
|  | msgid "COMMANDS" | ||||||
|  | msgstr "КОМАНДЫ" | ||||||
|  |  | ||||||
|  | #: internal/cliutils/template.go:74 | ||||||
|  | msgid "GLOBAL OPTIONS" | ||||||
|  | msgstr "ГЛОБАЛЬНЫЕ ОПЦИИ" | ||||||
|  |  | ||||||
|  | #: internal/cliutils/template.go:74 | ||||||
|  | msgid "COPYRIGHT" | ||||||
|  | msgstr "АВТОРСКОЕ ПРАВО" | ||||||
|  |  | ||||||
|  | #: internal/cliutils/template.go:97 | ||||||
|  | msgid "CATEGORY" | ||||||
|  | msgstr "КАТЕГОРИЯ" | ||||||
|  |  | ||||||
|  | #: internal/cliutils/template.go:99 internal/cliutils/template.go:100 | ||||||
|  | msgid "OPTIONS" | ||||||
|  | msgstr "ПАРАМЕТРЫ" | ||||||
|  |  | ||||||
|  | #: internal/config/config.go:59 | ||||||
| msgid "Error opening config file, using defaults" | msgid "Error opening config file, using defaults" | ||||||
| msgstr "" | msgstr "" | ||||||
| "Ошибка при открытии конфигурационного файла, используются значения по " | "Ошибка при открытии конфигурационного файла, используются значения по " | ||||||
| "умолчанию" | "умолчанию" | ||||||
|  |  | ||||||
| #: internal/config/config.go:77 | #: internal/config/config.go:72 | ||||||
| msgid "Error decoding config file, using defaults" | msgid "Error decoding config file, using defaults" | ||||||
| msgstr "" | msgstr "" | ||||||
| "Ошибка при декодировании конфигурационного файла, используются значения по " | "Ошибка при декодировании конфигурационного файла, используются значения по " | ||||||
| "умолчанию" | "умолчанию" | ||||||
|  |  | ||||||
| #: internal/config/config.go:89 | #: internal/config/config.go:84 | ||||||
| msgid "Unable to detect user config directory" | msgid "Unable to detect user config directory" | ||||||
| msgstr "Не удалось обнаружить каталог конфигурации пользователя" | msgstr "Не удалось обнаружить каталог конфигурации пользователя" | ||||||
|  |  | ||||||
| #: internal/config/config.go:97 | #: internal/config/config.go:92 | ||||||
| msgid "Unable to create ALR config directory" | msgid "Unable to create ALR config directory" | ||||||
| msgstr "Не удалось создать каталог конфигурации ALR" | msgstr "Не удалось создать каталог конфигурации ALR" | ||||||
|  |  | ||||||
| #: internal/config/config.go:106 | #: internal/config/config.go:101 | ||||||
| msgid "Unable to create ALR config file" | msgid "Unable to create ALR config file" | ||||||
| msgstr "Не удалось создать конфигурационный файл ALR" | msgstr "Не удалось создать конфигурационный файл ALR" | ||||||
|  |  | ||||||
| #: internal/config/config.go:112 | #: internal/config/config.go:107 | ||||||
| msgid "Error encoding default configuration" | msgid "Error encoding default configuration" | ||||||
| msgstr "Ошибка кодирования конфигурации по умолчанию" | msgstr "Ошибка кодирования конфигурации по умолчанию" | ||||||
|  |  | ||||||
| #: internal/config/config.go:121 | #: internal/config/config.go:116 | ||||||
| msgid "Unable to detect cache directory" | msgid "Unable to detect cache directory" | ||||||
| msgstr "Не удалось обнаружить каталог кэша" | msgstr "Не удалось обнаружить каталог кэша" | ||||||
|  |  | ||||||
| #: internal/config/config.go:131 | #: internal/config/config.go:126 | ||||||
| msgid "Unable to create repo cache directory" | msgid "Unable to create repo cache directory" | ||||||
| msgstr "Не удалось создать каталог кэша репозитория" | msgstr "Не удалось создать каталог кэша репозитория" | ||||||
|  |  | ||||||
| #: internal/config/config.go:137 | #: internal/config/config.go:132 | ||||||
| msgid "Unable to create package cache directory" | msgid "Unable to create package cache directory" | ||||||
| msgstr "Не удалось создать каталог кэша пакетов" | msgstr "Не удалось создать каталог кэша пакетов" | ||||||
|  |  | ||||||
| #: internal/config/lang.go:49 | #: internal/db/db.go:133 | ||||||
| msgid "Error parsing system language" |  | ||||||
| msgstr "Ошибка при парсинге языка системы" |  | ||||||
|  |  | ||||||
| #: internal/db/db.go:131 |  | ||||||
| msgid "Database version mismatch; resetting" | msgid "Database version mismatch; resetting" | ||||||
| msgstr "Несоответствие версий базы данных; сброс настроек" | msgstr "Несоответствие версий базы данных; сброс настроек" | ||||||
|  |  | ||||||
| #: internal/db/db.go:138 | #: internal/db/db.go:140 | ||||||
| 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/db/db_legacy.go:101 |  | ||||||
| msgid "Error opening database" |  | ||||||
| msgstr "Ошибка при открытии базы данных" |  | ||||||
|  |  | ||||||
| #: internal/dl/dl.go:170 | #: internal/dl/dl.go:170 | ||||||
| msgid "Source can be updated, updating if required" | msgid "Source can be updated, updating if required" | ||||||
| msgstr "Исходный код можно обновлять, обновляя при необходимости" | msgstr "Исходный код можно обновлять, обновляя при необходимости" | ||||||
| @@ -265,21 +325,21 @@ msgstr "Скачивание источника" | |||||||
|  |  | ||||||
| #: internal/dl/progress_tui.go:100 | #: internal/dl/progress_tui.go:100 | ||||||
| msgid "%s: done!\n" | msgid "%s: done!\n" | ||||||
| msgstr "" | msgstr "%s: выполнено!\n" | ||||||
|  |  | ||||||
| #: internal/dl/progress_tui.go:104 | #: internal/dl/progress_tui.go:104 | ||||||
| msgid "%s %s downloading at %s/s\n" | msgid "%s %s downloading at %s/s\n" | ||||||
| msgstr "" | msgstr "%s %s загружается — %s/с\n" | ||||||
|  |  | ||||||
| #: internal/logger/log.go:47 | #: internal/logger/log.go:47 | ||||||
| msgid "ERROR" | msgid "ERROR" | ||||||
| msgstr "ОШИБКА" | msgstr "ОШИБКА" | ||||||
|  |  | ||||||
| #: list.go:40 | #: list.go:41 | ||||||
| msgid "List ALR repo packages" | msgid "List ALR repo packages" | ||||||
| msgstr "Список пакетов репозитория ALR" | msgstr "Список пакетов репозитория ALR" | ||||||
|  |  | ||||||
| #: list.go:91 | #: list.go:92 | ||||||
| msgid "Error listing installed packages" | msgid "Error listing installed packages" | ||||||
| msgstr "Ошибка при составлении списка установленных пакетов" | msgstr "Ошибка при составлении списка установленных пакетов" | ||||||
|  |  | ||||||
| @@ -295,7 +355,7 @@ msgstr "Аргументы, которые будут переданы мене | |||||||
| msgid "Enable interactive questions and prompts" | msgid "Enable interactive questions and prompts" | ||||||
| msgstr "Включение интерактивных вопросов и запросов" | msgstr "Включение интерактивных вопросов и запросов" | ||||||
|  |  | ||||||
| #: main.go:90 | #: main.go:92 | ||||||
| msgid "" | msgid "" | ||||||
| "Running ALR as root is forbidden as it may cause catastrophic damage to your " | "Running ALR as root is forbidden as it may cause catastrophic damage to your " | ||||||
| "system" | "system" | ||||||
| @@ -303,31 +363,39 @@ msgstr "" | |||||||
| "Запуск ALR от имени root запрещён, так как это может привести к " | "Запуск ALR от имени root запрещён, так как это может привести к " | ||||||
| "катастрофическому повреждению вашей системы" | "катастрофическому повреждению вашей системы" | ||||||
|  |  | ||||||
| #: main.go:124 | #: main.go:125 | ||||||
|  | msgid "Show help" | ||||||
|  | msgstr "Показать справку" | ||||||
|  |  | ||||||
|  | #: main.go:129 | ||||||
| msgid "Error while running app" | msgid "Error while running app" | ||||||
| msgstr "Ошибка при запуске приложения" | msgstr "Ошибка при запуске приложения" | ||||||
|  |  | ||||||
| #: pkg/build/build.go:108 | #: pkg/build/build.go:156 | ||||||
| msgid "Failed to prompt user to view build script" | msgid "Failed to prompt user to view build script" | ||||||
| msgstr "Не удалось предложить пользователю просмотреть скрипт сборки" | msgstr "Не удалось предложить пользователю просмотреть скрипт сборки" | ||||||
|  |  | ||||||
| #: pkg/build/build.go:112 | #: pkg/build/build.go:160 | ||||||
| msgid "Building package" | msgid "Building package" | ||||||
| msgstr "Сборка пакета" | msgstr "Сборка пакета" | ||||||
|  |  | ||||||
| #: pkg/build/build.go:156 | #: pkg/build/build.go:208 | ||||||
|  | msgid "The checksums array must be the same length as sources" | ||||||
|  | msgstr "Массив контрольных сумм должен быть той же длины, что и источники" | ||||||
|  |  | ||||||
|  | #: pkg/build/build.go:235 | ||||||
| msgid "Downloading sources" | msgid "Downloading sources" | ||||||
| msgstr "Скачивание источников" | msgstr "Скачивание источников" | ||||||
|  |  | ||||||
| #: pkg/build/build.go:168 | #: pkg/build/build.go:257 | ||||||
| msgid "Building package metadata" | msgid "Building package metadata" | ||||||
| msgstr "Сборка метаданных пакета" | msgstr "Сборка метаданных пакета" | ||||||
|  |  | ||||||
| #: pkg/build/build.go:190 | #: pkg/build/build.go:279 | ||||||
| msgid "Compressing package" | msgid "Compressing package" | ||||||
| msgstr "Сжатие пакета" | msgstr "Сжатие пакета" | ||||||
|  |  | ||||||
| #: pkg/build/build.go:316 | #: pkg/build/build.go:438 | ||||||
| msgid "" | msgid "" | ||||||
| "Your system's CPU architecture doesn't match this package. Do you want to " | "Your system's CPU architecture doesn't match this package. Do you want to " | ||||||
| "build anyway?" | "build anyway?" | ||||||
| @@ -335,60 +403,52 @@ msgstr "" | |||||||
| "Архитектура процессора вашей системы не соответствует этому пакету. Вы все " | "Архитектура процессора вашей системы не соответствует этому пакету. Вы все " | ||||||
| "равно хотите выполнить сборку?" | "равно хотите выполнить сборку?" | ||||||
|  |  | ||||||
| #: pkg/build/build.go:327 | #: pkg/build/build.go:452 | ||||||
| msgid "This package is already installed" | msgid "This package is already installed" | ||||||
| msgstr "Этот пакет уже установлен" | msgstr "Этот пакет уже установлен" | ||||||
|  |  | ||||||
| #: pkg/build/build.go:355 | #: pkg/build/build.go:476 | ||||||
| msgid "Installing build dependencies" | msgid "Installing build dependencies" | ||||||
| msgstr "Установка зависимостей сборки" | msgstr "Установка зависимостей сборки" | ||||||
|  |  | ||||||
| #: pkg/build/build.go:397 | #: pkg/build/build.go:517 | ||||||
| msgid "Installing dependencies" | msgid "Installing dependencies" | ||||||
| msgstr "Установка зависимостей" | msgstr "Установка зависимостей" | ||||||
|  |  | ||||||
| #: pkg/build/build.go:443 | #: pkg/build/build.go:598 | ||||||
| msgid "Executing version()" | msgid "Would you like to remove the build dependencies?" | ||||||
| msgstr "Исполнение версия()" | msgstr "Хотели бы вы удалить зависимости сборки?" | ||||||
|  |  | ||||||
| #: pkg/build/build.go:463 | #: pkg/build/build.go:661 | ||||||
| msgid "Updating version" |  | ||||||
| msgstr "Обновление версии" |  | ||||||
|  |  | ||||||
| #: pkg/build/build.go:468 |  | ||||||
| msgid "Executing prepare()" | msgid "Executing prepare()" | ||||||
| msgstr "Исполнение prepare()" | msgstr "Исполнение prepare()" | ||||||
|  |  | ||||||
| #: pkg/build/build.go:478 | #: pkg/build/build.go:671 | ||||||
| msgid "Executing build()" | msgid "Executing build()" | ||||||
| msgstr "Исполнение build()" | msgstr "Исполнение build()" | ||||||
|  |  | ||||||
| #: pkg/build/build.go:488 | #: pkg/build/build.go:701 pkg/build/build.go:721 | ||||||
| msgid "Executing package()" | msgid "Executing %s()" | ||||||
| msgstr "Исполнение package()" | msgstr "Исполнение %s()" | ||||||
|  |  | ||||||
| #: pkg/build/build.go:510 | #: pkg/build/build.go:780 | ||||||
| msgid "Executing files()" | msgid "Error installing native packages" | ||||||
| msgstr "Исполнение files()" | msgstr "Ошибка при установке нативных пакетов" | ||||||
|  |  | ||||||
| #: pkg/build/build.go:588 | #: pkg/build/build.go:804 | ||||||
|  | msgid "Error installing package" | ||||||
|  | msgstr "Ошибка при установке пакета" | ||||||
|  |  | ||||||
|  | #: pkg/build/build.go:863 | ||||||
| msgid "AutoProv is not implemented for this package format, so it's skipped" | msgid "AutoProv is not implemented for this package format, so it's skipped" | ||||||
| msgstr "" | msgstr "" | ||||||
| "AutoProv не реализовано для этого формата пакета, поэтому будет пропущено" | "AutoProv не реализовано для этого формата пакета, поэтому будет пропущено" | ||||||
|  |  | ||||||
| #: pkg/build/build.go:599 | #: pkg/build/build.go:874 | ||||||
| msgid "AutoReq is not implemented for this package format, so it's skipped" | msgid "AutoReq is not implemented for this package format, so it's skipped" | ||||||
| msgstr "" | msgstr "" | ||||||
| "AutoReq не реализовано для этого формата пакета, поэтому будет пропущено" | "AutoReq не реализовано для этого формата пакета, поэтому будет пропущено" | ||||||
|  |  | ||||||
| #: pkg/build/build.go:706 |  | ||||||
| msgid "Would you like to remove the build dependencies?" |  | ||||||
| msgstr "Хотели бы вы удалить зависимости сборки?" |  | ||||||
|  |  | ||||||
| #: pkg/build/build.go:812 |  | ||||||
| msgid "The checksums array must be the same length as sources" |  | ||||||
| msgstr "Массив контрольных сумм должен быть той же длины, что и источники" |  | ||||||
|  |  | ||||||
| #: pkg/build/findDeps.go:35 | #: pkg/build/findDeps.go:35 | ||||||
| msgid "Command not found on the system" | msgid "Command not found on the system" | ||||||
| msgstr "Команда не найдена в системе" | msgstr "Команда не найдена в системе" | ||||||
| @@ -401,27 +461,19 @@ msgstr "Найденная предоставленная зависимость | |||||||
| msgid "Required dependency found" | msgid "Required dependency found" | ||||||
| msgstr "Найдена требуемая зависимость" | msgstr "Найдена требуемая зависимость" | ||||||
|  |  | ||||||
| #: pkg/build/install.go:42 | #: pkg/repos/pull.go:79 | ||||||
| msgid "Error installing native packages" |  | ||||||
| msgstr "Ошибка при установке нативных пакетов" |  | ||||||
|  |  | ||||||
| #: pkg/build/install.go:79 |  | ||||||
| msgid "Error installing package" |  | ||||||
| msgstr "Ошибка при установке пакета" |  | ||||||
|  |  | ||||||
| #: pkg/repos/pull.go:75 |  | ||||||
| msgid "Pulling repository" | msgid "Pulling repository" | ||||||
| msgstr "Скачивание репозитория" | msgstr "Скачивание репозитория" | ||||||
|  |  | ||||||
| #: pkg/repos/pull.go:99 | #: pkg/repos/pull.go:103 | ||||||
| msgid "Repository up to date" | msgid "Repository up to date" | ||||||
| msgstr "Репозиторий уже обновлён" | msgstr "Репозиторий уже обновлён" | ||||||
|  |  | ||||||
| #: pkg/repos/pull.go:156 | #: pkg/repos/pull.go:160 | ||||||
| msgid "Git repository does not appear to be a valid ALR repo" | msgid "Git repository does not appear to be a valid ALR repo" | ||||||
| msgstr "Репозиторий Git не поддерживается репозиторием ALR" | msgstr "Репозиторий Git не поддерживается репозиторием ALR" | ||||||
|  |  | ||||||
| #: pkg/repos/pull.go:172 | #: pkg/repos/pull.go:176 | ||||||
| msgid "" | msgid "" | ||||||
| "ALR repo's minimum ALR version is greater than the current version. Try " | "ALR repo's minimum ALR version is greater than the current version. Try " | ||||||
| "updating ALR if something doesn't work." | "updating ALR if something doesn't work." | ||||||
| @@ -441,46 +493,93 @@ msgstr "Название нового репозитория" | |||||||
| msgid "URL of the new repo" | msgid "URL of the new repo" | ||||||
| msgstr "URL-адрес нового репозитория" | msgstr "URL-адрес нового репозитория" | ||||||
|  |  | ||||||
| #: repo.go:79 repo.go:136 | #: repo.go:82 repo.go:147 | ||||||
| msgid "Error opening config file" | msgid "Error opening config file" | ||||||
| msgstr "Ошибка при открытии конфигурационного файла" | msgstr "Ошибка при открытии конфигурационного файла" | ||||||
|  |  | ||||||
| #: repo.go:85 repo.go:142 | #: repo.go:88 repo.go:153 | ||||||
| msgid "Error encoding config" | msgid "Error encoding config" | ||||||
| msgstr "Ошибка при кодировании конфигурации" | msgstr "Ошибка при кодировании конфигурации" | ||||||
|  |  | ||||||
| #: repo.go:103 | #: repo.go:113 | ||||||
| msgid "Remove an existing repository" | msgid "Remove an existing repository" | ||||||
| msgstr "Удалить существующий репозиторий" | msgstr "Удалить существующий репозиторий" | ||||||
|  |  | ||||||
| #: repo.go:110 | #: repo.go:120 | ||||||
| msgid "Name of the repo to be deleted" | msgid "Name of the repo to be deleted" | ||||||
| msgstr "Название репозитория  удалён" | msgstr "Название репозитория  удалён" | ||||||
|  |  | ||||||
| #: repo.go:128 | #: repo.go:139 | ||||||
| msgid "Repo does not exist" | msgid "Repo does not exist" | ||||||
| msgstr "Репозитория не существует" | msgstr "Репозитория не существует" | ||||||
|  |  | ||||||
| #: repo.go:148 | #: repo.go:159 | ||||||
| msgid "Error removing repo directory" | msgid "Error removing repo directory" | ||||||
| msgstr "Ошибка при удалении каталога репозитория" | msgstr "Ошибка при удалении каталога репозитория" | ||||||
|  |  | ||||||
| #: repo.go:154 | #: repo.go:170 | ||||||
| msgid "Error removing packages from database" | msgid "Error removing packages from database" | ||||||
| msgstr "Ошибка при удалении пакетов из базы данных" | msgstr "Ошибка при удалении пакетов из базы данных" | ||||||
|  |  | ||||||
| #: repo.go:166 | #: repo.go:182 | ||||||
| msgid "Pull all repositories that have changed" | msgid "Pull all repositories that have changed" | ||||||
| msgstr "Скачать все изменённые репозитории" | msgstr "Скачать все изменённые репозитории" | ||||||
|  |  | ||||||
|  | #: search.go:36 | ||||||
|  | msgid "Search packages" | ||||||
|  | msgstr "Поиск пакетов" | ||||||
|  |  | ||||||
|  | #: search.go:42 | ||||||
|  | msgid "Search by name" | ||||||
|  | msgstr "Искать по имени" | ||||||
|  |  | ||||||
|  | #: search.go:47 | ||||||
|  | msgid "Search by description" | ||||||
|  | msgstr "Искать по описанию" | ||||||
|  |  | ||||||
|  | #: search.go:52 | ||||||
|  | msgid "Search by repository" | ||||||
|  | msgstr "Искать по репозиторию" | ||||||
|  |  | ||||||
|  | #: search.go:57 | ||||||
|  | msgid "Search by provides" | ||||||
|  | msgstr "Иcкать по provides" | ||||||
|  |  | ||||||
|  | #: search.go:62 | ||||||
|  | msgid "Format output using a Go template" | ||||||
|  | msgstr "Формат выходных данных с использованием шаблона Go" | ||||||
|  |  | ||||||
|  | #: search.go:82 search.go:99 | ||||||
|  | msgid "Error parsing format template" | ||||||
|  | msgstr "Ошибка при разборе шаблона" | ||||||
|  |  | ||||||
|  | #: search.go:107 | ||||||
|  | msgid "Error executing template" | ||||||
|  | msgstr "Ошибка при выполнении шаблона" | ||||||
|  |  | ||||||
| #: upgrade.go:47 | #: upgrade.go:47 | ||||||
| msgid "Upgrade all installed packages" | msgid "Upgrade all installed packages" | ||||||
| msgstr "Обновить все установленные пакеты" | msgstr "Обновить все установленные пакеты" | ||||||
|  |  | ||||||
| #: upgrade.go:83 | #: upgrade.go:90 | ||||||
| msgid "Error checking for updates" | msgid "Error checking for updates" | ||||||
| msgstr "Ошибка при проверке обновлений" | msgstr "Ошибка при проверке обновлений" | ||||||
|  |  | ||||||
| #: upgrade.go:94 | #: upgrade.go:112 | ||||||
| msgid "There is nothing to do." | msgid "There is nothing to do." | ||||||
| msgstr "Здесь нечего делать." | msgstr "Здесь нечего делать." | ||||||
|  |  | ||||||
|  | #~ msgid "Error parsing system language" | ||||||
|  | #~ msgstr "Ошибка при парсинге языка системы" | ||||||
|  |  | ||||||
|  | #~ msgid "Error opening database" | ||||||
|  | #~ msgstr "Ошибка при открытии базы данных" | ||||||
|  |  | ||||||
|  | #~ msgid "Executing version()" | ||||||
|  | #~ msgstr "Исполнение версия()" | ||||||
|  |  | ||||||
|  | #~ msgid "Updating version" | ||||||
|  | #~ msgstr "Обновление версии" | ||||||
|  |  | ||||||
|  | #~ msgid "Executing package()" | ||||||
|  | #~ msgstr "Исполнение package()" | ||||||
|   | |||||||
| @@ -23,11 +23,62 @@ import "gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager" | |||||||
|  |  | ||||||
| type BuildOpts struct { | type BuildOpts struct { | ||||||
| 	Script      string | 	Script      string | ||||||
|  | 	Repository  string | ||||||
|  | 	Packages    []string | ||||||
| 	Manager     manager.Manager | 	Manager     manager.Manager | ||||||
| 	Clean       bool | 	Clean       bool | ||||||
| 	Interactive bool | 	Interactive bool | ||||||
| } | } | ||||||
|  |  | ||||||
|  | type BuildVarsPre struct { | ||||||
|  | 	Version       string   `sh:"version,required"` | ||||||
|  | 	Release       int      `sh:"release,required"` | ||||||
|  | 	Epoch         uint     `sh:"epoch"` | ||||||
|  | 	Description   string   `sh:"desc"` | ||||||
|  | 	Homepage      string   `sh:"homepage"` | ||||||
|  | 	Maintainer    string   `sh:"maintainer"` | ||||||
|  | 	Architectures []string `sh:"architectures"` | ||||||
|  | 	Licenses      []string `sh:"license"` | ||||||
|  | 	Provides      []string `sh:"provides"` | ||||||
|  | 	Conflicts     []string `sh:"conflicts"` | ||||||
|  | 	Depends       []string `sh:"deps"` | ||||||
|  | 	BuildDepends  []string `sh:"build_deps"` | ||||||
|  | 	OptDepends    []string `sh:"opt_deps"` | ||||||
|  | 	Replaces      []string `sh:"replaces"` | ||||||
|  | 	Sources       []string `sh:"sources"` | ||||||
|  | 	Checksums     []string `sh:"checksums"` | ||||||
|  | 	Backup        []string `sh:"backup"` | ||||||
|  | 	Scripts       Scripts  `sh:"scripts"` | ||||||
|  | 	AutoReq       []string `sh:"auto_req"` | ||||||
|  | 	AutoProv      []string `sh:"auto_prov"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (bv *BuildVarsPre) ToBuildVars() BuildVars { | ||||||
|  | 	return BuildVars{ | ||||||
|  | 		Name:          "", | ||||||
|  | 		Version:       bv.Version, | ||||||
|  | 		Release:       bv.Release, | ||||||
|  | 		Epoch:         bv.Epoch, | ||||||
|  | 		Description:   bv.Description, | ||||||
|  | 		Homepage:      bv.Homepage, | ||||||
|  | 		Maintainer:    bv.Maintainer, | ||||||
|  | 		Architectures: bv.Architectures, | ||||||
|  | 		Licenses:      bv.Licenses, | ||||||
|  | 		Provides:      bv.Provides, | ||||||
|  | 		Conflicts:     bv.Conflicts, | ||||||
|  | 		Depends:       bv.Depends, | ||||||
|  | 		BuildDepends:  bv.BuildDepends, | ||||||
|  | 		OptDepends:    bv.OptDepends, | ||||||
|  | 		Replaces:      bv.Replaces, | ||||||
|  | 		Sources:       bv.Sources, | ||||||
|  | 		Checksums:     bv.Checksums, | ||||||
|  | 		Backup:        bv.Backup, | ||||||
|  | 		Scripts:       bv.Scripts, | ||||||
|  | 		AutoReq:       bv.AutoReq, | ||||||
|  | 		AutoProv:      bv.AutoProv, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
| // BuildVars represents the script variables required | // BuildVars represents the script variables required | ||||||
| // to build a package | // to build a package | ||||||
| type BuildVars struct { | type BuildVars struct { | ||||||
| @@ -52,6 +103,7 @@ type BuildVars struct { | |||||||
| 	Scripts       Scripts  `sh:"scripts"` | 	Scripts       Scripts  `sh:"scripts"` | ||||||
| 	AutoReq       []string `sh:"auto_req"` | 	AutoReq       []string `sh:"auto_req"` | ||||||
| 	AutoProv      []string `sh:"auto_prov"` | 	AutoProv      []string `sh:"auto_prov"` | ||||||
|  | 	Base          string | ||||||
| } | } | ||||||
|  |  | ||||||
| type Scripts struct { | type Scripts struct { | ||||||
|   | |||||||
							
								
								
									
										16
									
								
								list.go
									
									
									
									
									
								
							
							
						
						
									
										16
									
								
								list.go
									
									
									
									
									
								
							| @@ -30,6 +30,7 @@ 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/pkg/build" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager" | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/repos" | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/repos" | ||||||
| ) | ) | ||||||
| @@ -78,7 +79,7 @@ func ListCmd() *cli.Command { | |||||||
| 			} | 			} | ||||||
| 			defer result.Close() | 			defer result.Close() | ||||||
|  |  | ||||||
| 			var installed map[string]string | 			installedAlrPackages := map[string]string{} | ||||||
| 			if c.Bool("installed") { | 			if c.Bool("installed") { | ||||||
| 				mgr := manager.Detect() | 				mgr := manager.Detect() | ||||||
| 				if mgr == nil { | 				if mgr == nil { | ||||||
| @@ -86,11 +87,20 @@ func ListCmd() *cli.Command { | |||||||
| 					os.Exit(1) | 					os.Exit(1) | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 				installed, err = mgr.ListInstalled(&manager.Opts{AsRoot: false}) | 				installed, err := mgr.ListInstalled(&manager.Opts{AsRoot: false}) | ||||||
| 				if err != nil { | 				if err != nil { | ||||||
| 					slog.Error(gotext.Get("Error listing installed packages"), "err", err) | 					slog.Error(gotext.Get("Error listing installed packages"), "err", err) | ||||||
| 					os.Exit(1) | 					os.Exit(1) | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
|  | 				for pkgName, version := range installed { | ||||||
|  | 					matches := build.RegexpALRPackageName.FindStringSubmatch(pkgName) | ||||||
|  | 					if matches != nil { | ||||||
|  | 						packageName := matches[build.RegexpALRPackageName.SubexpIndex("package")] | ||||||
|  | 						repoName := matches[build.RegexpALRPackageName.SubexpIndex("repo")] | ||||||
|  | 						installedAlrPackages[fmt.Sprintf("%s/%s", repoName, packageName)] = version | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			for result.Next() { | 			for result.Next() { | ||||||
| @@ -106,7 +116,7 @@ func ListCmd() *cli.Command { | |||||||
|  |  | ||||||
| 				version := pkg.Version | 				version := pkg.Version | ||||||
| 				if c.Bool("installed") { | 				if c.Bool("installed") { | ||||||
| 					instVersion, ok := installed[pkg.Name] | 					instVersion, ok := installedAlrPackages[fmt.Sprintf("%s/%s", pkg.Repository, pkg.Name)] | ||||||
| 					if !ok { | 					if !ok { | ||||||
| 						continue | 						continue | ||||||
| 					} else { | 					} else { | ||||||
|   | |||||||
							
								
								
									
										17
									
								
								main.go
									
									
									
									
									
								
							
							
						
						
									
										17
									
								
								main.go
									
									
									
									
									
								
							| @@ -31,8 +31,8 @@ import ( | |||||||
| 	"github.com/mattn/go-isatty" | 	"github.com/mattn/go-isatty" | ||||||
| 	"github.com/urfave/cli/v2" | 	"github.com/urfave/cli/v2" | ||||||
|  |  | ||||||
|  | 	"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/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/pkg/manager" | ||||||
|  |  | ||||||
| @@ -81,12 +81,14 @@ func GetApp() *cli.App { | |||||||
| 			GenCmd(), | 			GenCmd(), | ||||||
| 			HelperCmd(), | 			HelperCmd(), | ||||||
| 			VersionCmd(), | 			VersionCmd(), | ||||||
|  | 			SearchCmd(), | ||||||
| 		}, | 		}, | ||||||
| 		Before: func(c *cli.Context) error { | 		Before: func(c *cli.Context) error { | ||||||
| 			ctx := c.Context | 			ctx := c.Context | ||||||
|  | 			cfg := config.New() | ||||||
|  |  | ||||||
| 			cmd := c.Args().First() | 			cmd := c.Args().First() | ||||||
| 			if cmd != "helper" && !config.Config(ctx).Unsafe.AllowRunAsRoot && os.Geteuid() == 0 { | 			if cmd != "helper" && !cfg.AllowRunAsRoot(ctx) && os.Geteuid() == 0 { | ||||||
| 				slog.Error(gotext.Get("Running ALR as root is forbidden as it may cause catastrophic damage to your system")) | 				slog.Error(gotext.Get("Running ALR as root is forbidden as it may cause catastrophic damage to your system")) | ||||||
| 				os.Exit(1) | 				os.Exit(1) | ||||||
| 			} | 			} | ||||||
| @@ -98,9 +100,6 @@ func GetApp() *cli.App { | |||||||
|  |  | ||||||
| 			return nil | 			return nil | ||||||
| 		}, | 		}, | ||||||
| 		After: func(ctx *cli.Context) error { |  | ||||||
| 			return db.Close() |  | ||||||
| 		}, |  | ||||||
| 		EnableBashCompletion: true, | 		EnableBashCompletion: true, | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| @@ -110,15 +109,21 @@ func main() { | |||||||
| 	logger.SetupDefault() | 	logger.SetupDefault() | ||||||
|  |  | ||||||
| 	app := GetApp() | 	app := GetApp() | ||||||
|  | 	cfg := config.New() | ||||||
|  |  | ||||||
| 	ctx := context.Background() | 	ctx := context.Background() | ||||||
|  |  | ||||||
| 	// Set the root command to the one set in the ALR config | 	// Set the root command to the one set in the ALR config | ||||||
| 	manager.DefaultRootCmd = config.Config(ctx).RootCmd | 	manager.DefaultRootCmd = cfg.RootCmd(ctx) | ||||||
|  |  | ||||||
| 	ctx, cancel := signal.NotifyContext(ctx, syscall.SIGINT, syscall.SIGTERM) | 	ctx, cancel := signal.NotifyContext(ctx, syscall.SIGINT, syscall.SIGTERM) | ||||||
| 	defer cancel() | 	defer cancel() | ||||||
|  |  | ||||||
|  | 	// Make the application more internationalized | ||||||
|  | 	cli.AppHelpTemplate = cliutils.GetAppCliTemplate() | ||||||
|  | 	cli.CommandHelpTemplate = cliutils.GetCommandHelpTemplate() | ||||||
|  | 	cli.HelpFlag.(*cli.BoolFlag).Usage = gotext.Get("Show help") | ||||||
|  |  | ||||||
| 	err := app.RunContext(ctx, os.Args) | 	err := app.RunContext(ctx, os.Args) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		slog.Error(gotext.Get("Error while running app"), "err", err) | 		slog.Error(gotext.Get("Error while running app"), "err", err) | ||||||
|   | |||||||
							
								
								
									
										1025
									
								
								pkg/build/build.go
									
									
									
									
									
								
							
							
						
						
									
										1025
									
								
								pkg/build/build.go
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -18,10 +18,18 @@ package build | |||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"context" | 	"context" | ||||||
|  | 	"fmt" | ||||||
|  | 	"os" | ||||||
|  | 	"strings" | ||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
|  | 	"github.com/stretchr/testify/assert" | ||||||
|  | 	"mvdan.cc/sh/v3/syntax" | ||||||
|  |  | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/config" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/db" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/db" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/types" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/types" | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager" | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| @@ -134,93 +142,145 @@ func (m *TestManager) IsInstalled(pkg string) (bool, error) { | |||||||
| 	return true, nil | 	return true, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| // TODO: fix test | type TestConfig struct{} | ||||||
| func TestInstallBuildDeps(t *testing.T) { |  | ||||||
| 	type testEnv struct { |  | ||||||
| 		pf   PackageFinder |  | ||||||
| 		vars *types.BuildVars |  | ||||||
| 		opts types.BuildOpts |  | ||||||
|  |  | ||||||
| 		// Contains pkgs captured by FindPkgsFunc | func (c *TestConfig) PagerStyle(ctx context.Context) string { | ||||||
| 		// capturedPkgs []string | 	return "native" | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *TestConfig) GetPaths(ctx context.Context) *config.Paths { | ||||||
|  | 	return &config.Paths{ | ||||||
|  | 		CacheDir: "/tmp", | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestExecuteFirstPassIsSecure(t *testing.T) { | ||||||
|  | 	cfg := &TestConfig{} | ||||||
|  | 	pf := &TestPackageFinder{} | ||||||
|  | 	info := &distro.OSRelease{} | ||||||
|  | 	m := &TestManager{} | ||||||
|  |  | ||||||
|  | 	opts := types.BuildOpts{ | ||||||
|  | 		Manager:     m, | ||||||
|  | 		Interactive: false, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	ctx := context.Background() | ||||||
|  |  | ||||||
|  | 	b := NewBuilder( | ||||||
|  | 		ctx, | ||||||
|  | 		opts, | ||||||
|  | 		pf, | ||||||
|  | 		info, | ||||||
|  | 		cfg, | ||||||
|  | 	) | ||||||
|  |  | ||||||
|  | 	tmpFile, err := os.CreateTemp("", "testfile-") | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  | 	tmpFilePath := tmpFile.Name() | ||||||
|  | 	defer os.Remove(tmpFilePath) | ||||||
|  |  | ||||||
|  | 	_, err = os.Stat(tmpFilePath) | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  |  | ||||||
|  | 	testScript := fmt.Sprintf(`name='test' | ||||||
|  | version=1.0.0 | ||||||
|  | release=1 | ||||||
|  | rm -f %s`, tmpFilePath) | ||||||
|  |  | ||||||
|  | 	fl, err := syntax.NewParser().Parse(strings.NewReader(testScript), "alr.sh") | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  |  | ||||||
|  | 	_, _, err = b.executeFirstPass(fl) | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  |  | ||||||
|  | 	_, err = os.Stat(tmpFilePath) | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestExecuteFirstPassIsCorrect(t *testing.T) { | ||||||
| 	type testCase struct { | 	type testCase struct { | ||||||
| 		Name     string | 		Name     string | ||||||
| 		Prepare  func() *testEnv | 		Script   string | ||||||
| 		Expected func(t *testing.T, e *testEnv, res []string, err error) | 		Opts     types.BuildOpts | ||||||
|  | 		Expected func(t *testing.T, vars []*types.BuildVars) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	for _, tc := range []testCase{ | 	for _, tc := range []testCase{{ | ||||||
| 		/* | 		Name: "single package", | ||||||
| 			{ | 		Script: `name='test' | ||||||
| 				Name: "install only needed deps", | version='1.0.0' | ||||||
| 				Prepare: func() *testEnv { | release=1 | ||||||
| 					pf := TestPackageFinder{} | epoch=2 | ||||||
| 					vars := types.BuildVars{} | desc="Test package" | ||||||
| 					m := TestManager{} | homepage='https://example.com' | ||||||
| 					opts := types.BuildOpts{ | maintainer='Ivan Ivanov' | ||||||
| 						Manager:     &m, | `, | ||||||
| 						Interactive: false, | 		Opts: types.BuildOpts{ | ||||||
| 					} | 			Manager:     &TestManager{}, | ||||||
|  | 			Interactive: false, | ||||||
|  | 		}, | ||||||
|  | 		Expected: func(t *testing.T, vars []*types.BuildVars) { | ||||||
|  | 			assert.Equal(t, 1, len(vars)) | ||||||
|  | 			assert.Equal(t, vars[0].Name, "test") | ||||||
|  | 			assert.Equal(t, vars[0].Version, "1.0.0") | ||||||
|  | 			assert.Equal(t, vars[0].Release, int(1)) | ||||||
|  | 			assert.Equal(t, vars[0].Epoch, uint(2)) | ||||||
|  | 			assert.Equal(t, vars[0].Description, "Test package") | ||||||
|  | 		}, | ||||||
|  | 	}, { | ||||||
|  | 		Name: "multiple packages", | ||||||
|  | 		Script: `name=( | ||||||
|  | 	foo | ||||||
|  | 	bar | ||||||
|  | ) | ||||||
|  |  | ||||||
| 					env := &testEnv{ | version='0.0.1' | ||||||
| 						pf:           &pf, | release=1 | ||||||
| 						vars:         &vars, | epoch=2 | ||||||
| 						opts:         opts, | desc="Test package" | ||||||
| 						capturedPkgs: []string{}, |  | ||||||
| 					} |  | ||||||
|  |  | ||||||
| 					pf.FindPkgsFunc = func(ctx context.Context, pkgs []string) (map[string][]db.Package, []string, error) { | meta_foo() { | ||||||
| 						env.capturedPkgs = append(env.capturedPkgs, pkgs...) | 	desc="Foo package" | ||||||
| 						result := make(map[string][]db.Package) | } | ||||||
| 						result["bar"] = []db.Package{{ |  | ||||||
| 							Name: "bar-pkg", |  | ||||||
| 						}} |  | ||||||
| 						result["buz"] = []db.Package{{ |  | ||||||
| 							Name: "buz-pkg", |  | ||||||
| 						}} |  | ||||||
|  |  | ||||||
| 						return result, []string{}, nil | meta_bar() { | ||||||
| 					} |  | ||||||
|  |  | ||||||
| 					vars.BuildDepends = []string{ | } | ||||||
| 						"foo", | `, | ||||||
| 						"bar", | 		Opts: types.BuildOpts{ | ||||||
| 						"buz", | 			Packages:    []string{"foo"}, | ||||||
| 					} | 			Manager:     &TestManager{}, | ||||||
| 					m.IsInstalledFunc = func(pkg string) (bool, error) { | 			Interactive: false, | ||||||
| 						if pkg == "foo" { | 		}, | ||||||
| 							return true, nil | 		Expected: func(t *testing.T, vars []*types.BuildVars) { | ||||||
| 						} else { | 			assert.Equal(t, 1, len(vars)) | ||||||
| 							return false, nil | 			assert.Equal(t, vars[0].Name, "foo") | ||||||
| 						} | 			assert.Equal(t, vars[0].Description, "Foo package") | ||||||
| 					} | 		}, | ||||||
|  | 	}} { | ||||||
|  | 		t.Run(tc.Name, func(t *testing.T) { | ||||||
|  | 			cfg := &TestConfig{} | ||||||
|  | 			pf := &TestPackageFinder{} | ||||||
|  | 			info := &distro.OSRelease{} | ||||||
|  |  | ||||||
| 					return env |  | ||||||
| 				}, |  | ||||||
| 				Expected: func(t *testing.T, e *testEnv, res []string, err error) { |  | ||||||
| 					assert.NoError(t, err) |  | ||||||
| 					assert.Len(t, res, 2) |  | ||||||
| 					assert.ElementsMatch(t, res, []string{"bar-pkg", "buz-pkg"}) |  | ||||||
|  |  | ||||||
| 					assert.ElementsMatch(t, e.capturedPkgs, []string{"bar", "buz"}) |  | ||||||
| 				}, |  | ||||||
| 			}, |  | ||||||
| 		*/ |  | ||||||
| 	} { |  | ||||||
| 		t.Run(tc.Name, func(tt *testing.T) { |  | ||||||
| 			ctx := context.Background() | 			ctx := context.Background() | ||||||
| 			env := tc.Prepare() |  | ||||||
|  |  | ||||||
| 			result, err := installBuildDeps( | 			b := NewBuilder( | ||||||
| 				ctx, | 				ctx, | ||||||
| 				env.pf, | 				tc.Opts, | ||||||
| 				env.vars, | 				pf, | ||||||
| 				env.opts, | 				info, | ||||||
|  | 				cfg, | ||||||
| 			) | 			) | ||||||
|  |  | ||||||
| 			tc.Expected(tt, env, result, err) | 			fl, err := syntax.NewParser().Parse(strings.NewReader(tc.Script), "alr.sh") | ||||||
|  | 			assert.NoError(t, err) | ||||||
|  |  | ||||||
|  | 			_, allVars, err := b.executeFirstPass(fl) | ||||||
|  | 			assert.NoError(t, err) | ||||||
|  |  | ||||||
|  | 			tc.Expected(t, allVars) | ||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,84 +0,0 @@ | |||||||
| // This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan. |  | ||||||
| // It has been modified as part of "ALR - Any Linux Repository" by Евгений Храмов. |  | ||||||
| // |  | ||||||
| // ALR - Any Linux Repository |  | ||||||
| // Copyright (C) 2025 Евгений Храмов |  | ||||||
| // |  | ||||||
| // This program is free software: you can redistribute it and/or modify |  | ||||||
| // it under the terms of the GNU General Public License as published by |  | ||||||
| // the Free Software Foundation, either version 3 of the License, or |  | ||||||
| // (at your option) any later version. |  | ||||||
| // |  | ||||||
| // This program is distributed in the hope that it will be useful, |  | ||||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of |  | ||||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the |  | ||||||
| // GNU General Public License for more details. |  | ||||||
| // |  | ||||||
| // You should have received a copy of the GNU General Public License |  | ||||||
| // along with this program.  If not, see <http://www.gnu.org/licenses/>. |  | ||||||
|  |  | ||||||
| package build |  | ||||||
|  |  | ||||||
| import ( |  | ||||||
| 	"context" |  | ||||||
| 	"log/slog" |  | ||||||
| 	"os" |  | ||||||
| 	"path/filepath" |  | ||||||
|  |  | ||||||
| 	"github.com/leonelquinteros/gotext" |  | ||||||
|  |  | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/config" |  | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/db" |  | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/types" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| // InstallPkgs устанавливает нативные пакеты с использованием менеджера пакетов, |  | ||||||
| // затем строит и устанавливает пакеты ALR |  | ||||||
| func InstallPkgs(ctx context.Context, alrPkgs []db.Package, nativePkgs []string, opts types.BuildOpts) { |  | ||||||
| 	if len(nativePkgs) > 0 { |  | ||||||
| 		err := opts.Manager.Install(nil, nativePkgs...) |  | ||||||
| 		// Если есть нативные пакеты, выполняем их установку |  | ||||||
| 		if err != nil { |  | ||||||
| 			slog.Error(gotext.Get("Error installing native packages"), "err", err) |  | ||||||
| 			os.Exit(1) |  | ||||||
| 			// Логируем и завершаем выполнение при ошибке |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	InstallScripts(ctx, GetScriptPaths(ctx, alrPkgs), opts) |  | ||||||
| 	// Устанавливаем скрипты сборки через функцию InstallScripts |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // GetScriptPaths возвращает срез путей к скриптам, соответствующий |  | ||||||
| // данным пакетам |  | ||||||
| func GetScriptPaths(ctx context.Context, pkgs []db.Package) []string { |  | ||||||
| 	var scripts []string |  | ||||||
| 	for _, pkg := range pkgs { |  | ||||||
| 		// Для каждого пакета создаем путь к скрипту сборки |  | ||||||
| 		scriptPath := filepath.Join(config.GetPaths(ctx).RepoDir, pkg.Repository, pkg.Name, "alr.sh") |  | ||||||
| 		scripts = append(scripts, scriptPath) |  | ||||||
| 	} |  | ||||||
| 	return scripts |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // InstallScripts строит и устанавливает переданные alr скрипты сборки |  | ||||||
| func InstallScripts(ctx context.Context, scripts []string, opts types.BuildOpts) { |  | ||||||
| 	for _, script := range scripts { |  | ||||||
| 		opts.Script = script // Устанавливаем текущий скрипт в опции |  | ||||||
| 		builtPkgs, _, err := BuildPackage(ctx, opts) |  | ||||||
| 		// Выполняем сборку пакета |  | ||||||
| 		if err != nil { |  | ||||||
| 			slog.Error(gotext.Get("Error building package"), "err", err) |  | ||||||
| 			os.Exit(1) |  | ||||||
| 			// Логируем и завершаем выполнение при ошибке сборки |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		err = opts.Manager.InstallLocal(nil, builtPkgs...) |  | ||||||
| 		// Устанавливаем локально собранные пакеты |  | ||||||
| 		if err != nil { |  | ||||||
| 			slog.Error(gotext.Get("Error installing package"), "err", err) |  | ||||||
| 			os.Exit(1) |  | ||||||
| 			// Логируем и завершаем выполнение при ошибке установки |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
							
								
								
									
										336
									
								
								pkg/build/utils.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										336
									
								
								pkg/build/utils.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,336 @@ | |||||||
|  | // ALR - Any Linux Repository | ||||||
|  | // Copyright (C) 2025 Евгений Храмов | ||||||
|  | // | ||||||
|  | // This program is free software: you can redistribute it and/or modify | ||||||
|  | // it under the terms of the GNU General Public License as published by | ||||||
|  | // the Free Software Foundation, either version 3 of the License, or | ||||||
|  | // (at your option) any later version. | ||||||
|  | // | ||||||
|  | // This program is distributed in the hope that it will be useful, | ||||||
|  | // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | // GNU General Public License for more details. | ||||||
|  | // | ||||||
|  | // You should have received a copy of the GNU General Public License | ||||||
|  | // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  |  | ||||||
|  | package build | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"io" | ||||||
|  | 	"os" | ||||||
|  | 	"path/filepath" | ||||||
|  | 	"regexp" | ||||||
|  | 	"runtime" | ||||||
|  | 	"slices" | ||||||
|  | 	"strconv" | ||||||
|  | 	"strings" | ||||||
|  |  | ||||||
|  | 	// Импортируем пакеты для поддержки различных форматов пакетов (APK, DEB, RPM и ARCH). | ||||||
|  |  | ||||||
|  | 	_ "github.com/goreleaser/nfpm/v2/apk" | ||||||
|  | 	_ "github.com/goreleaser/nfpm/v2/arch" | ||||||
|  | 	_ "github.com/goreleaser/nfpm/v2/deb" | ||||||
|  | 	_ "github.com/goreleaser/nfpm/v2/rpm" | ||||||
|  | 	"mvdan.cc/sh/v3/syntax" | ||||||
|  |  | ||||||
|  | 	"github.com/goreleaser/nfpm/v2" | ||||||
|  | 	"github.com/goreleaser/nfpm/v2/files" | ||||||
|  |  | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/cpu" | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/db" | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/overrides" | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/types" | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro" | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // Функция readScript анализирует скрипт сборки с использованием встроенной реализации bash | ||||||
|  | func readScript(script string) (*syntax.File, error) { | ||||||
|  | 	fl, err := os.Open(script) // Открываем файл скрипта | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	defer fl.Close() // Закрываем файл после выполнения | ||||||
|  |  | ||||||
|  | 	file, err := syntax.NewParser().Parse(fl, "alr.sh") // Парсим скрипт с помощью синтаксического анализатора | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return file, nil // Возвращаем синтаксическое дерево | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Функция prepareDirs подготавливает директории для сборки. | ||||||
|  | func prepareDirs(dirs types.Directories) error { | ||||||
|  | 	err := os.RemoveAll(dirs.BaseDir) // Удаляем базовую директорию, если она существует | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	err = os.MkdirAll(dirs.SrcDir, 0o755) // Создаем директорию для источников | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	return os.MkdirAll(dirs.PkgDir, 0o755) // Создаем директорию для пакетов | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Функция buildContents создает секцию содержимого пакета, которая содержит файлы, | ||||||
|  | // которые будут включены в конечный пакет. | ||||||
|  | func buildContents(vars *types.BuildVars, dirs types.Directories, preferedContents *[]string) ([]*files.Content, error) { | ||||||
|  | 	contents := []*files.Content{} | ||||||
|  |  | ||||||
|  | 	processPath := func(path, trimmed string, prefered bool) error { | ||||||
|  | 		fi, err := os.Lstat(path) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if fi.IsDir() { | ||||||
|  | 			f, err := os.Open(path) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  | 			defer f.Close() | ||||||
|  |  | ||||||
|  | 			if !prefered { | ||||||
|  | 				_, err = f.Readdirnames(1) | ||||||
|  | 				if err != io.EOF { | ||||||
|  | 					return nil | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			contents = append(contents, &files.Content{ | ||||||
|  | 				Source:      path, | ||||||
|  | 				Destination: trimmed, | ||||||
|  | 				Type:        "dir", | ||||||
|  | 				FileInfo: &files.ContentFileInfo{ | ||||||
|  | 					MTime: fi.ModTime(), | ||||||
|  | 				}, | ||||||
|  | 			}) | ||||||
|  | 			return nil | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if fi.Mode()&os.ModeSymlink != 0 { | ||||||
|  | 			link, err := os.Readlink(path) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  | 			link = strings.TrimPrefix(link, dirs.PkgDir) | ||||||
|  |  | ||||||
|  | 			contents = append(contents, &files.Content{ | ||||||
|  | 				Source:      link, | ||||||
|  | 				Destination: trimmed, | ||||||
|  | 				Type:        "symlink", | ||||||
|  | 				FileInfo: &files.ContentFileInfo{ | ||||||
|  | 					MTime: fi.ModTime(), | ||||||
|  | 					Mode:  fi.Mode(), | ||||||
|  | 				}, | ||||||
|  | 			}) | ||||||
|  | 			return nil | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		fileContent := &files.Content{ | ||||||
|  | 			Source:      path, | ||||||
|  | 			Destination: trimmed, | ||||||
|  | 			FileInfo: &files.ContentFileInfo{ | ||||||
|  | 				MTime: fi.ModTime(), | ||||||
|  | 				Mode:  fi.Mode(), | ||||||
|  | 				Size:  fi.Size(), | ||||||
|  | 			}, | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if slices.Contains(vars.Backup, trimmed) { | ||||||
|  | 			fileContent.Type = "config|noreplace" | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		contents = append(contents, fileContent) | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if preferedContents != nil { | ||||||
|  | 		for _, trimmed := range *preferedContents { | ||||||
|  | 			path := filepath.Join(dirs.PkgDir, trimmed) | ||||||
|  | 			if err := processPath(path, trimmed, true); err != nil { | ||||||
|  | 				return nil, err | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} else { | ||||||
|  | 		err := filepath.Walk(dirs.PkgDir, func(path string, fi os.FileInfo, err error) error { | ||||||
|  | 			if err != nil { | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  | 			trimmed := strings.TrimPrefix(path, dirs.PkgDir) | ||||||
|  | 			return processPath(path, trimmed, false) | ||||||
|  | 		}) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return contents, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | var RegexpALRPackageName = regexp.MustCompile(`^(?P<package>[^+]+)\+alr-(?P<repo>.+)$`) | ||||||
|  |  | ||||||
|  | func getBasePkgInfo(vars *types.BuildVars, info *distro.OSRelease, opts *types.BuildOpts) *nfpm.Info { | ||||||
|  | 	return &nfpm.Info{ | ||||||
|  | 		Name:    fmt.Sprintf("%s+alr-%s", vars.Name, opts.Repository), | ||||||
|  | 		Arch:    cpu.Arch(), | ||||||
|  | 		Version: vars.Version, | ||||||
|  | 		Release: overrides.ReleasePlatformSpecific(vars.Release, info), | ||||||
|  | 		Epoch:   strconv.FormatUint(uint64(vars.Epoch), 10), | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Функция getPkgFormat возвращает формат пакета из менеджера пакетов, | ||||||
|  | // или ALR_PKG_FORMAT, если он установлен. | ||||||
|  | func getPkgFormat(mgr manager.Manager) string { | ||||||
|  | 	pkgFormat := mgr.Format() | ||||||
|  | 	if format, ok := os.LookupEnv("ALR_PKG_FORMAT"); ok { | ||||||
|  | 		pkgFormat = format | ||||||
|  | 	} | ||||||
|  | 	return pkgFormat | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Функция createBuildEnvVars создает переменные окружения, которые будут установлены | ||||||
|  | // в скрипте сборки при его выполнении. | ||||||
|  | func createBuildEnvVars(info *distro.OSRelease, dirs types.Directories) []string { | ||||||
|  | 	env := os.Environ() | ||||||
|  |  | ||||||
|  | 	env = append( | ||||||
|  | 		env, | ||||||
|  | 		"DISTRO_NAME="+info.Name, | ||||||
|  | 		"DISTRO_PRETTY_NAME="+info.PrettyName, | ||||||
|  | 		"DISTRO_ID="+info.ID, | ||||||
|  | 		"DISTRO_VERSION_ID="+info.VersionID, | ||||||
|  | 		"DISTRO_ID_LIKE="+strings.Join(info.Like, " "), | ||||||
|  | 		"ARCH="+cpu.Arch(), | ||||||
|  | 		"NCPU="+strconv.Itoa(runtime.NumCPU()), | ||||||
|  | 	) | ||||||
|  |  | ||||||
|  | 	if dirs.ScriptDir != "" { | ||||||
|  | 		env = append(env, "scriptdir="+dirs.ScriptDir) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if dirs.PkgDir != "" { | ||||||
|  | 		env = append(env, "pkgdir="+dirs.PkgDir) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if dirs.SrcDir != "" { | ||||||
|  | 		env = append(env, "srcdir="+dirs.SrcDir) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return env | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Функция setScripts добавляет скрипты-перехватчики к метаданным пакета. | ||||||
|  | func setScripts(vars *types.BuildVars, info *nfpm.Info, scriptDir string) { | ||||||
|  | 	if vars.Scripts.PreInstall != "" { | ||||||
|  | 		info.Scripts.PreInstall = filepath.Join(scriptDir, vars.Scripts.PreInstall) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if vars.Scripts.PostInstall != "" { | ||||||
|  | 		info.Scripts.PostInstall = filepath.Join(scriptDir, vars.Scripts.PostInstall) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if vars.Scripts.PreRemove != "" { | ||||||
|  | 		info.Scripts.PreRemove = filepath.Join(scriptDir, vars.Scripts.PreRemove) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if vars.Scripts.PostRemove != "" { | ||||||
|  | 		info.Scripts.PostRemove = filepath.Join(scriptDir, vars.Scripts.PostRemove) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if vars.Scripts.PreUpgrade != "" { | ||||||
|  | 		info.ArchLinux.Scripts.PreUpgrade = filepath.Join(scriptDir, vars.Scripts.PreUpgrade) | ||||||
|  | 		info.APK.Scripts.PreUpgrade = filepath.Join(scriptDir, vars.Scripts.PreUpgrade) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if vars.Scripts.PostUpgrade != "" { | ||||||
|  | 		info.ArchLinux.Scripts.PostUpgrade = filepath.Join(scriptDir, vars.Scripts.PostUpgrade) | ||||||
|  | 		info.APK.Scripts.PostUpgrade = filepath.Join(scriptDir, vars.Scripts.PostUpgrade) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if vars.Scripts.PreTrans != "" { | ||||||
|  | 		info.RPM.Scripts.PreTrans = filepath.Join(scriptDir, vars.Scripts.PreTrans) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if vars.Scripts.PostTrans != "" { | ||||||
|  | 		info.RPM.Scripts.PostTrans = filepath.Join(scriptDir, vars.Scripts.PostTrans) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /* | ||||||
|  | // Функция setVersion изменяет переменную версии в скрипте runner. | ||||||
|  | // Она используется для установки версии на вывод функции version(). | ||||||
|  | func setVersion(ctx context.Context, r *interp.Runner, to string) error { | ||||||
|  | 	fl, err := syntax.NewParser().Parse(strings.NewReader("version='"+to+"'"), "") | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	return r.Run(ctx, fl) | ||||||
|  | } | ||||||
|  | */ | ||||||
|  | // Returns not installed dependencies | ||||||
|  | func removeAlreadyInstalled(opts types.BuildOpts, dependencies []string) ([]string, error) { | ||||||
|  | 	filteredPackages := []string{} | ||||||
|  |  | ||||||
|  | 	for _, dep := range dependencies { | ||||||
|  | 		installed, err := opts.Manager.IsInstalled(dep) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 		if installed { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 		filteredPackages = append(filteredPackages, dep) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return filteredPackages, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Функция packageNames возвращает имена всех предоставленных пакетов. | ||||||
|  | func packageNames(pkgs []db.Package) []string { | ||||||
|  | 	names := make([]string, len(pkgs)) | ||||||
|  | 	for i, p := range pkgs { | ||||||
|  | 		names[i] = p.Name | ||||||
|  | 	} | ||||||
|  | 	return names | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Функция removeDuplicates убирает любые дубликаты из предоставленного среза. | ||||||
|  | func removeDuplicates(slice []string) []string { | ||||||
|  | 	seen := map[string]struct{}{} | ||||||
|  | 	result := []string{} | ||||||
|  |  | ||||||
|  | 	for _, s := range slice { | ||||||
|  | 		if _, ok := seen[s]; !ok { | ||||||
|  | 			seen[s] = struct{}{} | ||||||
|  | 			result = append(result, s) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return result | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func removeDuplicatesSources(sources, checksums []string) ([]string, []string) { | ||||||
|  | 	seen := map[string]string{} | ||||||
|  | 	keys := make([]string, 0) | ||||||
|  | 	for i, s := range sources { | ||||||
|  | 		if val, ok := seen[s]; !ok || strings.EqualFold(val, "SKIP") { | ||||||
|  | 			if !ok { | ||||||
|  | 				keys = append(keys, s) | ||||||
|  | 			} | ||||||
|  | 			seen[s] = checksums[i] | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	newSources := make([]string, len(keys)) | ||||||
|  | 	newChecksums := make([]string, len(keys)) | ||||||
|  | 	for i, k := range keys { | ||||||
|  | 		newSources[i] = k | ||||||
|  | 		newChecksums[i] = seen[k] | ||||||
|  | 	} | ||||||
|  | 	return newSources, newChecksums | ||||||
|  | } | ||||||
| @@ -14,39 +14,34 @@ | |||||||
| // You should have received a copy of the GNU General Public License | // You should have received a copy of the GNU General Public License | ||||||
| // along with this program.  If not, see <http://www.gnu.org/licenses/>. | // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
| 
 | 
 | ||||||
| package config | package build | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"context" | 	"testing" | ||||||
| 	"sync" |  | ||||||
| 
 | 
 | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/types" | 	"github.com/stretchr/testify/assert" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // Config returns a ALR configuration struct. | func TestRemoveDuplicatesSources(t *testing.T) { | ||||||
| // The first time it's called, it'll load the config from a file. | 	type testCase struct { | ||||||
| // Subsequent calls will just return the same value. | 		Name         string | ||||||
| // | 		Sources      []string | ||||||
| // Deprecated: use struct method | 		Checksums    []string | ||||||
| func Config(ctx context.Context) *types.Config { | 		NewSources   []string | ||||||
| 	return GetInstance(ctx).cfg | 		NewChecksums []string | ||||||
| } | 	} | ||||||
| 
 | 
 | ||||||
| // ======================= | 	for _, tc := range []testCase{{ | ||||||
| // FOR LEGACY ONLY | 		Name:         "prefer non-skip values", | ||||||
| // ======================= | 		Sources:      []string{"a", "b", "c", "a"}, | ||||||
| 
 | 		Checksums:    []string{"skip", "skip", "skip", "1"}, | ||||||
| var ( | 		NewSources:   []string{"a", "b", "c"}, | ||||||
| 	alrConfig     *ALRConfig | 		NewChecksums: []string{"1", "skip", "skip"}, | ||||||
| 	alrConfigOnce sync.Once | 	}} { | ||||||
| ) | 		t.Run(tc.Name, func(t *testing.T) { | ||||||
| 
 | 			s, c := removeDuplicatesSources(tc.Sources, tc.Checksums) | ||||||
| // Deprecated: For legacy only | 			assert.Equal(t, s, tc.NewSources) | ||||||
| func GetInstance(ctx context.Context) *ALRConfig { | 			assert.Equal(t, c, tc.NewChecksums) | ||||||
| 	alrConfigOnce.Do(func() { | 		}) | ||||||
| 		alrConfig = New() | 	} | ||||||
| 		alrConfig.Load(ctx) |  | ||||||
| 	}) |  | ||||||
| 
 |  | ||||||
| 	return alrConfig |  | ||||||
| } | } | ||||||
| @@ -79,7 +79,7 @@ func ParseOSRelease(ctx context.Context) (*OSRelease, error) { | |||||||
| 	runner, err := interp.New( | 	runner, err := interp.New( | ||||||
| 		interp.OpenHandler(handlers.NopOpen), | 		interp.OpenHandler(handlers.NopOpen), | ||||||
| 		interp.ExecHandler(handlers.NopExec), | 		interp.ExecHandler(handlers.NopExec), | ||||||
| 		interp.ReadDirHandler(handlers.NopReadDir), | 		interp.ReadDirHandler2(handlers.NopReadDir), | ||||||
| 		interp.StatHandler(handlers.NopStat), | 		interp.StatHandler(handlers.NopStat), | ||||||
| 		interp.Env(expand.ListEnviron()), | 		interp.Env(expand.ListEnviron()), | ||||||
| 	) | 	) | ||||||
|   | |||||||
| @@ -32,19 +32,19 @@ deps=("python3") | |||||||
| deps_arch=("python") | deps_arch=("python") | ||||||
| deps_alpine=("python3") | deps_alpine=("python3") | ||||||
|  |  | ||||||
| build_deps=("python3" "python3-setuptools") | build_deps=("python3" "python3-pip") | ||||||
| build_deps_arch=("python" "python-setuptools") | build_deps_arch=("python" "python-pip") | ||||||
| build_deps_alpine=("python3" "py3-setuptools") | build_deps_alpine=("python3" "py3-pip") | ||||||
|  |  | ||||||
| sources=("https://files.pythonhosted.org/packages/source/{{.SourceURL.Filename | firstchar}}/{{.Info.Name}}/{{.SourceURL.Filename}}") | sources=("https://files.pythonhosted.org/packages/source/{{.SourceURL.Filename | firstchar}}/{{.Info.Name}}/{{.SourceURL.Filename}}") | ||||||
| checksums=('blake2b-256:{{.SourceURL.Digests.blake2b_256}}') | checksums=('blake2b-256:{{.SourceURL.Digests.blake2b_256}}') | ||||||
|  |  | ||||||
| build() { | build() { | ||||||
| 	cd "$srcdir/{{.Info.Name}}-${version}" | 	cd "$srcdir/{{.Info.Name}}-${version}" | ||||||
| 	python3 setup.py build |   python3 -m build | ||||||
| } | } | ||||||
|  |  | ||||||
| package() { | package() { | ||||||
| 	cd "$srcdir/{{.Info.Name}}-${version}" | 	cd "$srcdir/{{.Info.Name}}-${version}" | ||||||
| 	python3 setup.py install --root="${pkgdir}/" --optimize=1 || return 1 | 	pip install --root="${pkgdir}/" . --no-deps --disable-pip-version-check | ||||||
| } | } | ||||||
|   | |||||||
| @@ -22,6 +22,8 @@ package repos | |||||||
| import ( | import ( | ||||||
| 	"context" | 	"context" | ||||||
| 	"errors" | 	"errors" | ||||||
|  | 	"fmt" | ||||||
|  | 	"io" | ||||||
| 	"log/slog" | 	"log/slog" | ||||||
| 	"net/url" | 	"net/url" | ||||||
| 	"os" | 	"os" | ||||||
| @@ -41,8 +43,10 @@ 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/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/types" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/types" | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| type actionType uint8 | type actionType uint8 | ||||||
| @@ -73,7 +77,7 @@ func (rs *Repos) Pull(ctx context.Context, repos []types.Repo) error { | |||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		slog.Info(gotext.Get("Pulling repository"), "name", repo.Name) | 		slog.Info(gotext.Get("Pulling repository"), "name", repo.Name) | ||||||
| 		repoDir := filepath.Join(config.GetPaths(ctx).RepoDir, repo.Name) | 		repoDir := filepath.Join(rs.cfg.GetPaths(ctx).RepoDir, repo.Name) | ||||||
|  |  | ||||||
| 		var repoFS billy.Filesystem | 		var repoFS billy.Filesystem | ||||||
| 		gitDir := filepath.Join(repoDir, ".git") | 		gitDir := filepath.Join(repoDir, ".git") | ||||||
| @@ -177,6 +181,96 @@ func (rs *Repos) Pull(ctx context.Context, repos []types.Repo) error { | |||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (rs *Repos) updatePkg(ctx context.Context, repo types.Repo, runner *interp.Runner, scriptFl io.ReadCloser) error { | ||||||
|  | 	parser := syntax.NewParser() | ||||||
|  |  | ||||||
|  | 	defer scriptFl.Close() | ||||||
|  | 	fl, err := parser.Parse(scriptFl, "alr.sh") | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	runner.Reset() | ||||||
|  | 	err = runner.Run(ctx, fl) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	type packages struct { | ||||||
|  | 		BasePkgName string   `sh:"basepkg_name"` | ||||||
|  | 		Names       []string `sh:"name"` | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	var pkgs packages | ||||||
|  |  | ||||||
|  | 	d := decoder.New(&distro.OSRelease{}, runner) | ||||||
|  | 	d.Overrides = false | ||||||
|  | 	d.LikeDistros = false | ||||||
|  | 	err = d.DecodeVars(&pkgs) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if len(pkgs.Names) > 1 { | ||||||
|  | 		if pkgs.BasePkgName == "" { | ||||||
|  | 			pkgs.BasePkgName = pkgs.Names[0] | ||||||
|  | 		} | ||||||
|  | 		for _, pkgName := range pkgs.Names { | ||||||
|  | 			pkgInfo := PackageInfo{} | ||||||
|  | 			funcName := fmt.Sprintf("meta_%s", pkgName) | ||||||
|  | 			runner.Reset() | ||||||
|  | 			err = runner.Run(ctx, fl) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  | 			meta, ok := d.GetFuncWithSubshell(funcName) | ||||||
|  | 			if !ok { | ||||||
|  | 				return errors.New("func is missing") | ||||||
|  | 			} | ||||||
|  | 			r, err := meta(ctx) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  | 			d := decoder.New(&distro.OSRelease{}, r) | ||||||
|  | 			d.Overrides = false | ||||||
|  | 			d.LikeDistros = false | ||||||
|  | 			err = d.DecodeVars(&pkgInfo) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  | 			pkg := pkgInfo.ToPackage(repo.Name) | ||||||
|  | 			resolveOverrides(r, pkg) | ||||||
|  | 			pkg.Name = pkgName | ||||||
|  | 			pkg.BasePkgName = pkgs.BasePkgName | ||||||
|  | 			err = rs.db.InsertPackage(ctx, *pkg) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	pkg := EmptyPackage(repo.Name) | ||||||
|  | 	err = d.DecodeVars(pkg) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	resolveOverrides(runner, pkg) | ||||||
|  | 	return rs.db.InsertPackage(ctx, *pkg) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (rs *Repos) processRepoChangesRunner(repoDir, scriptDir string) (*interp.Runner, error) { | ||||||
|  | 	env := append(os.Environ(), "scriptdir="+scriptDir) | ||||||
|  | 	return interp.New( | ||||||
|  | 		interp.Env(expand.ListEnviron(env...)), | ||||||
|  | 		interp.ExecHandler(handlers.NopExec), | ||||||
|  | 		interp.ReadDirHandler2(handlers.RestrictedReadDir(repoDir)), | ||||||
|  | 		interp.StatHandler(handlers.RestrictedStat(repoDir)), | ||||||
|  | 		interp.OpenHandler(handlers.RestrictedOpen(repoDir)), | ||||||
|  | 		interp.StdIO(handlers.NopRWC{}, handlers.NopRWC{}, handlers.NopRWC{}), | ||||||
|  | 	) | ||||||
|  | } | ||||||
|  |  | ||||||
| func (rs *Repos) processRepoChanges(ctx context.Context, repo types.Repo, r *git.Repository, w *git.Worktree, old, new *plumbing.Reference) error { | func (rs *Repos) processRepoChanges(ctx context.Context, repo types.Repo, r *git.Repository, w *git.Worktree, old, new *plumbing.Reference) error { | ||||||
| 	oldCommit, err := r.CommitObject(old.Hash()) | 	oldCommit, err := r.CommitObject(old.Hash()) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| @@ -235,15 +329,7 @@ func (rs *Repos) processRepoChanges(ctx context.Context, repo types.Repo, r *git | |||||||
| 	parser := syntax.NewParser() | 	parser := syntax.NewParser() | ||||||
|  |  | ||||||
| 	for _, action := range actions { | 	for _, action := range actions { | ||||||
| 		env := append(os.Environ(), "scriptdir="+filepath.Dir(filepath.Join(repoDir, action.File))) | 		runner, err := rs.processRepoChangesRunner(repoDir, filepath.Dir(filepath.Join(repoDir, action.File))) | ||||||
| 		runner, err := interp.New( |  | ||||||
| 			interp.Env(expand.ListEnviron(env...)), |  | ||||||
| 			interp.ExecHandler(handlers.NopExec), |  | ||||||
| 			interp.ReadDirHandler(handlers.RestrictedReadDir(repoDir)), |  | ||||||
| 			interp.StatHandler(handlers.RestrictedStat(repoDir)), |  | ||||||
| 			interp.OpenHandler(handlers.RestrictedOpen(repoDir)), |  | ||||||
| 			interp.StdIO(handlers.NopRWC{}, handlers.NopRWC{}, handlers.NopRWC{}), |  | ||||||
| 		) |  | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
| @@ -289,23 +375,7 @@ func (rs *Repos) processRepoChanges(ctx context.Context, repo types.Repo, r *git | |||||||
| 				return nil | 				return nil | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			pkg := db.Package{ | 			err = rs.updatePkg(ctx, repo, runner, r) | ||||||
| 				Description:  db.NewJSON(map[string]string{}), |  | ||||||
| 				Homepage:     db.NewJSON(map[string]string{}), |  | ||||||
| 				Maintainer:   db.NewJSON(map[string]string{}), |  | ||||||
| 				Depends:      db.NewJSON(map[string][]string{}), |  | ||||||
| 				BuildDepends: db.NewJSON(map[string][]string{}), |  | ||||||
| 				Repository:   repo.Name, |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			err = parseScript(ctx, parser, runner, r, &pkg) |  | ||||||
| 			if err != nil { |  | ||||||
| 				return err |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			resolveOverrides(runner, &pkg) |  | ||||||
|  |  | ||||||
| 			err = rs.db.InsertPackage(ctx, pkg) |  | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				return err | 				return err | ||||||
| 			} | 			} | ||||||
| @@ -322,18 +392,8 @@ func (rs *Repos) processRepoFull(ctx context.Context, repo types.Repo, repoDir s | |||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	parser := syntax.NewParser() |  | ||||||
|  |  | ||||||
| 	for _, match := range matches { | 	for _, match := range matches { | ||||||
| 		env := append(os.Environ(), "scriptdir="+filepath.Dir(match)) | 		runner, err := rs.processRepoChangesRunner(repoDir, filepath.Dir(match)) | ||||||
| 		runner, err := interp.New( |  | ||||||
| 			interp.Env(expand.ListEnviron(env...)), |  | ||||||
| 			interp.ExecHandler(handlers.NopExec), |  | ||||||
| 			interp.ReadDirHandler(handlers.RestrictedReadDir(repoDir)), |  | ||||||
| 			interp.StatHandler(handlers.RestrictedStat(repoDir)), |  | ||||||
| 			interp.OpenHandler(handlers.RestrictedOpen(repoDir)), |  | ||||||
| 			interp.StdIO(handlers.NopRWC{}, handlers.NopRWC{}, handlers.NopRWC{}), |  | ||||||
| 		) |  | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
| @@ -343,23 +403,7 @@ func (rs *Repos) processRepoFull(ctx context.Context, repo types.Repo, repoDir s | |||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		pkg := db.Package{ | 		err = rs.updatePkg(ctx, repo, runner, scriptFl) | ||||||
| 			Description:  db.NewJSON(map[string]string{}), |  | ||||||
| 			Homepage:     db.NewJSON(map[string]string{}), |  | ||||||
| 			Maintainer:   db.NewJSON(map[string]string{}), |  | ||||||
| 			Depends:      db.NewJSON(map[string][]string{}), |  | ||||||
| 			BuildDepends: db.NewJSON(map[string][]string{}), |  | ||||||
| 			Repository:   repo.Name, |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		err = parseScript(ctx, parser, runner, scriptFl, &pkg) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return err |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		resolveOverrides(runner, &pkg) |  | ||||||
|  |  | ||||||
| 		err = rs.db.InsertPackage(ctx, pkg) |  | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
|   | |||||||
							
								
								
									
										173
									
								
								pkg/repos/pull_internal_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										173
									
								
								pkg/repos/pull_internal_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,173 @@ | |||||||
|  | // ALR - Any Linux Repository | ||||||
|  | // Copyright (C) 2025 Евгений Храмов | ||||||
|  | // | ||||||
|  | // This program is free software: you can redistribute it and/or modify | ||||||
|  | // it under the terms of the GNU General Public License as published by | ||||||
|  | // the Free Software Foundation, either version 3 of the License, or | ||||||
|  | // (at your option) any later version. | ||||||
|  | // | ||||||
|  | // This program is distributed in the hope that it will be useful, | ||||||
|  | // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | // GNU General Public License for more details. | ||||||
|  | // | ||||||
|  | // You should have received a copy of the GNU General Public License | ||||||
|  | // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  |  | ||||||
|  | package repos | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 	"io" | ||||||
|  | 	"os" | ||||||
|  | 	"strings" | ||||||
|  | 	"testing" | ||||||
|  |  | ||||||
|  | 	"github.com/stretchr/testify/assert" | ||||||
|  |  | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/config" | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/db" | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/types" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type TestALRConfig struct{} | ||||||
|  |  | ||||||
|  | func (c *TestALRConfig) GetPaths(ctx context.Context) *config.Paths { | ||||||
|  | 	return &config.Paths{ | ||||||
|  | 		DBPath: ":memory:", | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (c *TestALRConfig) Repos(ctx context.Context) []types.Repo { | ||||||
|  | 	return []types.Repo{ | ||||||
|  | 		{ | ||||||
|  | 			Name: "test", | ||||||
|  | 			URL:  "https://test", | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func createReadCloserFromString(input string) io.ReadCloser { | ||||||
|  | 	reader := strings.NewReader(input) | ||||||
|  | 	return struct { | ||||||
|  | 		io.Reader | ||||||
|  | 		io.Closer | ||||||
|  | 	}{ | ||||||
|  | 		Reader: reader, | ||||||
|  | 		Closer: io.NopCloser(reader), | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestUpdatePkg(t *testing.T) { | ||||||
|  | 	type testCase struct { | ||||||
|  | 		name   string | ||||||
|  | 		file   string | ||||||
|  | 		verify func(context.Context, *db.Database) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	repo := types.Repo{ | ||||||
|  | 		Name: "test", | ||||||
|  | 		URL:  "https://test", | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for _, tc := range []testCase{ | ||||||
|  | 		{ | ||||||
|  | 			name: "single package", | ||||||
|  | 			file: `name=foo | ||||||
|  | version='0.0.1' | ||||||
|  | release=1 | ||||||
|  | desc="main desc" | ||||||
|  | deps=('sudo') | ||||||
|  | build_deps=('golang') | ||||||
|  | `, | ||||||
|  | 			verify: func(ctx context.Context, database *db.Database) { | ||||||
|  | 				result, err := database.GetPkgs(ctx, "1 = 1") | ||||||
|  | 				assert.NoError(t, err) | ||||||
|  | 				pkgCount := 0 | ||||||
|  | 				for result.Next() { | ||||||
|  | 					var dbPkg db.Package | ||||||
|  | 					err = result.StructScan(&dbPkg) | ||||||
|  | 					if err != nil { | ||||||
|  | 						t.Errorf("Expected no error, got %s", err) | ||||||
|  | 					} | ||||||
|  |  | ||||||
|  | 					assert.Equal(t, "foo", dbPkg.Name) | ||||||
|  | 					assert.Equal(t, db.NewJSON(map[string]string{"": "main desc"}), dbPkg.Description) | ||||||
|  | 					assert.Equal(t, db.NewJSON(map[string][]string{"": {"sudo"}}), dbPkg.Depends) | ||||||
|  | 					pkgCount++ | ||||||
|  | 				} | ||||||
|  | 				assert.Equal(t, 1, pkgCount) | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name: "multiple package", | ||||||
|  | 			file: `basepkg_name=foo | ||||||
|  | name=( | ||||||
|  | 	bar | ||||||
|  | 	buz | ||||||
|  | ) | ||||||
|  | version='0.0.1' | ||||||
|  | release=1 | ||||||
|  | desc="main desc" | ||||||
|  | deps=('sudo') | ||||||
|  | build_deps=('golang') | ||||||
|  | 		 | ||||||
|  | meta_bar() { | ||||||
|  | 	desc="foo desc" | ||||||
|  | } | ||||||
|  | 			 | ||||||
|  | meta_buz() { | ||||||
|  | 	deps+=('doas') | ||||||
|  | } | ||||||
|  | `, | ||||||
|  | 			verify: func(ctx context.Context, database *db.Database) { | ||||||
|  | 				result, err := database.GetPkgs(ctx, "1 = 1") | ||||||
|  | 				assert.NoError(t, err) | ||||||
|  |  | ||||||
|  | 				pkgCount := 0 | ||||||
|  | 				for result.Next() { | ||||||
|  | 					var dbPkg db.Package | ||||||
|  | 					err = result.StructScan(&dbPkg) | ||||||
|  | 					if err != nil { | ||||||
|  | 						t.Errorf("Expected no error, got %s", err) | ||||||
|  | 					} | ||||||
|  | 					if dbPkg.Name == "bar" { | ||||||
|  | 						assert.Equal(t, db.NewJSON(map[string]string{"": "foo desc"}), dbPkg.Description) | ||||||
|  | 						assert.Equal(t, db.NewJSON(map[string][]string{"": {"sudo"}}), dbPkg.Depends) | ||||||
|  | 					} | ||||||
|  |  | ||||||
|  | 					if dbPkg.Name == "buz" { | ||||||
|  | 						assert.Equal(t, db.NewJSON(map[string]string{"": "main desc"}), dbPkg.Description) | ||||||
|  | 						assert.Equal(t, db.NewJSON(map[string][]string{"": {"sudo", "doas"}}), dbPkg.Depends) | ||||||
|  | 					} | ||||||
|  | 					pkgCount++ | ||||||
|  | 				} | ||||||
|  | 				assert.Equal(t, 2, pkgCount) | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 	} { | ||||||
|  | 		t.Run(tc.name, func(t *testing.T) { | ||||||
|  | 			cfg := &TestALRConfig{} | ||||||
|  | 			ctx := context.Background() | ||||||
|  |  | ||||||
|  | 			database := db.New(&TestALRConfig{}) | ||||||
|  | 			database.Init(ctx) | ||||||
|  |  | ||||||
|  | 			rs := New(cfg, database) | ||||||
|  |  | ||||||
|  | 			path, err := os.MkdirTemp("", "test-update-pkg") | ||||||
|  | 			assert.NoError(t, err) | ||||||
|  | 			defer os.RemoveAll(path) | ||||||
|  |  | ||||||
|  | 			runner, err := rs.processRepoChangesRunner(path, path) | ||||||
|  | 			assert.NoError(t, err) | ||||||
|  |  | ||||||
|  | 			err = rs.updatePkg(ctx, repo, runner, createReadCloserFromString( | ||||||
|  | 				tc.file, | ||||||
|  | 			)) | ||||||
|  | 			assert.NoError(t, err) | ||||||
|  |  | ||||||
|  | 			tc.verify(ctx, database) | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @@ -1,70 +0,0 @@ | |||||||
| // ALR - Any Linux Repository |  | ||||||
| // Copyright (C) 2025 Евгений Храмов |  | ||||||
| // |  | ||||||
| // This program is free software: you can redistribute it and/or modify |  | ||||||
| // it under the terms of the GNU General Public License as published by |  | ||||||
| // the Free Software Foundation, either version 3 of the License, or |  | ||||||
| // (at your option) any later version. |  | ||||||
| // |  | ||||||
| // This program is distributed in the hope that it will be useful, |  | ||||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of |  | ||||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the |  | ||||||
| // GNU General Public License for more details. |  | ||||||
| // |  | ||||||
| // You should have received a copy of the GNU General Public License |  | ||||||
| // along with this program.  If not, see <http://www.gnu.org/licenses/>. |  | ||||||
|  |  | ||||||
| package repos |  | ||||||
|  |  | ||||||
| import ( |  | ||||||
| 	"context" |  | ||||||
| 	"sync" |  | ||||||
|  |  | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/config" |  | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/db" |  | ||||||
| 	database "gitea.plemya-x.ru/Plemya-x/ALR/internal/db" |  | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/types" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| // 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. |  | ||||||
| // |  | ||||||
| // Deprecated: use struct method |  | ||||||
| func Pull(ctx context.Context, repos []types.Repo) error { |  | ||||||
| 	return GetInstance(ctx).Pull(ctx, repos) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // FindPkgs looks for packages matching the inputs inside the database. |  | ||||||
| // It returns a map that maps the package name input to any packages found for it. |  | ||||||
| // It also returns a slice that contains the names of all packages that were not found. |  | ||||||
| // |  | ||||||
| // Deprecated: use struct method |  | ||||||
| func FindPkgs(ctx context.Context, pkgs []string) (map[string][]db.Package, []string, error) { |  | ||||||
| 	return GetInstance(ctx).FindPkgs(ctx, pkgs) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // ======================= |  | ||||||
| // FOR LEGACY ONLY |  | ||||||
| // ======================= |  | ||||||
|  |  | ||||||
| var ( |  | ||||||
| 	reposInstance *Repos |  | ||||||
| 	alrConfigOnce sync.Once |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| // Deprecated: For legacy only |  | ||||||
| func GetInstance(ctx context.Context) *Repos { |  | ||||||
| 	alrConfigOnce.Do(func() { |  | ||||||
| 		cfg := config.GetInstance(ctx) |  | ||||||
| 		db := database.GetInstance(ctx) |  | ||||||
|  |  | ||||||
| 		reposInstance = New( |  | ||||||
| 			cfg, |  | ||||||
| 			db, |  | ||||||
| 		) |  | ||||||
| 	}) |  | ||||||
|  |  | ||||||
| 	return reposInstance |  | ||||||
| } |  | ||||||
| @@ -67,6 +67,47 @@ func parseScript(ctx context.Context, parser *syntax.Parser, runner *interp.Runn | |||||||
| 	return d.DecodeVars(pkg) | 	return d.DecodeVars(pkg) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | type PackageInfo struct { | ||||||
|  | 	Version       string            `sh:"version,required"` | ||||||
|  | 	Release       int               `sh:"release,required"` | ||||||
|  | 	Epoch         uint              `sh:"epoch"` | ||||||
|  | 	Architectures db.JSON[[]string] `sh:"architectures"` | ||||||
|  | 	Licenses      db.JSON[[]string] `sh:"license"` | ||||||
|  | 	Provides      db.JSON[[]string] `sh:"provides"` | ||||||
|  | 	Conflicts     db.JSON[[]string] `sh:"conflicts"` | ||||||
|  | 	Replaces      db.JSON[[]string] `sh:"replaces"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (inf *PackageInfo) ToPackage(repoName string) *db.Package { | ||||||
|  | 	return &db.Package{ | ||||||
|  | 		Version:       inf.Version, | ||||||
|  | 		Release:       inf.Release, | ||||||
|  | 		Epoch:         inf.Epoch, | ||||||
|  | 		Architectures: inf.Architectures, | ||||||
|  | 		Licenses:      inf.Licenses, | ||||||
|  | 		Provides:      inf.Provides, | ||||||
|  | 		Conflicts:     inf.Conflicts, | ||||||
|  | 		Replaces:      inf.Replaces, | ||||||
|  | 		Description:   db.NewJSON(map[string]string{}), | ||||||
|  | 		Homepage:      db.NewJSON(map[string]string{}), | ||||||
|  | 		Maintainer:    db.NewJSON(map[string]string{}), | ||||||
|  | 		Depends:       db.NewJSON(map[string][]string{}), | ||||||
|  | 		BuildDepends:  db.NewJSON(map[string][]string{}), | ||||||
|  | 		Repository:    repoName, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func EmptyPackage(repoName string) *db.Package { | ||||||
|  | 	return &db.Package{ | ||||||
|  | 		Description:  db.NewJSON(map[string]string{}), | ||||||
|  | 		Homepage:     db.NewJSON(map[string]string{}), | ||||||
|  | 		Maintainer:   db.NewJSON(map[string]string{}), | ||||||
|  | 		Depends:      db.NewJSON(map[string][]string{}), | ||||||
|  | 		BuildDepends: db.NewJSON(map[string][]string{}), | ||||||
|  | 		Repository:   repoName, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
| var overridable = map[string]string{ | var overridable = map[string]string{ | ||||||
| 	"deps":       "Depends", | 	"deps":       "Depends", | ||||||
| 	"build_deps": "BuildDepends", | 	"build_deps": "BuildDepends", | ||||||
|   | |||||||
| @@ -21,166 +21,46 @@ package search | |||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"context" | 	"context" | ||||||
| 	"errors" |  | ||||||
| 	"io" |  | ||||||
| 	"io/fs" |  | ||||||
| 	"os" |  | ||||||
| 	"path/filepath" |  | ||||||
| 	"strconv" |  | ||||||
| 	"strings" |  | ||||||
|  |  | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/config" | 	"github.com/jmoiron/sqlx" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/db" |  | ||||||
|  | 	database "gitea.plemya-x.ru/Plemya-x/ALR/internal/db" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // Filter represents search filters. | type PackagesProvider interface { | ||||||
| type Filter int | 	GetPkgs(ctx context.Context, where string, args ...any) (*sqlx.Rows, error) | ||||||
|  |  | ||||||
| // Filters |  | ||||||
| const ( |  | ||||||
| 	FilterNone Filter = iota |  | ||||||
| 	FilterInRepo |  | ||||||
| 	FilterSupportsArch |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| // SoryBy represents a value that packages can be sorted by. |  | ||||||
| type SortBy int |  | ||||||
|  |  | ||||||
| // Sort values |  | ||||||
| const ( |  | ||||||
| 	SortByNone = iota |  | ||||||
| 	SortByName |  | ||||||
| 	SortByRepo |  | ||||||
| 	SortByVersion |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| // Package represents a package from ALR's database |  | ||||||
| type Package struct { |  | ||||||
| 	Name          string |  | ||||||
| 	Version       string |  | ||||||
| 	Release       int |  | ||||||
| 	Epoch         uint |  | ||||||
| 	Description   map[string]string |  | ||||||
| 	Homepage      map[string]string |  | ||||||
| 	Maintainer    map[string]string |  | ||||||
| 	Architectures []string |  | ||||||
| 	Licenses      []string |  | ||||||
| 	Provides      []string |  | ||||||
| 	Conflicts     []string |  | ||||||
| 	Replaces      []string |  | ||||||
| 	Depends       map[string][]string |  | ||||||
| 	BuildDepends  map[string][]string |  | ||||||
| 	OptDepends    map[string][]string |  | ||||||
| 	Repository    string |  | ||||||
| } | } | ||||||
|  |  | ||||||
| func convertPkg(p db.Package) Package { | type Searcher struct { | ||||||
| 	return Package{ | 	pp PackagesProvider | ||||||
| 		Name:          p.Name, | } | ||||||
| 		Version:       p.Version, |  | ||||||
| 		Release:       p.Release, | func New(pp PackagesProvider) *Searcher { | ||||||
| 		Epoch:         p.Epoch, | 	return &Searcher{ | ||||||
| 		Description:   p.Description.Val, | 		pp: pp, | ||||||
| 		Homepage:      p.Homepage.Val, |  | ||||||
| 		Maintainer:    p.Maintainer.Val, |  | ||||||
| 		Architectures: p.Architectures.Val, |  | ||||||
| 		Licenses:      p.Licenses.Val, |  | ||||||
| 		Provides:      p.Provides.Val, |  | ||||||
| 		Conflicts:     p.Conflicts.Val, |  | ||||||
| 		Replaces:      p.Replaces.Val, |  | ||||||
| 		Depends:       p.Depends.Val, |  | ||||||
| 		BuildDepends:  p.BuildDepends.Val, |  | ||||||
| 		OptDepends:    p.OptDepends.Val, |  | ||||||
| 		Repository:    p.Repository, |  | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| // Options contains the options for a search. | func (s *Searcher) Search( | ||||||
| type Options struct { | 	ctx context.Context, | ||||||
| 	Filter      Filter | 	opts *SearchOptions, | ||||||
| 	FilterValue string | ) ([]database.Package, error) { | ||||||
| 	SortBy      SortBy | 	var packages []database.Package | ||||||
| 	Limit       int64 |  | ||||||
| 	Query       string |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Search searches for packages in the database based on the given options. | 	where, args := opts.WhereClause() | ||||||
| func Search(ctx context.Context, opts Options) ([]Package, error) { | 	result, err := s.pp.GetPkgs(ctx, where, args...) | ||||||
| 	query := "(name LIKE ? OR description LIKE ? OR json_array_contains(provides, ?))" |  | ||||||
| 	args := []any{"%" + opts.Query + "%", "%" + opts.Query + "%", opts.Query} |  | ||||||
|  |  | ||||||
| 	if opts.Filter != FilterNone { |  | ||||||
| 		switch opts.Filter { |  | ||||||
| 		case FilterInRepo: |  | ||||||
| 			query += " AND repository = ?" |  | ||||||
| 		case FilterSupportsArch: |  | ||||||
| 			query += " AND json_array_contains(architectures, ?)" |  | ||||||
| 		} |  | ||||||
| 		args = append(args, opts.FilterValue) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if opts.SortBy != SortByNone { |  | ||||||
| 		switch opts.SortBy { |  | ||||||
| 		case SortByName: |  | ||||||
| 			query += " ORDER BY name" |  | ||||||
| 		case SortByRepo: |  | ||||||
| 			query += " ORDER BY repository" |  | ||||||
| 		case SortByVersion: |  | ||||||
| 			query += " ORDER BY version" |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if opts.Limit != 0 { |  | ||||||
| 		query += " LIMIT " + strconv.FormatInt(opts.Limit, 10) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	result, err := db.GetPkgs(ctx, query, args...) |  | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	var out []Package |  | ||||||
| 	for result.Next() { | 	for result.Next() { | ||||||
| 		pkg := db.Package{} | 		var dbPkg database.Package | ||||||
| 		err = result.StructScan(&pkg) | 		err = result.StructScan(&dbPkg) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return nil, err | 			return nil, err | ||||||
| 		} | 		} | ||||||
| 		out = append(out, convertPkg(pkg)) | 		packages = append(packages, dbPkg) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return out, err | 	return packages, nil | ||||||
| } |  | ||||||
|  |  | ||||||
| // GetPkg gets a single package from the database and returns it. |  | ||||||
| func GetPkg(ctx context.Context, repo, name string) (Package, error) { |  | ||||||
| 	pkg, err := db.GetPkg(ctx, "name = ? AND repository = ?", name, repo) |  | ||||||
| 	return convertPkg(*pkg), err |  | ||||||
| } |  | ||||||
|  |  | ||||||
| var ( |  | ||||||
| 	// ErrInvalidArgument is an error returned by GetScript when one of its arguments |  | ||||||
| 	// contain invalid characters |  | ||||||
| 	ErrInvalidArgument = errors.New("name and repository must not contain . or /") |  | ||||||
|  |  | ||||||
| 	// ErrScriptNotFound is returned by GetScript if it can't find the script requested |  | ||||||
| 	// by the user. |  | ||||||
| 	ErrScriptNotFound = errors.New("requested script not found") |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| // GetScript returns a reader containing the build script for a given package. |  | ||||||
| func GetScript(ctx context.Context, repo, name string) (io.ReadCloser, error) { |  | ||||||
| 	if strings.Contains(name, "./") || strings.ContainsAny(repo, "./") { |  | ||||||
| 		return nil, ErrInvalidArgument |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	scriptPath := filepath.Join(config.GetPaths(ctx).RepoDir, repo, name, "alr.sh") |  | ||||||
| 	fl, err := os.Open(scriptPath) |  | ||||||
| 	if errors.Is(err, fs.ErrNotExist) { |  | ||||||
| 		return nil, ErrScriptNotFound |  | ||||||
| 	} else if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return fl, nil |  | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										86
									
								
								pkg/search/search_options_builder.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								pkg/search/search_options_builder.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,86 @@ | |||||||
|  | // ALR - Any Linux Repository | ||||||
|  | // Copyright (C) 2025 Евгений Храмов | ||||||
|  | // | ||||||
|  | // This program is free software: you can redistribute it and/or modify | ||||||
|  | // it under the terms of the GNU General Public License as published by | ||||||
|  | // the Free Software Foundation, either version 3 of the License, or | ||||||
|  | // (at your option) any later version. | ||||||
|  | // | ||||||
|  | // This program is distributed in the hope that it will be useful, | ||||||
|  | // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | // GNU General Public License for more details. | ||||||
|  | // | ||||||
|  | // You should have received a copy of the GNU General Public License | ||||||
|  | // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  |  | ||||||
|  | package search | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"strings" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type SearchOptions struct { | ||||||
|  | 	conditions []string | ||||||
|  | 	args       []any | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (o *SearchOptions) WhereClause() (string, []any) { | ||||||
|  | 	if len(o.conditions) == 0 { | ||||||
|  | 		return "", nil | ||||||
|  | 	} | ||||||
|  | 	return strings.Join(o.conditions, " AND "), o.args | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type SearchOptionsBuilder struct { | ||||||
|  | 	options SearchOptions | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func NewSearchOptions() *SearchOptionsBuilder { | ||||||
|  | 	return &SearchOptionsBuilder{} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *SearchOptionsBuilder) withGeneralLike(key, value string) *SearchOptionsBuilder { | ||||||
|  | 	if value != "" { | ||||||
|  | 		b.options.conditions = append(b.options.conditions, fmt.Sprintf("%s LIKE ?", key)) | ||||||
|  | 		b.options.args = append(b.options.args, "%"+value+"%") | ||||||
|  | 	} | ||||||
|  | 	return b | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *SearchOptionsBuilder) withGeneralEqual(key string, value any) *SearchOptionsBuilder { | ||||||
|  | 	if value != "" { | ||||||
|  | 		b.options.conditions = append(b.options.conditions, fmt.Sprintf("%s = ?", key)) | ||||||
|  | 		b.options.args = append(b.options.args, value) | ||||||
|  | 	} | ||||||
|  | 	return b | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *SearchOptionsBuilder) withGeneralJsonArrayContains(key string, value any) *SearchOptionsBuilder { | ||||||
|  | 	if value != "" { | ||||||
|  | 		b.options.conditions = append(b.options.conditions, fmt.Sprintf("json_array_contains(%s, ?)", key)) | ||||||
|  | 		b.options.args = append(b.options.args, value) | ||||||
|  | 	} | ||||||
|  | 	return b | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *SearchOptionsBuilder) WithName(name string) *SearchOptionsBuilder { | ||||||
|  | 	return b.withGeneralLike("name", name) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *SearchOptionsBuilder) WithDescription(description string) *SearchOptionsBuilder { | ||||||
|  | 	return b.withGeneralLike("description", description) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *SearchOptionsBuilder) WithRepository(repository string) *SearchOptionsBuilder { | ||||||
|  | 	return b.withGeneralEqual("repository", repository) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *SearchOptionsBuilder) WithProvides(provides string) *SearchOptionsBuilder { | ||||||
|  | 	return b.withGeneralJsonArrayContains("provides", provides) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *SearchOptionsBuilder) Build() *SearchOptions { | ||||||
|  | 	return &b.options | ||||||
|  | } | ||||||
							
								
								
									
										65
									
								
								pkg/search/search_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								pkg/search/search_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,65 @@ | |||||||
|  | // ALR - Any Linux Repository | ||||||
|  | // Copyright (C) 2025 Евгений Храмов | ||||||
|  | // | ||||||
|  | // This program is free software: you can redistribute it and/or modify | ||||||
|  | // it under the terms of the GNU General Public License as published by | ||||||
|  | // the Free Software Foundation, either version 3 of the License, or | ||||||
|  | // (at your option) any later version. | ||||||
|  | // | ||||||
|  | // This program is distributed in the hope that it will be useful, | ||||||
|  | // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | // GNU General Public License for more details. | ||||||
|  | // | ||||||
|  | // You should have received a copy of the GNU General Public License | ||||||
|  | // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  |  | ||||||
|  | package search_test | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"testing" | ||||||
|  |  | ||||||
|  | 	"github.com/stretchr/testify/assert" | ||||||
|  |  | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/search" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func TestSearhOptionsBuilder(t *testing.T) { | ||||||
|  | 	type testCase struct { | ||||||
|  | 		name          string | ||||||
|  | 		prepare       func() *search.SearchOptions | ||||||
|  | 		expectedWhere string | ||||||
|  | 		expectedArgs  []any | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for _, tc := range []testCase{ | ||||||
|  | 		{ | ||||||
|  | 			name: "Empty fields", | ||||||
|  | 			prepare: func() *search.SearchOptions { | ||||||
|  | 				return search.NewSearchOptions(). | ||||||
|  | 					Build() | ||||||
|  | 			}, | ||||||
|  | 			expectedWhere: "", | ||||||
|  | 			expectedArgs:  []any{}, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name: "All fields", | ||||||
|  | 			prepare: func() *search.SearchOptions { | ||||||
|  | 				return search.NewSearchOptions(). | ||||||
|  | 					WithName("foo"). | ||||||
|  | 					WithDescription("bar"). | ||||||
|  | 					WithRepository("buz"). | ||||||
|  | 					WithProvides("test"). | ||||||
|  | 					Build() | ||||||
|  | 			}, | ||||||
|  | 			expectedWhere: "name LIKE ? AND description LIKE ? AND repository = ? AND json_array_contains(provides, ?)", | ||||||
|  | 			expectedArgs:  []any{"%foo%", "%bar%", "buz", "test"}, | ||||||
|  | 		}, | ||||||
|  | 	} { | ||||||
|  | 		t.Run(tc.name, func(t *testing.T) { | ||||||
|  | 			whereClause, args := tc.prepare().WhereClause() | ||||||
|  | 			assert.Equal(t, tc.expectedWhere, whereClause) | ||||||
|  | 			assert.ElementsMatch(t, tc.expectedArgs, args) | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										49
									
								
								repo.go
									
									
									
									
									
								
							
							
						
						
									
										49
									
								
								repo.go
									
									
									
									
									
								
							| @@ -30,7 +30,7 @@ import ( | |||||||
| 	"golang.org/x/exp/slices" | 	"golang.org/x/exp/slices" | ||||||
|  |  | ||||||
| 	"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" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/types" | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/types" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/repos" | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/repos" | ||||||
| ) | ) | ||||||
| @@ -60,33 +60,43 @@ func AddRepoCmd() *cli.Command { | |||||||
| 			name := c.String("name") | 			name := c.String("name") | ||||||
| 			repoURL := c.String("url") | 			repoURL := c.String("url") | ||||||
|  |  | ||||||
| 			cfg := config.Config(ctx) | 			cfg := config.New() | ||||||
|  | 			reposSlice := cfg.Repos(ctx) | ||||||
|  |  | ||||||
| 			for _, repo := range cfg.Repos { | 			for _, repo := range reposSlice { | ||||||
| 				if repo.URL == repoURL { | 				if repo.URL == repoURL { | ||||||
| 					slog.Error("Repo already exists", "name", repo.Name) | 					slog.Error("Repo already exists", "name", repo.Name) | ||||||
| 					os.Exit(1) | 					os.Exit(1) | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			cfg.Repos = append(cfg.Repos, types.Repo{ | 			reposSlice = append(reposSlice, types.Repo{ | ||||||
| 				Name: name, | 				Name: name, | ||||||
| 				URL:  repoURL, | 				URL:  repoURL, | ||||||
| 			}) | 			}) | ||||||
|  |  | ||||||
| 			cfgFl, err := os.Create(config.GetPaths(ctx).ConfigPath) | 			cfg.SetRepos(ctx, reposSlice) | ||||||
|  |  | ||||||
|  | 			cfgFl, err := os.Create(cfg.GetPaths(ctx).ConfigPath) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				slog.Error(gotext.Get("Error opening config file"), "err", err) | 				slog.Error(gotext.Get("Error opening config file"), "err", err) | ||||||
| 				os.Exit(1) | 				os.Exit(1) | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			err = toml.NewEncoder(cfgFl).Encode(cfg) | 			err = cfg.Save(cfgFl) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				slog.Error(gotext.Get("Error encoding config"), "err", err) | 				slog.Error(gotext.Get("Error encoding config"), "err", err) | ||||||
| 				os.Exit(1) | 				os.Exit(1) | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			err = repos.Pull(ctx, cfg.Repos) | 			db := database.New(cfg) | ||||||
|  | 			err = db.Init(ctx) | ||||||
|  | 			if err != nil { | ||||||
|  | 				slog.Error(gotext.Get("Error pulling repos"), "err", err) | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			rs := repos.New(cfg, db) | ||||||
|  | 			err = rs.Pull(ctx, cfg.Repos(ctx)) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				slog.Error(gotext.Get("Error pulling repos"), "err", err) | 				slog.Error(gotext.Get("Error pulling repos"), "err", err) | ||||||
| 				os.Exit(1) | 				os.Exit(1) | ||||||
| @@ -114,11 +124,12 @@ func RemoveRepoCmd() *cli.Command { | |||||||
| 			ctx := c.Context | 			ctx := c.Context | ||||||
|  |  | ||||||
| 			name := c.String("name") | 			name := c.String("name") | ||||||
| 			cfg := config.Config(ctx) | 			cfg := config.New() | ||||||
|  |  | ||||||
| 			found := false | 			found := false | ||||||
| 			index := 0 | 			index := 0 | ||||||
| 			for i, repo := range cfg.Repos { | 			reposSlice := cfg.Repos(ctx) | ||||||
|  | 			for i, repo := range reposSlice { | ||||||
| 				if repo.Name == name { | 				if repo.Name == name { | ||||||
| 					index = i | 					index = i | ||||||
| 					found = true | 					found = true | ||||||
| @@ -129,9 +140,9 @@ func RemoveRepoCmd() *cli.Command { | |||||||
| 				os.Exit(1) | 				os.Exit(1) | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			cfg.Repos = slices.Delete(cfg.Repos, index, index+1) | 			cfg.SetRepos(ctx, slices.Delete(reposSlice, index, index+1)) | ||||||
|  |  | ||||||
| 			cfgFl, err := os.Create(config.GetPaths(ctx).ConfigPath) | 			cfgFl, err := os.Create(cfg.GetPaths(ctx).ConfigPath) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				slog.Error(gotext.Get("Error opening config file"), "err", err) | 				slog.Error(gotext.Get("Error opening config file"), "err", err) | ||||||
| 				os.Exit(1) | 				os.Exit(1) | ||||||
| @@ -143,12 +154,17 @@ func RemoveRepoCmd() *cli.Command { | |||||||
| 				os.Exit(1) | 				os.Exit(1) | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			err = os.RemoveAll(filepath.Join(config.GetPaths(ctx).RepoDir, name)) | 			err = os.RemoveAll(filepath.Join(cfg.GetPaths(ctx).RepoDir, name)) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				slog.Error(gotext.Get("Error removing repo directory"), "err", err) | 				slog.Error(gotext.Get("Error removing repo directory"), "err", err) | ||||||
| 				os.Exit(1) | 				os.Exit(1) | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
|  | 			db := database.New(cfg) | ||||||
|  | 			err = db.Init(ctx) | ||||||
|  | 			if err != nil { | ||||||
|  | 				os.Exit(1) | ||||||
|  | 			} | ||||||
| 			err = db.DeletePkgs(ctx, "repository = ?", name) | 			err = db.DeletePkgs(ctx, "repository = ?", name) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				slog.Error(gotext.Get("Error removing packages from database"), "err", err) | 				slog.Error(gotext.Get("Error removing packages from database"), "err", err) | ||||||
| @@ -167,7 +183,14 @@ func RefreshCmd() *cli.Command { | |||||||
| 		Aliases: []string{"ref"}, | 		Aliases: []string{"ref"}, | ||||||
| 		Action: func(c *cli.Context) error { | 		Action: func(c *cli.Context) error { | ||||||
| 			ctx := c.Context | 			ctx := c.Context | ||||||
| 			err := repos.Pull(ctx, config.Config(ctx).Repos) | 			cfg := config.New() | ||||||
|  | 			db := database.New(cfg) | ||||||
|  | 			err := db.Init(ctx) | ||||||
|  | 			if err != nil { | ||||||
|  | 				os.Exit(1) | ||||||
|  | 			} | ||||||
|  | 			rs := repos.New(cfg, db) | ||||||
|  | 			err = rs.Pull(ctx, cfg.Repos(ctx)) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				slog.Error(gotext.Get("Error pulling repos"), "err", err) | 				slog.Error(gotext.Get("Error pulling repos"), "err", err) | ||||||
| 				os.Exit(1) | 				os.Exit(1) | ||||||
|   | |||||||
| @@ -25,7 +25,7 @@ elif (( $(echo "$COVERAGE < 80" | bc -l) )); then | |||||||
|     COLOR="#dfb317" |     COLOR="#dfb317" | ||||||
| fi | fi | ||||||
|  |  | ||||||
| cat <<EOF > coverage-badge.svg | cat <<EOF > assets/coverage-badge.svg | ||||||
| <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="109" height="20"> | <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="109" height="20"> | ||||||
|     <linearGradient id="smooth" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/> |     <linearGradient id="smooth" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/> | ||||||
|     <stop offset="1" stop-opacity=".1"/></linearGradient> |     <stop offset="1" stop-opacity=".1"/></linearGradient> | ||||||
|   | |||||||
							
								
								
									
										84
									
								
								scripts/i18n-badge.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										84
									
								
								scripts/i18n-badge.sh
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,84 @@ | |||||||
|  | #!/bin/bash | ||||||
|  | # ALR - Any Linux Repository | ||||||
|  | # Copyright (C) 2025 Евгений Храмов | ||||||
|  | # | ||||||
|  | # This program is free software: you can redistribute it and/or modify | ||||||
|  | # it under the terms of the GNU General Public License as published by | ||||||
|  | # the Free Software Foundation, either version 3 of the License, or | ||||||
|  | # (at your option) any later version. | ||||||
|  | # | ||||||
|  | # This program is distributed in the hope that it will be useful, | ||||||
|  | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | # GNU General Public License for more details. | ||||||
|  | # | ||||||
|  | # You should have received a copy of the GNU General Public License | ||||||
|  | # along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  |  | ||||||
|  |  | ||||||
|  | TRANSLATIONS_DIR="internal/translations/po" | ||||||
|  |  | ||||||
|  | if [ ! -d "$TRANSLATIONS_DIR" ]; then | ||||||
|  |     echo "Error: directory '$TRANSLATIONS_DIR' not found" | ||||||
|  |     exit 1 | ||||||
|  | fi | ||||||
|  |  | ||||||
|  | declare -A TOTAL_STRINGS_MAP | ||||||
|  | declare -A TRANSLATED_STRINGS_MAP | ||||||
|  |  | ||||||
|  | for PO_FILE in $(find "$TRANSLATIONS_DIR" -type f -name "*.po"); do | ||||||
|  |     LANG_DIR=$(dirname "$PO_FILE") | ||||||
|  |     LANG=$(basename "$LANG_DIR") | ||||||
|  |  | ||||||
|  |     STATS=$(LC_ALL=C msgfmt --statistics -o /dev/null "$PO_FILE" 2>&1) | ||||||
|  |  | ||||||
|  |     NUMBERS=($(echo "$STATS" | grep -o '[0-9]\+')) | ||||||
|  |  | ||||||
|  |     case ${#NUMBERS[@]} in | ||||||
|  |         1) TRANSLATED_STRINGS=${NUMBERS[0]}; UNTRANSLATED_STRINGS=0 ;;  # all translated | ||||||
|  |         2) TRANSLATED_STRINGS=${NUMBERS[0]}; UNTRANSLATED_STRINGS=${NUMBERS[1]} ;;  # no fuzzy | ||||||
|  |         3) TRANSLATED_STRINGS=${NUMBERS[0]}; UNTRANSLATED_STRINGS=${NUMBERS[2]} ;;  # with fuzzy | ||||||
|  |         *) TRANSLATED_STRINGS=0; UNTRANSLATED_STRINGS=0 ;;  | ||||||
|  |     esac | ||||||
|  |  | ||||||
|  |     TOTAL_STRINGS=$((TRANSLATED_STRINGS + UNTRANSLATED_STRINGS)) | ||||||
|  |  | ||||||
|  |     TOTAL_STRINGS_MAP[$LANG]=$((TOTAL_STRINGS_MAP[$LANG] + TOTAL_STRINGS)) | ||||||
|  |     TRANSLATED_STRINGS_MAP[$LANG]=$((TRANSLATED_STRINGS_MAP[$LANG] + TRANSLATED_STRINGS)) | ||||||
|  | done | ||||||
|  |  | ||||||
|  | for LANG in "${!TOTAL_STRINGS_MAP[@]}"; do | ||||||
|  |     TOTAL=${TOTAL_STRINGS_MAP[$LANG]} | ||||||
|  |     TRANSLATED=${TRANSLATED_STRINGS_MAP[$LANG]} | ||||||
|  |     if [ "$TOTAL" -eq 0 ]; then | ||||||
|  |         PERCENTAGE="0.00" | ||||||
|  |     else | ||||||
|  |         PERCENTAGE=$(echo "scale=2; ($TRANSLATED / $TOTAL) * 100" | bc) | ||||||
|  |     fi | ||||||
|  |     COLOR="#4c1" | ||||||
|  |     if (( $(echo "$PERCENTAGE < 50" | bc -l) )); then | ||||||
|  |         COLOR="#e05d44" | ||||||
|  |     elif (( $(echo "$PERCENTAGE < 80" | bc -l) )); then | ||||||
|  |         COLOR="#dfb317" | ||||||
|  |     fi | ||||||
|  | cat <<EOF > assets/i18n-$LANG-badge.svg | ||||||
|  | <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="129" height="20"> | ||||||
|  |     <linearGradient id="smooth" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/> | ||||||
|  |     <stop offset="1" stop-opacity=".1"/></linearGradient> | ||||||
|  |     <mask id="round"> | ||||||
|  |         <rect width="129" height="20" rx="3" fill="#fff"/> | ||||||
|  |     </mask> | ||||||
|  |     <g mask="url(#round)"> | ||||||
|  |         <rect width="75" height="20" fill="#555"/> | ||||||
|  |         <rect x="75" width="64" height="20" fill="${COLOR}"/> | ||||||
|  |         <rect width="129" height="20" fill="url(#smooth)"/> | ||||||
|  |     </g> | ||||||
|  |     <g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11"> | ||||||
|  |         <text x="37" y="15" fill="#010101" fill-opacity=".3">$LANG translate</text> | ||||||
|  |         <text x="37" y="14">$LANG translate</text> | ||||||
|  |         <text x="100" y="15" fill="#010101" fill-opacity=".3">${PERCENTAGE}%</text> | ||||||
|  |         <text x="100" y="14">${PERCENTAGE}%</text> | ||||||
|  |     </g> | ||||||
|  | </svg> | ||||||
|  | EOF | ||||||
|  | done | ||||||
| @@ -39,7 +39,7 @@ installPkg() { | |||||||
|   else |   else | ||||||
|     warn "Не обнаружена команда повышения привилегий (например, sudo, doas)" |     warn "Не обнаружена команда повышения привилегий (например, sudo, doas)" | ||||||
|   fi |   fi | ||||||
|    |  | ||||||
|   case $1 in |   case $1 in | ||||||
|   pacman) $rootCmd pacman --noconfirm -U ${@:2} ;; |   pacman) $rootCmd pacman --noconfirm -U ${@:2} ;; | ||||||
|   apk) $rootCmd apk add --allow-untrusted ${@:2} ;; |   apk) $rootCmd apk add --allow-untrusted ${@:2} ;; | ||||||
| @@ -78,6 +78,10 @@ elif command -v apk &>/dev/null; then | |||||||
|   info "Обнаружен apk" |   info "Обнаружен apk" | ||||||
|   pkgFormat="apk" |   pkgFormat="apk" | ||||||
|   pkgMgr="apk" |   pkgMgr="apk" | ||||||
|  | elif command -v apt-get &>/dev/null; then | ||||||
|  |   info "Обнаружен apt-get" | ||||||
|  |   pkgFormat="rpm" | ||||||
|  |   pkgMgr="apt-get" | ||||||
| else | else | ||||||
|   warn "Не обнаружен поддерживаемый менеджер пакетов!" |   warn "Не обнаружен поддерживаемый менеджер пакетов!" | ||||||
|   noPkgMgr=true |   noPkgMgr=true | ||||||
| @@ -98,7 +102,10 @@ if [ -z "$noPkgMgr" ]; then | |||||||
|   elif [ "$pkgMgr" == "apt" ]; then |   elif [ "$pkgMgr" == "apt" ]; then | ||||||
|     latestFile=$(echo "$fileList" | grep -E 'alr-bin-.*.amd64.deb' | sort -V | tail -n 1) |     latestFile=$(echo "$fileList" | grep -E 'alr-bin-.*.amd64.deb' | sort -V | tail -n 1) | ||||||
|   elif [[ "$pkgMgr" == "dnf" || "$pkgMgr" == "yum" || "$pkgMgr" == "zypper" ]]; then |   elif [[ "$pkgMgr" == "dnf" || "$pkgMgr" == "yum" || "$pkgMgr" == "zypper" ]]; then | ||||||
|     latestFile=$(echo "$fileList" | grep -E 'alr-bin-.*.x86_64.rpm' | sort -V | tail -n 1) |     latestFile=$(echo "$fileList" | grep -E 'alr-bin-.*\.x86_64\.rpm' | grep -v 'alt1' | sort -V | tail -n 1) | ||||||
|  |   elif [[ "$pkgMgr" == "apt-get"  ]]; then | ||||||
|  |     latestFile=$(echo "$fileList" | grep -E 'alr-bin-.*alt1.x86_64.rpm' | sort -V | tail -n 1) | ||||||
|  |  | ||||||
|   else |   else | ||||||
|     error "Не поддерживаемый менеджер пакетов для автоматической установки" |     error "Не поддерживаемый менеджер пакетов для автоматической установки" | ||||||
|   fi |   fi | ||||||
|   | |||||||
							
								
								
									
										119
									
								
								search.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										119
									
								
								search.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,119 @@ | |||||||
|  | // ALR - Any Linux Repository | ||||||
|  | // Copyright (C) 2025 Евгений Храмов | ||||||
|  | // | ||||||
|  | // This program is free software: you can redistribute it and/or modify | ||||||
|  | // it under the terms of the GNU General Public License as published by | ||||||
|  | // the Free Software Foundation, either version 3 of the License, or | ||||||
|  | // (at your option) any later version. | ||||||
|  | // | ||||||
|  | // This program is distributed in the hope that it will be useful, | ||||||
|  | // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | // GNU General Public License for more details. | ||||||
|  | // | ||||||
|  | // You should have received a copy of the GNU General Public License | ||||||
|  | // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  |  | ||||||
|  | package main | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"log/slog" | ||||||
|  | 	"os" | ||||||
|  | 	"text/template" | ||||||
|  |  | ||||||
|  | 	"github.com/leonelquinteros/gotext" | ||||||
|  | 	"github.com/urfave/cli/v2" | ||||||
|  |  | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/config" | ||||||
|  | 	database "gitea.plemya-x.ru/Plemya-x/ALR/internal/db" | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/search" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func SearchCmd() *cli.Command { | ||||||
|  | 	return &cli.Command{ | ||||||
|  | 		Name:    "search", | ||||||
|  | 		Usage:   gotext.Get("Search packages"), | ||||||
|  | 		Aliases: []string{"s"}, | ||||||
|  | 		Flags: []cli.Flag{ | ||||||
|  | 			&cli.StringFlag{ | ||||||
|  | 				Name:    "name", | ||||||
|  | 				Aliases: []string{"n"}, | ||||||
|  | 				Usage:   gotext.Get("Search by name"), | ||||||
|  | 			}, | ||||||
|  | 			&cli.StringFlag{ | ||||||
|  | 				Name:    "description", | ||||||
|  | 				Aliases: []string{"d"}, | ||||||
|  | 				Usage:   gotext.Get("Search by description"), | ||||||
|  | 			}, | ||||||
|  | 			&cli.StringFlag{ | ||||||
|  | 				Name:    "repository", | ||||||
|  | 				Aliases: []string{"repo"}, | ||||||
|  | 				Usage:   gotext.Get("Search by repository"), | ||||||
|  | 			}, | ||||||
|  | 			&cli.StringFlag{ | ||||||
|  | 				Name:    "provides", | ||||||
|  | 				Aliases: []string{"p"}, | ||||||
|  | 				Usage:   gotext.Get("Search by provides"), | ||||||
|  | 			}, | ||||||
|  | 			&cli.StringFlag{ | ||||||
|  | 				Name:    "format", | ||||||
|  | 				Aliases: []string{"f"}, | ||||||
|  | 				Usage:   gotext.Get("Format output using a Go template"), | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 		Action: func(c *cli.Context) error { | ||||||
|  | 			ctx := c.Context | ||||||
|  | 			cfg := config.New() | ||||||
|  | 			db := database.New(cfg) | ||||||
|  | 			err := db.Init(ctx) | ||||||
|  | 			defer db.Close() | ||||||
|  |  | ||||||
|  | 			if err != nil { | ||||||
|  | 				slog.Error(gotext.Get("Error initialization database"), "err", err) | ||||||
|  | 				os.Exit(1) | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			format := c.String("format") | ||||||
|  | 			var tmpl *template.Template | ||||||
|  | 			if format != "" { | ||||||
|  | 				tmpl, err = template.New("format").Parse(format) | ||||||
|  | 				if err != nil { | ||||||
|  | 					slog.Error(gotext.Get("Error parsing format template"), "err", err) | ||||||
|  | 					os.Exit(1) | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			s := search.New(db) | ||||||
|  |  | ||||||
|  | 			packages, err := s.Search( | ||||||
|  | 				ctx, | ||||||
|  | 				search.NewSearchOptions(). | ||||||
|  | 					WithName(c.String("name")). | ||||||
|  | 					WithDescription(c.String("description")). | ||||||
|  | 					WithRepository(c.String("repository")). | ||||||
|  | 					WithProvides(c.String("provides")). | ||||||
|  | 					Build(), | ||||||
|  | 			) | ||||||
|  | 			if err != nil { | ||||||
|  | 				slog.Error(gotext.Get("Error parsing format template"), "err", err) | ||||||
|  | 				os.Exit(1) | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			for _, dbPkg := range packages { | ||||||
|  | 				if tmpl != nil { | ||||||
|  | 					err = tmpl.Execute(os.Stdout, dbPkg) | ||||||
|  | 					if err != nil { | ||||||
|  | 						slog.Error(gotext.Get("Error executing template"), "err", err) | ||||||
|  | 						os.Exit(1) | ||||||
|  | 					} | ||||||
|  | 					fmt.Println() | ||||||
|  | 				} else { | ||||||
|  | 					fmt.Println(dbPkg.Name) | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			return nil | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										118
									
								
								upgrade.go
									
									
									
									
									
								
							
							
						
						
									
										118
									
								
								upgrade.go
									
									
									
									
									
								
							| @@ -29,16 +29,16 @@ import ( | |||||||
| 	"github.com/urfave/cli/v2" | 	"github.com/urfave/cli/v2" | ||||||
| 	"go.elara.ws/vercmp" | 	"go.elara.ws/vercmp" | ||||||
| 	"golang.org/x/exp/maps" | 	"golang.org/x/exp/maps" | ||||||
| 	"golang.org/x/exp/slices" |  | ||||||
|  |  | ||||||
| 	"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" | ||||||
| 	"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/internal/types" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/build" | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/build" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro" | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager" | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/manager" | ||||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/repos" | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/repos" | ||||||
|  | 	"gitea.plemya-x.ru/Plemya-x/ALR/pkg/search" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func UpgradeCmd() *cli.Command { | func UpgradeCmd() *cli.Command { | ||||||
| @@ -56,7 +56,14 @@ func UpgradeCmd() *cli.Command { | |||||||
| 		Action: func(c *cli.Context) error { | 		Action: func(c *cli.Context) error { | ||||||
| 			ctx := c.Context | 			ctx := c.Context | ||||||
|  |  | ||||||
| 			cfg := config.GetInstance(ctx) | 			cfg := config.New() | ||||||
|  | 			db := database.New(cfg) | ||||||
|  | 			rs := repos.New(cfg, db) | ||||||
|  | 			err := db.Init(ctx) | ||||||
|  | 			if err != nil { | ||||||
|  | 				slog.Error(gotext.Get("Error initialization database"), "err", err) | ||||||
|  | 				os.Exit(1) | ||||||
|  | 			} | ||||||
|  |  | ||||||
| 			info, err := distro.ParseOSRelease(ctx) | 			info, err := distro.ParseOSRelease(ctx) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| @@ -71,21 +78,32 @@ func UpgradeCmd() *cli.Command { | |||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			if cfg.AutoPull(ctx) { | 			if cfg.AutoPull(ctx) { | ||||||
| 				err = repos.Pull(ctx, config.Config(ctx).Repos) | 				err = rs.Pull(ctx, cfg.Repos(ctx)) | ||||||
| 				if err != nil { | 				if err != nil { | ||||||
| 					slog.Error(gotext.Get("Error pulling repos"), "err", err) | 					slog.Error(gotext.Get("Error pulling repos"), "err", err) | ||||||
| 					os.Exit(1) | 					os.Exit(1) | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			updates, err := checkForUpdates(ctx, mgr, info) | 			updates, err := checkForUpdates(ctx, mgr, cfg, db, rs, info) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				slog.Error(gotext.Get("Error checking for updates"), "err", err) | 				slog.Error(gotext.Get("Error checking for updates"), "err", err) | ||||||
| 				os.Exit(1) | 				os.Exit(1) | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			if len(updates) > 0 { | 			if len(updates) > 0 { | ||||||
| 				build.InstallPkgs(ctx, updates, nil, types.BuildOpts{ | 				builder := build.NewBuilder( | ||||||
|  | 					ctx, | ||||||
|  | 					types.BuildOpts{ | ||||||
|  | 						Manager:     mgr, | ||||||
|  | 						Clean:       c.Bool("clean"), | ||||||
|  | 						Interactive: c.Bool("interactive"), | ||||||
|  | 					}, | ||||||
|  | 					rs, | ||||||
|  | 					info, | ||||||
|  | 					cfg, | ||||||
|  | 				) | ||||||
|  | 				builder.InstallPkgs(ctx, updates, nil, types.BuildOpts{ | ||||||
| 					Manager:     mgr, | 					Manager:     mgr, | ||||||
| 					Clean:       c.Bool("clean"), | 					Clean:       c.Bool("clean"), | ||||||
| 					Interactive: c.Bool("interactive"), | 					Interactive: c.Bool("interactive"), | ||||||
| @@ -99,49 +117,65 @@ func UpgradeCmd() *cli.Command { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| func checkForUpdates(ctx context.Context, mgr manager.Manager, info *distro.OSRelease) ([]db.Package, error) { | func checkForUpdates( | ||||||
|  | 	ctx context.Context, | ||||||
|  | 	mgr manager.Manager, | ||||||
|  | 	cfg *config.ALRConfig, | ||||||
|  | 	db *database.Database, | ||||||
|  | 	rs *repos.Repos, | ||||||
|  | 	info *distro.OSRelease, | ||||||
|  | ) ([]database.Package, error) { | ||||||
| 	installed, err := mgr.ListInstalled(nil) | 	installed, err := mgr.ListInstalled(nil) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	pkgNames := maps.Keys(installed) | 	pkgNames := maps.Keys(installed) | ||||||
| 	found, _, err := repos.FindPkgs(ctx, pkgNames) |  | ||||||
| 	if err != nil { | 	s := search.New(db) | ||||||
| 		return nil, err |  | ||||||
|  | 	var out []database.Package | ||||||
|  | 	for _, pkgName := range pkgNames { | ||||||
|  | 		matches := build.RegexpALRPackageName.FindStringSubmatch(pkgName) | ||||||
|  | 		if matches != nil { | ||||||
|  | 			packageName := matches[build.RegexpALRPackageName.SubexpIndex("package")] | ||||||
|  | 			repoName := matches[build.RegexpALRPackageName.SubexpIndex("repo")] | ||||||
|  |  | ||||||
|  | 			pkgs, err := s.Search( | ||||||
|  | 				ctx, | ||||||
|  | 				search.NewSearchOptions(). | ||||||
|  | 					WithName(packageName). | ||||||
|  | 					WithRepository(repoName). | ||||||
|  | 					Build(), | ||||||
|  | 			) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return nil, err | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			if len(pkgs) == 0 { | ||||||
|  | 				continue | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			pkg := pkgs[0] | ||||||
|  |  | ||||||
|  | 			repoVer := pkg.Version | ||||||
|  | 			releaseStr := overrides.ReleasePlatformSpecific(pkg.Release, info) | ||||||
|  |  | ||||||
|  | 			if pkg.Release != 0 && pkg.Epoch == 0 { | ||||||
|  | 				repoVer = fmt.Sprintf("%s-%s", pkg.Version, releaseStr) | ||||||
|  | 			} else if pkg.Release != 0 && pkg.Epoch != 0 { | ||||||
|  | 				repoVer = fmt.Sprintf("%d:%s-%s", pkg.Epoch, pkg.Version, releaseStr) | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			c := vercmp.Compare(repoVer, installed[pkgName]) | ||||||
|  | 			if c == 0 || c == -1 { | ||||||
|  | 				continue | ||||||
|  | 			} else if c == 1 { | ||||||
|  | 				out = append(out, pkg) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	var out []db.Package |  | ||||||
| 	for pkgName, pkgs := range found { |  | ||||||
| 		if slices.Contains(config.Config(ctx).IgnorePkgUpdates, pkgName) { |  | ||||||
| 			continue |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		if len(pkgs) > 1 { |  | ||||||
| 			// Puts the element with the highest version first |  | ||||||
| 			slices.SortFunc(pkgs, func(a, b db.Package) int { |  | ||||||
| 				return vercmp.Compare(a.Version, b.Version) |  | ||||||
| 			}) |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		// First element is the package we want to install |  | ||||||
| 		pkg := pkgs[0] |  | ||||||
|  |  | ||||||
| 		repoVer := pkg.Version |  | ||||||
| 		releaseStr := overrides.ReleasePlatformSpecific(pkg.Release, info) |  | ||||||
|  |  | ||||||
| 		if pkg.Release != 0 && pkg.Epoch == 0 { |  | ||||||
| 			repoVer = fmt.Sprintf("%s-%s", pkg.Version, releaseStr) |  | ||||||
| 		} else if pkg.Release != 0 && pkg.Epoch != 0 { |  | ||||||
| 			repoVer = fmt.Sprintf("%d:%s-%s", pkg.Epoch, pkg.Version, releaseStr) |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		c := vercmp.Compare(repoVer, installed[pkgName]) |  | ||||||
| 		if c == 0 || c == -1 { |  | ||||||
| 			continue |  | ||||||
| 		} else if c == 1 { |  | ||||||
| 			out = append(out, pkg) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	return out, nil | 	return out, nil | ||||||
| } | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user